diff --git a/README.md b/README.md index c3278f7e..38925b20 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,8 @@ value})``. Possible options are: Version 0.1 default was `@`. * `charkey` (default: `_`): Prefix that is used to access the character content. Version 0.1 default was `#`. + * `commentskey` (default: `$comments`): Prefix that is used to access the comments. + * `parseComments` (default: `false`): Determines whether comments should be parsed. * `explicitCharkey` (default: `false`) * `trim` (default: `false`): Trim the whitespace at the beginning and end of text nodes. @@ -377,6 +379,7 @@ Possible options are: Version 0.1 default was `@`. * `charkey` (default: `_`): Prefix that is used to access the character content. Version 0.1 default was `#`. + * `commentskey` (default: `$comments`): Prefix that is used to access the comments. * `rootName` (default `root` or the root key name): root element name to be used in case `explicitRoot` is `false` or to override the root element name. * `renderOpts` (default `{ 'pretty': true, 'indent': ' ', 'newline': '\n' }`): diff --git a/lib/builder.js b/lib/builder.js index 58f36384..97f3f439 100644 --- a/lib/builder.js +++ b/lib/builder.js @@ -38,9 +38,10 @@ } Builder.prototype.buildObject = function(rootObj) { - var attrkey, charkey, render, rootElement, rootName; + var attrkey, charkey, commentskey, render, rootElement, rootName; attrkey = this.options.attrkey; charkey = this.options.charkey; + commentskey = this.options.commentskey; if ((Object.keys(rootObj).length === 1) && (this.options.rootName === defaults['0.2'].rootName)) { rootName = Object.keys(rootObj)[0]; rootObj = rootObj[rootName]; @@ -49,7 +50,7 @@ } render = (function(_this) { return function(element, obj) { - var attr, child, entry, index, key, value; + var attr, child, entry, i, index, key, len, value; if (typeof obj !== 'object') { if (_this.options.cdata && requiresCDATA(obj)) { element.raw(wrapCDATA(obj)); @@ -82,6 +83,11 @@ } else { element = element.txt(child); } + } else if (key === commentskey) { + for (i = 0, len = child.length; i < len; i++) { + value = child[i]; + element = element.comment(value); + } } else if (Array.isArray(child)) { for (index in child) { if (!hasProp.call(child, index)) continue; diff --git a/lib/defaults.js b/lib/defaults.js index 0a21da0a..abaeb9fa 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -33,6 +33,8 @@ normalizeTags: false, attrkey: "$", charkey: "_", + commentskey: "$comments", + parseComments: false, explicitArray: true, ignoreAttrs: false, mergeAttrs: false, diff --git a/lib/parser.js b/lib/parser.js index 9e8261eb..720698bf 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -106,7 +106,7 @@ }; Parser.prototype.reset = function() { - var attrkey, charkey, ontext, stack; + var attrkey, charkey, commentskey, ontext, stack; this.removeAllListeners(); this.saxParser = sax.parser(this.options.strict, { trim: false, @@ -137,6 +137,7 @@ stack = []; attrkey = this.options.attrkey; charkey = this.options.charkey; + commentskey = this.options.commentskey; this.saxParser.onopentag = (function(_this) { return function(node) { var key, newValue, obj, processedKey, ref; @@ -284,7 +285,7 @@ }; })(this); this.saxParser.ontext = ontext; - return this.saxParser.oncdata = (function(_this) { + this.saxParser.oncdata = (function(_this) { return function(text) { var s; s = ontext(text); @@ -293,6 +294,19 @@ } }; })(this); + if (this.options.parseComments) { + return this.saxParser.oncomment = (function(_this) { + return function(text) { + var s; + s = stack[stack.length - 1]; + if (s) { + s[commentskey] = s[commentskey] || []; + s[commentskey].push(text.slice(1, -1)); + return s; + } + }; + })(this); + } }; Parser.prototype.parseString = function(str, cb) { diff --git a/src/builder.coffee b/src/builder.coffee index 5653fde0..207f05ca 100644 --- a/src/builder.coffee +++ b/src/builder.coffee @@ -30,6 +30,7 @@ class exports.Builder buildObject: (rootObj) -> attrkey = @options.attrkey charkey = @options.charkey + commentskey = @options.commentskey # If there is a sane-looking first element to use as the root, # and the user hasn't specified a non-default rootName, @@ -69,7 +70,12 @@ class exports.Builder else element = element.txt child - # Case #3 Array data + # Case #3 Comment + else if key is commentskey + for value in child + element = element.comment value + + # Case #4 Array data else if Array.isArray child for own index, entry of child if typeof entry is 'string' @@ -80,11 +86,11 @@ class exports.Builder else element = render(element.ele(key), entry).up() - # Case #4 Objects + # Case #5 Objects else if typeof child is "object" element = render(element.ele(key), child).up() - # Case #5 String and remaining types + # Case #6 String and remaining types else if typeof child is 'string' && @options.cdata && requiresCDATA child element = element.ele(key).raw(wrapCDATA child).up() diff --git a/src/defaults.coffee b/src/defaults.coffee index a9bd214b..49e35fd9 100644 --- a/src/defaults.coffee +++ b/src/defaults.coffee @@ -42,6 +42,8 @@ exports.defaults = { normalizeTags: false attrkey: "$" charkey: "_" + commentskey: "$comments" + parseComments: false explicitArray: true ignoreAttrs: false mergeAttrs: false diff --git a/src/parser.coffee b/src/parser.coffee index 8a375197..f4cc31c3 100644 --- a/src/parser.coffee +++ b/src/parser.coffee @@ -100,6 +100,7 @@ class exports.Parser extends events.EventEmitter # aliases, so we don't have to type so much attrkey = @options.attrkey charkey = @options.charkey + commentskey = @options.commentskey @saxParser.onopentag = (node) => obj = {} @@ -225,6 +226,14 @@ class exports.Parser extends events.EventEmitter if s s.cdata = true + if @options.parseComments + @saxParser.oncomment = (text) => + s = stack[stack.length - 1] + if s + s[commentskey] = s[commentskey] or [] + s[commentskey].push text.slice(1,-1) + s + parseString: (str, cb) => if cb? and typeof cb is "function" @on "end", (result) -> diff --git a/test/builder.test.coffee b/test/builder.test.coffee index e9c12be3..5523dc89 100644 --- a/test/builder.test.coffee +++ b/test/builder.test.coffee @@ -137,7 +137,7 @@ module.exports = fileName = path.join __dirname, '/fixtures/build_sample.xml' fs.readFile fileName, (err, xmlData) -> xmlExpected = xmlData.toString() - xml2js.parseString xmlData, {'trim': true}, (err, obj) -> + xml2js.parseString xmlData, {'trim': true, 'parseComments': true}, (err, obj) -> equ err, null builder = new xml2js.Builder({}) xmlActual = builder.buildObject obj diff --git a/test/fixtures/build_sample.xml b/test/fixtures/build_sample.xml index 494a1b18..4e5a8733 100644 --- a/test/fixtures/build_sample.xml +++ b/test/fixtures/build_sample.xml @@ -3,7 +3,9 @@ Character data here! + + This is character diff --git a/test/fixtures/sample.xml b/test/fixtures/sample.xml index 0ffea408..41caf2dd 100644 --- a/test/fixtures/sample.xml +++ b/test/fixtures/sample.xml @@ -4,11 +4,13 @@ + Line One Line Two + This Foo(1) is Foo(2) diff --git a/test/parser.test.coffee b/test/parser.test.coffee index 6d531d56..c5075474 100644 --- a/test/parser.test.coffee +++ b/test/parser.test.coffee @@ -72,6 +72,11 @@ module.exports = # determine number of items in object equ Object.keys(r.sample.tagcasetest[0]).length, 3) + 'test parse with parseComments': skeleton(parseComments: true, (r) -> + console.log 'Result object: ' + util.inspect r, false, 10 + equ r.sample.$comments[0], 'This is the first sample comment!' + equ r.sample.listtest[0].$comments[0], 'This is the second sample comment!') + 'test parse with explicitCharkey': skeleton(explicitCharkey: true, (r) -> console.log 'Result object: ' + util.inspect r, false, 10 equ r.sample.chartest[0].$.desc, 'Test for CHARs' @@ -190,32 +195,32 @@ module.exports = 'test parse with explicitChildren and charsAsChildren and preserveChildrenOrder and includeWhiteChars': skeleton(explicitChildren: true, preserveChildrenOrder: true, charsAsChildren: true, includeWhiteChars: true, (r) -> console.log 'Result object: ' + util.inspect r, false, 10 - equ r.sample.$$[35]['#name'], 'textordertest' - equ r.sample.$$[35].$$[0]['#name'], '__text__' - equ r.sample.$$[35].$$[0]._, 'this is text with ' - equ r.sample.$$[35].$$[1]['#name'], 'b' - equ r.sample.$$[35].$$[1]._, 'markup' - equ r.sample.$$[35].$$[2]['#name'], '__text__' - equ r.sample.$$[35].$$[2]._, ' ' - equ r.sample.$$[35].$$[3]['#name'], 'em' - equ r.sample.$$[35].$$[3]._, 'like this' - equ r.sample.$$[35].$$[4]['#name'], '__text__' - equ r.sample.$$[35].$$[4]._, ' in the middle') + equ r.sample.$$[36]['#name'], 'textordertest' + equ r.sample.$$[36].$$[0]['#name'], '__text__' + equ r.sample.$$[36].$$[0]._, 'this is text with ' + equ r.sample.$$[36].$$[1]['#name'], 'b' + equ r.sample.$$[36].$$[1]._, 'markup' + equ r.sample.$$[36].$$[2]['#name'], '__text__' + equ r.sample.$$[36].$$[2]._, ' ' + equ r.sample.$$[36].$$[3]['#name'], 'em' + equ r.sample.$$[36].$$[3]._, 'like this' + equ r.sample.$$[36].$$[4]['#name'], '__text__' + equ r.sample.$$[36].$$[4]._, ' in the middle') 'test parse with explicitChildren and charsAsChildren and preserveChildrenOrder and includeWhiteChars and normalize': skeleton(explicitChildren: true, preserveChildrenOrder: true, charsAsChildren: true, includeWhiteChars: true, normalize: true, (r) -> console.log 'Result object: ' + util.inspect r, false, 10 # normalized whitespace-only text node becomes empty string - equ r.sample.$$[35]['#name'], 'textordertest' - equ r.sample.$$[35].$$[0]['#name'], '__text__' - equ r.sample.$$[35].$$[0]._, 'this is text with' - equ r.sample.$$[35].$$[1]['#name'], 'b' - equ r.sample.$$[35].$$[1]._, 'markup' - equ r.sample.$$[35].$$[2]['#name'], '__text__' - equ r.sample.$$[35].$$[2]._, '' - equ r.sample.$$[35].$$[3]['#name'], 'em' - equ r.sample.$$[35].$$[3]._, 'like this' - equ r.sample.$$[35].$$[4]['#name'], '__text__' - equ r.sample.$$[35].$$[4]._, 'in the middle') + equ r.sample.$$[36]['#name'], 'textordertest' + equ r.sample.$$[36].$$[0]['#name'], '__text__' + equ r.sample.$$[36].$$[0]._, 'this is text with' + equ r.sample.$$[36].$$[1]['#name'], 'b' + equ r.sample.$$[36].$$[1]._, 'markup' + equ r.sample.$$[36].$$[2]['#name'], '__text__' + equ r.sample.$$[36].$$[2]._, '' + equ r.sample.$$[36].$$[3]['#name'], 'em' + equ r.sample.$$[36].$$[3]._, 'like this' + equ r.sample.$$[36].$$[4]['#name'], '__text__' + equ r.sample.$$[36].$$[4]._, 'in the middle') 'test element without children': skeleton(explicitChildren: true, (r) -> console.log 'Result object: ' + util.inspect r, false, 10