The XSLT stylesheet homepage.xsl builds the Web pages that display the knowledge matrix.
File nav.xsl contains XSLT templates that place navigational links (such as ``Next,'' ``Previous,'' and so forth) on the top and bottom of the page. There is a small test driver script called nav-test.xsl that exercises nav.xsl for testing.
Since Knowmat is technically part of the TCC help system, and since the help system is currently generated by WebStyler, the homepage.html page in the Knowmat directory is the point where the output we generate connects to the rest of the help system.
Unlike a typical XSLT stylesheet, this application makes extensive use of the <exsl:document> extension to write a set of pages:
The homepage.html page is not generated directly by the homepage.xsl script. Instead, the script generates the homepage.g file that is input to WebStyler. Thus, whenever Make is run in the Knowmat directory, none of the changes will appear until Make is run in the root help directory /u/www/docs/tcc/help/.
The normal output of the homepage.xsl script, then, is written to homepage.g.
A people.html page is generated, using the <exsl:document> extension, by translating the people.xml file.
Each of the <major-head> input elements results in the generation of one major skill page named majorN.shtml, where N takes on values 1, 2, ....
Each of their child <skill-group> children results in a page named majorN-K.shtml, where the numbers K are generated similarly. Each of these pages contains output showing all the child <skill> elements, with lists of the skillholders by skill level, each skillholder's login name being a link to the anchor on the people.shtml page for that person.
The work done by the root template includes:
Insure that the <exsl:document> is available, and fail if not.
Generate the homepage.g file containing links to the major-topic pages, and links to the departments of the people file.
Call the skill-set template to produce the bullet list of major topics and links to the generated pages.
Call the people template to generate links to the departments on the root page, and also use exsl:document to generate the <people.shtml> page.
This template processes the <skill-set> element of the input file. It does two things:
Generate a bullet list, on the homepage.g page, with links to the majorN.shtml pages.
Call the make-major-page template to generate the majorN.shtml page for each child <skill-group> child.
This named template creates one page displaying all the skill groups in a major heading.
It requires four parameters:
The URL of this page's parent.
The title of this page's parent.
The URL of the page to be generated, minus its suffix. The suffix is omitted so the child page names can be generated by adding a hyphen, and the child number, before the suffix is pasted back on.
The title of the page to be generated.
This template uses two small named templates called make-major-page-head and make-major-page-tail; these don't really do anything but prepare arguments for the standard navigational templates from nav.xsl.
This named template builds the page displaying all input <skill> elements in one <skill-group>.
The template requires four parameters:
The URL of the parent page, minus its suffix. The suffix is omitted so we can paste on a hyphen and the child position() to get the name of the child page.
The title of the parent page.
The URL of the page to be generated, without its suffix.
The title of the page to be generated.
Each call to this template displays one <skill> element from the input file. It requires no parameters; the context element must be a <skill> element.
To group people by their skill level, this template calls the make-skill-level template three times, one for each possible skill level. That template produces output only if there is at least one <skilled> person at that level.
This template is invoked to process the people.xsl file, producing a page showing the TCC's departments with the people in that department listed underneath.
It needs two parameters: parent-url, the URL of the parent page, and parent-title, the parent page's title.
Within each department, an anchor named #g-N is generated, where N is the child number of that department. These anchors are used to link from the root page to the departments.
The output for each department is generated by the display-group template, which sorts the people in that department by last name (with first name as a tiebreaker).
A three-column table is used to vertically align the display of the names. The first column contains the login name of each person as a mailto: link. The second column shows their first name and the third column their surname. The second column is right-aligned to place the first name close to the surname.
If the <person> element had a href attribute, we also generate a link to that URL, which is the person's work homepage.
This was my first XSLT project of this size, and I'd like to offer a few reflections on the XSLT language that were shaped by this project.
In general, once past the brief but steep learning curve of XSLT, most things are fairly easy. You just write a rule for each element type of the input, and use <xsl:call-template> to break the logic into pieces of a manageable size.
On a purely mundane detail level, I often omitted the closing slash before the ``>'' of empty elements.
I also tended to forget to place the dollar sign before the variable name in XPath expressions like string-length($foo)!=0
Also noticed you don't need to use an <xsl:text> element to enclose an <xsl:value-of> element.
Yes, it's verbose. However, the names are long enough to be meaningful.
One thing I found annoying, coming from imperative languages with side effects, was not being able to set up a bunch of variables with branching logic, and then pass those variables down to the next lower template. In particular, have a look at the named template make-major-page-tail. The purpose of this template is to interface to template make-page-tail (imported from nav.xsl), which provides standard page-bottom navigational features.
The first two parameter we have to set up is next-url, which is a link to the next major page. However, the last <major-head> element doesn't have a following sibling, so in that case we have to pass an empty string so the ``next page'' link will be disabled.
The second parameter, next-title, is the title of the next major page. Like the first parameter, if there isn't a next page, we want to pass an empty string here as well.
But there is no way in XSLT to say ``set these two variables to one set of values in one case and another set otherwise, then pass them to this template.'' Instead, we have to this same two-branch logic inside each <xsl:with-param> construct. This is not only verbose, but violates the design rule that all logic should be centralized. What would be really handy is a function call, or at least a macro, so I can write a function that determines whether there is a next page, and call it in both places. That would at least mean that any changes to the definition of next page would have to be implemented in only one place. It would not, however, address the charge of verbosity.
There was one tiny detail that took me about a day to get working. The standard navigational template make-page-tail, from nav.xsl, requires that the ``Previous'' link in the page footer link to that page using the title of the page. That title is passed in as parameter prev-title.
In theory, this is pretty simple. If the current node position is 1, we pass in an empty string because there is no previous node. Otherwise, we pass in the title from the node immediately preceding the one we're now building.
In the case of the make-major-page-tail template, which builds the footer links for the majorN.shtml page. So all we need is the title attribute of the <major-head> element immediately preceding the context node.
The XPath expression I wrote for the following node turned out to work the first time:
But the one I wrote for the preceding node didn't work:
What I expect this expression to do is first to select the node-set of all preceding siblings, then select the <major-head> elements of that set, then select the last element of that set, then select its title attribute.
I don't know what's going on inside this expression. Perhaps it's a bug in xsltproc. One can't use <xsl:message> to debug the middle of a complex expression.
I tried breaking it down into smaller pieces and using intermediate variables, and displaying those variables, but it all amounted to a lot of wheel-spinning.
I have two questions about the above expression. First, consider this fragment:
The axis specifier clearly says, get all the sibling elements before the context node. However, does the next part (major-head) select just the <major-head> elements from that node set, or does it select the children of nodes in that set? If the latter, how is it possible that the other expression using following-sibling::major-head works!?
The second question is, when I use the predicate [last()], to exactly what context set does the last() function apply?
Anyway, here's the version that actually worked:
<xsl:variable name="left-sibx" select="position()-1"/> <xsl:value-of select="../major-head[$left-sibx]/@title"/>
This seems a bit rude: remember the context position minus one in left-sibx; go up to the parent, select the entire set of its <major-head> siblings, then select the [$left-sibx] element of that set.