removed obsolete example

removed obsolete example

xquery version "3.0";

(:
 : Copyright 2006-2009 The FLWOR Foundation.
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
:)

(:~
 : This module defines a set of functions which manage documents that
 : are persisted in the store. Specifically, it contains functions
 : to put, remove, list, or retrieve documents.
 :
 :
Please refer to our documentation for
 : more information about the lifecycle management and manipulation of documents.

 :
 : @see Data Lifecycle
 : @see http://www.zorba-xquery.com/errors
 :
 : @author Matthias Brantner, David Graf, Till Westmann, Markos Zaharioudakis
 :
 : @project store/documents
 :)
module namespace doc = "http://www.zorba-xquery.com/modules/store/dynamic/documents";

declare namespace zerr = "http://www.zorba-xquery.com/errors";
declare namespace err = "http://www.w3.org/2005/xqt-errors";
declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "2.0";

(:~
 : Adds a document to the store. The document is bound to the URI
 : given as first parameter. This URI is the name of the document and
 : can be used by the doc:document() function to retrieve
 : the document from the store.
 :
 : The semantics of the function is similar to the fn:put function. The difference
 : is that this function accepts only document nodes as parameters.
 : Please note that this function does not try to fetch the document from the
 : external resource identified by $uri. Instead, the file or http-client
 : modules can be used to retrieve the resource as string and fn:parse-xml can be used
 : to parse the string returning a document.
 :
 : @param $uri The URI of the document. If a relative URI is given, the URI
 :        is made absolute using the static base URI of the module.
 : @param $doc The document node to be added to the store.
 :
 : @return an empty XDM instance and a pending update list which, when
 :         applied, adds the given document to the store.
 :
 : @error zerr:ZAPI0020 if the store already contains a document for the given $uri.
 : @error err:FODC0004 if the given URI is not valid or couldn't be absolutized.
 :
 :)
declare updating function doc:put(
  $uri as xs:string,
  $doc as document-node()) external;

(:~
 : Removes the document with the given URI from the store.
 :
 : @param $uri The URI of the document to remove. If a relative URI is given,
 : the URI is made absolute using the static base URI of the module.

 : @return Returns an empty XDM instance and a pending update list which, when
 :         applied, removes the document bound to the given URI.
 :
 : @error zerr:ZXQD0002 if no document with the given URI exists in the store.
 : @error err:FODC0004 if the given URI is not valid or couldn't be absolutized.
 :)
declare updating function doc:remove($uri as xs:string) external;

(:~
 : Returns the document with the given URI from the store.
 :
 : The difference to fn:doc is that this function does not attempt to retrieve
 : the resource from the file system or the web before returning it.
 : Its only responsibility is to return the document from the store that was
 : bound using fn:put or doc:put.
 :
 : @param $uri The URI of the document to retrieve. If a relative URI is given,
 : the URI is made absolute using the static base URI of the module.
 :
 : @return Returns the document bound to the given URI.
 :
 : @error zerr:ZXQD0002 if no document with the given URI exists in the store.
 : @error err:FODC0004 if the given URI is not valid or couldn't be absolutized.
 :)
declare function doc:document($uri as xs:string) as document-node() external;

(:~
 : Retrieves a sequence of URIs of documents bound in the store.
 :
 : @return The list of URIs of the available documents in the store. The empty
 :         sequence is returned if not document exists in the store.
 :)
declare function doc:available-documents() as xs:string* external;

(:~
 : Returns true if a document with the given URI exists in the store.
 :
 : @param $uri The URI of the document to check for. If a relative URI is given,
 : the URI is made absolute using the static base URI of the module.
 :
 : @return Returns true if a document with the given URI exists in the store or false otherwise.
 :
 : @error err:FODC0004 if the given URI is not valid or couldn't be absolutized.
 :)
declare function doc:is-available-document($uri as xs:string) as xs:boolean external;

xquery version "3.0";

(:
 : Copyright 2006-2009 The FLWOR Foundation.
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
:)

(:~
 : This module defines a set of functions which manage documents that
 : are persisted in the store. Specifically, it contains functions
 : to put, remove, list, or retrieve documents.
 :
 :
Please refer to our documentation for
 : more information about the lifecycle management and manipulation of documents.

 :
 : @see Data Lifecycle
 : @see http://www.zorba-xquery.com/errors
 :
 : @author Matthias Brantner, David Graf, Till Westmann, Markos Zaharioudakis
 :
 : @project store/documents
 :)
module namespace doc = "http://www.zorba-xquery.com/modules/store/dynamic/documents";

declare namespace zerr = "http://www.zorba-xquery.com/errors";
declare namespace err = "http://www.w3.org/2005/xqt-errors";
declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "2.0";

(:~
 : Adds a document to the store. The document is bound to the URI
 : given as first parameter. This URI is the name of the document and
 : can be used by the doc:document() function to retrieve
 : the document from the store.
 :
 : The semantics of the function is similar to the fn:put function. The difference
 : is that this function accepts only document nodes as parameters.
 : Please note that this function does not try to fetch the document from the
 : external resource identified by $uri. Instead, the file or http-client
 : modules can be used to retrieve the resource as string and fn:parse-xml can be used
 : to parse the string returning a document.
 :
 : @param $uri The URI of the document. If a relative URI is given, the URI
 :        is made absolute using the static base URI of the module.
 : @param $doc The document node to be added to the store.
 :
 : @return an empty XDM instance and a pending update list which, when
 :         applied, adds the given document to the store.
 :
 : @error zerr:ZAPI0020 if the store already contains a document for the given $uri.
 : @error err:FODC0004 if the given URI is not valid or couldn't be absolutized.
 :
 : @example test/rbkt/Queries/zorba/store/retrieve.xq
 :)
declare updating function doc:put(
  $uri as xs:string,
  $doc as document-node()) external;

(:~
 : Removes the document with the given URI from the store.
 :
 : @param $uri The URI of the document to remove. If a relative URI is given,
 : the URI is made absolute using the static base URI of the module.

 : @return Returns an empty XDM instance and a pending update list which, when
 :         applied, removes the document bound to the given URI.
 :
 : @error zerr:ZXQD0002 if no document with the given URI exists in the store.
 : @error err:FODC0004 if the given URI is not valid or couldn't be absolutized.
 :)
declare updating function doc:remove($uri as xs:string) external;

(:~
 : Returns the document with the given URI from the store.
 :
 : The difference to fn:doc is that this function does not attempt to retrieve
 : the resource from the file system or the web before returning it.
 : Its only responsibility is to return the document from the store that was
 : bound using fn:put or doc:put.
 :
 : @param $uri The URI of the document to retrieve. If a relative URI is given,
 : the URI is made absolute using the static base URI of the module.
 :
 : @return Returns the document bound to the given URI.
 :
 : @error zerr:ZXQD0002 if no document with the given URI exists in the store.
 : @error err:FODC0004 if the given URI is not valid or couldn't be absolutized.
 :)
declare function doc:document($uri as xs:string) as document-node() external;

(:~
 : Retrieves a sequence of URIs of documents bound in the store.
 :
 : @return The list of URIs of the available documents in the store. The empty
 :         sequence is returned if not document exists in the store.
 :)
declare function doc:available-documents() as xs:string* external;

(:~
 : Returns true if a document with the given URI exists in the store.
 :
 : @param $uri The URI of the document to check for. If a relative URI is given,
 : the URI is made absolute using the static base URI of the module.
 :
 : @return Returns true if a document with the given URI exists in the store or false otherwise.
 :
 : @error err:FODC0004 if the given URI is not valid or couldn't be absolutized.
 :)
declare function doc:is-available-document($uri as xs:string) as xs:boolean external;


Removed import for semantics marklogic module.  Added spawning bfs support (aka future), Added Direction So you can flip SPO to OPS when relationships are one way

xquery version "1.0-ml";

module namespace transitive = "http://marklogic.com/transitive";

import module namespace sem = "http://marklogic.com/semantics"
    at "/MarkLogic/semantics.xqy";
declare option xdmp:mapping "false";
declare variable $DEFAULT-PARALLEL-THREADS := 4;
declare variable $THREAD-PARAM := "threads";
declare variable $CONCURRENT-PARAM := "concurrent";
declare variable $AXIS-PARAM  := "axis";

(:~
 :Takes a function and a sequence and converts the calls into a future
 xdmp:s
~:)
declare function transitive:future(
  $funct as function(*),
  $sequence as item()*,
  $threads as xs:integer
) {
  let $remainder :=
    if($sequence) then fn:count($sequence) else 0
  let $splits :=
    if($remainder) then fn:ceiling($remainder div $threads) else fn:ceiling(fn:count($sequence) div $threads)
  let $future :=
    for $s in (1 to $threads)
    let $inner-seq := fn:subsequence($sequence,(($s - 1) * $threads) + 1,$splits)
    return
      xdmp:spawn-function(
          function() {
             xdmp:lazy($inner-seq ! $funct(.))
          },
          <options xmlns="xdmp:eval">
            <priority>higher</priority>
            <result>true</result>
          </options>
     )
  return $future
};

(:~
 : Copied BFS implementation from /semantics/semantics-impl.xqy
~:)
declare function transitive:bfs(
  $s as sem:iri*,
  $limit as xs:integer,
  $adjV
) {
  transitive:bfs($s,$limit,$adjV,map:map())
};
(:~
 : Copied BFS implementation from /semantics/semantics-impl.xqy
~:)
declare function transitive:bfs(
  $s as sem:iri*,
  $limit as xs:integer,
  $adjV,
  $options as map:map
) {
    let $visited := map:map()
    let $_ := $s ! map:put($visited, ., fn:true())
    return transitive:bfs-inner($visited, $s, $limit, $adjV)
};
(:~
 : Copied BFS implementation from /semantics/semantics-impl.xqy
~:)
declare function transitive:bfs-inner(
    $visited as map:map,
    $queue as sem:iri*,
    $limit as xs:integer,
    $adjacentVertices
) {
   transitive:bfs-inner($visited,$queue,$limit,$adjacentVertices,map:map())
};
(:~
 : Copied BFS implementation from /semantics/semantics-impl.xqy
~:)
declare function transitive:bfs-inner(
    $visited as map:map,
    $queue as sem:iri*,
    $limit as xs:integer,
    $adjacentVertices,
    $options as map:map
    ) {
    if (fn:empty($queue) or $limit eq 0)
    then map:keys($visited) ! sem:iri(.) (: do something with results :)
    else
        let $thingstoEnqueue :=
          if(map:get($options,$CONCURRENT-PARAM) = fn:true())
          then
           let $func :=
             function($v) {
               if (map:contains($visited, $v))
               then ()
               else (map:put($visited, $v, fn:true()), $v)
             }
           let $threads := (map:get($options,$THREAD-PARAM),4)[1]
           return
              transitive:future($func, $adjacentVertices($queue),$threads)
          else
            for $v in $adjacentVertices($queue)
            return
                if (map:contains($visited, $v))
                then ()
                else (map:put($visited, $v, fn:true()), $v)
        return transitive:bfs-inner($visited, $thingstoEnqueue, $limit - 1, $adjacentVertices,$options)
};

(:~
 : Transitively searches up the predicate s->o
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:transitive-down(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
  transitive:transitive-down($seeds,$preds,$limit,cts:and-query(()))
};

(:~
 : Traverses up a relationship up subject->object via a predicate
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:transitive-down(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
   transitive:bfs($seeds, $limit, function($s) { cts:triples($s,$preds,(),(),$filter) ! sem:triple-object(.) })
};

(:~
 : Traverses up a relationship up object->subject via a predicate
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:transitive-up(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
   transitive:transitive-up($seeds,$preds,$limit,())
};

(:~
 : Traverses up a relationship up object->subject via a predicate
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
 declare function transitive:transitive-up(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
   transitive:bfs($seeds, $limit, function($o) { cts:triples((),$preds,$o,(),(),$filter) ! sem:triple-subject(.) })
};

(:~
 : Traverses up a relationship to find all ancestors excluding the $seed values
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:ancestors(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
   transitive:ancestors($seeds,$preds,$limit,())[fn:not((. = $seeds))]
};

(:~
 : Traverses up a relationship to find all ancestors excluding the $seed values
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:ancestors(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
   transitive:transitive-up($seeds,$preds,$limit,$filter)[fn:not((. = $seeds))]
};

(:~
 : Traverses up a relationship to find all ancestors including the $seed values
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:ancestors-or-self(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
   transitive:ancestors-or-self($seeds,$preds,$limit,())
};

(:~
 : Traverses up a relationship to find all ancestors including the $seed values
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:ancestors-or-self(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
 transitive:transitive-up($seeds,$preds,$limit,$filter)
};

(:~
 : Returns all descendant without seeds
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:descendants(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
   transitive:descendants($seeds,$preds,$limit,())
};
(:~
 : Returns all descendant without seeds
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:descendants(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
   transitive:transitive-down($seeds,$preds,$limit,$filter)[fn:not((. = $seeds))]
};
(:~
 : Returns all descendant with seed
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:descendants-or-self(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
   transitive:descendants-or-self($seeds,$preds,$limit,())
};
(:~
 : Returns all descendant without seeds
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:descendants-or-self(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
   transitive:transitive-down($seeds,$preds,$limit,$filter)
};

(:~
 : Returns all ancestors and descendants from a given node without the seeds
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:ancestors-descendants(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {(
    transitive:ancestors-descendants($seeds,$preds,$limit,())
)};
(:~
 : Returns all ancestors and descendants from a given node without the seeds
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:ancestors-descendants(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {(
  transitive:transitive-up($seeds,$preds,$limit,$filter)[fn:not((. = $seeds))],
  transitive:transitive-down($seeds,$preds,$limit,$filter)[fn:not((. = $seeds))]
)};

(:~
 : Returns all ancestors and descendants from a given node and the seeds
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:ancestors-descendants-or-self(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
   transitive:ancestors-descendants-or-self($seeds,$preds,$limit,())
};
(:~
 : Returns all ancestors and descendants and self from a starting seed
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:ancestors-descendants-or-self(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
(
  transitive:transitive-up($seeds,$preds,$limit,$filter),
  transitive:transitive-down($seeds,$preds,$limit,$filter)
)
};

(:~
 : Returns all Roots given a predicate.
 : The logic is that a parent should never be the object of a given subject via a predicate
 : @param $predicates - An IRI Predicate to filter relationships by.
 :)
declare function transitive:roots($predicates as sem:iri) {
   transitive:roots($predicates,(),())
};
(:~
 : Returns all Roots given a predicate.
 : The logic is that a parent should never be the object of a given subject via a predicate
 : @param $predicates - An IRI Predicate to filter relationships by.
 : @param $subject - a base subject if not passed will not bind subject
 :)
 declare function transitive:roots($predicates as sem:iri,$subject as sem:iri?) {
   transitive:roots($predicates,(),())
};
(:~
 : Returns 1 level deep child relationshps
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:children(
   $seeds as sem:iri*,
   $preds as sem:iri*
) {
   transitive:descendants($seeds,$preds,1,())
};

(:~
 : Returns 1 level deep child relationships
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $query - Predicate Query to filter while traversing
 :)
declare function transitive:children(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $query as cts:query?
) {
   transitive:descendants($seeds,$preds,1,$query)
};

(:~
 : Returns 1 level up ancestor relationships
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 :)
declare function transitive:ancestor(
   $seeds as sem:iri*,
   $preds as sem:iri*
) {
   transitive:ancestor($seeds,$preds,())
};

(:~
 : Returns 1 level up ancestor relationships
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $query - A cts:query to bind to function
 :)
declare function transitive:ancestor(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $query as cts:query?
) {
   transitive:ancestors($seeds,$preds,1,$query)
};

(:~
 : Returns all sibling subjects from a given node.  Logic is lookup 1 level then traverse-down
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 :)
 declare function transitive:siblings(
   $seeds as sem:iri*,
   $preds as sem:iri*
) {
   transitive:siblings($seeds,$preds,())
};

(:~
 :  Returns all sibling subjects from a given node.  Logic is lookup 1 level then traverse-down
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $query - A cts:query to bind to function
 :)
 declare function transitive:siblings(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $query as cts:query?
) {
   let $parents := transitive:ancestor($seeds,$preds,$query)
   return (
     transitive:ancestors($parents,$preds,1,$query),
     transitive:descendants($parents,$preds,1,$query)
     )[fn:not((. = $seeds))]
};

(:Transitive Extensions:)
(:~
 : Returns all Roots given a predicate.
 : The logic is that a parent should never be the object of a given subject via a predicate
 : @param $predicates - An IRI Predicate to filter relationships by.
 : @param $subject - a base subject if not passed will not bind subject
 : @param $query - a cts:query to limit scope of roots returned
 :)
declare function transitive:roots(
    $predicates as sem:iri,
    $subject as sem:iri?,
    $query as cts:query?
) {
 sem:sparql-values('select distinct ?subject {
    ?subject ?predicate [] .
    optional {
        ?parent ?predicate ?subject .
    }
    filter(! ?parent)
}',
 map:new((
    map:entry("predicate",$predicates),
    if($subject) then map:entry("subject",$subject) else ()
 )),
 (),
 $query
 ) ! map:get(.,"subject")
};
(:~
 :  Returns a treemap of the structure
 :)
declare function transitive:tree(
  $predicate as sem:iri
) as json:object* {
    let $roots := map:get(transitive:roots($predicate),"subject")
    return
      transitive:traverser($roots,$predicate)
};
(:~
 : Takes a seed subject/predicate and creates a nested tree structure traversing the predicate based on a seed subject.
 :)
declare function transitive:traverser(
    $subject as sem:iri,
    $predicate as sem:iri*
) {
  transitive:traverser($subject,$predicate,())
};

(:~
 : Takes a seed subject/predicate and creates a nested tree structure traversing the predicate based on a seed subject.
 : @param $subject - Starting Subject to traverse
 : @param $predicate - The relationship to use for the traversal
 :)
declare function transitive:traverser(
    $subject as sem:iri,
    $predicate as sem:iri*,
    $query as cts:query?
) {
   transitive:traverser($subject,$predicate,$query,map:map())
};

(:~
 : Takes a seed subject/predicate and creates a nested tree structure traversing the predicate based on a seed subject.
 : @param $subject - Starting Subject to traverse
 : @param $predicate - The relationship to use for the traversal
 : @param $query - A cts:query to constraint all results by
 : @param $property-map - A map:map where the key is the iri and the value can be a label to replace it with.
 :  Options:
 :    map:entry("property",map:map) -  property-map(map:map) is present, the keys will be converted mapped triples.
                                       If a string is specified as value then key  will be the value.
 :    map:entry("recursive",xs:boolean) - Determines if the traversal is recursive
 :    map:entry("maxdepth",xs:integer)  - Determines how deep to traverse before it stops
 :                                       -1 - infinitely
 :                                        0  - Is only the current node
 :                                     * - Any depth level
 :    map:entry("callback",function(*)) - Is a callback applied to each traversed link. The callback assumes the following signature:
 :            function($node,[$options,[depth]]) {}
 :)
declare function transitive:traverser(
    $subject as sem:iri*,
    $predicate as sem:iri*,
    $query as cts:query?,
    $options as map:map?
) {
    transitive:traverser-inner($subject,$predicate,$query,$options,0)
};

(:~
 : Transitive Inner wraps traversal library and supports the recursive calls.
 : @param $subject - Is the current subject to traverse
 : @param $predicate - The relationship to use for the traversal
 : @param $query - A cts:query to constraint all results by
 : @param $options - options node as a map:map
 : @param $depth - Is the current depth of the traverser
~:)
declare function transitive:traverser-inner(
    $subject as sem:iri*,
    $predicate as sem:iri*,
    $query as cts:query?,
    $options as map:map?,
    $depth as xs:integer
) {
   (:Get Options:)
   let $recursive := (map:get($options,"recursive"),fn:true())[1]
   let $max-depth := (map:get($options,"maxdepth"),-1)[1]
   let $has-depth := if($max-depth eq -1) then fn:true() else if($depth lt $max-depth) then fn:true() else fn:false()
   let $callback  := map:get($options,"callback")
   let $yield     := map:get($options,"yield") eq fn:true()
   let $node-name := (map:get($options,"child-name"),"nodes")[1]
   (:Setter Vars:)
   let $child :=
     switch(map:get($options,$AXIS-PARAM))
     case "ancestor" return transitive:ancestors($subject,$predicate,1)
     case "sibling"  return transitive:siblings($subject,$predicate)
     default return transitive:descendants($subject,$predicate,1)
   let $jso := json:object()
   let $_ := (
       map:put($jso,"subject",$subject),
       map:put($jso,"predicate",$predicate)
   )
   let $properties  :=
     if(fn:exists(map:get($options,"property")))
     then
        let $property-map := map:get($options,"property")
        let $keys := map:keys($property-map) ! sem:iri(.)
        let $triples := cts:triples($subject,$keys)
        return
          for $triple in $triples
          let $predicate := sem:triple-predicate($triple)
          let $label := map:get($property-map,$predicate)[. ne ""]
          let $label := if($label) then $label else $predicate
          let $object := sem:triple-object($triple)
          let $value :=
            typeswitch($object)
            case rdf:langString return fn:concat(fn:data($object),"^^@",rdf:langString-language($object))
            default return $object
          return
            map:put($jso,$label,(map:get($jso,$label),$value))
     else ()
   let $apply :=
        if(fn:exists($callback))
        then
            let $arity := fn:function-arity($callback)
            return
              switch($arity)
                case 1 return $callback($jso)
                case 2 return $callback($jso,$options)
                case 3 return $callback($jso,$options,$depth)
                default return fn:error(xs:QName("CALLBACK-ARITY-MISMATCH"),"No Supported Callback interface with arity",$arity)
        else ()
   let $child-name :=
     if($node-name instance of function(*))
     then $node-name($jso)
     else $node-name
   let $_ :=
       if($recursive and $has-depth and fn:not($yield))
       then map:put($jso,$child-name,json:to-array($child ! transitive:traverser-inner(.,$predicate,$query,$options,$depth + 1)))
       else ()
   return $jso
};

xquery version "1.0-ml";

module namespace transitive = "http://marklogic.com/transitive";

import module namespace semi = "http://marklogic.com/semantics/impl"
      at "/MarkLogic/semantics/sem-impl.xqy";

import module namespace sem = "http://marklogic.com/semantics"
    at "/MarkLogic/semantics.xqy";

(:~
 : Transitively searches up the predicate s->o
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:transitive-down(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
  transitive:transitive-down($seeds,$preds,$limit,cts:and-query(()))
};

(:~
 : Traverses up a relationship up subject->object via a predicate
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:transitive-down(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
   semi:bfs($seeds, $limit, function($s) { cts:triples($s,$preds,(),(),$filter) ! sem:triple-object(.) })
};

(:~
 : Traverses up a relationship up object->subject via a predicate
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:transitive-up(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
   transitive:transitive-up($seeds,$preds,$limit,())
};

(:~
 : Traverses up a relationship up object->subject via a predicate
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
 declare function transitive:transitive-up(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
   semi:bfs($seeds, $limit, function($o) { cts:triples((),$preds,$o,(),(),$filter) ! sem:triple-subject(.) })
};

(:~
 : Traverses up a relationship to find all ancestors excluding the $seed values
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:ancestors(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
   transitive:ancestors($seeds,$preds,$limit,())[fn:not((. = $seeds))]
};

(:~
 : Traverses up a relationship to find all ancestors excluding the $seed values
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:ancestors(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
   transitive:transitive-up($seeds,$preds,$limit,$filter)[fn:not((. = $seeds))]
};

(:~
 : Traverses up a relationship to find all ancestors including the $seed values
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:ancestors-or-self(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
   transitive:ancestors-or-self($seeds,$preds,$limit,())
};

(:~
 : Traverses up a relationship to find all ancestors including the $seed values
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:ancestors-or-self(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
 transitive:transitive-up($seeds,$preds,$limit,$filter)
};

(:~
 : Returns all descendant without seeds
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:descendants(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
   transitive:descendants($seeds,$preds,$limit,())
};
(:~
 : Returns all descendant without seeds
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:descendants(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
   transitive:transitive-down($seeds,$preds,$limit,$filter)[fn:not((. = $seeds))]
};
(:~
 : Returns all descendant with seed
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:descendants-or-self(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
   transitive:descendants-or-self($seeds,$preds,$limit,())
};
(:~
 : Returns all descendant without seeds
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:descendants-or-self(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
   transitive:transitive-down($seeds,$preds,$limit,$filter)
};

(:~
 : Returns all ancestors and descendants from a given node without the seeds
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:ancestors-descendants(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {(
    transitive:ancestors-descendants($seeds,$preds,$limit,())
)};
(:~
 : Returns all ancestors and descendants from a given node without the seeds
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:ancestors-descendants(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {(
  transitive:transitive-up($seeds,$preds,$limit,$filter)[fn:not((. = $seeds))],
  transitive:transitive-down($seeds,$preds,$limit,$filter)[fn:not((. = $seeds))]
)};

(:~
 : Returns all ancestors and descendants from a given node and the seeds
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:ancestors-descendants-or-self(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer
) {
   transitive:ancestors-descendants-or-self($seeds,$preds,$limit,())
};
(:~
 : Returns all ancestors and descendants and self from a starting seed
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 : @param $filter - A cts:query that will be passed as a condition of the relationship
 :)
declare function transitive:ancestors-descendants-or-self(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $limit as xs:integer,
   $filter as cts:query?
) {
(
  transitive:transitive-up($seeds,$preds,$limit,$filter)
  transitive:transitive-down($seeds,$preds,$limit,$filter)
)
};

(:~
 : Returns all Roots given a predicate.
 : The logic is that a parent should never be the object of a given subject via a predicate
 : @param $predicates - An IRI Predicate to filter relationships by.
 :)
declare function transitive:roots($predicates as sem:iri) {
   transitive:roots($predicates,(),())
};
(:~
 : Returns all Roots given a predicate.
 : The logic is that a parent should never be the object of a given subject via a predicate
 : @param $predicates - An IRI Predicate to filter relationships by.
 : @param $subject - a base subject if not passed will not bind subject
 :)
 declare function transitive:roots($predicates as sem:iri,$subject as sem:iri?) {
   transitive:roots($predicates,(),())
};
(:~
 : Returns 1 level deep child relationshps
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $limit - The recursive depth or degrees to traverse before stopping.
 :)
declare function transitive:children(
   $seeds as sem:iri*,
   $preds as sem:iri*
) {
   transitive:descendants($seeds,$preds,1,())
};

(:~
 : Returns 1 level deep child relationships
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $query - Predicate Query to filter while traversing
 :)
declare function transitive:children(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $query as cts:query?
) {
   transitive:descendants($seeds,$preds,1,$query)
};

(:~
 : Returns 1 level up ancestor relationships
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 :)
declare function transitive:ancestor(
   $seeds as sem:iri*,
   $preds as sem:iri*
) {
   transitive:ancestor($seeds,$preds,())
};

(:~
 : Returns 1 level up ancestor relationships
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $query - A cts:query to bind to function
 :)
declare function transitive:ancestor(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $query as cts:query?
) {
   transitive:ancestors($seeds,$preds,1,$query)
};

(:~
 : Returns all sibling subjects from a given node.  Logic is lookup 1 level then traverse-down
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 :)
 declare function transitive:siblings(
   $seeds as sem:iri*,
   $preds as sem:iri*
) {
   transitive:siblings($seeds,$preds,())
};

(:~
 :  Returns all sibling subjects from a given node.  Logic is lookup 1 level then traverse-down
 : @param $seeds - The seed subject
 : @param $preds - The seed predicate
 : @param $query - A cts:query to bind to function
 :)
 declare function transitive:siblings(
   $seeds as sem:iri*,
   $preds as sem:iri*,
   $query as cts:query?
) {
   let $parents := transitive:ancestor($seeds,$preds,$query)
   return
     transitive:children($parents,$preds,$query)[fn:not((. = $seeds))]
};

(:Transitive Extensions:)
(:~
 : Returns all Roots given a predicate.
 : The logic is that a parent should never be the object of a given subject via a predicate
 : @param $predicates - An IRI Predicate to filter relationships by.
 : @param $subject - a base subject if not passed will not bind subject
 : @param $query - a cts:query to limit scope of roots returned
 :)
declare function transitive:roots(
    $predicates as sem:iri,
    $subject as sem:iri?,
    $query as cts:query?
) {
 sem:sparql-values('select distinct ?subject {
    ?subject ?predicate [] .
    optional {
        ?parent ?predicate ?subject .
    }
    filter(! ?parent)
}',
 map:new((
    map:entry("predicate",$predicates),
    if($subject) then map:entry("subject",$subject) else ()
 )),
 (),
 $query
 ) ! map:get(.,"subject")
};
(:~
 :  Returns a treemap of the structure
 :)
declare function transitive:tree(
  $predicate as sem:iri
) as json:object* {
    let $roots := map:get(transitive:roots($predicate),"subject")
    return
      transitive:traverser($roots,$predicate)
};
(:~
 : Takes a seed subject/predicate and creates a nested tree structure traversing the predicate based on a seed subject.
 :)
declare function transitive:traverser(
    $subject as sem:iri,
    $predicate as sem:iri*
) {
  transitive:traverser($subject,$predicate,())
};

(:~
 : Takes a seed subject/predicate and creates a nested tree structure traversing the predicate based on a seed subject.
 : @param $subject - Starting Subject to traverse
 : @param $predicate - The relationship to use for the traversal
 :)
declare function transitive:traverser(
    $subject as sem:iri,
    $predicate as sem:iri*,
    $query as cts:query?
) {
   transitive:traverser($subject,$predicate,$query,map:map())
};

(:~
 : Takes a seed subject/predicate and creates a nested tree structure traversing the predicate based on a seed subject.
 : @param $subject - Starting Subject to traverse
 : @param $predicate - The relationship to use for the traversal
 : @param $query - A cts:query to constraint all results by
 : @param $property-map - A map:map where the key is the iri and the value can be a label to replace it with.
 :  Options:
 :    map:entry("property",map:map) -  property-map(map:map) is present, the keys will be converted mapped triples.
                                       If a string is specified as value then key  will be the value.
 :    map:entry("recursive",xs:boolean) - Determines if the traversal is recursive
 :    map:entry("maxdepth",xs:integer)  - Determines how deep to traverse before it stops
 :                                       -1 - infinitely
 :                                        0  - Is only the current node
 :                                     * - Any depth level
 :    map:entry("callback",function(*)) - Is a callback applied to each traversed link. The callback assumes the following signature:
 :            function($node,[$options,[depth]]) {}
 :)
declare function transitive:traverser(
    $subject as sem:iri,
    $predicate as sem:iri*,
    $query as cts:query?,
    $options as map:map?
) {
    transitive:traverser-inner($subject,$predicate,$query,$options,0)
};

(:~
 : Transitive Inner wraps traversal library and supports the recursive calls.

 : @param $subject - Is the current subject to traverse
 : @param $predicate - The relationship to use for the traversal
 : @param $query - A cts:query to constraint all results by
 : @param $options - options node as a map:map
 : @param $depth - Is the current depth of the traverser
~:)
declare function transitive:traverser-inner(
    $subject as sem:iri,
    $predicate as sem:iri*,
    $query as cts:query?,
    $options as map:map?,
    $depth as xs:integer
) {
   (:Get Options:)
   let $recursive := (map:get($options,"recursive"),fn:true())[1]
   let $max-depth := (map:get($options,"maxdepth"),-1)[1]
   let $has-depth := if($max-depth eq -1) then fn:true() else if($depth lt $max-depth) then fn:true() else fn:false()
   let $callback  := map:get($options,"callback")
   let $yield     := map:get($options,"yield") eq fn:true()
   (:Setter Vars:)
   let $child := transitive:descendants($subject,$predicate,1)
   let $jso := json:object()
   let $_ := (
       map:put($jso,"subject",$subject),
       map:put($jso,"predicate",$predicate)
   )
   let $properties  :=
     if(fn:exists(map:get($options,"property")))
     then
        let $property-map := map:get($options,"property")
        let $keys := map:keys($property-map) ! sem:iri(.)
        let $triples := cts:triples($subject,$keys)
        return
          for $triple in $triples
          let $predicate := sem:triple-predicate($triple)
          let $label := map:get($property-map,$predicate)[. ne ""]
          let $label := if($label) then $label else $predicate
          let $object := sem:triple-object($triple)
          let $value :=
            typeswitch($object)
            case rdf:langString return fn:concat(fn:data($object),"^^@",rdf:langString-language($object))
            default return $object
          return
            map:put($jso,$label,(map:get($jso,$label),$value))
     else ()
   let $apply :=
        if(fn:exists($callback))
        then
            let $arity := fn:function-arity($callback)
            return
              switch($arity)
                case 1 return $callback($jso)
                case 2 return $callback($jso,$options)
                case 3 return $callback($jso,$options,$depth)
                default return fn:error(xs:QName("CALLBACK-ARITY-MISMATCH"),"No Supported Callback interface with arity",$arity)
        else ()
   let $_ :=
       if($recursive and $has-depth and fn:not($yield))
       then map:put($jso,"nodes",json:to-array($child ! transitive:traverser-inner(.,$predicate,$query,$options,$depth + 1)))
       else ()
   return $jso
};


Adds a harder puzzle to solve

Adds a harder puzzle to solve

declare namespace fn = "sudoku";

declare variable $boardHard as xs:integer+ :=
  ( 5,3,0, 0,7,0, 0,0,0,
    6,0,0, 1,9,5, 0,0,0,
    0,9,8, 0,0,0, 0,6,0,
    8,0,0, 0,6,0, 0,0,3,
    4,0,0, 8,0,3, 0,0,1,
    7,0,0, 0,2,0, 0,0,6,
    0,6,0, 0,0,0, 2,8,0,
    0,0,0, 4,1,9, 0,0,5,
    0,0,0, 0,8,0, 0,7,9 );

declare variable $boardEasy as xs:integer+ :=
  ( 1,0,0, 3,0,0, 6,0,0,
    0,2,0, 5,0,0, 0,0,4,
    0,0,9, 0,0,0, 5,2,0,
    0,0,0, 9,6,3, 0,0,0,
    7,1,6, 0,0,0, 0,0,0,
    0,0,0, 0,8,0, 0,4,0,
    9,0,0, 0,0,5, 3,0,7,
    8,0,0, 4,0,6, 0,0,0,
    3,5,0, 0,0,0, 0,0,1 );

declare variable $rowStarts as xs:integer+ :=
  ( 1, 10, 19, 28, 37, 46, 55, 64, 73 );

declare variable $groups as xs:integer+ :=
  ( 1,1,1, 2,2,2, 3,3,3,
    1,1,1, 2,2,2, 3,3,3,
    1,1,1, 2,2,2, 3,3,3,
    4,4,4, 5,5,5, 6,6,6,
    4,4,4, 5,5,5, 6,6,6,
    4,4,4, 5,5,5, 6,6,6,
    7,7,7, 8,8,8, 9,9,9,
    7,7,7, 8,8,8, 9,9,9,
    7,7,7, 8,8,8, 9,9,9 );

declare function fn:getRow($board as xs:integer+,
                           $index as xs:integer) as xs:integer+
{
  let $rowStart := floor(($index - 1) div 9) * 9
  return
  $board[position() > $rowStart and position() <= $rowStart + 9] => one-or-more()
};

declare function fn:getCol($board as xs:integer+,
                           $index as xs:integer) as xs:integer+
{
  let $gap := ($index - 1) mod 9
    , $colIndexes := $rowStarts ! (. + $gap)
  return
  $board[position() = $colIndexes] => one-or-more()
};

declare function fn:getGroup($board as xs:integer+,
                             $index as xs:integer) as xs:integer+
{
  (
    let $group := $groups[$index]
    for $y at $x in $board
    where $groups[$x] = $group
    return
    $y
  ) => one-or-more()
};

declare function fn:getAllowedValues($board as xs:integer+,
                                     $index as xs:integer) as xs:integer*
{
  let $existingValues := ( fn:getRow($board, $index),
                           fn:getCol($board, $index),
                           fn:getGroup($board, $index) )
  for $x in (1 to 9)
  return
  if (not($x = $existingValues)) then
    $x
  else
    ()
};

declare function fn:tryValues($board as xs:integer+,
                              $emptyCells as xs:integer+,
                              $possibleValues as xs:integer+) as xs:integer*
{
  let $index as xs:integer := head($emptyCells)
    , $newBoard as xs:integer+ := ( subsequence($board, 1, $index - 1),
                                    head($possibleValues),
                                    subsequence($board, $index + 1) )
    , $result as xs:integer* := fn:populateValues($newBoard, tail($emptyCells))
  return
  if (empty($result)) then
    if (count($possibleValues) > 1) then
      fn:tryValues( $board,
                    $emptyCells,
                    tail($possibleValues) => one-or-more() )
    else ()
  else
    $result
};

declare function fn:populateValues($board as xs:integer+,
                                   $emptyCells as xs:integer*) as xs:integer*
{
  if (not(empty($emptyCells))) then
    let $index as xs:integer := head($emptyCells)
      , $possibleValues as xs:integer* := fn:getAllowedValues($board, $index)
                                          => distinct-values()
    return
    if (count($possibleValues) > 1) then
      fn:tryValues( $board,
                    $emptyCells => one-or-more(),
                    $possibleValues => one-or-more() )
    else if (count($possibleValues) = 1) then
      let $newBoard as xs:integer+ := ( subsequence($board, 1, $index - 1),
                                        exactly-one($possibleValues[1]),
                                        subsequence($board, $index + 1) )
      return
      fn:populateValues($newBoard, tail($emptyCells))
    else ()
  else
    $board
};

declare function fn:solveSudoku($startBoard as xs:integer+) as xs:integer+
{
  let $filter := function($i, $p) { if ($i eq 0) then $p else () }
    , $emptyCells as xs:integer* := for-each-pair($startBoard, 1 to 81, $filter)
    , $endBoard as xs:integer* := fn:populateValues($startBoard, $emptyCells)
  return
  if (empty($endBoard)) then
    $startBoard
  else
    one-or-more($endBoard)
};

declare function fn:drawResult($board as xs:integer+) as element()
{
  element html {
    element head {
      element title {
        'Sudoku - XSLT'
      },
      element style {
        'table { border-collapse: collapse; border: 1px solid black; } '||
        'td { padding: 10px; } '||
        '.norm { border-left: 1px solid #CCC; border-top: 1px solid #CCC; } '||
        '.csep { border-left: 1px solid black; } '||
        '.rsep { border-top: 1px solid black; }'
      }
    },
    element body {
      fn:drawBoard($board)
    }
  }
};

declare function fn:drawBoard($board as xs:integer+) as element()
{
  element table {
    for $i in 1 to 9
    return
    element tr {
      for $j at $p in 1 to 9
      let $pos := (($i - 1) * 9) + $j
      return
      element td {
        attribute class {
          if ($p mod 3 = 1) then 'csep' else 'norm',
          if ($i mod 3 = 1) then 'rsep' else 'norm'
        },
        $board[$pos]
      }
    }
  }
};

fn:solveSudoku($boardHard) => fn:drawResult()
(: fn:solveSudoku($boardEasy) => fn:drawResult() :)

declare namespace fn = "sudoku";

declare variable $boardHard as xs:integer+ :=
  ( 5,3,0, 0,7,0, 0,0,0,
    6,0,0, 1,9,5, 0,0,0,
    0,9,8, 0,0,0, 0,6,0,
    8,0,0, 0,6,0, 0,0,3,
    4,0,0, 8,0,3, 0,0,1,
    7,0,0, 0,2,0, 0,0,6,
    0,6,0, 0,0,0, 2,8,0,
    0,0,0, 4,1,9, 0,0,5,
    0,0,0, 0,8,0, 0,7,9 );

declare variable $boardEasy as xs:integer+ :=
  ( 1,0,0, 3,0,0, 6,0,0,
    0,2,0, 5,0,0, 0,0,4,
    0,0,9, 0,0,0, 5,2,0,
    0,0,0, 9,6,3, 0,0,0,
    7,1,6, 0,0,0, 0,0,0,
    0,0,0, 0,8,0, 0,4,0,
    9,0,0, 0,0,5, 3,0,7,
    8,0,0, 4,0,6, 0,0,0,
    3,5,0, 0,0,0, 0,0,1 );

declare variable $rowStarts as xs:integer+ :=
  ( 1, 10, 19, 28, 37, 46, 55, 64, 73 );

declare variable $groups as xs:integer+ :=
  ( 1,1,1, 2,2,2, 3,3,3,
    1,1,1, 2,2,2, 3,3,3,
    1,1,1, 2,2,2, 3,3,3,
    4,4,4, 5,5,5, 6,6,6,
    4,4,4, 5,5,5, 6,6,6,
    4,4,4, 5,5,5, 6,6,6,
    7,7,7, 8,8,8, 9,9,9,
    7,7,7, 8,8,8, 9,9,9,
    7,7,7, 8,8,8, 9,9,9 );

declare function fn:getRow($board as xs:integer+,
                           $index as xs:integer) as xs:integer+
{
  let $rowStart := floor(($index - 1) div 9) * 9
  return
  $board[position() > $rowStart and position() <= $rowStart + 9] => one-or-more()
};

declare function fn:getCol($board as xs:integer+,
                           $index as xs:integer) as xs:integer+
{
  let $gap := ($index - 1) mod 9
    , $colIndexes := $rowStarts ! (. + $gap)
  return
  $board[position() = $colIndexes] => one-or-more()
};

declare function fn:getGroup($board as xs:integer+,
                             $index as xs:integer) as xs:integer+
{
  (
    let $group := $groups[$index]
    for $y at $x in $board
    where $groups[$x] = $group
    return
    $y
  ) => one-or-more()
};

declare function fn:getAllowedValues($board as xs:integer+,
                                     $index as xs:integer) as xs:integer*
{
  let $existingValues := ( fn:getRow($board, $index),
                           fn:getCol($board, $index),
                           fn:getGroup($board, $index) )
  for $x in (1 to 9)
  return
  if (not($x = $existingValues)) then
    $x
  else
    ()
};

declare function fn:tryValues($board as xs:integer+,
                              $emptyCells as xs:integer+,
                              $possibleValues as xs:integer+) as xs:integer*
{
  let $index as xs:integer := head($emptyCells)
    , $newBoard as xs:integer+ := ( subsequence($board, 1, $index - 1),
                                    head($possibleValues),
                                    subsequence($board, $index + 1) )
    , $result as xs:integer* := fn:populateValues($newBoard, tail($emptyCells))
  return
  if (empty($result)) then
    if (count($possibleValues) > 1) then
      fn:tryValues( $board,
                    $emptyCells,
                    tail($possibleValues) => one-or-more() )
    else ()
  else
    $result
};

declare function fn:populateValues($board as xs:integer+,
                                   $emptyCells as xs:integer*) as xs:integer*
{
  if (not(empty($emptyCells))) then
    let $index as xs:integer := head($emptyCells)
      , $possibleValues as xs:integer* := fn:getAllowedValues($board, $index)
                                          => distinct-values()
    return
    if (count($possibleValues) > 1) then
      fn:tryValues( $board,
                    $emptyCells => one-or-more(),
                    $possibleValues => one-or-more() )
    else if (count($possibleValues) = 1) then
      let $newBoard as xs:integer+ := ( subsequence($board, 1, $index - 1),
                                        exactly-one($possibleValues[1]),
                                        subsequence($board, $index + 1) )
      return
      fn:populateValues($newBoard, tail($emptyCells))
    else ()
  else
    $board
};

declare function fn:solveSudoku($startBoard as xs:integer+) as xs:integer+
{
  let $filter := function($i, $p) { if ($i eq 0) then $p else () }
    , $emptyCells as xs:integer* := for-each-pair($startBoard, 1 to 81, $filter)
    , $endBoard as xs:integer* := fn:populateValues($startBoard, $emptyCells)
  return
  if (empty($endBoard)) then
    $startBoard
  else
    one-or-more($endBoard)
};

declare function fn:drawResult($board as xs:integer+) as element()
{
  element html {
    element head {
      element title {
        'Sudoku - XSLT'
      },
      element style {
        'table { border-collapse: collapse; border: 1px solid black; } '||
        'td { padding: 10px; } '||
        '.norm { border-left: 1px solid #CCC; border-top: 1px solid #CCC; } '||
        '.csep { border-left: 1px solid black; } '||
        '.rsep { border-top: 1px solid black; }'
      }
    },
    element body {
      fn:drawBoard($board)
    }
  }
};

declare function fn:drawBoard($board as xs:integer+) as element()
{
  element table {
    for $i in 1 to 9
    return
    element tr {
      for $j at $p in 1 to 9
      let $pos := (($i - 1) * 9) + $j
      return
      element td {
        attribute class {
          if ($p mod 3 = 1) then 'csep' else 'norm',
          if ($i mod 3 = 1) then 'rsep' else 'norm'
        },
        $board[$pos]
      }
    }
  }
};

fn:solveSudoku($boardEasy) => fn:drawResult()


fix caching. test was failing

fix caching. test was failing

(:
  Copyright 2012-2016 MarkLogic Corporation

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
:)
xquery version "1.0-ml";

module namespace hul = "http://marklogic.com/data-hub/hub-utils-lib";

import module namespace admin = "http://marklogic.com/xdmp/admin"
  at "/MarkLogic/admin.xqy";

import module namespace cvt = "http://marklogic.com/cpf/convert"
      at "/MarkLogic/conversion/convert.xqy";

declare namespace mt = "http://marklogic.com/xdmp/mimetypes";

declare namespace hub = "http://marklogic.com/data-hub";

declare option xdmp:mapping "false";

declare variable $_cache := map:map();
(:~
 : determine if a module exists or not
 :
 : @param $uri - the uri of the module to check
 : @return - true if exists, false otherwise
 :)
declare function hul:module-exists($uri) as xs:boolean
{
  hul:run-in-modules(function() {
    fn:doc-available($uri)
  })
};

declare function hul:run-in-modules($func as function() as item()*)
{
  xdmp:invoke-function($func,
    <options xmlns="xdmp:eval">
      <database>{xdmp:modules-database()}</database>
    </options>)
};

declare function hul:from-map-cache($name, $func)
{
  if (map:contains($_cache, $name)) then
    map:get($_cache, $name)
  else
    let $value := $func()
    let $_ := map:put($_cache, $name, $value)
    return
      $value
};

declare function hul:from-field-cache($name, $func)
{
  hul:from-field-cache($name, $func, ())
};

declare function hul:set-field-cache($name, $value, $duration as xs:dayTimeDuration?)
{
  (
    if (fn:exists($duration)) then (
      let $_ := xdmp:set-server-field($name || "-refresh-date", fn:current-dateTime() + $duration)
      return
        xdmp:set-server-field($name, $value)
    )
    else
      xdmp:set-server-field($name, $value)
  )[1]
};

declare function hul:invalidate-field-cache($name)
{
  xdmp:set-server-field($name, ()),
  xdmp:set-server-field($name || "-refresh-date", ())
};

declare function hul:from-field-cache($name, $func, $duration as xs:dayTimeDuration?)
{
  let $existing := xdmp:get-server-field($name)
  let $refresh-date := xdmp:get-server-field($name || "-refresh-date")
  return
    if (fn:exists($existing)) then
      if (fn:exists($refresh-date) and ($refresh-date < fn:current-dateTime())) then (
        xdmp:log("invalidating cache: " || $name),
        hul:set-field-cache($name, $func(), $duration)
      )
      else
        $existing[1]
    else
      if (fn:exists($duration)) then
        hul:set-field-cache($name, $func(), $duration)
      else
        hul:set-field-cache($name, $func(), $duration)
};

(:~
 : returns a list of file extensions for executable modules
 :
 : @return - extensions (xqy,xqy,sjs,...)
 :)
declare function hul:get-exe-extensions() as xs:string+
{
  hul:from-field-cache("exe-extensions", function() {
    let $config := admin:get-configuration()
    let $names := ("application/vnd.marklogic-xdmp", "application/vnd.marklogic-javascript", "application/xslt+xml")
    return
      admin:mimetypes-get($config)[mt:name = $names]/*:extensions/fn:tokenize(fn:string(.), " ")
  })
};

declare function hul:get-xqy-extensions() as xs:string*
{
  hul:from-field-cache("xqy-extensions", function() {
    let $config := admin:get-configuration()
    let $names := "application/vnd.marklogic-xdmp"
    return
      admin:mimetypes-get($config)[mt:name = $names]/*:extensions/fn:tokenize(fn:string(.), " ")
  })
};

declare function hul:get-sjs-extensions() as xs:string*
{
  hul:from-field-cache("sjs-extensions", function() {
    let $config := admin:get-configuration()
    let $names := "application/vnd.marklogic-javascript"
    return
      admin:mimetypes-get($config)[mt:name = $names]/*:extensions/fn:tokenize(fn:string(.), " ")
  })
};

declare function hul:get-xslt-extensions() as xs:string*
{
  hul:from-field-cache("xslt-extensions", function() {
    let $config := admin:get-configuration()
    let $names := "application/xslt+xml"
    return
      admin:mimetypes-get($config)[mt:name = $names]/*:extensions/fn:tokenize(fn:string(.), " ")
  })
};

declare function hul:get-file-from-uri($uri)
{
  cvt:basename($uri)
};

declare function hul:get-file-extension($filename as xs:string)
{
  fn:replace($filename, ".*\.([^\.]+)$", "$1")
};

(:
  Copyright 2012-2016 MarkLogic Corporation

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
:)
xquery version "1.0-ml";

module namespace hul = "http://marklogic.com/data-hub/hub-utils-lib";

import module namespace admin = "http://marklogic.com/xdmp/admin"
  at "/MarkLogic/admin.xqy";

import module namespace cvt = "http://marklogic.com/cpf/convert"
      at "/MarkLogic/conversion/convert.xqy";

declare namespace mt = "http://marklogic.com/xdmp/mimetypes";

declare namespace hub = "http://marklogic.com/data-hub";

declare option xdmp:mapping "false";

declare variable $_cache := map:map();
(:~
 : determine if a module exists or not
 :
 : @param $uri - the uri of the module to check
 : @return - true if exists, false otherwise
 :)
declare function hul:module-exists($uri) as xs:boolean
{
  hul:run-in-modules(function() {
    fn:doc-available($uri)
  })
};

declare function hul:run-in-modules($func as function() as item()*)
{
  xdmp:invoke-function($func,
    <options xmlns="xdmp:eval">
      <database>{xdmp:modules-database()}</database>
    </options>)
};

declare function hul:from-map-cache($name, $func)
{
  if (map:contains($_cache, $name)) then
    map:get($_cache, $name)
  else
    let $value := $func()
    let $_ := map:put($_cache, $name, $value)
    return
      $value
};

declare function hul:from-field-cache($name, $func)
{
  hul:from-field-cache($name, $func, ())
};

declare function hul:set-field-cache($name, $value, $duration as xs:dayTimeDuration?)
{
  (
    if (fn:exists($duration)) then (
      xdmp:set-server-field($name, $value),
      xdmp:set-server-field($name || "-refresh-date", fn:current-dateTime() + $duration)
    )
    else
      xdmp:set-server-field($name, $value)
  )[1]
};

declare function hul:invalidate-field-cache($name)
{
  xdmp:set-server-field($name, ()),
  xdmp:set-server-field($name || "-refresh-date", ())
};

declare function hul:from-field-cache($name, $func, $duration as xs:dayTimeDuration?)
{
  let $existing := xdmp:get-server-field($name)
  let $refresh-date := xdmp:get-server-field($name || "-refresh-date")
  return
    if (fn:exists($existing)) then
      if (fn:exists($refresh-date) and ($refresh-date < fn:current-dateTime())) then (
        xdmp:log("invalidating cache: " || $name),
        hul:set-field-cache($name, $func(), $duration)
      )
      else
        $existing[1]
    else
      if (fn:exists($duration)) then
        hul:set-field-cache($name, $func(), $duration)
      else
        hul:set-field-cache($name, $func(), $duration)
};

(:~
 : returns a list of file extensions for executable modules
 :
 : @return - extensions (xqy,xqy,sjs,...)
 :)
declare function hul:get-exe-extensions() as xs:string+
{
  hul:from-field-cache("exe-extensions", function() {
    let $config := admin:get-configuration()
    let $names := ("application/vnd.marklogic-xdmp", "application/vnd.marklogic-javascript", "application/xslt+xml")
    return
      admin:mimetypes-get($config)[mt:name = $names]/*:extensions/fn:tokenize(fn:string(.), " ")
  })
};

declare function hul:get-xqy-extensions() as xs:string*
{
  hul:from-field-cache("xqy-extensions", function() {
    let $config := admin:get-configuration()
    let $names := "application/vnd.marklogic-xdmp"
    return
      admin:mimetypes-get($config)[mt:name = $names]/*:extensions/fn:tokenize(fn:string(.), " ")
  })
};

declare function hul:get-sjs-extensions() as xs:string*
{
  hul:from-field-cache("sjs-extensions", function() {
    let $config := admin:get-configuration()
    let $names := "application/vnd.marklogic-javascript"
    return
      admin:mimetypes-get($config)[mt:name = $names]/*:extensions/fn:tokenize(fn:string(.), " ")
  })
};

declare function hul:get-xslt-extensions() as xs:string*
{
  hul:from-field-cache("xslt-extensions", function() {
    let $config := admin:get-configuration()
    let $names := "application/xslt+xml"
    return
      admin:mimetypes-get($config)[mt:name = $names]/*:extensions/fn:tokenize(fn:string(.), " ")
  })
};

declare function hul:get-file-from-uri($uri)
{
  cvt:basename($uri)
};

declare function hul:get-file-extension($filename as xs:string)
{
  fn:replace($filename, ".*\.([^\.]+)$", "$1")
};


Updated and merged Matthias' documentation code example for the parse-fragment() function

xquery version "3.0";

(:
 : Copyright 2006-2010 The FLWOR Foundation.
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
:)

(:~
 :

 : This module provides functions for reading XML files from string inputs.
 : It allows reading of well-formed XML documents as well as well-formed
 : external parsed entities, described by
 : XML 1.0 Well-Formed
 : Parsed Entities. The functions can also perform Schema and DTD
 : validation of the input documents.
 :

 :
 :
The following example parses a sequence of XML elements and returns
 : them in a streaming fashion - each at a time:

 :
 :
 : import module namespace parse-xml = "http://www.zorba-xquery.com/modules/xml";
 : import schema namespace opt = "http://www.zorba-xquery.com/modules/xml-options";
 : parse-xml:parse(
 :   "<from1>Jani</from1><from2>Jani</from2><from3>Jani</from3>",
 :   <opt:options>
 :     <opt:parse-external-parsed-entity/>
 :   </opt:options>
 : )
 :
 :
 :
Another useful option allows to skip an arbitrary number of levels
 : before returning a sequence of nodes as shown in the following example:

 :
 :
 : import module namespace parse-xml = "http://www.zorba-xquery.com/modules/xml";
 : import schema namespace opt = "http://www.zorba-xquery.com/modules/xml-options";
 : parse-xml:parse(
 :   "<root>
 :     <from1>Jani1</from1>
 :     <from2>Jani2</from2>
 :     <from3>Jani3</from3>
 :   </root>",
 :   <opt:options>
 :     <opt:parse-external-parsed-entity opt:skip-root-nodes="1"/>
 :   </opt:options>
 : )
 :
 :
 : @see XML 1.0 Well-Formed
 : Parsed Entities
 : @see
 : fn:parse-xml() function in XPath and XQuery Functions and Operators 3.0
 : @see LibXml2 parser
 :
 : @author Nicolae Brinza
 : @project data processing/data converters
 :
 :)
module namespace parse-xml = "http://www.zorba-xquery.com/modules/xml";

import schema namespace parse-xml-options = "http://www.zorba-xquery.com/modules/xml-options";

declare namespace zerr = "http://www.zorba-xquery.com/errors";
declare namespace err = "http://www.w3.org/xqt-errors";

declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "2.0";

(:~
 : A function to parse XML files and fragments (i.e.
 : external general parsed
 : entities). The functions takes two arguments: the first one is the
 : string to be parsed and the second argument is an <options/> element that
 : passes a list of options to the parsing function. They are described below.
 : The options element must conform to the xml-options.xsd schema. Some of these
 : will be passed to the underlying library (LibXml2) and further documentation
 : for them can be found at
 : LibXml2 parser.
 :
 : The list of available options:
 :
 :

 :

 : <base-uri/> - the element must have a "value" attribute, which will provide
 : the baseURI that will be used as the baseURI for every node returned by this
 : function.
 :

 :
 :

 : <no-error/> - if present, the option will disable fatal error processing. Any
 : failure to parse or validate the input in the requested manner will result
 : in the function returning an empty sequence and no error will raised.
 :

 :
 :

 : <strip-boundary-space/> - if present, it will enable the removal of blank nodes from the input.
 :

 :
 :

 : <schema-validate/> - if present, it will request that the input string be Schema
 : validated. The element accepts an attribute named "mode" which can have two
 : values: "strict and "lax". Enabling the option will produce a result that is
 : equivalent to processing the input with the option disabled, and then copying
 : the result using the XQuery "validate strict|lax" expression. This option can not
 : be used together with either the <DTD-validate/> or the <parse-external-parsed-entity/>
 : option. Doing so will raise a zerr:ZXQD0003 error.
 :

 :
 :

 : <DTD-validate/> - the option will enable the DTD-based validation. If this
 : option is enabled and the input references a DTD, then the input must be a
 : well-formed and DTD-valid XML document. The <DTD-load/> option must be used for
 : external DTD files to be loaded. If the option is enabled and the input does
 : not reference a DTD then the option is ignored. If the option is disabled, the
 : input is not required to reference a DTD and if it does reference a DTD then
 : the DTD is ignored for validation purposes. This option can not
 : be used together with either the <schema-validate/> or the <parse-external-parsed-entity>
 : option. Doing so will raise a zerr:ZXQD0003 error.
 :

 :
 :

 : <DTD-load/> - if present, it will enable loading of external DTD files.
 :

 :
 :

 : <default-DTD-attributes/> - if present, it will enable the default DTD attributes.
 :

 :
 :

 : <parse-external-parsed-entity/> - if present, it will enable the processing of XML
 : external entities. If the option
 : is enabled, the input must conform to the syntax extParsedEnt (production
 : [78] in XML 1.0, see
 : Well-Formed Parsed Entities). The result of the function call is a list
 : of nodes corresponding to the top-level components of the content of the
 : external entity: that is, elements, processing instructions, comments, and
 : text nodes. CDATA sections and character references are expanded, and
 : adjacent characters are merged so the result contains no adjacent text
 : nodes. If the option is disabled, the input must be a well-formed XML
 : document conforming to the Document production
 : (production [1] in XML 1.0).
 : This option can not be used together with either the <schema-validate/> or the <DTD-validate/>
 : option. Doing so will raise a zerr:ZXQD0003 error.
 : The <parse-external-parsed-entity/> option has two parameters, given by attributes. The first
 : attribute is "skip-root-nodes" and it can have a non-negative value. Specifying the paramter
 : tells the parser to skip the given number of root nodes and return only their children. E.g.
 : skip-root-nodes="1" is equivalent to parse-xml($xml-string)/node()/node() . skip-root-nodes="2" is equivalent
 : to parse-xml($xml-string)/node()/node()/node() , etc. The second attribute is "skip-top-level-text-nodes" with a
 : boolean value. Specifying "true" will tell the parser to skip top level text nodes, returning
 : only the top level elements, comments, PIs, etc. This parameter works in combination with
 : the "skip-root-nodes" paramter, thus top level text nodes are skipped after "skip-root-nodes" has
 : been applied.
 :

 :
 :

 : <substitute-entities/> - if present, it will enable the XML entities substitutions.
 :

 :
 :

 : <remove-redundant-ns/> - if present, the parser will remove redundant namespaces declarations.
 :

 :
 :

 : <no-CDATA/> - if present, the parser will merge CDATA nodes as text nodes.
 :

 :
 :

 : <xinclude-substitutions/> - if present, it will enable the XInclude substitutions.
 :

 :
 :

 : <no-xinclude-nodes/> - if present, the parser will not generate XInclude START/END nodes.
 :

 :
 :

 :
 :
 : @param $xml-string The string that holds the XML to be parsed. If empty,
 :                    the function will return an empty sequence
 : @param $options The options for the parsing
 : @return The parsed XML as a document node or a list of nodes, or an empty
 :         sequence.
 :
 : @error zerr:ZXQD0003 The error will be raised if the options to the function
 :                     are inconsistent.
 :
 : @error err:FODC0006 The error will be raised if the input string is not a
 :                     valid XML document or fragment (external general parsed
 :                     entity) or if DTD validation was enabled and the
 :                     document has not passed it.
 :
 : @error err:XQDY0027 The error will be raised if schema validation was enabled
 :                     and the input document has not passed it or if the parsing options are not
 :                     conformant to the xml-options.xsd schema.
 :
 : @example test/rbkt/Queries/zorba/parsing_and_serializing/parse-xml-fragment-03.xq
 : @example test/rbkt/Queries/zorba/parsing_and_serializing/parse-xml-fragment-01.xq
 : @example test/rbkt/Queries/zorba/parsing_and_serializing/parse-xml-fragment-07.xq
 :
 :)
declare function parse-xml:parse(
  $xml-string as xs:string?,
  $options as element()?) as node()* external;

(:~
 :
 : Note: this function is included for backwards compatibility purposes. It
 : is recommended that you use the parse-xml:parse() function instead.
 :
 :
 : A function to parse XML files and fragments (i.e.
 : external general parsed
 : entities). The functions takes two arguments: the first one is the
 : string to be parsed and the second argument is a flags string
 : (eEdDsSlLwWfF]*(;[\p{L}]*)?) selecting the options described below.
 :

 :

 :
 : The convention for the flags is that a lower-case letter enables
 : an option and the corresponding upper-case letter disables it; specifying
 : both is an error; specifying neither leaves it implementation-defined
 : whether the option is enabled or disabled. Specifying the same option twice
 : is not an error, but specifying inconsistent options (for example "eE") is
 : a dynamic error. The options are:
 :
 :

 :

 : eE - enables or disables processing of XML external entities. If the option
 : is enabled, the input must conform to the syntax extParsedEnt (production
 : [78] in XML 1.0, see
 : Well-Formed Parsed Entities). The result of the function call is a list
 : of nodes corresponding to the top-level components of the content of the
 : external entity: that is, elements, processing instructions, comments, and
 : text nodes. CDATA sections and character references are expanded, and
 : adjacent characters are merged so the result contains no adjacent text
 : nodes. If this option is enabled, none of the options d, s, or l may be
 : enabled. If the option is disabled, the input must be a well-formed XML
 : document conforming to the Document production
 : (production [1] in XML 1.0).
 :

 :
 :

 : dD - enables or disables DTD-based validation. If this option is enabled and
 : the input references a DTD, then the input must be a well-formed and
 : DTD-valid XML document. If the option is enabled and the input does not
 : reference a DTD then the option is ignored. If the option is disabled, the
 : input is not required to reference a DTD and if it does reference a DTD then
 : the DTD is ignored for validation purposes (though it will still be read for
 : purposes such as expanding entity references and identifying ID attributes).
 :

 :
 :

 : sS - enables or disables strict XSD-based validation. If this option is
 : enabled, the result is equivalent to processing the input with the option
 : disabled, and then copying the result using the XQuery "validate strict"
 : expression.
 :

 :
 :

 : lL - enables or disables lax XSD-based validation. If this option is enabled,
 : the result is equivalent to processing the input with the option disabled,
 : and then copying the result using the XQuery "validate lax " expression.
 :

 :
 :

 : wW - enables or disables whitespace stripping. If the option is enabled,
 : any whitespace-only text nodes that remain after any DTD-based or XSD-based
 : processing are stripped from the input; if it is disabled, such
 : whitespace-only text nodes are retained.
 :

 :
 :

 : fF - enables or disables fatal error processing. If fatal error processing
 : is enabled, then any failure to parse the input in the manner requested
 : results in a dynamic error. If fatal error processing is disabled, then any
 : failure to parse the input (and also, in the case of fn:doc, a failure to
 : obtain the input by dereferencing the supplied URI) results in the function
 : returning an empty sequence and raising no error.
 :

 :

 :
 : @param $xml-string The string that holds the XML to be parsed. If empty,
 :                    the function will return an empty sequence
 : @param $options The options for the parsing
 : @return The parsed XML as a document node or a list of nodes, or an empty
 :         sequence.
 :
 : @error zerr:ZXQD0003 The error will be raised if the options to the function
 :                     are inconsistent.
 :
 : @error err:FODC0006 The error will be raised if the input string is not a
 :                     valid XML document or fragment (external general parsed
 :                     entity) or if DTD validation was enabled and the
 :                     document has not passed it.
 :
 : @error err:XQDY0027 The error will be raised if schema validation was enabled
 :                     and the input document has not passed it.
 :
 :)
declare function parse-xml:parse-xml-fragment(
  $xml-string as xs:string?,
  $options as xs:string) as node()*
{
  let $new_options := <parse-xml-options:options>{
      if (contains($options, "e"))
        then <parse-xml-options:parse-external-parsed-entity/> else (),
      if (contains($options, "d"))
        then <parse-xml-options:DTD-validate/> else (),
      if (contains($options, "s"))
        then <parse-xml-options:schema-validate parse-xml-options:mode="strict"/>
        else (),
      if (contains($options, "l"))
        then <parse-xml-options:schema-validate parse-xml-options:mode="lax"/>
        else (),
      if (contains($options, "w"))
        then <parse-xml-options:strip-boundary-space/> else (),
      if (contains($options, "f"))
        then <parse-xml-options:no-error/> else ()
    }</parse-xml-options:options>
  return
    parse-xml:parse($xml-string, $new_options)
};

(:~
 :
 : Note: this function is included for backwards compatibility purposes. It
 : is recommended that you use the parse-xml:parse() function instead.
 :
 :
 : A function to parse XML files and fragments. The behavior is the
 : same as the parse-xml-fragment with two arguments.
 :
 : @param $xml-string The string that holds the XML to be parsed. If empty,
 :                    the function will return an empty sequence
 : @param $base-uri The baseURI that will be used as the baseURI for every
 :                    node returned by this function.
 : @param $options The options for the parsing (see parse-xml-fragment#2)
 : @return The parsed XML as a document node or a list of nodes, or an empty
 :         sequence.
 :
 : @error zerr:ZXQD0003 The error will be raised if the options to the function
 :                     are inconsistent.
 :
 : @error err:FODC0006 The error will be raised if the input string is not a
 :                     valid XML document or fragment (external general parsed
 :                     entity) or if DTD validation was enabled and the
 :                     document has not passed it.
 :
 : @error err:XQDY0027 The error will be raised if schema validation was enabled
 :                     and the input document has not passed it.
 :
 : @error err:FODC0007 This error will be raised if $base-uri parameter passed
 :                     to the function is not a valid absolute URI.
 :
 :)
declare function parse-xml:parse-xml-fragment(
  $xml-string as xs:string?,
  $base-uri as xs:string,
  $options as xs:string) as node()*
{
    let $new_options := <parse-xml-options:options>{
      if (contains($options, "e"))
        then <parse-xml-options:parse-external-parsed-entity/> else (),
      if (contains($options, "d"))
        then <parse-xml-options:DTD-validate/> else (),
      if (contains($options, "s"))
        then <parse-xml-options:schema-validate parse-xml-options:mode="strict"/>
        else (),
      if (contains($options, "l"))
        then <parse-xml-options:schema-validate parse-xml-options:mode="lax"/>
        else (),
      if (contains($options, "w"))
        then <parse-xml-options:strip-boundary-space/> else (),
      if (contains($options, "f"))
        then <parse-xml-options:no-error/> else (),
      <parse-xml-options:base-uri>{
        attribute{xs:QName("parse-xml-options:value")}{$base-uri}}
      </parse-xml-options:base-uri>
    }</parse-xml-options:options>
  return
    parse-xml:parse($xml-string, $new_options)
};

xquery version "3.0";

(:
 : Copyright 2006-2010 The FLWOR Foundation.
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
:)

(:~
 :

 : This module provides functions for reading XML files from string inputs.
 : It allows reading of well-formed XML documents as well as well-formed
 : external parsed entities, described by
 : XML 1.0 Well-Formed
 : Parsed Entities. The functions can also perform Schema and DTD
 : validation of the input documents.
 :

 :
 : @see XML 1.0 Well-Formed
 : Parsed Entities
 : @see
 : fn:parse-xml() function in XPath and XQuery Functions and Operators 3.0
 : @see LibXml2 parser
 :
 : @author Nicolae Brinza
 : @project data processing/data converters
 :
 :)
module namespace parse-xml = "http://www.zorba-xquery.com/modules/xml";

import schema namespace parse-xml-options = "http://www.zorba-xquery.com/modules/xml-options";

declare namespace zerr = "http://www.zorba-xquery.com/errors";
declare namespace err = "http://www.w3.org/xqt-errors";

declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "2.0";

(:~
 : A function to parse XML files and fragments (i.e.
 : external general parsed
 : entities). The functions takes two arguments: the first one is the
 : string to be parsed and the second argument is an <options/> element that
 : passes a list of options to the parsing function. They are described below.
 : The options element must conform to the xml-options.xsd schema. Some of these
 : will be passed to the underlying library (LibXml2) and further documentation
 : for them can be found at
 : LibXml2 parser.
 :
 : The list of available options:
 :
 :

 :

 : <base-uri/> - the element must have a "value" attribute, which will provide
 : the baseURI that will be used as the baseURI for every node returned by this
 : function.
 :

 :
 :

 : <no-error/> - if present, the option will disable fatal error processing. Any
 : failure to parse or validate the input in the requested manner will result
 : in the function returning an empty sequence and no error will raised.
 :

 :
 :

 : <strip-boundary-space/> - if present, it will enable the removal of blank nodes from the input.
 :

 :
 :

 : <schema-validate/> - if present, it will request that the input string be Schema
 : validated. The element accepts an attribute named "mode" which can have two
 : values: "strict and "lax". Enabling the option will produce a result that is
 : equivalent to processing the input with the option disabled, and then copying
 : the result using the XQuery "validate strict|lax" expression. This option can not
 : be used together with either the <DTD-validate/> or the <parse-external-parsed-entity/>
 : option. Doing so will raise a zerr:ZXQD0003 error.
 :

 :
 :

 : <DTD-validate/> - the option will enable the DTD-based validation. If this
 : option is enabled and the input references a DTD, then the input must be a
 : well-formed and DTD-valid XML document. The <DTD-load/> option must be used for
 : external DTD files to be loaded. If the option is enabled and the input does
 : not reference a DTD then the option is ignored. If the option is disabled, the
 : input is not required to reference a DTD and if it does reference a DTD then
 : the DTD is ignored for validation purposes. This option can not
 : be used together with either the <schema-validate/> or the <parse-external-parsed-entity>
 : option. Doing so will raise a zerr:ZXQD0003 error.
 :

 :
 :

 : <DTD-load/> - if present, it will enable loading of external DTD files.
 :

 :
 :

 : <default-DTD-attributes/> - if present, it will enable the default DTD attributes.
 :

 :
 :

 : <parse-external-parsed-entity/> - if present, it will enable the processing of XML
 : external entities. If the option
 : is enabled, the input must conform to the syntax extParsedEnt (production
 : [78] in XML 1.0, see
 : Well-Formed Parsed Entities). The result of the function call is a list
 : of nodes corresponding to the top-level components of the content of the
 : external entity: that is, elements, processing instructions, comments, and
 : text nodes. CDATA sections and character references are expanded, and
 : adjacent characters are merged so the result contains no adjacent text
 : nodes. If the option is disabled, the input must be a well-formed XML
 : document conforming to the Document production
 : (production [1] in XML 1.0).
 : This option can not be used together with either the <schema-validate/> or the <DTD-validate/>
 : option. Doing so will raise a zerr:ZXQD0003 error.
 : The <parse-external-parsed-entity/> option has two parameters, given by attributes. The first
 : attribute is "skip-root-nodes" and it can have a non-negative value. Specifying the paramter
 : tells the parser to skip the given number of root nodes and return only their children. E.g.
 : skip-root-nodes="1" is equivalent to parse-xml($xml-string)/node()/node() . skip-root-nodes="2" is equivalent
 : to parse-xml($xml-string)/node()/node()/node() , etc. The second attribute is "skip-top-level-text-nodes" with a
 : boolean value. Specifying "true" will tell the parser to skip top level text nodes, returning
 : only the top level elements, comments, PIs, etc. This parameter works in combination with
 : the "skip-root-nodes" paramter, thus top level text nodes are skipped after "skip-root-nodes" has
 : been applied.
 :

 :
 :

 : <substitute-entities/> - if present, it will enable the XML entities substitutions.
 :

 :
 :

 : <remove-redundant-ns/> - if present, the parser will remove redundant namespaces declarations.
 :

 :
 :

 : <no-CDATA/> - if present, the parser will merge CDATA nodes as text nodes.
 :

 :
 :

 : <xinclude-substitutions/> - if present, it will enable the XInclude substitutions.
 :

 :
 :

 : <no-xinclude-nodes/> - if present, the parser will not generate XInclude START/END nodes.
 :

 :
 :

 :
 :
 : @param $xml-string The string that holds the XML to be parsed. If empty,
 :                    the function will return an empty sequence
 : @param $options The options for the parsing
 : @return The parsed XML as a document node or a list of nodes, or an empty
 :         sequence.
 :
 : @error zerr:ZXQD0003 The error will be raised if the options to the function
 :                     are inconsistent.
 :
 : @error err:FODC0006 The error will be raised if the input string is not a
 :                     valid XML document or fragment (external general parsed
 :                     entity) or if DTD validation was enabled and the
 :                     document has not passed it.
 :
 : @error err:XQDY0027 The error will be raised if schema validation was enabled
 :                     and the input document has not passed it or if the parsing options are not
 :                     conformant to the xml-options.xsd schema.
 :
 : @example test/rbkt/Queries/zorba/parsing_and_serializing/parse-xml-fragment-03.xq
 : @example test/rbkt/Queries/zorba/parsing_and_serializing/parse-xml-fragment-01.xq
 : @example test/rbkt/Queries/zorba/parsing_and_serializing/parse-xml-fragment-07.xq
 :
 :)

declare function parse-xml:parse(
  $xml-string as xs:string?,
  $options as element()?) as node()* external;

(:~
 :
 : Note: this function is included for backwards compatibility purposes. It
 : is recommended that you use the parse-xml:parse() function instead.
 :
 :
 : A function to parse XML files and fragments (i.e.
 : external general parsed
 : entities). The functions takes two arguments: the first one is the
 : string to be parsed and the second argument is a flags string
 : (eEdDsSlLwWfF]*(;[\p{L}]*)?) selecting the options described below.
 :

 :

 :
 : The convention for the flags is that a lower-case letter enables
 : an option and the corresponding upper-case letter disables it; specifying
 : both is an error; specifying neither leaves it implementation-defined
 : whether the option is enabled or disabled. Specifying the same option twice
 : is not an error, but specifying inconsistent options (for example "eE") is
 : a dynamic error. The options are:
 :
 :

 :

 : eE - enables or disables processing of XML external entities. If the option
 : is enabled, the input must conform to the syntax extParsedEnt (production
 : [78] in XML 1.0, see
 : Well-Formed Parsed Entities). The result of the function call is a list
 : of nodes corresponding to the top-level components of the content of the
 : external entity: that is, elements, processing instructions, comments, and
 : text nodes. CDATA sections and character references are expanded, and
 : adjacent characters are merged so the result contains no adjacent text
 : nodes. If this option is enabled, none of the options d, s, or l may be
 : enabled. If the option is disabled, the input must be a well-formed XML
 : document conforming to the Document production
 : (production [1] in XML 1.0).
 :

 :
 :

 : dD - enables or disables DTD-based validation. If this option is enabled and
 : the input references a DTD, then the input must be a well-formed and
 : DTD-valid XML document. If the option is enabled and the input does not
 : reference a DTD then the option is ignored. If the option is disabled, the
 : input is not required to reference a DTD and if it does reference a DTD then
 : the DTD is ignored for validation purposes (though it will still be read for
 : purposes such as expanding entity references and identifying ID attributes).
 :

 :
 :

 : sS - enables or disables strict XSD-based validation. If this option is
 : enabled, the result is equivalent to processing the input with the option
 : disabled, and then copying the result using the XQuery "validate strict"
 : expression.
 :

 :
 :

 : lL - enables or disables lax XSD-based validation. If this option is enabled,
 : the result is equivalent to processing the input with the option disabled,
 : and then copying the result using the XQuery "validate lax " expression.
 :

 :
 :

 : wW - enables or disables whitespace stripping. If the option is enabled,
 : any whitespace-only text nodes that remain after any DTD-based or XSD-based
 : processing are stripped from the input; if it is disabled, such
 : whitespace-only text nodes are retained.
 :

 :
 :

 : fF - enables or disables fatal error processing. If fatal error processing
 : is enabled, then any failure to parse the input in the manner requested
 : results in a dynamic error. If fatal error processing is disabled, then any
 : failure to parse the input (and also, in the case of fn:doc, a failure to
 : obtain the input by dereferencing the supplied URI) results in the function
 : returning an empty sequence and raising no error.
 :

 :

 :
 : @param $xml-string The string that holds the XML to be parsed. If empty,
 :                    the function will return an empty sequence
 : @param $options The options for the parsing
 : @return The parsed XML as a document node or a list of nodes, or an empty
 :         sequence.
 :
 : @error zerr:ZXQD0003 The error will be raised if the options to the function
 :                     are inconsistent.
 :
 : @error err:FODC0006 The error will be raised if the input string is not a
 :                     valid XML document or fragment (external general parsed
 :                     entity) or if DTD validation was enabled and the
 :                     document has not passed it.
 :
 : @error err:XQDY0027 The error will be raised if schema validation was enabled
 :                     and the input document has not passed it.
 :
 :)
declare function parse-xml:parse-xml-fragment(
  $xml-string as xs:string?,
  $options as xs:string) as node()*
{
  let $new_options := <parse-xml-options:options>{
      if (contains($options, "e"))
        then <parse-xml-options:parse-external-parsed-entity/> else (),
      if (contains($options, "d"))
        then <parse-xml-options:DTD-validate/> else (),
      if (contains($options, "s"))
        then <parse-xml-options:schema-validate parse-xml-options:mode="strict"/>
        else (),
      if (contains($options, "l"))
        then <parse-xml-options:schema-validate parse-xml-options:mode="lax"/>
        else (),
      if (contains($options, "w"))
        then <parse-xml-options:strip-boundary-space/> else (),
      if (contains($options, "f"))
        then <parse-xml-options:no-error/> else ()
    }</parse-xml-options:options>
  return
    parse-xml:parse($xml-string, $new_options)
};

(:~
 :
 : Note: this function is included for backwards compatibility purposes. It
 : is recommended that you use the parse-xml:parse() function instead.
 :
 :
 : A function to parse XML files and fragments. The behavior is the
 : same as the parse-xml-fragment with two arguments.
 :
 : @param $xml-string The string that holds the XML to be parsed. If empty,
 :                    the function will return an empty sequence
 : @param $base-uri The baseURI that will be used as the baseURI for every
 :                    node returned by this function.
 : @param $options The options for the parsing (see parse-xml-fragment#2)
 : @return The parsed XML as a document node or a list of nodes, or an empty
 :         sequence.
 :
 : @error zerr:ZXQD0003 The error will be raised if the options to the function
 :                     are inconsistent.
 :
 : @error err:FODC0006 The error will be raised if the input string is not a
 :                     valid XML document or fragment (external general parsed
 :                     entity) or if DTD validation was enabled and the
 :                     document has not passed it.
 :
 : @error err:XQDY0027 The error will be raised if schema validation was enabled
 :                     and the input document has not passed it.
 :
 : @error err:FODC0007 This error will be raised if $base-uri parameter passed
 :                     to the function is not a valid absolute URI.
 :
 :)
declare function parse-xml:parse-xml-fragment(
  $xml-string as xs:string?,
  $base-uri as xs:string,
  $options as xs:string) as node()*
{
    let $new_options := <parse-xml-options:options>{
      if (contains($options, "e"))
        then <parse-xml-options:parse-external-parsed-entity/> else (),
      if (contains($options, "d"))
        then <parse-xml-options:DTD-validate/> else (),
      if (contains($options, "s"))
        then <parse-xml-options:schema-validate parse-xml-options:mode="strict"/>
        else (),
      if (contains($options, "l"))
        then <parse-xml-options:schema-validate parse-xml-options:mode="lax"/>
        else (),
      if (contains($options, "w"))
        then <parse-xml-options:strip-boundary-space/> else (),
      if (contains($options, "f"))
        then <parse-xml-options:no-error/> else (),
      <parse-xml-options:base-uri>{
        attribute{xs:QName("parse-xml-options:value")}{$base-uri}}
      </parse-xml-options:base-uri>
    }</parse-xml-options:options>
  return
    parse-xml:parse($xml-string, $new_options)
};