UNPKG

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