XSLT select unique node - xslt

this is driving me crazy, for xslt newbie like me.
Input:
<root>
<a><name>kyle</name></a>
<b><name>stan</name></b>
<b><name>wendy</name></b>
<b><name>cece</name></b>
</root>
Expected output:
<root>
<a><name>kyle</name></a>
<b><name>stan</name></b>
</root>
I was asked to return first unique node under 'root', how do I do that?
Either xslt 1.0 or 2.0 is fine.
Thank you so much!!!!

XSLT 2.0 solution :
<?xml version="2.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<root>
<xsl:for-each-group select="root/*" group-by="local-name()">
<xsl:copy-of select="."/>
</xsl:for-each-group>
</root>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<a>
<name>kyle</name>
</a>
<b>
<name>stan</name>
</b>
</root>

You can match any element that has a preceding sibling with the same name and not output anything.
Example XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*/*[preceding-sibling::*[name() = current()/name()]]"/>
</xsl:stylesheet>
Output (using Saxon 9 HE):
<root>
<a>
<name>kyle</name>
</a>
<b>
<name>stan</name>
</b>
</root>

Related

Use XSLT to wrap all nodes inside newly-inserted node

How do I use XSLT to transform this XML:
<root>
<base_currency_code>ZAR</base_currency_code>
<base_discount_amount>0</base_discount_amount>
<base_discount_invoiced>0</base_discount_invoiced>
</root>
into this:
<root>
<items>
<base_currency_code>ZAR</base_currency_code>
<base_discount_amount>0</base_discount_amount>
<base_discount_invoiced>0</base_discount_invoiced>
</items>
</root>
This is a rather trivial problem. You should have known the answer after spending an hour or less with an XSLT tutorial:
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>
<items>
<xsl:copy-of select="*"/>
</items>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

XSLT - add prefix for namespace

Here's my input XML:
<?xml version="1.0" encoding="UTF-8"?>
<Sync
xmlns="http://schema.infor.com/InforOAGIS/2" languageCode="en-US" versionID="2.8.0">
<Data>
<ID>0001</ID>
<Text>ABCD</Text>
</Data>
</Sync>
And here's my expected outcome:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:Sync xmlns:ns0="http://schema.infor.com/InforOAGIS/2"
languageCode="en-US"
versionID="2.8.0">
<DataArea xmlns:dns="http://schema.infor.com/InforOAGIS/2" xmlns="">
<ID>0001</ID>
<Text>ABCD</Text>
</DataArea>
</ns0:Sync>
My current XSLT as below (https://xsltfiddle.liberty-development.net/nbiE19N).
There are 2 problems:
I have the extra xmlns="" in DataArea element. I only want to add the dns namespace.
I cannot add the ns0 prefix for my namespace
<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="/*:Sync">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*:Sync/*:Data">
<DataArea>
<xsl:namespace name="dns" select="'http://schema.infor.com/InforOAGIS/2'"/>
<ID>
<xsl:value-of select="/*:Sync/*:Data/*:ID"/>
</ID>
<Text>
<xsl:value-of select="/*:Sync/*:Data/*:Text"/>
</Text>
</DataArea>
</xsl:template>
</xsl:stylesheet>
Any suggestion is appreciated!
Does this return the expected result:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://schema.infor.com/InforOAGIS/2">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/Sync">
<ns0:Sync xmlns:ns0="http://schema.infor.com/InforOAGIS/2">
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</ns0:Sync>
</xsl:template>
<xsl:template match="Data">
<DataArea xmlns:dns="http://schema.infor.com/InforOAGIS/2">
<xsl:apply-templates/>
</DataArea>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
P.S. I am not sure why you need the xmlns:dns="http://schema.infor.com/InforOAGIS/2" declaration; it's not being used anywhere.

how to transform a part of a xml to a new xml without namespace?

I have trouble in transform part of a xml to a new xml without namespace.
Input xml is:
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<node1 xmlns="http://a.com">
<ServiceData>
<b:test xmlns:b="http://b.com">
<b:somtag>
</b:somtag>
</b:test>
</ServiceData>
</node1>
and what I want is:
<a>
<c>
<ServiceData>
<b:test xmlns:b="http://b.com">
<b:somtag>
</b:somtag>
</b:test>
</ServiceData>
</c>
</a>
The format I want is with no namespace for ServiceData.
Any help is appreciated, thanks.
Added, I tried to use this xsl, but I can't remove "xmlns="http://a.com""
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:test="http://a.com" exclude-result-prefixes="test">
<xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="yes" />
<xsl:template match="/">
<a><c><ServiceData><xsl:copy-of select="//test:ServiceData/*"/></ServiceData></c></a>
</xsl:template>
</xsl:stylesheet>
The result I got is:
<a>
<c>
<ServiceData>
<b:test xmlns:b="http://b.com" xmlns="http://a.com">
<b:somtag>
</b:somtag>
</b:test>
</ServiceData>
</c>
</a>
How about:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://a.com">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/node1">
<a>
<c>
<ServiceData>
<xsl:copy-of select="ServiceData/*" copy-namespaces="no" />
</ServiceData>
</c>
</a>
</xsl:template>
</xsl:stylesheet>
Added:
I assumed you could use an XSLT 2.0 processor, because your stylesheet says version="2.0". If that's not true, then you cannot use xsl:copy-of; instead, you must reconstruct the elements with their original names and namespaces:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:test="http://a.com"
exclude-result-prefixes="test">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/test:node1">
<a>
<c>
<ServiceData>
<xsl:apply-templates select="test:ServiceData/*"/>
</ServiceData>
</c>
</a>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Note:
A redundant namespace declaration should not make any difference to the receiving application. The results received here are semantically identical to the result you show in your question.

Splitting Multiline XML tag into multiple Nodes

I have a XML below, where new lines are added after each line at Note__c tag. I need to produce the XML by splitting them into multiple Note__c tags.
Input XML-
<?xml version="1.0" encoding="UTF-8"?>
<snotification>
<data>
<schema>yify-xjmoeLTbNXA560rHQ</schema>
<payload>
<Note__c>01/15/2020
123456
DFGRTE766
6tgBFR</Note__c>
<Line_Length__c>72.0</Line_Length__c>
<CreatedById>00554000003OENsAAO</CreatedById>
<Contact_Name__c/>
<Sent_By_Name__c>SBM</Sent_By_Name__c>
<CreatedDate>2020-01-15T16:10:40.551Z</CreatedDate>
<Order_Number__c>14831</Order_Number__c>
<Does_not_require_reformatting__c>false</Does_not_require_reformatting__c>
</payload>
<event>
<replayId>139219</replayId>
</event>
</data>
<channel>/event/Order_Note__e</channel>
</snotification>
Where Note__c contains multiple strings with new line added after each(except the last one)
Expected Output -
<?xml version="1.0" encoding="UTF-8"?>
<snotification>
<data>
<schema>yify-xjmoeLTbNXA560rHQ</schema>
<payload>
<Notes>
<Note__c>01/15/2020</Note__c>
<Note__c>123456</Note__c>
<Note__c>DFGRTE766</Note__c>
<Note__c>6tgBFR</Note__c>
</Notes>
<Line_Length__c>72.0</Line_Length__c>
<CreatedById>00554000003OENsAAO</CreatedById>
<Contact_Name__c/>
<Sent_By_Name__c>SBM</Sent_By_Name__c>
<CreatedDate>2020-01-15T16:10:40.551Z</CreatedDate>
<Order_Number__c>14831</Order_Number__c>
<Does_not_require_reformatting__c>false</Does_not_require_reformatting__c>
</payload>
<event>
<replayId>139219</replayId>
</event>
</data>
<channel>/event/Order_Note__e</channel>
</snotification>
I have written this XSLT but it is missing few tags under the payload element -
<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:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="snotification/data/payload">
<Notes>
<xsl:for-each select="tokenize(Note__c,'\n')">
<Note__c>
<xsl:value-of select="."/>
</Note__c>
</xsl:for-each>
</Notes>
</xsl:template>
</xsl:stylesheet>
Output of this-
<?xml version="1.0" encoding="UTF-8"?>
<snotification>
<data>
<schema>yify-xjmoeLTbNXA560rHQ</schema>
<Notes>
<Note__c>01/15/2020</Note__c>
<Note__c> 123456</Note__c>
<Note__c> DFGRTE766</Note__c>
<Note__c> 6tgBFR</Note__c>
</Notes>
<event>
<replayId>139219</replayId>
</event>
</data>
<channel>/event/Order_Note__e</channel>
</snotification>
not sure what is missing.
Thanks
Sugata
Change your XSLT to
<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:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="snotification/data/payload/Note__c">
<Notes>
<xsl:for-each select="tokenize(.,'\n')">
<Note__c>
<xsl:value-of select="normalize-space(.)"/>
</Note__c>
</xsl:for-each>
</Notes>
</xsl:template>
</xsl:stylesheet>
The output should be as desired.

xpath selection on elements with namespaces

Here's a trivial but valid Docbook article:
<?xml version="1.0" encoding="utf-8"?>
<article xmlns="http://docbook.org/ns/docbook" version="5.0">
<title>I Am Title</title>
<para>I am content.</para>
</article>
Here's a stylesheet that selects title if I remove the xmlns attribute above, and not if I leave it in:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<xsl:apply-templates select="article"/>
</xsl:template>
<xsl:template match="article">
<p><xsl:value-of select="title"/></p>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
How do I talk XPath into selecting title through article if it has that namespace attribute?
You need to add an alias for your namespace and use that alias in your XPath
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:a="http://docbook.org/ns/docbook"
exclude-result-prefixes="a"
>
<xsl:output method="html"/>
<xsl:template match="/">
<xsl:apply-templates select="a:article"/>
</xsl:template>
<xsl:template match="a:article">
<p><xsl:value-of select="a:title"/></p>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>