AXON as fat-free XML

  |   Source

In this post, we will consider one advantage of AXON (see also early posts). It allows to resolve problems that arise when someone will try to translate XML to JSON. The root of this problem is in incompatibility of data models of XML and JSON. XML represents attributed trees with tagged nodes, but JSON represents compositions of arrays and associative arrays. This makes translation of XML difficult. You have to translate XML to fatty JSON (with conventions) in order to save initial structure of XML or have to reorganize initial structure in order to produce more optimal JSON. In the last case, inverse transformation is impossible without of losses. AXON exactly allows to represent XML document without losses.

The well knowing disadvantage of XML is it's verbosity. JSON usually is considered as fat-free alternative to XML. Let's consider example:

<person>
  <name>John Smith</name>
  <age>25</age>
  <address type="home">
     <street>21 2nd Street</street>
     <city>New York</city>
     <state>NY</state>
  </address>
  <address type="current">
     <street>1410 NE Campus Parkway</street>
     <city>Seattle</city>
     <state>WA</state>
  </address>
  <phone type="home">212-555-1234</phone>
  <phone type="fax">646-555-4567</phone>
</person>

Here is JSON alternative:

{"person": {
  "name": "John Smith",
  "age": 25,
  "address": [
    {"type": "home",
     "street": "21 2nd Street",
     "city": "New York",
     "state": "NY"
    },
    {"type": "current",
     "street": "1410 NE Campus Parkway",
     "city": "Seattle",
     "state": "WA"
    }
  ],
  "phone": [ 
    {"type": "home", "number": "212-555-1234"},
    {"type": "fax", "number": "646-555-4567"}
  ]
}}

AXON allows direct translation of XML that saves its element/attribute structure:

person {
  name {"John Smith"}
  age {25}
  address {
     type: "home"
     street {"21 2nd Street"}
     city {"New York"}
     state {"NY"}
  }
  address { 
     type: "current"
     street {"1410 NE Campus Parkway"}
     city {"Seattle"}
     state {"WA"}
  }
  phone {type:"home" "212-555-1234"}
  phone {type:"fax" "646-555-4567"}
}

AXON representation can be built from XML in 4 steps:

  1. Replace <tag> with tag {
  2. Replace </tag> with }
  3. Replace attr=value with attr: value
  4. Remove character , or replace it with one space character

The result of such transformation is equivalent to the original XML. One can also consider it as fat free form of XML.

The ability of AXON to represent XML and JSON documents without losses helps to solve the problems that arise when someone needs to work with XML and JSON at the same time. In AXON, you have not to reorganize the XML document in order to translate it to AXON format. You can translate to AXON with saving of the document structure.

Let's now illustrate the ability of AXON to represent XML without losses. For this purpose we will use pyaxon package.

from __future__ import unicode_literals, print_function
from axon.api import loads, dumps
from axon.objects import mapping, element, sequence, instance, empty
from axon.objects import Element, Instance, Mapping, Sequence, Empty
from axon.objects import GenericBuilder, register_builder
from axon import dump_as_str, as_unicode, factory, reduce
from xml.etree import ElementTree
import json
from io import StringIO

There are functions for ElementTree.Element and ElementTree.ElementTree types from xml.etree package. These functions will used for dumping objects of that types into AXON text.

@reduce(ElementTree.Element)
def element_reduce(elem):
    children = elem.getchildren()
    children = children[:]
    if elem.text and elem.text.strip():
        children.append(elem.text)
    if elem.attrib:
        if children:
            return element(elem.tag, elem.attrib, children)
        else:
            return mapping(elem.tag, elem.attrib)
    elif children:
            return sequence(elem.tag, children)
    else:
        return empty(elem.tag)
        
@reduce(ElementTree.ElementTree)
def etree_reduce(element):
    return element_reduce(element.getroot())

There is also the class ElementTreeBuilder for construction of ElementTree objects during loading from AXON text.

def update_attribs(d):
    for key, val in d.items():
        d[key] = unicode(val)

class ElementTreeBuilder(GenericBuilder):
    def empty(self, name):
        return ElementTree.Element(name)
    def mapping(self, name, attribs):
        update_attribs(attribs)
        return ElementTree.Element(name, attribs)
    def sequence(self, name, children):
        if type(children[-1]) is unicode:
            text = children.pop(-1)
        else:
            text = None
        e = ElementTree.Element(name)
        if children:
            e.extend(children)
        if text:
            e.text = text
        return e
    def element(self, name, attribs, children):
        if type(children[-1]) is unicode:
            text = children.pop(-1)
        else:
            text = None
        update_attribs(attribs)
        e = ElementTree.Element(name, attribs)
        if children:
            e.extend(children)
        if text:
            e.text = text
        return e  

Let's register ElementTree builder with name etree. This is new value for mode parameter in load/loads functions.

register_builder('etree', ElementTreeBuilder())  

Let's now consider XML text:

xml_text = u"""
<person>
  <name>John Smith</name>
  <age>25</age>
  <address type="home">
     <street>21 2nd Street</street>
     <city>New York</city>
     <state>NY</state>
  </address>
  <address type="current">
     <street>1410 NE Campus Parkway</street>
     <city>Seattle</city>
     <state>WA</state>
  </address>
  <phone type="home">212-555-1234</phone>
  <phone type="fax">646-555-4567</phone>
</person>
"""

Let's parse it into ElementTree that represents XML document.

tree = ElementTree.parse(StringIO(xml_text))
ElementTree.dump(tree)
<person>
  <name>John Smith</name>
  <age>25</age>
  <address type="home">
     <street>21 2nd Street</street>
     <city>New York</city>
     <state>NY</state>
  </address>
  <address type="current">
     <street>1410 NE Campus Parkway</street>
     <city>Seattle</city>
     <state>WA</state>
  </address>
  <phone type="home">212-555-1234</phone>
  <phone type="fax">646-555-4567</phone>
</person>

Here we will dump ElementTree object into AXON text.

axon_text = dumps([tree], pretty=1, braces=1)
print(axon_text)
person {
  name {"John Smith"}
  age {"25"}
  address {
    type: "home"
    street {"21 2nd Street"}
    city {"New York"}
    state {"NY"}}
  address {
    type: "current"
    street {"1410 NE Campus Parkway"}
    city {"Seattle"}
    state {"WA"}}
  phone {
    type: "home"
    "212-555-1234"}
  phone {
    type: "fax"
    "646-555-4567"}}

Let's again load ElementTree object from AXON text:

xml_tree = loads(axon_text, mode='etree')[0]

There is compact form of this tree in XML:

ElementTree.dump(xml_tree)
<person><name>John Smith</name><age>25</age><address type="home"><street>21 2nd Street</street><city>New York</city><state>NY</state></address><address type="current"><street>1410 NE Campus Parkway</street><city>Seattle</city><state>WA</state></address><phone type="home">212-555-1234</phone><phone type="fax">646-555-4567</phone></person>

There is compact representation in AXON for comparison:

axon_compact_text = dumps([xml_tree], braces=1)
print(axon_compact_text)
person{name{"John Smith"} age{"25"} address{type:"home" street{"21 2nd Street"} city{"New York"} state{"NY"}} address{type:"current" street{"1410 NE Campus Parkway"} city{"Seattle"} state{"WA"}} phone{type:"home" "212-555-1234"} phone{type:"fax" "646-555-4567"}}

There is JSON representation for comparison too (with loss of initial structure):

json_text = u"""
{"person": {
  "name": "John Smith",
  "age": 25,
  "address": [
    {"type": "home",
     "street": "21 2nd Street",
     "city": "New York",
     "state": "NY"
    },
    {"type": "current",
     "street": "1410 NE Campus Parkway",
     "city": "Seattle",
     "state": "WA"
    }
  ],
  "phone": [ 
    {"type": "home", "_": "212-555-1234"},
    {"type": "fax", "_": "646-555-4567"}
  ]
}}
"""
json.dumps(json.loads(json_text), separators=(',',':'))
Out[11]:
u'{"person":{"phone":[{"type":"home","_":"212-555-1234"},{"type":"fax","_":"646-555-4567"}],"age":25,"name":"John Smith","address":[{"city":"New York","state":"NY","street":"21 2nd Street","type":"home"},{"city":"Seattle","state":"WA","street":"1410 NE Campus Parkway","type":"current"}]}}'
 
Comments powered by Disqus
Share