XSLT transformation of boolean expressions - xslt

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.

Related

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

XSLT not generating expected output

Input XML:
<derivatives>
<derivative id="4" name="Audio Content">
<operator id="1" name="Reliance">
<referenceCode code="62033815">
<mobileCircle id="1" name="Maharashtra"/>
</referenceCode>
</operator>
<operator id="22" name="Aircel">
<referenceCode code="811327">
<mobileCircle id="1" name="Maharashtra"/>
</referenceCode>
</operator>
</derivative>
</derivatives>
Expected Output XML:
<hellotune>
<operator>Aircel</operator>
<vcode>811327</vcode>
</hellotune>
Current output (which is wrong):
<hellotune>
<operator>Aircel</operator>
<vcode/>
</hellotune>
XSL (which is not working):
<xsl:if test="derivatives/derivative/operator[#name='Aircel']">
<hellotune>
<operator>Aircel</operator>
<vcode><xsl:value-of select="referenceCode/#code"/></vcode>
</hellotune>
</xsl:if>
Note: Using XSL v1.0. Not mentioned the complete XSL for brevity.
Based on the XSL you provided, it can be presumed that the context node is the root node, but starting from the root node, the path referenceCode/#code does not match anything in your input. Appending derivatives/derivative/operator/ before that path would succeed in finding a referenceCode #code attribute, but it would find the wrong one. Try this push-style approach:
<xsl:template match="/">
<xsl:apply-templates select="derivatives/derivative/operator[#name='Aircel']" />
</xsl:template>
<xsl:template match="operator">
<hellotune>
<operator><xsl:value-of select="#name" /></operator>
<vcode><xsl:value-of select="referenceCode/#code"/></vcode>
</hellotune>
</xsl:template>
The xpath xpression in the element <vcode> is not referring to any node in the input document. The best way to match all the nodes is to use
//
like in your code you can use
//referenceCode/#code
(but this is only for information purpose and using same can't get your result).
you can try this way:
<xsl:template match="/">
<hellotune>
<xsl:for-each select="//operator">
<xsl:if test="./#name='Aircel'">
<operator><xsl:value-of select="#name"/></operator>
<vcode><xsl:value-of select="referenceCode/#code"/></vcode>
</xsl:if>
</xsl:for-each>
</hellotune>
</xsl:template>
Hope this helps :)

XSLT Matching 2 Elements to get another

I'm relatively new to XSLT but want to perform what I thought was a relatively simple match between elements to get another.
Here is a snippet from the XML. Version = 1.0 and output is text (converting xml to text).
<Accounts>
<Account>
<Id>273228MD301</Id>
<EPIProductCode>IPP4D3</EPIProductCode>
<Name>Mr John Smith</Name>
<Status>Open</Status>
<Owners>
<Owner>
<Id>273228M</Id>
</Owner>
</Owners>
<Advisers>
<Adviser>
<Id>286666</Id>
<PrimaryAdviser>true</PrimaryAdviser>
</Adviser>
</Advisers>
<Delete>false</Delete>
<LastModified>2012-06-08T15:19:19</LastModified>
</Account>
<Account>
<Id>273228MD399</Id>
<EPIProductCode>IPAAA</EPIProductCode>
<Name>Sir Leslie Patterson</Name>
<Status>Open</Status>
<Owners>
<Owner>
<Id>2732299</Id>
</Owner>
</Owners>
<Advisers>
<Adviser>
<Id>286666</Id>
<PrimaryAdviser>true</PrimaryAdviser>
</Adviser>
</Advisers>
<Delete>false</Delete>
<LastModified>2012-06-08T15:19:19</LastModified>
</Account>
<Account>
<Id>273228MD999</Id>
<EPIProductCode>IPYYY</EPIProductCode>
<Name>Dame Edna</Name>
<Status>Open</Status>
<Owners>
<Owner>
<Id>27322YY</Id>
</Owner>
</Owners>
<Advisers>
<Adviser>
<Id>286666</Id>
<PrimaryAdviser>true</PrimaryAdviser>
</Adviser>
</Advisers>
<Delete>false</Delete>
<LastModified>2012-06-08T15:19:19</LastModified>
</Account>
</Accounts>
<InvestmentHoldingBalances>
<HoldingBalance>
<AccountId>273228MD399</AccountId>
<InvestmentCode>TEST123</InvestmentCode>
<Exchange>FND</Exchange>
<UnitBalance>
<Settled Currency="AUD">0</Settled>
<Pending Currency="AUD">0</Pending>
<AsAtDate>2012-06-08T15:19:34</AsAtDate>
</UnitBalance>
<LastModified>2012-05-16T00:00:00</LastModified>
</HoldingBalance>
<HoldingBalance>
<AccountId>273228MD301</AccountId>
<InvestmentCode>0114AU</InvestmentCode>
<Exchange>FND</Exchange>
<UnitBalance>
<Settled Currency="AUD">0</Settled>
<Pending Currency="AUD">0</Pending>
<AsAtDate>2012-06-08T15:19:34</AsAtDate>
</UnitBalance>
<LastModified>2012-05-16T00:00:00</LastModified>
</HoldingBalance>
<HoldingBalance>
<AccountId>273228MD301</AccountId>
<InvestmentCode>0016AU</InvestmentCode>
<Exchange>FND</Exchange>
<UnitBalance>
<Settled Currency="AUD">0</Settled>
<Pending Currency="AUD">0</Pending>
<AsAtDate>2012-06-08T15:19:34</AsAtDate>
</UnitBalance>
<LastModified>2012-05-16T00:00:00</LastModified>
</HoldingBalance>
<HoldingBalance>
<AccountId>273228MD301</AccountId>
<InvestmentCode>0277AU</InvestmentCode>
<Exchange>FND</Exchange>
<UnitBalance>
<Settled Currency="AUD">0</Settled>
<Pending Currency="AUD">0</Pending>
<AsAtDate>2012-06-08T15:19:34</AsAtDate>
</UnitBalance>
<LastModified>2012-05-15T00:00:00</LastModified>
</HoldingBalance>
<HoldingBalance>
<AccountId>273228MD999</AccountId>
<InvestmentCode>TD0155</InvestmentCode>
<Exchange>FND</Exchange>
<UnitBalance>
<Settled Currency="AUD">0</Settled>
<Pending Currency="AUD">0</Pending>
<AsAtDate>2012-06-08T15:19:34</AsAtDate>
</UnitBalance>
<LastModified>2012-05-21T00:00:00</LastModified>
</HoldingBalance>
</InvestmentHoldingBalances>
What I'm tying to do is match nodes //Accounts/Account/Id with //InvestmentHoldingBalances/HoldingBalance/AccountId and when there is a match get the corresponding //Owners/Owner/Id that belongs to that Account Id.
The results Im getting when I do a match is the first //Owners/Owner/Id for all rows not the individual matching one. This is my xslt;
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes" />
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<!-- Create Header Record -->
<xsl:template match="xxxxxx">
<!-- Some other xslt here to extract header info -->
<xsl:apply-templates select="InvestmentHoldingBalances/HoldingBalance" />
</xsl:template>
<!-- Create HoldingBalance Records -->
<xsl:template match="HoldingBalance">
<xsl:value-of select="AccountId" />
<xsl:text>","</xsl:text>
<xsl:value-of select="InvestmentCode" />
<xsl:text>","</xsl:text>
<xsl:value-of select="Exchange" />
<xsl:text>","</xsl:text>
<xsl:if test="../../Accounts/Account/Id=AccountId">
<xsl:value-of select="../../Accounts/Account/Owners/Owner/Id" />
</xsl:if>
<xsl:text>"</xsl:text>
<xsl:text disable-output-escaping="yes">
</xsl:text>
</xsl:template>
</xsl:stylesheet>
The output is the same Owner Id for each row (ie '273228M' the last column), not the matching Owner Id according to the Account Id match;
273228MD399","TEST123","FND","273228M"
273228MD301","0114AU","FND","273228M"
273228MD301","0016AU","FND","273228M"
273228MD301","0277AU","FND","273228M"
273228MD999","TD0155","FND","273228M"
The result I'm after would look like this;
273228MD399","TEST123","FND","2732299"
273228MD301","0114AU","FND","273228M"
273228MD301","0016AU","FND","273228M"
273228MD301","0277AU","FND","273228M"
273228MD999","TD0155","FND","27322YY"
Thanks for any suggestions.
The basic problem is in the
<xsl:value-of select="../../Accounts/Account/Owners/Owner/Id"/>
The select expression selects all owner ids in the whole document regardless of the account id, and when you ask for the value-of a set of more than one node the result is defined by the spec to be the value of the first node in the set in document order.
You need to somehow constrain the set to just the matching accounts, the easiest way to do this is
<xsl:value-of select="../../Accounts/Account[Id = current()/AccountId]/Owners/Owner/Id"/>
but it is likely to be more efficient to define a key at the top level of the stylesheet (put it directly after the xsl:output element)
<xsl:key name="accountById" match="Account" use="Id"/>
Then you can extract the right account in the value-of using
<xsl:value-of select="key('accountById', AccountId)/Owners/Owner/Id"/>
Either way, you don't actually need the if around the value-of, because in the case where no accounts match the current AccountId you'll be asking for the value-of an empty node set, which is the empty string by definition.
And finally, you should never need disable-output-escaping when you are using <xsl:output method="text"/> - simply <xsl:text>
</xsl:text> will work just as well here.

XSL outputting more than expected

I'm fairly new to XSLT and XPath and have been banging my head against the wall for a while on this problem.
I have the following XML:
<reply>
<multi-results>
<multi-item>
<name>node1</name>
<information>
<block>
<slot>A</slot>
<state>Online</state>
<colour>purple</colour>
</block>
<block>
<slot>B</slot>
<state>Online</state>
<colour>yellow</colour>
</block>
<block>
<slot>C</slot>
<state>Online</state>
<colour>red</colour>
</block>
<block>
<slot>D</slot>
<state>Online</state>
<colour>blue</colour>
</block>
</information>
</multi-item>
</multi-results>
<address>
<label>this is an arbitrary bit of text included for this example</label>
</address>
</reply>
There are a variable number of "block" entries per file.
I want to "CSV" the data, and I'm using the following XSL:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="*/text()[normalize-space()]">
<xsl:value-of select="normalize-space()"/>
</xsl:template>
<xsl:template match="*/text()[not(normalize-space())]" />
<xsl:template match="block">
<xsl:value-of select="slot"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="state"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="colour"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Output:
node1A|Online|purple
B|Online|yellow
C|Online|red
D|Online|blue
this is an arbitrary bit of text included for this example
However, the output includes both the "name" and the "label"...
I want only what I'm explicitly asking for in the XSL:
A|Online|purple
B|Online|yellow
C|Online|red
D|Online|blue
I don't understand why. Can someone explain please?
Also, there may be multiple "name" elements, each with its own number of "block" elements.
Many thanks in advance
The elements outside the <block> are being processed using the default template rules. To prevent this you need to add
<xsl:template match="/">
<xsl:apply-templates select="block"/>
</xsl:template>
Then you don't need the template rules that match text nodes, because you never apply-templates to text nodes.
Just remove xsl:value-of from your first xsl:template. You get "name" and "label" contents because of it: it takes any text node and outputs its content. Moreover you don't need checking conditions on text nodes, leave one xsl:template for them with empty body:
<xsl:template match="*/text()"/>

How to apply an XSLT transformation that includes spaces to an XML doc using tDOM?

I have some XML of the form:
<definitions devices="myDevice">
<reg offset="0x0000" mnem="someRegister">
<field mnem="someField" msb="31" lsb="24 />
...
</reg>
...
</definitions>
I want the XML to be the definitive reference and use XSLT to transform it to HTML for documentation, .h for building (and maybe other forms too).
The HTML version is working fine and produces a table per register, with a row per field:
... (header boilerplate removed)
<xsl:for-each select="definitions/reg">
<table>
<tr>
<th><xsl:value-of select="#offset"/></th>
<th><xsl:value-of select="#mnem"/></th>
</tr>
<xsl:for-each select="field">
<tr>
<td><xsl:value-of select="#msb"/>..<xsl:value-of select="#lsb"/></td>
<td><xsl:value-of select="#mnem"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:for-each>
Converting to a .h isn't going so well. I'm completely failing to generate the required spaces in the output:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="definitions/reg">
#define <xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:text> </xsl:text>
<xsl:value-of select="#offset"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I'd hope for that to produce the output:
#define SOMEREGISTER 0x0000
But I actually get:
#define SOMEREGISTER0x0000
I don't understand why I get the space after the '#define', but not the one after the transformed mnemonic. I've tried a simpler solution with just an inline space, with the same results.
I'm too new to this (XSLT) to know whether I'm a) doing it wrong or b) finding a limitation in tDOM.
Testing with this:
# I could have read these from a file I suppose...
set in {<definitions devices="myDevice">
<reg offset="0x0000" mnem="someRegister">
<field mnem="someField" msb="31" lsb="24" />
</reg>
</definitions>}
set ss {<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="definitions/reg">
<xsl:text>#define </xsl:text>
<xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:text xml:space="preserve"> </xsl:text>
<xsl:value-of select="#offset"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>}
# Interesting code starts here
package require tdom
set indoc [dom parse $in]
set xslt [dom parse -keepEmpties $ss]
set outdoc [$indoc xslt $xslt]
puts [$outdoc asText]
I find that this works. The issue is that the tDOM parser doesn't handle the xml:space attribute correctly; without the magical -keepEmpties option, all the empty strings are stripped from the stylesheet and that leads to a wrong XSLT stylesheet being applied. But with the option, it appears to do the right thing.
Note that the XSLT engine itself is doing the right thing. It's the XML parser/DOM builder. (I think it's a bug; I'll look up where to report it.)
Per:
http://www.ibm.com/developerworks/xml/library/x-tipwhitesp/index.html
Try using the preserve space directive:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="definitions/reg">
<xsl:text xml:space="preserve">#define </xsl:text>
<xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<xsl:text xml:space="preserve"> </xsl:text>
<xsl:value-of select="#offset"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
You don't have an output method specified in your second stylesheet, so the default is gonna be XML. I'd advice you to use output method "text", then use <xsl:text> elements for any literal output. Check this example:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:for-each select="definitions/reg"><xsl:text>#define </xsl:text><xsl:value-of select="translate(#mnem,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/><xsl:text> </xsl:text><xsl:value-of select="#offset"/><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EDIT: by the way, that
at the end is a character code. It's simply the decimal value of the ASCII code for a line feed. This makes sure you start a new line for the next reg entry. If you need the Windows/DOS convention (carriage return + line feed), use
instead.