Atom.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the 2-clause BSD license.
  3. * See license.txt in the OpenLayers distribution or repository for the
  4. * full text of the license. */
  5. /**
  6. * @requires OpenLayers/Format/XML.js
  7. * @requires OpenLayers/Format/GML/v3.js
  8. * @requires OpenLayers/Feature/Vector.js
  9. */
  10. /**
  11. * Class: OpenLayers.Format.Atom
  12. * Read/write Atom feeds. Create a new instance with the
  13. * <OpenLayers.Format.AtomFeed> constructor.
  14. *
  15. * Inherits from:
  16. * - <OpenLayers.Format.XML>
  17. */
  18. OpenLayers.Format.Atom = OpenLayers.Class(OpenLayers.Format.XML, {
  19. /**
  20. * Property: namespaces
  21. * {Object} Mapping of namespace aliases to namespace URIs. Properties
  22. * of this object should not be set individually. Read-only. All
  23. * XML subclasses should have their own namespaces object. Use
  24. * <setNamespace> to add or set a namespace alias after construction.
  25. */
  26. namespaces: {
  27. atom: "http://www.w3.org/2005/Atom",
  28. georss: "http://www.georss.org/georss"
  29. },
  30. /**
  31. * APIProperty: feedTitle
  32. * {String} Atom feed elements require a title. Default is "untitled".
  33. */
  34. feedTitle: "untitled",
  35. /**
  36. * APIProperty: defaultEntryTitle
  37. * {String} Atom entry elements require a title. In cases where one is
  38. * not provided in the feature attributes, this will be used. Default
  39. * is "untitled".
  40. */
  41. defaultEntryTitle: "untitled",
  42. /**
  43. * Property: gmlParse
  44. * {Object} GML Format object for parsing features
  45. * Non-API and only created if necessary
  46. */
  47. gmlParser: null,
  48. /**
  49. * APIProperty: xy
  50. * {Boolean} Order of the GML coordinate: true:(x,y) or false:(y,x)
  51. * For GeoRSS the default is (y,x), therefore: false
  52. */
  53. xy: false,
  54. /**
  55. * Constructor: OpenLayers.Format.AtomEntry
  56. * Create a new parser for Atom.
  57. *
  58. * Parameters:
  59. * options - {Object} An optional object whose properties will be set on
  60. * this instance.
  61. */
  62. /**
  63. * APIMethod: read
  64. * Return a list of features from an Atom feed or entry document.
  65. * Parameters:
  66. * doc - {Element} or {String}
  67. *
  68. * Returns:
  69. * Array({<OpenLayers.Feature.Vector>})
  70. */
  71. read: function(doc) {
  72. if (typeof doc == "string") {
  73. doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
  74. }
  75. return this.parseFeatures(doc);
  76. },
  77. /**
  78. * APIMethod: write
  79. * Serialize or more feature nodes to Atom documents.
  80. *
  81. * Parameters:
  82. * features - {<OpenLayers.Feature.Vector>} or Array({<OpenLayers.Feature.Vector>})
  83. *
  84. * Returns:
  85. * {String} an Atom entry document if passed one feature node, or a feed
  86. * document if passed an array of feature nodes.
  87. */
  88. write: function(features) {
  89. var doc;
  90. if (OpenLayers.Util.isArray(features)) {
  91. doc = this.createElementNSPlus("atom:feed");
  92. doc.appendChild(
  93. this.createElementNSPlus("atom:title", {
  94. value: this.feedTitle
  95. })
  96. );
  97. for (var i=0, ii=features.length; i<ii; i++) {
  98. doc.appendChild(this.buildEntryNode(features[i]));
  99. }
  100. }
  101. else {
  102. doc = this.buildEntryNode(features);
  103. }
  104. return OpenLayers.Format.XML.prototype.write.apply(this, [doc]);
  105. },
  106. /**
  107. * Method: buildContentNode
  108. *
  109. * Parameters:
  110. * content - {Object}
  111. *
  112. * Returns:
  113. * {DOMElement} an Atom content node.
  114. *
  115. * TODO: types other than text.
  116. */
  117. buildContentNode: function(content) {
  118. var node = this.createElementNSPlus("atom:content", {
  119. attributes: {
  120. type: content.type || null
  121. }
  122. });
  123. if (content.src) {
  124. node.setAttribute("src", content.src);
  125. } else {
  126. if (content.type == "text" || content.type == null) {
  127. node.appendChild(
  128. this.createTextNode(content.value)
  129. );
  130. } else if (content.type == "html") {
  131. if (typeof content.value != "string") {
  132. throw "HTML content must be in form of an escaped string";
  133. }
  134. node.appendChild(
  135. this.createTextNode(content.value)
  136. );
  137. } else if (content.type == "xhtml") {
  138. node.appendChild(content.value);
  139. } else if (content.type == "xhtml" ||
  140. content.type.match(/(\+|\/)xml$/)) {
  141. node.appendChild(content.value);
  142. }
  143. else { // MUST be a valid Base64 encoding
  144. node.appendChild(
  145. this.createTextNode(content.value)
  146. );
  147. }
  148. }
  149. return node;
  150. },
  151. /**
  152. * Method: buildEntryNode
  153. * Build an Atom entry node from a feature object.
  154. *
  155. * Parameters:
  156. * feature - {<OpenLayers.Feature.Vector>}
  157. *
  158. * Returns:
  159. * {DOMElement} an Atom entry node.
  160. *
  161. * These entries are geared for publication using AtomPub.
  162. *
  163. * TODO: support extension elements
  164. */
  165. buildEntryNode: function(feature) {
  166. var attrib = feature.attributes;
  167. var atomAttrib = attrib.atom || {};
  168. var entryNode = this.createElementNSPlus("atom:entry");
  169. // atom:author
  170. if (atomAttrib.authors) {
  171. var authors = OpenLayers.Util.isArray(atomAttrib.authors) ?
  172. atomAttrib.authors : [atomAttrib.authors];
  173. for (var i=0, ii=authors.length; i<ii; i++) {
  174. entryNode.appendChild(
  175. this.buildPersonConstructNode(
  176. "author", authors[i]
  177. )
  178. );
  179. }
  180. }
  181. // atom:category
  182. if (atomAttrib.categories) {
  183. var categories = OpenLayers.Util.isArray(atomAttrib.categories) ?
  184. atomAttrib.categories : [atomAttrib.categories];
  185. var category;
  186. for (var i=0, ii=categories.length; i<ii; i++) {
  187. category = categories[i];
  188. entryNode.appendChild(
  189. this.createElementNSPlus("atom:category", {
  190. attributes: {
  191. term: category.term,
  192. scheme: category.scheme || null,
  193. label: category.label || null
  194. }
  195. })
  196. );
  197. }
  198. }
  199. // atom:content
  200. if (atomAttrib.content) {
  201. entryNode.appendChild(this.buildContentNode(atomAttrib.content));
  202. }
  203. // atom:contributor
  204. if (atomAttrib.contributors) {
  205. var contributors = OpenLayers.Util.isArray(atomAttrib.contributors) ?
  206. atomAttrib.contributors : [atomAttrib.contributors];
  207. for (var i=0, ii=contributors.length; i<ii; i++) {
  208. entryNode.appendChild(
  209. this.buildPersonConstructNode(
  210. "contributor",
  211. contributors[i]
  212. )
  213. );
  214. }
  215. }
  216. // atom:id
  217. if (feature.fid) {
  218. entryNode.appendChild(
  219. this.createElementNSPlus("atom:id", {
  220. value: feature.fid
  221. })
  222. );
  223. }
  224. // atom:link
  225. if (atomAttrib.links) {
  226. var links = OpenLayers.Util.isArray(atomAttrib.links) ?
  227. atomAttrib.links : [atomAttrib.links];
  228. var link;
  229. for (var i=0, ii=links.length; i<ii; i++) {
  230. link = links[i];
  231. entryNode.appendChild(
  232. this.createElementNSPlus("atom:link", {
  233. attributes: {
  234. href: link.href,
  235. rel: link.rel || null,
  236. type: link.type || null,
  237. hreflang: link.hreflang || null,
  238. title: link.title || null,
  239. length: link.length || null
  240. }
  241. })
  242. );
  243. }
  244. }
  245. // atom:published
  246. if (atomAttrib.published) {
  247. entryNode.appendChild(
  248. this.createElementNSPlus("atom:published", {
  249. value: atomAttrib.published
  250. })
  251. );
  252. }
  253. // atom:rights
  254. if (atomAttrib.rights) {
  255. entryNode.appendChild(
  256. this.createElementNSPlus("atom:rights", {
  257. value: atomAttrib.rights
  258. })
  259. );
  260. }
  261. // atom:source not implemented
  262. // atom:summary
  263. if (atomAttrib.summary || attrib.description) {
  264. entryNode.appendChild(
  265. this.createElementNSPlus("atom:summary", {
  266. value: atomAttrib.summary || attrib.description
  267. })
  268. );
  269. }
  270. // atom:title
  271. entryNode.appendChild(
  272. this.createElementNSPlus("atom:title", {
  273. value: atomAttrib.title || attrib.title || this.defaultEntryTitle
  274. })
  275. );
  276. // atom:updated
  277. if (atomAttrib.updated) {
  278. entryNode.appendChild(
  279. this.createElementNSPlus("atom:updated", {
  280. value: atomAttrib.updated
  281. })
  282. );
  283. }
  284. // georss:where
  285. if (feature.geometry) {
  286. var whereNode = this.createElementNSPlus("georss:where");
  287. whereNode.appendChild(
  288. this.buildGeometryNode(feature.geometry)
  289. );
  290. entryNode.appendChild(whereNode);
  291. }
  292. return entryNode;
  293. },
  294. /**
  295. * Method: initGmlParser
  296. * Creates a GML parser.
  297. */
  298. initGmlParser: function() {
  299. this.gmlParser = new OpenLayers.Format.GML.v3({
  300. xy: this.xy,
  301. featureNS: "http://example.com#feature",
  302. internalProjection: this.internalProjection,
  303. externalProjection: this.externalProjection
  304. });
  305. },
  306. /**
  307. * Method: buildGeometryNode
  308. * builds a GeoRSS node with a given geometry
  309. *
  310. * Parameters:
  311. * geometry - {<OpenLayers.Geometry>}
  312. *
  313. * Returns:
  314. * {DOMElement} A gml node.
  315. */
  316. buildGeometryNode: function(geometry) {
  317. if (!this.gmlParser) {
  318. this.initGmlParser();
  319. }
  320. var node = this.gmlParser.writeNode("feature:_geometry", geometry);
  321. return node.firstChild;
  322. },
  323. /**
  324. * Method: buildPersonConstructNode
  325. *
  326. * Parameters:
  327. * name - {String}
  328. * value - {Object}
  329. *
  330. * Returns:
  331. * {DOMElement} an Atom person construct node.
  332. *
  333. * Example:
  334. * >>> buildPersonConstructNode("author", {name: "John Smith"})
  335. * {<author><name>John Smith</name></author>}
  336. *
  337. * TODO: how to specify extension elements? Add to the oNames array?
  338. */
  339. buildPersonConstructNode: function(name, value) {
  340. var oNames = ["uri", "email"];
  341. var personNode = this.createElementNSPlus("atom:" + name);
  342. personNode.appendChild(
  343. this.createElementNSPlus("atom:name", {
  344. value: value.name
  345. })
  346. );
  347. for (var i=0, ii=oNames.length; i<ii; i++) {
  348. if (value[oNames[i]]) {
  349. personNode.appendChild(
  350. this.createElementNSPlus("atom:" + oNames[i], {
  351. value: value[oNames[i]]
  352. })
  353. );
  354. }
  355. }
  356. return personNode;
  357. },
  358. /**
  359. * Method: getFirstChildValue
  360. *
  361. * Parameters:
  362. * node - {DOMElement}
  363. * nsuri - {String} Child node namespace uri ("*" for any).
  364. * name - {String} Child node name.
  365. * def - {String} Optional string default to return if no child found.
  366. *
  367. * Returns:
  368. * {String} The value of the first child with the given tag name. Returns
  369. * default value or empty string if none found.
  370. */
  371. getFirstChildValue: function(node, nsuri, name, def) {
  372. var value;
  373. var nodes = this.getElementsByTagNameNS(node, nsuri, name);
  374. if (nodes && nodes.length > 0) {
  375. value = this.getChildValue(nodes[0], def);
  376. } else {
  377. value = def;
  378. }
  379. return value;
  380. },
  381. /**
  382. * Method: parseFeature
  383. * Parse feature from an Atom entry node..
  384. *
  385. * Parameters:
  386. * node - {DOMElement} An Atom entry or feed node.
  387. *
  388. * Returns:
  389. * {<OpenLayers.Feature.Vector>}
  390. */
  391. parseFeature: function(node) {
  392. var atomAttrib = {};
  393. var value = null;
  394. var nodes = null;
  395. var attval = null;
  396. var atomns = this.namespaces.atom;
  397. // atomAuthor*
  398. this.parsePersonConstructs(node, "author", atomAttrib);
  399. // atomCategory*
  400. nodes = this.getElementsByTagNameNS(node, atomns, "category");
  401. if (nodes.length > 0) {
  402. atomAttrib.categories = [];
  403. }
  404. for (var i=0, ii=nodes.length; i<ii; i++) {
  405. value = {};
  406. value.term = nodes[i].getAttribute("term");
  407. attval = nodes[i].getAttribute("scheme");
  408. if (attval) { value.scheme = attval; }
  409. attval = nodes[i].getAttribute("label");
  410. if (attval) { value.label = attval; }
  411. atomAttrib.categories.push(value);
  412. }
  413. // atomContent?
  414. nodes = this.getElementsByTagNameNS(node, atomns, "content");
  415. if (nodes.length > 0) {
  416. value = {};
  417. attval = nodes[0].getAttribute("type");
  418. if (attval) {
  419. value.type = attval;
  420. }
  421. attval = nodes[0].getAttribute("src");
  422. if (attval) {
  423. value.src = attval;
  424. } else {
  425. if (value.type == "text" ||
  426. value.type == "html" ||
  427. value.type == null ) {
  428. value.value = this.getFirstChildValue(
  429. node,
  430. atomns,
  431. "content",
  432. null
  433. );
  434. } else if (value.type == "xhtml" ||
  435. value.type.match(/(\+|\/)xml$/)) {
  436. value.value = this.getChildEl(nodes[0]);
  437. } else { // MUST be base64 encoded
  438. value.value = this.getFirstChildValue(
  439. node,
  440. atomns,
  441. "content",
  442. null
  443. );
  444. }
  445. atomAttrib.content = value;
  446. }
  447. }
  448. // atomContributor*
  449. this.parsePersonConstructs(node, "contributor", atomAttrib);
  450. // atomId
  451. atomAttrib.id = this.getFirstChildValue(node, atomns, "id", null);
  452. // atomLink*
  453. nodes = this.getElementsByTagNameNS(node, atomns, "link");
  454. if (nodes.length > 0) {
  455. atomAttrib.links = new Array(nodes.length);
  456. }
  457. var oAtts = ["rel", "type", "hreflang", "title", "length"];
  458. for (var i=0, ii=nodes.length; i<ii; i++) {
  459. value = {};
  460. value.href = nodes[i].getAttribute("href");
  461. for (var j=0, jj=oAtts.length; j<jj; j++) {
  462. attval = nodes[i].getAttribute(oAtts[j]);
  463. if (attval) {
  464. value[oAtts[j]] = attval;
  465. }
  466. }
  467. atomAttrib.links[i] = value;
  468. }
  469. // atomPublished?
  470. value = this.getFirstChildValue(node, atomns, "published", null);
  471. if (value) {
  472. atomAttrib.published = value;
  473. }
  474. // atomRights?
  475. value = this.getFirstChildValue(node, atomns, "rights", null);
  476. if (value) {
  477. atomAttrib.rights = value;
  478. }
  479. // atomSource? -- not implemented
  480. // atomSummary?
  481. value = this.getFirstChildValue(node, atomns, "summary", null);
  482. if (value) {
  483. atomAttrib.summary = value;
  484. }
  485. // atomTitle
  486. atomAttrib.title = this.getFirstChildValue(
  487. node, atomns, "title", null
  488. );
  489. // atomUpdated
  490. atomAttrib.updated = this.getFirstChildValue(
  491. node, atomns, "updated", null
  492. );
  493. var featureAttrib = {
  494. title: atomAttrib.title,
  495. description: atomAttrib.summary,
  496. atom: atomAttrib
  497. };
  498. var geometry = this.parseLocations(node)[0];
  499. var feature = new OpenLayers.Feature.Vector(geometry, featureAttrib);
  500. feature.fid = atomAttrib.id;
  501. return feature;
  502. },
  503. /**
  504. * Method: parseFeatures
  505. * Return features from an Atom entry or feed.
  506. *
  507. * Parameters:
  508. * node - {DOMElement} An Atom entry or feed node.
  509. *
  510. * Returns:
  511. * Array({<OpenLayers.Feature.Vector>})
  512. */
  513. parseFeatures: function(node) {
  514. var features = [];
  515. var entries = this.getElementsByTagNameNS(
  516. node, this.namespaces.atom, "entry"
  517. );
  518. if (entries.length == 0) {
  519. entries = [node];
  520. }
  521. for (var i=0, ii=entries.length; i<ii; i++) {
  522. features.push(this.parseFeature(entries[i]));
  523. }
  524. return features;
  525. },
  526. /**
  527. * Method: parseLocations
  528. * Parse the locations from an Atom entry or feed.
  529. *
  530. * Parameters:
  531. * node - {DOMElement} An Atom entry or feed node.
  532. *
  533. * Returns:
  534. * Array({<OpenLayers.Geometry>})
  535. */
  536. parseLocations: function(node) {
  537. var georssns = this.namespaces.georss;
  538. var locations = {components: []};
  539. var where = this.getElementsByTagNameNS(node, georssns, "where");
  540. if (where && where.length > 0) {
  541. if (!this.gmlParser) {
  542. this.initGmlParser();
  543. }
  544. for (var i=0, ii=where.length; i<ii; i++) {
  545. this.gmlParser.readChildNodes(where[i], locations);
  546. }
  547. }
  548. var components = locations.components;
  549. var point = this.getElementsByTagNameNS(node, georssns, "point");
  550. if (point && point.length > 0) {
  551. for (var i=0, ii=point.length; i<ii; i++) {
  552. var xy = OpenLayers.String.trim(
  553. point[i].firstChild.nodeValue
  554. ).split(/\s+/);
  555. if (xy.length !=2) {
  556. xy = OpenLayers.String.trim(
  557. point[i].firstChild.nodeValue
  558. ).split(/\s*,\s*/);
  559. }
  560. components.push(new OpenLayers.Geometry.Point(xy[1], xy[0]));
  561. }
  562. }
  563. var line = this.getElementsByTagNameNS(node, georssns, "line");
  564. if (line && line.length > 0) {
  565. var coords;
  566. var p;
  567. var points;
  568. for (var i=0, ii=line.length; i<ii; i++) {
  569. coords = OpenLayers.String.trim(
  570. line[i].firstChild.nodeValue
  571. ).split(/\s+/);
  572. points = [];
  573. for (var j=0, jj=coords.length; j<jj; j+=2) {
  574. p = new OpenLayers.Geometry.Point(coords[j+1], coords[j]);
  575. points.push(p);
  576. }
  577. components.push(
  578. new OpenLayers.Geometry.LineString(points)
  579. );
  580. }
  581. }
  582. var polygon = this.getElementsByTagNameNS(node, georssns, "polygon");
  583. if (polygon && polygon.length > 0) {
  584. var coords;
  585. var p;
  586. var points;
  587. for (var i=0, ii=polygon.length; i<ii; i++) {
  588. coords = OpenLayers.String.trim(
  589. polygon[i].firstChild.nodeValue
  590. ).split(/\s+/);
  591. points = [];
  592. for (var j=0, jj=coords.length; j<jj; j+=2) {
  593. p = new OpenLayers.Geometry.Point(coords[j+1], coords[j]);
  594. points.push(p);
  595. }
  596. components.push(
  597. new OpenLayers.Geometry.Polygon(
  598. [new OpenLayers.Geometry.LinearRing(points)]
  599. )
  600. );
  601. }
  602. }
  603. if (this.internalProjection && this.externalProjection) {
  604. for (var i=0, ii=components.length; i<ii; i++) {
  605. if (components[i]) {
  606. components[i].transform(
  607. this.externalProjection,
  608. this.internalProjection
  609. );
  610. }
  611. }
  612. }
  613. return components;
  614. },
  615. /**
  616. * Method: parsePersonConstruct
  617. * Parse Atom person constructs from an Atom entry node.
  618. *
  619. * Parameters:
  620. * node - {DOMElement} An Atom entry or feed node.
  621. * name - {String} Construcy name ("author" or "contributor")
  622. * data = {Object} Object in which to put parsed persons.
  623. *
  624. * Returns:
  625. * An {Object}.
  626. */
  627. parsePersonConstructs: function(node, name, data) {
  628. var persons = [];
  629. var atomns = this.namespaces.atom;
  630. var nodes = this.getElementsByTagNameNS(node, atomns, name);
  631. var oAtts = ["uri", "email"];
  632. for (var i=0, ii=nodes.length; i<ii; i++) {
  633. var value = {};
  634. value.name = this.getFirstChildValue(
  635. nodes[i],
  636. atomns,
  637. "name",
  638. null
  639. );
  640. for (var j=0, jj=oAtts.length; j<jj; j++) {
  641. var attval = this.getFirstChildValue(
  642. nodes[i],
  643. atomns,
  644. oAtts[j],
  645. null);
  646. if (attval) {
  647. value[oAtts[j]] = attval;
  648. }
  649. }
  650. persons.push(value);
  651. }
  652. if (persons.length > 0) {
  653. data[name + "s"] = persons;
  654. }
  655. },
  656. CLASS_NAME: "OpenLayers.Format.Atom"
  657. });