Avoid code repetition when using xsl:choose and xs

2019-07-30 13:36发布

Is there a clever way to simplify the following stylesheet in order to avoid repeating a whole when block when only one variable is changing between each of those?

Ideally I would like something like this, looping 6 times on $i:

<xsl:when test="$depth &gt; $i">
    [...]
    <xsl:value-of select="substring($npath,($nlength - $i*2),1) - 1"/>
    [...]
</xsl:when>

I'm using XSLT 1.0.

XML Input

<?xml version='1.0'?>
<?xml-stylesheet type="text/xsl" href="stylesheet.xsl" version="1.0"?>
<root>
  <item>Main_A
    <item>Item_A</item>
    <item>Item_B
      <item>Subitem_A</item>
      <item>Subitem_B</item>
    </item>
    <item>Item_C</item>
  </item>
  <item>Main_B
    <item>Item_A
      <item>Subitem_A</item>
      </item>
    <item>Item_B</item>
  </item>
</root>

XSLT 1.0 Stylesheet

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="root">
  <html>
    <body>
      <xsl:apply-templates select="item">
        <xsl:with-param name="depth" select="1"/>
      </xsl:apply-templates>
    </body>
  </html>
</xsl:template>

<xsl:template match="item">
  <xsl:param name="depth" select="1"/>
  <xsl:if test="$depth &lt; 10">
    <ul>
      <li>
        <xsl:text>path</xsl:text>
        <xsl:call-template name="loopnumformat">
          <xsl:with-param name="depth" select="$depth"/>
        </xsl:call-template>
        <xsl:text> = </xsl:text>
        <xsl:apply-templates match="item">
          <xsl:with-param name="depth" select="$depth + 1"/>
        </xsl:apply-templates>
      </li>
    </ul>
  </xsl:if>
</xsl:template>

<xsl:template name="loopnumformat">
  <xsl:param name="depth" select="1"/>
  <xsl:variable name="npath">
    <xsl:number level="multiple" from="*[10]"/>
  </xsl:variable>
  <xsl:variable name="nlength">
    <xsl:value-of select="string-length($npath)"/>
  </xsl:variable>

  <xsl:choose>
    <xsl:when test="$depth &gt; 2">
      <xsl:text>:</xsl:text>
      <xsl:value-of select="substring($npath,($nlength - 2*2),1) - 1"/>
      <xsl:call-template name="loopnumformat">
        <xsl:with-param name="depth" select="$depth - 1"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="$depth &gt; 1">
      <xsl:text>:</xsl:text>
      <xsl:value-of select="substring($npath,($nlength - 1*2),1) - 1"/>
      <xsl:call-template name="loopnumformat">
        <xsl:with-param name="depth" select="$depth - 1"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="$depth &gt; 0">
      <xsl:text>:</xsl:text>
      <xsl:value-of select="substring($npath,($nlength - 0*2),1) - 1"/>
      <xsl:call-template name="loopnumformat">
        <xsl:with-param name="depth" select="$depth - 1"/>
      </xsl:call-template>
    </xsl:when>
  </xsl:choose>

</xsl:template>

</xsl:stylesheet>

HTML Output

<html>
<body>
  <ul>
    <li>path:0 = Main_A
      <ul>
        <li>path:0:0 = Item_A</li>
      </ul>
      <ul>
        <li>path:0:1 = Item_B
          <ul>
            <li>path:0:1:0 = Subitem_A</li>
          </ul>
          <ul>
            <li>path:0:1:1 = Subitem_B</li>
          </ul>
        </li>
      </ul>
      <ul>
        <li>path:0:2 = Item_C</li>
      </ul>
    </li>
  </ul>
  <ul>
    <li>path:1 = Main_B
      <ul>
        <li>path:1:0 = Item_A
          <ul>
            <li>path:1:0:0 = Subitem_A</li>
          </ul>
        </li>
      </ul>
      <ul>
        <li>path:1:1 = Item_B</li>
      </ul>
    </li>
  </ul>
</body>
</html>

2条回答
叼着烟拽天下
2楼-- · 2019-07-30 13:57

Is there a clever way to simplify the following stylesheet in order to avoid repeating a whole when block when only one variable is changing between each of those?

It depends on the circumstances, but in your particular case, one way you could do it would be to move the xsl:choose inside, so that the parts that are the same in every case are expressed only once each:

  <xsl:text>:</xsl:text>
  <xsl:choose>
    <xsl:when test="$depth &gt; 2">
      <xsl:value-of select="substring($npath,($nlength - 2*2),1) - 1"/>
    </xsl:when>
    <xsl:when test="$depth &gt; 1">
      <xsl:value-of select="substring($npath,($nlength - 1*2),1) - 1"/>
    </xsl:when>
    <xsl:when test="$depth &gt; 0">
      <xsl:value-of select="substring($npath,($nlength - 0*2),1) - 1"/>
    </xsl:when>
  </xsl:choose>
  <xsl:call-template name="loopnumformat">
    <xsl:with-param name="depth" select="$depth - 1"/>
  </xsl:call-template>

But you seem to be going to extreme lengths to use xsl:number when it doesn't quite meet your needs. It appears that you can do the whole thing much more simply:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="root">
    <html>
      <body>
        <xsl:apply-templates select="item" />
      </body>
    </html>
  </xsl:template>

  <xsl:template match="item">
    <xsl:param name="path-prefix" select="'path'"/>
    <!-- if there are fewer than nine ':' characters in the path prefix for
         this item ... -->
    <xsl:if test="string-length($path-prefix) - string-length(translate($path-prefix, ':', '')) &lt; 9">
      <xsl:variable name="my-path"
          select="concat($path-prefix, ':', position() - 1)" />
      <ul>
        <li>
          <xsl:value-of select="$my-path"/>
          <xsl:text> = </xsl:text>
          <xsl:apply-templates select="text()[1]"/>
          <xsl:apply-templates select="item">
            <xsl:with-param name="path-prefix" select="$my-path"/>
          </xsl:apply-templates>
        </li>
      </ul>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

Perhaps you could even get rid of the xsl:if, which seems as if it may have been present to serve the limitations of the loopnumformat template in your original stylesheet -- this version has no inherent depth limitation. Note that it does assume that the text nodes you want to copy into the output document will be limited to the first in each <item>, but it would be easy enough to modify the stylesheet to instead copy all of them.

查看更多
乱世女痞
3楼-- · 2019-07-30 14:09

It looks like you are over-engineering here. You are simply looking for a way to count preceding <item> nodes, recursively up the document tree.

This simple transformation:

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

  <xsl:template match="root">
    <html>
      <body>
        <ul>
          <xsl:apply-templates select="item" />
        </ul>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="item">
      <li>
        <xsl:text>path</xsl:text>
        <xsl:apply-templates select="." mode="path" />
        <xsl:text> = </xsl:text>
        <xsl:value-of select="normalize-space(text()[1])" />
        <xsl:if test="item">
          <ul>
            <xsl:apply-templates select="item" />
          </ul>
        </xsl:if>
      </li>
  </xsl:template>

  <xsl:template match="item" mode="path">
      <xsl:apply-templates select="parent::item" mode="path" />
      <xsl:text>:</xsl:text>
      <xsl:value-of select="count(preceding-sibling::item)" />
  </xsl:template>

</xsl:stylesheet>

results in:

<html>
   <body>
      <ul>
         <li>path:0 = Main_A
            <ul>
               <li>path:0:0 = Item_A</li>
               <li>path:0:1 = Item_B
                  <ul>
                     <li>path:0:1:0 = Subitem_A</li>
                     <li>path:0:1:1 = Subitem_B</li>
                  </ul>
               </li>
               <li>path:0:2 = Item_C</li>
            </ul>
         </li>
         <li>path:1 = Main_B
            <ul>
               <li>path:1:0 = Item_A
                  <ul>
                     <li>path:1:0:0 = Subitem_A</li>
                  </ul>
               </li>
               <li>path:1:1 = Item_B</li>
            </ul>
         </li>
      </ul>
   </body>
</html>
查看更多
登录 后发表回答