grunt-xmlstoke v0.7.1
grunt-xmlstoke
An extended version of grunt-xmlpoke by Brian Dukes - a gruntjs port of the
xmlpoke
NAnt task.In addition to updating the values of existing nodes, as provided by grunt-xmlpoke, grunt-xmlstoke can perform all basic CRUD operations on XML Files: creating/inserting new nodes, deleting existing nodes, and reading the values of existing nodes (to then save them in
grunt.option
).The current API should be completely backward-compatible with grunt-xmlpoke
If you haven't used Grunt before, be sure to check out the Getting Started guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
npm install grunt-xmlstoke --save-dev
Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
grunt.loadNpmTasks('grunt-xmlstoke');
Overview
In your project's Gruntfile, add a section named xmlstoke
to the data object passed into grunt.initConfig()
.
grunt.initConfig({
xmlstoke: {
updateTitle: {
options: {
actions: [{
xpath: '//title',
value: 'The Good Parts'
}],
}
files: { 'dest.xml': 'src.xml' },
},
},
})
First character should be:
C or I - Create/Insert
R - Read
U - Update (this is the default, type can be left blank for Update as well)
D - Delete
For Insertion operations, this points to the parentNode/ownerNode to have the new elements/attributes inserted into.
For Updates, this function is passed the selected node. For Insertions, this function is passed the selected parentNode/ownerNode.
The name of the node to be inserted, e.g. "my-node"
to insert <my-node/>
into, or "@myattr"
to add the myattr
attribute to the node(s) selected with action.xpath
. Can also contain an XPath index (NOTE: unlike JavaScript Arrays ,XPath is 1-index) which is stripped from the name upon node creation, in order to create more than one of a kind:
actions: [
{ type: 'I', xpath: '/cds', node: 'cd', value: 'Slayer' },
{ type: 'I', xpath: '/cds', node: 'cd', value: 'More Slayer' }
]
Would find that /cds/cd
already exists after the first insertion, and instead update its value (compare: mysql insert on duplicate key update).
actions: [
{ type: 'I', xpath: '/cds', node: 'cd[1]', value: 'Slayer' },
{ type: 'I', xpath: '/cds', node: 'cd[2]', value: 'More Slayer' }
]
Would correctly insert two different CDs, the index being optional in the first line.
Examples:
callback: function (readValues) { // readValues := "100"
// Typecast to int before storing
return parseInt(readValues, 10);
}
callback: function (readValues) { // readValues := ["A", "B"]
if (readValues.length < 3) {
// Return null to throw a grunt.error and abort the task
// grunt.log.verbose("Aborting because there were only 2 nodes, expecting: 3+");
return null;
}
return readValues;
}
callback: function (readValues) { // readValues := null
if (readValues === null) {
// Prevent grunt from throwing an Error because no nodes matched
// grunt.log.verbose("Config elem not found, using default");
return grunt.option('myDefaultValue');;
}
return readValues;
}
An array of Action
objects, see Actions Options.
Examples:
actions: [
// Delete all <foo> nodes
{type: 'D', xpath: '//foo'},
// Insert <baz> into all <baz> under /myroot
{type: 'I', xpath: '/rootelem/bar', node: 'baz', },
// Add an athe foobar="100" attribute to them
{type: 'I', xpath: '/rootelem/bar/baz', node: '@foobar', value: '100'},
// Change it to 200 for the second of those <baz>, type=update assumed as default
{xpath: '/rootelem/bar/baz[2]/@foobar', value: '200'},
// Read the value of the foobar attr from the 5th overall occurence of <baz>,
// Save it in grunt.option as "myData"
{type: 'R', xpath: '//baz[5]/@foobar', saveAs: 'myData' }
}]
Alternatively, the reads
, deletions
, insertions
and updates
/replacements
shorthands can be used instead of actions
. That way you don't have to specify the action type manually.
The config arrays are processed in the order reads, deletions, insertions, replacements || updates, actions.
options.updates (alias: options.replacements)
Type: Array
,
Default value: undefined
An Array of Update Actions. action.type
is set automatically.
options.deletions
Type: Array
,
Default value: undefined
An Array of Deletion Actions. action.type
is set automatically.
options.reads
Type: Array
,
Default value: undefined
An Array of Read Actions. action.type
is set automatically.
options.insertions
Type: Array
,
Default value: undefined
An Array of Insertion Actions. action.type
is set automatically.
Example - Update with function as value
In this example, the value of an attribute is modified. So if the test.xml
file has the content <x y="abc" />
, the generated result in this case would be <x y="ABC" />
.
grunt.initConfig({
xmlstoke: {
upperCaseAttr: {
options: {
actions: [{
xpath: '/x/@y',
value: function (node) { return node.value.toUpperCase(); }
}]
},
files: {
'dest/output.xml': 'src/test.xml',
},
},
},
})
Example - Multiple XPath Selectors
In this example, the same value is updated in multiple locations. So if the testing.xml
file has the content <x y="999" />
, the generated result in this case would be <x y="111">111</x>
.
grunt.initConfig({
xmlstoke: {
updateAllTheThings: {
options: {
actions: [{
xpath: ['/x/@y','/x'],
value: '111'
]}
},
files: {
'dest/output.xml': 'src/test.xml',
},
},
},
})
Example - Deleting Nodes
Given <x><a /><a /><b /><c /></x>
, will delete all matched nodes and return <x></x>
(<x />
)
grunt.initConfig({
xmlstoke: {
deleteStuff: {
options: {
actions: [{
type: 'del'
xpath: ['/x/a', '//b'],
}]
},
files: {
'dest/output.xml': 'src/test.xml',
},
},
},
})
Example - Inserting Elements and Attributes
Given <x a="1"></x>
, returns <x a="2"><foo bar="baz"/></a>
. Notice how an Insertion just performs an update if the node exists already as seen with the @a
attribute
grunt.initConfig({
xmlstoke: {
updateAllTheThings: {
options: {
actions: [{
type: 'ins'
xpath: '//x',
node: '@a',
value: '2'
}, {
type: 'ins'
xpath: '//x',
node: 'foo'
}, {
type: 'ins'
xpath: '//x/foo',
node: '@bar'
value: 'baz'
}]
},
files: {
'dest/output.xml': 'src/test.xml',
},
},
},
})
Example - Namespaces and Reads
This example covers both basic Reads and namespaces. options.namespaces
must be specified whenever operations inolve namespaces. After that, simply use the usual ns:elemName
or ns:attrName
syntax everywhere.
Given <RDF><foo>A</foo><em:bar>B</em:bar></RDF>
, persists the values read from the tags in grunt.option, then retrieves them in value callbacks in order to swap them in the resulting XML: <RDF><foo>B</foo><em:bar>A</em:bar></RDF>
)
grunt.initConfig({
xmlstoke: {
rebuildScreensTag: {
options: {
namespaces: { 'em': 'http://www.mozilla.org/2004/em-rdf#' },
actions: [{
type: 'R'
xpath: '//foo',
saveAs: 'myFoo'
}, {
type: 'R'
xpath: '//em:bar',
saveAs: 'myBarAsArray',
returnArray: true
}, {
xpath: '//em:bar',
value: function (node) { return grunt.option('myFoo'); }
}, , {
xpath: '//foo',
value: function (node) { return grunt.option('myBarAsArray')[0]; }
}]
},
files: {
'dest/output.xml': 'src/test.xml',
},
},
},
})
Release History
- 0.1.0 — Initial release
- 0.2.0 — Multiple replacements at once
- 0.2.1 — Color filename when logged
- 0.3.0 — Allow specifying replacement value as a function
- 0.4.0 — Allow specifying namespaces
- 0.5.0 (point of fork from grunt-xmlpoke)
— Allow adding elements or attributes via
insertions
option - 0.5.1 — Bugfixes
- 0.5.2
— Allow index selection for insertion xpath (stripped from name for actual element creation)
— Allow removing elements via
deletions
option (barely tested) - 0.6.0 — Code cleanup
- 0.7.0
— Fixed deleting attribute nodes
— Added
updates
option as an alias forreplacements
— Addedreads
option to extract node values by xpath and save them togrunt.option
— Addedactions
option as a series of CRUD actions in arbitrary order — Added tests for deletions, reads and new alias parameters — TODO: Add tests for expected error scenarios