http://mathling.com/svg/draw library module
http://mathling.com/svg/draw
Module providing basic drawing functions: geo objects => SVG
Copyright© Mary Holstege 2020-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Status: Stable, subject to additions
Imports
http://mathling.com/art/coreimport module namespace core="http://mathling.com/art/core" at "../art/core.xqy"http://mathling.com/geometric/edge
import module namespace edge="http://mathling.com/geometric/edge" at "../geo/edge.xqy"http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"http://mathling.com/svg/gradients
import module namespace gradient="http://mathling.com/svg/gradients" at "../svg/gradients.xqy"http://mathling.com/svg/effects
import module namespace effect="http://mathling.com/svg/effects" at "../svg/effects.xqy"http://mathling.com/geometric/rectangle
import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"http://mathling.com/geometric
import module namespace geom="http://mathling.com/geometric" at "../geo/euclidean.xqy"http://mathling.com/core/config
import module namespace config="http://mathling.com/core/config" at "../core/config.xqy"http://mathling.com/core/errors
import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"http://mathling.com/geometric/point
import module namespace point="http://mathling.com/geometric/point" at "../geo/point.xqy"
Functions
Function: drawing-map
declare function drawing-map() as map(xs:string,function(*)?)
declare function drawing-map() as map(xs:string,function(*)?)
rendering-map()
Mapping from object kind to SVG rendering function
Returns
- map(xs:string,function(*)?)
declare function this:drawing-map() as map(xs:string,function(*)?) { util:merge-into(( $geom:DRAWING-MAP, map { "def": function-lookup(QName("http://mathling.com/type/defref", "draw"), 3), "ref": function-lookup(QName("http://mathling.com/type/defref", "draw"), 3), "text": function-lookup(QName("http://mathling.com/type/text", "draw"), 3), (: Hooks to drawing special functions; callbacks for object drawing :) "draw:draw": this:draw#3, "draw:svg-style": this:svg-style#1, "draw:dump-dynamic-parameters": this:dump-dynamic-parameters#1, (: Default no-projection :) "draw:project": function ($regions as map(xs:string,item()*)*) as map(xs:string,item()*)* {$regions} } )) }
Function: perspective-drawing-map
declare function perspective-drawing-map($canvas as map(xs:string,item()*),
$p as xs:double,
$q as xs:double,
$r as xs:double) as map(xs:string,function(*)?)
declare function perspective-drawing-map($canvas as map(xs:string,item()*), $p as xs:double, $q as xs:double, $r as xs:double) as map(xs:string,function(*)?)
perspective-drawing-map()
Drawing map for solid objects.
Params
- canvas as map(xs:string,item()*): the canvas we are projecting on to; should have a "depth" key If it doesn't we'll use the max of height/width
- p as xs:double: perspective x parameter
- q as xs:double: perspective y parameter
- r as xs:double: perspective z parameter
Returns
- map(xs:string,function(*)?)
declare function this:perspective-drawing-map( $canvas as map(xs:string,item()*), $p as xs:double, $q as xs:double, $r as xs:double ) as map(xs:string,function(*)?) { map { (: Special call back :) "draw:project": geom:project(?, $canvas, $p, $q, $r) } }
Function: perspective-drawing-map
declare function perspective-drawing-map($canvas as map(xs:string,item()*)) as map(xs:string,function(*)?)
declare function perspective-drawing-map($canvas as map(xs:string,item()*)) as map(xs:string,function(*)?)
perspective-drawing-map()
Using default perspective parameters (0.5)
Params
- canvas as map(xs:string,item()*)
Returns
- map(xs:string,function(*)?)
declare function this:perspective-drawing-map( $canvas as map(xs:string,item()*) ) as map(xs:string,function(*)?) { this:perspective-drawing-map( $canvas, 0.5, 0.5, 0.5 ) }
Function: perspective-drawing-map
declare function perspective-drawing-map() as map(xs:string,function(*)?)
declare function perspective-drawing-map() as map(xs:string,function(*)?)
perspective-drawing-map()
Using default perspective parameters (0.5), and medium resolution canvas size
(1600x1600x1600)
Returns
- map(xs:string,function(*)?)
declare function this:perspective-drawing-map() as map(xs:string,function(*)?) { this:perspective-drawing-map( core:canvas("medium")=>map:put("depth", core:width("medium")), 0.5, 0.5, 0.5 ) }
Function: draw
declare function draw($items as item()*,
$properties as map(xs:string,item()*)?,
$drawing as map(xs:string,function(*)?)) as item()*
declare function draw($items as item()*, $properties as map(xs:string,item()*)?, $drawing as map(xs:string,function(*)?)) as item()*
draw()
Render a set of objects as SVG.
Params
- items as item()*: the objects to render
- properties as map(xs:string,item()*)?: styling properties to apply to the items (default=none)
- drawing as map(xs:string,function(*)?): the map of drawing functions (default=this:drawing-map() merged with the default perspective map for solid objects)
Returns
- item()*
declare function this:draw( $items as item()*, $properties as map(xs:string,item()*)?, $drawing as map(xs:string,function(*)?) ) as item()* { let $properties := ($properties, map{})[1] for $item in $items return typeswitch ($item) case map(*) return ( let $kind := $item("kind") return ( if (empty($kind)) then ( if (empty($item=>map:keys())) then () else ( let $draw := $item("draw") return ( if (exists($draw) and ($draw instance of function(*))) then $draw($item, $properties, $drawing) else ( let $uri := $item("uri") let $draw := if (exists($uri)) then function-lookup(QName($uri, "draw"), 3) else () return ( if (exists($draw)) then $draw($item, $properties, $drawing) else errors:error("ART-UNKNOWN", trace($item,"item")) ) ) ) ) ) else ( let $draw := $drawing($kind) return ( if (empty($draw)) then ( let $draw := $item("draw") return ( if (exists($draw) and ($draw instance of function(*))) then $draw($item, $properties, $drawing) else ( let $uri := ($item("uri"),$config:TYPE-MAP($kind))[1] let $draw := if (exists($uri)) then function-lookup(QName($uri, "draw"), 3) else () return ( if (exists($draw)) then $draw($item, $properties, $drawing) else errors:error("ART-UNKNOWN", trace($item,"item")) ) ) ) ) else ( $draw($item, $properties, $drawing) ) ) ) ) ) case element(art:component) return <svg:metadata>{$item}</svg:metadata> case element(art:dynamic-parameter) return <svg:metadata>{$item}</svg:metadata> case element(art:parameter) return <svg:metadata>{$item}</svg:metadata> case element(art:metadata) return <svg:metadata>{$item/*}</svg:metadata> case element() return (: SVG passthrough :) if (namespace-uri-from-QName(node-name($item))="http://mathling.com/art") then ( comment { name($item) }, this:draw( $item/node(), util:merge-into(this:as-properties($item/@*), $properties), $drawing ), comment { "/"||name($item) } ) else ( element {node-name($item)} { this:merge-attributes( util:as-attributes($properties), $item/@* ), this:draw($item/node(), $properties, $drawing) } ) case text() return $item case document-node() return this:draw($item/node(), $properties, $drawing) default return errors:error("ART-UNKNOWN", trace($item,"item")) }
Function: draw
declare function draw($items as item()*,
$properties as map(xs:string,item()*)?) as item()*
declare function draw($items as item()*, $properties as map(xs:string,item()*)?) as item()*
Params
- items as item()*
- properties as map(xs:string,item()*)?
Returns
- item()*
declare function this:draw( $items as item()*, $properties as map(xs:string,item()*)? ) as item()* { this:draw($items, $properties, util:merge-into( this:drawing-map(), let $resolution := ($properties("resolution"),"medium")[1] let $p := ($properties("perspective.p"), 0.5)[1] let $q := ($properties("perspective.q"), 0.5)[1] let $r := ($properties("perspective.r"), 0.5)[1] let $canvas := core:canvas($resolution) return this:perspective-drawing-map($canvas, $p, $q, $r) ) ) }
Function: draw
declare function draw($items as item()*) as item()*
declare function draw($items as item()*) as item()*
Params
- items as item()*
Returns
- item()*
declare function this:draw($items as item()*) as item()* { this:draw($items, map{}, util:merge-into( this:drawing-map(), this:perspective-drawing-map() ) ) }
Function: perspective-draw
declare function perspective-draw($items as item()*,
$properties as map(xs:string,item()*)?,
$canvas as map(xs:string,item()*)) as item()*
declare function perspective-draw($items as item()*, $properties as map(xs:string,item()*)?, $canvas as map(xs:string,item()*)) as item()*
Params
- items as item()*
- properties as map(xs:string,item()*)?
- canvas as map(xs:string,item()*)
Returns
- item()*
declare function this:perspective-draw( $items as item()*, $properties as map(xs:string,item()*)?, $canvas as map(xs:string,item()*) ) as item()* { this:draw($items, $properties, util:merge-into( this:drawing-map(), let $p := ($properties("perspective.p"), 0.5)[1] let $q := ($properties("perspective.q"), 0.5)[1] let $r := ($properties("perspective.r"), 0.5)[1] return this:perspective-drawing-map($canvas, $p, $q, $r) ) ) }
Function: colour-buckets
declare function colour-buckets($matrix as map(*),
$colouring as function(item()*) as item(),
$exclude as function(item()*) as xs:boolean) as map(*)
declare function colour-buckets($matrix as map(*), $colouring as function(item()*) as item(), $exclude as function(item()*) as xs:boolean) as map(*)
colour-buckets()
Segregate the points in the matrix into colour buckets based on the
colouring function, which should return a colour string or stop number.
Service function for draw-point-matrix()
Params
- matrix as map(*): the point array (either core/matrix or core/array)
- colouring as function(item()*)asitem(): a function that maps a matrix value to a colour, the colour can be either a colour name or RGB string or a gradient stop number, an index into to the colours in $properties("gradient")
- exclude as function(item()*)asxs:boolean: return true() if we should skip this value
Returns
- map(*)
declare function this:colour-buckets( $matrix as map(*), $colouring as function(item()*) as item(), $exclude as function(item()*) as xs:boolean ) as map(*) { let $kind := $matrix("kind") (: Breaks encapsulation: if we changed keys we'd be in trouble here :) let $rows := $matrix("rows") let $columns := $matrix("columns") let $get := switch ($kind) case "matrix" return function-lookup(QName("http://mathling.com/core/matrix", "get-if-set"), 3) case "array" return function-lookup(QName("http://mathling.com/core/array", "get"), 3) case "imagearray" return function-lookup(QName("http://mathling.com/image/matrix", "get"), 3) case "point-matrix" return let $get := function-lookup(QName("http://mathling.com/geometric/matrix", "get-if-set"), 3) return function ($matrix as map(*), $row as xs:integer, $col as xs:integer) as item()* { $get($matrix, $col, $row) } default return errors:error("ART-BADPARMS", ("matrix.kind", $kind)) return ( map:merge( for $row in 1 to $rows return map:merge( for $col in 1 to $columns let $val := $get($matrix, $row, $col) return ( if (empty($val) or $exclude($val)) then () else ( let $colour := $colouring($val) return map {$colour: point:point($col, $row)} ) ), map {"duplicates": "combine"} ) , map {"duplicates": "combine"} ) (: fold-left(1 to $rows, map{}, function($buckets as map(*), $row as xs:integer) as map(*) { fold-left(1 to $columns, $buckets, function($buckets as map(*), $col as xs:integer) as map(*) { let $val := $get($matrix, $row, $col) return ( if (empty($val) or $exclude($val)) then $buckets else ( let $colour := $colouring($val) return $buckets=>map:put($colour, ($buckets($colour), point:point($col, $row))) ) ) } ) } ) :) ) }
Function: colour-buckets
declare function colour-buckets($matrix as map(*),
$colouring as function(item()*) as item()) as map(*)
declare function colour-buckets($matrix as map(*), $colouring as function(item()*) as item()) as map(*)
Params
- matrix as map(*)
- colouring as function(item()*)asitem()
Returns
- map(*)
declare function this:colour-buckets( $matrix as map(*), $colouring as function(item()*) as item() ) as map(*) { this:colour-buckets($matrix, $colouring, function($val as item()*) as xs:boolean { false() } ) }
Function: draw-point-matrix
declare function draw-point-matrix($matrix as map(*),
$colouring as function(item()*) as item(),
$properties as map(xs:string,item()*)) as element()*
declare function draw-point-matrix($matrix as map(*), $colouring as function(item()*) as item(), $properties as map(xs:string,item()*)) as element()*
draw-point-matrix()
Efficient rendering of a point matrix, for when you have a matrix that
covers the whole canvas in a relatively small number of colours. This
converts each set of points in a single colour bucket into a path with
tiny lines at each point.
Params
- matrix as map(*): a point matrix, in either core/matrix, core/array, or geometric/matrix format
- colouring as function(item()*)asitem(): a function that maps a matrix value to a colour, the colour can be either a colour name or RGB string or a gradient stop number, an index into to the colours in $properties("gradient")
- properties as map(xs:string,item()*): style properties plus "gradient": the gradient to index colour values into (default=none) "width": how wide/long a line to make (default=1)
Returns
- element()*
declare function this:draw-point-matrix( $matrix as map(*), $colouring as function(item()*) as item(), $properties as map(xs:string,item()*) ) as element()* { this:draw-colour-buckets( this:colour-buckets($matrix, $colouring), $properties ) }
Function: draw-colour-buckets
declare function draw-colour-buckets($buckets as map(*),
$properties as map(xs:string,item()*)) as element()*
declare function draw-colour-buckets($buckets as map(*), $properties as map(xs:string,item()*)) as element()*
draw-colour-buckets()
Efficient rendering of a colour buckets; workhorse of draw-point-matrix()
If you use numeric colour keys, properties must include "colours" with
the colour palette
Params
- buckets as map(*): colour buckets, e.g. output from colour-buckets()
- properties as map(xs:string,item()*): style properties plus "gradient": the gradient to index colour values into (default=none) "width": how wide/long a line to make (default=1)
Returns
- element()*
declare function this:draw-colour-buckets( $buckets as map(*), $properties as map(xs:string,item()*) ) as element()* { let $gradient := $properties("gradient") let $colours := gradient:colours($gradient) return this:draw-colour-buckets( $buckets, function($key as item()) as node()* { let $colour := if ($key instance of xs:numeric) then $colours[$key cast as xs:integer] else string($key) return util:as-attributes(this:svg-style(map {"colour": $colour})) }, $properties ) }
Function: draw-colour-buckets
declare function draw-colour-buckets($buckets as map(*),
$keyfn as function(item()) as node()*,
$properties as map(xs:string,item()*)) as element()*
declare function draw-colour-buckets($buckets as map(*), $keyfn as function(item()) as node()*, $properties as map(xs:string,item()*)) as element()*
draw-colour-buckets()
Efficient rendering of a colour buckets; workhorse of draw-point-matrix()
Params
- buckets as map(*): colour buckets, e.g. output from colour-buckets()
- keyfn as function(item())asnode()*: mapping of key to attributes
- properties as map(xs:string,item()*): style properties plus "gradient": the gradient to index colour values into (default=none) "width": how wide/long a line to make (default=1)
Returns
- element()*
declare function this:draw-colour-buckets( $buckets as map(*), $keyfn as function(item()) as node()*, $properties as map(xs:string,item()*) ) as element()* { let $style-properties := util:exclude($properties,("gradient","width")) let $gradient := $properties("gradient") let $colours := gradient:colours($gradient) let $width := ($properties("width"),1)[1] for $key in $buckets=>map:keys() let $all-points := $buckets($key) let $chunks := 1 + count($all-points) idiv 10000 for $chunk in 1 to $chunks let $points := $all-points[position() > ($chunk - 1)*10000 and position() <= $chunk*10000] let $path := string-join( for $pt in $points return ( edge:map-command('goto','absolute')||" "|| point:px($pt)||" "||point:py($pt)|| edge:map-command('line','absolute')||" "|| (point:px($pt) + $width)||" "||(point:py($pt) + 0*$width) ), " " ) where exists($points) return ( <svg:path d="{$path}">{ if (exists($style-properties("stroke-width"))) then () else attribute stroke-width {$width} , if (exists($style-properties("stroke-linecap"))) then () else attribute stroke-linecap {"round"} , util:as-attributes($style-properties), $keyfn($key) }</svg:path> ) }
Function: to-CSS
declare function to-CSS($name as xs:string,
$properties as map(xs:string,item()*))
declare function to-CSS($name as xs:string, $properties as map(xs:string,item()*))
to-CSS()
Render a property bundle as a CSS class style.
Params
- name as xs:string: the style name
- properties as map(xs:string,item()*): the properties
declare function this:to-CSS( $name as xs:string, $properties as map(xs:string,item()*) ) { let $style-properties := this:svg-style($properties) return ( "."||$name||" { "|| string-join(( map:for-each($style-properties, function ($key as xs:string, $value as item()*) as xs:string { $key||": "||string-join($value!string(.)," ") } ) ),"; ")|| " } " ) }
Function: drawing
declare function drawing($canvas as map(xs:string,item()*),
$metadata as element()*,
$defs as element()*,
$styles as xs:string*,
$content as item()*)
declare function drawing($canvas as map(xs:string,item()*), $metadata as element()*, $defs as element()*, $styles as xs:string*, $content as item()*)
drawing()
Render a complete SVG. The components here should have already been
drawn (i.e. rendered to SVG)
Params
- canvas as map(xs:string,item()*): defines size of canvas
- metadata as element()*: metadata elements
- defs as element()*: definitions
- styles as xs:string*: CSS styles
- content as item()*: body content
declare function this:drawing( $canvas as map(xs:string,item()*), $metadata as element()*, $defs as element()*, $styles as xs:string*, $content as item()* ) { <svg:svg version="1.1" viewBox="0 0 {box:width($canvas)} {box:height($canvas)}" > <svg:metadata>{$metadata}</svg:metadata> <svg:defs>{$defs}</svg:defs> <svg:style type="text/css">{$styles}</svg:style> <svg:g>{$content}</svg:g> </svg:svg> }
Function: merge-attributes
declare function merge-attributes($old as attribute(*)*,
$new as attribute(*)*) as attribute(*)*
declare function merge-attributes($old as attribute(*)*, $new as attribute(*)*) as attribute(*)*
Params
- old as attribute(*)*
- new as attribute(*)*
Returns
- attribute(*)*
declare function this:merge-attributes( $old as attribute(*)*, $new as attribute(*)* ) as attribute(*)* { let $names := $new!node-name(.) return ( $new, $old[not(node-name(.) = $names)] ) }
Function: as-properties
declare function as-properties($attributes as attribute(*)*) as map(xs:string,item()*)?
declare function as-properties($attributes as attribute(*)*) as map(xs:string,item()*)?
as-properties()
Convert attributes to a series of properties, for rendering.
Note: no validity checking here; assumes we have pre-sanitized.
Params
- attributes as attribute(*)*
Returns
- map(xs:string,item()*)?
declare function this:as-properties( $attributes as attribute(*)* ) as map(xs:string,item()*)? { fold-left($attributes, map {}, function ($map as map(xs:string,item()*), $attribute as attribute(*)) as map(xs:string,item()*) { $map=>map:put(name($attribute), data($attribute)) } ) }
Function: svg-style
declare function svg-style($properties as map(xs:string,item()*)) as map(xs:string,item()*)
declare function svg-style($properties as map(xs:string,item()*)) as map(xs:string,item()*)
svg-style()
Mapping of art properties to SVG style properties
Params
- properties as map(xs:string,item()*)
Returns
- map(xs:string,item()*)
declare function this:svg-style( $properties as map(xs:string,item()*) ) as map(xs:string,item()*) { let $properties := if (exists($properties("stroke"))) then ( $properties=>map:put("stroke", gradient:ref($properties("stroke"))) ) else $properties let $properties := if (exists($properties("fill"))) then ( $properties=>map:put("fill", gradient:ref($properties("fill"))) ) else $properties let $properties := if (exists($properties("colour"))) then ( if (empty($properties("fill"))) then ( $properties=>map:put("fill", gradient:ref($properties("colour"))) ) else $properties ) else $properties let $properties := if (exists($properties("colour"))) then ( if (empty($properties("stroke"))) then ( $properties=>map:put("stroke", gradient:ref($properties("colour"))) ) else $properties ) else $properties let $properties := if (exists($properties("filter"))) then ( $properties=>map:put("filter", effect:ref($properties("filter"))) ) else $properties let $properties := if (exists($properties("width"))) then ( $properties=>map:put("stroke-width", $properties("width")) ) else $properties return $properties=>map:remove("colour")=>map:remove("width") }
Function: dump-randomizer
declare function dump-randomizer($key as xs:string, $algorithm as map(xs:string, item()*))
declare function dump-randomizer($key as xs:string, $algorithm as map(xs:string, item()*))
dump-randomizer()
Dump the algorithm map as metadata suitable for SVG embedding.
distribution: Distribution to use, one of "constant", "uniform", "normal",
"skewed", "bernoulli", "flip", "zipf", "markov", "sums", "multimodal"
min: mimumum value (optional)
max: maximum value (optional)
pre-multiplier: multiplier before min/max (optional)
post-multipler: multiplier after min/max (optional)
cast: cast type (optional)
mean: mean of distribution (normal, skewed) (default=0)
std: standard deviation of distribution (normal, skewed) (default=mean)
skew: skew of distribution (skewed) (default=0)
p: probability (bernoulli, flip) (default=50)
sums: cumulative probability sums (zipf, markov)
alpha: alpha parameter (zipf) (needed if no sums) (default=0)
limit: number of sums (zipf) (needed if no sums) (default=1000)
start: index of starting symbol (markov) (default=uniform[1,dim])
dim: size of each dimension of Markov matrix (markov)
matrix: raw Markov matrix (used if sums not provided, not recommended)
sums: cumulative probability sums (zipf, markov)
Single element, sequence of doubles
matrix: raw Markov matrix (used if sums not provided, not recommended)
Single element, sequence of doubles
distributions: component distributions (multimodal)
Params
- key as xs:string
- algorithm as map(xs:string,item()*)
declare function this:dump-randomizer($key as xs:string, $algorithm as map(xs:string, item()*)) { if (exists($algorithm("describe"))) then ( let $description := $algorithm("describe")($algorithm) return if (empty($description)) then () else ( <art:algorithm name="{$key}" distribution="{$algorithm('distribution')}"> { $description } </art:algorithm> ) ) else ( <art:algorithm name="{$key}" distribution="{$algorithm('distribution')}"> { this:dump-attr("min",$algorithm), this:dump-attr("max",$algorithm), this:dump-attr("pre-multiplier",$algorithm), this:dump-attr("post-multiplier",$algorithm), this:dump-attr("post-shift",$algorithm), this:dump-attr("cast",$algorithm), this:dump-attr("mean",$algorithm), this:dump-attr("std",$algorithm), this:dump-attr("skew",$algorithm), this:dump-attr("p",$algorithm), this:dump-attr("alpha",$algorithm), this:dump-attr("limit",$algorithm), this:dump-attr("dim",$algorithm), this:dump-attr("start",$algorithm), this:dump-elt("sums",$algorithm), this:dump-elt("keys",$algorithm), this:dump-elt("matrix",$algorithm), for $distribution at $i in $algorithm("distributions") return this:dump-randomizer(string($i), $distribution), if (exists($algorithm("selector"))) then ( this:dump-randomizer("selector", $algorithm("selector")) ) else () } </art:algorithm> ) }
Function: dump-randomizers
declare function dump-randomizers($randomizers as map(xs:string, item()*))
declare function dump-randomizers($randomizers as map(xs:string, item()*))
Params
- randomizers as map(xs:string,item()*)
declare function this:dump-randomizers($randomizers as map(xs:string, item()*)) { let $keys := $randomizers=>map:keys() let $prefixes := distinct-values( for $k in $keys return ( if (contains($k, ".")) then substring-before($k,".") else () ) ) return ( for $prefix in $prefixes order by $prefix ascending return ( element art:component { attribute name {$prefix}, let $pkeys := $keys[starts-with(.,$prefix||".")] for $k in $pkeys order by $k ascending return ( this:dump-randomizer($k, $randomizers($k)) ) } ) , for $k in $keys where not(contains($k,".")) order by $k ascending return ( this:dump-randomizer($k, $randomizers($k)) ) ) }
Function: dump-parameters
declare function dump-parameters($parameters as map(xs:string, item()*))
declare function dump-parameters($parameters as map(xs:string, item()*))
Params
- parameters as map(xs:string,item()*)
declare function this:dump-parameters($parameters as map(xs:string, item()*)) { this:dump-parameters($parameters, xs:QName("art:parameter"), ()) }
Function: dump-dynamic-parameters
declare function dump-dynamic-parameters($parameters as map(xs:string, item()*), $i as xs:integer?)
declare function dump-dynamic-parameters($parameters as map(xs:string, item()*), $i as xs:integer?)
Params
- parameters as map(xs:string,item()*)
- i as xs:integer?
declare function this:dump-dynamic-parameters($parameters as map(xs:string, item()*), $i as xs:integer?) { this:dump-parameters($parameters, xs:QName("art:dynamic-parameter"), $i) }
Function: dump-dynamic-parameters
declare function dump-dynamic-parameters($parameters as map(xs:string, item()*))
declare function dump-dynamic-parameters($parameters as map(xs:string, item()*))
Params
- parameters as map(xs:string,item()*)
declare function this:dump-dynamic-parameters($parameters as map(xs:string, item()*)) { this:dump-parameters($parameters, xs:QName("art:dynamic-parameter"), ()) }
Function: dump-parameters
declare function dump-parameters($parameters as map(xs:string, item()*),
$parameter-qname as xs:QName,
$i as xs:integer?) as item()*
declare function dump-parameters($parameters as map(xs:string, item()*), $parameter-qname as xs:QName, $i as xs:integer?) as item()*
Params
- parameters as map(xs:string,item()*)
- parameter-qname as xs:QName
- i as xs:integer?
Returns
- item()*
declare function this:dump-parameters( $parameters as map(xs:string, item()*), $parameter-qname as xs:QName, $i as xs:integer? ) as item()* { let $keys := $parameters=>map:keys() let $prefixes := distinct-values( for $k in $keys return ( if (contains($k, ".")) then substring-before($k,".") else () ) ) return ( for $prefix in $prefixes order by $prefix ascending return ( element art:component { attribute name {$prefix}, let $pkeys := $keys[starts-with(.,$prefix||".")] for $k in $pkeys let $k2 := string-join(($k, $i),".") let $v := $parameters($k) order by $k ascending return ( if ($v instance of map(xs:string, item()*) and $v=>map:contains("distribution")) then ( this:dump-randomizer($k2, $v) ) else ( element {$parameter-qname} { attribute name {$k2}, for $vi in $v return this:dump-value($vi) } ) ) } ) , for $k in $keys let $v := $parameters($k) where not(contains($k,".")) and ($k ne "description") order by $k ascending return ( if ($v instance of map(xs:string, item()*) and $v=>map:contains("distribution")) then ( this:dump-randomizer($k, $v) ) else ( element art:parameter { attribute name {$k}, for $vi in $v return this:dump-value($vi) } ) ) ) }
Function: dump-value
declare function dump-value($value as item()?) as xs:string
declare function dump-value($value as item()?) as xs:string
Params
- value as item()?
Returns
- xs:string
declare function this:dump-value($value as item()?) as xs:string { typeswitch ($value) case empty-sequence() return "" case xs:QName return util:describe-qname($value) case map(*) return this:dump-map($value) case array(*) return this:dump-array($value) case function(*) return util:function-name($value) default return geom:quote($value) }
Function: dump-map
declare function dump-map($map as map(*)) as xs:string
declare function dump-map($map as map(*)) as xs:string
Params
- map as map(*)
Returns
- xs:string
declare function this:dump-map($map as map(*)) as xs:string { if ($map("describe") instance of function(*)) then $map("describe")($map) else ( "{"|| string-join(( for $k in $map=>map:keys() let $v := $map($k) order by string($k) ascending return string($k)||": "|| (if (count($v) > 1) then "("||string-join(for $v1 in $v return this:dump-value($v1),",")||")" else this:dump-value($v)) ),", ") ||"}" ) }
Function: dump-array
declare function dump-array($array as array(*))
declare function dump-array($array as array(*))
Params
- array as array(*)
declare function this:dump-array($array as array(*)) { "["|| string-join(( array:for-each($array, function($v as item()*) as xs:string {this:dump-value($v)}) ),", ") ||"]" }
Original Source Code
xquery version "3.1"; (:~ : Module providing basic drawing functions: geo objects => SVG : : Copyright© Mary Holstege 2020-2023 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @since December 2021 : @custom:Status Stable, subject to additions :) module namespace this="http://mathling.com/svg/draw"; declare namespace art="http://mathling.com/art"; declare namespace svg="http://www.w3.org/2000/svg"; declare namespace xsl="http://www.w3.org/1999/XSL/Transform"; declare namespace xhtml="http://www.w3.org/1999/xhtml"; declare namespace xlink="http://www.w3.org/1999/xlink"; declare namespace map="http://www.w3.org/2005/xpath-functions/map"; declare namespace array="http://www.w3.org/2005/xpath-functions/array"; declare namespace math="http://www.w3.org/2005/xpath-functions/math"; import module namespace core="http://mathling.com/art/core" at "../art/core.xqy"; import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"; import module namespace config="http://mathling.com/core/config" at "../core/config.xqy"; import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"; import module namespace geom="http://mathling.com/geometric" at "../geo/euclidean.xqy"; import module namespace point="http://mathling.com/geometric/point" at "../geo/point.xqy"; import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"; import module namespace edge="http://mathling.com/geometric/edge" at "../geo/edge.xqy"; import module namespace gradient="http://mathling.com/svg/gradients" at "../svg/gradients.xqy"; import module namespace effect="http://mathling.com/svg/effects" at "../svg/effects.xqy"; (:~ : rendering-map() : Mapping from object kind to SVG rendering function :) declare function this:drawing-map() as map(xs:string,function(*)?) { util:merge-into(( $geom:DRAWING-MAP, map { "def": function-lookup(QName("http://mathling.com/type/defref", "draw"), 3), "ref": function-lookup(QName("http://mathling.com/type/defref", "draw"), 3), "text": function-lookup(QName("http://mathling.com/type/text", "draw"), 3), (: Hooks to drawing special functions; callbacks for object drawing :) "draw:draw": this:draw#3, "draw:svg-style": this:svg-style#1, "draw:dump-dynamic-parameters": this:dump-dynamic-parameters#1, (: Default no-projection :) "draw:project": function ($regions as map(xs:string,item()*)*) as map(xs:string,item()*)* {$regions} } )) }; (:~ : perspective-drawing-map() : Drawing map for solid objects. : : @param $canvas: the canvas we are projecting on to; should have a "depth" key : If it doesn't we'll use the max of height/width : @param $p: perspective x parameter : @param $q: perspective y parameter : @param $r: perspective z parameter :) declare function this:perspective-drawing-map( $canvas as map(xs:string,item()*), $p as xs:double, $q as xs:double, $r as xs:double ) as map(xs:string,function(*)?) { map { (: Special call back :) "draw:project": geom:project(?, $canvas, $p, $q, $r) } }; (:~ : perspective-drawing-map() : Using default perspective parameters (0.5) :) declare function this:perspective-drawing-map( $canvas as map(xs:string,item()*) ) as map(xs:string,function(*)?) { this:perspective-drawing-map( $canvas, 0.5, 0.5, 0.5 ) }; (:~ : perspective-drawing-map() : Using default perspective parameters (0.5), and medium resolution canvas size : (1600x1600x1600) :) declare function this:perspective-drawing-map() as map(xs:string,function(*)?) { this:perspective-drawing-map( core:canvas("medium")=>map:put("depth", core:width("medium")), 0.5, 0.5, 0.5 ) }; (:~ : draw() : Render a set of objects as SVG. : : @param $items: the objects to render : @param $properties: styling properties to apply to the items (default=none) : @param $drawing: the map of drawing functions (default=this:drawing-map() merged : with the default perspective map for solid objects) :) declare function this:draw( $items as item()*, $properties as map(xs:string,item()*)?, $drawing as map(xs:string,function(*)?) ) as item()* { let $properties := ($properties, map{})[1] for $item in $items return typeswitch ($item) case map(*) return ( let $kind := $item("kind") return ( if (empty($kind)) then ( if (empty($item=>map:keys())) then () else ( let $draw := $item("draw") return ( if (exists($draw) and ($draw instance of function(*))) then $draw($item, $properties, $drawing) else ( let $uri := $item("uri") let $draw := if (exists($uri)) then function-lookup(QName($uri, "draw"), 3) else () return ( if (exists($draw)) then $draw($item, $properties, $drawing) else errors:error("ART-UNKNOWN", trace($item,"item")) ) ) ) ) ) else ( let $draw := $drawing($kind) return ( if (empty($draw)) then ( let $draw := $item("draw") return ( if (exists($draw) and ($draw instance of function(*))) then $draw($item, $properties, $drawing) else ( let $uri := ($item("uri"),$config:TYPE-MAP($kind))[1] let $draw := if (exists($uri)) then function-lookup(QName($uri, "draw"), 3) else () return ( if (exists($draw)) then $draw($item, $properties, $drawing) else errors:error("ART-UNKNOWN", trace($item,"item")) ) ) ) ) else ( $draw($item, $properties, $drawing) ) ) ) ) ) case element(art:component) return <svg:metadata>{$item}</svg:metadata> case element(art:dynamic-parameter) return <svg:metadata>{$item}</svg:metadata> case element(art:parameter) return <svg:metadata>{$item}</svg:metadata> case element(art:metadata) return <svg:metadata>{$item/*}</svg:metadata> case element() return (: SVG passthrough :) if (namespace-uri-from-QName(node-name($item))="http://mathling.com/art") then ( comment { name($item) }, this:draw( $item/node(), util:merge-into(this:as-properties($item/@*), $properties), $drawing ), comment { "/"||name($item) } ) else ( element {node-name($item)} { this:merge-attributes( util:as-attributes($properties), $item/@* ), this:draw($item/node(), $properties, $drawing) } ) case text() return $item case document-node() return this:draw($item/node(), $properties, $drawing) default return errors:error("ART-UNKNOWN", trace($item,"item")) }; declare function this:draw( $items as item()*, $properties as map(xs:string,item()*)? ) as item()* { this:draw($items, $properties, util:merge-into( this:drawing-map(), let $resolution := ($properties("resolution"),"medium")[1] let $p := ($properties("perspective.p"), 0.5)[1] let $q := ($properties("perspective.q"), 0.5)[1] let $r := ($properties("perspective.r"), 0.5)[1] let $canvas := core:canvas($resolution) return this:perspective-drawing-map($canvas, $p, $q, $r) ) ) }; declare function this:draw($items as item()*) as item()* { this:draw($items, map{}, util:merge-into( this:drawing-map(), this:perspective-drawing-map() ) ) }; declare function this:perspective-draw( $items as item()*, $properties as map(xs:string,item()*)?, $canvas as map(xs:string,item()*) ) as item()* { this:draw($items, $properties, util:merge-into( this:drawing-map(), let $p := ($properties("perspective.p"), 0.5)[1] let $q := ($properties("perspective.q"), 0.5)[1] let $r := ($properties("perspective.r"), 0.5)[1] return this:perspective-drawing-map($canvas, $p, $q, $r) ) ) }; (:~ : colour-buckets() : Segregate the points in the matrix into colour buckets based on the : colouring function, which should return a colour string or stop number. : Service function for draw-point-matrix() : : @param $matrix: the point array (either core/matrix or core/array) : @param $colouring: a function that maps a matrix value to a colour, : the colour can be either a colour name or RGB string or a gradient : stop number, an index into to the colours in $properties("gradient") : @param $exclude: return true() if we should skip this value :) declare function this:colour-buckets( $matrix as map(*), $colouring as function(item()*) as item(), $exclude as function(item()*) as xs:boolean ) as map(*) { let $kind := $matrix("kind") (: Breaks encapsulation: if we changed keys we'd be in trouble here :) let $rows := $matrix("rows") let $columns := $matrix("columns") let $get := switch ($kind) case "matrix" return function-lookup(QName("http://mathling.com/core/matrix", "get-if-set"), 3) case "array" return function-lookup(QName("http://mathling.com/core/array", "get"), 3) case "imagearray" return function-lookup(QName("http://mathling.com/image/matrix", "get"), 3) case "point-matrix" return let $get := function-lookup(QName("http://mathling.com/geometric/matrix", "get-if-set"), 3) return function ($matrix as map(*), $row as xs:integer, $col as xs:integer) as item()* { $get($matrix, $col, $row) } default return errors:error("ART-BADPARMS", ("matrix.kind", $kind)) return ( map:merge( for $row in 1 to $rows return map:merge( for $col in 1 to $columns let $val := $get($matrix, $row, $col) return ( if (empty($val) or $exclude($val)) then () else ( let $colour := $colouring($val) return map {$colour: point:point($col, $row)} ) ), map {"duplicates": "combine"} ) , map {"duplicates": "combine"} ) (: fold-left(1 to $rows, map{}, function($buckets as map(*), $row as xs:integer) as map(*) { fold-left(1 to $columns, $buckets, function($buckets as map(*), $col as xs:integer) as map(*) { let $val := $get($matrix, $row, $col) return ( if (empty($val) or $exclude($val)) then $buckets else ( let $colour := $colouring($val) return $buckets=>map:put($colour, ($buckets($colour), point:point($col, $row))) ) ) } ) } ) :) ) }; declare function this:colour-buckets( $matrix as map(*), $colouring as function(item()*) as item() ) as map(*) { this:colour-buckets($matrix, $colouring, function($val as item()*) as xs:boolean { false() } ) }; (:~ : draw-point-matrix() : Efficient rendering of a point matrix, for when you have a matrix that : covers the whole canvas in a relatively small number of colours. This : converts each set of points in a single colour bucket into a path with : tiny lines at each point. : : @param $matrix: a point matrix, in either core/matrix, core/array, or : geometric/matrix format : @param $colouring: a function that maps a matrix value to a colour, : the colour can be either a colour name or RGB string or a gradient : stop number, an index into to the colours in $properties("gradient") : @param $properties: style properties plus : "gradient": the gradient to index colour values into (default=none) : "width": how wide/long a line to make (default=1) :) declare function this:draw-point-matrix( $matrix as map(*), $colouring as function(item()*) as item(), $properties as map(xs:string,item()*) ) as element()* { this:draw-colour-buckets( this:colour-buckets($matrix, $colouring), $properties ) }; (:~ : draw-colour-buckets() : Efficient rendering of a colour buckets; workhorse of draw-point-matrix() : If you use numeric colour keys, properties must include "colours" with : the colour palette : : @param $buckets: colour buckets, e.g. output from colour-buckets() : @param $properties: style properties plus : "gradient": the gradient to index colour values into (default=none) : "width": how wide/long a line to make (default=1) :) declare function this:draw-colour-buckets( $buckets as map(*), $properties as map(xs:string,item()*) ) as element()* { let $gradient := $properties("gradient") let $colours := gradient:colours($gradient) return this:draw-colour-buckets( $buckets, function($key as item()) as node()* { let $colour := if ($key instance of xs:numeric) then $colours[$key cast as xs:integer] else string($key) return util:as-attributes(this:svg-style(map {"colour": $colour})) }, $properties ) }; (:~ : draw-colour-buckets() : Efficient rendering of a colour buckets; workhorse of draw-point-matrix() : : @param $buckets: colour buckets, e.g. output from colour-buckets() : @param $keyfn: mapping of key to attributes : @param $properties: style properties plus : "gradient": the gradient to index colour values into (default=none) : "width": how wide/long a line to make (default=1) :) declare function this:draw-colour-buckets( $buckets as map(*), $keyfn as function(item()) as node()*, $properties as map(xs:string,item()*) ) as element()* { let $style-properties := util:exclude($properties,("gradient","width")) let $gradient := $properties("gradient") let $colours := gradient:colours($gradient) let $width := ($properties("width"),1)[1] for $key in $buckets=>map:keys() let $all-points := $buckets($key) let $chunks := 1 + count($all-points) idiv 10000 for $chunk in 1 to $chunks let $points := $all-points[position() > ($chunk - 1)*10000 and position() <= $chunk*10000] let $path := string-join( for $pt in $points return ( edge:map-command('goto','absolute')||" "|| point:px($pt)||" "||point:py($pt)|| edge:map-command('line','absolute')||" "|| (point:px($pt) + $width)||" "||(point:py($pt) + 0*$width) ), " " ) where exists($points) return ( <svg:path d="{$path}">{ if (exists($style-properties("stroke-width"))) then () else attribute stroke-width {$width} , if (exists($style-properties("stroke-linecap"))) then () else attribute stroke-linecap {"round"} , util:as-attributes($style-properties), $keyfn($key) }</svg:path> ) }; (:~ : to-CSS() : Render a property bundle as a CSS class style. : : @param $name: the style name : @param $properties: the properties :) declare function this:to-CSS( $name as xs:string, $properties as map(xs:string,item()*) ) { let $style-properties := this:svg-style($properties) return ( "."||$name||" { "|| string-join(( map:for-each($style-properties, function ($key as xs:string, $value as item()*) as xs:string { $key||": "||string-join($value!string(.)," ") } ) ),"; ")|| " } " ) }; (:~ : drawing() : Render a complete SVG. The components here should have already been : drawn (i.e. rendered to SVG) : : @param $canvas: defines size of canvas : @param $metadata: metadata elements : @param $defs: definitions : @param $styles: CSS styles : @param $content: body content :) declare function this:drawing( $canvas as map(xs:string,item()*), $metadata as element()*, $defs as element()*, $styles as xs:string*, $content as item()* ) { <svg:svg version="1.1" viewBox="0 0 {box:width($canvas)} {box:height($canvas)}" > <svg:metadata>{$metadata}</svg:metadata> <svg:defs>{$defs}</svg:defs> <svg:style type="text/css">{$styles}</svg:style> <svg:g>{$content}</svg:g> </svg:svg> }; declare function this:merge-attributes( $old as attribute(*)*, $new as attribute(*)* ) as attribute(*)* { let $names := $new!node-name(.) return ( $new, $old[not(node-name(.) = $names)] ) }; (:~ : as-properties() : Convert attributes to a series of properties, for rendering. : Note: no validity checking here; assumes we have pre-sanitized. :) declare function this:as-properties( $attributes as attribute(*)* ) as map(xs:string,item()*)? { fold-left($attributes, map {}, function ($map as map(xs:string,item()*), $attribute as attribute(*)) as map(xs:string,item()*) { $map=>map:put(name($attribute), data($attribute)) } ) }; (:~ : svg-style() : Mapping of art properties to SVG style properties :) declare function this:svg-style( $properties as map(xs:string,item()*) ) as map(xs:string,item()*) { let $properties := if (exists($properties("stroke"))) then ( $properties=>map:put("stroke", gradient:ref($properties("stroke"))) ) else $properties let $properties := if (exists($properties("fill"))) then ( $properties=>map:put("fill", gradient:ref($properties("fill"))) ) else $properties let $properties := if (exists($properties("colour"))) then ( if (empty($properties("fill"))) then ( $properties=>map:put("fill", gradient:ref($properties("colour"))) ) else $properties ) else $properties let $properties := if (exists($properties("colour"))) then ( if (empty($properties("stroke"))) then ( $properties=>map:put("stroke", gradient:ref($properties("colour"))) ) else $properties ) else $properties let $properties := if (exists($properties("filter"))) then ( $properties=>map:put("filter", effect:ref($properties("filter"))) ) else $properties let $properties := if (exists($properties("width"))) then ( $properties=>map:put("stroke-width", $properties("width")) ) else $properties return $properties=>map:remove("colour")=>map:remove("width") }; declare %private function this:dump-attr($k as xs:string, $algorithm as map(xs:string, item()*)) { if ($algorithm=>map:contains($k)) then ( let $v := $algorithm($k) return attribute {$k} { typeswitch ($v) case function(*)+ return $v!util:function-name(.) case xs:QName+ return $v!util:describe-qname(.) default return $v } ) else () }; declare %private function this:dump-elt($k as xs:string, $algorithm as map(xs:string,item()*)) { if ($algorithm=>map:contains($k)) then ( let $v := $algorithm($k) return element {$k} { typeswitch ($v) case function(*)+ return $v!util:function-name(.) case xs:QName+ return $v!util:describe-qname(.) default return $v } ) else () }; (:~ : dump-randomizer() : Dump the algorithm map as metadata suitable for SVG embedding. : distribution: Distribution to use, one of "constant", "uniform", "normal", : "skewed", "bernoulli", "flip", "zipf", "markov", "sums", "multimodal" : min: mimumum value (optional) : max: maximum value (optional) : pre-multiplier: multiplier before min/max (optional) : post-multipler: multiplier after min/max (optional) : cast: cast type (optional) : mean: mean of distribution (normal, skewed) (default=0) : std: standard deviation of distribution (normal, skewed) (default=mean) : skew: skew of distribution (skewed) (default=0) : p: probability (bernoulli, flip) (default=50) : sums: cumulative probability sums (zipf, markov) : alpha: alpha parameter (zipf) (needed if no sums) (default=0) : limit: number of sums (zipf) (needed if no sums) (default=1000) : start: index of starting symbol (markov) (default=uniform[1,dim]) : dim: size of each dimension of Markov matrix (markov) : matrix: raw Markov matrix (used if sums not provided, not recommended) : sums: cumulative probability sums (zipf, markov) : Single element, sequence of doubles : matrix: raw Markov matrix (used if sums not provided, not recommended) : Single element, sequence of doubles : distributions: component distributions (multimodal) :) declare function this:dump-randomizer($key as xs:string, $algorithm as map(xs:string, item()*)) { if (exists($algorithm("describe"))) then ( let $description := $algorithm("describe")($algorithm) return if (empty($description)) then () else ( <art:algorithm name="{$key}" distribution="{$algorithm('distribution')}"> { $description } </art:algorithm> ) ) else ( <art:algorithm name="{$key}" distribution="{$algorithm('distribution')}"> { this:dump-attr("min",$algorithm), this:dump-attr("max",$algorithm), this:dump-attr("pre-multiplier",$algorithm), this:dump-attr("post-multiplier",$algorithm), this:dump-attr("post-shift",$algorithm), this:dump-attr("cast",$algorithm), this:dump-attr("mean",$algorithm), this:dump-attr("std",$algorithm), this:dump-attr("skew",$algorithm), this:dump-attr("p",$algorithm), this:dump-attr("alpha",$algorithm), this:dump-attr("limit",$algorithm), this:dump-attr("dim",$algorithm), this:dump-attr("start",$algorithm), this:dump-elt("sums",$algorithm), this:dump-elt("keys",$algorithm), this:dump-elt("matrix",$algorithm), for $distribution at $i in $algorithm("distributions") return this:dump-randomizer(string($i), $distribution), if (exists($algorithm("selector"))) then ( this:dump-randomizer("selector", $algorithm("selector")) ) else () } </art:algorithm> ) }; declare function this:dump-randomizers($randomizers as map(xs:string, item()*)) { let $keys := $randomizers=>map:keys() let $prefixes := distinct-values( for $k in $keys return ( if (contains($k, ".")) then substring-before($k,".") else () ) ) return ( for $prefix in $prefixes order by $prefix ascending return ( element art:component { attribute name {$prefix}, let $pkeys := $keys[starts-with(.,$prefix||".")] for $k in $pkeys order by $k ascending return ( this:dump-randomizer($k, $randomizers($k)) ) } ) , for $k in $keys where not(contains($k,".")) order by $k ascending return ( this:dump-randomizer($k, $randomizers($k)) ) ) }; declare function this:dump-parameters($parameters as map(xs:string, item()*)) { this:dump-parameters($parameters, xs:QName("art:parameter"), ()) }; declare function this:dump-dynamic-parameters($parameters as map(xs:string, item()*), $i as xs:integer?) { this:dump-parameters($parameters, xs:QName("art:dynamic-parameter"), $i) }; declare function this:dump-dynamic-parameters($parameters as map(xs:string, item()*)) { this:dump-parameters($parameters, xs:QName("art:dynamic-parameter"), ()) }; declare function this:dump-parameters( $parameters as map(xs:string, item()*), $parameter-qname as xs:QName, $i as xs:integer? ) as item()* { let $keys := $parameters=>map:keys() let $prefixes := distinct-values( for $k in $keys return ( if (contains($k, ".")) then substring-before($k,".") else () ) ) return ( for $prefix in $prefixes order by $prefix ascending return ( element art:component { attribute name {$prefix}, let $pkeys := $keys[starts-with(.,$prefix||".")] for $k in $pkeys let $k2 := string-join(($k, $i),".") let $v := $parameters($k) order by $k ascending return ( if ($v instance of map(xs:string, item()*) and $v=>map:contains("distribution")) then ( this:dump-randomizer($k2, $v) ) else ( element {$parameter-qname} { attribute name {$k2}, for $vi in $v return this:dump-value($vi) } ) ) } ) , for $k in $keys let $v := $parameters($k) where not(contains($k,".")) and ($k ne "description") order by $k ascending return ( if ($v instance of map(xs:string, item()*) and $v=>map:contains("distribution")) then ( this:dump-randomizer($k, $v) ) else ( element art:parameter { attribute name {$k}, for $vi in $v return this:dump-value($vi) } ) ) ) }; declare function this:dump-value($value as item()?) as xs:string { typeswitch ($value) case empty-sequence() return "" case xs:QName return util:describe-qname($value) case map(*) return this:dump-map($value) case array(*) return this:dump-array($value) case function(*) return util:function-name($value) default return geom:quote($value) }; declare function this:dump-map($map as map(*)) as xs:string { if ($map("describe") instance of function(*)) then $map("describe")($map) else ( "{"|| string-join(( for $k in $map=>map:keys() let $v := $map($k) order by string($k) ascending return string($k)||": "|| (if (count($v) > 1) then "("||string-join(for $v1 in $v return this:dump-value($v1),",")||")" else this:dump-value($v)) ),", ") ||"}" ) }; declare function this:dump-array($array as array(*)) { "["|| string-join(( array:for-each($array, function($v as item()*) as xs:string {this:dump-value($v)}) ),", ") ||"]" };