← Back to context

Comment by ndriscoll

3 months ago

Ah, I could've sworn that it worked in some version of the page that I tried as I iterated on things, but it could be that the browser just froze on my previously working page and I fooled myself.

Adding xmlns:exsl="http://exslt.org/common" to your xsl:stylesheet and doing select="exsl:node-set($nav-menu-items)/item" seems to work on both Chrome and Librewolf.

tried that, getting an empty match.

here is the actual stylesheet i am using:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0" xmlns:exsl="http://exslt.org/common" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
      <xsl:output method="html"/>

      <xsl:variable name="nav-menu">
        <item href="/">Home</item>
        <item href="/about.xhtml">About</item>
      </xsl:variable>

      <xsl:template match="document">
        <html>
          <head>
            <meta charset="utf-8" />
            <title><xsl:value-of select="title" /></title>
            <link rel="stylesheet" type="text/css" href="site.css" />
          </head>

          <body>
            <!-- <xsl:apply-templates select="document('nav-menu.xml')/menu"> -->
            <xsl:apply-templates select="exsl:node-set($nav-menu)/item">
              <xsl:with-param name="current" select="@name"/>
            </xsl:apply-templates>
            <xsl:apply-templates select="content" />
          </body>
        </html>
      </xsl:template>

      <xsl:template match="item">
        <xsl:param name="current"/>
        <xsl:choose>
          <xsl:when test="@href=$current">
            <a class="selected"><xsl:apply-templates/></a>
          </xsl:when>
          <xsl:otherwise>
            <a href="{@href}"><xsl:apply-templates/></a>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:template>

      <xsl:template match="content">
        <xsl:apply-templates select="@*|node()" />
      </xsl:template>

      <xsl:template match="@*|node()">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
      </xsl:template>
    </xsl:stylesheet>

documents look like this:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <?xml-stylesheet type="text/xsl" href="site.xsl"?>
    <document name="about">
      <title>About Us</title>
      <content>
        html content here, to be inserted without change
      </content>
    </document>

if i use the document() function, with nav-menu.xml looking like this:

    <menu>
      <item href="/">Home</item>
      <item href="/about.xhtml">About</item>
    </menu>

then i get the menu items, but the test <xsl:when test="@href=$current"> fails

  • It looks like it's related to your setting the default namespace xmlns="http://www.w3.org/1999/xhtml". You could either add a xmlns:example="http://example.org/templates" and then replace `item` with `example:item` everywhere, or you can override the default namespace within your variable's scope:

        <xsl:variable name="nav-menu-items" xmlns="">
            <item href="/">Home</item>
            <item href="/about.xhtml">About</item>
        </xsl:variable>
    

    I think you also don't really need to set the default namespace to xhtml, so I believe you could remove that and not worry about namespaces at all (except for xsl and exsl).

    The test is failing because it's `/about.xhtml` in the template but `about` outside. You'd either need to add a name attribute to item to compare on or make it match the href.

    That should make your thing work if I haven't fooled myself again. :)

    • I think you also don't really need to set the default namespace to xhtml

      you are right. i removed it, and it works. typical "copy from stackoverflow" error. these namespaces are a mystery and not intuitive at all. i suppose most people don't notice that because it only applies to xml data within the stylesheet. most people won't have that so they won't notice an issue. the less the better.

      for the other error, my mistake, duh! in my original example in https://news.ycombinator.com/item?id=44961352 i am comparing $current/@name to a hardcoded value, so if i want to keep that comparison i have to add that value to the nav-menu data. or use a value that's already in there.

      i went with adding a name="about" attribute to the nav-menu because it keeps the documents cleaner: <document name="about"> just looks better, and it also allows me to treat it like an ID that doesn't have to match the URL which allows renaming/moving documents around without having to change the content. (they might go from about.xhtml to about/index.xhtml for example)

      i am also probably going to use the document() function instead of exsl:node-set() because having the menu data in a separate file in this case is also easier to manage. it's good to know about that option though. being able to iterate over some local data is a really useful feature. i'll keep that around as an example.

      the final piece of the puzzle was:

          <xsl:if test="position() != last()"> | </xsl:if>
      

      to put a separator between the items, but not after.

      that sorted, now it all works. thank you again.

      btw, it's funny that we are turning hackernews into an xsl support forum. i guess i should write all that up into a post some day.

      1 reply →