Search code examples
xsltxslt-2.0xslt-3.0

How to create a merged xml node from either a list of xpaths (OR how to merge individual inconsistent nodes)


How would I create a collapsed xml structure from of an inconsistent list of xpaths? xslt 3.0 / 2.0 is preferred.

Input xml

<root>
    <accepted>
        <x xp="vehicle/car/models/model/part/partnumber"/>
        <x xp="vehicle/car/models/model/part/vendor"/>
        <x xp="vehicle/car/models/model/part/vendor/name"/>
        <x xp="vehicle/car/models/model/part/vendor/email"/>
    </accepted>
    <rejected>
        <x xp="vehicle/car/models/model/part/partnumber"/>
        <x xp="vehicle/car/models/model/part/vendor"/>
        <x xp="vehicle/car/models/model/part/vendor/name"/>
        <x xp="vehicle/car/models/model/part/vendor/email"/>
        <x xp="vehicle/car/models/model/part/vendor/telephone"/>
    </rejected>
    <offices>
        <x xp="country/city/name"/>
        <x xp="country/city/district/name"/>
        <x xp="country/city/district/numberofstores"/>
        <x xp="country/city/district/totalrevenue"/>
    </offices>
</root>

Desired output:

<xml>
    <vehicle>
        <car>
            <models>
                <model>
                    <part>
                        <partnumber/>
                        <vendor>
                            <name/>
                            <email/>
                            <telephone/>
                        </vendor>
                    </part>
                </model>
            </models>
        </car>
    </vehicle>
    <country>
        <city>
            <district>
                <name/>
                <numberofstores/>
                <totalrevenue/>
            </district>
        </city>
    </country>
</xml>

What I tried: I removed the duplicate xpaths using distinct-values() and then looped over this list of unique strings. For each unique string I applied tokenize() and created a nested xml element for each delimited portion of the string. The result is an xml node which I stored in a variable. But the problem now is that I end up with with a child node for each unique xpath and I couldn't figure out how to merge these nodes. The alternative question would be how would I merge the below xml structure into a collapsed tree? (keeping in mind that this source xml comes from a variable)

<xml>
    <vehicle>
        <car>
            <models>
                <model>
                    <part>
                        <partnumber/>
                    </part>
                </model>
            </models>
        </car>
    </vehicle>
    <vehicle>
        <car>
            <models>
                <model>
                    <part>
                        <vendor>
                            <name/>
                        </vendor>
                    </part>
                </model>
            </models>
        </car>
    </vehicle>
    ...
    <country>
        <city>
            <district>
                <name/>
                <numberofstores/>
                <totalrevenue/>
            </district>
        </city>
    </country>
</xml>

Solution

  • You can use a recursive grouping function:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:mf="http://example.com/mf"
        exclude-result-prefixes="#all"
        version="3.0">
    
      <xsl:function name="mf:group" as="element()*">
          <xsl:param name="paths" as="xs:string*"/>
          <xsl:for-each-group select="$paths" group-by="if (contains(., '/')) then substring-before(., '/') else if (. != '') then . else ()">
              <xsl:element name="{current-grouping-key()}">
                  <xsl:sequence select="mf:group(current-group() ! substring-after(., '/'))"/>
              </xsl:element>              
          </xsl:for-each-group>
      </xsl:function>
    
      <xsl:output method="xml" indent="yes"/>
    
      <xsl:template match="/">
        <xml>
            <xsl:sequence select="mf:group(//@xp)"/>
        </xml>
      </xsl:template>
      
    </xsl:stylesheet>
    

    https://xsltfiddle.liberty-development.net/pNvtBGK