├── .gitignore ├── package.json ├── LICENSE ├── README.md ├── bin └── musicjson └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .npmignore 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "musicjson", 3 | "version": "0.2.1", 4 | "description": "MusicXML to MusicJSON bi-directional converter", 5 | "homepage": "https://github.com/saebekassebil/musicjson", 6 | "main": "index.js", 7 | "dependencies": { 8 | "commander": ">=1.1.0", 9 | "sax": ">=0.5.0", 10 | "xmlbuilder": ">=0.4.2" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/saebekassebil/musicjson.git" 15 | }, 16 | "preferGlobal": true, 17 | "bin": { 18 | "musicjson": "./bin/musicjson" 19 | }, 20 | "keywords": [ 21 | "musicjson", 22 | "musicxml", 23 | "music", 24 | "converter", 25 | "translater" 26 | ], 27 | "author": { 28 | "name": "Jakob Miland", 29 | "email": "saebekassebil@gmail.com", 30 | "url": "http://saebekassebil.me" 31 | }, 32 | "license": "MIT" 33 | } 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Saebekassebil 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # musicjson 2 | 3 | MusicXML to MusicJSON bi-directional converter 4 | Translates MusicXML to MusicJSON format and back again! 5 | 6 | ## Getting Started 7 | Install the module with: `npm install -g musicjson` 8 | 9 | ## Examples 10 | ```javascript 11 | var music = require('musicjson'); 12 | 13 | music.musicJSON(xml, function(err, json) { 14 | // Do something with the MusicJSON data 15 | }); 16 | 17 | music.musicXML(json, function(err, xml) { 18 | // Do something with the MusicXML data 19 | }); 20 | ``` 21 | 22 | ## Command line tool 23 | The `musicjson` tool can read files from filepaths, but defaults to `stdin`. 24 | This makes it possible to compose commandline-chains with `musicjson` conversion 25 | in the middle 26 | 27 | ``` 28 | $ musicjson --help 29 | 30 | Usage: musicjson [options] 31 | If no is given, default to stdin 32 | 33 | Options: 34 | 35 | -h, --help output usage information 36 | -V, --version output the version number 37 | -j, --json convert file to MusicJSON 38 | -x, --xml convert file to MusicXML 39 | -i, --indent [level=2] indent the output nicely 40 | -o, --order [preserve=true] should we preserve tag order? 41 | 42 | $ cat autumn_in_new_york.xml | musicjson -j -i > autumn.json 43 | $ musicjson -o false -j jade.xml > jade.json 44 | $ musicjson -x jade.json > jade-unordered.xml 45 | ``` 46 | 47 | ## Contributing 48 | Feel free to submit any issues regarding this library's bugs and please feel 49 | also free to submit any feature requests! 50 | 51 | ## License 52 | Copyright (c) 2012 Saebekassebil 53 | Licensed under the MIT license. 54 | 55 | -------------------------------------------------------------------------------- /bin/musicjson: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Usage: musicjson [options] 5 | * If no is given, default to stdin 6 | * 7 | * Options: 8 | * 9 | * -h, --help output usage information 10 | * -V, --version output the version number 11 | * -j, --json convert file to MusicJSON 12 | * -x, --xml convert file to MusicXML 13 | * -i, --indent [level=2] indent the output nicely 14 | * -o, --order [preserve=true] should we preserve tag order? 15 | */ 16 | 17 | var fs = require('fs') 18 | , program = require('commander') 19 | , pkg = require('../package.json') 20 | , MusicJSON = require('../index.js') 21 | , source, preserve, indent, converted; 22 | 23 | function times(string, times) { 24 | return Array(times + 1).join(string); 25 | } 26 | 27 | function formatXML(str, indentlvl) { 28 | str = str.replace(/(>)(<)(\/*)/g, '$1\n$2$3'); 29 | 30 | var xml = '', pad = 0, i, length, indent, node, 31 | lines = str.split('\n'), indentlvl = indentlvl || 2; 32 | 33 | for (i = 0, length = lines.length; i < length; i++) { 34 | indent = 0; 35 | node = lines[i]; 36 | 37 | if(node.match(/.+<\/\w[^>]*>$/)) { 38 | indent = 0; 39 | } else if (node.match(/^<\/\w/) && pad > 0) { 40 | pad = pad - 1; 41 | } else if (node.match(/^<\w[^>]*[^\/]>.*$/)) { 42 | indent = 1; 43 | } else { 44 | indent = 0; 45 | } 46 | 47 | xml = xml + times(' ', pad * indentlvl) + node + '\n'; 48 | pad = pad + indent; 49 | } 50 | 51 | return xml; 52 | } 53 | 54 | function fail(message, err) { 55 | console.error(message); 56 | console.error(err.message); 57 | process.exit(1); 58 | } 59 | 60 | 61 | // Command line interface 62 | program 63 | .version(pkg.version) 64 | .usage('[options] \n If no is given, default to stdin') 65 | .option('-j, --json', 'convert file to MusicJSON') 66 | .option('-x, --xml', 'convert file to MusicXML') 67 | .option('-i, --indent [level=2]', 'indent the output nicely') 68 | .option('-o, --order [preserve=true]', 'should we preserve tag order?') 69 | .parse(process.argv); 70 | 71 | source = program.args[0]; 72 | preserve = program.order !== 'false'; 73 | 74 | function input(file, cb) { 75 | var stream = file ? fs.createReadStream(file) : process.stdin; 76 | var buffered = ''; 77 | stream.resume(); 78 | stream.setEncoding('utf8'); 79 | 80 | stream.on('data', function(chunk) { 81 | buffered += chunk; 82 | }); 83 | 84 | stream.on('error', function(e) { 85 | cb(e); 86 | }); 87 | 88 | stream.on('end', function() { 89 | cb(null, buffered); 90 | }); 91 | } 92 | 93 | if (!program.json && !program.xml) { 94 | program.help(); 95 | } else { 96 | input(source, function(err, data) { 97 | err && fail('Failed to read from source', err); 98 | if (program.json) { 99 | MusicJSON.musicJSON({ source: data, preserveOrder: preserve }, 100 | function(err, output) { 101 | err && fail('An error occured converting to MusicJSON:', err); 102 | 103 | indent = 'indent' in program ? (+program.indent || 2) : 0; 104 | converted = JSON.stringify(output, null, times(' ', indent)); 105 | console.log(converted); 106 | }); 107 | } else { 108 | try { 109 | data = JSON.parse(data); 110 | } catch(err) { 111 | fail('Invalid MusicJSON source', err); 112 | } 113 | 114 | MusicJSON.musicXML(data, function(err, root) { 115 | err && fail('An error occured converting to MusicXML:', err); 116 | 117 | if (program.indent) { 118 | indent = { 119 | pretty: true, 120 | indent: times(' ', +program.indent || 2), 121 | newline: '\n' 122 | } 123 | } 124 | 125 | converted = root.end(indent); 126 | console.log(converted); 127 | }); 128 | } 129 | }); 130 | } 131 | 132 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MusicJSON 3 | * - A bi-directional converter between MusicXML and MusicJSON 4 | * https://github.com/saebekassebil/musicjson 5 | * 6 | * Copyright (c) 2013 Saebekassebil 7 | * Licensed under the MIT license. 8 | */ 9 | 10 | var builder = require('xmlbuilder') 11 | , sax = require('sax') 12 | , events = require('events') 13 | , util = require('util'); 14 | 15 | var partwise = { 16 | id: '-//Recordare//DTD MusicXML 2.0 Partwise//EN', 17 | url: 'http://www.musicxml.org/dtds/partwise.dtd', 18 | type: 'score-partwise' 19 | }; 20 | 21 | var doctype = 'PUBLIC "' + partwise.id + '" "' + partwise.url + '"'; 22 | var attrkey = '$', charkey = '_', 23 | orderkey = '%', orderNumberKey = '&', namekey = '#name', 24 | specialKeys = [attrkey, charkey, orderkey, orderNumberKey, namekey]; 25 | 26 | function assignOrderNumber(obj, name, parent) { 27 | if (name in parent) { 28 | if (!(orderNumberKey in parent)) { 29 | parent[orderNumberKey] = 0; 30 | 31 | for (var child in parent) { 32 | if (specialKeys.indexOf(child) !== -1) { 33 | continue; 34 | } 35 | 36 | parent[orderNumberKey]++; 37 | parent[child][orderkey] = parent[orderNumberKey]; 38 | } 39 | } 40 | 41 | parent[orderNumberKey]++; 42 | obj[orderkey] = parent[orderNumberKey]; 43 | } else { 44 | if (orderNumberKey in parent) { 45 | parent[orderNumberKey]++; 46 | obj[orderkey] = parent[orderNumberKey]; 47 | } 48 | } 49 | } 50 | 51 | // XML Parser for parsing MusicXML documents 52 | function Parser(settings) { 53 | events.EventEmitter.call(this); 54 | 55 | // Initialize sax parser 56 | this.sax = sax.parser(true, { 57 | trim: false, 58 | normalize: false, 59 | xmlns: false 60 | }); 61 | 62 | // Stack to hold the tags when encountered 63 | this.stack = []; 64 | 65 | this.settings = settings || {}; 66 | 67 | // Initialize listeners 68 | this.sax.onerror = this.error.bind(this); 69 | this.sax.onopentag = this.open.bind(this); 70 | this.sax.onclosetag = this.close.bind(this); 71 | this.sax.ontext = this.sax.oncdata = this.text.bind(this); 72 | } 73 | util.inherits(Parser, events.EventEmitter); 74 | 75 | Parser.prototype.error = function(e) { 76 | this.emit('error', e); 77 | }; 78 | 79 | Parser.prototype.open = function(node) { 80 | var key, obj = {}; 81 | 82 | // Set the node name (deleted later) 83 | obj[namekey] = node.name.toLowerCase(); 84 | obj[charkey] = ''; 85 | 86 | // Iterate over all the attributes 87 | for (key in node.attributes) { 88 | if (!(attrkey in obj)) { 89 | obj[attrkey] = {}; 90 | } 91 | 92 | obj[attrkey][key] = node.attributes[key]; 93 | } 94 | 95 | this.stack.push(obj); 96 | }; 97 | 98 | Parser.prototype.close = function(node) { 99 | var obj = this.stack.pop(), name = obj[namekey], parent; 100 | 101 | delete obj[namekey]; 102 | if (orderNumberKey in obj) { 103 | delete obj[orderNumberKey]; 104 | } 105 | 106 | parent = this.stack[this.stack.length - 1]; 107 | 108 | if (!obj[charkey].trim().length) { // No text content 109 | delete obj[charkey]; 110 | } else if (Object.keys(obj).length === 1) { // Text node 111 | obj = obj[charkey]; 112 | } 113 | 114 | // If the object is empty, translate it to "true" 115 | if (obj && typeof obj === 'object' && !Object.keys(obj).length) { 116 | obj = true; 117 | } 118 | 119 | if (this.stack.length > 0) { 120 | // Assign order number, so that tag order is preserved 121 | if (this.settings.preserveOrder) { 122 | assignOrderNumber(obj, name, parent); 123 | } 124 | 125 | if (name in parent) { 126 | parent[name] = util.isArray(parent[name]) ? parent[name] : [parent[name]]; 127 | parent[name].push(obj); 128 | } else { 129 | parent[name] = obj; 130 | } 131 | } else { 132 | var returnobj = {}; 133 | returnobj[name] = obj; 134 | 135 | this.emit('end', returnobj); 136 | } 137 | }; 138 | 139 | Parser.prototype.text = function(text) { 140 | var last = this.stack[this.stack.length - 1]; 141 | if (last) { 142 | last[charkey] += text; 143 | } 144 | }; 145 | 146 | Parser.prototype.parse = function(string, callback) { 147 | this.on('end', function(result) { callback(null, result); }); 148 | this.on('error', callback); 149 | 150 | this.sax.write(string); 151 | }; 152 | 153 | // Translates a MusicJSON element to MusicXML 154 | function toXML(root, el, nodeName) { 155 | var element, i, attr, type = typeof el, children = []; 156 | 157 | if (!root) { 158 | // Create