forked from adamdruppe/arsd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rss.d
824 lines (708 loc) · 32.4 KB
/
rss.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
/++
RSS/Atom feed reading
References:
$(LIST
* https://cyber.harvard.edu/rss/rss.html
* http://www.rssboard.org/rss-specification
* https://tools.ietf.org/html/rfc4287
* https://en.wikipedia.org/wiki/Atom_(Web_standard)
)
+/
module arsd.rss;
import arsd.dom;
/// generic subset of rss and atom, normalized for easy consumption
struct Feed {
string title; ///
string description; ///
string lastUpdated; ///
///
static struct Item {
string title; ///
string link; ///
string description; /// could be html or text!
string author; /// Typical format: email (name)
string publicationDate; /// the format is 2005-07-31T12:29:29Z
string lastUpdatedDate; /// the format is 2005-07-31T12:29:29Z
string guid; ///
string enclosureUri; ///
string enclosureType; /// a mime type
string enclosureSize; ///
}
Item[] items; ///
}
/+
import arsd.cgi;
mixin GenericMain!handler;
void handler(Cgi cgi) {
cgi.setResponseContentType("application/atom+xml");
cgi.write(feedToAtom(parseFeed(Document.fromUrl("http://dpldocs.info/this-week-in-d/twid.rss", true).root)).toString);
}
+/
/++
Turns a generic feed back into an Atom document.
History:
Added March 18, 2021
+/
XmlDocument feedToAtom(Feed feed) {
auto document = new XmlDocument(`<feed xmlns="http://www.w3.org/2005/Atom"></feed>`);
document.root.addChild("title", feed.title);
document.root.addChild("subtitle", feed.description);
document.root.addChild("updated", feed.lastUpdated);
foreach(item; feed.items) {
auto entry = document.root.addChild("entry");
entry.addChild("title", item.title);
entry.addChild("link").setAttribute("href", item.link);
if(item.enclosureUri.length)
entry.addChild("link").
setAttribute("rel", "enclosure").
setAttribute("href", item.enclosureUri).
setAttribute("length", item.enclosureSize).
setAttribute("type", item.enclosureType);
entry.addChild("id", item.guid);
entry.addChild("published", item.publicationDate);
entry.addChild("updated", item.lastUpdatedDate);
entry.addChild("content", item.description).setAttribute("type", "html"); // or summary? idk
if(item.author.length) {
auto author = entry.addChild("author");
import std.string;
auto idx = item.author.indexOf("(");
if(idx == -1) {
author.addChild("email", item.author);
} else {
if(item.author.length > idx + 2)
author.addChild("name", item.author[idx + 1 .. $-1]);
author.addChild("email", item.author[0 .. idx -1]);
}
}
}
return document;
}
///
enum FeedType {
unknown, ///
rss, ///
atom ///
}
///
FeedType identifyFeed(Element e) {
assert(e !is null);
if(e.tagName == "rss")
return FeedType.rss;
if(e.tagName == "feed" || e.tagName == "atom:feed")
return FeedType.atom;
return FeedType.unknown;
}
/// Parses a feed generically
Feed parseFeed(Element e) {
final switch(identifyFeed(e)) {
case FeedType.unknown:
throw new Exception("Unknown feed type");
case FeedType.rss:
return parseRss(e).toGenericFeed();
case FeedType.atom:
return parseAtom(e).toGenericFeed();
}
}
// application/rss+xml
// though some use text/rss+xml or application/rdf+xml
// root node of <rss version="whatever">
struct RssChannel {
string title;
string link;
string description;
string lastBuildDate; // last time content in here changed
string pubDate; // format like "Sat, 07 Sep 2002 00:00:01 GMT" when it officially changes
string docs; // idk?
string cloud; // has domain, port, path, registerProcedure, protocol
string language; // optional
string copyright;
string managingEditor;
string webMaster;
string category;
string ttl; // in minutes, if present
RssImage image;
RssItem[] items;
Feed toGenericFeed() {
Feed f;
f.title = this.title;
f.description = this.description; // FIXME text vs html?
f.lastUpdated = this.lastBuildDate.rssDateToAtom;
foreach(item; items) {
Feed.Item fi;
fi.title = item.title;
fi.link = item.link;
fi.description = item.description; // FIXME: try to normalize text vs html
fi.author = item.author; // FIXME
fi.lastUpdatedDate = fi.publicationDate = item.pubDate.rssDateToAtom;
fi.guid = item.guid;
//fi.lastUpdatedDate; // not available i think
fi.enclosureUri = item.enclosure.url;
fi.enclosureType = item.enclosure.type;
fi.enclosureSize = item.enclosure.length;
f.items ~= fi;
}
return f;
}
}
struct RssImage {
string title; /// img alt
string url; /// like the img src
string link; /// like a href
string width;
string height;
string description; /// img title
}
struct RssItem {
string title;
string link;
string description; // may have html!
string author;
string category;
string comments; // a link
string pubDate;
string guid;
RssSource source;
RssEnclosure enclosure;
}
struct RssEnclosure {
string url;
string length;
string type;
}
struct RssSource {
string title;
string url;
}
/++
Parses RSS into structs. Requires the element to be RSS; if you are unsure
of the type and want a generic response, use parseFeed instead.
+/
RssChannel parseRss(Element element) {
assert(element !is null && element.tagName == "rss");
RssChannel c;
element = element.requireSelector(" > channel");
foreach(memberName; __traits(allMembers, RssChannel)) {
static if(memberName == "image") {
if(auto image = element.querySelector(" > image")) {
RssImage i;
foreach(mn; __traits(allMembers, RssImage)) {
__traits(getMember, i, mn) = image.optionSelector(" > " ~ mn).innerText;
}
c.image = i;
}
} else static if(memberName == "items") {
foreach(item; element.querySelectorAll(" > item")) {
RssItem i;
foreach(mn; __traits(allMembers, RssItem)) {
static if(mn == "source") {
if(auto s = item.querySelector(" > source")) {
i.source.title = s.innerText;
i.source.url = s.attrs.url;
}
} else static if(mn == "enclosure") {
if(auto s = item.querySelector(" > enclosure")) {
i.enclosure.url = s.attrs.url;
i.enclosure.type = s.attrs.type;
i.enclosure.length = s.attrs.length;
}
} else {
__traits(getMember, i, mn) = item.optionSelector(" > " ~ mn).innerText;
}
}
c.items ~= i;
}
} else static if(is(typeof( __traits(getMember, c, memberName).offsetof))) {
__traits(getMember, c, memberName) = element.optionSelector(" > " ~ memberName).innerText;
}
}
return c;
}
///
RssChannel parseRss(string s) {
auto document = new Document(s, true, true);
return parseRss(document.root);
}
/*
struct SyndicationInfo {
string updatePeriod; // sy:updatePeriod
string updateFrequency;
string updateBase;
string skipHours; // stored as <hour> elements
string skipDays; // stored as <day> elements
}
*/
// /////////////////// atom ////////////////////
// application/atom+xml
/+ rss vs atom
date format is different
atom:xxx links
root node is <feed>, organization has no <channel>, and <entry>
instead of <item>
+/
/++
+/
struct AtomFeed {
string title; /// has a type attribute - text or html
string subtitle; /// has a type attribute
string updated; /// io string
string id; ///
string link; /// i want the text/html type really, certainly not rel=self
string rights; ///
string generator; ///
AtomEntry[] entries; ///
///
Feed toGenericFeed() {
Feed feed;
feed.title = this.title;
feed.description = this.subtitle;
feed.lastUpdated = this.updated;
foreach(entry; this.entries) {
Feed.Item item;
item.title = entry.title;
item.link = entry.link;
if(entry.content.html.length || entry.content.text.length)
item.description = entry.content.html.length ? entry.content.html : entry.content.text; // FIXME
else
item.description = entry.summary.html.length ? entry.summary.html : entry.summary.text; // FIXME
item.author = entry.author.email;
if(entry.author.name.length)
item.author ~= " (" ~ entry.author.name ~ ")";
item.publicationDate = entry.published;
item.lastUpdatedDate = entry.updated;
item.guid = entry.id;
item.enclosureUri = entry.enclosure.url;
item.enclosureType = entry.enclosure.type;
item.enclosureSize = entry.enclosure.length;
feed.items ~= item;
}
return feed;
}
}
///
struct AtomEntry {
string title; ///
string link; /// the alternate
AtomEnclosure enclosure; ///
string id; ///
string updated; ///
string published; ///
AtomPerson author; ///
AtomPerson[] contributors; ///
AtomContent content; /// // should check type. may also have a src element for a link. type of html is escaped, type of xhtml is embedded.
AtomContent summary; ///
}
///
struct AtomEnclosure {
string url; ///
string length; ///
string type; ///
}
///
struct AtomContent {
string text; ///
string html; ///
}
///
struct AtomPerson {
string name; ///
string uri; ///
string email; ///
}
///
AtomFeed parseAtom(Element ele) {
AtomFeed af;
af.title = ele.optionSelector(` > title, > atom\:title`).innerText;
af.subtitle = ele.optionSelector(` > subtitle, > atom\:subtitle`).innerText;
af.id = ele.optionSelector(` > id, > atom\:id`).innerText;
af.updated = ele.optionSelector(` > updated, > atom\:updated`).innerText;
af.rights = ele.optionSelector(` > rights, > atom\:rights`).innerText;
af.generator = ele.optionSelector(` > generator, > atom\:generator`).innerText;
af.link = ele.optionSelector(` > link:not([rel])`).getAttribute("href");
foreach(entry; ele.querySelectorAll(` > entry`)) {
AtomEntry ae;
ae.title = entry.optionSelector(` > title, > atom\:title`).innerText;
ae.updated = entry.optionSelector(` > updated, > atom\:updated`).innerText;
ae.published = entry.optionSelector(` > published, > atom\:published`).innerText;
ae.id = entry.optionSelector(` > id, > atom\:id`).innerText;
ae.link = entry.optionSelector(` > link:not([rel]), > link[rel=alternate], > link[type="type/html"]`).getAttribute("href");
if(auto enclosure = entry.querySelector(` > link[rel=enclosure]`)) {
ae.enclosure.url = enclosure.attrs.href;
ae.enclosure.length = enclosure.attrs.length;
ae.enclosure.type = enclosure.attrs.type;
}
if(auto author = entry.querySelector(` > author`)) {
ae.author.name = author.optionSelector(` > name`).innerText;
ae.author.uri = author.optionSelector(` > uri`).innerText;
ae.author.email = author.optionSelector(` > email`).innerText;
}
foreach(contributor; entry.querySelectorAll(` > contributor`)) {
AtomPerson c;
c.name = contributor.optionSelector(` > name`).innerText;
c.uri = contributor.optionSelector(` > uri`).innerText;
c.email = contributor.optionSelector(` > email`).innerText;
ae.contributors ~= c;
}
if(auto e = entry.querySelector("content[type=xhtml]"))
ae.content.html = e.innerHTML;
if(auto e = entry.querySelector("content[type=html]"))
ae.content.html = e.innerText;
if(auto e = entry.querySelector("content[type=text], content:not([type])"))
ae.content.text = e.innerText;
if(auto e = entry.querySelector("summary[type=xhtml]"))
ae.summary.html = e.innerHTML;
if(auto e = entry.querySelector("summary[type=html]"))
ae.summary.html = e.innerText;
if(auto e = entry.querySelector("summary[type=text], summary:not([type])"))
ae.summary.text = e.innerText;
af.entries ~= ae;
}
return af;
}
AtomFeed parseAtom(string s) {
auto document = new Document(s, true, true);
return parseAtom(document.root);
}
string rssDateToAtom(string d) {
auto orig = d;
if(d.length < 22 || d[3] != ',')
return orig; // doesn't appear to be the right format
d = d[5 .. $];
import std.conv;
auto day = parse!int(d);
if(d.length == 0 || d[0] != ' ')
return orig;
d = d[1 .. $];
if(d.length < 4)
return orig;
int month;
string months = "JanFebMarAprMayJunJulAugSepOctNovDec";
foreach(i; 0 .. 12) {
if(months[i * 3 .. i * 3 + 3] == d[0 .. 3]) {
month = i + 1;
break;
}
}
d = d[4 .. $];
auto year = parse!int(d);
if(d.length == 0 || d[0] != ' ')
return orig;
d = d[1 .. $];
auto hour = parse!int(d);
if(d.length == 0 || d[0] != ':')
return orig;
d = d[1 .. $];
auto minute = parse!int(d);
if(d.length == 0 || d[0] != ':')
return orig;
d = d[1 .. $];
auto second = parse!int(d);
import std.format;
return format("%04d-%02d-%02dT%02d:%02d:%02dZ", year, month, day, hour, minute, second);
}
unittest {
assert(rssDateToAtom("Mon, 18 Nov 2019 12:05:44 GMT") == "2019-11-18T12:05:44Z");
}
unittest {
auto test1 = `<?xml version="1.0" encoding="ISO-8859-1"?>
<rss version="0.91">
<channel>
<title>WriteTheWeb</title>
<link>http://writetheweb.com</link>
<description>News for web users that write back</description>
<language>en-us</language>
<copyright>Copyright 2000, WriteTheWeb team.</copyright>
<managingEditor>[email protected]</managingEditor>
<webMaster>[email protected]</webMaster>
<image>
<title>WriteTheWeb</title>
<url>http://writetheweb.com/images/mynetscape88.gif</url>
<link>http://writetheweb.com</link>
<width>88</width>
<height>31</height>
<description>News for web users that write back</description>
</image>
<item>
<title>Giving the world a pluggable Gnutella</title>
<link>http://writetheweb.com/read.php?item=24</link>
<description>WorldOS is a framework on which to build programs that work like Freenet or Gnutella -allowing distributed applications using peer-to-peer routing.</description>
</item>
<item>
<title>Syndication discussions hot up</title>
<link>http://writetheweb.com/read.php?item=23</link>
<description>After a period of dormancy, the Syndication mailing list has become active again, with contributions from leaders in traditional media and Web syndication.</description>
</item>
<item>
<title>Personal web server integrates file sharing and messaging</title>
<link>http://writetheweb.com/read.php?item=22</link>
<description>The Magi Project is an innovative project to create a combined personal web server and messaging system that enables the sharing and synchronization of information across desktop, laptop and palmtop devices.</description>
</item>
<item>
<title>Syndication and Metadata</title>
<link>http://writetheweb.com/read.php?item=21</link>
<description>RSS is probably the best known metadata format around. RDF is probably one of the least understood. In this essay, published on my O'Reilly Network weblog, I argue that the next generation of RSS should be based on RDF.</description>
</item>
<item>
<title>UK bloggers get organised</title>
<link>http://writetheweb.com/read.php?item=20</link>
<description>Looks like the weblogs scene is gathering pace beyond the shores of the US. There's now a UK-specific page on weblogs.com, and a mailing list at egroups.</description>
</item>
<item>
<title>Yournamehere.com more important than anything</title>
<link>http://writetheweb.com/read.php?item=19</link>
<description>Whatever you're publishing on the web, your site name is the most valuable asset you have, according to Carl Steadman.</description>
</item>
</channel>
</rss>`;
{
auto e = parseRss(test1);
assert(e.items.length == 6);
assert(e.items[$-1].title == "Yournamehere.com more important than anything", e.items[$-1].title);
assert(e.items[0].title == "Giving the world a pluggable Gnutella");
assert(e.items[0].link == "http://writetheweb.com/read.php?item=24");
assert(e.image.url == "http://writetheweb.com/images/mynetscape88.gif");
auto df = e.toGenericFeed();
assert(df.items.length == 6);
assert(df.items[0].link == "http://writetheweb.com/read.php?item=24");
}
auto test2 = `<?xml version="1.0"?>
<!-- RSS generation done by 'Radio UserLand' on Fri, 13 Apr 2001 19:23:02 GMT -->
<rss version="0.92">
<channel>
<title>Dave Winer: Grateful Dead</title>
<link>http://www.scripting.com/blog/categories/gratefulDead.html</link>
<description>A high-fidelity Grateful Dead song every day. This is where we're experimenting with enclosures on RSS news items that download when you're not using your computer. If it works (it will) it will be the end of the Click-And-Wait multimedia experience on the Internet. </description>
<lastBuildDate>Fri, 13 Apr 2001 19:23:02 GMT</lastBuildDate>
<docs>http://backend.userland.com/rss092</docs>
<managingEditor>[email protected] (Dave Winer)</managingEditor>
<webMaster>[email protected] (Dave Winer)</webMaster>
<cloud domain="data.ourfavoritesongs.com" port="80" path="/RPC2" registerProcedure="ourFavoriteSongs.rssPleaseNotify" protocol="xml-rpc"/>
<item>
<description>It's been a few days since I added a song to the Grateful Dead channel. Now that there are all these new Radio users, many of whom are tuned into this channel (it's #16 on the hotlist of upstreaming Radio users, there's no way of knowing how many non-upstreaming users are subscribing, have to do something about this..). Anyway, tonight's song is a live version of Weather Report Suite from Dick's Picks Volume 7. It's wistful music. Of course a beautiful song, oft-quoted here on Scripting News. <i>A little change, the wind and rain.</i>
</description>
<enclosure url="http://www.scripting.com/mp3s/weatherReportDicksPicsVol7.mp3" length="6182912" type="audio/mpeg"/>
</item>
<item>
<description>Kevin Drennan started a <a href="http://deadend.editthispage.com/">Grateful Dead Weblog</a>. Hey it's cool, he even has a <a href="http://deadend.editthispage.com/directory/61">directory</a>. <i>A Frontier 7 feature.</i></description>
<source url="http://scriptingnews.userland.com/xml/scriptingNews2.xml">Scripting News</source>
</item>
<item>
<description><a href="http://arts.ucsc.edu/GDead/AGDL/other1.html">The Other One</a>, live instrumental, One From The Vault. Very rhythmic very spacy, you can listen to it many times, and enjoy something new every time.</description>
<enclosure url="http://www.scripting.com/mp3s/theOtherOne.mp3" length="6666097" type="audio/mpeg"/>
</item>
<item>
<description>This is a test of a change I just made. Still diggin..</description>
</item>
<item>
<description>The HTML rendering almost <a href="http://validator.w3.org/check/referer">validates</a>. Close. Hey I wonder if anyone has ever published a style guide for ALT attributes on images? What are you supposed to say in the ALT attribute? I sure don't know. If you're blind send me an email if u cn rd ths. </description>
</item>
<item>
<description><a href="http://www.cs.cmu.edu/~mleone/gdead/dead-lyrics/Franklin's_Tower.txt">Franklin's Tower</a>, a live version from One From The Vault.</description>
<enclosure url="http://www.scripting.com/mp3s/franklinsTower.mp3" length="6701402" type="audio/mpeg"/>
</item>
<item>
<description>Moshe Weitzman says Shakedown Street is what I'm lookin for for tonight. I'm listening right now. It's one of my favorites. "Don't tell me this town ain't got no heart." Too bright. I like the jazziness of Weather Report Suite. Dreamy and soft. How about The Other One? "Spanish lady come to me.."</description>
<source url="http://scriptingnews.userland.com/xml/scriptingNews2.xml">Scripting News</source>
</item>
<item>
<description><a href="http://www.scripting.com/mp3s/youWinAgain.mp3">The news is out</a>, all over town..<p>
You've been seen, out runnin round. <p>
The lyrics are <a href="http://www.cs.cmu.edu/~mleone/gdead/dead-lyrics/You_Win_Again.txt">here</a>, short and sweet. <p>
<i>You win again!</i>
</description>
<enclosure url="http://www.scripting.com/mp3s/youWinAgain.mp3" length="3874816" type="audio/mpeg"/>
</item>
<item>
<description><a href="http://www.getlyrics.com/lyrics/grateful-dead/wake-of-the-flood/07.htm">Weather Report Suite</a>: "Winter rain, now tell me why, summers fade, and roses die? The answer came. The wind and rain. Golden hills, now veiled in grey, summer leaves have blown away. Now what remains? The wind and rain."</description>
<enclosure url="http://www.scripting.com/mp3s/weatherReportSuite.mp3" length="12216320" type="audio/mpeg"/>
</item>
<item>
<description><a href="http://arts.ucsc.edu/gdead/agdl/darkstar.html">Dark Star</a> crashes, pouring its light into ashes.</description>
<enclosure url="http://www.scripting.com/mp3s/darkStar.mp3" length="10889216" type="audio/mpeg"/>
</item>
<item>
<description>DaveNet: <a href="http://davenet.userland.com/2001/01/21/theUsBlues">The U.S. Blues</a>.</description>
</item>
<item>
<description>Still listening to the US Blues. <i>"Wave that flag, wave it wide and high.."</i> Mistake made in the 60s. We gave our country to the assholes. Ah ah. Let's take it back. Hey I'm still a hippie. <i>"You could call this song The United States Blues."</i></description>
</item>
<item>
<description><a href="http://www.sixties.com/html/garcia_stack_0.html"><img src="http://www.scripting.com/images/captainTripsSmall.gif" height="51" width="42" border="0" hspace="10" vspace="10" align="right"></a>In celebration of today's inauguration, after hearing all those great patriotic songs, America the Beautiful, even The Star Spangled Banner made my eyes mist up. It made my choice of Grateful Dead song of the night realllly easy. Here are the <a href="http://searchlyrics2.homestead.com/gd_usblues.html">lyrics</a>. Click on the audio icon to the left to give it a listen. "Red and white, blue suede shoes, I'm Uncle Sam, how do you do?" It's a different kind of patriotic music, but man I love my country and I love Jerry and the band. <i>I truly do!</i></description>
<enclosure url="http://www.scripting.com/mp3s/usBlues.mp3" length="5272510" type="audio/mpeg"/>
</item>
<item>
<description>Grateful Dead: "Tennessee, Tennessee, ain't no place I'd rather be."</description>
<enclosure url="http://www.scripting.com/mp3s/tennesseeJed.mp3" length="3442648" type="audio/mpeg"/>
</item>
<item>
<description>Ed Cone: "Had a nice Deadhead experience with my wife, who never was one but gets the vibe and knows and likes a lot of the music. Somehow she made it to the age of 40 without ever hearing Wharf Rat. We drove to Jersey and back over Christmas with the live album commonly known as Skull and Roses in the CD player much of the way, and it was cool to see her discover one the band's finest moments. That song is unique and underappreciated. Fun to hear that disc again after a few years off -- you get Jerry as blues-guitar hero on Big Railroad Blues and a nice version of Bertha."</description>
<enclosure url="http://www.scripting.com/mp3s/darkStarWharfRat.mp3" length="27503386" type="audio/mpeg"/>
</item>
<item>
<description><a href="http://arts.ucsc.edu/GDead/AGDL/fotd.html">Tonight's Song</a>: "If I get home before daylight I just might get some sleep tonight." </description>
<enclosure url="http://www.scripting.com/mp3s/friendOfTheDevil.mp3" length="3219742" type="audio/mpeg"/>
</item>
<item>
<description><a href="http://arts.ucsc.edu/GDead/AGDL/uncle.html">Tonight's song</a>: "Come hear Uncle John's Band by the river side. Got some things to talk about here beside the rising tide."</description>
<enclosure url="http://www.scripting.com/mp3s/uncleJohnsBand.mp3" length="4587102" type="audio/mpeg"/>
</item>
<item>
<description><a href="http://www.cs.cmu.edu/~mleone/gdead/dead-lyrics/Me_and_My_Uncle.txt">Me and My Uncle</a>: "I loved my uncle, God rest his soul, taught me good, Lord, taught me all I know. Taught me so well, I grabbed that gold and I left his dead ass there by the side of the road."
</description>
<enclosure url="http://www.scripting.com/mp3s/meAndMyUncle.mp3" length="2949248" type="audio/mpeg"/>
</item>
<item>
<description>Truckin, like the doo-dah man, once told me gotta play your hand. Sometimes the cards ain't worth a dime, if you don't lay em down.</description>
<enclosure url="http://www.scripting.com/mp3s/truckin.mp3" length="4847908" type="audio/mpeg"/>
</item>
<item>
<description>Two-Way-Web: <a href="http://www.thetwowayweb.com/payloadsForRss">Payloads for RSS</a>. "When I started talking with Adam late last year, he wanted me to think about high quality video on the Internet, and I totally didn't want to hear about it."</description>
</item>
<item>
<description>A touch of gray, kinda suits you anyway..</description>
<enclosure url="http://www.scripting.com/mp3s/touchOfGrey.mp3" length="5588242" type="audio/mpeg"/>
</item>
<item>
<description><a href="http://www.sixties.com/html/garcia_stack_0.html"><img src="http://www.scripting.com/images/captainTripsSmall.gif" height="51" width="42" border="0" hspace="10" vspace="10" align="right"></a>In celebration of today's inauguration, after hearing all those great patriotic songs, America the Beautiful, even The Star Spangled Banner made my eyes mist up. It made my choice of Grateful Dead song of the night realllly easy. Here are the <a href="http://searchlyrics2.homestead.com/gd_usblues.html">lyrics</a>. Click on the audio icon to the left to give it a listen. "Red and white, blue suede shoes, I'm Uncle Sam, how do you do?" It's a different kind of patriotic music, but man I love my country and I love Jerry and the band. <i>I truly do!</i></description>
<enclosure url="http://www.scripting.com/mp3s/usBlues.mp3" length="5272510" type="audio/mpeg"/>
</item>
</channel>
</rss><?xml version="1.0"?>`;
{
auto e = parseRss(test2);
assert(e.items[$-1].enclosure.url == "http://www.scripting.com/mp3s/usBlues.mp3");
}
auto test3 = `<rss version="2.0">
<channel>
<title>Liftoff News</title>
<link>http://liftoff.msfc.nasa.gov/</link>
<description>Liftoff to Space Exploration.</description>
<language>en-us</language>
<pubDate>Tue, 10 Jun 2003 04:00:00 GMT</pubDate>
<lastBuildDate>Tue, 10 Jun 2003 09:41:01 GMT</lastBuildDate>
<docs>http://blogs.law.harvard.edu/tech/rss</docs>
<generator>Weblog Editor 2.0</generator>
<managingEditor>[email protected]</managingEditor>
<webMaster>[email protected]</webMaster>
<item>
<title>Star City</title>
<link>http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp</link>
<description>How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's <a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm">Star City</a>.</description>
<pubDate>Tue, 03 Jun 2003 09:39:21 GMT</pubDate>
<guid>http://liftoff.msfc.nasa.gov/2003/06/03.html#item573</guid>
</item>
<item>
<description>Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a <a href="http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm">partial eclipse of the Sun</a> on Saturday, May 31st.</description>
<pubDate>Fri, 30 May 2003 11:06:42 GMT</pubDate>
<guid>http://liftoff.msfc.nasa.gov/2003/05/30.html#item572</guid>
</item>
<item>
<title>The Engine That Does More</title>
<link>http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp</link>
<description>Before man travels to Mars, NASA hopes to design new engines that will let us fly through the Solar System more quickly. The proposed VASIMR engine would do that.</description>
<pubDate>Tue, 27 May 2003 08:37:32 GMT</pubDate>
<guid>http://liftoff.msfc.nasa.gov/2003/05/27.html#item571</guid>
</item>
<item>
<title>Astronauts' Dirty Laundry</title>
<link>http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp</link>
<description>Compared to earlier spacecraft, the International Space Station has many luxuries, but laundry facilities are not one of them. Instead, astronauts have other options.</description>
<pubDate>Tue, 20 May 2003 08:56:02 GMT</pubDate>
<guid>http://liftoff.msfc.nasa.gov/2003/05/20.html#item570</guid>
</item>
</channel>
</rss>`;
auto testAtom1 = `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Example Feed</title>
<subtitle>A subtitle.</subtitle>
<link href="http://example.org/feed/" rel="self" />
<link href="http://example.org/" />
<id>urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6</id>
<updated>2003-12-13T18:30:02Z</updated>
<entry>
<title>Atom-Powered Robots Run Amok</title>
<link href="http://example.org/2003/12/13/atom03" />
<link rel="alternate" type="text/html" href="http://example.org/2003/12/13/atom03.html"/>
<link rel="edit" href="http://example.org/2003/12/13/atom03/edit"/>
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
<updated>2003-12-13T18:30:02Z</updated>
<summary>Some text.</summary>
<content type="xhtml">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>This is the entry content.</p>
</div>
</content>
<author>
<name>John Doe</name>
<email>[email protected]</email>
</author>
</entry>
</feed>`;
{
auto e = parseAtom(testAtom1);
assert(e.entries.length == 1);
assert(e.link == "http://example.org/");
assert(e.title == "Example Feed");
assert(e.entries[0].title == "Atom-Powered Robots Run Amok");
assert(e.entries[0].link == "http://example.org/2003/12/13/atom03", e.entries[0].link);
assert(e.entries[0].summary.text == "Some text.", e.entries[0].summary.text);
assert(e.entries[0].summary.html.length == 0);
assert(e.entries[0].content.text.length == 0);
assert(e.entries[0].content.html.length > 10);
auto gf = e.toGenericFeed();
assert(gf.items[0].lastUpdatedDate == "2003-12-13T18:30:02Z");
}
{
auto xml = `<rss version="2.0">
<channel>
<title>NYT > World News</title>
<link>
https://www.nytimes.com/section/world?emc=rss&partner=rss
</link>
<atom:link href="https://rss.nytimes.com/services/xml/rss/nyt/World.xml" rel="self" type="application/rss+xml"/>
<description/>
<language>en-us</language>
<copyright>Copyright 2019 The New York Times Company</copyright>
<lastBuildDate>Sat, 07 Dec 2019 00:15:41 +0000</lastBuildDate>
<image>
<title>NYT > World News</title>
<url>
https://static01.nyt.com/images/misc/NYT_logo_rss_250x40.png
</url>
<link>
https://www.nytimes.com/section/world?emc=rss&partner=rss
</link>
</image>
<item>
<title>
France Is Hit by Second Day of Pension Strikes as Unions Dig In
</title>
<link>https://www.nytimes.com/2019/12/06/world/europe/france-pension-strike-macron.html?emc=rss&partner=rss</link>
<guid isPermaLink="true">
https://www.nytimes.com/2019/12/06/world/europe/france-pension-strike-macron.html
</guid>
<atom:link href="https://www.nytimes.com/2019/12/06/world/europe/france-pension-strike-macron.html?emc=rss&partner=rss" rel="standout"/>
<description>
Transportation was severely disrupted in Paris and other cities, a day after huge protests over government plans to overhaul pensions. Unions are planning more protests next week.
</description>
<dc:creator>Aurelien Breeden</dc:creator>
<pubDate>Fri, 06 Dec 2019 18:02:13 +0000</pubDate>
<category domain="http://www.nytimes.com/namespaces/keywords/nyt_geo">France</category>
<category domain="http://www.nytimes.com/namespaces/keywords/des">Demonstrations, Protests and Riots</category>
<category domain="http://www.nytimes.com/namespaces/keywords/des">Pensions and Retirement Plans</category>
<category domain="http://www.nytimes.com/namespaces/keywords/des">Politics and Government</category>
<category domain="http://www.nytimes.com/namespaces/keywords/des">Strikes</category>
<category domain="http://www.nytimes.com/namespaces/keywords/nyt_per">Macron, Emmanuel (1977- )</category>
<media:content height="151" medium="image" url="https://static01.nyt.com/images/2019/12/06/world/06france-strikes/merlin_165509820_476d5340-3717-4fbb-b187-097ae7718e48-moth.jpg" width="151"/>
<media:credit>Rafael Yaghobzadeh/Associated Press</media:credit>
<media:description>
A deserted Gare de Lyon train station in Paris on Friday. Unions are aiming for a protracted strike.
</media:description>
</item></channel></rss>`;
auto e = parseRss(xml);
assert(e.items[0].link == "https://www.nytimes.com/2019/12/06/world/europe/france-pension-strike-macron.html?emc=rss&partner=rss", e.items[0].link);
auto gf = e.toGenericFeed();
assert(gf.items[0].link == "https://www.nytimes.com/2019/12/06/world/europe/france-pension-strike-macron.html?emc=rss&partner=rss", e.items[0].link);
assert(gf.items[0].publicationDate == "2019-12-06T18:02:13Z");
}
}