-
Notifications
You must be signed in to change notification settings - Fork 0
Mapping: Lowering Research
TL;DR: Mapping languages:
- Javascript (JSON & string (& XML))
- Simple mapping (JSON & XML, not good enough)
- Handbars (XML)
- VueJS (XML)
- JSX (XML)
- XQuery (JSON & XML & string)
- XSLT (JSON & XML)
- XSPARQL (XML & string)
My favourites:
- Javascript for JSON + JSX for XML
- Xquery JSON + XML
All languages but XSPARQL aren't really RDF compliant (see rdf-compliance). Possible solutions at Possible solutions.
Sample input action: (JSON-LD)
{
"@context": "http://schema.org/",
"object": {
"@type": "Person",
"name": "John Doe",
"address": {
"postalCode": "2111"
},
"birthDate": {
"@type": "xs:dateTime",
"@value": "2020-03-18T08:31:04+0000"
},
"email": ["john.deo@personal.com", "john.deo@work.com"],
"knows": [
{
"@type": "Person",
"name": "Max Mustermann"
},
{
"@type": "Person",
"name": "Jane Doe"
}
],
"condition": true
}
}
Wanted JSON/XML request
{
"id": 1234,
"name": "John Doe",
"p_code": 2111,
"bday": "2020-03-18",
"emails": ["john.deo@personal.com", "john.deo@work.com"],
"rel": [
{
"name": "Max Mustermann"
},
{
"name": "Jane Doe"
}
],
"Person": true, // key=object.@type, value= object.@type == Person
"cond": "yes" // optional, only if object.condition === true
}
<?xml version="1.0" encoding="UTF-8" ?>
<root>
<id>1234</id>
<name>John Doe</name>
<p_code>2111</p_code>
<bday>2020-03-18</bday>
<emails>john.deo@personal.com</emails>
<emails>john.deo@work.com</emails>
<rel>
<name>Max Mustermann</name>
</rel>
<rel>
<name>Jane Doe</name>
</rel>
<Person>true</Person>
<cond>yes</cond>
</root>
The lowering mapping I had implemented
{
"id": 1234,
"name": "$.object.name",
"p_code": "$.object.address.postalCode |> (x => toNumber(x))",
"bday": "$.object.birthDate.@value |> (x => parseDate(x))",
"emails": "$.object.email"
}
missing: rel, Person
<?xml version="1.0" encoding="UTF-8"?>
<root>
<id>1234</id>
<name>$.object.name</name>
<p_code>$.object.address.postalCode</p_code>
<bday>$.object.birthDate.@value |> (x => parseDate(x))</bday>
</root>
missing: rel, emails, Person
+ Easy to use
+ Mapping itself is valid JSON/XML
- Not RDF compliant
- No array/list support
To JSON
({
"id": 1234,
"name": $.object.name,
"p_code": toNumber($.object.address.postalCode),
"bday": parseDate($.object.birthDate['@value']),
"emails": $.object.email.map(e => e),
"rel": $.object.knows.map(p => ({
name: p.name
})),
[$.object['@type']]: $.object['@type'] === 'Person',
...($.object.condition && {cond: 'yes'})
})
To XML
(`<root>
<id>1234</id>
<name>${$.object.name}</name>
<p_code>${toNumber($.object.address.postalCode)}</p_code>
<bday>${parseDate($.object.birthDate['@value'])}</bday>
${$.object.email.map(e =>
`<emails>${e}</emails>`
).join('\n')}
${$.object.knows.map(p => `
<rel>
<name>${p.name}</name>
</rel>`
).join('\n')}
<${$.object['@type']}> ${ $.object['@type'] === 'Person'} </${$.object['@type']}>
${$.object.condition ? '<cond>yes</cond>': ''}
</root>`)
+ Known/Popular
+ NP-Complete / can express anything
+- need to eval with JS Engine
- Not RDF compliant
- Difficulty expressing XML (need strings or function to convert json to xml)
templating language
handlebars is extension to mustache
handlebars main use is to generate html, can use for xml mapping
from http://mustache.github.io & http://handlebarsjs.com/
To XML: (in handlebars)
Handlebars.registerHelper('parseDate', (s) => s.substring(0,10));
Handlebars.registerHelper('get', function (path, opts) {
return get(opts, `data.root.${path}`)
});
Handlebars.registerHelper('eq', function(arg1, arg2) {
return (arg1 == arg2);
});
<root>
<id>1234</id>
<name>{{object.name}}</name>
<p_code>{{object.address.postalCode}}</p_code>
<bday>{{ parseDate (get "object.birthDate.@value")}}</bday>
{{#each object.email}}
<emails>{{this}}</emails>
{{/each}}
{{#each object.knows}}
<rel>
<name>{{this.name}}/name>
</rel>
{{/each}}
<{{get "object.@type"}}> {{ eq (get "object.@type") "Person"}} </{{get "object.@type"}}>
{{#if object.condition}}
<cond>yes</cond>
{{/if}}
</root>
+ simple
+ can use javascript functions
- not RDF compliant
- made for xml (html) output, can technically output JSON
Although intended for something else, vue can generate xml
To XML:
<template>
<root>
<id>1234</id>
<name>{{object.name}}</name>
<p_code>{{object.address.postalCode}}</p_code>
<bday>{{object.birthDate['@value'].substring(0, 10)}}</bday>
<emails v-for="email in object.email">
{{ email }}
</emails>
<rel v-for="p in object.knows">
<name>{{ p.name }}</name>
</rel>
<component :is="type">
{{object['@type'] == 'Person'}}
</component>
<cond v-if="object.condition">yes</span>
</root>
</template>
<script>
module.exports = {
data: {JSONLD_Object}
computed: {
type() {
return this.object["@type"];
}
}
}
</script>
+ popular
+ simple
+ JS functions
- Vue is more than just its templates
- not RDF compliant
- only xml output
JSX is Javascript extension for HTML (XML)
<root>
<id>1234</id>
<name>{$.object.name}</name>
<p_code>{toNumber($.object.address.postalCode)}</p_code>
<bday>{parseDate($.object.birthDate['@value'])}</bday>
{$.object.email.map(e =>
<emails>{e}</emails>
)}
{$.object.knows.map(p =>
<rel>
<name>{p.name}</name>
</rel>
)}
<${$.object['@type']}> ${ $.object['@type'] === 'Person'} </${$.object['@type']}>
{$.object.condition && <cond>yes</cond>}
{React.createElement($.object["@type"], null, ($.object["@type"] === "Person").toString())}
</root>
+ popular
+ basically extended Javascript with XML support
- Only xml output
- not RDF compliant
3.1 for JSON support, see http://docs.basex.org/wiki/XQuery_3.1
With Saxon HE: (saxonica.com)
java -cp D:\Libraries\Downloads\SaxonHE10-0J\saxon-he-10.0.jar net.sf.saxon.Query -q:"query.xq"
(with -qs="xquerystring" directly from string)
With fontoXpath: (npm library) See xquery.js
3 options:
- parse json in xquery - input is xquery maps/arrays
- transform json to xml in xquery, use xpath to query
- transform JSON-LD in a previous step to xml (rdf-xml or any xml representation of the json)
1 st option:
To XML:
declare namespace map = "http://www.w3.org/2005/xpath-functions/map";
declare namespace array = "http://www.w3.org/2005/xpath-functions/array";
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare option output:method "adaptive";
declare function local:parseDate($p as xs:string?)
as xs:string?
{
substring($p, 1, 10)
};
let $json := parse-json('{"@context":"http://schema.org/","object":{"@type":"Person","name":"John Doe","address":{"postalCode":"2111"},"birthDate":{"@type":"xs:dateTime","@value":"2020-03-18T08:31:04+0000"},"email":["john.deo@personal.com","john.deo@work.com"],"knows":[{"@type":"Person","name":"Max Mustermann"},{"@type":"Person","name":"Jane Doe"}],"condition":true}}')
let $obj := map:get($json, "object")
return
<root>
<id>1234</id>
<name>{$obj("name")}</name>
<p_code>{$obj("address")("postalCode")}</p_code>
<bday>{local:parseDate($obj("birthDate")("@value"))}</bday>
{array:for-each(
$obj("email"),
function($value) {
<emails>{$value}</emails>
}
)}
{array:for-each(
$obj("knows"),
function($value) {
<rel><name>{$value("name")}</name></rel>
}
)}
{element {$obj("@type")} {$obj("@type") = "Person"}}
{if($obj("condition")) then (
<cond>yes</cond>
) else ()}
</root>
To JSON:
(*same header as for xml*)
let $map :=
map {
"id": 1234,
"name": $obj("name"),
"p_code": number($obj("address")("postalCode")),
"bday": local:parseDate($obj("birthDate")("@value")),
"emails": array:for-each(
$obj("email"),
function($value) {
$value
}
),
"rel": array:for-each(
$obj("knows"),
function($value) {
map {"name": $value("name")}
}
),
$obj("@type"): $obj("@type") = "Person"
}
let $ret := if($obj("condition")) then map:put($map, "cond", "yes") else $map
return $ret
2nd option: Parse json to xml xquery - input now acts as xml: (only show XML->XML, XML->JSON would work similarly)
To JSON: (only works in Saxon, not fontoxpath npm). Result of json-to-xml
is bit weird, kind of annoying to query.
let $xml := json-to-xml('...')
let $obj := $xml/fn:map/fn:map[@key="object"]
return
<root>
<id>1234</id>
<name>{$obj/fn:string[@key="name"]/text()}</name>
<p_code>{$obj/fn:map[@key="address"]/fn:string[@key="postalCode"]/text()}</p_code>
<bday>{local:parseDate($obj/fn:map[@key="birthDate"]/fn:string[@key="@value"]/text())}</bday>
{for $e in $obj/fn:array[@key="email"]/fn:string
return <emails>{$e/text()}</emails>
}
{for $e in $obj/fn:array[@key="knows"]/fn:map
return <rel><name>{$e/fn:string[@key="name"]/text()}</name></rel>
}
{element {$obj/fn:string[@key="@type"]/text()} {$obj/fn:string[@key="@type"]/text() = "Person"}}
{if(xs:boolean($obj/fn:boolean[@key="condition"])) then (
<cond>yes</cond>
) else ()}
</root>
+ transformation language (not primary purpose, but eh..)
+ similar to traditional programming languages (like javascript)
+ single language for XML/JSON/any other string output
+- Need to write functions in xquery syntax (could do javascript functions with fontoxpath)
- JAVA needed for Saxon (might want to try Saxon C implementation)
- Not RDF compliant
- Need to learn language
3.0 again for json support
Using Saxon (Java)
Calling:
java -cp D:\Libraries\Downloads\SaxonHE10-0J\saxon-he-10.0.jar net.sf.saxon.Transform -xsl:"q.xslt" -it:"n" input="<escaped-json-string>"
Escaping json in browser:
JSON.stringify({json}).replace(/"/g, '\\"')
To Json: (with json->xml->json / xquery option 2)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:emp="http://www.semanticalllc.com/ns/employees#"
xmlns:h="http://www.w3.org/1999/xhtml"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:j="http://www.w3.org/2005/xpath-functions"
xmlns:local="http://myfunc.com"
exclude-result-prefixes="xs math xd h emp"
version="3.0"
expand-text="yes"
>
<xsl:param name="input" as="xs:string"/>
<xsl:output method="text" indent="yes" media-type="text/json" omit-xml-declaration="yes"/>
<xsl:variable name="json" select="json-to-xml($input)"/>
<xsl:template name="n">
<xsl:variable name="out">
<xsl:apply-templates select="$json/*"/>
</xsl:variable>
{xml-to-json($out)}
</xsl:template>
<xsl:function name="local:parseDate" as="xs:string">
<xsl:param name="p" as="xs:string"/>
<xsl:sequence select="substring($p, 1, 10)"/>
</xsl:function>
<xsl:template match="j:map" xpath-default-namespace="http://www.w3.org/2005/xpath-functions">
<xsl:variable name="obj" select="map[@key='object']"/>
<j:map>
<j:string key="id">1234</j:string>
<j:string key="name">{$obj/string[@key="name"]}</j:string>
<j:string key="p_code">{$obj/map[@key="address"]/string[@key="postalCode"]/text()}</j:string>
<j:string key="bday">{local:parseDate($obj/map[@key="birthDate"]/string[@key="@value"]/text())}</j:string>
<j:array key="emails">
<xsl:for-each select="$obj/array[@key='email']/*">
<j:string>{text()}</j:string>
</xsl:for-each>
</j:array>
<j:array key="rel">
<xsl:for-each select="$obj/array[@key='knows']/*">
<j:map>
<j:string key="name">{string[@key="name"]}</j:string>
</j:map>
</xsl:for-each>
</j:array>
<j:boolean key="{$obj/string[@key='@type']}">{$obj/string[@key='@type']="Person"}</j:boolean>
<xsl:if test="$obj/boolean[@key='condition']/text() = 'true'">
<j:string key="cond">yes</j:string>
</xsl:if>
</j:map>
</xsl:template>
</xsl:stylesheet>
To XML example very similar, e.g. <name>{$obj/string[@key="name"]}<name>
instead of <j:string key="name">{$obj/string[@key="name"]}</j:string>
.
+ Transformation language
+ mapping document is xml
+ JSON/XML output
- more difficult to learn than xquery (IMO)
- not RDF compliant
from https://github.com/semantalytics/xsparql
doc: https://www.w3.org/Submission/xsparql-language-specification/
calling:
java -jar D:\Libraries\Downloads\xsparql-cli-jar-with-dependencies.jar q.xs
prefix schema: <http://schema.org/>
declare function local:parseDate($p as xs:string?)
as xs:string?
{
substring($p, 1, 10)
};
declare function local:getType($p as xs:string?)
as xs:string?
{
tokenize($p, '/')[4]
};
for $Name $Code $Bday $Type from <data.ttl>
where {
<http://action.com> schema:object $Obj .
$Obj a $Type .
$Obj schema:name $Name .
$Obj schema:address/schema:postalCode $Code .
$Obj schema:birthDate $Bday .
}
return
<root>
<id>{"1234"}</id>
<name>{$Name}</name>
<p_code>{$Code}</p_code>
<bday>{local:parseDate($Bday)}</bday>
{
for $Email from <data.ttl>
where {
$Obj schema:email $Email .
}
return <emails>{$Email}</emails>
}
{
for $Ref from <data.ttl>
where {
$Obj schema:knows/schema:name $Ref .
}
return <rel><name>{$Ref}</name></rel>
}
{element {local:getType($Type)} {local:getType($Type) = "Person"}}
{if($Cond = "true") then (
<cond>yes</cond>
) else ()}
</root>
+ RDF compliant
+ can use XQuery
- slow (example takes 1.85 sec for this small example)
- no JSON output (as far as I have seen)
- need to convert JSON-LD input to ttl/tripples
- not very known language
- own syntax - no syntax highlighting
- Java jar
- oldish project (last real commit 3y ago)
Also took a look at Sparql Templates (STTL) (http://ns.inria.fr/sparql-template/), would be RDF compliant and technically turing complete, but too verbose/complicated.
So far all mappings could only work with the fixed JSON-LD structure. The same mapping with the semantically same data in a different structure would not work.
E.g. the following JSON-LD
{
"birthDate": {
"@value": "2020-03-18T08:31:04+0000"
},
"email": "john.deo@personal.com"
}
is equivalent to
{
"birthDate": "2020-03-18T08:31:04+0000",
"email": ["john.deo@personal.com"]
}
Yet the mapping would need to query the data differently.
1. Use SPARQL to query the data in a first step (something like to SAWSDL https://www.w3.org/TR/sawsdl/#lifting) and query the SPARQL results instead. Downsides: mapping more verbose (now with sparql queries), not sure how well this works with arrays/lists containing nodes, as sparql results are "flat" (e.g csv lines). Using sparql just to get properties seems a bit overkill. Similar to XSparql.
2. Transform RDF (JSON-LD) input to some fixed form. E.g. transform to expanded or flattened document form ("https://json-ld.org/spec/latest/json-ld/#expanded-document-form") (maybe still with context for easier queries) and create mapping for the new json structure. Also maybe replace insert nodes where they are used (e.g replace the "address": {"@id": "_:b1"}
references with the actual addresses) (Replacing doesn't work with circular dependencies though).
Or convert to RDF/XML for easier Xpath/Xquery/XSLT querying. (although RDF XML can also be represented in different ways with the same semantic meaning)
Downside is querying the data will be more verbose/complicated.
with JS Mapping: instead of
{
foo: $.object.name
}
becomes
{
// with a context/namespaces
foo: $.object[0].name[0]['@value']
// without context
foo: $['http://schema.org/object'][0]['http://schema.org/name'][0]['@value']
}
With xquery mapping:
let $obj := $json(1)("http://schema.org/object")(1)
return
map {
"name": $obj("http://schema.org/name")(1)("@value"),
"p_code": $obj("http://schema.org/address")(1)("http://schema.org/postalCode")(1)("@value")
}
3. Query with custom functions. Instead of querying directly the data (with xpath, xsl maps/arrays, Javascript objects) use a custom function to get the wanted rdf properties.
e.g.
function getPath(rdfPropertyPath: string): string | string[]
Would only work with Javascript mappings (and javascript xquery implementation - fontoxpath).
Function could maybe support SPARQL propertypaths (https://www.w3.org/TR/sparql11-query/#propertypaths) or even full SPARQL with https://github.com/linkeddata/rdflib.js .
with JS Mapping:
{
name: getPath('/object/name') // with sparql PP ('schema:object/schema:name') / (':object/:name')
}
with xquery (js-fontoxpath):
map {
"name": local:getPath("/object/name")
}
with handlebars:
<name>{{ getPath "/object/name"}}</name>
Only downside: most probably would not work with xslt.