XSL modify output based on presence of element data in another file - xslt

This is all new to me and I'm trying to learn how it works by examining and modifying some pre-existing XSL on our system, so please excuse that I don't really have a test script to troubleshoot.
I have an exported list of rooms that exist on our system - and a survey of criteria within those rooms.
I want to lookup the surveyed room and alter the behaviour of the output depending on whether it exists on the export or not.
Some sample data below which gives an idea of what I want to achieve. I'm just not sure how to (a) link to the reference xml and (b) query it in this way.
Reference listing XML (name = spaces.xml):
<Listing>
<Space>
<Code>Room1</Code>
</Space>
<Space>
<Code>Room2</Code>
</Space>
<Space>
<Code>Room3</Code>
</Space>
<Space>
<Code>Room4</Code>
</Space>
</Listing>
Survey XML example
<Survey>
<Record>
<Ref>1</Ref>
<Space>Room1</Space>
<Data>123</Data>
</Record>
<Record>
<Ref>2</Ref>
<Space>Room2</Space>
<Data>456</Data>
</Record>
<Record>
<Ref>3</Ref>
<Space>Room5</Space>
<Data>789</Data>
</Record>
</Survey>
As room 5 is not in the listing, the output should behave differently
XSLT so far. This doesn't include the reference to the listing.xml file or the proper test condition
xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml"/>
<xsl:template match="Record">
<OutputData>
<SurveyRef>
<xsl:value-of select="Ref"/>
</SurveyRef>
<xsl:choose>
<xsl:when test="##SPACE IS PRESENT IN SPACES.XML##">
<SpaceRef>
<xsl:value-of select="Space"/>
</SpaceRef>
<xsl:otherwise>
<SpaceRef>CheckTheSpace</SpaceRef>
</xsl:otherwise>
</xsl:choose>
<SurveyResult>
<xsl:value-of select="Data"/>
</SurveyResult>
</OutputData>
</xsl:template>
</xsl:stylesheet>
Hoped-for output
<OutputData>
<SurveyRef>1</SurveyRef>
<SpaceRef>Room1</SpaceRef>
<SurveyResult>123</SurveyResult>
</OutputData>
<OutputData>
<SurveyRef>2</SurveyRef>
<SpaceRef>Room2</SpaceRef>
<SurveyResult>456</SurveyResult>
</OutputData>
<OutputData>
<SurveyRef>3</SurveyRef>
<SpaceRef>CheckTheSpace</SpaceRef>
<SurveyResult>789</SurveyResult>
</OutputData>
Any help with this much appreciated, as I'm sure it will help me get to use this tool more
effectively.
Thanks.

how to (a) link to the reference xml
It depends on where exactly the reference document will be. If you place it at the same location where your XSLT stylesheet is, then you can refer to it using just its name within the document() function. Otherwise you need to use either a relative or a full absolute path.
and (b) query it in this way.
The most convenient way to resolve a cross-reference is by using a key - for example:
XSLT 2.0
<xsl:stylesheet version="2.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="spc" match="Space" use="Code" />
<xsl:template match="/Survey">
<root>
<xsl:for-each select="Record">
<OutputData>
<SurveyRef>
<xsl:value-of select="Ref"/>
</SurveyRef>
<SpaceRef>
<xsl:value-of select="if (key('spc', Space, document('space.xml'))) then Space else 'CheckTheSpace'"/>
</SpaceRef>
<SurveyResult>
<xsl:value-of select="Data"/>
</SurveyResult>
</OutputData>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>

Related

how to use for-each-group by to group values in XSLT 2.0

I am getting below output from database and based on that I have to group by the data based on composite_name and RN elements.
Output coming from Database -
<SelectFlowIdDetailsFromSOAInfraOutputCollection>
<SelectFlowIdDetailsFromSOAInfraOutput>
<FLOW_ID>200239</FLOW_ID>
<COMPOSITE_NAME>ABC</COMPOSITE_NAME>
<rn>1</rn>
</SelectFlowIdDetailsFromSOAInfraOutput>
-<SelectFlowIdDetailsFromSOAInfraOutput>
<FLOW_ID>200247</FLOW_ID>
<COMPOSITE_NAME>ABC</COMPOSITE_NAME>
<rn>1</rn>
</SelectFlowIdDetailsFromSOAInfraOutput>
<SelectFlowIdDetailsFromSOAInfraOutput>
<FLOW_ID>200301</FLOW_ID>
<COMPOSITE_NAME>GHI</COMPOSITE_NAME>
<rn>1</rn>
</SelectFlowIdDetailsFromSOAInfraOutput>
<SelectFlowIdDetailsFromSOAInfraOutput>
<FLOW_ID>200300</FLOW_ID>
<COMPOSITE_NAME>GHI</COMPOSITE_NAME>
<rn>1</rn>
</SelectFlowIdDetailsFromSOAInfraOutput>
</SelectFlowIdDetailsFromSOAInfraOutputCollection>
I have to perform group by on composite_name and rn so that expected output should be
<RRR>
<XYZ>
<COMPOSITE_NAME>abc</COMPOSITE_NAME>
FLOW_ID>200239,200247</FLOW_ID>
</XYZ>
<XYZ>
<COMPOSITE_NAME>GHI</COMPOSITE_NAME>
FLOW_ID>200300,200301</FLOW_ID>
</XYZ>
</RRR>
I have used below XSLT but not getting expected output.
<xsl:for-each-group select="/ns0:SelectFlowIdDetailsFromSOAInfraOutputCollection/ns0:SelectFlowIdDetailsFromSOAInfraOutput" group-by="concat(ns0:COMPOSITE_NAME ,'|', ns0:rn)">
<tns:EmailBody>
<tns:InterfaceName>
<xsl:value-of select="ns0:COMPOSITE_NAME"/>
</tns:InterfaceName>
<tns:FaultedId>
<xsl:for-each select="current-group">
<xsl:value-of select="ns0:FLOW_ID"/>
<xsl:if test="not(position() = last())">,</xsl:if>
</xsl:for-each>
</tns:FaultedId>
</tns:EmailBody>
</xsl:for-each-group>
Please help me with correct logic to achieve the output.
Thank you !
Your attempt makes very little sense. It looks like you copied snippets from various sources and thrown them together in random order hoping they will somehow combine themselves into a functioning code. This might work in a Harry Potter film, not in programming.
Try a straightforward textbook approach instead:
XSLT 2.0
<xsl:stylesheet version="2.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="/SelectFlowIdDetailsFromSOAInfraOutputCollection">
<RRR>
<xsl:for-each-group select="SelectFlowIdDetailsFromSOAInfraOutput" group-by="concat(COMPOSITE_NAME ,'|', rn)">
<XYZ>
<COMPOSITE_NAME>
<xsl:value-of select="COMPOSITE_NAME"/>
</COMPOSITE_NAME>
<FLOW_ID>
<xsl:value-of select="current-group()/FLOW_ID" separator=","/>
</FLOW_ID>
</XYZ>
</xsl:for-each-group>
</RRR>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/3MEdvgZ

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 - How do you check for a grandchild node with a certain path name. (xpath 1.0)

What I want to do is given an element as context, I want to determine if it has a child with a given name and determine if that child has a node with a given name so I can do operations with it. It is important that I do this in XPath 1.0 syntax.
The code that I've gotten so far is this.
<xsl:for-each select="child::*">
<xsl:if test="contains(name(), 'description')">
<xsl:for-each select="child::*">
<xsl:if test="contains(name(), 'text')">
<xsl:value-of select="node()"/>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
It works, but it's big and ugly and I know that there's a way to condense it. The for-eachs there are unnecessary, since I'm only expecting one child node to be named description, and for it to only have one text node.
I feel like this solution should work
<xsl:for-each select="./description/text">
..
</xsl:for-each>
But it isn't, and I'm not really good enough with XPath Syntax to know why.
The reason I'm asking is because though I've found answers that detect whether a child node has a name, and I've found answers that can get to that child node's context, I haven't found an answer that combines the two, though maybe I just haven't been searching hard enough, in which case I apologize.
Edit: Woops, sorry yeah I forgot to mention that the contains() part of the code was also just a hack because I wasn't sure how to compare their values with equality.
Also as long as the answer is there, <xsl:for-each select="description/text"> does not work either.
A sample of the XML in question is this
<leaf>
<description>
<text> Various Words
</text>
</description>
</leaf>
where the context is the leaf and I am trying to get to the text node.
Edit: The Second Coming:
The problem for me was that my XSLT file was using a default namespace (in my case named a). If I had added that then Borodin's answer would have been correct.
To be specific, this is the code which ended up working for me in the end, in case anyone wants to know.
<xsl:for-each select="a:description/a:text>
<xsl:value-of select="node()"/>
</xsl:for-each>
Thanks Guys ^-^
Do you really want to check whether the element names contain those strings? Or, as your narrative says, do you want elements with that exact name?
To do something like what you have already written, use
<xsl:for-each select="*[contains(name(), 'description')]/*[contains(name(), 'text')]">
<xsl:value-of select="node()"/>
</xsl:for-each>
But if you know the complete names it is a lot neater:
<xsl:for-each select="description/text">
<xsl:value-of select="node()"/>
</xsl:for-each>
If that doesn't work then we need to see more of your source XML and your transform.
Update
If I use this XML
<leaf>
<description>
<text>Various Words</text>
</description>
<description>
<text>More Words</text>
</description>
<description>
<text>Other Words</text>
</description>
</leaf>
and apply this stylesheet
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/leaf">
<xsl:for-each select="description/text">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
the output is the expected Various WordsMore WordsOther Words. I don't know how to help you unless you describe your situation better, except to say that transforms should be written with another template rather than for-each wherever possible. Like this variation which produces the same output as above.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/leaf">
<xsl:apply-templates select="description/text"/>
</xsl:template>
<xsl:template match="text">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>

XSLT formatting of string

I am a novice when it comes to XSLT.
I run the below select
<xsl:value-of select="./#name"/>
I get the following result
TestSomething.Cancel(GIVEN WHEN THEN)
I want the output to say
GIVEN WHEN THEN
instead of TestSomething.Cancel(GIVEN WHEN THEN)
Would be thankful if someone could point me in the right direction.
Use ...
<xsl:value-of select="substring-before(substring-after(./#name,'('),')')" />
It would help if you could post the source XML and some information on the xslt processor you are using, but at a guess I'd say this.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:value-of select="substring-before(substring-after(./#name, '('), ')')"/>
</xsl:template>
</xsl:stylesheet>

Trouble with xsl:for-each

I must be missing some fundamental concept of processing an XML document. Here is my source XML:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Root>
<Element>visitorNameAlt</Element>
<Element>visitorScore</Element>
<Element>visitorTimeouts</Element>
<Element>Blank</Element>
<Element>homeNameAlt</Element>
<Element>homeScore</Element>
<Element>homeTimeouts</Element>
<Element>Blank</Element>
<Element>period</Element>
<Element>optionalText</Element>
<Element>flag</Element>
<Element>Blank</Element>
<Element>scoreLogo</Element>
<Element>sponsorLogo</Element>
</Root>
And my XSL stylesheet:
<?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="xml" version="1.0" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="/Root">
<xsl:value-of select="position()"/>
<xsl:value-of select="Element"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
All I want is to pluck the "Element" names from the source XML doc with their relative position in front.
My output is just "1" followed by the first element and nothing more.
I am new to XSLT, but have processed other documents successfully with for-each.
Thanks in advance.
Bill
You're looping over Root tags, not Element tags. Try this:
<xsl:template match="/">
<xsl:for-each select="/Root/Element">
<xsl:value-of select="position()"/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
Note that you must change the second value-of select to "." or "text()".
XSLT is not an imperative programming language. The XSLT processor grabs each element in turn and tries to match it to your stylesheet. The idiomatic way to write this is without a for-each:
<xsl:template match="/Root">
<xsl:apply-templates select="Element"/>
</xsl:template>
<xsl:template match="Element">
<xsl:value-of select="position()"/>
<xsl:value-of select="."/>
</xsl:template>
The first template matches the root and tells the processor to apply the stylesheet to all the Element nodes inside the Root. The second template matches those nodes, and outputs the desired information.