xml.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. /*
  2. * Utilities: A classic collection of JavaScript utilities
  3. * Copyright 2112 Matthew Eernisse ([email protected])
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. var core = require('./core')
  19. , inflection = require('./inflection')
  20. /**
  21. @name xml
  22. @namespace xml
  23. */
  24. exports.XML = new (function () {
  25. // Default indention level
  26. var indentLevel = 4
  27. , tagFromType
  28. , obj2xml;
  29. tagFromType = function (item, prev) {
  30. var ret
  31. , type
  32. , types;
  33. if (item instanceof Array) {
  34. ret = 'array';
  35. } else {
  36. types = ['string', 'number', 'boolean', 'object'];
  37. for (var i = 0, ii = types.length; i < ii; i++) {
  38. type = types[i];
  39. if (typeof item == type) {
  40. ret = type;
  41. }
  42. }
  43. }
  44. if (prev && ret != prev) {
  45. return 'record'
  46. } else {
  47. return ret;
  48. }
  49. };
  50. obj2xml = function (o, opts) {
  51. var name = opts.name
  52. , level = opts.level
  53. , arrayRoot = opts.arrayRoot
  54. , pack
  55. , item
  56. , n
  57. , currentIndent = (new Array(level * indentLevel)).join(' ')
  58. , nextIndent = (new Array((level + 1) * indentLevel)).join(' ')
  59. , xml = '';
  60. switch (typeof o) {
  61. case 'string':
  62. case 'number':
  63. case 'boolean':
  64. xml = o.toString();
  65. break;
  66. case 'object':
  67. // Arrays
  68. if (o instanceof Array) {
  69. // Pack the processed version of each item into an array that
  70. // can be turned into a tag-list with a `join` method below
  71. // As the list gets iterated, if all items are the same type,
  72. // that's the tag-name for the individual tags. If the items are
  73. // a mixed, the tag-name is 'record'
  74. pack = [];
  75. for (var i = 0, ii = o.length; i < ii; i++) {
  76. item = o[i];
  77. if (!name) {
  78. // Pass any previous tag-name, so it's possible to know if
  79. // all items are the same type, or it's mixed types
  80. n = tagFromType(item, n);
  81. }
  82. pack.push(obj2xml(item, {
  83. name: name
  84. , level: level + 1
  85. , arrayRoot: arrayRoot
  86. }));
  87. }
  88. // If this thing is attached to a named property on an object,
  89. // use the name for the containing tag-name
  90. if (name) {
  91. n = name;
  92. }
  93. // If this is a top-level item, wrap in a top-level containing tag
  94. if (level == 0) {
  95. xml += currentIndent + '<' + inflection.pluralize(n) + ' type="array">\n'
  96. }
  97. xml += nextIndent + '<' + n + '>' +
  98. pack.join('</' + n + '>\n' + nextIndent +
  99. '<' + n + '>') + '</' + n + '>\n';
  100. // If this is a top-level item, close the top-level containing tag
  101. if (level == 0) {
  102. xml += currentIndent + '</' + inflection.pluralize(n) + '>';
  103. }
  104. }
  105. // Generic objects
  106. else {
  107. n = name || 'object';
  108. // If this is a top-level item, wrap in a top-level containing tag
  109. if (level == 0) {
  110. xml += currentIndent + '<' + n;
  111. // Lookahead hack to allow tags to have attributes
  112. for (var p in o) {
  113. if (p.indexOf('attr:') == 0) {
  114. xml += ' ' + p.replace(/^attr:/, '') + '="' +
  115. o[p] + '"'
  116. }
  117. }
  118. xml += '>\n';
  119. }
  120. for (var p in o) {
  121. item = o[p];
  122. // Data properties only
  123. if (typeof item == 'function') {
  124. continue;
  125. }
  126. // No attr hack properties
  127. if (p.indexOf('attr:') == 0) {
  128. continue;
  129. }
  130. xml += nextIndent;
  131. if (p == '#cdata') {
  132. xml += '<![CDATA[' + item + ']]>\n';
  133. }
  134. else {
  135. // Complex values, going to have items with multiple tags
  136. // inside
  137. if (typeof item == 'object') {
  138. if (item instanceof Array) {
  139. if (arrayRoot) {
  140. xml += '<' + p + ' type="array">\n'
  141. }
  142. }
  143. else {
  144. xml += '<' + p;
  145. // Lookahead hack to allow tags to have attributes
  146. for (var q in item) {
  147. if (q.indexOf('attr:') == 0) {
  148. xml += ' ' + q.replace(/^attr:/, '') + '="' +
  149. item[q] + '"'
  150. }
  151. }
  152. xml += '>\n';
  153. }
  154. }
  155. // Scalars, just a value and closing tag
  156. else {
  157. xml += '<' + p + '>'
  158. }
  159. xml += obj2xml(item, {
  160. name: p
  161. , level: level + 1
  162. , arrayRoot: arrayRoot
  163. });
  164. // Objects and Arrays, need indentation before closing tag
  165. if (typeof item == 'object') {
  166. if (item instanceof Array) {
  167. if (arrayRoot) {
  168. xml += nextIndent;
  169. xml += '</' + p + '>\n';
  170. }
  171. }
  172. else {
  173. xml += nextIndent;
  174. xml += '</' + p + '>\n';
  175. }
  176. }
  177. // Scalars, just close
  178. else {
  179. xml += '</' + p + '>\n';
  180. }
  181. }
  182. }
  183. // If this is a top-level item, close the top-level containing tag
  184. if (level == 0) {
  185. xml += currentIndent + '</' + n + '>\n';
  186. }
  187. }
  188. break;
  189. default:
  190. // No default
  191. }
  192. return xml;
  193. }
  194. /*
  195. * XML configuration
  196. *
  197. */
  198. this.config = {
  199. whitespace: true
  200. , name: null
  201. , fragment: false
  202. , level: 0
  203. , arrayRoot: true
  204. };
  205. /**
  206. @name xml#setIndentLevel
  207. @public
  208. @function
  209. @return {Number} Return the given `level`
  210. @description SetIndentLevel changes the indent level for XML.stringify and returns it
  211. @param {Number} level The indent level to use
  212. */
  213. this.setIndentLevel = function (level) {
  214. if(!level) {
  215. return;
  216. }
  217. return indentLevel = level;
  218. };
  219. /**
  220. @name xml#stringify
  221. @public
  222. @function
  223. @return {String} Return the XML entities of the given `obj`
  224. @description Stringify returns an XML representation of the given `obj`
  225. @param {Object} obj The object containing the XML entities to use
  226. @param {Object} opts
  227. @param {Boolean} [opts.whitespace=true] Don't insert indents and newlines after xml entities
  228. @param {String} [opts.name=typeof obj] Use custom name as global namespace
  229. @param {Boolean} [opts.fragment=false] If true no header fragment is added to the top
  230. @param {Number} [opts.level=0] Remove this many levels from the output
  231. @param {Boolean} [opts.arrayRoot=true]
  232. */
  233. this.stringify = function (obj, opts) {
  234. var config = core.mixin({}, this.config)
  235. , xml = '';
  236. core.mixin(config, (opts || {}));
  237. if (!config.whitespace) {
  238. indentLevel = 0;
  239. }
  240. if (!config.fragment) {
  241. xml += '<?xml version="1.0" encoding="UTF-8"?>\n';
  242. }
  243. xml += obj2xml(obj, {
  244. name: config.name
  245. , level: config.level
  246. , arrayRoot: config.arrayRoot
  247. });
  248. if (!config.whitespace) {
  249. xml = xml.replace(/>\n/g, '>');
  250. }
  251. return xml;
  252. };
  253. })();