Kinx Library - XML
Tags: ruby, javascript, kinx, xml
Published: 2020-07-19 13:29:16

Hello, everybody!

The script language [Kinx](https://github.com/Kray-G/kinx) is published with the concept of **Looks like JavaScript, Feels like Ruby, Stable like AC/DC(?)**.

This time it's an XML library.

* Reference
    * First motivation ... [The post of introduction](https://dev.to/krayg/kinx-as-a-script-language-4iko)
        * Kinx, I wanted a scripting language with a syntax of C family.
    * Repository ... https://github.com/Kray-G/kinx
        * I am waiting for pull requests.

XML is also often used, so it will be one of what a lot of people want to use easily with a scripting language.

## XML

### Parsing DOM

Build a DOM tree using `Xml.parseFile()` or `Xml.parseString()`. To read the file, do the following.

```javascript
var doc = Xml.parseFile("xmlfile.xml");
```

The following is an example of directly parsing a string.

```xml
var doc = Xml.parseString(%{
<?xml version="1.0" encoding="UTF-8" ?>
<artists>
  <product country="US" id="1">
    <name>Power Snack</name>
    <price>2400</price>
    <img file="powersnack.jpg"/>
  </product>
  <product country="US" id="2">
    <name>Babystar Icecream</name>
    <price>21000</price>
    <img file="babystar.jpg"/>
  </product>
  <product country="DE" id="3">
    <name>Panic! Panic! Panic!</name>
    <price>2400</price>
    <img file="panic.jpg"/>
  </product>
</artists>
});
```

The returned document object has the following methods.

|                   Method                    |                            Content                             |
| ------------------------------------------- | -------------------------------------------------------------- |
| `documentElement()`                         | Gets the root document                                         |
| `createElement(tagname)`                    | Creates an Element node                                        |
| `createTextNode(text)`                      | Creates a Text node                                            |
| `createComment(comment)`                    | Creates a Comment node                                         |
| `createCdataSection(content)`               | Creates a CDATA Section node                                   |
| `createProcessingInstruction(target, data)` | Creates a Processing Instruction node                          |
| `createEntityReference(name)`               | Creates an Entity Reference node                               |
| `createElementNS(nsUri, qname)`             | Creates an Element node by specifying the namespace            |
| `getElementById(id)`                        | Search for a node by specifying id                             |
| `getElementsByTagName(tagName)`             | Returns the node of tagName as an array                        |
| `xpath(expr)`                               | Evaluates the XPATH of expr and returns the result as an array |


### Root node

The root node is obtained using the `documentElement()` method as follows.

```javascript
var root = doc.documentElement();
```

#### XML node

XML nodes, including the root node, have the following properties and methods.

##### Properties

|   Properties   |    Content    |
| -------------- | ------------- |
| `type`         | Node type     |
| `name`         | QName         |
| `tagName`      | Tag name      |
| `localName`    | Local name    |
| `namespaceURI` | Namespace URI |
| `prefix`       | Prefix        |

#### Methods

|                Method                 |                            Content                             |
| ------------------------------------- | -------------------------------------------------------------- |
| `attributes()`                        | Returns an attribute list as an array.                         |
| `setAttribute(qname, value)`          | Set the attribute.                                             |
| `setAttributeNS(nsUri, qname, value)` | Set the attribute by specifying the namespace.                 |
| `removeAttribute(qname)`              | Removes an attribute.                                          |
| `removeAttributeNS(nsUri, localName)` | Removes the attribute by specifying the namespace.             |
| `parentNode()`                        | Returns the parent node.                                       |
| `children()`                          | Returns the child nodes as an array.                           |
| `firstChild()`                        | Returns the first child node.                                  |
| `lastChild()`                         | Returns the last child node.                                   |
| `nextSibling()`                       | Returns the next node.                                         |
| `previousSibling()`                   | Returns the previous node.                                     |
| `appendChild(node)`                   | Adds a node to the child node.                                 |
| `removeChild(node)`                   | Removes a node from child nodes.                               |
| `replaceChild(node1, node2)`          | Replaces the child node.                                       |
| `replaceNode(node)`                   | Replaces one's own node with another node.                     |
| `insertBefore(node)`                  | Add a node as the previous node.                               |
| `insertAfter(node)`                   | Add a node as the next node.                                   |
| `remove()`                            | Removes the node.                                              |
| `textContent()`                       | Gets the text.                                                 |
| `innerText()`                         | Gets the text.                                                 |
| `hasChildren()`                       | Returns 1 if a child node exists.                              |
| `hasAttributes()`                     | Returns 1 if there are attributes.                             |
| `getElementById(id)`                  | Search for a node by specifying id                             |
| `getElementsByTagName(tagName)`       | Returns the node of tagName as an array                        |
| `xpath(expr)`                         | Evaluates the XPATH of expr and returns the result as an array |

### XPath

XPath returns the nodes that match the XPATH expression in the form of a node set (array). The node set also has an `xpath()` method, and you can use it by chaining XPATH to the narrowed down nodes.

Run the following with the sample XML document above.

```javascript
var nodes = doc.xpath("//product")
               .xpath("price")
               .map(&(p) => p.innerText());

nodes.each(&(text) => {
    System.println(text);
});
```

Result.

```
2400
21000
2400
```

By the way, using a block syntax and a numbered parameter supported recently, you can also write the following.

```javascript
var nodes = doc.xpath("//product")
               .xpath("price")
               .map { => _1.innerText() };

nodes.each {
    System.println(_1);
};
```

## Sample source

I introduce the sample source included in the repo. There is `Xml.Writer` which is not explained, but I think it will be helpful as it is an example that can do DOM parsing like this.

```javascript
function displayXml(doc, node, indent) {
    System.print("  " * indent);
    if (node.type == Xml.ELEMENT_NODE) {
        System.print("ELEM %s" % node.name);
    } else if (node.type == Xml.TEXT_NODE) {
        System.print("TEXT %s" % node.value.trim());
    }

    var attr = node.attributes();
    for (var i = 0, len = attr.length(); i < len; ++i) {
        System.print("[%s=%s]" % attr[i].name % attr[i].value);
    }
    System.println("");

    var child = node.firstChild();
    while (child) {
        displayXml(doc, child, indent + 1);
        child = child.nextSibling();
    }
}

var doc = Xml.parseString(%{
<?xml version="1.0" encoding="UTF-8" ?>
<artists>
  <product country="US" id="1">
    <name>Power Snack</name>
    <price>2400</price>
    <img file="powersnack.jpg"/>
  </product>
  <product country="US" id="2">
    <name>Babystar Icecream</name>
    <price>21000</price>
    <img file="babystar.jpg"/>
  </product>
  <product country="DE" id="3">
    <name>Panic! Panic! Panic!</name>
    <price>2400</price>
    <img file="panic.jpg"/>
  </product>
</artists>
});
var root = doc.documentElement();
displayXml(doc, root);

var el = root.getElementById("3");
if (el) {
    el.remove();
}

System.println("");
System.println("getElementByTagName:");
var els = root.getElementsByTagName("img");
if (els.isArray) {
    els.each(&(el) => displayXml(doc, el));
}

System.println("");
System.println("XPath:");
var nodes = doc.xpath("//product").xpath("price");
if (nodes.isArray) {
    nodes.each(&(el) => displayXml(doc, el));
}

var xmlWriter = new Xml.Writer(System);
xmlWriter.write(doc);
xmlWriter.write(root);
```

Execution result.

```
ELEM artists
  TEXT
  ELEM product[country=US][id=1]
    TEXT
    ELEM name
      TEXT Power Snack
    TEXT
    ELEM price
      TEXT 2400
    TEXT
    ELEM img[file=powersnack.jpg]
    TEXT
  TEXT
  ELEM product[country=US][id=2]
    TEXT
    ELEM name
      TEXT Babystar Icecream
    TEXT
    ELEM price
      TEXT 21000
    TEXT
    ELEM img[file=babystar.jpg]
    TEXT
  TEXT
  ELEM product[country=DE][id=3]
    TEXT
    ELEM name
      TEXT Panic! Panic! Panic!
    TEXT
    ELEM price
      TEXT 2400
    TEXT
    ELEM img[file=panic.jpg]
    TEXT
  TEXT

getElementByTagName:
ELEM img[file=powersnack.jpg]
ELEM img[file=babystar.jpg]

XPath:
ELEM price
  TEXT 2400
ELEM price
  TEXT 21000
<artists>
        <product country="US" id="1">
                <name>Power Snack</name>
                <price>2400</price>
                <img file="powersnack.jpg" />
        </product>
        <product country="US" id="2">
                <name>Babystar Icecream</name>
                <price>21000</price>
                <img file="babystar.jpg" />
        </product>
</artists>
<artists>
        <product country="US" id="1">
                <name>Power Snack</name>
                <price>2400</price>
                <img file="powersnack.jpg" />
        </product>
        <product country="US" id="2">
                <name>Babystar Icecream</name>
                <price>21000</price>
                <img file="babystar.jpg" />
        </product>
</artists>
```

## Conclusion

It would be nice to be able to use XPath.

And if you combine XML and Zip, you can actually read and write Xlsx files (Excel files). The Xlsx file is standardized under the name Office Open XML (with various problems), and it is created by the XML file and a Zip file, so you can read it.

However, in fact, supporting all of Office Open XML requires a lot of codes, so what I can do right away will be just a simple reading and writing. If I have time, I will challenge it.

See you next time.