XMLDocumentCB.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. // Generated by CoffeeScript 2.4.1
  2. (function() {
  3. var NodeType, WriterState, XMLAttribute, XMLCData, XMLComment, XMLDTDAttList, XMLDTDElement, XMLDTDEntity, XMLDTDNotation, XMLDeclaration, XMLDocType, XMLDocument, XMLDocumentCB, XMLElement, XMLProcessingInstruction, XMLRaw, XMLStringWriter, XMLStringifier, XMLText, getValue, isFunction, isObject, isPlainObject,
  4. hasProp = {}.hasOwnProperty;
  5. ({isObject, isFunction, isPlainObject, getValue} = require('./Utility'));
  6. NodeType = require('./NodeType');
  7. XMLDocument = require('./XMLDocument');
  8. XMLElement = require('./XMLElement');
  9. XMLCData = require('./XMLCData');
  10. XMLComment = require('./XMLComment');
  11. XMLRaw = require('./XMLRaw');
  12. XMLText = require('./XMLText');
  13. XMLProcessingInstruction = require('./XMLProcessingInstruction');
  14. XMLDeclaration = require('./XMLDeclaration');
  15. XMLDocType = require('./XMLDocType');
  16. XMLDTDAttList = require('./XMLDTDAttList');
  17. XMLDTDEntity = require('./XMLDTDEntity');
  18. XMLDTDElement = require('./XMLDTDElement');
  19. XMLDTDNotation = require('./XMLDTDNotation');
  20. XMLAttribute = require('./XMLAttribute');
  21. XMLStringifier = require('./XMLStringifier');
  22. XMLStringWriter = require('./XMLStringWriter');
  23. WriterState = require('./WriterState');
  24. // Represents an XML builder
  25. module.exports = XMLDocumentCB = class XMLDocumentCB {
  26. // Initializes a new instance of `XMLDocumentCB`
  27. // `options.keepNullNodes` whether nodes with null values will be kept
  28. // or ignored: true or false
  29. // `options.keepNullAttributes` whether attributes with null values will be
  30. // kept or ignored: true or false
  31. // `options.ignoreDecorators` whether decorator strings will be ignored when
  32. // converting JS objects: true or false
  33. // `options.separateArrayItems` whether array items are created as separate
  34. // nodes when passed as an object value: true or false
  35. // `options.noDoubleEncoding` whether existing html entities are encoded:
  36. // true or false
  37. // `options.stringify` a set of functions to use for converting values to
  38. // strings
  39. // `options.writer` the default XML writer to use for converting nodes to
  40. // string. If the default writer is not set, the built-in XMLStringWriter
  41. // will be used instead.
  42. // `onData` the function to be called when a new chunk of XML is output. The
  43. // string containing the XML chunk is passed to `onData` as its first
  44. // argument, and the current indentation level as its second argument.
  45. // `onEnd` the function to be called when the XML document is completed with
  46. // `end`. `onEnd` does not receive any arguments.
  47. constructor(options, onData, onEnd) {
  48. var writerOptions;
  49. this.name = "?xml";
  50. this.type = NodeType.Document;
  51. options || (options = {});
  52. writerOptions = {};
  53. if (!options.writer) {
  54. options.writer = new XMLStringWriter();
  55. } else if (isPlainObject(options.writer)) {
  56. writerOptions = options.writer;
  57. options.writer = new XMLStringWriter();
  58. }
  59. this.options = options;
  60. this.writer = options.writer;
  61. this.writerOptions = this.writer.filterOptions(writerOptions);
  62. this.stringify = new XMLStringifier(options);
  63. this.onDataCallback = onData || function() {};
  64. this.onEndCallback = onEnd || function() {};
  65. this.currentNode = null;
  66. this.currentLevel = -1;
  67. this.openTags = {};
  68. this.documentStarted = false;
  69. this.documentCompleted = false;
  70. this.root = null;
  71. }
  72. // Creates a child element node from the given XMLNode
  73. // `node` the child node
  74. createChildNode(node) {
  75. var att, attName, attributes, child, i, len, ref, ref1;
  76. switch (node.type) {
  77. case NodeType.CData:
  78. this.cdata(node.value);
  79. break;
  80. case NodeType.Comment:
  81. this.comment(node.value);
  82. break;
  83. case NodeType.Element:
  84. attributes = {};
  85. ref = node.attribs;
  86. for (attName in ref) {
  87. if (!hasProp.call(ref, attName)) continue;
  88. att = ref[attName];
  89. attributes[attName] = att.value;
  90. }
  91. this.node(node.name, attributes);
  92. break;
  93. case NodeType.Dummy:
  94. this.dummy();
  95. break;
  96. case NodeType.Raw:
  97. this.raw(node.value);
  98. break;
  99. case NodeType.Text:
  100. this.text(node.value);
  101. break;
  102. case NodeType.ProcessingInstruction:
  103. this.instruction(node.target, node.value);
  104. break;
  105. default:
  106. throw new Error("This XML node type is not supported in a JS object: " + node.constructor.name);
  107. }
  108. ref1 = node.children;
  109. // write child nodes recursively
  110. for (i = 0, len = ref1.length; i < len; i++) {
  111. child = ref1[i];
  112. this.createChildNode(child);
  113. if (child.type === NodeType.Element) {
  114. this.up();
  115. }
  116. }
  117. return this;
  118. }
  119. // Creates a dummy node
  120. dummy() {
  121. // no-op, just return this
  122. return this;
  123. }
  124. // Creates a node
  125. // `name` name of the node
  126. // `attributes` an object containing name/value pairs of attributes
  127. // `text` element text
  128. node(name, attributes, text) {
  129. if (name == null) {
  130. throw new Error("Missing node name.");
  131. }
  132. if (this.root && this.currentLevel === -1) {
  133. throw new Error("Document can only have one root node. " + this.debugInfo(name));
  134. }
  135. this.openCurrent();
  136. name = getValue(name);
  137. if (attributes == null) {
  138. attributes = {};
  139. }
  140. attributes = getValue(attributes);
  141. // swap argument order: text <-> attributes
  142. if (!isObject(attributes)) {
  143. [text, attributes] = [attributes, text];
  144. }
  145. this.currentNode = new XMLElement(this, name, attributes);
  146. this.currentNode.children = false;
  147. this.currentLevel++;
  148. this.openTags[this.currentLevel] = this.currentNode;
  149. if (text != null) {
  150. this.text(text);
  151. }
  152. return this;
  153. }
  154. // Creates a child element node or an element type declaration when called
  155. // inside the DTD
  156. // `name` name of the node
  157. // `attributes` an object containing name/value pairs of attributes
  158. // `text` element text
  159. element(name, attributes, text) {
  160. var child, i, len, oldValidationFlag, ref, root;
  161. if (this.currentNode && this.currentNode.type === NodeType.DocType) {
  162. this.dtdElement(...arguments);
  163. } else {
  164. if (Array.isArray(name) || isObject(name) || isFunction(name)) {
  165. oldValidationFlag = this.options.noValidation;
  166. this.options.noValidation = true;
  167. root = new XMLDocument(this.options).element('TEMP_ROOT');
  168. root.element(name);
  169. this.options.noValidation = oldValidationFlag;
  170. ref = root.children;
  171. for (i = 0, len = ref.length; i < len; i++) {
  172. child = ref[i];
  173. this.createChildNode(child);
  174. if (child.type === NodeType.Element) {
  175. this.up();
  176. }
  177. }
  178. } else {
  179. this.node(name, attributes, text);
  180. }
  181. }
  182. return this;
  183. }
  184. // Adds or modifies an attribute
  185. // `name` attribute name
  186. // `value` attribute value
  187. attribute(name, value) {
  188. var attName, attValue;
  189. if (!this.currentNode || this.currentNode.children) {
  190. throw new Error("att() can only be used immediately after an ele() call in callback mode. " + this.debugInfo(name));
  191. }
  192. if (name != null) {
  193. name = getValue(name);
  194. }
  195. if (isObject(name)) { // expand if object
  196. for (attName in name) {
  197. if (!hasProp.call(name, attName)) continue;
  198. attValue = name[attName];
  199. this.attribute(attName, attValue);
  200. }
  201. } else {
  202. if (isFunction(value)) {
  203. value = value.apply();
  204. }
  205. if (this.options.keepNullAttributes && (value == null)) {
  206. this.currentNode.attribs[name] = new XMLAttribute(this, name, "");
  207. } else if (value != null) {
  208. this.currentNode.attribs[name] = new XMLAttribute(this, name, value);
  209. }
  210. }
  211. return this;
  212. }
  213. // Creates a text node
  214. // `value` element text
  215. text(value) {
  216. var node;
  217. this.openCurrent();
  218. node = new XMLText(this, value);
  219. this.onData(this.writer.text(node, this.writerOptions, this.currentLevel + 1), this.currentLevel + 1);
  220. return this;
  221. }
  222. // Creates a CDATA node
  223. // `value` element text without CDATA delimiters
  224. cdata(value) {
  225. var node;
  226. this.openCurrent();
  227. node = new XMLCData(this, value);
  228. this.onData(this.writer.cdata(node, this.writerOptions, this.currentLevel + 1), this.currentLevel + 1);
  229. return this;
  230. }
  231. // Creates a comment node
  232. // `value` comment text
  233. comment(value) {
  234. var node;
  235. this.openCurrent();
  236. node = new XMLComment(this, value);
  237. this.onData(this.writer.comment(node, this.writerOptions, this.currentLevel + 1), this.currentLevel + 1);
  238. return this;
  239. }
  240. // Adds unescaped raw text
  241. // `value` text
  242. raw(value) {
  243. var node;
  244. this.openCurrent();
  245. node = new XMLRaw(this, value);
  246. this.onData(this.writer.raw(node, this.writerOptions, this.currentLevel + 1), this.currentLevel + 1);
  247. return this;
  248. }
  249. // Adds a processing instruction
  250. // `target` instruction target
  251. // `value` instruction value
  252. instruction(target, value) {
  253. var i, insTarget, insValue, len, node;
  254. this.openCurrent();
  255. if (target != null) {
  256. target = getValue(target);
  257. }
  258. if (value != null) {
  259. value = getValue(value);
  260. }
  261. if (Array.isArray(target)) { // expand if array
  262. for (i = 0, len = target.length; i < len; i++) {
  263. insTarget = target[i];
  264. this.instruction(insTarget);
  265. }
  266. } else if (isObject(target)) { // expand if object
  267. for (insTarget in target) {
  268. if (!hasProp.call(target, insTarget)) continue;
  269. insValue = target[insTarget];
  270. this.instruction(insTarget, insValue);
  271. }
  272. } else {
  273. if (isFunction(value)) {
  274. value = value.apply();
  275. }
  276. node = new XMLProcessingInstruction(this, target, value);
  277. this.onData(this.writer.processingInstruction(node, this.writerOptions, this.currentLevel + 1), this.currentLevel + 1);
  278. }
  279. return this;
  280. }
  281. // Creates the xml declaration
  282. // `version` A version number string, e.g. 1.0
  283. // `encoding` Encoding declaration, e.g. UTF-8
  284. // `standalone` standalone document declaration: true or false
  285. declaration(version, encoding, standalone) {
  286. var node;
  287. this.openCurrent();
  288. if (this.documentStarted) {
  289. throw new Error("declaration() must be the first node.");
  290. }
  291. node = new XMLDeclaration(this, version, encoding, standalone);
  292. this.onData(this.writer.declaration(node, this.writerOptions, this.currentLevel + 1), this.currentLevel + 1);
  293. return this;
  294. }
  295. // Creates the document type declaration
  296. // `root` the name of the root node
  297. // `pubID` the public identifier of the external subset
  298. // `sysID` the system identifier of the external subset
  299. doctype(root, pubID, sysID) {
  300. this.openCurrent();
  301. if (root == null) {
  302. throw new Error("Missing root node name.");
  303. }
  304. if (this.root) {
  305. throw new Error("dtd() must come before the root node.");
  306. }
  307. this.currentNode = new XMLDocType(this, pubID, sysID);
  308. this.currentNode.rootNodeName = root;
  309. this.currentNode.children = false;
  310. this.currentLevel++;
  311. this.openTags[this.currentLevel] = this.currentNode;
  312. return this;
  313. }
  314. // Creates an element type declaration
  315. // `name` element name
  316. // `value` element content (defaults to #PCDATA)
  317. dtdElement(name, value) {
  318. var node;
  319. this.openCurrent();
  320. node = new XMLDTDElement(this, name, value);
  321. this.onData(this.writer.dtdElement(node, this.writerOptions, this.currentLevel + 1), this.currentLevel + 1);
  322. return this;
  323. }
  324. // Creates an attribute declaration
  325. // `elementName` the name of the element containing this attribute
  326. // `attributeName` attribute name
  327. // `attributeType` type of the attribute (defaults to CDATA)
  328. // `defaultValueType` default value type (either #REQUIRED, #IMPLIED, #FIXED or
  329. // #DEFAULT) (defaults to #IMPLIED)
  330. // `defaultValue` default value of the attribute
  331. // (only used for #FIXED or #DEFAULT)
  332. attList(elementName, attributeName, attributeType, defaultValueType, defaultValue) {
  333. var node;
  334. this.openCurrent();
  335. node = new XMLDTDAttList(this, elementName, attributeName, attributeType, defaultValueType, defaultValue);
  336. this.onData(this.writer.dtdAttList(node, this.writerOptions, this.currentLevel + 1), this.currentLevel + 1);
  337. return this;
  338. }
  339. // Creates a general entity declaration
  340. // `name` the name of the entity
  341. // `value` internal entity value or an object with external entity details
  342. // `value.pubID` public identifier
  343. // `value.sysID` system identifier
  344. // `value.nData` notation declaration
  345. entity(name, value) {
  346. var node;
  347. this.openCurrent();
  348. node = new XMLDTDEntity(this, false, name, value);
  349. this.onData(this.writer.dtdEntity(node, this.writerOptions, this.currentLevel + 1), this.currentLevel + 1);
  350. return this;
  351. }
  352. // Creates a parameter entity declaration
  353. // `name` the name of the entity
  354. // `value` internal entity value or an object with external entity details
  355. // `value.pubID` public identifier
  356. // `value.sysID` system identifier
  357. pEntity(name, value) {
  358. var node;
  359. this.openCurrent();
  360. node = new XMLDTDEntity(this, true, name, value);
  361. this.onData(this.writer.dtdEntity(node, this.writerOptions, this.currentLevel + 1), this.currentLevel + 1);
  362. return this;
  363. }
  364. // Creates a NOTATION declaration
  365. // `name` the name of the notation
  366. // `value` an object with external entity details
  367. // `value.pubID` public identifier
  368. // `value.sysID` system identifier
  369. notation(name, value) {
  370. var node;
  371. this.openCurrent();
  372. node = new XMLDTDNotation(this, name, value);
  373. this.onData(this.writer.dtdNotation(node, this.writerOptions, this.currentLevel + 1), this.currentLevel + 1);
  374. return this;
  375. }
  376. // Gets the parent node
  377. up() {
  378. if (this.currentLevel < 0) {
  379. throw new Error("The document node has no parent.");
  380. }
  381. if (this.currentNode) {
  382. if (this.currentNode.children) {
  383. this.closeNode(this.currentNode);
  384. } else {
  385. this.openNode(this.currentNode);
  386. }
  387. this.currentNode = null;
  388. } else {
  389. this.closeNode(this.openTags[this.currentLevel]);
  390. }
  391. delete this.openTags[this.currentLevel];
  392. this.currentLevel--;
  393. return this;
  394. }
  395. // Ends the document
  396. end() {
  397. while (this.currentLevel >= 0) {
  398. this.up();
  399. }
  400. return this.onEnd();
  401. }
  402. // Opens the current parent node
  403. openCurrent() {
  404. if (this.currentNode) {
  405. this.currentNode.children = true;
  406. return this.openNode(this.currentNode);
  407. }
  408. }
  409. // Writes the opening tag of the current node or the entire node if it has
  410. // no child nodes
  411. openNode(node) {
  412. var att, chunk, name, ref;
  413. if (!node.isOpen) {
  414. if (!this.root && this.currentLevel === 0 && node.type === NodeType.Element) {
  415. this.root = node;
  416. }
  417. chunk = '';
  418. if (node.type === NodeType.Element) {
  419. this.writerOptions.state = WriterState.OpenTag;
  420. chunk = this.writer.indent(node, this.writerOptions, this.currentLevel) + '<' + node.name;
  421. ref = node.attribs;
  422. for (name in ref) {
  423. if (!hasProp.call(ref, name)) continue;
  424. att = ref[name];
  425. chunk += this.writer.attribute(att, this.writerOptions, this.currentLevel);
  426. }
  427. chunk += (node.children ? '>' : '/>') + this.writer.endline(node, this.writerOptions, this.currentLevel);
  428. this.writerOptions.state = WriterState.InsideTag; // if node.type is NodeType.DocType
  429. } else {
  430. this.writerOptions.state = WriterState.OpenTag;
  431. chunk = this.writer.indent(node, this.writerOptions, this.currentLevel) + '<!DOCTYPE ' + node.rootNodeName;
  432. // external identifier
  433. if (node.pubID && node.sysID) {
  434. chunk += ' PUBLIC "' + node.pubID + '" "' + node.sysID + '"';
  435. } else if (node.sysID) {
  436. chunk += ' SYSTEM "' + node.sysID + '"';
  437. }
  438. // internal subset
  439. if (node.children) {
  440. chunk += ' [';
  441. this.writerOptions.state = WriterState.InsideTag;
  442. } else {
  443. this.writerOptions.state = WriterState.CloseTag;
  444. chunk += '>';
  445. }
  446. chunk += this.writer.endline(node, this.writerOptions, this.currentLevel);
  447. }
  448. this.onData(chunk, this.currentLevel);
  449. return node.isOpen = true;
  450. }
  451. }
  452. // Writes the closing tag of the current node
  453. closeNode(node) {
  454. var chunk;
  455. if (!node.isClosed) {
  456. chunk = '';
  457. this.writerOptions.state = WriterState.CloseTag;
  458. if (node.type === NodeType.Element) {
  459. chunk = this.writer.indent(node, this.writerOptions, this.currentLevel) + '</' + node.name + '>' + this.writer.endline(node, this.writerOptions, this.currentLevel); // if node.type is NodeType.DocType
  460. } else {
  461. chunk = this.writer.indent(node, this.writerOptions, this.currentLevel) + ']>' + this.writer.endline(node, this.writerOptions, this.currentLevel);
  462. }
  463. this.writerOptions.state = WriterState.None;
  464. this.onData(chunk, this.currentLevel);
  465. return node.isClosed = true;
  466. }
  467. }
  468. // Called when a new chunk of XML is output
  469. // `chunk` a string containing the XML chunk
  470. // `level` current indentation level
  471. onData(chunk, level) {
  472. this.documentStarted = true;
  473. return this.onDataCallback(chunk, level + 1);
  474. }
  475. // Called when the XML document is completed
  476. onEnd() {
  477. this.documentCompleted = true;
  478. return this.onEndCallback();
  479. }
  480. // Returns debug string
  481. debugInfo(name) {
  482. if (name == null) {
  483. return "";
  484. } else {
  485. return "node: <" + name + ">";
  486. }
  487. }
  488. // Node aliases
  489. ele() {
  490. return this.element(...arguments);
  491. }
  492. nod(name, attributes, text) {
  493. return this.node(name, attributes, text);
  494. }
  495. txt(value) {
  496. return this.text(value);
  497. }
  498. dat(value) {
  499. return this.cdata(value);
  500. }
  501. com(value) {
  502. return this.comment(value);
  503. }
  504. ins(target, value) {
  505. return this.instruction(target, value);
  506. }
  507. dec(version, encoding, standalone) {
  508. return this.declaration(version, encoding, standalone);
  509. }
  510. dtd(root, pubID, sysID) {
  511. return this.doctype(root, pubID, sysID);
  512. }
  513. e(name, attributes, text) {
  514. return this.element(name, attributes, text);
  515. }
  516. n(name, attributes, text) {
  517. return this.node(name, attributes, text);
  518. }
  519. t(value) {
  520. return this.text(value);
  521. }
  522. d(value) {
  523. return this.cdata(value);
  524. }
  525. c(value) {
  526. return this.comment(value);
  527. }
  528. r(value) {
  529. return this.raw(value);
  530. }
  531. i(target, value) {
  532. return this.instruction(target, value);
  533. }
  534. // Attribute aliases
  535. att() {
  536. if (this.currentNode && this.currentNode.type === NodeType.DocType) {
  537. return this.attList(...arguments);
  538. } else {
  539. return this.attribute(...arguments);
  540. }
  541. }
  542. a() {
  543. if (this.currentNode && this.currentNode.type === NodeType.DocType) {
  544. return this.attList(...arguments);
  545. } else {
  546. return this.attribute(...arguments);
  547. }
  548. }
  549. // DTD aliases
  550. // att() and ele() are defined above
  551. ent(name, value) {
  552. return this.entity(name, value);
  553. }
  554. pent(name, value) {
  555. return this.pEntity(name, value);
  556. }
  557. not(name, value) {
  558. return this.notation(name, value);
  559. }
  560. };
  561. }).call(this);