Merge old Neo4j graph generation POC
This was never completed and will be able to be deleted entirely, but I didn't want to lose this history by having it sit out in a branch. Joe is working on something better.master
commit
18d87a6b00
|
@ -154,6 +154,12 @@ c1map: $(dest_c1map)
|
|||
$(TAME_TS)
|
||||
$(TAME) dot $< $@
|
||||
|
||||
%.neo4j: %.xmlo
|
||||
$(TAME) neo4j $< $@
|
||||
%.neo4je: %.xmle
|
||||
$(TAME) neo4j $< $@
|
||||
|
||||
|
||||
%.svg: %.dote
|
||||
dot -Tsvg "$<" > "$@"
|
||||
%.svg: %.dot
|
||||
|
|
|
@ -0,0 +1,328 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!--
|
||||
Produces CREATE statements to export package dependency graph into Neo4j
|
||||
|
||||
Copyright (C) 2014-2019 Ryan Specialty Group, LLC.
|
||||
|
||||
This file is part of TAME.
|
||||
|
||||
TAME is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
Unlike the dot exporter, this is merciless: it'll export all the
|
||||
information that it can, with some minor cleanup to hide some unnecessary
|
||||
compiler implementation deatils (like classification splitting). You are
|
||||
expected to filter out what you're interested in when querying Neo4j.
|
||||
|
||||
This also produces more realtionships than the dot format, including
|
||||
relationships between packges. So Neo4j can be used as a static analysis
|
||||
tool down to the lowest levels of Tame.
|
||||
-->
|
||||
<stylesheet version="2.0"
|
||||
xmlns="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:lv="http://www.lovullo.com/rater"
|
||||
xmlns:l="http://www.lovullo.com/rater/linker"
|
||||
xmlns:neo="http://www.lovullo.com/calc/dot"
|
||||
xmlns:st="http://www.lovullo.com/liza/proguic/util/struct"
|
||||
xmlns:preproc="http://www.lovullo.com/rater/preproc">
|
||||
|
||||
<include href="util/serialize.xsl" />
|
||||
<include href="include/preproc/path.xsl" />
|
||||
|
||||
<output method="text" />
|
||||
|
||||
|
||||
<!--
|
||||
Newline character
|
||||
-->
|
||||
<variable name="nl" select="' '" />
|
||||
|
||||
<variable name="fqn-prefix" as="xs:string"
|
||||
select="'mega-demo/'" />
|
||||
|
||||
<variable name="neo:orig-root" as="document-node( element( lv:package ) )"
|
||||
select="/" />
|
||||
|
||||
<variable name="neo:orig-package" as="element( lv:package )"
|
||||
select="$neo:orig-root/lv:package" />
|
||||
|
||||
|
||||
<!--
|
||||
Linked symbols
|
||||
|
||||
This is expected to be executed in Neo4j _after_ package-level data has
|
||||
been added to the graph.
|
||||
-->
|
||||
<template match="lv:package[ l:dep ]" priority="7">
|
||||
<sequence select="concat(
|
||||
':param refs => ',
|
||||
st:to-neo4j-attrs(
|
||||
st:array( neo:linked-syms( l:dep ) ) ),
|
||||
';', $nl )" />
|
||||
</template>
|
||||
|
||||
|
||||
<function name="neo:linked-syms" as="element( st:item )+">
|
||||
<param name="deps" as="element( l:dep )" />
|
||||
|
||||
<variable name="pkg-path" as="xs:string"
|
||||
select="preproc:get-path( $deps/ancestor::lv:package/@name )" />
|
||||
|
||||
<sequence select="for $dep in $deps/preproc:sym
|
||||
return st:item(
|
||||
preproc:resolv-path(
|
||||
concat( $fqn-prefix, '/',
|
||||
$pkg-path, '/',
|
||||
$dep/@src, '/', $dep/@name ) ) )" />
|
||||
</function>
|
||||
|
||||
|
||||
<template match="lv:package" priority="5">
|
||||
<variable name="sym-deps" select="preproc:sym-deps" />
|
||||
|
||||
<sequence select="concat(
|
||||
':param syms => ',
|
||||
st:to-neo4j-attrs(
|
||||
st:array(
|
||||
for $sym in neo:get-package-syms( preproc:symtable )
|
||||
return st:item( neo:dict-from-sym( $sym ) ) ) ),
|
||||
';',
|
||||
$nl,
|
||||
|
||||
'CREATE (pkg:TamePackage ',
|
||||
st:to-neo4j-attrs(
|
||||
st:dict( (
|
||||
st:item( concat( $fqn-prefix, '/', @name ),
|
||||
'fqn' ),
|
||||
st:items-from-attrs( @* ) ) ) ),
|
||||
')',
|
||||
$nl,
|
||||
'WITH pkg',
|
||||
$nl,
|
||||
'UNWIND $syms AS symdata ',
|
||||
'MERGE (sym:TameSymbol {fqn: symdata.fqn}) ',
|
||||
'SET sym += symdata ',
|
||||
'CREATE (pkg)-[:DEFINES]->(sym) ',
|
||||
';', $nl,
|
||||
':commit', $nl,
|
||||
|
||||
':begin', $nl,
|
||||
':param deps => ',
|
||||
st:to-neo4j-attrs(
|
||||
st:array(
|
||||
for $dep in neo:get-package-deps( . )
|
||||
return st:item( neo:gen-dep-data( $dep ) ) ) ),
|
||||
$nl,
|
||||
'UNWIND $deps AS depdata ',
|
||||
'MATCH (from:TameSymbol {fqn: depdata.from}) ',
|
||||
'UNWIND depdata.to AS toname ',
|
||||
'MERGE (to:TameSymbol {fqn: toname}) ',
|
||||
'CREATE (from)-[:USES]->(to)',
|
||||
';', $nl )" />
|
||||
|
||||
|
||||
<!--
|
||||
<apply-templates select="preproc:sym-deps/preproc:sym-dep" />
|
||||
<apply-templates select="preproc:sym-deps/preproc:sym-dep/preproc:sym-ref" />
|
||||
-->
|
||||
</template>
|
||||
|
||||
|
||||
<function name="neo:sym-src" as="xs:string">
|
||||
<param name="sym" as="element( preproc:sym )" />
|
||||
|
||||
<sequence select="if ( $sym/@src and not( $sym/@src = '' ) ) then
|
||||
neo:package-lookup( $sym/@src )/@name
|
||||
else
|
||||
$sym/ancestor::lv:package/@name" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:sym-fqn" as="xs:string">
|
||||
<param name="sym" as="element( preproc:sym )" />
|
||||
|
||||
<sequence select="concat( $fqn-prefix, '/',
|
||||
neo:sym-src( $sym ), '/', $sym/@name )" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:dict-from-sym" as="element( st:dict )">
|
||||
<param name="sym" as="element( preproc:sym )" />
|
||||
|
||||
<variable name="attrs" as="attribute()+">
|
||||
<sequence select="$sym/@*" />
|
||||
|
||||
<attribute name="fqn" select="neo:sym-fqn( $sym )" />
|
||||
</variable>
|
||||
|
||||
<sequence select="st:dict( st:items-from-attrs( $attrs ) )" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:package-lookup" as="element( lv:package )">
|
||||
<param name="src" as="xs:string?" />
|
||||
|
||||
<sequence select="if ( $src and not( $src = '' ) ) then
|
||||
document( concat( $src, '.xmlo' ), $neo:orig-root )/lv:*
|
||||
else
|
||||
$neo:orig-package" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:is-mergable" as="xs:boolean">
|
||||
<param name="sym" as="element( preproc:sym )" />
|
||||
|
||||
<!-- externs aren't resolved until linking, so we cannot hope to merge
|
||||
them here -->
|
||||
<sequence select="( exists( $sym/@preproc:generated-from )
|
||||
or $sym/@type = 'cgen'
|
||||
or ( $sym/@type = 'tpl'
|
||||
and $sym/@preproc:generated = 'true' ) )
|
||||
and not( $sym/@extern = 'true' )" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:is-mergable-ref" as="xs:boolean">
|
||||
<param name="sym-ref" as="element( preproc:sym-ref )" />
|
||||
|
||||
<variable name="name" as="xs:string"
|
||||
select="$sym-ref/@name" />
|
||||
|
||||
<variable name="sym" as="element( preproc:sym )"
|
||||
select="$sym-ref/ancestor::lv:package/preproc:symtable
|
||||
/preproc:sym[ @name = $name ]" />
|
||||
|
||||
<sequence select="neo:is-mergable( $sym )" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:is-mergable-dep" as="xs:boolean">
|
||||
<param name="sym-dep" as="element( preproc:sym-dep )" />
|
||||
|
||||
<variable name="name" as="xs:string"
|
||||
select="$sym-dep/@name" />
|
||||
|
||||
<variable name="sym" as="element( preproc:sym )"
|
||||
select="$sym-dep/ancestor::lv:package/preproc:symtable
|
||||
/preproc:sym[ @name = $name ]" />
|
||||
|
||||
<sequence select="neo:is-mergable( $sym )" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:get-package-syms" as="element( preproc:sym )*">
|
||||
<param name="symtable" as="element( preproc:symtable )" />
|
||||
|
||||
<sequence select="$symtable/preproc:sym[
|
||||
not( @src )
|
||||
and not( neo:is-mergable( . ) ) ]" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:get-package-deps" as="element( preproc:sym-dep )*">
|
||||
<param name="package" as="element( lv:package )" />
|
||||
|
||||
<sequence select="$package/preproc:sym-deps/preproc:sym-dep[
|
||||
not( neo:is-mergable-dep( . ) ) ]" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:get-sym-deps-by-ref" as="element( preproc:sym-ref )*">
|
||||
<param name="sym-ref" as="element( preproc:sym-ref )" />
|
||||
|
||||
<variable name="name" as="xs:string"
|
||||
select="$sym-ref/@name" />
|
||||
|
||||
<variable name="local-deps" as="element( preproc:sym-dep )?"
|
||||
select="$sym-ref/ancestor::preproc:sym-deps/preproc:sym-dep[
|
||||
@name = $name ]" />
|
||||
|
||||
<variable name="deps" as="element( preproc:sym-dep )"
|
||||
select="if ( exists( $local-deps ) ) then
|
||||
$local-deps
|
||||
else
|
||||
neo:package-lookup-by-ref( $sym-ref )
|
||||
/preproc:sym-deps/preproc:sym-dep[ @name = $name ]" />
|
||||
|
||||
<sequence select="neo:get-sym-deps( $deps )" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:package-lookup-by-ref" as="element( lv:package )">
|
||||
<param name="sym-ref" as="element( preproc:sym-ref )" />
|
||||
|
||||
<variable name="name" as="xs:string"
|
||||
select="$sym-ref/@name" />
|
||||
|
||||
<variable name="sym" as="element( preproc:sym )"
|
||||
select="$sym-ref/ancestor::lv:package/preproc:symtable
|
||||
/preproc:sym[ @name = $name ]" />
|
||||
|
||||
<sequence select="neo:package-lookup( $sym/@src )" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:is-ignorable-ref" as="xs:boolean">
|
||||
<param name="sym-ref" as="element( preproc:sym-ref )" />
|
||||
|
||||
<!-- much quicker check than using neo:is-mergable -->
|
||||
<sequence select="$sym-ref/@name = '_CMATCH_'" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:get-sym-deps" as="element( preproc:sym-ref )*">
|
||||
<param name="sym-dep" as="element( preproc:sym-dep )" />
|
||||
|
||||
<sequence select="for $ref in $sym-dep/preproc:sym-ref[
|
||||
not( neo:is-ignorable-ref( . ) ) ]
|
||||
return if ( neo:is-mergable-ref( $ref ) ) then
|
||||
neo:get-sym-deps-by-ref( $ref )
|
||||
else
|
||||
$ref" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:gen-dep-data" as="element( st:dict )">
|
||||
<param name="dep" as="element( preproc:sym-dep )" />
|
||||
|
||||
<variable name="refs" as="element( preproc:sym-ref )*"
|
||||
select="neo:get-sym-deps( $dep )" />
|
||||
|
||||
<variable name="sym" as="element( preproc:sym )"
|
||||
select="$dep/ancestor::lv:package/preproc:symtable
|
||||
/preproc:sym[ @name = $dep/@name ]" />
|
||||
|
||||
<sequence select="st:dict( (
|
||||
st:item( neo:sym-fqn( $sym ), 'from' ),
|
||||
st:item(
|
||||
st:array(
|
||||
for $ref in $refs
|
||||
return st:item( neo:sym-ref-fqn( $ref ) ) ),
|
||||
'to' ) ) )" />
|
||||
</function>
|
||||
|
||||
|
||||
<function name="neo:sym-ref-fqn" as="xs:string">
|
||||
<param name="sym-ref" as="element( preproc:sym-ref )" />
|
||||
|
||||
<variable name="sym" as="element( preproc:sym )"
|
||||
select="$sym-ref/ancestor::lv:package/preproc:symtable
|
||||
/preproc:sym[ @name = $sym-ref/@name ]" />
|
||||
|
||||
<sequence select="neo:sym-fqn( $sym )" />
|
||||
</function>
|
||||
|
||||
</stylesheet>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
|
||||
:commit
|
||||
|
||||
// it's easier just to create these labels after-the-fact rather than muddy
|
||||
// the query generation
|
||||
:begin
|
||||
MATCH (n:TameSymbol {type: "class"}) SET n:TameClass;
|
||||
MATCH (n:TameSymbol {type: "rate"}) SET n:TameRate;
|
||||
MATCH (n:TameSymbol {type: "gen"}) SET n:TameGen;
|
||||
MATCH (n:TameSymbol {type: "cgen"}) SET n:TameCgen;
|
||||
MATCH (n:TameSymbol {type: "type"}) SET n:TameType;
|
||||
MATCH (n:TameSymbol {type: "const"}) SET n:TameConst;
|
||||
MATCH (n:TameSymbol {type: "func"}) SET n:TameFunc;
|
||||
MATCH (n:TameSymbol {type: "param"}) SET n:TameParam;
|
||||
MATCH (n:TameSymbol {type: "map"}) SET n:TameMapInput;
|
||||
MATCH (n:TameSymbol {type: "retmap"}) SET n:TameMapReturn;
|
||||
|
||||
MATCH (n:TameSymbol {extern: "true"}) SET n:TameExtern;
|
||||
|
||||
MATCH (n:TameSymbol {generated: "true"}) SET n:TameSymbolGenerated;
|
||||
MATCH (n:TamePackage {program: "true"}) SET n:TameProgram;
|
||||
:commit
|
||||
|
||||
// these are created in post.neo4j beacuse (a) they're used only for
|
||||
// querying and (b) they're more convenient to define with the new labels
|
||||
:begin
|
||||
CREATE INDEX ON :TameSymbol(name);
|
||||
CREATE INDEX ON :TameConst(value);
|
||||
CREATE INDEX ON :TameClass(orig_name);
|
||||
:commit
|
||||
|
||||
// cleanup that's easier to do here than in XSLT
|
||||
:begin
|
||||
// typedefs define constants, not use them
|
||||
MATCH (c:TameConst)<-[r:USES]-(t:TameType)
|
||||
CREATE UNIQUE (c)<-[:DEFINES]-(t)
|
||||
DELETE r;
|
||||
|
||||
// same with unions
|
||||
MATCH (t:TameType)<-[r:USES]-(u:TameType)
|
||||
CREATE UNIQUE (t)<-[:DEFINES]-(u)
|
||||
DELETE r;
|
||||
|
||||
// maps map, not use
|
||||
MATCH (n:TameMapInput)-[r:USES]->(p:TameParam)
|
||||
CREATE (n)-[:MAPS]->(p)
|
||||
DELETE r;
|
||||
:commit
|
||||
|
||||
// for convenience in querying; framework-wide conventions
|
||||
:begin
|
||||
MATCH (n:TameClass) WHERE n.orig_name STARTS WITH "submit-" SET n:SubmitRule;
|
||||
MATCH (n:TamePackage {program: "true"}) WHERE n.name STARTS WITH "suppliers/" SET n:Supplier;
|
||||
:commit
|
||||
|
||||
// megarater-specific
|
||||
:begin
|
||||
MATCH (c:TameConst)<-[*0..3]-(:TameType {name: "classCode"}) SET c:ClassCode;
|
||||
:commit
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// some of the indexes are created in post.neo4j
|
||||
:begin
|
||||
CREATE CONSTRAINT ON (n:TamePackage) ASSERT n.fqn IS UNIQUE;
|
||||
CREATE CONSTRAINT ON (n:TameSymbol) ASSERT n.fqn IS UNIQUE;
|
||||
:commit
|
||||
|
||||
:begin
|
||||
|
|
@ -0,0 +1,651 @@
|
|||
<!--
|
||||
Serialization utility functions
|
||||
|
||||
Copyright (C) 2017, 2018 R-T Specialty, LLC.
|
||||
|
||||
This file is part of the Liza Program UI Compiler.
|
||||
|
||||
liza-proguic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<stylesheet version="2.0"
|
||||
xmlns="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:struct="http://www.lovullo.com/liza/proguic/util/struct"
|
||||
xmlns:f="http://mikegerwitz.com/hoxsl/apply"
|
||||
xmlns:_struct="http://www.lovullo.com/liza/proguic/util/struct/_priv">
|
||||
|
||||
<import href="../hoxsl/src/apply.xsl" />
|
||||
|
||||
<!--
|
||||
@node Serialization
|
||||
@section Serialization
|
||||
@cindex Serialization
|
||||
|
||||
@luic{} uses a primitive API for representing and serializing objects,
|
||||
most notably JSON (@pxref{JSON Transformation}).
|
||||
This avoids having to handle string generation
|
||||
(and couple with an implementation) in various systems.
|
||||
-->
|
||||
|
||||
<variable name="struct:error-qname" as="xs:QName"
|
||||
select="QName(
|
||||
'http://www.lovullo.com/liza/proguic/util/struct/error',
|
||||
'err:BADSTRUCT' )" />
|
||||
|
||||
<!--
|
||||
@cindex Array
|
||||
An @dfn{array} is an untyped list of items.
|
||||
Usually,
|
||||
this provides @math{O(n)} lookups.
|
||||
It is ideal for linear processing of data.
|
||||
|
||||
The term ``array'' is abused in certain languages;
|
||||
if you are looking for a key/value store, use
|
||||
@ref{struct:dict#1,,@code{struct-dict}}.
|
||||
-->
|
||||
|
||||
|
||||
<!--
|
||||
Generate an empty array.
|
||||
-->
|
||||
<function name="struct:array" as="element( struct:array )">
|
||||
<struct:array />
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
Generate an untyped array of values.
|
||||
|
||||
Arrays must contain only @xmlnode{struct:item} elements,
|
||||
but unlike dictionaries,
|
||||
they @emph{must not} contain a@tie{}@xmlattr{key}.
|
||||
-->
|
||||
<function name="struct:array" as="element( struct:array )">
|
||||
<param name="values" as="element( struct:item )*" />
|
||||
|
||||
<struct:array>
|
||||
<sequence select="$values" />
|
||||
</struct:array>
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
@cindex Dictionary
|
||||
A @dfn{dictionary} is a key/value store.
|
||||
Like arrays,
|
||||
dictionaries contain items,
|
||||
but they are indexed by keys.
|
||||
Usually,
|
||||
languages implement this as a hash table,
|
||||
providing @math{O(1)} lookups.
|
||||
-->
|
||||
|
||||
|
||||
<!--
|
||||
Create an empty dictionary.
|
||||
-->
|
||||
<function name="struct:dict" as="element( struct:dict )">
|
||||
<struct:dict />
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
Create a dictionary of values.
|
||||
|
||||
This is a key-value store containing only @xmlnode{struct:item}
|
||||
elements with @xmlattr{key} attributes (@pxref{struct:item#2}).
|
||||
-->
|
||||
<function name="struct:dict" as="element( struct:dict )">
|
||||
<param name="values" as="element( struct:item )*" />
|
||||
|
||||
<struct:dict>
|
||||
<sequence select="$values" />
|
||||
</struct:dict>
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
@cindex Item
|
||||
An @dfn{item} can be either @dfn{keyed} or @dfn{unkeyed}:
|
||||
the former is suitable only for dictionaries,
|
||||
while the latter is suitable only for arrays.
|
||||
|
||||
@devnotice{Item type metadata should be added;
|
||||
otherwise, we can only serialize as a string.}
|
||||
-->
|
||||
|
||||
|
||||
<!--
|
||||
Associate a value with a key in a dictionary.
|
||||
|
||||
A key value may be a primitive value or another structure.
|
||||
Keyed items must be children of a@tie{}dictionary
|
||||
(@pxref{struct:dict#1,,@code{struct:dict}}).
|
||||
|
||||
Attribute values are converted into strings.
|
||||
-->
|
||||
<function name="struct:item" as="element( struct:item )">
|
||||
<param name="value" />
|
||||
<param name="id" as="xs:string" />
|
||||
|
||||
<struct:item key="{$id}">
|
||||
<sequence select="if ( $value instance of attribute() ) then
|
||||
string( $value )
|
||||
else
|
||||
$value" />
|
||||
</struct:item>
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
Create a keyless item.
|
||||
|
||||
A key value may be a primitive value or another structure.
|
||||
Keyless items must be children of a@tie{}array
|
||||
(@pxref{struct:array#1,,@code{struct:array}}).
|
||||
|
||||
Attribute values are converted into strings.
|
||||
-->
|
||||
<function name="struct:item" as="element( struct:item )">
|
||||
<param name="value" />
|
||||
|
||||
<struct:item>
|
||||
<sequence select="if ( $value instance of attribute() ) then
|
||||
string( $value )
|
||||
else
|
||||
$value" />
|
||||
</struct:item>
|
||||
</function>
|
||||
|
||||
<!--
|
||||
Since deriving item values from attributes is common,
|
||||
they will automatically be convered into strings.@footnote{
|
||||
Really, it makes no sense to permit attributes,
|
||||
since that will result in the attribute being assigned to the
|
||||
@xmlnode{struct:item} itself,
|
||||
which does not make any sense
|
||||
(and could corrupt internal state depending on what attribute
|
||||
was set).}
|
||||
-->
|
||||
|
||||
|
||||
<!--
|
||||
@subsection Auto-Generating Structures
|
||||
|
||||
It's common (and natural) to want to serialize key/value pairs from
|
||||
attributes.
|
||||
Two functions provide this convenience:
|
||||
-->
|
||||
|
||||
|
||||
<!--
|
||||
Generate keys from attributes.
|
||||
|
||||
A key/value pair will be created for each attribute in @var{attrs}
|
||||
using the attribute's local name as the@tie{}key. Whitespace in attribute
|
||||
values will be normalized.
|
||||
-->
|
||||
<function name="struct:items-from-attrs" as="element( struct:item )*">
|
||||
<param name="attrs" as="attribute()*" />
|
||||
|
||||
<sequence select="for $attr in $attrs
|
||||
return struct:item( normalize-space( $attr ),
|
||||
$attr/local-name() )" />
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
Convert an element into a dictinary using its attributes as
|
||||
key/value pairs.
|
||||
|
||||
The name of the element is not used.
|
||||
The attributes of the node are passed to
|
||||
@ref{struct:item#1,,@code{struct:item}}.
|
||||
-->
|
||||
<function name="struct:dict-from-attrs" as="element( struct:dict )">
|
||||
<param name="element" as="element()" />
|
||||
|
||||
<sequence select="struct:dict(
|
||||
struct:items-from-attrs( $element/@* ) )" />
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
Convert a sequence of elements into an array of dictionaries using element
|
||||
attributes as dictionary key/value pairs.
|
||||
|
||||
Each element is processed using
|
||||
@ref{struct:dict-from-attrs#1,,@code{struct:dict-from-attrs}}.
|
||||
-->
|
||||
<function name="struct:dict-array-from-elements" as="element( struct:array )">
|
||||
<param name="elements" as="element()*" />
|
||||
|
||||
<sequence select="struct:array(
|
||||
for $element in $elements
|
||||
return struct:item(
|
||||
struct:dict-from-attrs( $element ) ) )" />
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
Another function allows allows using one of the attibutes as
|
||||
a@tie{}key to recursively generate a dictionary of multiple
|
||||
elements,
|
||||
provided that those elements have unquie keys.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Recurisvely generate dictionary using an attribute as a key.
|
||||
|
||||
This generates a new dictionary with @code{n}@tie{}entires where the
|
||||
value of the key is another dictionary containing the key/value
|
||||
representation of the remaining attributes,
|
||||
where @code{n = count($element)}.
|
||||
|
||||
If @var{$recf} is non-empty,
|
||||
it will be applied to each element that generates an item in the
|
||||
parent dictionary;
|
||||
this allows for recursive processing.
|
||||
The function is applied within the context of the dictionary and
|
||||
should therefore return one or more @xmlnode{struct:item}s.
|
||||
|
||||
Beware: the given key @var{$key} is compared only by @code{local-name}.
|
||||
|
||||
@emph{No check is performed to ensure they all keys are unique in
|
||||
the toplevel dictionary.}
|
||||
Conflicts result in undefined behavior dependent on the serializer.
|
||||
For example,
|
||||
when serialized to JSON,
|
||||
the latter key takes precedence and the former keys are overwritten.
|
||||
|
||||
@devnotice{Should probably handle more gracefully a situation where the
|
||||
key attribute does not exist on one of the elements.}
|
||||
-->
|
||||
<function name="struct:dict-from-keyed-elements" as="element( struct:dict )">
|
||||
<param name="key" as="xs:string" />
|
||||
<param name="elements" as="element()*" />
|
||||
<param name="recf" as="item()*" />
|
||||
|
||||
<sequence select="
|
||||
struct:dict(
|
||||
for $element in $elements
|
||||
return struct:item(
|
||||
struct:dict(
|
||||
( struct:items-from-attrs(
|
||||
$element/@*[ not( local-name() = $key ) ] ),
|
||||
if ( $recf ) then
|
||||
f:apply( $recf, $element )
|
||||
else
|
||||
() ) ),
|
||||
$element/@*[ local-name() = $key ] ) )" />
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
Recurisvely generate dictionary using an attribute as a key.
|
||||
|
||||
This two-argument version simply invokes
|
||||
@coderef{struct:dict-from-keyed-elements#3} without a child function.
|
||||
-->
|
||||
<function name="struct:dict-from-keyed-elements" as="element( struct:dict )">
|
||||
<param name="key" as="xs:string" />
|
||||
<param name="elements" as="element()*" />
|
||||
|
||||
<sequence select="struct:dict-from-keyed-elements( $key, $elements, () )" />
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
An example usage of this function is provided in
|
||||
@ref{f:dict-from-keyed-elements}.
|
||||
|
||||
@float Figure, f:dict-from-keyed-elements
|
||||
Given some document:
|
||||
|
||||
@example
|
||||
<meta>
|
||||
<field id="foo" desc="Has nested" type="string">
|
||||
<nested name="n1" />
|
||||
</field>
|
||||
<field id="bar" desc="No nested" type="boolean" />
|
||||
</meta>
|
||||
@end example
|
||||
|
||||
With function:
|
||||
|
||||
@example
|
||||
<function name="nestedf" as="element( struct:item )+">
|
||||
<param name="field" as="element( field )" />
|
||||
<sequence select="struct:item( $field/nested/@@name, 'nestedf' )" />
|
||||
</function>
|
||||
@end example
|
||||
|
||||
Transformed with:
|
||||
|
||||
@example
|
||||
<sequence select="struct:dict-from-keyed-elements( 'id', meta, nestedf() )" />
|
||||
@end example
|
||||
|
||||
Results in:
|
||||
|
||||
@example
|
||||
<struct:dict>
|
||||
<struct:item key="foo">
|
||||
<struct:dict>
|
||||
<struct:item key="desc">Has nested</struct:item>
|
||||
<struct:item key="type">string</struct:item>
|
||||
<struct:item key="nestedf">n1</struct:item>
|
||||
</struct:dict>
|
||||
</struct:item>
|
||||
<struct:item key="bar">
|
||||
<struct:dict>
|
||||
<struct:item key="desc">No nested</struct:item>
|
||||
<struct:item key="type">boolean</struct:item>
|
||||
<struct:item key="nestedf"></struct:item>
|
||||
</struct:dict>
|
||||
</struct:item>
|
||||
</struct:dict>
|
||||
@end example
|
||||
@caption{Generating a dictionary from keyed elements.}
|
||||
@end float
|
||||
|
||||
|
||||
Extracting key/value pairs from element attributes is also a common
|
||||
operation:
|
||||
-->
|
||||
|
||||
|
||||
<!--
|
||||
Generate keyed items for each element in @var{$elements} using one
|
||||
attribute @var{$key}@tie{}as the key and another attribute
|
||||
@var{$value}@tie{}as the value.
|
||||
|
||||
Beware: the given key @var{$key} is compared only by @code{local-name}.
|
||||
|
||||
The arguments are ordered such that this is useful as a partially
|
||||
applied function for processing lists of elements with lambdas.
|
||||
-->
|
||||
<function name="struct:items-from-keyed-elements" as="element( struct:item )*">
|
||||
<param name="key" as="xs:string" />
|
||||
<param name="value" as="xs:string" />
|
||||
<param name="elements" as="element()*" />
|
||||
|
||||
<sequence select="for $element in $elements
|
||||
return struct:item(
|
||||
$element/@*[ local-name() = $value ],
|
||||
$element/@*[ local-name() = $key ] )" />
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
When generating dictionary items in a loop from numerous elements,
|
||||
it can be inconvenient keeping track of unique keys.
|
||||
If the goal is to create an array of items grouped by unique keys,
|
||||
you're in luck:
|
||||
-->
|
||||
|
||||
|
||||
<!--
|
||||
Group keyed items into arrays indexed by their respective keys.
|
||||
|
||||
Every unique key@tie{}@code{k} will result in an array@mdash{
|
||||
}indexed by@tie{}@code{k}@mdash{
|
||||
}containing each respective item.
|
||||
|
||||
@emph{Items without keys will not be retained!}
|
||||
-->
|
||||
<function name="struct:group-items-by-key" as="element( struct:item )*">
|
||||
<param name="items" as="element( struct:item )*" />
|
||||
|
||||
<for-each-group select="$items" group-by="@key">
|
||||
<struct:item key="{current-grouping-key()}">
|
||||
<struct:array>
|
||||
<sequence select="for $item in current-group()
|
||||
return struct:item( $item/node() )" />
|
||||
</struct:array>
|
||||
</struct:item>
|
||||
</for-each-group>
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
@menu
|
||||
* JSON Transformation:: Serializing to JSON.
|
||||
@end menu
|
||||
-->
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
@node JSON Transformation
|
||||
@subsection JSON Transformation
|
||||
@cindex JSON
|
||||
|
||||
The recommended way to serialize a structure as JSON is to apply
|
||||
@ref{struct:to-neo4j-attrs#1,,@code{struct:to-neo4j-attrs}}.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Transform structure into JSON.
|
||||
-->
|
||||
<function name="struct:to-neo4j-attrs" as="xs:string">
|
||||
<param name="struct" as="element()" />
|
||||
|
||||
<variable name="result" as="xs:string*">
|
||||
<apply-templates mode="struct:to-neo4j-attrs"
|
||||
select="$struct" />
|
||||
</variable>
|
||||
|
||||
<!-- force to a single string rather than a sequence of them -->
|
||||
<sequence select="string-join( $result, '' )" />
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
We assume that the structure is already well-formed;@footnote{
|
||||
That might not necessarily be assured by this implementation,
|
||||
but validations belong there (@pxref{Serialization}), not here.}
|
||||
this makes serialization a trivial task.
|
||||
|
||||
We proceed by recursive descent.
|
||||
Let's start with arrays.
|
||||
|
||||
@subsubsection Array Serialization
|
||||
|
||||
An array simply encapsulates items in square brackets:
|
||||
-->
|
||||
|
||||
<!--
|
||||
Transform array into JSON.
|
||||
-->
|
||||
<template mode="struct:to-neo4j-attrs" priority="5"
|
||||
match="struct:array">
|
||||
<text>[</text>
|
||||
<apply-templates mode="struct:to-neo4j-attrs"
|
||||
select="node()" />
|
||||
<text>]</text>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
Items are simple too,
|
||||
since we don't have to deal with keys.
|
||||
If the item contains an element,
|
||||
we consider it to be a nested structure and recurse:
|
||||
-->
|
||||
|
||||
<!--
|
||||
Transform nested structure into JSON.
|
||||
-->
|
||||
<template mode="struct:to-neo4j-attrs" priority="5"
|
||||
match="struct:item[ element() ]">
|
||||
<sequence select="struct:to-neo4j-attrs( ./element() )" />
|
||||
|
||||
<if test="following-sibling::struct:item">
|
||||
<sequence select="','" />
|
||||
</if>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
Otherwise, we consider it to be a primitive.
|
||||
At this point,
|
||||
items are untyped,
|
||||
so we have no choice but to serialize as a string:
|
||||
-->
|
||||
|
||||
<!--
|
||||
Transform primitive data into JSON.
|
||||
|
||||
Until items are typed, we have no choice but to serialize all items
|
||||
as strings.
|
||||
-->
|
||||
<template mode="struct:to-neo4j-attrs" priority="4"
|
||||
match="struct:item">
|
||||
<sequence select="concat(
|
||||
'"',
|
||||
_struct:json-escape-str( . ),
|
||||
'"' )" />
|
||||
|
||||
<if test="following-sibling::struct:item">
|
||||
<sequence select="','" />
|
||||
</if>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
Note that we took care to escape the provided value so that double
|
||||
quotes do not break out of the serialized string.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Escape double quotes within a string.
|
||||
-->
|
||||
<function name="_struct:json-escape-str" as="xs:string">
|
||||
<param name="str" as="xs:string" />
|
||||
|
||||
<sequence select="replace(
|
||||
replace( $str, '\\', '\\\\' ), '"', '\\"' )" />
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
@subsubsection Dictionary Serialization
|
||||
|
||||
Dictionaries are serialized similarly.
|
||||
In JSON,
|
||||
we represent them as objects:
|
||||
-->
|
||||
|
||||
|
||||
<!--
|
||||
Transform dictionary into JSON object.
|
||||
-->
|
||||
<template mode="struct:to-neo4j-attrs" priority="5"
|
||||
match="struct:dict">
|
||||
<text>{</text>
|
||||
<apply-templates mode="struct:to-neo4j-attrs-dict"
|
||||
select="node()" />
|
||||
<text>}</text>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
Since dictionaries are key/value,
|
||||
every item needs to be assigned to a field on the object,
|
||||
where field name is specified by @xmlattr{key}.
|
||||
Otherwise,
|
||||
serialization proceeds the same way as arrays:
|
||||
-->
|
||||
|
||||
<!--
|
||||
Transform dictionary key into JSON field on an object.
|
||||
|
||||
The field name is specified by @xmlnode{struct:item/@@key}.
|
||||
Until items are typed, we have no choice but to serialize all items
|
||||
as strings.
|
||||
-->
|
||||
<template mode="struct:to-neo4j-attrs-dict" priority="5"
|
||||
match="struct:item[ @key ]">
|
||||
<sequence select="concat(_struct:neo4j-translate-prop( @key ),
|
||||
':' )" />
|
||||
|
||||
<apply-templates mode="struct:to-neo4j-attrs"
|
||||
select="." />
|
||||
</template>
|
||||
|
||||
|
||||
<function name="_struct:neo4j-translate-prop" as="xs:string">
|
||||
<param name="name" as="xs:string" />
|
||||
|
||||
<sequence select="replace( $name, '-', '_' )" />
|
||||
</function>
|
||||
|
||||
|
||||
<!--
|
||||
Note that we escape the field.
|
||||
|
||||
At a lower priority,
|
||||
we have a catch-all that will fail if it encounters a non-keyed
|
||||
structure:
|
||||
-->
|
||||
|
||||
<!--
|
||||
Error on unrecognized structures during dictionary JSON
|
||||
transformation.
|
||||
-->
|
||||
<template mode="struct:to-neo4j-attrs-dict" priority="1"
|
||||
match="node()">
|
||||
<sequence select="error( $struct:error-qname,
|
||||
concat( 'Unexpected non-key structure: ',
|
||||
name( . ) ),
|
||||
. )"/>
|
||||
</template>
|
||||
|
||||
|
||||
<!--
|
||||
@subsubsection Miscellaneous
|
||||
|
||||
We use comments in test cases to annotate structures.
|
||||
It's unlikely that they will be used in practice,
|
||||
but since they are nodes too,
|
||||
we need to make sure we don't consider them to be errors:
|
||||
-->
|
||||
|
||||
<!--
|
||||
Ignore comments during processing.
|
||||
-->
|
||||
<template mode="struct:to-neo4j-attrs" priority="2"
|
||||
match="comment()">
|
||||
</template>
|
||||
|
||||
<!--
|
||||
Everything else we don't know about during processing results in an
|
||||
error:
|
||||
-->
|
||||
|
||||
<!--
|
||||
Error on unrecognized structures during JSON transformation.
|
||||
-->
|
||||
<template mode="struct:to-neo4j-attrs" priority="1"
|
||||
match="node()">
|
||||
<sequence select="error( $struct:error-qname,
|
||||
concat( 'Unexpected structure: ',
|
||||
string( . ) ),
|
||||
. )"/>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
And we're done.
|
||||
-->
|
||||
|
||||
</stylesheet>
|
Loading…
Reference in New Issue