1

I'm trying to programatically update an an existing XSD in java that looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns="com/company/common" xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="com/company/common/" elementFormDefault="qualified">
    <xs:include schemaLocation="DerivedAttributes.xsd" />
    <xs:element name="MyXSD" type="MyXSD" />
    <xs:complexType name="Container1">
        <xs:sequence>
            <xs:element name="element1" type="element1" minOccurs="0"
                maxOccurs="unbounded" />
            <xs:element name="element2" type="element2" minOccurs="0"
                maxOccurs="unbounded" />
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Container2">
        <xs:sequence>
            <xs:element name="element3" type="Type1" minOccurs="0" />
            <xs:element name="element4" type="Type2" minOccurs="0" />
        </xs:sequence>
    </xs:complexType>
</xs:schema>

I am able to add a new element to Container 1 very easily with DOM and XPath like this:

Document doc = DocumentBuilderFactory.newInstance()
            .newDocumentBuilder().parse(
                    new InputSource("test.xsd"));

    // use xpath to find node to add to
    XPath xPath = XPathFactory.newInstance().newXPath();
    NodeList nodes = (NodeList) xPath
            .evaluate(
                    "/schema/complexType[@name=\"Container1\"]/sequence",
                    doc.getDocumentElement(), XPathConstants.NODESET);

    // create element to add
    org.w3c.dom.Element newElement = doc.createElement("xs:element");
    newElement.setAttribute("name", "element5");
    newElement.setAttribute("type", "type5");
    newElement.setAttribute("minOccurs", "0");
    newElement.setAttribute("manOccurs", "unbounded");

    nodes.item(0).appendChild(newElement);

    // output
    TransformerFactory.newInstance().newTransformer().transform(
            new DOMSource(doc.getDocumentElement()),
            new StreamResult(System.out));

And I am able to get this result:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns="com/company/common" xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="com/company/common/" elementFormDefault="qualified">
    <xs:include schemaLocation="DerivedAttributes.xsd" />
    <xs:element name="MyXSD" type="MyXSD" />
    <xs:complexType name="Container1">
        <xs:sequence>
            <xs:element name="element1" type="element1" minOccurs="0"
                maxOccurs="unbounded" />
            <xs:element name="element2" type="element2" minOccurs="0"
                maxOccurs="unbounded" />
            <xs:element name="element3" type="element2" minOccurs="0"
                maxOccurs="unbounded" />
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Container2">
        <xs:sequence>
            <xs:element name="element3" type="Type1" minOccurs="0" />
            <xs:element name="element2" type="Type2" minOccurs="0" />
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Container3">
        <xs:sequence>
            <xs:element name="element4" type="Type1" minOccurs="0" />
        </xs:sequence>
    </xs:complexType>
</xs:schema>

So my question is how can I add a new complex type named "Container 3"...with a sequence...that contains "element 5" using the same DOM apprach so it looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns="com/company/common" xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="com/company/common/" elementFormDefault="qualified">
    <xs:include schemaLocation="DerivedAttributes.xsd" />
    <xs:element name="MyXSD" type="MyXSD" />
    <xs:complexType name="Container1">
        <xs:sequence>
            <xs:element name="element1" type="element1" minOccurs="0"
                maxOccurs="unbounded" />
            <xs:element name="element2" type="element2" minOccurs="0"
                maxOccurs="unbounded" />
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Container2">
        <xs:sequence>
            <xs:element name="element3" type="Type1" minOccurs="0" />
            <xs:element name="element4" type="Type2" minOccurs="0" />
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Container3">
        <xs:sequence>
            <xs:element name="element5" type="Type1" minOccurs="0" />
        </xs:sequence>
    </xs:complexType>
</xs:schema>

Right now im using a DOM parser that adds a new complex type...But im not sure how to create a complex type that also has sequence with an element. This is what I have so far...

Document doc = DocumentBuilderFactory.newInstance()
            .newDocumentBuilder().parse(
                    new InputSource("test.xsd"));

    // use xpath to find node to add to
    XPath xPath = XPathFactory.newInstance().newXPath();
    NodeList nodes = (NodeList) xPath.evaluate("/schema", doc
            .getDocumentElement(), XPathConstants.NODESET);

    // create element to add
    org.w3c.dom.Element newElement = doc.createElement("xs:complexType");
    newElement.setAttribute("name", "Container3");

    nodes.item(0).appendChild(newElement);

    // output
    TransformerFactory.newInstance().newTransformer().transform(
            new DOMSource(doc.getDocumentElement()),
            new StreamResult(System.out));

Thanks!

Ian Roberts
  • 120,891
  • 16
  • 170
  • 183
stepheaw
  • 1,683
  • 3
  • 22
  • 35

3 Answers3

4

First of all, a word of warning. You should handle namespaces properly if you want to use XPath - the XPath language is only defined over namespace-well-formed XML, and while some DOM and XPath implementations appear to work on a DOM tree parsed without namespaces, it is not guaranteed, and if you swap in a different parser for any reason things are likely to break.

Given how cumbersome it is to use namespaces in javax.xml.xpath, I would be inclined to swap to a more Java-friendly object model such as dom4j instead.

Though you don't actually need to use an XPath at all in this case, as you're just adding a new child element to the root element of the document:

// this is org.dom4j.Document, not w3c
SAXReader reader = new SAXReader();
Document doc = reader.read(new File("test.xsd"));

doc.getRootElement()
  .addElement("xs:complexType", "http://www.w3.org/2001/XMLSchema")
    .addAttribute("name", "Container4")
    .addElement("xs:sequence", "http://www.w3.org/2001/XMLSchema")
      .addElement("xs:element", "http://www.w3.org/2001/XMLSchema")
        .addAttribute("name", "element5")
        .addAttribute("type", "xs:string")
        .addAttribute("minOccurs", "0");

System.out.println(doc.asXML());

Or in W3C DOM:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// enable namespaces - for some obscure reason this is false by default
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(
                new InputSource("test.xsd"));

// create element to add
org.w3c.dom.Element newComplexType = doc
        .createElementNS("http://www.w3.org/2001/XMLSchema", "xs:complexType");
org.w3c.dom.Element newSequence = doc
        .createElementNS("http://www.w3.org/2001/XMLSchema", "xs:sequence");
org.w3c.dom.Element newElement = doc
        .createElementNS("http://www.w3.org/2001/XMLSchema", "xs:element");
newComplexType.setAttributeNS(null, "name", "Container3");

newElement.setAttributeNS(null, "name", "element5");
newElement.setAttributeNS(null, "type", "type5");
newElement.setAttributeNS(null, "minOccurs", "0");
newElement.setAttributeNS(null, "manOccurs", "unbounded");

doc.getDocumentElement().appendChild(newComplexType)
        .appendChild(newSequence).appendChild(newElement);

// output
TransformerFactory.newInstance().newTransformer().transform(
        new DOMSource(doc),
        new StreamResult(System.out));

Given we're dealing with XML that involves namespaces we must use the NS variants of DOM methods, rather than the ancient non-namespace-aware methods that predate the introduction of the XML namespaces spec.

Ian Roberts
  • 120,891
  • 16
  • 170
  • 183
0

That's really something you should do with XSLT, in my opinion. An XML Schema is an XML document - and since you'd like to transform it to another XML document, XSLT is the perfect tool for that. Using Java and DOM is endlessly complicated (highly subjective).

To execute this stylesheet you need an XSLT processor in Java, but there are many good resources that show how to do that (this seems to be one of them).

XSLT Stylesheet

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xsl:strip-space elements="*"/>

    <xsl:output method="xml" encoding="UTF-8" indent="yes" />

    <xsl:template match="/*">
      <xsl:copy>
        <xsl:apply-templates/>
        <!--Introduce the new element-->
        <xs:complexType name="Container3">
            <xs:sequence>
                <xs:element name="element5" type="Type1" minOccurs="0" />
            </xs:sequence>
        </xs:complexType>
      </xsl:copy>
    </xsl:template>

    <!--Identity template, produces an exact copy of the input-->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

</xsl:transform>

XML Output

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="com/company/common">
   <xs:include schemaLocation="DerivedAttributes.xsd"/>
   <xs:element name="MyXSD" type="MyXSD"/>
   <xs:complexType name="Container1">
      <xs:sequence>
         <xs:element name="element1" type="element1" minOccurs="0" maxOccurs="unbounded"/>
         <xs:element name="element2" type="element2" minOccurs="0" maxOccurs="unbounded"/>
      </xs:sequence>
   </xs:complexType>
   <xs:complexType name="Container2">
      <xs:sequence>
         <xs:element name="element3" type="Type1" minOccurs="0"/>
         <xs:element name="element4" type="Type2" minOccurs="0"/>
      </xs:sequence>
   </xs:complexType>
   <xs:complexType name="Container3">
      <xs:sequence>
         <xs:element name="element5" type="Type1" minOccurs="0"/>
      </xs:sequence>
   </xs:complexType>
</xs:schema>

stepheaw
  • 1,683
  • 3
  • 22
  • 35
Mathias Müller
  • 22,203
  • 13
  • 58
  • 75
  • Thanks for the answer. I do not want to use XSLT becuase this does not solve my problem. I need to programatically add Container 3 and multiple elements but I do not know what the elements are until the application is running. With this approach, I would need to programatilly create this XSLT – stepheaw Feb 19 '15 at 15:50
  • @stepheaw I'm not sure I understand. Do you mean the names of elements you need to add to the Schema are determined at runtime? Then how do you determine them? Where do you get this information from? You can hand _parameters_ to an XSLT stylesheet in case there is something which is not statically known. – Mathias Müller Feb 19 '15 at 15:54
  • Nothing is known. Elements will be added to existing containers, and I did this with: http://stackoverflow.com/questions/28592796/how-to-programmatically-update-and-add-elements-to-an-xsd. It also needs to be able to add a new conatainer though if needed – stepheaw Feb 19 '15 at 15:58
  • @stepheaw I'm still not with you. "Nothing is known"? Please link to questions that immediately precede this one. In my opinion, also something which would have been _much_ easier with XSLT. But anyway, please edit your question and state your requirement **very clearly**, mentioning all the details and all possible situations. – Mathias Müller Feb 19 '15 at 16:00
  • I revised it the best I could. Thanks for your help! – stepheaw Feb 19 '15 at 16:14
  • >With this approach, I would need to programatilly create this XSLT. No, anything that you can do with a Java/DOM program, you can do with XSLT, probably in a fraction of the number of lines of code. – Michael Kay Feb 19 '15 at 21:52
-1

Thanks for your suggestions. Although @Ian Roberts solution may be more elegant with SAX, I was able to do it with DOM like this:

    Document doc = DocumentBuilderFactory.newInstance()
            .newDocumentBuilder().parse(
                    new InputSource("test.xsd"));

    // use xpath to find node to add to
    XPath xPath = XPathFactory.newInstance().newXPath();
    NodeList nodes = (NodeList) xPath.evaluate("/schema", doc
            .getDocumentElement(), XPathConstants.NODESET);

    // create element to add
    org.w3c.dom.Element newComplexType = doc
            .createElement("xs:complexType");
    org.w3c.dom.Element newSequence = doc.createElement("xs:sequence");
    org.w3c.dom.Element newElement = doc.createElement("xs:element");
    newComplexType.setAttribute("name", "Container3");

    newElement.setAttribute("name", "element5");
    newElement.setAttribute("type", "type5");
    newElement.setAttribute("minOccurs", "0");
    newElement.setAttribute("manOccurs", "unbounded");

    nodes.item(0).appendChild(newComplexType).appendChild(newSequence)
            .appendChild(newElement);

    // output
    TransformerFactory.newInstance().newTransformer().transform(
            new DOMSource(doc.getDocumentElement()),
            new StreamResult(System.out));
stepheaw
  • 1,683
  • 3
  • 22
  • 35
  • When you're dealing with namespaced XML you should really use the namespace-aware DOM methods like `createElementNS` instead of the old DOM level 1 `createElement` – Ian Roberts Feb 19 '15 at 18:54
  • I don't know why somebody down voted me. I wanted to do it with DOM and I did, and I posted my solution. Thanks for the help though. – stepheaw Feb 20 '15 at 02:49
  • yes, I did think that was a bit harsh (it wasn't my downvote) – Ian Roberts Feb 20 '15 at 08:42
  • @stepheaw, although i am very late in response to this post, i am trying something very similar and looking at the solution , Doesn't `nodes.item(0).appendChild(newComplexType).appendChild(newSequence) .appendChild(newElement);` add the element outside the tag looking at the Xpath? – anil keshav May 10 '16 at 10:57
  • @anilkeshav Sorry I worked on this so long ago that I have no idea – stepheaw May 10 '16 at 15:04
  • @stepheaw , no worries i was being dumb and had not tried it , i tried it and it work fine, so its all good, it adds it to root element and not outside it – anil keshav May 10 '16 at 15:06