XSL: output of “nested” structures

2019-07-20 19:38发布

I have an XML document of the following kind:

<item>
  <item>
    <item>
     ... (same elements <item> ... </item> here) 
    </item>
  </item>
</item>

... and the following XSL-transform:

<xsl:template match="item"><xsl:text>
open</xsl:text>
 <xsl:apply-templates/><xsl:text>
close</xsl:text>
</xsl:template>

What I obtain is:

open
open
open
close
close
close

So I wonder whether it is possible to somehow get an output with indents like this:

open
   open
      open
      close
   close
close

Thanks for your help!

P.S. It should be definitely possible to obtain what I want to by letting output method of the transformation to be HTML. However, I need to make indents "directly" in the text, not using any kind of HTML's lists etc.

标签: xml xslt
3条回答
Juvenile、少年°
2楼-- · 2019-07-20 20:13

This transformation:

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

 <xsl:template match="item">
  <xsl:param name="pIndent" select="'  '"/>

  <xsl:value-of select="concat('&#xA;', $pIndent, 'open')"/>

  <xsl:apply-templates>
   <xsl:with-param name="pIndent"
        select="concat($pIndent, '  ')"/>
  </xsl:apply-templates>

  <xsl:value-of select="concat('&#xA;', $pIndent, 'close')"/>
 </xsl:template>

 <xsl:template match="node()[not(self::item)]">
  <xsl:apply-templates/>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<item>
    <item>
        <item>      ...
            <item> ... </item>
        </item>
    </item>
</item>

produces the wanted, indented output:

  open
    open
      open
        open
        close
      close
    close
  close

Explanation:

The $pIndent parameter is used to hold the string of whitespace to be prepended to the non-white space output. Whenever an xsl:apply-templates is used, the value passed with this parameter is expanded by two spaces.

查看更多
欢心
3楼-- · 2019-07-20 20:16

There's a 'cheat' way of doing this using substring. To modify the template you already have:

<xsl:template match="item">
  <xsl:variable name="indent" select="substring('          ',1,count(ancestor::*)*2)" />
  <xsl:text>&#10;</xsl:text>
  <xsl:value-of select="$indent" />
  <xsl:text>open</xsl:text>
  <xsl:apply-templates/>
  <xsl:text>&#10;</xsl:text>
  <xsl:value-of select="$indent" />
  <xsl:text>close</xsl:text>
</xsl:template>

As you can see, it simply inserts a number of spaces based on how many ancestors the element has (multiplied by 2), by taking a portion of a string of spaces. I've used 10 here, which means it'll stop indenting at 5 levels, but you can use simply use a longer string if your XML is deeper than that.

It also has the advantage that you can do custom indenting quite easily, by using a different string. You could use 1-2-3-4-5-6-7-8-9- for example, if you wanted to show clearly how indented each line is.

I also replaced the carriage returns with &#10;, just to make the code easier to indent for readability.

查看更多
走好不送
4楼-- · 2019-07-20 20:22

It's simple, just use tabs inside you <xsl:text> elements (I don't know how to put them here):

<xsl:template match="item"><xsl:text>
open</xsl:text>

However, to achieve the indented structure you would need to create a named template or xpath function that outputs several times the tab (or spaces).

查看更多
登录 后发表回答