passing parameter to parsing of imported file - xslt

In parsing a WSDL, I come across many wsdl:import and xsd:import elements. I would like to parse the imports and pass the #location or #schemaLocation to the parser.
The intent is to have the file list grow when an imported file imports a file for example filea.wsdl;filez.xsd;filev.xsd. This way I can eliminate a previously imported file.
I would think something along along these lines:
<xsl:param name="file-list"/>
<xsl:template match="/">
<xsl:param name="file-list"/>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="wsdl:import">
<xsl:apply-templates select="document(#location)">
<xsl:with-param name="file-list" select="concat($file-list, ';', #location)`"/>
</xsl:apply-templates>
</xsl:template>

Your basic idea seems to be fine. You just need to pass the the file-list parameter along when applying templates, so:
add a <xsl:with-param name="file-list" value="$file-list"/> to the xsl:apply-templates in your first template to actually pass the parameter, and
add a <xsl:param name="file-list"/> to your second template to introduce the parameter there.

Related

XSLT 3.0: Create element only if input element has data

We are using XSLT internally to map a single input schema to a large number of distinct output schemas. Most of the servers using these schemas return errors on empty elements, so empty elements cannot appear in the output. In many cases, a piece of data in the input will simply map to a piece of data in the output, possibly with a minor transformation, e.g.:
<!-- Input -->
<ourns:DateCreated>2021-12-09</ourns:DateCreated>
<!-- Output -->
<otherns:CreatedDt>2021-12-09<otherns:CreatedDt>
The XSLT for this is straightforward, even with the "no empty elements" requirement:
<xsl:if test="ourns:DateCreated != ''">
<otherns:CreatedDt>
<xsl:value-of select="ourns:DateCreated/text()"/>
</otherns:CreatedDt>
</xsl:if>
However, when you're mapping thousands of elements across hundreds of schemas, this business of wrapping everything in <xsl:if/> gets tiresome. You could add a function, say:
<xsl:function name="ourfn:createElementIfData">
<xsl:param name="tag" as="xs:string"/>
<xsl:param name="data" as="xs:string"/>
<xsl:if test="$data != ''">
<xsl:element name="{$tag}"><xsl:value-of select="$data"/></xsl:element>
</xsl:if>
</xsl:function>
...
<xsl:sequence select="ourfn:createElementIfData('otherns:CreatedDt', ourns:DateCreated)"/>
But this function will only work if it lives in a stylesheet where both namespaces are declared. If you wanted to share it (as you probably would such a general-purpose function), you would end up needing to either
Declare every possible otherns in the shared stylesheet, or
Pass in the fully qualified namespace on every invocation,
both of which feel wrong.
This seems like such a common use case that I feel like there must be a simple way to do it. What am I missing?
You could define your basic rules like this:
<xsl:template match="ourns:DateCreated"
mode="copySimpleElement">
<otherns:CreatedDt>{.}</otherns:CreatedDt>
</xsl:template>
and then override it for empty elements:
<xsl:template match="*[. = '']"
mode="copySimpleElement"
priority="20"/>
and then you just have to apply-templates to the relevant elements in the appropriate mode.
You haven't shown any context but perhaps <xsl:template match="ourns:*[not(has-children())]"/> suffices to prevent any processing of the elements without content and adding <xsl:template match="ourns:DateCreated[has-children()]" expand-text="yes"><otherns:CreatedDt>{.}</otherns:CreatedDt></xsl:template> suffices to map the non-empty element to the wanted output element.
Of course <xsl:template match="ourns:*[not(has-children())]"/> could be set up as <xsl:template match="*[not(has-children())]"/> if the rule can be applied to input elements from any namespace or can take a sequence of patterns with <xsl:template match="ourns:*[not(has-children())] | ourns2:*[not(has-children())]"/>.
All the above assumes you are processing those nodes through other templates e.g. the identity transformation <xsl:mode on-no-match="shallow-copy"/>.
If you want to take the function approach I would check if you can pass in an xs:QName:
<xsl:function name="ourfn:createElementIfData">
<xsl:param name="node-name" as="xs:QName"/>
<xsl:param name="data" as="xs:string"/>
<xsl:if test="$data != ''">
<xsl:element name="{$node-name}" namespace="{namespace-uri-from-QName($node-name)}"><xsl:value-of select="$data"/></xsl:element>
</xsl:if>
</xsl:function>
and use e.g. <xsl:sequence select="ourfn:createElementIfData(QName('http://yourothernamespace/', 'otherns:CreatedDt'), ourns:DateCreated)"/>.

Issue: SVG <use> Element is Converted to String in XSL Template

I'm fairly new to XSL, XPATH etc. Some of this code is mine, some are someone else's.
Problem: When the template below gets called with the templates I've outlined further below, all the xsl:text nodes after the if test is output as a string instead of an HTML node, and thus the icon is not rendered.
This question has to do with understanding the why? of what's going on. My exact question is at the bottom of this post.
So, I have a template that I call that generates SVG elements with a <use> element for use with an SVG sprite.
<xsl:template name="svg-link">
<xsl:param name="svg-id"/>
<xsl:param name="svg-class"/>
<xsl:param name="svg-title"/>
<span class="{$svg-class} svgstore svgstore--{$svg-loc}">
<svg>
<xsl:if test="$svg-title != ''">
<title><xsl:value-of select="$svg-title"/></title>
</xsl:if>
<xsl:text disable-output-escaping="yes"><use xlink:href="</xsl:text>
<xsl:value-of select="concat('#', $svg-loc)" />
<xsl:text disable-output-escaping="yes">"></use></xsl:text>
</svg>
</span>
</xsl:template>
All sorts of templates call/apply this template. There is one in particular that I'm having an issue with. We have two snippets implemented by the CMS that output the same markup, but the configurations for the snippets are implemented differently, i.e. Page Template A vs Page Template B. The snippet in question is made of multiple XSL templates. The templates are organized like so:
Snippet Template: entry point for snippet for all callers. Accepts a couple of params related to CSS classes. Creates a few wrapper elements for the snippet. Calls the following template.
"Model" Template: is a template that needs to be defined by each page template. As mentioned above, each page template uses a different approach to implementing configuration options for the snippet. The idea is to make the following template agnostic about how the snippet was configured in the first place because this template is responsible for knowing those details and passing it on to the following template.
Snippet Item Template: renders most of the markup for the snippet based on the info passed to it by the "Model" Template.
Here's some simplified pseudo-code demonstrating above:
<xsl:template name="snippet">
<xsl:param name="outer-classes"/>
<xsl:param name="inner-classes"/>
<xsl:variable name="items">
<xsl:call-template name="snippet-model"/>
</xsl:variable>
<!-- Render Snippet if it has content. -->
<xsl:if test="count( $items )">
<div class="{ $outer-classes }">
<div class="{ $inner-classes }">
<xsl:copy-of select="$items">
</div>
</div>
</xsl:if>
</xsl:template>
<!-- Placeholder. Defined by each page template. -->
<xsl:template name="snippet-model"/>
<xsl:template name="snippet-item">
<xsl:param name="a"/>
<xsl:param name="b"/>
<xsl:param name="b"/>
<div class="snippet-item {$a}">
<xsl:apply-templates select="$b"/>
<xsl:call-template name="svg-link">
<xsl:with-param name="svg-id">alpha</xsl:with-param>
<xsl:with-param name="svg-class">alpha</xsl:with-param>
<xsl:with-param name="svg-title">The Title</xsl:with-param>
</xsl:call-template>
</div>
</xsl:template>
And an example of how a page template uses the above:
<xsl:template match="table[#class = 'snippet-alpha']">
<xsl:call-template="snippet">
<xsl:with-param name="outer-classes">page-template-a other</xsl:with-param>
<xsl:with-param name="inner-classes">some-template-modifier</xsl:with-param>
</xsl:call-template>
</xsl:template>
<!-- Template definition of `snippet-model` template. -->
<xsl:template name="snippet-model">
<!-- Another page template might not use `tbody/tr` to loop over. -->
<xsl:for-each select="tbody/tr">
<xsl:call-template="snippet-item">
<xsl:with-param name="a" select="td[1]"/>
<xsl:with-param name="b" select="td[2]"/>
<xsl:with-param name="c" select="td[3]"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
I've narrowed down my issue to likely be the xsl:variable capturing the results of xsl:call-template in the snippet template. And/Or the referencing of that variable later with xsl:copy-of.
What Have I Tried?
Below I have working and non-working solutions, all of which I do not fully grokk why they may or may not work.
Works: Adding xmlns:xlink="http://www.w3.org/1999/xlink" to xsl:stylesheet for the file that contains the svg-link template and then modding svg-link template, see code below this list.
Works: Instead of outputting the value of the xsl:variable that captures the results of xsl:call-template with xsl:copy-of. I replace xsl:copy-of with a second xsl:call-template that is identical to that of the call that was done inside the variable.
Does Not Work: Used xsl:sequence instead of xsl:copy-of.
Does Not Work: Tried data typing(?) the xsl:variable that captures the results of xsl:call-template with the as attribute. I.e. as="node()*".
<xsl:template name="svg-link">
<xsl:param name="svg-id"/>
<xsl:param name="svg-class"/>
<xsl:param name="svg-title"/>
<span class="{$svg-class} svgstore svgstore--{$svg-loc}">
<svg>
<xsl:if test="$svg-title != ''">
<title><xsl:value-of select="$svg-title"/></title>
</xsl:if>
<use xlink:href="{concat( '#', $svg-loc )}"></use>
</svg>
</span>
</xsl:template>
Question: Why are some of the contents of the svg-link template being output as a string (instead of HTML) based on how the result of a call to xsl:call-template is captured/called? As you can see, I have working and non-working solutions - I would like to know why. Thanks!
First of all, disable-output-escaping is an optional serialization feature. Additionally, the XSLT 2 or 3 specs spell out when it doesn't work at all, see https://www.w3.org/TR/xslt-30/#disable-output-escaping
If output escaping is disabled for an xsl:value-of or xsl:text
instruction evaluated when temporary output state is in effect, the
request to disable output escaping is ignored.
and https://www.w3.org/TR/xslt-30/#dt-temporary-output-state
xsl:variable, xsl:param, xsl:with-param, xsl:function, xsl:key,
xsl:sort, xsl:accumulator-rule, and xsl:merge-key always evaluate the
instructions in their contained sequence constructor in temporary
output state
So inside your xsl:variable any disable-output-escaping can't work.
The whole attempt to use it to try to construct an SVG use element is completely unnecessary, you can create any result elements as literal result elements e.g. <use xlink:href="{concat( '#', $svg-loc )}"></use> (with an appropriate XLink namespace declaration in scope for the attribute from that namespace), or, if you need to compute part of the name or namespace, using xsl:element.

How best to re-use XSLT code when different groups of documents have different optional tag attributes for the same tags?

Here is the scenario. I have XML documents with tags that look like this:
<para a="A" b="B" c="C">
appearing in different classes of XML documents. The a and b attributes are completely generic and are handled exactly the same way in all documents. The optional c attribute is document class dependent, and will require different transforms, depending on the document class. I would like to write a stylesheet to be included or imported into document class-specific stylesheets which take care of doing the transform for and attributes a and b, which attribute c handled by the parent stylesheet. I can think of at least a couple of ways to do this, but am wondering if there is some canonical best way.
Let's call the stylesheet to be shared st-generic.xsl. Each of the templates in st-generic.xsl would be named:
<xsl:template match="para" name="generic-para">...</xsl:template>
The document class specific stylesheets would then import st-generic.xsl (rather than include, to set precedence), and would include templates that look like this:
<xsl:template match="para">
<xsl:call-template name="generic-para"/>
{other stuff}
<xsl:apply-templates/>
</xsl:template>
This probably works, but seems a bit inelegant. For example, in most cases the generic-para template is all that is needed, so this template will need to similarly include an
<xsl:apply-templates/>
node in the template body. I'm wondering if there is a better way to do this?
Without seeing more of your code and input, I don't see why you should use named templates.
Let's consider two hypothetical elements in your input:
<para a="A" b="B" c="C">paracontent</para>
<div a="A" b="B" c="C">divcontent</div>
Now, let us assume both attribute a and b are generic attributes that can be handled in the same way, no matter what element they occur on. c is processed in more than one way, depending on the parent element.
There is of course a template matching para elements
<xsl:template match="para">
and I don't see why you need a named template to process the attributes of this element. Why not simply apply-templates to all attributes?
<xsl:template match="para">
<xsl:apply-templates select="#*"/>
<!--Do stuff other than processing attributes...-->
</xsl:template>
Then, other templates (not named ones) would match the two generic attributes:
<xsl:template match="#a">
<!--Process attribute a, no matter the parent element-->
</xsl:template>
and
<xsl:template match="#b">
<!--Process attribute b, no matter the parent element-->
</xsl:template>
or perhaps even
<xsl:template match="#a|#b">
<!--Process attributes a or b, no matter the parent element-->
</xsl:template>
whereas you would write separate templates for attribute c:
<xsl:template match="para/#c">
<!--Process attribute c, if para is the parent-->
</xsl:template>
and
<xsl:template match="div/#c">
<!--Process attribute c, if div is the parent-->
</xsl:template>
All of this code is still in separate templates and can be modularized and imported or included ad libitum.
The best I can think of at the moment is:
Include this in your st-generic.xsl file:
<xsl:template match="para">
{ do para processing }
<xsl:apply-templates select="para" mode="custom" />
<xsl:apply-templates />
</xsl:template>
<xsl:template match="para" mode="custom" priority="-5" />
Then when you need custom behavior, you can put this in your main template:
<xsl:template match="para" mode="custom">
{ do custom para processing }
</xsl:template>
this will be invoked between the { do para processing } and the <xsl:apply-templates /> in the generic file, so you can have the custom template focus on custom behavior.
XSL provides a canonical way of solving this problem using the <xsl:apply-imports/> element.
In this particular example, you would have the st-generic.xsl stylesheet import a customizations.xsl stylesheet which would include optional customizations:
st-generic.xsl:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="customizations.xsl"/>
<xsl:template match="para">
<xsl:if test="#a and string(#a)">
{do stuff}
</xsl:if>
<xsl:if test="#b and string(#b)">
{do other stuff}
</xsl:if>
<xsl:apply-imports/>
<xsl:apply-templates/>
</xsl:template>
/xsl:stylesheet>
customizations.xsl:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="para">
<xsl:if test="#c and string(#c)">
{do special stuff}
</xsl:if>
</xsl:template>
/xsl:stylesheet>
A completely flexible solution is provided if you also name the templates in the st-generic.xsl stylesheet, which then allows you to import st-generic.xsl into yet another stylesheet, calling the named templates as needed or using <xsl:apply-imports/> as used in the previous example.
So, my original answer proved not to work because of the unfortunate way that <xsl:apply-imports/> works when there is no matching template. See the answer to this question for details. The bottom line is it resorts to default templates, which ends up messing the output when you use it the way I had it in my original answer.
In pondering what to do about this I came up with an alternative solution I like better anyway, namely use attribute sets defined in an included stylesheet. For example, to handle the case proposed in the original question, you could do something like this:
The main stylesheet would look like this:
<xsl:include href="generic-attributes.xsl"/>
<xsl:template match="para">
<fo:block xsl:use-attribute-sets="para_default-attrs">
<xsl:if test="#a and string(#a)">
{do stuff}
</xsl:if>
<xsl:if test="#b and string(#b)">
{do other stuff}
</xsl:if>
~~ other stuff ~~
<xsl:apply-templates/>
</fo:block>
</xsl:template>
The generic-attributes.xsl file would then contain this:
<xsl:attribute-set name="para_default-attrs">
<xsl:attribute name="a">A</xsl:attribute>
<xsl:attribute name="b">B</xsl:attribute>
</xsl:attribute-set>

XSLT calling template based on the condition

I have a problem in my real-time XSLT files. Based on that, i am putting my question here. I have 3 xslt files such as 1.xsl and master.xsl. This master.xsl is imported into 1.xsl
On the master.xsl, i am using this below code
<xsl:call-template name="content">
<xsl:with-param name="request" select="$request"/>
<xsl:call-template>
Like wise, on the 1.xsl,
<xsl:template name="content">
<xsl:param name="request" as="node()"/>
....
</xsl:template>
In this case, on the file 1.xsl, some time, for the template 'content', the parameter request, there wont be passed. it will be passed in some time.
so, the above template will be(without parameter 'request') in some cases
<xsl:template name="content">
....
</xsl:template>
when there is no parameter, this is showing error as of now
XTSE0680: Parameter request is not declared in the called template
so, in this case, kindly give me some ideas to modify the coding on master.xsl
The reasons for the error message have been pointed out in the answer to XSLT calling template with xsl:with-param on different template. You have to modify the template to declare the parameter. Or you would need to change the code in master.xsl to only pass the parameter with e.g.
<xsl:choose>
<xsl:when test="$request">
<xsl:call-template name="content">
<xsl:with-param name="request" select="$request"/>
<xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="content"/>
</xsl:otherwise>
</xsl:choose>
That will only pass in $request if it is a non-empty sequence. Of course if your code is included in a stylesheet where the template does declare the parameter and the variable $request is not empty you will continue to experience the error. There is no way to check at run-time whether the template expects a param or not.

Generic way to pass XSL param to templates

I have several stylesheets and templates, and I'd like to add some behaviour to all of them.
Let's say something like that :
<xsl:template match="*" priority='10'>
<xsl:apply-templates select="." mode="someFunStuffsToDo"/>
<xsl:next-match/>
</xsl:template>
But, as i need some generic templates, i have issues with params because I don't know all the different types of param I could have.
Is there any 'easy' way to say something like that :
<xsl:template match="*" priority='10'>
<xsl:param select="All the params you get"/>
<xsl:apply-templates select="." mode="someFunStuffsToDo">
<xsl:with-param select="All the params you got"/>
</xsl:apply-templates>
<xsl:next-match/>
</xsl:template>
I could imagine some solution based on generic param which would contain nodes of params but I'd need to rewrite most of my actual templates to switch different specifics params declaration for the generic one...
EDIT :
Ok, I think I just find a solution just before posting my question :
Tunnel parameters.
Is that working for my purpose as I understand it, i mean
<xsl:template match="*" priority='10'>
<xsl:apply-templates select="." mode="someFunStuffsToDo"/>
<xsl:next-match/>
</xsl:template>
will work if i just set my parameters before and after with the attribute tunnel='yes' ?
(I haven't tested yet and shame on me, but i assume that next-match will preserve the current mode)
Yes, tunnel parameters help, you however need to make sure your passing code and your receiving templates have the <xsl:with-param name="foo" tunnel="yes" select="bar"/> respectively <xsl:param name="foo" tunnel="yes"/>. But any templates in between, like the one you have, will then not need a with-param.