I'd like to include contents of an XML document into another XML document and transform it via xmlstarlet+XSLT. I'm trying to use XInclude. (A newbie to both XInclude and XSLT, I am.) The xmlstarlet, though, won't process the included XML doc, it just leaves the inclusion node there untouched.
File a.xml:
<?xml version="1.0" ?>
<doc xmlns:xi="http://www.w3.org/2001/XInclude">
a
<xi:include href="b.xml" />
b
</doc>
File b.xml:
<?xml version="1.0" ?>
<snippet>
c
</snippet>
The x.xsl "pass-through" template:
<?xml version="1.0" encoding="windows-1250" ?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" />
<xsl:template match="/">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:transform>
The command line to be run:
xmlstarlet tr x.xsl a.xml
And the expected output would be something along the lines of:
<?xml version="1.0" ?>
<doc xmlns:xi="http://www.w3.org/2001/XInclude">
a
<snippet>
c
</snippet>
b
</doc>
Yet, the result I get is:
<?xml version="1.0"?>
<doc xmlns:xi="http://www.w3.org/2001/XInclude">
a
<xi:include href="b.xml"/>
b
</doc>
Now, what am I doing wrong?
As npostavs already suggested, xmlstarlet does not XInclude documents by default, you need to explicitly mention this as --xinclude. Then, the result is the one you expected:
$ xml tr --xinclude x.xsl a.xml
<?xml version="1.0"?>
<doc xmlns:xi="http://www.w3.org/2001/XInclude">
a
<snippet>
c
</snippet>
b
</doc>
Except for the xi: namespace declaration, which you cannot eliminate with XSLT 1.0 and a simple <xsl:copy-of select="."/>. If that's an issue, the stylesheet becomes a little more complicated, since copy-namespaces="no" is not available in XSLT 1.0:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" />
<xsl:template match="/">
<xsl:apply-templates select="." mode="copy-no-namespaces"/>
</xsl:template>
<xsl:template match="*" mode="copy-no-namespaces">
<xsl:element name="{local-name()}" namespace="{namespace-uri()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="node()" mode="copy-no-namespaces"/>
</xsl:element>
</xsl:template>
<xsl:template match="comment()| processing-instruction()" mode="copy-no-namespaces">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
This is the standard approach to mimic copy-namespaces="no" in XSLT 1.0, as described here by Michael Kay. Then, the result will be
$ xml tr --xinclude x.xsl a.xml
<?xml version="1.0"?>
<doc>
a
<snippet>
c
</snippet>
b
</doc>
Related
I have two different node-sets that contain one or more elements with the same name.
I want to select these same-named elements by using the intersect operation in XPath 2.0 and XPath 1.0 as well.
This is the sample code i have tried.
Input:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<child1>
<a />
<b />
<d />
</child1>
<child2>
<c />
<a />
<d />
</child2>
</root>
The code I tried
Xpath 1.0:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<!-- TODO: Auto-generated template -->
<xsl:variable name="ns2" select="/root/child2/child::*"/>
<xsl:copy-of select="/root/child1/child[.=$ns2]"/>
</xsl:template>
</xsl:stylesheet>
Xpath 2.0:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<!-- TODO: Auto-generated template -->
<xsl:variable name="ns1" select="/root/child1/child::*"/>
<xsl:variable name="ns2" select="/root/child2/child::*"/>
<xsl:copy-of select="$ns1 intersect $ns2"/>
</xsl:template>
</xsl:stylesheet>
Issue: I am getting the empty result.
Expected result:
<?xml version="1.0" encoding="UTF-8"?>
<a/>
<d/>
Please suggest what I am missing.
I have tried the set operation intersection for two different noteset.I have attached my code sample below,
You are not really doing "intersection" here, because all of the child elements of child1 are different to the child elements of child2. Just because two elements share the same name, that does not make them the same element.
What it looks like you are after is elements under child1 that have the same name as an element under child2
If you want your XSLT 1.0 solution to get results, you need to change the xsl:copy-of to this
<xsl:copy-of select="/root/child1/child::*[.=$ns2]"/>
Or this...
<xsl:copy-of select="/root/child1/*[.=$ns2]"/>
However, this will return all child1 child elements, because you are checking the value of the elements are the same, not the name. One way to do it is this...
<xsl:for-each select="/root/child1/*">
<xsl:variable name="name" select="local-name()" />
<xsl:copy-of select=".[$ns2[local-name() = $name]]" />
</xsl:for-each>
Alternatively, define a key like so:
<xsl:key name="child2" match="child2/*" use="local-name()" />
Then you could do this...
<xsl:copy-of select="/root/child1/*[key('child2', local-name())]"/>
In XSLT 2.0, you could do something like this....
<xsl:copy-of select="$ns1[some $name in $ns2/local-name() satisfies $name=local-name()]"/>
Not repeating Tim's correct remark, but just to the problem:
XSLT 1.0 (Not possible with a single XPath expression, unless an XSLT standard function, such as current() is used):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="child1/*">
<xsl:copy-of select="self::*[../../child2/*[name() = name(current())]]"/>
</xsl:template>
</xsl:stylesheet>
XPath 2.0:
Use:
/*/child1/*[name() = /*/child2/*/name()]
This can be verified with the below XSLT 2.0 transformation which evaluates the expression and outputs the selected nodes:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:sequence select=
"/*/child1/*[name() = /*/child2/*/name()]"/>
</xsl:template>
</xsl:stylesheet>
When applied on the provided XML document:
<root>
<child1>
<a />
<b />
<d />
</child1>
<child2>
<c />
<a />
<d />
</child2>
</root>
both transformations produce the wanted, correct result:
<a/>
<d/>
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>
I am new to XSLT transformation and cant seem to get the following result
I have:
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.002.001.03" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.002.001.03 pain.002.001.03.xsd">
<CstmrPmtStsRpt>
need this:
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.002.001.03">
<CstmrPmtStsRpt>
Where the rest of the document should remain the same.
I have put together the following XSLT:
<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output indent="yes" method="xml" encoding="utf-8" />
<!-- template to copy elements -->
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#xsi:schemaLocation">
</xsl:template>
<!-- template to copy attributes -->
<xsl:template match="#*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<!-- template to copy the rest of the nodes -->
<xsl:template match="comment() | text() | processing-instruction()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
but get this
<?xml version="1.0" encoding="utf-8"?>
<Document>
<CstmrPmtStsRpt>
Any tips would be appreciated.
I am writing xslt for filtering the data based on one of the fields.
Here is my input xml:
<?xml version="1.0" encoding="UTF-8"?>
<Consumer>
<header>
<msfnm>MSFNM</msfnm>
<mslnm>MSLNM</mslnm>
<msmnm>MSMNM</msmnm>
<msssn>MSSSN</msssn>
<csscstno>CSSCSTNO</csscstno>
<msact>MSACT</msact>
</header>
<data>
<msfnm>Nitin</msfnm>
<mslnm>Jain</mslnm>
<msmnm/>
<msssn>123</msssn>
<csscstno>111</csscstno>
<msact>1234</msact>
</data>
<data>
<msfnm>Nitin1</msfnm>
<mslnm>Jain1</mslnm>
<msmnm>L1</msmnm>
<msssn>1233</msssn>
<csscstno>111</csscstno>
<msact>1233556</msact>
</data>
<data>
<msfnm>Nitin2</msfnm>
<mslnm>Jain2</mslnm>
<msmnm>L1</msmnm>
<msssn>1234</msssn>
<csscstno>123</csscstno>
<msact>12334256</msact>
</data>
<data>
<msfnm>Nitin</msfnm>
<mslnm>Jain</mslnm>
<msmnm/>
<msssn>123</msssn>
<csscstno>111</csscstno>
<msact>1234</msact>
</data>
and I want my output xml should be like
<?xml version="1.0" encoding="UTF-8"?>
<Consumer>
<data>
<msfnm>Nitin</msfnm>
<mslnm>Jain</mslnm>
<msmnm/>
<msssn>123</msssn>
<csscstno>111</csscstno>
<msact>1234</msact>
</data>
<data>
<msfnm>Nitin2</msfnm>
<mslnm>Jain2</mslnm>
<msmnm>L1</msmnm>
<msssn>1234</msssn>
<csscstno>123</csscstno>
<msact>12334256</msact>
</data>
</Consumer>
Condition: basically what I want, to take first occurrence of csscstno only. If in next occurrence, csscstno is same, then the whole set should be rejected.
My xslt:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xml>
<xsl:for-each select="/Consumer/data">
<Consumer>
<msfnm><xsl:value-of select="msfnm"/></msfnm>
<mslnm><xsl:value-of select="mslnm"/></mslnm>
<msmnm><xsl:value-of select="msmnm"/></msmnm>
<msssn><xsl:value-of select="msssn"/></msssn>
<xsl:if test="position()=1">
<csscstno><xsl:value-of select="csscstno"/></csscstno>
</xsl:if>
<msact><xsl:value-of select="msact"/></msact>
</Consumer>
</xsl:for-each>
</xml>
</xsl:template>
</xsl:stylesheet>
This is not working. Let me know, what I am doing wrong here.
You can use keys. Just like the stylesheet below:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes" />
<!-- create a key for your target node -->
<xsl:key name="Customer_No" match="csscstno" use="."/>
<!-- copy only the 1st matched keys in the output -->
<xsl:template match="/">
<Consumer>
<xsl:copy-of select="Consumer/data[count(csscstno | key('Customer_No', csscstno)[1]) = 1]"/>
<!-- or you can use the following line instead
<xsl:copy-of select="Consumer/data[generate-id(csscstno) = generate-id(key('Customer_No', csscstno)[1])]"/>
-->
</Consumer>
</xsl:template>
</xsl:transform>
when this is applied to your input XML, the result is:
<?xml version="1.0" encoding="utf-8"?>
<Consumer>
<data>
<msfnm>Nitin</msfnm>
<mslnm>Jain</mslnm>
<msmnm/>
<msssn>123</msssn>
<csscstno>111</csscstno>
<msact>1234</msact>
</data>
<data>
<msfnm>Nitin2</msfnm>
<mslnm>Jain2</mslnm>
<msmnm>L1</msmnm>
<msssn>1234</msssn>
<csscstno>123</csscstno>
<msact>12334256</msact>
</data>
</Consumer>
Or, you can use keys for grouping data by csscstno to get the first occurence. Also, You can use a copy-of to select "data":
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="dataByCsscstno" match = "data" use = "csscstno"/>
<xsl:template match="/">
<Consumer>
<xsl:copy-of select="/Consumer/data[generate-id() = generate-id(key('dataByCsscstno', csscstno)[1])]"/>
</Consumer>
</xsl:template>
</xsl:stylesheet>
Eventually I have found another way, which is working as expected.
Here is my xslt:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xml>
<xsl:for-each-group select="/Consumer/data" group-by="csscstno">
<xsl:copy-of select="current-group()[1]"/>
</xsl:for-each-group>
</xml>
</xsl:template>
</xsl:stylesheet>
My real code is not this, but the problem that i am stating here applies to my real code.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book.child.1>
<title>charithram</title>
<author>sarika</author>
</book.child.1>
<book.child.2>
<title>doublebell</title>
<author>psudarsanan</author>
</book.child.2>
</books>
XSLT 1:
<?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" encoding="UTF-8"
indent="yes" />
<xsl:template match="/">
<xsl:for-each select="books/*">
<newbook>
<title>
<xsl:value-of select="title" />
</title>
</newbook>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
There in no output generated for this XSLT. I am trying using online tool: http://www.freeformatter.com/xsl-transformer.html
I could not understand what was wrong, Finally when I modified the XSLT like as written below,
XSLT 2:
<?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" encoding="UTF-8"
indent="yes" />
<xsl:template match="/">
<mytag>
<xsl:for-each select="books/*">
<newbook>
<title>
<xsl:value-of select="title" />
</title>
</newbook>
</xsl:for-each>
</mytag>
</xsl:template>
</xsl:stylesheet>
The output is generated in this case:
outputXML:
<?xml version="1.0" encoding="UTF-8"?>
<mytag>
<newbook>
<title>charithram</title>
</newbook>
<newbook>
<title>doublebell</title>
</newbook>
</mytag>
Can you please explain why is this behavior?
Also I don't know how exactly to ask this question, so please edit or let me know if i need to change the question title.
Your first XSLT will theoretically produce the output
<?xml version="1.0" encoding="UTF-8"?>
<newbook>
<title>charithram</title>
</newbook>
<newbook>
<title>doublebell</title>
</newbook>
But that output is not valid XML, because it has 2 root tags, which is not well-formed XML.
In this situation, you have probably the following choices
specify a root element like you did in XSLT 2
change the output from XML to TEXT, but be aware that any XML program will not be able to read the output