0
votes

I am transforming XML files with XSL. My main setup is a "common" XSL file, which contains standard processes for all files, and several more-specific stylesheets which run on different types/classes of documents. The common sheet is imported into the specific sheets and the specific sheets are actually executed.

I sometimes need to have certain XML documents transform things differently than they would normally do in the common file. A very simple, stripped-down example is below:

XML

<data>
<name>John</name>
<age>25</age>
</data>

"Common" XSL (common.xsl)

<xsl:template match="//data">
<h1><xsl:value-of select="name" /></h1>
<div>Age: <xsl:value-of select="age" /></div>
</xsl:template>

Specific/executed XSL

<xsl:import href="common.xsl />
<!-- The following have not worked, but are two attempts at making this work-->
<!-- Attempt 1-->
    <xsl:template match="//h1">
    <p>My name is <xsl:value-of select="." />!</p>
    </xsl:template>
<!-- Attempt 2-->
    <xsl:template match="//data">
    <p>My name is <xsl:value-of select="name" />!</p>
    </xsl:template>

Based on my initial debugging, it seems the problem is the second transformations aren't finding any elements to transform. I could potentially move the code in question from the common file to the specific ones, and/or use xsl:choose statements to prevent it from running within certain documents. However, I would like to find a cleaner solution (if possible) that would let me add the additional bit to the specific stylesheet I need and not need to alter any of the other stylesheets. I've also looked into XSL pipelining, but I wasn't able to figure out how to make that work in this case.

Thank you for any help in advance!

1
An XSLT stylesheet, no matter how it's constructed, works on the input XML, not on its own products. If you want to create some nodes and then process these nodes further, you can do it by storing the result of step 1 in a variable, then applying templates to the variable. - michael.hor257k

1 Answers

0
votes

I don't see why the second example wouldn't have data to work with, I think you rather want

<xsl:template match="data"> 
<xsl:apply-imports/>
<p>My name is <xsl:value-of select="name" />!</p>
</xsl:template>

but that certainly should then output the h1, the div from the imported template and the p from the importing stylesheet.

As for pipelining, you can do that with XProc or you can do it in XPath 3.1 or XQuery 3.1 or XSLT 3 using fold-left, transform e.g. in XQuery 3.1 your first sample could look like

let $xslt1 := <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all">

    <xsl:mode on-no-match="shallow-copy"/>

    <xsl:template match="data">
        <h1><xsl:value-of select="name" /></h1>
        <div>Age: <xsl:value-of select="age" /></div>
    </xsl:template>
    
</xsl:stylesheet>,

$xslt2 := <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all">

    <xsl:mode on-no-match="shallow-copy"/>
  
    <xsl:template match="h1">
        <p>My name is <xsl:value-of select="." />!</p>
    </xsl:template>

</xsl:stylesheet>

let $xslts := ($xslt1, $xslt2)
return
    fold-left(
        $xslts,
        .,
        function($node, $xslt) {
            transform(
                map {
                    'source-node' : $node,
                    'stylesheet-node' : $xslt
                }
            )?output
        }
    )

https://xqueryfiddle.liberty-development.net/3Nzd8c2