UNPKG

10.7 kBJavaScriptView Raw
1var common = require('./common');
2
3function 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
35function writeIndentation(options, depth, firstLine) {
36 return (!firstLine && options.spaces ? '\n' : '') + Array(depth + 1).join(options.spaces);
37}
38
39function 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]; // ensure Number and Boolean are converted to String
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
57function writeDeclaration(declaration, options, depth) {
58 return options.ignoreDeclaration ? '' : '<?xml' + writeAttributes(declaration[options.attributesKey], options, depth) + '?>';
59}
60
61function 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
78function writeComment(comment, options) {
79 return options.ignoreComment ? '' : '<!--' + comment + '-->';
80}
81
82function writeCdata(cdata, options) {
83 return options.ignoreCdata ? '' : '<![CDATA[' + cdata + ']]>';
84}
85
86function writeDoctype(doctype, options) {
87 return options.ignoreDoctype ? '' : '<!DOCTYPE ' + doctype + '>';
88}
89
90function writeText(text, options) {
91 text = '' + text; // ensure Number and Boolean are converted to String
92 text = text.replace(/&amp;/g, '&'); // desanitize to avoid double sanitization
93 return options.ignoreText ? '' : text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
94}
95
96function 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; // skip to next key
106 case 'cdata':
107 if (options.indentCdata) {
108 return true;
109 }
110 break; // skip to next key
111 case 'instruction':
112 if (options.indentInstruction) {
113 return true;
114 }
115 break; // skip to next key
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
128function 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
147function 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
164function 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; // skip to next key
172 case options.textKey:
173 if (options.indentText || anyContent) {
174 return true;
175 }
176 break; // skip to next key
177 case options.cdataKey:
178 if (options.indentCdata || anyContent) {
179 return true;
180 }
181 break; // skip to next key
182 case options.instructionKey:
183 if (options.indentInstruction || anyContent) {
184 return true;
185 }
186 break; // skip to next key
187 case options.doctypeKey:
188 case options.commentKey:
189 return true;
190 default:
191 return true;
192 }
193 }
194 }
195 return false;
196}
197
198function 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
219function 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; // skip
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
242module.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};