From ad5e45f908666710f2dc35007ea2d79eb3f2cde4 Mon Sep 17 00:00:00 2001 From: Leonel Sanches da Silva <53848829+leonelsanchesdasilva@users.noreply.github.com> Date: Thu, 19 Mar 2026 10:28:33 -0700 Subject: [PATCH 1/2] Addressing bug reported at https://github.com/DesignLiquido/xslt-processor/issues/183. --- src/xpath/lib | 2 +- src/xslt/xslt.ts | 96 ++---------------------------------- tests/xslt/issue-183.test.ts | 48 ++++++++++++++++++ 3 files changed, 53 insertions(+), 93 deletions(-) create mode 100644 tests/xslt/issue-183.test.ts diff --git a/src/xpath/lib b/src/xpath/lib index e745af7..5ec18e4 160000 --- a/src/xpath/lib +++ b/src/xpath/lib @@ -1 +1 @@ -Subproject commit e745af7c89b22fd04f4ae77489e49f60a7a68bf1 +Subproject commit 5ec18e4561f754e78d94d0dc6a3c12b020230c64 diff --git a/src/xslt/xslt.ts b/src/xslt/xslt.ts index 1bfe9da..8662595 100644 --- a/src/xslt/xslt.ts +++ b/src/xslt/xslt.ts @@ -4184,99 +4184,11 @@ export class Xslt { this.currentTemplateStack.pop(); } else { - // No template matched the root element. - // Apply the default XSLT behavior: process child nodes + // No template matched the root context. + // Apply the built-in template: recursively process descendants at any depth. const rootNode = context.nodeList[context.position]; - if (rootNode && rootNode.childNodes && rootNode.childNodes.length > 0) { - // Filter out DTD sections and apply templates to remaining children - const childNodes = rootNode.childNodes.filter((n: XNode) => n.nodeName !== '#dtd-section'); - if (childNodes.length > 0) { - const childContext = context.clone(childNodes); - // Process each child node using xsltApplyTemplates logic - for (let j = 0; j < childContext.contextSize(); ++j) { - const currentNode = childContext.nodeList[j]; - - if (currentNode.nodeType === DOM_TEXT_NODE) { - const textNodeContext = context.clone([currentNode], 0); - this.commonLogicTextNode(textNodeContext, currentNode, output); - } else { - const clonedContext = childContext.clone([currentNode], 0); - const selection = selectBestTemplate( - expandedTemplates, - clonedContext, - this.matchResolver, - this.xPath, - this.warningsCallback - ); - - if (selection.selectedTemplate) { - const templateContext = clonedContext.clone([currentNode], 0); - templateContext.inApplyTemplates = true; - - // Track this template execution for apply-imports - const metadata = this.templateSourceMap.get(selection.selectedTemplate); - const matchPattern = xmlGetAttribute(selection.selectedTemplate, 'match'); - const modeAttr = xmlGetAttribute(selection.selectedTemplate, 'mode'); - - this.currentTemplateStack.push({ - template: selection.selectedTemplate, - stylesheetDepth: metadata?.importDepth ?? 0, - mode: modeAttr || null, - match: matchPattern - }); - - await this.xsltChildNodes(templateContext, selection.selectedTemplate, output); - - this.currentTemplateStack.pop(); - } else { - // If no template matches this child, recursively process its children - if (currentNode.childNodes && currentNode.childNodes.length > 0) { - const grandchildNodes = currentNode.childNodes.filter((n: XNode) => n.nodeName !== '#dtd-section'); - if (grandchildNodes.length > 0) { - const grandchildContext = context.clone(grandchildNodes); - // Recursively process grandchildren - for (let k = 0; k < grandchildContext.contextSize(); ++k) { - const grandchildNode = grandchildContext.nodeList[k]; - if (grandchildNode.nodeType === DOM_TEXT_NODE) { - const textNodeContext = context.clone([grandchildNode], 0); - this.commonLogicTextNode(textNodeContext, grandchildNode, output); - } else { - const grandchildClonedContext = grandchildContext.clone([grandchildNode], 0); - const grandchildSelection = selectBestTemplate( - expandedTemplates, - grandchildClonedContext, - this.matchResolver, - this.xPath, - this.warningsCallback - ); - if (grandchildSelection.selectedTemplate) { - const grandchildTemplateContext = grandchildClonedContext.clone([grandchildNode], 0); - grandchildTemplateContext.inApplyTemplates = true; - - // Track this template execution for apply-imports - const metadata = this.templateSourceMap.get(grandchildSelection.selectedTemplate); - const matchPattern = xmlGetAttribute(grandchildSelection.selectedTemplate, 'match'); - const modeAttr = xmlGetAttribute(grandchildSelection.selectedTemplate, 'mode'); - - this.currentTemplateStack.push({ - template: grandchildSelection.selectedTemplate, - stylesheetDepth: metadata?.importDepth ?? 0, - mode: modeAttr || null, - match: matchPattern - }); - - await this.xsltChildNodes(grandchildTemplateContext, grandchildSelection.selectedTemplate, output); - - this.currentTemplateStack.pop(); - } - } - } - } - } - } - } - } - } + if (rootNode) { + await this.applyBuiltInTemplate(rootNode, expandedTemplates, null, context, output); } } } diff --git a/tests/xslt/issue-183.test.ts b/tests/xslt/issue-183.test.ts new file mode 100644 index 0000000..f7727ac --- /dev/null +++ b/tests/xslt/issue-183.test.ts @@ -0,0 +1,48 @@ +import { Xslt, XmlParser } from '../../src/index'; + +describe('Issue #183: Template match with nested elements', () => { + it('should match //message when nested under ', async () => { + const xslt = new Xslt(); + const xmlParser = new XmlParser(); + + const xml = xmlParser.xmlParse('Hello World.'); + const stylesheet = xmlParser.xmlParse(` + +
+
+
`); + + const result = await xslt.xsltProcess(xml, stylesheet); + expect(result).toBe('
Hello World.
'); + }); + + it('should match with explicit path /page/child/message', async () => { + const xslt = new Xslt(); + const xmlParser = new XmlParser(); + + const xml = xmlParser.xmlParse('Hello World.'); + const stylesheet = xmlParser.xmlParse(` + +
+
+
`); + + const result = await xslt.xsltProcess(xml, stylesheet); + expect(result).toBe('
Hello World.
'); + }); + + it('should match //message when NOT nested (direct child of root)', async () => { + const xslt = new Xslt(); + const xmlParser = new XmlParser(); + + const xml = xmlParser.xmlParse('Hello World.'); + const stylesheet = xmlParser.xmlParse(` + +
+
+
`); + + const result = await xslt.xsltProcess(xml, stylesheet); + expect(result).toBe('
Hello World.
'); + }); +}); From fe4fe14294b59563521f1ab4f7e4be8adb223b34 Mon Sep 17 00:00:00 2001 From: Leonel Sanches da Silva <53848829+leonelsanchesdasilva@users.noreply.github.com> Date: Thu, 19 Mar 2026 10:40:49 -0700 Subject: [PATCH 2/2] Addressing Copilot's comments. --- src/xslt/xslt.ts | 2 +- tests/xslt/issue-183.test.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/xslt/xslt.ts b/src/xslt/xslt.ts index 8662595..8ad43ea 100644 --- a/src/xslt/xslt.ts +++ b/src/xslt/xslt.ts @@ -949,7 +949,7 @@ export class Xslt { } const childNodes = node.childNodes.filter( - (n: XNode) => n.nodeType !== DOM_ATTRIBUTE_NODE + (n: XNode) => n.nodeType !== DOM_ATTRIBUTE_NODE && n.nodeName !== '#dtd-section' ); for (const childNode of childNodes) { diff --git a/tests/xslt/issue-183.test.ts b/tests/xslt/issue-183.test.ts index f7727ac..0aea773 100644 --- a/tests/xslt/issue-183.test.ts +++ b/tests/xslt/issue-183.test.ts @@ -6,7 +6,7 @@ describe('Issue #183: Template match with nested elements', () => { const xmlParser = new XmlParser(); const xml = xmlParser.xmlParse('Hello World.'); - const stylesheet = xmlParser.xmlParse(` + const stylesheet = xmlParser.xmlParse(`
@@ -21,7 +21,7 @@ describe('Issue #183: Template match with nested elements', () => { const xmlParser = new XmlParser(); const xml = xmlParser.xmlParse('Hello World.'); - const stylesheet = xmlParser.xmlParse(` + const stylesheet = xmlParser.xmlParse(`
@@ -36,7 +36,7 @@ describe('Issue #183: Template match with nested elements', () => { const xmlParser = new XmlParser(); const xml = xmlParser.xmlParse('Hello World.'); - const stylesheet = xmlParser.xmlParse(` + const stylesheet = xmlParser.xmlParse(`