Suppose we have the following source xml.
<Data Key="SS_001PG"
OC:DataId="001PG"
OC:UniqueIdentifier="01-003"
OC:Status="available"
OC:DateOfBirth="2010-06-29"
OC:Sex="m">
<Event EventOID="123"
OC:EventLocation="we"
OC:StartDate="2010-07-12"
OC:Status="started"
OC:Age="0"
EventRepeatKey="1"></Event>
<Event EventOID="123"
OC:StartDate="2010-07-14"
OC:Status="started"
OC:Age="0"
EventRepeatKey="2"></Event>
</Data>
<Data Key="SS_1"
OC:DataId="1"
OC:UniqueIdentifier="1"
OC:Status="available"
OC:DateOfBirth="2010-07-14"
OC:Sex="m">
<Event EventOID="123"
OC:StartDate="2010-07-16"
OC:EndDate="2010-07-14"
OC:Status="started"
OC:Age="-1"
EventRepeatKey="1"></Event>
</Data>
We have the following xslt code to process it.
<xsl:variable name="repeatedEvents" select="//Event[#EventOID='123']"/>
<xsl:for-each select="$repeatedEvents">
<xsl:sort select="#EventRepeatKey" data-type="number"/>
<xsl:variable name="prevIndex" select="position()-1"/>
<xsl:variable name="prevEvent"
select="$repeatedEvents[position()=$prevIndex]"/>
<xsl:choose>
<xsl:when test="position()=1">
<xsl:value-of select="#EventRepeatKey"/>
</xsl:when>
<xsl:otherwise>
<xsl:if test="$prevEvent/#EventRepeatKey != #EventRepeatKey">
<xsl:value-of select="#EventRepeatKey"/>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
Now, as you can see, we are selecting all Events having the same EventOID and then sort the elements using EventRepeatkey. So, after the sorting, the Event under the second Data gets in between the Events of the first Data. Inside the loop, while the second element is being processed, We can access the first element using previous index, but when the third element is being processed, We can’t access the second element using the previous index. Is this because the second element is in a lower position in the tree than the third element? Any suggestion how we could solve the problem?
Can someone help?
It seems that you want to perform grouping.
Here is a simple use of the Muenchian method for grouping:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kEvByRepK" match="Event[#EventOID='123']"
use="#EventRepeatKey"/>
<xsl:template match=
"Event[#EventOID='123'
and
generate-id()
=
generate-id(key('kEvByRepK', #EventRepeatKey)[1])
]">
<xsl:value-of select="#EventRepeatKey"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
when this transformation is performed on the following XML document (wrapping the provided non-well-formed fragment):
<t xmlns:OC="my:OC" >
<Data Key="SS_001PG" OC:DataId="001PG" OC:UniqueIdentifier="01-003"
OC:Status="available" OC:DateOfBirth="2010-06-29" OC:Sex="m">
<Event EventOID="123" OC:EventLocation="we" OC:StartDate="2010-07-12"
OC:Status="started" OC:Age="0" EventRepeatKey="1"/>
<Event EventOID="123" OC:StartDate="2010-07-14" OC:Status="started"
OC:Age="0"
EventRepeatKey="2"/>
</Data>
<Data Key="SS_1" OC:DataId="1" OC:UniqueIdentifier="1" OC:Status="available"
OC:DateOfBirth="2010-07-14" OC:Sex="m">
<Event EventOID="123" OC:StartDate="2010-07-16" OC:EndDate="2010-07-14"
OC:Status="started" OC:Age="-1" EventRepeatKey="1"/>
</Data>
</t>
the wanted, correct result is produced:
1
2
Explanation: Read about the Muenchian method for grouping.
Related
I have a variable that contains a sequence of sorted nodes. When I use string-join to print out a list of the values, the order they are output is the order they are in the document, not the sorted order. Should this work, or am I stuck manually building the list with position() != last()?
Template
<?xml version='1.0'?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="SortedElements" as="element(Element)+">
<xsl:perform-sort select="/Root/Element">
<xsl:sort select="ElementNumber" data-type="number"/>
</xsl:perform-sort>
</xsl:variable>
<xsl:template match="/">
<xsl:text>Iterating through variable: </xsl:text>
<xsl:for-each select="$SortedElements">
<xsl:value-of select="ElementNumber"/>
</xsl:for-each>
<xsl:text> string-join: </xsl:text>
<xsl:value-of select="string-join($SortedElements/ElementNumber, ', ')"/>
</xsl:template>
</xsl:stylesheet>
Example Input
<?xml version="1.0"?>
<Root>
<Element>
<ElementNumber>3</ElementNumber>
</Element>
<Element>
<ElementNumber>1</ElementNumber>
</Element>
<Element>
<ElementNumber>2</ElementNumber>
</Element>
</Root>
Output
Iterating through variable: 123 string-join: 3, 1, 2
Your /ElementNumber step in the string-join sorts in document order so use string-join (for $e in $SortedElements return $e/ElementNumber,',') instead. Note that xsl:value-of takes a separator attribute, so instead of the string-join you can as well use <xsl:value-of select="for $e in $SortedElements return $e/ElementNumber" separator=", "/> or, if you switch to a version="3.0" stylesheet, <xsl:value-of select="$sortedElements!ElementNumber" separator=", "/>.
I am using xslt2.0 for convert one xml format to another xml format. This is my sample xml document.
<w:document>
<w:body>
<w:p>Para1</w:p>
<w:p>Para2</w:p>
<w:p>Para3</w:p>
<w:p>Para4</w:p>
</w:body>
</w:document>
Initially this is my xml format.so, i handled each and every <w:p> elements through my function in xslt given below...
<xsl:template match="document">
<Document>
<xsl:sequence select="mf:group(body/p, 1,count(//w:body//w:p)-1)"/>
</Document>
</xsl:template>
So,In that xslt function, i have coded how to reformat those elements.It's working fine...
But now,Xml format is restructured like given below...
<w:document>
<w:body>
<w:tbl><!--some text with children elements--></w:tbl>
<w:tbl><!--some text with children elements--></w:tbl>
<w:p>Para1</w:p>
<w:p>Para2</w:p>
<w:p>Para3</w:p>
<w:p>Para4</w:p>
</w:body>
</w:document>
So, As of now i have to handle both and elements in a same sequence.....
What i want to do is,
If i encounter elemtents then i have to call my template given below...
<xsl:template match="document">
<Document>
<xsl:for-each select="w:tbl">
<xsl:apply-templates select="w:tbl">
</xsl:apply-templates>
</xsl:for-each>
<xsl:sequence select="mf:group(body/p, 1,count(//w:body//w:p)-1)"/>
</Document>
</xsl:template>
<xsl:template match="w:tbl">
<!--xslt code here -->
</xsl:template>
But the for-each statement is not executed when I trying transformation...
So, Please guide me to get out of this issue...
I think instead of
<xsl:template match="document">
<Document>
<xsl:for-each select="w:tbl">
<xsl:apply-templates select="w:tbl">
</xsl:apply-templates>
</xsl:for-each>
<xsl:sequence select="mf:group(body/p, 1,count(//w:body//w:p)-1)"/>
</Document>
</xsl:template>
you simply want
<xsl:template match="document">
<Document>
<xsl:apply-templates select="w:body/w:tbl"/>
<xsl:sequence select="mf:group(body/p, 1,count(//w:body//w:p)-1)"/>
</Document>
</xsl:template>
If that does not do what you want then please show the result you want.
I want to insert PCDATA from the child element into a selected node attribute
XML
<root>
<tag>
<tag1>SOME TEXT</tag1>
</tag>
</root>
MY XSL
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xi="http://www.w3.org/2001/XInclude" version="1.0">
<xsl:template match="root">
<tag-out>
<xsl:attribute name="text">
<!-- What should I select? -->
<xsl:value-of select="tag/tag1/???"/>
</xsl:attribute>
<tag-out>
</xsl:template>
...........
</xsl:stylesheet>
Desired output XML
<root-out text="SOME TEXT">
<tag-out/>
</root-out>
Thanks
What's wrong with simply doing
<tag-out text="{tag/tag1}"></tag-out>
? Of course your sample with
<tag-out>
<xsl:attribute name="text">
<xsl:value-of select="tag/tag1"/>
</xsl:attribute>
<tag-out>
is also possible. But as your post is tagged XSLT 2.0 I would at least do
<tag-out>
<xsl:attribute name="text" select="tag/tag1"/>
<tag-out>
if you really need xsl:attribute.
It seems that this question was not discussed on stackoverflow before, save for Working With Nested XPath Predicates ... Refined where the solution not involving nested predicates was offered.
So I tried to write the oversimplified sample of what I'd like to get:
Input:
<root>
<shortOfSupply>
<food animal="doggie"/>
<food animal="horse"/>
</shortOfSupply>
<animalsDictionary>
<cage name="A" animal="kittie"/>
<cage name="B" animal="dog"/>
<cage name="C" animal="cow"/>
<cage name="D" animal="zebra"/>
</animals>
</root>
Output:
<root>
<hungryAnimals>
<cage name="B"/>
<cage name="D"/>
</hungryAnimals>
</root>
or, alternatively, if there is no intersections,
<root>
<everythingIsFine/>
</root>
And i want to get it using a nested predicates:
<xsl:template match="cage">
<cage>
<xsl:attribute name="name">
<xsl:value-of select="#name"/>
</xsl:attribute>
</cage>
</xsl:template>
<xsl:template match="/root/animalsDictionary">
<xsl:choose>
<!-- in <food> in <cage> -->
<xsl:when test="cage[/root/shortOfSupply/food[ext:isEqualAnimals(./#animal, ?????/#animal)]]">
<hungryAnimals>
<xsl:apply-templates select="cage[/root/shortOfSupply/food[ext:isEqualAnimals(#animal, ?????/#animal)]]"/>
</hungryAnimals>
</xsl:when>
<xsl:otherwise>
<everythingIsFine/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
So what should i write in place of that ??????
I know i could rewrite the entire stylesheet using one more template and extensive usage of variables/params, but it makes even this stylesheet significantly more complex, let alone the real stylesheet i have for real problem.
It is written in XPath reference that the dot . sign means the current context node, but it doesn't tell whether there is any possibility to get the node of context before that; and i just can't believe XPath is missing this obvious feature.
XPath 2.0 one-liner:
for $a in /*/animalsDictionary/cage
return
if(/*/shortOfSupply/*[my:isA($a/#animal, #animal)])
then $a
else ()
When applied on the provided XML document selects:
<cage name="B"/>
<cage name="D"/>
One cannot use a single XPath 1.0 expression to find that a given cage contains a hungry animal.
Here is an XSLT solution (XSLT 2.0 is used only to avoid using an extension function for the comparison -- in an XSLT 1.0 solution one will use an extension function for the comparison and the xxx:node-set() extension to test if the RTF produced by applying templates in the body of the variable contains any child element):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="xs my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<my:Dict>
<a genName="doggie">
<name>dog</name>
<name>bulldog</name>
<name>puppy</name>
</a>
<a genName="horse">
<name>horse</name>
<name>zebra</name>
<name>pony</name>
</a>
<a genName="cat">
<name>kittie</name>
<name>kitten</name>
</a>
</my:Dict>
<xsl:variable name="vDict" select=
"document('')/*/my:Dict/a"/>
<xsl:template match="/">
<root>
<xsl:variable name="vhungryCages">
<xsl:apply-templates select=
"/*/animalsDictionary/cage"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$vhungryCages/*">
<hungryAnimals>
<xsl:copy-of select="$vhungryCages"/>
</hungryAnimals>
</xsl:when>
<xsl:otherwise>
<everythingIsFine/>
</xsl:otherwise>
</xsl:choose>
</root>
</xsl:template>
<xsl:template match="cage">
<xsl:if test="
/*/shortOfSupply/*[my:isA(current()/#animal,#animal)]">
<cage name="{#name}"/>
</xsl:if>
</xsl:template>
<xsl:function name="my:isA" as="xs:boolean">
<xsl:param name="pSpecName" as="xs:string"/>
<xsl:param name="pGenName" as="xs:string"/>
<xsl:sequence select=
"$pSpecName = $vDict[#genName = $pGenName]/name"/>
</xsl:function>
</xsl:stylesheet>
When this transformation is applied on the provided XML document (corrected to be well-formed):
<root>
<shortOfSupply>
<food animal="doggie"/>
<food animal="horse"/>
</shortOfSupply>
<animalsDictionary>
<cage name="A" animal="kittie"/>
<cage name="B" animal="dogs"/>
<cage name="C" animal="cow"/>
<cage name="D" animal="zebras"/>
</animalsDictionary>
</root>
the wanted, correct result is produced:
<root>
<hungryAnimals>
<cage name="B"/>
<cage name="D"/>
</hungryAnimals>
</root>
Explanation: Do note the use of the XSLT current() function.
XPath 1.0 is not "relationally complete" - it can't do arbitrary joins. If you're in XSLT, you can always get round the limitations by binding variables to intermediate nodesets, or (sometimes) by using the current() function.
XPath 2.0 introduces range variables, which makes it relationally complete, so this limitation has gone.
Doesn't <xsl:when test="cage[#animal = /root/shortOfSupply/food/#animal]"> suffice to express your test condition?
Notice The dot operator in XPath is related to the current context. In XSLT the current template context_ is given by the function current(), which most of the time (not always) coincides with the ..
You can perform the test (and the apply templates as well), using the parent axis abbreviation (../):
cage[#animal=../../shortOfSupply/food/#animal]
Moreover the match pattern in the the first template is wrong, it should be relative to the root:
/root/animalsDictionary
#Martin suggestion is also obviously correct.
Your final template slightly modified:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="root/animalsDictionary">
<xsl:choose>
<xsl:when test="cage[#animal=../../shortOfSupply/food/#animal]">
<hungryAnimals>
<xsl:apply-templates select="cage[#animal
=../../shortOfSupply/food/#animal]"/>
</hungryAnimals>
</xsl:when>
<xsl:otherwise>
<everythingIsFine/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="cage">
<cage name="{#name}"/>
</xsl:template>
</xsl:stylesheet>
I am new to XSLT.
I have a input XML file which needs to be shown as a different output XML. I am using the xslt for transformation.
Input XML:
<Row>
<Column>abc.xyz.ijm</Column>
<Row>
Output XML:
<abc>
<xyz>
<ijm>String</ijm>
</xyz>
</abc>
I tried using xsl:when along with substring-before and substring-after functions but the result xml is not close to what I want.
How to know the last occurence of '.' so that <ijm>String</ijm> is constructed followed by the end tags of the words that are found before each of the previous occurences of the '.' so that </xyz> and </abc> can be added as shown in the output xml above?
Any code snippet is not at all appreciated.
Thanks in advance.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Column/text()" name="tokenize">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length()">
<xsl:choose>
<xsl:when test="not(contains($pText,'.'))">
<xsl:element name="{$pText}">String</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{substring-before($pText,'.')}">
<xsl:call-template name="tokenize">
<xsl:with-param name="pText"
select="substring-after($pText,'.')"/>
</xsl:call-template>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document (corrected to be well-formed):
<Row>
<Column>abc.xyz.ijm</Column>
</Row>
produces the wanted, correct result:
<abc>
<xyz>
<ijm>String</ijm>
</xyz>
</abc>
Explanation:
Recursively called named template with stop condition: the $pText parameter is either the empty string or a string that doesn't contain the period character.
Intermediate action: Create an element whose name is the substring-before the '.' character, then call yourself recursively with the text after the first period character as parameter.
Stop action: Create an element with name -- the whole string in the parameter, and value: the string "String".