Can this XSLT snippet be more concise? - xslt

In the following code snippet, I have 3 for-each's but it seems to me they ought to be able to combine into one. It works as is, but I was wondering if anyone knew of a more elegant way to write it?
<xsl:for-each select="/essentials/webservice">
<xsl:for-each select="document(#filename)/productSearchResponse/products/product">
<xsl:sort select="producingRegion" order="ascending"/>
<xsl:for-each select="producingRegion[not(preceding::producingRegion=.)]">
<xsl:value-of select="."/>
<br/>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>

It's hard to give an answer without knowing the expected result and structure of the input XML, but I believe this should work.
<xsl:for-each select="/essentials/webservice">
<xsl:for-each select="document(#filename)/productSearchResponse/products/product/producingRegion[not(preceding::producingRegion=.)]">
<xsl:value-of select="."/>
<br/>
</xsl:for-each>
</xsl:for-each>
Edit: I just noticed that document(#filename) is a function call, not a node test. In that case, I think you need two for-eaches.
You can make it a little more concise by using a double slash in the XPath expression, though I tend to think that's not a very good practice:
<xsl:for-each select="/essentials/webservice">
<xsl:for-each select="document(#filename)//producingRegion[not(preceding::producingRegion=.)]">
<xsl:value-of select="."/>
<br/>
</xsl:for-each>
</xsl:for-each>

Related

XSLT 2.0 tokenising delimiters within delimiters

In XSLT 2.0 I have long string (parameter) with a delimiter (;) inside a delimiter (~), more specifically a triplet inside a delimiter.
Data is organized like so:
<parameter>qrsbfs;qsvsv;tfgz~dknk;fvtea;gtvath~pksdi;ytbdi;oiunhu</parameter>
The first tokenize($mystring,'~') in a for-each produces :
qrsbfs;qsvsv;tfgz
dknk;fvtea;gtvath
pksdi;ytbdi;oiunhu
Within that tokenization, I need to treat it by looping again:
qrsbfs
qsvsv
tfgz
dknk
fvtea
gtvath
pksdi
ytbdi
oiunhu
I can do intensive string manipulation to get there using concat, string-length, and substring-before/substring-after, but I wondered if there wasn't a more elegant solution that my neophyte mind wasn't overlooking?
EDIT, adding nested tokenize that returned incorrect results:
<xsl:for-each select="tokenize($myparameter,'~')">
<xsl:for-each select="tokenize(.,';')">
<xsl:if test="position()=1">
<xsl:value-of select="."/>
</xsl:if>
<xsl:if test="position()=2">
<xsl:value-of select="."/>
</xsl:if>
<xsl:if test="position()=3">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
If you wanted a one line solution, you could do something like this, using nested for-in-return statements:
<xsl:sequence select="for $n in tokenize(.,'~') return concat(string-join(tokenize($n,';'),'
'),'
')"/>
If you don't need to tokenize them separately, you could replace the ~ with ; and tokenize all 9 elements at the same time:
tokenize(replace(parameter,'~',';'),';')
For what it's worth, the code in https://xsltfiddle.liberty-development.net/pPqsHUe uses
<xsl:template match="parameter">
<xsl:for-each select="tokenize(., '~')">
<xsl:value-of select="tokenize(., ';')" separator="
"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
and with output method text produces
qrsbfs
qsvsv
tfgz
dknk
fvtea
gtvath
pksdi
ytbdi
oiunhu

xsl declare variable and reassign/change value in for-each is not working

I'm declaring variable "flag" in for-each and reassigning value inner for-each. I'm getting error duplicate variable within the scope.
My code is:
<xsl:variable name="flag" select="'0'"/>
<xsl:template match="/">
<xsl:for-each select="Properties/Property">
<xsl:variable name="flag" select="'0'"/>
<xsl:choose>
<xsl:when test="$language='en-CA'">
<xsl:for-each select="Localization/[Key=$language]">
<xsl:value-of select="Value/Value"/>
<xsl:variable name="flag" select="'1'"/>
</xsl:for-each>
<xsl:if test="$flag ='0'">
<xsl:value-of select="$flag"/>
</xsl:if>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
Can we update/re-assign variable value? If not Do we have any other options?
Any help?
XSLT is not a procedural language and variables in XSLT don't behave like variables in procedural languages; they behave more like variables in mathematics. That is, they are names for values. The formula x=x+1 makes no sense in mathematics and it makes no sense in XSLT either.
It's always difficult to reverse-engineer a specification from procedural code, especially from incorrect procedural code. So tell us what you are trying to achieve, and we will tell you the XSLT way (that is, the declarative/functional way) of doing it.
XSLT variables are single-assignment.
you can create an xsl template and do xsl recursion.
for example:
<xsl:template name="IncrementUntil5">
<xsl:param name="counter" select="number(1)" />
<xsl:if test="$counter < 6">
<test><xsl:value-of select="$counter"/></test>
<xsl:call-template name="IncrementUntil5">
<xsl:with-param name="counter" select="$counter + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
then call it like this:
<xsl:template match="/">
<div>
<xsl:call-template name="IncrementUntil5"/>
</div>
</xsl:template>
Try this:
<xsl:template match="/">
<xsl:for-each select="Properties/Property">
<xsl:choose>
<xsl:when test="$language='en-CA' and Localization/Key='en-CA'">
<xsl:value-of select="Value/Value"/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
You don't need to iterate through a collection to determine if something's present, the simple XPath Localization/Key='en-CA' will be true if there's any element matching it that exists.

Detecting space or text between node and its following-sibling using XSLT

I have an XML document which contains the following example extract:
<p>
Some text <GlossaryTermRef href="123">term 1</GlossaryTermRef><GlossaryTermRef href="345">term 2</GlossaryTermRef>.
</p>
I am using XSLT to transform this to XHTML using the following template:
<xsl:template match="GlossaryTermRef">
<a href="#{#href}" class="glossary">
<xsl:apply-templates select="node()|text()"/>
</a>
</xsl:template>
This works quite well, however I need to insert a space between the two GlossaryTermRef elements if they appear next to each other?
Is there a way to detect whether there is either space or text between the current node and the following-sibling? I can't always insert a space GlossaryTermRef item, as it may be followed by a punctuation mark.
I managed to solve this myself my modifying the template as follows:
<xsl:template match="GlossaryTermRef">
<a href="#{#href}" class="glossary">
<xsl:apply-templates select="node()|text()"/>
</a>
<xsl:if test="following-sibling::node()[1][self::GlossaryTermRef]">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:template>
Can anyone suggest a better way, or see any problems with this solution?
Firstly, "node()|text()" is a longwinded equivalent of "node()". Perhaps you meant "*|node()" which would select the element and text children but not the comments or PIs.
Your solution is probably as good as any. Another would be to use grouping:
<xsl:for-each-group select="node()" group-adjacent="boolean(self::GlossaryTermRef)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:for-each select="current-group()">
<xsl:if test="position() gt 1"><xsl:text> </xsl:text></xsl:if>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
Naah, that's not pretty at all.
My next attempt would be to use sibling recursion (where the parent does apply-templates on the first child, and each child does apply-templates on the immediately following sibling), but I don't think that's going to be an improvement either.
What about this one? what do you feel?
<xsl:template match="GlossaryTermRef">
<a href="#{#href}" class="glossary">
<xsl:apply-templates select="node()|text()"/>
</a>
</xsl:template>

avoid mismatched tags outputting HTML with XSLT

I'm new to XSLT, and there's one specific thing I don't know how to do, despite hours of searching for the answer.
I'm outputting blocks of HTML (result sets), and sometimes the result is a hyperlink, sometimes it is not.
The simple flow looks like this:
<a...> if #url
some HTML code
</a> if #url
But if I do:
when #url
<a...>
/when
some HTML code
when #url
</a>
/when
... I'm told that I have mismatched tags.
I was using CDATA text for the anchor set, but a lot of messages say that this is a "hack" approach.
I'm trying to avoid having to repeat the entire HTML code block only to include the anchors on only one of them.
How do I do this?
-------edit / additional info-----------
Does this make more sense?
<xsl:template match="Row">
<xsl:choose>
<xsl:when test="#url!=''">
<a><xsl:attribute name="href"><xsl:value-of select="#url" /></xsl:attribute>
</xsl:when>
</xsl:choose>
<img />
<xsl:choose>
<xsl:when test="#url!=''">
</a>
</xsl:when>
</xsl:choose>
</xsl:template>
In XSLT, your output is a tree of nodes. Writing an element node is a single atomic operation; it can't be split into separate operations of writing a start tag and writing an end tag. You can't create half a node.
If you do try to treat <a> and </a> as separate and separable operations, you will get this error, because the stylesheet must be well-formed XML.
So, stand back and explain what you are trying to achieve, and then we can tell you how to achieve it properly in XSLT.
One way to refactor the XSLT to conditionally apply the hyperlink and not repeat the logic to produce the <img/> (or whatever more complex logic you are trying to avoid repeating) is to extract that logic out into a different template(s) as either a named template or a template with a #mode.
For instance:
<xsl:template match="Row">
<xsl:choose>
<xsl:when test="#url!=''">
<a>
<xsl:attribute name="href">
<xsl:value-of select="#url"/>
</xsl:attribute>
<xsl:apply-templates select="." mode="image"/>
</a>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="." mode="image"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--The "common" logic to produce an image element, whether or not it will be surrounded by an anchor linking to the #url -->
<xsl:template match="Row" mode="image">
<img/>
</xsl:template>
An alternative way of accomplishing the same thing, but using templates instead of <xsl:choose>:
<xsl:template match="Row[#url]">
<a href="#url">
<xsl:apply-templates select="." mode="image"/>
</a>
</xsl:template>
<xsl:template match="Row">
<xsl:apply-templates select="." mode="image"/>
</xsl:template>
<xsl:template match="Row" mode="image">
<img/>
</xsl:template>

xsl:variable contains nodeset. How to output nth node of variable?

I am transforming an XML document. There is an attribute #prettydate that is a string similar to "Friday, May 7, 2010". I want to split that string and add links to the month and the year. I am using the exslt:strings module and I can add any other necessary EXSLT module.
This is my code so far:
<xsl:template match="//calendar">
<xsl:variable name="prettyparts">
<xsl:value-of select="str:split(#prettydate,', ')"/>
</xsl:variable>
<table class='day'>
<thead>
<caption><xsl:value-of select="$prettyparts[1]"/>,
<a>
<xsl:attribute name='href'><xsl:value-of select="$baseref"/>?date=<xsl:value-of select="#highlight"/>&per=m</xsl:attribute>
<xsl:value-of select='$prettyparts[2]'/>
</a>
<xsl:value-of select='$prettyparts[3]'/>,
<a>
<xsl:attribute name='href'><xsl:value-of select="$baseref"/>?date=<xsl:value-of select="#highlight"/>&per=y</xsl:attribute>
<xsl:value-of select='$prettyparts[4]'/>
</a>
</caption>
<!--etcetera-->
I have verified, by running $prettyparts through a <xml:for-each/> that I am getting the expected nodeset:
<token>Friday</token>
<token>May</token>
<token>7</token>
<token>2010</token>
But no matter which way I attempt to refer to a particular <token> directly (not in a foreach) I get nothing or various errors to do with invalid types. Here's some of the syntax I've tried:
<xsl:value-of select="$prettyparts[2]"/>
<xsl:value-of select="$prettyparts/token[2]"/>
<xsl:value-of select="exsl:node-set($prettyparts/token[2])"/>
<xsl:value-of select="exsl:node-set($prettyparts/token)[2]"/>
Any idea what the expression ought to be?
ETA: Thanks to #DevNull's suggestion, the correct expression is:
<xsl:value-of select="exsl:node-set($prettyparts)[position()=2]"/>
and, I have to set the variable this way:
<xsl:variable name="prettyparts" select="str:split(#prettydate,', ')" />
Try using [position()=2] instead of [2] in your predicates.
Example:
<xsl:value-of select="$prettyparts[position()=2]"/>