简体   繁体   中英

XSLT: How do I match on the first occurrence of a given element?

<xsl:template match="//foo[1]">

matches multiple elements (every <foo> that has no previous <foo> siblings)

<xsl:template match="(//foo)[1]">

is an error.

How do I match on just the first occurrence of an element in the document?

Here's an example of the input document:

<test>
    <foo>1</foo>
    <another>
        <foo>2</foo>
    </another>
    <more>
        <bar>3</bar>
        <foo>4</foo>
    </more>
    <something>
        <foo>5</foo>
        <bar>6</bar>
        <foo>7</foo>
    </something>
    <final>
        <hum>8</hum>
        <foo>9</foo>
        <foo>10</foo>
    </final>
</test>

My intention is to match the foo with text 1 and no others . Assume foo can occur anywhere in the document.

To match only the first occurrence of foo in the document, you have to check both that there are no preceding foo elements and that there are no foo ancestors:

<xsl:template match="foo[not(preceding::foo or ancestor::foo)]">

Consider the following sample input XML:

<r>
  <a/>
  <foo>
    <b/>
    <foo/>
  </foo>
  <foo>
    <foo/>
  </foo>
  <c/>
</r>

This XSLT,

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="xml" indent="yes"/>

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

  <xsl:template match="foo[not(preceding::foo or ancestor::foo)]">
    <FirstFoo>
      <xsl:apply-templates/>
    </FirstFoo>
  </xsl:template>

  <xsl:template match="foo">
    <LaterFoo>
      <xsl:apply-templates/>
    </LaterFoo>
  </xsl:template>

</xsl:stylesheet>

will output this XML,

<?xml version="1.0" encoding="UTF-8"?>
<r>
  <a/>
  <FirstFoo>
      <b/>
      <LaterFoo/>
  </FirstFoo>
  <LaterFoo>
      <LaterFoo/>
  </LaterFoo>
  <c/>
</r>

but if you only check the preceding axis, you'll output this XML:

<?xml version="1.0" encoding="UTF-8"?>
<r>
  <a/>
  <FirstFoo>
      <b/>
      <FirstFoo/>
  </FirstFoo>
  <LaterFoo>
      <LaterFoo/>
  </LaterFoo>
  <c/>
</r>

This problem can also be solved with a key where you then identify the first item in the group of foo elements, using XSLT 2.0 this looks like

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

    <xsl:key name="foo-group" match="foo" use="node-name(.)"/>

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

    <xsl:template match="foo[. is key('foo-group', node-name(.))[1]]">
        <foo id="first-foo">
            <xsl:apply-templates/>
        </foo>
    </xsl:template>  

</xsl:transform>

online at http://xsltransform.net/gWEamLb .

Using XSLT 1.0 you can use the local-name or name (or if needed namespace and name) to achieve the same, only as the is operator does not exist you need to use generate-id to identify the first item:

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

    <xsl:key name="foo-group" match="foo" use="local-name()"/>


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

    <xsl:template match="foo[generate-id() = generate-id(key('foo-group', local-name())[1])]">
        <foo id="first-foo">
            <xsl:apply-templates/>
        </foo>
    </xsl:template>


</xsl:transform>

Online at http://xsltransform.net/ejivdH9 .

Finally using XSLT 3.0 we can write this as

<xsl:stylesheet
  version="3.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs">

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:key name="foo-group" match="foo" use="node-name(.)"/>

  <xsl:param name="foo-name" as="xs:QName" select="xs:QName('foo')"/>

  <xsl:template match="key('foo-group', $foo-name)[1]">
    <foo id="first-foo">
        <xsl:apply-templates/>
    </foo>
  </xsl:template>  

</xsl:stylesheet>

It would be nicer not to need the <xsl:param name="foo-name" as="xs:QName" select="xs:QName('foo')"/> and to use <xsl:template match="key('foo-group', xs:QName('foo'))[1]"> instead but even the more powerful XSLT 3.0 match patterns only allow us to use a function like key if the arguments are literals or variables/parameters.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM