0.7.1 • Published 9 years ago

grunt-xmlstoke v0.7.1

Weekly downloads
118
License
MIT
Repository
github
Last release
9 years ago

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 for replacements Added reads option to extract node values by xpath and save them to grunt.option Added actions 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