Refined code for handling link management index creation stage 3

(:~
 : DITA for Small Teams
 :
 : Link management application RESTXQ implementation.
 :
 : Copyright (c) 2015 DITA for Small Teams (dita-for-small-teams.org)
 :
 : See the BaseX RESTXQ documentation for details on how the RESTXQ
 : mechanism works.
 :
 :)
module namespace page = 'http://basex.org/modules/web-page';

import module namespace html = 'dba/html';
import module namespace bxutil="http://dita-for-small-teams.org/xquery/modules/basex-utils";
import module namespace lmutil="http://dita-for-small-teams.org/xquery/modules/linkmgmt-utils";
import module namespace df="http://dita-for-small-teams.org/xquery/modules/dita-utils";
import module namespace linkmgr='http://basex.org/modules/linkmgr' at "linkmgrViews.xqm";
import module namespace lmm="http://dita-for-small-teams.org/xquery/modules/linkmgr-model";
import module namespace lmc="http://dita-for-small-teams.org/xquery/modules/linkmgr-controller";
import module namespace dfstcnst="http://dita-for-small-teams.org/xquery/modules/dfst-constants";

(:~
 : This function generates the welcome page.
 : @return HTML page
 :)
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()
  as element(Q{http://www.w3.org/1999/xhtml}html)
{

      <title>DITA for Small Teams Link Manager</title>
      <link rel="stylesheet" type="text/css" href="static/style.css"/>

www.dita-for-small-teams.org

DITA for Small Teams Link Manager

Repositories and Branches

                Repository
                Branches

            {page:listReposAndBranches()}

};

declare
  %rest:path("/repo/{$repo}/{$branch}")
  %rest:query-param("infoMessage",    "{$infoMessage}")
  %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:branchMainView($repo as xs:string, $branch as xs:string, $infoMessage as xs:string?)
  as element(Q{http://www.w3.org/1999/xhtml}html)
{

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

www.dita-for-small-teams.org

Git repo {$repo}/{$branch}

      [Update Link Management Indexes]
      { if ($infoMessage and $infoMessage != '')
           then
{$infoMessage}

           else ()
      }

DITA Maps

                Title
                Path
                Root Map
                Actions

            { if (false())
                 then
No maps found

                 else page:listMapsInBranch($repo, $branch)
            }

DITA Topics

                Title
                Path
                Actions

            { if (false())
                 then
No maps found

                 else page:listTopicsInBranch($repo, $branch)
            }

};

(:~
 : List the databases that represent git repositories and branches
 : within those repositories.
 :
 : Result is a set of HTML table rows.
 :)
 declare function page:listReposAndBranches() as element()* {

    (: Get the repository info as an XML structure :)
    let $repos := bxutil:getGitRepositoryInfos()
    for $repo in $repos (: Sequence of <repo> elements :)
        let $branch := $repo/branch[1]
        let $path := concat($repo/@name, '/', $branch/@name)
        let $target := concat($repo/@name, '_', $branch/@name)
        return (

         {string($repo/@name)}
         {string($branch/@name)}

,
        for $branch in $repo/branch[position() gt 1]
            let $path := concat($repo/@name, '/', $branch/@name)
            let $target := concat($repo/@name, '_', $branch/@name)
            return

               {string($branch/@name)}

        )
        (:

      some repo
      master

,

      develop

    :)
 };

(:~
 : List the DITA maps within the specified branch of the specified repository.
 :
 : Result is a set of HTML table rows.
 :)
 declare function page:listMapsInBranch($repo as xs:string, $branch as xs:string) as element()* {
    let $dbName := bxutil:getDbNameForRepoAndBranch($repo, $branch)
    let $maps := df:getMaps($dbName)
    for $map in $maps
      let $docURI := document-uri($map)
      return

        {df:getTitleText($map/*)}
        {linkmgr:makeLinkToDocSource(document-uri(root($map)))}
        {
          let $resolvedMap as element()? := lmutil:getResolvedMapForMap($map/*)
          return if (not($resolvedMap))
             then "?"
             else if (lmc:isRootMap($map))
                     then '✓'
                     else ''
        }

        [{html:linkToTarget('Navigation Tree', concat('/linkmgr/navtreeView/', document-uri($map)),
         'navtree')}]
        [{html:linkToTarget('Map Tree', concat('/linkmgr/maptreeView/', document-uri($map)), 'maptree')}]
        [{html:linkToTarget('Dependencies', concat('/linkmgr/dependencyView/', document-uri($map)), 'dependencies')}]
        [{html:linkToTarget('Reltables', concat('/linkmgr/reltableView/', document-uri($map)), 'reltables')}]
        [{html:linkToTarget('Key Spaces', concat('/linkmgr/keyspaceView/', document-uri($map)), 'keyspaces')}]
        [{html:linkToTarget('Preview', concat('/linkmgr/docview/', document-uri($map), '/preview'), 'preview')}]
        [{html:linkToTarget('Where Used?', concat('/linkmgr/whereUsedView/', $docURI), 'whereused')}]

 };

(:~
 : List the DITA topics within the specified branch of the specified repository.
 :
 : Result is a set of HTML table rows.
 :)
 declare function page:listTopicsInBranch($repo as xs:string, $branch as xs:string) as element()* {
    let $dbName := bxutil:getDbNameForRepoAndBranch($repo, $branch)
    let $topics := df:getTopicDocs($dbName)
    for $topic in $topics
      let $docURI := document-uri($topic)
      return

        {df:getTitleText($topic/*)}
        {linkmgr:makeLinkToDocSource(document-uri(root($topic)))}
        [{html:linkToTarget('Where Used?', concat('/linkmgr/whereUsedView/', $docURI), 'whereused')}]
            [{html:linkToTarget('Dependencies', concat('/linkmgr/dependencyView/', $docURI), 'dependencies')}]
        [{html:linkToTarget('Preview', concat('/linkmgr/docview/', $docURI, '/preview'), 'preview')}]

 };

(:~
 : This function returns an XML response message.
 : @param $world  string to be included in the response
 : @return response element
 :)
declare
  %rest:path("/linkmgr")
  %rest:GET
  function page:linkmgr(
    )
    as element(response)
{
  <response>
    <title>Linkmgr</title>
    <time>Link manager response: The current time is: { current-time() }</time>
  </response>
};

(: REST API to trigger creation or update of link management indexes. :)
declare
  %updating
  %rest:path("/repo/{$repo}/{$branch}/updateLinkManagementIndexes")
  %rest:GET
  %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:updateLinkManagementIndexes($repo as xs:string, $branch as xs:string) {

  let $contentDbName := bxutil:getDbNameForRepoAndBranch($repo, $branch)
  let $metadataDbName := bxutil:getMetadataDbNameForRepoAndBranch($repo, $branch)
  let $logID := "lmindex-update-log"

  return db:output(web:redirect(concat("/repo/", $repo, "/", $branch, "/updateLMIStage1"),
                                 map { 'contentDbName' : $contentDbName,
                                       'metadataDbName' : $metadataDbName,
                                       'logID' : $logID
                                     }))
};

(: Link management index stage 1: Clear the database and construct all
 : direct link resource records. On return, redirects to stage 2.
 :)
declare
  %updating
  %rest:path("/repo/{$repo}/{$branch}/updateLMIStage1")
  %rest:query-param("contentDbName",  "{$contentDbName}")
  %rest:query-param("metadataDbName", "{$metadataDbName}")
  %rest:query-param("logID",          "{$logID}")
  %rest:GET
  %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:updateStage1($repo as xs:string,
                             $branch as xs:string,
                             $contentDbName as xs:string,
                             $metadataDbName as xs:string,
                             $logID as xs:string) {

  let $directLinks := lmutil:findAllDirectLinks($contentDbName)

  return
      try {
          db:delete($metadataDbName, $dfstcnst:where-used-dir),
          db:delete($metadataDbName, $dfstcnst:resolved-map-dir),
          db:delete($metadataDbName, $dfstcnst:keyspaces-dir),
          (: FIXME: Initialize the update log document :)
          (: Now create new resource use records for direct links :)

          lmm:createDirectLinkResourceRecords($metadataDbName, $directLinks, $logID),
          db:output(web:redirect(concat("/repo/", $repo, "/", $branch, "/updateLMIStage2"),
                                 map { 'contentDbName' : $contentDbName,
                                       'metadataDbName' : $metadataDbName,
                                       'logID' : $logID
                                     }))
      } catch * {
          db:output(web:redirect("/error",
                                 map { 'contentDbName' : $contentDbName,
                                       'metadataDbName' : $metadataDbName,
                                       'error' : $err:description,
                                       'logID' : $logID

                                     }))
      }

};

(:~
 : Link management index update stage 2: Construct key spaces.
 : On return redirects to stage 3.
 :)
declare
  %updating
  %rest:path("/repo/{$repo}/{$branch}/updateLMIStage2")
  %rest:query-param("contentDbName",  "{$contentDbName}")
  %rest:query-param("metadataDbName", "{$metadataDbName}")
  %rest:query-param("logID",          "{$logID}")
  %rest:GET
  %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:updateStage2($repo as xs:string,
                             $branch as xs:string,
                             $contentDbName as xs:string,
                             $metadataDbName as xs:string,
                             $logID as xs:string
                             ) {
   try {

        (: Now create resolved maps for each of the root maps.
         The resolved maps serve to enable key resolution
         without creating separate data sets just for the key spaces.
       :)
      lmm:constructKeySpaces(
         $contentDbName,
         $metadataDbName,
         $logID),
      db:output(web:redirect(concat("/repo/", $repo, "/", $branch, "/updateLMIStage3"),
                                       map { 'contentDbName' : $contentDbName,
                                             'metadataDbName' : $metadataDbName,
                                             'logID' : $logID
                                           }))
    } catch * {
          db:output(web:redirect("/error",
                                 map { 'contentDbName' : $contentDbName,
                                       'metadataDbName' : $metadataDbName,
                                       'error' : $err:description,
                                       'logID' : $logID

                                     }))
    }

};

(:~
 : Link management index update stage 3: Construct indirect link
 : where-used records.
 : On return redirects back to the page for the repo and branch.
 :)
declare
  %updating
  %rest:path("/repo/{$repo}/{$branch}/updateLMIStage3")
  %rest:query-param("contentDbName",  "{$contentDbName}")
  %rest:query-param("metadataDbName", "{$metadataDbName}")
  %rest:query-param("logID",          "{$logID}")
  %rest:GET
  %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:updateStage3($repo as xs:string,
                             $branch as xs:string,
                             $contentDbName as xs:string,
                             $metadataDbName as xs:string,
                             $logID as xs:string
                             ) {

    try {

      let $indirectLinks := lmutil:findAllIndirectLinks($contentDbName)
        (: Now create resolved maps for each of the root maps.
         The resolved maps serve to enable key resolution
         without creating separate data sets just for the key spaces.
       :)
      return
      lmm:createIndirectLinkResourceRecords(
                  $metadataDbName,
                  $indirectLinks,
                  $logID),
      db:output(web:redirect(concat("/repo/", $repo, "/", $branch),
                                       map { 'infoMessage' : 'Link management indexes updated'
                                           }))
    } catch * {
          db:output(web:redirect("/error",
                                 map { 'contentDbName' : $contentDbName,
                                       'metadataDbName' : $metadataDbName,
                                       'error' : $err:description,
                                       'logID' : $logID

                                     }))
    }

};

(:~
 : Generic error report page. This is a placeholder in advance of more sophisticated
 : error reporting infrastructure.
 :)
declare
  %rest:path("/error")
  %rest:query-param("contentDbName",    "{$contentDbName}")
  %rest:query-param("metadataDbName", "{$metadataDbName}")
  %rest:query-param("error", "{$error}")
  %rest:GET
  %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:reportError($contentDbName as xs:string?,
                            $metadataDbName as xs:string?,
                            $error as xs:string?
                           ) {

     <title>Error</title>

Error

Error

content database: {$contentDbName}

Metadata database: {$metadataDbName}

Error message: {$error}

};

declare function page:formatLogAsHtml($log) as element() {

  {for $entry in $log/*
       return concat('[', upper-case(name($entry)), ']', ' ', string($entry), '
')
  }

};

(:~
 : DITA for Small Teams
 :
 : Link management application RESTXQ implementation.
 :
 : Copyright (c) 2015 DITA for Small Teams (dita-for-small-teams.org)
 :
 : See the BaseX RESTXQ documentation for details on how the RESTXQ
 : mechanism works.
 :
 :)
module namespace page = 'http://basex.org/modules/web-page';

import module namespace html = 'dba/html';
import module namespace bxutil="http://dita-for-small-teams.org/xquery/modules/basex-utils";
import module namespace lmutil="http://dita-for-small-teams.org/xquery/modules/linkmgmt-utils";
import module namespace df="http://dita-for-small-teams.org/xquery/modules/dita-utils";
import module namespace linkmgr='http://basex.org/modules/linkmgr' at "linkmgrViews.xqm";
import module namespace lmm="http://dita-for-small-teams.org/xquery/modules/linkmgr-model";
import module namespace lmc="http://dita-for-small-teams.org/xquery/modules/linkmgr-controller";
import module namespace dfstcnst="http://dita-for-small-teams.org/xquery/modules/dfst-constants";

(:~
 : This function generates the welcome page.
 : @return HTML page
 :)
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()
  as element(Q{http://www.w3.org/1999/xhtml}html)
{

      <title>DITA for Small Teams Link Manager</title>
      <link rel="stylesheet" type="text/css" href="static/style.css"/>

www.dita-for-small-teams.org

DITA for Small Teams Link Manager

Repositories and Branches

                Repository
                Branches

            {page:listReposAndBranches()}

};

declare
  %rest:path("/repo/{$repo}/{$branch}")
  %rest:query-param("infoMessage",    "{$infoMessage}")
  %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:branchMainView($repo as xs:string, $branch as xs:string, $infoMessage as xs:string?)
  as element(Q{http://www.w3.org/1999/xhtml}html)
{

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

www.dita-for-small-teams.org

Git repo {$repo}/{$branch}

      [Update Link Management Indexes]
      { if ($infoMessage and $infoMessage != '')
           then
{$infoMessage}

           else ()
      }

DITA Maps

                Title
                Path
                Root Map
                Actions

            { if (false())
                 then
No maps found

                 else page:listMapsInBranch($repo, $branch)
            }

DITA Topics

                Title
                Path
                Actions

            { if (false())
                 then
No maps found

                 else page:listTopicsInBranch($repo, $branch)
            }

};

(:~
 : List the databases that represent git repositories and branches
 : within those repositories.
 :
 : Result is a set of HTML table rows.
 :)
 declare function page:listReposAndBranches() as element()* {

    (: Get the repository info as an XML structure :)
    let $repos := bxutil:getGitRepositoryInfos()
    for $repo in $repos (: Sequence of <repo> elements :)
        let $branch := $repo/branch[1]
        let $path := concat($repo/@name, '/', $branch/@name)
        let $target := concat($repo/@name, '_', $branch/@name)
        return (

         {string($repo/@name)}
         {string($branch/@name)}

,
        for $branch in $repo/branch[position() gt 1]
            let $path := concat($repo/@name, '/', $branch/@name)
            let $target := concat($repo/@name, '_', $branch/@name)
            return

               {string($branch/@name)}

        )
        (:

      some repo
      master

,

      develop

    :)
 };

(:~
 : List the DITA maps within the specified branch of the specified repository.
 :
 : Result is a set of HTML table rows.
 :)
 declare function page:listMapsInBranch($repo as xs:string, $branch as xs:string) as element()* {
    let $dbName := bxutil:getDbNameForRepoAndBranch($repo, $branch)
    let $maps := df:getMaps($dbName)
    for $map in $maps
      let $docURI := document-uri($map)
      return

        {df:getTitleText($map/*)}
        {linkmgr:makeLinkToDocSource(document-uri(root($map)))}
        {
          let $resolvedMap as element()? := lmutil:getResolvedMapForMap($map/*)
          return if (not($resolvedMap))
             then "?"
             else if (lmc:isRootMap($map))
                     then '✓'
                     else ''
        }

        [{html:linkToTarget('Navigation Tree', concat('/linkmgr/navtreeView/', document-uri($map)),
         'navtree')}]
        [{html:linkToTarget('Map Tree', concat('/linkmgr/maptreeView/', document-uri($map)), 'maptree')}]
        [{html:linkToTarget('Dependencies', concat('/linkmgr/dependencyView/', document-uri($map)), 'dependencies')}]
        [{html:linkToTarget('Reltables', concat('/linkmgr/reltableView/', document-uri($map)), 'reltables')}]
        [{html:linkToTarget('Key Spaces', concat('/linkmgr/keyspaceView/', document-uri($map)), 'keyspaces')}]
        [{html:linkToTarget('Preview', concat('/linkmgr/docview/', document-uri($map), '/preview'), 'preview')}]
        [{html:linkToTarget('Where Used?', concat('/linkmgr/whereUsedView/', $docURI), 'whereused')}]

 };

(:~
 : List the DITA topics within the specified branch of the specified repository.
 :
 : Result is a set of HTML table rows.
 :)
 declare function page:listTopicsInBranch($repo as xs:string, $branch as xs:string) as element()* {
    let $dbName := bxutil:getDbNameForRepoAndBranch($repo, $branch)
    let $topics := df:getTopicDocs($dbName)
    for $topic in $topics
      let $docURI := document-uri($topic)
      return

        {df:getTitleText($topic/*)}
        {linkmgr:makeLinkToDocSource(document-uri(root($topic)))}
        [{html:linkToTarget('Where Used?', concat('/linkmgr/whereUsedView/', $docURI), 'whereused')}]
            [{html:linkToTarget('Dependencies', concat('/linkmgr/dependencyView/', $docURI), 'dependencies')}]
        [{html:linkToTarget('Preview', concat('/linkmgr/docview/', $docURI, '/preview'), 'preview')}]

 };

(:~
 : This function returns an XML response message.
 : @param $world  string to be included in the response
 : @return response element
 :)
declare
  %rest:path("/linkmgr")
  %rest:GET
  function page:linkmgr(
    )
    as element(response)
{
  <response>
    <title>Linkmgr</title>
    <time>Link manager response: The current time is: { current-time() }</time>
  </response>
};

(: REST API to trigger creation or update of link management indexes. :)
declare
  %updating
  %rest:path("/repo/{$repo}/{$branch}/updateLinkManagementIndexes")
  %rest:GET
  %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:updateLinkManagementIndexes($repo as xs:string, $branch as xs:string) {

  let $contentDbName := bxutil:getDbNameForRepoAndBranch($repo, $branch)
  let $metadataDbName := bxutil:getMetadataDbNameForRepoAndBranch($repo, $branch)
  let $logID := "lmindex-update-log"

  return db:output(web:redirect(concat("/repo/", $repo, "/", $branch, "/updateLMIStage1"),
                                 map { 'contentDbName' : $contentDbName,
                                       'metadataDbName' : $metadataDbName,
                                       'logID' : $logID
                                     }))
};

(: Link management index stage 1: Clear the database and construct all
 : direct link resource records. On return, redirects to stage 2.
 :)
declare
  %updating
  %rest:path("/repo/{$repo}/{$branch}/updateLMIStage1")
  %rest:query-param("contentDbName",  "{$contentDbName}")
  %rest:query-param("metadataDbName", "{$metadataDbName}")
  %rest:query-param("logID",          "{$logID}")
  %rest:GET
  %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:updateStage1($repo as xs:string,
                             $branch as xs:string,
                             $contentDbName as xs:string,
                             $metadataDbName as xs:string,
                             $logID as xs:string) {

  let $directLinks := lmutil:findAllDirectLinks($contentDbName)

  return
      try {
          db:delete($metadataDbName, $dfstcnst:where-used-dir),
          db:delete($metadataDbName, $dfstcnst:resolved-map-dir),
          db:delete($metadataDbName, $dfstcnst:keyspaces-dir),
          (: FIXME: Initialize the update log document :)
          (: Now create new resource use records for direct links :)

          lmm:createDirectLinkResourceRecords($metadataDbName, $directLinks, $logID),
          db:output(web:redirect(concat("/repo/", $repo, "/", $branch, "/updateLMIStage2"),
                                 map { 'contentDbName' : $contentDbName,
                                       'metadataDbName' : $metadataDbName,
                                       'logID' : $logID
                                     }))
      } catch * {
          db:output(web:redirect("/error",
                                 map { 'contentDbName' : $contentDbName,
                                       'metadataDbName' : $metadataDbName,
                                       'error' : $err:description,
                                       'logID' : $logID

                                     }))
      }

};

(:~
 : Link management index update stage 2: Construct key spaces.
 : On return redirects to stage 3.
 :)
declare
  %updating
  %rest:path("/repo/{$repo}/{$branch}/updateLMIStage2")
  %rest:query-param("contentDbName",  "{$contentDbName}")
  %rest:query-param("metadataDbName", "{$metadataDbName}")
  %rest:query-param("logID",          "{$logID}")
  %rest:GET
  %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:updateStage2($repo as xs:string,
                             $branch as xs:string,
                             $contentDbName as xs:string,
                             $metadataDbName as xs:string,
                             $logID as xs:string
                             ) {
   try {

        (: Now create resolved maps for each of the root maps.
         The resolved maps serve to enable key resolution
         without creating separate data sets just for the key spaces.
       :)
      lmm:constructKeySpaces(
         $contentDbName,
         $metadataDbName,
         $logID),
      db:output(web:redirect(concat("/repo/", $repo, "/", $branch, "/updateLMIStage3"),
                                       map { 'contentDbName' : $contentDbName,
                                             'metadataDbName' : $metadataDbName,
                                             'logID' : $logID
                                           }))
    } catch * {
          db:output(web:redirect("/error",
                                 map { 'contentDbName' : $contentDbName,
                                       'metadataDbName' : $metadataDbName,
                                       'error' : $err:description,
                                       'logID' : $logID

                                     }))
    }

};

(:~
 : Link management index update stage 3: Construct indirect link
 : where-used records.
 : On return redirects back to the page for the repo and branch.
 :)
declare
  %updating
  %rest:path("/repo/{$repo}/{$branch}/updateLMIStage3")
  %rest:query-param("contentDbName",  "{$contentDbName}")
  %rest:query-param("metadataDbName", "{$metadataDbName}")
  %rest:query-param("logID",          "{$logID}")
  %rest:GET
  %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:updateStage3($repo as xs:string,
                             $branch as xs:string,
                             $contentDbName as xs:string,
                             $metadataDbName as xs:string,
                             $logID as xs:string
                             ) {
   try {

        (: Now create resolved maps for each of the root maps.
         The resolved maps serve to enable key resolution
         without creating separate data sets just for the key spaces.
       :)
      lmm:constructKeySpaces(
         $contentDbName,
         $metadataDbName,
         $logID),
      db:output(web:redirect(concat("/repo/", $repo, "/", $branch),
                                       map { 'infoMessage' : 'Link management indexes updated'
                                           }))
    } catch * {
          db:output(web:redirect("/error",
                                 map { 'contentDbName' : $contentDbName,
                                       'metadataDbName' : $metadataDbName,
                                       'error' : $err:description,
                                       'logID' : $logID

                                     }))
    }

};

(: REST API to trigger creation or update of link management indexes. :)
declare
  %rest:path("/error")
  %rest:query-param("contentDbName",    "{$contentDbName}")
  %rest:query-param("metadataDbName", "{$metadataDbName}")
  %rest:query-param("error", "{$error}")
  %rest:GET
  %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:reportError($contentDbName as xs:string?,
                            $metadataDbName as xs:string?,
                            $error as xs:string?
                           ) {

     <title>Error</title>

Error

Error

content database: {$contentDbName}

Metadata database: {$metadataDbName}

Error message: {$error}

};

declare function page:formatLogAsHtml($log) as element() {

  {for $entry in $log/*
       return concat('[', upper-case(name($entry)), ']', ' ', string($entry), '
')
  }

};


removed test because required expression is not implemented currently

let $x := ("a", "b", "c")
where fn:boolean($x[0])
return $x


Update BaseX RESTXQ

Update BaseX RESTXQ

xquery version "3.0";
(:~
 : RESTXQ 1.0: RESTful Annotations for XQuery (EXQuery Unofficial Draft 21 March 2016)
 :
 : @see http://exquery.github.io/exquery/exquery-restxq-specification/restxq-1.0-specification.html
 : @see http://docs.basex.org/wiki/RESTXQ_Module
 :)
module namespace rest = "http://exquery.org/ns/restxq";

declare namespace a = "http://reecedunn.co.uk/xquery/annotations";
declare namespace o = "http://reecedunn.co.uk/xquery/options";

declare namespace wadl = "http://wadl.dev.java.net/2009/02";

declare option o:requires-import "basex/7.7; until=basex/9.2; location-uri=(none)";

declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:DELETE() external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:GET() external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:HEAD() external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:OPTIONS() external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:POST() external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:POST($body-param as xs:string) external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:PUT() external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:PUT($body-param as xs:string) external;

declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:consumes($mimetype1 as xs:string, $mimetypes as xs:string ...) external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:cookie-param($name as xs:string, $param-reference as xs:string, $default-value as xs:string ...) external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:form-param($name as xs:string, $param-reference as xs:string, $default-value as xs:string ...) external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:header-param($name as xs:string, $param-reference as xs:string, $default-value as xs:string ...) external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:path($path as xs:string) external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:produces($mimetype1 as xs:string, $mimetypes as xs:string ...) external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:annotation function rest:query-param($name as xs:string, $param-reference as xs:string, $default-value as xs:string ...) external;

declare %a:since("exquery-restxq", "1.0-20160321") function rest:resource-functions() as document-node(element(rest:resource-functions)) external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:since("basex", "7.7") function rest:base-uri() as xs:anyURI external;
declare %a:since("exquery-restxq", "1.0-20160321") %a:since("basex", "7.7") function rest:uri() as xs:anyURI external;

declare %a:since("basex", "7.7") function rest:wadl() as element(wadl:application) external;
declare %a:since("basex", "8.6") function rest:init() as empty-sequence() external;
declare %a:since("basex", "9.4") function rest:init($update as xs:boolean) as empty-sequence() external;


-Fixed serialize-uri.xq

-Fixed serialize-uri.xq

import module namespace uri = "http://www.zorba-xquery.com/modules/uri";

let $xml-uri-test := fn:doc("uri-source.xml")//uri-element/text

for $uri in $xml-uri-test
let $parsed-uri := uri:parse($uri)
return concat(uri:serialize($parsed-uri), " == ", $uri/text(), "
")

import module namespace uri = "http://www.zorba-xquery.com/modules/uri";

let $xml-uri-test := fn:doc("uri-source.xml")//uri-element/text

for $uri in $xml-uri-test
let $parsed-uri := uri:parse($uri)
return concat($uri/text(), "
")

(:return concat(uri:serialize($parsed-uri), " == ", $uri/text(), "
"):)


Hide groups collection

Hide groups collection

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

xquery version "1.0";

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";
import module namespace config="http://exist-db.org/mods/config" at "config.xqm";
declare namespace session = "http://exist-db.org/xquery/session";
declare namespace xmldb = "http://exist-db.org/xquery/xmldb";

declare option exist:serialize "method=text media-type=text/javascript";

declare function local:sub-collections($root as xs:string, $children as xs:string*) {
    fn:string-join(
        for $child in $children
        return
            local:collections(concat($root, '/', $child)),
        ","
    )
};

declare function local:collections($root as xs:string) {

    let $user := security:get-user-credential-from-session()[1],
    $children := xmldb:get-child-collections($root),
    $can-write := security:can-write-collection($user, $root) return

        if(security:can-read-collection($user, $root)) then

            (: is this the users collection? :)
            if($root eq $security:users-collection) then
            (
                (: users collection is treated specially :)
                if($children = $user)then
                (
                    (: found a mods home collection for the currently logged in user :)
                    let $home-collection-uri := security:get-home-collection-uri($user) return
                        <s>{{ "title": "Home", "isFolder": true, "key": "{$home-collection-uri}",
                            "writable": {if ($can-write) then 'true' else 'false' },
                            "addClass": "{if ($can-write) then 'writable' else 'readable'}",
                            "icon": "../css/dynatree/ltFld.user.gif"
                            {
                                let $home-children := xmldb:get-child-collections($home-collection-uri) return
                                    if(fn:exists($home-children)) then
                                        <s>, "children": [
                                        {
                                            local:sub-collections($home-collection-uri, $home-children)
                                        }
                                        ]
                                        </s>
                                    else
                                        ()
                            }
                        }}</s>
                )else()
            )

            (: groups collection is treated specially, i.e. skipped :)
            else if($root eq $sharing:groups-collection)then
            ()

            else
            (   (: normal collection:)
                <s>{{ "title": "{fn:replace($root, '.*/','')}", "isFolder": true, "key": "{$root}",
                    "writable": {if ($can-write) then 'true' else 'false' },
                    "addClass": "{if ($can-write) then 'writable' else 'readable'}"
                    {
                        if (fn:exists($children)) then
                            <s>, "children": [
                            {
                                local:sub-collections($root, $children)
                            }
                            ]
                            </s>
                        else
                            ()
                    }
                }}</s>
            )
        else
            ()
};

<s>[{local:collections($config:mods-root)}]</s>

xquery version "1.0";

import module namespace security="http://exist-db.org/mods/security" at "security.xqm";
import module namespace config="http://exist-db.org/mods/config" at "config.xqm";
declare namespace session = "http://exist-db.org/xquery/session";
declare namespace xmldb = "http://exist-db.org/xquery/xmldb";

declare option exist:serialize "method=text media-type=text/javascript";

declare function local:sub-collections($root as xs:string, $children as xs:string*) {
    fn:string-join(
        for $child in $children
        return
            local:collections(concat($root, '/', $child)),
        ","
    )
};

declare function local:collections($root as xs:string) {

    let $user := security:get-user-credential-from-session()[1],
    $children := xmldb:get-child-collections($root),
    $can-write := security:can-write-collection($user, $root) return

        if(security:can-read-collection($user, $root)) then

            (: is this the users collection? :)
            if($root eq $security:users-collection) then
            (
                (: users collection is treated specially :)
                if($children = $user)then
                (
                    (: found a mods home collection for the currently logged in user :)
                    let $home-collection-uri := security:get-home-collection-uri($user) return
                        <s>{{ "title": "Home", "isFolder": true, "key": "{$home-collection-uri}",
                            "writable": {if ($can-write) then 'true' else 'false' },
                            "addClass": "{if ($can-write) then 'writable' else 'readable'}",
                            "icon": "../css/dynatree/ltFld.user.gif"
                            {
                                let $home-children := xmldb:get-child-collections($home-collection-uri) return
                                    if(fn:exists($home-children)) then
                                        <s>, "children": [
                                        {
                                            local:sub-collections($home-collection-uri, $home-children)
                                        }
                                        ]
                                        </s>
                                    else
                                        ()
                            }
                        }}</s>
                )else()
            )
            else
            (   (: normal collection:)
                <s>{{ "title": "{fn:replace($root, '.*/','')}", "isFolder": true, "key": "{$root}",
                    "writable": {if ($can-write) then 'true' else 'false' },
                    "addClass": "{if ($can-write) then 'writable' else 'readable'}"
                    {
                        if (fn:exists($children)) then
                            <s>, "children": [
                            {
                                local:sub-collections($root, $children)
                            }
                            ]
                            </s>
                        else
                            ()
                    }
                }}</s>
            )
        else
            ()
};

<s>[{local:collections($config:mods-root)}]</s>


Add a few more builder tests

Add a few more builder tests

xquery version "3.1";

(:~
 : Tests for o:builder()
 :)
module namespace test = 'http://xokomola.com/xquery/origami/tests';

import module namespace o = 'http://xokomola.com/xquery/origami'
    at '../origami.xqm';

declare %unit:test function test:extract-nothing()
{
    unit:assert-equals(
        o:doc(
<x y="10"/>
, o:builder()),
        ['p', ['x', map { 'y': '10' }]],
        'No argument = identity'
    ),

    (: TODO: is this the correct result? :)
    unit:assert-equals(
        o:doc(
<x y="10"/>
,o:builder(())),
        ['p', ['x', map { 'y': '10' }]],
        'Empty argument = identity'
    ),

    unit:assert-equals(
        o:doc(
<x y="10"/>
, o:builder(['y'])),
        (),
        'If no rule matches return nothing'
    )
};

declare %unit:test function test:extract-whole-document()
{
    unit:assert-equals(
        o:xml(
<x y="10"/>
, o:builder(['*'])),

<x y="10"/>
,
        'Copies every element'
    )
};

(: ISSUE: removing an element doesn't allow a handler to be added :)

declare %unit:test function test:extract-whole-document-with-holes()
{
    unit:assert-equals(
        o:doc(

                <x>
                    <c>
                        <xxx/>
                    </c>
                </x>
                <y>
                    <c>
                        <yyy/>
                    </c>
                </y>

,
            o:builder(['p', ['c', ()]])
        ),
        ["p", ["x"], ["y"]],
        'Whole document leaving out c elements'
    )
};

(:~
 : When the second argument of o:doc is not a builder it is implicitly converted
 : into one.
 :)
declare %unit:test function test:implicit-builder()
{
      unit:assert-equals(
        o:xml(o:doc(<x/>,['x'])),
        <x/>
      )
};

declare %unit:test function test:implicit-builder-multiple-root-rules()
{
      unit:assert-equals(
        o:xml(o:doc(<x/>,(['x'],['y']))),
        <x/>
      )
};

(:~
 : A context function will typecheck context arguments and return the context
 : that will be available in the template rules ($c).
 :)
declare %unit:test function test:context-function()
{
    unit:assert-equals(
        o:apply(
          o:doc(
            <x>
<y/>
</x>,
            o:builder(['p', function($n,$c) { ['foo', $c?1] }])
          ),
          [12]
        ),
        ['foo', 12],
        "One argument template"
    )

};

declare %unit:test("expected", "Q{http://xokomola.com/xquery/origami}invalid-handler")
function test:invalid-handler()
{
    unit:assert-equals(
        o:doc(
            <x>
<y/>
</x>,
            o:builder(['p', function($n,$a,$b) {1}])
        ),
        (),
        "Handlers with arity > 2 are not supported")
};

declare variable $test:html :=

            <title>title</title>

                        A	B	C

                        10	20	30

item 1

item 2

item 3

item 3

item 4

item 5

item 6

item 7

item 8

    ;

declare variable $test:html-no-lists :=

            <title>title</title>

                        A	B	C

                        10	20	30

    ;

declare function test:xf($rules)
{
    o:xml(o:doc($test:html, o:builder($rules)))
};

declare %unit:test function test:copy-whole-page()
{
    unit:assert-equals(
        test:xf(['html']),
        $test:html,
        'Take the whole html document'
    )
};

declare %unit:test function test:extract-lists()
{
    unit:assert-equals(
        test:xf(
            ['ol']
        ),
        (
            $test:html//ol[@id='list-1'],
            $test:html//ol[@id='list-2'],
            $test:html//ol[@id='list-3']
        ),
        'Take all lists in order'
    ),
    unit:assert-equals(
        test:xf(
            ['div', (), ['ol']]
        ),
        ($test:html//ol[@id='list-1'], $test:html//ol[@id='list-2']),
        'Take some lists using nested rule'
    )
};

declare %unit:test function test:remove-lists()
{
    unit:assert-equals(
        test:xf(
            ['html', ['ol', ()]]
        ),
        $test:html-no-lists,
        'Remove all lists'
    )
};

declare %unit:test function test:remove-all-but-first()
{
    unit:assert-equals(
        test:xf(
            ['ol[@id="list-1"]', ['li[1]'], ['li', ()]]
        ),

item 1

,
        'Take first list and remove all but first item'
    )
};

declare %unit:test function test:list-handler()
{
    unit:assert-equals(
        o:xml(
            o:apply(
                o:doc(

item 1

item 2

,
                    o:builder(
                        ['ol', o:wrap(['list']),
                            ['li[1]'], ['li', ()]
                        ]
                    )
                )
            )
        ),
        <list>

item 1

        </list>,
        'Add list handler'
    )
};

declare variable $test:html-table :=

This is a table

          hello world!
          foobar

          bla bla
          foobar

  ;

declare function test:extract-table($rules)
{
    o:xml(
        o:apply(
            o:doc(
                $test:html-table,
                $rules
            )
        )
    )
};

declare %unit:test function test:table-extractions-1()
{
    unit:assert-equals(
        test:extract-table(['table']),
        $test:html-table//table,
        'Extract the table'
    )
};

declare %unit:test function test:table-extractions-2()
{
    unit:assert-equals(
        test:extract-table(['td|th']),
        $test:html-table//(td|th),
        'Extract the table cells (td and th) directly'
    ),

    unit:assert-equals(
        test:extract-table(['table', (), ['tr', (), ['td|th']]]),
        $test:html-table//(td|th),
        'Extract the table cells (td and th) in proper context'
    )
};

declare %unit:test function test:table-extractions-3()
{
    unit:assert-equals(
        test:extract-table(['table', ['@*', ()]]),

                hello world!
                foobar

                bla bla
                foobar

        ,
        'Extract the table but remove all attributes'
    ),

    unit:assert-equals(
        test:extract-table(['table', ['@*[name(.) != "class"]', ()]]),

                hello world!
                foobar

                bla bla
                foobar

        ,
        'Extract the table but remove some attributes'
    )

};

declare %unit:test function test:table-extractions-4()
{
    unit:assert(
        let $extracted-rows := test:extract-table((
          ['tr[th]'],
          ['tr[td]']
        ))
        return
          every $row in ($test:html-table//tr[th],$test:html-table//tr[td])
          satisfies $row = $extracted-rows,
        'Header and data row separately (root rules are in undefined order
         so results come back in undefined order as well)'
    )
};

declare %unit:test function test:table-extractions-5()
{
    unit:assert-equals(
        test:extract-table(
          ['table', (),
            ['tr[th]'],
            ['tr[td]']
          ]),
        ($test:html-table//tr[th], $test:html-table//tr[td]),
        'The same example as in test:table-extractions-4 but worked around the issue'
    )
};

declare %unit:test function test:table-extractions-6()
{
    unit:assert-equals(
        test:extract-table(
          ['table',
            ['td|th',
              ['text()', ()]
            ]
          ]
        ),

        ,
        'Remove all text nodes from the table cells'
    )
};

declare %unit:test function test:table-extractions-7()
{
    unit:assert-equals(
        test:extract-table(
          ['table',
            ['td|th',
              ['*', (),
                ['text()']
              ]
            ]
          ]
        ),

                hello world!
                foobar

                bla bla
                foobar

        ,
        'Clear inline markup inside the cells'
    )
};

declare %unit:test function test:table-extractions-8()
{
    unit:assert-equals(
        test:extract-table(
          ['table',
            ['tr',
              ['@*', ()],
              ['td|th',
                ['*', (),
                  ['text()']
                ]
              ]
            ]
          ]
        ),

                hello world!
                foobar

                bla bla
                foobar

        ,
        'Clear inline markup inside the cells and remove the attributes'
    )
};

xquery version "3.1";

(:~
 : Tests for o:builder()
 :)
module namespace test = 'http://xokomola.com/xquery/origami/tests';

import module namespace o = 'http://xokomola.com/xquery/origami'
    at '../origami.xqm';

declare %unit:test function test:extract-nothing()
{
    unit:assert-equals(
        o:doc(
<x y="10"/>
, o:builder()),
        ['p', ['x', map { 'y': '10' }]],
        'No argument = identity'
    ),

    (: TODO: is this the correct result? :)
    unit:assert-equals(
        o:doc(
<x y="10"/>
,o:builder(())),
        ['p', ['x', map { 'y': '10' }]],
        'Empty argument = identity'
    ),

    unit:assert-equals(
        o:doc(
<x y="10"/>
, o:builder(['y'])),
        (),
        'If no rule matches return nothing'
    )
};

declare %unit:test function test:extract-whole-document()
{
    unit:assert-equals(
        o:xml(
<x y="10"/>
, o:builder(['*'])),

<x y="10"/>
,
        'Copies every element'
    )
};

(: ISSUE: removing an element doesn't allow a handler to be added :)

declare %unit:test function test:extract-whole-document-with-holes()
{
    unit:assert-equals(
        o:doc(

                <x>
                    <c>
                        <xxx/>
                    </c>
                </x>
                <y>
                    <c>
                        <yyy/>
                    </c>
                </y>

,
            o:builder(['p', ['c', ()]])
        ),
        ["p", ["x"], ["y"]],
        'Whole document leaving out c elements'
    )
};

(:~
 : When the second argument of o:doc is not a builder it is implicitly converted
 : into one.
 :)
declare %unit:test function test:implicit-builder()
{
      unit:assert-equals(
        o:xml(o:doc(<x/>,['x'])),
        <x/>
      )
};

declare %unit:test function test:implicit-builder-multiple-root-rules()
{
      unit:assert-equals(
        o:xml(o:doc(<x/>,(['x'],['y']))),
        <x/>
      )
};

(:~
 : A context function will typecheck context arguments and return the context
 : that will be available in the template rules ($c).
 :)
declare %unit:test function test:context-function()
{
    unit:assert-equals(
        o:apply(
          o:doc(
            <x>
<y/>
</x>,
            o:builder(['p', function($n,$c) { ['foo', $c?1] }])
          ),
          [12]
        ),
        ['foo', 12],
        "One argument template"
    )

};

declare %unit:test("expected", "Q{http://xokomola.com/xquery/origami}invalid-handler")
function test:invalid-handler()
{
    unit:assert-equals(
        o:doc(
            <x>
<y/>
</x>,
            o:builder(['p', function($n,$a,$b) {1}])
        ),
        (),
        "Handlers with arity > 2 are not supported")
};

declare variable $test:html :=

            <title>title</title>

                        A	B	C

                        10	20	30

item 1

item 2

item 3

item 3

item 4

item 5

item 6

item 7

item 8

    ;

declare variable $test:html-no-lists :=

            <title>title</title>

                        A	B	C

                        10	20	30

    ;

declare function test:xf($rules)
{
    o:xml(o:doc($test:html, o:builder($rules)))
};

declare %unit:test function test:copy-whole-page()
{
    unit:assert-equals(
        test:xf(['html']),
        $test:html,
        'Take the whole html document'
    )
};

declare %unit:test function test:extract-lists()
{
    unit:assert-equals(
        test:xf(
            ['ol']
        ),
        (
            $test:html//ol[@id='list-1'],
            $test:html//ol[@id='list-2'],
            $test:html//ol[@id='list-3']
        ),
        'Take all lists in order'
    ),
    unit:assert-equals(
        test:xf(
            ['div', (), ['ol']]
        ),
        ($test:html//ol[@id='list-1'], $test:html//ol[@id='list-2']),
        'Take some lists using nested rule'
    )
};

declare %unit:test function test:remove-lists()
{
    unit:assert-equals(
        test:xf(
            ['html', ['ol', ()]]
        ),
        $test:html-no-lists,
        'Remove all lists'
    )
};

declare %unit:test function test:remove-all-but-first()
{
    unit:assert-equals(
        test:xf(
            ['ol[@id="list-1"]', ['li[1]'], ['li', ()]]
        ),

item 1

,
        'Take first list and remove all but first item'
    )
};

declare %unit:test function test:list-handler()
{
    unit:assert-equals(
        o:xml(
            o:apply(
                o:doc(

item 1

item 2

,
                    o:builder(
                        ['ol', o:wrap(['list']),
                            ['li[1]'], ['li', ()]
                        ]
                    )
                )
            )
        ),
        <list>

item 1

        </list>,
        'Add list handler'
    )
};

declare variable $test:html-table :=

This is a table

          hello world!
          foobar

          bla bla
          foobar

  ;

declare function test:extract-table($rules)
{
    o:xml(
        o:apply(
            o:doc(
                $test:html-table,
                $rules
            )
        )
    )
};

declare %unit:test function test:table-extractions-1()
{
    unit:assert-equals(
        test:extract-table(['table']),
        $test:html-table//table,
        'Extract the table'
    )
};

declare %unit:test function test:table-extractions-2()
{
    unit:assert-equals(
        test:extract-table(['td|th']),
        $test:html-table//(td|th),
        'Extract the table cells (td and th) directly'
    ),

    unit:assert-equals(
        test:extract-table(['table', (), ['tr', (), ['td|th']]]),
        $test:html-table//(td|th),
        'Extract the table cells (td and th) in proper context'
    )
};

declare %unit:test function test:table-extractions-3()
{
    unit:assert-equals(
        test:extract-table(['table', ['@*', ()]]),

                hello world!
                foobar

                bla bla
                foobar

        ,
        'Extract the table but remove all attributes'
    ),

    unit:assert-equals(
        test:extract-table(['table', ['@*[name(.) != "class"]', ()]]),

                hello world!
                foobar

                bla bla
                foobar

        ,
        'Extract the table but remove some attributes'
    )

};

declare %unit:test %unit:ignore function test:table-extractions-4()
{
    unit:assert-equals(
        test:extract-table((['tr[th]'],['tr[td]'])),
        ($test:html-table//tr[th], $test:html-table//tr[td]),
        'Header and data row separately (FIXME: root rules are in undefined order
         so results come back in undefined order as well'
    )
};

declare %unit:test function test:table-extractions-5()
{
    unit:assert-equals(
        test:extract-table((['table', (), ['tr[th]'],['tr[td]']])),
        ($test:html-table//tr[th], $test:html-table//tr[td]),
        'The same example as in test:table-extractions-4 but worked around the issue'
    )
};

declare %unit:test function test:table-extractions-6()
{
    unit:assert-equals(
        test:extract-table(['table', ['td|th', ['text()', ()]]]),

        ,
        'Remove all text nodes from the table cells'
    )
};

declare %unit:test function test:table-extractions-7()
{
    unit:assert-equals(
        test:extract-table(['table', ['td|th', ['*', (), ['text()']]]]),

                hello world!
                foobar

                bla bla
                foobar

        ,
        'Clear inline markup inside the cells'
    )
};