diff --git a/src/dom/xml-functions.ts b/src/dom/xml-functions.ts index 6854082..3ec6190 100644 --- a/src/dom/xml-functions.ts +++ b/src/dom/xml-functions.ts @@ -317,6 +317,12 @@ function xmlElementLogicTrivial(node: XNode, buffer: string[], options: XmlOutpu continue; } + // In HTML output mode, skip namespace declarations (xmlns and xmlns:*) + if (options.outputMethod === 'html' && + (attribute.nodeName === 'xmlns' || attribute.nodeName.startsWith('xmlns:'))) { + continue; + } + if (attribute.nodeName && attribute.nodeValue !== null && attribute.nodeValue !== undefined) { buffer.push(` ${xmlFullNodeName(attribute)}="${xmlEscapeAttr(attribute.nodeValue)}"`); } diff --git a/src/xslt/xslt.ts b/src/xslt/xslt.ts index 96a755e..bfa017c 100644 --- a/src/xslt/xslt.ts +++ b/src/xslt/xslt.ts @@ -4979,6 +4979,12 @@ export class Xslt { } else if (namespaceUri) { domSetAttribute(newNode, `xmlns:${aliasPrefix}`, namespaceUri); } + } else if (namespaceUri) { + const prefix = templatePrefix || (qualifiedName.includes(':') ? qualifiedName.split(':')[0] : null); + const nsAttr = prefix ? `xmlns:${prefix}` : 'xmlns'; + if (!this.isNamespaceDeclaredOnAncestor(output, nsAttr, namespaceUri)) { + domSetAttribute(newNode, nsAttr, namespaceUri); + } } // Apply attribute sets from use-attribute-sets attribute on literal elements diff --git a/tests/xslt/message-number-namespace.test.ts b/tests/xslt/message-number-namespace.test.ts index e779408..ccabdce 100644 --- a/tests/xslt/message-number-namespace.test.ts +++ b/tests/xslt/message-number-namespace.test.ts @@ -694,6 +694,46 @@ describe('Error messages for misplaced elements', () => { }); }); +describe('Namespace declarations on literal result elements (issue #165)', () => { + it('Namespace-prefixed literal result element includes xmlns declaration', async () => { + const xmlString = ``; + + const xsltString = ` + + + Out + +`; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + assert.equal(outXmlString, 'Out'); + }); + + it('Nested namespace-prefixed elements do not duplicate xmlns declaration', async () => { + const xmlString = ``; + + const xsltString = ` + + + Text + +`; + + const xsltClass = new Xslt(); + const xmlParser = new XmlParser(); + const xml = xmlParser.xmlParse(xmlString); + const xslt = xmlParser.xmlParse(xsltString); + + const outXmlString = await xsltClass.xsltProcess(xml, xslt); + assert.equal(outXmlString, 'Text'); + }); +}); + describe('Modes and Multiple Template Sets', () => { it('Template in non-default mode not selected if no mode specified', async () => { const xmlString = `