Using XSLT to create a well formatted nested table from unknown XML

可紊 提交于 2019-12-06 14:59:38

As I said in the comments, this is not going to be simple. Basically, you want to remove any duplicate nodes that have the same path (where a path is calculated using only node names) and display the resulting hierarchy.

This requires making a pre-processing pass to assign all nodes their path. We also need to provide each node with its parent path - so that it can be called later by its new parent (which formerly could be its uncle or great-uncle or...).

In the second - and final - step we will be applying Muenchian grouping to the result of the first pass, leaving only distinct nodes by path.

In this example I will be processing only elements, and the result will be presented as an unordered list.

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="node-by-path" match="node" use="@path" />
<xsl:key name="node-by-parent-path" match="node" use="@parent-path" />

<xsl:template match="/">
    <!-- first-pass -->
    <xsl:variable name="first-pass">
        <xsl:apply-templates select="*" mode="firstpass"/>
    </xsl:variable>
    <!-- output -->
    <ul>
        <xsl:apply-templates select="exsl:node-set($first-pass)/node[@parent-path='']" />
    </ul>
</xsl:template>

<xsl:template match="*" mode="firstpass">
    <xsl:variable name="parent-path">
        <xsl:for-each select="ancestor::*">
            <xsl:value-of select="concat('/', name())"/>
        </xsl:for-each>                 
    </xsl:variable> 
    <node name="{name()}" parent-path="{$parent-path}" path="{concat($parent-path, '/', name())}">
        <xsl:apply-templates select="*" mode="firstpass"/>
    </node>
</xsl:template>

<xsl:template match="node">
    <li>
        <xsl:value-of select="@name"/>
        <xsl:variable name="next" select="key('node-by-parent-path', @path)" />
        <xsl:if test="$next">
            <ul>
                <xsl:apply-templates select="$next[count(. | key('node-by-path', @path)[1]) = 1]"/>
            </ul>
        </xsl:if>
    </li>
</xsl:template> 

</xsl:stylesheet>

Test input XML

<root>
   <parent>
      <child>
         <string>A</string>
         <string>B</string>
      </child>
      <child>
         <string>C</string>
         <number>1</number>
         <number>2</number>
         <grandchild>
            <string>DD</string>
            <substring>EE</substring>
            <number>33</number>
         </grandchild>
      </child>
   </parent>
   <parent>
      <child>
         <string>F</string>
         <date>2015-02-12</date>
      </child>
      <nephew>
         <string>G</string>
      </nephew>
   </parent>
   <uncle>
      <niece>
         <string>H</string>
      </niece>
   </uncle>
</root>

Result

<?xml version="1.0" encoding="UTF-8"?>
<ul>
   <li>root<ul>
         <li>parent<ul>
               <li>child<ul>
                     <li>string</li>
                     <li>number</li>
                     <li>grandchild<ul>
                           <li>string</li>
                           <li>substring</li>
                           <li>number</li>
                        </ul>
                     </li>
                     <li>date</li>
                  </ul>
               </li>
               <li>nephew<ul>
                     <li>string</li>
                  </ul>
               </li>
            </ul>
         </li>
         <li>uncle<ul>
               <li>niece<ul>
                     <li>string</li>
                  </ul>
               </li>
            </ul>
         </li>
      </ul>
   </li>
</ul>

Rendered

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!