XSLT: generate-id() and 2 equal arguments - xslt

Using XSLT 2.0:
# Linenumber 8370 this code:
<TestCaseElement>
<Name><![CDATA[DUT_AC_ON]]></Name>
<TaggedValues>
</TaggedValues>
<Description>
<Line><![CDATA[{TEXT_LANG} DUT AC ON]]></Line>
<Line><![CDATA[{TEXT_ENGL} DUT AC ON]]></Line>
</Description>
<ModelingToolID><![CDATA[EAID_E9ACC0C9_D383_4ef0_99FF_F87C90BDF43C]]></ModelingToolID>
<Hash><![CDATA[1238228468]]></Hash>
<ID><![CDATA[1115]]></ID>
<Stereotypes>
<Stereotype><![CDATA[StepStart]]></Stereotype>
</Stereotypes>
<Role><![CDATA[TESTSTEP]]></Role>
</TestCaseElement>
and later in the XML-Document the same ModelingToolID
Here is an external Link to the picture to visualize: http://i.imgur.com/vTmki.png
I generate ID's with this XSL-Code:
<xsl:for-each select="/TestCases/TestCase/TestCaseElement/ModelingToolID[
( not( ../Stereotypes ) or ( ../Stereotypes/Stereotype != 'Precondition' and
../Stereotypes/Stereotype != 'Postcondition' ) ) and
(../Stereotypes/Stereotype = 'StepStart') and
( ../Role = 'TESTSTEP' or ../Role = 'VP' ) and
../Description and
( generate-id() = generate-id( key( 'ModelingToolID', .)[ 1 ] ) ) ]">
You see in Linenumber 8370 and 10296 two identic ModelingToolID's.
I need both TestCaseElements in my Transformation and in my desired output.
But, understandably, only the first will be taken.
What can i do to get both TestCaseElement's?

You see in Linenumber 8370 and 10296 two identic ModelingToolID's.
I need both TestCaseElements in my Transformation and in my desired
output. But, understandably, only the first will be taken. What can
i do to get both TestCaseElement's?
The function key() (without a predicate appended to it) by definition produces a node-set of nodes, each having the same key as the second argument.
Therefore, inside the xsl:for-each instruction you need:
key( 'ModelingToolID', .)
This selects all nodes that match the match pattern in the match attribute of the xsl:key named "ModelingToolID" -- exactly what you want to obtain.
You can use this expression in various XSLT instructions:
<xsl:variable name="vGroup" select="key( 'ModelingToolID', .)"/>
Or:
<xsl:for-each select="key( 'ModelingToolID', .)">
<!-- Process the group here -->
</xsl:for-each>
Or whatever you need to do.

Related

how to check an existing node and its contains in an xml file using xslt and differenciate the node by an id number

Is there any possibility to check if it exists a duplicate node in an xml file, then check the contains of this node and if possible differenciate the both node by an Id number using xslt?
I have for example this xml file
<Racine>
<el1>
<fils1>context1</fils1>
<fils2>test1</fils2>
<fils1>context1</fils1>
</el1>
<el2>
<fils1>context2</fils1>
<fils2>test2</fils2>
<fils1>context2</fils1>
</el2>
<el3>...........<el3>
</Racine>
In this case for example, the node "fils1" appears twice in the node "el1" and the contains of each of them is the same. I need in this case to transform my xml file so that this node will become
<fils1 id=1>context1</fils1>.
I add automatically an Id-number to differenciate the both node with the same name and same contain. And when the node got the same name with different contains( also a space in the contain of the node has to be considered as a difference), then lets the node as they are.
How can i make this possible using XSLT? Could someone here help me to transform that?
Thanks a lot for your help.
Franky
Thanks for yours remarks. Here is the expected output:
<Racine>
<el1>
<fils1 id=1>context1</fils1>
<fils2>test1</fils2>
<fils1 id=2>context1</fils1>
</el1>
<el2>
<fils1 id=1>context2</fils1>
<fils2>test2</fils2>
<fils1 id=2>context2</fils1>
</el2>
<el3>...........
<fils1 id=3>context1</fils1>
<el3>
</Racine>
Conerning the node "fils2" in "el1" and "el2", the contain is different, then i want to keep them unchange and when not, i want to add an id number to make the difference.
This XSLT 1.0 style-sheet...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="kCommon" match="*[starts-with(name(),'fils')]"
use="concat(name(),'|',.)" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(name(),'fils')]
[count(key('kCommon',concat(name(),'|',.))) >= 2]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:variable name="this-key" select="concat(name(),'|',.)" />
<xsl:attribute name="id"><xsl:value-of select="
count(preceding::*
[starts-with(name(),'fils')]
[count(.|key('kCommon',$this-key)) =
count( key('kCommon',$this-key)) ]
) + 1" /></xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
...will transform this input...
<Racine>
<el1>
<fils1>context1</fils1>
<fils2>test1</fils2>
<fils1>context1</fils1>
</el1>
<el2>
<fils1>context2</fils1>
<fils2>test2</fils2>
<fils1>context2</fils1>
</el2>
<el3>
<fils1>context1</fils1>
</el3>
</Racine>
...into...
<Racine>
<el1>
<fils1 id="1">context1</fils1>
<fils2>test1</fils2>
<fils1 id="2">context1</fils1>
</el1>
<el2>
<fils1 id="1">context2</fils1>
<fils2>test2</fils2>
<fils1 id="2">context2</fils1>
</el2>
<el3>
<fils1 id="3">context1</fils1>
</el3>
</Racine>
Explanation
We build a key ('kCommon') of the fils elements that have both common name and common text content. We look for elements which have membership in thier key group of at least two - in other words filX type elements that have a name and text in common with at least one other filX element. This is our last template.
For each such common element, we build two sets:
The set of all preceding fil elements in the common group. This set is given by...
Set 1
preceding::*[starts-with(name(),'fils')]
And this node's common name+content group, which is...
Set 2
key('kCommon',concat(name(),'|',.))
But because we will reference concat(name(),'|',.) quiet a few times, and in places where we don't have direct access to this context node, we just compute the concat() and substitute into Set 2 like this. Set 2 is now quick to compute.
Set 2
key('kCommon',$this-key)
And then we take a set intersection using the Kaysian method.
$set1[count(.|$set2)=count($set2)]
This intersection is the set of all like-minded element to this one that preceded it. We just count them up and add one, and this gives us an ordinal for the id property.

XSLT1.0: Multiple values in choose-when in the right part of equation?

Is there any way to put more then one value in the right part of equation?
<xsl:choose>
<xsl:when test="Properties/LabeledProperty[Label='Category Code']/Value = 600,605,610">
This code above returns:
XPath error : Invalid expression
Properties/LabeledProperty[Label='Category Code']/Value = 600,605,610
^
compilation error: file adsml2adpay.xsl line 107 element when
The reason I don't want to use 'OR' is because there are around 20 numbers should be in the right part for each 'when' test.
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:config="http://tempuri.org/config"
exclude-result-prefixes="config"
>
<config:categories>
<value>600</value>
<value>605</value>
<value>610</value>
</config:categories>
<xsl:variable
name = "vCategories"
select = "document('')/*/config:categories/value"
/>
<xsl:key
name = "kPropertyByLabel"
match = "Properties/LabeledProperty/Value"
use = "../Label"
/>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="key('kPropertyByLabel', 'Category Code') = $vCategories">
<!-- ... -->
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This works because the XPath = operator compares every node from the left hand side to every node on the right hand side when working on node sets (comparable to an SQL INNER JOIN).
Hence all you need to do is create a node set from your individual values. Using a temporary namespace you can do that right in your XSLT file.
Also note that I've introduced an <xsl:key> to make selecting property values by their label more efficient.
EDIT: You could also create an external config.xml file and do this:
<xsl:variable name="vConfig" select="document('config.xml')" />
<!-- ... -->
<xsl:when test="key('kPropertyByLabel', 'Category Code') = $vConfig/categories/value">
With XSLT 2.0 the concept of sequences has been added. It's simpler to do the same thing there:
<xsl:when test="key('kPropertyByLabel', 'Category Code') = tokenize('600,605,610', ',')">
This means you could easily pass in the string '600,605,610' as an external parameter.

convert <xsl:variable> string value , to the value which I can select with <xsl:value-of select>

I have a variable from which I need to dynamically generate nodes
<xsl:template match="banner_discount_1 | banner_discount_2 | banner_discount_3">
<xsl:variable name="link">banner_discount_<xsl:value-of select="substring-after(name(.) ,'banner_discount_')" />_link</xsl:variable>
<xsl:value-of select="$link" />
</xsl:template>
<xsl:value-of> selects the string, but I want to be able to select the node which name matches the name of a variable.
In my case the node looks something like this:
<banner_discount_1_link />
<banner_discount_2_link />
...
Here is the xml I'm using
<banner_discount_1> 12 </banner_discount_1>
<banner_discount_2> 21 </banner_discount_2>
<banner_discount_3> 32 </banner_discount_3>
<banner_discount_1_link> link1 </banner_discount_1_link>
<banner_discount_2_link> link2 </banner_discount_2_link>
<banner_discount_3_link> link3 </banner_discount_3_link>
#MartinHonnen is on the right track, but you need to set the selection context as well.
Since you're in a template that's selecting the banner_discount_ nodes, that is your context. From your XML sample, it looks like the nodes you want to select are siblings, so this should work:
<xsl:value-of select="../*[local-name() = $link]"/>
It is preferable to target the nodes directly, but if they could be anywhere in the document, then you may resort to
<xsl:value-of select="//*[local-name() = $link]"/>
This is a last resort because it is potentially O(n) with respect to the number of nodes in the document.
Use <xsl:value-of select="*[local-name() = $link]"/>. If that does not help then consider to show a sample of the XML.

Walk/loop through an XSL key: how?

Is there a way to walk-through a key and output all the values it contains?
<xsl:key name="kElement" match="Element/Element[#idref]" use="#idref" />
I though of it this way:
<xsl:for-each select="key('kElement', '.')">
<li><xsl:value-of select="." /></li>
</xsl:for-each>
However, this does not work. I simply want to list all the values in a key for testing purposes.
The question is simply: how can this be done?
You can't. That's not what keys are for.
You can loop through every element in a key using a single call to key() if and only if the key of each element is the same.
If you need to loop over everything the key is defined over, you can use the expression in the match="..." attribute of your <key> element.
So if you had a file like this:
<root>
<element name="Bill"/>
<element name="Francis"/>
<element name="Louis"/>
<element name="Zoey"/>
</root>
And a key defined like this:
<xsl:key name="survivors" match="element" use="#name"/>
You can loop through what the key uses by using the contents of its match attribute:
<xsl:for-each select="element">
<!-- stuff -->
</xsl:for-each>
Alternatively, if each element had something in common:
<root>
<element name="Bill" class="survivor"/>
<element name="Francis" class="survivor"/>
<element name="Louis" class="survivor"/>
<element name="Zoey" class="survivor"/>
</root>
Then you could define your key like this:
<xsl:key name="survivors" match="element" use="#class"/>
And iterate over all elements like this:
<xsl:for-each select="key('survivors', 'survivor')">
<!-- stuff -->
</xsl:for-each>
Because each element shares the value "survivor" for the class attribute.
In your case, your key is
<xsl:key name="kElement" match="Element/Element[#idref]" use="#idref" />
So you can loop through everything it has like this:
<xsl:for-each select="Element/Element[#idref]">
<!-- stuff -->
</xsl:for-each>
You CAN create a key to use for looping - if you simply specify a constant in the use attribute of the key element:
<xsl:key name="survivors" match="element" use="'all'"/>
Then you can loop over all elements in the following way:
<xsl:for-each select="key('survivors','all')">
...
</xsl:for-each>
Or count them:
<xsl:value-of select="count(key('survivors','all'))"/>
Note that the constant can be any string or even a number - but 'all' reads well.
However, you cannot use this key to lookup information about the individual entries (because they all have the same key).
In other words there are two types of possible keys:
"lookup keys" = standard keys with varying indexes in the use attribute
"looping keys" = keys with a constant in the use attribute
I do not know how efficient this method is to execute, it does however make the maintenance of the XSL more efficient by avoiding repetition of the same (potentially very complex) XPath expression throughout the XSL code.
Rather than think of the XSL keys in programming language terms, think of them as record sets of SQL. That will give a better understanding. For a given key index created as
<xsl:key name="paths" match="path" use="keygenerator()">
it can be "iterated"/"walk-through" as below
<xsl:for-each select="//path[generate-id()=generate-id(key('paths',keygenerator())[1])]">
To understand this magic number [1], let s go through the below example :
Consider this XML snippet
<root>
<Person>
<name>Johny</name>
<date>Jan10</date>
<cost itemID="1">34</cost>
<cost itemID="1">35</cost>
<cost itemID="2">12</cost>
<cost itemID="3">09</cost>
</Person>
<Person>
<name>Johny</name>
<date>Jan09</date>
<cost itemID="1">21</cost>
<cost itemID="1">41</cost>
<cost itemID="2">11</cost>
<cost itemID="2">14</cost>
</Person>
</root>
transformed using this XSL.
<xsl:for-each select="*/Person">
<personrecords>
<xsl:value-of select="generate-id(.)" />--
<xsl:value-of select="name"/>--
<xsl:value-of select="date"/>--
</personrecords>
</xsl:for-each>
<xsl:for-each select="*/*/cost">
<costrecords>
<xsl:value-of select="generate-id(.)" />--
<xsl:value-of select="../name"/>--
<xsl:value-of select="../date"/>--
<xsl:value-of select="#itemID"/>--
<xsl:value-of select="text()"/>
</costrecords>
</xsl:for-each>
The above XSL transformation lists the unique id of the Person nodes and the cost nodes in the form of idpxxxxxxx as the result below shows.
1. <personrecords>idp2661952--Johny--Jan10-- </personrecords>
2. <personrecords>idp4012736--Johny--Jan09-- </personrecords>
3. <costrecords>idp2805696--Johny-- Jan10-- 1-- 34</costrecords>
4. <costrecords>idp4013568--Johny-- Jan10-- 1-- 35</costrecords>
5. <costrecords>idp2808192--Johny-- Jan10-- 2-- 12</costrecords>
6. <costrecords>idp2808640--Johny-- Jan10-- 3-- 09</costrecords>
7. <costrecords>idp2609728--Johny-- Jan09-- 1-- 21</costrecords>
8. <costrecords>idp4011648--Johny-- Jan09-- 1-- 41</costrecords>
9. <costrecords>idp2612224--Johny-- Jan09-- 2-- 11</costrecords>
10.<costrecords>idp2610432--Johny-- Jan09-- 2-- 14</costrecords>
Let us create a key on the cost records using a combination of name and itemID values.
<xsl:key name="keyByNameItem" match="cost" use="concat(../name, '+', #itemID)"/>
Manually looking at the XML, the number of unique keys for the above would be three : Johny+1, Johny+2 and Johny+3.
Now lets test out this key by using the snippet below.
<xsl:for-each select="*/*/cost">
<costkeygroup>
<xsl:value-of select="generate-id(.)" />--
(1)<xsl:value-of select="generate-id(key('keyByNameItem',concat(../name, '+', #itemID) )[1] ) " />--
(2)<xsl:value-of select="generate-id(key('keyByNameItem',concat(../name, '+', #itemID) )[2] ) " />--
(3)<xsl:value-of select="generate-id(key('keyByNameItem',concat(../name, '+', #itemID) )[3] ) " />--
(4)<xsl:value-of select="generate-id(key('keyByNameItem',concat(../name, '+', #itemID) )[4] ) " />
</costkeygroup>
</xsl:for-each>
And here is the result:
1. <costkeygroup>idp2805696-- (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648</costkeygroup>
2. <costkeygroup>idp4013568-- (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648</costkeygroup>
3. <costkeygroup>idp2808192-- (1)idp2808192-- (2)idp2612224-- (3)idp2610432-- (4)</costkeygroup>
4. <costkeygroup>idp2808640-- (1)idp2808640-- (2)-- (3)-- (4)</costkeygroup>
5. <costkeygroup>idp2609728-- (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648</costkeygroup>
6. <costkeygroup>idp4011648-- (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648</costkeygroup>
7. <costkeygroup>idp2612224-- (1)idp2808192-- (2)idp2612224-- (3)idp2610432-- (4)</costkeygroup>
8. <costkeygroup>idp2610432-- (1)idp2808192-- (2)idp2612224-- (3)idp2610432-- (4)</costkeygroup>
Our interest is in trying to understand the importance of [1],[2], [3],[4]. In our case, the keygenerator is concat(../name, '+', #itemID).
For a given key, [1] refers to the first occurence of a node that satisfies the keygenerator. Similarly [2] refers to the second occurence of a node that satisfies the keygenerator. Thus [2], [3],[4], etc. are all nodes that satisfy the same key, and thus can be considered duplicates for the given key. The number of duplicates depends on the input XML. Thus:
Key Johny+1 satisfies 4 nodes (1)idp2805696-- (2)idp4013568-- (3)idp2609728-- (4)idp4011648
Key Johny+2 satisfies 3 nodes (1)idp2808192-- (2)idp2612224-- (3)idp2610432-- (4)
Key Johny+3 satisfies 1 node (1)idp2808640-- (2)-- (3)-- (4)
Thus we see that ALL 8 cost nodes of the XML can be accessed through the key.
Here is a image that combines the transformation results to help better understand.
The red squares indicate the matching nodes for Johny+1. The green squares indicate the matching nodes for Johny+3. Match the idpxxxxxxx values in <costkeygroup> to the values in <costrecords>. The <costrecords> help map the idpxxxxxxx values to the source XML.
The takeaway is that,
an XSL key does not filter or eliminate nodes. All nodes including duplicates can be accessed through the key. Thus when we say "walk through" of the key, there is no concept of a resultant subset of nodes from the original set of nodes made available to the key for processing.
To "walk through" only unique nodes of the key in the above example, use
<xsl:for-each select="*/*/workTime[generate-id()=generate-id(key('keyByNameItem', concat(../name, '+', #itemID) )[1] ) ] ">
[1] signifies that the first record for a given key value is denoted as the unique record. [1] is almost always used because there will exist at least one node that satisfies a given key value. If we are sure that there will be a minimum of 2 records to satisfy each key value in the key, we can go ahead and use [2] to identify the second record in the record set as the unique record.
P.S The words nodes / records / elements are used interchangeably.
There is no way to walk-through the keys, although we can output all the values it contains. In XSLT2 it is quite easier than in XSLT1 (e.g., using fn:generate-id according to the previous answer).
Using fn:distinct-values
<xsl:variable name="e" select="."/>
<xsl:for-each select="distinct-values(Element/Element[#idref]/#idref)">
<li key="{.}"><xsl:value-of select="key('kElement', ., $e )" /></li>
</xsl:for-each>
Using xsl:for-each-group
<xsl:for-each-group select="Element/Element[#idref]" group-by="#idref">
<li key="{current-grouping-key()}"><xsl:value-of select="current-group()" /></li>
</xsl:for-each-group>

Biztalk explode in XSLT transformations

I'm receiving XML into BizTalk. One part is element and the value is ids separated by comma
<Stores>15,34</Stores>
I need to transform this into
<Stores>
<Store>Store 1</Store>
<Store>Store 4</Store>
</Stores>
What I need to do is to explode the value by comma, take each value and get value from database (15 -> Store 1, 34 -> Store 2).
How can I make the explode in xslt, how ca I get value from database for each exploded value. I already have procedure in db for that, just need to know how to call it.
Here is an XSLT 1.0 compatible solution that does the explode:
<!-- straightforward -->
<xsl:template match="Stores">
<xsl:copy>
<xsl:call-template name="explode">
<xsl:with-param name="str" select="text()" />
</xsl:call-template>
</xsl:copy>
</xsl:template>
<!-- string processing through recursion -->
<xsl:template name="explode">
<xsl:param name="str" select="''" />
<xsl:variable name="temp" select="concat($str, ',')" />
<xsl:variable name="head" select="substring-before($temp, ',')" />
<xsl:variable name="tail" select="substring-after($temp, ',')" />
<xsl:if test="$head != ''">
<Store>
<xsl:value-of select="$head" />
</Store>
<xsl:call-template name="explode">
<xsl:with-param name="str" select="$tail" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Output for <Stores>15,34</Stores> would be:
<Stores>
<Store>Store 15</Store>
<Store>Store 34</Store>
</Stores>
David Hall's solution seems to contain a pointer how to use an XSLT extension function to make calls to that database from XSLT.
The BizTalk Mapper does not support XSLT 2.0 (see MSDN Documentation http://msdn.microsoft.com/en-us/library/aa559261(BTS.10).aspx) so you will need to use the EXSLT extensions if you want to use the mapper.
There is a great post here by Richard Hallgren that covers how to use EXSLT within the BizTalk Mapper.
One additional thought is about an alternative solution. It is not clear if you absolutely must make your database calls one by one - would making a single call work?
It is possible to provide a stored procedure a delimited string as a parameter and to then use a function to break this string up. I've included an example of such a function below, the example being a table function. You'll be able find lots of other implementations on the web.
With the table function you can join against this in you store lookup procedure.
If this meets your needs it should be a lot faster since you now perform just a single database hit and can perform set operations to get back your list of stores.
CREATE function fn_ParseCSVString
(
#INPUTCSV varchar(MAX)
)
RETURNS #TBL TABLE
(
COL1 INT
)
AS
BEGIN
DECLARE #NUM_STR NVARCHAR(MAX)
SET #NUM_STR = #INPUTCSV
SET #NUM_STR = REPLACE(#NUM_STR,' ','')
-- this will trim any intermediate spaces
WHILE LEN(#NUM_STR) >= 0
BEGIN
DECLARE ##SUBSTR VARCHAR(100)
IF CHARINDEX(',',#NUM_STR,0) <> 0
BEGIN
SET ##SUBSTR = SUBSTRING(#NUM_STR,0,CHARINDEX(',',#NUM_STR,0))
INSERT INTO #TBL VALUES(##SUBSTR)
END
ELSE
BEGIN
INSERT INTO #TBL VALUES(#NUM_STR)
BREAK
END
SET ##SUBSTR = ##SUBSTR + ','
SET #NUM_STR = SUBSTRING(#NUM_STR, CHARINDEX(',',#NUM_STR,0) + 1, LEN(#NUM_STR))
END
RETURN
END
I assume you know how to write the overall transform but need help with the tokenization of the string containing the store numbers.
If you're using XSLT 2.0, look at the definition of the tokenize() function. This will split the string value at a specified delimiter, allowing you to perform this transformation. In XSLT 1 you could look at EXSLT regex extension functions.