Skip to content

Instantly share code, notes, and snippets.

@lcahlander
Last active May 29, 2019 14:00
Show Gist options
  • Save lcahlander/9111ce9330f80458dce6eded9b40ebbf to your computer and use it in GitHub Desktop.
Save lcahlander/9111ce9330f80458dce6eded9b40ebbf to your computer and use it in GitHub Desktop.
This library module walks an XML Schema and generates the stubs for a typeswitch transform to process a document from that schema.
(:
Copyright (c) 2017. EasyMetaHub, LLC
This work is licensed under a
<a rel="license" href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.
:)
xquery version "3.1";
(:~
This library module walks an XML Schema and generates the stubs for a typeswitch
transform to process a document from that schema.
Here is an example where the transform is running against the XML Schema's XSD.
xquery version "3.1";
import module namespace tsgen="http://easymetahub.com/ns/tsgen" at "tsgen.xqm";
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare option output:method "text";
tsgen:schema(fn:doc('https://www.w3.org/2001/XMLSchema.xsd')/xs:schema, map {
'modulePrefix': 'xsd2json',
'moduleNamespace': 'http://easymetahub.com/ns/xsd2json',
'schemaPrefix': 'xs',
'schemaNamespace': 'http://www.w3.org/2001/XMLSchema'
})
:)
module namespace tsgen="http://easymetahub.com/ns/tsgen";
declare namespace map="http://www.w3.org/2005/xpath-functions/map";
declare namespace xs="http://www.w3.org/2001/XMLSchema";
declare function tsgen:process-node($node as node()?, $model as map(*)) as map(*) {
if ($node) then
typeswitch($node)
case element(xs:attribute) return tsgen:attribute($node, $model)
case element(xs:attributeGroup) return tsgen:attributeGroup($node, $model)
case element(xs:complexType) return tsgen:complexType($node, $model)
case element(xs:element) return tsgen:element($node, $model)
case element(xs:extension) return tsgen:extension($node, $model)
case element(xs:import) return tsgen:import($node, $model)
case element(xs:include) return tsgen:include($node, $model)
(:
case element(xs:schema) return tsgen:schema($node, $model)
:)
default return tsgen:recurse($node, $model)
else $model
};
declare function tsgen:recurse($node as node()?, $model as map(*)) as map(*) {
if ($node)
then map:merge(for $cnode in $node/node() return tsgen:process-node($cnode, $model))
else $model
};
(:~
: @author Auto generated
: @version 1.0
: @param $node the current node being processed
: @param $model a map(*) used for passing additional information between the levels
:)
declare function tsgen:attribute($node as node(), $model as map(*)) as map(*) {
(: Attributes:
Child Elements:
annotation
simpleType
:)
map:merge(($model, map:entry('attributes', (map:get($model, 'attributes'), $node/@name/string()))))
};
(:~
: @author Auto generated
: @version 1.0
: @param $node the current node being processed
: @param $model a map(*) used for passing additional information between the levels
:)
declare function tsgen:attributeGroup($node as node(), $model as map(*)) as map(*) {
(: Attributes:
Child Elements:
annotation
:)
map:merge(($model,
if ($node/@ref)
then
let $name := $node/@ref/string()
let $doc := map:get($model, 'schema')
let $refGroup := $doc//attributeGroup[@name/string() eq $name]
return if ($refGroup) then tsgen:attributeGroup($refGroup, $model) else $model
else tsgen:recurse($node, $model)
))};
(:~
: @author Auto generated
: @version 1.0
: @param $node the current node being processed
: @param $model a map(*) used for passing additional information between the levels
:)
declare function tsgen:complexType($node as node()?, $model as map(*)) as map(*) {
(: Attributes:
Child Elements:
annotation
:)
tsgen:recurse($node, $model)
};
(:~
: @author Auto generated
: @version 1.0
: @param $node the current node being processed
: @param $model a map(*) used for passing additional information between the levels
:)
declare function tsgen:element($node as node(), $model as map(*)) as map(*) {
(: Attributes:
Child Elements:
annotation
complexType
simpleType
:)
if ($node/@name)
then map:merge(($model, map:entry('elements', (map:get($model, 'elements'), $node/@name/string()))))
else map:merge(($model, map:entry('elements', (map:get($model, 'elements'), $node/@ref/string()))))
};
declare function tsgen:formatted-list($string-list as xs:string*)
{
let $nl := "&#10;"
let $indented-list := for $string-item in distinct-values($string-list)
order by $string-item
return concat(': ', $string-item)
return string-join($indented-list, $nl)
};
(:~
: @author Auto generated
: @version 1.0
: @param $node the current node being processed
: @param $model a map(*) used for passing additional information between the levels
:)
declare function tsgen:element-function($node as node(), $model as map(*)) as xs:string {
(: Attributes:
Child Elements:
annotation
complexType
simpleType
:)
let $nl := "&#10;"
let $name := $node/@name/string()
let $model := map:merge(($model, tsgen:recurse($node, $model)))
let $attribute-names := map:get($model, 'attributes')
let $child-names := map:get($model, 'elements')
let $subelements := concat("(: Attributes:", $nl, tsgen:formatted-list($attribute-names), $nl,
" : Child Elements:", $nl, tsgen:formatted-list($child-names), $nl,
" :)", $nl)
let $xqdoc := string-join(("(:~", $nl,
for $doc in $node//xs:annotation/xs:documentation return (xs:string($doc), $nl, $nl),
" : @author Auto generated", $nl,
" : @version 1.0", $nl,
" : @param $node the current node being processed" , $nl,
" : @param $model a map(*) used for passing additional information between the levels", $nl,
" :)", $nl), "")
return concat($xqdoc, 'declare function ', map:get($model, 'modulePrefix'), ':', $name, '($node as node(), $model as map(*)) {', $nl, ' ', $subelements, ' ', map:get($model, 'modulePrefix'), ':recurse($node, $model)', $nl, '};', $nl)
};
(:~
: @author Auto generated
: @version 1.0
: @param $node the current node being processed
: @param $model a map(*) used for passing additional information between the levels
:)
declare function tsgen:extension($node as node(), $model as map(*)) as map(*) {
(: Attributes:
Child Elements:
annotation
:)
let $name := $node/@base/string()
let $doc := map:get($model, 'schema')
return tsgen:complexType($doc//complexType[@name/string() eq $name], $model)
};
(:~
: @author Auto generated
: @version 1.0
: @param $node the current node being processed
: @param $model a map(*) used for passing additional information between the levels
:)
declare function tsgen:import($node as node(), $model as map(*)) as map(*) {
(: Attributes:
namespace
schemaLocation
Child Elements:
:)
$model
};
(:~
: @author Auto generated
: @version 1.0
: @param $node the current node being processed
: @param $model a map(*) used for passing additional information between the levels
:)
declare function tsgen:include($node as node(), $model as map(*)) as map(*) {
(: Attributes:
schemaLocation
Child Elements:
:)
$model
};
(:~
: @author Auto generated
: @version 1.0
: @param $node the current node being processed
: @param $model a map(*) used for passing additional information between the levels
:)
declare function tsgen:schema($node as node(), $model as map(*)) {
(: Attributes:
attributeFormDefault
blockDefault
elementFormDefault
finalDefault
id
lang
targetNamespace
version
Child Elements:
annotation
import
include
redefine
:)
let $nl := "&#10;"
let $doc := $node
let $names := distinct-values($doc//xs:element/@name/string())
let $cnames := string-join(for $cname in $names
order by $cname
return concat(' case element(', map:get($model, 'schemaPrefix'), ':', $cname, ') return ', map:get($model, 'modulePrefix'), ':', $cname, '($node, $model)', $nl))
let $main := concat('xquery version "3.1";', $nl, $nl,
'module namespace ', map:get($model, 'modulePrefix'), '="', map:get($model, 'moduleNamespace') ,'";', $nl, $nl,
'declare namespace ', map:get($model, 'schemaPrefix'), '="', map:get($model, 'schemaNamespace'), '";', $nl, $nl,
'declare namespace map="http://www.w3.org/2005/xpath-functions/map";', $nl, $nl,
'declare function ', map:get($model, 'modulePrefix'), ':process-node($node as node()?, $model as map(*)) {', $nl,
' if ($node) then ', $nl,
' typeswitch($node) ', $nl,
' case text() return $node ', $nl, $cnames,
' default return ', map:get($model, 'modulePrefix'), ':recurse($node, $model) ', $nl,
' else () ', $nl,
'};', $nl, $nl,
'declare function ', map:get($model, 'modulePrefix'), ':recurse($node as node()?, $model as map(*)) as item()* {', $nl,
' if ($node) then for $cnode in $node/node() return ', map:get($model, 'modulePrefix'), ':process-node($cnode, $model) else ()', $nl,
'};', $nl, $nl
)
let $new-model := map:merge(($model, map:entry('schema', $node)))
let $functions := for $element in $doc//xs:element[@name]
let $name := $element/@name/string()
where $element/node()
order by $name
return tsgen:element-function($element, $model)
return concat($main, string-join($functions, $nl))
};
declare function tsgen:walk-schemas($doc as node()?, $depth as xs:integer) as node()*
{
if ($doc) then
let $schema := $doc//xs:schema
let $includes := $schema//xs:include
let $imports := $schema//xs:import
let $prefixes := in-scope-prefixes($schema)
let $namespace := $schema/@targetNamespace/string()
return for $prefix in $prefixes
let $namespaceURI := namespace-uri-for-prefix($prefix, $schema)
let $import := $imports[@namespace/string() = $namespaceURI]
let $schemaLoc := $schema//xs:import[@namespace/string() = $namespaceURI]/@schemaLocation/string()
return if (exists($prefix) and string-length($prefix) > 0)
then (<schemaEntry>
<modulePrefix/>
<moduleNamespace/>
<schemaPrefix>{$prefix}</schemaPrefix>
<schemaNamespace>{$namespaceURI}</schemaNamespace>
<schemaLocation>{resolve-uri($schemaLoc, base-uri($doc))}</schemaLocation>
</schemaEntry>,
tsgen:walk-schemas(doc($schemaLoc), $depth + 1))
else if ($depth = 0)
then (<schemaEntry>
<modulePrefix/>
<moduleNamespace/>
<schemaPrefix>{$prefix}</schemaPrefix>
<schemaNamespace>{$namespaceURI}</schemaNamespace>
<schemaLocation>{resolve-uri($schemaLoc, base-uri($doc))}</schemaLocation>
</schemaEntry>,
tsgen:walk-schemas(doc($schemaLoc), $depth + 1))
else ()
else ()
};
declare function tsgen:preload($schemaLocation as xs:string) as node()
{
let $doc := doc($schemaLocation)
let $schemaEntries := for $schemaEntry in tsgen:walk-schemas($doc, 0)
order by $schemaEntry/schemaNamespace/text()
return $schemaEntry
let $namespaces := distinct-values($schemaEntries/schemaNamespace/text())
return <schemas>{for $namespace in $namespaces
order by $namespace
return $schemaEntries[schemaNamespace/text() = $namespace][1]}</schemas>
};
@lcahlander
Copy link
Author

This code will generate an XQuery typeswitch transform stubs. https://en.wikibooks.org/wiki/XQuery/Typeswitch_Transformations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment