xslt analyze string not allowing "value-of" selection - xslt

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>

Related

XSLT: copy few nodes only of xml to a variable

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>

XPath: Selecting from a nodeset returned from a function

I'm just starting out with XSLT, so bear with me. I am processing a fairly complex document with a structure similar to the one below. It is divided into two sections, data and meta. For each data/item I need to look up the "actual" class in the corresponding meta/item.
<root>
<data>
<item id="i1">
<h3 id="p2">akkakk</h3>
<p id="p3">osijaoid</p>
<p id="p4">jqidqjwd</p>
<item>
</data>
<meta>
<item ref="i1">
<p ref="p2" class="heading"/>
<p ref="p3" class="heading"/>
<p ref="p4" class="body"/>
</item>
</meta>
</root>
I need to group adjacent p elements in meta on their class attribute. I thought a helper function could make this a bit cleaner:
<xsl:function name="fn:node-groups" as="node()*">
<xsl:param name="parent"/>
<xsl:variable name="nodeRefs" select="root($parent)//meta/item[#ref = $parent/#id]/p"/>
<xsl:for-each-group select="$nodeRefs" group-adjacent="#class">
<group class="{#class}">
<xsl:for-each select="current-group()">
<node ref="{#ref}"/>
</xsl:for-each>
</group>
</xsl:for-each-group>
</xsl:function>
And then use it when processing the data nodes, like so. My problem is that I can not select further from the nodeset returned by the function.
<xsl:template match="//data/item">
<!-- works -->
<xsl:variable name="test1" select="fn:node-groups(.)"/>
<!-- works -->
<xsl:variable name="test2" select="fn:node-groups(.)/*"/>
<!-- does not work -->
<xsl:variable name="test3" select="fn:node-groups(.)/group[#class = 'heading']"/>
</xsl:template>
I can write a star as in test2, and that gives me all node nodes. But anything else just gives me an empty nodeset. Or at least it looks that way, but at this point I really don't know anymore.
I suppose your stylesheet has an xmlns="http://example.com/foo" namespace declaration scope for the function that puts the result elements in the function in that namespace and then obviously fn:node-groups(.)/group will not select them as it selects a group element in no namespace. So make sure your function overrides that namespace with <xsl:for-each-group select="$nodeRefs" group-adjacent="#class" xmlns="">.
Another reason could be that your stylesheet defines xpath-default-namespace.
I also think that your third variable needs to be <xsl:variable name="test3" select="mf:node-groups(.)/node"/> as obviously the node sequence returned by your function already is a sequence of group elements, if you want to filter that sequence then use <xsl:variable name="test3" select="fn:node-groups(.)[#class = 'heading']"/>

Preceding sibling in XSLT

I am trying to put a specific condition on the basis of preceding sibling check.
I tried various options but nothing worked.
Here is the sample XML
<abc>
<title>something</title>
<element>1</element>
<element>2</element>
<element>3</element>
<element>4</element>
</abc>
I have a template match for <element> tag and I want to check if its immediate element is <title> Do some additioanl processing else do some other processing. Any pointers appriciated.
<xsl:template match="element">
I am boring. <xsl:value-of select="." />
</xsl:template>
<xsl:template match="element[preceding-sibling::*[1][self::title]]">
I am special. <xsl:value-of select="." />
</xsl:template>
If the current context is an element element then you can get its nearest preceding sibling element (regardless of name) using
preceding-sibling::*[1]
and so to check whether that element is a title you can use
preceding-sibling::*[1][self::title]
<xsl:template match="element[preceding-sibling::element()[1][self::title]]"/>

How to compare (partial) subtrees of an XML document using XSLT, returning a boolean value based on the comparison as a single tag?

Assume the existence of a software system that enables the use of XSLTs to specify a certain predicate on an XML message. Specifically: transform an input document to an output document of the following form: <predicate>true</predicate> (or <predicate>false</predicate>).
For some simple cases (like message contains XPath) this is rather trivial, but I now need write an XSLT for something like the following:
<change>
<!-- state before change -->
<item>
<name>
<first>...</first>
<last>...</last>
</name>
<something>
...
</something>
</item>
<!-- state after change -->
<item>
<name>
<first>...</first>
<last>...</last>
</name>
<something>
...
</something>
</item>
</change>
And I would like to return <predicate>true</predicate> for a definition of a mutation if:
The before or after state (or both) actually contain a subtree of something data (as this part is optional), so basically change/item[1]/something | change/item[2]/something, and
The before and the after state with both having any something data removed are not identical to each other.
The second part could be something like the following pseudocode: $before variable is change/item[1]/something with any existing something subtree removed from it, $after variable is change/item[2]/something with any existing something subtree removed from it and then perhaps something like not(deep-equal($before,$after))...?
Anyone here have any idea how I could do this using XSLT 2.0, as I suspect this to be totally impossible in XSLT 1.0?
Try along the lines of
<xsl:template match="change">
<xsl:choose>
<xsl:when test="item[1]/something | item[2]/something">
<xsl:variable name="before" as="element(item)">
<xsl:apply-templates select="item[1]" mode="rs"/>
</xsl:variable>
<xsl:variable name="after" as="element(item)">
<xsl:apply-templates select="item[2]" mode="rs"/>
</xsl:variable>
<predicate><xsl:value-of select="not(deep-equal($before,$after))"/></predicate>
</xsl:when>
<xsl:otherwise>
<predicate>false</predicate>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#* | node()" mode="rs">
<xsl:copy>
<xsl:apply-templates select="#* , node()" mode="rs"/>
</xsl:copy>
</xsl:template>
<xsl:template match="something" mode="rs"/>
Untested but should give you an idea. It basically runs a transformation in mode="rs" (remove-something) on the two item elements to strip the something element, then it does the comparison you posted (not(deep-equal($before,$after))).

XSLT - Sorting multiple values

I've already created my XSLT but id like to be able to sort the data, also add some kind of index so i can group the items together, the difficulty Im having is the the node i want to sort by contains multiple values - values id like to sort by.
For example here is my XML:
<item>
<title>Item 1</title>
<subjects>English,Maths,Science,</subjects>
<description>Blah Blah Bah...</description>
</item>
<item>
<title>Item 2</title>
<subjects>Geography,Physical Education</subjects>
<description>Blah Blah Bah...</description>
</item>
<item>
<title>Item 3</title>
<subjects>History, Technology</subjects>
<description>Blah Blah Bah...</description>
</item>
<item>
<title>Item 4</title>
<subjects>Maths</subjects>
<description>Blah Blah Bah...</description>
</item>
So if i sort by <subjects> I get this order:
English,Maths,Science,
Geography,Physical Education
History, Technology
Maths
But I would like this kind of output:
English
Geography
History
Maths
Maths
Physical Education
Science
Technology
Outputting the XML for each subject contained in <subjects>, so Item1 contains subjects Maths, English & Science so I want to output that Title and Description 3 times because its relevant to all 3 subjects.
Whats the best way in XSLT to do this?
I think one way to do this would be by using the node-set extenstion function to do multi-pass processing. Firstly you would loop through the existing subject nodes, splitting them by commas, to create a new set of item nodes; one per subject.
Next, you would loop through this new node set in subject order.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="urn:schemas-microsoft-com:xslt" extension-element-prefixes="exsl" version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="newitems">
<xsl:for-each select="items/item">
<xsl:call-template name="splititems">
<xsl:with-param name="itemtext" select="subjects"/>
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="exsl:node-set($newitems)/item">
<xsl:sort select="text()"/>
<xsl:value-of select="text()"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="splititems">
<xsl:param name="itemtext"/>
<xsl:choose>
<xsl:when test="contains($itemtext, ',')">
<item>
<xsl:value-of select="substring-before($itemtext, ',')"/>
</item>
<xsl:call-template name="splititems">
<xsl:with-param name="itemtext" select="substring-after($itemtext, ',')"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="string-length($itemtext) > 0">
<item>
<xsl:value-of select="$itemtext"/>
</item>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Note that the above example uses Microsoft's Extension functions. Depending on what XSLT processor you are using, you may have to specify another namespace for the processor.
You may also need to do some 'trimming' of the subjects, because in your XML sample above, there is a space before one of the subjects (Technology) in the comma-delimited list.
Well, processing the contents of text nodes isn't really the mandate of XSLT. If you can, you should probably change the representation to add some more XML structure into the subjects elements. Otherwise you'll have to write some really clever string processing code using XPath string functions, or perhaps use a Java-based XSLT processor and hand off the string processing to a Java method. It's not straightforward.