xml-stream.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. const _ = require('./under-dash');
  2. const utils = require('./utils');
  3. // constants
  4. const OPEN_ANGLE = '<';
  5. const CLOSE_ANGLE = '>';
  6. const OPEN_ANGLE_SLASH = '</';
  7. const CLOSE_SLASH_ANGLE = '/>';
  8. function pushAttribute(xml, name, value) {
  9. xml.push(` ${name}="${utils.xmlEncode(value.toString())}"`);
  10. }
  11. function pushAttributes(xml, attributes) {
  12. if (attributes) {
  13. const tmp = [];
  14. _.each(attributes, (value, name) => {
  15. if (value !== undefined) {
  16. pushAttribute(tmp, name, value);
  17. }
  18. });
  19. xml.push(tmp.join(""));
  20. }
  21. }
  22. class XmlStream {
  23. constructor() {
  24. this._xml = [];
  25. this._stack = [];
  26. this._rollbacks = [];
  27. }
  28. get tos() {
  29. return this._stack.length ? this._stack[this._stack.length - 1] : undefined;
  30. }
  31. get cursor() {
  32. // handy way to track whether anything has been added
  33. return this._xml.length;
  34. }
  35. openXml(docAttributes) {
  36. const xml = this._xml;
  37. // <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  38. xml.push('<?xml');
  39. pushAttributes(xml, docAttributes);
  40. xml.push('?>\n');
  41. }
  42. openNode(name, attributes) {
  43. const parent = this.tos;
  44. const xml = this._xml;
  45. if (parent && this.open) {
  46. xml.push(CLOSE_ANGLE);
  47. }
  48. this._stack.push(name);
  49. // start streaming node
  50. xml.push(OPEN_ANGLE);
  51. xml.push(name);
  52. pushAttributes(xml, attributes);
  53. this.leaf = true;
  54. this.open = true;
  55. }
  56. addAttribute(name, value) {
  57. if (!this.open) {
  58. throw new Error('Cannot write attributes to node if it is not open');
  59. }
  60. if (value !== undefined) {
  61. pushAttribute(this._xml, name, value);
  62. }
  63. }
  64. addAttributes(attrs) {
  65. if (!this.open) {
  66. throw new Error('Cannot write attributes to node if it is not open');
  67. }
  68. pushAttributes(this._xml, attrs);
  69. }
  70. writeText(text) {
  71. const xml = this._xml;
  72. if (this.open) {
  73. xml.push(CLOSE_ANGLE);
  74. this.open = false;
  75. }
  76. this.leaf = false;
  77. xml.push(utils.xmlEncode(text.toString()));
  78. }
  79. writeXml(xml) {
  80. if (this.open) {
  81. this._xml.push(CLOSE_ANGLE);
  82. this.open = false;
  83. }
  84. this.leaf = false;
  85. this._xml.push(xml);
  86. }
  87. closeNode() {
  88. const node = this._stack.pop();
  89. const xml = this._xml;
  90. if (this.leaf) {
  91. xml.push(CLOSE_SLASH_ANGLE);
  92. } else {
  93. xml.push(OPEN_ANGLE_SLASH);
  94. xml.push(node);
  95. xml.push(CLOSE_ANGLE);
  96. }
  97. this.open = false;
  98. this.leaf = false;
  99. }
  100. leafNode(name, attributes, text) {
  101. this.openNode(name, attributes);
  102. if (text !== undefined) {
  103. // zeros need to be written
  104. this.writeText(text);
  105. }
  106. this.closeNode();
  107. }
  108. closeAll() {
  109. while (this._stack.length) {
  110. this.closeNode();
  111. }
  112. }
  113. addRollback() {
  114. this._rollbacks.push({
  115. xml: this._xml.length,
  116. stack: this._stack.length,
  117. leaf: this.leaf,
  118. open: this.open,
  119. });
  120. return this.cursor;
  121. }
  122. commit() {
  123. this._rollbacks.pop();
  124. }
  125. rollback() {
  126. const r = this._rollbacks.pop();
  127. if (this._xml.length > r.xml) {
  128. this._xml.splice(r.xml, this._xml.length - r.xml);
  129. }
  130. if (this._stack.length > r.stack) {
  131. this._stack.splice(r.stack, this._stack.length - r.stack);
  132. }
  133. this.leaf = r.leaf;
  134. this.open = r.open;
  135. }
  136. get xml() {
  137. this.closeAll();
  138. return this._xml.join('');
  139. }
  140. }
  141. XmlStream.StdDocAttributes = {
  142. version: '1.0',
  143. encoding: 'UTF-8',
  144. standalone: 'yes',
  145. };
  146. module.exports = XmlStream;