how to i get the tokenize spliting function content order instead of xml order - xslt

When i use XSLT 2.0 key and tokenize function, it's return items order getting changed based on key value. in our output we required retain the same order of tokenize sequence.
Input File
<?xml version="1.0" encoding="UTF-8"?> <a> <bd id="a">a</bd> <bd id="b">b</bd> <bd id="e">e</bd> <bd id="d">d</bd> </a>
XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:key name="idcollect" match="*[#id]" use="#id" />
<xsl:variable name="name" select="'d,b,e,a'"/>
<xsl:template match="/">
<xsl:for-each select="key('idcollect',tokenize($name,','))" >
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
current Output
<?xml version="1.0" encoding="UTF-8"?><bd id="a">a</bd><bd id="b">b</bd><bd id="e">e</bd><bd id="d">d</bd>
Expected output
<?xml version="1.0" encoding="UTF-8"?><bd id="d">d</bd><bd id="b">b</bd><bd id="e">e</bd><bd id="a">a</bd>

I think you want e.g.
<xsl:variable name="main-doc" select="/"/>
<xsl:for-each select="for $token in tokenize($name,',') return key('idcollect', $token, $main-doc)">
<xsl:copy-of select="."/>
</xsl:for-each>
or in XSLT 3
<xsl:variable name="main-doc" select="/"/>
<xsl:for-each select="tokenize($name,',') ! key('idcollect', ., $main-doc)">
<xsl:copy-of select="."/>
</xsl:for-each>
Of course in both cases the for-each/copy-of nesting is not needed and e.g.
<xsl:copy-of select="let $main-doc := / return tokenize($name,',') ! key('idcollect', ., $main-doc)"/>
or
<xsl:variable name="main-doc" select="/"/>
<xsl:copy-of select="for $token in tokenize($name,',') return key('idcollect', $token, $main-doc)"/>
would suffice.

Try:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="idcollect" match="*[#id]" use="#id" />
<xsl:variable name="name" select="'d,b,e,a'"/>
<xsl:template match="/">
<xsl:variable name="ids" select="tokenize($name,',')"/>
<xsl:for-each select="key('idcollect', $ids)" >
<xsl:sort select="index-of($ids, .)"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Or, if you prefer:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="idcollect" match="*[#id]" use="#id" />
<xsl:variable name="name" select="'d,b,e,a'"/>
<xsl:template match="/">
<xsl:variable name="xml" select="/"/>
<xsl:for-each select="tokenize($name, ',')" >
<xsl:copy-of select="key('idcollect', ., $xml)"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Related

precalculating node-set via copy-of and accessing ancestors (XSLT 1.0)

I want to precalculate a subtree of nodes in an source XML, and the process them seperately (because I want the subset to be processed in different ways), and access some values from ancestors.
simple example
<numbers count="5">
<number value="1"/>
<number value="2"/>
<number value="3"/>
<number value="4"/>
<number value="5"/>
</numbers>
and lets say I have an xslt (MSXML) to extract the even nodes somehow
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<evens>
<xsl:for-each select="numbers/number">
<xsl:choose>
<xsl:when test="#value mod 2 = 0">
<even>
<xsl:attribute name="count">
<xsl:value-of select="../#count"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="#value"/>
</xsl:attribute>
</even>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</evens>
</xsl:template>
</xsl:stylesheet>
and we get..
<evens>
<even count="5" value="2" />
<even count="5" value="4" />
</evens>
nice...
but how can I seperate the filtering from the processing so something like...
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template name="calculateNodes">
<xsl:for-each select="numbers/number">
<xsl:choose>
<xsl:when test="#value mod 2 = 0">
<xsl:copy-of select="."/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="nodes">
<xsl:call-template name="calculateNodes"/>
</xsl:variable>
<evens>
<xsl:for-each select="msxsl:node-set($nodes)/number">
<even>
<xsl:attribute name="count">
<xsl:value-of select="../#count"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="#value"/>
</xsl:attribute>
</even>
</xsl:for-each>
</evens>
</xsl:template>
</xsl:stylesheet>
this gives.
<evens>
<even count="" value="2" />
<even count="" value="4" />
</evens>
so...the ancestors arent copied.
Is there an idiomatic way to get out of this?
A copied node exists on its own, outside of the original tree. In your example, the parent of number is the $nodes variable, which does not have any attributes.
Why don't you do simply:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/numbers">
<xsl:variable name="nodes" select="number[#value mod 2 = 1]"/>
<evens>
<xsl:for-each select="$nodes">
<even count="{../#count}" value="{#value}"/>
</xsl:for-each>
</evens>
</xsl:template>
</xsl:stylesheet>
This way you have a variable containing a reference to the original nodes, instead of a copy. Then you also have access to the original parent. And the content of the variable is a node-set; you don't need to convert it.
this seems to work
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template name="calculateNodes">
<xsl:for-each select="numbers/number">
<xsl:choose>
<xsl:when test="#value mod 2 = 0">
<numberWrapper>
<xsl:attribute name="count">
<xsl:value-of select="../#count"/>
</xsl:attribute>
<xsl:copy-of select="."/>
</numberWrapper>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="nodes">
<xsl:call-template name="calculateNodes"/>
</xsl:variable>
<evens>
<xsl:for-each select="msxsl:node-set($nodes)/numberWrapper">
<even>
<xsl:attribute name="count">
<xsl:value-of select="#count"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="number/#value"/>
</xsl:attribute>
</even>
</xsl:for-each>
</evens>
</xsl:template>
</xsl:stylesheet>

correct xsl template matching syntax

I'm learning XSL and hope to get some help. I want to extract part of the following datasets.xml and output them as tab delimited texts:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<listDatasetsResponse xmlns="http://www.algorithmics.com/schema">
<status>OK</status>
<datasets size="31">
<dataset>
<id>stress_20150910_20150910_160259</id>
<basedOn>Mimm_20150910_20150910_030922</basedOn>
<active>false</active>
<sandbox>true</sandbox>
<ownedBy>admin</ownedBy>
<createdOn>2015-09-10T16:04:24.199-04:00</createdOn>
<createdBy>rtcesupp</createdBy>
<evaluated>true</evaluated>
<stopped>false</stopped>
</dataset>
<dataset>
<id>imm_20150910_20150910_140315</id>
<basedOn>Mimm_20150910_20150910_030922</basedOn>
<active>true</active>
<sandbox>true</sandbox>
<ownedBy>admin</ownedBy>
<createdOn>2015-09-10T14:04:42.696-04:00</createdOn>
<createdBy>rtcesupp</createdBy>
<evaluated>true</evaluated>
<stopped>false</stopped>
</dataset>
</datasets>
</listDatasetsResponse>
here is the XSL I used:
$ vi dataset.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1"/>
<xsl:strip-space elements="*" />
<xsl:template match="/listDatasetsResponse/datasets/dataset">
<xsl:value-of select="id"/><xsl:text> </xsl:text>
<xsl:choose>
<xsl:when test="active='true'">
<xsl:text>ACTIVE</xsl:text>
</xsl:when>
<xsl:when test="stopped='true'">
<xsl:text>,STOPPED</xsl:text>
</xsl:when>
<xsl:when test="evaluated='true'">
<xsl:text>,EVALUATED</xsl:text>
</xsl:when>
</xsl:choose>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
I expected the result of:
stress_20150910_20150910_160259 EVALUATED
imm_20150910_20150910_140315 ACTIVE,EVALUATED
but what I got is:
OKstress_20150910_20150910_160259Mimm_20150910_20150910_030922falsetrueadmin2015-09-10T16:04:24.199-04:00rtcesupptruefalseimm_20150910_20150910_140315Mimm_20150910_20150910_030922truetrueadmin2015-09-10T14:04:42.696-04:00rtcesupptruefalse
It seemed like the XSL stylesheet was ignored. Could some one point me to correct XSL template matching syntax?
The fundamental problem in your XSL is that none of the XPath expression being used match the element in the source XML. Notice your XML has default namespace declared at the root element :
xmlns="http://www.algorithmics.com/schema"
Descendant elements without explicit prefix and without local default namespace inherits ancestor default namespace implicitly. To match element in namespace, simply declare a prefix that point to the namespace-uri and use that prefix in your XPath expressions, for example :
<xsl:stylesheet .....
xmlns:d="http://www.algorithmics.com/schema">
.....
<xsl:template match="/d:listDatasetsResponse/d:datasets/d:dataset">
<xsl:value-of select="d:id"/><xsl:text> </xsl:text>
.....
</xsl:template>
</xsl:stylesheet>
How about ...
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:a="http://www.algorithmics.com/schema"
version="1.0" >
<xsl:output method="text" encoding="utf-8"/>
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()" />
<xsl:template match="/">
<xsl:apply-templates select="a:listDatasetsResponse/a:datasets/a:dataset" />
</xsl:template>
<xsl:template match="a:dataset">
<xsl:value-of select="a:id"/>
<xsl:text> </xsl:text>
<xsl:apply-templates select="a:active|a:stopped|a:evaluated" />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="a:active[.='true']">
<xsl:if test="preceding-sibling::a:active[.='true']|
preceding-sibling::a:stopped[.='true']|
preceding-sibling::a:evaluated[.='true']">,</xsl:if>
<xsl:text>ACTIVE</xsl:text>
</xsl:template>
<xsl:template match="a:stopped[.='true']">
<xsl:if test="preceding-sibling::a:active[.='true']|
preceding-sibling::a:stopped[.='true']|
preceding-sibling::a:evaluated[.='true']">,</xsl:if>
<xsl:text>STOPPED</xsl:text>
</xsl:template>
<xsl:template match="a:evaluated[.='true']">
<xsl:if test="preceding-sibling::a:active[.='true']|
preceding-sibling::a:stopped[.='true']|
preceding-sibling::a:evaluated[.='true']">,</xsl:if>
<xsl:text>EVALUATED</xsl:text>
</xsl:template>
</xsl:stylesheet>
... or this version ...
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:a="http://www.algorithmics.com/schema"
version="1.0" >
<xsl:output method="text" encoding="utf-8"/>
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()" />
<xsl:template match="/">
<xsl:apply-templates select="a:listDatasetsResponse/a:datasets/a:dataset" />
</xsl:template>
<xsl:template match="a:dataset">
<xsl:value-of select="a:id"/>
<xsl:text> </xsl:text>
<xsl:apply-templates select="a:active|a:stopped|a:evaluated" />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="a:active[.='true'] | a:stopped[.='true'] | a:evaluated[.='true']">
<xsl:if test="preceding-sibling::a:active[.='true']|
preceding-sibling::a:stopped[.='true']|
preceding-sibling::a:evaluated[.='true']">,</xsl:if>
<xsl:value-of select="translate( local-name(), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
</xsl:template>
</xsl:stylesheet>

Select a parallel node in xslt

Consider following xml:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<base>
<a>
<b>
<c>Text 1</c>
</b>
</a>
</base>
<base>
<a>
<b>
<c>Text 2</c>
</b>
</a>
</base>
</root>
and this xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="root/base[1]" />
</xsl:template>
<xsl:template match="base//*">
<xsl:if test="text()">
<xsl:value-of select="text()" />
<!-- i need Text 2 here -->
</xsl:if>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
The original xml is much more nested an i don't know the exact structure. But there is a parallel node with the same structure. If my template is at //root/base[1]/a/b/c I want to reference //root/base[2]/a/b/c
However I only know that I am in some node below //root/base[1] and that there is the same node in //root/base[2].
Is there a possibility to accomplish this goal?
This stylesheet requires no extensions, and does not make any assumption on unique names; the boring part is to pass the correct "twin node" every time you call xsl:apply-templates:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="root/base[1]">
<xsl:with-param name="twin" select="root/base[2]"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*">
<xsl:param name="twin"/>
<xsl:if test="text()">
<xsl:value-of select="text()" />
<xsl:value-of select="$twin/text()"/>
</xsl:if>
<xsl:for-each select="*">
<xsl:variable name="pos" select="position()"/>
<xsl:apply-templates select=".">
<xsl:with-param name="twin" select="$twin/*[$pos]"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Solution with using saxon:evaluate extension (http://saxon.sourceforge.net/saxon7.9/extensions.html#evaluate)
You can also use dyn:evaluate if you are using xslt 1.0 processors (http://exslt.org/dyn/index.html):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
xmlns:saxon="http://saxon.sf.net/"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="root/base[1]" />
</xsl:template>
<xsl:template match="base//*">
<xsl:if test="text()">
<xsl:variable name="path">
<xsl:call-template name="constructPath">
<xsl:with-param name="node" select="."/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="rewritedPath" select="concat('/root/base[2]', substring($path, 11))"/>
<xsl:value-of select="text()" />
<xsl:value-of select="saxon:evaluate($rewritedPath)" />
</xsl:if>
<xsl:apply-templates />
</xsl:template>
<xsl:template name="constructPath">
<xsl:param name="node"/>
<xsl:choose>
<xsl:when test="$node/parent::node()/name()">
<xsl:call-template name="constructPath">
<xsl:with-param name="node" select="$node/parent::node()"/>
</xsl:call-template>
<xsl:value-of select="concat('/', $node/name())"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('/', $node/name())"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Here we use recursive template "constructPath" to create string and process it in evaluate() function. This will select the exact node as in the parallel branch. Hope this will help.
Try the code below. This code will work only if each base element has children with unique element name.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="root/base[1]" />
</xsl:template>
<xsl:template match="base//*">
<xsl:if test="text()">
<xsl:variable name="name" select="name()"/>
<xsl:value-of select="text()" />
<xsl:value-of select="ancestor::base/following-sibling::base[1]//*[name() = $name]" />
</xsl:if>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="text()" />
However you can use dyn:evaluate from exslt extension or saxon:eval

Processing a list in XSLT

I have list of elements in variable
|ELEMENT1|ELEMENT2|ELEMENT3|ELEMENT4|ELEMENT5|
If any of request elements matches this , I should display local name and its value.
Request XML :
<Root>
<element1>Test1</element1>
<child>
<element2>222</element2>
</child>
<secondChild>
<element2>234</element2>
</secondChild>
<thirdchild>
<element3>5w2</element3>
</thirdchild>
</Root>
XSL:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text"/>
<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"></xsl:variable>
<xsl:variable name="list"><xsl:value-of select="'|ELEMENT1|ELEMENT2|ELEMENT3|ELEMENT4|ELEMENT5|'"/></xsl:variable>
<xsl:template match="/">
<xsl:for-each select="//*[contains(translate($list,$lower,$upper),concat('|',translate(local-name(),$lower,$upper),'|'))]">
<xsl:value-of select="concat(local-name(),':',.,'|')"></xsl:value-of>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Expected Output :
element1:Test1|element2:222|element3:5w2|
But I am getting
element1:Test1|element2:222|element2:234|element3:5w2|
This is because I have element2 in two places in XML. I should not read second element2 while processing.
Can you please help on this
Filter out any elements that have a preceding element of the same name.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:variable name="lower" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="upper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
<xsl:variable name="list" select="'|ELEMENT1|ELEMENT2|ELEMENT3|ELEMENT4|ELEMENT5|'" />
<xsl:template match="/">
<xsl:for-each select="//*[
contains(
concat('|', translate($list, $lower, $upper), '|'),
concat('|', translate(local-name(), $lower, $upper), '|')
)
]">
<xsl:if test="not(preceding::*[local-name() = local-name(current())])">
<xsl:value-of select="concat(local-name(), ':', ., '|')" />
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
You can define a key
<xsl:key name="name" match="*" use="local-name()"/>
and then in your condition you check
<xsl:for-each select="//*[generate-id() = generate-id(key('name', local-name())[1])][contains(translate($list,$lower,$upper),concat('|',translate(local-name(),$lower,$upper),'|'))]">

How to assign formated value to other variable in xslt

<xsl:value-of select="substring-before($temp1,';')" disable-output-escaping="yes"/>
where temp1="fassdf sdf; asdf &dfsdfsdf;fsdfsf;"
The above code I am using to split value using ";". The problem is temp1 having &, so it splits this value by the escaped sequence character ;. So i am getting wrong output. But if I use the disable-output-escaping="yes" then the "&" is converted to &.
How to get the formatted value from the string? So if i split the string i will not get any issue. Because I will get string with & instead of &
Lets assume a sample XML for your/our convenience ..
<?xml version="1.0" encoding="utf-8"?>
<root>
<child>sharepoint; R&D;Department;</child>
</root>
XSLT code to output the desired one:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="text" indent="no"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="node()"/>
</xsl:template>
<xsl:template match="child">
<xsl:call-template name="SplitString">
<xsl:with-param name="StringVal" select="concat(.,';')"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="SplitString">
<xsl:param name="StringVal"/>
<xsl:variable name="first" select="substring-before($StringVal, ';')" />
<xsl:variable name="remaining" select="substring-after($StringVal, ';')" />
<xsl:value-of select="normalize-space($first)" disable-output-escaping="yes" />
<xsl:if test="$remaining">
<xsl:value-of select="'
'"/>
<xsl:call-template name="SplitString">
<xsl:with-param name="StringVal" select="$remaining" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
This is the Output you get:
sharepoint
R&D
Department