XPATH or XSL to match two node-sets using custom comparison

前端 未结 4 961
鱼传尺愫
鱼传尺愫 2021-01-01 06:22

EDIT: I also have access to ESXLT functions.

I have two node sets of string tokens. One set contains values like these:

/Geography/N         


        
相关标签:
4条回答
  • 2021-01-01 07:04

    I guess I couldn't make the XPath above work. I started with the following XML doc to initialize the two nodesets:

    <?xml version="1.0"?>
    <sets>
      <set>
        <text>/Geography/North America/California/San Francisco</text>
        <text>/Geography/Asia/Japan/Tokyo/Shinjuku</text>
      </set>
      <set>
        <text>/Geography/North America/</text>
        <text>/Geography/Asia/Japan/</text>
      </set>
    </sets>
    

    I think this stylesheet ought to implement Robert's solution, but I only get a count of '1':

    <?xml version="1.0"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
      <xsl:output method="text"/>
    
      <xsl:template match="/">
        <xsl:variable name="set1" select="sets/set[1]/text/text()"/>
        <xsl:variable name="set2" select="sets/set[2]/text/text()"/>
        <xsl:value-of select="count($set1[starts-with(., $set2)])"/>
        <xsl:text>
    </xsl:text>
      </xsl:template>
    
    </xsl:stylesheet>
    

    I did write a stylesheet that uses a recursive template and does produce the correct count of '2' with the given input doc, but it's far less elegant than Robert's answer. If only I could get the XPath to work--always wanting to learn.

    0 讨论(0)
  • 2021-01-01 07:12

    There is a simple and pure XSLT 1.0 solution (no extensions needed) for finding the count of matches:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="text"/>
    
        <xsl:template match="/">
            <xsl:variable name="vStars">
                <xsl:for-each select="*/regions/*">
                    <xsl:for-each select="/*/cities/*[starts-with(.,current())]">
                        <xsl:value-of select="'*'"/>
                    </xsl:for-each>
                </xsl:for-each>
            </xsl:variable>
    
            <xsl:value-of select="string-length($vStars)"/>
        </xsl:template>
    </xsl:stylesheet>
    

    When this transformation is applied on the following XML document:

    <t>
        <cities>
            <city>/Geography/North America/California/San Francisco</city>
            <city>/Geography/Asia/Japan/Tokyo/Shinjuku</city>
        </cities>
        <regions>
            <region>/Geography/North America/</region>
            <region>/Geography/Asia/Japan/</region>
        </regions>
    </t>
    

    the correct result is produced:

    2

    Do note that one character (an asterisk) is produced for every match found and all these asterisks form the content of the $vStars variable. We then simply output its string-length().

    0 讨论(0)
  • 2021-01-01 07:13

    Robert's last xsl:variable is good for getting a result tree fragment containing the matching text values, but unless (as he suggests) you use EXSLT or MS extensions to XSLT 1.0 to convert the RTF to a node set, you can't get a count of the matching text nodes.

    Here is the XSLT stylesheet I mentioned in my prior response that recurs over the sample input document I gave to give a count of text nodes in set 1 for which a node in set 2 matches part or all of it:

    <?xml version="1.0"?>
    <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
      <xsl:output indent="yes" method="text"/>
    
      <xsl:template match="/">
        <xsl:call-template name="count-matches">
          <xsl:with-param name="set1-node" select="sets/set[1]/text[1]"/>
          <xsl:with-param name="set2-node" select="sets/set[2]/text[1]"/>
          <xsl:with-param name="total-count" select="0"/>
        </xsl:call-template>
        <xsl:text>
    </xsl:text>
      </xsl:template>
    
      <xsl:template name="count-matches">
        <xsl:param name="set1-node"/>
        <xsl:param name="set2-node"/>
        <xsl:param name="total-count" select="0"/>
        <xsl:variable name="this-count">
          <xsl:choose>
            <xsl:when test="contains($set1-node, $set2-node)">
              <xsl:value-of select="1"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:value-of select="0"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:variable>
        <xsl:choose>
          <xsl:when test="$set2-node/following-sibling::text">
            <xsl:call-template name="count-matches">
              <xsl:with-param name="set1-node"
                              select="$set1-node"/>
              <xsl:with-param name="set2-node"
                              select="$set2-node/following-sibling::text[1]"/>
              <xsl:with-param name="total-count"
                              select="$total-count + $this-count"/>
            </xsl:call-template>
          </xsl:when>
          <xsl:when test="$set1-node/following-sibling::text">
            <xsl:call-template name="count-matches">
              <xsl:with-param name="set1-node"
                              select="$set1-node/following-sibling::text[1]"/>
              <xsl:with-param name="set2-node"
                              select="$set2-node/preceding-sibling::text[last()]"/>
              <xsl:with-param name="total-count"
                              select="$total-count + $this-count"/>
            </xsl:call-template>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="$total-count + $this-count"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:template>
    
    </xsl:stylesheet>
    

    Not particularly concise, but because XSLT does not let programmers assign new values to already-defined variables, recursion is often necessary. I don't see a way in XSLT 1.0 to get a count of the sort requested by Zack using xsl:for-each.

    0 讨论(0)
  • 2021-01-01 07:19

    This:

    <xsl:variable name="matches" select="$set1[starts-with(., $set2)]"/>
    

    will set $matches to a node-set containing every node in $set1 whose text value starts with the text value of a node in $set2. That's what you're looking for, right?

    Edit:

    Well, I'm just wrong about this. Here's why.

    starts-with expects its two arguments to both be strings. If they're not, it will convert them to strings before evaluating the function.

    If you give it a node-set as one of its arguments, it uses the string value of the node-set, which is the text value of the first node in the set. So in the above, $set2 never gets searched; only the first node in the list ever gets examined, and so the predicate will only find nodes in $set1 that start with the value of the first node in $set2.

    I was misled because this pattern (which I've been using a lot in the last few days) does work:

    <xsl:variable name="hits" select="$set1[. = $set2]"/>
    

    But that predicate is using an comparison between node-sets, not between text values.

    The ideal way to do this would be by nesting predicates. That is, "I want to find every node in $set1 for which there's a node in $set2 whose value starts with..." and here's where XPath breaks down. Starts with what? What you'd like to write is something like:

    <xsl:variable name="matches" select="$set1[$set2[starts-with(?, .)]]"/>
    

    only there's no expression you can write for the ? that will return the node currently being tested by the outer predicate. (Unless I'm missing something blindingly obvious.)

    To get what you want, you have to test each node individually:

    <xsl:variable name="matches">
      <xsl:for-each select="$set1">
        <xsl:if test="$set2[starts-with(current(), .)]">
          <xsl:copy-of select="."/>
        </xsl:if>
      </xsl:for-each>
    </xsl:variable>
    

    That's not a very satisfying solution because it evaluates to a result tree fragment, not a node-set. You'll have to use an extension function (like msxsl:node-set) to convert the RTF to a node-set if you want to use the variable in an XPath expression.

    0 讨论(0)
提交回复
热议问题