@@ -133,7 +133,7 @@ export class Xslt {
133133 }
134134
135135 await this . xsltProcessContext ( expressionContext , stylesheet , this . outputDocument ) ;
136- const transformedOutputXml = xmlTransformedText ( outputDocument , {
136+ const transformedOutputXml : string = xmlTransformedText ( outputDocument , {
137137 cData : this . options . cData ,
138138 escape : this . options . escape ,
139139 selfClosingTags : this . options . selfClosingTags ,
@@ -160,8 +160,6 @@ export class Xslt {
160160 select : any ,
161161 value : any ,
162162 nodes : any ,
163- mode : any ,
164- templates : any ,
165163 paramContext : any ,
166164 commentData : any ,
167165 commentNode : any ,
@@ -172,129 +170,10 @@ export class Xslt {
172170 case 'apply-imports' :
173171 throw new Error ( `not implemented: ${ template . localName } ` ) ;
174172 case 'apply-templates' :
175- select = xmlGetAttribute ( template , 'select' ) ;
176- if ( select ) {
177- nodes = this . xPath . xPathEval ( select , context ) . nodeSetValue ( ) ;
178- } else {
179- nodes = context . nodeList [ context . position ] . childNodes ;
180- }
181-
182- // TODO: Check why apply-templates was sorting and filing parameters
183- // automatically.
184- /* this.xsltWithParam(sortContext, template);
185- this.xsltSort(sortContext, template); */
186-
187- mode = xmlGetAttribute ( template , 'mode' ) ;
188- top = template . ownerDocument . documentElement ;
189-
190- templates = [ ] ;
191- for ( let element of top . childNodes . filter (
192- ( c : XNode ) => c . nodeType == DOM_ELEMENT_NODE && this . isXsltElement ( c , 'template' )
193- ) ) {
194- // Actual template should be executed.
195- // `<xsl:apply-templates>` should have an ancestor `<xsl:template>`
196- // for comparison.
197- const templateAncestor = template . getAncestorByLocalName ( 'template' ) ;
198- if ( templateAncestor === undefined ) {
199- continue ;
200- }
201-
202- if ( templateAncestor . id === element . id ) {
203- continue ;
204- }
205-
206- if ( ! mode || element . getAttributeValue ( 'mode' ) === mode ) {
207- templates . push ( element ) ;
208- }
209- }
210-
211- const modifiedContext = context . clone ( nodes ) ;
212- for ( let i = 0 ; i < templates . length ; ++ i ) {
213- for ( let j = 0 ; j < modifiedContext . contextSize ( ) ; ++ j ) {
214- // If the current node is text, there's no need to test all the templates
215- // against it. Just appending it to its parent is fine.
216- if ( modifiedContext . nodeList [ j ] . nodeType === DOM_TEXT_NODE ) {
217- const textNodeContext = context . clone (
218- [ modifiedContext . nodeList [ j ] ] ,
219- undefined ,
220- 0 ,
221- undefined
222- ) ;
223- // TODO: verify if it is okay to pass the own text node as template.
224- this . commonLogicTextNode ( textNodeContext , modifiedContext . nodeList [ j ] , output ) ;
225- } else {
226- const clonedContext = modifiedContext . clone (
227- [ modifiedContext . nodeList [ j ] ] ,
228- undefined ,
229- 0 ,
230- undefined
231- ) ;
232- clonedContext . inApplyTemplates = true ;
233- // The output depth should be restarted, since
234- // another template is being applied from this point.
235- clonedContext . outputDepth = 0 ;
236- await this . xsltProcessContext ( clonedContext , templates [ i ] , output ) ;
237- }
238- }
239- }
240-
173+ await this . xsltApplyTemplates ( context , template , output ) ;
241174 break ;
242175 case 'attribute' :
243- nameExpr = xmlGetAttribute ( template , 'name' ) ;
244- name = this . xsltAttributeValue ( nameExpr , context ) ;
245-
246- const documentFragment = domCreateDocumentFragment ( this . outputDocument ) ;
247- await this . xsltChildNodes ( context , template , documentFragment ) ;
248- value = xmlValue2 ( documentFragment ) ;
249-
250- if ( output . nodeType === DOM_DOCUMENT_FRAGMENT_NODE ) {
251- domSetTransformedAttribute ( output , name , value ) ;
252- } else {
253- let sourceNode = context . nodeList [ context . position ] ;
254- let parentSourceNode = sourceNode . parentNode ;
255- let outputNode = sourceNode . outputNode ;
256-
257- // At this point, the output node should exist.
258- // If not, a new node is created.
259- if ( outputNode === null || outputNode === undefined ) {
260- outputNode = new XNode (
261- sourceNode . nodeType ,
262- sourceNode . nodeName ,
263- sourceNode . nodeValue ,
264- context . outputNodeList [ context . outputPosition ] ,
265- sourceNode . namespaceUri
266- ) ;
267- sourceNode . outputNode = outputNode ;
268- }
269-
270- // Corner case:
271- // It can happen here that we don't have the root node set.
272- // In this case we need to append a copy of the root
273- // source node to receive the attribute.
274- if ( outputNode . localName === '#document' ) {
275- const sourceRootNode = context . root . childNodes [ 0 ] ;
276- const newRootNode = domCreateElement ( this . outputDocument , sourceRootNode . nodeName ) ;
277- newRootNode . transformedNodeName = sourceRootNode . nodeName ;
278- newRootNode . transformedLocalName = sourceRootNode . localName ;
279- domAppendTransformedChild ( outputNode , newRootNode ) ;
280- outputNode = newRootNode ;
281- parentSourceNode = newRootNode ;
282- }
283-
284- // If the parent transformation is something like `xsl:element`, we should
285- // add a copy of the attribute to this element.
286- domSetTransformedAttribute ( output , name , value ) ;
287-
288- // Some operations start by the tag attributes, and not by the tag itself.
289- // When this is the case, the output node is not set yet, so
290- // we add the transformed attributes into the original tag.
291- if ( parentSourceNode && parentSourceNode . outputNode ) {
292- domSetTransformedAttribute ( parentSourceNode . outputNode , name , value ) ;
293- } else {
294- domSetTransformedAttribute ( parentSourceNode , name , value ) ;
295- }
296- }
297-
176+ await this . xsltAttribute ( context , template , output ) ;
298177 break ;
299178 case 'attribute-set' :
300179 throw new Error ( `not implemented: ${ template . localName } ` ) ;
@@ -494,6 +373,153 @@ export class Xslt {
494373 }
495374 }
496375
376+ /**
377+ * Implements `xsl:apply-templates`.
378+ * @param context The Expression Context.
379+ * @param template The template.
380+ * @param output The output. Only used if there's no corresponding output node already defined.
381+ * @protected
382+ */
383+ protected async xsltApplyTemplates ( context : ExprContext , template : XNode , output ?: XNode ) {
384+ const getAllTemplates = ( top : XNode , template : XNode , mode : string | null ) => {
385+ let templates = [ ] ;
386+ for ( let element of top . childNodes . filter (
387+ ( c : XNode ) => c . nodeType == DOM_ELEMENT_NODE && this . isXsltElement ( c , 'template' )
388+ ) ) {
389+ // TODO: Remember why this logic was here.
390+ // In the past the idea was to avoid executing the same matcher repeatedly,
391+ // but this proved to be a *terrible* idea some time later.
392+ // Will keep this code for a few more versions, then remove it.
393+ /* const templateAncestor = template.getAncestorByLocalName('template');
394+ if (templateAncestor === undefined) {
395+ continue;
396+ }
397+
398+ if (templateAncestor.id === element.id) {
399+ continue;
400+ } */
401+
402+ if ( ! mode || element . getAttributeValue ( 'mode' ) === mode ) {
403+ templates . push ( element ) ;
404+ }
405+ }
406+
407+ return templates ;
408+ }
409+
410+ const select = xmlGetAttribute ( template , 'select' ) ;
411+ let nodes : XNode [ ] = [ ] ;
412+ if ( select ) {
413+ nodes = this . xPath . xPathEval ( select , context ) . nodeSetValue ( ) ;
414+ } else {
415+ nodes = context . nodeList [ context . position ] . childNodes ;
416+ }
417+
418+ // TODO: Check why apply-templates was sorting and filing parameters
419+ // automatically.
420+ /* this.xsltWithParam(sortContext, template);
421+ this.xsltSort(sortContext, template); */
422+
423+ const mode : string | null = xmlGetAttribute ( template , 'mode' ) ;
424+ const top = template . ownerDocument . documentElement ;
425+
426+ const templates = getAllTemplates ( top , template , mode ) ;
427+
428+ const modifiedContext = context . clone ( nodes ) ;
429+ for ( let i = 0 ; i < templates . length ; ++ i ) {
430+ for ( let j = 0 ; j < modifiedContext . contextSize ( ) ; ++ j ) {
431+ // If the current node is text, there's no need to test all the templates
432+ // against it. Just appending it to its parent is fine.
433+ if ( modifiedContext . nodeList [ j ] . nodeType === DOM_TEXT_NODE ) {
434+ const textNodeContext = context . clone (
435+ [ modifiedContext . nodeList [ j ] ] ,
436+ undefined ,
437+ 0 ,
438+ undefined
439+ ) ;
440+ // TODO: verify if it is okay to pass the own text node as template.
441+ this . commonLogicTextNode ( textNodeContext , modifiedContext . nodeList [ j ] , output ) ;
442+ } else {
443+ const clonedContext = modifiedContext . clone (
444+ [ modifiedContext . nodeList [ j ] ] ,
445+ undefined ,
446+ 0 ,
447+ undefined
448+ ) ;
449+ clonedContext . inApplyTemplates = true ;
450+ // The output depth should be restarted, since
451+ // another template is being applied from this point.
452+ clonedContext . outputDepth = 0 ;
453+ await this . xsltProcessContext ( clonedContext , templates [ i ] , output ) ;
454+ }
455+ }
456+ }
457+ }
458+
459+ /**
460+ * Implements `xsl:attribute`.
461+ * @param context The Expression Context.
462+ * @param template The template.
463+ * @param output The output. Only used if there's no corresponding output node already defined.
464+ * @protected
465+ */
466+ protected async xsltAttribute ( context : ExprContext , template : XNode , output ?: XNode ) {
467+ const nameExpr = xmlGetAttribute ( template , 'name' ) ;
468+ const name = this . xsltAttributeValue ( nameExpr , context ) ;
469+
470+ const documentFragment = domCreateDocumentFragment ( this . outputDocument ) ;
471+ await this . xsltChildNodes ( context , template , documentFragment ) ;
472+ const value = xmlValue2 ( documentFragment ) ;
473+
474+ if ( output . nodeType === DOM_DOCUMENT_FRAGMENT_NODE ) {
475+ domSetTransformedAttribute ( output , name , value ) ;
476+ } else {
477+ let sourceNode = context . nodeList [ context . position ] ;
478+ let parentSourceNode = sourceNode . parentNode ;
479+ let outputNode = sourceNode . outputNode ;
480+
481+ // At this point, the output node should exist.
482+ // If not, a new node is created.
483+ if ( outputNode === null || outputNode === undefined ) {
484+ outputNode = new XNode (
485+ sourceNode . nodeType ,
486+ sourceNode . nodeName ,
487+ sourceNode . nodeValue ,
488+ context . outputNodeList [ context . outputPosition ] ,
489+ sourceNode . namespaceUri
490+ ) ;
491+ sourceNode . outputNode = outputNode ;
492+ }
493+
494+ // Corner case:
495+ // It can happen here that we don't have the root node set.
496+ // In this case we need to append a copy of the root
497+ // source node to receive the attribute.
498+ if ( outputNode . localName === '#document' ) {
499+ const sourceRootNode = context . root . childNodes [ 0 ] ;
500+ const newRootNode = domCreateElement ( this . outputDocument , sourceRootNode . nodeName ) ;
501+ newRootNode . transformedNodeName = sourceRootNode . nodeName ;
502+ newRootNode . transformedLocalName = sourceRootNode . localName ;
503+ domAppendTransformedChild ( outputNode , newRootNode ) ;
504+ outputNode = newRootNode ;
505+ parentSourceNode = newRootNode ;
506+ }
507+
508+ // If the parent transformation is something like `xsl:element`, we should
509+ // add a copy of the attribute to this element.
510+ domSetTransformedAttribute ( output , name , value ) ;
511+
512+ // Some operations start by the tag attributes, and not by the tag itself.
513+ // When this is the case, the output node is not set yet, so
514+ // we add the transformed attributes into the original tag.
515+ if ( parentSourceNode && parentSourceNode . outputNode ) {
516+ domSetTransformedAttribute ( parentSourceNode . outputNode , name , value ) ;
517+ } else {
518+ domSetTransformedAttribute ( parentSourceNode , name , value ) ;
519+ }
520+ }
521+ }
522+
497523 /**
498524 * Implements `xsl:choose`, its child nodes `xsl:when`, and
499525 * `xsl:otherwise`.
@@ -579,7 +605,7 @@ export class Xslt {
579605
580606 /**
581607 * Implements `xsl:for-each`.
582- * @param input The Expression Context.
608+ * @param context The Expression Context.
583609 * @param template The template.
584610 * @param output The output.
585611 */
@@ -609,7 +635,7 @@ export class Xslt {
609635
610636 /**
611637 * Implements `xsl:include`.
612- * @param input The Expression Context.
638+ * @param context The Expression Context.
613639 * @param template The template.
614640 * @param output The output.
615641 */
@@ -756,6 +782,7 @@ export class Xslt {
756782 * - `xsltProcessContext`, `apply-templates` operation, when the current node is text.
757783 * @param context The Expression Context.
758784 * @param template The template, that contains the node value to be written.
785+ * @param output The output.
759786 */
760787 private commonLogicTextNode ( context : ExprContext , template : XNode , output : XNode ) {
761788 if ( output . nodeType === DOM_DOCUMENT_FRAGMENT_NODE ) {
@@ -882,7 +909,7 @@ export class Xslt {
882909 return false ;
883910 }
884911
885- protected xsltAttribute ( attributeName : string , context : ExprContext ) : XNode {
912+ protected findAttributeInContext ( attributeName : string , context : ExprContext ) : XNode {
886913 return context . nodeList [ context . position ] . childNodes . find (
887914 ( a : XNode ) => a . nodeType === DOM_ATTRIBUTE_NODE && a . nodeName === attributeName
888915 ) ;
0 commit comments