简体   繁体   中英

How to remove the first child of an XML node with XSL?

Data.xls

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

<xsl:variable name="Data" select="document('Data.xml')/*[1]"/>

<xsl:template match="/">
  <xsl:call-template name="remove-first-child">
    <xsl:with-param name="Node" select="$Data"/>
  </xsl:call-template>
</xsl:template>

<xsl:template name="remove-first-child">
  <xsl:param name="Node"/>
  <xsl:element name="{name($Node)}">
    <xsl:copy-of select="$Node/@* | $Node/*[position() > 1]"/>
  </xsl:element>
</xsl:template>

<!-- same as above, but for testing over 2 recursion levels -->
<xsl:template name="remove-first-child1">
  <xsl:param name="Node"/>
  <xsl:call-template name="remove-first-child">
    <xsl:with-param name="Node">
      <xsl:element name="{name($Node)}">
        <xsl:copy-of select="$Node/@* | $Node/*[position() > 1]"/>
      </xsl:element>
    </xsl:with-param>
  </xsl:call-template>
</xsl:template>

</xsl:stylesheet>

How to remove the first child in Data and produce output that could be passed to remove-first-child again?

When calling the first template I get:

$> msxsl Other.xml Data.xsl
<Alphabet>
  <B/>
  <C/>
  ...
</Alphabet>

When I change to call the second template I get:

$> msxsl Other.xml Data.xsl
Error occured while executing stylesheet 'Data.xsl'.
Code:  0x80004005
Reference to variable or parameter 'Node' must evaluate to a node list.

The desired result would be:

<Alphabet>
  <C/>
  ...
</Alphabet>

Update: How I tried to apply the suggested solutions.

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

<xsl:template match="node()[parent::*][not(preceding-sibling::*)]" mode="remove-first-child"/>

<xsl:template name="remove-first-child-a">
  <xsl:param name="Node"/>
  <xsl:apply-templates select="$Node" mode="remove-first-child"/>
</xsl:template>

<xsl:template name="remove-first-child-b">
  <xsl:param name="Node"/>
  <xsl:variable name="Temp">
    <xsl:apply-templates select="$Node" mode="remove-first-child"/>
  </xsl:variable>
  <xsl:apply-templates select="$Temp" mode="remove-first-child"/>  
</xsl:template>

<xsl:template match="/">
  <xsl:call-template name="remove-first-child-a">
  <!-- xsl:call-template name="remove-first-child-b" -->
    <xsl:with-param name="Node" select="$Data"/>
  </xsl:call-template>
</xsl:template>

But this also only yields the same results as mentioned above. a :

$> msxsl Other.xml Data.xsl
<Alphabet>
  <B/>
  <C/>
  ...
</Alphabet>

b :

$> msxsl Other.xml Data.xsl
Error occured while executing stylesheet 'Data.xsl'.
Code:  0x80004005
Expression must evaluate to a node-set.
-->$Temp<--

So I guess the key is the type system...

And as a general note: The main manipulation is happening for Other.xml , it's not just a dummy call. Some data is taken from Data.xml and it's to be processed before joined with Other.xsl . That processing consists only of the removal of the first child dependent on some conditions (not part of this question) which happen to be implemented recursively, because it's XSL. And along the recursions it might be necessary to remove several first children of the same and of different, traversed nodes.

Whata I'm trying to express in XSL here is a projection node --> node , removing the first child of the input node.

For this kind of recursion in XSLT (ie, following the input document's structure), you want to use apply-templates , not call-template . This avoids the issue you're getting (missing node set), since the template will only be applied to nodes that exist.

The following transform sets up a standard identity template, then adds an override that emits nothing for first children.

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

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

    <!-- Skip first child -->
    <!-- the "parent" check is needed so that the root node is not skipped -->
    <xsl:template match="node()[parent::*][not(preceding-sibling::*)]" />

    <xsl:variable name="data-doc" select="document('Data.xml')"/>

    <xsl:template match="/">
        <xsl:apply-templates select="$data-doc/*" />
    </xsl:template>

</xsl:stylesheet>

Applied on a dummy document where the external file Data.xml contains the following sample input,

<A>
    <B1>
        <C1 />
        <C2 />
    </B1>
    <B2>
        <C1 />
        <C2 />
    </B2>
    <B3>
        <C1 />
        <C2>
            <D1 />
            <D2 />
        </C2>
        <C3>
            <D1 />
        </C3>
    </B3>
</A>

This gives the result:

<?xml version="1.0" encoding="utf-8"?>
<A>
    <B2>
        <C2 />
    </B2>
    <B3>
        <C2>
            <D2 />
        </C2>
        <C3>
        </C3>
    </B3>
</A>

You can use :

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="/*//*[position()&lt;=1]"/>     
</xsl:stylesheet>

And change the 1 by 2 if you want to filter both the first and second element (it works as explained in harpo's answer).

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