电速宝智配引擎
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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. var sax = require('sax');
  2. var expat /*= require('node-expat');*/ = { on: function () { }, parse: function () { } };
  3. var helper = require('./options-helper');
  4. var isArray = require('./array-helper').isArray;
  5. var options;
  6. var pureJsParser = true;
  7. var currentElement;
  8. function validateOptions(userOptions) {
  9. options = helper.copyOptions(userOptions);
  10. helper.ensureFlagExists('ignoreDeclaration', options);
  11. helper.ensureFlagExists('ignoreInstruction', options);
  12. helper.ensureFlagExists('ignoreAttributes', options);
  13. helper.ensureFlagExists('ignoreText', options);
  14. helper.ensureFlagExists('ignoreComment', options);
  15. helper.ensureFlagExists('ignoreCdata', options);
  16. helper.ensureFlagExists('ignoreDoctype', options);
  17. helper.ensureFlagExists('compact', options);
  18. helper.ensureFlagExists('alwaysChildren', options);
  19. helper.ensureFlagExists('addParent', options);
  20. helper.ensureFlagExists('trim', options);
  21. helper.ensureFlagExists('nativeType', options);
  22. helper.ensureFlagExists('nativeTypeAttributes', options);
  23. helper.ensureFlagExists('sanitize', options);
  24. helper.ensureFlagExists('instructionHasAttributes', options);
  25. helper.ensureFlagExists('captureSpacesBetweenElements', options);
  26. helper.ensureAlwaysArrayExists(options);
  27. helper.ensureKeyExists('declaration', options);
  28. helper.ensureKeyExists('instruction', options);
  29. helper.ensureKeyExists('attributes', options);
  30. helper.ensureKeyExists('text', options);
  31. helper.ensureKeyExists('comment', options);
  32. helper.ensureKeyExists('cdata', options);
  33. helper.ensureKeyExists('doctype', options);
  34. helper.ensureKeyExists('type', options);
  35. helper.ensureKeyExists('name', options);
  36. helper.ensureKeyExists('elements', options);
  37. helper.ensureKeyExists('parent', options);
  38. helper.checkFnExists('doctype', options);
  39. helper.checkFnExists('instruction', options);
  40. helper.checkFnExists('cdata', options);
  41. helper.checkFnExists('comment', options);
  42. helper.checkFnExists('text', options);
  43. helper.checkFnExists('instructionName', options);
  44. helper.checkFnExists('elementName', options);
  45. helper.checkFnExists('attributeName', options);
  46. helper.checkFnExists('attributeValue', options);
  47. helper.checkFnExists('attributes', options);
  48. return options;
  49. }
  50. function nativeType(value) {
  51. var nValue = Number(value);
  52. if (!isNaN(nValue)) {
  53. return nValue;
  54. }
  55. var bValue = value.toLowerCase();
  56. if (bValue === 'true') {
  57. return true;
  58. } else if (bValue === 'false') {
  59. return false;
  60. }
  61. return value;
  62. }
  63. function addField(type, value) {
  64. var key;
  65. if (options.compact) {
  66. if (
  67. !currentElement[options[type + 'Key']] &&
  68. (isArray(options.alwaysArray) ? options.alwaysArray.indexOf(options[type + 'Key']) !== -1 : options.alwaysArray)
  69. ) {
  70. currentElement[options[type + 'Key']] = [];
  71. }
  72. if (currentElement[options[type + 'Key']] && !isArray(currentElement[options[type + 'Key']])) {
  73. currentElement[options[type + 'Key']] = [currentElement[options[type + 'Key']]];
  74. }
  75. if (type + 'Fn' in options && typeof value === 'string') {
  76. value = options[type + 'Fn'](value, currentElement);
  77. }
  78. if (type === 'instruction' && ('instructionFn' in options || 'instructionNameFn' in options)) {
  79. for (key in value) {
  80. if (value.hasOwnProperty(key)) {
  81. if ('instructionFn' in options) {
  82. value[key] = options.instructionFn(value[key], key, currentElement);
  83. } else {
  84. var temp = value[key];
  85. delete value[key];
  86. value[options.instructionNameFn(key, temp, currentElement)] = temp;
  87. }
  88. }
  89. }
  90. }
  91. if (isArray(currentElement[options[type + 'Key']])) {
  92. currentElement[options[type + 'Key']].push(value);
  93. } else {
  94. currentElement[options[type + 'Key']] = value;
  95. }
  96. } else {
  97. if (!currentElement[options.elementsKey]) {
  98. currentElement[options.elementsKey] = [];
  99. }
  100. var element = {};
  101. element[options.typeKey] = type;
  102. if (type === 'instruction') {
  103. for (key in value) {
  104. if (value.hasOwnProperty(key)) {
  105. break;
  106. }
  107. }
  108. element[options.nameKey] = 'instructionNameFn' in options ? options.instructionNameFn(key, value, currentElement) : key;
  109. if (options.instructionHasAttributes) {
  110. element[options.attributesKey] = value[key][options.attributesKey];
  111. if ('instructionFn' in options) {
  112. element[options.attributesKey] = options.instructionFn(element[options.attributesKey], key, currentElement);
  113. }
  114. } else {
  115. if ('instructionFn' in options) {
  116. value[key] = options.instructionFn(value[key], key, currentElement);
  117. }
  118. element[options.instructionKey] = value[key];
  119. }
  120. } else {
  121. if (type + 'Fn' in options) {
  122. value = options[type + 'Fn'](value, currentElement);
  123. }
  124. element[options[type + 'Key']] = value;
  125. }
  126. if (options.addParent) {
  127. element[options.parentKey] = currentElement;
  128. }
  129. currentElement[options.elementsKey].push(element);
  130. }
  131. }
  132. function manipulateAttributes(attributes) {
  133. if ('attributesFn' in options && attributes) {
  134. attributes = options.attributesFn(attributes, currentElement);
  135. }
  136. if ((options.trim || 'attributeValueFn' in options || 'attributeNameFn' in options || options.nativeTypeAttributes) && attributes) {
  137. var key;
  138. for (key in attributes) {
  139. if (attributes.hasOwnProperty(key)) {
  140. if (options.trim) attributes[key] = attributes[key].trim();
  141. if (options.nativeTypeAttributes) {
  142. attributes[key] = nativeType(attributes[key]);
  143. }
  144. if ('attributeValueFn' in options) attributes[key] = options.attributeValueFn(attributes[key], key, currentElement);
  145. if ('attributeNameFn' in options) {
  146. var temp = attributes[key];
  147. delete attributes[key];
  148. attributes[options.attributeNameFn(key, attributes[key], currentElement)] = temp;
  149. }
  150. }
  151. }
  152. }
  153. return attributes;
  154. }
  155. function onInstruction(instruction) {
  156. var attributes = {};
  157. if (instruction.body && (instruction.name.toLowerCase() === 'xml' || options.instructionHasAttributes)) {
  158. var attrsRegExp = /([\w:-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|(\w+))\s*/g;
  159. var match;
  160. while ((match = attrsRegExp.exec(instruction.body)) !== null) {
  161. attributes[match[1]] = match[2] || match[3] || match[4];
  162. }
  163. attributes = manipulateAttributes(attributes);
  164. }
  165. if (instruction.name.toLowerCase() === 'xml') {
  166. if (options.ignoreDeclaration) {
  167. return;
  168. }
  169. currentElement[options.declarationKey] = {};
  170. if (Object.keys(attributes).length) {
  171. currentElement[options.declarationKey][options.attributesKey] = attributes;
  172. }
  173. if (options.addParent) {
  174. currentElement[options.declarationKey][options.parentKey] = currentElement;
  175. }
  176. } else {
  177. if (options.ignoreInstruction) {
  178. return;
  179. }
  180. if (options.trim) {
  181. instruction.body = instruction.body.trim();
  182. }
  183. var value = {};
  184. if (options.instructionHasAttributes && Object.keys(attributes).length) {
  185. value[instruction.name] = {};
  186. value[instruction.name][options.attributesKey] = attributes;
  187. } else {
  188. value[instruction.name] = instruction.body;
  189. }
  190. addField('instruction', value);
  191. }
  192. }
  193. function onStartElement(name, attributes) {
  194. var element;
  195. if (typeof name === 'object') {
  196. attributes = name.attributes;
  197. name = name.name;
  198. }
  199. attributes = manipulateAttributes(attributes);
  200. if ('elementNameFn' in options) {
  201. name = options.elementNameFn(name, currentElement);
  202. }
  203. if (options.compact) {
  204. element = {};
  205. if (!options.ignoreAttributes && attributes && Object.keys(attributes).length) {
  206. element[options.attributesKey] = {};
  207. var key;
  208. for (key in attributes) {
  209. if (attributes.hasOwnProperty(key)) {
  210. element[options.attributesKey][key] = attributes[key];
  211. }
  212. }
  213. }
  214. if (
  215. !(name in currentElement) &&
  216. (isArray(options.alwaysArray) ? options.alwaysArray.indexOf(name) !== -1 : options.alwaysArray)
  217. ) {
  218. currentElement[name] = [];
  219. }
  220. if (currentElement[name] && !isArray(currentElement[name])) {
  221. currentElement[name] = [currentElement[name]];
  222. }
  223. if (isArray(currentElement[name])) {
  224. currentElement[name].push(element);
  225. } else {
  226. currentElement[name] = element;
  227. }
  228. } else {
  229. if (!currentElement[options.elementsKey]) {
  230. currentElement[options.elementsKey] = [];
  231. }
  232. element = {};
  233. element[options.typeKey] = 'element';
  234. element[options.nameKey] = name;
  235. if (!options.ignoreAttributes && attributes && Object.keys(attributes).length) {
  236. element[options.attributesKey] = attributes;
  237. }
  238. if (options.alwaysChildren) {
  239. element[options.elementsKey] = [];
  240. }
  241. currentElement[options.elementsKey].push(element);
  242. }
  243. element[options.parentKey] = currentElement; // will be deleted in onEndElement() if !options.addParent
  244. currentElement = element;
  245. }
  246. function onText(text) {
  247. if (options.ignoreText) {
  248. return;
  249. }
  250. if (!text.trim() && !options.captureSpacesBetweenElements) {
  251. return;
  252. }
  253. if (options.trim) {
  254. text = text.trim();
  255. }
  256. if (options.nativeType) {
  257. text = nativeType(text);
  258. }
  259. if (options.sanitize) {
  260. text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  261. }
  262. addField('text', text);
  263. }
  264. function onComment(comment) {
  265. if (options.ignoreComment) {
  266. return;
  267. }
  268. if (options.trim) {
  269. comment = comment.trim();
  270. }
  271. addField('comment', comment);
  272. }
  273. function onEndElement(name) {
  274. var parentElement = currentElement[options.parentKey];
  275. if (!options.addParent) {
  276. delete currentElement[options.parentKey];
  277. }
  278. currentElement = parentElement;
  279. }
  280. function onCdata(cdata) {
  281. if (options.ignoreCdata) {
  282. return;
  283. }
  284. if (options.trim) {
  285. cdata = cdata.trim();
  286. }
  287. addField('cdata', cdata);
  288. }
  289. function onDoctype(doctype) {
  290. if (options.ignoreDoctype) {
  291. return;
  292. }
  293. doctype = doctype.replace(/^ /, '');
  294. if (options.trim) {
  295. doctype = doctype.trim();
  296. }
  297. addField('doctype', doctype);
  298. }
  299. function onError(error) {
  300. error.note = error; //console.error(error);
  301. }
  302. module.exports = function (xml, userOptions) {
  303. var parser = pureJsParser ? sax.parser(true, {}) : parser = new expat.Parser('UTF-8');
  304. var result = {};
  305. currentElement = result;
  306. options = validateOptions(userOptions);
  307. if (pureJsParser) {
  308. parser.opt = {strictEntities: true};
  309. parser.onopentag = onStartElement;
  310. parser.ontext = onText;
  311. parser.oncomment = onComment;
  312. parser.onclosetag = onEndElement;
  313. parser.onerror = onError;
  314. parser.oncdata = onCdata;
  315. parser.ondoctype = onDoctype;
  316. parser.onprocessinginstruction = onInstruction;
  317. } else {
  318. parser.on('startElement', onStartElement);
  319. parser.on('text', onText);
  320. parser.on('comment', onComment);
  321. parser.on('endElement', onEndElement);
  322. parser.on('error', onError);
  323. //parser.on('startCdata', onStartCdata);
  324. //parser.on('endCdata', onEndCdata);
  325. //parser.on('entityDecl', onEntityDecl);
  326. }
  327. if (pureJsParser) {
  328. parser.write(xml).close();
  329. } else {
  330. if (!parser.parse(xml)) {
  331. throw new Error('XML parsing error: ' + parser.getError());
  332. }
  333. }
  334. if (result[options.elementsKey]) {
  335. var temp = result[options.elementsKey];
  336. delete result[options.elementsKey];
  337. result[options.elementsKey] = temp;
  338. delete result.text;
  339. }
  340. return result;
  341. };