Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to insert schemalocation in a xml document via DOM

Tags:

dom

xsd

jaxp

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

like image 570
kasten Avatar asked Feb 02 '11 11:02

kasten


3 Answers

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>
like image 118
jasso Avatar answered Sep 21 '22 14:09

jasso


You can add the namespaces in the root when creating the document.

String NS_URL = "namespaceURL";

doc = builder.newDocument();
Element root = doc.createElementNS(NS_URL, "root");
root.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance", 
    "xs:schemaLocation", NS_URL + " pathToMySchema.xsd");
doc.appendChild(root);

Then for each element added to the doc instead of createElement() use createElementNS()

doc.createElementNS(NS_URL, name);

This results in what you were looking for.

<root 
    xmlns="namespaceURL"
    xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" 
    xs:schemaLocation="namespaceURL pathToMySchema.xsd"
    >
like image 30
teknopaul Avatar answered Sep 22 '22 14:09

teknopaul


Here's how to give a hint to the parser in order to solve your problem: http://bytes.com/topic/java/answers/16892-xerces-how-perfrom-schema-validations-without-using-xsi-schemalocation

It goes like this:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setValidating(true);
dbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
"http://www.w3.org/2001/XMLSchema");
dbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLocation",
"http://www.example.com/Report.xsd");

Here is a validation example with some source code. It might help you. http://www.ibm.com/developerworks/xml/library/x-tipvalschm/

(If all comes to worse, you can always search-and-replace. I know it is not the ideal solution, but the javax.xml.transform.OutputKeys doesn't seem to have a member related to the schemalocation attribute.)

like image 38
Please treat your mods well. Avatar answered Sep 21 '22 14:09

Please treat your mods well.