jlp
jlp

Reputation: 10358

Get a following sibling to element but on the same branch?

This flat XML represents tree structure using level field. How can I get elements that are deeper in the tree to the given node but only in the same branch?

So given that company name is A and I need to get company B and C (not E which is in different branch).

<Companies>
      <Company>
        <Name>A</Name>
        <Level>0</Level>
      </Company>
      <Company>
        <Name>B</Name>
        <Level>1</Level>
      </Company>
      <Company>
        <Name>C</Name>
        <Level>1</Level>
      </Company>
      <Company>
        <Name>D</Name>
        <Level>0</Level>
      </Company>
      <Company>
        <Name>E</Name>
        <Level>1</Level>
      </Company>
    </Companies>

There can be more than level one. I want it returned too. I am using XLST 1.0.

Upvotes: 2

Views: 521

Answers (4)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243619

Use:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kDescendants" match="Company[Level > 0]"
  use="generate-id(preceding-sibling::Company[Level=0][1])"/>

 <xsl:template match="/">
     <xsl:copy-of select="key('kDescendants', generate-id(/*/*[Name='A']))"/>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<Companies>
    <Company>
        <Name>A</Name>
        <Level>0</Level>
    </Company>
    <Company>
        <Name>B</Name>
        <Level>1</Level>
    </Company>
    <Company>
        <Name>C</Name>
        <Level>1</Level>
    </Company>
    <Company>
        <Name>D</Name>
        <Level>0</Level>
    </Company>
    <Company>
        <Name>E</Name>
        <Level>1</Level>
    </Company>
</Companies>

the wanted, correct result is produced:

<Company>
   <Name>B</Name>
   <Level>1</Level>
</Company>
<Company>
   <Name>C</Name>
   <Level>1</Level>
</Company>

Explanation:

The key "kDescendants" defines the mapping between a top node in the tree and its descendants -- given the generate-id($someTopNode), it produces all descendants in the (logical) tree topped by that $someTopNode.

Upvotes: 2

Lukasz
Lukasz

Reputation: 7662

This is my try - but I believe that there are simpler ways... And sorry: it's XSLT 2.0 only.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/Companies">
        <xsl:param name="nodeName" select="'A'" />

        <xsl:variable name="level" select="Company[Name = $nodeName]/Level/text()" as="xs:int"/>
        <xsl:variable name="seq" as="node()*" select="Company[Name = $nodeName]/following-sibling::*" />
        <xsl:variable name="levels" as="xs:int*" select="$seq/Level/text()" />
        <xsl:copy-of select="subsequence($seq, 0, index-of($levels, $level)[1])" />
    </xsl:template>
</xsl:stylesheet>

Upvotes: 0

Tim C
Tim C

Reputation: 70648

One way to achieve this in XSLT1.0 is to define a key, that groups elements by the first-most preceding-sibling with a lower level

<xsl:key 
   name="companies" 
   match="Company" 
   use="generate-id(preceding-sibling::Company[Level &lt; current()/Level][1])" />

Then, assuming you were positioned on a specific Company element for which you want the lower levels, you can just do this

This will initially get the elements on the next level. To get the subsequent levels, you need a template to match the Company elements, copy them, and recursively apply the template

<xsl:template match="Company">
   <Company>
     <xsl:apply-templates select="@*|node()"/>
   </Company>
   <xsl:apply-templates select="key('companies', generate-id())" />
</xsl:template>

Here is the full XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" indent="yes"/>
   <xsl:key name="companies" match="Company" use="generate-id(preceding-sibling::Company[Level &lt; current()/Level][1])" />

   <xsl:template match="/Companies">
      <xsl:apply-templates select="Company[Name='A']" mode="parent" />
   </xsl:template>

   <xsl:template match="Company" mode="parent">
      <xsl:apply-templates select="key('companies', generate-id())" />
   </xsl:template>

   <xsl:template match="Company">
      <Company>
         <xsl:apply-templates select="@*|node()"/>
      </Company>
      <xsl:apply-templates select="key('companies', generate-id())" />
   </xsl:template>

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

When applied to your sample XML, the following is output

<Company>
   <Name>B</Name>
   <Level>1</Level>
</Company>
<Company>
   <Name>C</Name>
   <Level>1</Level>
</Company>

Upvotes: 1

Sean B. Durkin
Sean B. Durkin

Reputation: 12729

Assume the focus node (current()) is the basis of your calculation.

Define...

<xsl:variable name="set1" select="following-sibling::Company[ Level &gt; current()]" />
<xsl:variable name="set2" select="following-sibling::Company[ Level &lt; 
      preceding-sibling::Company[1]/Level][1]/preceding-sibling::Company" />

Then the child companies are at the intersection of these two sets, which is...

$set1[. = $set2]

Upvotes: 0

Related Questions