XSLT Recursive Parent/Child Combination
<p>Very interesting Python <a href="https://stackoverflow.com/questions/37170543/recursively-search-for-parent-child-combinations-and-build-tree-in-python-and-xm">bounty question</a> which I know can be solved with XSLT 1.0. Please note this is not a duplicate question as previous post centered on Python methods while this is attempting an XSLT solution to the same problem. Below is my attempt but is constrained to a pre-set number of parent/child combinations here being four levels deep and walks through each level conditionally.</p>

<p>Is there a way to generalize my solution for any combination level? I understand this may require tokenizing values with the <code>--&gt;</code> separator. Expected output is current output but need a dynamic solution. I include the Python script to show final end result. To be clear in conflict of interest, I will not use any answer here in above post but do kindly invite you to do so! </p>

<p><strong>XML</strong> Input</p>

<pre><code>&lt;?xml version="1.0" encoding="UTF-8" standalone="yes"?&gt;
&lt;nodes&gt;
    &lt;node name="Car" child="Engine"/&gt;
    &lt;node name="Car" child="Wheel"/&gt;
    &lt;node name="Engine" child="Piston"/&gt;
    &lt;node name="Engine" child="Carb"/&gt;
    &lt;node name="Carb" child="Bolt"/&gt;
    &lt;node name="Spare Wheel"/&gt;
    &lt;node name="Bolt" child="Thread"/&gt;
    &lt;node name="Carb" child="Foat"/&gt;
    &lt;node name="Truck" child="Engine"/&gt;
    &lt;node name="Engine" child="Bolt"/&gt;
    &lt;node name="Wheel" child="Hubcap"/&gt;
&lt;/nodes&gt;
</code></pre>

<p><strong>XSLT</strong> </p>

<pre><code>&lt;xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"&gt;
&lt;xsl:output version="1.0" encoding="UTF-8" indent="yes" /&gt;
&lt;xsl:strip-space elements="*"/&gt;

  &lt;xsl:template match="nodes"&gt;
    &lt;data&gt;
      &lt;xsl:apply-templates select="node[not(@name=ancestor::nodes/node/@child)]"/&gt;
    &lt;/data&gt;
  &lt;/xsl:template&gt;

  &lt;xsl:template match="node"&gt;
    &lt;xsl:variable select="@name" name="currname"/&gt;
    &lt;xsl:variable select="@child" name="currchild"/&gt;
    &lt;xsl:variable select="/nodes/node" name="nodeset1"/&gt;
    &lt;xsl:variable select="/nodes/node[@name=$currchild]" name="nodeset2"/&gt;
    &lt;xsl:variable select="/nodes/node[@name=$nodeset2/@child]" name="nodeset3"/&gt;
    &lt;xsl:variable select="/nodes/node[@name=$nodeset3/@child]" name="nodeset4"/&gt;

        &lt;xsl:for-each select="$nodeset2"&gt;
          &lt;xsl:variable select="@child" name="nodeset2child"/&gt;

          &lt;xsl:for-each select="$nodeset3"&gt;
              &lt;xsl:variable select="@child" name="nodeset3child"/&gt;              
              &lt;xsl:if test="@name=$nodeset2child"&gt;

                &lt;xsl:for-each select="$nodeset4"&gt;
                  &lt;xsl:if test="@name=$nodeset3child"&gt;
                    &lt;xsl:value-of select="$currname"/&gt; --&gt; &lt;xsl:value-of select="$currchild"/&gt; --&gt; &lt;xsl:value-of select="$nodeset2child"/&gt; --&gt; &lt;xsl:value-of select="$nodeset3child"/&gt; --&gt; &lt;xsl:value-of select="@child"/&gt;&lt;xsl:text&gt;&amp;#xa;&lt;/xsl:text&gt;
                  &lt;/xsl:if&gt;                  
                &lt;/xsl:for-each&gt;
                &lt;xsl:if test="$nodeset2child!=$nodeset3/@child and $nodeset3child != $nodeset4/@name"&gt;                  
                    &lt;xsl:value-of select="$currname"/&gt; --&gt; &lt;xsl:value-of select="$currchild"/&gt; --&gt; &lt;xsl:value-of select="$nodeset2child"/&gt; --&gt; &lt;xsl:value-of select="$nodeset3child"/&gt;&lt;xsl:text&gt;&amp;#xa;&lt;/xsl:text&gt;
                &lt;/xsl:if&gt;

              &lt;/xsl:if&gt;              
          &lt;/xsl:for-each&gt;

          &lt;xsl:if test="not($nodeset2child=$nodeset3/@child or ancestor::nodes/node[@name=$nodeset2child]/@child)"&gt;
              &lt;xsl:value-of select="$currname"/&gt; --&gt; &lt;xsl:value-of select="$currchild"/&gt; --&gt; &lt;xsl:value-of select="$nodeset2child"/&gt;&lt;xsl:text&gt;&amp;#xa;&lt;/xsl:text&gt;
          &lt;/xsl:if&gt;

        &lt;/xsl:for-each&gt;
        &lt;xsl:value-of select="@name[not(ancestor::node/@child=$nodeset2/@name)]"/&gt;&lt;xsl:text&gt;&amp;#xa;&lt;/xsl:text&gt;

  &lt;/xsl:template&gt;  
&lt;/xsl:transform&gt;
</code></pre>

<p><strong>XML</strong> Transformed Output</p>

<pre><code>&lt;?xml version='1.0' encoding='UTF-8'?&gt;
&lt;data&gt;Car --&amp;gt; Engine --&amp;gt; Piston
Car --&amp;gt; Engine --&amp;gt; Carb --&amp;gt; Bolt --&amp;gt; Thread
Car --&amp;gt; Engine --&amp;gt; Carb --&amp;gt; Foat
Car --&amp;gt; Engine --&amp;gt; Bolt --&amp;gt; Thread

Car --&amp;gt; Wheel --&amp;gt; Hubcap

Spare Wheel
Truck --&amp;gt; Engine --&amp;gt; Piston
Truck --&amp;gt; Engine --&amp;gt; Carb --&amp;gt; Bolt --&amp;gt; Thread
Truck --&amp;gt; Engine --&amp;gt; Carb --&amp;gt; Foat
Truck --&amp;gt; Engine --&amp;gt; Bolt --&amp;gt; Thread

&lt;/data&gt;
</code></pre>

<p><strong>Python</strong> script <em>(running an xpath on the transformed output root node)</em></p>

<pre class="lang-py prettyprint-override"><code>import lxml.etree as ET

# LOAD XML AND XSL DOCS
dom = ET.parse('Input.xml')
xslt = ET.parse('XSLTScript.xsl')

# TRANSFORM XML
transform = ET.XSLT(xslt)
newdom = transform(dom)

# XPATH NEW DOM ROOT NODE (&lt;data&gt;)        
print(newdom.xpath('/data')[0].text.replace("\n\n", "\n"))

# Car --&gt; Engine --&gt; Piston
# Car --&gt; Engine --&gt; Carb --&gt; Bolt --&gt; Thread
# Car --&gt; Engine --&gt; Carb --&gt; Foat
# Car --&gt; Engine --&gt; Bolt --&gt; Thread
# Car --&gt; Wheel --&gt; Hubcap
# Spare Wheel
# Truck --&gt; Engine --&gt; Piston
# Truck --&gt; Engine --&gt; Carb --&gt; Bolt --&gt; Thread
# Truck --&gt; Engine --&gt; Carb --&gt; Foat
# Truck --&gt; Engine --&gt; Bolt --&gt; Thread
</code></pre>