3

I have the following XML. It contains books and references. Some references trace back to Book 1, e.g. Book 4 refers to Book 3 which refers to Book 1.

<?xml version="1.0" encoding="UTF-8"?>
<sandbox>
    <book xml:id="h1">
        <name>Book 1</name>
    </book>
    <book xml:id="h2">
        <name>Book 2</name>
    </book>
    <book xml:id="h3">
        <name>Book 3</name>
        <ref target="#h1"/>
    </book>
    <book xml:id="h4">
        <name>Book 4</name>
        <ref target="#h3"/>
    </book>
</sandbox>

I wrote the following XSLT that enriches the code by tracing back the references to the original source and adding an according textual statement:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="2.0" xmlns:sandbox="sandbox.org"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:function name="sandbox:trace">
        <xsl:param name="target"/>
        <xsl:choose>
            <xsl:when test="document($target)/ref">
                <xsl:value-of select="sandbox:trace(document($target)/ref/@target)"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="document($target)/name"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:function>

    <xsl:template match="ref">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:text>This book refers to </xsl:text>
            <xsl:value-of select="sandbox:trace(@target)"/>
            <xsl:text>!</xsl:text>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

It produces the desired output:

<?xml version="1.0" encoding="UTF-8"?>
<sandbox>
    <book xml:id="h1">
        <name>Book 1</name>
    </book>
    <book xml:id="h2">
        <name>Book 2</name>
    </book>
    <book xml:id="h3">
        <name>Book 3</name>
        <ref target="#h1">This book refers to Book 1!</ref>
    </book>
    <book xml:id="h4">
        <name>Book 4</name>
        <ref target="#h3">This book refers to Book 1!</ref>
    </book>
</sandbox>

My question: Is this a "good" way or are the more appropriate solutions for this task?

friedemann_bach
  • 1,418
  • 14
  • 29

1 Answers1

3

Given references and ids I am usually tempted to use xsl:key and the key function, in your case as the input uses xml:id attributes not even that is necessary as you can simply use the id function then to find referenced elements. So that seems more straightforwards:

<xsl:stylesheet version="2.0" xmlns:sandbox="sandbox.org"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs sandbox">
    <xsl:output indent="yes"/>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:function name="sandbox:check-ref" as="xs:string">
        <xsl:param name="target" as="element(book)"/>
        <xsl:sequence select="if ($target/ref)
                              then sandbox:check-ref($target/ref/id(substring(@target, 2)))
                              else $target/name"/>
    </xsl:function>

    <xsl:template match="ref">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:text>This book refers to </xsl:text>
            <xsl:value-of select="sandbox:check-ref(id(substring(@target, 2)))"/>
            <xsl:text>!</xsl:text>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

I have also added type annotations as implementors of XSLT 2 processors like Michael Kay usually point out that it is a way to improve type safety and performance of code.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110