2
votes

I have an XML document with a structure similar to the following:

<article>
   <header>
      <metadata>
          bunch of metadata nodes here
          <abstract>
              <p>this is one of the abstract's paragraphs</p>
          </abstract>
      </metadata>
   </header>
   <body>
      <sec>
         <title>This is title 1</title>
         <p>Paragraph 1</p>
         <p>paragraph 2</p>
         <title>This is title 2</title>
      <sec>
         <title>This is title 3</title>
         <p>paragraph 1 under title 3</p>
         <p>paragraph 2 under title 3</p>
   </body>
 </article>

The real XML can certainly get a lot more complex that the above, but it should suffice to illustrate.

I need to apply a specific template only to the first <p> element occurring inside the <body> element. I can easily write an xpath expresion that selects for the the node I am interested in:

(//body//p)[1]

Unfortunately this xpath expression can not be used as the match expression in an XSLT

<xsl:template match="(//body//p)[1]">
<p_first>
    <xsl:call-template name="copyElementAttributes" />
    <xsl:apply-templates select="@*|node()" />
</p_first>
</xsl:template>

The template above will throw an error when trying to run it through the XSL engine. Is there a match expression that I can use to select the element?

2

2 Answers

3
votes

You'd have to use something like

<xsl:template match="body//p[count(preceding::p) = count(ancestor::body/preceding::p)]">

i.e. to find a p inside the body for which the number of other p elements ahead of it is the same as the number ahead of the opening <body> tag.

Alternatively

<xsl:template match="body//p[not(preceding::p[ancestor::body])]">

would look for a p that does not have any predecessor p elements that are themselves descendants of the body. The ancestor::body constraint is necessary because every body//p is preceded by the p that is inside the abstract. It would get more complicated still if there could be more than one body element, since we're only checking for ps that are inside a body element, not necessarily the same body.

1
votes

Try match="body//p[not(preceding::p)]".