Rinky
Rinky

Reputation: 31

XML to JSON with namespaces

I am trying to convert XML with XML namespace to JSON data using XSLT as shown below:

Basically if the XML element has a node of the form element xmnls= "urn:ietf:params:xml:ns:yang:ietf-modulename" . The JSON translation should be "ietf-modulename:element" as in example below

XML:

<notification
    xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
    <event-time>2013-12-21T00:01:00Z</event-time>
    <event>
    <event-class>fault</event-class>
    <reporting-entity>
    <card>Ethernet0</card>
    </reporting-entity>
    <severity>major</severity>
    </event>
 </notification>

JSON Conversion:

{
"ietf-restconf:notification": {
"event-time": "2013-12-21T00:01:00Z",
"event": {
"event-class": "fault",
"reporting-entity": { "card": "Ethernet0" },
"severity": "major"
}

Using the below xslt :

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>

<xsl:template match="/">{
  <xsl:apply-templates select="*"/>}
  </xsl:template>

  <!-- Object or Element Property-->
  <xsl:template match="*">
  "<xsl:value-of select="name()"/>" : <xsl:call-template name="Properties"/>
                                       </xsl:template>

                                       <!-- Array Element -->
                                       <xsl:template match="*" mode="ArrayElement">
                                       <xsl:call-template name="Properties"/>
                                       </xsl:template>

                                       <!-- Object Properties -->
                                       <xsl:template name="Properties">
                                       <xsl:variable name="childName" select="name(*[1])"/>
                                       <xsl:choose>
                                       <xsl:when test="not(*|@*)">"<xsl:value-of select="."/>"</xsl:when>
                                       <xsl:when test="count(*[name()=$childName]) > 1">{ "<xsl:value-of select="$childName"/>" :[<xsl:apply-templates select="*" mode="ArrayElement"/>] }</xsl:when>
                                       <xsl:otherwise>{
                                         <xsl:apply-templates select="@*"/>
                                           <xsl:apply-templates select="*"/>
                                       }</xsl:otherwise>
</xsl:choose>
<xsl:if test="following-sibling::*">,</xsl:if>
</xsl:template>

<!-- Attribute Property -->
<xsl:template match="@*">"<xsl:value-of select="name()"/>" : "<xsl:value-of select="."/>",
  </xsl:template>
    </xsl:stylesheet>

But the namespaces are removed in this . How can I edit the XSL file to convert to "ietf-restconf:notification" in JSON?

Another example :

<interfaces
xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>0/4</name>
<type
xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">ianaift:ethernetCsmacd
</type>
<enabled>false</enabled>
<link-up-down-trap-enable>enabled</link-up-down-trap-enable>
<ipv4
xmlns="urn:ietf:params:xml:ns:yang:ietf-ip">
<enabled>false</enabled>
</ipv4>
<ipv6
xmlns="urn:ietf:params:xml:ns:yang:ietf-ip">
<enabled>false</enabled>
<mtu>1500</mtu>
<forwarding>false</forwarding>
<dup-addr-detect-transmits>1</dup-addr-detect-transmits>
<autoconf>
<create-global-addresses>false</create-global-addresses>
</autoconf>
</ipv6>
</interface>
</interfaces> 

should map to

    {"ietf-interfaces:interfaces": {
  "interface": {
    "name": "0/4",
    "iana-if-type:type": "ianaift:ethernetCsmacd
",
    "enabled": "false",
    "link-up-down-trap-enable": "enabled",
    "ietf-ip:ipv4": {
      "enabled": "false"
    },
    "ietf-ip:ipv6": {
      "enabled": "false",
      "mtu": "1500",
      "forwarding": "false",
      "dup-addr-detect-transmits": "1",
      "autoconf": {
        "create-global-addresses": "false"
      }
    }
  }
}}

Upvotes: 1

Views: 10586

Answers (2)

Daniel Haley
Daniel Haley

Reputation: 52858

The difficult piece of your requirements is the prefix.

When you say (slightly improved for readability):

Basically if the XML element has a node of the form element xmnls="urn:ietf:params:xml:ns:yang:ietf-modulename", the JSON translation should be "ietf-modulename:element" as in example below

it seems like you're talking about elements that have the xmlns="" namespace declaration. This isn't an attribute that the XSLT processor sees in the XML. It's the namespace that is bound to the element (and it's descendants unless they have a different namespace specified).

So instead of a notification element, it's really a

{urn:ietf:params:xml:ns:yang:ietf-restconf}notification

element.

In your example, all of the descendant elements are in the same namespace too.

event-time as an example:

{urn:ietf:params:xml:ns:yang:ietf-restconf}event-time

Here's a good namespace reference for better explanation: http://www.jclark.com/xml/xmlns.htm

I think the only thing you can do is check the namespace uri on the root element and then check to see if the default namespace has changed.

Looking at your examples, it seems like you're wanting part of the uri after the last :. You can get this by using a recursive template call, but it would be much easier in XSLT 2.0.

Here's an example. I'm indenting for readability, but it's not necessary. Just remove the template named indent and the indent variables and their references. (This would also be much easier in XSLT 2.0.)

It's not comprehensive, but works with your example and should give you a starting point.

Example can also be seen here: http://xsltransform.net/6r5Gh3i (NEEDS TO BE UPDATED WHEN SERVICE IS AVAILABLE)

XML Input

<interfaces
    xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
    <interface attr="x">
        <name>0/4</name>
        <type
            xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">ianaift:ethernetCsmacd
        </type>
        <enabled>false</enabled>
        <link-up-down-trap-enable>enabled</link-up-down-trap-enable>
        <ipv4
            xmlns="urn:ietf:params:xml:ns:yang:ietf-ip">
            <enabled>false</enabled>
        </ipv4>
        <ipv6
            xmlns="urn:ietf:params:xml:ns:yang:ietf-ip">
            <enabled>false</enabled>
            <mtu>1500</mtu>
            <forwarding>false</forwarding>
            <dup-addr-detect-transmits>1</dup-addr-detect-transmits>
            <autoconf>
                <create-global-addresses>false</create-global-addresses>
            </autoconf>
        </ipv6>
    </interface>
</interfaces>

XSLT 1.0

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="/*" priority="1">
    <xsl:variable name="prefix">
      <xsl:call-template name="getPrefix">
        <xsl:with-param name="uri" select="namespace-uri()"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:value-of select="concat('{&quot;',$prefix,local-name(),'&quot;')"/>
    <xsl:text>: {&#xA;</xsl:text>
    <xsl:apply-templates/>
    <xsl:text>}}</xsl:text>
  </xsl:template>

  <xsl:template match="*[not(*)]">
    <xsl:variable name="indent">
      <xsl:call-template name="indent">
        <xsl:with-param name="level" select="count(ancestor::*)"/>
      </xsl:call-template>      
    </xsl:variable>
    <xsl:variable name="prefix">
      <xsl:if test="namespace-uri()!=namespace-uri(..)">
        <xsl:call-template name="getPrefix">
          <xsl:with-param name="uri" select="namespace-uri()"/>
        </xsl:call-template>        
      </xsl:if>
    </xsl:variable>
    <xsl:value-of select="concat($indent,'&quot;',$prefix,local-name(),'&quot;: ')"/>
    <xsl:apply-templates/>
    <xsl:if test="following-sibling::*">,</xsl:if>
    <xsl:text>&#xA;</xsl:text>
  </xsl:template>

  <xsl:template match="*[*]">
    <xsl:variable name="indent">
      <xsl:call-template name="indent">
        <xsl:with-param name="level" select="count(ancestor::*)"/>
      </xsl:call-template>      
    </xsl:variable>
    <xsl:variable name="prefix">
      <xsl:if test="namespace-uri()!=namespace-uri(..)">
        <xsl:call-template name="getPrefix">
          <xsl:with-param name="uri" select="namespace-uri()"/>
        </xsl:call-template>        
      </xsl:if>
    </xsl:variable>
    <xsl:value-of select="concat($indent,'&quot;',$prefix,local-name(),'&quot;')"/>
    <xsl:text>: {&#xA;</xsl:text>
    <xsl:apply-templates/>
    <xsl:value-of select="concat($indent,'}')"/>
    <xsl:if test="following-sibling::*">,</xsl:if>
    <xsl:text>&#xA;</xsl:text>    
  </xsl:template>

  <xsl:template match="text()">
    <xsl:value-of select="concat('&quot;',normalize-space(),'&quot;')"/>
  </xsl:template>

  <xsl:template name="indent">
    <xsl:param name="level"/>
    <xsl:if test="$level >= 1">
      <xsl:text>  </xsl:text>
    </xsl:if>
    <xsl:if test="$level - 1 >= 1">
      <xsl:call-template name="indent">
        <xsl:with-param name="level" select="$level - 1"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

  <xsl:template name="getPrefix">
    <xsl:param name="uri"/>
    <xsl:if test="namespace-uri()">
      <xsl:choose>
        <xsl:when test="contains($uri,':')">
          <xsl:call-template name="getPrefix">
            <xsl:with-param name="uri" select="substring-after($uri,':')"/>
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$uri"/>
          <xsl:if test="$uri">:</xsl:if>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

Output

{"ietf-interfaces:interfaces": {
  "interface": {
    "name": "0/4",
    "type": "ianaift:ethernetCsmacd",
    "enabled": "false",
    "link-up-down-trap-enable": "enabled",
    "ietf-ip:ipv4": {
      "enabled": "false"
    },
    "ietf-ip:ipv6": {
      "enabled": "false",
      "mtu": "1500",
      "forwarding": "false",
      "dup-addr-detect-transmits": "1",
      "autoconf": {
        "create-global-addresses": "false"
      }
    }
  }
}}

The only difference in this output and the output you requested is with the type element. Michael explains this best in his answer:

Note that in your (new) example, the type element is in the same namespace as its parent node. The namespace declaration xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type" does not affect it. In fact, it does not affect any node in the entire XML.

Upvotes: 1

michael.hor257k
michael.hor257k

Reputation: 116993

Try adding this before you output the element's name:

<xsl:if test="namespace-uri()!=namespace-uri(..)">
    <xsl:value-of select="concat(substring-after(namespace-uri(), 'urn:ietf:params:xml:ns:yang:'), ':')"/>
</xsl:if>

This is based partly on the following statement from the standard you linked to:

A namespace-qualified member name MUST be used for all members of a top-level JSON object, and then also whenever the namespaces of the node and its parent node are different.

and partly on your statement that:

my requirement is everything after "urn:ietf:params:xml:ns:yang:" should be extracted and added as prefix to any element having namespace

Note that in your (new) example, the type element is in the same namespace as its parent node. The namespace declaration xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type" does not affect it. In fact, it does not affect any node in the entire XML.


Unrelated to your question: I would suggest you use xsl:text to provide the JSON markup and indenting. This will make your stylesheet more readable and easier to modify.

Upvotes: 0

Related Questions