开发者

XSLT: find number of given node

开发者 https://www.devze.com 2023-04-03 19:03 出处:网络
I have an XML structure representing a logical derivation, and it looks like this: <derivation> <step name=\"foo\">

I have an XML structure representing a logical derivation, and it looks like this:

<derivation>
  <step name="foo">
    <derive>[...]</derive>
    <by>[...]</by>
  </step>
  <step name="bar">
    <derive>[...]</derive>
    <by>
      <from name="foo"/>, conjunction elimination
    </by>
  </step>
  <step name="baz">
    <derive>[...]</derive>
    <by>
      <from name="bar"/>, existential quantification
    </by>
  </step>
  [...]
<开发者_开发知识库;/derivation>

Each <step> in the derivation has a number -- for instance, that with name="foo" would be number 1, that with name="bar" would be number 2, that with name="baz" would be number 3, and so on. When inside the <step>, I can find the number with <xsl:number/>. Good so far.

Now, where an element <from name="bar"/> occurs, I want it to be replaced by the number of the <step> element with name="bar". There are three subproblems to solve here:

  • Find the most recent ancestor of the <from> element that is a <derivation>.
  • From that, find the first child that is a <step> element with name="bar". In the above instance, this would find the second child of the <derivation>.
  • Identify the number of that element. In the above, that would be 2.

Can someone tie together solutions to these subproblems to satisfy my requirement?


This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="from[@name]">
  <xsl:variable name="vReferred" select=
    "ancestor::derivation[1]/step[@name = current()/@name]"/>

   <xsl:for-each select="$vReferred">
     <xsl:number count="step" level="any"/>
   </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<derivation>
    <step name="foo">
        <derive>[...]</derive>
        <by>[...]</by>
    </step>
    <step name="bar">
        <derive>[...]</derive>
        <by>
            <from name="foo"/>, conjunction elimination     
        </by>
    </step>
    <step name="baz">
        <derive>[...]</derive>
        <by>
            <from name="bar"/>, existential quantification     
        </by>
    </step>   [...] 
</derivation>

produces the wanted, correct result:

<derivation>
   <step name="foo">
      <derive>[...]</derive>
      <by>[...]</by>
   </step>
   <step name="bar">
      <derive>[...]</derive>
      <by>1, conjunction elimination     
        </by>
   </step>
   <step name="baz">
      <derive>[...]</derive>
      <by>2, existential quantification     
        </by>
   </step>   [...] 
</derivation>


Fun with XPath axes...

count(ancestor::derivation/step[@name=$name]/preceding-sibling::step)+1

This grabs the nearest ancestor that is a derivation, gets it's child step with the correct name, and then gets all preceding siblings named step. It counts them and adds 1 (since step 2 would have 1 preceding sibling).

XPath axis reference


Another solution, using keys (may be more efficient if on average there are more than one references to the same step):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kStepByName" match="derivation/step"
         use="@name"/>

 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="from[@name]">
  <xsl:variable name="vReferred" select=
    "key('kStepByName',@name)"/>

   <xsl:for-each select="$vReferred">
     <xsl:number count="step" level="any"/>
   </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>


While the two solutions already posted are both okay, just for the reference I'm adding another possible solution.

Something like :

<xsl:template match="from">
  <xsl:variable name="ref" select="@name"/>
  <xsl:apply-templates mode="number" select="ancestor::derivation/step">
    <xsl:with-param name="ref" select="$ref"/>
  </xsl:apply-templates>
</xsl:template>

<xsl:template name="step" mode="number">
  <xsl:param name="ref"/>
  <xsl:if test="@name=$ref">
    Item no. <xsl:value-of select="position()"/>
  </xsl:if>
</xsl:template>

Please note that I haven't been writing XSLT for quite a few years now, while the idea behind this solution should be correct, the syntax may be completely wrong.

0

精彩评论

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