开发者

How to traverse a nested XML structure using templates

开发者 https://www.devze.com 2023-03-09 21:42 出处:网络
I am new to XSL and in the process of picking this up from scratch to solve a problem. I have a source XML file that contains the following structure:-

I am new to XSL and in the process of picking this up from scratch to solve a problem.

I have a source XML file that contains the following structure:-

<root>
  <Header>

  </Header>

  <DetailRecord>
    <CustomerNumber>1</CustomerNumber>
    <DetailSubRecord>
      <Address>London</Address>
    </DetailSubRecord>
    <DetailSubRecord>
      <Address>Hull</Address>
    </DetailSubRecord>

  </DetailRecord>

  <DetailRecord>
    <CustomerNumber>2</CustomerNumber>
    <DetailSubRecord>
      <Address>Birmingham</Address>
    </DetailSubRecord>
    <DetailSubRecord>
      <Address>Manchester</Address>
    </DetailSubRecord>

  </DetailRecord>
  <Footer>

  </Footer>

</root>

where there are mutiple <DetailRecord>s each with multiple <DetailSubRecord>s.

I have managed to put together an XSL that outputs a single nested set of multiple DetailReco开发者_如何学Pythonrds to a flat file but I haven't been able to puzzle out how to refer to the 2nd nested level of address records in the XSL...

Here is my XSL so far:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:strip-space elements="*"/>
  <xsl:output method="text"/>
  <xsl:variable name="spaces" select="' '"/>
  <xsl:variable name="newline">
    <xsl:text>&#10;</xsl:text>
  </xsl:variable>
  <xsl:template match="/">
    <xsl:value-of select="root/Header/HeaderField"/>
    <xsl:copy-of select="$newline"/>
    <xsl:for-each select="root/DetailRecord">
      <xsl:value-of select="CustomerNumber"/>
      <xsl:copy-of select="$newline"/>
    </xsl:for-each>
    Trailer - recordCount - <xsl:value-of select="count(root/DetailRecord)"/>
  </xsl:template>
</xsl:stylesheet>


XSLT is a functional language, not a procedural; what most newcomers to XSLT don't realise is that the XSLT processor automatically handles each node in the tree, in the order they appear in the source. Without a template to define what to do with each node however, nothing is output.

In most cases, you don't need to use <xsl:for-each> just to get the child elements processed, this is already done for you, you just need to define a template that describes how you want each element to be output. Like this:

<xsl:template match="root">
  <xsl:apply-templates />
  <xsl:text>Trailer - recordCount - </xsl:text>
  <xsl:value-of select="count(DetailRecord)" />
</xsl:template>

<xsl:template match="HeaderField | CustomerNumber | Address">
  <xsl:value-of select="concat(.,$newline)" />
</xsl:template>

<xsl:template match="DetailSubRecord">
  <!-- do something with subrecord here -->
  <xsl:apply-templates />
</xsl:template>

The <xsl:apply-templates /> in the first template just tells the XSLT processor to deal with the child elements, after which it adds in the record count.

The second template handles any element with the three names in it's match atrtibute, and in each case outputs the content (.) concatenated with a new line.

The third template in it's current form is actually superfluous, the processor will do that anyway, but you can replace that comment with something more useful.

You'll notice this doesn't give any information on how to handle a DetailRecord element; because all you want to do is process it's children, you don't need to specify anything, as that's taken as a given.


Here you have a simple example on how to (literally) apply templates to your situation. Because you wasnt so clear about the required output, I've invented one.


XSLT 1.0 tested under Saxon 6.5.5

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:strip-space elements="*"/>
    <xsl:output method="text"/>

    <xsl:variable name="spaces" select="' '"/>
    <xsl:variable name="nl">
        <xsl:text>&#10;</xsl:text>
    </xsl:variable>

    <xsl:template match="/root">
        <xsl:apply-templates select="DetailRecord"/>
        <xsl:apply-templates select="Footer"/>
    </xsl:template>

    <xsl:template match="DetailRecord">
        <xsl:value-of select="concat(
            'Customer ',
            CustomerNumber, $nl)"
            />
        <xsl:apply-templates select="DetailSubRecord"/>
        <xsl:value-of select="concat(
            'Address Count:',
            count(DetailSubRecord),$nl,$nl
            )"
          />
    </xsl:template>

    <xsl:template match="DetailSubRecord">
        <xsl:value-of select="concat('-',Address,$nl)"/>
    </xsl:template>

    <xsl:template match="Footer">
        <xsl:value-of select="concat(
            'Customer Count:',
            count(preceding-sibling::DetailRecord),$nl
            )"
            />
    </xsl:template>

</xsl:stylesheet>

Applied on your input gets:

Customer 1
-London
-Hull
Address Count:2

Customer 2
-Birmingham
-Manchester
Address Count:2

Customer Count:2
0

精彩评论

暂无评论...
验证码 换一张
取 消