├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bench.coffee ├── index.js ├── lib ├── main.coffee ├── toJSON.coffee └── toXML.coffee ├── package.json └── test ├── mocha.opts ├── toJSON.coffee └── toXML.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | build 5 | *.node -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | build 5 | *.node -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.7 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Fractal 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![status](https://secure.travis-ci.org/wearefractal/xmlson.png?branch=master) 2 | 3 | ## Information 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
Packagexmlson
DescriptionXML-JSON translation
Node Version>= 0.4
18 | 19 | ## Dependencies 20 | 21 | xmlson uses ltx which uses libexpat - a super fast native XML parser. You may need to install it if your OS does not have it installed already. 22 | 23 | ``` 24 | $ sudo apt-get install libexpat1 libexpat1-dev 25 | ``` 26 | 27 | ## Usage 28 | 29 | ### Synchronous 30 | 31 | ```coffee-script 32 | xmlson = require 'xmlson' 33 | 34 | myObject = xmlson.toJSON '' 35 | # myObject = {cool:{whatever:[{"@name":"ya"}]} 36 | 37 | myString = xmlson.toXML {cool:{whatever:[{"@name":"ya"}]}} 38 | # myString = 39 | ``` 40 | 41 | ### Asynchronous 42 | 43 | ```coffee-script 44 | xmlson = require 'xmlson' 45 | 46 | xmlson.toJSON '', (err, myObject) -> 47 | # myObject = {cool:{whatever:[{"@name":"ya"}]} 48 | 49 | xmlson.toXML {cool:{whatever:[{"@name":"ya"}]}}, (err, myString) -> 50 | # myString = 51 | ``` 52 | 53 | ## Benchmarks 54 | 55 | These were done on a laptop with an i7 - you will probably get much better results on an actual server. 56 | 57 | Summary: 58 | 59 | A complex JSON object can be turned into an XML string in 0.052ms (avg) 60 | 61 | A complex XML string can be turned into a JSON object in 0.015ms (avg) 62 | 63 | ``` 64 | xmlson.toJSON(tiny) x 36,903 ops/sec ±5.34% (50 runs sampled) 65 | xmlson.toJSON(simple) x 29,429 ops/sec ±3.82% (51 runs sampled) 66 | xmlson.toJSON(standard) x 12,098 ops/sec ±2.30% (53 runs sampled) 67 | xmlson.toJSON(complex) x 12,932 ops/sec ±1.85% (54 runs sampled) 68 | xmlson.toXML(tiny) x 2,034,542 ops/sec ±4.60% (60 runs sampled) 69 | xmlson.toXML(simple) x 230,857 ops/sec ±0.26% (63 runs sampled) 70 | xmlson.toXML(standard) x 62,608 ops/sec ±1.38% (62 runs sampled) 71 | xmlson.toXML(complex) x 36,974 ops/sec ±0.87% (60 runs sampled) 72 | ``` 73 | 74 | ## LICENSE 75 | 76 | (MIT License) 77 | 78 | Copyright (c) 2012 Fractal 79 | 80 | Permission is hereby granted, free of charge, to any person obtaining 81 | a copy of this software and associated documentation files (the 82 | "Software"), to deal in the Software without restriction, including 83 | without limitation the rights to use, copy, modify, merge, publish, 84 | distribute, sublicense, and/or sell copies of the Software, and to 85 | permit persons to whom the Software is furnished to do so, subject to 86 | the following conditions: 87 | 88 | The above copyright notice and this permission notice shall be 89 | included in all copies or substantial portions of the Software. 90 | 91 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 92 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 93 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 94 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 95 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 96 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 97 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /bench.coffee: -------------------------------------------------------------------------------- 1 | xmlson = require './' 2 | Benchmark = require 'benchmark' 3 | 4 | tiny = "" 5 | simple = "quux" 6 | standard = 'RufuslabradorMartywhippet' 7 | complex = '12/01/2011' 8 | 9 | tinyjs = xmlson.toJSON tiny 10 | simplejs = xmlson.toJSON simple 11 | standardjs = xmlson.toJSON standard 12 | complexjs = xmlson.toJSON complex 13 | 14 | 15 | suite = new Benchmark.Suite 'xmlson' 16 | suite.on "error", (event, bench) -> throw bench.error 17 | suite.on "cycle", (event, bench) -> console.log String bench 18 | 19 | suite.add "xmlson.toJSON(tiny)", -> xmlson.toJSON tiny 20 | suite.add "xmlson.toJSON(simple)", -> xmlson.toJSON simple 21 | suite.add "xmlson.toJSON(standard)", -> xmlson.toJSON standard 22 | suite.add "xmlson.toJSON(complex)", -> xmlson.toJSON complex 23 | 24 | 25 | suite.add "xmlson.toXML(tiny)", -> xmlson.toXML tinyjs 26 | suite.add "xmlson.toXML(simple)", -> xmlson.toXML simplejs 27 | suite.add "xmlson.toXML(standard)", -> xmlson.toXML standardjs 28 | suite.add "xmlson.toXML(complex)", -> xmlson.toXML complexjs 29 | suite.run() 30 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('coffee-script'); 2 | module.exports = require('./lib/main'); -------------------------------------------------------------------------------- /lib/main.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | toJSON: require './toJSON' 3 | toXML: require './toXML' -------------------------------------------------------------------------------- /lib/toJSON.coffee: -------------------------------------------------------------------------------- 1 | ltx = require 'ltx' 2 | async = require 'async' 3 | 4 | createObjectAsync = (input, cb) -> 5 | output = {} 6 | 7 | attrs = (done) -> 8 | addAttribute = (key, done) -> 9 | value = input.attrs[key] 10 | output["@#{key}"] = value 11 | done() 12 | async.forEach Object.keys(input.attrs), addAttribute, done 13 | 14 | children = (done) -> 15 | addChild = (child, done) -> 16 | if typeof child is 'string' # Text value for element 17 | output.text = child 18 | done() 19 | else if typeof child is 'object' # Actual child element 20 | createObjectAsync child, (err, res) -> 21 | return done err if err? 22 | output[child.name] ?= [] 23 | output[child.name].push res 24 | done() 25 | async.forEach input.children, addChild, done 26 | 27 | async.parallel [attrs, children], (err) -> cb err, output 28 | 29 | createObject = (input) -> 30 | output = {} 31 | output["@#{key}"] = value for key, value of input.attrs # Attributes 32 | for child in input.children # Children 33 | if typeof child is 'string' # Text value for element 34 | output.text = child 35 | else if typeof child is 'object' # Actual child element 36 | output[child.name] ?= [] 37 | output[child.name].push createObject child 38 | return output 39 | 40 | toJSON = (input, cb) -> 41 | return null unless input? 42 | input = ltx.parse input 43 | return null unless input instanceof ltx.Element 44 | root = {} 45 | if cb? 46 | createObjectAsync input, (err, res) -> 47 | root[input.name] = res 48 | cb err, root 49 | return 50 | else 51 | root[input.name] = createObject input 52 | return root 53 | 54 | module.exports = toJSON 55 | -------------------------------------------------------------------------------- /lib/toXML.coffee: -------------------------------------------------------------------------------- 1 | ltx = require 'ltx' 2 | async = require 'async' 3 | 4 | createStanzaAsync = (input, output, cb) -> 5 | create = (key, done) -> 6 | value = input[key] 7 | if typeof value is 'string' 8 | if key is 'text' # Text value 9 | output.t value 10 | return done() 11 | else if key.indexOf('@') is 0 and key.length > 1 # Attribute 12 | output.attrs ?= {} 13 | [_,name] = key.split '@' 14 | output.attrs[name] = value 15 | return done() 16 | else # Child with text, no attributes 17 | output.c(key).t value 18 | return done() 19 | else if Array.isArray value # Multiple children 20 | addChild = (el, done) -> createStanzaAsync el, output.c(key), done 21 | async.forEach value, addChild, done 22 | return 23 | async.forEach Object.keys(input), create, (err) -> cb err, output 24 | return 25 | 26 | createStanza = (input, output) -> 27 | for key, value of input 28 | if typeof value is 'string' 29 | if key is 'text' # Text value 30 | output.t value 31 | else if key.indexOf('@') is 0 and key.length > 1 # Attribute 32 | output.attrs ?= {} 33 | [_,name] = key.split '@' 34 | output.attrs[name] = value 35 | else # Child with text, no attributes 36 | output.c(key).t value 37 | else if Array.isArray value # Multiple children 38 | createStanza el, output.c(key) for el in value 39 | return output 40 | 41 | toXML = (input, cb) -> 42 | return null unless input? 43 | return null unless typeof input is 'object' 44 | return null if Array.isArray input 45 | rootName = Object.keys(input)[0] 46 | output = new ltx.Element rootName 47 | input = input[rootName] 48 | if cb? 49 | createStanzaAsync input, output, (err, res) -> 50 | res = res.toString() if res? 51 | cb err, res 52 | return 53 | else 54 | return createStanza(input, output).toString() 55 | 56 | module.exports = toXML 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"xmlson", 3 | "description":"XML<->JSON translation", 4 | "version":"0.0.5", 5 | "homepage":"http://github.com/wearefractal/xmlson", 6 | "repository":"git://github.com/wearefractal/xmlson.git", 7 | "author":"Fractal (http://wearefractal.com/)", 8 | "main":"./index.js", 9 | 10 | "dependencies":{ 11 | "coffee-script":"*", 12 | "ltx":"*", 13 | "async":"*" 14 | }, 15 | "devDependencies":{ 16 | "mocha":"*", 17 | "should":"*", 18 | "benchmark":"*" 19 | }, 20 | "scripts":{ 21 | "test":"mocha --compilers coffee:coffee-script" 22 | }, 23 | "engines":{ 24 | "node":">= 0.4.0" 25 | }, 26 | "licenses":[ 27 | { 28 | "type":"MIT", 29 | "url":"http://github.com/wearefractal/xmlson/raw/master/LICENSE" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec -------------------------------------------------------------------------------- /test/toJSON.coffee: -------------------------------------------------------------------------------- 1 | xmlson = require '../' 2 | should = require 'should' 3 | require 'mocha' 4 | 5 | describe 'toJSON()', -> 6 | it 'simple', (done) -> 7 | input = 'RufuslabradorMartywhippet' 8 | expected = 9 | animals: 10 | "@type":"pets" 11 | "@test":"yeah" 12 | dog: [ 13 | { 14 | "@type":"dumb" 15 | name: [ 16 | text:"Rufus" 17 | ] 18 | breed: [ 19 | text:"labrador" 20 | ] 21 | } 22 | { 23 | name: [ 24 | text:"Marty" 25 | ] 26 | breed: [ 27 | text:"whippet" 28 | ] 29 | } 30 | {} 31 | ], 32 | cat: [ 33 | { 34 | "@name":"Matilda" 35 | } 36 | ] 37 | output = xmlson.toJSON input 38 | should.exist output 39 | output.should.eql expected 40 | 41 | xmlson.toJSON input, (err, res) -> 42 | should.not.exist err 43 | should.exist res 44 | res.should.eql expected 45 | done() 46 | 47 | it 'standard', (done) -> 48 | input = '12/01/2011' 49 | expected = 50 | iq: 51 | "@id":"123456" 52 | "@type":"set" 53 | "@to":"call57@test.net/1" 54 | "@from":"9001@cool.com/1" 55 | say: [ 56 | { 57 | "@xmlns":"urn:xmpp:tropo:say:1" 58 | "@voice":"allison" 59 | audio: [ 60 | { 61 | "@src":"http://acme.com/greeting.mp3" 62 | text:"Thanks for calling ACME company" 63 | } 64 | { 65 | "@src":"http://acme.com/package-shipped.mp3" 66 | text:"Your package was shipped on" 67 | } 68 | ] 69 | "say-as": [ 70 | { 71 | "@interpret-as":"date" 72 | text:"12/01/2011" 73 | } 74 | ] 75 | } 76 | ] 77 | output = xmlson.toJSON input 78 | should.exist output 79 | output.should.eql expected 80 | done() -------------------------------------------------------------------------------- /test/toXML.coffee: -------------------------------------------------------------------------------- 1 | xmlson = require '../' 2 | should = require 'should' 3 | require 'mocha' 4 | 5 | describe 'toXML()', -> 6 | it 'standard', (done) -> 7 | expected = 'RufuslabradorMartywhippet' 8 | input = 9 | animals: 10 | "@type":"pets" 11 | "@test":"yeah" 12 | dog: [ 13 | { 14 | "@type":"dumb" 15 | name: [ 16 | text:"Rufus" 17 | ] 18 | breed: [ 19 | text:"labrador" 20 | ] 21 | } 22 | { 23 | name: [ 24 | text:"Marty" 25 | ] 26 | breed: [ 27 | text:"whippet" 28 | ] 29 | } 30 | {} 31 | ], 32 | cat: [ 33 | { 34 | "@name":"Matilda" 35 | } 36 | ] 37 | output = xmlson.toXML input 38 | should.exist output 39 | output.should.eql expected 40 | 41 | xmlson.toXML input, (err, res) -> 42 | should.not.exist err 43 | should.exist res 44 | res.should.eql expected 45 | done() 46 | 47 | it 'complex', (done) -> 48 | expected = '12/01/2011' 49 | input = 50 | iq: 51 | "@id":"123456" 52 | "@type":"set" 53 | "@to":"call57@test.net/1" 54 | "@from":"9001@cool.com/1" 55 | say: [ 56 | { 57 | "@xmlns":"urn:xmpp:tropo:say:1" 58 | "@voice":"allison" 59 | audio: [ 60 | { 61 | "@src":"http://acme.com/greeting.mp3" 62 | text:"Thanks for calling ACME company" 63 | } 64 | { 65 | "@src":"http://acme.com/package-shipped.mp3" 66 | text:"Your package was shipped on" 67 | } 68 | ] 69 | "say-as": [ 70 | { 71 | "@interpret-as":"date" 72 | text:"12/01/2011" 73 | } 74 | ] 75 | } 76 | ] 77 | output = xmlson.toXML input 78 | should.exist output 79 | output.should.equal expected 80 | 81 | xmlson.toXML input, (err, res) -> 82 | should.not.exist err 83 | should.exist res 84 | res.should.eql expected 85 | done() --------------------------------------------------------------------------------