XSLT: copy few nodes only of xml to a variable - xslt

Is there a way to copy 2 or 3 nodes of XML to a variable using XSLT? I'm looking for nodes and not the node values.
My sample XML is:
<node1>
<node2>
<node3>abc</node3>
<node4>def</node4>
</node2>
</node1>
<node1>
<node2>
<node3>123</node3>
<node4>456</node4>
</node2>
</node1>
And my XSLT sample is:
<xsl:for-each select="/node1/node2">
<xsl:if test="current()/node4 ! = '456'">
<xsl:copy-of select="./node3" />
<xsl:copy-of select="./node4" />
</xsl:if>
</xsl:foreach>
The problem with this is that I'm getting node4 everytime as the first node of the XML instead of current one. On node3 I'm getting the current one and there's no problem.

Your problem may be caused by the RTF(Resulting Tree Fragment) problem of XSLT-1.0:
A variable cannot contain a nodeset (but only a RTF)
I explained this problem in this SO answer.
RTFs cannot be queried by XPath-1.0 expressions, so they are only useful in a very limited subset of situations.
One solution would be using the newer XSLT-2.0.

It may help you to just select the nodes that you want all at once.
<xsl:variable name="sample">
<xsl:copy-of select="/node1/node2[node4!='456']/*[name()='node3' or name()='node4']"/>
</xsl:variable>

Related

Counting previous nodes returns unexpected results

I'd like to use itemized list instead of hard breaks, see the example:
<para>line1<?linebreak?>
line2<?linebreak?>
line3</para>
However, I am experiencing weird behavior in my recursive template which prevents processing the second line correctly. I've created simplified test case - not recursive any more. If count(preceding::processing-instruction('linebreak')) = 0 expression is used this way, nothing is returned, but I would expect the second line.
<line>line1</line><node>
line2<?linebreak?>
line3</node>
line2
That <node> element is for debugging purposes here. It confirms I process expected data.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="para[processing-instruction('linebreak')]">
<xsl:call-template name="getLine">
<xsl:with-param name="node" select="./node()"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="getLine">
<xsl:param name="node"/>
<line>
<xsl:copy-of
select="$node/self::processing-instruction('linebreak')[not(preceding::processing-instruction('linebreak'))]/preceding::node()"
/>
</line>
<xsl:call-template name="getSecondLine">
<xsl:with-param name="node"
select="$node/self::processing-instruction('linebreak')[not(preceding::processing-instruction('linebreak'))]/following::node()"
/>
</xsl:call-template>
</xsl:template>
<xsl:template name="getSecondLine">
<xsl:param name="node"/>
<node>
<xsl:copy-of select="$node"/>
</node>
<xsl:copy-of
select="$node/self::processing-instruction('linebreak')[count(preceding::processing-instruction('linebreak')) = 0]/preceding::node()"
/>
</xsl:template>
</xsl:stylesheet>
Tested in Saxon HE/EE 9.6.0.7 (in Oxygen XML Editor 18).
The processing of the first linebreak works correctly:
<line>
<xsl:copy-of
select="$node/self::processing-instruction('linebreak')
[not(preceding::processing-instruction('linebreak'))]
/preceding::node()"/>
</line>
though only on this sample; on more complex data, you would get the wrong results because you should be using the preceding-sibling axis rather than the preceding axis.
But the code could be greatly simplified, I would write the select expression as:
select="$node[self::processing-instruction('linebreak')][1]
/preceding-sibling::node()"
The processing of the second linebreak seems very confused. You are passing the parameter
$node/self::processing-instruction('linebreak')
[not(preceding::processing-instruction('linebreak'))]
/following::node()"
which is effectively
select="$node[self::processing-instruction('linebreak')][1]
/following-sibling::node()"
which selects the three nodes
line2<?linebreak?>line3
(plus whitespace) which you are outputting within a <node> element, producing
<node>line2<?linebreak?>line3</node>
(again ignoring whitespace)
and then you do
select="$node/self::processing-instruction('linebreak')
[count(preceding::processing-instruction('linebreak'))=0]
/preceding::node()"
Here $node/self::processing-instruction('linebreak') selects the second of these three nodes, which is the second linebreak processing instruction. The count of preceding (or preceding-sibling) processing instructions is 1, because the one you are dealing with is the second.
I'm not quite sure what you were thinking of, but I suspect your mistake is to think of "preceding" and "following" as selecting relative to the position of the node within the $node sequence, rather than relative to other nodes within the original source tree. I would recommend reading the section of an XPath reference book that describes the various axes.

xslt analyze string not allowing "value-of" selection

In the template below I'm trying to grab some information from an existing XML document and to re-write that same document with the addition of some specific ids.
Here's a snippet of the xml:
<item id='ta-' type='articulus'>
<fileName filestem='ta-'>ta-.xml</fileName>
<title>Super Sent., lib. 1 d. 1 q. 3 a. 1 tit.</title>
<questionTitle>Utrum utendum sit omnibus aliis a Deo</questionTitle>
</item>
In the template below, the regular expression works great. However the attempt to rebuild and fails. Saxon tells me 'XPTY0019: Required item type of first operand of '/' is node(); supplied value has item type xs:string'. I'm not sure what that means. But I think, after the "analyze-string' element, I'm no longer on the correct node in my tree to successfully perform the "value-of" selections. (See the template below). Thanks for your help.
<xsl:template match="item">
<xsl:analyze-string select="./title" regex="([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)">
<xsl:matching-substring>
<xsl:variable name="fs"><xsl:value-of select="concat('ta-l', regex-group(1) ,'d', regex-group(2) ,'q', regex-group(3),'a',regex-group(4))"></xsl:value-of></xsl:variable>
<item xml:id="{$fs}">
<fileName filestem='{$fs}'><xsl:value-of select="concat($fs, '.xml')"/></fileName>
<title><xsl:value-of select="./title"/></title>
<questionTitle><xsl:value-of select="./questionTitle"/></questionTitle>
</item>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:template>
You need to store the context node outside of the analyze-string with
<xsl:variable name="item" select="."/>
then inside of the analyze-string you can use
<title><xsl:value-of select="$item/title"/></title>
<questionTitle><xsl:value-of select="$item/questionTitle"/></questionTitle>

Denormalize a document using XSLT

I am new to XSLT so excuse me if this is a noob question.
Let's say I have this XML document (one hotel element with 2 private_rates):
<hotel>
<private_rates>
<private_rate>
<id>1</id>
</private_rate>
<private_rate>
<id>2</id>
</private_rate>
</private_rates>
</hotel>
Is there any way to use XSLT to transform it into 2 hotel elements, each with one private rate ?
<hotel>
<private_rates>
<private_rate>
<id>1</id>
</private_rate>
</private_rates>
</hotel>
<hotel>
<private_rates>
<private_rate>
<id>2</id>
</private_rate>
</private_rates>
</hotel>
How would the XSLT for that look like? Any help will be greatly appreciated! thanks.
As an alternative to Jollymorphic's solution, my preference would be
<xsl:template match="private_rates">
<hotel>
<xsl:copy-of select="."/>
</hotel>
</xsl:template>
Since a legal XML document has to have a single, containing document element, I presume that this sequence of hotels is going inside something. That being said, how about this:
<xsl:template match="hotel">
<xsl:for-each select="private_rates/private_rate">
<hotel>
<private_rates>
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</private_rates>
</hotel>
</xsl:for-each>
</xsl:template>
The apply-templates usage here presumes that you also have an identity template in your stylesheet that will copy source content that isn't more specifically matched by other templates (such as this one).
Underscores are frowned upon in element and attribute names, incidentally.

Xpath is not working on variable having xml string

I am facing issue in writing xapth. Let me explain the problem.
I am writing xslt to transform some xml. The xslt also loads one xml file from disk into xslt variable.
PeopleXml.xml:
<TestXml>
<People>
<Person id="MSA1" name="Sachin">
<Profession>
<Role>Developer</Role>
</Profession>
</Person>
<Person id="ZAG4" name="Rahul">
<Profession>
<Role>Tester</Role>
</Profession>
</Person>
</People>
</TestXml>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://MyNamespace"
version="2.0">
<xsl:variable name="PeopleXml" select ="document('PeopleXml.xml')"/>
<xsl:variable name="peopleList" select="$PeopleXml/TestXml/People/Person"/>
<xsl:variable name="person1" select="MSA1"/>
<xsl:variable name="person" select="$peopleList/Person[#id=$person1]/#name"/>
<xsl:template match="/">
<xsl:value-of select="$person"/>
</xsl:template>
</xsl:stylesheet>
Issue: The xpath "$peopleList/Person[#id=$person1]/#name" is not returning anything. Infact, $peopleList/Person also does not work. However, I can see two person nodes in $peopleList variable when I debugged the code.
Could anyone help me, what I am doing wrong in xpath?
EDIT
Above xapth issue has been resolved after applying Daniel's solution. Now, only issue remained is with accessing child nodes of person based on some condition.
Following test does not work.
<xsl:variable name="roleDev" select="'Developer'"/>
<xsl:when test="$peopleList/Profession/Role=$roleDev">
<xsl:value-of select="We have atleast one Developer"/>
</xsl:when>
Your problem is here:
<xsl:variable name="person1" select="MSA1"/>
This results in having the $person1 variable empty.
Why?
Because the expression MSA1 is evaluated -- the current node doesn't have any children named "MSA1" and nothing is selected.
Solution:
Specify the wanted string as string literal:
<xsl:variable name="person1" select="'MSA1'"/>
Your Second Question:
Now, only issue remained is with accessing child nodes of person based
on some condition.
Use:
boolean($peopleList[Profession/Role = 'Developer'])
This produces true() exactly when there is a node in $peopleList such that it has at least one Profession/Role crand-child whose string value is the string "Developer"
Since the variable peopleList is already Person nodes, you should access them like this:
<xsl:variable name="person" select="$peopleList[#id=$person1]/#name"/>

Matching different node handles to get a node value with XSLT

XSLT available is 1.0.
I'm working on a dual-language site in an XML-based CMS (Symphony CMS), and need to replace the English version of a category name with the French version.
This is my source XML.
<data>
<our-news-categories-for-list-fr>
<entry id="118">
<title-fr handle="technology">Technologie</title-fr>
</entry>
<entry id="117">
<title-fr handle="healthcare">Santé</title-fr>
</entry>
</our-news-categories-for-list-fr>
<our-news-article-fr>
<entry id="100">
<categories>
<item id="117" handle="healthcare" section-handle="our-news-categories" section-name="Our News categories">Healthcare</item>
<item id="118" handle="technology" section-handle="our-news-categories" section-name="Our News categories">Technology</item>
</categories>
<main-text-fr mode="formatted"><p>Blah blah</p></main-text-fr>
</entry>
</our-news-article-fr>
</data>
This is part of the XSLT that I currently have for the French version.
<xsl:template match="data">
<xsl:apply-templates select="our-news-article-fr/entry"/>
</xsl:template>
<xsl:template match="our-news-article-fr/entry">
<xsl:if test="categories/item">
<p class="category">In:</p>
<ul class="category">
<xsl:for-each select="categories/item">
<li><xsl:value-of select="."/></li>
</xsl:for-each>
</ul>
</xsl:if>
</xsl:template match>
The problem: the visible text of the anchor (<xsl:value-of select="."/>) gives the English version of the category title.
The handles of the following nodes match (all handles are in English), and so I'm thinking I should be able to match one from the other.
/data/our-news-categories-for-list-fr/entry/title-fr/#handle (value of title-fr node is French translation of category title)
/data/our-news-article-fr/entry/categories/item/#handle
I'm new to XSLT and am struggling to find how to do this.
Many thanks.
Add <xsl:key name="k1" match="our-news-categories-for-list-fr/entry" use="#id"/> as a child of your XSLT stylesheet element. Then use e.g. <li><xsl:value-of select="key('k1', #id)/title-fr"/></li>.
../our-news-categories-for-list-fr/entry/title-fr/text() instead of #handle should do it
Your problem is that you are in
<our-news-article-fr>
and need to reference
<our-news-categories-for-list-fr>
so I do a parent .. to walk up the tree and then down the entry nodes
Within the xsl:for-each repetition instruction, the context is our-news-article-fr/entry/categories/item. If you use . you select the current context, that's why you are receiving the english version there.
Another approach (not saying the simplest and the best one) is simply specify an XPath expression which locates the correct node. You can use the ancestor:: axis to go out from the current context to data and then use your test node. The needed predicate must match against the current context using current() function:
<xsl:value-of select="
ancestor::data[1]/
our-news-categories-for-list-fr/
entry/
title-fr
[#handle=current()/#handle]
"/>
If data is the root of your document you can obviously use an absolute location path:
/
data/
our-news-categories-for-list-fr/
entry/
title-fr
[#handle=current()/#handle]