Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Westi on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

A-Z List links after each letter of the alphabet 1

Status
Not open for further replies.

73pixieGirl

Programmer
Oct 3, 2006
65
US
Hello,
I'm very new to XML and I've inherited a website built entirely in XML (lucky me). I have a request to build an A-Z index that will list our inhouse resources alphabetically. So when a user clicks on the letter M, the page jumps down to a list of resources that begin with M. But the requestor would also like for the index to be displayed after each letter grouping of the alphabet (a simple Back to the Top link won't do for some reason). Can anyone tell me what steps I need to take to build a page like this? I've read many XML books online and hardcopy, and can understand the logistics, but actually creating the code is a different story. I know how to build the xml file that contains the names of the resources, that's the easy part! But styling it per the requestor's request has got me stumped.
TIA!
 
Well, since I am an XSLT person, I always suggest the use of XSLT for this type of thing.

You have a requirement for grouping. Unless you have XSLT 2.0 available (few do), you will need to use the Muench method described by me in this thread: thread426-1210886. Look in the second half of the thread or just search for the word "Muench".

The anchor names are clearly computable, i.e. href="#a" etc. So it merely is a matter of emitting your 'index' after each alphabetical break in the resource list. You can even use a modest variant of the Muench method to prevent your XSLT from emitting letters for which there are no resources.

Give it a try...

Also, if you are new to XML, you are probably new to XSLT, so you might do a small tutorial at and other places online to better understand the workings of XSLT.

Tom Morrison
 
Ok, so I think I got the Muench code to work (it's taken me this long to figure it out!), but I'm doing something wrong. It looks like it's repeating the A-Z list based on the number of resources in the list. Here's the jist of what I have:

at the top:
<xsl:key name="testing" match="resource" use="substring(name,1,1)" />

<xsl:template match="//resources">

<xsl:for-each select="//resource[generate-id(.)=generate-id(key('testing', substring(name,1,1))[1])]">
<xsl:for-each select="key('testing', substring(name,1,1))">

--here is where the resources are listed, with their descriptions

</xsl:for-each>

<span style="letter-spacing:7px; font-size=12pt"><a href="#A">A</a>-<a href="#B">B</a>-<a href="#C">C</a>-<a href="#D">D</a>-<a href="#E">E</a>-<a href="#F">F</a>-<a href="#G">G</a>-<a href="#H">H</a>-<a href="#I">I</a>-<a href="#J">J</a>-<a href="#K">K</a>-<a href="#L">L</a>-<a href="#M">M</a>-<a href="#N">N</a>-<a href="#O">O</a>-<a href="#P">P</a>-<a href="#Q">Q</a>-<a href="#R">R</a>-<a href="#S">S</a>-<a href="#T">T</a>-<a href="#U">U</a>-<a href="#V">V</a>-<a href="#W">W</a>-<a href="#X">X</a>-<a href="#Y">Y</a>-<a href="#Z">Z</a></span>

</xsl:for-each>
</xsl:template>

Is my problem with the substring(name,1,1)? I'm trying to pull the first character of each resource so it recognizes that as a unique value, which in your example from your past post would be the xpos.
Thanks!!!!
 
That brings up another question...what's the difference between XSL and XSLT?

The input document isn't in the typical format I've been seeing in these posts or books. Each resource is in it's own .xml file, and there are multiple files (.xsl, .xml, and a sitemap.xmap - unix platform) that are used to create the page with the list of resources (remember, I inherited this site...and the guy that built it is a brainiac!). There are 170 resources. The snippet of code I gave earlier is the meat of it. It's the only file I really need to modify, and I do have it working, except it repeats the list of resources 170 times. The resource files have the following nodes:
<resource>
<name>Resource Name</name>
<description>Resource Description</description>
</resource>
I know this probably is of little help, but I think there's something in the code snippet that needs tweaking to only print out the resources once, not 170 times!
 
Unfortunately, this represents a significant amount of feature creep. Grouping across multiple documents, while possible, is not as easy as we would like.

So, another question, in the interest of simplifying the problem:
Can you use a two step solution to produce the result?​

If you are doing this, perhaps once a day, in order to produce a static web page, a two step solution is feasible. However, if you are recreating the HTML upon demand for a browser a two step solution is perhaps not too practical.

Tom Morrison
 
Upon rethinking this, I may be suffering from solution bloviation.

The requirement here is reasonably simple, and really requires only the ability to sort. I often forget just how flexible XSL variables can be, but this is one problem that can benefit from creatively using XSL variables.

I don't have the time to do this right now, but here is an outline of a possible approach.[ul][li]create an XSL variable that has a node for each resource[/li][li]create a second variable by iterating over the first variable (xsl:for-each), with an included xsl:sort on the <name> element. Create a node in the second variable for each unique letter. You will use this second variable for creating your A-Z index links whereever you need them.[/li][li]Finally, begin producing your HTML output. Iterate of the first variable with another included xsl:sort on the <name> element, in the same manner as when producing the second variable, but this time producing the desired HTML output for each resource. When you detect a logic break in the first letter of the resource name, do another for-each over the second variable to produce the HTML for the index.[/li][/ul]

Take a stab at this and see what you come up with.

Tom Morrison
 
Ok, I'll have to work on your new approach tomorrow, and if you don't hear back from me by Monday, call the XML Police! :)

So there's no way to rework the Muench method to prevent the list from repeating 170 times? It's a shame b/c it does work...
Thanks for your help!
 
Put your XSLT on here and I will give it a look. (The snippets just aren't communicating well enough...)

Tom Morrison
 
Okay, so I had a coupla minutes...

Code:
<?xml version="1.0"?>
<resource-list>
<name>ZZZ.xml</name>
<name>YZY.xml</name>
<name>XYZ.xml</name>
<name>XYX.xml</name>
<name>BCD.xml</name>
<name>MMM.xml</name>
<name>ABC.xml</name>
<name>AAA.xml</name>
</resource-list>
where each of these xml documents has the following structure:
Code:
<?xml version="1.0"?>
<resource>
<name>ABC Resource</name>
<description>Resource Description</description>
</resource>
You may extrapolate to determine the <name> value of the other documents. However, I did not always use upper case letters in the <name> element.

Here is an XSLT stylesheet.
Code:
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform">[/URL]

<xsl:template match="/">
	<xsl:variable name="allResources">
		<xsl:for-each select="resource-list/name">
			<xsl:copy-of select="document(.)/*"/>
		</xsl:for-each>
	</xsl:variable>
	<xsl:variable name="allSorted">
		<xsl:for-each select="$allResources/resource">
			<xsl:sort select="name"/>
			<sorted-entry><letter><xsl:value-of select="translate(substring(name,1,1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/></letter><xsl:copy-of select="."/></sorted-entry>
		</xsl:for-each>
	</xsl:variable>
<html>
<body>
<xsl:for-each select="$allSorted/sorted-entry">
<xsl:sort select="letter"/>
<xsl:sort select="resource/name"/>
	<xsl:variable name="lastLetter" select="letter"/>
	<xsl:if test="not(preceding-sibling::sorted-entry[letter=$lastLetter])">
		<xsl:for-each select="$allSorted/sorted-entry">
		<xsl:sort select="letter"/>
			<xsl:variable name="lastLink" select="letter"/>
			<xsl:if test="not(preceding-sibling::sorted-entry[letter=$lastLink])">
			<a><xsl:attribute name="href">#<xsl:value-of select="letter"/></xsl:attribute>
			   <xsl:value-of select="letter"/>
			</a><xsl:text> </xsl:text>
			</xsl:if>
		</xsl:for-each>
		<a><xsl:attribute name="name"><xsl:value-of select="$lastLetter"/></xsl:attribute></a>
	</xsl:if>
<h2><xsl:value-of select="resource/name"/></h2><br/><h3><xsl:value-of select="resource/description"/></h3>
</xsl:for-each>
<xsl:for-each select="$allSorted/sorted-entry">
<xsl:sort select="letter"/>
	<xsl:variable name="lastLink" select="letter"/>
	<xsl:if test="not(preceding-sibling::sorted-entry[letter=$lastLink])">
	<a><xsl:attribute name="href">#<xsl:value-of select="letter"/></xsl:attribute><xsl:value-of select="letter"/></a><xsl:text> </xsl:text>
	</xsl:if>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

And here is the result:
Code:
<html>
  <body>
    <a href="#A">A</a> 
    <a href="#B">B</a> 
    <a href="#M">M</a> 
    <a href="#X">X</a> 
    <a href="#Y">Y</a> 
    <a href="#Z">Z</a> 
    <a name="A"></a>
    <h2>AAA Resource</h2>
    <br>
    <h3>Resource Description</h3>
    <h2>ABC Resource</h2>
    <br>
    <h3>Resource Description</h3>
    <a href="#A">A</a> 
    <a href="#B">B</a> 
    <a href="#M">M</a> 
    <a href="#X">X</a> 
    <a href="#Y">Y</a> 
    <a href="#Z">Z</a> 
    <a name="B"></a>
    <h2>BCD Resource</h2>
    <br>
    <h3>Resource Description</h3>
    <a href="#A">A</a> 
    <a href="#B">B</a> 
    <a href="#M">M</a> 
    <a href="#X">X</a> 
    <a href="#Y">Y</a> 
    <a href="#Z">Z</a> 
    <a name="M"></a>
    <h2>MMM Resource</h2>
    <br>
    <h3>Resource Description</h3>
    <a href="#A">A</a> 
    <a href="#B">B</a> 
    <a href="#M">M</a> 
    <a href="#X">X</a> 
    <a href="#Y">Y</a> 
    <a href="#Z">Z</a> 
    <a name="X"></a>
    <h2>XYX Resource</h2>
    <br>
    <h3>Resource Description</h3>
    <h2>xyz Resource</h2>
    <br>
    <h3>Resource Description</h3>
    <a href="#A">A</a> 
    <a href="#B">B</a> 
    <a href="#M">M</a> 
    <a href="#X">X</a> 
    <a href="#Y">Y</a> 
    <a href="#Z">Z</a> 
    <a name="Y"></a>
    <h2>YZY Resource</h2>
    <br>
    <h3>Resource Description</h3>
    <a href="#A">A</a> 
    <a href="#B">B</a> 
    <a href="#M">M</a> 
    <a href="#X">X</a> 
    <a href="#Y">Y</a> 
    <a href="#Z">Z</a> 
    <a name="Z"></a>
    <h2>zzz Resource</h2>
    <br>
    <h3>Resource Description</h3>
    <a href="#A">A</a> 
    <a href="#B">B</a> 
    <a href="#M">M</a> 
    <a href="#X">X</a> 
    <a href="#Y">Y</a> 
    <a href="#Z">Z</a> 
  </body>
</html>

Hope this helps.

Tom Morrison
 
I'm beyond lost...what you posted looks great, I just don't understand it! I don't have an input document to work from, just a bunch of individual files in a folder.

Here's my .xsl file (still not sure what the difference is between xsl and xslt):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl=" version="1.0">

<xsl:include href="boilerplate_styling.xsl"/>

<xsl:key name="testing" match="resource" use="substring(name,1,1)" />

<xsl:template match="//resources">
<center>
<span style="letter-spacing:7px; font-size=12pt"><a href="#A">A</a>-<a href="#B">B</a>-<a href="#C">C</a>-<a href="#D">D</a>-<a href="#E">E</a>-<a href="#F">F</a>-<a href="#G">G</a>-<a href="#H">H</a>-<a href="#I">I</a>-<a href="#J">J</a>-<a href="#K">K</a>-<a href="#L">L</a>-<a href="#M">M</a>-<a href="#N">N</a>-<a href="#O">O</a>-<a href="#P">P</a>-<a href="#Q">Q</a>-<a href="#R">R</a>-<a href="#S">S</a>-<a href="#T">T</a>-<a href="#U">U</a>-<a href="#V">V</a>-<a href="#W">W</a>-<a href="#X">X</a>-<a href="#Y">Y</a>-<a href="#Z">Z</a>
</span>
</center>
<p/>
<xsl:apply-templates/>
</xsl:template>

<xsl:template match="//resource">
<!-- displays 'New' or 'Trial' or whatever may be in the <status> element's 'value' attribute -->
<xsl:call-template name="display_resource_message"/>

<xsl:element name="a">
<xsl:attribute name="name"><xsl:value-of select="substring(name,1,1)"/></xsl:attribute>
</xsl:element>

<xsl:for-each select="//resource[generate-id(.)=generate-id(key('testing', substring(name,1,1))[1])]">
<xsl:for-each select="key('testing', substring(name,1,1))">

<span class="link-in-list">
<xsl:call-template name="display-smartlink">
<xsl:with-param name="link_text" select="name"/>
<xsl:with-param name="url_low" select="url/@low"/>
<xsl:with-param name="url_high" select="url/@high"/>
</xsl:call-template>
</span>

<xsl:element name="a">
<xsl:attribute name="href">/website/resource/<xsl:value-of select="@filename"/>
</xsl:attribute>
<img src="/website/icons/info_icon2.gif" width="30" height="20" border="0"/>
</xsl:element>

<br/>
<span style="font-size:10pt"><xsl:value-of select="short_description"/>
</span><p/>

</xsl:for-each>
<center>
<span style="letter-spacing:7px; font-size=12pt"><a href="#A">A</a>-<a href="#B">B</a>-<a href="#C">C</a>-<a href="#D">D</a>-<a href="#E">E</a>-<a href="#F">F</a>-<a href="#G">G</a>-<a href="#H">H</a>-<a href="#I">I</a>-<a href="#J">J</a>-<a href="#K">K</a>-<a href="#L">L</a>-<a href="#M">M</a>-<a href="#N">N</a>-<a href="#O">O</a>-<a href="#P">P</a>-<a href="#Q">Q</a>-<a href="#R">R</a>-<a href="#S">S</a>-<a href="#T">T</a>-<a href="#U">U</a>-<a href="#V">V</a>-<a href="#W">W</a>-<a href="#X">X</a>-<a href="#Y">Y</a>-<a href="#Z">Z</a>
</span>
</center><p/>
</xsl:for-each>

</xsl:template>
</xsl:stylesheet>

Obviously this is after I added the Muench code.
Thanks!
 
Each resource is in it's own .xml file... There are 170 resources.

I took this to mean that the XSL must gather the resource names and descriptions from 170 different XML files. But from this XSL it appears that you already have the resource names and descriptions collected into one document, correct?

(BTW, use the e-mail notification check box to get notification when I reply in this thread. A couple more turns on this puppy and it'll be done! :-D )

Tom Morrison
 
Each resource and description is in a separate file in a folder named resources:
resource_name1.xml
resource_name2.xml
resource_name3.xml etc.

There are other .xml and .xsl files that are used to build this one page with all of the resources (using a sitemap.xmap), and I'm assuming one of those files is used to grab all the .xml files from the resource folder, pulls the name node to get the resource name, sorts the resource names, then the .xsl file above is used to build the page with the list of resources and their descriptions (and a link to the actual resource page, but that's not an issue here).

Does that make any sense? From what I can tell, since this is on a unix platform with cocoon and the sitemap, it's a little different than using a windows based platform, no?

(email notification has been checked!) :)
 
still not sure what the difference is between xsl and xslt
XSLT is a subset, dealing with transformations of XML documents, of a much larger standard called XSL. We are really dealing with XSLT here.

I am not an expert on Cocoon. I have looked at the Cocoon website to try to determine if the document names could be found in the sitemap XML document, but I have so far not determined how that might be accomplished.

But for now, you seem to have all this wrapped up in one input document, so let's turn to that.
Code:
[highlight]<xsl:template match="//resource">[/highlight]
        <!-- displays 'New' or 'Trial' or whatever may be in the <status> element's 'value' attribute -->
        <xsl:call-template name="display_resource_message"/>

                <xsl:element name="a">
                        <xsl:attribute name="name"><xsl:value-of select="substring(name,1,1)"/></xsl:attribute>
                </xsl:element>

        [highlight]<xsl:for-each select="//resource[/highlight][generate-id(.)=generate-id(key('testing', substring(name,1,1))[1])]">
                <xsl:for-each select="key('testing', substring(name,1,1))">
The problem is with the combination of the two highlighted areas, both of which will iterate for each resource in the input document.

Q: Do you really have all the resources in one document as impled by the coding of the XSLT?

Tom Morrison
 
Not literally...this is where it's a bit confusing to me. The author has a comment in the sitemap - generate an xml list of filenames from given directory using Directory Generator - but there isn't an actual file that's created. So I'm guessing whatever this Directory Generator is is pulling the resource names out of each file, then another .xml or .xsl file is sorting in alphabetical order...at least it doesn't look like the file I sent you is doing the sort.

As far as the highlighted areas go...the template match=//resource was there originally. The <xsl:for-each select='...' is what I added based on the Muench code example you pointed me to. I'm not really sure what the select= is saying, but I modified the Muench code using my node names. If it's iterating for each resource, now that I think about it, should //resource be changed to //name?

I really appreciate all of your help!! It can't be easy dealing with people (i.e. Me) who don't know what they're taking about! :)
 
Well, it actually is reasonably easy because you are quite responsive when asked for information.

Now for a bit of XPath information. The XPath expression '//resource' means 'give me a nodeset of all <resource> elements anywhere in the document'. So, if you are in a template that will be matched by all <resource> elements, and you output something for all <resource> elements inside that template, oops...

The example I showed in the other thread does not have the equivalent to the xsl:template processing instruction that I highlighted above. The xsl:for-each is in the outermost template.

My next post will annotate my XSLT for the benefit of the ages...

Tom Morrison
 
Here is the same XSLT with annotation added.
Code:
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform">[/URL]
<xsl:output method="html" indent="yes"/>

<xsl:template match="/">
[b]<!-- load a variable, $allResources, with all resource nodes from external documents -->[/b]
	<xsl:variable name="allResources">
		<xsl:for-each select="resource-list/name">
			<xsl:copy-of select="document(.)/*"/>
		</xsl:for-each>
	</xsl:variable>
[b]<!-- load a variable, $allSorted, with all resource nodes from external documents
        sorted by <name> and capture for easy reference the initial letter of the name -->[/b]
	<xsl:variable name="allSorted">
		<xsl:for-each select="$allResources/resource">
			<xsl:sort select="name"/>
			<sorted-entry><letter><xsl:value-of select="translate(substring(name,1,1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/></letter><xsl:copy-of select="."/></sorted-entry>
		</xsl:for-each>
	</xsl:variable>
<html>
<body>
[b]<!-- iterate over the sorted resource nodes -->[/b]
<xsl:for-each select="$allSorted/sorted-entry">
<xsl:sort select="letter"/>
<xsl:sort select="resource/name"/>
	<xsl:variable name="lastLetter" select="letter"/>
    [b]<!-- if the index letter is not the same as the previous index letter
         a.  output the link array, and
		 b.  output the anchor <a> tag for the beginning of the new letter 
		 (note: The first letter will not = the null previous letter
		        which takes care of the links before the first letter -->[/b]
	<xsl:if test="not(preceding-sibling::sorted-entry[letter=$lastLetter])">
	    [b]<!-- output the link array  using the same logic presented for the outer loop
		     only this time it is used to produce a single link for each letter actually used -->[/b]
		<xsl:for-each select="$allSorted/sorted-entry">
		<xsl:sort select="letter"/>
			<xsl:variable name="lastLink" select="letter"/>
			<xsl:if test="not(preceding-sibling::sorted-entry[letter=$lastLink])">
			[b]<!-- since we are 'computing' the link href value, we use xsl:attribute -->[/b]
			<a><xsl:attribute name="href">#<xsl:value-of select="letter"/></xsl:attribute>
			   <xsl:value-of select="letter"/>
			</a><xsl:text> </xsl:text>
			</xsl:if>
		</xsl:for-each>
		[b]<!-- produce the anchor tag before the first entry for the new letter -->[/b]
		<a><xsl:attribute name="name"><xsl:value-of select="$lastLetter"/></xsl:attribute></a>
	</xsl:if>
[b]<!-- output a resource -->[/b]
<h2><xsl:value-of select="resource/name"/></h2><br/><h3><xsl:value-of select="resource/description"/></h3>
</xsl:for-each>
[b]<!-- output the final link array, after the last entry -->[/b]
<xsl:for-each select="$allSorted/sorted-entry">
<xsl:sort select="letter"/>
	<xsl:variable name="lastLink" select="letter"/>
	<xsl:if test="not(preceding-sibling::sorted-entry[letter=$lastLink])">
	<a><xsl:attribute name="href">#<xsl:value-of select="letter"/></xsl:attribute><xsl:value-of select="letter"/></a><xsl:text> </xsl:text>
	</xsl:if>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

Tom Morrison
 
Hello! I think I'm ready to tackle this again, but I am still in weekend mode. :)

Ok, so I understand the //resource expression...now can you explain what the <xsl:template match="/"> means and what it's supposed to do?

If I follow, you're saying the code you provided above is more of what I need for my problem, instead of using the Muench example? I'll give it a try! More coffee first...
Thanks!
 
[tt]<xsl:template match="/">[/tt] is used to match the root element of the document. The XPath expression for a document's root element is "/". While I have seen stylesheets that do not use a template to match the document root, it is very typical. It is much like starting your car by using the key -- very typical, but not the only way.

WRT my grouping vs Muench Method, I would say that this solution, because it requires some 'computing' to obtain the grouping element, just seems to be easier than Muench. Maybe after my first cup of coffee I can come up with another automotive analogy...

Tom Morrison
 
I guess b/c there are 5 files that build one page, when I open a file and immediately see the <xsl:template match="/">, I'm confused as to which root element of what document it's matching.

For example...here's what's in the sitemap.xmap:

<map:match pattern="resource/*">
<map:generate src="template/template_3pane.xml"/>
<map:transform src="xsl/merge_to_template.xsl" >
<map:parameter name="mapping_file_name" value="mapping_3pane_resource.xml"/>
</map:transform>
<map:transform src="xsl/process_myincludes.xsl" label="resource3">
<map:parameter name="resourcename" value="{1}"/>
</map:transform>
<map:transform type="cinclude"/>
<map:transform src="xsl/final2.xsl"/>
<map:serialize type="html"/>
</map:match>

In the final2.xsl file, the first match is:

<xsl:template match="//page">
<xsl:apply-templates/>
</xsl:template>

but the page node is located in the merge_to_template.xsl file, with the following:

<xsl:template match="/">
<page>
<pageinfo>
<xsl:attribute name="pagename"><xsl:value-of select="$mappingfile//mapping/@pagename"/></xsl:attribute>
<xsl:attribute name="highlight-navigation"><xsl:value-of select="$mappingfile//mapping/@highlight-navigation"/></xsl:attribute>
</pageinfo>
<xsl:apply-templates/>
</page>
</xsl:template>

Is it just me, or is this style of building a website using XML more difficult for a newbie to learn from?
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top