You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

XMLStringifier.js 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. // Generated by CoffeeScript 2.4.1
  2. (function() {
  3. // Converts values to strings
  4. var XMLStringifier,
  5. hasProp = {}.hasOwnProperty;
  6. module.exports = XMLStringifier = (function() {
  7. class XMLStringifier {
  8. // Initializes a new instance of `XMLStringifier`
  9. // `options.version` The version number string of the XML spec to validate against, e.g. 1.0
  10. // `options.noDoubleEncoding` whether existing html entities are encoded: true or false
  11. // `options.stringify` a set of functions to use for converting values to strings
  12. // `options.noValidation` whether values will be validated and escaped or returned as is
  13. // `options.invalidCharReplacement` a character to replace invalid characters and disable character validation
  14. constructor(options) {
  15. var key, ref, value;
  16. // Checks whether the given string contains legal characters
  17. // Fails with an exception on error
  18. // `str` the string to check
  19. this.assertLegalChar = this.assertLegalChar.bind(this);
  20. // Checks whether the given string contains legal characters for a name
  21. // Fails with an exception on error
  22. // `str` the string to check
  23. this.assertLegalName = this.assertLegalName.bind(this);
  24. options || (options = {});
  25. this.options = options;
  26. if (!this.options.version) {
  27. this.options.version = '1.0';
  28. }
  29. ref = options.stringify || {};
  30. for (key in ref) {
  31. if (!hasProp.call(ref, key)) continue;
  32. value = ref[key];
  33. this[key] = value;
  34. }
  35. }
  36. // Defaults
  37. name(val) {
  38. if (this.options.noValidation) {
  39. return val;
  40. }
  41. return this.assertLegalName('' + val || '');
  42. }
  43. text(val) {
  44. if (this.options.noValidation) {
  45. return val;
  46. }
  47. return this.assertLegalChar(this.textEscape('' + val || ''));
  48. }
  49. cdata(val) {
  50. if (this.options.noValidation) {
  51. return val;
  52. }
  53. val = '' + val || '';
  54. val = val.replace(']]>', ']]]]><![CDATA[>');
  55. return this.assertLegalChar(val);
  56. }
  57. comment(val) {
  58. if (this.options.noValidation) {
  59. return val;
  60. }
  61. val = '' + val || '';
  62. if (val.match(/--/)) {
  63. throw new Error("Comment text cannot contain double-hypen: " + val);
  64. }
  65. return this.assertLegalChar(val);
  66. }
  67. raw(val) {
  68. if (this.options.noValidation) {
  69. return val;
  70. }
  71. return '' + val || '';
  72. }
  73. attValue(val) {
  74. if (this.options.noValidation) {
  75. return val;
  76. }
  77. return this.assertLegalChar(this.attEscape(val = '' + val || ''));
  78. }
  79. insTarget(val) {
  80. if (this.options.noValidation) {
  81. return val;
  82. }
  83. return this.assertLegalChar('' + val || '');
  84. }
  85. insValue(val) {
  86. if (this.options.noValidation) {
  87. return val;
  88. }
  89. val = '' + val || '';
  90. if (val.match(/\?>/)) {
  91. throw new Error("Invalid processing instruction value: " + val);
  92. }
  93. return this.assertLegalChar(val);
  94. }
  95. xmlVersion(val) {
  96. if (this.options.noValidation) {
  97. return val;
  98. }
  99. val = '' + val || '';
  100. if (!val.match(/1\.[0-9]+/)) {
  101. throw new Error("Invalid version number: " + val);
  102. }
  103. return val;
  104. }
  105. xmlEncoding(val) {
  106. if (this.options.noValidation) {
  107. return val;
  108. }
  109. val = '' + val || '';
  110. if (!val.match(/^[A-Za-z](?:[A-Za-z0-9._-])*$/)) {
  111. throw new Error("Invalid encoding: " + val);
  112. }
  113. return this.assertLegalChar(val);
  114. }
  115. xmlStandalone(val) {
  116. if (this.options.noValidation) {
  117. return val;
  118. }
  119. if (val) {
  120. return "yes";
  121. } else {
  122. return "no";
  123. }
  124. }
  125. dtdPubID(val) {
  126. if (this.options.noValidation) {
  127. return val;
  128. }
  129. return this.assertLegalChar('' + val || '');
  130. }
  131. dtdSysID(val) {
  132. if (this.options.noValidation) {
  133. return val;
  134. }
  135. return this.assertLegalChar('' + val || '');
  136. }
  137. dtdElementValue(val) {
  138. if (this.options.noValidation) {
  139. return val;
  140. }
  141. return this.assertLegalChar('' + val || '');
  142. }
  143. dtdAttType(val) {
  144. if (this.options.noValidation) {
  145. return val;
  146. }
  147. return this.assertLegalChar('' + val || '');
  148. }
  149. dtdAttDefault(val) {
  150. if (this.options.noValidation) {
  151. return val;
  152. }
  153. return this.assertLegalChar('' + val || '');
  154. }
  155. dtdEntityValue(val) {
  156. if (this.options.noValidation) {
  157. return val;
  158. }
  159. return this.assertLegalChar('' + val || '');
  160. }
  161. dtdNData(val) {
  162. if (this.options.noValidation) {
  163. return val;
  164. }
  165. return this.assertLegalChar('' + val || '');
  166. }
  167. assertLegalChar(str) {
  168. var regex, res;
  169. if (this.options.noValidation) {
  170. return str;
  171. }
  172. if (this.options.version === '1.0') {
  173. // Valid characters from https://www.w3.org/TR/xml/#charsets
  174. // any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
  175. // #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
  176. // This ES5 compatible Regexp has been generated using the "regenerate" NPM module:
  177. // let xml_10_InvalidChars = regenerate()
  178. // .addRange(0x0000, 0x0008)
  179. // .add(0x000B, 0x000C)
  180. // .addRange(0x000E, 0x001F)
  181. // .addRange(0xD800, 0xDFFF)
  182. // .addRange(0xFFFE, 0xFFFF)
  183. regex = /[\0-\x08\x0B\f\x0E-\x1F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/g;
  184. if (this.options.invalidCharReplacement !== void 0) {
  185. str = str.replace(regex, this.options.invalidCharReplacement);
  186. } else if (res = str.match(regex)) {
  187. throw new Error(`Invalid character in string: ${str} at index ${res.index}`);
  188. }
  189. } else if (this.options.version === '1.1') {
  190. // Valid characters from https://www.w3.org/TR/xml11/#charsets
  191. // any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
  192. // [#x1-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
  193. // This ES5 compatible Regexp has been generated using the "regenerate" NPM module:
  194. // let xml_11_InvalidChars = regenerate()
  195. // .add(0x0000)
  196. // .addRange(0xD800, 0xDFFF)
  197. // .addRange(0xFFFE, 0xFFFF)
  198. regex = /[\0\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/g;
  199. if (this.options.invalidCharReplacement !== void 0) {
  200. str = str.replace(regex, this.options.invalidCharReplacement);
  201. } else if (res = str.match(regex)) {
  202. throw new Error(`Invalid character in string: ${str} at index ${res.index}`);
  203. }
  204. }
  205. return str;
  206. }
  207. assertLegalName(str) {
  208. var regex;
  209. if (this.options.noValidation) {
  210. return str;
  211. }
  212. str = this.assertLegalChar(str);
  213. regex = /^([:A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]|[\uD800-\uDB7F][\uDC00-\uDFFF])([\x2D\.0-:A-Z_a-z\xB7\xC0-\xD6\xD8-\xF6\xF8-\u037D\u037F-\u1FFF\u200C\u200D\u203F\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]|[\uD800-\uDB7F][\uDC00-\uDFFF])*$/;
  214. if (!str.match(regex)) {
  215. throw new Error(`Invalid character in name: ${str}`);
  216. }
  217. return str;
  218. }
  219. // Escapes special characters in text
  220. // See http://www.w3.org/TR/2000/WD-xml-c14n-20000119.html#charescaping
  221. // `str` the string to escape
  222. textEscape(str) {
  223. var ampregex;
  224. if (this.options.noValidation) {
  225. return str;
  226. }
  227. ampregex = this.options.noDoubleEncoding ? /(?!&(lt|gt|amp|apos|quot);)&/g : /&/g;
  228. return str.replace(ampregex, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\r/g, '&#xD;');
  229. }
  230. // Escapes special characters in attribute values
  231. // See http://www.w3.org/TR/2000/WD-xml-c14n-20000119.html#charescaping
  232. // `str` the string to escape
  233. attEscape(str) {
  234. var ampregex;
  235. if (this.options.noValidation) {
  236. return str;
  237. }
  238. ampregex = this.options.noDoubleEncoding ? /(?!&(lt|gt|amp|apos|quot);)&/g : /&/g;
  239. return str.replace(ampregex, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/\t/g, '&#x9;').replace(/\n/g, '&#xA;').replace(/\r/g, '&#xD;');
  240. }
  241. };
  242. // strings to match while converting from JS objects
  243. XMLStringifier.prototype.convertAttKey = '@';
  244. XMLStringifier.prototype.convertPIKey = '?';
  245. XMLStringifier.prototype.convertTextKey = '#text';
  246. XMLStringifier.prototype.convertCDataKey = '#cdata';
  247. XMLStringifier.prototype.convertCommentKey = '#comment';
  248. XMLStringifier.prototype.convertRawKey = '#raw';
  249. return XMLStringifier;
  250. }).call(this);
  251. }).call(this);