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
5 changes: 4 additions & 1 deletion src/dom/xml-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,10 @@ function xmlTransformedTextRecursive(node: XNode, buffer: string[], options: Xml
const nodeType = node.nodeType
const nodeValue = node.nodeValue;
if (nodeType === DOM_TEXT_NODE) {
if (node.nodeValue && node.nodeValue.trim() !== '') {
// For text nodes created by xsl:text, don't trim whitespace
// For other text nodes, skip whitespace-only ones
const isFromXslText = node.fromXslText === true;
if (node.nodeValue && (isFromXslText || node.nodeValue.trim() !== '')) {
const finalText =
node.escape && options.escape ? xmlEscapeText(node.nodeValue): xmlUnescapeText(node.nodeValue);
buffer.push(finalText);
Expand Down
2 changes: 1 addition & 1 deletion src/dom/xml-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ export class XmlParser {
} else if (!tag && char === '<') {
let text = xml.slice(start, i);
if (text && parent !== root) {
domAppendChild(parent, domCreateTextNode(xmlDocument, text));
domAppendChild(parent, domCreateTextNode(xmlDocument, he.decode(text)));
}
if (xml.slice(i + 1, i + 4) === '!--') {
let endTagIndex = xml.slice(i + 4).indexOf('-->');
Expand Down
2 changes: 2 additions & 0 deletions src/dom/xnode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class XNode {

visited: boolean;
escape: boolean;
fromXslText: boolean;

static _unusedXNodes: any[] = [];

Expand All @@ -36,6 +37,7 @@ export class XNode {
this.childNodes = [];
this.visited = false;
this.escape = true;
this.fromXslText = false;
this.siblingPosition = -1;

this.init(type, name, opt_value, opt_owner, opt_namespace);
Expand Down
2 changes: 1 addition & 1 deletion src/xpath/lib
4 changes: 2 additions & 2 deletions src/xpath/selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class XPathSelector {
nodeType: this.getNodeType(node),
nodeName: node.nodeName || '#document',
localName: node.localName || node.nodeName,
namespaceURI: node.namespaceUri || null,
namespaceUri: node.namespaceUri || null,
textContent: node.nodeValue,
Comment thread
leonelsanchesdasilva marked this conversation as resolved.
parentNode: null, // Não converter para evitar ciclos
childNodes: [], // Será preenchido depois
Expand Down Expand Up @@ -99,7 +99,7 @@ export class XPathSelector {
text: xpathNode.textContent,
value: xpathNode.textContent,
localName: xpathNode.localName,
namespaceURI: xpathNode.namespaceURI,
namespaceUri: xpathNode.namespaceUri,
parentNode: xpathNode.parentNode ? this.convertFromXPathNode(xpathNode.parentNode) : undefined,
children: xpathNode.childNodes ? Array.from(xpathNode.childNodes).map(child => this.convertFromXPathNode(child)) : undefined,
attributes: xpathNode.attributes ? Array.from(xpathNode.attributes).map(attr => this.convertFromXPathNode(attr)) : undefined,
Expand Down
8 changes: 1 addition & 7 deletions src/xpath/xpath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,7 @@ class NodeConverter {
const adapted = node as any;

// Add XPathNode-compatible properties if not present
if (!('namespaceURI' in adapted)) {
Object.defineProperty(adapted, 'namespaceURI', {
get() { return this.namespaceUri; },
enumerable: true,
configurable: true
});
}
// namespaceUri is now the standard property in XPathNode interface

if (!('textContent' in adapted)) {
Object.defineProperty(adapted, 'textContent', {
Expand Down
51 changes: 36 additions & 15 deletions src/xslt/xslt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,10 +357,14 @@
// This is the XSLT 3.0 compliant behavior - only ONE template executes per node.
for (let j = 0; j < modifiedContext.contextSize(); ++j) {
const currentNode = modifiedContext.nodeList[j];

// If the current node is text, there's no need to test all the templates
// against it. Just appending it to its parent is fine.
if (currentNode.nodeType === DOM_TEXT_NODE) {
// Check if this whitespace-only text node should be stripped
if (!this.xsltPassText(currentNode)) {
// Skip whitespace-only text nodes in apply-templates
continue;
}
const textNodeContext = context.clone(
[currentNode],
0
Expand Down Expand Up @@ -1265,13 +1269,17 @@
protected xsltText(context: ExprContext, template: XNode, output?: XNode) {
const text = xmlValue(template);
const node = domCreateTextNode(this.outputDocument, text);
// Mark this node as coming from xsl:text so it won't be trimmed during serialization
node.fromXslText = true;
const disableOutputEscaping = template.childNodes.filter(
(a) => a.nodeType === DOM_ATTRIBUTE_NODE && a.nodeName === 'disable-output-escaping'
);
if (disableOutputEscaping.length > 0 && disableOutputEscaping[0].nodeValue === 'yes') {
node.escape = false;
}
const destinationTextNode = output || this.outputDocument;
// Set siblingPosition to preserve insertion order during serialization
node.siblingPosition = destinationTextNode.childNodes.length;
destinationTextNode.appendChild(node);
}

Expand Down Expand Up @@ -1429,8 +1437,6 @@

for (const t of expandedTemplates) {
try {
// For initial template selection, evaluate patterns from document root
// without axis override to ensure consistent matching for all patterns
// For initial template selection, evaluate patterns from document root
// without axis override to ensure consistent matching for all patterns
const matchedNodes = this.xsltMatch(t.matchPattern, contextClone);
Expand All @@ -1444,19 +1450,28 @@
}

if (matchCandidates.length > 0) {
// Sort by: importPrecedence DESC, effectivePriority DESC, documentOrder DESC
matchCandidates.sort((a, b) => {
if (a.priority.importPrecedence !== b.priority.importPrecedence) {
return b.priority.importPrecedence - a.priority.importPrecedence;
}
if (a.priority.effectivePriority !== b.priority.effectivePriority) {
return b.priority.effectivePriority - a.priority.effectivePriority;
}
return b.priority.documentOrder - a.priority.documentOrder;
});
// First, check if "/" pattern matches - it's the document entry point and should be preferred
const rootPatternMatch = matchCandidates.find(c => c.priority.matchPattern === '/');
let winner: { priority: TemplatePriority; matchedNodes: XNode[] };

if (rootPatternMatch) {
// Use the root template as entry point
winner = rootPatternMatch;
} else {
// Sort by: importPrecedence DESC, effectivePriority DESC, documentOrder DESC
matchCandidates.sort((a, b) => {
if (a.priority.importPrecedence !== b.priority.importPrecedence) {
return b.priority.importPrecedence - a.priority.importPrecedence;

Check warning on line 1464 in src/xslt/xslt.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 1465 in src/xslt/xslt.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
if (a.priority.effectivePriority !== b.priority.effectivePriority) {
return b.priority.effectivePriority - a.priority.effectivePriority;
}
return b.priority.documentOrder - a.priority.documentOrder;
});
winner = matchCandidates[0];
}

// Detect conflicts
const winner = matchCandidates[0];
const conflicts = matchCandidates.filter(t =>
t.priority.importPrecedence === winner.priority.importPrecedence &&
t.priority.effectivePriority === winner.priority.effectivePriority
Expand Down Expand Up @@ -1625,7 +1640,13 @@
// siblings of the children.
const contextClone = context.clone();
for (let i = 0; i < template.childNodes.length; ++i) {
await this.xsltProcessContext(contextClone, template.childNodes[i], output);
const child = template.childNodes[i];
// Skip attribute nodes - they are stored in childNodes but should not be
// processed as template content. Attributes belong to the element itself.
if (child.nodeType === DOM_ATTRIBUTE_NODE) {
continue;
}
await this.xsltProcessContext(contextClone, child, output);
}
}

Expand Down
Loading