Solved

XSLT: how to add bookmarks based on h1 nodes in one file and link to them in another

Posted on 2003-12-02
10
376 Views
Last Modified: 2012-08-13
Case xml file (the solution to the described problem should of course work with any other xhtml file):

<body>
  <h1>titel 1</h1>
  <p>some body <b>content</b> here</p>
  <div>
    <h1>2nd title</h1>
    <p>lorem etc.</p>
  <div>
  untagged content
  <div><span><h1>last titel</h1>test test test test</span></div>
</body>

Using two seperate XSLT files I want to generate two seperate HTML files from the above XML.

The first file (index.htm) will contain a list with links to bookmarks in the second file (main.htm).
So the first XSLT needs to generate a list of links en the second one needs to insert bookmarks just before every h1 tag.

The first XSLT file I have already working:

<xsl:template match="//h1">
    <a class="index-header" href="info.asp#bookmark{position()}" target="main">
        <xsl:value-of select="node()"/>       
    </a>
</xsl:template>

But I am not sure how to get the second one, the one that will generate main.htm.
Everything I try will result in a list of the bookmarks after the body but that's of course not what I want, as said I want the bookmarks inserted just before the related h1 tag.

Regards,
Benny

0
Comment
Question by:bvluggen
  • 5
  • 3
  • 2
10 Comments
 
LVL 9

Expert Comment

by:sparkplug
Comment Utility
Hi,

Try the following XSLT which uses the identity template:

<?xml version="1.0" encoding="UTF-8"?>
<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:template match="@*|node()">
            <xsl:copy>
                  <xsl:apply-templates select="@*|node()"/>
            </xsl:copy>
      </xsl:template>
      <xsl:template match="h1">
            <a name="#bookmark{generate-id(.)}"/>
            <xsl:copy>
                  <xsl:apply-templates select="@*|node()"/>
            </xsl:copy>
      </xsl:template>
</xsl:stylesheet>

You can't really use position() in this example as it returns only the position of the current node relative to the parent node. Both title 2 and title 3 will therefore be named bookmark1. The generate-id() function can be used to generate a unique id.


>S'Plug<
0
 

Author Comment

by:bvluggen
Comment Utility
Hi sparkplug,

Thanks for your comment.

If i would use your XSLT how would I need to change the first XSLT so it 'knows' about the generated bookmarks?
If I replace the position function in the first XSLT with the generate-id function won't it generate different id's? How dow I get the same names for either the bookmark or the link in both generated xhtml files?

Benny
0
 
LVL 9

Accepted Solution

by:
sparkplug earned 250 total points
Comment Utility
Good point.

I had thought of that, but for some reason, assumed that if you put the generate-id(.) in the first XSLT as well it would generate the same numbers in both. It doesn't. Note however that the position() function doesn't produce unique bookmarks in either XSLT. The first one generates 1 for each bookmark.

Use the following function instead:

<a name="#bookmark{count(preceding::h1)+1}"/>

This will count the number of preceding h1 tags plus one.

>S'Plug<
0
 

Author Comment

by:bvluggen
Comment Utility
I just tested it and as expected the generated id's will differ in both files.

I was thinking that maybe we could the use the content from the h1 tags and use this to generate a unique compact id?
I think it's save to assume that the content for all h1 tags is unique on a page. What do you think?
I did some testing and it seems to work. The only problem is that the content of some of the h1 tags can be large and contain characters that would break the whole linking idea.

Do you know how to write an XSLT function that generates an id based on the text content of the h1 tag. And for example strips spaces, vowels and charaters not in the alfabet and maybe limits the size to 20 positions?

Benny

0
 

Author Comment

by:bvluggen
Comment Utility
Whoops; missed your comment of 9:21h. I will test it in a minute...
0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 

Author Comment

by:bvluggen
Comment Utility
Hi S'Plug,

Yep, we got a winner; count(preceding::h1)+1 does the trick!

Thanks alot!
Regards Benny
0
 
LVL 11

Expert Comment

by:petiex
Comment Utility
Hold on just a minute there, S'Plug. Good solution, but I must protest your libelous statement against the generate-id() function. ;-)

I'm pretty sure that generate-id() does generate the same id for a given node in a given document even if you are calling it from different templates or different stylesheets. I mean, otherwise, what good would the function be? Using the following xsl to generate the index.htm, you get the same id's (hence matching anchor tags) that were generated by your first xsl for the main.htm file:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
    <html><body>
    <xsl:apply-templates select="//h1"/>
    </body></html>
</xsl:template>

<xsl:template match="h1">
    <a class="index-header" href="main.htm#bookmark{generate-id()}" target="main">
        <xsl:value-of select="."/><br/>      
    </a>
</xsl:template>
</xsl:stylesheet>
0
 
LVL 9

Expert Comment

by:sparkplug
Comment Utility
Not with MSXML 4. On the example XML given, my XSLT transforms to:

<body>
      <a name="#bookmarkh151003368" />
      <h1>titel 1</h1>
      <p>some body <b>content</b> here</p>
      <div>
            <a name="#bookmarkh151004120" />
            <h1>2nd title</h1>
            <p>lorem etc.</p>
      </div>
  untagged content
  <div>
            <span>
                  <a name="#bookmarkh150401224" />
                  <h1>last titel</h1>test test test test</span>
      </div>
</body>

Your XSLT transforms to:

<html><body><a class="index-header" href="main.htm#bookmarkh147482984" target="main">titel 1<br></a><a class="index-header" href="main.htm#bookmarkh147483784" target="main">2nd title<br></a><a class="index-header" href="main.htm#bookmarkh147484472" target="main">last titel<br></a></body></html>

In fact the IDs are different every time either of the transformations are repeated.

Here's an except from the W3C Recomendation http://www.w3.org/TR/xslt

----------------------------------------------------------------
Function: string generate-id(node-set?)

The generate-id function returns a string that uniquely identifies the node in the argument node-set that is first in document order. The unique identifier must consist of ASCII alphanumeric characters and must start with an alphabetic character. Thus, the string is syntactically an XML name. An implementation is free to generate an identifier in any convenient way provided that it always generates the same identifier for the same node and that different identifiers are always generated from different nodes. An implementation is under no obligation to generate the same identifiers each time a document is transformed. There is no guarantee that a generated unique identifier will be distinct from any unique IDs specified in the source document. If the argument node-set is empty, the empty string is returned. If the argument is omitted, it defaults to the context node.
----------------------------------------------------------------

Note the line "An implementation is under no obligation to generate the same identifiers each time a document is transformed. "

QED :^)

>S'Plug<
0
 

Author Comment

by:bvluggen
Comment Utility
Hi Petiex,

First of all thank you for your comment.

Although your comment is directed at S'Plug I felt I had to do one more test using the generate-id func. After all if you are right then I would have been not telling the truth, right. ;-)  ...but again the test showed that the thwo htm files end up with (slightly) differant id's.

For the first 3 links the following id's are generated: kIDALA0W,kIDANA0W, kIDARA0W
For the first 3 anchors following id's are generated: kIDALI0W,kIDANI0W,kIDARI0W

Maybe this is a parser thing? I am using MSXML's 4 XSLT parser. But than again, if you think about it, doesn't it make sense that a function that's supposed to generate a unique id will do so every time it is called? What I was asking for isn't a unique id but a context related static id that only changes when the content changes and that's what S'Plug delivered.


Regards,
Benny
0
 
LVL 11

Expert Comment

by:petiex
Comment Utility
[rant]
Sometimes Microsoft makes me so mad, I could just shake my tiny fists in impotent rage. I'm sure they have lawyers reading through the standards to determine the minimum required functionality.
Lawyer: "Master, look! We're under no obligation!"
Gates: "Excellent. Everything is falling into place. Mwuhaha."
[/rant]

Xalan does generate the same id for every transformation, for what it's worth. Thanks for the heads-up about the evil empire. I'm glad my company is only doing server-side transformations. For now, anyway.
0

Featured Post

Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

Join & Write a Comment

The Confluence of Individual Knowledge and the Collective Intelligence At this writing (summer 2013) the term API (http://dictionary.reference.com/browse/API?s=t) has made its way into the popular lexicon of the English language.  A few years ago, …
I was working on a PowerPoint add-in the other day and a client asked me "can you implement a feature which processes a chart when it's pasted into a slide from another deck?". It got me wondering how to hook into built-in ribbon events in Office.
This video gives you a great overview about bandwidth monitoring with SNMP and WMI with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're looking for how to monitor bandwidth using netflow or packet s…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

15 Experts available now in Live!

Get 1:1 Help Now