XSLT filter with an index counter

2019-07-04 17:00发布

I've written some XSLT that uses one XML document to filter another. Now I'd like to number my output elements using position(), but my filtering condition is not built in to my <xsl:for-each>, so position() gives me results with numbering gaps. How do I get rid of the gaps?

<xsl:variable name="Astring">
  <a><b>10</b><b>20</b><b>30</b></a>
</xsl:variable>
<xsl:variable name="A" select="msxml:node-set($Astring)" />

<xsl:variable name="Bstring">
  <c><d>20</d><d>30</d></c>
</xsl:variable>
<xsl:variable name="B" select="msxml:node-set($Bstring)" />

<e>
  <xsl:for-each select="$A/a/b">
    <xsl:variable name="matchvalue" select="text()" />
    <xsl:if test="count($B/c/d[text() = $matchvalue]) &gt; 0">
      <xsl:element name="f">
        <xsl:attribute name="i">
          <xsl:value-of select="position()" />
        </xsl:attribute>
        <xsl:copy-of select="text()" />
      </xsl:element>
    </xsl:if>
  </xsl:for-each>
</e>

Here's the result:

<e>
  <f i="2">20</f>
  <f i="3">30</f>
</e>

...but I want this:

<e>
  <f i="1">20</f>
  <f i="2">30</f>
</e>

Is there any way to incorporate the above <xsl:if> filtering test inside the <xsl:for-each> select attribute?

Note: I've seen this question re: filtering and this one re: counters, but the former doesn't number its results and the latter uses position().

标签: xslt xpath
2条回答
地球回转人心会变
2楼-- · 2019-07-04 17:08

I'm unsure why people always want to use <xsl:for-each> when <xsl:apply-templates> is clearly the better alternative.

Ok, this is rhetorical. I know why this is: <xsl:for-each> seems to be easier. But it isn't — the following two templates replace your entire for-each construct:

<xsl:template match="/">
  <e><xsl:apply-templates select="$B/c/d[$A/a/b = .]" /></e>
</xsl:template>

<xsl:template match="d">
  <f i="{position()}"><xsl:value-of select="." /></f>
</xsl:template>

Output:

<e>
  <f i="1">20</f>
  <f i="2">30</f>
</e>

Well, I cheated a bit. You can condense the <xsl:for-each> version to this:

<xsl:template match="/">
  <e>
    <xsl:for-each select="$B/c/d[$A/a/b = .]">
      <f i="{position()}"><xsl:value-of select="." /></f>
    </xsl:for-each>
  </e>
</xsl:template>

Still, it lacks the lightness of the <xsl:apply-templates> variant, IMHO.

查看更多
疯言疯语
3楼-- · 2019-07-04 17:26

Is there any particular reason why you can't include your condition inside the for-each loop?

<xsl:for-each select="$A/a/b[$B/c/d = .]">

I tested this out in Visual Studio and it works fine for this example.

This was the output:

<e>
  <f i="1">20</f>
  <f i="2">30</f>
</e>
查看更多
登录 后发表回答