[bugfix] query history - not too sure about the collection text - thoughts anyone?

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

xquery version "1.0";

(:~
    The core XQuery script for the bibliographic demo. It receives a template XML document
    from the controller and expands it. If a search was trigger by the user, the script
    proceeds as follows:

the input form parameters are transformed into a simple XML structure
            to describe the query

an XPath string is generated from the XML query structure

the XPath is executed and the sort criteria applied

query results, XML query and sort criteria are stored into the HTTP session

the template is expanded, forms are regenerated to match the query

    To apply a filter to an existing query, we just extend the XML representation
    of the query.
:)
declare namespace biblio="http:/exist-db.org/xquery/biblio";
declare namespace group="http://exist-db.org/mods/sharing/group";
declare namespace request="http://exist-db.org/xquery/request";
declare namespace session="http://exist-db.org/xquery/session";
declare namespace util="http://exist-db.org/xquery/util";
declare namespace xmldb="http://exist-db.org/xquery/xmldb";
declare namespace functx = "http://www.functx.com";

import module namespace config="http://exist-db.org/mods/config" at "config.xqm";
import module namespace jquery="http://exist-db.org/xquery/jquery" at "resource:org/exist/xquery/lib/jquery.xql";
(:
import module namespace jquery="http://exist-db.org/xquery/jquery" at "jquery.xql";
:)
import module namespace mods="http://www.loc.gov/mods/v3" at "retrieve-mods.xql";
import module namespace sort="http://exist-db.org/xquery/sort" at "java:org.exist.xquery.modules.sort.SortModule";
import module namespace security="http://exist-db.org/mods/security" at "security.xqm";
import module namespace sharing="http://exist-db.org/mods/sharing" at "sharing.xqm";

declare option exist:serialize "method=xhtml media-type=application/xhtml+xml omit-xml-declaration=no enforce-xhtml=yes";

declare function functx:replace-first( $arg as xs:string?, $pattern as xs:string, $replacement as xs:string )  as xs:string {
   replace($arg, concat('(^.*?)', $pattern),
             concat('$1',$replacement))
 } ;

(:~
    Simple mapping from field names to an XPath expression
:)
declare variable $biblio:FIELDS :=
	<fields>
		<field name="Title">(
		mods:mods[ft:query(mods:titleInfo, '$q', $options)]
		      union
		mods:mods[ft:query(mods:relatedItem/mods:titleInfo, '$q', $options)]
		)</field>
		<field name="Name">(
		mods:mods[ft:query(mods:name, '$q', $options)]
		      union
		mods:mods[ft:query(mods:relatedItem/mods:name, '$q', $options)]
		)</field>
		<field name="Date">(
		mods:mods[ft:query(mods:originInfo/mods:dateCreated, '$q', $options)]
		      union
		mods:mods[ft:query(mods:originInfo/mods:dateIssued, '$q', $options)]
		      union
		mods:mods[ft:query(mods:originInfo/mods:dateCaptured, '$q', $options)]
		      union
		mods:mods[ft:query(mods:originInfo/mods:copyrightDate, '$q', $options)]
		      union
		mods:mods[ft:query(mods:relatedItem/mods:originInfo/mods:dateCreated, '$q', $options)]
		      union
		mods:mods[ft:query(mods:relatedItem/mods:originInfo/mods:dateIssued, '$q', $options)]
		      union
		mods:mods[ft:query(mods:relatedItem/mods:originInfo/mods:dateCaptured, '$q', $options)]
		      union
		mods:mods[ft:query(mods:relatedItem/mods:originInfo/mods:copyrightDate, '$q', $options)]
		)</field>
		<field name="Abstract">mods:mods[ft:query(mods:abstract, '$q', $options)]</field>
        <field name="Note">mods:mods[ft:query(mods:note, '$q', $options)]</field>
        <field name="Subject">mods:mods[ft:query(mods:subject, '$q', $options)]</field>
        <field name="All Except Date">(
        mods:mods[ft:query(.//*, '$q', $options)]
		)</field>

	</fields>;

(:
    Default template to be used for form generation if no
    query was specified
:)
declare variable $biblio:TEMPLATE_QUERY :=
    <query>
        <collection>{$config:mods-root}</collection>
        <and>
            <field m="1" name="All"></field>
        </and>
    </query>;

(:~
    Regenerate the HTML form to match the query, e.g. after adding more filter
    clauses.
:)
declare function biblio:form-from-query($query as element()) as element()+ {
    for $field at $pos in $query//field
    return

            {
                let $operator :=
                    if ($field/preceding-sibling::*) then
                        $field/../local-name(.)
                    else
                        ()
                return
                    <select name="operator{$pos}">
                    { if (empty($operator)) then attribute style { "display: none;" } else () }
                    {
                        for $opt in ("and", "or", "not")
                        return
                            <option>
                            {
                                if ($opt eq $operator) then
                                    attribute selected { "selected" }
                                else
                                    ()
                            }
                            { $opt }
                            </option>
                    }
                    </select>
            }

                <jquery:input name="input{$pos}" value="{$field/string()}">
                    <jquery:autocomplete url="autocomplete.xql"
                        width="300" multiple="false"
                        matchContains="false"
                        paramsCallback="autocompleteCallback">
                    </jquery:autocomplete>
                </jquery:input>

                in <select name="field{$pos}">
                {
                    for $f in $biblio:FIELDS/field
                    return
                        <option>
                            { if ($f/@name eq $field/@name) then attribute selected { "selected" } else () }
                            {$f/@name/string()}
                        </option>
                }
                </select>

};

(:~
    Generate an XPath query string from the given XML representation
    of the query.
:)
declare function biblio:generate-query($xml as element()) as xs:string* {
    typeswitch ($xml)
        case element(query) return
            for $child in $xml/*
            return
                biblio:generate-query($child)
        case element(and) return
            (
                biblio:generate-query($xml/*[1]),
                " intersect ",
                biblio:generate-query($xml/*[2])
            )
        case element(or) return
            (
                biblio:generate-query($xml/*[1]),
                " union ",
                biblio:generate-query($xml/*[2])
            )
        case element(not) return
            (
                biblio:generate-query($xml/*[1]),
                " except ",
                biblio:generate-query($xml/*[2])
            )
        case element(field) return
            let $expr0 := $biblio:FIELDS/field[@name = $xml/@name]
            let $expr := if ($expr0) then $expr0 else $biblio:FIELDS/field[last()]
            let $collection := concat("collection('", $xml/ancestor::query/collection/string(), "')//")
            return
                ($collection, replace($expr, '\$q', $xml/string()))
        case element(collection) return
            if (not($xml/..//field)) then
                ('collection("', $xml/string(), '")//mods:mods')
            else
                ()
        default return
            ()
};

(:~
    Transform the XML representation of the query into a simple string
    for display to the user in the query history.
:)
declare function biblio:xml-query-to-string($xml as element()) as xs:string* {
    typeswitch ($xml)
        case element(query) return
            for $query-term in $xml/*
                return biblio:xml-query-to-string($query-term)
        case element(and) return
            (
                biblio:xml-query-to-string($xml/*[1]),
                " AND ",
                biblio:xml-query-to-string($xml/*[2])
            )
        case element(or) return
            (
                biblio:xml-query-to-string($xml/*[1]),
                " OR ",
                biblio:xml-query-to-string($xml/*[2])
            )
        case element(not) return
            (
                biblio:xml-query-to-string($xml/*[1]),
                " NOT ",
                biblio:xml-query-to-string($xml/*[2])
            )
        case element(collection) return
            fn:concat("collection(""", $xml, """):")
        case element(field) return
            concat($xml/@name, ':', $xml/string())
        default return
            ()
};

(:~
    Process single form parameter. Called from biblio:process-form().
:)
declare function biblio:process-form-parameters($params as xs:string*) as element() {
    let $param := $params[1]
    let $n := substring-after($param, 'input')
    let $value := request:get-parameter($param, "")
    let $field := request:get-parameter(concat("field", $n), 'all')
    let $operator := request:get-parameter(concat("operator", $n), "and")
    return
        if (count($params) eq 1) then
            <field m="{$n}" name="{$field}">{$value}</field>
        else
            element { xs:QName($operator) } {
                biblio:process-form-parameters(subsequence($params, 2)),
                <field m="{$n}" name="{$field}">{$value}</field>
            }
};

(:~
    Process the received form parameters and create an XML representation of
    the query. Filter out empty parameters and take care of boolean operators.
:)
declare function biblio:process-form() as element(query)? {
    let $collection := request:get-parameter("collection", $config:mods-root)
    let $fields :=
        (:  Get a list of all input parameters which are not empty,
            ordered by input name. :)
        for $param in request:get-parameter-names()[starts-with(., 'input')]
        let $value := request:get-parameter($param, ())
        where string-length($value) gt 0
        order by $param descending
        return
            $param
    return
        if (exists($fields)) then
            (:  process-form recursively calls itself for every parameter and
                generates and XML representation of the query. :)
            <query>
                <collection>{$collection}</collection>
                { biblio:process-form-parameters($fields) }
            </query>
        else
            <query>
                <collection>{$collection}</collection>
            </query>
};

(:~
    Helper function used to sort by name within the "order by"
    clause of the query.
:)
declare function biblio:orderByName($m as element()) as xs:string?
{
    for $name in $m/mods:name[1]
    return
        mods:retrieve-name($name, 1)
};

(: Map order parameter to xpath for order by clause :)
declare function biblio:orderExpr($field as xs:string?) as xs:string
{
    if (sort:has-index('mods:name')) then
        if ($field eq "Score") then
            "ft:score($hit) descending"
        else if ($field = "Author") then
            "sort:index('mods:name', $hit)"
        else if ($field = "Title") then
            "sort:index('mods:title', $hit)"
        else
            "sort:index('mods:date', $hit)"
    else
        if ($field eq "Score") then
            "ft:score($hit) descending"
        else if ($field = "Author") then
            "biblio:orderByName($hit)"
        else if ($field = "Title") then
            "$hit/mods:titleInfo[1]/mods:title[1]"
        else
            "$hit/mods:originInfo/mods:dateCreated[1] descending"
};

(:~
    Evaluate the actual XPath query and order the results
:)
declare function biblio:evaluate-query($query as xs:string, $sort as xs:string?) {
    let $sortExpr := biblio:orderExpr($sort)
    let $orderedQuery :=
            concat("for $hit in ", $query, " order by ", $sortExpr, " return $hit")
    let $options :=
        <options>
            <default-operator>and</default-operator>
        </options>
    return
        util:eval($orderedQuery)
};

(:~
    Add a query to the user's query history. We store the XML representation
    of the query.
:)
declare function biblio:add-to-history($queryAsXML as element()) {
    let $oldHistory := session:get-attribute('history')
    let $newHistory :=
        let $n := if ($oldHistory) then max(for $n in $oldHistory/query/@n return xs:int($n)) + 1 else 1
        return
            <history>
                <query id="q{$n}" n="{$n}">
                    { $queryAsXML/* }
                </query>
                { $oldHistory/query }
            </history>
    return
        session:set-attribute('history', $newHistory)
};

(:~
    Retrieve a query from the query history
:)
declare function biblio:query-from-history($id as xs:string) {
    let $history := session:get-attribute('history')
    return
        $history/query[@id = $id]
};

(:~
    Returns the query history as a HTML list. The queries are
    transformed into a simple string representation.
:)
declare function biblio:query-history() {

    {
        let $history := session:get-attribute('history')
        for $query at $pos in $history/query
        return

{biblio:xml-query-to-string($query)}

    }

};

(:~
    Evaluate the query given as XML and store its results into the HTTP session
    for later reference.
:)
declare function biblio:eval-query($queryAsXML as element()?) {
    if ($queryAsXML) then
        let $query := string-join(biblio:generate-query($queryAsXML), '')
        let $log := util:log("DEBUG", ("QUERY: ", $query))
        let $sort0 := request:get-parameter("sort", ())
        let $sort := if ($sort0) then $sort0 else session:get-attribute("sort")
        let $results := biblio:evaluate-query($query, $sort)
        (:~ Take the query results and store them into the HTTP session. :)
        let $null := session:set-attribute('mods:cached', $results)
        let $null := session:set-attribute('query', $queryAsXML)
        let $null := session:set-attribute('sort', $queryAsXML)
        let $null := biblio:add-to-history($queryAsXML)
        return
            count($results)
    else
        0
};

(:~
    Clear the last query result.
:)
declare function biblio:clear() {
    let $null := session:remove-attribute('mods:cached')
    let $null := session:remove-attribute('query')
    let $null := session:remove-attribute('sort')
    return
        ()
};

(:~
    Scan the input HTML template and expand the biblio:* tags
    found therein.
:)
declare function biblio:process-templates($query as element()?, $hitCount as xs:integer?, $node as node()) {
    typeswitch ($node)
        case element(biblio:login) return
            let $user := request:get-attribute("xquery.user")
            return (
                if ($user eq 'guest') then

Login

                else

Logged in as {$user}. Logout

            )
        case element(biblio:form-from-query) return
            biblio:form-from-query($query)
        case element(biblio:result-count) return
            text { $hitCount }
        case element(biblio:query-history) return
            biblio:query-history()
        case element(biblio:collection-path) return
            let $collection := functx:replace-first(request:get-parameter("collection", $config:mods-root), "/db/", "")
            return
                <input class="collection-input" type="text" name="collection" value="{$collection}" readonly="true"/>
        case element(biblio:form-select-current-user-groups) return
            biblio:form-select-current-user-groups($node/@name)
        case element(biblio:current-user) return
            {request:get-attribute("xquery.user")}
        case element(biblio:conditional) return
            let $result := biblio:conditional($node) return
                if($result)then
                (
                    for $child in $result/node() return
                        biblio:process-templates($query, $hitCount, $child)
                )
                else()
        case element(biblio:is-collection-owner) return
            security:is-collection-owner(security:get-user-credential-from-session()[1], request:get-parameter("collection", $config:mods-root))
        case element(biblio:has-collection-write-permissions) return
            security:can-write-collection(security:get-user-credential-from-session()[1], request:get-parameter("collection", $config:mods-root))
        case element(biblio:form-select-collection) return
            biblio:form-select-collection($node/@name)
        case element(biblio:form-collection-sharing) return
            biblio:form-collection-sharing(request:get-parameter("collection", $config:mods-root))
        case element(biblio:form-add-member-to-sharing-group) return
            biblio:form-add-member-to-sharing-group()
        case element(biblio:form-add-sharing-group) return
            biblio:form-add-sharing-group()
        case element() return
            element { node-name($node) } {
                $node/@*,
                for $child in $node/node() return
                    biblio:process-templates($query, $hitCount, $child)
            }
        default return
            $node
};

declare function biblio:form-select-current-user-groups($select-name as xs:string) as element(select) {
    let $user := request:get-attribute("xquery.user") return
        <select name="{$select-name}">
        {
            for $group in xmldb:get-user-groups($user) return
                <option value="{$group}">{$group}</option>
        }
        </select>
};

declare function biblio:form-select-collection($select-name as xs:string) as element(select) {

    let $current-collection := request:get-parameter("collection", $config:mods-root) return

        <select name="{$select-name}">
        {
            if(security:can-write-collection(security:get-user-credential-from-session()[1], "/db"))then
            (
                element option {
                    if("/db" eq $current-collection)then
                    (
                        attribute selected { "selected" }
                    )else(),
                    text { "/db" }
                }
            )else(),

            for $sub-collection-path in biblio:get-writeable-subcollection-paths("/db") return
                element option {
                    if($sub-collection-path eq $current-collection)then
                    (
                        attribute selected { "selected" }
                    )else(),
                    text { $sub-collection-path }
                }
        }
        </select>
};

declare function biblio:form-add-member-to-sharing-group() {
    <jquery:dialog id="add-member-to-group-sharing-dialog" modal="true" title="Add User to Group" width="200">
        <select id="members-list">
        {
            for $user in security:get-other-biblio-users() return
                <option value="{$user}">{$user}</option>
        }
        </select>
        <input id="add-member-to-group-button" type="button" value="Add"/>
    </jquery:dialog>
};

declare function biblio:form-add-sharing-group() {
    <jquery:dialog id="new-group-sharing-dialog" modal="true" title="New Group" width="200">
        <input type="text" id="new-group-name"/>
        <input id="add-group-button" type="button" value="Add"/>
    </jquery:dialog>
};

declare function biblio:form-collection-sharing($collection as xs:string) {
    <jquery:dialog id="sharing-collection-dialog" modal="true" title="Collection Sharing" trigger="#collection-sharing" width="450">
        <jquery:button label="Update" function="updateCollectionSharing"/>
        <jquery:button id="cancel" label="Cancel"/>
        <form id="update-collection-sharing-form" action="operations.xql">
            <input id="sharing-collection-path_" type="hidden" name="collection"/>

Sharing settings for:

                <input type="checkbox" id="sharing-collection-with-group" name="sharing-collection-with" value="group">
                {
                 (:   if(sharing:group-readable($collection))then(attribute checked{ "checked" })else()
                 :)
                 ()
                }
                </input>
                Share with Group
                {
                let $groups := sharing:get-groups(),
                $collection-group-id := sharing:get-group-id($collection) return

                            Group
                            <select id="group-list" name="group">
                            {
                                for $group in $groups return
                                    if($group/@id eq $collection-group-id)then
                                    (
                                        <option selected="selected" value="{$group/@id}">{$group/group:name/text()}</option>
                                    )
                                    else
                                    (
                                        <option value="{$group/@id}">{$group/group:name/text()}</option>
                                    )
                            }
                            </select>
                            <input id="new-group-button" type="button" value="New Group"/>

                                Members
                                <ui id="group-members-list">
                                {
                                    (: get members of the selected group, or if none selected then the first group :)
                                    let $selected-group := if(not(empty($groups[@id eq $collection-group-id])))then($groups[@id eq $collection-group-id])else($groups[1]) return
                                        if($selected-group)then
                                        (
                                            for $group-member at $i in sharing:get-group-members($selected-group/@id) return

                                                <input id="group-member-{$i}" type="checkbox" name="group-member" value="{$group-member}" checked="checked"/>
                                                {$group-member}

                                        )else()
                                }
                                </ui>

                            <input id="add-new-member-to-group-button" type="button" value="Add Member"/>

                                <input id="group-sharing-premissions-read" type="checkbox" name="group-sharing-permissions" value="read">
                                {
                                (:
                                    if(sharing:group-readable($collection))then(attribute checked{ "checked" })else()
                                    :)
                                    ()
                                }
                                </input>
                                Read

                                <input id="group-sharing-premissions-write" type="checkbox" name="group-sharing-permissions" value="write">
                                {
                                    (:
                                    if(sharing:group-writeable($collection))then(attribute checked{ "checked" })else()
                                    :)
                                    ()
                                }
                                </input>
                                Write

            }

                <input type="checkbox" id="sharing-collection-with-other" name="sharing-collection-with" value="other">
                {
                    (:
                    if(sharing:other-readable($collection))then(attribute checked{ "checked" })else()
                    :)
                    ()
                }
                </input>
                Share with Everyone

                            <input id="other-sharing-premissions-read" type="checkbox" name="other-sharing-permissions" value="read">
                            {
                                (:
                                if(sharing:other-readable($collection))then(attribute checked{ "checked" })else()
                                :)
                                ()
                            }
                            </input>
                            Read

                            <input id="other-sharing-premissions-write" type="checkbox" name="other-sharing-permissions" value="write">
                            {
                                (:
                                if(sharing:other-writeable($collection))then(attribute checked{ "checked" })else()
                                :)
                                ()
                            }
                            </input>
                            Write

        </form>
    </jquery:dialog>
};

declare function biblio:get-writeable-subcollection-paths($path as xs:string) {

	for $sub in xmldb:get-child-collections($path)
	let $col := concat($path, "/", $sub) return
		(
			if(security:can-write-collection(security:get-user-credential-from-session()[1], $col))then
			(
			 $col
			)else(),
			biblio:get-writeable-subcollection-paths($col)
		)
};

declare function biblio:conditional($node as element(biblio:conditional)) {
    let $test-result := biblio:process-templates((), (), $node/biblio:test/biblio:*) return
        if($test-result) then
        (
            $node/biblio:result
        )else()
};

(:~
    Filter an existing result set by applying an additional
    clause with "and".
:)
declare function biblio:apply-filter() {
    let $prevQuery := session:get-attribute("query")
    let $filter := request:get-parameter("filter", ())
    let $value := request:get-parameter("value", ())

    return
        if ($filter = ('All', 'Title') and count($prevQuery/field) eq 1) then
            <query>
                <field>{$prevQuery/field/@name,
                    normalize-space(concat($prevQuery/field/string(), ' ', $value))
                }</field>
            </query>
        else
            <query>
                <and>
                { $prevQuery/* }
                { <field name="{$filter}">{$value}</field> }
                </and>
            </query>
};

session:create(),
(: We receive an HTML template as input :)
let $input := request:get-data()
let $filter := request:get-parameter("filter", ())
let $history := request:get-parameter("history", ())
let $clear := request:get-parameter("clear", ())
let $mylist := request:get-parameter("mylist", ())
let $collection := request:get-parameter("collection", ())

(: Process request parameters and generate an XML representation of the query :)
let $queryAsXML :=
    if ($history) then
        biblio:query-from-history($history)
    else if ($clear) then
        biblio:clear()
    else if ($filter) then
        biblio:apply-filter()
    else if ($mylist eq 'display') then
        ()
    else
        biblio:process-form()
let $log := util:log("DEBUG", ("$queryAsXML: ", $queryAsXML))
(: Evaluate the query :)
let $results :=
    if ($mylist) then (
        if ($mylist eq 'clear') then
            session:set-attribute("personal-list", ())
        else
            (),
        let $list := session:get-attribute("personal-list")
        let $items :=
            for $item in $list/listitem
            return
                util:node-by-id(doc(substring-before($item/@id, '#')), substring-after($item/@id, '#'))
        let $null := session:set-attribute('mods:cached', $items)
        return
            count($items)
    ) else
        biblio:eval-query($queryAsXML)
(:  Process the HTML template received as input :)
let $output :=
    jquery:process(
        biblio:process-templates(if ($queryAsXML//field) then $queryAsXML else $biblio:TEMPLATE_QUERY, $results, $input)
    )
return
    $output

xquery version "1.0";

(:~
    The core XQuery script for the bibliographic demo. It receives a template XML document
    from the controller and expands it. If a search was trigger by the user, the script
    proceeds as follows:

the input form parameters are transformed into a simple XML structure
            to describe the query

an XPath string is generated from the XML query structure

the XPath is executed and the sort criteria applied

query results, XML query and sort criteria are stored into the HTTP session

the template is expanded, forms are regenerated to match the query

    To apply a filter to an existing query, we just extend the XML representation
    of the query.
:)
declare namespace biblio="http:/exist-db.org/xquery/biblio";
declare namespace group="http://exist-db.org/mods/sharing/group";
declare namespace request="http://exist-db.org/xquery/request";
declare namespace session="http://exist-db.org/xquery/session";
declare namespace util="http://exist-db.org/xquery/util";
declare namespace xmldb="http://exist-db.org/xquery/xmldb";
declare namespace functx = "http://www.functx.com";

import module namespace config="http://exist-db.org/mods/config" at "config.xqm";
import module namespace jquery="http://exist-db.org/xquery/jquery" at "resource:org/exist/xquery/lib/jquery.xql";
import module namespace mods="http://www.loc.gov/mods/v3" at "retrieve-mods.xql";
import module namespace sort="http://exist-db.org/xquery/sort" at "java:org.exist.xquery.modules.sort.SortModule";
import module namespace security="http://exist-db.org/mods/security" at "security.xqm";
import module namespace sharing="http://exist-db.org/mods/sharing" at "sharing.xqm";

declare option exist:serialize "method=xhtml media-type=application/xhtml+xml omit-xml-declaration=no enforce-xhtml=yes";

declare function functx:replace-first( $arg as xs:string?, $pattern as xs:string, $replacement as xs:string )  as xs:string {
   replace($arg, concat('(^.*?)', $pattern),
             concat('$1',$replacement))
 } ;

(:~
    Simple mapping from field names to an XPath expression
:)
declare variable $biblio:FIELDS :=
	<fields>
		<field name="Title">(
		mods:mods[ft:query(mods:titleInfo, '$q', $options)]
		      union
		mods:mods[ft:query(mods:relatedItem/mods:titleInfo, '$q', $options)]
		)</field>
		<field name="Name">(
		mods:mods[ft:query(mods:name, '$q', $options)]
		      union
		mods:mods[ft:query(mods:relatedItem/mods:name, '$q', $options)]
		)</field>
		<field name="Date">(
		mods:mods[ft:query(mods:originInfo/mods:dateCreated, '$q', $options)]
		      union
		mods:mods[ft:query(mods:originInfo/mods:dateIssued, '$q', $options)]
		      union
		mods:mods[ft:query(mods:originInfo/mods:dateCaptured, '$q', $options)]
		      union
		mods:mods[ft:query(mods:originInfo/mods:copyrightDate, '$q', $options)]
		      union
		mods:mods[ft:query(mods:relatedItem/mods:originInfo/mods:dateCreated, '$q', $options)]
		      union
		mods:mods[ft:query(mods:relatedItem/mods:originInfo/mods:dateIssued, '$q', $options)]
		      union
		mods:mods[ft:query(mods:relatedItem/mods:originInfo/mods:dateCaptured, '$q', $options)]
		      union
		mods:mods[ft:query(mods:relatedItem/mods:originInfo/mods:copyrightDate, '$q', $options)]
		)</field>
		<field name="Abstract">mods:mods[ft:query(mods:abstract, '$q', $options)]</field>
        <field name="Note">mods:mods[ft:query(mods:note, '$q', $options)]</field>
        <field name="Subject">mods:mods[ft:query(mods:subject, '$q', $options)]</field>
        <field name="All Except Date">(
        mods:mods[ft:query(.//*, '$q', $options)]
		)</field>

	</fields>;

(:
    Default template to be used for form generation if no
    query was specified
:)
declare variable $biblio:TEMPLATE_QUERY :=
    <query>
        <collection>{$config:mods-root}</collection>
        <and>
            <field m="1" name="All"></field>
        </and>
    </query>;

(:~
    Regenerate the HTML form to match the query, e.g. after adding more filter
    clauses.
:)
declare function biblio:form-from-query($query as element()) as element()+ {
    for $field at $pos in $query//field
    return

            {
                let $operator :=
                    if ($field/preceding-sibling::*) then
                        $field/../local-name(.)
                    else
                        ()
                return
                    <select name="operator{$pos}">
                    { if (empty($operator)) then attribute style { "display: none;" } else () }
                    {
                        for $opt in ("and", "or", "not")
                        return
                            <option>
                            {
                                if ($opt eq $operator) then
                                    attribute selected { "selected" }
                                else
                                    ()
                            }
                            { $opt }
                            </option>
                    }
                    </select>
            }

                <jquery:input name="input{$pos}" value="{$field/string()}">
                    <jquery:autocomplete url="autocomplete.xql"
                        width="300" multiple="false"
                        matchContains="false"
                        paramsCallback="autocompleteCallback">
                    </jquery:autocomplete>
                </jquery:input>

                in <select name="field{$pos}">
                {
                    for $f in $biblio:FIELDS/field
                    return
                        <option>
                            { if ($f/@name eq $field/@name) then attribute selected { "selected" } else () }
                            {$f/@name/string()}
                        </option>
                }
                </select>

};

(:~
    Generate an XPath query string from the given XML representation
    of the query.
:)
declare function biblio:generate-query($xml as element()) as xs:string* {
    typeswitch ($xml)
        case element(query) return
            for $child in $xml/*
            return
                biblio:generate-query($child)
        case element(and) return
            (
                biblio:generate-query($xml/*[1]),
                " intersect ",
                biblio:generate-query($xml/*[2])
            )
        case element(or) return
            (
                biblio:generate-query($xml/*[1]),
                " union ",
                biblio:generate-query($xml/*[2])
            )
        case element(not) return
            (
                biblio:generate-query($xml/*[1]),
                " except ",
                biblio:generate-query($xml/*[2])
            )
        case element(field) return
            let $expr0 := $biblio:FIELDS/field[@name = $xml/@name]
            let $expr := if ($expr0) then $expr0 else $biblio:FIELDS/field[last()]
            let $collection := concat("collection('", $xml/ancestor::query/collection/string(), "')//")
            return
                ($collection, replace($expr, '\$q', $xml/string()))
        case element(collection) return
            if (not($xml/..//field)) then
                ('collection("', $xml/string(), '")//mods:mods')
            else
                ()
        default return
            ()
};

(:~
    Transform the XML representation of the query into a simple string
    for display to the user in the query history.
:)
declare function biblio:xml-query-to-string($xml as element()) as xs:string* {
    typeswitch ($xml)
        case element(query) return
            biblio:xml-query-to-string($xml/*[1])
        case element(and) return
            (
                biblio:xml-query-to-string($xml/*[1]),
                " AND ",
                biblio:xml-query-to-string($xml/*[2])
            )
        case element(or) return
            (
                biblio:xml-query-to-string($xml/*[1]),
                " OR ",
                biblio:xml-query-to-string($xml/*[2])
            )
        case element(not) return
            (
                biblio:xml-query-to-string($xml/*[1]),
                " NOT ",
                biblio:xml-query-to-string($xml/*[2])
            )
        case element(field) return
            concat($xml/@name, ':', $xml/string())
        default return
            ()
};

(:~
    Process single form parameter. Called from biblio:process-form().
:)
declare function biblio:process-form-parameters($params as xs:string*) as element() {
    let $param := $params[1]
    let $n := substring-after($param, 'input')
    let $value := request:get-parameter($param, "")
    let $field := request:get-parameter(concat("field", $n), 'all')
    let $operator := request:get-parameter(concat("operator", $n), "and")
    return
        if (count($params) eq 1) then
            <field m="{$n}" name="{$field}">{$value}</field>
        else
            element { xs:QName($operator) } {
                biblio:process-form-parameters(subsequence($params, 2)),
                <field m="{$n}" name="{$field}">{$value}</field>
            }
};

(:~
    Process the received form parameters and create an XML representation of
    the query. Filter out empty parameters and take care of boolean operators.
:)
declare function biblio:process-form() as element(query)? {
    let $collection := request:get-parameter("collection", $config:mods-root)
    let $fields :=
        (:  Get a list of all input parameters which are not empty,
            ordered by input name. :)
        for $param in request:get-parameter-names()[starts-with(., 'input')]
        let $value := request:get-parameter($param, ())
        where string-length($value) gt 0
        order by $param descending
        return
            $param
    return
        if (exists($fields)) then
            (:  process-form recursively calls itself for every parameter and
                generates and XML representation of the query. :)
            <query>
                <collection>{$collection}</collection>
                { biblio:process-form-parameters($fields) }
            </query>
        else
            <query>
                <collection>{$collection}</collection>
            </query>
};

(:~
    Helper function used to sort by name within the "order by"
    clause of the query.
:)
declare function biblio:orderByName($m as element()) as xs:string?
{
    for $name in $m/mods:name[1]
    return
        mods:retrieve-name($name, 1)
};

(: Map order parameter to xpath for order by clause :)
declare function biblio:orderExpr($field as xs:string?) as xs:string
{
    if (sort:has-index('mods:name')) then
        if ($field eq "Score") then
            "ft:score($hit) descending"
        else if ($field = "Author") then
            "sort:index('mods:name', $hit)"
        else if ($field = "Title") then
            "sort:index('mods:title', $hit)"
        else
            "sort:index('mods:date', $hit)"
    else
        if ($field eq "Score") then
            "ft:score($hit) descending"
        else if ($field = "Author") then
            "biblio:orderByName($hit)"
        else if ($field = "Title") then
            "$hit/mods:titleInfo[1]/mods:title[1]"
        else
            "$hit/mods:originInfo/mods:dateCreated[1] descending"
};

(:~
    Evaluate the actual XPath query and order the results
:)
declare function biblio:evaluate-query($query as xs:string, $sort as xs:string?) {
    let $sortExpr := biblio:orderExpr($sort)
    let $orderedQuery :=
            concat("for $hit in ", $query, " order by ", $sortExpr, " return $hit")
    let $options :=
        <options>
            <default-operator>and</default-operator>
        </options>
    return
        util:eval($orderedQuery)
};

(:~
    Add a query to the user's query history. We store the XML representation
    of the query.
:)
declare function biblio:add-to-history($queryAsXML as element()) {
    let $oldHistory := session:get-attribute('history')
    let $newHistory :=
        let $n := if ($oldHistory) then max(for $n in $oldHistory/query/@n return xs:int($n)) + 1 else 1
        return
            <history>
                <query id="q{$n}" n="{$n}">
                    { $queryAsXML/* }
                </query>
                { $oldHistory/query }
            </history>
    return
        session:set-attribute('history', $newHistory)
};

(:~
    Retrieve a query from the query history
:)
declare function biblio:query-from-history($id as xs:string) {
    let $history := session:get-attribute('history')
    return
        $history/query[@id = $id]
};

(:~
    Returns the query history as a HTML list. The queries are
    transformed into a simple string representation.
:)
declare function biblio:query-history() {

    {
        let $history := session:get-attribute('history')
        for $query at $pos in $history/query
        return

{biblio:xml-query-to-string($query)}

    }

};

(:~
    Evaluate the query given as XML and store its results into the HTTP session
    for later reference.
:)
declare function biblio:eval-query($queryAsXML as element()?) {
    if ($queryAsXML) then
        let $query := string-join(biblio:generate-query($queryAsXML), '')
        let $log := util:log("DEBUG", ("QUERY: ", $query))
        let $sort0 := request:get-parameter("sort", ())
        let $sort := if ($sort0) then $sort0 else session:get-attribute("sort")
        let $results := biblio:evaluate-query($query, $sort)
        (:~ Take the query results and store them into the HTTP session. :)
        let $null := session:set-attribute('mods:cached', $results)
        let $null := session:set-attribute('query', $queryAsXML)
        let $null := session:set-attribute('sort', $queryAsXML)
        let $null := biblio:add-to-history($queryAsXML)
        return
            count($results)
    else
        0
};

(:~
    Clear the last query result.
:)
declare function biblio:clear() {
    let $null := session:remove-attribute('mods:cached')
    let $null := session:remove-attribute('query')
    let $null := session:remove-attribute('sort')
    return
        ()
};

(:~
    Scan the input HTML template and expand the biblio:* tags
    found therein.
:)
declare function biblio:process-templates($query as element()?, $hitCount as xs:integer?, $node as node()) {
    typeswitch ($node)
        case element(biblio:login) return
            let $user := request:get-attribute("xquery.user")
            return (
                if ($user eq 'guest') then

Login

                else

Logged in as {$user}. Logout

            )
        case element(biblio:form-from-query) return
            biblio:form-from-query($query)
        case element(biblio:result-count) return
            text { $hitCount }
        case element(biblio:query-history) return
            biblio:query-history()
        case element(biblio:collection-path) return
            let $collection := functx:replace-first(request:get-parameter("collection", $config:mods-root), "/db/", "")
            return
                <input class="collection-input" type="text" name="collection" value="{$collection}" readonly="true"/>
        case element(biblio:form-select-current-user-groups) return
            biblio:form-select-current-user-groups($node/@name)
        case element(biblio:current-user) return
            {request:get-attribute("xquery.user")}
        case element(biblio:conditional) return
            let $result := biblio:conditional($node) return
                if($result)then
                (
                    for $child in $result/node() return
                        biblio:process-templates($query, $hitCount, $child)
                )
                else()
        case element(biblio:is-collection-owner) return
            security:is-collection-owner(security:get-user-credential-from-session()[1], request:get-parameter("collection", $config:mods-root))
        case element(biblio:has-collection-write-permissions) return
            (   util:log("debug", concat("LOOK HERE: user=", security:get-user-credential-from-session()[1], " collection=", request:get-parameter("collection", $config:mods-root))),
                security:can-write-collection(security:get-user-credential-from-session()[1], request:get-parameter("collection", $config:mods-root))
            )
        case element(biblio:form-select-collection) return
            biblio:form-select-collection($node/@name)
        case element(biblio:form-collection-sharing) return
            biblio:form-collection-sharing(request:get-parameter("collection", $config:mods-root))
        case element(biblio:form-add-member-to-sharing-group) return
            biblio:form-add-member-to-sharing-group()
        case element(biblio:form-add-sharing-group) return
            biblio:form-add-sharing-group()
        case element() return
            element { node-name($node) } {
                $node/@*,
                for $child in $node/node() return
                    biblio:process-templates($query, $hitCount, $child)
            }
        default return
            $node
};

declare function biblio:form-select-current-user-groups($select-name as xs:string) as element(select) {
    let $user := request:get-attribute("xquery.user") return
        <select name="{$select-name}">
        {
            for $group in xmldb:get-user-groups($user) return
                <option value="{$group}">{$group}</option>
        }
        </select>
};

declare function biblio:form-select-collection($select-name as xs:string) as element(select) {

    let $current-collection := request:get-parameter("collection", $config:mods-root) return

        <select name="{$select-name}">
        {
            if(security:can-write-collection(security:get-user-credential-from-session()[1], "/db"))then
            (
                element option {
                    if("/db" eq $current-collection)then
                    (
                        attribute selected { "selected" }
                    )else(),
                    text { "/db" }
                }
            )else(),

            for $sub-collection-path in biblio:get-writeable-subcollection-paths("/db") return
                element option {
                    if($sub-collection-path eq $current-collection)then
                    (
                        attribute selected { "selected" }
                    )else(),
                    text { $sub-collection-path }
                }
        }
        </select>
};

declare function biblio:form-add-member-to-sharing-group() {
    <jquery:dialog id="add-member-to-group-sharing-dialog" modal="true" title="Add User to Group" width="200">
        <select id="members-list">
        {
            for $user in security:get-other-biblio-users() return
                <option value="{$user}">{$user}</option>
        }
        </select>
        <input id="add-member-to-group-button" type="button" value="Add"/>
    </jquery:dialog>
};

declare function biblio:form-add-sharing-group() {
    <jquery:dialog id="new-group-sharing-dialog" modal="true" title="New Group" width="200">
        <input type="text" id="new-group-name"/>
        <input id="add-group-button" type="button" value="Add"/>
    </jquery:dialog>
};

declare function biblio:form-collection-sharing($collection as xs:string) {
    <jquery:dialog id="sharing-collection-dialog" modal="true" title="Collection Sharing" trigger="#collection-sharing" width="450">
        <jquery:button label="Update" function="updateCollectionSharing"/>
        <jquery:button id="cancel" label="Cancel"/>
        <form id="update-collection-sharing-form" action="operations.xql">
            <input id="sharing-collection-path_" type="hidden" name="collection"/>

Sharing settings for:

                <input type="checkbox" id="sharing-collection-with-group" name="sharing-collection-with" value="group">
                {
                 (:   if(sharing:group-readable($collection))then(attribute checked{ "checked" })else()
                 :)
                 ()
                }
                </input>
                Share with Group
                {
                let $groups := sharing:get-groups(),
                $collection-group-id := sharing:get-group-id($collection) return

                            Group
                            <select id="group-list" name="group">
                            {
                                for $group in $groups return
                                    if($group/@id eq $collection-group-id)then
                                    (
                                        <option selected="selected" value="{$group/@id}">{$group/group:name/text()}</option>
                                    )
                                    else
                                    (
                                        <option value="{$group/@id}">{$group/group:name/text()}</option>
                                    )
                            }
                            </select>
                            <input id="new-group-button" type="button" value="New Group"/>

                                Members
                                <ui id="group-members-list">
                                {
                                    (: get members of the selected group, or if none selected then the first group :)
                                    let $selected-group := if(not(empty($groups[@id eq $collection-group-id])))then($groups[@id eq $collection-group-id])else($groups[1]) return
                                        if($selected-group)then
                                        (
                                            for $group-member at $i in sharing:get-group-members($selected-group/@id) return

                                                <input id="group-member-{$i}" type="checkbox" name="group-member" value="{$group-member}" checked="checked"/>
                                                {$group-member}

                                        )else()
                                }
                                </ui>

                            <input id="add-new-member-to-group-button" type="button" value="Add Member"/>

                                <input id="group-sharing-premissions-read" type="checkbox" name="group-sharing-permissions" value="read">
                                {
                                (:
                                    if(sharing:group-readable($collection))then(attribute checked{ "checked" })else()
                                    :)
                                    ()
                                }
                                </input>
                                Read

                                <input id="group-sharing-premissions-write" type="checkbox" name="group-sharing-permissions" value="write">
                                {
                                    (:
                                    if(sharing:group-writeable($collection))then(attribute checked{ "checked" })else()
                                    :)
                                    ()
                                }
                                </input>
                                Write

            }

                <input type="checkbox" id="sharing-collection-with-other" name="sharing-collection-with" value="other">
                {
                    (:
                    if(sharing:other-readable($collection))then(attribute checked{ "checked" })else()
                    :)
                    ()
                }
                </input>
                Share with Everyone

                            <input id="other-sharing-premissions-read" type="checkbox" name="other-sharing-permissions" value="read">
                            {
                                (:
                                if(sharing:other-readable($collection))then(attribute checked{ "checked" })else()
                                :)
                                ()
                            }
                            </input>
                            Read

                            <input id="other-sharing-premissions-write" type="checkbox" name="other-sharing-permissions" value="write">
                            {
                                (:
                                if(sharing:other-writeable($collection))then(attribute checked{ "checked" })else()
                                :)
                                ()
                            }
                            </input>
                            Write

        </form>
    </jquery:dialog>
};

declare function biblio:get-writeable-subcollection-paths($path as xs:string) {

	for $sub in xmldb:get-child-collections($path)
	let $col := concat($path, "/", $sub) return
		(
			if(security:can-write-collection(security:get-user-credential-from-session()[1], $col))then
			(
			 $col
			)else(),
			biblio:get-writeable-subcollection-paths($col)
		)
};

declare function biblio:conditional($node as element(biblio:conditional)) {
    let $test-result := biblio:process-templates((), (), $node/biblio:test/biblio:*) return
        if($test-result) then
        (
            $node/biblio:result
        )else()
};

(:~
    Filter an existing result set by applying an additional
    clause with "and".
:)
declare function biblio:apply-filter() {
    let $prevQuery := session:get-attribute("query")
    let $filter := request:get-parameter("filter", ())
    let $value := request:get-parameter("value", ())
    return
        if ($filter = ('All', 'Title') and count($prevQuery/field) eq 1) then
            <query>
                <field>{$prevQuery/field/@name,
                    normalize-space(concat($prevQuery/field/string(), ' ', $value))
                }</field>
            </query>
        else
            <query>
                <and>
                { $prevQuery/* }
                { <field name="{$filter}">{$value}</field> }
                </and>
            </query>
};

session:create(),
(: We receive an HTML template as input :)
let $input := request:get-data()
let $filter := request:get-parameter("filter", ())
let $history := request:get-parameter("history", ())
let $clear := request:get-parameter("clear", ())
let $mylist := request:get-parameter("mylist", ())
let $collection := request:get-parameter("collection", ())

(: Process request parameters and generate an XML representation of the query :)
let $queryAsXML :=
    if ($history) then
        biblio:query-from-history($history)
    else if ($clear) then
        biblio:clear()
    else if ($filter) then
        biblio:apply-filter()
    else if ($mylist eq 'display') then
        ()
    else
        biblio:process-form()
let $log := util:log("DEBUG", ("$queryAsXML: ", $queryAsXML))
(: Evaluate the query :)
let $results :=
    if ($mylist) then (
        if ($mylist eq 'clear') then
            session:set-attribute("personal-list", ())
        else
            (),
        let $list := session:get-attribute("personal-list")
        let $items :=
            for $item in $list/listitem
            return
                util:node-by-id(doc(substring-before($item/@id, '#')), substring-after($item/@id, '#'))
        let $null := session:set-attribute('mods:cached', $items)
        return
            count($items)
    ) else
        biblio:eval-query($queryAsXML)
(:  Process the HTML template received as input :)
let $output :=
    jquery:process(
        biblio:process-templates(if ($queryAsXML//field) then $queryAsXML else $biblio:TEMPLATE_QUERY, $results, $input)
    )
return
    $output


fixed #544

fixed #544

xquery version "1.0-ml";

module namespace hent = "http://marklogic.com/data-hub/hub-entities";

import module namespace es-wrapper = "http://marklogic.com/data-hub/es-wrapper"
  at "/com.marklogic.hub/lib/entity-services-wrapper.xqy";

import module namespace search = "http://marklogic.com/appservices/search"
  at "/MarkLogic/appservices/search/search.xqy";

declare variable $ENTITY-MODEL-COLLECTION := "http://marklogic.com/entity-services/models";

declare option xdmp:mapping "false";

declare function hent:get-model($entity-name as xs:string)
{
  hent:get-model($entity-name, ())
};

declare function hent:get-model($entity-name as xs:string, $used-models as xs:string*)
{
  let $model := fn:collection($ENTITY-MODEL-COLLECTION)[info/title = $entity-name]
  where fn:exists($model)
  return
    let $model-map as map:map? := $model
    let $refs := $model//*[fn:local-name(.) = '$ref'][fn:starts-with(., "#/definitions")] ! fn:replace(., "#/definitions/", "")
    let $_ :=
      let $definitions := map:get($model-map, "definitions")
      for $ref in $refs[fn:not(. = $used-models)]
      let $other-model as map:map? := hent:get-model($ref, ($used-models, $entity-name))
      let $other-defs := map:get($other-model, "definitions")
      for $key in map:keys($other-defs)
      return
        map:put($definitions, $key, map:get($other-defs, $key))
    return
      $model-map
};

declare function hent:uber-model() as map:map
{
  hent:uber-model(fn:collection($ENTITY-MODEL-COLLECTION)/object-node())
};

declare function hent:uber-model($models as object-node()*) as map:map
{
  let $uber-model := map:map()
  let $definitions :=
    let $m := map:map()
    let $_ :=
      for $model as map:map in $models
      let $defs := map:get($model, "definitions")
      for $key in map:keys($defs)
      return
        map:put($m, $key, map:get($defs, $key))
    return
      map:put($uber-model, "definitions", $m)
  return
    $uber-model
};

declare function hent:search-options-generate(
  $model as map:map
)
{
  let $entity-type-names := map:keys(map:get($model, "definitions"))
  let $seen-keys := map:map()
  let $all-constraints := json:array()
  let $all-tuples-definitions := json:array()
  let $_ :=
    for $entity-type-name in $entity-type-names
    let $entity-type := map:get(map:get($model, "definitions"), $entity-type-name)
    let $primary-key-name := map:get($entity-type, "primaryKey")
    let $properties := map:get($entity-type, "properties")
    let $tuples-range-definitions := json:array()
    let $_range-constraints :=
      for $property-name in map:get($entity-type, "rangeIndex") ! json:array-values(.)
      let $specified-datatype := es-wrapper:resolve-datatype($model,$entity-type-name,$property-name)
      let $property := map:get($properties, $property-name)
      let $datatype := es-wrapper:indexable-datatype($specified-datatype)
      let $collation := if ($datatype eq "string")
        then attribute
          collation {
            head( (map:get($property, "collation"), "http://marklogic.com/collation/en") )
          }
        else ()
      let $range-definition :=
        <search:range type="xs:{ $datatype }" facet="true">
          { $collation }
          <search:path-index>/*:envelope/*:instance/{$entity-type-name}/{$property-name}</search:path-index>
          <search:facet-option>limit=10</search:facet-option>
          <search:facet-option>frequency-order</search:facet-option>
          <search:facet-option>descending</search:facet-option>
        </search:range>
      let $constraint-template :=
        <search:constraint name="{ $property-name } ">
          {$range-definition}
        </search:constraint>
      (: the collecting array will be added once after accumulation :)
      let $_ := json:array-push($tuples-range-definitions, $range-definition)
      return
        json:array-push($all-constraints, hent:wrap-duplicates($seen-keys, $property-name, $constraint-template))
    let $_ :=
      if (json:array-size($tuples-range-definitions) gt 1)
      then
        json:array-push($all-tuples-definitions,
          <search:tuples name="{ $entity-type-name }">
            {json:array-values($tuples-range-definitions)}
          </search:tuples>)
      else if (json:array-size($tuples-range-definitions) eq 1)
      then
        json:array-push($all-tuples-definitions,
          <search:values name="{ $entity-type-name }">
            {json:array-values($tuples-range-definitions)}
          </search:values>)
      else ()
    let $_word-constraints :=
      for $property-name in json:array-values(map:get($entity-type, "wordLexicon"))
      return
      json:array-push($all-constraints, hent:wrap-duplicates($seen-keys, $property-name,
        <search:constraint name="{ $property-name } ">
          <search:word>
            <search:element ns="" name="{ $property-name }"/>
          </search:word>
        </search:constraint>))
    return ()
  let $types-expr := string-join( $entity-type-names, "|" )
  let $type-constraint :=
    <search:constraint name="entity-type">
      <search:value>
        <search:element ns="http://marklogic.com/entity-services" name="title"/>
      </search:value>
    </search:constraint>
  return
  <options xmlns="http://marklogic.com/appservices/search">
    <constraint name="Collection">
      <collection/>
    </constraint>
    {
    $type-constraint,
    json:array-values($all-constraints),
    json:array-values($all-tuples-definitions),
    comment {
      "Uncomment to return no results for a blank search, rather than the default of all results
",       xdmp:quote(
    <term>
      <empty apply="no-results"/>
    </term>),
      "
"
    },
    <values name="uris">
      <uri/>
    </values>,
    comment { "Change to 'filtered' to exclude false-positives in certain searches" },
    <search-option>unfiltered</search-option>,
    comment { "Modify document extraction to change results returned" },
    <extract-document-data selected="include">
      <extract-path>/*:envelope/*:instance/({ $types-expr })</extract-path>
    </extract-document-data>,

(:    comment { "Change or remove this additional-query to broaden search beyond entity instance documents" },
    <additional-query>
      <cts:element-query xmlns:cts="http://marklogic.com/cts">
      <cts:element xmlns:es="http://marklogic.com/entity-services">es:instance</cts:element>
      <cts:true-query/>
      </cts:element-query>
    </additional-query>,:)
    <return-facets>true</return-facets>,
    comment { "To return snippets, comment out or remove this option" },
    <transform-results apply="empty-snippet" />
    }
  </options>
};

declare function hent:wrap-duplicates(
    $duplicate-map as map:map,
    $property-name as xs:string,
    $item as element()
) as item()
{
    if (map:contains($duplicate-map, $property-name))
    then
        comment { "This item is a duplicate and is commented out so as to create a valid artifact.
",
            xdmp:quote($item),
            "
"
        }
    else (
        map:put($duplicate-map, $property-name, true()),
        $item)
};

declare function hent:dump-search-options($entities as json:array)
{
  let $uber-model := hent:uber-model(json:array-values($entities) ! xdmp:to-json(.)/object-node())
  return
    hent:search-options-generate($uber-model)
};

declare function hent:dump-indexes($entities as json:array)
{
  let $uber-model := hent:uber-model(json:array-values($entities) ! xdmp:to-json(.)/object-node())
  return
    hent:database-properties-generate($uber-model)
};

declare function hent:database-properties-generate(
  $model as map:map
) as document-node()
{
  let $definitions := map:get($model, "definitions")
  let $entity-type-names := map:keys($definitions)
  let $range-path-indexes := json:array()
  let $word-lexicons := json:array()
  let $range-element-indexes := json:array()
  let $_ :=
    for $entity-type-name in $entity-type-names
    let $entity-type := map:get($definitions, $entity-type-name)
    let $properties := map:get($entity-type, "properties")
    let $range-index-properties := map:get($entity-type, "rangeIndex")
    return (
      for $range-index-property in $range-index-properties ! json:array-values(.)
      let $ri-map := json:object()
      let $property := map:get($properties, $range-index-property)
      let $specified-datatype := es-wrapper:resolve-datatype($model, $entity-type-name, $range-index-property)
      let $datatype := es-wrapper:indexable-datatype($specified-datatype)
      let $collation := head( (map:get($property, "collation"), "http://marklogic.com/collation/en") )
      let $_ := map:put($ri-map, "collation", $collation)
      let $invalid-values := "reject"
      let $_ := map:put($ri-map, "invalid-values", $invalid-values)
      let $_ := map:put($ri-map, "path-expression", "/*:envelope/*:instance/" || $entity-type-name || "/" || $range-index-property)
      let $_ := map:put($ri-map, "range-value-positions", false())
      let $_ := map:put($ri-map, "scalar-type", $datatype)
      return json:array-push($range-path-indexes, $ri-map)
      ,
      let $word-lexicon-properties := map:get($entity-type, "wordLexicon")
      for $word-lexicon-property in $word-lexicon-properties ! json:array-values(.)
      let $wl-map := json:object()
      let $property := map:get($properties, $word-lexicon-property)
      let $collation := head( (map:get($property, "collation"), "http://marklogic.com/collation/en") )
      let $_ := map:put($wl-map, "collation", $collation)
      let $_ := map:put($wl-map, "localname", $word-lexicon-property)
      let $_ := map:put($wl-map, "namespace-uri", "")
      return json:array-push($word-lexicons, $wl-map)
      ,
      let $primary-key-property := map:get($entity-type, "primaryKey")
      where $primary-key-property
      return
        let $pk-map := json:object()
        let $property := map:get($properties, $primary-key-property)
        let $specified-datatype := es-wrapper:resolve-datatype($model, $entity-type-name, $primary-key-property)
        let $datatype := es-wrapper:indexable-datatype($specified-datatype)
        let $collation := head( (map:get($property, "collation"), "http://marklogic.com/collation/en") )
        let $_ := map:put($pk-map, "collation", $collation)
        let $_ := map:put($pk-map, "localname", $primary-key-property)
        let $_ := map:put($pk-map, "namespace-uri", "")
        let $_ := map:put($pk-map, "range-value-positions", fn:false())
        let $_ := map:put($pk-map, "scalar-type", $datatype)
        let $_ := map:put($pk-map, "invalid-values", "reject")
        return json:array-push($range-element-indexes, $pk-map)
    )
  let $path-namespaces := json:array()
  let $pn := json:object()
  let $_ := map:put($pn, "prefix", "es")
  let $_ := map:put($pn, "namespace-uri", "http://marklogic.com/entity-services")
  let $_ := json:array-push($path-namespaces, $pn)
  let $database-properties := json:object()
  let $_ := map:put($database-properties, "path-namespace", $path-namespaces)
  let $_ := map:put($database-properties, "element-word-lexicon", $word-lexicons)
  let $_ := map:put($database-properties, "range-path-index", $range-path-indexes)
  let $_ := map:put($database-properties, "range-element-index", $range-element-indexes)
  return xdmp:to-json($database-properties)
};

xquery version "1.0-ml";

module namespace hent = "http://marklogic.com/data-hub/hub-entities";

import module namespace es-wrapper = "http://marklogic.com/data-hub/es-wrapper"
  at "/com.marklogic.hub/lib/entity-services-wrapper.xqy";

import module namespace search = "http://marklogic.com/appservices/search"
  at "/MarkLogic/appservices/search/search.xqy";

declare variable $ENTITY-MODEL-COLLECTION := "http://marklogic.com/entity-services/models";

declare option xdmp:mapping "false";

declare function hent:get-model($entity-name as xs:string)
{
  let $model := fn:collection($ENTITY-MODEL-COLLECTION)[info/title = $entity-name]
  where fn:exists($model)
  return
    let $model-map as map:map? := $model
    let $refs := $model//*[fn:local-name(.) = '$ref'][fn:starts-with(., "#/definitions")] ! fn:replace(., "#/definitions/", "")
    let $_ :=
      let $definitions := map:get($model-map, "definitions")
      for $ref in $refs
      let $other-model as map:map? := hent:get-model($ref)
      let $other-defs := map:get($other-model, "definitions")
      for $key in map:keys($other-defs)
      return
        map:put($definitions, $key, map:get($other-defs, $key))
    return
      $model-map
};

declare function hent:uber-model() as map:map
{
  hent:uber-model(fn:collection($ENTITY-MODEL-COLLECTION)/object-node())
};

declare function hent:uber-model($models as object-node()*) as map:map
{
  let $uber-model := map:map()
  let $definitions :=
    let $m := map:map()
    let $_ :=
      for $model as map:map in $models
      let $defs := map:get($model, "definitions")
      for $key in map:keys($defs)
      return
        map:put($m, $key, map:get($defs, $key))
    return
      map:put($uber-model, "definitions", $m)
  return
    $uber-model
};

declare function hent:search-options-generate(
  $model as map:map
)
{
  let $entity-type-names := map:keys(map:get($model, "definitions"))
  let $seen-keys := map:map()
  let $all-constraints := json:array()
  let $all-tuples-definitions := json:array()
  let $_ :=
    for $entity-type-name in $entity-type-names
    let $entity-type := map:get(map:get($model, "definitions"), $entity-type-name)
    let $primary-key-name := map:get($entity-type, "primaryKey")
    let $properties := map:get($entity-type, "properties")
    let $tuples-range-definitions := json:array()
    let $_range-constraints :=
      for $property-name in map:get($entity-type, "rangeIndex") ! json:array-values(.)
      let $specified-datatype := es-wrapper:resolve-datatype($model,$entity-type-name,$property-name)
      let $property := map:get($properties, $property-name)
      let $datatype := es-wrapper:indexable-datatype($specified-datatype)
      let $collation := if ($datatype eq "string")
        then attribute
          collation {
            head( (map:get($property, "collation"), "http://marklogic.com/collation/en") )
          }
        else ()
      let $range-definition :=
        <search:range type="xs:{ $datatype }" facet="true">
          { $collation }
          <search:path-index>/*:envelope/*:instance/{$entity-type-name}/{$property-name}</search:path-index>
          <search:facet-option>limit=10</search:facet-option>
          <search:facet-option>frequency-order</search:facet-option>
          <search:facet-option>descending</search:facet-option>
        </search:range>
      let $constraint-template :=
        <search:constraint name="{ $property-name } ">
          {$range-definition}
        </search:constraint>
      (: the collecting array will be added once after accumulation :)
      let $_ := json:array-push($tuples-range-definitions, $range-definition)
      return
        json:array-push($all-constraints, hent:wrap-duplicates($seen-keys, $property-name, $constraint-template))
    let $_ :=
      if (json:array-size($tuples-range-definitions) gt 1)
      then
        json:array-push($all-tuples-definitions,
          <search:tuples name="{ $entity-type-name }">
            {json:array-values($tuples-range-definitions)}
          </search:tuples>)
      else if (json:array-size($tuples-range-definitions) eq 1)
      then
        json:array-push($all-tuples-definitions,
          <search:values name="{ $entity-type-name }">
            {json:array-values($tuples-range-definitions)}
          </search:values>)
      else ()
    let $_word-constraints :=
      for $property-name in json:array-values(map:get($entity-type, "wordLexicon"))
      return
      json:array-push($all-constraints, hent:wrap-duplicates($seen-keys, $property-name,
        <search:constraint name="{ $property-name } ">
          <search:word>
            <search:element ns="" name="{ $property-name }"/>
          </search:word>
        </search:constraint>))
    return ()
  let $types-expr := string-join( $entity-type-names, "|" )
  let $type-constraint :=
    <search:constraint name="entity-type">
      <search:value>
        <search:element ns="http://marklogic.com/entity-services" name="title"/>
      </search:value>
    </search:constraint>
  return
  <options xmlns="http://marklogic.com/appservices/search">
    <constraint name="Collection">
      <collection/>
    </constraint>
    {
    $type-constraint,
    json:array-values($all-constraints),
    json:array-values($all-tuples-definitions),
    comment {
      "Uncomment to return no results for a blank search, rather than the default of all results
",       xdmp:quote(
    <term>
      <empty apply="no-results"/>
    </term>),
      "
"
    },
    <values name="uris">
      <uri/>
    </values>,
    comment { "Change to 'filtered' to exclude false-positives in certain searches" },
    <search-option>unfiltered</search-option>,
    comment { "Modify document extraction to change results returned" },
    <extract-document-data selected="include">
      <extract-path>/*:envelope/*:instance/({ $types-expr })</extract-path>
    </extract-document-data>,

(:    comment { "Change or remove this additional-query to broaden search beyond entity instance documents" },
    <additional-query>
      <cts:element-query xmlns:cts="http://marklogic.com/cts">
      <cts:element xmlns:es="http://marklogic.com/entity-services">es:instance</cts:element>
      <cts:true-query/>
      </cts:element-query>
    </additional-query>,:)
    <return-facets>true</return-facets>,
    comment { "To return snippets, comment out or remove this option" },
    <transform-results apply="empty-snippet" />
    }
  </options>
};

declare function hent:wrap-duplicates(
    $duplicate-map as map:map,
    $property-name as xs:string,
    $item as element()
) as item()
{
    if (map:contains($duplicate-map, $property-name))
    then
        comment { "This item is a duplicate and is commented out so as to create a valid artifact.
",
            xdmp:quote($item),
            "
"
        }
    else (
        map:put($duplicate-map, $property-name, true()),
        $item)
};

declare function hent:dump-search-options($entities as json:array)
{
  let $uber-model := hent:uber-model(json:array-values($entities) ! xdmp:to-json(.)/object-node())
  return
    hent:search-options-generate($uber-model)
};

declare function hent:dump-indexes($entities as json:array)
{
  let $uber-model := hent:uber-model(json:array-values($entities) ! xdmp:to-json(.)/object-node())
  return
    hent:database-properties-generate($uber-model)
};

declare function hent:database-properties-generate(
  $model as map:map
) as document-node()
{
  let $definitions := map:get($model, "definitions")
  let $entity-type-names := map:keys($definitions)
  let $range-path-indexes := json:array()
  let $word-lexicons := json:array()
  let $range-element-indexes := json:array()
  let $_ :=
    for $entity-type-name in $entity-type-names
    let $entity-type := map:get($definitions, $entity-type-name)
    let $properties := map:get($entity-type, "properties")
    let $range-index-properties := map:get($entity-type, "rangeIndex")
    return (
      for $range-index-property in $range-index-properties ! json:array-values(.)
      let $ri-map := json:object()
      let $property := map:get($properties, $range-index-property)
      let $specified-datatype := es-wrapper:resolve-datatype($model, $entity-type-name, $range-index-property)
      let $datatype := es-wrapper:indexable-datatype($specified-datatype)
      let $collation := head( (map:get($property, "collation"), "http://marklogic.com/collation/en") )
      let $_ := map:put($ri-map, "collation", $collation)
      let $invalid-values := "reject"
      let $_ := map:put($ri-map, "invalid-values", $invalid-values)
      let $_ := map:put($ri-map, "path-expression", "/*:envelope/*:instance/" || $entity-type-name || "/" || $range-index-property)
      let $_ := map:put($ri-map, "range-value-positions", false())
      let $_ := map:put($ri-map, "scalar-type", $datatype)
      return json:array-push($range-path-indexes, $ri-map)
      ,
      let $word-lexicon-properties := map:get($entity-type, "wordLexicon")
      for $word-lexicon-property in $word-lexicon-properties ! json:array-values(.)
      let $wl-map := json:object()
      let $property := map:get($properties, $word-lexicon-property)
      let $collation := head( (map:get($property, "collation"), "http://marklogic.com/collation/en") )
      let $_ := map:put($wl-map, "collation", $collation)
      let $_ := map:put($wl-map, "localname", $word-lexicon-property)
      let $_ := map:put($wl-map, "namespace-uri", "")
      return json:array-push($word-lexicons, $wl-map)
      ,
      let $primary-key-property := map:get($entity-type, "primaryKey")
      where $primary-key-property
      return
        let $pk-map := json:object()
        let $property := map:get($properties, $primary-key-property)
        let $specified-datatype := es-wrapper:resolve-datatype($model, $entity-type-name, $primary-key-property)
        let $datatype := es-wrapper:indexable-datatype($specified-datatype)
        let $collation := head( (map:get($property, "collation"), "http://marklogic.com/collation/en") )
        let $_ := map:put($pk-map, "collation", $collation)
        let $_ := map:put($pk-map, "localname", $primary-key-property)
        let $_ := map:put($pk-map, "namespace-uri", "")
        let $_ := map:put($pk-map, "range-value-positions", fn:false())
        let $_ := map:put($pk-map, "scalar-type", $datatype)
        let $_ := map:put($pk-map, "invalid-values", "reject")
        return json:array-push($range-element-indexes, $pk-map)
    )
  let $path-namespaces := json:array()
  let $pn := json:object()
  let $_ := map:put($pn, "prefix", "es")
  let $_ := map:put($pn, "namespace-uri", "http://marklogic.com/entity-services")
  let $_ := json:array-push($path-namespaces, $pn)
  let $database-properties := json:object()
  let $_ := map:put($database-properties, "path-namespace", $path-namespaces)
  let $_ := map:put($database-properties, "element-word-lexicon", $word-lexicons)
  let $_ := map:put($database-properties, "range-path-index", $range-path-indexes)
  let $_ := map:put($database-properties, "range-element-index", $range-element-indexes)
  return xdmp:to-json($database-properties)
};


[MOD] RESTXQ example updated

[MOD] RESTXQ example updated

(:~
 : This module contains some basic examples for RESTXQ annotations
 : @author BaseX Team
 :)
module namespace page = 'http://basex.org/modules/web-page';

declare %restxq:path("")
        %output:method("xhtml")
        %output:omit-xml-declaration("no")
        %output:doctype-public("-//W3C//DTD XHTML 1.0 Transitional//EN")
        %output:doctype-system("http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd")
  function page:start() {

  let $title := 'Welcome to the RESTXQ Service' return

      <title>{ $title }</title>
      <link rel="stylesheet" type="text/css" href="/style.css"/>

{ $title }

RESTXQ is a new API
      that facilitates the use of XQuery as a Server Side processing language
      for the Web.

      This page has been generated by a RESTXQ module, located in the web
      server's root directory.

The following links return dynamic pages generated by the same code:

hello/World

hello/Universe

The next example processes a simple form via POST:

      <form method="post" action="form">

Your message:

        <input name="message" size="50"></input>
        <input type="submit" />

      </form>

The source of this file is shown below:

{ unparsed-text(static-base-uri()) }

...back to main page

};

declare %restxq:path("hello/{$world}")
        %restxq:GET
        %restxq:header-param("User-Agent", "{$agent}")
        function page:hello($world as xs:string, $agent as xs:string*) {
  <response>
    <title>Hello { $world }!</title>
    <info>You requested this page with { $agent }.</info>
    <time>The current time is: { current-time() }</time>
  </response>
};

declare %restxq:path("form/")
        %restxq:POST
        %restxq:form-param("message","{$message}", "'no message delivered'")
        function page:hello-postman($message as xs:string) {
  <response>
    <title>Hello!</title>
    <info>It seems you posted a message: { $message }</info>
  </response>
};

(:~
 : This module contains some basic examples for RESTXQ annotations
 : @author BaseX Team
 :)
module namespace page = 'http://basex.org/modules/web-page';

declare namespace rest = 'http://exquery.org/ns/restxq';

declare %rest:path("")
        %output:method("xhtml")
        %output:omit-xml-declaration("no")
        %output:doctype-public("-//W3C//DTD XHTML 1.0 Transitional//EN")
        %output:doctype-system("http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd")
  function page:start() {

  let $title := 'Welcome to the RESTXQ Service' return

      <title>{ $title }</title>
      <link rel="stylesheet" type="text/css" href="/style.css"/>

{ $title }

RESTXQ is a new API
      that facilitates the use of XQuery as a Server Side processing language
      for the Web.

      This page has been generated by a RESTXQ module, located in the web
      server's root directory.

The following links return dynamic pages generated by the same code:

hello/World

hello/Universe

The next example processes a simple form via POST:

      <form method="post" action="form">

Your message:

        <input name="message" size="50"></input>
        <input type="submit" />

      </form>

The source of this file is shown below:

{ unparsed-text(static-base-uri()) }

...back to main page

};

declare %rest:path("hello/{$world}")
        %rest:GET
        %rest:header-param("User-Agent", "{$agent}")
        function page:hello($world as xs:string, $agent as xs:string*) {
  <response>
    <title>Hello { $world }!</title>
    <info>You requested this page with { $agent }.</info>
    <time>The current time is: { current-time() }</time>
  </response>
};

declare %rest:path("form/")
        %rest:POST
        %rest:form-param("message","{$message}", "'no message delivered'")
        function page:hello-postman($message as xs:string) {
  <response>
    <title>Hello!</title>
    <info>It seems you posted a message: { $message }</info>
  </response>
};


[ignore] fixed link irc/web interface. Needs to be propagated to demo server

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

xquery version "1.0";
(: $Id$ :)

declare namespace irc="http://exist-db.org/xquery/irclog";
declare namespace request="http://exist-db.org/xquery/request";

declare function irc:display-page($channel as xs:string) as element() {
    util:declare-option("exist:serialize", "media-type=text/html method=xhtml"),

            <title>IRC Log</title>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
            <link type="text/css" href="styles/irclog.css" rel="stylesheet"/>


fixing so that reference URI points to correct browser path

module namespace lib-uri-resolution="http://www.cagrid.org/xquery/library/resolver";
declare namespace openMDR = "http://www.cagrid.org/schema/openMDR";
declare namespace cgResolver = "http://www.cagrid.org/schema/cgResolver";
declare namespace ISO11179= "http://www.cagrid.org/schema/ISO11179";

import module namespace
lib-util="http://www.cagrid.org/xquery/library/util"
at "../library/m-lib-util.xquery";

declare function lib-uri-resolution:resolve($urn as xs:string?, $return-type as xs:string) as xs:anyURI
{
xs:anyURI(
if (starts-with($urn,"urn:lsid:ncicb.nci.nih.gov:nci-thesaurus:"))
    then (
        for $resource as element()* in collection(lib-util:resolverPath())//cgResolver:resource[starts-with($urn, @urn)]
        let $resource-urn := data($resource/@urn)
        return
            for $uri in $resource/cgResolver:uri
            where $uri/@rank = "1"
            and $uri/@return=$return-type
            return replace(xs:string($urn), xs:string($resource-urn), xs:string($uri/text()))
        )
    else if (starts-with($urn,"US-NCICB-CACORE-EVS-METATHESAURUSCONCEPT"))
    then (
        let $urn := concat("urn:",$urn)
        for $resource as element()* in collection(lib-util:resolverPath())//cgResolver:resource[starts-with($urn, @urn)]
        let $resource-urn := data($resource/@urn)
        return
            for $uri in $resource/cgResolver:uri
            where $uri/@rank = "1"
            and $uri/@return=$return-type
            return replace(xs:string($urn), xs:string($resource-urn), xs:string($uri/text()))
        )
    else $urn
)

};

declare function lib-uri-resolution:html-anchor($urn as xs:string, $return-type as xs:string) as element(a)
{
let $uri as xs:anyURI := lib-uri-resolution:resolve($urn, $return-type)

return
   element a {
      attribute href {$uri},
      $urn
      }
};

declare function lib-uri-resolution:html-anchor($urn as xs:string) as element(a)
{
   lib-uri-resolution:html-anchor($urn, 'html')
   };

module namespace lib-uri-resolution="http://www.cagrid.org/xquery/library/resolver";
declare namespace openMDR = "http://www.cagrid.org/schema/openMDR";
declare namespace cgResolver = "http://www.cagrid.org/schema/cgResolver";
declare namespace ISO11179= "http://www.cagrid.org/schema/ISO11179";

import module namespace
lib-util="http://www.cagrid.org/xquery/library/util"
at "../library/m-lib-util.xquery";

declare function lib-uri-resolution:resolve($urn as xs:string?, $return-type as xs:string) as xs:anyURI
{
xs:anyURI(
    if (starts-with($urn,"urn:"))
    then (
        for $resource as element()* in collection(lib-util:resolverPath())//cgResolver:resource[starts-with($urn, @urn)]
        let $resource-urn := data($resource/@urn)
        return
            for $uri in $resource/cgResolver:uri
            where $uri/@rank = "1"
            and $uri/@return=$return-type
            return replace(xs:string($urn), xs:string($resource-urn), xs:string($uri/text()))
        )
    else $urn
)

};

declare function lib-uri-resolution:html-anchor($urn as xs:string, $return-type as xs:string) as element(a)
{
let $uri as xs:anyURI := lib-uri-resolution:resolve($urn, $return-type)

return
   element a {
      attribute href {$uri},
      $urn
      }
};

declare function lib-uri-resolution:html-anchor($urn as xs:string) as element(a)
{
   lib-uri-resolution:html-anchor($urn, 'html')
   };


Added resizable rows in layout. Fixed relative CSS path to be absolute

module namespace template="http://cefn.com/readinglog/template";

import module namespace notes="http://cefn.com/readinglog/notes" at "/home/cefn/Documents/highwire/participatory firmware design/readinglog/scripts/server/xquery/lib/notes.xq";
import module namespace file = "http://expath.org/ns/file";

declare default element namespace "http://www.w3.org/1999/xhtml";

declare namespace ann = "http://www.zorba-xquery.com/annotations";

(: templates a webpage, including the nodes specified :)
declare function template:webpage($nodes){

		    <meta content="text/html; charset=utf-16" http-equiv="content-type"></meta>
			<link href="/style.css" rel="stylesheet" type="text/css"></link>

						Log

						{template:menu()}

						<form action="/search.xq">

									<input type="text" name="text" />
									<input type="submit" value="Search"/>

						</form>

								{$nodes}

};

(: Returns all the document nodes of well-formed XHTML files in a given directory :)
declare %ann:nondeterministic function template:loadhtml($dirpath) {
	for $file in file:list($dirpath) where matches($file,'.+\.html$')
	let $absfile := file:resolve-path(concat($dirpath,$file))
	return doc($absfile)
};

(: Returns all the document nodes of the XHTML note files :)
declare function template:menu(){

Authors

		{
			for $item in $notes:headings where not($item='Authors')
			return

{$item}

		}

};

(: TODO CH Weird Zorba bug to report - declaring content within XML attribute
	literal causes spurious output of a text node as well as insertion of the text
	in the style attribute - try let $share := 100 idiv count($items)  and style="width:{$share}%;"
	within a td literal instead of attribute constructor below to observe the problem :)
declare function template:tabulate($items){
	let $tdstyle := concat("width:",100 idiv count($items),"%;")
	return

				{
					for $item in $items return

						{$item}

				}

};

module namespace template="http://cefn.com/readinglog/template";

import module namespace notes="http://cefn.com/readinglog/notes" at "/home/cefn/Documents/highwire/participatory firmware design/readinglog/scripts/server/xquery/lib/notes.xq";
import module namespace file = "http://expath.org/ns/file";

declare default element namespace "http://www.w3.org/1999/xhtml";

declare namespace ann = "http://www.zorba-xquery.com/annotations";

(: templates a webpage, including the nodes specified :)
declare function template:webpage($nodes){

		    <meta content="text/html; charset=utf-16" http-equiv="content-type"></meta>
			<link href="style.css" rel="stylesheet" type="text/css"></link>

						PhD Reading Log

						{template:menu()}

						<form action="/search.xq">

									<input type="text" name="text" />
									<input type="submit" value="Search"/>

						</form>

								{$nodes}

};

(: Returns all the document nodes of well-formed XHTML files in a given directory :)
declare %ann:nondeterministic function template:loadhtml($dirpath) {
	for $file in file:list($dirpath) where matches($file,'.+\.html$')
	let $absfile := file:resolve-path(concat($dirpath,$file))
	return doc($absfile)
};

(: Returns all the document nodes of the XHTML note files :)
declare function template:menu(){

Authors

		{
			for $item in $notes:headings where not($item='Authors')
			return

{$item}

		}

};

(: TODO CH Weird Zorba bug to report - declaring content within XML attribute
	literal causes spurious output of a text node as well as insertion of the text
	in the style attribute - try let $share := 100 idiv count($items)  and style="width:{$share}%;"
	within a td literal instead of attribute constructor below to observe the problem :)
declare function template:tabulate($items){
	let $tdstyle := concat("width:",100 idiv count($items),"%;")
	return

				{
					for $item in $items return

						{$item}

				}

};


comment out merge when a document is registered

(:~
: This is the Care Services Discovery stored query registry
: @version 1.0
: @see https://github.com/his-interop/openinfoman
:
:)
module namespace csd_dm = "https://github.com/his-interop/openinfoman/csd_dm";

(:import module namespace csd_mcs = "https://github.com/his-interop/openinfoman/csd_mcs" at "csd_merge_cached_services.xqm";:)

declare namespace csd = "urn:ihe:iti:csd:2013";
declare default element  namespace   "urn:ihe:iti:csd:2013";

declare variable $csd_dm:document_manager := 'csd_docs.xml';

declare updating function csd_dm:init($db) {
  if ( not(csd_dm:dm_exists($db)))  then
    db:add($db, <documentLibrary/>,$csd_dm:document_manager)
  else
      ()
};

declare function csd_dm:dm_exists($db) {
  db:is-xml($db,$csd_dm:document_manager)
};

declare  function csd_dm:document_source_exists($db,$name) {
  let $source := csd_dm:document_source($db,$name)
  return  (db:is-xml($db,$source))

};

declare  function csd_dm:open_document($db,$name) {
  if (csd_dm:document_source_exists($db,$name)) then
    db:open($db,csd_dm:document_source($db,$name))
  else
    ()
};

declare updating function csd_dm:register_document($db,$name,$source) {
  let $dm :=  db:open($db,$csd_dm:document_manager)/documentLibrary
  let $reg_doc := <document name="{$name}" source="{$source}"/>
  let $existing := $dm/document[@name = $name]
  return if (not(exists($existing)))  then
       (insert node $reg_doc into $dm (:, csd_mcs:merge($db) :) )
     else
       (replace  node $existing with $reg_doc (:, csd_mcs:merge($db) :))
};

declare  function csd_dm:is_registered($db,$name) {
  if (csd_dm:dm_exists($db)) then
    let $dm :=  db:open($db,$csd_dm:document_manager)/documentLibrary
    let $existing := $dm/document[@name = $name]
    return exists($existing)
  else
    false()

};

declare updating function csd_dm:deregister_document($db,$name) {
  if (csd_dm:dm_exists($db)) then
    let $dm :=  db:open($db,$csd_dm:document_manager)/documentLibrary
    let $existing := $dm/document[@name = $name]
    return if (exists($existing))  then
      (delete node $existing(:, csd_mcs:merge($db):))
    else ()
  else ()

};

declare function csd_dm:registered_documents($db) {
  if (csd_dm:dm_exists($db)) then
    for $name in db:open($db,$csd_dm:document_manager)/documentLibrary/document/text{@name}
    where csd_dm:document_source_exists($db,$name)
      return $name
  else
    ()
};

declare function csd_dm:document_source($db,$name) {
    db:open($db,$csd_dm:document_manager)/documentLibrary/document[@name = $name]/@source
};

(:~
: This is the Care Services Discovery stored query registry
: @version 1.0
: @see https://github.com/his-interop/openinfoman
:
:)
module namespace csd_dm = "https://github.com/his-interop/openinfoman/csd_dm";

import module namespace csd_mcs = "https://github.com/his-interop/openinfoman/csd_mcs" at "csd_merge_cached_services.xqm";

declare namespace csd = "urn:ihe:iti:csd:2013";
declare default element  namespace   "urn:ihe:iti:csd:2013";

declare variable $csd_dm:document_manager := 'csd_docs.xml';

declare updating function csd_dm:init($db) {
  if ( not(csd_dm:dm_exists($db)))  then
    db:add($db, <documentLibrary/>,$csd_dm:document_manager)
  else
      ()
};

declare function csd_dm:dm_exists($db) {
  db:is-xml($db,$csd_dm:document_manager)
};

declare  function csd_dm:document_source_exists($db,$name) {
  let $source := csd_dm:document_source($db,$name)
  return  (db:is-xml($db,$source))

};

declare  function csd_dm:open_document($db,$name) {
  if (csd_dm:document_source_exists($db,$name)) then
    db:open($db,csd_dm:document_source($db,$name))
  else
    ()
};

declare updating function csd_dm:register_document($db,$name,$source) {
  let $dm :=  db:open($db,$csd_dm:document_manager)/documentLibrary
  let $reg_doc := <document name="{$name}" source="{$source}"/>
  let $existing := $dm/document[@name = $name]
  return if (not(exists($existing)))  then
      (insert node $reg_doc into $dm, csd_mcs:merge($db))
    else
      (replace  node $existing with $reg_doc, csd_mcs:merge($db))
};

declare  function csd_dm:is_registered($db,$name) {
  if (csd_dm:dm_exists($db)) then
    let $dm :=  db:open($db,$csd_dm:document_manager)/documentLibrary
    let $existing := $dm/document[@name = $name]
    return exists($existing)
  else
    false()

};

declare updating function csd_dm:deregister_document($db,$name) {
  if (csd_dm:dm_exists($db)) then
    let $dm :=  db:open($db,$csd_dm:document_manager)/documentLibrary
    let $existing := $dm/document[@name = $name]
    return if (exists($existing))  then
      (delete node $existing, csd_mcs:merge($db))
    else ()
  else ()

};

declare function csd_dm:registered_documents($db) {
  if (csd_dm:dm_exists($db)) then
    for $name in db:open($db,$csd_dm:document_manager)/documentLibrary/document/text{@name}
    where csd_dm:document_source_exists($db,$name)
      return $name
  else
    ()
};

declare function csd_dm:document_source($db,$name) {
    db:open($db,$csd_dm:document_manager)/documentLibrary/document[@name = $name]/@source
};