1 | var common = require('./common');
|
2 |
|
3 | function validateOptions(userOptions) {
|
4 | var options = common.copyOptions(userOptions);
|
5 | common.ensureFlagExists('ignoreDeclaration', options);
|
6 | common.ensureFlagExists('ignoreInstruction', options);
|
7 | common.ensureFlagExists('ignoreAttributes', options);
|
8 | common.ensureFlagExists('ignoreText', options);
|
9 | common.ensureFlagExists('ignoreComment', options);
|
10 | common.ensureFlagExists('ignoreCdata', options);
|
11 | common.ensureFlagExists('ignoreDoctype', options);
|
12 | common.ensureFlagExists('compact', options);
|
13 | common.ensureFlagExists('indentText', options);
|
14 | common.ensureFlagExists('indentCdata', options);
|
15 | common.ensureFlagExists('indentAttributes', options);
|
16 | common.ensureFlagExists('indentInstruction', options);
|
17 | common.ensureFlagExists('fullTagEmptyElement', options);
|
18 | common.ensureSpacesExists(options);
|
19 | if (typeof options.spaces === 'number') {
|
20 | options.spaces = Array(options.spaces + 1).join(' ');
|
21 | }
|
22 | common.ensureKeyExists('declaration', options);
|
23 | common.ensureKeyExists('instruction', options);
|
24 | common.ensureKeyExists('attributes', options);
|
25 | common.ensureKeyExists('text', options);
|
26 | common.ensureKeyExists('comment', options);
|
27 | common.ensureKeyExists('cdata', options);
|
28 | common.ensureKeyExists('doctype', options);
|
29 | common.ensureKeyExists('type', options);
|
30 | common.ensureKeyExists('name', options);
|
31 | common.ensureKeyExists('elements', options);
|
32 | return options;
|
33 | }
|
34 |
|
35 | function writeIndentation(options, depth, firstLine) {
|
36 | return (!firstLine && options.spaces ? '\n' : '') + Array(depth + 1).join(options.spaces);
|
37 | }
|
38 |
|
39 | function writeAttributes(attributes, options, depth) {
|
40 | if (options.ignoreAttributes) {
|
41 | return '';
|
42 | }
|
43 | var key, attr, result = '';
|
44 | for (key in attributes) {
|
45 | if (attributes.hasOwnProperty(key)) {
|
46 | attr = '' + attributes[key];
|
47 | result += (options.spaces && options.indentAttributes? writeIndentation(options, depth+1, false) : ' ')
|
48 | result += key + '="' + attr.replace(/"/g, '"') + '"';
|
49 | }
|
50 | }
|
51 | if (attributes && Object.keys(attributes).length && options.spaces && options.indentAttributes) {
|
52 | result += writeIndentation(options, depth, false);
|
53 | }
|
54 | return result;
|
55 | }
|
56 |
|
57 | function writeDeclaration(declaration, options, depth) {
|
58 | return options.ignoreDeclaration ? '' : '<?xml' + writeAttributes(declaration[options.attributesKey], options, depth) + '?>';
|
59 | }
|
60 |
|
61 | function writeInstruction(instruction, options, depth) {
|
62 | if (options.ignoreInstruction) {
|
63 | return '';
|
64 | }
|
65 | var key;
|
66 | for (key in instruction) {
|
67 | if (instruction.hasOwnProperty(key)) {
|
68 | break;
|
69 | }
|
70 | }
|
71 | if (typeof instruction[key] === 'object') {
|
72 | return '<?' + key + writeAttributes(instruction[key][options.attributesKey], options, depth) + '?>';
|
73 | } else {
|
74 | return '<?' + key + (instruction[key] ? ' ' + instruction[key] : '') + '?>';
|
75 | }
|
76 | }
|
77 |
|
78 | function writeComment(comment, options) {
|
79 | return options.ignoreComment ? '' : '<!--' + comment + '-->';
|
80 | }
|
81 |
|
82 | function writeCdata(cdata, options) {
|
83 | return options.ignoreCdata ? '' : '<![CDATA[' + cdata + ']]>';
|
84 | }
|
85 |
|
86 | function writeDoctype(doctype, options) {
|
87 | return options.ignoreDoctype ? '' : '<!DOCTYPE ' + doctype + '>';
|
88 | }
|
89 |
|
90 | function writeText(text, options) {
|
91 | text = '' + text;
|
92 | text = text.replace(/&/g, '&');
|
93 | return options.ignoreText ? '' : text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
94 | }
|
95 |
|
96 | function hasContent(element, options) {
|
97 | var i;
|
98 | if (element.elements && element.elements.length) {
|
99 | for (i = 0; i < element.elements.length; ++i) {
|
100 | switch (element.elements[i][options.typeKey]) {
|
101 | case 'text':
|
102 | if (options.indentText) {
|
103 | return true;
|
104 | }
|
105 | break;
|
106 | case 'cdata':
|
107 | if (options.indentCdata) {
|
108 | return true;
|
109 | }
|
110 | break;
|
111 | case 'instruction':
|
112 | if (options.indentInstruction) {
|
113 | return true;
|
114 | }
|
115 | break;
|
116 | case 'doctype':
|
117 | case 'comment':
|
118 | case 'element':
|
119 | return true;
|
120 | default:
|
121 | return true;
|
122 | }
|
123 | }
|
124 | }
|
125 | return false;
|
126 | }
|
127 |
|
128 | function writeElement(element, options, depth) {
|
129 | var xml = '';
|
130 | xml += '<' + element.name;
|
131 | if (element[options.attributesKey]) {
|
132 | xml += writeAttributes(element[options.attributesKey], options, depth);
|
133 | }
|
134 | if (options.fullTagEmptyElement || (element[options.elementsKey] && element[options.elementsKey].length) || (element[options.attributesKey] && element[options.attributesKey]['xml:space'] === 'preserve')) {
|
135 | xml += '>';
|
136 | if (element[options.elementsKey] && element[options.elementsKey].length) {
|
137 | xml += writeElements(element[options.elementsKey], options, depth + 1);
|
138 | }
|
139 | xml += options.spaces && hasContent(element, options) ? '\n' + Array(depth + 1).join(options.spaces) : '';
|
140 | xml += '</' + element.name + '>';
|
141 | } else {
|
142 | xml += '/>';
|
143 | }
|
144 | return xml;
|
145 | }
|
146 |
|
147 | function writeElements(elements, options, depth, firstLine) {
|
148 | return elements.reduce(function (xml, element) {
|
149 | var indent = writeIndentation(options, depth, firstLine && !xml);
|
150 | switch (element.type) {
|
151 | case 'element': return xml + indent + writeElement(element, options, depth);
|
152 | case 'comment': return xml + indent + writeComment(element[options.commentKey], options);
|
153 | case 'doctype': return xml + indent + writeDoctype(element[options.doctypeKey], options);
|
154 | case 'cdata': return xml + (options.indentCdata ? indent : '') + writeCdata(element[options.cdataKey], options);
|
155 | case 'text': return xml + (options.indentText ? indent : '') + writeText(element[options.textKey], options);
|
156 | case 'instruction':
|
157 | var instruction = {};
|
158 | instruction[element[options.nameKey]] = element[options.attributesKey] ? element : element[options.instructionKey];
|
159 | return xml + (options.indentInstruction ? indent : '') + writeInstruction(instruction, options, depth);
|
160 | }
|
161 | }, '');
|
162 | }
|
163 |
|
164 | function hasContentCompact(element, options, anyContent) {
|
165 | var key;
|
166 | for (key in element) {
|
167 | if (element.hasOwnProperty(key)) {
|
168 | switch (key) {
|
169 | case options.parentKey:
|
170 | case options.attributesKey:
|
171 | break;
|
172 | case options.textKey:
|
173 | if (options.indentText || anyContent) {
|
174 | return true;
|
175 | }
|
176 | break;
|
177 | case options.cdataKey:
|
178 | if (options.indentCdata || anyContent) {
|
179 | return true;
|
180 | }
|
181 | break;
|
182 | case options.instructionKey:
|
183 | if (options.indentInstruction || anyContent) {
|
184 | return true;
|
185 | }
|
186 | break;
|
187 | case options.doctypeKey:
|
188 | case options.commentKey:
|
189 | return true;
|
190 | default:
|
191 | return true;
|
192 | }
|
193 | }
|
194 | }
|
195 | return false;
|
196 | }
|
197 |
|
198 | function writeElementCompact(element, name, options, depth, indent) {
|
199 | var xml = '';
|
200 | if (name) {
|
201 | xml += '<' + name;
|
202 | if (element[options.attributesKey]) {
|
203 | xml += writeAttributes(element[options.attributesKey], options, depth);
|
204 | }
|
205 | if (options.fullTagEmptyElement || hasContentCompact(element, options, true) || element[options.attributesKey] && element[options.attributesKey]['xml:space'] === 'preserve') {
|
206 | xml += '>';
|
207 | } else {
|
208 | xml += '/>';
|
209 | return xml;
|
210 | }
|
211 | }
|
212 | xml += writeElementsCompact(element, options, depth + 1, false);
|
213 | if (name) {
|
214 | xml += (indent ? writeIndentation(options, depth, false) : '') + '</' + name + '>';
|
215 | }
|
216 | return xml;
|
217 | }
|
218 |
|
219 | function writeElementsCompact(element, options, depth, firstLine) {
|
220 | var i, key, nodes, xml = '';
|
221 | for (key in element) {
|
222 | if (element.hasOwnProperty(key)) {
|
223 | nodes = element[key] instanceof Array ? element[key] : [element[key]];
|
224 | for (i = 0; i < nodes.length; ++i) {
|
225 | switch (key) {
|
226 | case options.declarationKey: xml += writeDeclaration(nodes[i], options, depth); break;
|
227 | case options.instructionKey: xml += (options.indentInstruction ? writeIndentation(options, depth, firstLine) : '') + writeInstruction(nodes[i], options, depth); break;
|
228 | case options.attributesKey: case options.parentKey: break;
|
229 | case options.textKey: xml += (options.indentText ? writeIndentation(options, depth, firstLine) : '') + writeText(nodes[i], options); break;
|
230 | case options.cdataKey: xml += (options.indentCdata ? writeIndentation(options, depth, firstLine) : '') + writeCdata(nodes[i], options); break;
|
231 | case options.doctypeKey: xml += writeIndentation(options, depth, firstLine) + writeDoctype(nodes[i], options); break;
|
232 | case options.commentKey: xml += writeIndentation(options, depth, firstLine) + writeComment(nodes[i], options); break;
|
233 | default: xml += writeIndentation(options, depth, firstLine) + writeElementCompact(nodes[i], key, options, depth, hasContentCompact(nodes[i], options));
|
234 | }
|
235 | firstLine = firstLine && !xml;
|
236 | }
|
237 | }
|
238 | }
|
239 | return xml;
|
240 | }
|
241 |
|
242 | module.exports = function (js, options) {
|
243 | 'use strict';
|
244 | options = validateOptions(options);
|
245 | var xml = '';
|
246 | if (options.compact) {
|
247 | xml = writeElementsCompact(js, options, 0, true);
|
248 | } else {
|
249 | if (js[options.declarationKey]) {
|
250 | xml += writeDeclaration(js[options.declarationKey], options, 0);
|
251 | }
|
252 | if (js[options.elementsKey] && js[options.elementsKey].length) {
|
253 | xml += writeElements(js[options.elementsKey], options, 0, !xml);
|
254 | }
|
255 | }
|
256 | return xml;
|
257 | };
|