xml-parser.js 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. /**
  2. * Module dependencies.
  3. */
  4. /**
  5. * Expose `parse`.
  6. */
  7. /**
  8. * Parse the given string of `xml`.
  9. *
  10. * @param {String} xml
  11. * @return {Object}
  12. * @api public
  13. */
  14. function parse(xml) {
  15. xml = xml.trim()
  16. // strip comments
  17. xml = xml.replace(/<!--[\s\S]*?-->/g, '')
  18. return document()
  19. /**
  20. * XML document.
  21. */
  22. function document() {
  23. return {
  24. declaration: declaration(),
  25. root: tag()
  26. }
  27. }
  28. /**
  29. * Declaration.
  30. */
  31. function declaration() {
  32. const m = match(/^<\?xml\s*/)
  33. if (!m) return
  34. // tag
  35. const node = {
  36. attributes: {}
  37. }
  38. // attributes
  39. while (!(eos() || is('?>'))) {
  40. const attr = attribute()
  41. if (!attr) return node
  42. node.attributes[attr.name] = attr.value
  43. }
  44. match(/\?>\s*/)
  45. return node
  46. }
  47. /**
  48. * Tag.
  49. */
  50. function tag() {
  51. const m = match(/^<([\w-:.]+)\s*/)
  52. if (!m) return
  53. // name
  54. const node = {
  55. name: m[1],
  56. attributes: {},
  57. children: []
  58. }
  59. // attributes
  60. while (!(eos() || is('>') || is('?>') || is('/>'))) {
  61. const attr = attribute()
  62. if (!attr) return node
  63. node.attributes[attr.name] = attr.value
  64. }
  65. // self closing tag
  66. if (match(/^\s*\/>\s*/)) {
  67. return node
  68. }
  69. match(/\??>\s*/)
  70. // content
  71. node.content = content()
  72. // children
  73. let child
  74. while (child = tag()) {
  75. node.children.push(child)
  76. }
  77. // closing
  78. match(/^<\/[\w-:.]+>\s*/)
  79. return node
  80. }
  81. /**
  82. * Text content.
  83. */
  84. function content() {
  85. const m = match(/^([^<]*)/)
  86. if (m) return m[1]
  87. return ''
  88. }
  89. /**
  90. * Attribute.
  91. */
  92. function attribute() {
  93. const m = match(/([\w:-]+)\s*=\s*("[^"]*"|'[^']*'|\w+)\s*/)
  94. if (!m) return
  95. return {name: m[1], value: strip(m[2])}
  96. }
  97. /**
  98. * Strip quotes from `val`.
  99. */
  100. function strip(val) {
  101. return val.replace(/^['"]|['"]$/g, '')
  102. }
  103. /**
  104. * Match `re` and advance the string.
  105. */
  106. function match(re) {
  107. const m = xml.match(re)
  108. if (!m) return
  109. xml = xml.slice(m[0].length)
  110. return m
  111. }
  112. /**
  113. * End-of-source.
  114. */
  115. function eos() {
  116. return xml.length == 0
  117. }
  118. /**
  119. * Check for `prefix`.
  120. */
  121. function is(prefix) {
  122. return xml.indexOf(prefix) == 0
  123. }
  124. }
  125. module.exports = parse