0

Hello, this is not a Web Development question, but this is the only XML forum on Daniweb. I need to copy a specific number of nodes in an XSLT transform. For example, I need to copy the first 5 <student> nodes, with all child nodes and attribute nodes, even though the original XML file may contain 100 student nodes. Thanks in advance.

A more general question would be how to copy a specific range of nodes based on their position/index within the original XML file. For example, copy student nodes 50 through 88.

2
Contributors
8
Replies
9
Views
9 Years
Discussion Span
Last Post by tgreer
0

There's a couple of ways to do this; probably the most imperative is:

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

  <xsl:template match="/">
    <xsl:apply-templates>
      <xsl:with-param name="first" select="1"/>
      <xsl:with-param name="last" select="8"/>
    </xsl:apply-templates>
  </xsl:template>
  
  <xsl:template match="students">
    <xsl:param name="first"/>
    <xsl:param name="last"/>    
    <students>
      <xsl:for-each select="student[position()&gt;=$first and position()&lt;=$last]">
        <xsl:copy-of select="."/>
      </xsl:for-each>
    </students>
  </xsl:template>

</xsl:stylesheet>

That's assuming an input like:

<students>
  <student a="1">data</student>
  <student a="2">data</student>
   ....
  <student a="20">data</student>
</students>
0

No, this still copies everything... perhaps my processor doesn't support position().

0

Which processor are you using? If your processor doesn't support position( ).. that's strange; because it's been around a long time. Anyway, try this replacement for the 2nd template:

<xsl:template match="students">
    <xsl:param name="first"/>
    <xsl:param name="last"/>    
    <students>
      <xsl:for-each select="student">
        <xsl:variable name="position"><xsl:number/></xsl:variable>
        <xsl:if test="( number( $position ) &gt;= $first ) and ( number( $position ) &lt;= $last )">
          <xsl:copy-of select="."/>
        </xsl:if>
      </xsl:for-each>
    </students>
  </xsl:template>

Something like this should also work:

<xsl:template match="students">
    <xsl:param name="first"/>
    <xsl:param name="last"/>    
    <students>
      <xsl:for-each select="student">
        <xsl:variable name="num_preceding" select="count( preceding-sibling::student )"/>
        <xsl:if test="( $num_preceding &gt;= ( $first - 1 ) ) and ( $num_preceding &lt; $last )">
          <xsl:copy-of select="."/>
        </xsl:if>
      </xsl:for-each>
    </students>
  </xsl:template>

Also, what's the structure of your input document? Is it as simple as the example I gave ( i.e. all student elements are sibling level within the root node ) or more complex?

0

Altova.

It's a bit more complex. I need to do a deep copy of a second-level node, but it's the only second level node. Something like this:

<?xml version="1.0" encoding="utf-8"?>
<Students xmlns="myNameSpace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<student>
  <firstname>Thomas</firstname>
  <lastname>Greer</lastname>
</student>
..
..
</Students>

The <student> node is what I want to copy, all attributes, child nodes, and attributes of child nodes. I need to copy a range of them. The "student" node contains child-nodes, and those nodes may have their own children, and so on. Essentially, I'm dividing the XML file into smaller chunks based on the total number student nodes.

I will test your other examples. Thanks.

0

I'm only getting the text nodes, not the element nodes or attributes, and I'm getting the entire file not just the first few students.

1

It sounds like the `students' template isn't being matched atall ( that would cause the output to end up only as text nodes ). Silly question - did you change the capitalization of the match="students" in the 2nd template to match the capitalization of the `Students' node in your document?

The only other thing I can think of is changing the <apply-templates> in the first template to explicitly select only the Students ( root ) node, i.e:

<xsl:template match="/">
  <xsl:apply-templates [b]select="Students"[/b]>
    <xsl:with-param name="first" select="1"/>
    <xsl:with-param name="last" select="8"/>
  </xsl:apply-templates>
</xsl:template>

If the root node is `Students', that should be the same as <apply-templates> without an explicit select, but, I guess all xslt processors are a bit different..

Votes + Comments
Thanks for your help.
0

Yes, I changed my example. I'm not really dealing with Students and student, but the structure is the same: root node, second-level node with attributes and child nodes. I will thrash around a bit more and let you know. Thanks again for taking a look.

0

Namespaces were messing me up. The techniques above work just fine. In order to split the file into several groups, I ended up using this:

<?xml version="1.0" encoding="UTF-8"?>

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

<xsl:variable name="nodes-to-group" select="ceiling(count(Students/student) div 2)" />

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
  <xsl:for-each-group select="Students/student" group-by="ceiling(position() div $nodes-to-group)">
    <xsl:result-document href="{TEMPFOLDER}{format-number(position(),'000000000')}.xml">
      <Students>
        <xsl:apply-templates select=".|following::student[position() &lt; $nodes-to-group]"/>
      </Students>
    </xsl:result-document>
  </xsl:for-each-group>
</xsl:template>

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

</xsl:stylesheet>

Use any hard-coded value or calculation to determine group size in the nodes-to-group variable.

This topic has been dead for over six months. Start a new discussion instead.
Have something to contribute to this discussion? Please be thoughtful, detailed and courteous, and be sure to adhere to our posting rules.