Allow multiple collections in one configuration

xquery version "3.0";

(:~
 : XTriples
 :
 : A generic webservice to extract rdf statements from XML resources
 :
 : @author Torsten Schrade
 : @email <Torsten.Schrade@adwmainz.de>
 : @version 1.2.0
 : @licence MIT
 :
 : Main module containing the webservice
:)

(: ### PROLOGUE ### :)

import module namespace functx="http://www.functx.com";

declare namespace xtriples = "http://xtriples.spatialhumanities.de/";

(: ### GLOBAL VARIABLES :)

declare variable $setConfiguration := xtriples:getConfiguration();
declare variable $setFormat := xtriples:getFormat();
declare variable $xtriplesWebserviceURL := "http://xtriples.spatialhumanities.de/";
declare variable $any23WebserviceURL := "https://any23.apache.org/";
declare variable $redeferWebserviceURL := "http://rhizomik.net/redefer-services/";
declare variable $redeferWebserviceRulesURL := "http://rhizomik.net:8080/html/redefer/rdf2svg/showgraph.jrule";

(: ### CONFIGURATION FUNCTIONS ### :)

(: retrieves the XTriples configuration from GET or POST :)
declare function xtriples:getConfiguration() {

	(: checks GET/POST for a configuration sent with the configuration parameter :)
	let $submittedConfiguration := request:get-parameter("configuration", "")
	(: checks the request body for a configuration :)
	let $submittedConfigurationPOSTBody := request:get-data()

	let $setConfiguration :=
		(: case 1 - URI to a configuration file was sent - GET request :)
		if (substring($submittedConfiguration, 1, 4) = "http") then
			fn:doc(xs:anyURI($submittedConfiguration))
		(: case 2 - nothing submitted with a configuration parameter, use request body - direct POST request :)
		else if ($submittedConfiguration = "") then
			if ($submittedConfigurationPOSTBody) then
				$submittedConfigurationPOSTBody
			(: if no configuration could be retrieved to this point use a standard config that issues an error :)
			else
				fn:doc("configuration.xml")
		(: case 3 - full configuration sent with the configuration parameter - form style POST request :)
		else util:parse($submittedConfiguration)

	return $setConfiguration
};

(: gets and sets the return format :)
declare function xtriples:getFormat() {

	let $HEADER := request:get-header("format")
	let $GET := request:get-parameter("format", "rdf")
	let $setFormat :=
		if ($HEADER != "") then $HEADER
		else $GET

	return $setFormat
};

(: ### CRAWLING FUNCTIONS ### :)

(: evaluates an XPATH expression in curly braces within a (URI) string and returns the string :)
declare function xtriples:expressionBasedUriResolver($uri as xs:string, $currentResource as node()) as xs:string {

	let $expression := concat('$currentResource', substring-after(substring-before($uri, "}"), "{"))
	let $result := if (xtriples:expressionSanityCheck($expression) = true()) then
		try { util:eval($expression) } catch * { $err:description }
		else ""
	let $uriWithSubstitution := replace($uri, "\{.*\}", $result)

	return $uriWithSubstitution
};

(: evaluates a possible XPATH expression in curly braces within a document string and retrieves the document :)
declare function xtriples:expressionBasedResourceResolver($collection as node()*, $resource as node()*) as item()* {

	let $collectionContent := if (fn:doc-available($collection/@uri)) then fn:doc($collection/@uri) else ""

	let $resourcesURI := string($resource/@uri)
	let $resourcesExpression := concat('$collectionContent', substring-after(substring-before($resourcesURI, "}"), "{"))
	let $resourcesNodes := if (xtriples:expressionSanityCheck($resourcesExpression) = true()) then
		try { util:eval($resourcesExpression) } catch * { $err:description }
		else $resourcesExpression

	let $resources :=
		for $resource at $index in $resourcesNodes
			return
				if ($resource instance of element()) then
					$resource
				else
					element {"resource"} {
						attribute {"uri"} {replace($resourcesURI, "\{.*\}", $resource)}
					}
	return $resources
};

(: gets resources from a collection, possibly expression based :)
declare function xtriples:getResources($collection as node()*) as item()* {

	let $resources :=
		for $resource in $collection/resource
		return
			if (matches($resource/@uri, "\{") and matches($resource/@uri, "\}")) then
				xtriples:expressionBasedResourceResolver($collection, $resource)
			else $resource

	return $resources
};

(: ### EXTRACTION FUNCTIONS ### :)

(:
Separate relatively similar functions are used for subject, predicate and object extraction.
This keeps the query simpler even if it repeats some code. At the same time it opens up the possibility
to treat statement part extractions different in future versions (as it is already the case with subject/object
vs predicate extraction routines.
:)

(: safety filter for XPATH/XQuery expressions - dissallows executions from dangerous/not needed function namespaces :)
declare function xtriples:expressionSanityCheck($expression as xs:string) as xs:boolean {

	let $pattern := "((fn:.*\(.*\))|(doc*\(.*\))|(collection*\(.*\))|(v:.*\(.*\))|(backups:.*\(.*\))|(compression:.*\(.*\))|(contentextraction:.*\(.*\))|(counter:.*\(.*\))|(cqlparser:.*\(.*\))|(datetime:.*\(.*\))|(examples:.*\(.*\))|(exi:.*\(.*\))|(file:.*\(.*\))|(httpclient:.*\(.*\))|(image:.*\(.*\))|(inspection:.*\(.*\))|(jindi:.*\(.*\))|(kwic:.*\(.*\))|(lucene:.*\(.*\))|(mail:.*\(.*\))|(math:.*\(.*\))|(ngram:.*\(.*\))|(repo:.*\(.*\))|(request:.*\(.*\))|(response:.*\(.*\))|(scheduler:.*\(.*\))|(securitymanager:.*\(.*\))|(sequences:.*\(.*\))|(session:.*\(.*\))|(sort:.*\(.*\))|(sql:.*\(.*\))|(system:.*\(.*\))|(testing:.*\(.*\))|(text:.*\(.*\))|(transform:.*\(.*\))|(util:.*\(.*\))|(validation:.*\(.*\))|(xmldb:.*\(.*\))|(xmldiff:.*\(.*\))|(xqdoc:.*\(.*\))|(xslfo:.*\(.*\))|(config:.*\(.*\))|(docbook:.*\(.*\))|(app:.*\(.*\))|(dash:.*\(.*\))|(service:.*\(.*\))|(login-helper:.*\(.*\))|(packages:.*\(.*\))|(service:.*\(.*\))|(usermanager:.*\(.*\))|(demo:.*\(.*\))|(cex:.*\(.*\))|(ex:.*\(.*\))|(apputil:.*\(.*\))|(site:.*\(.*\))|(pretty:.*\(.*\))|(date:.*\(.*\))|(tei2:.*\(.*\))|(dbutil:.*\(.*\))|(docs:.*\(.*\))|(dq:.*\(.*\))|(review:.*\(.*\))|(epub:.*\(.*\))|(l18n:.*\(.*\))|(intl:.*\(.*\))|(restxq:.*\(.*\))|(tmpl:.*\(.*\))|(templates:.*\(.*\))|(trigger:.*\(.*\))|(jsjson:.*\(.*\))|(xqdoc:.*\(.*\)))"
	let $check := matches($expression, $pattern)

	return if ($check = true()) then false() else true()
};

(: extracts subject statements from the current resource :)
declare function xtriples:extractSubjects($currentResource as node(), $statement as node()*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as item()* {

	let $externalResource :=
		if (exists($statement/subject/@resource)) then
			if (fn:doc-available(xtriples:expressionBasedUriResolver($statement/subject/@resource, $currentResource))) then
				fn:doc(xtriples:expressionBasedUriResolver($statement/subject/@resource, $currentResource))
			else ""
		else ""

	let $subjectExpressionConcatenation :=
		if ($externalResource) then concat("$externalResource", string($statement/subject))
		else concat('$currentResource', string($statement/subject))

	let $subjectExpression :=
		if (matches($subjectExpressionConcatenation, "$repeatIndex")) then replace($subjectExpressionConcatenation, "$repeatIndex", $repeatIndex)
		else $subjectExpressionConcatenation

	let $subjectNodes := if (xtriples:expressionSanityCheck($subjectExpression) = true()) then
		try { util:eval($subjectExpression) } catch * { $err:description }
		else $subjectExpression

	let $subjects :=
		for $subjectValue in $subjectNodes
		return functx:copy-attributes(<subject>{string($subjectValue)}</subject>, $statement/subject)

	return $subjects
};

(: extracts predicate statements from the current resource :)
declare function xtriples:extractPredicate($currentResource as node(), $statement as node()*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as item()* {

	let $externalResource :=
		if (exists($statement/predicate/@resource)) then
			if (fn:doc-available(xtriples:expressionBasedUriResolver($statement/predicate/@resource, $currentResource))) then
				fn:doc(xtriples:expressionBasedUriResolver($statement/predicate/@resource, $currentResource))
			else ""
		else ""

	let $predicateExpressionConcatenation :=
		if ($externalResource) then concat("$externalResource", string($statement/predicate))
		else concat('$currentResource', string($statement/predicate))

	let $predicateExpression :=
		if (matches($predicateExpressionConcatenation, "$repeatIndex")) then replace($predicateExpressionConcatenation, "$repeatIndex", $repeatIndex)
		else $predicateExpressionConcatenation

	let $predicateValue := if (xtriples:expressionSanityCheck($predicateExpression) = true()) then
		try { string(util:eval($predicateExpression)) } catch * { $err:description }
		else $predicateExpression

	let $predicate := <predicate prefix="{$statement/predicate/@prefix}">{string($predicateValue)}</predicate>

	return $predicate
};

(: extracts object statements from the current resource :)
declare function xtriples:extractObjects($currentResource as node(), $statement as node()*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as item()* {

	let $externalResource :=
		if (exists($statement/object/@resource)) then
			if (fn:doc-available(xtriples:expressionBasedUriResolver($statement/object/@resource, $currentResource))) then
				fn:doc(xtriples:expressionBasedUriResolver($statement/object/@resource, $currentResource))
			else ""
		else ""

	let $objectExpressionConcatenation :=
		if ($externalResource) then concat("$externalResource", string($statement/object))
		else concat("$currentResource", string($statement/object))

	let $objectExpression :=
		if (matches($objectExpressionConcatenation, "$repeatIndex")) then replace($objectExpressionConcatenation, "$repeatIndex", $repeatIndex)
		else $objectExpressionConcatenation

	let $objectNodes := if (xtriples:expressionSanityCheck($objectExpression) = true()) then
		try { util:eval($objectExpression) } catch * { $err:description }
		else $objectExpression

	let $objects :=
		for $objectValue in $objectNodes
		return functx:copy-attributes(<object>{string($objectValue)}</object>, $statement/object)

	return $objects
};

(: extracts expressions in prepend/append attributes from the current resource :)
declare function xtriples:extractPrependAppend($currentResource as node(), $expressionString as xs:string*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as xs:string* {
	let $expression := substring-after(substring-before($expressionString, "}"), "{")
	let $result :=
		if ($expression and xtriples:expressionSanityCheck($expression) = true()) then
			try {
				replace($expressionString, "\{.*\}", util:eval(concat("$currentResource", $expression)))
			} catch * { $err:description }
		else $expressionString
	return $result
};

(: XTriples core function - evaluates all configured statements for the current resource :)
declare function xtriples:extractTriples($currentResource as node(), $resourceIndex as xs:integer, $configuration as node()*) as item()* {

	(: set the content of the current resource :)
	let $currentResource :=
		(: it can be a resource tag with an URI :)
		if (xs:anyURI($currentResource/@uri)) then
			fn:doc($currentResource/@uri)
		(: or a resource tag with children :)
		else $currentResource/*

	(: start statement pattern extraction :)
	for $statement in $configuration//triples/*

		let $repeat := if ($statement/@repeat > 0) then $statement/@repeat else 1

		for $repeatIndex in (1 to $repeat)

			(: if a condition expression is given in the current statement, evaluate it :)
			let $condition :=
				if (exists($statement/condition)) then
					if (xtriples:expressionSanityCheck(string($statement/condition)) = true()) then
						try { util:eval(concat('$currentResource', string($statement/condition))) } catch * { $err:description }
					else true()
				else true()

			(: n possible subjects per statement declaration :)
			let $subjects :=
				if (starts-with(string($statement/subject), '/')) then
					xtriples:extractSubjects($currentResource, $statement, $repeatIndex, $resourceIndex)
				else $statement/subject

			(: 1 predicate per statement declaration :)
			let $predicate :=
				if (starts-with(string($statement/predicate), '/')) then
					xtriples:extractPredicate($currentResource, $statement, $repeatIndex, $resourceIndex)
				else <predicate prefix="{$statement/predicate/@prefix}">{string($statement/predicate)}</predicate>

			(: n possible objects per statement declaration :)
			let $objects :=
				if (starts-with(string($statement/object), '/')) then
					xtriples:extractObjects($currentResource, $statement, $repeatIndex, $resourceIndex)
				else $statement/object

			(: build statements - but only if the condition expression returned any value - boolean, string, node set etc. :)
			let $statements :=
				if ($condition != false()) then
					for $subject in $subjects
						let $subjectReturn :=
							if ($subject = "") then "" else
							for $object in $objects

								let $subjectPrepend := xtriples:extractPrependAppend($currentResource, $subject/@prepend, $repeatIndex, $resourceIndex)
								let $subjectAppend := xtriples:extractPrependAppend($currentResource, $subject/@append, $repeatIndex, $resourceIndex)

								let $predicatePrepend := xtriples:extractPrependAppend($currentResource, $predicate/@prepend, $repeatIndex, $resourceIndex)
								let $predicateAppend := xtriples:extractPrependAppend($currentResource, $predicate/@append, $repeatIndex, $resourceIndex)

								let $objectPrepend := xtriples:extractPrependAppend($currentResource, $object/@prepend, $repeatIndex, $resourceIndex)
								let $objectAppend := xtriples:extractPrependAppend($currentResource, $object/@append, $repeatIndex, $resourceIndex)

								let $objectReturn :=
									if ($object = "") then ""
									else <statement>{(
										functx:remove-attributes(functx:copy-attributes(<subject>{concat($subjectPrepend, $subject, $subjectAppend)}</subject>, $subject), ('append', 'prepend')),
										functx:remove-attributes(functx:copy-attributes(<predicate>{concat($predicatePrepend, $predicate, $predicateAppend)}</predicate>, $predicate), ('append', 'prepend')),
										functx:remove-attributes(functx:copy-attributes(<object>{concat($objectPrepend, $object, $objectAppend)}</object>, $object), ('append', 'prepend'))
									)}</statement>
							return $objectReturn
					return $subjectReturn
				else ""

	return $statements
};

(: ### FORMAT FUNCTIONS ### :)

(: internal/intermediate XTriples RDF format, one RDF node per XTriples statement - will later be condensed by the any23 webservice to "real" RDF :)
declare function xtriples:generateRDFTriples($xtriples as node()*) as item()* {

	for $statement in $xtriples//statements/statement

		let $subjectType := $statement/subject/@type
		let $subjectPrefix := $statement/subject/@prefix
		let $subjectUri := $xtriples//vocabularies/vocabulary[@prefix=$subjectPrefix]/@uri
		let $subjectValue := $statement/subject

		let $predicatePrefix := $statement/predicate/@prefix
		let $predicateUri := $xtriples//vocabularies/vocabulary[@prefix=$predicatePrefix]/@uri
		let $predicateValue := $statement/predicate

		let $objectType := $statement/object/@type
		let $objectPrefix := $statement/object/@prefix
		let $objectUri := $xtriples//vocabularies/vocabulary[@prefix=$objectPrefix]/@uri
		let $objectValue := $statement/object/text()

		(: rdf triple construction using computed constructors with qualified names; builds single rdf triples which are later combined by any23 webservice :)
		let $triple :=
			(: outer RDF tag :)
			element {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:Description")} {
			(: either is a blank node, then gets rdf:nodeID attribute :)
			(if ($subjectType = 'bnode') then
				attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:nodeID")} { $subjectValue }
			(: or is a URI, then gets rdf:about attribute :)
			else
				attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:about")} { concat($subjectUri, $subjectValue) }),
				(: predicate = inner element of the RDF tag :)
				element {QName($predicateUri, concat($predicatePrefix, ":", $predicateValue))} {
				(: object uri; set rdf:resource as attribute to predicate :)
				if ($objectType = 'uri') then
					attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:resource")} {
						if ($objectPrefix) then concat($objectUri, $objectValue) else $objectValue
					}
				(: object blank node; set rdf:nodeIFD as attribute to predicate :)
				else if ($objectType = 'bnode') then
					attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:nodeID")} { $objectValue }
				(: typed object literals; append rdf:datatype as attribute and set value as text node :)
				else if ($statement/object/@datatype) then
					(attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:datatype")} { concat("http://www.w3.org/2001/XMLSchema#", $statement/object/@datatype) }, $objectValue)
				(: language tagged object literals; append rdf:datatype as attribute and set value as text node :)
				else if ($statement/object/@lang) then
					(attribute {"xml:lang"} { $statement/object/@lang }, $objectValue)
				(: plain object literals; set value as text node :)
				else
					$objectValue
			}
		}

		return
			$triple
};

(: gets the internal RDF format :)
declare function xtriples:getRDFTriples($xtriples as node()*, $vocabularies as node()*) as item()* {
	let $rdfTriples := <rdftriples>{$vocabularies}{xtriples:generateRDFTriples($xtriples)}</rdftriples>
	return $rdfTriples
};

(: gets the internal RDF format and sends it to any23 for further transformation :)
declare function xtriples:getRDF($xtriples as node()*, $vocabularies as node()*) as item()* {
	(: internal RDF format :)
	let $rdfstylesheet := doc("rdf.xsl")
	let $rdfTriples := xtriples:getRDFTriples($xtriples, $vocabularies)
	let $rdfInternal := transform:transform($rdfTriples, $rdfstylesheet, ())

	(: official RDF format via any23 :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "rdfxml")), $rdfInternal, false(), $headers)
	let $rdf := $POST_request//httpclient:body/*

	return $rdf
};

(: gets ntriples format from any23 by sending in extracted RDF :)
declare function xtriples:getNTRIPLES($rdf as node()*) as item()* {

	(: url encoded ntriples :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "nt")), $rdf, false(), $headers)
	let $ntriples := util:unescape-uri(replace(string($POST_request//httpclient:body), '%00', ''), "UTF-8")

	return $ntriples
};

(: gets turtle format from any23 by sending in extracted RDF :)
declare function xtriples:getTURTLE($rdf as node()*) as item()* {

	(: eXist returns base64Binary turtle :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "turtle")), $rdf, false(), $headers)
	let $turtle := util:binary-to-string(xs:base64Binary($POST_request//httpclient:body), "UTF-8")

	return $turtle
};

(: gets nquads format from any23 by sending in extracted RDF :)
declare function xtriples:getNQUADS($rdf as node()*) as item()* {

	(: eXist returns base64Binary nquads :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "nq")), $rdf, false(), $headers)
	let $nquads := util:binary-to-string(xs:base64Binary($POST_request//httpclient:body), "UTF-8")

	return $nquads
};

(: gets json format from any23 by sending in extracted RDF :)
declare function xtriples:getJSON($rdf as node()*) as item()* {

	(: eXist returns base64Binary json :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "json")), $rdf, false(), $headers)
	let $json := util:binary-to-string(xs:base64Binary($POST_request//httpclient:body), "UTF-8")

	return $json
};

(: gets trix format from any23 by sending in extracted RDF :)
declare function xtriples:getTRIX($rdf as node()*) as item()* {

	(: eXist returns base64Binary json :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "trix")), $rdf, false(), $headers)
	let $trix := $POST_request//httpclient:body/*

	return $trix
};

(: gets svg format from rhizomik webservice by temporarily storing the extracted RDF and pointing the redefer to the temporary file :)
declare function xtriples:getSVG($rdf as node()*) as item()* {

	(: svg format with temporary file :)
	let $filename := concat(util:uuid(), ".xml")
	let $store := xmldb:store("/db/apps/xtriples/temp/", $filename, $rdf)
	let $svgHeaders :=
		<headers>
			<header name="format" value="RDF/XML"/>
			<header name="mode" value="svg" />
			<headers name="rules" value="{$redeferWebserviceRulesURL}" />
		</headers>

	let $GET_request := httpclient:get(xs:anyURI(concat($redeferWebserviceURL, "render?rdf=", $xtriplesWebserviceURL, "temp/", $filename, "&format=RDF/XML&mode=svg&rules=", $redeferWebserviceRulesURL)), false(), $svgHeaders)
	let $svg := $GET_request//httpclient:body/*
	let $delete := xmldb:remove("/db/apps/xtriples/temp/", $filename)

	return $svg
};

(: ### QUERY BODY ### :)

(: set basic vars :)
let $collections := $setConfiguration/xtriples/collection
let $configuration := $setConfiguration/xtriples/configuration
let $vocabularies := $configuration/vocabularies

(: dynamic namespace declaration for all configured vocabularies :)
let $namespaces :=
	if ($configuration/vocabularies/*) then
		for $namespace in $configuration/vocabularies/*
		return util:declare-namespace($namespace/@prefix, $namespace/@uri)
	else ""

(: extract triples from collections :)
let $extraction :=

	for $collection in $collections

		let $maxResources :=
			if ($collection/@max > 0) then
				$collection/@max
			else 0

		let $resources := xtriples:getResources($collection)

		let $triples :=
			for $resource at $resourceIndex in $resources
				let $currentResource :=
					if (name($resource) = 'resource') then
						$resource
					else <resource>{$resource}</resource>
			return
				if ($maxResources > 0 and $resourceIndex <= $maxResources)
				then
					<statements>{xtriples:extractTriples($currentResource, $resourceIndex, $configuration)}</statements>
				else if ($maxResources = 0)
				then
					<statements>{xtriples:extractTriples($currentResource, $resourceIndex, $configuration)}</statements>
				else ""

	return <result>{(functx:copy-attributes(<collection>{$resources}</collection>, $collection),<triples>{$triples}</triples>)}</result>

(: construct internal result format :)
let $xtriples :=
	<xtriples>
		<configuration>
			{$vocabularies}
			<triples>{$extraction//triples/*}</triples>
		</configuration>
		{$extraction//collection}
	</xtriples>

(: transform and return result :)
return (
	if ($setFormat = "xtriples") then
		$xtriples
	else if ($setFormat = "rdftriples") then
		xtriples:getRDFTriples($xtriples, $vocabularies)
	else if ($setFormat = "ntriples") then (
		response:set-header("Content-Type", "text/plain; charset=UTF-8"),
		response:stream(xtriples:getNTRIPLES(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "turtle") then (
		response:set-header("Content-Type", "text/turtle; charset=UTF-8"),
		response:stream(xtriples:getTURTLE(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "nquads") then (
		response:set-header("Content-Type", "text/x-nquads; charset=UTF-8"),
		response:stream(xtriples:getNQUADS(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "json") then (
		response:set-header("Content-Type", "text/json; charset=UTF-8"),
		response:stream(xtriples:getJSON(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "trix") then (
		response:set-header("Content-Type", "application/trix; charset=UTF-8"),
		xtriples:getTRIX(xtriples:getRDF($xtriples, $vocabularies))
		)
	else if ($setFormat = "svg") then
		(: response:set-header("Content-Type", "image/svg+xml; charset=UTF-8"), :)
		xtriples:getSVG(xtriples:getRDF($xtriples, $vocabularies))
	else
		(: response:set-header("Content-Type", "application/rdf+xml; charset=UTF-8"), :)
		xtriples:getRDF($xtriples, $vocabularies)
)

xquery version "3.0";

(:~
 : XTriples
 :
 : A generic webservice to extract rdf statements from XML resources
 :
 : @author Torsten Schrade
 : @email <Torsten.Schrade@adwmainz.de>
 : @version 1.2.0
 : @licence MIT
 :
 : Main module containing the webservice
:)

(: ### PROLOGUE ### :)

import module namespace functx="http://www.functx.com";

declare namespace xtriples = "http://xtriples.spatialhumanities.de/";

(: ### GLOBAL VARIABLES :)

declare variable $setConfiguration := xtriples:getConfiguration();
declare variable $setFormat := xtriples:getFormat();
declare variable $xtriplesWebserviceURL := "http://xtriples.spatialhumanities.de/";
declare variable $any23WebserviceURL := "http://any23.org/";
declare variable $redeferWebserviceURL := "http://rhizomik.net/redefer-services/";
declare variable $redeferWebserviceRulesURL := "http://rhizomik.net:8080/html/redefer/rdf2svg/showgraph.jrule";

(: ### CONFIGURATION FUNCTIONS ### :)

(: retrieves the XTriples configuration from GET or POST :)
declare function xtriples:getConfiguration() {

	(: checks GET/POST for a configuration sent with the configuration parameter :)
	let $submittedConfiguration := request:get-parameter("configuration", "")
	(: checks the request body for a configuration :)
	let $submittedConfigurationPOSTBody := request:get-data()

	let $setConfiguration :=
		(: case 1 - URI to a configuration file was sent - GET request :)
		if (substring($submittedConfiguration, 1, 4) = "http") then
			fn:doc(xs:anyURI($submittedConfiguration))
		(: case 2 - nothing submitted with a configuration parameter, use request body - direct POST request :)
		else if ($submittedConfiguration = "") then
			if ($submittedConfigurationPOSTBody) then
				$submittedConfigurationPOSTBody
			(: if no configuration could be retrieved to this point use a standard config that issues an error :)
			else
				fn:doc("configuration.xml")
		(: case 3 - full configuration sent with the configuration parameter - form style POST request :)
		else util:parse($submittedConfiguration)

	return $setConfiguration
};

(: gets and sets the return format :)
declare function xtriples:getFormat() {

	let $HEADER := request:get-header("format")
	let $GET := request:get-parameter("format", "rdf")
	let $setFormat :=
		if ($HEADER != "") then $HEADER
		else $GET

	return $setFormat
};

(: ### CRAWLING FUNCTIONS ### :)

(: evaluates an XPATH expression in curly braces within a (URI) string and returns the string :)
declare function xtriples:expressionBasedUriResolver($uri as xs:string, $currentResource as node()) as xs:string {

	let $expression := concat('$currentResource', substring-after(substring-before($uri, "}"), "{"))
	let $result := if (xtriples:expressionSanityCheck($expression) = true()) then
		try { util:eval($expression) } catch * { $err:description }
		else ""
	let $uriWithSubstitution := replace($uri, "\{.*\}", $result)

	return $uriWithSubstitution
};

(: evaluates a possible XPATH expression in curly braces within a document string and retrieves the document :)
declare function xtriples:expressionBasedResourceResolver($collection as node()*, $resource as node()*) as item()* {

	let $collectionContent := if (fn:doc-available($collection/@uri)) then fn:doc($collection/@uri) else ""

	let $resourcesURI := string($resource/@uri)
	let $resourcesExpression := concat('$collectionContent', substring-after(substring-before($resourcesURI, "}"), "{"))
	let $resourcesNodes := if (xtriples:expressionSanityCheck($resourcesExpression) = true()) then
		try { util:eval($resourcesExpression) } catch * { $err:description }
		else $resourcesExpression

	let $resources :=
		for $resource at $index in $resourcesNodes
			return
				if ($resource instance of element()) then
					$resource
				else
					element {"resource"} {
						attribute {"uri"} {replace($resourcesURI, "\{.*\}", $resource)}
					}
	return $resources
};

(: gets resources from a collection, possibly expression based :)
declare function xtriples:getResources($collection as node()*) as item()* {

	let $resources :=
		for $resource in $collection/resource
		return
			if (matches($resource/@uri, "\{") and matches($resource/@uri, "\}")) then
				xtriples:expressionBasedResourceResolver($collection, $resource)
			else $resource

	return $resources
};

(: ### EXTRACTION FUNCTIONS ### :)

(:
Separate relatively similar functions are used for subject, predicate and object extraction.
This keeps the query simpler even if it repeats some code. At the same time it opens up the possibility
to treat statement part extractions different in future versions (as it is already the case with subject/object
vs predicate extraction routines.
:)

(: safety filter for XPATH/XQuery expressions - dissallows executions from dangerous/not needed function namespaces :)
declare function xtriples:expressionSanityCheck($expression as xs:string) as xs:boolean {

	let $pattern := "((fn:.*\(.*\))|(doc*\(.*\))|(collection*\(.*\))|(v:.*\(.*\))|(backups:.*\(.*\))|(compression:.*\(.*\))|(contentextraction:.*\(.*\))|(counter:.*\(.*\))|(cqlparser:.*\(.*\))|(datetime:.*\(.*\))|(examples:.*\(.*\))|(exi:.*\(.*\))|(file:.*\(.*\))|(httpclient:.*\(.*\))|(image:.*\(.*\))|(inspection:.*\(.*\))|(jindi:.*\(.*\))|(kwic:.*\(.*\))|(lucene:.*\(.*\))|(mail:.*\(.*\))|(math:.*\(.*\))|(ngram:.*\(.*\))|(repo:.*\(.*\))|(request:.*\(.*\))|(response:.*\(.*\))|(scheduler:.*\(.*\))|(securitymanager:.*\(.*\))|(sequences:.*\(.*\))|(session:.*\(.*\))|(sort:.*\(.*\))|(sql:.*\(.*\))|(system:.*\(.*\))|(testing:.*\(.*\))|(text:.*\(.*\))|(transform:.*\(.*\))|(util:.*\(.*\))|(validation:.*\(.*\))|(xmldb:.*\(.*\))|(xmldiff:.*\(.*\))|(xqdoc:.*\(.*\))|(xslfo:.*\(.*\))|(config:.*\(.*\))|(docbook:.*\(.*\))|(app:.*\(.*\))|(dash:.*\(.*\))|(service:.*\(.*\))|(login-helper:.*\(.*\))|(packages:.*\(.*\))|(service:.*\(.*\))|(usermanager:.*\(.*\))|(demo:.*\(.*\))|(cex:.*\(.*\))|(ex:.*\(.*\))|(apputil:.*\(.*\))|(site:.*\(.*\))|(pretty:.*\(.*\))|(date:.*\(.*\))|(tei2:.*\(.*\))|(dbutil:.*\(.*\))|(docs:.*\(.*\))|(dq:.*\(.*\))|(review:.*\(.*\))|(epub:.*\(.*\))|(l18n:.*\(.*\))|(intl:.*\(.*\))|(restxq:.*\(.*\))|(tmpl:.*\(.*\))|(templates:.*\(.*\))|(trigger:.*\(.*\))|(jsjson:.*\(.*\))|(xqdoc:.*\(.*\)))"
	let $check := matches($expression, $pattern)

	return if ($check = true()) then false() else true()
};

(: extracts subject statements from the current resource :)
declare function xtriples:extractSubjects($currentResource as node(), $statement as node()*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as item()* {

	let $externalResource :=
		if (exists($statement/subject/@resource)) then
			if (fn:doc-available(xtriples:expressionBasedUriResolver($statement/subject/@resource, $currentResource))) then
				fn:doc(xtriples:expressionBasedUriResolver($statement/subject/@resource, $currentResource))
			else ""
		else ""

	let $subjectExpressionConcatenation :=
		if ($externalResource) then concat("$externalResource", string($statement/subject))
		else concat('$currentResource', string($statement/subject))

	let $subjectExpression :=
		if (matches($subjectExpressionConcatenation, "$repeatIndex")) then replace($subjectExpressionConcatenation, "$repeatIndex", $repeatIndex)
		else $subjectExpressionConcatenation

	let $subjectNodes := if (xtriples:expressionSanityCheck($subjectExpression) = true()) then
		try { util:eval($subjectExpression) } catch * { $err:description }
		else $subjectExpression

	let $subjects :=
		for $subjectValue in $subjectNodes
		return functx:copy-attributes(<subject>{string($subjectValue)}</subject>, $statement/subject)

	return $subjects
};

(: extracts predicate statements from the current resource :)
declare function xtriples:extractPredicate($currentResource as node(), $statement as node()*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as item()* {

	let $externalResource :=
		if (exists($statement/predicate/@resource)) then
			if (fn:doc-available(xtriples:expressionBasedUriResolver($statement/predicate/@resource, $currentResource))) then
				fn:doc(xtriples:expressionBasedUriResolver($statement/predicate/@resource, $currentResource))
			else ""
		else ""

	let $predicateExpressionConcatenation :=
		if ($externalResource) then concat("$externalResource", string($statement/predicate))
		else concat('$currentResource', string($statement/predicate))

	let $predicateExpression :=
		if (matches($predicateExpressionConcatenation, "$repeatIndex")) then replace($predicateExpressionConcatenation, "$repeatIndex", $repeatIndex)
		else $predicateExpressionConcatenation

	let $predicateValue := if (xtriples:expressionSanityCheck($predicateExpression) = true()) then
		try { string(util:eval($predicateExpression)) } catch * { $err:description }
		else $predicateExpression

	let $predicate := <predicate prefix="{$statement/predicate/@prefix}">{string($predicateValue)}</predicate>

	return $predicate
};

(: extracts object statements from the current resource :)
declare function xtriples:extractObjects($currentResource as node(), $statement as node()*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as item()* {

	let $externalResource :=
		if (exists($statement/object/@resource)) then
			if (fn:doc-available(xtriples:expressionBasedUriResolver($statement/object/@resource, $currentResource))) then
				fn:doc(xtriples:expressionBasedUriResolver($statement/object/@resource, $currentResource))
			else ""
		else ""

	let $objectExpressionConcatenation :=
		if ($externalResource) then concat("$externalResource", string($statement/object))
		else concat("$currentResource", string($statement/object))

	let $objectExpression :=
		if (matches($objectExpressionConcatenation, "$repeatIndex")) then replace($objectExpressionConcatenation, "$repeatIndex", $repeatIndex)
		else $objectExpressionConcatenation

	let $objectNodes := if (xtriples:expressionSanityCheck($objectExpression) = true()) then
		try { util:eval($objectExpression) } catch * { $err:description }
		else $objectExpression

	let $objects :=
		for $objectValue in $objectNodes
		return functx:copy-attributes(<object>{string($objectValue)}</object>, $statement/object)

	return $objects
};

(: extracts expressions in prepend/append attributes from the current resource :)
declare function xtriples:extractPrependAppend($currentResource as node(), $expressionString as xs:string*, $repeatIndex as xs:integer, $resourceIndex as xs:integer) as xs:string* {
	let $expression := substring-after(substring-before($expressionString, "}"), "{")
	let $result :=
		if ($expression and xtriples:expressionSanityCheck($expression) = true()) then
			try {
				replace($expressionString, "\{.*\}", util:eval(concat("$currentResource", $expression)))
			} catch * { $err:description }
		else $expressionString
	return $result
};

(: XTriples core function - evaluates all configured statements for the current resource :)
declare function xtriples:extractTriples($currentResource as node(), $resourceIndex as xs:integer, $configuration as node()*) as item()* {

	(: set the content of the current resource :)
	let $currentResource :=
		(: it can be a resource tag with an URI :)
		if (xs:anyURI($currentResource/@uri)) then
			fn:doc($currentResource/@uri)
		(: or a resource tag with children :)
		else $currentResource/*

	(: start statement pattern extraction :)
	for $statement in $configuration//triples/*

		let $repeat := if ($statement/@repeat > 0) then $statement/@repeat else 1

		for $repeatIndex in (1 to $repeat)

			(: if a condition expression is given in the current statement, evaluate it :)
			let $condition :=
				if (exists($statement/condition)) then
					if (xtriples:expressionSanityCheck(string($statement/condition)) = true()) then
						try { util:eval(concat('$currentResource', string($statement/condition))) } catch * { $err:description }
					else true()
				else true()

			(: n possible subjects per statement declaration :)
			let $subjects :=
				if (starts-with(string($statement/subject), '/')) then
					xtriples:extractSubjects($currentResource, $statement, $repeatIndex, $resourceIndex)
				else $statement/subject

			(: 1 predicate per statement declaration :)
			let $predicate :=
				if (starts-with(string($statement/predicate), '/')) then
					xtriples:extractPredicate($currentResource, $statement, $repeatIndex, $resourceIndex)
				else <predicate prefix="{$statement/predicate/@prefix}">{string($statement/predicate)}</predicate>

			(: n possible objects per statement declaration :)
			let $objects :=
				if (starts-with(string($statement/object), '/')) then
					xtriples:extractObjects($currentResource, $statement, $repeatIndex, $resourceIndex)
				else $statement/object

			(: build statements - but only if the condition expression returned any value - boolean, string, node set etc. :)
			let $statements :=
				if ($condition != false()) then
					for $subject in $subjects
						let $subjectReturn :=
							if ($subject = "") then "" else
							for $object in $objects

								let $subjectPrepend := xtriples:extractPrependAppend($currentResource, $subject/@prepend, $repeatIndex, $resourceIndex)
								let $subjectAppend := xtriples:extractPrependAppend($currentResource, $subject/@append, $repeatIndex, $resourceIndex)

								let $predicatePrepend := xtriples:extractPrependAppend($currentResource, $predicate/@prepend, $repeatIndex, $resourceIndex)
								let $predicateAppend := xtriples:extractPrependAppend($currentResource, $predicate/@append, $repeatIndex, $resourceIndex)

								let $objectPrepend := xtriples:extractPrependAppend($currentResource, $object/@prepend, $repeatIndex, $resourceIndex)
								let $objectAppend := xtriples:extractPrependAppend($currentResource, $object/@append, $repeatIndex, $resourceIndex)

								let $objectReturn :=
									if ($object = "") then ""
									else <statement>{(
										functx:remove-attributes(functx:copy-attributes(<subject>{concat($subjectPrepend, $subject, $subjectAppend)}</subject>, $subject), ('append', 'prepend')),
										functx:remove-attributes(functx:copy-attributes(<predicate>{concat($predicatePrepend, $predicate, $predicateAppend)}</predicate>, $predicate), ('append', 'prepend')),
										functx:remove-attributes(functx:copy-attributes(<object>{concat($objectPrepend, $object, $objectAppend)}</object>, $object), ('append', 'prepend'))
									)}</statement>
							return $objectReturn
					return $subjectReturn
				else ""

	return $statements
};

(: ### FORMAT FUNCTIONS ### :)

(: internal/intermediate XTriples RDF format, one RDF node per XTriples statement - will later be condensed by the any23 webservice to "real" RDF :)
declare function xtriples:generateRDFTriples($xtriples as node()*) as item()* {

	for $statement in $xtriples//statements/statement

		let $subjectType := $statement/subject/@type
		let $subjectPrefix := $statement/subject/@prefix
		let $subjectUri := $xtriples//vocabularies/vocabulary[@prefix=$subjectPrefix]/@uri
		let $subjectValue := $statement/subject

		let $predicatePrefix := $statement/predicate/@prefix
		let $predicateUri := $xtriples//vocabularies/vocabulary[@prefix=$predicatePrefix]/@uri
		let $predicateValue := $statement/predicate

		let $objectType := $statement/object/@type
		let $objectPrefix := $statement/object/@prefix
		let $objectUri := $xtriples//vocabularies/vocabulary[@prefix=$objectPrefix]/@uri
		let $objectValue := $statement/object/text()

		(: rdf triple construction using computed constructors with qualified names; builds single rdf triples which are later combined by any23 webservice :)
		let $triple :=
			(: outer RDF tag :)
			element {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:Description")} {
			(: either is a blank node, then gets rdf:nodeID attribute :)
			(if ($subjectType = 'bnode') then
				attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:nodeID")} { $subjectValue }
			(: or is a URI, then gets rdf:about attribute :)
			else
				attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:about")} { concat($subjectUri, $subjectValue) }),
				(: predicate = inner element of the RDF tag :)
				element {QName($predicateUri, concat($predicatePrefix, ":", $predicateValue))} {
				(: object uri; set rdf:resource as attribute to predicate :)
				if ($objectType = 'uri') then
					attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:resource")} {
						if ($objectPrefix) then concat($objectUri, $objectValue) else $objectValue
					}
				(: object blank node; set rdf:nodeIFD as attribute to predicate :)
				else if ($objectType = 'bnode') then
					attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:nodeID")} { $objectValue }
				(: typed object literals; append rdf:datatype as attribute and set value as text node :)
				else if ($statement/object/@datatype) then
					(attribute {QName("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:datatype")} { concat("http://www.w3.org/2001/XMLSchema#", $statement/object/@datatype) }, $objectValue)
				(: language tagged object literals; append rdf:datatype as attribute and set value as text node :)
				else if ($statement/object/@lang) then
					(attribute {"xml:lang"} { $statement/object/@lang }, $objectValue)
				(: plain object literals; set value as text node :)
				else
					$objectValue
			}
		}

		return
			$triple
};

(: gets the internal RDF format :)
declare function xtriples:getRDFTriples($xtriples as node()*, $vocabularies as node()*) as item()* {
	let $rdfTriples := <rdftriples>{$vocabularies}{xtriples:generateRDFTriples($xtriples)}</rdftriples>
	return $rdfTriples
};

(: gets the internal RDF format and sends it to any23 for further transformation :)
declare function xtriples:getRDF($xtriples as node()*, $vocabularies as node()*) as item()* {
	(: internal RDF format :)
	let $rdfstylesheet := doc("rdf.xsl")
	let $rdfTriples := xtriples:getRDFTriples($xtriples, $vocabularies)
	let $rdfInternal := transform:transform($rdfTriples, $rdfstylesheet, ())

	(: official RDF format via any23 :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "rdfxml")), $rdfInternal, false(), $headers)
	let $rdf := $POST_request//httpclient:body/*

	return $rdf
};

(: gets ntriples format from any23 by sending in extracted RDF :)
declare function xtriples:getNTRIPLES($rdf as node()*) as item()* {

	(: url encoded ntriples :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "nt")), $rdf, false(), $headers)
	let $ntriples := util:unescape-uri(replace(string($POST_request//httpclient:body), '%00', ''), "UTF-8")

	return $ntriples
};

(: gets turtle format from any23 by sending in extracted RDF :)
declare function xtriples:getTURTLE($rdf as node()*) as item()* {

	(: eXist returns base64Binary turtle :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "turtle")), $rdf, false(), $headers)
	let $turtle := util:binary-to-string(xs:base64Binary($POST_request//httpclient:body), "UTF-8")

	return $turtle
};

(: gets nquads format from any23 by sending in extracted RDF :)
declare function xtriples:getNQUADS($rdf as node()*) as item()* {

	(: eXist returns base64Binary nquads :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "nq")), $rdf, false(), $headers)
	let $nquads := util:binary-to-string(xs:base64Binary($POST_request//httpclient:body), "UTF-8")

	return $nquads
};

(: gets json format from any23 by sending in extracted RDF :)
declare function xtriples:getJSON($rdf as node()*) as item()* {

	(: eXist returns base64Binary json :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "json")), $rdf, false(), $headers)
	let $json := util:binary-to-string(xs:base64Binary($POST_request//httpclient:body), "UTF-8")

	return $json
};

(: gets trix format from any23 by sending in extracted RDF :)
declare function xtriples:getTRIX($rdf as node()*) as item()* {

	(: eXist returns base64Binary json :)
	let $headers := <headers><header name="Content-Type" value="application/rdf+xml; charset=UTF-8"/></headers>
	let $POST_request := httpclient:post(xs:anyURI(concat($any23WebserviceURL, "trix")), $rdf, false(), $headers)
	let $trix := $POST_request//httpclient:body/*

	return $trix
};

(: gets svg format from rhizomik webservice by temporarily storing the extracted RDF and pointing the redefer to the temporary file :)
declare function xtriples:getSVG($rdf as node()*) as item()* {

	(: svg format with temporary file :)
	let $filename := concat(util:uuid(), ".xml")
	let $store := xmldb:store("/db/apps/xtriples/temp/", $filename, $rdf)
	let $svgHeaders :=
		<headers>
			<header name="format" value="RDF/XML"/>
			<header name="mode" value="svg" />
			<headers name="rules" value="{$redeferWebserviceRulesURL}" />
		</headers>

	let $GET_request := httpclient:get(xs:anyURI(concat($redeferWebserviceURL, "render?rdf=", $xtriplesWebserviceURL, "temp/", $filename, "&format=RDF/XML&mode=svg&rules=", $redeferWebserviceRulesURL)), false(), $svgHeaders)
	let $svg := $GET_request//httpclient:body/*
	let $delete := xmldb:remove("/db/apps/xtriples/temp/", $filename)

	return $svg
};

(: ### QUERY BODY ### :)

(: set basic vars :)
let $collection := $setConfiguration/xtriples/collection
let $configuration := $setConfiguration/xtriples/configuration
let $vocabularies := $configuration/vocabularies

(: dynamic namespace declaration for all configured vocabularies :)
let $namespaces :=
	if ($configuration/vocabularies/*) then
		for $namespace in $configuration/vocabularies/*
		return util:declare-namespace($namespace/@prefix, $namespace/@uri)
	else ""

(: get resources for extraction :)
let $resources := xtriples:getResources($collection)

let $maxResources :=
	if ($collection/@max > 0) then
		$collection/@max
	else 0

(: extract triples from resources :)
let $triples :=
	for $resource at $resourceIndex in $resources
		let $currentResource :=
			if (name($resource) = 'resource') then
				$resource
			else <resource>{$resource}</resource>
	return
		if ($maxResources > 0 and $resourceIndex <= $maxResources)
		then
			<statements>{xtriples:extractTriples($currentResource, $resourceIndex, $configuration)}</statements>
		else if ($maxResources = 0)
		then
			<statements>{xtriples:extractTriples($currentResource, $resourceIndex, $configuration)}</statements>
		else ""

let $xtriples :=
	<xtriples>
		<configuration>
			{$vocabularies}
			<triples>{$triples}</triples>
		</configuration>
		{functx:copy-attributes(<collection>{$resources}</collection>, $collection)}
	</xtriples>

return (
	if ($setFormat = "xtriples") then
		$xtriples
	else if ($setFormat = "rdftriples") then
		xtriples:getRDFTriples($xtriples, $vocabularies)
	else if ($setFormat = "ntriples") then (
		response:set-header("Content-Type", "text/plain; charset=UTF-8"),
		response:stream(xtriples:getNTRIPLES(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "turtle") then (
		response:set-header("Content-Type", "text/turtle; charset=UTF-8"),
		response:stream(xtriples:getTURTLE(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "nquads") then (
		response:set-header("Content-Type", "text/x-nquads; charset=UTF-8"),
		response:stream(xtriples:getNQUADS(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "json") then (
		response:set-header("Content-Type", "text/json; charset=UTF-8"),
		response:stream(xtriples:getJSON(xtriples:getRDF($xtriples, $vocabularies)), "method=text")
		)
	else if ($setFormat = "trix") then (
		response:set-header("Content-Type", "application/trix; charset=UTF-8"),
		xtriples:getTRIX(xtriples:getRDF($xtriples, $vocabularies))
		)
	else if ($setFormat = "svg") then
		(: response:set-header("Content-Type", "image/svg+xml; charset=UTF-8"), :)
		xtriples:getSVG(xtriples:getRDF($xtriples, $vocabularies))
	else
		(: response:set-header("Content-Type", "application/rdf+xml; charset=UTF-8"), :)
		xtriples:getRDF($xtriples, $vocabularies)
)


Fixed try catch query

Fixed try catch query

let $x := 'a'
return
try {
    $x cast as xs:integer
} catch (err:XPST0051) {
  "Cast failed: item does not have a known, atomic type"
}

let $x := 'a'
return
try {
    $x cast as integer
} catch (err:XPST0051) {
  "Cast failed: item does not have a known, atomic type"
}


[bugfix] Small fixes in kwic module: be more forgiving if configuration is missing; make configuration optional; remove some eXist-db specific stuff and switch to standard XQuery 3.0 syntax.

svn path=/trunk/eXist/; revision=18696

(:~
    KWIC module: formats query results to display keywords in context (KWIC). A configurable
    amount of text is displayed to the left and right of a matching keyword (or phrase).

    The module works with all indexes that support match highlighting (matches are tagged
    with an <exist:match> element). This includes the old full text index, the new
    Lucene-based full text index, as well as the NGram index.

    The kwic:summarize() function represents the main entry point into the module.
    To have more control over the text extraction context, you can also call
    kwic:get-summary() instead. For example, the following snippet will only print the
    first match within a given set of context nodes ($ancestor):

    let $matches := kwic:get-matches($hit)

    for $ancestor in $matches/ancestor::para | $matches/ancestor::title | $matches/ancestor::td

    return

        kwic:get-summary($ancestor, ($ancestor//exist:match)[1], $config)

:)
module namespace kwic="http://exist-db.org/xquery/kwic";

declare variable $kwic:CHARS_SUMMARY := 120;
declare variable $kwic:CHARS_KWIC := 40;

(:~
	Like fn:substring, but takes a node argument. If the node is an element,
	a new element is created with the same node-name as the old one and the
	shortened text content.
:)
declare function kwic:substring($node as item(), $start as xs:int, $count as xs:int) as item()? {
	let $str := substring($node, $start, $count)
	return
		if ($node instance of element()) then
			element { node-name($node) } { $str }
		else
			$str
};

declare function kwic:display-text($text as text()?) as node()? {
    if ($text/parent::exist:match) then
    	{$text}
    else
        $text
};

declare function kwic:callback($callback as function?, $node as node(), $mode as xs:string) as xs:string? {
    if (exists($callback)) then
        $callback($node, $mode)
    else
        $node
};

(:~
	Generate the left-hand context of the match. Returns a sequence of nodes
	and strings, whose total string length is less than or equal to $max characters.

	Note: this function calls itself recursively until $nodes is empty or
	the returned sequence has the desired total string length.
:)
declare function kwic:truncate-previous($root as node(), $node as node()?, $truncated as item()*,
	$max as xs:int, $chars as xs:int, $callback as (function(node(), xs:string) as xs:string?)?) {
	if ($node) then
		let $next := $node/preceding::text()[1] intersect $root//text()
		return
			if (empty($next)) then
				$truncated
			else if ($chars + string-length($next) gt $max) then
			    let $str := kwic:callback($callback, $next, "before")
			    return
    			    if (exists($str)) then
        				let $remaining := $max - $chars
        				return (
        				    "...",
        				    kwic:substring($str, string-length($str) - $remaining + 1, $remaining),
        				    $truncated
        			    )
        			else
        			    kwic:truncate-previous($root, $next, $truncated, $max, $chars, $callback)
			else
				kwic:truncate-previous($root, $next,
				    (kwic:callback($callback, $next, "before"), $truncated),
					$max, $chars + string-length($next), $callback)
	else
		$truncated
};

(:~
	Generate the right-hand context of the match. Returns a sequence of nodes
	and strings, whose total string length is less than or equal to $max characters.

	Note: this function calls itself recursively until $nodes is empty or
	the returned sequence has the desired total string length.
:)
declare function kwic:truncate-following($root as node(), $node as node()?, $truncated as item()*,
	$max as xs:int, $chars as xs:int, $callback as (function(node(), xs:string) as xs:string?)?) {
	if ($node) then
		let $next := $node/following::text()[1] intersect $root//text()
		return
			if (empty($next)) then
				$truncated
			else if ($chars + string-length($next) gt $max) then
			    let $str := kwic:callback($callback, $next, "after")
			    return
    			    if (exists($str)) then
        				let $remaining := $max - $chars
        				return
        				    ($truncated, kwic:substring($next, 1, $remaining), "...")
        			else
        			    kwic:truncate-following($root, $next, $truncated, $max, $chars, $callback)
			else
				kwic:truncate-following($root, $next,
				    ($truncated, kwic:callback($callback, $next, "after")),
					$max, $chars + string-length($next), $callback)
	else
		$truncated
};

(:~
	Computes the total string length of the nodes in the argument sequence
:)
declare function kwic:string-length($nodes as item()*) as xs:integer {
	if (exists($nodes)) then
		sum(for $n in $nodes return string-length($n))
	else
		0
};

declare function kwic:get-summary($root as node(), $node as element(exist:match),
	$config as element(config)?) as element() {
	kwic:get-summary($root, $node, $config, ())
};

(:~
	Print a summary of the match in $node. Output a predefined amount of text to
	the left and the right of the match.

	@param $root root element which should be used as context for the match. It defines the
	    boundaries for the text extraction. Text will be taken from this context.
	@param $node the exist:match element to process.
	@param $config configuration element which determines the behaviour of the function
	@param $callback (optional) reference to a callback function which will be called
	once for every text node before it is appended to the displayed text. The function
	should accept 2 parameters: 1) a single text node, 2) a string indicating the
	current direction in which text is appended, i.e. "before" or "after". The function
	may return the empty sequence if the current node should be ignore (e.g. if it belongs
	to a "footnote" which should not be displayed). Otherwise it should return a single
	string.
:)
declare function kwic:get-summary($root as node(), $node as element(exist:match),
	$config as element(config)?,
    $callback as (function(node(), xs:string) as xs:string?)?
) as element() {
	let $chars := xs:int(($config/@width, $kwic:CHARS_KWIC)[1])
	let $table := $config/@table = ('yes', 'true')
	let $prevTrunc := kwic:truncate-previous($root, $node, (), $chars, 0, $callback)
	let $remain :=
		if (not($table)) then
			$chars * 2 - kwic:string-length($prevTrunc)
		else
			$chars
	let $followingTrunc := kwic:truncate-following($root, $node, (), $remain, 0, $callback)
	return
		if (not($table)) then

				{$prevTrunc}
				{
					if ($config/@link) then
						{ $node/text() }
					else
						{ $node/text() }
				}
				{$followingTrunc}

		else

				{$prevTrunc}

				{
					if ($config/@link) then
						{$node/text()}
					else
						$node/text()
				}

				{$followingTrunc}

};

(:~
    Expand the element in $hit. Creates an in-memory copy of the element and marks
    all matches with an exist:match tag, which will be used by all other functions in
    this module. You need to call kwic:expand before kwic:get-summary.
    kwic:summarize will call it automatically.
:)
declare function kwic:expand($hit as element()) as element() {
    util:expand($hit, "expand-xincludes=no")
};

(:~
    Return all matches within the specified element, $hit. Matches are returned as
    exist:match elements. The returned nodes are part of a new document whose
    root element is a copy of the specified $hit element.

    @param $hit an arbitrary XML element which has been selected by one of the full text
		operations or an ngram search.
:)
declare function kwic:get-matches($hit as element()) as element(exist:match)* {
    let $expanded := kwic:expand($hit)
	return $expanded//exist:match
};

declare function kwic:summarize($hit as element(), $config as element(config)?) as element()* {
    kwic:summarize($hit, $config, ())
};

(:~
	Main function of the KWIC module: takes the passed element and returns an
	XHTML fragment containing a chunk of text before and after the first full text
	match in the node.

	The optional config parameter is used to configure the behaviour of the function:

	<config width="character width" table="yes|no" link="URL to which the match is linked"/>

	By default, kwic:summarize returns an XHTML fragment with the following structure:

	<p xmlns="http://www.w3.org/1999/xhtml">
		<span class="previous">Text before match</span>
		<a href="passed URL if any" class="hi">The highlighted term</a>
		<span class="following">Text after match</span>
	</p>

	If table=yes is passed with the config element, a tr table row will be returned instead
	of a span (using the same class names).

	@param $hit an arbitrary XML element which has been selected by one of the full text
		operations or an ngram search.
	@param $config configuration element to configure the behaviour of the function
:)
declare function kwic:summarize($hit as element(), $config as element(config)?,
    $callback as (function(node(), xs:string) as xs:string?)?) as element()* {
    let $expanded := util:expand($hit, "expand-xincludes=no")
	for $match in $expanded//exist:match
	return
		kwic:get-summary($expanded, $match, $config, $callback)
};

(:
declare function kwic:summarize-all($hits as element()*, $config as element(config),
    $wrap as function(node(), function(node()) as element()) as element()) {
    let $hits := util:expand($hits, "expand-xincludes=no")
    for $hit in $hits
    return
        $wrap($hit, function($node) {
            for $match in $node//exist:match
            return
        		kwic:get-summary($node, $match, $config, ())
        })
};
:)

(:~
    KWIC module: formats query results to display keywords in context (KWIC). A configurable
    amount of text is displayed to the left and right of a matching keyword (or phrase).

    The module works with all indexes that support match highlighting (matches are tagged
    with an <exist:match> element). This includes the old full text index, the new
    Lucene-based full text index, as well as the NGram index.

    The kwic:summarize() function represents the main entry point into the module.
    To have more control over the text extraction context, you can also call
    kwic:get-summary() instead. For example, the following snippet will only print the
    first match within a given set of context nodes ($ancestor):

    let $matches := kwic:get-matches($hit)

    for $ancestor in $matches/ancestor::para | $matches/ancestor::title | $matches/ancestor::td

    return

        kwic:get-summary($ancestor, ($ancestor//exist:match)[1], $config)

:)
module namespace kwic="http://exist-db.org/xquery/kwic";

declare variable $kwic:CHARS_SUMMARY := 120;
declare variable $kwic:CHARS_KWIC := 40;

(:~
	Retrieve the following and preceding text chunks for a given match.

	@param $match the text node containing the match
	@param $mode the selection mode: either "previous" or "following"
:)
declare function kwic:get-context($root as element(), $match as element(exist:match), $mode as xs:string) as node()* {
	let $sibs :=
		if ($mode eq 'previous') then
			$match/preceding::text()
		else
			$match/text()/following::text()
	for $sib in $sibs
	where exists($root[.//$sib])
	return
		if ($sib/parent::exist:match) then
			{$sib}
		else
			$sib
};

(:~
	Like fn:substring, but takes a node argument. If the node is an element,
	a new element is created with the same node-name as the old one and the
	shortened text content.
:)
declare function kwic:substring($node as item(), $start as xs:int, $count as xs:int) as item()? {
	let $str := substring($node, $start, $count)
	return
		if ($node instance of element()) then
			element { node-name($node) } { $str }
		else
			$str
};

declare function kwic:display-text($text as text()?) as node()? {
    if ($text/parent::exist:match) then
    	{$text}
    else
        $text
};

declare function kwic:callback($callback as function?, $node as node(), $mode as xs:string) as xs:string? {
    if (exists($callback)) then
        util:call($callback, $node, $mode)
    else
        $node
};

(:~
	Generate the left-hand context of the match. Returns a sequence of nodes
	and strings, whose total string length is less than or equal to $max characters.

	Note: this function calls itself recursively until $nodes is empty or
	the returned sequence has the desired total string length.
:)
declare function kwic:truncate-previous($root as node(), $node as node()?, $truncated as item()*,
	$max as xs:int, $chars as xs:int, $callback as function?) {
	if ($node) then
		let $next := $node/preceding::text()[1]
		return
			if (empty($root//$next)) then
				$truncated
			else if ($chars + string-length($next) gt $max) then
			    let $str := kwic:callback($callback, $next, "before")
			    return
    			    if (exists($str)) then
        				let $remaining := $max - $chars
        				return (
        				    "...",
        				    kwic:substring($str, string-length($str) - $remaining + 1, $remaining),
        				    $truncated
        			    )
        			else
        			    kwic:truncate-previous($root, $next, $truncated, $max, $chars, $callback)
			else
				kwic:truncate-previous($root, $next,
				    (kwic:callback($callback, $next, "before"), $truncated),
					$max, $chars + string-length($next), $callback)
	else
		$truncated
};

(:~
	Generate the right-hand context of the match. Returns a sequence of nodes
	and strings, whose total string length is less than or equal to $max characters.

	Note: this function calls itself recursively until $nodes is empty or
	the returned sequence has the desired total string length.
:)
declare function kwic:truncate-following($root as node(), $node as node()?, $truncated as item()*,
	$max as xs:int, $chars as xs:int, $callback as function?) {
	if ($node) then
		let $next := $node/following::text()[1]
		return
			if (empty($root//$next)) then
				$truncated
			else if ($chars + string-length($next) gt $max) then
			    let $str := kwic:callback($callback, $next, "after")
			    return
    			    if (exists($str)) then
        				let $remaining := $max - $chars
        				return
        				    ($truncated, kwic:substring($next, 1, $remaining), "...")
        			else
        			    kwic:truncate-following($root, $next, $truncated, $max, $chars, $callback)
			else
				kwic:truncate-following($root, $next,
				    ($truncated, kwic:callback($callback, $next, "after")),
					$max, $chars + string-length($next), $callback)
	else
		$truncated
};

(:~
	Computes the total string length of the nodes in the argument sequence
:)
declare function kwic:string-length($nodes as item()*) as xs:integer {
	if (exists($nodes)) then
		sum(for $n in $nodes return string-length($n))
	else
		0
};

declare function kwic:get-summary($root as node(), $node as element(exist:match),
	$config as element(config)) as element() {
	kwic:get-summary($root, $node, $config, ())
};

(:~
	Print a summary of the match in $node. Output a predefined amount of text to
	the left and the right of the match.

	@param $root root element which should be used as context for the match. It defines the
	    boundaries for the text extraction. Text will be taken from this context.
	@param $node the exist:match element to process.
	@param $config configuration element which determines the behaviour of the function
	@param $callback (optional) reference to a callback function which will be called
	once for every text node before it is appended to the displayed text. The function
	should accept 2 parameters: 1) a single text node, 2) a string indicating the
	current direction in which text is appended, i.e. "before" or "after". The function
	may return the empty sequence if the current node should be ignore (e.g. if it belongs
	to a "footnote" which should not be displayed). Otherwise it should return a single
	string.
:)
declare function kwic:get-summary($root as node(), $node as element(exist:match),
	$config as element(config), $callback as function?) as element() {
	let $chars := xs:int($config/@width)
	let $table := $config/@table = ('yes', 'true')
	let $prevTrunc := kwic:truncate-previous($root, $node, (), $chars, 0, $callback)
	let $remain :=
		if (not($table)) then
			$chars * 2 - kwic:string-length($prevTrunc)
		else
			$chars
	let $followingTrunc := kwic:truncate-following($root, $node, (), $remain, 0, $callback)
	return
		if (not($table)) then

				{$prevTrunc}
				{
					if ($config/@link) then
						{ $node/text() }
					else
						{ $node/text() }
				}
				{$followingTrunc}

		else

				{$prevTrunc}

				{
					if ($config/@link) then
						{$node/text()}
					else
						$node/text()
				}

				{$followingTrunc}

};

(:~
    Expand the element in $hit. Creates an in-memory copy of the element and marks
    all matches with an exist:match tag, which will be used by all other functions in
    this module. You need to call kwic:expand before kwic:get-summary.
    kwic:summarize will call it automatically.
:)
declare function kwic:expand($hit as element()) as element() {
    util:expand($hit, "expand-xincludes=no")
};

(:~
    Return all matches within the specified element, $hit. Matches are returned as
    exist:match elements. The returned nodes are part of a new document whose
    root element is a copy of the specified $hit element.

    @param $hit an arbitrary XML element which has been selected by one of the full text
		operations or an ngram search.
:)
declare function kwic:get-matches($hit as element()) as element(exist:match)* {
    let $expanded := kwic:expand($hit)
	return $expanded//exist:match
};

declare function kwic:summarize($hit as element(), $config as element(config)) as element()* {
    kwic:summarize($hit, $config, ())
};

(:~
	Main function of the KWIC module: takes the passed element and returns an
	XHTML fragment containing a chunk of text before and after the first full text
	match in the node.

	The optional config parameter is used to configure the behaviour of the function:

	<config width="character width" table="yes|no" link="URL to which the match is linked"/>

	By default, kwic:summarize returns an XHTML fragment with the following structure:

	<p xmlns="http://www.w3.org/1999/xhtml">
		<span class="previous">Text before match</span>
		<a href="passed URL if any" class="hi">The highlighted term</a>
		<span class="following">Text after match</span>
	</p>

	If table=yes is passed with the config element, a tr table row will be returned instead
	of a span (using the same class names).

	@param $hit an arbitrary XML element which has been selected by one of the full text
		operations or an ngram search.
	@param $config configuration element to configure the behaviour of the function
:)
declare function kwic:summarize($hit as element(), $config as element(config),
    $callback as function?) as element()* {
    let $expanded := util:expand($hit, "expand-xincludes=no")
	for $match in $expanded//exist:match
	return
		kwic:get-summary($expanded, $match, $config, $callback)
};


removing unused module

removing unused module

import module namespace csr_proc = "https://github.com/openhie/openinfoman/csr_proc";
import module namespace csd_webconf =  "https://github.com/openhie/openinfoman/csd_webconf";
import module namespace functx = 'http://www.functx.com';

declare namespace csd =  "urn:ihe:iti:csd:2013";
declare namespace fhir = "http://hl7.org/fhir";
declare variable $careServicesRequest as item() external;

(:
   The query will be executed against the root element of the CSD document.

   The dynamic context of this query has $careServicesRequest set to contain any of the search
   and limit paramaters as sent by the Service Finder
:)

let $search_name := "urn:ihe:iti:csd:2014:stored-function:facility-search"

let $careServicesSubRequest :=
  <csd:careServicesRequest>
    <csd:function urn="{$search_name}" resource="{$careServicesRequest/@resource}" base_url="{$careServicesRequest/@base_url}">
      <csd:requestParams>
         {
	  let $id := $careServicesRequest/fhir:_id/text()
	  return if (functx:all-whitespace($id)) then () else <csd:id entityID="{$id}"/>
	 }
         {
	  let $cn := $careServicesRequest/fhir:name/text()
	  return if (functx:all-whitespace($cn)) then () else  <csd:primaryName>{$cn}</csd:primaryName>
	 }
	 {
	  let $org := string($careServicesRequest/fhir:manaingOrganization/@value)
	  return if (functx:all-whitespace($org)) then () else <csd:organizations><csd:organization>{$org}</csd:organization></csd:organizations>
	 }

         {
	  let $t_start := $careServicesRequest/page/text()
	  return if (functx:is-a-number($t_start))
	    then
	      let $start := max((xs:int($t_start),1))
	      let $t_count := $careServicesRequest/fhir:_count/text()
	      let $count := if(functx:is-a-number($t_count)) then  max((xs:int($t_count),1)) else 50
	      let $startIndex := ($start - 1)*$count + 1
	      return <csd:start>{$startIndex}</csd:start>
	    else ()
	 }
	 {
	   let $count := $careServicesRequest/fhir:_count/text()
	   return
	      if(functx:is-a-number($count))
	      then  <csd:max>{max((xs:int($count),1))} </csd:max>
	      else ()
	 }
	 {
	  let $since := $careServicesRequest/fhir:_since/text()
	  return if (functx:all-whitespace($since)) then () else <csd:record updated="{$since}"/>
	 }
      </csd:requestParams>
    </csd:function>
  </csd:careServicesRequest>

let $contents := csr_proc:process_CSR_stored_results( /. , $careServicesSubRequest)

   (:note this is a CSD:csd element, not a document :)
return $contents/csd:facilityDirectory/csd:facility

import module namespace csr_proc = "https://github.com/openhie/openinfoman/csr_proc";
import module namespace csd_webconf =  "https://github.com/openhie/openinfoman/csd_webconf";
import module namespace fadpt = "https://github.com/openhie/openinfoman/adapter/fhir";
import module namespace functx = 'http://www.functx.com';

declare namespace csd =  "urn:ihe:iti:csd:2013";
declare namespace fhir = "http://hl7.org/fhir";
declare variable $careServicesRequest as item() external;

(:
   The query will be executed against the root element of the CSD document.

   The dynamic context of this query has $careServicesRequest set to contain any of the search
   and limit paramaters as sent by the Service Finder
:)

let $search_name := "urn:ihe:iti:csd:2014:stored-function:facility-search"

let $careServicesSubRequest :=
  <csd:careServicesRequest>
    <csd:function urn="{$search_name}" resource="{$careServicesRequest/@resource}" base_url="{$careServicesRequest/@base_url}">
      <csd:requestParams>
         {
	  let $id := $careServicesRequest/fhir:_id/text()
	  return if (functx:all-whitespace($id)) then () else <csd:id entityID="{$id}"/>
	 }
         {
	  let $cn := $careServicesRequest/fhir:name/text()
	  return if (functx:all-whitespace($cn)) then () else  <csd:primaryName>{$cn}</csd:primaryName>
	 }
	 {
	  let $org := string($careServicesRequest/fhir:manaingOrganization/@value)
	  return if (functx:all-whitespace($org)) then () else <csd:organizations><csd:organization>{$org}</csd:organization></csd:organizations>
	 }

         {
	  let $t_start := $careServicesRequest/page/text()
	  return if (functx:is-a-number($t_start))
	    then
	      let $start := max((xs:int($t_start),1))
	      let $t_count := $careServicesRequest/fhir:_count/text()
	      let $count := if(functx:is-a-number($t_count)) then  max((xs:int($t_count),1)) else 50
	      let $startIndex := ($start - 1)*$count + 1
	      return <csd:start>{$startIndex}</csd:start>
	    else ()
	 }
	 {
	   let $count := $careServicesRequest/fhir:_count/text()
	   return
	      if(functx:is-a-number($count))
	      then  <csd:max>{max((xs:int($count),1))} </csd:max>
	      else ()
	 }
	 {
	  let $since := $careServicesRequest/fhir:_since/text()
	  return if (functx:all-whitespace($since)) then () else <csd:record updated="{$since}"/>
	 }
      </csd:requestParams>
    </csd:function>
  </csd:careServicesRequest>

let $contents := csr_proc:process_CSR_stored_results( /. , $careServicesSubRequest)

   (:note this is a CSD:csd element, not a document :)
return $contents/csd:facilityDirectory/csd:facility


fixing turn logic

fixing turn logic

git-svn-id: a9772e85eaf7a5aa6a3955a90127d64c3c3bddcb@1460 d2cdb213-60f8-4f1b-b4b2-f6b941818144

xquery version "1.0";

declare namespace c4="http://falutin.net/connect4";

import module namespace layout="http://falutin.net/connect4/layout" at "layout.xqy";
import module namespace util="http://falutin.net/connect4/util" at "util.xqy";

declare variable $lux:http as document-node() external;

declare function c4:players($game as element(c4:game), $user as element(c4:player)?) {
  for $player at $i in $game/c4:players/c4:player
  let $pdisplay := if ($i eq 1) then {$player/string()} else $player/string()
  let $p := if ($user) then $pdisplay else
  {$pdisplay}
  return if ($i eq 1) then $p else (" vs. ", $p)
};

    (:
    lux:transform (<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"><xsl:template match="@*"><xsl:value-of select="format-dateTime(., '[M01]/[D01]/[Y0001] [h]:[m]')" /></xsl:template></xsl:stylesheet>,
    :)

declare function c4:main() {
  let $game-id := util:param ($lux:http, 'game')
  let $game := collection()/c4:game[@id=$game-id]
  let $player-name := util:param($lux:http, 'player')
  let $players := $game/c4:players/c4:player
  let $player := $players[.=$player-name]
  let $active := if (count($players) gt 1) then $game/c4:players/c4:player[1] else ()
  let $error := util:param($lux:http, "error")
  let $body :=

Game: {c4:players ($game, $player), " started at ", util:formatDateTime($game-id)}

{if ($error) then
{$error}
 else ()}

    <form action="play.xqy" method="post" name="play">
      <input type="hidden" name="player" value="{$player-name}" />
      <input type="hidden" name="game" value="{$game-id}" />
      <input type="hidden" id="col" name="col" value="" />
    </form>

{
      for $row in $game/c4:grid/c4:row
      return
{
      for $cell at $i in $row/c4:cell return
      {
        let $color := $cell/string()
        where $color
        return attribute style { concat ("background: ", $color, "};") }
      }
      }

    }

{
      if ($player is $active) then
        (attribute active { "true" }, "Your turn" )
      else if ($active) then
        concat($active, "'s turn")
      else
        "Waiting for some competition to show up"
    }

player={$player} active={$active}

    <textarea rows="8" cols="132">{$game}</textarea>

  return layout:outer('/connect4/view.xqy', $body)
};

c4:main()

xquery version "1.0";

declare namespace c4="http://falutin.net/connect4";

import module namespace layout="http://falutin.net/connect4/layout" at "layout.xqy";
import module namespace util="http://falutin.net/connect4/util" at "util.xqy";

declare variable $lux:http as document-node() external;

declare function c4:players($game as element(c4:game), $user as element(c4:player)?) {
  for $player at $i in $game/c4:players/c4:player
  let $p := if ($user) then string($player) else
  {string($player)}
  return if ($i eq 1) then $p else (" vs. ", $p)
};

    (:
    lux:transform (<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"><xsl:template match="@*"><xsl:value-of select="format-dateTime(., '[M01]/[D01]/[Y0001] [h]:[m]')" /></xsl:template></xsl:stylesheet>,
    :)

declare function c4:main() {
  let $game-id := util:param ($lux:http, 'game')
  let $game := collection()/c4:game[@id=$game-id]
  let $player-name := util:param($lux:http, 'player')
  let $player := $game/c4:players/c4:player[.=$player-name]
  let $active := $game/c4:players/c4:player[1]
  let $body :=

Game: {c4:players ($game, $player), " started at ", util:formatDateTime($game-id)}

    <form action="play.xqy" method="post" name="play">
      <input type="hidden" name="player" value="{$player-name}" />
      <input type="hidden" name="game" value="{$game-id}" />
      <input type="hidden" id="col" name="col" value="" />
    </form>

{
      for $row in $game/c4:grid/c4:row
      return
{
      for $cell at $i in $row/c4:cell return
      {
        let $color := $cell/string()
        where $color
        return attribute style { concat ("background: ", $color, "};") }
      }
      }

    }

{
      if ($player is $active) then
        (attribute active { "true" }, "Your turn" )
      else if ($active) then
        concat($active, "'s turn")
      else
        "Waiting for some competition to show up"
    }

player={$player} active={$active}

    <textarea rows="8" cols="132">{$game}</textarea>

  return layout:outer('/connect4/view.xqy', $body)
};

c4:main()


Restructured search page results for notes so that they are organised by note type, then document.

import module namespace notes="http://cefn.com/readinglog/notes" at "/home/cefn/Documents/highwire/participatory firmware design/readinglog/scripts/server/xquery/lib/notes.xq";
import module namespace template="http://cefn.com/readinglog/template" at "/home/cefn/Documents/highwire/participatory firmware design/readinglog/scripts/server/xquery/lib/template.xq";
declare default element namespace "http://www.w3.org/1999/xhtml";

declare variable $text external;

(: Defines the set of text nodes matching the requested string:)
declare variable $needles := $notes:allnotes//text()[contains(.,$text)];
declare variable $types := distinct-values(for $item in $needles return notes:type($item));
declare variable $hashes := distinct-values(for $item in $needles return notes:paperhash($item)); (:CH TODO use root nodes instead? :)

template:webpage(

Search {$text} ({count($needles)})

		{
			for $type in $types order by notes:typepriority($type) descending,$type return (

{$type}

,
				for $note in $needles[notes:type(.)=$type]/root() return

							{string($note//h1)}
							{notes:paperlinks($note)}

						{
							for $needle in $needles where $needle/root() is $note return

									{$needle}

						}

			)
		}

)

import module namespace notes="http://cefn.com/readinglog/notes" at "/home/cefn/Documents/highwire/participatory firmware design/readinglog/scripts/server/xquery/lib/notes.xq";
import module namespace template="http://cefn.com/readinglog/template" at "/home/cefn/Documents/highwire/participatory firmware design/readinglog/scripts/server/xquery/lib/template.xq";
declare default element namespace "http://www.w3.org/1999/xhtml";

declare variable $text external;

(: Defines the set of text nodes matching the requested string:)
declare variable $needles := notes:allnotes()//text()[contains(.,$text)];

template:webpage(

Search {$text} ({count($needles)})

		{
		for $needle in $needles return

				{$needle}
				{notes:paperlinks($needle)}

		}

)


[bugfix] Remove incorrect test

[bugfix] Remove incorrect test

(:
 : eXist-db Open Source Native XML Database
 : Copyright (C) 2001 The eXist-db Authors
 :
 : info@exist-db.org
 : http://www.exist-db.org
 :
 : This library is free software; you can redistribute it and/or
 : modify it under the terms of the GNU Lesser General Public
 : License as published by the Free Software Foundation; either
 : version 2.1 of the License, or (at your option) any later version.
 :
 : This library is distributed in the hope that it will be useful,
 : but WITHOUT ANY WARRANTY; without even the implied warranty of
 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 : Lesser General Public License for more details.
 :
 : You should have received a copy of the GNU Lesser General Public
 : License along with this library; if not, write to the Free Software
 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 :)
xquery version "3.0";

module namespace z="http://exist-db.org/testsuite/zips";

declare namespace util = "http://exist-db.org/xquery/util";

import module namespace test="http://exist-db.org/xquery/xqsuite" at "resource:org/exist/xquery/lib/xqsuite/xqsuite.xql";
import module namespace compression="http://exist-db.org/xquery/compression";

declare variable $z:collection-name := "unzip-test";
declare variable $z:collection := "/db/" || $z:collection-name;

declare variable $z:myFile-name := "!#$%()*+,-.:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{}~ ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñÑªº¿αßΓπΣσµτΦΘΩδ∞φε.xml";
declare variable $z:myFile-serialized := "<file/>";

(: declare UTF8 encoded binary :)
declare variable $z:myStaticUTF8ContentBase64 := xs:base64Binary("UEsDBBQACAgIAOBYl0UAAAAAAAAAAAAAAADCAAAAISMkJSgpKissLS46Oz0/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW11eX2FiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e31+IMOHw7zDqcOiw6TDoMOlw6fDqsOrw6jDr8Ouw6zDhMOFw4nDpsOGw7TDtsOyw7vDucO/w5bDnMKiwqPCpeKCp8aSw6HDrcOzw7rDscORwqrCusK/zrHDn86Tz4DOo8+DwrXPhM6mzpjOqc604oiez4bOtS54bWyzsa/IzVEoSy0qzszPs1Uy1DNQUkjNS85PycxLt1UKDXHTtVCyt+OyScvMSdW3AwBQSwcIwWbL3zAAAAAuAAAAUEsBAhQAFAAICAgA4FiXRcFmy98wAAAALgAAAMIAAAAAAAAAAAAAAAAAAAAAACEjJCUoKSorLC0uOjs9P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltdXl9hYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent9fiDDh8O8w6nDosOkw6DDpcOnw6rDq8Oow6/DrsOsw4TDhcOJw6bDhsO0w7bDssO7w7nDv8OWw5zCosKjwqXigqfGksOhw63Ds8O6w7HDkcKqwrrCv86xw5/Ok8+AzqPPg8K1z4TOps6YzqnOtOKIns+GzrUueG1sUEsFBgAAAAABAAEA8AAAACABAAAAAA==");

(: Verify zero byte sized resource :)
declare
	%test:assertEquals("")
function z:zeroByteBinResource() {
    let $collection-uri :="/db/"
    let $resource-name :="empty.txt"
    let $contents :=""

    let $empty-file := xmldb:store-as-binary($collection-uri, $resource-name, $contents)

    let $zip := compression:zip(<entry type="uri" name="{$collection-uri}">{$collection-uri||$resource-name}</entry>, true())

    return util:binary-to-string(util:binary-doc($collection-uri||$resource-name))
};

(:
 : eXist-db Open Source Native XML Database
 : Copyright (C) 2001 The eXist-db Authors
 :
 : info@exist-db.org
 : http://www.exist-db.org
 :
 : This library is free software; you can redistribute it and/or
 : modify it under the terms of the GNU Lesser General Public
 : License as published by the Free Software Foundation; either
 : version 2.1 of the License, or (at your option) any later version.
 :
 : This library is distributed in the hope that it will be useful,
 : but WITHOUT ANY WARRANTY; without even the implied warranty of
 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 : Lesser General Public License for more details.
 :
 : You should have received a copy of the GNU Lesser General Public
 : License along with this library; if not, write to the Free Software
 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 :)
xquery version "3.0";

module namespace z="http://exist-db.org/testsuite/zips";

declare namespace util = "http://exist-db.org/xquery/util";

import module namespace test="http://exist-db.org/xquery/xqsuite" at "resource:org/exist/xquery/lib/xqsuite/xqsuite.xql";
import module namespace compression="http://exist-db.org/xquery/compression";

declare variable $z:collection-name := "unzip-test";
declare variable $z:collection := "/db/" || $z:collection-name;

declare variable $z:myFile-name := "!#$%()*+,-.:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{}~ ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñÑªº¿αßΓπΣσµτΦΘΩδ∞φε.xml";
declare variable $z:myFile-serialized := "<file/>";

(: declare UTF8 encoded binary :)
declare variable $z:myStaticUTF8ContentBase64 := xs:base64Binary("UEsDBBQACAgIAOBYl0UAAAAAAAAAAAAAAADCAAAAISMkJSgpKissLS46Oz0/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW11eX2FiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e31+IMOHw7zDqcOiw6TDoMOlw6fDqsOrw6jDr8Ouw6zDhMOFw4nDpsOGw7TDtsOyw7vDucO/w5bDnMKiwqPCpeKCp8aSw6HDrcOzw7rDscORwqrCusK/zrHDn86Tz4DOo8+DwrXPhM6mzpjOqc604oiez4bOtS54bWyzsa/IzVEoSy0qzszPs1Uy1DNQUkjNS85PycxLt1UKDXHTtVCyt+OyScvMSdW3AwBQSwcIwWbL3zAAAAAuAAAAUEsBAhQAFAAICAgA4FiXRcFmy98wAAAALgAAAMIAAAAAAAAAAAAAAAAAAAAAACEjJCUoKSorLC0uOjs9P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltdXl9hYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent9fiDDh8O8w6nDosOkw6DDpcOnw6rDq8Oow6/DrsOsw4TDhcOJw6bDhsO0w7bDssO7w7nDv8OWw5zCosKjwqXigqfGksOhw63Ds8O6w7HDkcKqwrrCv86xw5/Ok8+AzqPPg8K1z4TOps6YzqnOtOKIns+GzrUueG1sUEsFBgAAAAABAAEA8AAAACABAAAAAA==");

declare
    %test:user("guest", "guest")
	%test:assertTrue
function z:fnZipUtf8Content() {
  let $z:myZipEntries := (<entry name="{$z:myFile-name}" type="xml">{$z:myFile-serialized}</entry>),
      $z:myZipContentUTF8Base64 := compression:zip($z:myZipEntries, true(), "", "UTF8")
  return
    ($z:myZipContentUTF8Base64 eq  $z:myStaticUTF8ContentBase64)
};

(: Verify zero byte sized resource :)
declare
	%test:assertEquals("")
function z:zeroByteBinResource() {
    let $collection-uri :="/db/"
    let $resource-name :="empty.txt"
    let $contents :=""

    let $empty-file := xmldb:store-as-binary($collection-uri, $resource-name, $contents)

    let $zip := compression:zip(<entry type="uri" name="{$collection-uri}">{$collection-uri||$resource-name}</entry>, true())

    return util:binary-to-string(util:binary-doc($collection-uri||$resource-name))
};


changed pr->hwr

changed pr->hwr

(:~
: This is a module conttaining configuration options for the openinfoman webap
: @version 1.0
: @see https://github.com/his-interop/openinfoman @see http://ihe.net
:
:)

module namespace csd_webconf = "https://github.com/his-interop/openinfoman/csd_webconf";
import module namespace request = "http://exquery.org/ns/request";

(:Import statements for stored queries.  Each module needs to be imported:)
import module namespace csd_bsq =  "https://github.com/his-interop/openinfoman/csd_bsq" at "csd_base_stored_queries.xqm";
(:import module namespace csd_hwrsq = "https://github.com/his-interop/openinfoman-hwr/csd_hwrsq" at  "csd_health_worker_registry_stored_queries.xqm";
import module namespace csd_orsq = "https://github.com/his-interop/openinfoman-hwr/csd_orsq" at  "csd_organization_registry_stored_queries.xqm";
import module namespace csd_frsq = "https://github.com/his-interop/openinfoman-hwr/csd_frsq" at  "csd_facility_registry_stored_queries.xqm";
import module namespace csd_srsq = "https://github.com/his-interop/openinfoman-hwr/csd_srsq" at  "csd_service_registry_stored_queries.xqm";
:)

(:import list of registered stored functions from modules :)
declare variable $csd_webconf:stored_functions :=
(
  $csd_bsq:stored_functions
(:  , $csd_hwrsq:stored_functions
  , $csd_orsq:stored_functions
  , $csd_frsq:stored_functions
  , $csd_srsq:stored_functions
:)
);

(:Database we are working on:)
declare variable $csd_webconf:db :=  'provider_directory';

(:BASE URL for openinforman. Overwrite this if you are proxying the openinfoman.   :)
declare variable $csd_webconf:baseurl :=  concat(request:scheme(),"://",request:hostname(),":",request:port(), "/");

(: DO NOT EDIT BELOW THIS LINE :)

declare function csd_webconf:get_stored_query($uuid) {
  let $method_name := csd_webconf:lookup_stored_method($uuid)
  return if ($method_name) then function-lookup( xs:QName($method_name), 2) else ()

};

declare function csd_webconf:get_updating_stored_query($uuid) {
  let $method_name := csd_webconf:lookup_updating_stored_method($uuid)
  return if ($method_name) then function-lookup( xs:QName($method_name), 2) else ()

};

declare function csd_webconf:has_stored_query($uuid) {
  let $sq := csd_webconf:get_stored_query($uuid)
  return if (exists($sq) )  then true() else false()
};

declare function csd_webconf:has_updating_stored_query($uuid) {
  let $sq := csd_webconf:get_updating_stored_query($uuid)
  return if (exists($sq) )  then true() else false()
};

declare function csd_webconf:execute_stored_query($doc,$uuid,$requestParams) {
  let $method := csd_webconf:get_stored_query($uuid)
  return if (exists($method ))
  then
     $method($requestParams,$doc)
  else
    ()

};

declare function csd_webconf:lookup_stored_method($uuid)
{
  $csd_webconf:stored_functions[@uuid = $uuid and not(@updating)]/@method
};

declare function csd_webconf:lookup_updating_stored_method($uuid)
{
  $csd_webconf:stored_functions[@uuid = $uuid and @updating = 1]/@method
};

declare function csd_webconf:lookup_stored_content_type($uuid)
{
   ($csd_webconf:stored_functions[@uuid = $uuid]/@content-type , "text/xml")[1]
};

(:~
: This is a module conttaining configuration options for the openinfoman webap
: @version 1.0
: @see https://github.com/his-interop/openinfoman @see http://ihe.net
:
:)

module namespace csd_webconf = "https://github.com/his-interop/openinfoman/csd_webconf";
import module namespace request = "http://exquery.org/ns/request";

(:Import statements for stored queries.  Each module needs to be imported:)
import module namespace csd_bsq =  "https://github.com/his-interop/openinfoman/csd_bsq" at "csd_base_stored_queries.xqm";
(:import module namespace csd_prsq = "https://github.com/his-interop/openinfoman-pr/csd_prsq" at  "csd_provider_registry_stored_queries.xqm";
import module namespace csd_orsq = "https://github.com/his-interop/openinfoman-pr/csd_orsq" at  "csd_organization_registry_stored_queries.xqm";
import module namespace csd_frsq = "https://github.com/his-interop/openinfoman-pr/csd_frsq" at  "csd_facility_registry_stored_queries.xqm";
import module namespace csd_srsq = "https://github.com/his-interop/openinfoman-pr/csd_srsq" at  "csd_service_registry_stored_queries.xqm";
:)

(:import list of registered stored functions from modules :)
declare variable $csd_webconf:stored_functions :=
(
  $csd_bsq:stored_functions
(:  , $csd_prsq:stored_functions
  , $csd_orsq:stored_functions
  , $csd_frsq:stored_functions
  , $csd_srsq:stored_functions
:)
);

(:Database we are working on:)
declare variable $csd_webconf:db :=  'provider_directory';

(:BASE URL for openinforman. Overwrite this if you are proxying the openinfoman.   :)
declare variable $csd_webconf:baseurl :=  concat(request:scheme(),"://",request:hostname(),":",request:port(), "/");

(: DO NOT EDIT BELOW THIS LINE :)

declare function csd_webconf:get_stored_query($uuid) {
  let $method_name := csd_webconf:lookup_stored_method($uuid)
  return if ($method_name) then function-lookup( xs:QName($method_name), 2) else ()

};

declare function csd_webconf:get_updating_stored_query($uuid) {
  let $method_name := csd_webconf:lookup_updating_stored_method($uuid)
  return if ($method_name) then function-lookup( xs:QName($method_name), 2) else ()

};

declare function csd_webconf:has_stored_query($uuid) {
  let $sq := csd_webconf:get_stored_query($uuid)
  return if (exists($sq) )  then true() else false()
};

declare function csd_webconf:has_updating_stored_query($uuid) {
  let $sq := csd_webconf:get_updating_stored_query($uuid)
  return if (exists($sq) )  then true() else false()
};

declare function csd_webconf:execute_stored_query($doc,$uuid,$requestParams) {
  let $method := csd_webconf:get_stored_query($uuid)
  return if (exists($method ))
  then
     $method($requestParams,$doc)
  else
    ()

};

declare function csd_webconf:lookup_stored_method($uuid)
{
  $csd_webconf:stored_functions[@uuid = $uuid and not(@updating)]/@method
};

declare function csd_webconf:lookup_updating_stored_method($uuid)
{
  $csd_webconf:stored_functions[@uuid = $uuid and @updating = 1]/@method
};

declare function csd_webconf:lookup_stored_content_type($uuid)
{
   ($csd_webconf:stored_functions[@uuid = $uuid]/@content-type , "text/xml")[1]
};