Generare "ad hoc" un menù multilivello: struttura dati e conversione da XML a HTML utilizzando XSLT

Ho utilizzato questa tecnica per generare dinamicamente un accordion menù multilivello ...
Consideramo il seguente diagramma ad albero, rappresentazione della struttura di un possibile menù di navigazione:
- Root (nodeID=1, parentID=0)
- Item 1 (nodeID=2, parentID=1)
  - item 1.1 (nodeID=3, parentID=2)
  - item 1.2 (nodeID=4, parentID=2)
     - item 1.2.1 (nodeID=5, parentID=4)
  - item 1.3 (nodeID=6, parentID=2)
- Item 2 (nodeID=7, parentID=1)
La seguente struttura può essere memorizzata nella seguente tabella di un database: MenuTable(nodeID,parentID,title).
A partire dalla tabella MenùTable è possibile generare facilmente la rappresentazione XML del grafo ad albero utilizzando il seguente codice:
Dim sqlString As String = "SELECT NodeID, ParentID, title FROM MenuTable ORDER BY NodeID"
        Dim cmd As SqlCommand = New SqlCommand(sqlString)
        Dim conn As SqlConnection
        conn = New SqlConnection(DBConnection)
        Dim ds As DataSet = New DataSet
        Dim da As SqlDataAdapter = New SqlDataAdapter
        da.SelectCommand = cmd
        da.Fill(ds)
        Dim dt As DataTable = ds.Tables(0)

        ' create an XmlDocument (with an XML declaration)
        Dim XDoc As New XmlDocument()
        Dim XDec As XmlDeclaration = XDoc.CreateXmlDeclaration("1.0", Nothing, Nothing)
        XDoc.AppendChild(XDec)

        ' iterate through the sorted data
        ' and build the XML document
        For Each Row As DataRow In dt.Rows
            ' create an element node to insert
            ' note: Element names may not have spaces so use ID
            ' note: Element names may not start with a digit so add underscore
            Dim NewNode As XmlElement = XDoc.CreateElement("_" & Row("NodeID").ToString())
            NewNode.SetAttribute("NodeID", Row("NodeID").ToString())
            NewNode.SetAttribute("ParentID", Row("ParentID").ToString())
            NewNode.SetAttribute("title", Row("title").ToString())

            ' special case for top level node
            If IsDBNull(Row("ParentID")) Then
                XDoc.AppendChild(NewNode)
            Else
                ' root node
                ' use XPath to find the parent node in the tree
                Dim SearchString As [String]
                SearchString = [String].Format("//*[@NodeID=""{0}""] ", Row("ParentID").ToString())
                Dim Parent As XmlNode = XDoc.SelectSingleNode(SearchString)

                If Parent IsNot Nothing Then
                    Parent.AppendChild(NewNode)
                Else

                    ' Handle Error: Employee with no boss
                End If
            End If
        Next
        XDoc.Save(Server.MapPath("~/menu/menu.xml"))
In tal modo nella cartella menu della nostra applicazione verrà generato il file menu.xml così fatto:
<?xml version="1.0"?>
<_1 NodeID="1" ParentID="0" title="Root">
  <_2 NodeID="2" ParentID="1" title="item 1">
    <_3 NodeID="3" ParentID="2" title="item 1.1" />
    <_4 NodeID="4" ParentID="2" title="item 1.2" >
      <_5 NodeID="5" ParentID="5" title="item 1.2.1" />
    </_4>
    <_6 NodeID="6" ParentID="2" title="1.3" />
  </_2>
  <_7 NodeID="7" ParentID="1" title="item 2"/>
</_1>
Vogliamo ora trasformare la struttura XML in una stringa HML così fatta:
<li><a name="_2" href="#">item 1</a>
   <ul>
      <li><a name="_3" href="page.aspx?nodeID=3">item 1.1</a></li>
      <li><a name="_4" href="#">item 1.2</a>
         <ul>
            <li><a name="_5" href="page.aspx?nodeID=5">item 1.2.1</a>
         </ul>
      </li>
      <li><a name="_6" href="page.aspx?nodeID=6">item 1.3</a></li>
   </ul>
</li>
<li><a name="_7" href="page.aspx?nodeID=7">item 2</a></li>
Definiamo quindi il seguente file XSLT di trasformazione e memorizzaiamolo nella cartella "menu" con il nome menu.xslt
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes"/>


    <!--The <xsl:template> element contains rules to apply when a specified node is matc-->
    <xsl:template match="node()">
        
        <!--calcola il numero di nodi figli dell'elemento corrente-->
        <xsl:variable name="total-el">
            <xsl:value-of select="count(descendant::*)"/>
        </xsl:variable>

        <xsl:variable name="name">
            <xsl:value-of select="name()"/>
        </xsl:variable>       
            
        <!--se il nodo nonè la root ed il numero di nodi figlio è >0 allora applica il seguente modello ai nodi figlio dell'elemento corrente..-->

            <xsl:if test="$total-el > 0 and $name != '_1'">
                <li>
                    <a name="{name()}" href="#" rel="self">
                        <xsl:value-of select="@NodeHTML"/>
                    </a>
                    <ul>
                        <xsl:apply-templates />
                    </ul>
                </li>
            </xsl:if>


            <xsl:if test="$total-el > 0 and $name = '_1'">
                    <ul class="topnav">
                        <xsl:apply-templates />
                    </ul>
            </xsl:if>      
        
            <!--altrimenti ...-->
            <xsl:if test="$total-el = 0">
                <li>
                    <a name="{name()}" href="articles.aspx?NodeID={name()}">
                        <xsl:value-of select="@NodeHTML"/>
                    </a>
                </li>
            </xsl:if>


    </xsl:template>

</xsl:stylesheet>
Non ci resta infine che applicare la trasformazione mediante il segunte codice:
Dim xDoc As New XmlDocument()
xDoc.Load(Server.MapPath("~/menu/menu.xml"))
Dim sw As New System.IO.StringWriter()
Dim xslTrans As New XslCompiledTransform()
xslTrans.Load(Server.MapPath("~/menu/menu.xslt"))
xslTrans.Transform(xDoc.CreateNavigator(), New XsltArgumentList(), sw)

Using writer As System.IO.StreamWriter = System.IO.File.AppendText(HttpRuntime.AppDomainAppPath & "menu\menu.txt")
   writer.WriteLine(sw.ToString())
End Using
producendo nel file menu.txt nella cartella menu il codice html desiderato.