1 | var EventEmitter = require('events').EventEmitter
|
2 | , sax = require('sax')
|
3 | , helper = require('./helper')
|
4 | , flow
|
5 | ;
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | flow = 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 |
|
34 | if (stack.length === 0 && !emitter.listeners('tag:' + node.name).length) {
|
35 | return;
|
36 | }
|
37 |
|
38 |
|
39 |
|
40 |
|
41 | topNode = {
|
42 | $name: node.name,
|
43 | $attrs: node.attributes
|
44 | };
|
45 |
|
46 |
|
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 |
|
113 | if (stack.length === 0) return;
|
114 |
|
115 |
|
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 |
|
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 |
|
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 |
|
167 | flow.ALWAYS = 1;
|
168 | flow.SOMETIMES = 0;
|
169 | flow.NEVER = -1;
|
170 |
|
171 | flow.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 |
|
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 |
|
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 | };
|