1

I have a java REST API and we recently changed domain. The api is versioned although up to now this has involved adding removing elements across the versions.

I would like to change the namespaces if someone goes back to previous versions but I am struggling. I have realised now, after some hacking about, that it is probably because I am changing the namespace of the xml that is actually being referenced. I was thinking of it as a text document but I guess the tool is not ?

So looking at this xml with the n#namespace url veg.com ->

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ns2:apple xmlns:ns2="http://veg.com/app/api/apple" xmlns:ns1="http://veg.com/app/api" xmlns:ns3="http://veg.com/app/api/apple/red" 
    xmlns:ns4="http://veg.com/app/banana" xmlns:ns5="http://veg.com/app/api/pear" xmlns:ns6="http://veg.com/app/api/orange" 
    ns1:created="2016-05-23T16:47:55+01:00" ns1:href="http://falseserver:8080/app/api/apple/1" ns1:id="1">


    <ns2:name>granny smith</ns2:title>
    <ns2:flavour>sweet</ns2:status>
    <ns2:origin>southwest region</ns2:grantCategory>

    ...
</ns2:apple>

I would like to change the namespaces to fruit.com. This is a very hacky unit test which shows the broad approach that I have been trying...

 @Test
 public void testNamespaceChange() throws Exception {

        Document appleDoc = load("apple.xml");

        XPath xpath = XPathFactory.newInstance().newXPath();
        org.w3c.dom.Node node = (org.w3c.dom.Node) xpath.evaluate("//*[local-name()='apple']", appleDoc , XPathConstants.NODE);

        NamedNodeMap nodeMap = node.getAttributes();

        for (int i = 0; i < nodeMap.getLength(); i++) {
            if (nodeMap.item(i).getNodeName().startsWith("xmlns:ns")) {
                nodeMap.item(i).setTextContent( nodeMap.item(i).getNodeValue().replace( "veg.com", "fruit.com"));
            }
        }

        //Check values have been set 
        for (int i = 0; i < nodeMap.getLength(); i++) {
            System.out.println(nodeMap.item(i).getNodeName());
            System.out.println(nodeMap.item(i).getNodeValue());
            System.out.println("----------------");
        }

        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        Transformer transformer = TransformerFactory.newInstance().newTransformer();
        transformer.transform(new DOMSource(node), result);
        System.out.println("XML IN String format is: \n" +
      writer.toString());
}

So the result of this is that the loop of nodeMap items shows the updates taking hold

i.e. all updated along these lines

xmlns:ns1
http://fruit.com/app/api
-------------------------------------------
xmlns:ns2
http://fruit.com/app/api/apple
-------------------------------------------
xmlns:ns3
http://fruit.com/app/api/apple/red
-------------------------------------------
...

but when I print out the transfomed document I get what I see in the api response...

 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <ns2:apple xmlns:ns2="http://veg.com/app/api/apple" xmlns:ns1="http://veg.com/app/api" xmlns:ns3="http://fruit.com/app/api/apple/red" 
        xmlns:ns4="http://fruit.com/app/banana" xmlns:ns5="http://fruit.com/app/api/pear" xmlns:ns6="http://fruit.com/app/api/orange" 
        ns1:created="2016-05-23T16:47:55+01:00" ns1:href="http://falseserver:8080/app/api/apple/1" ns1:id="1">

The sibling (and further down the hierarchy) namespaces have been changed but ns1 and ns2 have remained unchanged.

Can anyone tell me why and whether there is a simple way for me to update them ? I guess the next step for me might be to stream the xml doc into a string, update them as text and then reload it as an xml document but I'm hoping I'm being defeatist and there is a more elegant solution ?

3
  • What exactly do you mean by "change the namespace"? Do you want to transform XML document in "old" namespace into XML document in the "new" namespace? Commented Apr 17, 2018 at 16:11
  • Really all I want to do is update the namespace attributes in the main node. So I want to update the contents of the xmlns:ns1, xmlns:ns2, xmlns:ns3 etc attributes (change the url to our new domain). It's taken me quite a long time to realise it is some code which is blocking this change (org.w3c.dom or javax.xml.transform) ? Commented Apr 17, 2018 at 16:27
  • The problem is, you're approaching this from syntactical point of view. While this might work in same cases, namespaces are, logically seen, not just some xmlns:someprefix attributes spread around. I think the easiest correct way to solve this would be an XSLT transformation which remaps certain namespaces. Commented Apr 17, 2018 at 17:58

1 Answer 1

2

I would solve it with an XSLT like this:

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

  <xsl:template match="*[namespace-uri()='http://veg.com/app/api/apple']" priority="1">
    <xsl:element name="local-name()" namespace="http://fruit.com/app/api/apple">
      <xsl:apply-templates select="@*|node()"/>
    </xsl:element>
  </xsl:template>

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

This stylesheet combines the identity transform with a template which changes namespace of elements in http://veg.com/app/api/apple to http://fruit.com/app/api/apple.

I think it is much simpler that Java code that you have. You'd be also more flexible, should you find out you have more differences between version of you XML apart just namespaces.

Please consider this to be a rough sketch. I wrote a book on XSLT some 15 years ago, but did not use XSLT for more than 6 or 7 years.

Sign up to request clarification or add additional context in comments.

2 Comments

Okay thanks I think you are right. I'm struggling with the syntax a bit but I'll spend a day trying to learn xslt and post another question if necessary when I have made a respectable attempt.
I got my problem solved with this. The only change I had to make was that element name had to be surrounded with curly braces to support computing it. Example: <xsl:element name="{local-name()}" namespace="http://fruit.com/app/api/apple">

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.