Reputation: 10358
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
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
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
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 < 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 < 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
Reputation: 12729
Assume the focus node (current()) is the basis of your calculation.
Define...
<xsl:variable name="set1" select="following-sibling::Company[ Level > current()]" />
<xsl:variable name="set2" select="following-sibling::Company[ Level <
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