1 | var escapeForXML = require('./escapeForXML');
|
---|
2 | var Stream = require('stream').Stream;
|
---|
3 |
|
---|
4 | var DEFAULT_INDENT = ' ';
|
---|
5 |
|
---|
6 | function xml(input, options) {
|
---|
7 |
|
---|
8 | if (typeof options !== 'object') {
|
---|
9 | options = {
|
---|
10 | indent: options
|
---|
11 | };
|
---|
12 | }
|
---|
13 |
|
---|
14 | var stream = options.stream ? new Stream() : null,
|
---|
15 | output = "",
|
---|
16 | interrupted = false,
|
---|
17 | indent = !options.indent ? ''
|
---|
18 | : options.indent === true ? DEFAULT_INDENT
|
---|
19 | : options.indent,
|
---|
20 | instant = true;
|
---|
21 |
|
---|
22 |
|
---|
23 | function delay (func) {
|
---|
24 | if (!instant) {
|
---|
25 | func();
|
---|
26 | } else {
|
---|
27 | process.nextTick(func);
|
---|
28 | }
|
---|
29 | }
|
---|
30 |
|
---|
31 | function append (interrupt, out) {
|
---|
32 | if (out !== undefined) {
|
---|
33 | output += out;
|
---|
34 | }
|
---|
35 | if (interrupt && !interrupted) {
|
---|
36 | stream = stream || new Stream();
|
---|
37 | interrupted = true;
|
---|
38 | }
|
---|
39 | if (interrupt && interrupted) {
|
---|
40 | var data = output;
|
---|
41 | delay(function () { stream.emit('data', data) });
|
---|
42 | output = "";
|
---|
43 | }
|
---|
44 | }
|
---|
45 |
|
---|
46 | function add (value, last) {
|
---|
47 | format(append, resolve(value, indent, indent ? 1 : 0), last);
|
---|
48 | }
|
---|
49 |
|
---|
50 | function end() {
|
---|
51 | if (stream) {
|
---|
52 | var data = output;
|
---|
53 | delay(function () {
|
---|
54 | stream.emit('data', data);
|
---|
55 | stream.emit('end');
|
---|
56 | stream.readable = false;
|
---|
57 | stream.emit('close');
|
---|
58 | });
|
---|
59 | }
|
---|
60 | }
|
---|
61 |
|
---|
62 | function addXmlDeclaration(declaration) {
|
---|
63 | var encoding = declaration.encoding || 'UTF-8',
|
---|
64 | attr = { version: '1.0', encoding: encoding };
|
---|
65 |
|
---|
66 | if (declaration.standalone) {
|
---|
67 | attr.standalone = declaration.standalone
|
---|
68 | }
|
---|
69 |
|
---|
70 | add({'?xml': { _attr: attr } });
|
---|
71 | output = output.replace('/>', '?>');
|
---|
72 | }
|
---|
73 |
|
---|
74 | // disable delay delayed
|
---|
75 | delay(function () { instant = false });
|
---|
76 |
|
---|
77 | if (options.declaration) {
|
---|
78 | addXmlDeclaration(options.declaration);
|
---|
79 | }
|
---|
80 |
|
---|
81 | if (input && input.forEach) {
|
---|
82 | input.forEach(function (value, i) {
|
---|
83 | var last;
|
---|
84 | if (i + 1 === input.length)
|
---|
85 | last = end;
|
---|
86 | add(value, last);
|
---|
87 | });
|
---|
88 | } else {
|
---|
89 | add(input, end);
|
---|
90 | }
|
---|
91 |
|
---|
92 | if (stream) {
|
---|
93 | stream.readable = true;
|
---|
94 | return stream;
|
---|
95 | }
|
---|
96 | return output;
|
---|
97 | }
|
---|
98 |
|
---|
99 | function element (/*input, …*/) {
|
---|
100 | var input = Array.prototype.slice.call(arguments),
|
---|
101 | self = {
|
---|
102 | _elem: resolve(input)
|
---|
103 | };
|
---|
104 |
|
---|
105 | self.push = function (input) {
|
---|
106 | if (!this.append) {
|
---|
107 | throw new Error("not assigned to a parent!");
|
---|
108 | }
|
---|
109 | var that = this;
|
---|
110 | var indent = this._elem.indent;
|
---|
111 | format(this.append, resolve(
|
---|
112 | input, indent, this._elem.icount + (indent ? 1 : 0)),
|
---|
113 | function () { that.append(true) });
|
---|
114 | };
|
---|
115 |
|
---|
116 | self.close = function (input) {
|
---|
117 | if (input !== undefined) {
|
---|
118 | this.push(input);
|
---|
119 | }
|
---|
120 | if (this.end) {
|
---|
121 | this.end();
|
---|
122 | }
|
---|
123 | };
|
---|
124 |
|
---|
125 | return self;
|
---|
126 | }
|
---|
127 |
|
---|
128 | function create_indent(character, count) {
|
---|
129 | return (new Array(count || 0).join(character || ''))
|
---|
130 | }
|
---|
131 |
|
---|
132 | function resolve(data, indent, indent_count) {
|
---|
133 | indent_count = indent_count || 0;
|
---|
134 | var indent_spaces = create_indent(indent, indent_count);
|
---|
135 | var name;
|
---|
136 | var values = data;
|
---|
137 | var interrupt = false;
|
---|
138 |
|
---|
139 | if (typeof data === 'object') {
|
---|
140 | var keys = Object.keys(data);
|
---|
141 | name = keys[0];
|
---|
142 | values = data[name];
|
---|
143 |
|
---|
144 | if (values && values._elem) {
|
---|
145 | values._elem.name = name;
|
---|
146 | values._elem.icount = indent_count;
|
---|
147 | values._elem.indent = indent;
|
---|
148 | values._elem.indents = indent_spaces;
|
---|
149 | values._elem.interrupt = values;
|
---|
150 | return values._elem;
|
---|
151 | }
|
---|
152 | }
|
---|
153 |
|
---|
154 | var attributes = [],
|
---|
155 | content = [];
|
---|
156 |
|
---|
157 | var isStringContent;
|
---|
158 |
|
---|
159 | function get_attributes(obj){
|
---|
160 | var keys = Object.keys(obj);
|
---|
161 | keys.forEach(function(key){
|
---|
162 | attributes.push(attribute(key, obj[key]));
|
---|
163 | });
|
---|
164 | }
|
---|
165 |
|
---|
166 | switch(typeof values) {
|
---|
167 | case 'object':
|
---|
168 | if (values === null) break;
|
---|
169 |
|
---|
170 | if (values._attr) {
|
---|
171 | get_attributes(values._attr);
|
---|
172 | }
|
---|
173 |
|
---|
174 | if (values._cdata) {
|
---|
175 | content.push(
|
---|
176 | ('<![CDATA[' + values._cdata).replace(/\]\]>/g, ']]]]><![CDATA[>') + ']]>'
|
---|
177 | );
|
---|
178 | }
|
---|
179 |
|
---|
180 | if (values.forEach) {
|
---|
181 | isStringContent = false;
|
---|
182 | content.push('');
|
---|
183 | values.forEach(function(value) {
|
---|
184 | if (typeof value == 'object') {
|
---|
185 | var _name = Object.keys(value)[0];
|
---|
186 |
|
---|
187 | if (_name == '_attr') {
|
---|
188 | get_attributes(value._attr);
|
---|
189 | } else {
|
---|
190 | content.push(resolve(
|
---|
191 | value, indent, indent_count + 1));
|
---|
192 | }
|
---|
193 | } else {
|
---|
194 | //string
|
---|
195 | content.pop();
|
---|
196 | isStringContent=true;
|
---|
197 | content.push(escapeForXML(value));
|
---|
198 | }
|
---|
199 |
|
---|
200 | });
|
---|
201 | if (!isStringContent) {
|
---|
202 | content.push('');
|
---|
203 | }
|
---|
204 | }
|
---|
205 | break;
|
---|
206 |
|
---|
207 | default:
|
---|
208 | //string
|
---|
209 | content.push(escapeForXML(values));
|
---|
210 |
|
---|
211 | }
|
---|
212 |
|
---|
213 | return {
|
---|
214 | name: name,
|
---|
215 | interrupt: interrupt,
|
---|
216 | attributes: attributes,
|
---|
217 | content: content,
|
---|
218 | icount: indent_count,
|
---|
219 | indents: indent_spaces,
|
---|
220 | indent: indent
|
---|
221 | };
|
---|
222 | }
|
---|
223 |
|
---|
224 | function format(append, elem, end) {
|
---|
225 |
|
---|
226 | if (typeof elem != 'object') {
|
---|
227 | return append(false, elem);
|
---|
228 | }
|
---|
229 |
|
---|
230 | var len = elem.interrupt ? 1 : elem.content.length;
|
---|
231 |
|
---|
232 | function proceed () {
|
---|
233 | while (elem.content.length) {
|
---|
234 | var value = elem.content.shift();
|
---|
235 |
|
---|
236 | if (value === undefined) continue;
|
---|
237 | if (interrupt(value)) return;
|
---|
238 |
|
---|
239 | format(append, value);
|
---|
240 | }
|
---|
241 |
|
---|
242 | append(false, (len > 1 ? elem.indents : '')
|
---|
243 | + (elem.name ? '</' + elem.name + '>' : '')
|
---|
244 | + (elem.indent && !end ? '\n' : ''));
|
---|
245 |
|
---|
246 | if (end) {
|
---|
247 | end();
|
---|
248 | }
|
---|
249 | }
|
---|
250 |
|
---|
251 | function interrupt(value) {
|
---|
252 | if (value.interrupt) {
|
---|
253 | value.interrupt.append = append;
|
---|
254 | value.interrupt.end = proceed;
|
---|
255 | value.interrupt = false;
|
---|
256 | append(true);
|
---|
257 | return true;
|
---|
258 | }
|
---|
259 | return false;
|
---|
260 | }
|
---|
261 |
|
---|
262 | append(false, elem.indents
|
---|
263 | + (elem.name ? '<' + elem.name : '')
|
---|
264 | + (elem.attributes.length ? ' ' + elem.attributes.join(' ') : '')
|
---|
265 | + (len ? (elem.name ? '>' : '') : (elem.name ? '/>' : ''))
|
---|
266 | + (elem.indent && len > 1 ? '\n' : ''));
|
---|
267 |
|
---|
268 | if (!len) {
|
---|
269 | return append(false, elem.indent ? '\n' : '');
|
---|
270 | }
|
---|
271 |
|
---|
272 | if (!interrupt(elem)) {
|
---|
273 | proceed();
|
---|
274 | }
|
---|
275 | }
|
---|
276 |
|
---|
277 | function attribute(key, value) {
|
---|
278 | return key + '=' + '"' + escapeForXML(value) + '"';
|
---|
279 | }
|
---|
280 |
|
---|
281 | module.exports = xml;
|
---|
282 | module.exports.element = module.exports.Element = element;
|
---|