1
votes

I have a large database XML file with defaultnamespace to be transformed using XSLT. Here is a minimal example.

The file is called server.xml. It contains incorrect data.

<?xml version="1.0" encoding="UTF-8"?>
<bookstore xmlns="http://www.mybook.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <book author="Incorrect_Author">
    <title>Correct_Title</title>  
 </book>
</bookstore>

The author needs to be changed to the correct value by matching the title of the book in another XML file.

This second xml file is called client.xml. It contains the correct author name for the book.

<?xml version="1.0" encoding="UTF-8"?>
<bookstore xmlns="http://www.mybook.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <book author="Correct_Author">
    <title>Correct_Title</title>  
    <additionalstuff/>
 </book>
</bookstore>

However, it also has additional info I don't want. So I want to modify server based on client, matching by book title.

I have written the following XSLT (based on this answer). It works to do the job if I artificially remove the default xmlns in my xml files.

<?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" indent="yes"/>

<xsl:param name="clientXml" select="'client.xml'" />

<xsl:variable name="client" select="document($clientXml)//book" />

<xsl:template match="book/@author">
    <xsl:attribute name="author">
        <xsl:value-of select="$client[child::title=current()/../title]/@author" />
    </xsl:attribute>
</xsl:template>

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

However, I need this to work with the files as they are (containing the default ns). I know this is a current question, so I have tried the following code based on answers I found. It doesn't work. Is it a problem with the XPath prefixing?

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:v="http://www.mybook.com" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="v" >

 <xsl:output method="xml" indent="yes"/>

 <xsl:param name="clientXml" select="'client.xml'" />

 <xsl:variable name="client" select="document($clientXml)//v:book" />

 <xsl:template match="v:book/@author">
    <xsl:attribute name="author">
        <xsl:value-of select="$client[child::title=current()/../title]/@author" />
    </xsl:attribute>
 </xsl:template>

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

3 Answers

1
votes

XPaths, including those used in XSLT, are namespace-aware... and unfortunately do not support the concept of a default namespace, at least in the 1.0 version of the specs. To match a namespaced node, you must use a properly bound prefix in your stylesheet's paths even if you didn't do so in the input document.

1
votes

You're almost there. Prefixing is the correct approach. You just missed prefixing the title element selector:

<xsl:template match="v:book/@author">
    <xsl:attribute name="status">
        <xsl:value-of select="$client[child::v:title=current()/../v:title]/@author" />
    </xsl:attribute>
</xsl:template>

Since the namespace in the source is the default namespace, that namespace applies to all descendant elements, not only bookstore or book. In the stylesheet you assign that namespace to a prefix because XPath requires that any element selectors in a namespace be explicitly prefixed.

1
votes

If namespaces are not so significant for your transformation changing the template:

<xsl:template match="book">

to:

<xsl:template match="*[local-name(.)=‘book']">

Will make the template work irrespective of its namespace.If you need to handle simple transformations using this might help you.