tags:

views:

157

answers:

5

I woud like to add a node in the leftmost leaf, with the given name. For example,

<root>
  <aaa name="a">
    <aaa name="x"/>
  </aaa>
  <aaa name="b">
    <aaa name="y">
      <aaa name="z"/>
    </aaa>
  </aaa>
  <aaa name="c">
    <aaa name="z"/>
  </aaa>
</root>

Given name= "z" and given new node is <aaa name="w">. New tree should be the following form:

<root>
  <aaa name="a">
    <aaa name="x"/>
  </aaa>
  <aaa name="b">
    <aaa name="y">
      <aaa name="z">
        <aaa name="w"/>
      </aaa>   
    </aaa>
  </aaa>
  <aaa name="c">
    <aaa name="z"/>
  </aaa>
</root>
A: 

You can start with something like this:

<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

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

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
      <xsl:if test="@name='z'">
        <aaa name="w"/>
      </xsl:if>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>
Rubens Farias
+2  A: 

If by leftmost you mean the 'z' node with the greatest depth, you could first define a variable to work out the depth of the left-most 'z', and then add the 'w' node when you match a node at such a depth

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
   <!-- Work out depth of left most 'z' node -->
   <xsl:variable name="LeftMost">
      <xsl:for-each select="//*[@name='z']">
         <xsl:sort select="count(ancestor::*)" order="descending"/>
         <xsl:if test="position() = 1">
            <xsl:value-of select="count(ancestor::*)"/>
         </xsl:if>
      </xsl:for-each>
   </xsl:variable>
   <xsl:template match="/">
      <xsl:apply-templates/>
   </xsl:template>
   <xsl:template match="@*|node()">
      <xsl:copy>
         <xsl:apply-templates select="@*|node()"/>
         <xsl:if test="@name='z' and count(ancestor::*) = $LeftMost">
            <aaa name="w"/>
         </xsl:if>
      </xsl:copy>
   </xsl:template>
</xsl:stylesheet>

Using this, if you had two 'z' nodes at the same depth you would end up with both be given a 'w' node.

An alternative approach is to use generate-id() to get the ID of the fist 'z' at the greatest depth, and then add the 'w' when you match the node with the same id. This would then only add a 'w' node to the first 'z' node it finds at the greatest depth.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
   <xsl:variable name="LeftMost">
        <xsl:for-each select="//*[@name='z']">
      <xsl:sort select="count(ancestor::*)" order="descending"/>
            <xsl:if test="position() = 1">
                <xsl:value-of select="generate-id()"/>
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>
    <xsl:template match="/">
        <xsl:apply-templates/>
    </xsl:template>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
            <xsl:if test="@name='z' and generate-id() = $LeftMost">
                <aaa name="w"/>
            </xsl:if>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>
Tim C
+1. BTW: This is line by line *exactly* the solution I have come up with without first reading your post.
Tomalak
Ah! I've just seen I have mis-understood what leftmost means. To get the first occurence of the node, just replace 'ansestor' with 'preceding' in the examples.
Tim C
+2  A: 

A variant of @Tim C's variable based approach would involve an <xsl:key> and the preceding axis, like this:

<xsl:stylesheet 
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
> 
  <xsl:key 
    name="kCountPreceding" match="aaa[@name='z']" 
    use="count(preceding::aaa[@name='z'])"
  />
  <xsl:variable name="vLeftMostId" select="
    generate-id(key('kCountPreceding', 0))" 
  />

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*" />
      <xsl:if test="generate-id() = $vLeftMostId">
        <aaa name="w" />
      </xsl:if>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Downside of this approach is, that no variables are allowed in a match expression. This means this cannot be made dynamic, the key's match="aaa[@name='z']" must be hard-coded.

Tomalak
A: 

Why don't you use preceding axis?

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  <xsl:template match="/">
    <xsl:apply-templates />
  </xsl:template>
  <xsl:template match="*[@name='z' and not(preceding::*[@name='z'])]">
    <xsl:copy>
      <xsl:copy-of select="@*"/>
      <aaa name="w" />
      <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>
  <xsl:template match="*">
    <xsl:copy>
      <xsl:copy-of select="@*"/>
      <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

BTW, the leftmost leaf is the first leaf in the document order, isn't it?

<xsl:for-each select="/descendant::*[@name='z' and not(*)][1]">
  ...
  <aaa name="w" />
  ...
</xsl:for-each>
Erlock
yes, the first leaf, with given name
Alexander
A: 

the leftmost leaf is the first leaf in the document order, isn't it?

yes, the first leaf, with given name

Alexander
@Alexander: Please do not use Stack Overflow like a forum. The lower section is for answers to your question, not for follow-up posts or comments. Unless you answer your own question (which would be fine), use the comment feature or modify your question to add missing detail. Please delete this post.
Tomalak