Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/xpath/lib
98 changes: 5 additions & 93 deletions src/xslt/xslt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Comment thread
leonelsanchesdasilva marked this conversation as resolved.
}
}
}
Expand Down
48 changes: 48 additions & 0 deletions tests/xslt/issue-183.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Xslt, XmlParser } from '../../src/index';
Comment thread
leonelsanchesdasilva marked this conversation as resolved.

describe('Issue #183: Template match with nested elements', () => {
it('should match //message when nested under <child>', async () => {
const xslt = new Xslt();
const xmlParser = new XmlParser();

const xml = xmlParser.xmlParse('<page><child><message>Hello World.</message></child></page>');
const stylesheet = xmlParser.xmlParse(`<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="//message">
<div><xsl:value-of select="."/></div>
</xsl:template>
</xsl:stylesheet>`);

const result = await xslt.xsltProcess(xml, stylesheet);
expect(result).toBe('<div>Hello World.</div>');
});

it('should match with explicit path /page/child/message', async () => {
const xslt = new Xslt();
const xmlParser = new XmlParser();

const xml = xmlParser.xmlParse('<page><child><message>Hello World.</message></child></page>');
const stylesheet = xmlParser.xmlParse(`<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/page/child/message">
<div><xsl:value-of select="."/></div>
</xsl:template>
</xsl:stylesheet>`);

const result = await xslt.xsltProcess(xml, stylesheet);
expect(result).toBe('<div>Hello World.</div>');
});

it('should match //message when NOT nested (direct child of root)', async () => {
const xslt = new Xslt();
const xmlParser = new XmlParser();

const xml = xmlParser.xmlParse('<page><message>Hello World.</message></page>');
const stylesheet = xmlParser.xmlParse(`<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="//message">
<div><xsl:value-of select="."/></div>
</xsl:template>
</xsl:stylesheet>`);

const result = await xslt.xsltProcess(xml, stylesheet);
expect(result).toBe('<div>Hello World.</div>');
});
});
Loading