membersound
membersound

Reputation: 86885

How to access ancestor elements in XSLT properly?

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

Answers (1)

Tim C
Tim C

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>&#10;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

Upvotes: 1

Related Questions