views:

311

answers:

6

Hello,

Does anyone know how to copy only the first n nodes in a xml file and delete the rest using xslt? So say I only want to copy the first 10 nodes and delete the rest of the nodes that belong to the same parent.

Thanks in advance!

+3  A: 

You should just remove them from your result set, as:

<!-- note you must to encode 'greater than' and 'lower than' characters -->
<xsl:for-each select="parent/nodes[position() &lt;= 10]">
    ...
</xsl:for-each>
Rubens Farias
+1 Really simple and neat. I would do the same.
Rashmi Pandit
A: 

Use the identity transform, which will copy the source tree to the output tree, and add a template to exclude the elements you want to eliminate. Then, as you don't want to eliminate all of them but only those after the first ten, add one final template for the special ones that are to be allowed through, based on their position:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

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

  <xsl:template match="inner"/>

  <xsl:template match="inner[position() &lt; 11]">
    <xsl:call-template name="identity"/>
  </xsl:template>

</xsl:stylesheet>

to be used with the XML

<?xml version="1.0" encoding="UTF-8"?>
<outer>
  <inner foo="1"/>
  <inner foo="2"/>
  <inner foo="3"/>
  <inner foo="4"/>
  <inner foo="5"/>
  <inner foo="6"/>
  <inner foo="7"/>
  <inner foo="8"/>
  <inner foo="9"/>
  <inner foo="10"/>
  <inner foo="11"/>
  <inner foo="12"/>
  <inner foo="13"/>
  <inner foo="14"/>
</outer>
NickFitz
This transform will mess up any descendant element that has 11 or more elements named `inner`.
Robert Rossney
A: 

Thank you NickFitz! It works!

lizb
A: 

Add the following template to the identity transform:

<xsl:template match="/*/*[position() &lt; 11]"/>

How it works: The identity transform copies any node it matches to the result document, recursively. But the match criteria on the identity transform have the lowest possible priority; if a node is matched by any template with a higher priority, that template will be used instead. (The priority rules are obscure, but they're so well designed that you rarely need to know about them; generally speaking, if a node is matched by two templates, XSLT will select the template whose pattern is more specific.)

In this case, we're saying that if a node is an element that's a child of the top-level element (the top level element is the first element under the root, or /*, and its child elements are thus /*/*) and its position in that list of nodes is 11 or higher, it shouldn't be copied.

Edit:

Oof. Everything about the above is correct except for the most important thing. What I wrote will copy every child of the top-level element except for the first ten.

Here's a complete (and correct) version of the templates you'll need:

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

<xsl:template match="/*/*[position() &gt; 10]"/>

That's it. The first template copies everything that's not matched by the second template. The second template matches all elements after the first 10 and does nothing with them, so they don't get copied to the output.

Robert Rossney
A: 

Hello Robert,

Did you mean to change it to:

(see post above)

I've replaced the first line of the third template with the line you gave. I've tested that this works. Is this what you meant?

Many thanks.

lizb
A: 

Sorry the code didn't paste properly below. Here is what it should be:

    <xsl:template match="node()|@*" name="identity">
     <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
    </xsl:template>
    <xsl:template match="inner"/>    
    <xsl:template match="/*/*[position() &lt; 11]">
<xsl:call-template name="identity"/>  
    </xsl:template>
lizb
I've edited my answer to make it a) more detailed and b) not wrong.
Robert Rossney