[d24f17c] | 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;
|
---|