Xpath expression gives multiple values - xslt

I have the requirement in which I need to match values
Buyer,BilltoParty,Shipto which can the value AGIIS-EBID or EAN or GLN
I am using XPath1.0 to extract these values and use the condition.
I am able to get correct value for the Buyer*
The problem I am facing is for Shipto and Billtoparty
Xpath for getting value for Shipto and Billto
//*[local-name()='ShipNotice']/*[local-name()='ShipNoticeBody']/*[local-name()='ShipNoticePartners']/*[local-name()='OtherPartner']/*[local-name()='PartnerInformation']/*[local-name()='PartnerIdentifier']/#*[local-name()='Agency']
Which gives the value as:
Agency AGIIS-EBID
Agency SCAC
Agency AGIIS-EBID
Agency AGIIS-EBID
INPUT XML Updated one
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:ces:names:specification:ces:schema:all:4:0">
<soapenv:Header/>
<soapenv:Body>
<ces:ShipNotice Version="4.0" xmlns:ces="urn:ces:names:specification:ces:schema:all:4:0">
<ces:Header>
<ces:From>
</ces:From>
<ces:To>
</ces:To>
</ces:Header>
<ces:ShipNoticeBody>
<ces:ShipNoticePartners>
<ces:Buyer>
<ces:PartnerInformation>
<ces:PartnerIdentifier Agency="AGIIS-EBID">8049915600000</ces:PartnerIdentifier>
</ces:PartnerInformation>
</ces:Buyer>
<ces:OtherPartner PartnerRole="ShipFrom">
<ces:PartnerInformation>
<ces:PartnerIdentifier Agency="AGIIS-EBID">0447026370000</ces:PartnerIdentifier>
</ces:PartnerInformation>
</ces:OtherPartner>
<ces:OtherPartner PartnerRole="Carrier">
<ces:PartnerInformation>
<ces:PartnerIdentifier Agency="SCAC">B935</ces:PartnerIdentifier>
</ces:PartnerInformation>
</ces:OtherPartner>
<ces:OtherPartner PartnerRole="ShipTo">
<ces:PartnerInformation>
<ces:PartnerIdentifier Agency="AGIIS-EBID">8049915600000</ces:PartnerIdentifier>
</ces:PartnerInformation>
</ces:OtherPartner>
<ces:OtherPartner PartnerRole="BillToParty">
<ces:PartnerInformation>
<ces:PartnerIdentifier Agency="AGIIS-EBID">1024122440000</ces:PartnerIdentifier>
</ces:PartnerInformation>
</ces:OtherPartner>
</ces:ShipNoticePartners>
<ces:ShipNoticeDetails>
</ces:ShipNoticeDetails>
</ces:ShipNoticeBody>
</ces:ShipNotice>
</soapenv:Body>
</soapenv:Envelope>
Below is the code I am using.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dpconfig="http://www.datapower.com/param/config" xmlns:cidx="urn:cidx:names:specification:ces:schema:all:4:0" xmlns:dp="http://www.datapower.com/extensions" extension-element-prefixes="dp">
<xsl:param name="dpconfig:AGIIS-EBID" select=" 'AGIIS-EBID' "/>
<xsl:param name="dpconfig:EAN" select="'EAN'"/>
<xsl:param name="dpconfig:GLN" select="'GLN'"/>
<xsl:template match="/">
<xsl:copy-of select="."/>
<xsl:variable name="Buyer" select="//*[local-name()='ShipNotice']/*[local-name()='ShipNoticeBody']/*[local-name()='ShipNoticePartners']/*[local-name()='Buyer']/*[local-name()='PartnerInformation']/*[local-name()='PartnerIdentifier']/#*[local-name()='Agency']"/>
<xsl:variable name="Shipto" select="/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ShipNotice']/*[local-name()='ShipNoticeBody']/*[local-name()='ShipNoticePartners']/*[local-name()='OtherPartner']/*[local-name()='PartnerInformation']/*[local-name()='PartnerIdentifier']/#*[local-name()='Agency']"/>
<xsl:variable name="Billto" select="/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='ShipNotice']/*[local-name()='ShipNoticeBody']/*[local-name()='ShipNoticePartners']/*[local-name()='OtherPartner']/*[local-name()='PartnerInformation']/*[local-name()='PartnerIdentifier']/#*[local-name()='Agency']"/>
<xsl:choose>
<xsl:when test="$Buyer='AGIIS-EBID' and $Shipto='AGIIS-EBID' and $Billto='AGIIS-EBID' or $Buyer='EAN' and $Shipto='EAN' and $Billto='EAN' or $Buyer='GLN' and $Shipto='GLN' and $Billto='GLN'">
</xsl:when>
<xsl:otherwise>
Partner didn't match the AGIIS-EBID or EAN or GLN
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Can anyone please help me on Xpath and also let me know the way I am doing the condition match is correct or not?
Expecting output
I am expecting the variables Buyer, Shipto, Billto should match the value == AGIIS-EBID, but the Xpath which I am using gives
Agency AGIIS-EBID
Agency SCAC
Agency AGIIS-EBID
Agency AGIIS-EBID

From looking at your XSLT, and seeing that your Shipto and Billto variables have the same declaration, I think what you are saying is that these currently match four nodes (or rather attributes), but you only want one.
Specifically, from looking at your XML, it looks like the crucial information is on the OtherPartner node
<ces:OtherPartner PartnerRole="ShipTo">
<ces:OtherPartner PartnerRole="BillToParty">
This means you need to add an extra condition after the current condition that matches OtherPartner. For example, for ShipTo the condition would be this:
<xsl:variable name="Shipto" select="
/*[local-name()='Envelope']
/*[local-name()='Body']
/*[local-name()='ShipNotice']
/*[local-name()='ShipNoticeBody']
/*[local-name()='ShipNoticePartners']
/*[local-name()='OtherPartner'][#PartnerRole='ShipTo']
/*[local-name()='PartnerInformation']
/*[local-name()='PartnerIdentifier']
/#*[local-name()='Agency']"/>
Of course, as pointed out in comments, if you know the namespace uri in the XML, it is preferable to declare them in the XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dpconfig="http://www.datapower.com/param/config"
xmlns:cidx="urn:cidx:names:specification:ces:schema:all:4:0"
xmlns:dp="http://www.datapower.com/extensions"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ces="urn:ces:names:specification:ces:schema:all:4:0" extension-element-prefixes="dp">
Then you can "simplify" your variable to this for example
<xsl:variable name="Shipto" select="
soapenv:Envelope
/soapenv:Body
/ces:ShipNotice
/ces:ShipNoticeBody
/ces:ShipNoticePartners
/ces:OtherPartner[#PartnerRole='ShipTo']
/ces:PartnerInformation
/ces:PartnerIdentifier/#Agency"/>
And similary for your Billto variable. Just change the condition on the #PartnerRole attribute.
Additionally, you might want to consider putting some parentheses around you final condition
<xsl:when test="
($Buyer='AGIIS-EBID' and $Shipto='AGIIS-EBID' and $Billto='AGIIS-EBID')
or ($Buyer='EAN' and $Shipto='EAN' and $Billto='EAN')
or ($Buyer='GLN' and $Shipto='GLN' and $Billto='GLN')">
Or better still do this...
<xsl:when test="($Buyer='AGIIS-EBID' or $Buyer='EAN' or $Buyer='GLN')
and $Shipto=$Buyer
and $Billto=$Buyer">

Related

XSLT check values in for each and set value of element

I am not expert in XSLT. I have requirement to check all "code" values, set statuscode as per below rules.
If all code values are 200, then set statuscode to 200 and reasonphrase to success
If all code values are either 200 or 204, then set status code to 200 and reasonphrase is concatnatiopn of all reason which have code other than 200.
If atleast one code contains value other than 200 and 204, then set statuscode to 503 and reasonphrase is concatnatiopn of all reason which have code other than 200.
I have tried couple of ways which are to store all code values in one variable and execeute contains function with above conditions as well as create code variables and store values and then check string length. But i did not get any success.
I am looking for some more generic way if possible as this requirement is part of complex xslt and below is just an example of requirement.
Once i get logic for below code, i should be able to fit logic in complex xslt.
I also tried to search in answers but could not get any solution which fits this requirement.
I am looking for solution in xslt 1.0 as other part of xslt is written in xslt 1.0
Input -
<root>
<Node1>
<code>200</code>
<reason>Success</reason>
</Node1>
<Node1>
<code>200</code>
<reason>Success</reason>
</Node1>
<Node1>
<code>204</code>
<reason>Business Error</reason>
</Node1>
<Node1>
<code>500</code>
<reason>Tech Error</reason>
</Node1>
<Node1>
<code>200</code>
<reason>Success</reason>
</Node1>
</root>
Output-
<root>
<output>
<statuscode></statuscode>
<reasonphrase></reasonphrase>
</output>
</root>
Thank you.
Try this as your starting point:
XSLT 1.0
<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:strip-space elements="*"/>
<xsl:template match="/root">
<xsl:copy>
<output>
<xsl:choose>
<xsl:when test="not(Node1[code!=200])">
<!-- all code values are 200 -->
<statuscode>200</statuscode>
<reasonphrase>success</reasonphrase>
</xsl:when>
<xsl:when test="not(Node1[code!=200 and code!=204])">
<!-- all code values are either 200 or 204 -->
<statuscode>200</statuscode>
<reasonphrase>
<xsl:for-each select="Node1[code!=200]">
<xsl:value-of select="code"/>
<xsl:if test="position()!=last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</reasonphrase>
</xsl:when>
<xsl:otherwise>
<statuscode>503</statuscode>
<reasonphrase>
<xsl:for-each select="Node1[code!=200]">
<xsl:value-of select="code"/>
<xsl:if test="position()!=last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</reasonphrase>
</xsl:otherwise>
</xsl:choose>
</output>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that this could be streamlined to eliminate repeating code - but the point here is to show how to test for the given conditions.

XSLT transformation of boolean expressions

I'm quite new to the XSLT world and need assistance with the following:
A program takes the following string:
cn = 'James Bond' and (sn='Bon*' or givenName='Jam*')
and generates the following XML which is my input XML that I need to process using a stylesheet.
Input XML:
<?xml version="1.0" encoding="UTF-8"?>
<queryString>
<parameters>
<parameter id = "1">
<name>cn</name>
<value>James Bond</value>
<comparativeOperator>=</comparativeOperator>
<parens>
<leftParen>((</leftParen>
<rightParen>)</rightParen>
</parens>
</parameter>
<parameter id = "25">
<name>sn</name>
<value>Bon*</value>
<comparativeOperator>=</comparativeOperator>
<parens>
<leftParen>((</leftParen>
<rightParen>)</rightParen>
</parens>
</parameter>
<parameter id = "50">
<name>givenName</name>
<value>Jam*</value>
<comparativeOperator>=</comparativeOperator>
<parens>
<leftParen>(</leftParen>
<rightParen>)))</rightParen>
</parens>
</parameter>
</parameters>
<logicalOperators>
<operator id = "20">
<value>and</value>
<precedingParameterId>1</precedingParameterId>
<followingParameterId>25</followingParameterId>
</operator>
<operator id = "46">
<value>or</value>
<precedingParameterId>25</precedingParameterId>
<followingParameterId>50</followingParameterId>
</operator>
</logicalOperators>
</queryString>
Desired Output:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:filter>
<ns0:and>
<ns0:or>
<ns0:equalityMatch name="cn">
<ns0:value>James Bond</ns0:value>
</ns0:equalityMatch>
</ns0:or>
<ns0:or>
<ns0:approxMatch name="givenName">
<ns0:value>Jam*</ns0:value>
</ns0:approxMatch>
<ns0:approxMatch name="sn">
<ns0:value>Bon*</ns0:value>
</ns0:approxMatch>
</ns0:or>
</ns0:and>
</ns0:filter>
My existing xslt is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:ns0="urn:oasis:names:tc:DSML:2:0:core" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="ns0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/queryString/logicalOperators/operator">
<ns0:filter>
<MyOp>
<xsl:value-of select="value"/>
</MyOp>
<xsl:for-each select="../../parameters/parameter">
<xsl:if test="comparativeOperator = '='">
<ns0:equalityMatch name="{name}">
<value>
<xsl:value-of select="value"/>
</value>
</ns0:equalityMatch>
</xsl:if>
</xsl:for-each>
</ns0:filter>
<!--/xsl:element-->
</xsl:template>
<xsl:template match="parens">
<xsl:element name="leftParensoutput">
<xsl:value-of select="leftParen"/>
</xsl:element>
<xsl:element name="rightParensoutput">
<xsl:value-of select="rightParen"/>
</xsl:element>
</xsl:template>
<xsl:template match="parameters/parameter">
<xsl:element name="FilterParameters">
<xsl:apply-templates select="parens"/>
<xsl:element name="queryfilterParameterElement">
<xsl:element name="id">
<xsl:value-of select="#id"/>
</xsl:element>
<xsl:element name="name">
<xsl:value-of select="name"/>
</xsl:element>
<xsl:element name="value">
<xsl:value-of select="value"/>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Current output:
<?xml version="1.0" encoding="UTF-8"?>
<FilterParameters>
<leftParensoutput>((</leftParensoutput>
<rightParensoutput>)</rightParensoutput>
<queryfilterParameterElement>
<id>1</id>
<name>cn</name>
<value>James Bon*</value>
</queryfilterParameterElement>
</FilterParameters>
<FilterParameters>
<leftParensoutput>((</leftParensoutput>
<rightParensoutput>)</rightParensoutput>
<queryfilterParameterElement>
<id>25</id>
<name>sn</name>
<value>Bon*</value>
</queryfilterParameterElement>
</FilterParameters>
<FilterParameters>
<leftParensoutput>(</leftParensoutput>
<rightParensoutput>)))</rightParensoutput>
<queryfilterParameterElement>
<id>50</id>
<name>givenName</name>
<value>Jam*</value>
</queryfilterParameterElement>
</FilterParameters>
<ns0:filter xmlns:ns0="urn:oasis:names:tc:DSML:2:0:core">
<MyOp>and</MyOp>
<ns0:equalityMatch name="cn">
<value>James Bon*</value>
</ns0:equalityMatch>
<ns0:equalityMatch name="sn">
<value>Bon*</value>
</ns0:equalityMatch>
<ns0:equalityMatch name="givenName">
<value>Jam*</value>
</ns0:equalityMatch>
</ns0:filter>
<ns0:filter xmlns:ns0="urn:oasis:names:tc:DSML:2:0:core">
<MyOp>or</MyOp>
<ns0:equalityMatch name="cn">
<value>James Bon*</value>
</ns0:equalityMatch>
<ns0:equalityMatch name="sn">
<value>Bon*</value>
</ns0:equalityMatch>
<ns0:equalityMatch name="givenName">
<value>Jam*</value>
</ns0:equalityMatch>
</ns0:filter>
I am dealing with multiple issues. Wasn't sure if I needed to split up the questions, but decided on presenting the whole issue.
Thanks in advance!
i) As I loop through the logicalOperators, how do I match the precedingParameterId to the under parameters.
ii) In the desired output, how do I create the node: - ie. dynamically add "ns0" AND get the "value" of parameter/operator
iii)) I'm not sure how to remove the extraneous FilterParameters element. If I remove the section , my output looks like:
<?xml version="1.0" encoding="UTF-8"?>**cnJames Bon*=(()snBon*=(()givenNameJam*=()))**<ns0:filter xmlns:ns0="urn:oasis:names:tc:DSML:2:0:core">
<MyOp>and</MyOp>
<ns0:equalityMatch name="cn">
<value>James Bon*</value>
</ns0:equalityMatch>
<ns0:equalityMatch name="sn">
<value>Bon*</value>
</ns0:equalityMatch>
<ns0:equalityMatch name="givenName">
<value>Jam*</value>
</ns0:equalityMatch>
</ns0:filter><ns0:filter xmlns:ns0="urn:oasis:names:tc:DSML:2:0:core">
<MyOp>or</MyOp>
<ns0:equalityMatch name="cn">
<value>James Bon*</value>
</ns0:equalityMatch>
<ns0:equalityMatch name="sn">
<value>Bon*</value>
</ns0:equalityMatch>
<ns0:equalityMatch name="givenName">
<value>Jam*</value>
</ns0:equalityMatch>
</ns0:filter>
Added logic:
The pretzel logic is as follows:
create a root node ;
for each queryString/logicalOperators/operator, create a node where operator is the value of logicalOperators/operator/value;
Inside this node, use logicalOperators/operator/precedingParameterId and followingParameterId to match them up with
queryString/parameters/parameter/parens/leftParen and rightParen;
if the pattern is the same, then get queryString/parameters/parameter/name and value and close with ns0:operator tag
This should get
<ns0:equalityMatch name="cn">
<ns0:value>James Bond</ns0:value>
</ns0:equalityMatch>
if they are not the same, create a node and for the logicalOperators/operator/precedingParameterId, get the corresponding
queryString/parameters/parameter #id/name and value and close; for the logicalOperators/operator/followingParameterId, get the corresponding
queryString/parameters/parameter #id/name and value and close;
This should give:
<ns0:or>
<ns0:approxMatch name="givenName">
<ns0:value>Jam*</ns0:value>
</ns0:approxMatch>
<ns0:approxMatch name="sn">
<ns0:value>Bon*</ns0:value>
</ns0:approxMatch>
if queryString/parameters/parameter/value does not contain star use node equalityMatch else use node approxMatch
Who designed this intermediate XML representation of the expression, and why did they do it this way? Is the syntax and semantics of this XML representation well defined, or is it just "specified by example"? Its method of indicating operator precedence by sequences of left- and right- parentheses is highly idiosyncratic. I would say it was designed by someone with very little knowledge or experience of parser design.
Given a free choice, I would personally start from the original free-form expression rather than from this intermediate XML representation. Of course, I would want to know the full grammar of the expression language - at the moment we only have one example expression to work from. It looks like a fairly simple grammar, and parsing simple grammars in XSLT is not difficult if you know the theory. There are good examples of parser-writing in XSLT from Dimitre Novatchev and from Gunther Rademacher.
Unfortunately it's fairly clear from your post that you don't know the theory, and so I'm in the difficult position of trying to suggest a way forward that's constrained by what we know about your level of knowledge and experience.
So, since the lexical analysis phase of parsing has already been done, let's take the problem as given and see what we can do with your intermediate XML representation. The only difficult part of the problem (for me: there might be other parts that are difficult for you) is to work out how to use the left- and right parens info to construct the hierarchical expression tree in your output.
If we take the three "parameters" in your expression (which would usually be called "terms") we can assign each of them a level number: 1, 2, 2 respectively. This represents the depth of the term as it appears in the final expression tree. The level of each term can be deduced from your intermediate input by counting parentheses: the level of a term is
the sum of the number of left parentheses in this and preceding terms
minus
the sum of the number of right parentheses in preceding terms
minus 1
Translating that computation into XSLT terms depends strongly on whether you are using XSLT 1.0 or 2.0, which you haven't actually said. If we assume 2.0, it's
sum((.|preceding-sibling::parameter)/parens/string-length(left-paren))
-
sum(preceding-sibling::parameter/parens/string-length(right-paren))
-1
Now, once you've got the level numbers, constructing a tree is a "well-known" problem, a classic exercise in using grouping constructs. Unfortunately it's well beyond beginner XSLT level, but here goes.
Given a sequence of elements with level numbers:
<a level="1"/>
<b level="2"/>
<c level="3"/>
<d level="3"/>
<e level="2"/>
we can turn them into a tree structure
<a><b><c/><d/></b><e/></a>
using recursive grouping as follows. We write a template that does one level of grouping, and then calls itself recursively to do the next level:
<xsl:template name="grouping">
<xsl:param name="input" as="element()*"/>
<xsl:if test="exists($input)">
<xsl:variable name="level" select="$input[1]/#level"/>
<xsl:for-each-group select="$input"
group-starting-with="*[#level=$level]">
<xsl:copy>
<xsl:call-template name="grouping">
<xsl:with-param name="input"
select="current-group()[position() gt 1]"/>
</xsl:call-template>
</xsl:copy>
</xsl:for-each-group>
</xsl:if>
</xsl:template>
Again, that's using XSLT 2.0. A solution using XSLT 1.0 is going to be much, much harder.
I've given you an outline here and I appreciate that with your level of XSLT experience, fleshing it out is going to be quite hard work. However, I hope you now have a better understanding of the task ahead.

XSL predicate increase for each iteration of a for-each

Quick Question: Is there a way to increment the predicate of an XPATH, by using a variable, like itereating through an array in C? For example /XPATH/element[i]
I am trying to use an XSL to access data from an XML using XPATHS. The XML is the output of a database where the parent node is the table name and its children are the columns. The XSL must be able to convert the text value of the children into attributes with the column name of the element of the table name.
The problem I am trying to solve is that each table can have multiple rows which is outputted to the XML as sibling nodes with the same names. There could be infinite rows in any table so I am trying to use a for-each with the XPATH of the table name to process each row. This works but when I try to use the document function with the XPATH with a predicate to the first XPATH and then the next XPATH and then the next, I do not know how to do it. I can only access the first XPATH. I want a way to be able to access the next XPATH on each iteration of the for-each. Is there anything which can increment each loop and that the predicate and use to point to the next XPATH?
The XML code below is a sample which I am using for testing, it is called DB.xml:
<?xml version="1.0"?>
<dataset>
<rtbp>
<cfmtype>dog</cfmtype>
<cfmid>1</cfmid>
</rtbp>
<rtbp>
<cfmtype>cat</cfmtype>
<cfmid>2</cfmid>
</rtbp>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>1</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>1</RTBP__CFMID>
</FunctionSet>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>2</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>2</RTBP__CFMID>
</FunctionSet>
</dataset>
Below is the XSL I am using:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="dataset/rtbp">
<xsl:element name="RTBP">
<xsl:attribute name="CFMtype">
<xsl:value-of select="document('DB.xml')/dataset/rtbp[1]/cfmtype" />
</xsl:attribute>
<xsl:attribute name="CFMid">
<xsl:value-of select="document('DB.xml')/dataset/rtbp[1]/cfmid" />
</xsl:attribute>
<xsl:text>
</xsl:text>
<xsl:for-each select="/dataset/FunctionSet">
<xsl:element name="FunctionSet">
<xsl:attribute name="RTBP__CFMid">
<xsl:value-of select="document('DB.xml')/dataset/FunctionSet[1]/FUNCTIONSET__IDENTIFIER" />
</xsl:attribute>
<xsl:attribute name="RTBP_FunctionSet">
<xsl:value-of select="document('DB.xml')/dataset/FunctionSet[1]/RTBP__CFMID" />
</xsl:attribute>
</xsl:element>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:element>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The predicates are set to 1 at the moment but I wish it to be a variable which iterates on each loop so the XPATH changes to the next occurence of the table name.
The expected result is below:
<?xml version="1.0"?>
<RTBP CFMtype="dog" CFMid="1">
<FunctionSet RTBP__CFMid="1" RTBP_FunctionSet="1"/>
</RTBP>
<RTBP CFMtype="cat" CFMid="2">
<FunctionSet RTBP__CFMid="2" RTBP_FunctionSet="2"/>
</RTBP>
As you may be able to tell the second table (FunctionSet) is a child of the first (RTBP) hence the for-each inside the for-each. I need a method that will put the first row of the FunctionSet into the First row of the RTBP and likewise for the second rows.
I am new to XML, XSL and Posting questions.
The purpose is to re-create a hierarchical XML from a flat XML
exported from a database using DBunit. The association could be done
by cmfid
You should definitely use a key based on matching the cfmid value - especially if you are expecting a large number of rows. Try:
XSLT 1.0
<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:key name="func" match="FunctionSet" use="RTBP__CFMID" />
<xsl:template match="/">
<root>
<xsl:for-each select="dataset/rtbp">
<RTBP CFMtype="{cfmtype}" CFMid="{cfmid}">
<xsl:for-each select="key('func', cfmid)">
<FunctionSet RTBP__CFMid="{RTBP__CFMID}" RTBP_FunctionSet="{FUNCTIONSET__IDENTIFIER}"/>
</xsl:for-each>
</RTBP>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
When the above is applied to the following test input:
<?xml version="1.0"?>
<dataset>
<rtbp>
<cfmtype>dog</cfmtype>
<cfmid>124</cfmid>
</rtbp>
<rtbp>
<cfmtype>cat</cfmtype>
<cfmid>256</cfmid>
</rtbp>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>Canine</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>124</RTBP__CFMID>
</FunctionSet>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>Feline</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>256</RTBP__CFMID>
</FunctionSet>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>Hound</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>124</RTBP__CFMID>
</FunctionSet>
</dataset>
the result is:
<?xml version="1.0" encoding="utf-8"?>
<root>
<RTBP CFMtype="dog" CFMid="124">
<FunctionSet RTBP__CFMid="124" RTBP_FunctionSet="Canine"/>
<FunctionSet RTBP__CFMid="124" RTBP_FunctionSet="Hound"/>
</RTBP>
<RTBP CFMtype="cat" CFMid="256">
<FunctionSet RTBP__CFMid="256" RTBP_FunctionSet="Feline"/>
</RTBP>
</root>
Note that your requested output format needlessly duplicates the cfmid value in both parent and child.
I think you're looking for something like (updated after quetion update) :
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="rtbp">
<xsl:copy>
<xsl:for-each select="*">
<xsl:attribute name="{local-name()}" select="."/>
</xsl:for-each>
<xsl:apply-templates
select="//FunctionSet[RTBP__CFMID = current()/cfmid]"
mode="insertFunctionSet"/>
</xsl:copy>
</xsl:template>
<xsl:template match="FunctionSet"/>
<xsl:template match="FunctionSet" mode="insertFunctionSet">
<xsl:copy>
<xsl:for-each select="*">
<xsl:attribute name="{local-name()}" select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The idea, here, is to handle differently the element FunctionSet in the context of rtbp element.
It should not be part of the output when you recursively loop over the whole tree (that's the goal of the <xsl:template match="FunctionSet"/>).
But it should be handled inside the rtbp element and so we apply the templates on the relevant FunctionSet in a specific mode at this point. That's the goal of the <xsl:template match="FunctionSet" mode="insertFunctionSet">...</xsl:template>
With your input:
<?xml version="1.0"?>
<dataset>
<rtbp>
<cfmtype>dog</cfmtype>
<cfmid>1</cfmid>
</rtbp>
<rtbp>
<cfmtype>cat</cfmtype>
<cfmid>2</cfmid>
</rtbp>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>1</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>1</RTBP__CFMID>
</FunctionSet>
<FunctionSet>
<FUNCTIONSET__IDENTIFIER>2</FUNCTIONSET__IDENTIFIER>
<RTBP__CFMID>2</RTBP__CFMID>
</FunctionSet>
</dataset>
The result is:
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<rtbp cfmtype="dog" cfmid="1">
<FunctionSet FUNCTIONSET__IDENTIFIER="1" RTBP__CFMID="1"/>
</rtbp>
<rtbp cfmtype="cat" cfmid="2">
<FunctionSet FUNCTIONSET__IDENTIFIER="2" RTBP__CFMID="2"/>
</rtbp>
</dataset>
For anyone who had as little knowledge as me when I posted this question and whom wish to find out the same infomation here is my solution to the question. Short answer to the quick question 'can you increment a variable'. No! But you can set a variable and move the position with the following snippet:
<xsl:for-each select="/dataset/rtbp">
<xsl:variable name="i" select="position()" />
</xsl:for-each>
This snippet loops through rtbp tables in the source XML and moves the position one position more each loop. This creates an object which you can use inside a XPath to test for a condition of each occurence of the Xpath with the same URI path. Such as:
<xsl:for-each select="/dataset/rtbp">
<xsl:variable name="i" select="position()" />
<xsl:if test="/dataset/FunctionSet[$i]/cfmid = /dataset/rtbp[$n]/cfmid">
<!--code if condition is true-->
</xsl:for-each>
The [$variable name] is how you direct an XPath to the occurence of the element name. So when i = 1 it looks for the first occurence of the element name in the XPath and then when i = 2 it looks for the second occurence of the element name in the XPath.
The Key function is a good tool to search for a key condition inside a template. However I can only use 1 key function per a template. if you wish to have a multi condition test you have to use a choose when statement with multiple if statements being anded with eeach other. for example:
This is a snippet from my advanced code which has multiple for-each loops inside each other and choose when statements to decide if a XML element is a child of the parent via its Identifiers which are child elements of the parent elements in the example XML in my question.
With the position function and the XPath predicate entry combined with choose when statements with ands you can build up a complex XSL which can re-create a flat XML table list of a database into a hierarchical XML form.
Vincent's Key function answer worked for the small complexity of this question but this answer includes an answer about the XPath predicates so I think it is more relevant of an answer to the question. Please look at Vincent's answer and consider using Key Functions for your solution because it is very useful

Add a new namespace prefix to the body of a SOAP request, that is not present or referenced in the original XML

I have XML list of values that will be populated by a form. Based on the value of a particular element, in this case Type, I need to dynamically construct a SOAP request. Only certain elements from the Source XML will be used, contingent on the Type. In addition, I need to add a namespace prefix to every element in the SOAP body that is not present or referenced in any way in the Source XML.
Given XML input in the following format:
<User>
<Action>Update</Action>
<Id>123-45-5678</Id>
<Type>Student</Type>
<Validate>true</Validate>
<Grade>11</Grade>
<Classroom/>
<UserName/>
<Password/>
</User>
If I apply the following transform:
<xsl:stylesheet version="1.0" xmlns:ztx="http://tempuri.org/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:variable name="Action" select="/User/Action"/>
<xsl:variable name="Id" select="/User/Id"/>
<xsl:variable name="Type" select="/User/Type"/>
<xsl:variable name="Method" select="concat('ztx:', $Action, $Type)"/>
<xsl:template match="/">
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<xsl:element name="{$Method}">
<xsl:if test="$Action != 'Create'">
<xsl:copy-of select="/User/Id"/>
</xsl:if>
<xsl:if test="$Action != 'Delete'">
<xsl:choose>
<xsl:when test="$Type = 'Teacher'">
<xsl:copy-of select="/User/Classroom"/>
</xsl:when>
<xsl:when test="$Type = 'Student'">
<xsl:copy-of select="/User/Grade"/>
</xsl:when>
<xsl:when test="$Type = 'Administrator'">
<xsl:copy-of select="/User/UserName"/>
<xsl:copy-of select="/User/Password"/>
</xsl:when>
</xsl:choose>
<xsl:copy-of select="/User/Validate"/>
</xsl:if>
</xsl:element>
</soapenv:Body>
</soapenv:Envelope>
</xsl:template>
</xsl:stylesheet>
I get the following output:
<soapenv:Envelope xmlns:ztx="http://tempuri.org/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<ztx:UpdateStudent>
<Id>123-45-5678</Id>
<Grade>11</Grade>
<Validate>true</Validate>
</ztx:UpdateStudent>
</soapenv:Body>
</soapenv:Envelope>
My desired output, however, is:
<soapenv:Envelope xmlns:ztx="http://tempuri.org/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<ztx:UpdateStudent>
<ztx:Id>123-45-5678</ztx:Id>
<ztx:Grade>11</ztx:Grade>
<ztx:Validate>true</ztx:Validate>
</ztx:UpdateStudent>
</soapenv:Body>
</soapenv:Envelope>
I would like to do this elegantly, if possible, and in a single transform. Looking for a generic way, perhaps with another template, to just process every element in the SOAP body and add the namespace, rather than hard-code them individually.
NOTE: I am restricted to XSLT 1.0
Thank you!
Like you mentioned in your question you can add another template to add the namespace prefix to the elements for your SOAP response.
Here is the template
<xsl:template name="addNamespace" match="User/*">
<xsl:element name="{concat('ztx:',name(.))}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
And then you would just change all of your copy-of to apply-templates then to use the new template.

XSLT 2.0 Xpath error "not a node item"

my first post here - sure hope someone will know the answer!
I have been able to find solutions for many issues I had, but not for this one.
The questions and answers on this site on the same subject did not solve my issue...
I have an xml containing Format specifications like
<Format>
<TagNr>92</TagNr>
<Option>A</Option>
<Format>//[N]15d</Format>
</Format>
<Format>
<TagNr>92</TagNr>
<Option>B</Option>
<Format>//3!a/3!a/15d</Format>
</Format>
TagNr + option is a unique combination within this nodeset.
I defined a key to make using the set of formats easier:
<xsl:key name="xx" match="//Format/Format" use="concat(../TagNr, ../Option)"/>
I can indeed use this key and get the correct value, but only in non-special elements; I get an error "Error in XPath 2.0 expression Not a node item" when using this key within for-each or other constructs like the one below.
What I try to achieve is the following: In other nodes processed there is a string of options for which I wish to retrieve the format for each character.
For example:
<Tag>
<TagNr>92</TagNr>
<Options>AB</Options>
</Tag>
I have been trying lots of variants of the below but no luck:
<xsl:variable name="TN"><xsl:value-of select="TagNr"/></xsl:variable>
<xsl:variable name="optList">
<xsl:analyze-string select="./Options" regex="[A-Z]">
<xsl:matching-substring>
<xsl:variable name="TNO" select="concat($TN, .)"/>
<opt>
<tag><xsl:value-of select="$TNO"/></tag>
<fmt><xsl:value-of select="key('xx', $TNO)"/></fmt>
</opt>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>
Splitting into individual characters using the regex goes fine and when retrieving (only) the value for opt/tag that goes fine too.
But when I add opt/fmt, I run into the mentioned error message for the Xpath expression select="key('xx', $TNO)".
I tried defining a variable based on the key function as suggested in another thread on this site, but did not succeed.
Can anyone help me?
The key() function (with two arguments) searches the document containing the context node. If the context item is not a node - for example, within analyze-string - then you will get this error, because it doesn't know which document to search. The answer is to use the third argument of key() to supply this information.
The problem is that the context changes in your analyze-string element. Maybe the following solution will help you.
For an XML file like that :
<a>
<Format>
<TagNr>92</TagNr>
<Option>A</Option>
<Format>//[N]15d</Format>
</Format>
<Format>
<TagNr>92</TagNr>
<Option>B</Option>
<Format>//3!a/3!a/15d</Format>
</Format>
<Tag>
<TagNr>92</TagNr>
<Options>AB</Options>
</Tag>
</a>
Consider the following XSLT :
<?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:key name="xx" match="//Format/Format" use="concat(../TagNr, ../Option)"/>
<xsl:template match="/">
<result>
<xsl:apply-templates select="//Tag"/>
</result>
</xsl:template>
<xsl:template match="Tag">
<xsl:call-template name="createOPT">
<xsl:with-param name="str" as="xs:string" select="Options"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="createOPT">
<xsl:param name="str"/>
<xsl:if test="string-length($str) > 0">
<xsl:variable name="firstChar" select="substring($str,1,1)"/>
<xsl:variable name="TNO" select="concat(TagNr,$firstChar)"/>
<opt>
<tag><xsl:value-of select="$TNO"/></tag>
<fmt><xsl:value-of select="key('xx', $TNO)"/></fmt>
</opt>
<xsl:call-template name="createOPT">
<xsl:with-param name="str" select="substring($str,2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The result is :
<?xml version="1.0" encoding="UTF-8"?>
<result>
<opt>
<tag>92A</tag>
<fmt>//[N]15d</fmt>
</opt>
<opt>
<tag>92B</tag>
<fmt>//3!a/3!a/15d</fmt>
</opt>
</result>
The easiest XSLT 2.0 way to process a string character by character is the following:
<xsl:for-each select="string-to-codepoints($vStr)">
<xsl:variable name="$vChar" select=
"codepoints-to-string(.)"/>
<!-- Process $vChar here: -->
</xsl:for-each/>
You can combine this with saving the original document context into a variable (say $vDoc) and using this variable as the 3rd argument of the key() function -- which is again an XSLT 2.0 - only feature.
So, you'll have something like:
key('xx', concat($TN, $vChar), $vDoc)
Summary:
Use the string-to-codepoints() and codepoints-to-string() functions for char-by-char processing.
Use the 3-rd argument of the key() function to specify context different from the current.