xml-writer.js 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. 'use strict';
  2. /*
  3. Copyright 2012-2015, Yahoo Inc.
  4. Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
  5. */
  6. const INDENT = ' ';
  7. function attrString(attrs) {
  8. return Object.entries(attrs || {})
  9. .map(([k, v]) => ` ${k}="${v}"`)
  10. .join('');
  11. }
  12. /**
  13. * a utility class to produce well-formed, indented XML
  14. * @param {ContentWriter} contentWriter the content writer that this utility wraps
  15. * @constructor
  16. */
  17. class XMLWriter {
  18. constructor(contentWriter) {
  19. this.cw = contentWriter;
  20. this.stack = [];
  21. }
  22. indent(str) {
  23. return this.stack.map(() => INDENT).join('') + str;
  24. }
  25. /**
  26. * writes the opening XML tag with the supplied attributes
  27. * @param {String} name tag name
  28. * @param {Object} [attrs=null] attrs attributes for the tag
  29. */
  30. openTag(name, attrs) {
  31. const str = this.indent(`<${name + attrString(attrs)}>`);
  32. this.cw.println(str);
  33. this.stack.push(name);
  34. }
  35. /**
  36. * closes an open XML tag.
  37. * @param {String} name - tag name to close. This must match the writer's
  38. * notion of the tag that is currently open.
  39. */
  40. closeTag(name) {
  41. if (this.stack.length === 0) {
  42. throw new Error(`Attempt to close tag ${name} when not opened`);
  43. }
  44. const stashed = this.stack.pop();
  45. const str = `</${name}>`;
  46. if (stashed !== name) {
  47. throw new Error(
  48. `Attempt to close tag ${name} when ${stashed} was the one open`
  49. );
  50. }
  51. this.cw.println(this.indent(str));
  52. }
  53. /**
  54. * writes a tag and its value opening and closing it at the same time
  55. * @param {String} name tag name
  56. * @param {Object} [attrs=null] attrs tag attributes
  57. * @param {String} [content=null] content optional tag content
  58. */
  59. inlineTag(name, attrs, content) {
  60. let str = '<' + name + attrString(attrs);
  61. if (content) {
  62. str += `>${content}</${name}>`;
  63. } else {
  64. str += '/>';
  65. }
  66. str = this.indent(str);
  67. this.cw.println(str);
  68. }
  69. /**
  70. * closes all open tags and ends the document
  71. */
  72. closeAll() {
  73. this.stack
  74. .slice()
  75. .reverse()
  76. .forEach(name => {
  77. this.closeTag(name);
  78. });
  79. }
  80. }
  81. module.exports = XMLWriter;