Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Improve roundtrip for some combinations of parser options #461

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 62 additions & 32 deletions lib/builder.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

94 changes: 62 additions & 32 deletions src/builder.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class exports.Builder
buildObject: (rootObj) ->
attrkey = @options.attrkey
charkey = @options.charkey
childkey = @options.childkey

# If there is a sane-looking first element to use as the root,
# and the user hasn't specified a non-default rootName,
Expand All @@ -54,44 +55,73 @@ class exports.Builder
for key, entry of child
element = render(element.ele(key), entry).up()
else
for own key, child of obj
# Case #1 Attribute
if key is attrkey
if typeof child is "object"
# Inserts tag attributes
for attr, value of child
element = element.att(attr, value)

# Case #2 Char data (CDATA, etc.)
else if key is charkey
if @options.cdata && requiresCDATA child
element = element.raw wrapCDATA child
else
element = element.txt child
children_as_array = false
# First analyze some metadata keys

# Attributes
if obj? and attrkey of obj and typeof obj[attrkey] is "object"
child = obj[attrkey]
# Inserts tag attributes
for attr, value of child
element = element.att(attr, value)

# Char data (CDATA, etc.)
if obj? and charkey of obj
child = obj[charkey]
if @options.cdata && requiresCDATA child
element = element.raw wrapCDATA child
else
element = element.txt child

# Case #3 Array data
else if Array.isArray child
# Objects with explicitChildren
if obj? and childkey of obj
child = obj[childkey]
if Array.isArray child
children_as_array = true
for own index, entry of child
if typeof entry is 'string'
if @options.cdata && requiresCDATA entry
element = element.ele(key).raw(wrapCDATA entry).up()
if typeof entry is "object"
if ( Object.keys(entry).length is 1 )
name = Object.keys(entry)[0]
element = render(element.ele(name),entry[name]).up()
else if '#name' of entry
element = render(element.ele(entry['#name']), entry).up()
else
element = element.ele(key, entry).up()
else
element = render(element.ele(key), entry).up()

# Case #4 Objects
throw new Error('Missing #name attribute when children')
else if typeof child is "object"
element = render(element.ele(key), child).up()
element = render(element, child)

# Case #5 String and remaining types
else
if typeof child is 'string' && @options.cdata && requiresCDATA child
element = element.ele(key).raw(wrapCDATA child).up()
if not children_as_array
# With the preserverChildrenOrder option, the parser will include
# the children element both as an array under 'childkey'
# and as individual keys.
for own key, child of obj
# Skip metadata keys that we have already covered
if key is '#name' or key is attrkey or key is charkey or key is childkey
continue

# Case #3 Array data
else if Array.isArray child
for own index, entry of child
if typeof entry is 'string'
if @options.cdata && requiresCDATA entry
element = element.ele(key).raw(wrapCDATA entry).up()
else
element = element.ele(key, entry).up()
else
element = render(element.ele(key), entry).up()

# Case #4 Objects
else if typeof child is "object"
element = render(element.ele(key), child).up()

# Case #5 String and remaining types
else
if not child?
child = ''
element = element.ele(key, child.toString()).up()
if typeof child is 'string' && @options.cdata && requiresCDATA child
element = element.ele(key).raw(wrapCDATA child).up()
else
if not child?
child = ''
element = element.ele(key, child.toString()).up()

element

Expand Down
142 changes: 142 additions & 0 deletions test/builder.test.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,30 @@ module.exports =
diffeq expected, actual
test.finish()

'test obj is a string with cdata': (test) ->
expected = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root><![CDATA[& <<]]></root>
"""
opts = cdata: true
builder = new xml2js.Builder opts
obj = "& <<"
actual = builder.buildObject obj
diffeq expected, actual
test.finish()

'test obj content with cdata': (test) ->
expected = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root><![CDATA[& <<]]></root>
"""
opts = cdata: true
builder = new xml2js.Builder opts
obj = { root: { '_': "& <<" } }
actual = builder.buildObject obj
diffeq expected, actual
test.finish()

'test building obj with array': (test) ->
expected = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
Expand All @@ -281,3 +305,121 @@ module.exports =
actual = builder.buildObject obj
diffeq expected, actual
test.finish()

'test round-trip explicitChildren': (test) ->
xml = '<a id="0"><b id="1">Text B1</b><c id="2">Text C2</c><b id="3">Text B3</b></a>'
expected = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<a id="0">
<b id="1">Text B1</b>
<b id="3">Text B3</b>
<c id="2">Text C2</c>
</a>

"""
opts = cdata: true, explicitChildren: true
parser_opts = explicitChildren: true
parser = new xml2js.Parser parser_opts
builder = new xml2js.Builder opts
parser.parseString xml, (err, data) ->
equ err, null
actual = builder.buildObject data
diffeq expected, actual
test.finish()

'test round-trip explicitChildren & preserveChildrenOrder': (test) ->
xml = '<a id="0"><b id="1">Text B1</b><c id="2">Text C2</c><b id="3">Text B3</b></a>'
expected = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<a id="0">
<b id="1">Text B1</b>
<c id="2">Text C2</c>
<b id="3">Text B3</b>
</a>

"""
opts = cdata: true, explicitChildren: true, preserveChildrenOrder: true
parser_opts = explicitChildren: true, preserveChildrenOrder: true
parser = new xml2js.Parser parser_opts
builder = new xml2js.Builder opts
parser.parseString xml, (err, data) ->
equ err, null
actual = builder.buildObject data
diffeq expected, actual
test.finish()
'test children as single key objects': (test) ->
obj = {
a: {
'$': { id: 0 },
'$$': [
{
b: {
'$': { id: 1},
'_': 'Text B1'
}
},
{
c: {
'$': { id: 2},
'_': 'Text C2'
}
},
{
b: {
'$': { id: 3},
'_': 'Text B3'
}
}
]
}
}
expected = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<a id="0">
<b id="1">Text B1</b>
<c id="2">Text C2</c>
<b id="3">Text B3</b>
</a>

"""
opts = {}
builder = new xml2js.Builder opts
actual = builder.buildObject obj
diffeq expected, actual
test.finish()
'test children without any name': (test) ->
obj = {
a: {
'$': { id: 0 },
'$$': [
{
'$': { id: 1},
'_': 'Text B1'
},
{
'$': { id: 2},
'_': 'Text C2'
},
{
'$': { id: 3},
'_': 'Text B3'
}
]
}
}
expected = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<a id="0">
<b id="1">Text B1</b>
<c id="2">Text C2</c>
<b id="3">Text B3</b>
</a>

"""
opts = {}
builder = new xml2js.Builder opts
assert.throws(
() =>
actual = builder.buildObject obj
/Missing #name attribute when children/)
test.finish()