XSL: How to concatenate nodes with conditions? - xslt

I have the following code (eg):
<response>
<parameter>
<cottage>
<cot>
<res>
<hab desc="Lakeside">
<reg cod="OB" prr="600.84>
<lwz>TR#2#AB#200.26#0#QB#OK#20120829#20120830#EU#3-0#</lwz>
<lwz>TR#2#AB#200.26#0#QB#OK#20120830#20120831#EU#3-0#</lwz>
<lwz>TR#2#AB#200.26#0#QB#OK#20120831#20120901#EU#3-0#</lwz>
I need to create a concatenated string that includes the whole of the first 'lwz' line and then the price (200.26, but it can be different in each line) for each corresponding line.
So the output, separating each line with | would be:
TR#2#AB#200.26#0#QB#OK#20120829#20120830#EU#3-0#|200.26|200.26
Thanks

This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="lwz[1]">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="lwz[position() >1]">
<xsl:value-of select=
"concat('
',
substring-before(substring-after(substring-after(substring-after(.,'#'),'#'),'#'),'#')
)
"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the provided text (converted to a well-formed XML document !!!):
<response>
<parameter>
<cottage>
<cot>
<res>
<hab desc="Lakeside">
<reg cod="OB" prr="600.84">
<lwz>TR#2#AB#200.26#0#QB#OK#20120829#20120830#EU#3-0#</lwz>
<lwz>TR#2#AB#200.26#0#QB#OK#20120830#20120831#EU#3-0#</lwz>
<lwz>TR#2#AB#200.26#0#QB#OK#20120831#20120901#EU#3-0#</lwz>
</reg>
</hab>
</res>
</cot>
</cottage>
</parameter>
</response>
produces the wanted, correct result:
TR#2#AB#200.26#0#QB#OK#20120829#20120830#EU#3-0#
200.26
200.26
II XSLT 2.0 solution:
This transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="lwz[1]">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="lwz[position() >1]">
<xsl:value-of select=
"concat('
', tokenize(.,'#')[4])"/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the above XML document, again produces the wanted, correct result. Note the use of the standard XPath 2.0 function tokenize():
TR#2#AB#200.26#0#QB#OK#20120829#20120830#EU#3-0#
200.26
200.26

You can use the XPath substring function to select substrings from your lwz node data. You don't really give much more detail about your problem, if you want a more detailed answer, perhaps provide the full XML document and your best-guess XSLT

Related

Replace values using Key defined with inline variable

I'm trying to understand the usage of keys and can't get my example to work.
Starting with this XML:
<items>
<item>Blue</item>
<item>Green</item>
<item>Orange</item>
</items>
I want to get this output XML:
<items>
<item>PURPLE</item>
<item>BLACK</item>
<item>PINK</item>
</items>
I defined the mapping directly in a variable in the XSLT transformation:
<?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"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="mappings">
<mapping orig="Blue" repla="PURPLE"/>
<mapping orig="Green" repla="BLACK"/>
<mapping orig="Orange" repla="PINK"/>
</xsl:variable>
<xsl:key name="mappingsKey" match="$mappings/mapping" use="#orig"/>
<xsl:template match="items">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:copy>
<xsl:value-of select="key('mappingsKey',.)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I know I'm missing the instruction to tell which is the replacement value, but don't know how to define it.
Example available here: https://xsltfiddle.liberty-development.net/ejivdHp/1
Thanks.
The $mappings variable is in a different document than the processed XML. You need to point the key() function to there. And you also need to select the repla attribute:
<xsl:value-of select="key('mappingsKey', ., $mappings)/#repla"/>
Referencing the variable in the match pattern of the xsl:key element is meaningless.
https://xsltfiddle.liberty-development.net/ejivdHp/2

How to create template to match based upon an XSLT parameter

I'm trying to create a standard-use XSLT that will perform a given task based upon a user-provided XPATH expression as an XSLT parameter.
That is, I need something like this:
<xsl:template match="$paramContainingXPATH">
<!-- perform the task on the node(s) in the given xpath -->
</xsl:template>
For example, suppose I have some XML:
<xml>
<nodeA>whatever</nodeA>
<nodeB>whatever</nodeB>
<nodeC>whatever</nodeC>
<nodeD>whatever</nodeD>
<nodeE>whatever</nodeE>
</xml>
The XSLT needs to transform just a node or nodes matching a provided XPATH expression. So, if the xslt parameter is "/xml/nodeC", it processes nodeC. If the xslt parameter is "*[local-name() = 'nodeC' or local-name() = 'nodeE']", it processes nodeC and nodeE.
This should work for absolutely any XML message. That is, the XSLT cannot have any direct knowledge of the content of the XML. So, it could be a raw XML, or a SOAP Envelope.
I was guessing I might need to grab all the nodes matching the xpath, and then looping over them calling a named template, and using the standard identity template for all other nodes.
All advice is appreciated.
If you really need that feature with XSLT 1.0 or 2.0 then I think you should consider writing one stylesheet that takes that string parameter with the XPath expression and then simply generates the code of a second stylesheet where the XPath expression is used as a match pattern and the other needed templates like the identity template are included statically. Dynamic XPath evaluation is only available in XSLT 3.0 or in earlier versions as a proprietary extension mechanism.
You cannot match a template using a parameter - but you can traverse the tree and compare the path of each node with the given path. Here's a simple example:
XSLT 1.0
<?xml version="1.0" encoding="UTF-8"?>
<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:param name="path" select="'/world/America/USA/California'"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="*"/>
</root>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="path-to-me">
<xsl:for-each select="ancestor-or-self::node()">
<xsl:value-of select="name()" />
<xsl:if test="position()!=last()">
<xsl:text>/</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:if test="$path=$path-to-me">
<xsl:call-template name="action"/>
</xsl:if>
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template name="action">
<return>
<xsl:value-of select="." />
</return>
</xsl:template>
</xsl:stylesheet>
Applied to a slightly more ambitious test input of:
<world>
<Europe>
<Germany>1</Germany>
<France>2</France>
<Italy>3</Italy>
</Europe>
<America>
<USA>
<NewYork>4</NewYork>
<California>5</California>
</USA>
<Canada>6</Canada>
</America>
</world>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<return>5</return>
</root>
This could be made more efficient by passing the accumulated path as a parameter of the recursive template, so that each node needs only to add its own name to the chain.
Note:
The given path must be absolute;
Predicates (including positional predicates) and attributes are not implemented in this. They probably could be, with a bit more effort;
Namespaces are ignored (I don't see how you could pass an XPath as a parameter and include namespaces anyway).
If your processor supports an evaluate() extension function, you could forgo the calculated text path and test for intersection instead.
Edit:
Here's an example using EXSLT dyn:evaluate() and set:intersection():
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dyn="http://exslt.org/dynamic"
xmlns:set="http://exslt.org/sets"
extension-element-prefixes="dyn set">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="path" select="'/world/America/USA/California'"/>
<xsl:variable name="path-set" select="dyn:evaluate($path)" />
<xsl:template match="/">
<root>
<xsl:apply-templates select="*"/>
</root>
</xsl:template>
<xsl:template match="*">
<xsl:if test="set:intersection(. , $path-set)">
<xsl:call-template name="action"/>
</xsl:if>
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template name="action">
<return>
<xsl:value-of select="." />
</return>
</xsl:template>
</xsl:stylesheet>
Note that this will also work with with paths like:
/world/America/USA/*[2]
//California
and many others that the text comparison method could not accommodate.
I'm sending the element name as a param to the XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:output method="xml"/>
<xsl:param name="user"/>
<xsl:template match="/">
<xsl:call-template name="generic" />
</xsl:template>
<xsl:template name="generic">
<count><xsl:value-of select="count(.//*[local-name()=$user])"/></count>
</xsl:template>
</xsl:stylesheet>
I hope this could help!

Text value of input xml element as final xslt output

I have a scenario where the input(source) xml is having an element which contains a valid well formed xml as string. I am trying to write an xslt that would give me the text value of that desired element which contains the payload xml. In essence, output should only be text of the element that contains it. Here is what I am trying, am I missing something obvious here. I am using xslt 1.0
Thanks.
Input xml:
<BatchOrders xmlns="http://Microsoft.ABCD.OracleDB/STMT">
<BatchOrdersRECORD>
<BatchOrdersRECORD>
<ActualPayload>
<PersonName>
<PersonGivenName>CaptainJack</PersonGivenName>
<PersonMiddleName>Walter</PersonMiddleName>
<PersonSurName>Sparrow</PersonSurName>
<PersonNameSuffixText>Sr.</PersonNameSuffixText>
</PersonName>
</ActualPayload>
</BatchOrdersRECORD>
</BatchOrdersRECORD>
</BatchOrders>
Xslt:
<?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="text()|#*" name="sourcecopy" mode="xml-to-string">
<xsl:value-of select="*"/>
</xsl:template>
<xsl:template name="xml-to-string-called-template">
<xsl:param name ="param1">
<xsl:element name ="DestPayload">
<xsl:text disable-output-escaping ="yes"><![CDATA[</xsl:text>
<xsl:call-template name ="sourcecopy"/>
<xsl:text disable-output-escaping ="yes">]]></xsl:text>
</xsl:element>
</xsl:param>
</xsl:template>
</xsl:stylesheet>
Desired Output:
<PersonName>
<PersonGivenName>CaptainJack</PersonGivenName>
<PersonMiddleName>Walter</PersonMiddleName>
<PersonSurName>Sparrow</PersonSurName>
<PersonNameSuffixText>Sr.</PersonNameSuffixText>
</PersonName>
Do you really need the mode="xml-to-string"?
Change
<xsl:template match="text()|#*" name="sourcecopy" mode="xml-to-string">
<xsl:value-of select="*"/>
</xsl:template>
to
<xsl:template match="text()|#*" name="sourcecopy">
<xsl:value-of select="." disable-output-escaping ="yes"/>
</xsl:template>
Would this template suffice?

change of xml structure using xslt

I want to convert the following source xml structure to the target xml structure using xslt transformation. I am mot able to convert the following source xml to target xml using xslt. Please help us in coneverting this.
Source XML
<XxhrPiEmpcompOutIntCollection>
<XxhrPiEmpcompOutInt>
<employeeNumber>
200000562
</employeeNumber>
<competencyName>
Comp1
</competencyName>
<proficiencyLevel>
Prof1
</proficiencyLevel>
<compDateTo>
16-NOV-12
</compDateTo>
</XxhrPiEmpcompOutInt>
<XxhrPiEmpcompOutInt>
<employeeNumber>
200000562
</employeeNumber>
<competencyName>
Comp2
</competencyName>
<proficiencyLevel>
Prof2
</proficiencyLevel>
<compDateTo>
16-NOV-12
</compDateTo>
</XxhrPiEmpcompOutInt>
</XxhrPiEmpcompOutIntCollection>
Target xml
<EmployeeCompetencyRequest>
<EmployeeNumber>200000562</EmployeeNumber>
<Competencies>
<Competency>
<Name>Comp1</Name>
<ProficiencyLevel>Prof1</ProficiencyLevel>
<EndDate>16-NOV-12</EndDate>
</Competency>
<Competency>
<Name>Comp2</Name>
<ProficiencyLevel>Prof2</ProficiencyLevel>
<EndDate>16-NOV-12</EndDate>
</Competency>
</Competencies>
</<EmployeeCompetencyRequest>
Some hint :
<EmployeeCompetencyRequest>
template match /XxhrPiEmpcompOutIntCollection
for-each-group XxhrPiEmpcompOutInt group by employeeNumber
<EmployeeNumber> value-of current-group-key </EmployeeNumber>
<Competencies>
for-each current-group
<Competency>
value-of name
value-of proficiencyLevel
</competency>
/for-each
</Competencies>
/for-each-group
<EmployeeCompetencyRequest>
Hope you can finish it.
You can group users with for-each-group:
<?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="2.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<EmployeeCompetencyRequest>
<xsl:for-each-group select="//XxhrPiEmpcompOutInt" group-by="employeeNumber">
<Name><xsl:value-of select="current-grouping-key()"/></Name>
<Competencies>
<xsl:for-each select="current-group()">
<Competency>
<Name><xsl:value-of select="competencyName"/></Name>
<ProfiencyLevel><xsl:value-of select="profiencyLevel"/></ProfiencyLevel>
<EndDate><xsl:value-of select="compDateTo"/></EndDate>
</Competency>
</xsl:for-each>
</Competencies>
</xsl:for-each-group>
</EmployeeCompetencyRequest>
</xsl:template>
Salut,
For XSLT 1.0 you must group widht xsl:key:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="groups" match="/XxhrPiEmpcompOutIntCollection/XxhrPiEmpcompOutInt" use="employeeNumber" />
<xsl:template match="/">
<EmployeeCompetencyRequest>
<xsl:apply-templates select="//XxhrPiEmpcompOutInt[generate-id() = generate-id(key('groups', employeeNumber)[1])]"/>
</EmployeeCompetencyRequest>
</xsl:template>
<xsl:template match="XxhrPiEmpcompOutInt">
<Name><xsl:value-of select="employeeNumber"/></Name>
<Competencies>
<xsl:for-each select="key('groups', employeeNumber)">
<Competency>
<Name><xsl:value-of select="competencyName"/></Name>
<ProfiencyLevel><xsl:value-of select="proficiencyLevel"/></ProfiencyLevel>
<EndDate><xsl:value-of select="compDateTo"/></EndDate>
</Competency>
</xsl:for-each>
</Competencies>
</xsl:template>
</xsl:stylesheet>
I can't test it with Oracle ...

can we use dynamic variable name in the select statement in xslt?

I wanted to use a dynamic variable name in the select statement in xslt.
<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="/">
<xsl:variable name="input" select="input/message" />
<xsl:variable name="Name" select="'MyName'" />
<xsl:variable name="Address" select="MyAddress" />
<xsl:variable name="output" select="concat('$','$input')" /> <!-- This is not working -->
<output>
<xsl:value-of select="$output" />
</output>
</xsl:template>
The possible values for the variable "input" is 'Name' or 'Address'.
The select statement of the output variable should have a dynamic variable name based on the value of input variable. I don't want to use xsl:choose. I wanted to select the value dynamically.
Please provide me a solution.
Thanks,
dhinu
XSLT 1.0 and XSLT 2.0 don't have dynamic evaluation.
Solution for your problem:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output method="text"/>
<my:values>
<name>MyName</name>
<address>MyAdress</address>
</my:values>
<xsl:template match="/">
<xsl:variable name="vSelector"
select="input/message"/>
<xsl:value-of select=
"document('')/*/my:values/*[name()=$vSelector]"/>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document:
<input>
<message>address</message>
</input>
produces the wanted, correct result:
MyAdress
when the same transformation is applied on this XML document:
<input>
<message>name</message>
</input>
again the wanted, correct result is produced:
MyName
Finally: If you do not wish to use the document() function, but would go for using the xxx:node-set() extension function, then this solution (looking very similar) is what you want, where you may consult your XSLTprocessor documentation for the exact namespace of the extension:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" >
<xsl:output method="text"/>
<xsl:variable name="vValues">
<name>MyName</name>
<address>MyAdress</address>
</xsl:variable>
<xsl:template match="/">
<xsl:variable name="vSelector"
select="input/message"/>
<xsl:value-of select=
"ext:node-set($vValues)/*[name()=$vSelector]"/>
</xsl:template>
</xsl:stylesheet>
Beside #Dimitre's good answer, for this particular case (output string value) you could also use:
<xsl:variable name="output"
select="concat(substring($Name, 1 div ($input = 'Name')),
substring($Address, 1 div ($input = 'Address')))"/>