Permalink
Browse files

Finalizes attribute and tag name processing

resolves #116

- updates readme
- adds out-of-the-box processors
- updates tests
  • Loading branch information...
1 parent a4b9205 commit 83f18688dabdcda78a089c7adf54f1cbafd4e150 @creynders creynders committed Dec 16, 2013
Showing with 138 additions and 30 deletions.
  1. +53 −0 README.md
  2. +19 −0 lib/processors.js
  3. +12 −14 lib/xml2js.js
  4. +12 −0 src/processors.coffee
  5. +10 −12 src/xml2js.coffee
  6. +4 −4 test/parser.test.coffee
  7. +28 −0 test/processors.test.coffee
View
53 README.md
@@ -173,6 +173,41 @@ At the moment, a one to one bi-directional conversion is guaranteed only for
default configuration, except for `attrkey`, `charkey` and `explicitArray` options
you can redefine to your taste. Writing CDATA is not currently supported.
+Processing attribute and tag names
+----------------------------------
+
+Since 0.4.1 you can optionally provide the parser with attribute and tag name processors:
+
+```javascript
+
+function nameToUpperCase(name){
+ return name.toUpperCase();
+}
+
+//transform all attribute and tag names to uppercase
+parseString(xml, {tagNameProcessors: [nameToUpperCase], attrNameProcessors: [nameToUpperCase]}, function (err, result) {
+});
+```
+
+The `tagNameProcessors` and `attrNameProcessors` options both accept an `Array` of functions with the following signature:
+```javascript
+function (name){
+ //do something with `name`
+ return name
+}
+```
+
+Some processors are provided out-of-the-box and can be found in `lib/processors.js`:
+
+- `normalize`: transforms the name to lowercase.
+(Automatically used when `options.normalize` is set to `true`)
+
+- `firstCharLowerCase`: transforms the first character to lower case.
+E.g. 'MyTagName' becomes 'myTagName'
+
+- `stripPrefix`: strips the xml namespace prefix. E.g `<foo:Bar/>` will become 'Bar'.
+(N.B.: the `xmlns` prefix is NOT stripped.)
+
Options
=======
@@ -220,6 +255,24 @@ value})``. Possible options are:
* `strict` (default `true`): Set sax-js to strict or non-strict parsing mode.
Defaults to `true` which is *highly* recommended, since parsing HTML which
is not well-formed XML might yield just about anything. Added in 0.2.7.
+ * `attrNameProcessors` (default: `null`): Allows the addition of attribute name processing functions.
+ Accepts an `Array` of functions with following signature:
+ ```javascript
+ function (name){
+ //do something with `name`
+ return name
+ }
+ ```
+ Added in 0.4.1
+ * `tagNameProcessors` (default: `null`):Allows the addition of tag name processing functions.
+ Accepts an `Array` of functions with following signature:
+ ```javascript
+ function (name){
+ //do something with `name`
+ return name
+ }
+ ```
+ Added in 0.4.1
Options for the `Builder` class
-------------------------------
View
19 lib/processors.js
@@ -0,0 +1,19 @@
+// Generated by CoffeeScript 1.6.3
+(function() {
+ var prefixMatch;
+
+ prefixMatch = new RegExp(/(?!xmlns)^.*:/);
+
+ exports.normalize = function(str) {
+ return str.toLowerCase();
+ };
+
+ exports.firstCharLowerCase = function(str) {
+ return str.charAt(0).toLowerCase() + str.slice(1);
+ };
+
+ exports.stripPrefix = function(str) {
+ return str.replace(prefixMatch, '');
+ };
+
+}).call(this);
View
26 lib/xml2js.js
@@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.6.3
(function() {
- var bom, builder, events, isEmpty, normalizeTag, processName, sax,
+ var bom, builder, events, isEmpty, processName, processors, sax,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
@@ -13,14 +13,12 @@
bom = require('./bom');
+ processors = require('./processors');
+
isEmpty = function(thing) {
return typeof thing === "object" && (thing != null) && Object.keys(thing).length === 0;
};
- normalizeTag = function(name) {
- return name.toLowerCase();
- };
-
processName = function(processors, processedName) {
var process, _i, _len;
for (_i = 0, _len = processors.length; _i < _len; _i++) {
@@ -49,8 +47,8 @@
charsAsChildren: false,
async: false,
strict: true,
- attrNameProcessor: null,
- tagNameProcessor: null
+ attrNameProcessors: null,
+ tagNameProcessors: null
},
"0.2": {
explicitCharkey: false,
@@ -70,8 +68,8 @@
charsAsChildren: false,
async: false,
strict: true,
- attrNameProcessor: null,
- tagNameProcessor: null,
+ attrNameProcessors: null,
+ tagNameProcessors: null,
rootName: 'root',
xmldec: {
'version': '1.0',
@@ -196,10 +194,10 @@
this.options.xmlnskey = this.options.attrkey + "ns";
}
if (this.options.normalizeTags) {
- if (!this.options.tagNameProcessor) {
- this.options.tagNameProcessor = [];
+ if (!this.options.tagNameProcessors) {
+ this.options.tagNameProcessors = [];
}
- this.options.tagNameProcessor.push(normalizeTag);
+ this.options.tagNameProcessors.unshift(processors.normalize);
}
this.reset();
}
@@ -252,15 +250,15 @@
obj[attrkey] = {};
}
newValue = node.attributes[key];
- processedKey = _this.options.attrNameProcessor ? processName(_this.options.attrNameProcessor, key) : key;
+ processedKey = _this.options.attrNameProcessors ? processName(_this.options.attrNameProcessors, key) : key;
if (_this.options.mergeAttrs) {
_this.assignOrPush(obj, processedKey, newValue);
} else {
obj[attrkey][processedKey] = newValue;
}
}
}
- obj["#name"] = _this.options.tagNameProcessor ? processName(_this.options.tagNameProcessor, node.name) : node.name;
+ obj["#name"] = _this.options.tagNameProcessors ? processName(_this.options.tagNameProcessors, node.name) : node.name;
if (_this.options.xmlns) {
obj[_this.options.xmlnskey] = {
uri: node.uri,
View
12 src/processors.coffee
@@ -0,0 +1,12 @@
+# matches all xml prefixes, except for `xmlns:`
+prefixMatch = new RegExp /(?!xmlns)^.*:/
+
+exports.normalize = (str) ->
+ return str.toLowerCase()
+
+exports.firstCharLowerCase = (str) ->
+ return str.charAt(0).toLowerCase() + str.slice(1)
+
+exports.stripPrefix = (str) ->
+ return str.replace(prefixMatch, '')
+
View
22 src/xml2js.coffee
@@ -2,14 +2,12 @@ sax = require 'sax'
events = require 'events'
builder = require 'xmlbuilder'
bom = require './bom'
+processors = require './processors'
# Underscore has a nice function for this, but we try to go without dependencies
isEmpty = (thing) ->
return typeof thing is "object" && thing? && Object.keys(thing).length is 0
-normalizeTag = (name) ->
- return name.toLowerCase()
-
processName = (processors, processedName) ->
processedName = process(processedName) for process in processors
return processedName
@@ -43,8 +41,8 @@ exports.defaults =
# callbacks are async? not in 0.1 mode
async: false
strict: true
- attrNameProcessor: null
- tagNameProcessor: null
+ attrNameProcessors: null
+ tagNameProcessors: null
"0.2":
explicitCharkey: false
@@ -65,8 +63,8 @@ exports.defaults =
# not async in 0.2 mode either
async: false
strict: true
- attrNameProcessor: null
- tagNameProcessor: null
+ attrNameProcessors: null
+ tagNameProcessors: null
# xml building options
rootName: 'root'
xmldec: {'version': '1.0', 'encoding': 'UTF-8', 'standalone': true}
@@ -151,9 +149,9 @@ class exports.Parser extends events.EventEmitter
if @options.xmlns
@options.xmlnskey = @options.attrkey + "ns"
if @options.normalizeTags
- if ! @options.tagNameProcessor
- @options.tagNameProcessor = []
- @options.tagNameProcessor.push normalizeTag
+ if ! @options.tagNameProcessors
+ @options.tagNameProcessors = []
+ @options.tagNameProcessors.unshift processors.normalize
@reset()
@@ -205,14 +203,14 @@ class exports.Parser extends events.EventEmitter
if attrkey not of obj and not @options.mergeAttrs
obj[attrkey] = {}
newValue = node.attributes[key]
- processedKey = if @options.attrNameProcessor then processName(@options.attrNameProcessor, key) else key
+ processedKey = if @options.attrNameProcessors then processName(@options.attrNameProcessors, key) else key
if @options.mergeAttrs
@assignOrPush obj, processedKey, newValue
else
obj[attrkey][processedKey] = newValue
# need a place to store the node name
- obj["#name"] = if @options.tagNameProcessor then processName(@options.tagNameProcessor, node.name) else node.name
+ obj["#name"] = if @options.tagNameProcessors then processName(@options.tagNameProcessors, node.name) else node.name
if (@options.xmlns)
obj[@options.xmlnskey] = {uri: node.uri, local: node.local}
stack.push obj
View
8 test/parser.test.coffee
@@ -347,22 +347,22 @@ module.exports =
assert.deepEqual resWithNew, resWithoutNew
test.done()
- 'test single attrNameProcessor': skeleton(attrNameProcessor: [nameToUpperCase], (r)->
+ 'test single attrNameProcessors': skeleton(attrNameProcessors: [nameToUpperCase], (r)->
console.log 'Result object: ' + util.inspect r, false, 10
equ r.sample.attrNameProcessTest[0].$.hasOwnProperty('CAMELCASEATTR'), true
equ r.sample.attrNameProcessTest[0].$.hasOwnProperty('LOWERCASEATTR'), true)
- 'test multiple attrNameProcessor': skeleton(attrNameProcessor: [nameToUpperCase, nameCutoff], (r)->
+ 'test multiple attrNameProcessors': skeleton(attrNameProcessors: [nameToUpperCase, nameCutoff], (r)->
console.log 'Result object: ' + util.inspect r, false, 10
equ r.sample.attrNameProcessTest[0].$.hasOwnProperty('CAME'), true
equ r.sample.attrNameProcessTest[0].$.hasOwnProperty('LOWE'), true)
- 'test single tagNameProcessor': skeleton(tagNameProcessor: [nameToUpperCase], (r)->
+ 'test single tagNameProcessors': skeleton(tagNameProcessors: [nameToUpperCase], (r)->
console.log 'Result object: ' + util.inspect r, false, 10
equ r.hasOwnProperty('SAMPLE'), true
equ r.SAMPLE.hasOwnProperty('TAGNAMEPROCESSTEST'), true)
- 'test multiple tagNameProcessor': skeleton(tagNameProcessor: [nameToUpperCase, nameCutoff], (r)->
+ 'test multiple tagNameProcessors': skeleton(tagNameProcessors: [nameToUpperCase, nameCutoff], (r)->
console.log 'Result object: ' + util.inspect r, false, 10
equ r.hasOwnProperty('SAMP'), true
equ r.SAMP.hasOwnProperty('TAGN'), true)
View
28 test/processors.test.coffee
@@ -0,0 +1,28 @@
+processors = require '../lib/processors'
+assert = require 'assert'
+equ = assert.equal
+
+module.exports =
+ 'test normalize': (test) ->
+ demo = 'This shOUld BE loWErcase'
+ result = processors.normalize demo
+ equ result, 'this should be lowercase'
+ test.done()
+
+ 'test firstCharLowerCase': (test) ->
+ demo = 'ThiS SHould OnlY LOwercase the fIRST cHar'
+ result = processors.firstCharLowerCase demo
+ equ result, 'thiS SHould OnlY LOwercase the fIRST cHar'
+ test.done()
+
+ 'test stripPrefix': (test) ->
+ demo = 'stripMe:DoNotTouch'
+ result = processors.stripPrefix demo
+ equ result, 'DoNotTouch'
+ test.done()
+
+ 'test stripPrefix, ignore xmlns': (test) ->
+ demo = 'xmlns:shouldHavePrefix'
+ result = processors.stripPrefix demo
+ equ result, 'xmlns:shouldHavePrefix'
+ test.done()

0 comments on commit 83f1868

Please sign in to comment.