util.datamapper

Given a declarative description of some data, lets you extract that data from an XML stanza, or turn such data into an XML stanza.

Available starting with 0.12.0.

Introduction

As with all utils, to use util.datamapper you have to import it first, by adding this line near the top of your code:

local datamapper = require "util.datamapper";

Say you have this piece of XML (from XEP-0092):

<query xmlns="jabber:iq:version">
  <name>Prosody</name>
  <version>0.12.4</version>
  <os>Linux</os>
</query>

Just looking at it, it looks like a record, dict, object (loved child has many names) with three fields. We can describe it using a variant of JSON Schema with an extension for XML support from the OpenAPI specification, like this:

local schema = {
    type = "object";
    xml = {
        name = "query";
        namespace = "jabber:iq:version";
    };
    properties = {
        name = { type = "string" },
        version = { type = "string" },
        os = { type = "string" },
    },
};

Now you can extract those fields into a convenient table using the parse function.

data = datamapper.parse(schema, stanza)

The data variable should now hold a table like this:

data = {
    name="Prosody";
    version="0.12.4";
    os="Linux";
}

If you want to turn the data back into XML, you can do that using the unparse function

stanza = datamapper.unparse(schema, data)

Of course, the above could instead have been written like this:

-- parse-equivalent
data = {
    name = stanza:get_child_text("name");
    version = stanza:get_child_text("version");
    os = stanza:get_child_text("os");
}

-- unparse-equivalent
stanza = st.stanza("query", { xmlns = "jabber:iq:version" })
    :text_tag("name", "Prosody")
    :text_tag("version", "0.12.4")
    :text_tag("os", "Linux")
:reset();

For such a simple case as this, you probably should, util.datamapper is a pretty big and complex library. But the more complex XML you have to deal with, the more complicated code it will spare you from writing. Especially when dealing with deeply nested trees of optional elements, where you have to check whether each child element really was included or risk an attempt to index a nil value error.

More (and more complicated) examples can be found for example in the test cases for util.datamapper

Reference

schema

Example

local = {
    type = "object";
    xml = {
        name = "query";
        namespace = "jabber:iq:version";
    };
    properties = {
        name = "string";
        version = "string";
        os = "string";
    };
};

This describes an XML stanza (from XEP-0092) looking like this:

<query xmlns="jabber:iq:version">
  <name>Prosody</name>
  <version>0.12.4</version>
  <os>Linux</os>
</query>

parse

data = datamapper.parse(schema, stanza)
-- This might give you a table like:
data = {
    name="Prosody";
    version="0.12.4";
    os="Linux";
}

unparse

stanza = datamapper.unparse(schema, data)
-- would give you back the XML snippet from earlier

Recognised JSON Schema subset

$ref
A Schema with a JSON Pointer in a $ref field is replaced by the pointed-to Schema.
type
Only as a single type name string, arrays are not understood.
enum
Only supported in combination with tag names as value.
const
Treated as an enum field with a single value.
items
Supported.
properties
Supported.

XML schema extension

In addition to the xml fields described in OpenAPI, the following experimental extension fields are supported:

text
Boolean. Signifies that this is the text content of the current element, for use in combination with attributes.
x_name_is_value
Boolean. Enables behavior to treat the element name as the value. Meant for use with empty tags (<example/>) where the tag name itself is part of an enumeration, or is to be treated as a truthy value if it is present.
x_single_attribute
String. Name of attribute to use as the value for simplifying treatment of XML elements that only carry a single value in an attribute.