Transform XML to Referencing using XSL

I have an XML document which I want to transform using XSL so that it conforms (as much as possible) to the Vancouver referencing system.

Somehow I need to sort all references within the references section so that they match the order that they were first cited in the document text. I also need to make the references within the document text use the correct matching numerals within their superscripts.

Does anybody know how I can achieve this using XSLT 1.0?
REQUIRED OUTPUT HTML:
=====================
<p>
   Some stuff
   <sup><a href="#id_r1">[1]</a></sup>
   <sup><a href="#id_r3">[2]</a></sup>
</p>
<p>
   Some stuff
   <sup><a href="#id_r2">[3]</a></sup>
   <sup><a href="#id_r3">[2]</a></sup>
</p>
<h1>References</h1>
<ol>
   <li id="id_r1">Title of the Book</li>
   <li id="id_r3">Title of the Book</li>
   <li id="id_r2">Title of the Book</li>
</ol>
 
 
INPUT XML:
==========
<paragraph>
	<text>Some stuff</text>
	<cite="id_r1" /><cite="id_r3" />
</paragraph>
<paragraph>
	<text>Some stuff</text>
	<cite="id_r2" /><cite="id_r3" />
</paragraph>
<references>
	<book id="id_r1">
		<title>Title of the Book</title>
	</book>
	<book id="id_r2">
		<title>Title of the Book</title>
	</book>
	<book id="id_r3">
		<title>Title of the Book</title>
	</book>
</references>

Open in new window

LVL 13
numberkruncherAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Gertone (Geert Bormans)Information ArchitectCommented:
Hi numbercruncher,
this is not too hard, but can you send us wellformed xML?
<cite="id_r1" /> is not an allowed XML form
I assume you mean something like this
<cite ref="id_r1" />
but I need the exact (wellformed) XML of course
numberkruncherAuthor Commented:
Sorry Gertone, your right, it should be like you suggested. Somehow I managed to loose that when copying and pasting into this question.
<paragraph>
        <text>Some stuff</text>
        <cite ref="id_r1" /><cite ref="id_r3" />
</paragraph>
<paragraph>
        <text>Some stuff</text>
        <cite ref="id_r2" /><cite ref="id_r3" />
</paragraph>
<references>
        <book id="id_r1">
                <title>Title of the Book</title>
        </book>
        <book id="id_r2">
                <title>Title of the Book</title>
        </book>
        <book id="id_r3">
                <title>Title of the Book</title>
        </book>
</references>

Open in new window

Gertone (Geert Bormans)Information ArchitectCommented:
well, this is a bit trickier than I first thought
I picked a random character that you would never use in a reference (I hope) &7890;" , if you do, pick another
I then made a string with a single reference of every id ordered per first appearance (used muenchian grouping for that)
using substring I then get all the ids prior to this one id
using translate I strip out the weird character, the string-length difference is what I need for numbering
the translate() is th ereason why I can't put the weird character in a parameter... hence you have three diffrent refences of this character to change, when you want to change it to something else


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:key name="cite" match="cite" use="@ref"/>
    <xsl:param name="reference-list">
        <xsl:for-each select="//cite[generate-id() = generate-id(key('cite', @ref)[1])]">
            <xsl:value-of select="@ref"/>
            <xsl:value-of select="'&#7890;'"/>
        </xsl:for-each>
    </xsl:param>
    <xsl:template match="paragraph">
    <p>
        <xsl:value-of select="text"/>
        <xsl:apply-templates select="cite"/>
    </p>
</xsl:template>
    <xsl:template match="cite">
        <xsl:variable name="list-before" select="substring-before($reference-list, concat(@ref,'&#7890;'))"></xsl:variable>
        <xsl:variable name="stripped-list-before" select="translate($list-before, '&#7890;', '')"></xsl:variable>
        <xsl:variable name="difference" select="string-length($list-before) - string-length($stripped-list-before) + 1"></xsl:variable>
        <sup>
            <a href="#{@ref}">
                <xsl:text>[</xsl:text>
                <xsl:value-of select="$difference"/>
                <xsl:text>]</xsl:text>
            </a>
        </sup>
    </xsl:template>
    <xsl:template match="references">
        <ol>
            <xsl:apply-templates select="book"/>
        </ol>
    </xsl:template>
    <xsl:template match="book">
        <li id="@id">
            <xsl:value-of select="title"/>
        </li>
    </xsl:template>
</xsl:stylesheet>

Open in new window

Introduction to Web Design

Develop a strong foundation and understanding of web design by learning HTML, CSS, and additional tools to help you develop your own website.

numberkruncherAuthor Commented:
That's almost perfect. The superscripts are perfect; but the references section is not being sorted into the matching order.

In the following input XML I have given each book a unique name, this makes the problem slightly clearer. I have also attached a screenshot of the generated output.
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="ref.xsl"?>
<document>
	<paragraph>
		<text>Some stuff</text>
		<cite ref="id_r1" /><cite ref="id_r3" />
	</paragraph>
	<paragraph>
		<text>Some stuff</text>
		<cite ref="id_r2" /><cite ref="id_r3" />
	</paragraph>
	<references>
		<book id="id_r1">
			<title>Title of the Book A</title>
		</book>
		<book id="id_r2">
			<title>Title of the Book B</title>
		</book>
		<book id="id_r3">
			<title>Title of the Book C</title>
		</book>
	</references>
</document>

Open in new window

output.png
Gertone (Geert Bormans)Information ArchitectCommented:
oh yes, forgot that,
just change the template for references
(add a sort)
    <xsl:template match="references">
        <ol>
            <xsl:apply-templates select="book">
                <xsl:sort select="string-length(substring-before($reference-list, concat(@id,'&#7890;')))" data-type="number" order="ascending"/>
            </xsl:apply-templates>
        </ol>
    </xsl:template>

Open in new window

numberkruncherAuthor Commented:
This seems to be working really well. At the moment, if a reference is not cited, it gets a difference value of zero which means it gets put at the top of the references list. This means that the superscripts cite the wrong references.

I worked out a way of hiding references which have not been cited (shown below). A fixed item is at the start of the references-list that you created. Thus all cited references are have a difference value of 1+, an non-cited references a difference value of 0.

Instead of hiding these references, I would rather sort them to the bottom of the list. But despite my efforts, I have been unable to find a way of doing this. In theory I just want to give it a difference value of something large like 99999 instead of 0.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:key name="cite" match="cite" use="@ref"/>
    <xsl:param name="reference-list">
        <xsl:value-of select="'_nullVoid&#7890;'"/>
        <xsl:for-each select="//cite[generate-id() = generate-id(key('cite', @ref)[1])]">
            <xsl:value-of select="@ref"/>
            <xsl:value-of select="'&#7890;'"/>
        </xsl:for-each>
    </xsl:param>
	<xsl:template match="/">
		<html>
			<head>
				<title>Demo Page</title>
			</head>
			<body>
				<p>
					<xsl:value-of select="$reference-list" />
				</p>
				<xsl:apply-templates />
			</body>
		</html>
	</xsl:template>
    <xsl:template match="paragraph">
	    <p>
	        <xsl:apply-templates/>
	    </p>
	</xsl:template>
	<xsl:template match="text">
		<xsl:value-of select="." />
	</xsl:template>
    <xsl:template match="cite">
        <xsl:variable name="list-before" select="substring-before($reference-list, concat(@ref,'&#7890;'))" />
        <xsl:variable name="stripped-list-before" select="translate($list-before, '&#7890;', '')" />
        <xsl:variable name="difference" select="string-length($list-before) - string-length($stripped-list-before)" />
        <sup>
            <a href="#{@ref}">
                <xsl:text>[</xsl:text>
                <xsl:value-of select="$difference"/>
                <xsl:text>]</xsl:text>
            </a>
        </sup>
    </xsl:template>
    <xsl:template match="references">
        <ol>
            <xsl:apply-templates select="book">
                <xsl:sort select="string-length(substring-before($reference-list, concat(@id,'&#7890;')))" data-type="number" order="ascending"/>
            </xsl:apply-templates>
        </ol>
    </xsl:template>
    <xsl:template match="book">
<!--    	<xsl:if test="string-length(substring-before($reference-list, concat(@id,'&#7890;')))">-->
	        <li id="{@id}">
				<b>
					<xsl:value-of select="string-length(substring-before($reference-list, concat(@id,'&#7890;')))"/>
				</b>
	            <xsl:value-of select="title"/>
	        </li>
<!--        </xsl:if>-->
    </xsl:template>
</xsl:stylesheet>

Open in new window

screenshot2.png
Gertone (Geert Bormans)Information ArchitectCommented:
You can simply check that a book is cited by adding the key in the apply-templates


    <xsl:template match="references">
        <ol>
            <xsl:apply-templates select="book[key('cite', @id)]"><!-- key added in predicate here -->
                <xsl:sort select="string-length(substring-before($reference-list, concat(@id,'&#7890;')))" data-type="number" order="ascending"/>
            </xsl:apply-templates>
        </ol>
    </xsl:template>

Open in new window

Gertone (Geert Bormans)Information ArchitectCommented:
and if you want to list both of them
        <ol>
            <xsl:apply-templates select="book[key('cite', @id)]">
                <xsl:sort select="string-length(substring-before($reference-list, concat(@id,'&#7890;')))" data-type="number" order="ascending"/>
            </xsl:apply-templates>
            <xsl:apply-templates select="book[not(key('cite', @id))]">
                <xsl:sort select="string-length(substring-before($reference-list, concat(@id,'&#7890;')))" data-type="number" order="ascending"/>
            </xsl:apply-templates>
        </ol>

Open in new window

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
numberkruncherAuthor Commented:
Fantastic work Gertone! :)
numberkruncherAuthor Commented:
Thanks for all of your help!
Gertone (Geert Bormans)Information ArchitectCommented:
welcome
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Web Languages and Standards

From novice to tech pro — start learning today.