XSL Process attributes in an order - xslt

I need to process attributes (of any node that has them) in a particular order. For example:
<test>
<event
era ="modern"
year ="1996"
quarter = "first"
day = "13"
month= "January"
bcad ="ad"
hour ="18"
minute = "23"
>The big game began.</event>
<happening
era ="modern"
day = "18"
bcad ="ad"
month= "February"
hour ="19"
minute = "24"
>The big game ended.</happening>
<other>Before time existed.</other>
</test>
This
<xsl:template match="test//*">
<div>
<xsl:apply-templates select="#*" />
<xsl:apply-templates />
</div>
</xsl:template>
<xsl:template match="#*">
<span class="{name()}">
<xsl:value-of select="."/>
</span>
</xsl:template>
would format things as I need them. That is, I'd get
<div><span class="era">modern</span>
<span class="year">1996</span>
<span class="quarter">first</span>
<span class="day">13</span>
<span class="month">January</span>
<span class="bcad">ad</span>
<span class="hour">18</span>
<span class="minute">23</span>The big game began.</div>
<div><span class="era">modern</span>
<span class="day">18</span>
<span class="bcad">ad</span>
<span class="month">February</span>
<span class="hour">19</span>
<span class="minute">24</span>The big game ended.</div>
<div>Before time existed.</div>
(though without the newlines I've added here for legibility).
But the order of the attributes wouldn't (necessarily) be right.
To fix that, I could change <xsl:apply-templates select="#*" /> to <xsl:call-template name="atts" /> and add a template that applies templates in the needed order, like this:
<xsl:template match="test//*">
<div>
<xsl:call-template name="atts" />
<xsl:apply-templates />
</div>
</xsl:template>
<xsl:template name="atts">
<xsl:apply-templates select="#era" />
<xsl:apply-templates select="#bcad" />
<xsl:apply-templates select="#year" />
<xsl:apply-templates select="#quarter" />
<xsl:apply-templates select="#month" />
<xsl:apply-templates select="#day" />
<xsl:apply-templates select="#hour" />
<xsl:apply-templates select="#minute" />
</xsl:template>
<xsl:template match="#*">
<span class="{name()}">
<xsl:value-of select="."/>
</span>
</xsl:template>
Is this the best-practices way to process attributes in a specified order? I keep wondering if there is a way that uses keys or a global variable.
I need to use XSLT 1.0 and in the real case, there are several dozen attributes, not just eight.

In contrast to elements for example, the order of attributes is not significant in XML, i.e. XPath and XSLT may process them in any order. Thus, the only way to force a given order, is to specify it somehow. One way is to call them explicitly as in your last code example. You can also extract all attribute names and store them in a separate XML file, e.g. something like
<attributes>
<attribute>era</attribute>
<attribute>year</attribute>
<attribute>month</attribute>
...
<attributes>
Now you can load these elements with the document() function and iterate over all attribute elements:
<xsl:variable name="attributes" select="document('attributes.xml')//attribute"/>
...
<xsl:template match="*">
<xsl:variable name="self" select="."/>
<xsl:for-each select="$attributes">
<xsl:apply-templates select="$self/#*[name()=current()]"/>
</xsl:for-each>
</xsl:template>

Related

Inline element data not appearing where expected after XSL transform

With the following XML
<para>Refer to Table 3 and Figure <grphcref refid="apm00-02-02-000018" shownow="0">6</grphcref>
for the door dimensions and clearances.</para>
and this XSL:
<xsl:template match="prcitem">
<xsl:for-each select="para">
<p>
<xsl:value-of select="." />
<xsl:apply-templates select="./grphcref" />
</p>
</xsl:for-each>
<xsl:apply-templates select="grphcref" />
<xsl:apply-templates select="table" />
<xsl:apply-templates select="unlist" />
</xsl:template>
<xsl:template match="grphcref">
<xsl:variable name="gotoimg" select="concat('#',#refid)"/>
<a href="{$gotoimg}" >
<xsl:value-of select="." /> - <xsl:value-of select="#refid" /> </a>
</xsl:template>
I get:
<p>Refer to Table 3 and Figure 6 for the door dimensions and clearances.
6 - apm00-02-02-000018
when I expected:
<p>Refer to Table 3 and Figure 6 - apm00-02-02-000018
for the door dimensions and clearances.
Can anyone offer guidance as to where I went wrong?
thx
If a para element is supposed to be transformed to a p element then in my view the "natural" way in XSLT is a template
<xsl:template match="para">
<p>
<xsl:apply-templates/>
</p>
</xsl:template>
If any other elements need special treatment add a template e.g.
<xsl:template match="grphcref">
<xsl:variable name="gotoimg" select="concat('#',#refid)"/>
<a href="{$gotoimg}" >
<xsl:value-of select="." /> - <xsl:value-of select="#refid" /> </a>
</xsl:template>
Text nodes are copied through to the result by the built-in templates.
The input sample doesn't explain why you mix template matching and for-each and why or whether you need xsl:apply-templates with any particularly selected nodes; as long as the input order should be preserved a simple processing of all child nodes with <xsl:apply-templates/> should suffice.

How to Add Color To a Div in XSL when Color is in the XML document

I have an XSL 1.0 document with the following:
<div class="workgroup_title">
<xsl:value-of select="./#name"/>
</div>
I need to set the color for this element. The color is in the XML file
<abc.xyz.color>FF5733</abc.xyz.color>
To get it, I use this:
<xsl:value-of select="./abc.xyz.color"/>
What I would have liked to do is
<div class="workgroup_title" style="color:"#<xsl:value-of select="./abc.xyz.color"/>>
<xsl:value-of select="./#name"/>
</div>
That's not allowed, though.
Or:
<xsl:attribute style="color:">#<xsl:value-of select="./abc.xyz.color"/></xsl:attribute>
But color is not one of the attributes that can be set like that.
You can use attribute value templates to compute (parts of) the value of a literal result element: <div style="color: #{abc.xyz.color}">...</div>
The following templates should suffice your needs:
<xsl:template match="text()" /> <!-- Removes the text from the <abc.xyz.color>FF5733</abc.xyz.color> element -->
<xsl:template match="/*"> <!-- Copies the root element and its namespace -->
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="div[#class='workgroup_title']"> <!-- Applies the template to the <div> element -->
<xsl:copy>
<xsl:attribute name="style"><xsl:value-of select="concat('color: #',../abc.xyz.color,';')"/></xsl:attribute>
<xsl:copy-of select="node()|#*" />
</xsl:copy>
</xsl:template>
Its output is:
<root xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<div style="color: #FF5733;" class="workgroup_title">
<xsl:value-of select="./#name"/>
</div>
</root>

Select template based on attribute-defined order

I've got the following template (excerpt):
<xsl:template match="section[#visible='1']">
<dl>
<dt><xsl:call-template name="content"/></dt>
<xsl:apply-templates select="(page|file)[#visible='1']" />
<xsl:apply-templates select="section[#visible=1]" mode="child" />
</dl>
</xsl:template>
<xsl:template match="section[#visible='1']" mode="child">
<dd><xsl:apply-templates select="." /></dd>
</xsl:template>
My problem is with the two apply-template elements at the end. The source XML elements (page, file, section,...) all have a pos attribute containing a number, which defines when they should be added to the output. But the way I have it currently, prevents a section with a lower pos value to be displayed before a page element with a higher position for example.
How can I achieve that? I've tried this:
<xsl:template match="section[#visible='1']">
<dl>
<dt><xsl:call-template name="content"/></dt>
<xsl:call-template name="kids"/>
</dl>
</xsl:template>
<xsl:template name="kids">
<xsl:for-each select="node()">
<xsl:sort select="#pos"/>
<!-- what would go here? -->
</xsl:for-each>
</xsl:template>
But I don't know what to put into the for-each loop. I could just duplicate the existing 2 templates, slap a name on them, and then call them with the current node as parameter, but that wouldn't be DRY. There must be a better way.
Have you tried this?
<xsl:template match="section[#visible='1']">
<dl>
<dt><xsl:call-template name="content"/></dt>
<xsl:apply-templates select="(page|file|section)[#visible='1']" mode="m">
<xsl:sort select="#pos"/>
</xsl:apply-templates>
</dl>
</xsl:template>
<xsl:template match="*" mode="m">
<xsl:apply-templates select="."/>
</xsl:template>
<xsl:template match="section" mode="m">
<xsl:apply-templates select="." mode="child"/>
</xsl:template>

Force check of each entry when applying template?

I have a template which fetches images and then outputs them into one of two templates.
I would like it to track each individual image and output based on the values of each one. Currently, if one image is wide, it outputs them all according to the wide template. I would rather utilize both templates.
<xsl:template name="get-images">
<xsl:param name="image-entry"/>
<xsl:choose>
<xsl:when test="($image-entry/image/meta/#width) > ($image-entry/image/meta/#height)">
<xsl:apply-templates select="$image-entry/image" mode="wide">
<xsl:with-param name="image-class" select="'full-width'"/>
<xsl:with-param name="caption-class" select="'full-width-caption'"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="$image-entry/image" mode="tall">
<xsl:with-param name="image-class" select="'center'"/>
<xsl:with-param name="caption-class" select="'full-width-caption'"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="image" mode="wide">
<xsl:param name="image-class" />
<xsl:param name="caption-class" />
<a href="{$root}/image/full{#path}/{filename}">
<img src="{$root}/image/wide{#path}/{filename}" alt="{../description}" class="{$image-class}"/>
</a>
<p class="{$caption-class}">
Image courtesy of: <xsl:value-of select="../title"/>
</p>
</xsl:template>
<xsl:template match="image" mode="tall">
<xsl:param name="image-class" />
<xsl:param name="caption-class" />
<span class="centered">
<a href="{$root}/image/full{#path}/{filename}">
<img src="{$root}/image/tall{#path}/{filename}" alt="{../description}" class="{$image-class}"/>
</a>
</span>
<p class="{$caption-class}">
Image courtesy of: <xsl:value-of select="../title"/>
</p>
</xsl:template>
Bonus question: How can I ignore the caption if value source doesn't exist?
<article-images field-id="24" subsection-id="5" items="1">
<item id="109" creation-date="2014-04-24T05:16:52+01:00">
<image size="317 KB" path="/uploads" type="image/jpeg">
<filename>funny-lolcat.jpg</filename>
<meta creation="2014-04-24T05:16:52+01:00" width="1600" height="1200" />
</image>
<description mode="formatted"><p>Aww!</p>
</description>
<title handle="" />
<source handle="" />
</item>
</article-images>
Instead of two modes, use one mode and move the wide vs not-wide logic into the template match expressions:
<xsl:template match="image[meta/#width > meta/#height]">
<!-- logic for wide image -->
</xsl:template>
<xsl:template match="image">
<!-- logic for not-wide image -->
</xsl:template>
And now you can just apply templates to all the images in one go without the choose:
<xsl:apply-templates select="$image-entry/image"/>
To ignore the caption if there's no source, I'd move the caption logic into another template matching the source element
<xsl:template match="source" mode="caption">
<p class="full-width-caption">
Image courtesy of: <xsl:value-of select="../title"/>
</p>
</xsl:template>
Then in the main template do:
<xsl:apply-templates select="../source" mode="caption"/>
If there is a source this will produce a caption, if there isn't then it'll produce nothing.
Given the example you've just added to the question it looks like you want to exclude the caption no when the source element "doesn't exist" but rather if it has no value. You can do this by changing the above apply-templates to
<xsl:apply-templates select="../source[string()]" mode="caption" />
This would add a caption for <source handle="">something</source> but not for <source handle="" />.
What this is doing is filtering so we only select the ../source element if the [string()] predicate is true. The string() function returns the "string value" of the context element (the source in this case) and a string in boolean context is treated as false if it is empty and true otherwise. So in this case the effect is to apply templates to the source element only if it has a non-empty value.

How to remove some tags without removing the content in xslt and pass entire content XML as input to another template

I am working on XSLT.
Source XML:
<?xml version="1.0" encoding="iso-8859-1"?>
<Content>
<alertHeader>
<ol xmlns="http://www.w3.org/1999/xhtml">
<li>
<strong>Review</strong>
your current available balance. It can be obtained 24 hours a day, 7 days a week through
Account Activity
any 123 ATM or by calling
<a id="dynamicvariable" href="#" name="Customercare">[Customercare]</a>
at
<a id="dynamicvariable" href="#" name="contactNo">[contactNo]</a>
.
</li>
<li>
<strong>Take into consideration</strong>
<ul>
<li>Please get in touch with us</li>
<li>Please consider this as important info</li>
</ul>
</li>
<li>
<strong>Current</strong>
Statementt doesnot match the requirement
<a id="dynamicvariable" href="#" name="Actual acccount">[Actual acccount]</a>
,plus
<a id="dynamicvariable" href="#" name="totalcharges">[totalcharges]</a>
Make u r response as positive.
</li>
</ol>
<p xmlns="http://www.w3.org/1999/xhtml"></p>
<div xmlns="http://www.w3.org/1999/xshtml"></div>
<span xmlns="http://www.w3.org/1999/xshtml"></span>
</alertHeader>
</Content>
I want to write a XSLT to pass entire content in the tag alertHeader as value to another template.
I want to modify this code as follows.
1.Remove the tags <p></p>, and <div></div>,<span></span> and <a></a>. I want to remove only tags but not the value of the tags. It should be there as it is.
2.Pass the content including tags to "Process" template.
Output required:
<aaa>
<text>
<ol xmlns="http://www.w3.org/1999/xhtml">
<li>
<strong>Review</strong>
your current available balance. It can be obtained 24 hours a day, 7 days a week through
<dynamicvariable name="Customercare"/>
at
<dynamicvariable name="contactNo"/>
.
</li>
<li>
<strong>Take into consideration</strong>
<ul>
<li>Please get in touch with us</li>
<li>Please consider this as important info</li>
</ul>
</li>
<li>
<strong>Current</strong>
Statementt doesnot match the requirement
</li>
</ol>
</text>
</aaa>
current XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="alertHeader">
<xsl:call-template name="process">
<xsl:with-param name="text" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="process">
<xsl:param name="text" />
<xsl:variable name="head" select="substring-before($text, '[')" />
<xsl:variable name="tag" select="substring-before(substring-after($text, '['), ']')" />
<xsl:variable name="tail" select="substring-after($text, ']')" />
<xsl:choose>
<xsl:when test="$head != '' and $tag != ''">
<xsl:value-of select="$head" />
<dynamicVariable name="{$tag}" />
<!-- recursive step: process the remainder of the string -->
<xsl:call-template name="process">
<xsl:with-param name="text" select="$tail" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Can any one say what all the changes required for my code.
Thanks.
There is no need to do any initial processing of the XML document and then pass it to the process template. From looking at your question, it looks like you have a requirement (which you haven't explicitly mentioned) to transform 'tags' in the text, such as form '[Total_Fee]', to dynamicVariable elements.
So, what you need to do is firstly just have a template to ignore your chosen nodes, but continue matching the elements and text within them.
<xsl:template match="Content|alertHeader|xhtml:p|xshtml:div|xshtml:span|xhtml:a">
<xsl:apply-templates select="#*|node()"/>
</xsl:template>
Do note the complication here is that you have defined different namespaces for some of your nodes (and these would have to be declared in the XSLT document). If you have multiple namespaces, you could also do the following.
<xsl:template match="Content|alertHeader|*[local-name() = 'p']|*[local-name() = 'span']|*[local-name() = 'div']|*[local-name() = 'a']">
Without namespaces you could do the following
<xsl:template match="Content|alertHeader|p|div|span|a">
Next, your named process template can then be combined to match text() elements
<xsl:template match="text()" name="process">
This allows it to match text elements, and recursively call itself to look for tags within the text.
Here is the full XSLT
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xshtml="http://www.w3.org/1999/xshtml"
exclude-result-prefixes="xhtml xshtml">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Content|alertHeader|xhtml:p|xshtml:div|xshtml:span|xhtml:a">
<xsl:apply-templates select="#*|node()"/>
</xsl:template>
<xsl:template match="text()" name="process">
<xsl:param name="text" select="." />
<xsl:choose>
<xsl:when test="contains($text, ']') and contains($text, '[')">
<xsl:value-of select="substring-before($text, '[')"/>
<dynamicVariable name="{substring-before(substring-after($text, '['), ']')}"/>
<xsl:call-template name="process">
<xsl:with-param name="text" select="substring-after($text, ']')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#*|*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
<ol xmlns="http://www.w3.org/1999/xhtml">
<li>
<strong>Review</strong> your current available balance. It can be obtained 24 hours a day, 7 days a week through Account Activity any Wells Fargo ATM or by calling <dynamicVariable name="Call_Center_Name" xmlns="" /> at <dynamicVariable name="Phone_Number" xmlns="" /> . </li>
<li>
<strong>Take into account</strong>
<ul>
<li>Your pending transactions and any additional transactions that have not yet been deducted from your available balance, such as checks you have written or upcoming scheduled automatic payments.</li>
<li>Any transactions that have been returned because you did not have enough money in your account at that time; they may be resubmitted for payment by the person or party who received the payment from you</li>
</ul>
</li>
<li>
<strong>Deposit</strong> enough money to establish and maintain a positive account balance. A deposit of at least <dynamicVariable name="Absolute_Available_Balance" xmlns="" /> ,plus <dynamicVariable name="Total_Fee" xmlns="" /> in fees, would have been required to make your account balance positive at the time we sent this notice. </li>
</ol>
The problem in xslt-1.0 is that you can't access a result node or nodeset, as in there is no XPath or function or anything to refer to the transformation result of a template.
Is it essential that you call process as it is? The way I'd go about this is to make process a template that transforms exactly one node, and then call it from every node below alertHeader, like:
<xsl:template match="alertHeader">
<!-- switch modes so we know we need to call process -->
<xsl:apply-templates mode="callproc"/>
</xsl:template>
<xsl:template match="*" mode="callproc">
<!-- call process on THIS node -->
<xsl:call-template name="process">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
<!-- descend -->
<xsl:apply-templates mode="callproc"/>
</xsl:template>
<!-- all the stuff that needs stripping -->
<xsl:template match="p|div|span|a" mode="callproc">
<!-- call process on the text() portion -->
<xsl:call-template name="process">
<xsl:with-param name="text" select="text()"/>
</xsl:call-template>
<!-- no descent here -->
</xsl:template>
Update:
Without process template
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xh="http://www.w3.org/1999/xhtml"
xmlns:xsh="http://www.w3.org/1999/xshtml">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Content">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="alertHeader">
<xsl:element name="aaa">
<xsl:element name="text">
<!-- switch modes so we know we need to call process -->
<xsl:apply-templates/>
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<!-- descend -->
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<!-- all the stuff that needs stripping -->
<xsl:template match="xh:p|xh:div|xh:span|xsh:div|xsh:span">
<xsl:apply-templates/>
</xsl:template>
<!-- was process -->
<xsl:template match="xh:a[#name]">
<xsl:element name="dynamicvariable" namespace="http://www.w3.org/1999/xhtml">
<xsl:attribute name="name">
<xsl:value-of select="#name"/>
</xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template match="xh:a"/>
<xsl:template match="text()">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
Output:
<ol xmlns="http://www.w3.org/1999/xhtml">
<li>
<strong>Review</strong>
your current available balance. It can be obtained 24 hours a day, 7 days a week through
any Wells Fargo ATM or by calling
<dynamicvariable name="Call_Center_Name"/>
at
<dynamicvariable name="Phone_Number"/>
.
</li>
<li>
<strong>Take into account</strong>
<ul>
<li>Your pending transactions and any additional transactions that have not yet been deducted from your available balance, such as checks you have written or upcoming scheduled automatic payments.</li>
<li>Any transactions that have been returned because you did not have enough money in your account at that time; they may be resubmitted for payment by the person or party who received the payment from you</li>
</ul>
</li>
<li>
<strong>Deposit</strong>
enough money to establish and maintain a positive account balance. A deposit of at least
<dynamicvariable name="Absolute_Available_Balance"/>
,plus
<dynamicvariable name="Total_Fee"/>
in fees, would have been required to make your account balance positive at the time we sent this notice.
</li>
</ol>
</text>
</aaa>