How to extract node names without doubles - xslt

I'm wondering if there is an xsl code that could loop through an xml file and extract only the nodenames. Also making sure it doesn't write it twice.
This is for my project at my client's side. I need to convert xml to text and I'm doing this with groovy and xslt. As the xml files are large, it takes a lot of time to get through the xml file to find the different 'value-off''s.
I've already this, but it gives me ALL the nodes and it's values, so each node more then once
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="/currencies/record"></xsl:apply-templates>
</xsl:template>
<xsl:key name="names" match="*|#*" use="name()"/>
<xsl:template match="/currencies/record">
<xsl:for-each select="//*">
<xsl:value-of select="local-name()"/>=<xsl:apply-templates select="current()/text()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The xml looks like this:
<record deprecated="false"
id="CUR00001"
date.creation="2010-03-22"
adm.status="current">
<authority-code>AED</authority-code>
<code-4217>AED</code-4217>
<label>
<lg.version lg="bul" script="Cyrillic">Дирхам на ОАЕ</lg.version>
<lg.version lg="ces">UAE dirham n. emirátský dirham</lg.version>
<lg.version lg="dan">Emiratarabisk dirham</lg.version>
<lg.version lg="deu" gender.grammar="M">VAE-Dirham</lg.version>
<lg.version lg="deu" number.grammar="P">VAE-Dirhams</lg.version>
<lg.version lg="fin">Arabiemiirikuntien dirhami</lg.version>
<lg.version lg="fra" gender.grammar="M">Dirham des Émirats arabes unis</lg.version>
...
</label>
<currency.subunit>
<lg.version lg="bul" script="Cyrillic">филс</lg.version>
<lg.version lg="ces">fils</lg.version>
<lg.version lg="dan">fils</lg.version>
<lg.version lg="deu" gender.grammar="M">Fils</lg.version>
<lg.version lg="ell" script="Greek">φιλς</lg.version>
<lg.version lg="eng" gender.grammar="N">fils</lg.version>
...
<start.use>1990-01-01</start.use>
<linkCOUid>COU0450</linkCOUid>
<use.context>TED_SCHEMA</use.context>
</record>
In my example "lg.version" is present a lot of times, I'm expecting it to be there just 1 time.
Thanks for any help.

Always say which version of XSLT you are using.
In XSLT 2.0 it is simply distinct-values(//*/node-name()) (assuming that by "nodes" you really mean "elements").
If you're on 1.0 and can't move forward for some reason, use Muenchian grouping - which makes it much more complicated.

Related

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

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>

XSLT dynamic Sum based on a parameter

I am calling this XSL from Apache Camel and I set the header of the message to the parameter, but still I don't get the result.
I want an XSLT which gives me the sum based on a parameter the problem is that the parameter could be multiple values or one value
<EmpJob>
<EmpJob>
<userId>testID</userId>
<EmpPayCompRecurring>
<payComponent>1010</payComponent>
<endDate>2020-06-30T00:00:00.000</endDate>
<paycompvalue>3025.67</paycompvalue>
<userId>testID</userId>
<currencyCode>EUR</currencyCode>
<startDate>2020-06-01T00:00:00.000</startDate>
</EmpPayCompRecurring>
<EmpPayCompRecurring>
<payComponent>6097</payComponent>
<endDate>2019-12-31T00:00:00.000</endDate>
<paycompvalue>100.0</paycompvalue>
<userId>testID</userId>
<currencyCode>EUR</currencyCode>
<startDate>2018-12-06T00:00:00.000</startDate>
</EmpPayCompRecurring>
</EmpJob>
</EmpJob>
I created an XSLT transformation
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name = "custReturnDate" />
<xsl:template match="/EmpJob">
<xsl:variable name="apos">'</xsl:variable>
<xsl:variable name="payrecur">'1010','6097' </xsl:variable>
<mainroot>
<xsl:for-each select="EmpJob">
<root>
<__metadata>
<uri><xsl:value-of select="concat('EmpCompensation(seqNumber=1L,startDate=datetime',$apos ,$custReturnDate, $apos,',userId=',$apos,userId,$apos,')')"/></uri>
</__metadata>
<customDouble13><xsl:value-of select="sum(EmpPayCompRecurring[$payrecur]/paycompvalue) "/></customDouble13>
</root>
</xsl:for-each>
</mainroot>
</xsl:template>
</xsl:stylesheet>
"payrecur" should be the parameter later on according to this parameter I should sum node values.
P.S. I can change the parameter to anything because I am calling the template from code.
Given XSLT 2 or 3, I would declare the parameter as a sequence of strings or integers and then compare sum(EmpPayCompRecurring[payComponent = $payrecur]/paycompvalue).
So declare e.g <xsl:param name="payrecur" as="xs:string*" select="'1010','6097'"/>.
A minimal stylesheet would be
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:param name = "custReturnDate" />
<xsl:param name="payrecur" as="xs:string*" select="'1010','6097'"/>
<xsl:output indent="yes"/>
<xsl:template match="/EmpJob">
<xsl:variable name="apos">'</xsl:variable>
<mainroot>
<xsl:for-each select="EmpJob">
<root>
<__metadata>
<uri><xsl:value-of select="concat('EmpCompensation(seqNumber=1L,startDate=datetime',$apos ,$custReturnDate, $apos,',userId=',$apos,userId,$apos,')')"/></uri>
</__metadata>
<customDouble13>
<xsl:value-of select="sum(EmpPayCompRecurring[payComponent = $payrecur]/paycompvalue)"/>
</customDouble13>
</root>
</xsl:for-each>
</mainroot>
</xsl:template>
</xsl:stylesheet>
At https://xsltfiddle.liberty-development.net/bEzkTcM I get <customDouble13>3125.67</customDouble13>. You will probably want to add exclude-result-prefixes="#all" on the xsl:stylesheet element.
Hello everyone in case anyone faces this type of scenario as I mentiond before I added the parameter in the header so that the XSLT template will get it. Unfortunatly the template received it as a String so I had to use the function tokenize() to convert the parameter to array then I could use the template provided by Martin Honnen.
This scenario will work Apache Camel as well as SAP CPI
Best Regards
Ibrahim

Replace xsi:nil=“true” with open and close tags

I need to do the following transformation in order to get a message pass through a integration broker which does not understand xsi:nil=“true”. I know that for strings having some thing like <abc></abc> is not same as <abc xsi:nil=“true”/> but I have no option.
My input XML:
<PART>
<LENGTH_UOM xsi:nil="1"/>
<WIDTH xsi:nil="1"/>
</PART>
Expected outcome:
<PART>
<LENGTH_UOM><LENGTH_UOM>
<WIDTH></WIDTH>
</PART>
Please let me know your suggestions.
To remove all xsi:nil attributes combine the identity template with an empty template matching xsi:nil.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://xsi.com">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="node()|#*"> <!-- identity template -->
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="#xsi:nil" /> <!-- empty template -->
</xsl:stylesheet>
If you only want to remove those whose value is true use the following empty template instead.
<xsl:template match="#xsi:nil[.='1' or .='true']" />
Concerning the opening and closing tag topic I suggest reading this SO question in which Martin Honnen states that (in the comments of the answer):
I am afraid whether an empty element is marked up as or or is not something that matters with XML and is usually not something you can control with XSLT processors.

Can't read the namespaces and attribute in xslt

I know that this is simple problem. I'm still learning and getting familiarize with the XSLT coding. I have a problem in my XSLT and I don't know if I did it correctly. I need to get the value from the input file and store it in the new element tag name and that I don't need to populate the namespaces and attributes what's on the parent root element. I did a research about this and I saw many references but I can't apply it. The XSLT(v02) that I made is working fine (just copy from the references) if the root element doesn't have any namespaces and attributes. But, when I put a namespaces and attribute, no output populated.
Input file
<Root xmlns="http://abcd.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" releaseID="9.2" versionID="2.12.3" xsi:schemaLocation="abcd.com abcd.xsd">
<Element>
<Field>AAAAA</Field>
</Element>
<Element>
<Field>BBBBB</Field>
</Element>
<Element>
<Field>CCCCC</Field>
</Element>
xslt file
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<NewRecord>
<xsl:for-each select="Root/Element">
<NewTransaction>
<Position>
<xsl:value-of select="position()"/>
</Position>
<TransactionID>
<xsl:value-of select="Field"/>
</TransactionID>
</NewTransaction>
</xsl:for-each>
</NewRecord>
</xsl:template>
output generated
<NewRecord/>
My expected output should look like this:
<NewRecord>
<NewTransaction>
<Position>1</Position>
<TransactionID>AAAAA</TransactionID>
</NewTransaction>
<NewTransaction>
<Position>2</Position>
<TransactionID>BBBBB</TransactionID>
</NewTransaction>
<NewTransaction>
<Position>3</Position>
<TransactionID>CCCCC</TransactionID>
</NewTransaction>
I think the problem is in the <xsl:template match="/">, I'm still confused on the nodes that I need to put. Thank you for your help.
If you are really using XSLT 2.0, you only need to add:
xpath-default-namespace="http://abcd.com"
to the stylesheet tag, and leave everything else as is.
If you're using xslt 1.0, you'll have to declare the same namespace in the stylesheet, and use the prefix you map to the namespace to qualify the names of the elements:
The prefix can be whatever you want. I picked abcd to match your example, but it could be any legal identifier.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:abcd="http://abcd.com">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<NewRecord>
<xsl:for-each select="abcd:Root/abcd:Element">
<NewTransaction>
<Position>
<xsl:value-of select="position()"/>
</Position>
<TransactionID>
<xsl:value-of select="abcd:Field"/>
</TransactionID>
</NewTransaction>
</xsl:for-each>
</NewRecord>
</xsl:template>
</xsl:stylesheet>

with xslt how do I use choose/when to remove elements from document

Here is my example document
<a >
<b flag='foo'>
<c/>
<d/>
</b>
</a>
I am looking to remove the "c" element only when the flag attribute on b is 'bar'.
Ie if flag='foo' the "c" element should not be removed. I do not currently have an xsl debugging tool on my pc, and have been unable to find an online tool that shows xslt error information, and have been running the following test xsl transform on http://xslttest.appspot.com/ :
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" version="1.0" encoding="ISO-8859-1"/>
<!--Identity template to copy all content by default-->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:choose>
<xsl:when test="/a/b[#flag='bar']">
<xsl:template match="/a/b/c"/>
</xsl:when>
</xsl:choose>
</xsl:stylesheet>
When I run this I get Error: Failed to compile stylesheet. 3 errors detected.
I'm looking for help (1) fixing the problems with the xsl code and (2) anything out there like jsfiddle for xsl that can debug/test fragements of xsl code.
You can't put a choose outside a template, but you don't need to - you can use predicates in match expressions so just declare your no-op template to match the elements you want to remove:
<xsl:template match="b[#flag='bar']/c" />
or more generally, if the parent of the c element might have various names
<xsl:template match="c[../#flag='bar']" />
or
<xsl:template match="*[#flag='bar']/c" />