Search This Blog

Showing posts with label xml. Show all posts
Showing posts with label xml. Show all posts

Wednesday, October 30, 2019

Modifying XML using E4X

OpenAf uses the Java Mozilla Rhino javascript engine and one of the features that, despite being obsolete, has still been kept is E4X.

E4X simply allows for easy interaction and also modification of XML content in Javascript.

Let's start by the XML example used in "Accessing XML using E4X":

<shopcart>
  <!-- Client id -->
  <client type="personal" id="12345">
    <name>Joe Smith</name>
  </client>

  <!-- Store id -->
  <store>
    <id>12345</id>
    <type>diy</type>
  </store>

  <!-- Items on the shopcart -->
  <items>
    <item id="123">
       <qty>1</qty>
       <name>Item 1</name>
    </item>
    <item id="456">
       <qty>10</qty>
       <name>Item 2</name>
    </item>
  </items>
</shopcart>

Then you can load it to a xml javascript variable through various ways:

var xml = io.readFileXML("myxml.xml");
var xml = new XMLList(myXMLString);
var xml = <shopcart><!-- Client id --><client type="personal" id="12345">...

Modifying data using the xml variable in javascript

Any element that you can access directly, you can change it:

> xml.client.name.toString()
"Joe Smith"
> xml.client.name = "Scott Tiger"
"Scott Tiger"
> xml.client.name.toString()
"Scott Tiger"

Changing attributes:

> xml.client.@id.toString()
"12345"
> xml.client.@id = "67890"
"67890"
> xml.client.@id.toString()
"67890"

And any comments are kept:

> xml.toString()
<shopcart>
  <!-- Client id -->
  <client id="12345" type="personal">
    <name>Scott Tiger</name>
  </client>
  <!-- Store id -->
  <store>
    <id>12345</id>
...    

Adding and removing children elements

To add you can simply think like an array:

> xml.items.item[2] = <item id="789"><qty>2</qty><name>Item 3</name></item>
> xml.items.item.length()
3

And to remove a children element you can use delete:

> delete xml.items.item[2]
> xml.items.item.length()
2

There are more methods, listed on the end of this section, to add or remove elements like appendChild(child), prependChild(child), insertChildAfter(), insertChildBefore()_

XML Object methods

Method Description
appendChild(child) Adds the child XML object at the end of the corresponding XML element children.
prependChild(child) Inserts a child XML object prior to the beginning of the corresponding XML element children.
insertChildAfter(childRef, childNew) Inserts childNew after childRef.
insertChildBefore(childRef, childNew) Inserts childNew before childRef.
copy() Returns a deep copy of a XML object starting on the corresponding XML element children (without the parent).

Accessing XML using E4X

OpenAf uses the Java Mozilla Rhino javascript engine and one of the features that, despite being obsolete, has still been kept is E4X.

E4X simply allows for easy interaction of XML content in Javascript.

Let's start by the following XML example:

<shopcart>
  <!-- Client id -->
  <client type="personal" id="12345">
    <name>Joe Smith</name>
  </client>

  <!-- Store id -->
  <store>
    <id>12345</id>
    <type>diy</type>
  </store>

  <!-- Items on the shopcart -->
  <items>
    <item id="123">
       <qty>1</qty>
       <name>Item 1</name>
    </item>
    <item id="456">
       <qty>10</qty>
       <name>Item 2</name>
    </item>
  </items>
</shopcart>

Loading XML to OpenAF

You can load this xml from a file:

var xml = io.readFileXML("myxml.xml");

From a string:

var xml = new XMLList(myXMLString);

Or directly:

var xml = <shopcart><!-- Client id --><client type="personal" id="12345">...

All of them will result in a xml javascript variable of type "xml".

Accessing data using the xml variable in javascript

To access any element simply think like you would access it if it was a JSON map:

> xml.client.name.toString()
"Joe Smith"
> xml.store.id.toString()
"12345"

Each element has several methods from which we are using the toString method. There is a list of methods on the end of this section.

For attributes it's the same but add the prefix "@":

> xml.client.@type.toString()
"personal"

For multiple items you can think of it now as an array:

> xml.items.item[0].@id.toString()
123
> xml.items.item[1].qty.toString()
10

But this, is also valid (think of it as the first "store" tag element):

> xml.store[0].id.toString()
"12345"

Accessing all children nodes

> xml.children()[1].name.toString()
"Joe Smith"

Why is the children element on position 1 and not in 0? Because comments are also nodes:

> xml.children()[0].toString()
"<!-- Client id -->"

Using for cycles

Don't forget that you are in javascript, so you can do for cycles:

var totalQty = 0;
for(var i in xml.items.item) {
    totalQty += Number(xml.items.item[i].qty);
}
// 11

You can also search all descendants in a for cycle:

for(var i in xml..name) {
    print(xml..name[i].toString());
}
// Joe Smith
// Item 1
// Item 2

Using namespaces

You can also use namespaces:

var xml = <links><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://www.google.com">Test link</a></links>;

var ns = new Namespace("http://www.w3.org/1999/xlink");
var url = xml..a;
var link = url.@ns::href;
// http://www.google.com

XML Object methods

Method Description
attribute(attributeName) Returns the attribute attributeName from the corresponding XML element node.
attributes() Returns a list of the attributes associated with the corresponding XML element node.
child(propertyName) Returns the children elements named propertyName associated with the corresponding XML element node.
children() Returns all children elements associated with the corresponding XML element node.
descendants(name) Equivalent to "xml..name".
elements(name) Equivalent to children() if no name is provided or equivalent to child if a name is provided.
parent() Returns the parent element associated with the corresponding XML element node.
comments() Returns the children elements of type 'comment' associated with the corresponding XML element node.
text() Returns the text component of the corresponding XML element node.
name() Returns a map with the name and local name associated with the corresponding XML element node.
localName() Returns the local name associated with the corresponding XML element node.
namespace(prefix) Returns the namespace for the provided prefix of the namespace associated with the corresponding XML element node.
namespaceDeclarations() Returns an array of namespaces associated with the corresponding XML element node.
childIndex() Returns the index number on the childrens list of the corresponding XML element node.
length() Returns the size of the children elements of the corresponding XML element node.
nodeKind() Returns if the corresponding XML element node is a element of a comment.
toString() Returns the string of the current XML element node.
toXMLString() Returns the XML encoded string of the current XML element node.

Saturday, September 7, 2019

Quick XML to/from JSON conversion

At a first glance XML and JSON have some similiarities but XML is actually document oriented while JSON is data-oriented. Nevertheless for some cases, specially when processing in Javascript, it's useful to convert XML into JSON and JSON into XML. Let's check some basic examples.

Simple example XML to JSON

Consider the following example:

<orders>
    <orderId type="normal">1234</orderId>
    <items>
        <item>
            <id>1234</id>
            <qty>1</qty>
        </item>
        <item>
            <id>5678</id>
            <qty>2</qty>
        </item>
    <items>
</orders>

To simply convert it to JSON in OpenAF just execute:

> af.fromXML2Obj("<orders>\n\t<orderId type=\"normal\">1234</orderId>\n\t<items>\n\t\t<item>\n\t\t\t<id>1234</id>\n\t\t\t<qty>1</qty>\n\t\t</item>\n\t\t<item>\n\t\t\t<id>5678</id>\n\t\t\t<qty
>2</qty>\n\t\t</item>\n\t</items>\n</orders>")
{
  "orders": {
    "orderId": "1234",
    "items": {
      "item": [
        {
          "id": "1234",
          "qty": "1"
        },
        {
          "id": "5678",
          "qty": "2"
        }
      ]
    }
  }
}

The result is pretty much similar to the original XML with just one small detail: the orderId's type attribute. It's missing from the final JSON object because JSON doesn't have "an array of attributes per key".

But there is a workaround:

> af.fromXML2Obj("<orders>\n\t<orderId type='normal'>1234</orderId>\n\t<items>\n\t\t<item>\n\t\t\t<id>1234</id>\n\t\t\t<qty>1</qty>\n\t\t</item>\n\t\t<item>\n\t\t\t<id>5678</id>\n\t\t\t<qty>2</qty>\n\t\t</item>\n\t</items>\n</orders>", ["orderId"])
{
  "orders": {
    "orderId": {
      "_type": "normal",
      "_": "1234"
    },
    "items": {
      "item": [
        {
          "id": "1234",
          "qty": "1"
        },
        {
          "id": "5678",
          "qty": "2"
        }
      ]
    }
  }
}

The last optional argument of af.fromXML2Obj is actually an array of keys meaning that if the XML tag name is found on the XML OpenAF will make it a map with keys prefixed with "_".

In this case, "_" is the XML tag associated value (e.g. 1234) and "_type" is the tag attribute type with its corresponding value (e.g. "normal").

Simple example JSON to XML

The reverse of the previous example is also possible:

> var obj = af.fromXML2Obj("<orders>\n\t<orderId type='normal'>1234</orderId>\n\t<items>\n\t\t<item>\n\t\t\t<id>1234</id>\n\t\t\t<qty>1</qty>\n\t\t</item>\n\t\t<item>\n\t\t\t<id>5678</id>\n\t\t\t<qty>2</qty>\n\t\t</item>\n\t</items>\n</orders>");
> af.fromObj2XML(obj);
<orders><orderId>1234</orderId><items><item><id>1234</id><qty>1</qty></item><item><id>5678</id><qty>2</qty></item></items></orders>

Note: Keep in mind that af.fromXML2Obj and af.fromObj2XML are just "simplifiers" to handle XML/JSON conversion. For full support of XML you should use OpenAF's XML plugin.

RSS example

One pratical use for these functions is the hability to easily convert RSS feeds into JSON and JSON into a RSS feed:

> mapArray(af.fromXML2Obj($rest().get("http://feeds.reuters.com/reuters/technologyNews")).rss.channel.item, ["title", "pubDate"])
[
  {
    "title": "Apple says Uighurs targeted in iPhone attack but disputes Google findings",
    "pubDate": "Fri, 06 Sep 2019 20:14:45 -0400"
  },
  {
    "title": "U.S. states launch antitrust probes of tech companies, focus on Facebook, Google",
    "pubDate": "Fri, 06 Sep 2019 18:05:56 -0400"
  },
  {
    "title": "Alphabet says received civil investigative demand from U.S. DoJ",
    "pubDate": "Fri, 06 Sep 2019 17:28:58 -0400"
  },
  {
[...]

Using arrays with parallel

OpenAF is a mix of Javascript and Java, but "pure" javascript isn't "thread-safe" in the Java world. Nevertheless be...