XSLT Transformation connect nodes with same names with parent node - xslt

I want to transform the following XML-data to the desired output shown below - without success until now. So I hope of an input from an expert relating the xsl. Thank you in advance.
Input XML-Data
<?xml version="1.0" encoding="utf-8"?>
<pages>
<page number="1">
<pageArticles>
<articleID id="100"/>
<articleID id="200"/>
<articleID id="300"/>
</pageArticles>
</page>
<page number="5">
<pageArticles/>
</page>
<page number="9">
<pageArticles>
<articleID id="400"/>
<articleID id="500"/>
</pageArticles>
</page>
</pages>
Desired Ouput
<?xml version="1.0" encoding="utf-8"?>
<pages>
<page>
<number>1</number>
<articleID>100</articleID>
</page>
<page>
<number>1</number>
<articleID>200</articleID>
</page>
<page>
<number>1</number>
<articleID>300</articleID>
</page>
<page>
<number>5</number>
<articleID></articleID>
</page>
<page>
<number>9</number>
<articleID>400</articleID>
</page>
<page>
<number>9</number>
<articleID>500</articleID>
</page>
</pages>
Many thanks in advance for any assistance!
Urs

This XSLT-1.0 stylesheet does the job:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="text()" />
<xsl:template match="/pages">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="//articleID | pageArticles[not(*)]">
<page>
<number><xsl:value-of select="ancestor::page/#number[1]" /></number>
<articleID><xsl:value-of select="#id" /></articleID>
</page>
</xsl:template>
</xsl:stylesheet>
Output is:
<pages>
<page>
<number>1</number>
<articleID>100</articleID>
</page>
<page>
<number>1</number>
<articleID>200</articleID>
</page>
<page>
<number>1</number>
<articleID>300</articleID>
</page>
<page>
<number>5</number>
<articleID/>
</page>
<page>
<number>9</number>
<articleID>400</articleID>
</page>
<page>
<number>9</number>
<articleID>500</articleID>
</page>
</pages>

Write a template for those articleID elements to collect the number from the grandparent and add a template for empty pageArticles:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
expand-text="yes"
version="3.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="pages">
<xsl:copy>
<xsl:apply-templates select="page/pageArticles"/>
</xsl:copy>
</xsl:template>
<xsl:template match="pageArticles[not(has-children())]">
<page>
<number>{../#number}</number>
<articleID/>
</page>
</xsl:template>
<xsl:template match="articleID">
<page>
<number>{../../#number}</number>
<articleID>{#id}</articleID>
</page>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bFDb2BT and the above is XSLT 3. In earlier version of XSLT you have to replace the text value templates (e.g. {../#number}) with xsl:value-of (e.g. <xsl:value-of select="../#number"/>) and the predicate [not(has-children())] with [not(node()].

Related

XSLT transformation validate XSI:type (or) element node exists

I have an input XML, which needs to be filtered if element node exists 'ns1:getGenResponse' (or) validate with xsi:type = "Gen" of 'multiRef' element
If either one of the condition successful then I can process 'multiRef' records.
Input XML:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
<soapenv:Body>
<ns1:getGenResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://service.pen.eewe.en.de>
<ns1:getGenReturn xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="soapenc:Array" soapenc:arrayType="xsd:anyType[2]">
<item href="#id0" />
<item href="#id1" />
</ns1:getGenReturn>
</ns1:getGenResponse>
<multiRef xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" id="id0" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns2:Gen">
<name xsi:type="xsd:string">ulm</name>
<mail xsi:type="xsd:string">ulm#gmail.com</mail>
</multiRef>
<multiRef xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" id="id1" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns3:Gen">
<name xsi:type="xsd:string">ABC</name>
<mail xsi:type="xsd:string">abc#gmail.com</mail>
</multiRef>
</soapenv:Body>
</soapenv:Envelope>
Trying with node exist 'ns1:getGenResponse':
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:response="http://tempuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
exclude-result-prefixes="soap response"
>
<!-- Output -->
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<getGenResponse>
<xsl:for-each select="//soap:Body[ns1:getGenResponse]/multiRef">
<getGenReturn>
<name>
<xsl:value-of select="name" />
</name>
<mail>
<xsl:value-of select="mail" />
</mail>
</getGenReturn>
</xsl:for-each>
</getGenResponse>
</xsl:template>
</xsl:stylesheet>
With this XSLT, I can generate blank . I can't able to generate my desire output.
I am also trying to validate the data with xsi:type of 'multiRef' element node
with below code, but I am unable to execute the XSLT
**Trying to validate with xsi:type ="Gen"**
<xsl:stylesheet version="1.0" xmlns:response="http://tempuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="soap response">
<!-- Output -->
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<getGenResponse>
<xsl:for-each select="//soap:Body/multiRef[substring- after(#xsi:type, ':')='Gen']">
<getGenReturn>
<name><xsl:value-of select="name"/></name>
<mail><xsl:value-of select="mail"/></mail>
</getGenReturn>
</xsl:for-each>
</getGenResponse>
</xsl:template>
</xsl:stylesheet>
Output expects:
**Output Expected**
<?xml version="1.0" encoding="UTF-8"?>
<getGenResponse>
<getGenReturn>
<name> ULM </name>
<mail>ulm#gmail.com<mail>
</getGenReturn>
<getGenReturn>
<name>ABC</name>
<mail>abc#gmail.com<mail>
</getGenReturn>
/getGenResponse>
Thank you very much.
yes, I declared xmlns:ns1="http://service.pen.eewe.en.de" in the XSLT. now I am able to execute the code with desired result.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:response="http://tempuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
exclude-result-prefixes="soap response" xmlns:ns1="http://service.pen.eewe.en.de"
>
<!-- Output -->
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<getGenResponse>
<xsl:for-each select="//soap:Body[ns1:getGenResponse]/multiRef">
<getGenReturn>
<name>
<xsl:value-of select="name" />
</name>
<mail>
<xsl:value-of select="mail" />
</mail>
</getGenReturn>
</xsl:for-each>
</getGenResponse>
</xsl:template>
</xsl:stylesheet>

XSLT for-each-group from variable does not work

for-each-group from XSLT 2.0 works as expected from a file but not from a variable.
Have this file:
~$ cat test.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<delimiter/>
<c>A</c><c>B</c>
<delimiter/>
<c>C</c>
</root>
Using stylesheet for grouping this file:
<?xml version="1.0" encoding="UTF-8"?>
<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" omit-xml-declaration="no" />
<xsl:template match="*">
<!-- variable not used for file test -->
<xsl:variable name="fields">
<root>
<delimiter/>
<c>A</c><c>B</c>
<delimiter/>
<c>C</c>
</root>
</xsl:variable>
<xsl:for-each-group select="*" group-starting-with="delimiter">
<field>
<xsl:for-each select="current-group()">
<xsl:value-of select="self::c"/>
</xsl:for-each>
</field>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
I get the result I want:
<?xml version="1.0" encoding="UTF-8"?>
<field>AB</field>
<field>C</field>
Trying to group the variable name="fields" with:
<xsl:for-each-group select="$fields/*" group-starting-with="delimiter">
I get the result:
<?xml version="1.0" encoding="UTF-8"?>
<field/>
Why does for-each-group works on a file but not from a variable?
The variable fields is a document-node(), you can define the type of the variable to be element()
<xsl:variable name="fields" as="element()">
<root>
<delimiter/>
<c>A</c><c>B</c>
<delimiter/>
<c>C</c>
</root>
</xsl:variable>

Select value of element which is referenced by other

I have (this is an example) the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<body>
<list>
<toot id="1">
<value>A</value>
</toot>
<toot id="2">
<value>B</value>
</toot>
<toot id="3">
<value>C</value>
</toot>
<toot id="4">
<value>D</value>
</toot>
</list>
<otherlist>
<foo>
<value ref="2" />
</foo>
<foo>
<value ref="3" />
</foo>
</otherlist>
</body>
And the following XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/body">
<xsl:apply-templates select="otherlist"/>
</xsl:template>
<xsl:template match="otherlist">
<xsl:for-each select="foo">
<result>
<value><xsl:value-of select="/body/list/toot[#id=value/#ref]/value" /></value><!-- This is the important -->
<ref><xsl:value-of select="value/#ref" /></ref>
</result>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
And this is the result when it make/transform the xml:
<?xml version="1.0" encoding="UTF-8"?>
<result>
<value/>
<ref>2</ref>
</result>
<result>
<value/>
<ref>3</ref>
</result>
And the problem is that is empty. What I wanna get is:
<?xml version="1.0" encoding="UTF-8"?>
<result>
<value>B</value>
<ref>2</ref>
</result>
<result>
<value>C</value>
<ref>3</ref>
</result>
I think the problem is the XPath /body/list/toot[#id=value/#ref]/value specifically the condition [#id=value/#ref]. Is not correct it? How to use a value of other element witch is reference by the current one?
Yes the problem is in XPath where the context is changing so you are actually looking for toot element which has #id attribute and value child with #ref attribute (which is actually child of foo) and these two are equal.
You can employ current() function to make it working
<xsl:value-of select="/body/list/toot[#id=current()/value/#ref]/value"/>
Or you can store value of #ref into a variable and use this variable in predicate
<xsl:variable name="tmpRef" select="value/#ref" />
<xsl:value-of select="/body/list/toot[#id=$tmpRef]/value"/>

Split first child node from the rest using XSLT

Can anyone help me to split the child nodes using XSLT. Only child1 should be split from rest of the children.
<parent>
<child1>
<child2>
<child3>
<parent>
output:
<parent>
<child1>
<element>
<child2>
<child3>
</element>
<parent>
Here's a simple solution.
When this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<parent>
<xsl:copy-of select="child1"/>
<element>
<xsl:copy-of select="*[not(self::child1)]"/>
</element>
</parent>
</xsl:template>
</xsl:stylesheet>
...is applied against the provided XML:
<parent>
<child1/>
<child2/>
<child3/>
</parent>
...the wanted result is produced:
<parent>
<child1/>
<element>
<child2/>
<child3/>
</element>
</parent>
If I understand correctly, you have xml like this:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="data.xsl"?>
<data>
<parent>
<child>123</child>
<child>345</child>
<child>678</child>
</parent>
</data>
And want to show it like this:
<span>123</span>
<ul>
<li>345</li>
<li>678</li>
</ul>
If it is right, use next code:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<html>
<head>
<title></title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="parent">
<strong><xsl:value-of select="child[1]/text()"/></strong>
<ul>
<xsl:for-each select="child">
<xsl:if test="position() != 1">
<li><xsl:value-of select="text()"/></li>
</xsl:if>
</xsl:for-each>
</ul>
</xsl:template>
</xsl:stylesheet>

Excluding Namespaces in XML results

I am trying to figure out how best I can process the XML file below so the resulting XML file excludes namespace declarations.
XML Input
<?xml version="1.0" encoding="UTF-8"?>
<page xmlns:b="http://book.com/" xmlns:p="http://page.com/">
<b:title>Book Title</b:title>
<p:number>page001</p:number>
<p:number>page002</p:number>
<p:number>page001</p:number>
<p:number>page002</p:number>
</page>
Current XSL File
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:b="http://book.com/"
xmlns:p="http://page.com/"
>
<xsl:output method="xml" indent="yes" encoding="UTF-8" />
<xsl:template match="resource">
<xsl:apply-templates select="b:title" />
<xsl:apply-templates select="p:number" />
</xsl:template>
<xsl:template match="b:title">
<title exclude-result-prefixes="#all">
<xsl:value-of select="." />
</title>
</xsl:template>
<xsl:template match="p:number">
<page exclude-result-prefixes="#all">
<xsl:value-of select="." />
</page>
</xsl:template>
</xsl:stylesheet>
Current Output
<title xmlns:b="http://book.com/" xmlns:p="http://page.com/" exclude-result-prefixes="#all">Book Title</title>
<page xmlns:b="http://book.com/" xmlns:p="http://page.com/" exclude-result-prefixes="#all">page001</page>
<page xmlns:b="http://book.com/" xmlns:p="http://page.com/" exclude-result-prefixes="#all">page002</page>
<page xmlns:b="http://book.com/" xmlns:p="http://page.com/" exclude-result-prefixes="#all">page001</page>
<page xmlns:b="http://book.com/" xmlns:p="http://page.com/" exclude-result-prefixes="#all">page002</page>
Desired Output
<?xml version="1.0" encoding="UTF-8"?>
<title>Book Title</title>
<page>page001</page>
<page>page002</page>
<page>page001</page>
<page>page002</page>
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="*[local-name()='number']">
<page>
<xsl:value-of select="."/>
</page>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<page xmlns:b="http://book.com/" xmlns:p="http://page.com/">
<b:title>Book Title</b:title>
<p:number>page001</p:number>
<p:number>page002</p:number>
<p:number>page001</p:number>
<p:number>page002</p:number>
</page>
produces the wanted, correct result:
<page>
<title>Book Title</title>
<page>page001</page>
<page>page002</page>
<page>page001</page>
<page>page002</page>
</page>
Explanation:
Use of the xsl:element instruction to create (not copy!) a new element with name that is the local-name() of the matched element.
A template matching elements with local-name() number to "rename" them to page
Use the exclude-result-prefixes attribute on the xsl:stylesheet element.
In your case, something like:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:b="http://book.com/"
xmlns:p="http://page.com/"
exclude-result-prefixes="b p"
>
<xsl:output method="xml" indent="yes" encoding="UTF-8" />
<xsl:template match="resource">
<xsl:apply-templates select="b:title" />
<xsl:apply-templates select="p:number" />
</xsl:template>
<xsl:template match="b:title">
<title>
<xsl:value-of select="." />
</title>
</xsl:template>
<xsl:template match="p:number">
<page>
<xsl:value-of select="." />
</page>
</xsl:template>
</xsl:stylesheet>