├── img └── raphinesse.png ├── test ├── .jshintrc ├── args.spec.js ├── paths.spec.js ├── xmlpoke.spec.js ├── xml.spec.js └── document.spec.js ├── .jshintrc ├── .gitignore ├── .travis.yml ├── gulpfile.js ├── src ├── args.js ├── xmlpoke.js ├── paths.js ├── xml.js └── document.js ├── LICENSE ├── package.json └── README.md /img/raphinesse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikeobrien/node-xmlpoke/HEAD/img/raphinesse.png -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.jshintrc", 3 | "expr": true, 4 | "mocha": true 5 | } 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "undef": true, 4 | "unused": true, 5 | "strict": "global" 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | # node.js versions to test with 4 | node_js: 5 | - "node" # latest stable Node.js release 6 | - "lts/*" # latest LTS Node.js release 7 | 8 | # Further releases that have not reached EOL. 9 | # See https://github.com/nodejs/Release 10 | - "6" 11 | - "4" 12 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var gulp = require('gulp'), 4 | jshint = require('gulp-jshint'), 5 | mocha = require('gulp-mocha'), 6 | process = require('child_process'); 7 | 8 | gulp.task('default', ['test']); 9 | 10 | gulp.task('lint', function() { 11 | return gulp.src(['**/*.js', '!node_modules/**/*']) 12 | .pipe(jshint()) 13 | .pipe(jshint.reporter('default')) 14 | .pipe(jshint.reporter('fail')); 15 | }); 16 | 17 | gulp.task('test', ['lint'], function() { 18 | return gulp.src('test/*.js', { read: false }) 19 | .pipe(mocha({ reporter: 'spec' })); 20 | }); 21 | 22 | gulp.task('watch', function() { 23 | gulp.watch(['test/*.js', 'src/*.js'], function() { 24 | process.spawn('gulp', ['test'], { stdio: 'inherit' }); 25 | }); 26 | }); -------------------------------------------------------------------------------- /src/args.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _ = require('lodash'); 4 | 5 | module.exports = function (args) { 6 | args = _.toArray(args); 7 | return { 8 | first: function() { return _.first(args); }, 9 | last: function() { return _.last(args); }, 10 | applyLeading: function(func) { 11 | return args.length < 2 ? func() : 12 | func.apply(null, args.splice(0, args.length - 1)); 13 | }, 14 | toObject: function() { 15 | if (args.length > 1 && _.isString(args[0])) { 16 | var object = {}; 17 | object[args[0]] = args[1]; 18 | return object; 19 | } 20 | else if (args.length == 1 && _.isObject(args[0])) return args[0]; 21 | return {}; 22 | } 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /src/xmlpoke.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var paths = require('./paths'), 4 | Args = require('./args'), 5 | Document = require('./document'), 6 | _ = require('lodash'); 7 | 8 | module.exports = function() { 9 | var args = Args(arguments); 10 | var modify = args.last(); 11 | 12 | if (_.startsWith(_.trim(args.first()), '<')) 13 | { 14 | var document = Document.load(args.first()); 15 | modify(document, {}); 16 | return document.toString(); 17 | } 18 | else 19 | { 20 | var files = args.applyLeading(paths.expand); 21 | 22 | files.forEach(function(file) { 23 | var document = Document.open(file.path); 24 | modify(document, file.context); 25 | document.save(); 26 | }); 27 | } 28 | }; 29 | 30 | module.exports.XmlString = Document.XmlString; 31 | module.exports.CDataValue = Document.CDataValue; -------------------------------------------------------------------------------- /src/paths.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var glob = require('glob'), 4 | _ = require('lodash'); 5 | 6 | function expand() { 7 | 8 | var paths = _.chain(arguments).toArray().flatten().value(); 9 | var getPath = _.chain(paths).last().isFunction().value() ? 10 | paths.pop() : function(x) { return x.path; }; 11 | 12 | if (!_.some(paths)) throw Error('No paths specified!'); 13 | 14 | return _.chain(paths) 15 | .map(function(x) { 16 | var string = _.isString(x); 17 | return { 18 | id: string ? 0 : _.uniqueId(), 19 | paths: glob.sync(string ? x : getPath(x), { nodir: true }), 20 | context: string ? {} : x }; 21 | }) 22 | .map(function(x) { 23 | return x.paths.map(function(path) { 24 | return { id: x.id, path: path, context: x.context }; }); 25 | }) 26 | .flatten() 27 | .uniqBy(function(x) { return x.id + ':' + x.path; }) 28 | .value(); 29 | } 30 | 31 | module.exports = { 32 | expand: expand 33 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mike O'Brien 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xmlpoke", 3 | "version": "0.1.16", 4 | "description": "Module for modifying XML files.", 5 | "homepage": "https://github.com/mikeobrien/node-xmlpoke", 6 | "main": "src/xmlpoke.js", 7 | "bugs": { 8 | "url": "https://github.com/mikeobrien/node-xmlpoke/issues" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/mikeobrien/node-xmlpoke.git" 13 | }, 14 | "keywords": [ 15 | "xmlpoke", 16 | "xml" 17 | ], 18 | "scripts": { 19 | "test": "gulp" 20 | }, 21 | "author": "Mike O'Brien", 22 | "license": "MIT", 23 | "readmeFilename": "README.md", 24 | "files": [ 25 | "src/*.js", 26 | "README.md", 27 | "LICENSE" 28 | ], 29 | "devDependencies": { 30 | "cases": "^0.1.1", 31 | "chai": "^1.10.0", 32 | "gulp": "~3.8.10", 33 | "gulp-filter": "~1.0.2", 34 | "gulp-jshint": "~1.9.0", 35 | "gulp-mocha": "~1.1.1", 36 | "temp": "^0.8.1", 37 | "touch": "0.0.3" 38 | }, 39 | "dependencies": { 40 | "glob": "^7.1.2", 41 | "lodash": "^4.17.5", 42 | "xmldom": "^0.1.27", 43 | "xpath": "~0.0.27" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/args.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var expect = require('chai').expect, 4 | cases = require('cases'), 5 | Args = require('../src/args'); 6 | 7 | function args() { return arguments; } 8 | 9 | describe('args', function() { 10 | 11 | describe('last', function() { 12 | 13 | it('should return last arg', cases([ 14 | [ args(), undefined ], 15 | [ args('a'), 'a' ], 16 | [ args('a', 'b'), 'b' ] 17 | ], function (args, expected) { 18 | expect(Args(args).last()).to.equal(expected); 19 | })); 20 | 21 | }); 22 | 23 | describe('leading', function() { 24 | 25 | it('should apply leading args', cases([ 26 | [ args(), [ undefined, undefined ] ], 27 | [ args('a'), [ undefined, undefined ] ], 28 | [ args('a', 'b'), ['a', undefined] ], 29 | [ args('a', 'b', 'c'), ['a', 'b'] ] 30 | ], function (args, expected) { 31 | expect(Args(args).applyLeading( 32 | function (a, b) { return [a, b]; })) 33 | .to.deep.equal(expected); 34 | })); 35 | 36 | }); 37 | 38 | describe('toObject', function() { 39 | 40 | it('should convert to object', cases([ 41 | [ args('key', 'value'), { key: 'value' } ], 42 | [ args('key', 'value1', 'value2'), { key: 'value1' } ], 43 | [ args({ key: 'value' }), { key: 'value' } ], 44 | [ args(1), {} ] 45 | ], function (args, expected) { 46 | expect(Args(args).toObject()).to.deep.equal(expected); 47 | })); 48 | 49 | }); 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /test/paths.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs'), 4 | path = require('path'), 5 | touch = require('touch'), 6 | expect = require('chai').expect, 7 | temp = require('temp').track(), 8 | cases = require('cases'), 9 | paths = require('../src/paths'), 10 | _ = require('lodash'); 11 | 12 | describe('paths', function() { 13 | 14 | describe('expand', function() { 15 | 16 | var basePath, relative, absolute; 17 | 18 | before(function() { 19 | 20 | basePath = temp.mkdirSync(); 21 | 22 | touch.sync(path.join(basePath, 'a.xml')); 23 | touch.sync(path.join(basePath, 'b.xml')); 24 | fs.mkdirSync(path.join(basePath, 'dir')); 25 | touch.sync(path.join(basePath, 'dir', 'a.xml')); 26 | touch.sync(path.join(basePath, 'dir', 'b.xml')); 27 | 28 | relative = function(file) { 29 | return path.relative(basePath, file.path); 30 | }; 31 | 32 | absolute = function() { 33 | return path.join.apply(null, [ basePath ].concat(_.toArray(arguments))); 34 | }; 35 | }); 36 | 37 | after(function() { 38 | temp.cleanupSync(); 39 | }); 40 | 41 | it('should resolve files', function() { 42 | 43 | cases([ 44 | [[ absolute('a.*'), absolute('dir', 'b.*') ]], 45 | [[ [ absolute('a.*'), absolute('dir', 'b.*') ] ]], 46 | [[ [ absolute('a.*')], [ absolute('dir', 'b.*') ] ]], 47 | [[ { path: absolute('a.*') }, { path: absolute('dir', 'b.*') } ]], 48 | [[ [ { path: absolute('a.*') }, { path: absolute('dir', 'b.*') } ] ]], 49 | [[ [ { path: absolute('a.*') }], [{ path: absolute('dir', 'b.*') } ] ]], 50 | [[ absolute('a.*'), { path: absolute('dir', 'b.*') } ]], 51 | [[ absolute('a.*'), { customPath: absolute('dir', 'b.*') }, function(x) { return x.customPath; } ]] 52 | ], function (params) { 53 | 54 | var files = paths.expand.apply(null, params); 55 | 56 | expect(files).to.have.length(2); 57 | 58 | expect(relative(files[0])).to.equal('a.xml'); 59 | expect(files[0].context).to.exist(); 60 | 61 | expect(relative(files[1])).to.equal('dir/b.xml'); 62 | expect(files[1].context).to.exist(); 63 | })(); 64 | 65 | }); 66 | 67 | it('should not resolve directories', function() { 68 | var files = paths.expand(absolute('**', '*')); 69 | 70 | expect(files).to.have.length(4); 71 | 72 | expect(relative(files[0])).to.equal('a.xml'); 73 | expect(relative(files[1])).to.equal('b.xml'); 74 | expect(relative(files[2])).to.equal('dir/a.xml'); 75 | expect(relative(files[3])).to.equal('dir/b.xml'); 76 | 77 | }); 78 | 79 | it('should return unique string results', function() { 80 | var files = paths.expand(absolute('**', 'a.*'), absolute('**', 'a.*')); 81 | 82 | expect(files).to.have.length(2); 83 | 84 | expect(relative(files[0])).to.equal('a.xml'); 85 | expect(relative(files[1])).to.equal('dir/a.xml'); 86 | 87 | }); 88 | 89 | it('should not return unique object results', function() { 90 | var files = paths.expand({ path: absolute('**', 'a.*') }, { path: absolute('**', 'a.*') }); 91 | 92 | expect(files).to.have.length(4); 93 | 94 | expect(relative(files[0])).to.equal('a.xml'); 95 | expect(relative(files[1])).to.equal('dir/a.xml'); 96 | expect(relative(files[2])).to.equal('a.xml'); 97 | expect(relative(files[3])).to.equal('dir/a.xml'); 98 | 99 | }); 100 | 101 | it('should pass context with objects', function() { 102 | var context = { path: absolute('a.*') }; 103 | var files = paths.expand(context); 104 | 105 | expect(relative(files[0])).to.equal('a.xml'); 106 | expect(files[0].context).to.equal(context); 107 | 108 | }); 109 | 110 | it('should pass empty context with strings', function() { 111 | var files = paths.expand(absolute('a.*')); 112 | 113 | expect(relative(files[0])).to.equal('a.xml'); 114 | expect(files[0].context).to.deep.equal({}); 115 | 116 | }); 117 | 118 | it('should fail if no files', cases([ 119 | [[ ]], 120 | [[ function(x) { return x.customPath; } ]] 121 | ], function (params) { 122 | expect(function() { paths.expand.apply(null, params); }).to.throw(Error); 123 | })); 124 | 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /src/xml.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _ = require('lodash'), 4 | xmldom = require('xmldom'), 5 | xmlParser = new xmldom.DOMParser(); 6 | 7 | var ELEMENT_NODE = 1; 8 | var ATTRIBUTE_NODE = 2; 9 | 10 | function XmlString(xml) { 11 | this.source = xml; 12 | } 13 | 14 | function isXmlString(object) { 15 | return object instanceof XmlString; 16 | } 17 | 18 | function CDataValue(value) { 19 | this.value = value; 20 | } 21 | 22 | function isCDataValue(object) { 23 | return object instanceof CDataValue; 24 | } 25 | 26 | function parsePredicates(path) { 27 | var regex = /((\[|and)\s*([^'=]*)\s*=\s*'([^']*)')/g; 28 | var predicates = [], predicate; 29 | while ((predicate = regex.exec(path))) { 30 | predicates.push({ 31 | name: _.trimStart(_.trim(predicate[3]), '@'), 32 | value: predicate[4], 33 | attribute: _.startsWith(_.trim(predicate[3]), '@') 34 | }); 35 | } 36 | return predicates; 37 | } 38 | 39 | function parseXPath(path) { 40 | var segments = path.match(/(.*)\/([^\[]*)(.*)/); 41 | if (!segments) return null; 42 | var node = { 43 | path: _.trim(segments[1]), 44 | name: _.trimStart(_.trim(segments[2]), '@'), 45 | attribute: _.startsWith(_.trim(segments[2]), '@'), 46 | keys: parsePredicates(segments[3]) 47 | }; 48 | return node; 49 | } 50 | 51 | function joinXPath(path1, path2) { 52 | return path1 ? _.trimEnd(path1, '/') + '/' + _.trimStart(path2, '/') : path2; 53 | } 54 | 55 | function parseQualifiedName(name) { 56 | var nameParts = name.split(':'); 57 | return { 58 | prefix: nameParts.length > 1 ? _.first(nameParts) : null, 59 | name: _.last(nameParts) 60 | }; 61 | } 62 | 63 | function mapNamespaces(name, parent, pathNamespaces) { 64 | name = parseQualifiedName(name); 65 | var namespace = pathNamespaces && name.prefix ? pathNamespaces[name.prefix] : null; 66 | var prefix = namespace ? parent.lookupPrefix(namespace) : null; 67 | return { 68 | namespace: namespace ? namespace : null, 69 | name: (prefix ? prefix + ':' : '') + name.name 70 | }; 71 | } 72 | 73 | function removeNode(node) { 74 | node.parentNode.removeChild(node); 75 | } 76 | 77 | function clearChildNodes(node) { 78 | while (node.firstChild) { 79 | node.removeChild(node.firstChild); 80 | } 81 | } 82 | 83 | function setNodeValue(node, value) { 84 | if (_.isFunction(value)) value = value(node, getNodeValue(node)); 85 | if (node.nodeType == ATTRIBUTE_NODE) { 86 | node.value = node.nodeValue = String(value); 87 | } 88 | else if (isCDataValue(value)) { 89 | clearChildNodes(node); 90 | node.appendChild(node.ownerDocument 91 | .createCDATASection(value.value)); 92 | } 93 | else if (isXmlString(value)) { 94 | clearChildNodes(node); 95 | node.appendChild(xmlParser.parseFromString(value.source)); 96 | } 97 | else { 98 | node.textContent = String(value); 99 | } 100 | } 101 | 102 | function getNodeValue(node) { 103 | return node.nodeType == ATTRIBUTE_NODE ? 104 | node.value : node.textContent; 105 | } 106 | 107 | function addNode(namespaces, parent, name, isAttribute, value) { 108 | name = mapNamespaces(name, parent, namespaces); 109 | var node; 110 | if (isAttribute) { 111 | node = parent.ownerDocument.createAttributeNS(name.namespace, name.name); 112 | parent.setAttributeNode(node); 113 | } 114 | else { 115 | node = parent.ownerDocument.createElementNS(name.namespace, name.name); 116 | parent.appendChild(node); 117 | } 118 | if (value) setNodeValue(node, value); 119 | return node; 120 | } 121 | 122 | function getAttributesNamed(namespaces, nodes, name) { 123 | return _.map(nodes, function(x) { 124 | return x.getAttributeNode(name) || 125 | addNode(namespaces, x, name, true); 126 | }); 127 | } 128 | 129 | function getChildElementsNamed(namespaces, nodes, name) { 130 | return _.chain(nodes) 131 | .map(function(x) { 132 | var results = 133 | _.chain(x.childNodes) 134 | .toArray() 135 | .filter({ 136 | nodeName: name, 137 | nodeType: ELEMENT_NODE, 138 | }).value(); 139 | return results.length > 0 ? results : 140 | addNode(namespaces, x, name); 141 | }) 142 | .flatten() 143 | .value(); 144 | } 145 | 146 | module.exports = { 147 | XmlString: XmlString, 148 | isXmlString: isXmlString, 149 | CDataValue: CDataValue, 150 | isCDataValue: isCDataValue, 151 | parseXPath: parseXPath, 152 | joinXPath: joinXPath, 153 | removeNode: removeNode, 154 | clearChildNodes: clearChildNodes, 155 | setNodeValue: setNodeValue, 156 | getNodeValue: getNodeValue, 157 | mapNamespaces: mapNamespaces, 158 | addNode: addNode, 159 | getAttributesNamed: getAttributesNamed, 160 | getChildElementsNamed: getChildElementsNamed 161 | }; 162 | -------------------------------------------------------------------------------- /test/xmlpoke.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var expect = require('chai').expect, 4 | temp = require('temp').track(), 5 | path = require('path'), 6 | fs = require('fs'), 7 | xmlpoke = require('../src/xmlpoke'); 8 | 9 | describe('xmlpoke', function() { 10 | 11 | var basePath, fileAPath, fileBPath, fileDirAPath, fileDirBPath; 12 | 13 | beforeEach(function() { 14 | 15 | basePath = temp.mkdirSync(); 16 | 17 | fileAPath = path.join(basePath, 'a.xml'); 18 | fs.writeFileSync(fileAPath, ''); 19 | 20 | fileBPath = path.join(basePath, 'b.xml'); 21 | fs.writeFileSync(fileBPath, ''); 22 | 23 | fs.mkdirSync(path.join(basePath, 'dir')); 24 | 25 | fileDirAPath = path.join(basePath, 'dir', 'a.xml'); 26 | fs.writeFileSync(fileDirAPath, ''); 27 | 28 | fileDirBPath = path.join(basePath, 'dir', 'b.xml'); 29 | fs.writeFileSync(fileDirBPath, ''); 30 | 31 | }); 32 | 33 | afterEach(function() { 34 | temp.cleanupSync(); 35 | }); 36 | 37 | it('should poke files', function() { 38 | 39 | xmlpoke(path.join(basePath, '**/a.*'), function(xml) { 40 | xml.set('a/b', 'c'); 41 | }); 42 | 43 | expect(fs.readFileSync(fileAPath).toString()).to.equal('c'); 44 | expect(fs.readFileSync(fileBPath).toString()).to.equal(''); 45 | expect(fs.readFileSync(fileDirAPath).toString()).to.equal('c'); 46 | expect(fs.readFileSync(fileDirBPath).toString()).to.equal(''); 47 | 48 | }); 49 | 50 | it('should should poke paths and objects with default path', function() { 51 | 52 | xmlpoke(path.join(basePath, 'a.*'), 53 | { path: path.join(basePath, 'dir/a.*'), value: 'd' }, 54 | function(xml, context) { 55 | xml.set('a/b', context.value || 'c'); 56 | }); 57 | 58 | expect(fs.readFileSync(fileAPath).toString()).to.equal('c'); 59 | expect(fs.readFileSync(fileBPath).toString()).to.equal(''); 60 | expect(fs.readFileSync(fileDirAPath).toString()).to.equal('d'); 61 | expect(fs.readFileSync(fileDirBPath).toString()).to.equal(''); 62 | 63 | }); 64 | 65 | it('should should poke paths and objects with custom path', function() { 66 | 67 | xmlpoke(path.join(basePath, 'a.*'), 68 | { customPath: path.join(basePath, 'dir/a.*'), value: 'd' }, 69 | function(x) { return x.customPath; }, 70 | function(xml, context) { 71 | xml.set('a/b', context.value || 'c'); 72 | }); 73 | 74 | expect(fs.readFileSync(fileAPath).toString()).to.equal('c'); 75 | expect(fs.readFileSync(fileBPath).toString()).to.equal(''); 76 | expect(fs.readFileSync(fileDirAPath).toString()).to.equal('d'); 77 | expect(fs.readFileSync(fileDirBPath).toString()).to.equal(''); 78 | 79 | }); 80 | 81 | it('should poke files with cdata', function() { 82 | 83 | xmlpoke(path.join(basePath, '**/a.*'), function(xml) { 84 | xml.set('a/b', xml.CDataValue('c')); 85 | }); 86 | 87 | expect(fs.readFileSync(fileAPath).toString()).to.equal(''); 88 | expect(fs.readFileSync(fileBPath).toString()).to.equal(''); 89 | expect(fs.readFileSync(fileDirAPath).toString()).to.equal(''); 90 | expect(fs.readFileSync(fileDirBPath).toString()).to.equal(''); 91 | 92 | }); 93 | 94 | it('should poke files with cdata outside dsl', function() { 95 | 96 | var cdata = new xmlpoke.CDataValue('c'); 97 | 98 | xmlpoke(path.join(basePath, '**/a.*'), function(xml) { 99 | xml.set('a/b', cdata); 100 | }); 101 | 102 | expect(fs.readFileSync(fileAPath).toString()).to.equal(''); 103 | expect(fs.readFileSync(fileBPath).toString()).to.equal(''); 104 | expect(fs.readFileSync(fileDirAPath).toString()).to.equal(''); 105 | expect(fs.readFileSync(fileDirBPath).toString()).to.equal(''); 106 | 107 | }); 108 | 109 | it('should poke files with xml', function() { 110 | 111 | xmlpoke(path.join(basePath, '**/a.*'), function(xml) { 112 | xml.set('a/b', xml.XmlString('')); 113 | }); 114 | 115 | expect(fs.readFileSync(fileAPath).toString()).to.equal(''); 116 | expect(fs.readFileSync(fileBPath).toString()).to.equal(''); 117 | expect(fs.readFileSync(fileDirAPath).toString()).to.equal(''); 118 | expect(fs.readFileSync(fileDirBPath).toString()).to.equal(''); 119 | 120 | }); 121 | 122 | it('should poke files with xml outside dsl', function() { 123 | 124 | var xmlstring = new xmlpoke.XmlString(''); 125 | 126 | xmlpoke(path.join(basePath, '**/a.*'), function(xml) { 127 | xml.set('a/b', xmlstring); 128 | }); 129 | 130 | expect(fs.readFileSync(fileAPath).toString()).to.equal(''); 131 | expect(fs.readFileSync(fileBPath).toString()).to.equal(''); 132 | expect(fs.readFileSync(fileDirAPath).toString()).to.equal(''); 133 | expect(fs.readFileSync(fileDirBPath).toString()).to.equal(''); 134 | 135 | }); 136 | 137 | it('should poke string', function() { 138 | 139 | var xml = xmlpoke('', function(xml) { 140 | xml.set('a', 'b'); 141 | }); 142 | 143 | expect(xml).to.equal('b'); 144 | 145 | }); 146 | 147 | it('should poke boolean', function() { 148 | 149 | var xml = xmlpoke('', function(xml) { 150 | xml.set('a', true); 151 | }); 152 | 153 | expect(xml).to.equal('true'); 154 | 155 | }); 156 | 157 | it('should poke numbers', function() { 158 | 159 | var xml = xmlpoke('', function(xml) { 160 | xml.set('a', 5); 161 | }); 162 | 163 | expect(xml).to.equal('5'); 164 | 165 | }); 166 | 167 | }); 168 | -------------------------------------------------------------------------------- /src/document.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs'), 4 | Args = require('./args'), 5 | TRAILING_SPACES_REGEX = /\s*$/, 6 | xmldom = require('xmldom'), 7 | xmlParser = new xmldom.DOMParser(), 8 | xmlSerializer = new xmldom.XMLSerializer(), 9 | xpath = require('xpath'), 10 | xml = require('./xml'), 11 | _ = require('lodash'); 12 | 13 | function throwNoMatchingNodesError(path) { 14 | var warning = "No matching nodes for xpath '" + path + "'."; 15 | console.log(warning); 16 | throw new Error(warning); 17 | } 18 | 19 | function setNodeValues(namespaces, nodes, value) { 20 | 21 | if (!nodes || nodes.length == 0) return; 22 | 23 | if (_.isString(value)|| _.isBoolean(value) || _.isNumber(value) || 24 | xml.isXmlString(value) || xml.isCDataValue(value) || _.isFunction(value)) 25 | nodes.forEach(function(node) { xml.setNodeValue(node, value); }); 26 | else { 27 | var set = _.partial(setNodeValues, namespaces); 28 | _.forOwn(value, function(value, name) { 29 | if (_.startsWith(name, '@')) 30 | set(xml.getAttributesNamed(namespaces, nodes, name.slice(1)), value); 31 | else set(xml.getChildElementsNamed(namespaces, nodes, name), value); 32 | }); 33 | } 34 | } 35 | 36 | function createNodes(query, path, errorOnNoMatches) { 37 | var child = xml.parseXPath(path); 38 | var parents = query.getNodes(child.path); 39 | if (parents.length == 0) { 40 | if (errorOnNoMatches) throwNoMatchingNodesError(child.path); 41 | return []; 42 | } 43 | var add = _.partial(xml.addNode, query.namespaces); 44 | return parents.map(function(parent) { 45 | var node = add(parent, child.name, child.attribute); 46 | if (child.keys.length > 0) child.keys.forEach(function(key) { 47 | add(node, key.name, key.attribute, key.value); 48 | }); 49 | return node; 50 | }); 51 | } 52 | 53 | function buildQuery(document, namespaces) { 54 | var select = namespaces ? xpath.useNamespaces(namespaces) : xpath.select; 55 | return { 56 | namespaces: namespaces, 57 | getNodes: function(path) { 58 | return select(path, document); 59 | } 60 | }; 61 | } 62 | 63 | function queryNodes(query, path, errorOnNoMatches, addIfMissing, alwaysAdd) { 64 | 65 | if (alwaysAdd) return createNodes(query, path, errorOnNoMatches); 66 | 67 | var nodes = query.getNodes(path); 68 | 69 | if (nodes.length > 0) return nodes; 70 | else if (addIfMissing) return createNodes(query, path, errorOnNoMatches); 71 | else if (errorOnNoMatches) throwNoMatchingNodesError(path); 72 | 73 | return nodes; 74 | } 75 | 76 | function ensurePath(query, path) { 77 | 78 | var pathParts = path.split('/'); 79 | var currentPath = ''; 80 | var exists = true; 81 | 82 | pathParts.forEach(function(pathPart) { 83 | currentPath += (currentPath ? '/' : '') + pathPart; 84 | if (!exists || !(exists = (query.getNodes(currentPath).length > 0))) 85 | createNodes(query, currentPath); 86 | }); 87 | } 88 | 89 | function clearNodes(query) { 90 | query.forEach(function(x) { xml.clearChildNodes(x); }); 91 | } 92 | 93 | function removeNodes(query) { 94 | query.forEach(function(x) { xml.removeNode(x); }); 95 | } 96 | 97 | function openXmlFile(path, encoding) { 98 | encoding = encoding || 'utf8'; 99 | var document = loadXml(fs.readFileSync(path, encoding)); 100 | document.save = function() { 101 | fs.writeFileSync(path, document.toString(), encoding); 102 | }; 103 | return document; 104 | } 105 | 106 | function loadXml(source) { 107 | var document = xmlParser.parseFromString(source); 108 | var trailingSpaces = TRAILING_SPACES_REGEX.exec(source)[0]; 109 | var basePath, namespaces, errorOnNoMatches; 110 | 111 | var query = function(path, errorOnNoMatches, addIfMissing, alwaysAdd) { 112 | return queryNodes( 113 | buildQuery(document, namespaces), 114 | xml.joinXPath(basePath, path), 115 | errorOnNoMatches, addIfMissing, alwaysAdd); 116 | }; 117 | 118 | var setValues = function(values, namespaces, errorOnNoMatches, addIfMissing, alwaysAdd) { 119 | _.forOwn(values, function(value, path) { 120 | setNodeValues(namespaces, query(path, 121 | errorOnNoMatches, addIfMissing, alwaysAdd), value); 122 | }); 123 | }; 124 | 125 | var dsl = { 126 | 127 | withBasePath: function(path) { 128 | basePath = path; 129 | return dsl; 130 | }, 131 | 132 | addNamespace: function(prefix, uri) { 133 | namespaces = namespaces || {}; 134 | namespaces[prefix] = uri; 135 | return dsl; 136 | }, 137 | 138 | errorOnNoMatches: function() { 139 | errorOnNoMatches = true; 140 | return dsl; 141 | }, 142 | 143 | clear: function(path) { 144 | clearNodes(query(path, errorOnNoMatches)); 145 | return dsl; 146 | }, 147 | 148 | remove: function(path) { 149 | removeNodes(query(path)); 150 | return dsl; 151 | }, 152 | 153 | set: function() { 154 | setValues(Args(arguments).toObject(), namespaces, errorOnNoMatches, false, false); 155 | return dsl; 156 | }, 157 | 158 | ensure: function(path) { 159 | ensurePath(buildQuery(document, namespaces), basePath ? xml.joinXPath(basePath, path) : path); 160 | return dsl; 161 | }, 162 | 163 | add: function() { 164 | setValues(Args(arguments).toObject(), namespaces, errorOnNoMatches, true, true); 165 | return dsl; 166 | }, 167 | 168 | setOrAdd: function() { 169 | setValues(Args(arguments).toObject(), namespaces, errorOnNoMatches, true, false); 170 | return dsl; 171 | }, 172 | 173 | XmlString: function(value) { 174 | return new xml.XmlString(value); 175 | }, 176 | 177 | CDataValue: function(value) { 178 | return new xml.CDataValue(value); 179 | }, 180 | 181 | toString: function() { 182 | var s = xmlSerializer.serializeToString(document); 183 | return s.replace(TRAILING_SPACES_REGEX, trailingSpaces); 184 | } 185 | }; 186 | return dsl; 187 | } 188 | 189 | module.exports = { 190 | open: openXmlFile, 191 | load: loadXml, 192 | XmlString: xml.XmlString, 193 | CDataValue: xml.CDataValue 194 | }; 195 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xmlpoke 2 | 3 | [![npm version](http://img.shields.io/npm/v/xmlpoke.svg?style=flat)](https://npmjs.org/package/xmlpoke) [![build status](http://img.shields.io/travis/mikeobrien/node-xmlpoke.svg?style=flat)](https://travis-ci.org/mikeobrien/node-xmlpoke) [![Dependency Status](http://img.shields.io/david/mikeobrien/node-xmlpoke.svg?style=flat)](https://david-dm.org/mikeobrien/node-xmlpoke) [![npm downloads](http://img.shields.io/npm/dm/xmlpoke.svg?style=flat)](https://npmjs.org/package/xmlpoke) 4 | 5 | Node module for modifying XML files. Inspired by [NAnt XmlPoke](http://nant.sourceforge.net/release/0.92/help/tasks/xmlpoke.html). 6 | 7 | ## Install 8 | 9 | ```bash 10 | $ npm install xmlpoke --save 11 | ``` 12 | 13 | ## Usage 14 | 15 | The xmlpoke module exports a function that modifies xml files: 16 | 17 | `xmlpoke(path1, [path2], [...], [pathMap], modify)` 18 | 19 | Or modifies an xml string: 20 | 21 | `xmlpoke(xml, modify)` 22 | 23 | #### Paths 24 | 25 | Paths are [globs](https://github.com/isaacs/node-glob) and can be any combination of strings, objects with a property that contains the path, or arrays of those. By default, objects are assumed to have a `path` property (Although this can be overridden). Here are some examples of valid path input: 26 | 27 | ```js 28 | var xmlpoke = require('xmlpoke'); 29 | 30 | xmlpoke('**/*.xml', ...); 31 | 32 | xmlpoke('**/*.xml', '**/*.config', ...); 33 | 34 | xmlpoke([ '**/*.xml', '**/*.config' ], ...); 35 | 36 | xmlpoke([ '**/*.xml', '**/*.config' ], { path: '*.proj' }, ...); 37 | 38 | xmlpoke([ '**/*.xml', { path: '*.proj' } ], '**/*.config', ...); 39 | 40 | xmlpoke([ 'data/*.xml', { path: '*.proj' } ], [ '**/*.config', { path: '*.xml' } ], ...); 41 | ``` 42 | 43 | As noted earlier, path objects are expected to have a `path` property. If you would like to override that you can pass in a function, *after* your paths, that map the path: 44 | 45 | ```js 46 | var projects = [ 47 | { 48 | path: '/some/path', 49 | config: 'app.config' 50 | }, 51 | ... 52 | ]; 53 | 54 | xmlpoke(projects, function(p) { return path.join(p.path, p.config); }, ...); 55 | ``` 56 | 57 | #### Modification 58 | 59 | The last argument is a function that performs the modifications. This function is passed a DSL for manipulating the xml file. 60 | 61 | ```js 62 | xmlpoke('**/*.config', function(xml) { 63 | xml.set('data/connString', 'server=oh;db=hai'); 64 | }); 65 | 66 | var xml = xmlpoke('', function(xml) { 67 | xml.set('oh', 'hai'); 68 | }); 69 | ``` 70 | 71 | Path objects, when supplied, are passed in the second argument: 72 | 73 | ```js 74 | var projects = [ 75 | { 76 | path: 'app.config', 77 | version: '1.2.3.4' 78 | }, 79 | ... 80 | ]; 81 | 82 | xmlpoke(projects, function(xml, project) { 83 | xml.set('app/version', project.version); 84 | }); 85 | ``` 86 | 87 | ##### Clearing Child Nodes 88 | 89 | You can clear the children of all matching nodes with the `clear()` method: 90 | 91 | ```js 92 | xmlpoke('**/*.config', function(xml) { 93 | xml.clear('some/path'); 94 | }); 95 | ``` 96 | 97 | The `errorOnNoMatches` option will cause this method to throw an exception if the specified XPath yields no results. 98 | 99 | ##### Removing Nodes 100 | 101 | You can remove all matching nodes with the `remove()` method: 102 | 103 | ```js 104 | xmlpoke('**/*.config', function(xml) { 105 | xml.remove('some/path'); 106 | }); 107 | ``` 108 | 109 | The `errorOnNoMatches` option *does not* cause this method to throw an exception if the specified XPath yields no results. 110 | 111 | ##### Ensuring the Existence of Elements 112 | 113 | You can ensure the existence of elements with the `ensure(xpath)` method. This method will create the entire path. In order to do this the path must contain only elements. It must not contain any axis specifiers. Element axes can have a predicate that compares the *equality* of one or more attributes or elements with a *string* value. Only the `=` comparison operator and `and` logical operators are allowed. If these predicates are specified, and the element does not exist, the predicate values will be set in the new element. Any predicates that do not meet these exact requirements are ignored. The following are examples of acceptable XPaths and the resulting xml when the source is ``: 114 | 115 | ```js 116 | ensure('el1/el2/el3'); 117 | ``` 118 | ```xml 119 | 120 | 121 | 122 | 123 | 124 | ``` 125 | ```js 126 | ensure("el1/el2[@attr1='1' and @attr2='2']/el3[el4='3']"); 127 | ``` 128 | ```xml 129 | 130 | 131 | 132 | 3 133 | 134 | 135 | 136 | ``` 137 | 138 | ##### Setting Values and Content 139 | 140 | You can set the values or content of all matching nodes with the `set()`, `add()` and `setOrAdd()` methods. These methods take an XPath and a value or an object with XPath and value properties: 141 | 142 | `set([xpath, value]|[object])` 143 | 144 | `add([xpath, value]|[object])` 145 | 146 | `setOrAdd([xpath, value]|[object])` 147 | 148 | ```js 149 | xmlpoke('**/*.config', function(xml) { 150 | xml.set('some/path', 'value') 151 | .add('some/path', 'value') 152 | .setOrAdd('some/path', 'value') 153 | .set({ 'first/path': 'value1', 'second/path': 'value2' }) 154 | .add({ 'first/path': 'value1', 'second/path': 'value2' }) 155 | .setOrAdd({ 'first/path': 'value1', 'second/path': 'value2' }); 156 | }); 157 | ``` 158 | 159 | The `set()` method expects all elements and attributes in the XPath to exist. If they do not, they will be ignored by default. To throw an exception specify the `errorOnNoMatches` option. 160 | 161 | The `add()` method will create a new target node regardless of if there is a match. As such, this method is not very useful for attributes unless you are sure it doesn't already exist. On the other hand the `setOrAdd()` method will attempt to create the node if it doesn't exist, then set its value or content. *This will not create the entire XPath as `ensure()` does, only the target element or attribute i.e. the last node in the XPath query.* So the parent XPath must exist otherwise it will be ignored by default (To instead throw an exception, specify the `errorOnNoMatches` option). To be created, the target must be an attribute or an element. Element XPaths can have a predicate that compares the *equality* of one or more attributes or elements with a *string* value. Only the `=` comparison operator and `and` logical operators are allowed. If these predicates are specified, and the element does not exist, the predicate values will be set in the new element. Any predicates that do not meet these exact requirements are ignored. The following are examples of acceptable XPaths and the resulting xml when the source is ``: 162 | 163 | ```js 164 | setOrAdd('el1/@attr', 'value'); 165 | ``` 166 | ```xml 167 | 168 | ``` 169 | ```js 170 | setOrAdd('el1/el2', 'value'); 171 | ``` 172 | ```xml 173 | 174 | value 175 | 176 | ``` 177 | ```js 178 | setOrAdd("el1[el2='value1']/el3[@attr1='value2' and @attr2='value3']", 'value4'); 179 | ``` 180 | ```xml 181 | 182 | value1 183 | value4 184 | 185 | ``` 186 | 187 | Values can be strings, CData, raw xml, a function or an object containing multiple attribute and element values. For example: 188 | 189 | ```js 190 | xmlpoke('**/*.config', function(xml) { 191 | 192 | // Simple string value 193 | xml.set('some/path', 'value'); 194 | 195 | // CData value 196 | xml.set('some/path', xml.CDataValue('value')); 197 | 198 | // Raw xml 199 | xml.set('some/path', xml.XmlString('hai')); 200 | 201 | // Function 202 | xml.set('some/path', function(node, value) { return 'value'; }); 203 | 204 | // XPath and object with element and attribute values 205 | xml.set('some/path', { 206 | '@attr': 'value', 207 | el1: 'value', 208 | el2: xml.CDataValue('value'), 209 | el3: xml.XmlString('hai'), 210 | el4: function(node, value) { return 'value'; } 211 | }); 212 | 213 | // Object 214 | xml.set({ 215 | 'some/path/@attr': 'value', 216 | 'some/path/el1': 'value', 217 | 'some/path/el2': xml.CDataValue('value'), 218 | 'some/path/el3': xml.XmlString('hai'), 219 | 'some/path/el4': function(node, value) { return 'value'; }, 220 | 'some/path/el5': { 221 | '@attr': 'value', 222 | el1: 'value', 223 | el2: xml.CDataValue('value'), 224 | el3: xml.XmlString('hai'), 225 | el4: function(node, value) { return 'value'; } 226 | } 227 | }); 228 | }); 229 | ``` 230 | 231 | The `CDataValue()` and `XmlString()` methods exposed by the DSL are simply convenience methods for creating `CDataValue` and `XmlString` objects. These constructors are defined on the module and can be created directly as follows: 232 | 233 | ```js 234 | var xmlpoke = require('xmlpoke'); 235 | 236 | var cdata = new xmlpoke.CDataValue('value'); 237 | var xmlstring = new xmlpoke.XmlString('hai'); 238 | 239 | xmlpoke('**/*.config', function(xml) { 240 | xml.set('some/path', cdata); 241 | xml.set('some/path', xmlstring); 242 | }); 243 | ``` 244 | 245 | #### Options 246 | 247 | ##### Base XPath 248 | 249 | A base XPath can be specified so you do not have to specify the full XPath in following calls: 250 | 251 | ```js 252 | xmlpoke('**/*.config', function(xml) { 253 | xml.withBasePath('configuration/appSettings') 254 | .set("add[@name='key1']", 'value1') 255 | .set("add[@name='key2']", 'value2'); 256 | }); 257 | ``` 258 | 259 | ##### Namespaces 260 | 261 | Namespaces can be registered as follows: 262 | 263 | ```js 264 | xmlpoke('**/*.config', function(xml) { 265 | xml.addNamespace('y', 'uri:oh') 266 | .addNamespace('z', 'uri:hai') 267 | .set("y:config/z:value", 'value'); 268 | }); 269 | ``` 270 | 271 | ##### Empty Results 272 | 273 | By default, XPaths that do not yield any results are quietly ignored. To throw an exception instead, you can configure as follows: 274 | 275 | ```js 276 | xmlpoke('**/*.config', function(xml) { 277 | xml.errorOnNoMatches() 278 | ...; 279 | }); 280 | ``` 281 | 282 | Contributors 283 | ------------ 284 | 285 | | [![Raphael von der Grün](https://raw.githubusercontent.com/mikeobrien/node-xmlpoke/master/img/raphinesse.png)](https://github.com/raphinesse) | 286 | |:---:| 287 | | [Raphael von der Grün](https://github.com/raphinesse) | 288 | 289 | ## License 290 | MIT License 291 | -------------------------------------------------------------------------------- /test/xml.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var expect = require('chai').expect, 4 | cases = require('cases'), 5 | xmldom = require('xmldom'), 6 | xmlParser = new xmldom.DOMParser(), 7 | xpath = require('xpath'), 8 | xml = require('../src/xml'); 9 | 10 | describe('xml', function() { 11 | 12 | describe('parseXPath', function() { 13 | 14 | it('should parse xpath', cases([ 15 | [ 'a/b/c', { path: 'a/b', name: 'c', attribute: false, keys: [] } ], 16 | [ 'a/b/@c', { path: 'a/b', name: 'c', attribute: true, keys: [] } ], 17 | 18 | [ "a/b/c[name='value']", { path: 'a/b', name: 'c', attribute: false, 19 | keys: [ { name: 'name', value: 'value', attribute: false } ] } ], 20 | [ "a/b/c[@name='value']", { path: 'a/b', name: 'c', attribute: false, 21 | keys: [ { name: 'name', value: 'value', attribute: true } ] } ], 22 | [ "a/b[@name1='value1']/c[@name2='value2']", { path: "a/b[@name1='value1']", name: 'c', attribute: false, 23 | keys: [ { name: 'name2', value: 'value2', attribute: true } ]} ], 24 | [ "a/b/c[@name1='value1' and @name2='value2']", { path: 'a/b', name: 'c', attribute: false, 25 | keys: [ { name: 'name1', value: 'value1', attribute: true }, 26 | { name: 'name2', value: 'value2', attribute: true } ]} ], 27 | [ "a/b/c[@name1='value1' and @name2='value2' and @name3='value3']", { path: 'a/b', name: 'c', attribute: false, 28 | keys: [ { name: 'name1', value: 'value1', attribute: true }, 29 | { name: 'name2', value: 'value2', attribute: true }, 30 | { name: 'name3', value: 'value3', attribute: true } ]} ], 31 | 32 | [ "a/b/c [ @name = 'value' ]", { path: 'a/b', name: 'c', attribute: false, 33 | keys: [ { name: 'name', value: 'value', attribute: true } ] } ], 34 | [ "a/b/c[0]", { path: 'a/b', name: 'c', attribute: false, keys: [] } ], 35 | [ "a/b/c[last()]", { path: 'a/b', name: 'c', attribute: false, keys: [] } ] 36 | ], function (query, result) { 37 | expect(xml.parseXPath(query)).to.deep.equal(result); 38 | })); 39 | 40 | }); 41 | 42 | describe('getNodeValue', function() { 43 | 44 | it('should parse xpath', cases([ 45 | [ '', 'a/@b' ], 46 | [ 'c', 'a/b' ], 47 | [ '', 'a/b' ] 48 | ], function (source, path) { 49 | var node = xpath.select(path , xmlParser.parseFromString(source))[0]; 50 | expect(xml.getNodeValue(node)).to.deep.equal('c'); 51 | })); 52 | 53 | }); 54 | 55 | describe('XmlString', function() { 56 | 57 | it('should indicate if object is xml string', cases([ 58 | [ {}, false ], 59 | [ new xml.XmlString(''), true ] 60 | ], function (object, isXmlString) { 61 | expect(xml.isXmlString(object)).to.equal(isXmlString); 62 | })); 63 | 64 | it('should return xml string', function () { 65 | var xmlString = new xml.XmlString(''); 66 | expect(xmlString.source).to.equal(''); 67 | }); 68 | 69 | }); 70 | 71 | describe('CDataValue', function() { 72 | 73 | it('should indicate if object is cdata value', cases([ 74 | [ {}, false ], 75 | [ new xml.CDataValue('value'), true ] 76 | ], function (object, isCDataValue) { 77 | expect(xml.isCDataValue(object)).to.equal(isCDataValue); 78 | })); 79 | 80 | it('should return cdata value', function () { 81 | var cdata = new xml.CDataValue('value'); 82 | expect(cdata.value).to.equal('value'); 83 | }); 84 | 85 | }); 86 | 87 | it('should join xpath', cases([ 88 | [ '/a/', '/b/', '/a/b/' ], 89 | [ null, '/b/', '/b/' ] 90 | ], function (path1, path2, result) { 91 | expect(xml.joinXPath(path1, path2)).to.equal(result); 92 | })); 93 | 94 | it('should map namespaces', cases([ 95 | [ '', 'attr', null, 'attr' ], 96 | [ '', 'x:attr', 'uri:yada', 'attr' ], 97 | [ '', 'x:attr', 'uri:yada', 'z:attr' ] 98 | ], function (source, sourceName, namespace, name) { 99 | var parent = xmlParser.parseFromString(source).firstChild; 100 | var node = xml.mapNamespaces(sourceName, parent, { x: 'uri:yada' }); 101 | expect(node.namespace).to.equal(namespace); 102 | expect(node.name).to.equal(name); 103 | })); 104 | 105 | it('should clear children', function () { 106 | var doc = xmlParser.parseFromString(''); 107 | xml.clearChildNodes(doc.firstChild); 108 | expect(doc.toString()).to.equal(''); 109 | }); 110 | 111 | it('should set node value', cases([ 112 | [ '', 'c', 'c' ], 113 | [ '', 'c', '' ], 114 | 115 | [ '', function(node, value) { return node.name + value ; }, '' ], 116 | [ 'c', function(node, value) { return node.nodeName + value; }, 'bc' ], 117 | 118 | [ '', new xml.CDataValue('c'), '' ], 119 | [ 'c', function(node, value) { return new xml.CDataValue(node.nodeName + value); }, 120 | '' ], 121 | 122 | [ '', new xml.XmlString('d'), 'd' ], 123 | [ 'c', function(node, value) { 124 | return new xml.XmlString('<' + value + '>' + node.nodeName + ''); }, 125 | 'b' ] 126 | ], function (source, value, result) { 127 | var doc = xmlParser.parseFromString(source); 128 | xml.setNodeValue(doc.firstChild.firstChild || 129 | doc.firstChild.attributes[0], value); 130 | expect(doc.toString()).to.equal(result); 131 | })); 132 | 133 | it('should get node value', cases([ 134 | [ 'c' ], 135 | [ '' ] 136 | ], function (source) { 137 | var doc = xmlParser.parseFromString(source); 138 | var result = xml.getNodeValue(doc.firstChild.firstChild || 139 | doc.firstChild.attributes[0]); 140 | expect(result).to.equal('c'); 141 | })); 142 | 143 | describe('addNode', function() { 144 | 145 | it('should add attribute with value', function () { 146 | var doc = xmlParser.parseFromString(''); 147 | xml.addNode({}, doc.firstChild, 'b', true, 'c'); 148 | expect(doc.toString()).to.equal(''); 149 | }); 150 | 151 | it('should add element without value', function () { 152 | var doc = xmlParser.parseFromString(''); 153 | xml.addNode({}, doc.firstChild, 'b', false); 154 | expect(doc.toString()).to.equal(''); 155 | }); 156 | 157 | it('should add element with string value', function () { 158 | var doc = xmlParser.parseFromString(''); 159 | xml.addNode({}, doc.firstChild, 'b', false, 'c'); 160 | expect(doc.toString()).to.equal('c'); 161 | }); 162 | 163 | it('should add attribute with mapped namespace', function () { 164 | var doc = xmlParser.parseFromString(''); 165 | xml.addNode({ x: 'uri:yada' }, doc.firstChild, 'x:b', true, 'c'); 166 | expect(doc.toString()).to.equal(''); 167 | }); 168 | 169 | it('should add element with mapped namespace', function () { 170 | var doc = xmlParser.parseFromString(''); 171 | xml.addNode({ x: 'uri:yada' }, doc.firstChild, 'x:b', false); 172 | expect(doc.toString()).to.equal(''); 173 | }); 174 | 175 | }); 176 | 177 | describe('getAttributesNamed', function() { 178 | 179 | it('should get attributes named', function () { 180 | var doc = xmlParser.parseFromString(''); 181 | var results = xml.getAttributesNamed({}, doc.firstChild.childNodes, 'd'); 182 | 183 | expect(results).to.have.length(2); 184 | 185 | expect(results[0].name).to.equal('d'); 186 | expect(results[0].value).to.equal('1'); 187 | 188 | expect(results[1].name).to.equal('d'); 189 | expect(results[1].value).to.equal('3'); 190 | }); 191 | 192 | it('should create if not found', function () { 193 | var doc = xmlParser.parseFromString(''); 194 | var results = xml.getAttributesNamed({}, doc.firstChild.childNodes, 'd'); 195 | 196 | expect(results).to.have.length(2); 197 | 198 | expect(results[0].name).to.equal('d'); 199 | expect(results[0].value).to.be.empty; 200 | 201 | expect(results[1].name).to.equal('d'); 202 | expect(results[1].value).to.be.empty; 203 | 204 | results[0].value = ''; 205 | results[1].value = ''; 206 | 207 | expect(doc.toString()).to.equal(''); 208 | }); 209 | 210 | it('should create if not found and map namespace', function () { 211 | var doc = xmlParser.parseFromString(''); 212 | var results = xml.getAttributesNamed({ x: 'uri:yada' }, doc.firstChild.childNodes, 'x:d'); 213 | 214 | expect(results).to.have.length(1); 215 | 216 | expect(results[0].name).to.equal('z:d'); 217 | expect(results[0].value).to.be.empty; 218 | 219 | results[0].value = ''; 220 | 221 | expect(doc.toString()).to.equal(''); 222 | }); 223 | 224 | }); 225 | 226 | describe('getChildElementsNamed', function() { 227 | 228 | it('should get elements named', function () { 229 | var doc = xmlParser.parseFromString('1243'); 230 | var results = xml.getChildElementsNamed({}, doc.firstChild.childNodes, 'd'); 231 | 232 | expect(results).to.have.length(2); 233 | 234 | expect(results[0].nodeName).to.equal('d'); 235 | expect(results[0].textContent).to.equal('1'); 236 | 237 | expect(results[1].nodeName).to.equal('d'); 238 | expect(results[1].textContent).to.equal('3'); 239 | }); 240 | 241 | it('should create if not found', function () { 242 | var doc = xmlParser.parseFromString('24'); 243 | var results = xml.getChildElementsNamed({}, doc.firstChild.childNodes, 'd'); 244 | 245 | expect(results).to.have.length(2); 246 | 247 | expect(results[0].nodeName).to.equal('d'); 248 | expect(results[0].textContent).to.be.empty; 249 | 250 | expect(results[1].nodeName).to.equal('d'); 251 | expect(results[1].textContent).to.be.empty; 252 | 253 | expect(doc.toString()).to.equal('24'); 254 | }); 255 | 256 | it('should create if not found and map namespace', function () { 257 | var doc = xmlParser.parseFromString(''); 258 | var results = xml.getChildElementsNamed({ x: 'uri:yada' }, doc.firstChild.childNodes, 'x:d'); 259 | 260 | expect(results).to.have.length(1); 261 | 262 | expect(results[0].prefix).to.equal('z'); 263 | expect(results[0].localName).to.equal('d'); 264 | expect(results[0].textContent).to.be.empty; 265 | 266 | expect(doc.toString()).to.equal(''); 267 | }); 268 | 269 | }); 270 | 271 | }); 272 | -------------------------------------------------------------------------------- /test/document.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var expect = require('chai').expect, 4 | cases = require('cases'), 5 | temp = require('temp').track(), 6 | fs = require('fs'), 7 | Document = require('../src/document'); 8 | 9 | describe('document', function() { 10 | 11 | var xmlfile; 12 | 13 | beforeEach(function() { 14 | xmlfile = temp.openSync().path; 15 | }); 16 | 17 | afterEach(function() { 18 | temp.cleanupSync(); 19 | }); 20 | 21 | it('should open an xml file', function() { 22 | fs.writeFileSync(xmlfile, ''); 23 | expect(Document.open(xmlfile).toString()) 24 | .to.equal(''); 25 | }); 26 | 27 | it('should save an xml file', function() { 28 | fs.writeFileSync(xmlfile, ''); 29 | var doc = Document.open(xmlfile); 30 | doc.set('a/b', 'c'); 31 | doc.save(); 32 | expect(fs.readFileSync(xmlfile).toString()) 33 | .to.equal('c'); 34 | }); 35 | 36 | describe('clear', function() { 37 | 38 | it('should clear child nodes', function() { 39 | expect(Document.load('') 40 | .clear('a').toString()) 41 | .to.equal(''); 42 | }); 43 | 44 | it('should not fail if no matches by default', function() { 45 | expect(Document.load('') 46 | .clear('a/b').toString()) 47 | .to.equal(''); 48 | }); 49 | 50 | it('should fail if no matches when configured', function() { 51 | expect(function() { 52 | Document.load('') 53 | .errorOnNoMatches() 54 | .clear('a/b'); }) 55 | .to.throw("No matching nodes for xpath 'a/b'."); 56 | }); 57 | 58 | }); 59 | 60 | describe('remove', function() { 61 | 62 | it('should remove nodes', function() { 63 | expect(Document.load('') 64 | .remove('a/b').toString()) 65 | .to.equal(''); 66 | }); 67 | 68 | }); 69 | 70 | describe('ensure', function() { 71 | 72 | it('should create missing nodes', cases([ 73 | [ "a/b/c", '' ], 74 | [ "a/b[c='1']/d[@e='2']", '1' ], 75 | [ "a/b[@c='1']/d[e='2']", '2' ], 76 | [ "a/b[@c='1' and @d='2']/e[f='3']", '3' ], 77 | ], function (xpath, result) { 78 | expect(Document.load('') 79 | .ensure(xpath).toString()) 80 | .to.equal(result); 81 | })); 82 | 83 | it('should create missing nodes with base path', function () { 84 | expect(Document.load('') 85 | .withBasePath('a') 86 | .ensure('b/c').toString()) 87 | .to.equal(''); 88 | }); 89 | 90 | it('should ignore existing elements', function() { 91 | expect(Document.load('3') 92 | .ensure("a/b[@c='1' and @d='2']/e[f='3']").toString()) 93 | .to.equal('3'); 94 | }); 95 | 96 | }); 97 | 98 | describe('set', function() { 99 | 100 | describe('base path', function() { 101 | 102 | it('should add base path when configured', function() { 103 | expect(Document.load('') 104 | .withBasePath('a') 105 | .set('b', 'c').toString()) 106 | .to.equal('c'); 107 | }); 108 | 109 | }); 110 | 111 | describe('object values', function() { 112 | 113 | it('should add multiple', function() { 114 | expect(Document.load('') 115 | .set({ 'a/b': 'd', 'a/c': 'e' }).toString()) 116 | .to.equal('de'); 117 | }); 118 | 119 | }); 120 | 121 | describe('missing elements', function() { 122 | 123 | it('should not fail to set missing value by default', function() { 124 | expect(function() { Document.load('').set('a/b/c', 'c'); }) 125 | .not.to.throw(); 126 | }); 127 | 128 | it('should fail to set missing value when configured', function() { 129 | expect(function() { 130 | Document.load('') 131 | .errorOnNoMatches() 132 | .set('a/b/c', 'c'); }) 133 | .to.throw("No matching nodes for xpath 'a/b/c'."); 134 | }); 135 | 136 | }); 137 | 138 | describe('namespaces', function() { 139 | 140 | it('should set node value with default namespace when configured', function() { 141 | expect(Document.load('') 142 | .addNamespace('z', 'uri:yada') 143 | .set('/z:a/z:b', 'c').toString()) 144 | .to.equal('c'); 145 | }); 146 | 147 | it('should set attribute value with default namespace when configured', function() { 148 | expect(Document.load('') 149 | .addNamespace('z', 'uri:yada') 150 | .set('/z:a/@b', 'c').toString()) 151 | .to.equal(''); 152 | }); 153 | 154 | it('should set node value with namespace when configured', function() { 155 | expect(Document.load('') 156 | .addNamespace('z', 'uri:yada') 157 | .set('a/z:b', 'c').toString()) 158 | .to.equal('c'); 159 | }); 160 | 161 | it('should set attribute value with namespace when configured', function() { 162 | expect(Document.load('') 163 | .addNamespace('z', 'uri:yada') 164 | .set('a/@z:b', 'c').toString()) 165 | .to.equal(''); 166 | }); 167 | 168 | }); 169 | 170 | describe('string', function() { 171 | 172 | it('should set attribute value', function() { 173 | expect(Document.load('') 174 | .set('a/@b', 'c').toString()) 175 | .to.equal(''); 176 | }); 177 | 178 | it('should set node value', function() { 179 | expect(Document.load('') 180 | .set('a', 'b').toString()) 181 | .to.equal('b'); 182 | }); 183 | 184 | it('should set node cdata value', function() { 185 | expect(Document.load('') 186 | .set('a', new Document.CDataValue('b')).toString()) 187 | .to.equal(''); 188 | }); 189 | 190 | it('should set node xml value', function() { 191 | expect(Document.load('') 192 | .set('a', new Document.XmlString('')).toString()) 193 | .to.equal(''); 194 | }); 195 | 196 | }); 197 | 198 | describe('function', function() { 199 | 200 | it('should set attribute value', function() { 201 | expect(Document.load('') 202 | .set('a/@b', function(node, value) { 203 | return [ node.nodeName, value, 'd' ].join('-'); }).toString()) 204 | .to.equal(''); 205 | }); 206 | 207 | it('should set node value', function() { 208 | expect(Document.load('b') 209 | .set('a', function(node, value) { 210 | return [ node.nodeName, value, 'c' ].join('-'); }).toString()) 211 | .to.equal('a-b-c'); 212 | }); 213 | 214 | it('should set node cdata value', function() { 215 | expect(Document.load('b') 216 | .set('a', function(node, value) { 217 | return new Document.CDataValue([ node.nodeName, value, 'c' ].join('-')); }).toString()) 218 | .to.equal(''); 219 | }); 220 | 221 | it('should set node xml value', function() { 222 | expect(Document.load('b') 223 | .set('a', function(node, value) { 224 | return new Document.XmlString('<' + value + '>' + node.nodeName + ''); }).toString()) 225 | .to.equal('a'); 226 | }); 227 | 228 | }); 229 | 230 | describe('object string', function() { 231 | 232 | it('should set attribute value', function() { 233 | expect(Document.load('') 234 | .set('a', { '@b': 'c' }).toString()) 235 | .to.equal(''); 236 | }); 237 | 238 | it('should set node value', function() { 239 | expect(Document.load('') 240 | .set('a', { b: 'c' }).toString()) 241 | .to.equal('c'); 242 | }); 243 | 244 | it('should set node cdata value', function() { 245 | expect(Document.load('') 246 | .set('a', { b: new Document.CDataValue('c') }).toString()) 247 | .to.equal(''); 248 | }); 249 | 250 | it('should set node sql value', function() { 251 | expect(Document.load('') 252 | .set('a', { b: new Document.XmlString('') }).toString()) 253 | .to.equal(''); 254 | }); 255 | 256 | }); 257 | 258 | describe('object function', function() { 259 | 260 | it('should set attribute value', function() { 261 | expect(Document.load('') 262 | .set('a', { '@b': function(node, value) { 263 | return [ node.nodeName, value, 'd' ].join('-'); } }).toString()) 264 | .to.equal(''); 265 | }); 266 | 267 | it('should set node value', function() { 268 | expect(Document.load('c') 269 | .set('a', { b: function(node, value) { 270 | return [ node.nodeName, value, 'd' ].join('-'); } }).toString()) 271 | .to.equal('b-c-d'); 272 | }); 273 | 274 | it('should set node cdata value', function() { 275 | expect(Document.load('c') 276 | .set('a', { b: function(node, value) { 277 | return new Document.CDataValue([ node.nodeName, value ].join('-')); } }).toString()) 278 | .to.equal(''); 279 | }); 280 | 281 | it('should set node xml value', function() { 282 | expect(Document.load('c') 283 | .set('a', { b: function(node, value) { 284 | return new Document.XmlString('<' + value + '>' + node.nodeName + ''); } }).toString()) 285 | .to.equal('b'); 286 | }); 287 | 288 | }); 289 | 290 | }); 291 | 292 | describe('add', function() { 293 | 294 | describe('base path', function() { 295 | 296 | it('should add base path when configured', function() { 297 | expect(Document.load('') 298 | .withBasePath('a') 299 | .add('b', 'c').toString()) 300 | .to.equal('c'); 301 | }); 302 | 303 | }); 304 | 305 | describe('object values', function() { 306 | 307 | it('should add multiple', function() { 308 | expect(Document.load('') 309 | .add({ 'a/b': 'd', 'a/c': 'e' }).toString()) 310 | .to.equal('de'); 311 | }); 312 | 313 | }); 314 | 315 | describe('missing elements', function() { 316 | 317 | it('should not fail to set missing value by default', function() { 318 | expect(function() { Document.load('').add('a/b/c', 'c'); }) 319 | .not.to.throw(); 320 | }); 321 | 322 | it('should fail to set missing value when configured', function() { 323 | expect(function() { 324 | Document.load('') 325 | .errorOnNoMatches() 326 | .add('a/b/c', 'c'); }) 327 | .to.throw('No matching nodes for xpath \'a/b\'.'); 328 | }); 329 | 330 | }); 331 | 332 | describe('namespaces', function() { 333 | 334 | it('should set node value with default namespace when configured', function() { 335 | expect(Document.load('') 336 | .addNamespace('z', 'uri:yada') 337 | .add('/z:a/z:b', 'c').toString()) 338 | .to.equal('c'); 339 | }); 340 | 341 | it('should set attribute value with default namespace when configured', function() { 342 | expect(Document.load('') 343 | .addNamespace('z', 'uri:yada') 344 | .add('/z:a/@b', 'c').toString()) 345 | .to.equal(''); 346 | }); 347 | 348 | it('should set node value with namespace when configured', function() { 349 | expect(Document.load('') 350 | .addNamespace('z', 'uri:yada') 351 | .add('a/z:b', 'c').toString()) 352 | .to.equal('c'); 353 | }); 354 | 355 | it('should set attribute value with namespace when configured', function() { 356 | expect(Document.load('') 357 | .addNamespace('z', 'uri:yada') 358 | .add('a/@z:b', 'c').toString()) 359 | .to.equal(''); 360 | }); 361 | 362 | }); 363 | 364 | describe('string', function() { 365 | 366 | it('should set attribute value', function() { 367 | expect(Document.load('') 368 | .add('a/@b', 'c').toString()) 369 | .to.equal(''); 370 | }); 371 | 372 | it('should set node value', function() { 373 | expect(Document.load('') 374 | .add('a/b', 'c').toString()) 375 | .to.equal('c'); 376 | }); 377 | 378 | it('should set node cdata value', function() { 379 | expect(Document.load('') 380 | .add('a/b', new Document.CDataValue('c')).toString()) 381 | .to.equal(''); 382 | }); 383 | 384 | it('should set node xml value', function() { 385 | expect(Document.load('') 386 | .add('a/b', new Document.XmlString('')).toString()) 387 | .to.equal(''); 388 | }); 389 | 390 | }); 391 | 392 | describe('function', function() { 393 | 394 | it('should set attribute value', function() { 395 | expect(Document.load('') 396 | .add('a/@b', function(node, value) { 397 | return [ node.nodeName, value, 'c' ].join('-'); }).toString()) 398 | .to.equal(''); 399 | }); 400 | 401 | it('should set node value', function() { 402 | expect(Document.load('') 403 | .add('a/b', function(node, value) { 404 | return [ node.nodeName, value, 'c' ].join('-'); }).toString()) 405 | .to.equal('b--c'); 406 | }); 407 | 408 | it('should set node cdata value', function() { 409 | expect(Document.load('') 410 | .add('a/b', function(node, value) { 411 | return new Document.CDataValue([ node.nodeName, value, 'c' ].join('-')); }).toString()) 412 | .to.equal(''); 413 | }); 414 | 415 | it('should set node xml value', function() { 416 | expect(Document.load('') 417 | .add('a/b', function(node, value) { 418 | return new Document.XmlString('<' + node.nodeName + '>' + value + ''); }).toString()) 419 | .to.equal(''); 420 | }); 421 | 422 | }); 423 | 424 | describe('object string', function() { 425 | 426 | it('should set attribute value', function() { 427 | expect(Document.load('') 428 | .add('a/b', { '@c': 'd' }).toString()) 429 | .to.equal(''); 430 | }); 431 | 432 | it('should set node value', function() { 433 | expect(Document.load('') 434 | .add('a/b', { c: 'd' }).toString()) 435 | .to.equal('d'); 436 | }); 437 | 438 | it('should set node cdata value', function() { 439 | expect(Document.load('') 440 | .add('a/b', { c: new Document.CDataValue('d') }).toString()) 441 | .to.equal(''); 442 | }); 443 | 444 | it('should set node sql value', function() { 445 | expect(Document.load('') 446 | .add('a/b', { c: new Document.XmlString('') }).toString()) 447 | .to.equal(''); 448 | }); 449 | 450 | }); 451 | 452 | describe('object function', function() { 453 | 454 | it('should set attribute value', function() { 455 | expect(Document.load('') 456 | .add('a/b', { '@c': function(node, value) { 457 | return [ node.nodeName, value, 'd' ].join('-'); } }).toString()) 458 | .to.equal(''); 459 | }); 460 | 461 | it('should set node value', function() { 462 | expect(Document.load('') 463 | .add('a/b', { c: function(node, value) { 464 | return [ node.nodeName, value, 'd' ].join('-'); } }).toString()) 465 | .to.equal('c--d'); 466 | }); 467 | 468 | it('should set node cdata value', function() { 469 | expect(Document.load('') 470 | .add('a/b', { c: function(node, value) { 471 | return new Document.CDataValue([ node.nodeName, value ].join('-')); } }).toString()) 472 | .to.equal(''); 473 | }); 474 | 475 | it('should set node xml value', function() { 476 | expect(Document.load('') 477 | .add('a/b', { c: function(node, value) { 478 | return new Document.XmlString('<' + node.nodeName + '>' + value + ''); } }).toString()) 479 | .to.equal(''); 480 | }); 481 | 482 | }); 483 | 484 | }); 485 | 486 | describe('setOrAdd', function() { 487 | 488 | describe('base path', function() { 489 | 490 | it('should add base path when configured', function() { 491 | expect(Document.load('') 492 | .withBasePath('a') 493 | .setOrAdd('b', 'c').toString()) 494 | .to.equal('c'); 495 | }); 496 | 497 | }); 498 | 499 | describe('object values', function() { 500 | 501 | it('should add multiple', function() { 502 | expect(Document.load('') 503 | .setOrAdd({ 'a/b': 'd', 'a/c': 'e' }).toString()) 504 | .to.equal('de'); 505 | }); 506 | 507 | }); 508 | 509 | describe('missing elements', function() { 510 | 511 | it('should not fail to create missing parent node by default', function() { 512 | expect(function() { Document.load('').setOrAdd('a/b/c', 'd'); }) 513 | .not.to.throw(); 514 | }); 515 | 516 | it('should fail to create missing parent node when configured', function() { 517 | expect(function() { 518 | Document.load('') 519 | .errorOnNoMatches() 520 | .setOrAdd('a/b/c', 'd'); }) 521 | .to.throw("No matching nodes for xpath 'a/b'."); 522 | }); 523 | 524 | }); 525 | 526 | describe('namespaces', function() { 527 | 528 | it('should add node value with default namespace when configured', function() { 529 | expect(Document.load('') 530 | .addNamespace('z', 'uri:yada') 531 | .setOrAdd('/z:a/z:b', 'c').toString()) 532 | .to.equal('c'); 533 | }); 534 | 535 | it('should add attribute value with default namespace when configured', function() { 536 | expect(Document.load('') 537 | .addNamespace('z', 'uri:yada') 538 | .setOrAdd('/z:a/@b', 'c').toString()) 539 | .to.equal(''); 540 | }); 541 | 542 | it('should add non existing node value with namespace when configured', function() { 543 | expect(Document.load('') 544 | .addNamespace('z', 'uri:yada') 545 | .setOrAdd('a/z:b', 'c').toString()) 546 | .to.equal('c'); 547 | }); 548 | 549 | it('should add attribute value with namespace when configured', function() { 550 | expect(Document.load('') 551 | .addNamespace('z', 'uri:yada') 552 | .setOrAdd('a/@z:b', 'c').toString()) 553 | .to.equal(''); 554 | }); 555 | 556 | it('should add node object value with namespace when configured', function() { 557 | expect(Document.load('') 558 | .addNamespace('z', 'uri:yada') 559 | .setOrAdd('a', { 'z:b': 'c' }).toString()) 560 | .to.equal('c'); 561 | }); 562 | 563 | it('should add attribute object value with namespace when configured', function() { 564 | expect(Document.load('') 565 | .addNamespace('z', 'uri:yada') 566 | .setOrAdd('a', { '@z:b': 'c' }).toString()) 567 | .to.equal(''); 568 | }); 569 | 570 | }); 571 | 572 | describe('string', function() { 573 | 574 | it('should add attribute value', function() { 575 | expect(Document.load('') 576 | .setOrAdd('a/@b', 'c').toString()) 577 | .to.equal(''); 578 | }); 579 | 580 | it('should add node value', function() { 581 | expect(Document.load('') 582 | .setOrAdd('a/b', 'c').toString()) 583 | .to.equal('c'); 584 | }); 585 | 586 | it('should add node cdata value', function() { 587 | expect(Document.load('') 588 | .setOrAdd('a/b', new Document.CDataValue('c')).toString()) 589 | .to.equal(''); 590 | }); 591 | 592 | it('should add node xml value', function() { 593 | expect(Document.load('') 594 | .setOrAdd('a/b', new Document.XmlString('')).toString()) 595 | .to.equal(''); 596 | }); 597 | 598 | }); 599 | 600 | describe('function', function() { 601 | 602 | it('should add attribute value with function', function() { 603 | expect(Document.load('') 604 | .setOrAdd('a/@b', function(node, value) { 605 | return [ node.nodeName, value, 'c' ].join('-'); }).toString()) 606 | .to.equal(''); 607 | }); 608 | 609 | it('should add node value with function', function() { 610 | expect(Document.load('') 611 | .setOrAdd('a/b', function(node, value) { 612 | return [ node.nodeName, value, 'c' ].join('-'); }).toString()) 613 | .to.equal('b--c'); 614 | }); 615 | 616 | it('should add node cdata value', function() { 617 | expect(Document.load('') 618 | .setOrAdd('a/b', function(node, value) { 619 | return new Document.CDataValue([ node.nodeName, value, 'c' ].join('-')); }).toString()) 620 | .to.equal(''); 621 | }); 622 | 623 | it('should add node xml value', function() { 624 | expect(Document.load('') 625 | .setOrAdd('a/b', function(node) { 626 | return new Document.XmlString('' + node.nodeName + ''); }).toString()) 627 | .to.equal('b'); 628 | }); 629 | 630 | }); 631 | 632 | describe('object string', function() { 633 | 634 | it('should add attribute value with object string', function() { 635 | expect(Document.load('') 636 | .setOrAdd('a', { '@b': 'c' }).toString()) 637 | .to.equal(''); 638 | }); 639 | 640 | it('should add node value with object string', function() { 641 | expect(Document.load('') 642 | .setOrAdd('a', { b: 'c' }).toString()) 643 | .to.equal('c'); 644 | }); 645 | 646 | it('should add node cdata value', function() { 647 | expect(Document.load('') 648 | .setOrAdd('a', { b: new Document.CDataValue('c') }).toString()) 649 | .to.equal(''); 650 | }); 651 | 652 | it('should add node sql value', function() { 653 | expect(Document.load('') 654 | .setOrAdd('a', { b: new Document.XmlString('') }).toString()) 655 | .to.equal(''); 656 | }); 657 | 658 | }); 659 | 660 | describe('object function', function() { 661 | 662 | it('should add attribute value with object function', function() { 663 | expect(Document.load('') 664 | .setOrAdd('a', { '@b': function(node, value) { 665 | return [ node.nodeName, value, 'c' ].join('-'); } }).toString()) 666 | .to.equal(''); 667 | }); 668 | 669 | it('should add node value with object function', function() { 670 | expect(Document.load('') 671 | .setOrAdd('a', { b: function(node, value) { 672 | return [ node.nodeName, value, 'c' ].join('-'); } }).toString()) 673 | .to.equal('b--c'); 674 | }); 675 | 676 | it('should set node cdata value', function() { 677 | expect(Document.load('') 678 | .setOrAdd('a', { b: function(node, value) { 679 | return new Document.CDataValue([ node.nodeName, value, 'c' ].join('-')); } }).toString()) 680 | .to.equal(''); 681 | }); 682 | 683 | it('should set node xml value', function() { 684 | expect(Document.load('') 685 | .setOrAdd('a', { b: function(node) { 686 | return new Document.XmlString('' + node.nodeName + ''); } }).toString()) 687 | .to.equal('b'); 688 | }); 689 | 690 | }); 691 | 692 | }); 693 | 694 | }); 695 | --------------------------------------------------------------------------------