UNPKG

6.84 kBJavaScriptView Raw
1var EventEmitter = require('events').EventEmitter
2 , sax = require('sax')
3 , helper = require('./helper')
4 , flow
5 ;
6
7/**
8 * @param ReadSteam inStream the stream to be parsed
9 */
10flow = module.exports = function xmlFlow(inStream, opts) {
11 var emitter = new EventEmitter()
12 , saxOptions = {}
13 , saxStream = null
14 , stack = []
15 , topNode = null
16 , currentCdata = null
17 , options = opts || {}
18 ;
19
20 options.preserveMarkup = options.preserveMarkup || flow.SOMETIMES;
21 options.simplifyNodes = options.hasOwnProperty('simplifyNodes') ? options.simplifyNodes : true;
22 options.useArrays = options.hasOwnProperty('useArrays') ? options.useArrays : flow.SOMETIMES;
23
24 saxOptions.lowercase = options.hasOwnProperty('lowercase') ? options.lowercase : true;
25 saxOptions.trim = options.hasOwnProperty('trim') ? options.trim : true;
26 saxOptions.normalize = options.hasOwnProperty('normalize') ? options.normalize : true;
27
28 saxOptions.cdataAsText = options.hasOwnProperty('cdataAsText') ? options.cdataAsText : false;
29
30 saxStream = sax.createStream(options.strict || false, saxOptions);
31
32 saxStream.on('opentag', function openTag(node) {
33 //Ignore nodes we don't care about.
34 if (stack.length === 0 && !emitter.listeners('tag:' + node.name).length) {
35 return;
36 }
37
38 //@TODO: If useArrays is set to flow.NEVER and this tag is already
39 //a member of last, we could ignore it and not mess
40
41 topNode = {
42 $name: node.name,
43 $attrs: node.attributes
44 };
45
46 //If useArrays is set to flow.NEVER, we don't want $markup
47 if (options.useArrays > flow.NEVER) {
48 topNode.$markup = [];
49 }
50
51 stack.push(topNode);
52 });
53
54 saxStream.on('text', function onText(text) {
55 if (topNode) {
56 if (options.useArrays > flow.NEVER) {
57 topNode.$markup.push(text);
58 } else if (topNode.$text) {
59 topNode.$text += text;
60 } else {
61 topNode.$text = text;
62 }
63 }
64 });
65
66 saxStream.on('opencdata', function onOpenCdata() {
67 if (topNode) {
68 if (!options.cdataAsText) {
69 currentCdata = {
70 $name: '$cdata',
71 text: ''
72 };
73 if (options.useArrays > flow.NEVER) {
74 topNode.$markup.push(currentCdata);
75 } else {
76 topNode.$cdata = currentCdata;
77 }
78 }
79 }
80 });
81
82 saxStream.on('cdata', function onCdata(text) {
83 if (topNode) {
84 if (currentCdata !== null) {
85 currentCdata.text += text;
86 } else if (options.useArrays > flow.NEVER) {
87 topNode.$markup.push(text);
88 } else if (topNode.$text) {
89 topNode.$text += text;
90 } else {
91 topNode.$text = text;
92 }
93 }
94 });
95
96 saxStream.on('closecdata', function onCloseCdata() {
97 currentCdata = null;
98 });
99
100 saxStream.on('script', function onScript(script) {
101 if (topNode) {
102 topNode.$script = script;
103 }
104 });
105
106 saxStream.on('closetag', function onCloseTag(tagName) {
107 var compressed
108 , newTop = null
109 , keepArrays = options.useArrays > flow.SOMETIMES
110 ;
111
112 //If we're not going to send out a node, goodbye!
113 if (stack.length === 0) return;
114
115 //Objectify Markup if needed...
116 if (options.useArrays > flow.NEVER) {
117 if (options.preserveMarkup <= flow.NEVER) {
118 topNode.$markup = helper.condenseArray(topNode.$markup);
119 topNode = helper.objectifyMarkup(topNode, keepArrays);
120 } else if (options.preserveMarkup === flow.SOMETIMES) {
121 compressed = helper.condenseArray(topNode.$markup);
122 if (helper.shouldObjectifyMarkup(compressed)) {
123 topNode.$markup = compressed;
124 topNode = helper.objectifyMarkup(topNode, keepArrays);
125 }
126 }
127 }
128
129 //emit the node if there are listeners
130 if (emitter.listeners('tag:' + tagName).length) {
131 emitter.emit(
132 'tag:' + tagName,
133 options.simplifyNodes ? helper.simplifyNode(topNode, false, options.useArrays > flow.SOMETIMES) : topNode
134 );
135 }
136
137 //Pop stack, and add to parent node
138 stack.pop();
139 if (stack.length > 0) {
140 newTop = stack[stack.length - 1];
141 if (options.useArrays > flow.NEVER) {
142 newTop.$markup.push(topNode);
143 } else if (!newTop[tagName]) {
144 newTop[tagName] = helper.simplifyNode(topNode, true);
145 }
146 }
147 topNode = newTop;
148 });
149
150 saxStream.on('end', function onEnd() {
151 emitter.emit('end');
152 });
153
154 inStream.pipe(saxStream);
155
156 emitter.pause = function pause() {
157 inStream.pause();
158 };
159
160 emitter.resume = function resume() {
161 inStream.resume();
162 };
163
164 return emitter;
165};
166
167flow.ALWAYS = 1;
168flow.SOMETIMES = 0;
169flow.NEVER = -1;
170
171flow.toXml = function toXml(obj, options) {
172 var opt = options || {}
173 , idt = opt.indent || ''
174 , ret = idt ? '\n' : ''
175 , selfClosing = opt.hasOwnProperty('selfClosing') ? opt.selfClosing : true
176 , escape = opt.escape || helper.escape
177 ;
178
179 function getXml(node, nodeName, indent) {
180 var output = ''
181 , keys
182 , name = nodeName
183 , thisIndent = indent ? ret + indent : ''
184 , nextIndent = indent + idt
185 , guts = ''
186 ;
187
188 if (node.constructor === Array) {
189 node.forEach(function eachSubNode(subNode) {
190 output += getXml(subNode, name, indent);
191 });
192 return output;
193 }
194
195 if (!name && node.$name) {
196 name = node.$name;
197 }
198
199 if (name) {
200 output = thisIndent + '<' + name;
201 if (node.$attrs && typeof node.$attrs === 'object') {
202 keys = Object.keys(node.$attrs);
203 keys.forEach(function eachKey(key) {
204 output += ' ' + key + '=' + JSON.stringify(String(node.$attrs[key]));
205 });
206 }
207 }
208
209 if (node === null || node === undefined || node === '') {
210 //do nothing. Empty on purpose
211 } else if (typeof node === 'object') {
212 keys = Object.keys(node);
213 keys.forEach(function eachKey(key) {
214 var value = node[key];
215 switch (key) {
216 case '$name':
217 case '$attrs':
218 //Do nothing. Already taken care of
219 break;
220
221 case '$text':
222 case '$markup':
223 guts += getXml(value, null, nextIndent);
224 break;
225
226 case '$script':
227 guts += getXml(value, 'script', nextIndent);
228 break;
229
230 case '$cdata':
231 guts += thisIndent + '<![CDATA[' + value + ']]>';
232 break;
233
234 default:
235 guts += getXml(value, key, nextIndent);
236 }
237 });
238 } else {
239 guts += thisIndent + escape(node);
240 }
241
242 if (name) {
243 if (guts) {
244 output += '>' + guts + ret + indent + '</' + name + '>';
245 } else if (selfClosing) {
246 output += '/>';
247 } else {
248 output += '></' + name + '>';
249 }
250 } else {
251 output += guts;
252 }
253 return output;
254 }
255 return getXml(obj, opt.nodeName, '');
256};