• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 273
  • Last Modified:

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

0
numberkruncher
Asked:
numberkruncher
  • 6
  • 5
1 Solution
 
Geert BormansCommented:
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 BormansCommented:
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
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
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 BormansCommented:
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 BormansCommented:
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 BormansCommented:
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
 
numberkruncherAuthor Commented:
Fantastic work Gertone! :)
0
 
numberkruncherAuthor Commented:
Thanks for all of your help!
0
 
Geert BormansCommented:
welcome
0

Featured Post

[Webinar] Database Backup and Recovery

Does your company store data on premises, off site, in the cloud, or a combination of these? If you answered “yes”, you need a data backup recovery plan that fits each and every platform. Watch now as as Percona teaches us how to build agile data backup recovery plan.

  • 6
  • 5
Tackle projects and never again get stuck behind a technical roadblock.
Join Now