From 887e03a86faa42090f6841a6b15a72c367397260 Mon Sep 17 00:00:00 2001 From: Andrew Sales Date: Fri, 4 Aug 2023 13:57:58 +0100 Subject: [PATCH 01/10] [MOD] towards #21 --- context.xqm | 44 ++++++++++++++++++++++++++++-------------- evaluate.xqm | 43 ++++++++++++++++++++++++++++++++++------- svrl.xqm | 13 +++++++++---- test/test-evaluate.xqm | 1 + utils.xqm | 12 ++++++++++++ 5 files changed, 88 insertions(+), 25 deletions(-) diff --git a/context.xqm b/context.xqm index 852aee1..108f836 100644 --- a/context.xqm +++ b/context.xqm @@ -19,11 +19,13 @@ declare variable $c:ALL_PATTERNS as xs:string := '#ALL'; : @param instance the document instance : @param schema the Schematron schema : @param phase the active phase + : @param options map of options :) declare function c:get-context( $instance as node(), $schema as element(sch:schema), - $phase as xs:string? + $phase as xs:string?, + $options as map(*)? ) as map(*) { @@ -39,11 +41,15 @@ as map(*) $instance, $namespaces, $schema/sch:ns, - map{} + map{}, + $options ) else map{} - return map{ + return + map:merge( + ($options, + map{ 'phase' : $active-phase, 'patterns' : $active-patterns, 'ns-decls' : $namespaces, @@ -52,7 +58,8 @@ as map(*) 'diagnostics' : $schema/sch:diagnostics/sch:diagnostic, 'properties' : $schema/sch:properties/sch:property, 'functions' : $schema/xqy:function - } + }) + ) }; (:NAMESPACE DECLARATIONS:) @@ -140,13 +147,15 @@ as element(sch:let)* : @param instance the document instance : @param namespaces namespace declarations : @param bindings global variable bindings + : @param options map of options :) declare function c:evaluate-global-variables( $variables as element(sch:let)*, $instance as node(), $namespaces as xs:string?, $ns-elems as element(sch:ns)*, - $bindings as map(*) + $bindings as map(*), + $options as map(*)? ) as map(*) { @@ -167,7 +176,8 @@ as map(*) $instance, $prolog || '$' || $var/@name, $ns-elems, - $bindings + $bindings, + $options ) (: let $_ := trace('[5]$bindings='||serialize($binding, map{'method':'adaptive'})) :) @@ -177,7 +187,8 @@ as map(*) $instance, $namespaces, $ns-elems, - $binding + $binding, + $options ) }; @@ -189,13 +200,15 @@ as map(*) : @param variables pattern variables : @param instance the document instance : @param prolog global variable and namespace declarations + : @param options map of options :) declare function c:evaluate-root-context-variables( $variables as element(sch:let)*, $instance as node()+, $namespaces as xs:string?, $ns-elems as element(sch:ns)*, - $bindings as map(*) + $bindings as map(*), + $options as map(*)? ) as map(*) { @@ -215,7 +228,8 @@ as map(*) $instance, $prolog || utils:local-variable-decls($var) || ' return $' || $var/@name, $ns-elems, - $bindings + $bindings, + $options ) (: let $_ := trace('[5]$bindings='||serialize($binding, map{'method':'adaptive'})) :) @@ -225,7 +239,8 @@ as map(*) $instance, $namespaces, $ns-elems, - $binding + $binding, + $options ) }; @@ -242,16 +257,17 @@ declare function c:evaluate-global-variable( $instance as node(), $query as xs:string?, $ns-elems as element(sch:ns)*, - $bindings as map(*) + $bindings as map(*), + $options as map(*)? ) as map(*) { (: let $_ := trace('>>>QUERY='||$query) :) let $value as item()* := if($variable/@value) - then xquery:eval( + then utils:eval( $query => utils:escape(), map:merge(($bindings, map{'':$instance})), - map{'pass':'true'} + map:merge($options, map{'pass':'true'}) ) else $variable/* let $bindings := map:merge( @@ -283,7 +299,7 @@ as map(*) { if($documents) then - let $uris := xquery:eval( + let $uris := utils:eval( utils:make-query-prolog($context) || $documents => utils:escape(), map:merge(($context?globals, map{'':$context?instance})), map{'pass':'true'} (:report exception details:) diff --git a/evaluate.xqm b/evaluate.xqm index 0ade267..888b548 100644 --- a/evaluate.xqm +++ b/evaluate.xqm @@ -14,6 +14,33 @@ import module namespace utils = 'http://www.andrewsales.com/ns/xqs-utils' at declare namespace sch = "http://purl.oclc.org/dsdl/schematron"; declare namespace svrl = "http://purl.oclc.org/dsdl/svrl"; +(:~ Evaluates the schema to produce SVRL output. + : @param instance the document instance + : @param schema the Schematron schema + : @param phase the active phase + : @param options map of options + :) +declare function eval:schema( + $instance as node(), + $schema as element(sch:schema), + $phase as xs:string?, + $options as map(*)? +) +{ + if($options?dry-run eq 'true') + then + + {output:schema-title($schema/sch:title)} + {$schema/@schemaVersion} + {output:namespace-decls-as-svrl($schema/sch:ns)} + {for $phase in ($schema/sch:phase/@id/data(), '') + let $context as map(*) := context:get-context($instance, $schema, $phase, $options) + return eval:phase($context)} + + else + eval:schema($instance, $schema, $phase) +}; + (:~ Evaluates the schema to produce SVRL output. : @param instance the document instance : @param schema the Schematron schema @@ -25,7 +52,7 @@ declare function eval:schema( $phase as xs:string? ) { - let $context as map(*) := context:get-context($instance, $schema, $phase) + let $context as map(*) := context:get-context($instance, $schema, $phase, map{}) return @@ -57,7 +84,8 @@ declare function eval:pattern( $context?instance, $context?ns-decls, $pattern/../sch:ns, - $context?globals + $context?globals, + map{'dry-run':$context?dry-run} ) (: let $_ := trace('PATTERN $globals='||serialize($globals, map{'method':'adaptive'})) :) let $context := map:put($context, 'globals', $globals) @@ -122,10 +150,10 @@ as element()* ' ' ) (: let $_ := trace('[2]RULE query='||$query) :) - let $rule-context := xquery:eval( + let $rule-context := utils:eval( $query => utils:escape(), map:merge((map{'':$context?instance}, $context?globals)), - map{'pass':'true'} (:report exception details:) + map{'pass':'true', 'dry-run':$context?dry-run} ) return if($rule-context) @@ -181,10 +209,10 @@ declare function eval:assertion( $context as map(*) ) { - let $result := xquery:eval( + let $result := utils:eval( $prolog || $assertion/@test => utils:escape(), map:merge((map{'':$rule-context}, $context?globals)), - map{'pass':'true'} + map{'pass':'true', 'dry-run':$context?dry-run} ) return typeswitch($assertion) @@ -212,7 +240,8 @@ declare function eval:phase($context as map(*)) $context?instance, $context?ns-decls, $phase/../sch:ns, - $context?globals + $context?globals, + map{'dry-run':$context?dry-run} ) let $context := map:put($context, 'globals', $globals) diff --git a/svrl.xqm b/svrl.xqm index ce08e2a..335960f 100644 --- a/svrl.xqm +++ b/svrl.xqm @@ -3,6 +3,9 @@ :) module namespace output = 'http://www.andrewsales.com/ns/xqs-output'; +import module namespace utils = 'http://www.andrewsales.com/ns/xqs-utils' at + 'utils.xqm'; + declare namespace sch = "http://purl.oclc.org/dsdl/schematron"; declare namespace svrl = "http://purl.oclc.org/dsdl/svrl"; @@ -113,15 +116,17 @@ declare function output:assertion-message-content( typeswitch($node) case element(sch:name) return if($node/@path) - then xquery:eval( + then utils:eval( $prolog || $node/@path, - map:merge((map{'':$rule-context}, $context?globals)) + map:merge((map{'':$rule-context}, $context?globals)), + map{'pass':'true'} ) else name($rule-context) case element(sch:value-of) - return xquery:eval( + return utils:eval( $prolog || $node/@select, - map:merge((map{'':$rule-context}, $context?globals)) + map:merge((map{'':$rule-context}, $context?globals)), + map{'pass':'true'} ) => string() case element(sch:emph) diff --git a/test/test-evaluate.xqm b/test/test-evaluate.xqm index dc1b87e..631111d 100644 --- a/test/test-evaluate.xqm +++ b/test/test-evaluate.xqm @@ -814,6 +814,7 @@ declare %unit:test function _:global-variable-bindings() document{}, '', (), + map{}, map{} ) return ( diff --git a/utils.xqm b/utils.xqm index 907717b..e17486a 100644 --- a/utils.xqm +++ b/utils.xqm @@ -124,3 +124,15 @@ declare function utils:check-duplicate-variable-names($decls as element(sch:let) || $names[index-of($names, .)[2]] ) else() }; + +(:~ Wrapper around xquery:eval() :) +declare function utils:eval( + $query as xs:string, + $bindings as map(*), + $options as map(*) +) as item()* +{ + if($options?dry-run eq 'true') + then () + else xquery:eval($query, $bindings, map{'pass':'true'}) +}; \ No newline at end of file From 904e8f57e73599781b0d3f846d9984e8787e79c9 Mon Sep 17 00:00:00 2001 From: Andrew Sales Date: Sat, 5 Aug 2023 13:14:44 +0100 Subject: [PATCH 02/10] [MOD] progress on #21 --- context.xqm | 31 ++++++++++++++++++------------- evaluate.xqm | 10 +++++++--- svrl.xqm | 6 ++++-- test/test-compile.xqm | 1 + test/test-context.xqm | 12 ++++++++---- test/test-evaluate.xqm | 24 ++++++++++++++++++++++++ utils.xqm | 20 +++++++++++++++++--- 7 files changed, 79 insertions(+), 25 deletions(-) diff --git a/context.xqm b/context.xqm index 108f836..dcd1f3c 100644 --- a/context.xqm +++ b/context.xqm @@ -48,17 +48,19 @@ as map(*) return map:merge( - ($options, - map{ - 'phase' : $active-phase, - 'patterns' : $active-patterns, - 'ns-decls' : $namespaces, - 'globals' : $globals, - 'instance' : $instance, - 'diagnostics' : $schema/sch:diagnostics/sch:diagnostic, - 'properties' : $schema/sch:properties/sch:property, - 'functions' : $schema/xqy:function - }) + ( + $options, + map{ + 'phase' : $active-phase, + 'patterns' : $active-patterns, + 'ns-decls' : $namespaces, + 'globals' : $globals, + 'instance' : $instance, + 'diagnostics' : $schema/sch:diagnostics/sch:diagnostic, + 'properties' : $schema/sch:properties/sch:property, + 'functions' : $schema/xqy:function + } + ) ) }; @@ -251,6 +253,7 @@ as map(*) : @param query the query to evaluate : @param ns-elems namespace declarations : @param bindings map of global variable bindings + : @param options map of options :) declare function c:evaluate-global-variable( $variable as element(sch:let), @@ -267,7 +270,8 @@ as map(*) then utils:eval( $query => utils:escape(), map:merge(($bindings, map{'':$instance})), - map:merge($options, map{'pass':'true'}) + map:merge($options, map{'pass':'true'}), + $variable/@value ) else $variable/* let $bindings := map:merge( @@ -302,7 +306,8 @@ as map(*) let $uris := utils:eval( utils:make-query-prolog($context) || $documents => utils:escape(), map:merge(($context?globals, map{'':$context?instance})), - map{'pass':'true'} (:report exception details:) + map{'pass':'true'}, (:report exception details:) + $documents ) return map:put( $context, diff --git a/evaluate.xqm b/evaluate.xqm index 888b548..4362234 100644 --- a/evaluate.xqm +++ b/evaluate.xqm @@ -35,7 +35,9 @@ declare function eval:schema( {output:namespace-decls-as-svrl($schema/sch:ns)} {for $phase in ($schema/sch:phase/@id/data(), '') let $context as map(*) := context:get-context($instance, $schema, $phase, $options) - return eval:phase($context)} + return + ($context?globals?*[self::svrl:*[@err:*]], + eval:phase($context))} else eval:schema($instance, $schema, $phase) @@ -153,7 +155,8 @@ as element()* let $rule-context := utils:eval( $query => utils:escape(), map:merge((map{'':$context?instance}, $context?globals)), - map{'pass':'true', 'dry-run':$context?dry-run} + map{'pass':'true', 'dry-run':$context?dry-run}, + $rule/@context ) return if($rule-context) @@ -212,7 +215,8 @@ declare function eval:assertion( let $result := utils:eval( $prolog || $assertion/@test => utils:escape(), map:merge((map{'':$rule-context}, $context?globals)), - map{'pass':'true', 'dry-run':$context?dry-run} + map{'pass':'true', 'dry-run':$context?dry-run}, + $assertion/@test ) return typeswitch($assertion) diff --git a/svrl.xqm b/svrl.xqm index 335960f..7cb089c 100644 --- a/svrl.xqm +++ b/svrl.xqm @@ -119,14 +119,16 @@ declare function output:assertion-message-content( then utils:eval( $prolog || $node/@path, map:merge((map{'':$rule-context}, $context?globals)), - map{'pass':'true'} + map{'pass':'true'}, + $node/@path ) else name($rule-context) case element(sch:value-of) return utils:eval( $prolog || $node/@select, map:merge((map{'':$rule-context}, $context?globals)), - map{'pass':'true'} + map{'pass':'true'}, + $node/@select ) => string() case element(sch:emph) diff --git a/test/test-compile.xqm b/test/test-compile.xqm index af513b5..bf5e8b4 100644 --- a/test/test-compile.xqm +++ b/test/test-compile.xqm @@ -1328,6 +1328,7 @@ declare %unit:test function _:global-variable-bindings() document{}, '', (), + map{}, map{} ) return ( diff --git a/test/test-context.xqm b/test/test-context.xqm index 3f204d4..5a5c094 100644 --- a/test/test-context.xqm +++ b/test/test-context.xqm @@ -271,7 +271,8 @@ declare %unit:test function _:get-context-patterns() let $patterns := c:get-context( , $schema, - 'phase1' + 'phase1', + map{} )?patterns return unit:assert-equals( @@ -296,7 +297,8 @@ declare %unit:test function _:get-context-active-phase() let $phase := c:get-context( , $schema, - 'phase1' + 'phase1', + map{} )?phase return unit:assert-equals( @@ -322,7 +324,8 @@ declare %unit:test function _:get-context-namespaces() let $ns-decls := c:get-context( , $schema, - '' + '', + map{} )?ns-decls return unit:assert-equals( @@ -350,7 +353,8 @@ declare %unit:test function _:get-context-globals() let $globals := c:get-context( , $schema, - 'phase1' + 'phase1', + map{} )?globals return unit:assert-equals( diff --git a/test/test-evaluate.xqm b/test/test-evaluate.xqm index 631111d..e944aa3 100644 --- a/test/test-evaluate.xqm +++ b/test/test-evaluate.xqm @@ -1299,4 +1299,28 @@ declare %unit:test function _:map-rule-variable() unit:assert(empty($result/svrl:failed-assert)), unit:assert(empty($result/svrl:successful-report)) ) +}; + +declare %unit:test function _:global-variable-syntax-error() +{ + let $result := eval:schema( + document{}, + doc('global-variable-syntax-error.sch')/*, + '', + map{'dry-run':'true'} + ) + return ( + unit:assert-equals( + $result/svrl:failed-assert/@location/data(), + "/Q{http://purl.oclc.org/dsdl/schematron}schema[1]/Q{http://purl.oclc.org/dsdl/schematron}let[1]/@value" + ), + unit:assert-equals( + $result/svrl:failed-assert/@err:code/data(), + "err:XPST0003" + ), + unit:assert-equals( + $result/svrl:failed-assert/svrl:text, + No specifier after lookup operator: ';'. + ) + ) }; \ No newline at end of file diff --git a/utils.xqm b/utils.xqm index e17486a..3884637 100644 --- a/utils.xqm +++ b/utils.xqm @@ -125,14 +125,28 @@ declare function utils:check-duplicate-variable-names($decls as element(sch:let) ) else() }; -(:~ Wrapper around xquery:eval() :) +(:~ Wrapper around xquery:eval() + : @param $query string of the query to evaluate + : @param bindings map of bindings + : @param options map of options + : @param node the schema node being evaluated + :) declare function utils:eval( $query as xs:string, $bindings as map(*), - $options as map(*) + $options as map(*), + $node as node() ) as item()* { if($options?dry-run eq 'true') - then () + then + try{ + xquery:parse($query, map{'pass':'true'}) + } + catch * { + + {$err:description} + } else xquery:eval($query, $bindings, map{'pass':'true'}) }; \ No newline at end of file From 44fb3f8dc1cd1448297ed208211f48320448b45a Mon Sep 17 00:00:00 2001 From: Andrew Sales Date: Sat, 5 Aug 2023 13:23:33 +0100 Subject: [PATCH 03/10] [ADD] schema test case re #21 --- test/global-variable-syntax-error.sch | 8 ++++++++ utils.xqm | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 test/global-variable-syntax-error.sch diff --git a/test/global-variable-syntax-error.sch b/test/global-variable-syntax-error.sch new file mode 100644 index 0000000..3a93ad9 --- /dev/null +++ b/test/global-variable-syntax-error.sch @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/utils.xqm b/utils.xqm index 3884637..a7e99ff 100644 --- a/utils.xqm +++ b/utils.xqm @@ -125,7 +125,8 @@ declare function utils:check-duplicate-variable-names($decls as element(sch:let) ) else() }; -(:~ Wrapper around xquery:eval() +(:~ Wrapper around xquery:eval(). In "dry-run" mode, the query passed in is + : parsed only, and any errors caught reported as svrl:failed-assert. : @param $query string of the query to evaluate : @param bindings map of bindings : @param options map of options From d308eab19bf406faf12755a67a03c6f22b4fff33 Mon Sep 17 00:00:00 2001 From: Andrew Sales Date: Sat, 5 Aug 2023 14:17:07 +0100 Subject: [PATCH 04/10] [MOD] patterns will have global context, so report errors there --- evaluate.xqm | 38 +++++++++++++++++++++----------------- utils.xqm | 1 + 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/evaluate.xqm b/evaluate.xqm index 4362234..10cea42 100644 --- a/evaluate.xqm +++ b/evaluate.xqm @@ -35,9 +35,7 @@ declare function eval:schema( {output:namespace-decls-as-svrl($schema/sch:ns)} {for $phase in ($schema/sch:phase/@id/data(), '') let $context as map(*) := context:get-context($instance, $schema, $phase, $options) - return - ($context?globals?*[self::svrl:*[@err:*]], - eval:phase($context))} + return eval:phase($context)} else eval:schema($instance, $schema, $phase) @@ -97,15 +95,19 @@ declare function eval:pattern( (: let $_ := trace('PATTERN '||$pattern/@id||' prolog='||$prolog) :) (: let $_ := trace('PATTERN $bindings '||serialize($context?globals, map{'method':'adaptive'})) :) - return (:TODO active-pattern/@name:)( - - {$pattern/(@id, @name, @role), - if($pattern/@documents) then attribute{'documents'}{$context?instance ! base-uri(.)} else()} - , - $context?instance ! eval:rules( - $pattern/sch:rule, - utils:make-query-prolog($context), - map:put($context, 'instance', .) + return ( + if($context?dry-run eq 'true') + then $context?globals?*[svrl:*] + else ( + + {$pattern/(@id, @name, @role), + if($pattern/@documents) then attribute{'documents'}{$context?instance ! base-uri(.)} else()} + , + $context?instance ! eval:rules( + $pattern/sch:rule, + utils:make-query-prolog($context), + map:put($context, 'instance', .) + ) ) ) }; @@ -155,7 +157,7 @@ as element()* let $rule-context := utils:eval( $query => utils:escape(), map:merge((map{'':$context?instance}, $context?globals)), - map{'pass':'true', 'dry-run':$context?dry-run}, + map{'dry-run':$context?dry-run}, $rule/@context ) return @@ -215,7 +217,7 @@ declare function eval:assertion( let $result := utils:eval( $prolog || $assertion/@test => utils:escape(), map:merge((map{'':$rule-context}, $context?globals)), - map{'pass':'true', 'dry-run':$context?dry-run}, + map{'dry-run':$context?dry-run}, $assertion/@test ) return @@ -238,6 +240,8 @@ declare function eval:phase($context as map(*)) let $phase := $context?phase let $_ := utils:check-duplicate-variable-names($phase/sch:let) + let $dry-run as map(*) := map{'dry-run':$context?dry-run} + (:add phase variables to context:) let $globals as map(*) := context:evaluate-root-context-variables( $phase/sch:let, @@ -245,9 +249,9 @@ declare function eval:phase($context as map(*)) $context?ns-decls, $phase/../sch:ns, $context?globals, - map{'dry-run':$context?dry-run} + $dry-run ) let $context := map:put($context, 'globals', $globals) - - return $context?patterns ! eval:pattern(., $context) + + return $context?patterns ! eval:pattern(., map:merge(($context, $dry-run))) }; \ No newline at end of file diff --git a/utils.xqm b/utils.xqm index a7e99ff..d87ccf6 100644 --- a/utils.xqm +++ b/utils.xqm @@ -145,6 +145,7 @@ declare function utils:eval( xquery:parse($query, map{'pass':'true'}) } catch * { + , {$err:description} From 907194ec9b276b6e8e66cc442b21d9788019519e Mon Sep 17 00:00:00 2001 From: Andrew Sales Date: Sun, 6 Aug 2023 12:10:00 +0100 Subject: [PATCH 05/10] [MOD] pseudo-pattern for syntax checking; fired-rule for every check --- evaluate.xqm | 49 ++++++++++++++++++++++++++++++++++++------ test/test-evaluate.xqm | 2 +- utils.xqm | 6 +++--- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/evaluate.xqm b/evaluate.xqm index 10cea42..7e5a32b 100644 --- a/evaluate.xqm +++ b/evaluate.xqm @@ -33,6 +33,7 @@ declare function eval:schema( {output:schema-title($schema/sch:title)} {$schema/@schemaVersion} {output:namespace-decls-as-svrl($schema/sch:ns)} + {for $phase in ($schema/sch:phase/@id/data(), '') let $context as map(*) := context:get-context($instance, $schema, $phase, $options) return eval:phase($context)} @@ -95,23 +96,53 @@ declare function eval:pattern( (: let $_ := trace('PATTERN '||$pattern/@id||' prolog='||$prolog) :) (: let $_ := trace('PATTERN $bindings '||serialize($context?globals, map{'method':'adaptive'})) :) + let $rules := $pattern/sch:rule return ( if($context?dry-run eq 'true') - then $context?globals?*[svrl:*] + then + ($context?globals?*[self::svrl:*], eval:all-rules($rules, $context)) else ( {$pattern/(@id, @name, @role), if($pattern/@documents) then attribute{'documents'}{$context?instance ! base-uri(.)} else()} , - $context?instance ! eval:rules( - $pattern/sch:rule, - utils:make-query-prolog($context), - map:put($context, 'instance', .) - ) + eval:rules($rules, $context) ) ) }; +declare function eval:all-rules( + $rules as element(sch:rule)*, + $context as map(*) +) +as element()* +{ + $context?instance + ! + (for $rule in $rules + return + eval:rule( + $rule, + utils:make-query-prolog($context), + map:put($context, 'instance', .) + )) +}; + +declare function eval:rules( + $rules as element(sch:rule)*, + $context as map(*) +) +as element()* +{ + $context?instance + ! + eval:rules( + $rules, + utils:make-query-prolog($context), + map:put($context, 'instance', .) + ) +}; + (:~ Evaluate rules, stopping once one fires. : (Necessitated by ISO2020 6.5.) : @param rules the rules to evaluate @@ -162,7 +193,11 @@ as element()* ) return if($rule-context) - then( + then + if($context?dry-run eq 'true') + then $rule-context[self::svrl:*] + else + ( {$rule/(@id, @name, @context, @role, @flag), if($rule/../@documents) then attribute{'document'}{$context?instance/base-uri()} else ()} diff --git a/test/test-evaluate.xqm b/test/test-evaluate.xqm index e944aa3..c1c87e7 100644 --- a/test/test-evaluate.xqm +++ b/test/test-evaluate.xqm @@ -1320,7 +1320,7 @@ declare %unit:test function _:global-variable-syntax-error() ), unit:assert-equals( $result/svrl:failed-assert/svrl:text, - No specifier after lookup operator: ';'. + No specifier after lookup operator: ';'. @value='?' ) ) }; \ No newline at end of file diff --git a/utils.xqm b/utils.xqm index d87ccf6..16a7dc9 100644 --- a/utils.xqm +++ b/utils.xqm @@ -141,14 +141,14 @@ declare function utils:eval( { if($options?dry-run eq 'true') then + (, try{ xquery:parse($query, map{'pass':'true'}) } catch * { - , - {$err:description} - } + {$err:description} {' @'||$node/name()}='{$node/data()}' + }) else xquery:eval($query, $bindings, map{'pass':'true'}) }; \ No newline at end of file From b16c96cb522eff2b84811445b75ee89941fea43c Mon Sep 17 00:00:00 2001 From: Andrew Sales Date: Sun, 6 Aug 2023 14:44:34 +0100 Subject: [PATCH 06/10] [MOD] assertions in dry-run mode (WIP) --- evaluate.xqm | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/evaluate.xqm b/evaluate.xqm index 7e5a32b..c21913d 100644 --- a/evaluate.xqm +++ b/evaluate.xqm @@ -33,7 +33,7 @@ declare function eval:schema( {output:schema-title($schema/sch:title)} {$schema/@schemaVersion} {output:namespace-decls-as-svrl($schema/sch:ns)} - + {for $phase in ($schema/sch:phase/@id/data(), '') let $context as map(*) := context:get-context($instance, $schema, $phase, $options) return eval:phase($context)} @@ -111,6 +111,12 @@ declare function eval:pattern( ) }; +(:~ Evaluates all the rules in a pattern. + : Initially added for use in dry-run mode, to check for syntax errors. + : N.B. we don't need to map the instance each time for this purpose, since we + : are not evaluating @documents, but this approach could be used for + : evaluating sch:rule-set (see https://github.com/AndrewSales/XQS/tree/%234). + :) declare function eval:all-rules( $rules as element(sch:rule)*, $context as map(*) @@ -195,15 +201,19 @@ as element()* if($rule-context) then if($context?dry-run eq 'true') - then $rule-context[self::svrl:*] + then + ( + $rule-context[self::svrl:*], + eval:assertions($rule, $prolog, <_/>, $context) (:pass dummy context node:) + ) else - ( - - {$rule/(@id, @name, @context, @role, @flag), - if($rule/../@documents) then attribute{'document'}{$context?instance/base-uri()} else ()} - , - eval:assertions($rule, $prolog, $rule-context, $context) - ) + ( + + {$rule/(@id, @name, @context, @role, @flag), + if($rule/../@documents) then attribute{'document'}{$context?instance/base-uri()} else ()} + , + eval:assertions($rule, $prolog, $rule-context, $context) + ) else () }; @@ -256,6 +266,13 @@ declare function eval:assertion( $assertion/@test ) return + if($context?dry-run eq 'true') + then + ( + $result[self::svrl:*], + output:assertion-message($assertion, $prolog, $rule-context, $context) + ) + else typeswitch($assertion) case element(sch:assert) return if($result) then () From f48b81c26e6c6cb03226d1d4f89665ba73243b94 Mon Sep 17 00:00:00 2001 From: Andrew Sales Date: Mon, 7 Aug 2023 17:15:02 +0100 Subject: [PATCH 07/10] [MOD] name/@path; value-of/@select, re #21 --- svrl.xqm | 52 ++++++++++++++++++++++++++++++++++++++++------------ utils.xqm | 2 +- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/svrl.xqm b/svrl.xqm index 7cb089c..f0680dd 100644 --- a/svrl.xqm +++ b/svrl.xqm @@ -33,6 +33,14 @@ declare function output:assertion-message( $context as map(*) ) { + if($context?dry-run eq 'true') + then output:assertion-message-content( + $assertion/node(), + $prolog, + $rule-context, + $context + ) + else element{ QName("http://purl.oclc.org/dsdl/svrl", if($assertion/self::sch:assert) then 'failed-assert' else 'successful-report')} @@ -109,27 +117,32 @@ declare function output:assertion-message-content( $rule-context as node(), $context as map(*) ) +as item()* { + if($context?dry-run eq 'true') + then + for $node in $content/(self::sch:name|self::sch:value-of) + return + typeswitch($node) + case element(sch:name) + return if($node/@path) + then output:name-value-of($node/@path, $prolog, $rule-context, $context) + else name($rule-context) + case element(sch:value-of) + return output:name-value-of($node/@select, $prolog, $rule-context, $context) + default return () + else {(:TODO attributes:) for $node in $content return typeswitch($node) case element(sch:name) return if($node/@path) - then utils:eval( - $prolog || $node/@path, - map:merge((map{'':$rule-context}, $context?globals)), - map{'pass':'true'}, - $node/@path - ) + then output:name-value-of($node/@path, $prolog, $rule-context, $context) + => string() else name($rule-context) case element(sch:value-of) - return utils:eval( - $prolog || $node/@select, - map:merge((map{'':$rule-context}, $context?globals)), - map{'pass':'true'}, - $node/@select - ) + return output:name-value-of($node/@select, $prolog, $rule-context, $context) => string() case element(sch:emph) return output:assertion-child-elements($node) @@ -139,4 +152,19 @@ declare function output:assertion-message-content( return output:assertion-child-elements($node) default return $node } +}; + +declare function output:name-value-of( + $attr as attribute(), + $prolog as xs:string?, + $rule-context as node(), + $context as map(*) +) +{ + utils:eval( + $prolog || $attr, + map:merge((map{'':$rule-context}, $context?globals)), + map{'dry-run':$context?dry-run}, + $attr + ) }; \ No newline at end of file diff --git a/utils.xqm b/utils.xqm index 16a7dc9..45815e1 100644 --- a/utils.xqm +++ b/utils.xqm @@ -148,7 +148,7 @@ declare function utils:eval( catch * { - {$err:description} {' @'||$node/name()}='{$node/data()}' + {$err:description}{' @'||$node/name()}='{$node/data()}' }) else xquery:eval($query, $bindings, map{'pass':'true'}) }; \ No newline at end of file From 1695398495c0fd8fdd6e18c5882b82851ac172cd Mon Sep 17 00:00:00 2001 From: Andrew Sales Date: Mon, 7 Aug 2023 18:30:39 +0100 Subject: [PATCH 08/10] [MOD] unit tests re #21 --- svrl.xqm | 4 +- test/test-evaluate.xqm | 188 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) diff --git a/svrl.xqm b/svrl.xqm index f0680dd..e80912c 100644 --- a/svrl.xqm +++ b/svrl.xqm @@ -127,9 +127,11 @@ as item()* case element(sch:name) return if($node/@path) then output:name-value-of($node/@path, $prolog, $rule-context, $context) - else name($rule-context) + [self::svrl:*] + else () case element(sch:value-of) return output:name-value-of($node/@select, $prolog, $rule-context, $context) + [self::svrl:*] default return () else {(:TODO attributes:) diff --git a/test/test-evaluate.xqm b/test/test-evaluate.xqm index c1c87e7..cf6796d 100644 --- a/test/test-evaluate.xqm +++ b/test/test-evaluate.xqm @@ -1323,4 +1323,192 @@ declare %unit:test function _:global-variable-syntax-error() No specifier after lookup operator: ';'. @value='?' ) ) +}; + +declare %unit:test function _:pattern-variable-syntax-error() +{ + let $result := + eval:schema( + document{}, + + + + + + + + , + '', + map{'dry-run':'true'} + ) + return + ( + unit:assert-equals( + $result/svrl:failed-assert[ends-with(@location ,'/Q{http://purl.oclc.org/dsdl/schematron}pattern[1]/Q{http://purl.oclc.org/dsdl/schematron}let[1]/@value')] + /svrl:text, + Incomplete FLWOR expression, expecting 'return'. @value='$' + ) + ) +}; + +declare %unit:test function _:rule-context-syntax-error() +{ + let $result := + eval:schema( + document{}, + + + + + + + , + '', + map{'dry-run':'true'} + ) + return + ( + unit:assert-equals( + $result/svrl:failed-assert/svrl:text, + Expecting expression. @context='' + ) + ) +}; + +declare %unit:test function _:dry-run-all-rules-processed() +{ + let $result := + eval:schema( + document{}, + + + + + + + , + '', + map{'dry-run':'true'} + ) + return + ( + unit:assert-equals( + count($result/svrl:fired-rule), + 2 + ) + ) +}; + +declare %unit:ignore function _:rule-variable-syntax-error() +{ + let $result := + eval:schema( + document{}, + + + + + + + + , + '', + map{'dry-run':'true'} + ) + return + ( + unit:assert-equals( + count($result/svrl:failed-assert[ends-with(@location, '/Q{http://purl.oclc.org/dsdl/schematron}pattern[1]/Q{http://purl.oclc.org/dsdl/schematron}rule[1]/@context')]), + 1 + ), + unit:assert-equals( + $result/svrl:failed-assert[ends-with(@location, '/Q{http://purl.oclc.org/dsdl/schematron}pattern[1]/Q{http://purl.oclc.org/dsdl/schematron}rule[1]/@context')] + /svrl:text, + TODO + ) + ) +}; + +declare %unit:test function _:report-test-syntax-error() +{ + let $result := + eval:schema( + document{}, + + + + + + + , + '', + map{'dry-run':'true'} + ) + return + ( + unit:assert-equals( + count($result/svrl:failed-assert[ends-with(@location, '/Q{http://purl.oclc.org/dsdl/schematron}pattern[1]/Q{http://purl.oclc.org/dsdl/schematron}rule[1]/Q{http://purl.oclc.org/dsdl/schematron}report[1]/@test')]), + 1 + ), + unit:assert-equals( + $result/svrl:failed-assert/svrl:text, + Namespace prefix not declared: ns. @test='ns:*' + ) + ) +}; + +declare %unit:test function _:name-path-syntax-error() +{ + let $result := + eval:schema( + document{}, + + + + + + + , + '', + map{'dry-run':'true'} + ) + return + ( + unit:assert-equals( + count($result/svrl:failed-assert[ends-with(@location, '/Q{http://purl.oclc.org/dsdl/schematron}pattern[1]/Q{http://purl.oclc.org/dsdl/schematron}rule[1]/Q{http://purl.oclc.org/dsdl/schematron}report[1]/Q{http://purl.oclc.org/dsdl/schematron}name[1]/@path')]), + 1 + ), + unit:assert-equals( + $result/svrl:failed-assert/svrl:text, + Unexpected end of query: '.'. @path='...' + ) + ) +}; + +declare %unit:test function _:value-of-select-syntax-error() +{ + let $result := + eval:schema( + document{}, + + + + + + + , + '', + map{'dry-run':'true'} + ) + return + ( + unit:assert-equals( + count($result/svrl:failed-assert[ends-with(@location, '/Q{http://purl.oclc.org/dsdl/schematron}pattern[1]/Q{http://purl.oclc.org/dsdl/schematron}rule[1]/Q{http://purl.oclc.org/dsdl/schematron}report[1]/Q{http://purl.oclc.org/dsdl/schematron}value-of[1]/@select')]), + 1 + ), + unit:assert-equals( + $result/svrl:failed-assert/svrl:text, + Unexpected end of query: '.'. @select='...' + ) + ) }; \ No newline at end of file From 4e78ed77b5fae2b43fb3f5d34a11eb908c97e341 Mon Sep 17 00:00:00 2001 From: Andrew Sales Date: Tue, 8 Aug 2023 20:25:44 +0100 Subject: [PATCH 09/10] [MOD] better reporting for rule/let, re #21 --- evaluate.xqm | 8 ++++++++ test/test-evaluate.xqm | 19 +++++++++++++------ utils.xqm | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/evaluate.xqm b/evaluate.xqm index c21913d..600ee16 100644 --- a/evaluate.xqm +++ b/evaluate.xqm @@ -185,6 +185,13 @@ declare function eval:rule( as element()* { let $_ := utils:check-duplicate-variable-names($rule/sch:let) + let $variable-errors := utils:evaluate-rule-variables( + $rule/sch:let, + $prolog, + map:merge((map{'':$context?instance}, $context?globals)), + $context, + () + ) let $query := string-join( ($prolog, utils:local-variable-decls($rule/sch:let), if($rule/sch:let) then 'return ' else '', $rule/@context), @@ -203,6 +210,7 @@ as element()* if($context?dry-run eq 'true') then ( + $variable-errors[self::svrl:*], $rule-context[self::svrl:*], eval:assertions($rule, $prolog, <_/>, $context) (:pass dummy context node:) ) diff --git a/test/test-evaluate.xqm b/test/test-evaluate.xqm index cf6796d..00d4ef5 100644 --- a/test/test-evaluate.xqm +++ b/test/test-evaluate.xqm @@ -1399,7 +1399,7 @@ declare %unit:test function _:dry-run-all-rules-processed() ) }; -declare %unit:ignore function _:rule-variable-syntax-error() +declare %unit:test function _:rule-variable-syntax-error() { let $result := eval:schema( @@ -1408,6 +1408,7 @@ declare %unit:ignore function _:rule-variable-syntax-error() + @@ -1418,13 +1419,12 @@ declare %unit:ignore function _:rule-variable-syntax-error() return ( unit:assert-equals( - count($result/svrl:failed-assert[ends-with(@location, '/Q{http://purl.oclc.org/dsdl/schematron}pattern[1]/Q{http://purl.oclc.org/dsdl/schematron}rule[1]/@context')]), + count($result/svrl:failed-assert[ends-with(@location, '/Q{http://purl.oclc.org/dsdl/schematron}pattern[1]/Q{http://purl.oclc.org/dsdl/schematron}rule[1]/Q{http://purl.oclc.org/dsdl/schematron}let[1]/@value')]), 1 ), unit:assert-equals( - $result/svrl:failed-assert[ends-with(@location, '/Q{http://purl.oclc.org/dsdl/schematron}pattern[1]/Q{http://purl.oclc.org/dsdl/schematron}rule[1]/@context')] - /svrl:text, - TODO + $result/svrl:failed-assert[ends-with(@location, '/Q{http://purl.oclc.org/dsdl/schematron}pattern[1]/Q{http://purl.oclc.org/dsdl/schematron}rule[1]/Q{http://purl.oclc.org/dsdl/schematron}let[1]/@value')]/svrl:text, + Incomplete FLWOR expression, expecting 'return'. @value='' ) ) }; @@ -1511,4 +1511,11 @@ declare %unit:test function _:value-of-select-syntax-error() Unexpected end of query: '.'. @select='...' ) ) -}; \ No newline at end of file +}; + +(:TODO +pattern/@documents +diagnostics +properties +rule/let +:) \ No newline at end of file diff --git a/utils.xqm b/utils.xqm index 45815e1..05b4db6 100644 --- a/utils.xqm +++ b/utils.xqm @@ -125,6 +125,42 @@ declare function utils:check-duplicate-variable-names($decls as element(sch:let) ) else() }; +(:~ In dry-run mode only, evaluate rule variables. + : Provides more localized information if syntax errors are present in rule + : variable declarations. + :) +declare function utils:evaluate-rule-variables( + $variables as element(sch:let)*, + $prolog as xs:string?, + $bindings as map(*), + $context as map(*), + $errors as element()* +) +as element()* +{ + if($context?dry-run eq 'true') + then + if(exists($variables)) + then + let $var := head($variables) + let $prolog := $prolog || utils:local-variable-decls($var) + let $errs := utils:eval( + $prolog || ' return $' || $var/@name => utils:escape(), + $bindings, + map{'dry-run':$context?dry-run}, + $var/@value + ) + return utils:evaluate-rule-variables( + tail($variables), + $prolog, + $bindings, + $context, + ($errors,$errs) + ) + else $errors + else () +}; + (:~ Wrapper around xquery:eval(). In "dry-run" mode, the query passed in is : parsed only, and any errors caught reported as svrl:failed-assert. : @param $query string of the query to evaluate From 7b8766ac4c868f93e89f544a674abd9b6be17f8c Mon Sep 17 00:00:00 2001 From: Andrew Sales Date: Tue, 9 Jan 2024 13:40:47 +0000 Subject: [PATCH 10/10] [MOD] parse user-defined functions, re #21 - test failing and ignored prior to merge into main --- evaluate.xqm | 9 ++++--- test/test-evaluate.xqm | 60 +++++++++++++++++++++++++++++++++++++++++- utils.xqm | 19 +++++++++++++ 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/evaluate.xqm b/evaluate.xqm index 600ee16..087bea7 100644 --- a/evaluate.xqm +++ b/evaluate.xqm @@ -11,14 +11,16 @@ import module namespace output = 'http://www.andrewsales.com/ns/xqs-output' at import module namespace utils = 'http://www.andrewsales.com/ns/xqs-utils' at 'utils.xqm'; +declare namespace xqy = 'http://www.w3.org/2012/xquery'; declare namespace sch = "http://purl.oclc.org/dsdl/schematron"; declare namespace svrl = "http://purl.oclc.org/dsdl/svrl"; -(:~ Evaluates the schema to produce SVRL output. +(:~ Evaluates the schema to produce SVRL output, applying the processing options + : specified. : @param instance the document instance : @param schema the Schematron schema : @param phase the active phase - : @param options map of options + : @param options map of processing options :) declare function eval:schema( $instance as node(), @@ -34,7 +36,8 @@ declare function eval:schema( {$schema/@schemaVersion} {output:namespace-decls-as-svrl($schema/sch:ns)} - {for $phase in ($schema/sch:phase/@id/data(), '') + {$schema/xqy:function ! utils:parse-function(., $options)[self::svrl:*]} + {for $phase in ($schema/sch:phase/@id, '') let $context as map(*) := context:get-context($instance, $schema, $phase, $options) return eval:phase($context)} diff --git a/test/test-evaluate.xqm b/test/test-evaluate.xqm index 00d4ef5..12e20fb 100644 --- a/test/test-evaluate.xqm +++ b/test/test-evaluate.xqm @@ -6,6 +6,7 @@ module namespace _ = 'http://www.andrewsales.com/ns/xqs-evaluation-tests'; declare namespace sch = "http://purl.oclc.org/dsdl/schematron"; declare namespace svrl = "http://purl.oclc.org/dsdl/svrl"; +declare namespace xqy = 'http://www.w3.org/2012/xquery'; import module namespace eval = 'http://www.andrewsales.com/ns/xqs-evaluate' at '../evaluate.xqm'; @@ -1513,9 +1514,66 @@ declare %unit:test function _:value-of-select-syntax-error() ) }; +declare %unit:test function _:phase-variable-scope-error() +{ + let $result := + eval:schema( + document{}, + + + + + + + + + + + + + + , + 'two', + map{'dry-run':'true'} + ) + return + ( + unit:assert-equals( + ($result/svrl:failed-assert)[1]/svrl:text, + Undeclared variable: $foo. @select='$foo' + ) + ) +}; + +(:TODO:) +declare %unit:ignore function _:function-syntax-error() +{ + let $result := + eval:schema( + document{}, + + + + + + + + , + '', + map{'dry-run':'true'} + ) + return + ( + unit:assert-equals( + ($result/svrl:failed-assert)[1]/svrl:text, + Calculation is incomplete. xqy:function='declare function local:foo(){{**}};' + ) + ) +}; + (:TODO pattern/@documents diagnostics properties -rule/let +functions :) \ No newline at end of file diff --git a/utils.xqm b/utils.xqm index 05b4db6..c82aa7a 100644 --- a/utils.xqm +++ b/utils.xqm @@ -6,6 +6,7 @@ declare namespace xqs = 'http://www.andrewsales.com/ns/xqs'; declare namespace sch = "http://purl.oclc.org/dsdl/schematron"; declare namespace svrl = "http://purl.oclc.org/dsdl/svrl"; declare namespace map = "http://www.w3.org/2005/xpath-functions/map"; +declare namespace xqy = 'http://www.w3.org/2012/xquery'; (:~ Builds the string of variable declarations in the prolog, for initial : evaluation. @@ -187,4 +188,22 @@ declare function utils:eval( {$err:description}{' @'||$node/name()}='{$node/data()}' }) else xquery:eval($query, $bindings, map{'pass':'true'}) +}; + +declare function utils:parse-function( + $node as element(xqy:function), + $options as map(*) +) +as element()+ +{ + , + try{ + xquery:parse($node || 0, map{'pass':'true'}) + } + catch * { + + {$err:description}{' '||$node/name()}='{$node/data()}' + + } }; \ No newline at end of file