8000 fix: unprefixed attributes: backport to 0.8.x by hungarian-notation · Pull Request #904 · xmldom/xmldom · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

fix: unprefixed attributes: backport to 0.8.x #904

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: release-0.8.x
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
58 changes: 57 additions & 1 deletion lib/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,51 @@ function needNamespaceDefine(node, isHTML, visibleNamespaces) {
}
return true;
}

// match chromium's behavior of ns1, ns2, ns3, etc. (gecko would be "a")
var GENERATED_PREFIX_PREFIX = 'ns';

/**
* Assume a prefix for an attribute that is not in the default namespace and doesn't have a
* specified prefix.
*
* @param {Node} node
* @param {{ prefix: string | null; namespace: string | null }[]} visibleNamespaces
* @returns
* The prefix To use when serializing the attribute node.
*/
function assumePrefix(node, visibleNamespaces) {
var prefix = node.prefix || '';
var uri = node.namespaceURI;

if (uri === NAMESPACE.XMLNS) return node.prefix;
if (uri === '' || uri == null) return null;
if (prefix !== '' && prefix !== null) return node.prefix;

var nextGeneratedPrefixSuffix = 1;
var existingPrefix = null;

for (var nsi = 0; nsi < visibleNamespaces.length; ++nsi) {
var ns = visibleNamespaces[nsi];

// don't consider namespaces we can't refer to with a prefix
if (ns.prefix == null || ns.prefix == '') continue;

if (ns.namespace === uri) {
// use existing visible namespace declaration
existingPrefix = ns.prefix;
break;
} else if (ns.prefix.slice(0, GENERATED_PREFIX_PREFIX.length) == GENERATED_PREFIX_PREFIX) {
var foundSuffix = parseInt(ns.prefix.slice(GENERATED_PREFIX_PREFIX.length));
if (!isNaN(foundSuffix) && nextGeneratedPrefixSuffix <= foundSuffix) {
nextGeneratedPrefixSuffix = foundSuffix + 1;
}
}
}

return existingPrefix != null ? existingPrefix : GENERATED_PREFIX_PREFIX + String(nextGeneratedPrefixSuffix);
}

/**
* Well-formed constraint: No < in Attribute Values
* > The replacement text of any entity referred to directly or indirectly
Expand All @@ -1501,6 +1546,7 @@ function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){
if (!visibleNamespaces) {
visibleNamespaces = [];
}
var doc = node.nodeType === DOCUMENT_NODE ? node : node.ownerDocument;

if(nodeFilter){
node = nodeFilter(node);
Expand Down Expand Up @@ -1570,7 +1616,17 @@ function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){
}

for(var i=0;i<len;i++){
var attr = attrs.item(i);
var attr = attrs.item(i);
var assumedPrefix = assumePrefix(attr, visibleNamespaces);

if (assumedPrefix != null && assumedPrefix != attr.prefix) {
var qualifiedName = assumedPrefix + ':' + attr.localName;
attr = cloneNode(doc, attr, true); // don't clobber the real attr object
attr.nodeName = qualifiedName;
attr.name = qualifiedName;
attr.prefix = assumedPrefix;
}

if (needNamespaceDefine(attr,isHTML, visibleNamespaces)) {
var prefix = attr.prefix||'';
var uri = attr.namespaceURI;
Expand Down
10 changes: 7 additions & 3 deletions test/dom/ns-test.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,19 @@ describe('XML Namespace Parse', () => {

const te = doc.createElementNS(n1, 'test')
te.setAttributeNS(n1, 'bar', 'valx')
expect(te.toString()).toBe('<test xmlns="' + n1 + '" bar="valx"/>')
expect(te.toString()).toBe(
'<test xmlns:ns1="' + n1 + '" ns1:bar="valx" xmlns="' + n1 + '"/>'
)
el.appendChild(te)
const tx = doc.createElementNS(n2, 'test')
tx.setAttributeNS(n2, 'bar', 'valx')
expect(tx.toString()).toBe('<test xmlns="' + n2 + '" bar="valx"/>')
expect(tx.toString()).toBe(
'<test xmlns:ns1="' + n2 + '" ns1:bar="valx" xmlns="' + n2 + '"/>'
)
el.appendChild(tx)
}
expect(doc.toString()).toBe(
'<html test="a" xmlns="http://www.w3.org/1999/xhtml" xmlns:rmf="http://www.frankston.com/public"><rmf:foo hello="asdfa"><test xmlns="http://www.frankston.com/public" bar="valx"></test><test xmlns="http://rmf.vc/n2" bar="valx"></test></rmf:foo></html>'
'<html test="a" xmlns="http://www.w3.org/1999/xhtml" xmlns:rmf="http://www.frankston.com/public"><rmf:foo hello="asdfa"><test rmf:bar="valx" xmlns="http://www.frankston.com/public"></test><test xmlns:ns1="http://rmf.vc/n2" ns1:bar="valx" xmlns="http://rmf.vc/n2"></test></rmf:foo></html>'
)
})
})
92 changes: 91 additions & 1 deletion test/dom/serializer.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict'

const { DOMParser, XMLSerializer } = require('../../lib')
const { MIME_TYPE } = require('../../lib/conventions')
const { NAMESPACE, MIME_TYPE } = require('../../lib/conventions')

describe('XML Serializer', () => {
it('supports text node containing "]]>"', () => {
Expand Down Expand Up @@ -253,4 +253,94 @@ describe('XML Serializer', () => {
)
})
})

describe('properly handles the addition of namespaced unprefixed attributes', () => {
test('should add applicable local namespace to unprefixed attributes', () => {
const str = '<foo xmlns:a="uri:a"/>'
const doc = new DOMParser().parseFromString(str, MIME_TYPE.XML_TEXT)
const root = doc.documentElement

const attr = doc.createAttributeNS('uri:a', 'a')
attr.value = 'value'
root.setAttributeNode(attr)
expect(new XMLSerializer().serializeToString(root)).toBe(
'<foo xmlns:a="uri:a" a:a="value"/>'
)
})

test('should invent novel unused prefix as needed for unprefixed namespaced attributes', () => {
const str = '<foo/>'
const doc = new DOMParser().parseFromString(str, MIME_TYPE.XML_TEXT)
const root = doc.documentElement

const attr = doc.createAttributeNS('uri:a', 'a')
attr.value = 'value'
root.setAttributeNode(attr)

const serializedResult = new XMLSerializer().serializeToString(root)
expect(serializedResult).toBe('<foo xmlns:ns1="uri:a" ns1:a="value"/>')

// If we want a more generic approach to the test:
// const generatedPrefixMatch = /xmlns:(?<prefix>\w+)="uri:a"/.exec(serializedResult);
// expect(generatedPrefixMatch).not.toBeNull();
// const serializedAttributeMatch = new RegExp(`${generatedPrefixMatch.groups.prefix}:a="value"`).exec(serializedResult);
// expect(serializedAttributeMatch).not.toBeNull();
})

test('should generate multiple unique prefixes as needed', () => {
const str = '<foo><child/></foo>'
const doc = new DOMParser().parseFromString(str, MIME_TYPE.XML_TEXT)
const root = doc.documentElement

root.setAttributeNS('uri:a', 'a', 'a')
root.setAttributeNS('uri:b', 'b', 'b')
root.firstChild.setAttributeNS('uri:a', 'a2', 'a')

const serializedResult = new XMLSerializer().serializeToString(root)
expect(serializedResult).toBe(
'<foo xmlns:ns1="uri:a" ns1:a="a" xmlns:ns2="uri:b" ns2:b="b"><child ns1:a2="a"/></foo>'
)
})

test('do not assume prefixes for attributes whose namespaceURI matches the xmlns namespace', () => {
const str = '<foo/>'
const doc = new DOMParser().parseFromString(str, MIME_TYPE.XML_TEXT)
const root = doc.documentElement

const attr = doc.createAttribute('xmlns')
attr.namespaceURI = NAMESPACE.XMLNS
attr.value = 'uri:value'
root.setAttributeNode(attr)

const serializedResult = new XMLSerializer().serializeToString(doc)
expect(serializedResult).toBe('<foo xmlns="uri:value"/>')
})

test('do not assume prefixes for empty string namespaceURIs', () => {
const str = '<foo/>'
const doc = new DOMParser().parseFromString(str, MIME_TYPE.XML_TEXT)
const root = doc.documentElement

const attr = doc.createAttribute('example')
attr.namespaceURI = ''
attr.value = 'value'
root.setAttributeNode(attr)

const serializedResult = new XMLSerializer().serializeToString(doc)
expect(serializedResult).toBe('<foo example="value"/>')
})

test('gracefully handle existing prefixes that match the pattern of generated prefixes', () => {
const str = '<foo xmlns:ns1="uri:a" xmlns:nsx="uri:x"/>'
const doc = new DOMParser().parseFromString(str, MIME_TYPE.XML_TEXT)
const root = doc.documentElement

root.setAttributeNS('uri:b', 'b', 'b')

const serializedResult = new XMLSerializer().serializeToString(doc)
expect(serializedResult).toBe(
'<foo xmlns:ns1="uri:a" xmlns:nsx="uri:x" xmlns:ns2="uri:b" ns2:b="b"/>'
)
})
})
})
0