├── .gitignore ├── .travis.yml ├── LICENSE ├── examples ├── examples.js └── server.js ├── lib ├── escapeForXML.js └── xml.js ├── package.json ├── readme.md └── test └── xml.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - node 5 | - "5" 6 | - "4" 7 | - "0.10" 8 | - "0.12" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2011-2017 Dylan Greene 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /examples/examples.js: -------------------------------------------------------------------------------- 1 | var xml = require('../lib/xml'); 2 | 3 | console.log('===== Example 1 ===='); 4 | var example1 = { url: 'http://www.google.com/search?aq=f&sourceid=chrome&ie=UTF-8&q=opower' }; 5 | console.log(xml(example1)); 6 | //http://www.google.com/search?aq=f&sourceid=chrome&ie=UTF-8&q=opower 7 | 8 | console.log('\n===== Example 2 ===='); 9 | var example2 = [ { url: { _attr: { hostname: 'www.google.com', path: '/search?aq=f&sourceid=chrome&ie=UTF-8&q=opower' } } } ]; 10 | console.log(xml(example2)); 11 | // 12 | 13 | console.log('\n===== Example 3 ===='); 14 | var example3 = [ { toys: [ { toy: 'Transformers' } , { toy: 'GI Joe' }, { toy: 'He-man' } ] } ]; 15 | console.log(xml(example3)); 16 | //TransformersGI JoeHe-man 17 | console.log(xml(example3, { indent: true })); 18 | /* 19 | 20 | Transformers 21 | GI Joe 22 | He-man 23 | 24 | */ 25 | 26 | console.log('\n===== Example 4 ===='); 27 | var example4 = [ { toys: [ { _attr: { decade: '80s', locale: 'US'} }, { toy: 'Transformers' } , { toy: 'GI Joe' }, { toy: 'He-man' } ] } ]; 28 | console.log(xml(example4, { indent: true })); 29 | /* 30 | 31 | Transformers 32 | GI Joe 33 | He-man 34 | 35 | */ 36 | 37 | console.log('\n===== Example 5 ===='); 38 | var example5 = [ { toys: [ { _attr: { decade: '80s', locale: 'US'} }, { toy: 'Transformers' } , { toy: [ { _attr: { knowing: 'half the battle' } }, 'GI Joe'] }, { toy: [ { name: 'He-man' }, { description: { _cdata: 'Master of the Universe!'} } ] } ] } ]; 39 | console.log(xml(example5, { indent: true, declaration: true })); 40 | /* 41 | TransformersGI JoeHe-man 42 | 43 | Transformers 44 | GI Joe 45 | He-man 46 | 47 | 48 | Transformers 49 | GI Joe 50 | He-man 51 | 52 | 53 | Transformers 54 | 55 | GI Joe 56 | 57 | 58 | He-man 59 | Master of the Universe!]]> 60 | 61 | 62 | */ 63 | 64 | console.log('\n===== Example 6 ===='); 65 | var elem = xml.Element({ _attr: { decade: '80s', locale: 'US'} }); 66 | var xmlStream = xml({ toys: elem }, { indent: true } ); 67 | xmlStream.on('data', function (chunk) {console.log("data:", chunk)}); 68 | elem.push({ toy: 'Transformers' }); 69 | elem.push({ toy: 'GI Joe' }); 70 | elem.push({ toy: [{name:'He-man'}] }); 71 | elem.close(); 72 | 73 | /* 74 | data: 75 | data: Transformers 76 | data: GI Joe 77 | data: 78 | He-man 79 | 80 | data: 81 | */ 82 | -------------------------------------------------------------------------------- /examples/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'), 2 | XML = require('../lib/xml'); 3 | 4 | var server = http.createServer(function(req, res) { 5 | res.writeHead(200, {"Content-Type": "text/xml"}); 6 | 7 | var elem = XML.Element({ _attr: { decade: '80s', locale: 'US'} }); 8 | var xml = XML({ toys: elem }, {indent:true, stream:true}); 9 | 10 | res.write('\n'); 11 | 12 | xml.pipe(res); 13 | 14 | process.nextTick(function () { 15 | elem.push({ toy: 'Transformers' }); 16 | elem.push({ toy: 'GI Joe' }); 17 | elem.push({ toy: [{name:'He-man'}] }); 18 | elem.close(); 19 | }); 20 | 21 | }); 22 | 23 | server.listen(parseInt(process.env.PORT) || 3000); 24 | console.log("server listening on port %d …", server.address().port); 25 | -------------------------------------------------------------------------------- /lib/escapeForXML.js: -------------------------------------------------------------------------------- 1 | 2 | var XML_CHARACTER_MAP = { 3 | '&': '&', 4 | '"': '"', 5 | "'": ''', 6 | '<': '<', 7 | '>': '>' 8 | }; 9 | 10 | function escapeForXML(string) { 11 | return string && string.replace 12 | ? string.replace(/([&"<>'])/g, function(str, item) { 13 | return XML_CHARACTER_MAP[item]; 14 | }) 15 | : string; 16 | } 17 | 18 | module.exports = escapeForXML; 19 | -------------------------------------------------------------------------------- /lib/xml.js: -------------------------------------------------------------------------------- 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 | ('/g, ']]]]>') + ']]>' 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 ? '' : '') 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; 283 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xml", 3 | "version": "1.0.1", 4 | "description": "Fast and simple xml generator. Supports attributes, CDATA, etc. Includes tests and examples.", 5 | "homepage": "http://github.com/dylang/node-xml", 6 | "keywords": [ 7 | "xml", 8 | "create", 9 | "builder", 10 | "json", 11 | "simple" 12 | ], 13 | "author": "Dylan Greene (https://github.com/dylang)", 14 | "contributors": [ 15 | "Dylan Greene (https://github.com/dylang)", 16 | "Dodo (https://github.com/dodo)", 17 | "Felix Geisendrfer (felix@debuggable.com)", 18 | "Mithgol", 19 | "carolineBda (https://github.com/carolineBda)", 20 | "Eric Vantillard https://github.com/evantill", 21 | "Sean Dwyer https://github.com/reywood" 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "http://github.com/dylang/node-xml" 26 | }, 27 | "bugs": { 28 | "url": "http://github.com/dylang/node-xml/issues" 29 | }, 30 | "devDependencies": { 31 | "ava": "^0.11.0" 32 | }, 33 | "scripts": { 34 | "test": "ava" 35 | }, 36 | "main": "lib/xml.js", 37 | "license": "MIT" 38 | } 39 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # xml [![Build Status](https://api.travis-ci.org/dylang/node-xml.svg)](http://travis-ci.org/dylang/node-xml) 2 | 3 | [![NPM](https://nodei.co/npm/xml.png?downloads=true)](https://nodei.co/npm/xml/) 4 | 5 | > Fast and simple Javascript-based XML generator/builder for Node projects. 6 | 7 | ## Install 8 | 9 | $ npm install xml 10 | 11 | ## API 12 | 13 | ### `xml(xmlObject, options)` 14 | 15 | Returns a `XML` string. 16 | 17 | ```js 18 | var xml = require('xml'); 19 | var xmlString = xml(xmlObject, options); 20 | ``` 21 | 22 | #### `xmlObject` 23 | 24 | `xmlObject` is a normal JavaScript Object/JSON object that defines the data for the XML string. 25 | 26 | Keys will become tag names. 27 | Values can be an `array of xmlObjects` or a value such as a `string` or `number`. 28 | 29 | ```js 30 | xml({a: 1}) === '1' 31 | xml({nested: [{ keys: [{ fun: 'hi' }]}]}) === 'hi' 32 | ``` 33 | 34 | There are two special keys: 35 | 36 | `_attr` 37 | 38 | Set attributes using a hash of key/value pairs. 39 | 40 | ```js 41 | xml({a: [{ _attr: { attributes: 'are fun', too: '!' }}, 1]}) === '1' 42 | ```` 43 | `_cdata` 44 | 45 | Value of `_cdata` is wrapped in xml `![CDATA[]]` so the data does not need to be escaped. 46 | 47 | ```js 48 | xml({a: { _cdata: "i'm not escaped: !"}}) === '!]]>' 49 | ``` 50 | 51 | Mixed together: 52 | ```js 53 | xml({a: { _attr: { attr:'hi'}, _cdata: "I'm not escaped" }}) === '' 54 | ``` 55 | 56 | #### `options` 57 | 58 | `indent` _optional_ **string** What to use as a tab. Defaults to no tabs (compressed). 59 | For example you can use `'\t'` for tab character, or `' '` for two-space tabs. 60 | 61 | `stream` Return the result as a `stream`. 62 | 63 | **Stream Example** 64 | 65 | ```js 66 | var elem = xml.element({ _attr: { decade: '80s', locale: 'US'} }); 67 | var stream = xml({ toys: elem }, { stream: true }); 68 | stream.on('data', function (chunk) {console.log("data:", chunk)}); 69 | elem.push({ toy: 'Transformers' }); 70 | elem.push({ toy: 'GI Joe' }); 71 | elem.push({ toy: [{name:'He-man'}] }); 72 | elem.close(); 73 | 74 | /* 75 | result: 76 | data: 77 | data: Transformers 78 | data: GI Joe 79 | data: 80 | He-man 81 | 82 | data: 83 | */ 84 | ``` 85 | 86 | `Declaration` _optional_ Add default xml declaration as first node. 87 | 88 | _options_ are: 89 | * encoding: 'value' 90 | * standalone: 'value' 91 | 92 | **Declaration Example** 93 | 94 | ```js 95 | xml([ { a: 'test' }], { declaration: true }) 96 | //result: 'test' 97 | 98 | xml([ { a: 'test' }], { declaration: { standalone: 'yes', encoding: 'UTF-16' }}) 99 | //result: 'test' 100 | ``` 101 | 102 | ## Examples 103 | 104 | **Simple Example** 105 | 106 | ```js 107 | var example1 = [ { url: 'http://www.google.com/search?aq=f&sourceid=chrome&ie=UTF-8&q=opower' } ]; 108 | console.log(XML(example1)); 109 | //http://www.google.com/search?aq=f&sourceid=chrome&ie=UTF-8&q=opower 110 | ``` 111 | 112 | **Example with attributes** 113 | 114 | ```js 115 | var example2 = [ { url: { _attr: { hostname: 'www.google.com', path: '/search?aq=f&sourceid=chrome&ie=UTF-8&q=opower' } } } ]; 116 | console.log(XML(example2)); 117 | //result: 118 | ``` 119 | 120 | **Example with array of same-named elements and nice formatting** 121 | 122 | ```js 123 | var example3 = [ { toys: [ { toy: 'Transformers' } , { toy: 'GI Joe' }, { toy: 'He-man' } ] } ]; 124 | console.log(XML(example3)); 125 | //result: TransformersGI JoeHe-man 126 | console.log(XML(example3, true)); 127 | /* 128 | result: 129 | 130 | Transformers 131 | GI Joe 132 | He-man 133 | 134 | */ 135 | ``` 136 | 137 | **More complex example** 138 | 139 | ```js 140 | var example4 = [ { toys: [ { _attr: { decade: '80s', locale: 'US'} }, { toy: 'Transformers' } , { toy: 'GI Joe' }, { toy: 'He-man' } ] } ]; 141 | console.log(XML(example4, true)); 142 | /* 143 | result: 144 | 145 | Transformers 146 | GI Joe 147 | He-man 148 | 149 | */ 150 | ``` 151 | 152 | **Even more complex example** 153 | 154 | ```js 155 | var example5 = [ { toys: [ { _attr: { decade: '80s', locale: 'US'} }, { toy: 'Transformers' } , { toy: [ { _attr: { knowing: 'half the battle' } }, 'GI Joe'] }, { toy: [ { name: 'He-man' }, { description: { _cdata: 'Master of the Universe!'} } ] } ] } ]; 156 | console.log(XML(example5, true)); 157 | /* 158 | result: 159 | 160 | Transformers 161 | 162 | GI Joe 163 | 164 | 165 | He-man 166 | Master of the Universe!]]> 167 | 168 | 169 | */ 170 | ``` 171 | 172 | ## Tests 173 | 174 | Tests included use [AVA](https://ava.li). Use `npm test` to run the tests. 175 | 176 | $ npm test 177 | 178 | ## Examples 179 | 180 | There are examples in the examples directory. 181 | 182 | # Contributing 183 | 184 | Contributions to the project are welcome. Feel free to fork and improve. I accept pull requests when tests are included. 185 | -------------------------------------------------------------------------------- /test/xml.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import xml from '../lib/xml'; 3 | 4 | test('no elements', t => { 5 | t.is(xml(), ''); 6 | t.is(xml([]), ''); 7 | t.is(xml('test'), 'test'); 8 | t.is(xml('scotch & whisky'), 'scotch & whisky'); 9 | t.is(xml('bob\'s escape character'), 'bob's escape character'); 10 | }); 11 | 12 | test('simple options', t => { 13 | t.is(xml([{a: {}}]), ''); 14 | t.is(xml([{a: null}]), ''); 15 | t.is(xml([{a: []}]), ''); 16 | t.is(xml([{a: -1}]), '-1'); 17 | t.is(xml([{a: false}]), 'false'); 18 | t.is(xml([{a: 'test'}]), 'test'); 19 | t.is(xml({a: {}}), ''); 20 | t.is(xml({a: null}), ''); 21 | t.is(xml({a: []}), ''); 22 | t.is(xml({a: -1}), '-1'); 23 | t.is(xml({a: false}), 'false'); 24 | t.is(xml({a: 'test'}), 'test'); 25 | t.is(xml([{a: 'test'}, {b: 123}, {c: -0.5}]), 'test123-0.5'); 26 | }); 27 | 28 | test('deeply nested objects', t => { 29 | t.is(xml([{a: [{b: [{c: 1}, {c: 2}, {c: 3}]}]}]), '123'); 30 | }); 31 | 32 | test('indents property', t => { 33 | t.is(xml([{a: [{b: [{c: 1}, {c: 2}, {c: 3}]}]}], true), '\n \n 1\n 2\n 3\n \n'); 34 | t.is(xml([{a: [{b: [{c: 1}, {c: 2}, {c: 3}]}]}], ' '), '\n \n 1\n 2\n 3\n \n'); 35 | t.is(xml([{a: [{b: [{c: 1}, {c: 2}, {c: 3}]}]}], '\t'), '\n\t\n\t\t1\n\t\t2\n\t\t3\n\t\n'); 36 | t.is(xml({guid: [{_attr: {premalink: true}}, 'content']}, true), 'content'); 37 | }); 38 | 39 | test('supports xml attributes', t => { 40 | t.is(xml([{b: {_attr: {}}}]), ''); 41 | t.is(xml([{ 42 | a: { 43 | _attr: { 44 | attribute1: 'some value', 45 | attribute2: 12345 46 | } 47 | } 48 | }]), ''); 49 | t.is(xml([{ 50 | a: [{ 51 | _attr: { 52 | attribute1: 'some value', 53 | attribute2: 12345 54 | } 55 | }] 56 | }]), ''); 57 | t.is(xml([{ 58 | a: [{ 59 | _attr: { 60 | attribute1: 'some value', 61 | attribute2: 12345 62 | } 63 | }, 'content'] 64 | }]), 'content'); 65 | }); 66 | 67 | test('supports cdata', t => { 68 | t.is(xml([{a: {_cdata: 'This is some CDATA'}}]), 'CDATA]]>'); 69 | t.is(xml([{ 70 | a: { 71 | _attr: {attribute1: 'some value', attribute2: 12345}, 72 | _cdata: 'This is some CDATA' 73 | } 74 | }]), 'CDATA]]>'); 75 | t.is(xml([{a: {_cdata: 'This is some CDATA with ]]> and then again ]]>'}}]), 'CDATA with ]]]]> and then again ]]]]>]]>'); 76 | }); 77 | 78 | test('supports encoding', t => { 79 | t.is(xml([{ 80 | a: [{ 81 | _attr: { 82 | anglebrackets: 'this is strong', 83 | url: 'http://google.com?s=opower&y=fun' 84 | } 85 | }, 'text'] 86 | }]), 'text'); 87 | }); 88 | 89 | test('supports stream interface', t => { 90 | const elem = xml.element({_attr: {decade: '80s', locale: 'US'}}); 91 | const xmlStream = xml({toys: elem}, {stream: true}); 92 | const results = ['', 'Transformers', 'He-man', 'GI Joe', '']; 93 | 94 | elem.push({toy: 'Transformers'}); 95 | elem.push({toy: [{name: 'He-man'}]}); 96 | elem.push({toy: 'GI Joe'}); 97 | elem.close(); 98 | 99 | xmlStream.on('data', stanza => { 100 | t.is(stanza, results.shift()); 101 | }); 102 | 103 | return new Promise( (resolve, reject) => { 104 | xmlStream.on('close', () => { 105 | t.same(results, []); 106 | resolve(); 107 | }); 108 | xmlStream.on('error', reject); 109 | }); 110 | }); 111 | 112 | test('streams end properly', t => { 113 | const elem = xml.element({ _attr: { decade: '80s', locale: 'US'} }); 114 | const xmlStream = xml({ toys: elem }, { stream: true }); 115 | 116 | let gotData; 117 | 118 | t.plan(7); 119 | 120 | elem.push({ toy: 'Transformers' }); 121 | elem.push({ toy: 'GI Joe' }); 122 | elem.push({ toy: [{name:'He-man'}] }); 123 | elem.close(); 124 | 125 | xmlStream.on('data', data => { 126 | t.ok(data); 127 | gotData = true; 128 | }); 129 | 130 | xmlStream.on('end', () => { 131 | t.ok(gotData); 132 | }); 133 | 134 | return new Promise( (resolve, reject) => { 135 | xmlStream.on('close', () => { 136 | t.ok(gotData); 137 | resolve(); 138 | }); 139 | xmlStream.on('error', reject); 140 | }); 141 | }); 142 | 143 | test('xml declaration options', t => { 144 | t.is(xml([{a: 'test'}], {declaration: true}), 'test'); 145 | t.is(xml([{a: 'test'}], {declaration: {encoding: 'foo'}}), 'test'); 146 | t.is(xml([{a: 'test'}], {declaration: {standalone: 'yes'}}), 'test'); 147 | t.is(xml([{a: 'test'}], {declaration: false}), 'test'); 148 | t.is(xml([{a: 'test'}], {declaration: true, indent: '\n'}), '\ntest'); 149 | t.is(xml([{a: 'test'}], {}), 'test'); 150 | }); 151 | --------------------------------------------------------------------------------