Reputation: 86885
https://xsltfiddle.liberty-development.net/bFDb2C4
I want to convert the following xml to csv:
<employees>
<global>
<attr>test</attr>
</global>
<employee>
<details>
<name>Joe</name>
<age>34</age>
<stage>
<type code="A" count="1"/>
<type code="B" count="2"/>
<type code="C" count="3"/>
</stage>
</details>
</employee>
<employee>
<details>
<age>24</age>
<name>Sam</name>
</details>
<stage>
<type code="A" count="1"/>
</stage>
</employee>
</employees>
Result should be:
test;Joe;34;A;1
test;Joe;34;B;2
test;Joe;34;C;3
test;Sam;24;A;1
Therefore I thought I could just match the deepest level (which is type
here), and append all the ancestor::
. The following xslt works in general, but also outputs a lot of "noise" around the desired result (see the xsltfidde):
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" omit-xml-declaration="yes" indent="no"/>
<xsl:template match="type">
<xsl:value-of select="ancestor::employees/global/attr,
ancestor::employee/details/name,
ancestor::employee/details/age,
@code,
@count"
separator=";" />
</xsl:template>
</xsl:stylesheet>
Question: where does the "noise" come from? How can I just receive the csv lines?
This is the current result:
test
Joe
34
test;Joe;34;A;1
test;Joe;34;B;2
test;Joe;34;C;3
24
Sam
test;Sam;24;A;1
Upvotes: 0
Views: 1716
Reputation: 70648
The template you have is fine, and doing its job. The issue is when XSLT starts processing, it looks for a template matching the document element /
, of which there is none in your template. When XSLT is looking for a template and there is no matching one in your template, it uses built-in templates.
Essentially, it will navigate all over the XML and output text nodes where it find it. So, you get a lot of text output before it finally gets to your template matching type
.
There are couple of solutions. One is to have a template matching /
and explicitly select the type
nodes
<xsl:template match="/">
<xsl:apply-templates select="//type" />
</xsl:template>
Another option is to have a template matching text()
nodes, to ignore them, thus overriding the default template behaviour
<xsl:template match="text()" />
However, as you are using XSLT 3.0, you can also do this instead of adding a new template to specify the action to take on no matching template
<xsl:mode on-no-match="shallow-skip"/>
Note, in all cases, you will need to output a line break in your existing template.
Try this XSLT
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" omit-xml-declaration="yes" indent="no"/>
<xsl:mode on-no-match="shallow-skip"/>
<xsl:template match="type">
<xsl:value-of select="ancestor::global/attr,
ancestor::employee/details/name,
ancestor::employee/details/age,
@code,
@count"
separator=";" />
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1