Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to display XSD validated XML using XSLT

I've been fighting with this for some time now, and haven't been able to find a clear answer to this yet.

As I understand correctly I can store data in an XML file, validate it using an XSD and then display the data neatly using an XSLT.

However I've been having issues trying to perform XPath queries to select the data I wish to display in my XSLT. When I use generic selectors like './/' or '*' I get the results I'd expect. However when I try to use more specific selectors like : 'root/responses' or any other variant hereof, I get no results.

The XML file is validated correctly by the XSD, so I guess my data is at least somewhat correct. When I remove the XSD reference in the XML file, effectively removing the data validation, my XPath queries suddenly do work! Is there something I'm missing? I've tried adding namespace references to the XSLT but to no avail.

I've described the XSD, Sample XL and Sample XSLT below. Any help or hints would be appreciated!


The XSD, defining the structure, is as follows. This XSD describes a simple document, which nests three elements, and applies a restraint; the code of the responses'code must be unique.

<?xml version="1.0" encoding="utf-8"?>
    <xs:schema id="uitext"
        targetNamespace="http://foo.bar/responsecode.xsd"
        elementFormDefault="qualified"
        xmlns:responsecodes="http://foo.bar/responsecode.xsd"
        xmlns:xs="http://www.w3.org/2001/XMLSchema">

        <xs:element name="root" type="responsecodes:rootType">
            <xs:key name="responseCode">
                <xs:selector xpath="responsecodes:responses/responsecodes:response">
                    <xs:annotation>
                        <xs:documentation>All defined responsecodes</xs:documentation>
                    </xs:annotation>
                </xs:selector>
                <xs:field xpath="@code">
                    <xs:annotation>
                        <xs:documentation>Unique responsecode</xs:documentation>
                    </xs:annotation>
                </xs:field>
            </xs:key>
        </xs:element>

        <xs:complexType name="rootType">
            <xs:sequence>
                <xs:element name="responses" minOccurs="1" maxOccurs="1" type="responsecodes:responseList">
                    <xs:annotation>
                        <xs:documentation>Defined responsecodes</xs:documentation>
                    </xs:annotation>
                </xs:element>
            </xs:sequence>
        </xs:complexType>

        <xs:complexType name="responseList">
            <xs:sequence>
                <xs:element name="response" minOccurs="0" maxOccurs="unbounded" type="responsecodes:response"/>
            </xs:sequence>
        </xs:complexType>

        <xs:complexType name="response">
            <xs:sequence>
                <xs:element name="description" type="xs:string" minOccurs="0" maxOccurs="1">
                    <xs:annotation>
                        <xs:documentation>
                            Explains the use of the responsecode.
                        </xs:documentation>
                    </xs:annotation>
                </xs:element>
            </xs:sequence>
            <xs:attribute name="code" type="xs:string" use="required">
                <xs:annotation>
                    <xs:documentation>Unique code representing the response provided.</xs:documentation>
                </xs:annotation>
            </xs:attribute>
        </xs:complexType>
    </xs:schema>

An example XML document can be as follows:

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="responsecode.xsl"?>
<root xmlns="http://foo.bar/responsecode.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://foo.bar/responsecode.xsd responsecode.xsd">
    <responses>
        <response code="firstCode">
            <description>Explanation of first code</description>
        </response>
        <response code="secondCode">
            <description>Explanation of second code</description>
        </response>
        <response code="thirdCode">
            <description>Explanation of third code.</description>
        </response>
    </responses>
</root>

The test XSLT document referred to in the XML file is as follows. (This would display the codes as mentioned in a format that would resemble VS2008 enumeration definitions, but that aside)

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <html><body><h2>Responses</h2>

                <xsl:for-each select="root/responses/response">
                    <xsl:choose>
                        <xsl:when test="description != ''">
                            <br/>'''&lt;description&gt;
                            <br/>'''<xsl:value-of select="description" />
                            <br/>'''&lt;/description&gt;
                        </xsl:when>
                    </xsl:choose>
                    <br/>
                    <xsl:value-of select="@code" />

                </xsl:for-each>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>
like image 951
Robert Sirre Avatar asked Nov 18 '09 15:11

Robert Sirre


3 Answers

Simple problem: Your XML elements are in a namespace your XSLT knows nothing about.

<root xmlns="http://foo.bar/responsecode.xsd">
  <responses>
    <!-- ... -->
  </responses>
</root>

puts your <root> and all descendant elements into the "http://foo.bar/responsecode.xsd" namespace.

Change your XSL like this:

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:foo="http://foo.bar/responsecode.xsd"
  exclude-result-prefixes="foo"
>
  <xsl:template match="/">
    <html>
      <body>
        <h2>Responses</h2>
        <xsl:for-each select="foo:root/foo:responses/foo:response">
          <!-- ... -->
        </xsl:for-each>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

Note how the namespace is declared and given a prefix. Later, all nodes in that namespace are referred to using that prefix. exclude-result-prefixes is used to make sure the namespace does not appear in the output unnecessarily.

like image 58
Tomalak Avatar answered Nov 15 '22 23:11

Tomalak


And of course as soon as you post a question, you find an answer yourself!

It turns out there must have been a typo in the namespace reference. After double checking this post:

xslt-transform-xml-with-namespaces

Which basically turns out to be the same question. (I searched before posting....honest!), I tried adding a namespace reference again, and this time it worked flawlessly!

I mapped the namespace to the prefix 'nsm' (NameSpaceMapping) and voilá!

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:nsm="http://foo.bar/responsecode.xsd">
    <xsl:template match="/">
        <html><body><h2>Responses</h2>

                        <xsl:for-each select="nsm:root/nsm:responses/nsm:response">
                                <xsl:choose>
                                        <xsl:when test="nsm:description != ''">
                                                <br/>'''&lt;description&gt;
                                                <br/>'''<xsl:value-of select="nsm:description" />
                                                <br/>'''&lt;/description&gt;
                                        </xsl:when>
                                </xsl:choose>
                                <br/>
                                <xsl:value-of select="@code" />

                        </xsl:for-each>
                </body>
        </html>
    </xsl:template>
</xsl:stylesheet>
like image 7
Robert Sirre Avatar answered Nov 15 '22 21:11

Robert Sirre


It's a namespace problem. You'll have to add a namespace declaration for http://foo.bar/responsecode.xsd, and refer to elements using that namespace. More info can be found here.

So basically you'll need something like this:

<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:test="http://foo.bar/responsecode.xsd">
  <xsl:template match="/">
    <html>
      <body>
        <h2>Responses</h2>

        <xsl:for-each select="test:root/test:responses/test:response">
          <xsl:choose>
            <xsl:when test="test:description != ''">
              <br/>'''&lt;description&gt;
              <br/>'''<xsl:value-of select="test:description" />
              <br/>'''&lt;/description&gt;
            </xsl:when>
          </xsl:choose>
          <br/>
          <xsl:value-of select="@code" />

        </xsl:for-each>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

Note the "xmlns:test" in the xsl:stylesheet's attributes. I gave this a test and it works.

like image 4
Welbog Avatar answered Nov 15 '22 23:11

Welbog