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.

Geert BormansInformation 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
0
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

0
Geert BormansInformation 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

0
Cloud Class® Course: Microsoft Windows 7 Basic

This introductory course to Windows 7 environment will teach you about working with the Windows operating system. You will learn about basic functions including start menu; the desktop; managing files, folders, and libraries.

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
0
Geert BormansInformation 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

0
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
0
Geert BormansInformation 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

0
Geert BormansInformation 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

0

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! :)
0
numberkruncherAuthor Commented:
Thanks for all of your help!
0
Geert BormansInformation ArchitectCommented:
welcome
0
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.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.