Skip to content

Commit

Permalink
fix(dom): Preserve default namespace when serializing (#321)
Browse files Browse the repository at this point in the history
Co-authored-by: Christian Bewernitz <coder@karfau.de>
(cherry picked from commit 8b33c19)
  • Loading branch information
lupestro authored and karfau committed Sep 14, 2021
1 parent e075e99 commit b035c62
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 0 deletions.
25 changes: 25 additions & 0 deletions lib/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,20 @@ Node.prototype = {
hasAttributes:function(){
return this.attributes.length>0;
},
/**
* Look up the prefix associated to the given namespace URI, starting from this node.
* **The default namespace declarations are ignored by this method.**
* See Namespace Prefix Lookup for details on the algorithm used by this method.
*
* _Note: The implementation seems to be incomplete when compared to the algorithm described in the specs._
*
* @param {string | null} namespaceURI
* @returns {string | null}
* @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespacePrefix
* @see https://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespacePrefixAlgo
* @see https://dom.spec.whatwg.org/#dom-node-lookupprefix
* @see https://github.com/xmldom/xmldom/issues/322
*/
lookupPrefix:function(namespaceURI){
var el = this;
while(el){
Expand Down Expand Up @@ -1175,12 +1189,23 @@ function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){
var prefixedNodeName = nodeName
if (!isHTML && !node.prefix && node.namespaceURI) {
var defaultNS
// lookup current default ns from `xmlns` attribute
for (var ai = 0; ai < attrs.length; ai++) {
if (attrs.item(ai).name === 'xmlns') {
defaultNS = attrs.item(ai).value
break
}
}
if (!defaultNS) {
// lookup current default ns in visibleNamespaces
for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) {
var namespace = visibleNamespaces[nsi]
if (namespace.prefix === '' && namespace.namespace === node.namespaceURI) {
defaultNS = namespace.namespace
break
}
}
}
if (defaultNS !== node.namespaceURI) {
for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) {
var namespace = visibleNamespaces[nsi]
Expand Down
107 changes: 107 additions & 0 deletions test/dom/serializer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,113 @@ describe('XML Serializer', () => {
)
})
})
describe('is insensitive to namespace order', () => {
it('should preserve prefixes for inner elements and attributes', () => {
const NS = 'http://www.w3.org/test'
const xml = `
<xml xmlns="${NS}">
<one attr="first"/>
<group xmlns:inner="${NS}">
<two attr="second"/>
<inner:three inner:attr="second"/>
</group>
</xml>
`.trim()
const dom = new DOMParser().parseFromString(xml, 'text/xml')
const doc = dom.documentElement
const one = doc.childNodes.item(1)
expect(one).toMatchObject({
localName: 'one',
nodeName: 'one',
prefix: null,
namespaceURI: NS,
})
const group = doc.childNodes.item(3)
expect(group).toMatchObject({
localName: 'group',
nodeName: 'group',
prefix: null,
namespaceURI: NS,
})
const two = group.childNodes.item(1)
expect(two).toMatchObject({
localName: 'two',
nodeName: 'two',
prefix: null,
namespaceURI: NS,
})
const three = group.childNodes.item(3)
expect(three).toMatchObject({
localName: 'three',
nodeName: 'inner:three',
prefix: 'inner',
namespaceURI: NS,
})
expect(new XMLSerializer().serializeToString(dom)).toEqual(xml)
})
it('should preserve missing prefixes for inner prefixed elements and attributes', () => {
const NS = 'http://www.w3.org/test'
const xml = `
<xml xmlns:inner="${NS}">
<inner:one attr="first"/>
<inner:group xmlns="${NS}">
<inner:two attr="second"/>
<three attr="second"/>
</inner:group>
</xml>
`.trim()
const dom = new DOMParser().parseFromString(xml, 'text/xml')
const doc = dom.documentElement
const one = doc.childNodes.item(1)
expect(one).toMatchObject({
localName: 'one',
nodeName: 'inner:one',
prefix: 'inner',
namespaceURI: NS,
})
const group = doc.childNodes.item(3)
expect(group).toMatchObject({
localName: 'group',
nodeName: 'inner:group',
prefix: 'inner',
namespaceURI: NS,
})
const two = group.childNodes.item(1)
expect(two).toMatchObject({
localName: 'two',
nodeName: 'inner:two',
prefix: 'inner',
namespaceURI: NS,
})
const three = group.childNodes.item(3)
expect(three).toMatchObject({
localName: 'three',
nodeName: 'three',
prefix: null,
namespaceURI: NS,
})
expect(new XMLSerializer().serializeToString(dom)).toEqual(xml)
})
it('should produce unprefixed svg elements when prefixed namespace comes first', () => {
const svg = `
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg">
<g><circle/></g>
</svg>`.trim()
const dom = new DOMParser().parseFromString(svg, 'text/xml')

expect(new XMLSerializer().serializeToString(dom)).toEqual(svg)
})
it('should produce unprefixed svg elements when default namespace comes first', () => {
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g><circle/></g>
</svg>
`.trim()
const dom = new DOMParser().parseFromString(svg, 'text/xml')

expect(new XMLSerializer().serializeToString(dom)).toEqual(svg)
})
})
describe('properly escapes attribute values', () => {
it('should escape special characters in namespace attributes', () => {
const input = `<xml xmlns='<&"' xmlns:attr='"&<'><test attr:test=""/></xml>`
Expand Down

0 comments on commit b035c62

Please sign in to comment.