Search code examples
xmlxsltmenunodesdescendant

Finding an specific node in a XML file


I have a XML with 5 or 6 levels. All nodes has an unique ID. By this ID, I need to find a specif node and create a list showing only the ancestors of this node. I'll try to explain better with codes:

That's my XML with all options:

<MenuConfig>
<Menu name="name-1" location="location 1" title="Menu 1">
    <Item name="name-1.1" location="location 1.1.jsp" title="Menu 1.1"/>
    <Item name="name-1.2" location="location 1.2.jsp" title="Menu 1.2"/>
    <Item name="name-1.3" location="location 1.3.jsp" title="Menu 1.3">
        <Item name="name-1.3.1" location="location 1.3.1.jsp" title="Menu 1.3.1"/>
        <Item name="name-1.3.2" location="location 1.3.2.jsp" title="Menu 1.3.2"/>
    </Item>
    <Item name="name-1.4" location="location 1.4.jsp" title="Menu 1.3">
        <Item name="name-1.4.1" location="location 1.4.1.jsp" title="Menu 1.4.1"/>
        <Item name="name-1.4.2" location="location 1.4.2.jsp" title="Menu 1.4.2"/>
    </Item>
</Menu>
<Menu name="name-2" location="location 2" title="Menu 2">
    <Item name="name-2.1" location="location 2.1.jsp" title="Menu 2.1"/>
    <Item name="name-2.2" location="location 2.2.jsp" title="Menu 2.2"/>
    <Item name="name-2.3" location="location 2.3.jsp" title="Menu 2.3">
        <Item name="name-2.3.1" location="location 2.3.1.jsp" title="Menu 2.3.1"/>
        <Item name="name-2.3.2" location="location 2.3.2.jsp" title="Menu 2.3.2"/>
    </Item>
    <Item name="name-2.4" location="location 2.4.jsp" title="Menu 2.3">
        <Item name="name-2.4.1" location="location 2.4.1.jsp" title="Menu 2.4.1"/>
        <Item name="name-2.4.2" location="location 2.4.2.jsp" title="Menu 2.4.2"/>
    </Item>
</Menu>
<Menu name="name-3" location="location 3" title="Menu 3">
    <Item name="name-3.1" location="location 3.1.jsp" title="Menu 3.1"/>
    <Item name="name-3.2" location="location 3.2.jsp" title="Menu 3.2"/>
    <Item name="name-3.3" location="location 3.3.jsp" title="Menu 3.3">
        <Item name="name-3.3.1" location="location 3.3.1.jsp" title="Menu 3.3.1"/>
        <Item name="name-3.3.2" location="location 3.3.2.jsp" title="Menu 3.3.2"/>
    </Item>
    <Item name="name-3.4" location="location 3.4.jsp" title="Menu 3.3">
        <Item name="name-3.4.1" location="location 3.4.1.jsp" title="Menu 3.4.1">
            <Item name="name-3.4.1.1" location="location 3.4.1.1.jsp" title="Menu 3.4.1.1"/>
            <Item name="name-3.4.1.2" location="location 3.4.1.2.jsp" title="Menu 3.4.1.2"/>
        </Item>
        <Item name="name-3.4.2" location="location 3.4.2.jsp" title="Menu 3.4.2">
            <Item name="name-3.4.2.1" location="location 3.4.2.1.jsp" title="Menu 3.4.2.1"/>
            <Item name="name-3.4.2.2" location="location 3.4.2.2.jsp" title="Menu 3.4.2.2"/>
        </Item>
    </Item>
</Menu>
</Menu>
</MenuConfig>

That's the XSLT that I'm trying to create:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:xalan="http://xml.apache.org/xslt">
<xsl:output omit-xml-declaration="yes" indent="yes"/>

<xsl:param name="menu"/>

<xsl:template match="/MenuConfig/Menu">
    <div id="data-sidebar">
        <ul class="sidebar list-unstyled">
            <li>
            <xsl:attribute name="class">
                <xsl:if test="@name = $menu">active</xsl:if>
            </xsl:attribute>
                <xsl:element name="a">
                    <xsl:attribute name="href">
                        <xsl:value-of select="@location"/>
                    </xsl:attribute>
                    <xsl:value-of select="@title"/>
                </xsl:element>
                <ul>
                    <xsl:apply-templates/>
                </ul>
            </li>
        </ul>
    </div>
</xsl:template>

<xsl:template match="Item[not(Item)]">
    <li>
    <xsl:attribute name="class">
        <xsl:if test="@name = $menu">active</xsl:if>
    </xsl:attribute>
        <xsl:element name="a">
            <xsl:attribute name="href">
                <xsl:value-of select="@location"/>
            </xsl:attribute>
            <xsl:value-of select="@title"/>
        </xsl:element>
    </li>
</xsl:template>

<xsl:template match="Item[Item]">
    <li>
    <xsl:attribute name="class">
        <xsl:if test="@name = $menu">active</xsl:if>
    </xsl:attribute>
        <xsl:element name="a">
            <xsl:attribute name="href">
                <xsl:value-of select="@location"/>
            </xsl:attribute>
            <xsl:value-of select="@title"/>
        </xsl:element>
        <ul>
            <xsl:apply-templates select="Item" />
        </ul>
    </li>
</xsl:template>

</xsl:stylesheet>

I have this external param "menu" where I'll get the current list position (name property)

Let's imagine that the name param is "name-3.4.1.2". In this case, I should show the menu-3 only, the menu-3.4, menu-3.4.1 and children and menu-3.4.2 (sibling of 3.4.1) but without the children. Maybe (it's not confirmed yet by my business manager) I should show only 3 or 4 ancestors levels. Like, if the menu selected is level 6 (1.2.3.4.5.6), I must show up to level 3 (1.2.3) only.

I have no idea how to do it. My real XML is deeper than that and it could be indefinitely deep.

Sorry if I'm not clear enough, but I can't explain better.

Thanks guys...

EDIT: It's the expected output. It's a HTML:

  <div id="data-sidebar">
        <ul class="sidebar list-unstyled">
            <li class=""><a href="location 3.jsp">Menu 3</a>
                <ul>
                    <li class=""><a href="location 3.4.jsp">Menu 3.4</a>
                        <ul>
                            <li class=""><a href="location 3.4.1">Menu 3.4.1</a>
                                <ul>
                                    <li class=""><a href="location 3.4.1.1">Menu 3.4.1.1</a></li>
                                    <li class="active"><a href="location 3.4.1.2">Menu 3.4.1.2</a></li>
                                </ul>
                            </li>
                            <li class=""><a href="location 3.4.2">Menu 3.4.2</a></li>
                        </ul
                    </li>
                </ul>
            </li>
        </ul>
    </div>

At the browser it should appear like this:

  • Menu 3
    • Menu 3.4
      • Menu 3.4.1
        • Menu 3.4.1.1
        • Menu 3.4.1.2
      • Menu 3.4.2

Solution

  • Although this is not exactly simple, I think you are making it much more complicated than it needs to be. Try this as your starting point:

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:param name="name" select="'name-3.4.1.2'"/>
    
    <xsl:template match="*">
        <xsl:if test="descendant-or-self::*[@name=$name]">
            <xsl:copy>
                <xsl:copy-of select="@*"/>
                <xsl:apply-templates select="*"/>
            </xsl:copy>
        </xsl:if>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Applied to your example input (corrected for well formedness!) the result is:

    <?xml version="1.0" encoding="UTF-8"?>
    <MenuConfig>
       <Menu name="name-3" location="location 3" title="Menu 3">
          <Item name="name-3.4" location="location 3.4.jsp" title="Menu 3.3">
             <Item name="name-3.4.1" location="location 3.4.1.jsp" title="Menu 3.4.1">
                <Item name="name-3.4.1.2" location="location 3.4.1.2.jsp" title="Menu 3.4.1.2"/>
             </Item>
          </Item>
       </Menu>
    </MenuConfig>