!import
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 *
15 * The Original Code is mozilla.org code.
16 *
17 * The Initial Developer of the Original Code is Robert Sayre.
18 * Portions created by the Initial Developer are Copyright (C) 2006
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Contributor(s):
22 * Ben Goodger <beng@google.com>
23 * Myk Melez <myk@mozilla.org>
24 * Michael Ventnor <m.ventnor@gmail.com>
25 * Will Guaraldi <will.guaraldi@pculture.org>
26 *
27 * Alternatively, the contents of this file may be used under the terms of
28 * either the GNU General Public License Version 2 or later (the "GPL"), or
29 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
38 *
39 * ***** END LICENSE BLOCK ***** */
40
LOG
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
41 function LOG(str) {
42 dump("*** " + str + "\n");
43 }
44
45 const Ci = Components.interfaces;
46 const Cc = Components.classes;
47 const Cr = Components.results;
48 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
49 Components.utils.import("resource://gre/modules/ISO8601DateUtils.jsm");
50
51 const FP_CONTRACTID = "@mozilla.org/feed-processor;1";
52 const FP_CLASSID = Components.ID("{26acb1f0-28fc-43bc-867a-a46aabc85dd4}");
53 const FP_CLASSNAME = "Feed Processor";
54 const FR_CONTRACTID = "@mozilla.org/feed-result;1";
55 const FR_CLASSID = Components.ID("{072a5c3d-30c6-4f07-b87f-9f63d51403f2}");
56 const FR_CLASSNAME = "Feed Result";
57 const FEED_CONTRACTID = "@mozilla.org/feed;1";
58 const FEED_CLASSID = Components.ID("{5d0cfa97-69dd-4e5e-ac84-f253162e8f9a}");
59 const FEED_CLASSNAME = "Feed";
60 const ENTRY_CONTRACTID = "@mozilla.org/feed-entry;1";
61 const ENTRY_CLASSID = Components.ID("{8e4444ff-8e99-4bdd-aa7f-fb3c1c77319f}");
62 const ENTRY_CLASSNAME = "Feed Entry";
63 const TEXTCONSTRUCT_CONTRACTID = "@mozilla.org/feed-textconstruct;1";
64 const TEXTCONSTRUCT_CLASSID =
65 Components.ID("{b992ddcd-3899-4320-9909-924b3e72c922}");
66 const TEXTCONSTRUCT_CLASSNAME = "Feed Text Construct";
67 const GENERATOR_CONTRACTID = "@mozilla.org/feed-generator;1";
68 const GENERATOR_CLASSID =
69 Components.ID("{414af362-9ad8-4296-898e-62247f25a20e}");
70 const GENERATOR_CLASSNAME = "Feed Generator";
71 const PERSON_CONTRACTID = "@mozilla.org/feed-person;1";
72 const PERSON_CLASSID = Components.ID("{95c963b7-20b2-11db-92f6-001422106990}");
73 const PERSON_CLASSNAME = "Feed Person";
74
75 const IO_CONTRACTID = "@mozilla.org/network/io-service;1"
76 const BAG_CONTRACTID = "@mozilla.org/hash-property-bag;1"
77 const ARRAY_CONTRACTID = "@mozilla.org/array;1";
78 const SAX_CONTRACTID = "@mozilla.org/saxparser/xmlreader;1";
79 const UNESCAPE_CONTRACTID = "@mozilla.org/feed-unescapehtml;1";
80
81
82 var gIoService = null;
83
84 const XMLNS = "http://www.w3.org/XML/1998/namespace";
85 const RSS090NS = "http://my.netscape.com/rdf/simple/0.9/";
86 const WAIROLE_NS = "http://www.w3.org/2005/01/wai-rdf/GUIRoleTaxonomy#";
87
88 /***** Some general utils *****/
strToURI
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
89 function strToURI(link, base) {
90 var base = base || null;
91 if (!gIoService)
92 gIoService = Cc[IO_CONTRACTID].getService(Ci.nsIIOService);
93 try {
94 return gIoService.newURI(link, null, base);
95 }
96 catch(e) {
97 return null;
98 }
99 }
100
isArray
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
101 function isArray(a) {
102 return isObject(a) && a.constructor == Array;
103 }
104
isObject
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
105 function isObject(a) {
106 return (a && typeof a == "object") || isFunction(a);
107 }
108
isFunction
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
109 function isFunction(a) {
110 return typeof a == "function";
111 }
112
isIID
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
113 function isIID(a, iid) {
114 var rv = false;
115 try {
116 a.QueryInterface(iid);
117 rv = true;
118 }
119 catch(e) {
120 }
121 return rv;
122 }
123
isIArray
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
124 function isIArray(a) {
125 return isIID(a, Ci.nsIArray);
126 }
127
isIFeedContainer
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
128 function isIFeedContainer(a) {
129 return isIID(a, Ci.nsIFeedContainer);
130 }
131
stripTags
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
132 function stripTags(someHTML) {
133 return someHTML.replace(/<[^>]+>/g,"");
134 }
135
136 /**
137 * Searches through an array of links and returns a JS array
138 * of matching property bags.
139 */
140 const IANA_URI = "http://www.iana.org/assignments/relation/";
findAtomLinks
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
141 function findAtomLinks(rel, links) {
142 var rvLinks = [];
143 for (var i = 0; i < links.length; ++i) {
144 var linkElement = links.queryElementAt(i, Ci.nsIPropertyBag2);
145 // atom:link MUST have @href
146 if (bagHasKey(linkElement, "href")) {
147 var relAttribute = null;
148 if (bagHasKey(linkElement, "rel"))
149 relAttribute = linkElement.getPropertyAsAString("rel")
150 if ((!relAttribute && rel == "alternate") || relAttribute == rel) {
151 rvLinks.push(linkElement);
152 continue;
153 }
154 // catch relations specified by IANA URI
155 if (relAttribute == IANA_URI + rel) {
156 rvLinks.push(linkElement);
157 }
158 }
159 }
160 return rvLinks;
161 }
162
xmlEscape
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
163 function xmlEscape(s) {
164 s = s.replace(/&/g, "&");
165 s = s.replace(/>/g, ">");
166 s = s.replace(/</g, "<");
167 s = s.replace(/"/g, """);
168 s = s.replace(/'/g, "'");
169 return s;
170 }
171
arrayContains
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
172 function arrayContains(array, element) {
173 for (var i = 0; i < array.length; ++i) {
174 if (array[i] == element) {
175 return true;
176 }
177 }
178 return false;
179 }
180
181 // XXX add hasKey to nsIPropertyBag
bagHasKey
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
182 function bagHasKey(bag, key) {
183 try {
184 bag.getProperty(key);
185 return true;
186 }
187 catch (e) {
188 return false;
189 }
190 }
191
makePropGetter
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
192 function makePropGetter(key) {
FeedPropGetter
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
193 return function FeedPropGetter(bag) {
194 try {
195 return value = bag.getProperty(key);
196 }
197 catch(e) {
198 }
199 return null;
200 }
201 }
202
W3CToIETFDate
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
203 function W3CToIETFDate(dateString) {
204 var date = ISO8601DateUtils.parse(dateString);
205 return date.toUTCString();
206 }
207
208 const RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
209 // namespace map
210 var gNamespaces = {
211 "http://webns.net/mvcb/":"admin",
212 "http://backend.userland.com/rss":"",
213 "http://blogs.law.harvard.edu/tech/rss":"",
214 "http://www.w3.org/2005/Atom":"atom",
215 "http://purl.org/atom/ns#":"atom03",
216 "http://purl.org/rss/1.0/modules/content/":"content",
217 "http://purl.org/dc/elements/1.1/":"dc",
218 "http://purl.org/dc/terms/":"dcterms",
219 "http://www.w3.org/1999/02/22-rdf-syntax-ns#":"rdf",
220 "http://purl.org/rss/1.0/":"rss1",
221 "http://my.netscape.com/rdf/simple/0.9/":"rss1",
222 "http://wellformedweb.org/CommentAPI/":"wfw",
223 "http://purl.org/rss/1.0/modules/wiki/":"wiki",
224 "http://www.w3.org/XML/1998/namespace":"xml",
225 "http://search.yahoo.com/mrss/":"media",
226 "http://search.yahoo.com/mrss":"media"
227 }
228
229 // We allow a very small set of namespaces in XHTML content,
230 // for attributes only
231 var gAllowedXHTMLNamespaces = {
232 "http://www.w3.org/XML/1998/namespace":"xml",
233 "http://www.w3.org/TR/xhtml2":"xhtml2",
234 "http://www.w3.org/2005/07/aaa":"aaa",
235 // if someone ns qualifies XHTML, we have to prefix it to avoid an
236 // attribute collision.
237 "http://www.w3.org/1999/xhtml":"xhtml"
238 }
239
FeedResult
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
240 function FeedResult() {}
241 FeedResult.prototype = {
242 bozo: false,
243 doc: null,
244 version: null,
245 headers: null,
246 uri: null,
247 stylesheet: null,
248
FR_registerExtensionPrefix
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
249 registerExtensionPrefix: function FR_registerExtensionPrefix(ns, prefix) {
250 throw Cr.NS_ERROR_NOT_IMPLEMENTED;
251 },
252
253 // XPCOM stuff
254 classDescription: FR_CLASSNAME,
255 classID: FR_CLASSID,
256 contractID: FR_CONTRACTID,
257 QueryInterface: XPCOMUtils.generateQI([Ci.nsIFeedResult])
258 }
259
Feed
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
260 function Feed() {
261 this.subtitle = null;
262 this.title = null;
263 this.items = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
264 this.link = null;
265 this.id = null;
266 this.generator = null;
267 this.authors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
268 this.contributors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
269 this.baseURI = null;
270 this.enclosureCount = 0;
271 this.type = Ci.nsIFeed.TYPE_FEED;
272 }
273
274 Feed.prototype = {
275 searchLists: {
276 title: ["title", "rss1:title", "atom03:title", "atom:title"],
277 subtitle: ["description","dc:description","rss1:description",
278 "atom03:tagline","atom:subtitle"],
279 items: ["items","atom03_entries","entries"],
280 id: ["atom:id","rdf:about"],
281 generator: ["generator"],
282 authors : ["authors"],
283 contributors: ["contributors"],
284 title: ["title","rss1:title", "atom03:title","atom:title"],
285 link: [["link",strToURI],["rss1:link",strToURI]],
286 categories: ["categories", "dc:subject"],
287 rights: ["atom03:rights","atom:rights"],
288 cloud: ["cloud"],
289 image: ["image", "rss1:image", "atom:logo"],
290 textInput: ["textInput", "rss1:textinput"],
291 skipDays: ["skipDays"],
292 skipHours: ["skipHours"],
293 updated: ["pubDate", "lastBuildDate", "atom03:modified", "dc:date",
294 "dcterms:modified", "atom:updated"]
295 },
296
Feed_normalize
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
297 normalize: function Feed_normalize() {
298 fieldsToObj(this, this.searchLists);
299 if (this.skipDays)
300 this.skipDays = this.skipDays.getProperty("days");
301 if (this.skipHours)
302 this.skipHours = this.skipHours.getProperty("hours");
303
304 if (this.updated)
305 this.updated = dateParse(this.updated);
306
307 // Assign Atom link if needed
308 if (bagHasKey(this.fields, "links"))
309 this._atomLinksToURI();
310
311 this._calcEnclosureCountAndFeedType();
312
313 // Resolve relative image links
314 if (this.image && bagHasKey(this.image, "url"))
315 this._resolveImageLink();
316
317 this._resetBagMembersToRawText([this.searchLists.subtitle,
318 this.searchLists.title]);
319 },
320
Feed_calcEnclosureCountAndFeedType
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
321 _calcEnclosureCountAndFeedType: function Feed_calcEnclosureCountAndFeedType() {
322 var entries_with_enclosures = 0;
323 var audio_count = 0;
324 var image_count = 0;
325 var video_count = 0;
326 var other_count = 0;
327
328 for (var i = 0; i < this.items.length; ++i) {
329 var entry = this.items.queryElementAt(i, Ci.nsIFeedEntry);
330 entry.QueryInterface(Ci.nsIFeedContainer);
331
332 if (entry.enclosures && entry.enclosures.length > 0) {
333 ++entries_with_enclosures;
334
335 for (var e = 0; e < entry.enclosures.length; ++e) {
336 var enc = entry.enclosures.queryElementAt(e, Ci.nsIWritablePropertyBag2);
337 if (enc.hasKey("type")) {
338 var enctype = enc.get("type");
339
340 if (/^audio/.test(enctype)) {
341 ++audio_count;
342 } else if (/^image/.test(enctype)) {
343 ++image_count;
344 } else if (/^video/.test(enctype)) {
345 ++video_count;
346 } else {
347 ++other_count;
348 }
349 } else {
350 ++other_count;
351 }
352 }
353 }
354 }
355
356 var feedtype = Ci.nsIFeed.TYPE_FEED;
357
358 // For a feed to be marked as TYPE_VIDEO, TYPE_AUDIO and TYPE_IMAGE,
359 // we enforce two things:
360 //
361 // 1. all entries must have at least one enclosure
362 // 2. all enclosures must be video for TYPE_VIDEO, audio for TYPE_AUDIO or image
363 // for TYPE_IMAGE
364 //
365 // Otherwise it's a TYPE_FEED.
366 if (entries_with_enclosures == this.items.length && other_count == 0) {
367 if (audio_count > 0 && !video_count && !image_count) {
368 feedtype = Ci.nsIFeed.TYPE_AUDIO;
369
370 } else if (image_count > 0 && !audio_count && !video_count) {
371 feedtype = Ci.nsIFeed.TYPE_IMAGE;
372
373 } else if (video_count > 0 && !audio_count && !image_count) {
374 feedtype = Ci.nsIFeed.TYPE_VIDEO;
375 }
376 }
377
378 this.type = feedtype;
379 this.enclosureCount = other_count + video_count + audio_count + image_count;
380 },
381
Feed_linkToURI
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
382 _atomLinksToURI: function Feed_linkToURI() {
383 var links = this.fields.getPropertyAsInterface("links", Ci.nsIArray);
384 var alternates = findAtomLinks("alternate", links);
385 if (alternates.length > 0) {
386 var href = alternates[0].getPropertyAsAString("href");
387 var base;
388 if (bagHasKey(alternates[0], "xml:base"))
389 base = alternates[0].getPropertyAsAString("xml:base");
390 this.link = this._resolveURI(href, base);
391 }
392 },
393
Feed_resolveImageLink
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
394 _resolveImageLink: function Feed_resolveImageLink() {
395 var base;
396 if (bagHasKey(this.image, "xml:base"))
397 base = this.image.getPropertyAsAString("xml:base");
398 var url = this._resolveURI(this.image.getPropertyAsAString("url"), base);
399 if (url)
400 this.image.setPropertyAsAString("url", url.spec);
401 },
402
Feed_resolveURI
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
403 _resolveURI: function Feed_resolveURI(linkSpec, baseSpec) {
404 var uri = null;
405 try {
406 var base = baseSpec ? strToURI(baseSpec, this.baseURI) : this.baseURI;
407 uri = strToURI(linkSpec, base);
408 }
409 catch(e) {
410 LOG(e);
411 }
412
413 return uri;
414 },
415
416 // reset the bag to raw contents, not text constructs
Feed_resetBagMembers
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
417 _resetBagMembersToRawText: function Feed_resetBagMembers(fieldLists) {
418 for (var i=0; i<fieldLists.length; i++) {
419 for (var j=0; j<fieldLists[i].length; j++) {
420 if (bagHasKey(this.fields, fieldLists[i][j])) {
421 var textConstruct = this.fields.getProperty(fieldLists[i][j]);
422 this.fields.setPropertyAsAString(fieldLists[i][j],
423 textConstruct.text);
424 }
425 }
426 }
427 },
428
429 // XPCOM stuff
430 classDescription: FEED_CLASSNAME,
431 classID: FEED_CLASSID,
432 contractID: FEED_CONTRACTID,
433 QueryInterface: XPCOMUtils.generateQI([Ci.nsIFeed, Ci.nsIFeedContainer])
434 }
435
Entry
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
436 function Entry() {
437 this.summary = null;
438 this.content = null;
439 this.title = null;
440 this.fields = Cc["@mozilla.org/hash-property-bag;1"].
441 createInstance(Ci.nsIWritablePropertyBag2);
442 this.link = null;
443 this.id = null;
444 this.baseURI = null;
445 this.updated = null;
446 this.published = null;
447 this.authors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
448 this.contributors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
449 }
450
451 Entry.prototype = {
452 fields: null,
453 enclosures: null,
454 mediaContent: null,
455
456 searchLists: {
457 title: ["title", "rss1:title", "atom03:title", "atom:title"],
458 link: [["link",strToURI],["rss1:link",strToURI]],
459 id: [["guid", makePropGetter("guid")], "rdf:about",
460 "atom03:id", "atom:id"],
461 authors : ["authors"],
462 contributors: ["contributors"],
463 summary: ["description", "rss1:description", "dc:description",
464 "atom03:summary", "atom:summary"],
465 content: ["content:encoded","atom03:content","atom:content"],
466 rights: ["atom03:rights","atom:rights"],
467 published: ["pubDate", "atom03:issued", "dcterms:issued", "atom:published"],
468 updated: ["pubDate", "atom03:modified", "dc:date", "dcterms:modified",
469 "atom:updated"]
470 },
471
Entry_normalize
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
472 normalize: function Entry_normalize() {
473 fieldsToObj(this, this.searchLists);
474
475 // Assign Atom link if needed
476 if (bagHasKey(this.fields, "links"))
477 this._atomLinksToURI();
478
479 // Populate enclosures array
480 this._populateEnclosures();
481
482 // The link might be a guid w/ permalink=true
483 if (!this.link && bagHasKey(this.fields, "guid")) {
484 var guid = this.fields.getProperty("guid");
485 var isPermaLink = true;
486
487 if (bagHasKey(guid, "isPermaLink"))
488 isPermaLink = guid.getProperty("isPermaLink").toLowerCase() != "false";
489
490 if (guid && isPermaLink)
491 this.link = strToURI(guid.getProperty("guid"));
492 }
493
494 if (this.updated)
495 this.updated = dateParse(this.updated);
496 if (this.published)
497 this.published = dateParse(this.published);
498
499 this._resetBagMembersToRawText([this.searchLists.content,
500 this.searchLists.summary,
501 this.searchLists.title]);
502 },
503
Entry_populateEnclosures
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
504 _populateEnclosures: function Entry_populateEnclosures() {
505 if (bagHasKey(this.fields, "links"))
506 this._atomLinksToEnclosures();
507
508 // Add RSS2 enclosure to enclosures
509 if (bagHasKey(this.fields, "enclosure"))
510 this._enclosureToEnclosures();
511
512 // Add media:content to enclosures
513 if (bagHasKey(this.fields, "mediacontent"))
514 this._mediacontentToEnclosures();
515
516 // Add media:content in media:group to enclosures
517 if (bagHasKey(this.fields, "mediagroup"))
518 this._mediagroupToEnclosures();
519 },
520
521 __enclosure_map: null,
522
Entry_addToEnclosures
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
523 _addToEnclosures: function Entry_addToEnclosures(new_enc) {
524 // items we add to the enclosures array get displayed in the FeedWriter and
525 // they must have non-empty urls.
526 if (!bagHasKey(new_enc, "url") || new_enc.getPropertyAsAString("url") == "")
527 return;
528
529 if (this.__enclosure_map == null)
530 this.__enclosure_map = {};
531
532 var previous_enc = this.__enclosure_map[new_enc.getPropertyAsAString("url")];
533
534 if (previous_enc != undefined) {
535 previous_enc.QueryInterface(Ci.nsIWritablePropertyBag2);
536
537 if (!bagHasKey(previous_enc, "type") && bagHasKey(new_enc, "type"))
538 previous_enc.setPropertyAsAString("type", new_enc.getPropertyAsAString("type"));
539
540 if (!bagHasKey(previous_enc, "length") && bagHasKey(new_enc, "length"))
541 previous_enc.setPropertyAsAString("length", new_enc.getPropertyAsAString("length"));
542
543 return;
544 }
545
546 if (this.enclosures == null) {
547 this.enclosures = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
548 this.enclosures.QueryInterface(Ci.nsIMutableArray);
549 }
550
551 this.enclosures.appendElement(new_enc, false);
552 this.__enclosure_map[new_enc.getPropertyAsAString("url")] = new_enc;
553 },
554
Entry_linkToEnclosure
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
555 _atomLinksToEnclosures: function Entry_linkToEnclosure() {
556 var links = this.fields.getPropertyAsInterface("links", Ci.nsIArray);
557 var enc_links = findAtomLinks("enclosure", links);
558 if (enc_links.length == 0)
559 return;
560
561 for (var i = 0; i < enc_links.length; ++i) {
562 var link = enc_links[i];
563
564 // an enclosure must have an href
565 if (!(link.getProperty("href")))
566 return;
567
568 var enc = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
569
570 // copy Atom bits over to equivalent enclosure bits
571 enc.setPropertyAsAString("url", link.getPropertyAsAString("href"));
572 if (bagHasKey(link, "type"))
573 enc.setPropertyAsAString("type", link.getPropertyAsAString("type"));
574 if (bagHasKey(link, "length"))
575 enc.setPropertyAsAString("length", link.getPropertyAsAString("length"));
576
577 this._addToEnclosures(enc);
578 }
579 },
580
Entry_enclosureToEnclosures
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
581 _enclosureToEnclosures: function Entry_enclosureToEnclosures() {
582 var enc = this.fields.getPropertyAsInterface("enclosure", Ci.nsIPropertyBag2);
583
584 if (!(enc.getProperty("url")))
585 return;
586
587 this._addToEnclosures(enc);
588 },
589
Entry_mediacontentToEnclosures
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
590 _mediacontentToEnclosures: function Entry_mediacontentToEnclosures() {
591 var mediacontent = this.fields.getPropertyAsInterface("mediacontent", Ci.nsIArray);
592
593 for (var i = 0; i < mediacontent.length; ++i) {
594 var contentElement = mediacontent.queryElementAt(i, Ci.nsIWritablePropertyBag2);
595
596 // media:content don't require url, but if it's not there, we should
597 // skip it.
598 if (!bagHasKey(contentElement, "url"))
599 continue;
600
601 var enc = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
602
603 // copy media:content bits over to equivalent enclosure bits
604 enc.setPropertyAsAString("url", contentElement.getPropertyAsAString("url"));
605 if (bagHasKey(contentElement, "type")) {
606 enc.setPropertyAsAString("type", contentElement.getPropertyAsAString("type"));
607 }
608 if (bagHasKey(contentElement, "fileSize")) {
609 enc.setPropertyAsAString("length", contentElement.getPropertyAsAString("fileSize"));
610 }
611
612 this._addToEnclosures(enc);
613 }
614 },
615
Entry_mediagroupToEnclosures
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
616 _mediagroupToEnclosures: function Entry_mediagroupToEnclosures() {
617 var group = this.fields.getPropertyAsInterface("mediagroup", Ci.nsIPropertyBag2);
618
619 var content = group.getPropertyAsInterface("mediacontent", Ci.nsIArray);
620 for (var i = 0; i < content.length; ++i) {
621 var contentElement = content.queryElementAt(i, Ci.nsIWritablePropertyBag2);
622 // media:content don't require url, but if it's not there, we should
623 // skip it.
624 if (!bagHasKey(contentElement, "url"))
625 continue;
626
627 var enc = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
628
629 // copy media:content bits over to equivalent enclosure bits
630 enc.setPropertyAsAString("url", contentElement.getPropertyAsAString("url"));
631 if (bagHasKey(contentElement, "type")) {
632 enc.setPropertyAsAString("type", contentElement.getPropertyAsAString("type"));
633 }
634 if (bagHasKey(contentElement, "fileSize")) {
635 enc.setPropertyAsAString("length", contentElement.getPropertyAsAString("fileSize"));
636 }
637
638 this._addToEnclosures(enc);
639 }
640 },
641
642 // XPCOM stuff
643 classDescription: ENTRY_CLASSNAME,
644 classID: ENTRY_CLASSID,
645 contractID: ENTRY_CONTRACTID,
646 QueryInterface: XPCOMUtils.generateQI(
647 [Ci.nsIFeedEntry, Ci.nsIFeedContainer]
648 )
649 }
650
651 Entry.prototype._atomLinksToURI = Feed.prototype._atomLinksToURI;
652 Entry.prototype._resolveURI = Feed.prototype._resolveURI;
653 Entry.prototype._resetBagMembersToRawText =
654 Feed.prototype._resetBagMembersToRawText;
655
656 // TextConstruct represents and element that could contain (X)HTML
TextConstruct
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
657 function TextConstruct() {
658 this.lang = null;
659 this.base = null;
660 this.type = "text";
661 this.text = null;
662 this.unescapeHTML = Cc[UNESCAPE_CONTRACTID].
663 getService(Ci.nsIScriptableUnescapeHTML);
664 }
665
666 TextConstruct.prototype = {
TC_plainText
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
667 plainText: function TC_plainText() {
668 if (this.type != "text") {
669 return this.unescapeHTML.unescape(stripTags(this.text));
670 }
671 return this.text;
672 },
673
TC_createDocumentFragment
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
674 createDocumentFragment: function TC_createDocumentFragment(element) {
675 if (this.type == "text") {
676 var doc = element.ownerDocument;
677 var docFragment = doc.createDocumentFragment();
678 var node = doc.createTextNode(this.text);
679 docFragment.appendChild(node);
680 return docFragment;
681 }
682 var isXML;
683 if (this.type == "xhtml")
684 isXML = true
685 else if (this.type == "html")
686 isXML = false;
687 else
688 return null;
689
690 return this.unescapeHTML.parseFragment(this.text, isXML,
691 this.base, element);
692 },
693
694 // XPCOM stuff
695 classDescription: TEXTCONSTRUCT_CLASSNAME,
696 classID: TEXTCONSTRUCT_CLASSID,
697 contractID: TEXTCONSTRUCT_CONTRACTID,
698 QueryInterface: XPCOMUtils.generateQI([Ci.nsIFeedTextConstruct])
699 }
700
701 // Generator represents the software that produced the feed
Generator
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
702 function Generator() {
703 this.lang = null;
704 this.agent = null;
705 this.version = null;
706 this.uri = null;
707
708 // nsIFeedElementBase
709 this._attributes = null;
710 this.baseURI = null;
711 }
712
713 Generator.prototype = {
714
get_attributes
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
715 get attributes() {
716 return this._attributes;
717 },
718
set_attributes
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
719 set attributes(value) {
720 this._attributes = value;
721 this.version = this._attributes.getValueFromName("","version");
722 var uriAttribute = this._attributes.getValueFromName("","uri") ||
723 this._attributes.getValueFromName("","url");
724 this.uri = strToURI(uriAttribute, this.baseURI);
725
726 // RSS1
727 uriAttribute = this._attributes.getValueFromName(RDF_NS,"resource");
728 if (uriAttribute) {
729 this.agent = uriAttribute;
730 this.uri = strToURI(uriAttribute, this.baseURI);
731 }
732 },
733
734 // XPCOM stuff
735 classDescription: GENERATOR_CLASSNAME,
736 classID: GENERATOR_CLASSID,
737 contractID: GENERATOR_CONTRACTID,
738 QueryInterface: XPCOMUtils.generateQI(
739 [Ci.nsIFeedGenerator, Ci.nsIFeedElementBase]
740 )
741 }
742
Person
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
743 function Person() {
744 this.name = null;
745 this.uri = null;
746 this.email = null;
747
748 // nsIFeedElementBase
749 this.attributes = null;
750 this.baseURI = null;
751 }
752
753 Person.prototype = {
754 // XPCOM stuff
755 classDescription: PERSON_CLASSNAME,
756 classID: PERSON_CLASSID,
757 contractID: PERSON_CONTRACTID,
758 QueryInterface: XPCOMUtils.generateQI(
759 [Ci.nsIFeedPerson, Ci.nsIFeedElementBase]
760 )
761 }
762
763 /**
764 * Map a list of fields into properties on a container.
765 *
766 * @param container An nsIFeedContainer
767 * @param fields A list of fields to search for. List members can
768 * be a list, in which case the second member is
769 * transformation function (like parseInt).
770 */
fieldsToObj
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
771 function fieldsToObj(container, fields) {
772 var props,prop,field,searchList;
773 for (var key in fields) {
774 searchList = fields[key];
775 for (var i=0; i < searchList.length; ++i) {
776 props = searchList[i];
777 prop = null;
778 field = isArray(props) ? props[0] : props;
779 try {
780 prop = container.fields.getProperty(field);
781 }
782 catch(e) {
783 }
784 if (prop) {
785 prop = isArray(props) ? props[1](prop) : prop;
786 container[key] = prop;
787 }
788 }
789 }
790 }
791
792 /**
793 * Lower cases an element's localName property
794 * @param element A DOM element.
795 *
796 * @returns The lower case localName property of the specified element
797 */
LC
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
798 function LC(element) {
799 return element.localName.toLowerCase();
800 }
801
802 // TODO move these post-processor functions
803 // create a generator element
atomGenerator
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
804 function atomGenerator(s, generator) {
805 generator.QueryInterface(Ci.nsIFeedGenerator);
806 generator.agent = trimString(s);
807 return generator;
808 }
809
810 // post-process atom:logo to create an RSS2-like structure
atomLogo
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
811 function atomLogo(s, logo) {
812 logo.setPropertyAsAString("url", trimString(s));
813 }
814
815 // post-process an RSS category, map it to the Atom fields.
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
816 function rssCatTerm(s, cat) {
817 // add slash handling?
818 cat.setPropertyAsAString("term", trimString(s));
819 return cat;
820 }
821
822 // post-process a GUID
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
823 function rssGuid(s, guid) {
824 guid.setPropertyAsAString("guid", trimString(s));
825 return guid;
826 }
827
828 // post-process an RSS author element
829 //
830 // It can contain a field like this:
831 //
832 // <author>lawyer@boyer.net (Lawyer Boyer)</author>
833 //
834 // or, delightfully, a field like this:
835 //
836 // <dc:creator>Simon St.Laurent (mailto:simonstl@simonstl.com)</dc:creator>
837 //
838 // We want to split this up and assign it to corresponding Atom
839 // fields.
840 //
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
841 function rssAuthor(s,author) {
842 author.QueryInterface(Ci.nsIFeedPerson);
843 // check for RSS2 string format
844 var chars = trimString(s);
845 var matches = chars.match(/(.*)\((.*)\)/);
846 var emailCheck =
847 /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
848 if (matches) {
849 var match1 = trimString(matches[1]);
850 var match2 = trimString(matches[2]);
851 if (match2.indexOf("mailto:") == 0)
852 match2 = match2.substring(7);
853 if (emailCheck.test(match1)) {
854 author.email = match1;
855 author.name = match2;
856 }
857 else if (emailCheck.test(match2)) {
858 author.email = match2;
859 author.name = match1;
860 }
861 else {
862 // put it back together
863 author.name = match1 + " (" + match2 + ")";
864 }
865 }
866 else {
867 author.name = chars;
868 if (chars.indexOf('@'))
869 author.email = chars;
870 }
871 return author;
872 }
873
874 //
875 // skipHours and skipDays map to arrays, so we need to change the
876 // string to an nsISupports in order to stick it in there.
877 //
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
878 function rssArrayElement(s) {
879 var str = Cc["@mozilla.org/supports-string;1"].
880 createInstance(Ci.nsISupportsString);
881 str.data = s;
882 str.QueryInterface(Ci.nsISupportsString);
883 return str;
884 }
885
886 /***** Some feed utils from TBird *****/
887
888 /**
889 * Tests a RFC822 date against a regex.
890 * @param aDateStr A string to test as an RFC822 date.
891 *
892 * @returns A boolean indicating whether the string is a valid RFC822 date.
893 */
isValidRFC822Date
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
894 function isValidRFC822Date(aDateStr) {
895 var regex = new RegExp(RFC822_RE);
896 return regex.test(aDateStr);
897 }
898
899 /**
900 * Removes leading and trailing whitespace from a string.
901 * @param s The string to trim.
902 *
903 * @returns A new string with whitespace stripped.
904 */
trimString
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
905 function trimString(s) {
906 return(s.replace(/^\s+/, "").replace(/\s+$/, ""));
907 }
908
909 // Regular expression matching RFC822 dates
910 const RFC822_RE = "^((Mon|Tue|Wed|Thu|Fri|Sat|Sun)([a-z]+)?,? *)?\\d\\d?"
911 + " +(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)([a-z]+)?"
912 + " +\\d\\d(\\d\\d)? +\\d?\\d:\\d\\d(:\\d\\d)?"
913 + " +([+-]?\\d\\d\\d\\d|GMT|UT[C]?|(E|C|M|P)(ST|DT)|[A-IK-Z])$";
914
915 /**
916 * XXX -- need to decide what this should return.
917 * XXX -- Is there a Date class usable from C++?
918 *
919 * Tries tries parsing various date formats.
920 * @param dateString
921 * A string that is supposedly an RFC822 or RFC3339 date.
922 * @returns A Date.toString XXX--fixme
923 */
dateParse
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
924 function dateParse(dateString) {
925 var date = trimString(dateString);
926
927 if (date.search(/^\d\d\d\d/) != -1) //Could be a ISO8601/W3C date
928 return W3CToIETFDate(dateString);
929
930 if (isValidRFC822Date(date))
931 return date;
932
933 if (!isNaN(parseInt(date, 10))) {
934 //It's an integer, so maybe it's a timestamp
935 var d = new Date(parseInt(date, 10) * 1000);
936 var now = new Date();
937 var yeardiff = now.getFullYear() - d.getFullYear();
938 if ((yeardiff >= 0) && (yeardiff < 3)) {
939 // it's quite likely the correct date. 3 years is an arbitrary cutoff,
940 // but this is an invalid date format, and there's no way to verify
941 // its correctness.
942 return d.toString();
943 }
944 }
945 // Can't help.
946 return null;
947 }
948
949
950 const XHTML_NS = "http://www.w3.org/1999/xhtml";
951
952 // The XHTMLHandler handles inline XHTML found in things like atom:summary
XHTMLHandler
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
953 function XHTMLHandler(processor, isAtom, waiPrefixes) {
954 this._buf = "";
955 this._processor = processor;
956 this._depth = 0;
957 this._isAtom = isAtom;
958 // a stack of lists tracking in-scope namespaces
959 this._inScopeNS = [];
960 this._waiPrefixes = waiPrefixes;
961 }
962
963 // The fidelity can be improved here, to allow handling of stuff like
964 // SVG and MathML. XXX
965 XHTMLHandler.prototype = {
966
967 // look back up at the declared namespaces
968 // we always use the same prefixes for our safe stuff
XH__isInScope
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
969 _isInScope: function XH__isInScope(ns) {
970 for (var i in this._inScopeNS) {
971 for (var uri in this._inScopeNS[i]) {
972 if (this._inScopeNS[i][uri] == ns)
973 return true;
974 }
975 }
976 return false;
977 },
978
XH_startDocument
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
979 startDocument: function XH_startDocument() {
980 },
XH_endDocument
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
981 endDocument: function XH_endDocument() {
982 },
XH_startElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
983 startElement: function XH_startElement(uri, localName, qName, attributes) {
984 ++this._depth;
985 this._inScopeNS.push([]);
986
987 // RFC4287 requires XHTML to be wrapped in a div that is *not* part of
988 // the content. This prevents people from screwing up namespaces, but
989 // we need to skip it here.
990 if (this._isAtom && this._depth == 1 && localName == "div")
991 return;
992
993 // If it's an XHTML element, record it. Otherwise, it's ignored.
994 if (uri == XHTML_NS) {
995 this._buf += "<" + localName;
996 var uri;
997 for (var i=0; i < attributes.length; ++i) {
998 uri = attributes.getURI(i);
999 // XHTML attributes aren't in a namespace
1000 if (uri == "") {
1001 this._buf += (" " + attributes.getLocalName(i) + "='" +
1002 xmlEscape(attributes.getValue(i)) + "'");
1003 } else {
1004 // write a small set of allowed attribute namespaces
1005 var prefix = gAllowedXHTMLNamespaces[uri];
1006 if (prefix != null) {
1007 // The attribute value we'll attempt to write
1008 var attributeValue = xmlEscape(attributes.getValue(i));
1009
1010 // More QName abuse from W3C
1011 var rolePrefix = "";
1012 if (attributes.getLocalName(i) == "role") {
1013 for (var aPrefix in this._waiPrefixes) {
1014 if (attributeValue.indexOf(aPrefix + ":") == 0) {
1015 // Now, due to the terrible layer mismatch
1016 // that is QNames in content, we have to see
1017 // if the attribute value clashes with our
1018 // namespace declarations.
1019 var isCollision = false;
1020 for (var uriKey in gAllowedXHTMLNamespaces) {
1021 if (gAllowedXHTMLNamespaces[uriKey] == aPrefix)
1022 isCollision = true;
1023 }
1024
1025 if (isCollision) {
1026 rolePrefix = aPrefix + i;
1027 attributeValue =
1028 rolePrefix + ":" +
1029 attributeValue.substring(aPrefix.length + 1);
1030 } else {
1031 rolePrefix = aPrefix;
1032 }
1033
1034 break;
1035 }
1036 }
1037
1038 if (rolePrefix)
1039 this._buf += (" xmlns:" + rolePrefix +
1040 "='" + WAIROLE_NS + "'");
1041 }
1042
1043 // it's an allowed attribute NS.
1044 // write the attribute
1045 this._buf += (" " + prefix + ":" +
1046 attributes.getLocalName(i) +
1047 "='" + attributeValue + "'");
1048
1049 // write an xmlns declaration if necessary
1050 if (prefix != "xml" && !this._isInScope(uri)) {
1051 this._inScopeNS[this._inScopeNS.length - 1].push(uri);
1052 this._buf += " xmlns:" + prefix + "='" + uri + "'";
1053 }
1054 }
1055 }
1056 }
1057 this._buf += ">";
1058 }
1059 },
XH_endElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1060 endElement: function XH_endElement(uri, localName, qName) {
1061 --this._depth;
1062 this._inScopeNS.pop();
1063
1064 // We need to skip outer divs in Atom. See comment in startElement.
1065 if (this._isAtom && this._depth == 0 && localName == "div")
1066 return;
1067
1068 // When we peek too far, go back to the main processor
1069 if (this._depth < 0) {
1070 this._processor.returnFromXHTMLHandler(trimString(this._buf),
1071 uri, localName, qName);
1072 return;
1073 }
1074 // If it's an XHTML element, record it. Otherwise, it's ignored.
1075 if (uri == XHTML_NS) {
1076 this._buf += "</" + localName + ">";
1077 }
1078 },
XH_characters
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1079 characters: function XH_characters(data) {
1080 this._buf += xmlEscape(data);
1081 },
XH_startPrefixMapping
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1082 startPrefixMapping: function XH_startPrefixMapping(prefix, uri) {
1083 if (prefix && uri == WAIROLE_NS)
1084 this._waiPrefixes[prefix] = WAIROLE_NS;
1085 },
FP_endPrefixMapping
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1086 endPrefixMapping: function FP_endPrefixMapping(prefix) {
1087 if (prefix)
1088 delete this._waiPrefixes[prefix];
1089 },
XH_processingInstruction
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1090 processingInstruction: function XH_processingInstruction() {
1091 },
1092 }
1093
1094 /**
1095 * The ExtensionHandler deals with elements we haven't explicitly
1096 * added to our transition table in the FeedProcessor.
1097 */
ExtensionHandler
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1098 function ExtensionHandler(processor) {
1099 this._buf = "";
1100 this._depth = 0;
1101 this._hasChildElements = false;
1102
1103 // The FeedProcessor
1104 this._processor = processor;
1105
1106 // Fields of the outermost extension element.
1107 this._localName = null;
1108 this._uri = null;
1109 this._qName = null;
1110 this._attrs = null;
1111 }
1112
1113 ExtensionHandler.prototype = {
EH_startDocument
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1114 startDocument: function EH_startDocument() {
1115 },
EH_endDocument
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1116 endDocument: function EH_endDocument() {
1117 },
EH_startElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1118 startElement: function EH_startElement(uri, localName, qName, attrs) {
1119 ++this._depth;
1120 var prefix = gNamespaces[uri] ? gNamespaces[uri] + ":" : "";
1121 var key = prefix + localName;
1122
1123 if (this._depth == 1) {
1124 this._uri = uri;
1125 this._localName = localName;
1126 this._qName = qName;
1127 this._attrs = attrs;
1128 }
1129
1130 // if we descend into another element, we won't send text
1131 this._hasChildElements = (this._depth > 1);
1132
1133 },
EH_endElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1134 endElement: function EH_endElement(uri, localName, qName) {
1135 --this._depth;
1136 if (this._depth == 0) {
1137 var text = this._hasChildElements ? null : trimString(this._buf);
1138 this._processor.returnFromExtHandler(this._uri, this._localName,
1139 text, this._attrs);
1140 }
1141 },
EH_characters
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1142 characters: function EH_characters(data) {
1143 if (!this._hasChildElements)
1144 this._buf += data;
1145 },
EH_startPrefixMapping
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1146 startPrefixMapping: function EH_startPrefixMapping() {
1147 },
EH_endPrefixMapping
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1148 endPrefixMapping: function EH_endPrefixMapping() {
1149 },
EH_processingInstruction
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1150 processingInstruction: function EH_processingInstruction() {
1151 },
1152 };
1153
1154
1155 /**
1156 * ElementInfo is a simple container object that describes
1157 * some characteristics of a feed element. For example, it
1158 * says whether an element can be expected to appear more
1159 * than once inside a given entry or feed.
1160 */
ElementInfo
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1161 function ElementInfo(fieldName, containerClass, closeFunc, isArray) {
1162 this.fieldName = fieldName;
1163 this.containerClass = containerClass;
1164 this.closeFunc = closeFunc;
1165 this.isArray = isArray;
1166 this.isWrapper = false;
1167 }
1168
1169 /**
1170 * FeedElementInfo represents a feed element, usually the root.
1171 */
FeedElementInfo
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1172 function FeedElementInfo(fieldName, feedVersion) {
1173 this.isWrapper = false;
1174 this.fieldName = fieldName;
1175 this.feedVersion = feedVersion;
1176 }
1177
1178 /**
1179 * Some feed formats include vestigial wrapper elements that we don't
1180 * want to include in our object model, but we do need to keep track
1181 * of during parsing.
1182 */
WrapperElementInfo
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1183 function WrapperElementInfo(fieldName) {
1184 this.isWrapper = true;
1185 this.fieldName = fieldName;
1186 }
1187
1188 /***** The Processor *****/
FeedProcessor
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1189 function FeedProcessor() {
1190 this._reader = Cc[SAX_CONTRACTID].createInstance(Ci.nsISAXXMLReader);
1191 this._buf = "";
1192 this._feed = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
1193 this._handlerStack = [];
1194 this._xmlBaseStack = []; // sparse array keyed to nesting depth
1195 this._depth = 0;
1196 this._state = "START";
1197 this._result = null;
1198 this._extensionHandler = null;
1199 this._xhtmlHandler = null;
1200 this._haveSentResult = false;
1201
1202 // http://www.w3.org/WAI/PF/GUI/ uses QNames in content :(
1203 this._waiPrefixes = {};
1204
1205 // The nsIFeedResultListener waiting for the parse results
1206 this.listener = null;
1207
1208 // These elements can contain (X)HTML or plain text.
1209 // We keep a table here that contains their default treatment
1210 this._textConstructs = {"atom:title":"text",
1211 "atom:summary":"text",
1212 "atom:rights":"text",
1213 "atom:content":"text",
1214 "atom:subtitle":"text",
1215 "description":"html",
1216 "rss1:description":"html",
1217 "dc:description":"html",
1218 "content:encoded":"html",
1219 "title":"text",
1220 "rss1:title":"text",
1221 "atom03:title":"text",
1222 "atom03:tagline":"text",
1223 "atom03:summary":"text",
1224 "atom03:content":"text"};
1225 this._stack = [];
1226
1227 this._trans = {
1228 "START": {
1229 //If we hit a root RSS element, treat as RSS2.
1230 "rss": new FeedElementInfo("RSS2", "rss2"),
1231
1232 // If we hit an RDF element, if could be RSS1, but we can't
1233 // verify that until we hit a rss1:channel element.
1234 "rdf:RDF": new WrapperElementInfo("RDF"),
1235
1236 // If we hit a Atom 1.0 element, treat as Atom 1.0.
1237 "atom:feed": new FeedElementInfo("Atom", "atom"),
1238
1239 // Treat as Atom 0.3
1240 "atom03:feed": new FeedElementInfo("Atom03", "atom03"),
1241 },
1242
1243 /********* RSS2 **********/
1244 "IN_RSS2": {
1245 "channel": new WrapperElementInfo("channel")
1246 },
1247
1248 "IN_CHANNEL": {
1249 "item": new ElementInfo("items", Cc[ENTRY_CONTRACTID], null, true),
1250 "managingEditor": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
1251 rssAuthor, true),
1252 "dc:creator": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
1253 rssAuthor, true),
1254 "dc:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
1255 rssAuthor, true),
1256 "dc:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
1257 rssAuthor, true),
1258 "category": new ElementInfo("categories", null, rssCatTerm, true),
1259 "cloud": new ElementInfo("cloud", null, null, false),
1260 "image": new ElementInfo("image", null, null, false),
1261 "textInput": new ElementInfo("textInput", null, null, false),
1262 "skipDays": new ElementInfo("skipDays", null, null, false),
1263 "skipHours": new ElementInfo("skipHours", null, null, false),
1264 "generator": new ElementInfo("generator", Cc[GENERATOR_CONTRACTID],
1265 atomGenerator, false),
1266 },
1267
1268 "IN_ITEMS": {
1269 "author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
1270 rssAuthor, true),
1271 "dc:creator": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
1272 rssAuthor, true),
1273 "dc:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
1274 rssAuthor, true),
1275 "dc:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
1276 rssAuthor, true),
1277 "category": new ElementInfo("categories", null, rssCatTerm, true),
1278 "enclosure": new ElementInfo("enclosure", null, null, false),
1279 "media:content": new ElementInfo("mediacontent", null, null, true),
1280 "media:group": new ElementInfo("mediagroup", null, null, false),
1281 "guid": new ElementInfo("guid", null, rssGuid, false)
1282 },
1283
1284 "IN_SKIPDAYS": {
1285 "day": new ElementInfo("days", null, rssArrayElement, true)
1286 },
1287
1288 "IN_SKIPHOURS":{
1289 "hour": new ElementInfo("hours", null, rssArrayElement, true)
1290 },
1291
1292 "IN_MEDIAGROUP": {
1293 "media:content": new ElementInfo("mediacontent", null, null, true)
1294 },
1295
1296 /********* RSS1 **********/
1297 "IN_RDF": {
1298 // If we hit a rss1:channel, we can verify that we have RSS1
1299 "rss1:channel": new FeedElementInfo("rdf_channel", "rss1"),
1300 "rss1:image": new ElementInfo("image", null, null, false),
1301 "rss1:textinput": new ElementInfo("textInput", null, null, false),
1302 "rss1:item": new ElementInfo("items", Cc[ENTRY_CONTRACTID], null, true),
1303 },
1304
1305 "IN_RDF_CHANNEL": {
1306 "admin:generatorAgent": new ElementInfo("generator",
1307 Cc[GENERATOR_CONTRACTID],
1308 null, false),
1309 "dc:creator": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
1310 rssAuthor, true),
1311 "dc:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
1312 rssAuthor, true),
1313 "dc:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
1314 rssAuthor, true),
1315 },
1316
1317 /********* ATOM 1.0 **********/
1318 "IN_ATOM": {
1319 "atom:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
1320 null, true),
1321 "atom:generator": new ElementInfo("generator", Cc[GENERATOR_CONTRACTID],
1322 atomGenerator, false),
1323 "atom:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
1324 null, true),
1325 "atom:link": new ElementInfo("links", null, null, true),
1326 "atom:logo": new ElementInfo("atom:logo", null, atomLogo, false),
1327 "atom:entry": new ElementInfo("entries", Cc[ENTRY_CONTRACTID],
1328 null, true)
1329 },
1330
1331 "IN_ENTRIES": {
1332 "atom:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
1333 null, true),
1334 "atom:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
1335 null, true),
1336 "atom:link": new ElementInfo("links", null, null, true),
1337 },
1338
1339 /********* ATOM 0.3 **********/
1340 "IN_ATOM03": {
1341 "atom03:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
1342 null, true),
1343 "atom03:contributor": new ElementInfo("contributors",
1344 Cc[PERSON_CONTRACTID],
1345 null, true),
1346 "atom03:link": new ElementInfo("links", null, null, true),
1347 "atom03:entry": new ElementInfo("atom03_entries", Cc[ENTRY_CONTRACTID],
1348 null, true),
1349 "atom03:generator": new ElementInfo("generator", Cc[GENERATOR_CONTRACTID],
1350 atomGenerator, false),
1351 },
1352
1353 "IN_ATOM03_ENTRIES": {
1354 "atom03:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
1355 null, true),
1356 "atom03:contributor": new ElementInfo("contributors",
1357 Cc[PERSON_CONTRACTID],
1358 null, true),
1359 "atom03:link": new ElementInfo("links", null, null, true),
1360 "atom03:entry": new ElementInfo("atom03_entries", Cc[ENTRY_CONTRACTID],
1361 null, true)
1362 }
1363 }
1364 }
1365
1366 // See startElement for a long description of how feeds are processed.
1367 FeedProcessor.prototype = {
1368
1369 // Set ourselves as the SAX handler, and set the base URI
FP_init
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1370 _init: function FP_init(uri) {
1371 this._reader.contentHandler = this;
1372 this._reader.errorHandler = this;
1373 this._result = Cc[FR_CONTRACTID].createInstance(Ci.nsIFeedResult);
1374 if (uri) {
1375 this._result.uri = uri;
1376 this._reader.baseURI = uri;
1377 this._xmlBaseStack[0] = uri;
1378 }
1379 },
1380
1381 // This function is called once we figure out what type of feed
1382 // we're dealing with. Some feed types require digging a bit further
1383 // than the root.
FP_docVerified
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1384 _docVerified: function FP_docVerified(version) {
1385 this._result.doc = Cc[FEED_CONTRACTID].createInstance(Ci.nsIFeed);
1386 this._result.doc.baseURI =
1387 this._xmlBaseStack[this._xmlBaseStack.length - 1];
1388 this._result.doc.fields = this._feed;
1389 this._result.version = version;
1390 },
1391
1392 // When we're done with the feed, let the listener know what
1393 // happened.
FP_sendResult
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1394 _sendResult: function FP_sendResult() {
1395 this._haveSentResult = true;
1396 try {
1397 // Can be null when a non-feed is fed to us
1398 if (this._result.doc)
1399 this._result.doc.normalize();
1400 }
1401 catch (e) {
1402 LOG("FIXME: " + e);
1403 }
1404
1405 try {
1406 if (this.listener != null)
1407 this.listener.handleResult(this._result);
1408 }
1409 finally {
1410 this._result = null;
1411 }
1412 },
1413
1414 // Parsing functions
FP_parseFromStream
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1415 parseFromStream: function FP_parseFromStream(stream, uri) {
1416 this._init(uri);
1417 this._reader.parseFromStream(stream, null, stream.available(),
1418 "application/xml");
1419 this._reader = null;
1420 },
1421
FP_parseFromString
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1422 parseFromString: function FP_parseFromString(inputString, uri) {
1423 this._init(uri);
1424 this._reader.parseFromString(inputString, "application/xml");
1425 this._reader = null;
1426 },
1427
FP_parseAsync
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1428 parseAsync: function FP_parseAsync(requestObserver, uri) {
1429 this._init(uri);
1430 this._reader.parseAsync(requestObserver);
1431 },
1432
1433 // nsIStreamListener
1434
1435 // The XMLReader will throw sensible exceptions if these get called
1436 // out of order.
FP_onStartRequest
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1437 onStartRequest: function FP_onStartRequest(request, context) {
1438 // this will throw if the request is not a channel, but so will nsParser.
1439 var channel = request.QueryInterface(Ci.nsIChannel);
1440 channel.contentType = "application/vnd.mozilla.maybe.feed";
1441 this._reader.onStartRequest(request, context);
1442 },
1443
FP_onStopRequest
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1444 onStopRequest: function FP_onStopRequest(request, context, statusCode) {
1445 try {
1446 this._reader.onStopRequest(request, context, statusCode);
1447 }
1448 finally {
1449 this._reader = null;
1450 }
1451 },
1452
FP_onDataAvailable
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1453 onDataAvailable:
1454 function FP_onDataAvailable(request, context, inputStream, offset, count) {
1455 this._reader.onDataAvailable(request, context, inputStream, offset, count);
1456 },
1457
1458 // nsISAXErrorHandler
1459
1460 // We only care about fatal errors. When this happens, we may have
1461 // parsed through the feed metadata and some number of entries. The
1462 // listener can still show some of that data if it wants, and we'll
1463 // set the bozo bit to indicate we were unable to parse all the way
1464 // through.
FP_reportError
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1465 fatalError: function FP_reportError() {
1466 this._result.bozo = true;
1467 //XXX need to QI to FeedProgressListener
1468 if (!this._haveSentResult)
1469 this._sendResult();
1470 },
1471
1472 // nsISAXContentHandler
1473
FP_startDocument
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1474 startDocument: function FP_startDocument() {
1475 //LOG("----------");
1476 },
1477
FP_endDocument
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1478 endDocument: function FP_endDocument() {
1479 if (!this._haveSentResult)
1480 this._sendResult();
1481 },
1482
1483 // The transitions defined above identify elements that contain more
1484 // than just text. For example RSS items contain many fields, and so
1485 // do Atom authors. The only commonly used elements that contain
1486 // mixed content are Atom Text Constructs of type="xhtml", which we
1487 // delegate to another handler for cleaning. That leaves a couple
1488 // different types of elements to deal with: those that should occur
1489 // only once, such as title elements, and those that can occur
1490 // multiple times, such as the RSS category element and the Atom
1491 // link element. Most of the RSS1/DC elements can occur multiple
1492 // times in theory, but in practice, the only ones that do have
1493 // analogues in Atom.
1494 //
1495 // Some elements are also groups of attributes or sub-elements,
1496 // while others are simple text fields. For the most part, we don't
1497 // have to pay explicit attention to the simple text elements,
1498 // unless we want to post-process the resulting string to transform
1499 // it into some richer object like a Date or URI.
1500 //
1501 // Elements that have more sophisticated content models still end up
1502 // being dictionaries, whether they are based on attributes like RSS
1503 // cloud, sub-elements like Atom author, or even items and
1504 // entries. These elements are treated as "containers". It's
1505 // theoretically possible for a container to have an attribute with
1506 // the same universal name as a sub-element, but none of the feed
1507 // formats allow this by default, and I don't of any extension that
1508 // works this way.
1509 //
FP_startElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1510 startElement: function FP_startElement(uri, localName, qName, attributes) {
1511 this._buf = "";
1512 ++this._depth;
1513 var elementInfo;
1514
1515 //LOG("<" + localName + ">");
1516
1517 // Check for xml:base
1518 var base = attributes.getValueFromName(XMLNS, "base");
1519 if (base) {
1520 this._xmlBaseStack[this._depth] =
1521 strToURI(base, this._xmlBaseStack[this._xmlBaseStack.length - 1]);
1522 }
1523
1524 // To identify the element we're dealing with, we look up the
1525 // namespace URI in our gNamespaces dictionary, which will give us
1526 // a "canonical" prefix for a namespace URI. For example, this
1527 // allows Dublin Core "creator" elements to be consistently mapped
1528 // to "dc:creator", for easy field access by consumer code. This
1529 // strategy also happens to shorten up our state table.
1530 var key = this._prefixForNS(uri) + localName;
1531
1532 // Check to see if we need to hand this off to our XHTML handler.
1533 // The elements we're dealing with will look like this:
1534 //
1535 // <title type="xhtml">
1536 // <div xmlns="http://www.w3.org/1999/xhtml">
1537 // A title with <b>bold</b> and <i>italics</i>.
1538 // </div>
1539 // </title>
1540 //
1541 // When it returns in returnFromXHTMLHandler, the handler should
1542 // give us back a string like this:
1543 //
1544 // "A title with <b>bold</b> and <i>italics</i>."
1545 //
1546 // The Atom spec explicitly says the div is not part of the content,
1547 // and explicitly allows whitespace collapsing.
1548 //
1549 if ((this._result.version == "atom" || this._result.version == "atom03") &&
1550 this._textConstructs[key] != null) {
1551 var type = attributes.getValueFromName("","type");
1552 if (type != null && type.indexOf("xhtml") >= 0) {
1553 this._xhtmlHandler =
1554 new XHTMLHandler(this, (this._result.version == "atom"),
1555 this._waiPrefixes);
1556 this._reader.contentHandler = this._xhtmlHandler;
1557 return;
1558 }
1559 }
1560
1561 // Check our current state, and see if that state has a defined
1562 // transition. For example, this._trans["atom:entry"]["atom:author"]
1563 // will have one, and it tells us to add an item to our authors array.
1564 if (this._trans[this._state] && this._trans[this._state][key]) {
1565 elementInfo = this._trans[this._state][key];
1566 }
1567 else {
1568 // If we don't have a transition, hand off to extension handler
1569 this._extensionHandler = new ExtensionHandler(this);
1570 this._reader.contentHandler = this._extensionHandler;
1571 this._extensionHandler.startElement(uri, localName, qName, attributes);
1572 return;
1573 }
1574
1575 // This distinguishes wrappers like 'channel' from elements
1576 // we'd actually like to do something with (which will test true).
1577 this._handlerStack[this._depth] = elementInfo;
1578 if (elementInfo.isWrapper) {
1579 this._state = "IN_" + elementInfo.fieldName.toUpperCase();
1580 this._stack.push([this._feed, this._state]);
1581 }
1582 else if (elementInfo.feedVersion) {
1583 this._state = "IN_" + elementInfo.fieldName.toUpperCase();
1584
1585 // Check for the older RSS2 variants
1586 if (elementInfo.feedVersion == "rss2")
1587 elementInfo.feedVersion = this._findRSSVersion(attributes);
1588 else if (uri == RSS090NS)
1589 elementInfo.feedVersion = "rss090";
1590
1591 this._docVerified(elementInfo.feedVersion);
1592 this._stack.push([this._feed, this._state]);
1593 this._mapAttributes(this._feed, attributes);
1594 }
1595 else {
1596 this._state = this._processComplexElement(elementInfo, attributes);
1597 }
1598 },
1599
1600 // In the endElement handler, we decrement the stack and look
1601 // for cleanup/transition functions to execute. The second part
1602 // of the state transition works as above in startElement, but
1603 // the state we're looking for is prefixed with an underscore
1604 // to distinguish endElement events from startElement events.
FP_endElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1605 endElement: function FP_endElement(uri, localName, qName) {
1606 var elementInfo = this._handlerStack[this._depth];
1607 //LOG("</" + localName + ">");
1608 if (elementInfo && !elementInfo.isWrapper)
1609 this._closeComplexElement(elementInfo);
1610
1611 // cut down xml:base context
1612 if (this._xmlBaseStack.length == this._depth + 1)
1613 this._xmlBaseStack = this._xmlBaseStack.slice(0, this._depth);
1614
1615 // our new state is whatever is at the top of the stack now
1616 if (this._stack.length > 0)
1617 this._state = this._stack[this._stack.length - 1][1];
1618 this._handlerStack = this._handlerStack.slice(0, this._depth);
1619 --this._depth;
1620 },
1621
1622 // Buffer up character data. The buffer is cleared with every
1623 // opening element.
FP_characters
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1624 characters: function FP_characters(data) {
1625 this._buf += data;
1626 },
1627 // TODO: It would be nice to check new prefixes here, and if they
1628 // don't conflict with the ones we've defined, throw them in a
1629 // dictionary to check.
FP_startPrefixMapping
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1630 startPrefixMapping: function FP_startPrefixMapping(prefix, uri) {
1631 // Thanks for QNames in content, W3C
1632 // This will even be a perf hit for every single feed
1633 // http://www.w3.org/WAI/PF/GUI/
1634 if (prefix && uri == WAIROLE_NS)
1635 this._waiPrefixes[prefix] = WAIROLE_NS;
1636 },
1637
FP_endPrefixMapping
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1638 endPrefixMapping: function FP_endPrefixMapping(prefix) {
1639 if (prefix)
1640 delete this._waiPrefixes[prefix];
1641 },
1642
FP_processingInstruction
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1643 processingInstruction: function FP_processingInstruction(target, data) {
1644 if (target == "xml-stylesheet") {
1645 var hrefAttribute = data.match(/href=[\"\'](.*?)[\"\']/);
1646 if (hrefAttribute && hrefAttribute.length == 2)
1647 this._result.stylesheet = strToURI(hrefAttribute[1], this._result.uri);
1648 }
1649 },
1650
1651 // end of nsISAXContentHandler
1652
1653 // Handle our more complicated elements--those that contain
1654 // attributes and child elements.
FP__processComplexElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1655 _processComplexElement:
1656 function FP__processComplexElement(elementInfo, attributes) {
1657 var obj, key, prefix;
1658
1659 // If the container is an entry/item, it'll need to have its
1660 // more esoteric properties put in the 'fields' property bag.
1661 if (elementInfo.containerClass == Cc[ENTRY_CONTRACTID]) {
1662 obj = elementInfo.containerClass.createInstance(Ci.nsIFeedEntry);
1663 obj.baseURI = this._xmlBaseStack[this._xmlBaseStack.length - 1];
1664 this._mapAttributes(obj.fields, attributes);
1665 }
1666 else if (elementInfo.containerClass) {
1667 obj = elementInfo.containerClass.createInstance(Ci.nsIFeedElementBase);
1668 obj.baseURI = this._xmlBaseStack[this._xmlBaseStack.length - 1];
1669 obj.attributes = attributes; // just set the SAX attributes
1670 }
1671 else {
1672 obj = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
1673 this._mapAttributes(obj, attributes);
1674 }
1675
1676 // We should have a container/propertyBag that's had its
1677 // attributes processed. Now we need to attach it to its
1678 // container.
1679 var newProp;
1680
1681 // First we'll see what's on top of the stack.
1682 var container = this._stack[this._stack.length - 1][0];
1683
1684 // Check to see if it has the property
1685 var prop;
1686 try {
1687 prop = container.getProperty(elementInfo.fieldName);
1688 }
1689 catch(e) {
1690 }
1691
1692 if (elementInfo.isArray) {
1693 if (!prop) {
1694 container.setPropertyAsInterface(elementInfo.fieldName,
1695 Cc[ARRAY_CONTRACTID].
1696 createInstance(Ci.nsIMutableArray));
1697 }
1698
1699 newProp = container.getProperty(elementInfo.fieldName);
1700 // XXX This QI should not be necessary, but XPConnect seems to fly
1701 // off the handle in the browser, and loses track of the interface
1702 // on large files. Bug 335638.
1703 newProp.QueryInterface(Ci.nsIMutableArray);
1704 newProp.appendElement(obj,false);
1705
1706 // If new object is an nsIFeedContainer, we want to deal with
1707 // its member nsIPropertyBag instead.
1708 if (isIFeedContainer(obj))
1709 newProp = obj.fields;
1710
1711 }
1712 else {
1713 // If it doesn't, set it.
1714 if (!prop) {
1715 container.setPropertyAsInterface(elementInfo.fieldName,obj);
1716 }
1717 newProp = container.getProperty(elementInfo.fieldName);
1718 }
1719
1720 // make our new state name, and push the property onto the stack
1721 var newState = "IN_" + elementInfo.fieldName.toUpperCase();
1722 this._stack.push([newProp, newState, obj]);
1723 return newState;
1724 },
1725
1726 // Sometimes we need reconcile the element content with the object
1727 // model for a given feed. We use helper functions to do the
1728 // munging, but we need to identify array types here, so the munging
1729 // happens only to the last element of an array.
FP__closeComplexElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1730 _closeComplexElement: function FP__closeComplexElement(elementInfo) {
1731 var stateTuple = this._stack.pop();
1732 var container = stateTuple[0];
1733 var containerParent = stateTuple[2];
1734 var element = null;
1735 var isArray = isIArray(container);
1736
1737 // If it's an array and we have to post-process,
1738 // grab the last element
1739 if (isArray)
1740 element = container.queryElementAt(container.length - 1, Ci.nsISupports);
1741 else
1742 element = container;
1743
1744 // Run the post-processing function if there is one.
1745 if (elementInfo.closeFunc)
1746 element = elementInfo.closeFunc(this._buf, element);
1747
1748 // If an nsIFeedContainer was on top of the stack,
1749 // we need to normalize it
1750 if (elementInfo.containerClass == Cc[ENTRY_CONTRACTID])
1751 containerParent.normalize();
1752
1753 // If it's an array, re-set the last element
1754 if (isArray)
1755 container.replaceElementAt(element, container.length - 1, false);
1756 },
1757
FP_prefixForNS
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1758 _prefixForNS: function FP_prefixForNS(uri) {
1759 if (!uri)
1760 return "";
1761 var prefix = gNamespaces[uri];
1762 if (prefix)
1763 return prefix + ":";
1764 if (uri.toLowerCase().indexOf("http://backend.userland.com") == 0)
1765 return "";
1766 else
1767 return null;
1768 },
1769
FP__mapAttributes
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1770 _mapAttributes: function FP__mapAttributes(bag, attributes) {
1771 // Cycle through the attributes, and set our properties using the
1772 // prefix:localNames we find in our namespace dictionary.
1773 for (var i = 0; i < attributes.length; ++i) {
1774 var key = this._prefixForNS(attributes.getURI(i)) + attributes.getLocalName(i);
1775 var val = attributes.getValue(i);
1776 bag.setPropertyAsAString(key, val);
1777 }
1778 },
1779
1780 // Only for RSS2esque formats
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1781 _findRSSVersion: function FP__findRSSVersion(attributes) {
1782 var versionAttr = trimString(attributes.getValueFromName("", "version"));
1783 var versions = { "0.91":"rss091",
1784 "0.92":"rss092",
1785 "0.93":"rss093",
1786 "0.94":"rss094" }
1787 if (versions[versionAttr])
1788 return versions[versionAttr];
1789 if (versionAttr.substr(0,2) != "2.")
1790 return "rssUnknown";
1791 return "rss2";
1792 },
1793
1794 // unknown element values are returned here. See startElement above
1795 // for how this works.
FP_returnExt
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1796 returnFromExtHandler:
1797 function FP_returnExt(uri, localName, chars, attributes) {
1798 --this._depth;
1799
1800 // take control of the SAX events
1801 this._reader.contentHandler = this;
1802 if (localName == null && chars == null)
1803 return;
1804
1805 // we don't take random elements inside rdf:RDF
1806 if (this._state == "IN_RDF")
1807 return;
1808
1809 // Grab the top of the stack
1810 var top = this._stack[this._stack.length - 1];
1811 if (!top)
1812 return;
1813
1814 var container = top[0];
1815 // Grab the last element if it's an array
1816 if (isIArray(container)) {
1817 var contract = this._handlerStack[this._depth].containerClass;
1818 // check if it's something specific, but not an entry
1819 if (contract && contract != Cc[ENTRY_CONTRACTID]) {
1820 var el = container.queryElementAt(container.length - 1,
1821 Ci.nsIFeedElementBase);
1822 // XXX there must be a way to flatten these interfaces
1823 if (contract == Cc[PERSON_CONTRACTID])
1824 el.QueryInterface(Ci.nsIFeedPerson);
1825 else
1826 return; // don't know about this interface
1827
1828 var propName = localName;
1829 var prefix = gNamespaces[uri];
1830
1831 // synonyms
1832 if ((uri == "" ||
1833 prefix &&
1834 ((prefix.indexOf("atom") > -1) ||
1835 (prefix.indexOf("rss") > -1))) &&
1836 (propName == "url" || propName == "href"))
1837 propName = "uri";
1838
1839 try {
1840 if (el[propName] !== "undefined") {
1841 var propValue = chars;
1842 // convert URI-bearing values to an nsIURI
1843 if (propName == "uri") {
1844 var base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
1845 propValue = strToURI(chars, base);
1846 }
1847 el[propName] = propValue;
1848 }
1849 }
1850 catch(e) {
1851 // ignore XPConnect errors
1852 }
1853 // the rest of the function deals with entry- and feed-level stuff
1854 return;
1855 }
1856 else {
1857 container = container.queryElementAt(container.length - 1,
1858 Ci.nsIWritablePropertyBag2);
1859 }
1860 }
1861
1862 // Make the buffer our new property
1863 var propName = this._prefixForNS(uri) + localName;
1864
1865 // But, it could be something containing HTML. If so,
1866 // we need to know about that.
1867 if (this._textConstructs[propName] != null &&
1868 this._handlerStack[this._depth].containerClass !== null) {
1869 var newProp = Cc[TEXTCONSTRUCT_CONTRACTID].
1870 createInstance(Ci.nsIFeedTextConstruct);
1871 newProp.text = chars;
1872 // Look up the default type in our table
1873 var type = this._textConstructs[propName];
1874 var typeAttribute = attributes.getValueFromName("","type");
1875 if (this._result.version == "atom" && typeAttribute != null) {
1876 type = typeAttribute;
1877 }
1878 else if (this._result.version == "atom03" && typeAttribute != null) {
1879 if (typeAttribute.toLowerCase().indexOf("xhtml") >= 0) {
1880 type = "xhtml";
1881 }
1882 else if (typeAttribute.toLowerCase().indexOf("html") >= 0) {
1883 type = "html";
1884 }
1885 else if (typeAttribute.toLowerCase().indexOf("text") >= 0) {
1886 type = "text";
1887 }
1888 }
1889
1890 // If it's rss feed-level description, it's not supposed to have html
1891 if (this._result.version.indexOf("rss") >= 0 &&
1892 this._handlerStack[this._depth].containerClass != ENTRY_CONTRACTID) {
1893 type = "text";
1894 }
1895 newProp.type = type;
1896 newProp.base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
1897 container.setPropertyAsInterface(propName, newProp);
1898 }
1899 else {
1900 container.setPropertyAsAString(propName, chars);
1901 }
1902 },
1903
1904 // Sometimes, we'll hand off SAX handling duties to an XHTMLHandler
1905 // (see above) that will scrape out non-XHTML stuff, normalize
1906 // namespaces, and remove the wrapper div from Atom 1.0. When the
1907 // XHTMLHandler is done, it'll callback here.
FP_returnFromXHTMLHandler
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1908 returnFromXHTMLHandler:
1909 function FP_returnFromXHTMLHandler(chars, uri, localName, qName) {
1910 // retake control of the SAX content events
1911 this._reader.contentHandler = this;
1912
1913 // Grab the top of the stack
1914 var top = this._stack[this._stack.length - 1];
1915 if (!top)
1916 return;
1917 var container = top[0];
1918
1919 // Assign the property
1920 var newProp = newProp = Cc[TEXTCONSTRUCT_CONTRACTID].
1921 createInstance(Ci.nsIFeedTextConstruct);
1922 newProp.text = chars;
1923 newProp.type = "xhtml";
1924 newProp.base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
1925 container.setPropertyAsInterface(this._prefixForNS(uri) + localName,
1926 newProp);
1927
1928 // XHTML will cause us to peek too far. The XHTML handler will
1929 // send us an end element to call. RFC4287-valid feeds allow a
1930 // more graceful way to handle this. Unfortunately, we can't count
1931 // on compliance at this point.
1932 this.endElement(uri, localName, qName);
1933 },
1934
1935 // XPCOM stuff
1936 classDescription: FP_CLASSNAME,
1937 classID: FP_CLASSID,
1938 contractID: FP_CONTRACTID,
1939 QueryInterface: XPCOMUtils.generateQI(
1940 [Ci.nsIFeedProcessor, Ci.nsISAXContentHandler, Ci.nsISAXErrorHandler,
1941 Ci.nsIStreamListener, Ci.nsIRequestObserver]
1942 )
1943 }
1944
1945 var components = [FeedProcessor, FeedResult, Feed, Entry,
1946 TextConstruct, Generator, Person];
NSGetModule
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
FP_onDataAvailable
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
FP__processComplexElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
FP_returnExt
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
FP_returnFromXHTMLHandler
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1947 function NSGetModule(compMgr, fileSpec) {
1948 return XPCOMUtils.generateModule(components);
1949
1950 }