How to insert schemalocation in a xml document via DOM

kasten picture kasten · Feb 2, 2011 · Viewed 14.2k times · Source

i create a xml document with JAXP and search a way to insert the schemalocation. At the moment my application produces:

<?xml version="1.0" encoding="UTF-8"?>
<root>
...
</root>

But i need:

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="namespaceURL" 
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="namespaceURL pathToMySchema.xsd">
...
</root>

My code:

StreamResult result = new StreamResult(writer);
Document doc = getDocument();

Transformer trans = transfac.newTransformer();
trans.setOutputProperty(OutputKeys.INDENT, "yes");
trans.setOutputProperty(OutputKeys.METHOD, "xml");
trans.setOutputProperty(OutputKeys.VERSION, "1.0");
trans.setOutputProperty(OutputKeys.ENCODING, "UTF-8");

DOMSource source = new DOMSource(depl.getAsElement(doc));
trans.transform(source, result);

Thanks for your time,
Kasten

Answer

jasso picture jasso · Feb 10, 2011

In XML data model namespace nodes are not actually read from parent element but each element has its own namespace nodes. Therefore simply adding a new default namespace to root element doesn't work but results in a document like this

<root xmlns="namespaceURL">
    <child xmlns=""/>
    ...
</root>

Notice the appearing of empty default namespace xmlns="" on the child element(s). What actually needs to be done is to modify the namespace of every node or to create a new document with the desired default namespace and copy the contents, element and attribute names etc. of the old document to the new one. These can be done by recursively going through the original document. With Java DOM implementation this can be laborious, I've heard. One short cut might be to read the document with a namespace-unaware DOM and then add as attribute the new default namespace. Other solution is to change the namespace with an XSLT transformation, which seems quite suitable in this case, since you actually are already generating the output via XSLT transformation.

Use this XSLT stylesheet to add a new default namespace and the schema location to the root element. This stylesheet preserves old namespaces but adds all elements to new default namespace if they previously were in no-namespace.

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

    <!-- Template to add a default namespace to a document -->
    <!-- Elements without a namespace are "moved" to default namespace -->
    <!-- Elements with a namespace are copied as such -->

  <!-- string for default namespace uri and schema location -->
  <xsl:variable name="ns" select="'namespaceURL'"/>
  <xsl:variable name="schemaLoc" select="'namespaceURL pathToMySchema.xsd'"/>

    <!-- template for root element -->
    <!-- adds default namespace and schema location -->
  <xsl:template match="/*" priority="1">
    <xsl:element name="{local-name()}" namespace="{$ns}">
      <xsl:attribute name="xsi:schemaLocation"
        namespace="http://www.w3.org/2001/XMLSchema-instance">
        <xsl:value-of select="$schemaLoc"/>
        </xsl:attribute>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:element>
  </xsl:template>

    <!--template for elements without a namespace -->
  <xsl:template match="*[namespace-uri() = '']">
    <xsl:element name="{local-name()}" namespace="{$ns}">
      <xsl:apply-templates select="@* | node()"/>
    </xsl:element>
  </xsl:template>

    <!--template for elements with a namespace -->
  <xsl:template match="*[not(namespace-uri() = '')]">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

    <!--template to copy attributes, text, PIs and comments -->
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Instead of creating the transformer with

Transformer trans = transfac.newTransformer();

(which creates and stylesheet that does an identy transformation), create an XSLT input source and give it as a parameter to newTransformer()

javax.xml.transform.Source xsltSource = new javax.xml.transform.stream.StreamSource(xsltFile);
Transformer trans = transFact.newTransformer(xsltSource);

where xsltFile is a File object pointing to that XSLT file.

Set the output properties as you wish and call transform() as in your sample code. The result should be what you desired, but I have not tested this in Java. The given XSLT file is tested for some trivial cases and there is a sample input and output at the end of this answer.

Some minor notes:

  1. The original document object is not modified in this process. The new default namespace only appears in the output of the transform() method.
  2. The namespace prefix for schema-instance namespace is usually xsi:, not xs: as in your example code (xs: is used in schema definitions (as well as xsd:)) .

Sample input and output for the XSLT stylesheet shown above

Input:

<root>
    <child>text</child>
    <child attribute="attr-value"/>
    <?pi-target pi-content?>
    <nsx:ns-child xmlns:nsx="ns1x">
        <no-ns-child>text</no-ns-child>
        <!-- comment -->
        <nsx:ns-child nsx:ns-attribute="nsx-attr-value">text</nsx:ns-child>
    </nsx:ns-child>
    <defns-child xmlns="default-ns">
        <def-child attr="val">text</def-child>
        <child xmlns=""/>
    </defns-child>
    <child>text</child>
</root>

Output:

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="namespaceURL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="namespaceURL pathToMySchema.xsd">
    <child>text</child>
    <child attribute="attr-value"/>
    <?pi-target pi-content?>
    <nsx:ns-child xmlns:nsx="ns1x">
        <no-ns-child>text</no-ns-child>
        <!-- comment -->
        <nsx:ns-child nsx:ns-attribute="nsx-attr-value">text</nsx:ns-child>
    </nsx:ns-child>
    <defns-child xmlns="default-ns">
        <def-child attr="val">text</def-child>
        <child xmlns="namespaceURL"/>
    </defns-child>
    <child>text</child>
</root>