FeedProcessor.js 59 KB

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