diff --git a/confirm.html b/confirm.html
new file mode 100644
index 00000000..138c8fa2
--- /dev/null
+++ b/confirm.html
@@ -0,0 +1,25 @@
+
\ No newline at end of file
diff --git a/data/controller.xql b/data/controller.xql
index 030fe722..49504299 100644
--- a/data/controller.xql
+++ b/data/controller.xql
@@ -1,11 +1,15 @@
-xquery version "3.0";
+xquery version "3.1";
declare namespace exist="http://exist.sourceforge.net/NS/exist";
declare namespace request="http://exist-db.org/xquery/request";
+declare namespace session="http://exist-db.org/xquery/session";
declare namespace response="http://exist-db.org/xquery/response";
declare namespace transform="http://exist-db.org/xquery/transform";
-
+declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization";
+declare namespace sm="http://exist-db.org/xquery/securitymanager";
import module namespace config="https://github.com/edirom/mermeid/config" at "../modules/config.xqm";
+import module namespace crud="https://github.com/edirom/mermeid/crud" at "../modules/crud.xqm";
+import module namespace common="https://github.com/edirom/mermeid/common" at "../modules/common.xqm";
import module namespace xmldb="http://exist-db.org/xquery/xmldb";
import module namespace console="http://exist-db.org/xquery/console";
@@ -16,6 +20,44 @@ declare variable $exist:controller external;
declare variable $exist:prefix external;
declare variable $exist:root external;
+(:~
+ : Wrapper function for outputting JSON
+ :
+ : @param $response-body the response body
+ : @param $response-code the response status code
+ :)
+declare %private function local:stream-json($response-body, $response-code as xs:integer) as empty-sequence() {
+ response:set-status-code($response-code),
+ response:stream(
+ serialize($response-body,
+
+ json
+
+ ),
+ 'method=text media-type=application/json encoding=utf-8'
+ )
+};
+
+(:~
+ : Wrapper function for redirecting to the main page
+ :)
+declare %private function local:redirect-to-main-page() as element(exist:dispatch) {
+
+
+
+};
+
+(:~
+ : Check whether the overwrite flag is set
+ : Considered positive (string) values include '1', 'yes', 'ja', 'y', 'on', 'true', 'true()'
+ : everything else will be `false()`
+ : @return boolean true() or false()
+ :)
+declare %private function local:overwrite() as xs:boolean {
+ let $overwriteString := request:get-parameter('overwrite', 'false')
+ return $overwriteString = ('1', 'yes', 'ja', 'y', 'on', 'true', 'true()') (: some string values that are considered boolean "true()" :)
+};
+
(console:log('/data Controller'),
if (ends-with($exist:resource, ".xml")) then
(console:log('/data Controller: XML data session: '||session:exists()),
@@ -63,6 +105,103 @@ if (ends-with($exist:resource, ".xml")) then
}
default return (response:set-status-code(405), <_/>)
)
+(:~
+ : copy files endpoint
+ : For POST requests with Accept header 'application/json' the response
+ : of the crud operation (a map object) is returned as JSON, for all other
+ : Accept headers the client is redirected to the main page (after the
+ : execution of the crud operation)
+ :)
+else if($exist:path = '/copy' and request:get-method() eq 'POST') then
+ let $source := request:get-parameter('source', '')
+ let $target := request:get-parameter('target', util:uuid() || '.xml') (: generate a unique filename if none is provided :)
+ let $title := request:get-parameter('title', ()) (: empty titles will get passed on and filled in later :)
+ let $overwrite := local:overwrite()
+ let $backend-response := crud:copy($source, $target, $overwrite, $title)
+ return
+ if(request:get-header('Accept') eq 'application/json')
+ then local:stream-json(map:remove($backend-response, 'document-node'), $backend-response?code)
+ else local:redirect-to-main-page()
+(:~
+ : delete files endpoint
+ : For POST requests with Accept header 'application/json' the response
+ : of the crud operation (an array) is returned as JSON, for all other
+ : Accept headers the client is redirected to the main page (after the
+ : execution of the crud operation)
+ :)
+else if($exist:path = '/delete' and request:get-method() eq 'POST') then
+ let $filename := request:get-parameter('filename', '')
+ let $backend-response := crud:delete($filename)
+ return
+ if(request:get-header('Accept') eq 'application/json')
+ then local:stream-json($backend-response, $backend-response(1)?code)
+ else local:redirect-to-main-page()
+(:~
+ : read files endpoint
+ : For GET requests with Accept header 'application/json' the response
+ : of the crud operation (a map object) is returned as JSON, for an
+ : "application/xml" the raw XML document is returned
+ :)
+else if($exist:path = '/read' and request:get-method() eq 'GET') then
+ let $filename := request:get-parameter('filename', '')
+ let $backend-response := crud:read($filename)
+ return
+ if(request:get-header('Accept') eq 'application/json')
+ then local:stream-json(map:remove($backend-response, 'document-node'), $backend-response?code)
+ else if(contains(request:get-header('Accept'), 'application/xml'))
+ then $backend-response?document-node
+ else ()
+(:~
+ : rename files endpoint
+ : this simply chains a "copy" and a "delete" (if the first operation was successfull)
+ : the returned object is a merge of the copy-response and the delete-response with a precedence for the former
+ :)
+else if($exist:path = '/rename' and request:get-method() eq 'POST') then
+ let $source := request:get-parameter('source', '')
+ let $target := request:get-parameter('target', util:uuid() || '.xml') (: generate a unique filename if none is provided :)
+ let $title := request:get-parameter('title', ()) (: empty titles will get passed on and filled in later :)
+ let $overwrite := local:overwrite()
+ let $backend-response-copy := crud:copy($source, $target, $overwrite, $title)
+ let $update-references :=
+ if($backend-response-copy instance of map(*) and $backend-response-copy?code = 200)
+ then common:update-targets(collection($config:data-root), $source, $target, false())
+ else console:log($backend-response-copy)
+ let $backend-response-delete :=
+ if($update-references instance of map(*) and $update-references?replacements ge 0)
+ then crud:delete($source)
+ else console:log($update-references)
+ return
+ if(request:get-header('Accept') eq 'application/json')
+ then if($backend-response-delete instance of array(*))
+ then local:stream-json(map:remove(map:merge(($backend-response-delete(1), $backend-response-copy)), 'document-node'), $backend-response-copy?code)
+ else (
+ local:stream-json($backend-response-copy, $backend-response-copy?code),
+ console:log($backend-response-delete)
+ )
+ else local:redirect-to-main-page()
+(:~
+ : create files endpoint
+ :
+ :)
+else if($exist:path = '/create' and request:get-method() eq 'POST') then
+ let $templatepath := request:get-parameter('template', $config:app-root || '/forms/model/new_file.xml')
+ let $title := request:get-parameter('title', '')
+ let $username := common:get-current-username() => string()
+ let $change-message := 'file created with MerMEId'
+ let $template :=
+ if(doc-available($templatepath))
+ then doc($templatepath) => common:set-mei-title-in-memory($title) => common:add-change-entry-to-revisionDesc-in-memory($username, $change-message)
+ else ()
+ let $filename := request:get-parameter('filename', common:mermeid-id('file') || '.xml')
+ let $overwrite := local:overwrite()
+ let $backend-response :=
+ if($template and $filename)
+ then crud:create($template, $filename, $overwrite)
+ else ()
+ return
+ if(request:get-header('Accept') eq 'application/json' and $backend-response instance of map(*))
+ then local:stream-json(map:remove($backend-response, 'document-node'), $backend-response?code)
+ else local:redirect-to-main-page()
else
(: everything else is passed through :)
(console:log('/data Controller: passthrough'),
diff --git a/modules/common.xqm b/modules/common.xqm
new file mode 100644
index 00000000..8d28fbd9
--- /dev/null
+++ b/modules/common.xqm
@@ -0,0 +1,300 @@
+xquery version "3.1";
+
+(:~
+ : Common MerMEId XQuery functions
+ :)
+module namespace common="https://github.com/edirom/mermeid/common";
+
+declare namespace mei="http://www.music-encoding.org/ns/mei";
+declare namespace map="http://www.w3.org/2005/xpath-functions/map";
+declare namespace err="http://www.w3.org/2005/xqt-errors";
+declare namespace util="http://exist-db.org/xquery/util";
+
+import module namespace config="https://github.com/edirom/mermeid/config" at "config.xqm";
+import module namespace functx="http://www.functx.com";
+
+(:~
+ : Function for outputting the "Year" information on the main list page
+ :
+ : @param $doc the MEI document to extract the information from
+ : @return the string representation of a period of time
+ :)
+declare function common:display-date($doc as node()?) as xs:string {
+ if($doc//mei:workList/mei:work/mei:creation/mei:date/(@notbefore|@notafter|@startdate|@enddate)!='') then
+ concat(substring($doc//mei:workList/mei:work/mei:creation/mei:date/@notbefore,1,4),
+ substring($doc//mei:workList/mei:work/mei:creation/mei:date/@startdate,1,4),
+ '–',
+ substring($doc//mei:workList/mei:work/mei:creation/mei:date/@enddate,1,4),
+ substring($doc//mei:workList/mei:work/mei:creation/mei:date/@notafter,1,4))
+ else if($doc//mei:workList/mei:work/mei:creation/mei:date/@isodate!='') then
+ substring($doc//mei:workList/mei:work/mei:creation/mei:date[1]/@isodate,1,4)
+ else if($doc//mei:workList/mei:work/mei:expressionList/mei:expression[mei:creation/mei:date][1]/mei:creation/mei:date/(@notbefore|@notafter|@startdate|@enddate)!='') then
+ concat(substring($doc//mei:workList/mei:work/mei:expressionList/mei:expression[mei:creation/mei:date][1]/mei:creation/mei:date/@notbefore,1,4),
+ substring($doc//mei:workList/mei:work/mei:expressionList/mei:expression[mei:creation/mei:date][1]/mei:creation/mei:date/@startdate,1,4),
+ '–',
+ substring($doc//mei:workList/mei:work/mei:expressionList/mei:expression[mei:creation/mei:date][1]/mei:creation/mei:date/@enddate,1,4),
+ substring($doc//mei:workList/mei:work/mei:expressionList/mei:expression[mei:creation/mei:date][1]/mei:creation/mei:date/@notafter,1,4))
+ else
+ substring($doc//mei:workList/mei:work/mei:expressionList/mei:expression[mei:creation/mei:date][1]/mei:creation/mei:date[@isodate][1]/@isodate,1,4)
+};
+
+(:~
+ : Function for outputting the "Collection" information on the main list page
+ :
+ : @param $doc the MEI document to extract the information from
+ : @return the string representation of a MerMEId collection
+ :)
+declare function common:get-edition-and-number($doc as node()?) as xs:string {
+ let $c := ($doc//mei:fileDesc/mei:seriesStmt/mei:identifier[@type="file_collection"])[1] => normalize-space()
+ let $no := ($doc//mei:meiHead/mei:workList/mei:work/mei:identifier[normalize-space(@label)=$c])[1] => normalize-space()
+ (: shorten very long identifiers (i.e. lists of numbers) :)
+ let $part1 := substring($no, 1, 11)
+ let $part2 := substring($no, 12)
+ let $delimiter := substring(concat(translate($part2,'0123456789',''),' '),1,1)
+ let $n :=
+ if (string-length($no)>11) then
+ concat($part1,substring-before($part2,$delimiter),'...')
+ else
+ $no
+ return concat($c, ' ', $n)
+};
+
+(:~
+ : Get the composers of a work.
+ : This is used for outputting the "Composer" information on the main list page
+ : as well as for crud:read()
+ :
+ : @param $doc the MEI document to extract the information from
+ : @return a string-join of the composers, an empty sequence if none are given
+ :)
+declare function common:get-composers($doc as node()?) as xs:string? {
+ $doc//mei:workList/mei:work/mei:contributor/mei:persName[@role='composer'] => string-join(', ')
+};
+
+(:~
+ : Get the (main) title of a work.
+ : This is used for outputting the "Title" information on the main list page
+ : as well as for crud:read()
+ :
+ : @param $doc the MEI document to extract the information from
+ : @return the (main) title
+ :)
+declare function common:get-title($doc as node()?) as xs:string {
+ ($doc//mei:workList/mei:work/mei:title[text()])[1] => normalize-space()
+};
+
+(:~
+ : Propose a new filename based on an existing one
+ : This is simply done by adding "-copy" to the basenam of the file
+ :
+ : @param $filename the existing filename
+ : @return a proposed filename
+ :)
+declare function common:propose-filename($filename as xs:string) as xs:string {
+ let $tokens := $filename => tokenize('\.')
+ let $suffix :=
+ if(count($tokens) gt 1)
+ then $tokens[last()]
+ else 'xml'
+ return
+ if(count($tokens) eq 1)
+ then $tokens || '-copy.' || $suffix
+ else (subsequence($tokens, 1, count($tokens) -1) => string-join('.')) || '-copy.' || $suffix
+};
+
+(:~
+ : Add a change entry to the revisionDesc of an existing persistent document stored in the eXist database.
+ : NB, this will modify the existing document in the database!
+ : For changing documents or fragments in memory, see `common:add-change-entry-to-revisionDesc-in-memory#3`
+ :
+ : @param $document the input MEI document to add the change entry to
+ : @param $user the user identified with this change entry
+ : @param $desc a description of the change
+ : @return a map object with properties "message" and "filename".
+ A successful operation will return "Success" as message, the error description otherwise
+ :)
+declare function common:add-change-entry-to-revisionDesc-in-document($document as document-node(),
+ $user as xs:string, $desc as xs:string) as map(*) {
+ let $change :=
+
+
+ {$user}
+
+
+ {$desc}
+
+
+ return
+ try {
+ update insert $change into $document/mei:mei/mei:meiHead/mei:revisionDesc,
+ map {
+ 'message': 'Success',
+ 'filename': util:document-name($document)
+ }
+ }
+ catch * {
+ map {
+ 'message': $err:description,
+ 'filename': util:document-name($document)
+ }
+ }
+};
+
+(:~
+ : Add a change entry to the revisionDesc in memory.
+ : NB, this will _not_ modify any existing document in the database!
+ : For changing existing documents in the database, see `common:add-change-entry-to-revisionDesc-in-document#3`
+ :
+ : @param $node the input MEI document as node() or document-node()
+ : @param $user the user identified with this change entry
+ : @param $desc a description of the change
+ : @return the modified input node
+ :)
+declare function common:add-change-entry-to-revisionDesc-in-memory($node as node(),
+ $user as xs:string, $desc as xs:string) as node() {
+ typeswitch($node)
+ case $elem as element(mei:revisionDesc) return
+ element { node-name($elem) } {
+ $elem/@*, for $child in $elem/node() return common:add-change-entry-to-revisionDesc-in-memory($child, $user, $desc),
+
+
+ {$user}
+
+
+ {$desc}
+
+
+ }
+ case $elem as element() return
+ element { node-name($elem) } {
+ $elem/@*, for $child in $elem/node() return common:add-change-entry-to-revisionDesc-in-memory($child, $user, $desc)
+ }
+ case document-node() return common:add-change-entry-to-revisionDesc-in-memory($node/node(), $user, $desc)
+ default return $node
+};
+
+(:~
+ : Generate an ID by prefixing an unique ID with an optional prefix
+ :
+ : @param $prefix an optional prefix for the ID
+ : @return a unique ID
+ :)
+declare function common:mermeid-id($prefix as xs:string?) as xs:string {
+ $prefix || '_' || substring(util:uuid(),1,13)
+};
+
+(:~
+ : Update target attributes
+ :
+ : @param $collection the collection of XML documents to look for and update references
+ : @param $old-identifier the old identifier of the XML document
+ : @param $new-identifier the new identifier of the XML document
+ : @param $dry-run "true()" will perform a dry run without changing the references
+ : @return a map object with properties "old_identifier", "new_identifier", "dry_run",
+ "replacements", "changed_documents", and "message". "replacements" and "changed_documents"
+ are their respective numbers and are negative (-1) if an error occured.
+ :)
+declare function common:update-targets($collection as node()*, $old-identifier as xs:string,
+ $new-identifier as xs:string, $dry-run as xs:boolean) as map(*) {
+ try {
+ let $targets := $collection//@target[contains(., $old-identifier)]
+ let $documents := $targets/root() ! document-uri(.)
+ return (
+ if($dry-run) then ()
+ else (
+ for $target in $targets
+ let $replacement := replace($target, $old-identifier, $new-identifier)
+ return
+ update replace $target with $replacement
+ ),
+ map {
+ 'old_identifier': $old-identifier,
+ 'new_identifier': $new-identifier,
+ 'replacements': count($targets),
+ 'changed_documents': count($documents),
+ 'message': 'Success',
+ 'dry_run': $dry-run
+ }
+ )}
+ catch * {
+ map {
+ 'old_identifier': $old-identifier,
+ 'new_identifier': $new-identifier,
+ 'replacements': -1,
+ 'changed_documents': -1,
+ 'message': $err:description,
+ 'dry_run': $dry-run
+ }
+ }
+};
+
+(:~
+ : Set the MEI title in an existing persistent document stored in the eXist database.
+ : NB, this will modify the existing document in the database!
+ : For changing documents or fragments in memory, see `common:set-mei-title-in-memory#2`
+ :
+ : @param $doc the MEI document to change the title
+ : @param $new_title the new title
+ : @return a map object with properties "message", "title", and "filename"
+ :)
+declare function common:set-mei-title-in-document($doc as document-node(), $new_title as xs:string) as map(*) {
+ try {(
+ for $title in $doc//mei:workList/mei:work[1]/mei:title[text()][1]
+ return
+ update value $title with $new_title
+ ),
+ map {
+ 'message': 'Success',
+ 'title': $new_title,
+ 'filename': util:document-name($doc)
+ }
+ }
+ catch * {
+ map {
+ 'message': $err:description,
+ 'title': $new_title,
+ 'filename': util:document-name($doc)
+ }
+ }
+};
+
+(:~
+ : Set the MEI title of a MEI document in memory.
+ : NB, this will _not_ modify any existing document in the database!
+ : For changing existing documents in the database, see `common:set-mei-title-in-document#2`
+ :
+ : @param $node the input MEI document as node() or document-node()
+ : @param $new_title the new title
+ : @return the modified input node
+ :)
+declare function common:set-mei-title-in-memory($node as node(), $new_title as xs:string) as node() {
+ typeswitch($node)
+ case $elem as element(mei:title) return
+ if(not($elem/preceding-sibling::mei:title) and $elem/parent::mei:work[1] and $elem/ancestor::mei:workList)
+ then
+ element { node-name($elem) } {
+ $elem/@*, $new_title
+ }
+ else
+ element { node-name($elem) } {
+ $elem/@*, for $child in $elem/node() return common:set-mei-title-in-memory($child, $new_title)
+ }
+ case $elem as element() return
+ element { node-name($elem) } {
+ $elem/@*, for $child in $elem/node() return common:set-mei-title-in-memory($child, $new_title)
+ }
+ case document-node() return common:set-mei-title-in-memory($node/node(), $new_title)
+ default return $node
+};
+
+(:~
+ : Get the current username
+ : Wrapper function around `sm:id#0`
+ :
+ : @return the username of the currently logged in user
+ :)
+declare function common:get-current-username() as xs:string? {
+ sm:id()//sm:real/sm:username => data()
+};
diff --git a/modules/copy-file.xq b/modules/copy-file.xq
deleted file mode 100755
index 63f96640..00000000
--- a/modules/copy-file.xq
+++ /dev/null
@@ -1,59 +0,0 @@
-xquery version "3.0";
-
-import module namespace login="http://kb.dk/this/login" at "./login.xqm";
-import module namespace config="https://github.com/edirom/mermeid/config" at "./config.xqm";
-
-declare namespace functx = "http://www.functx.com";
-declare namespace m="http://www.music-encoding.org/ns/mei";
-declare namespace xmldb="http://exist-db.org/xquery/xmldb";
-declare namespace request="http://exist-db.org/xquery/request";
-declare namespace response="http://exist-db.org/xquery/response";
-declare namespace fn="http://www.w3.org/2005/xpath-functions";
-declare namespace util="http://exist-db.org/xquery/util";
-
-
-declare option exist:serialize "method=xml media-type=text/html";
-
-declare variable $dcmroot := $config:app-root;
-
-declare function functx:copy-attributes
- ( $copyTo as element() ,
- $copyFrom as element() ) as element() {
-
- element { node-name($copyTo)}
- { $copyTo/@*[not(node-name(.) = $copyFrom/@*/node-name(.))],
- $copyFrom/@*,
- $copyTo/node() }
-
- } ;
-
-
-let $return_to := config:link-to-app("modules/list_files.xq")
-
-
-let $log-in := login:function()
-let $res := response:redirect-to($return_to cast as xs:anyURI)
-let $parameters := request:get-parameter-names()
-
-return
-
- {
- for $parameter in $parameters
- let $new_file := concat($dcmroot,util:uuid(),".xml")
- let $old_file := concat($dcmroot,$parameter)
- where request:get-parameter($parameter,"")="copy" and contains($parameter,"xml")
- return
- let $odoc := doc($old_file)
- let $stored := xmldb:store($dcmroot,$new_file, $odoc )
- let $new_doc := doc($new_file)
- for $title in $new_doc//m:workList/m:work[1]/m:title[string()][1]
- let $new_title_text := concat($title//string()," (copy) ")
- let $new_title :=
- {$new_title_text}
- let $upd := update replace $title[string()] with
- functx:copy-attributes($new_title,$title)
- return {$title[string()][1]//string()} | {$new_title_text} |
- }
-
-
-
diff --git a/modules/create-file.xq b/modules/create-file.xq
deleted file mode 100755
index d7588ce0..00000000
--- a/modules/create-file.xq
+++ /dev/null
@@ -1,28 +0,0 @@
-xquery version "3.0";
-
-import module namespace login="http://kb.dk/this/login" at "./login.xqm";
-import module namespace config="https://github.com/edirom/mermeid/config" at "./config.xqm";
-
-declare namespace xs="http://www.w3.org/2001/XMLSchema";
-declare namespace util="http://exist-db.org/xquery/util";
-declare namespace xmldb="http://exist-db.org/xquery/xmldb";
-declare namespace request="http://exist-db.org/xquery/request";
-declare namespace response="http://exist-db.org/xquery/response";
-declare option exist:serialize "method=xml encoding=UTF-8 media-type=text/html";
-
-let $log-in := login:function()
-let $exist_path := request:get-parameter("path","")
-let $new_doc := doc("../forms/model/new_file.xml")
-
-let $file := concat(util:uuid(),".xml")
-let $file_arg := concat("doc=",$file)
-
-let $return_to := concat(config:link-to-app("/forms/edit-work-case.xml"), "?", $file_arg)
-let $res := response:redirect-to($return_to cast as xs:anyURI)
-let $result := xmldb:store($config:data-root, $file, $new_doc)
-
-return
-
-file | {$file} |
-redirect | {$return_to} |
-
diff --git a/modules/crud.xqm b/modules/crud.xqm
new file mode 100644
index 00000000..82638bcb
--- /dev/null
+++ b/modules/crud.xqm
@@ -0,0 +1,162 @@
+xquery version "3.1";
+
+(:~
+ : Basic CRUD (Create, Read, Update, Delete) functions for the MerMEId data store
+ :
+ : All operations assume data is kept at $config:data-root and the file hierarchy is flat,
+ : i.e. there are no subfolders
+ :)
+module namespace crud="https://github.com/edirom/mermeid/crud";
+
+declare namespace mei="http://www.music-encoding.org/ns/mei";
+declare namespace request="http://exist-db.org/xquery/request";
+declare namespace response="http://exist-db.org/xquery/response";
+declare namespace xmldb="http://exist-db.org/xquery/xmldb";
+declare namespace map="http://www.w3.org/2005/xpath-functions/map";
+declare namespace err="http://www.w3.org/2005/xqt-errors";
+declare namespace jb="http://exist.sourceforge.net/NS/exist/java-binding";
+declare namespace util="http://exist-db.org/xquery/util";
+declare namespace sm="http://exist-db.org/xquery/securitymanager";
+
+import module namespace config="https://github.com/edirom/mermeid/config" at "config.xqm";
+import module namespace common="https://github.com/edirom/mermeid/common" at "common.xqm";
+
+(:~
+ : Delete files within the data directory
+ :
+ : @param $filenames the files to delete
+ : @return an array of map object with filename, message and code properties concerning the delete operation
+ :)
+declare function crud:delete($filenames as xs:string*) as array(map(xs:string,xs:string)*) {
+ array {
+ for $filename in $filenames
+ return
+ try {(
+ xmldb:remove($config:data-root, $filename),
+ map {
+ 'filename': $filename,
+ 'message': 'deleted successfully',
+ 'code': 200
+ }
+ )}
+ catch jb:org.xmldb.api.base.XMLDBException {
+ map {
+ 'filename': $filename,
+ 'message': 'failed to delete: ' || $err:description,
+ 'code': 401
+ }
+ }
+ catch * {
+ map {
+ 'filename': $filename,
+ 'message': 'failed to delete: ' || string-join(($err:code, $err:description), '; '),
+ 'code': 500
+ }
+ }
+ }
+};
+
+(:~
+ : Create a file within the data directory
+ :
+ : @param $node the XML document to store
+ : @param $filename the filename for the new file
+ : @param $overwrite whether an existent file may be overwritten
+ : @return a map object with filename, message and code properties concerning the create operation
+ :)
+declare function crud:create($node as node(), $filename as xs:string, $overwrite as xs:boolean) as map(*) {
+ try {
+ if(not(doc-available($config:data-root || '/' || $filename)) or $overwrite)
+ then if(xmldb:store($config:data-root, $filename, $node))
+ then crud:read($filename) => map:put('message', 'created successfully')
+ else map {
+ 'filename': $filename,
+ 'message': 'failed to create file',
+ 'code': 500
+ }
+ else map {
+ 'filename': $filename,
+ 'message': 'file already exists and no overwrite flag was set',
+ 'code': 401
+ }
+ }
+ catch jb:org.xmldb.api.base.XMLDBException {
+ map {
+ 'filename': $filename,
+ 'message': 'failed to create file: ' || $err:description,
+ 'code': 401
+ }
+ }
+ catch * {
+ map {
+ 'filename': $filename,
+ 'message': 'failed to create file: ' || string-join(($err:code, $err:description), '; '),
+ 'code': 500
+ }
+ }
+};
+
+(:~
+ : Copy a file within the data directory
+ :
+ : @param $source-filename the input filename to copy
+ : @param $target-filename the output filename to copy to
+ : @param $overwrite whether an existent target file may be overwritten
+ : @param $new_title an optional new title. If omitted, the string "(copy)" will be appended to the old title
+ : @return a map object with source, target, message and code properties concerning the copy operation
+ :)
+declare function crud:copy($source-filename as xs:string, $target-filename as xs:string,
+ $overwrite as xs:boolean, $new_title as xs:string?) as map(*) {
+ let $source :=
+ if(doc-available($config:data-root || '/' || $source-filename))
+ then doc($config:data-root || '/' || $source-filename)
+ else ()
+ let $title :=
+ if($new_title)
+ then $new_title
+ else common:get-title($source) || ' (Copy)'
+ let $username := common:get-current-username() => string()
+ let $change-message := 'file copied from ' || $source-filename || ' to ' || $target-filename
+ let $create-target :=
+ if($source) then crud:create($source => common:set-mei-title-in-memory($title) => common:add-change-entry-to-revisionDesc-in-memory($username, $change-message), $target-filename, $overwrite)
+ else ()
+ return
+ if($create-target instance of map(*))
+ then $create-target => map:put('title', $new_title)
+ else map {
+ 'source': $source-filename,
+ 'target': $target-filename,
+ 'message': 'source does not exist',
+ 'code': 404
+ }
+};
+
+(:~
+ : Read a file from the data directory
+ :
+ : @param $filename the filename (aka MerMEId identifier) to read
+ : @return a map object with some metadata (e.g. "title", "composer") and the complete MEI document as "document-node"
+ :)
+declare function crud:read($filename as xs:string) as map(*) {
+ let $doc :=
+ if(doc-available($config:data-root || '/' || $filename))
+ then doc($config:data-root || '/' || $filename)
+ else ()
+ return
+ if($doc)
+ then map {
+ 'filename': $filename,
+ 'document-node': $doc,
+ 'composer': common:get-composers($doc),
+ 'title': common:get-title($doc),
+ 'year': common:display-date($doc),
+ 'collection': common:get-edition-and-number($doc),
+ 'message': 'read successfully',
+ 'code': 200
+ }
+ else map {
+ 'filename': $filename,
+ 'message': 'file not found or permissions missing',
+ 'code': 404
+ }
+};
diff --git a/modules/delete-file.xq b/modules/delete-file.xq
deleted file mode 100755
index 7c41f9a1..00000000
--- a/modules/delete-file.xq
+++ /dev/null
@@ -1,45 +0,0 @@
-xquery version "3.1" encoding "UTF-8";
-
-import module namespace login="http://kb.dk/this/login" at "./login.xqm";
-import module namespace config="https://github.com/edirom/mermeid/config" at "./config.xqm";
-
-declare namespace functx = "http://www.functx.com";
-declare namespace m="http://www.music-encoding.org/ns/mei";
-declare namespace xmldb="http://exist-db.org/xquery/xmldb";
-declare namespace request="http://exist-db.org/xquery/request";
-declare namespace response="http://exist-db.org/xquery/response";
-declare namespace fn="http://www.w3.org/2005/xpath-functions";
-
-declare option exist:serialize "method=xml media-type=text/html";
-
-declare variable $dcmroot := $config:data-root;
-
-let $return_to := config:link-to-app("modules/list_files.xq")
-
-
-let $log-in := login:function()
-let $res := response:redirect-to($return_to cast as xs:anyURI)
-let $parameters := request:get-parameter-names()
-
-return
-
- {
- for $resource in $parameters
- where request:get-parameter($resource,"")="delete" and contains($resource,"xml")
- return xmldb:remove(xs:anyURI($dcmroot), $resource)
- }
-
-
-(:
-xquery version "3.1" encoding "UTF-8";
-
-declare namespace xmldb="http://exist-db.org/xquery/xmldb";
-
-let $dcmroot := "/db/dcm/"
-let $resource := "nielsen_cnw0126.xml"
-
-return xmldb:remove(xs:anyURI($dcmroot), $resource)
-:)
-
-
-
diff --git a/modules/list_files.xq b/modules/list_files.xq
index cad39517..b37f8495 100755
--- a/modules/list_files.xq
+++ b/modules/list_files.xq
@@ -3,6 +3,7 @@ xquery version "3.0" encoding "UTF-8";
import module namespace loop="http://kb.dk/this/getlist" at "./main_loop.xqm";
import module namespace app="http://kb.dk/this/listapp" at "./list_utils.xqm";
import module namespace config="https://github.com/edirom/mermeid/config" at "./config.xqm";
+import module namespace common="https://github.com/edirom/mermeid/common" at "./common.xqm";
declare namespace xl="http://www.w3.org/1999/xlink";
declare namespace request="http://exist-db.org/xquery/request";
@@ -64,44 +65,35 @@ declare function local:format-reference(
else
"even"
- let $date_output :=
- if($doc//m:workList/m:work/m:creation/m:date/(@notbefore|@notafter|@startdate|@enddate)!='') then
- concat(substring($doc//m:workList/m:work/m:creation/m:date/@notbefore,1,4),
- substring($doc//m:workList/m:work/m:creation/m:date/@startdate,1,4),
- '-',
- substring($doc//m:workList/m:work/m:creation/m:date/@enddate,1,4),
- substring($doc//m:workList/m:work/m:creation/m:date/@notafter,1,4))
- else if($doc//m:workList/m:work/m:creation/m:date/@isodate!='') then
- substring($doc//m:workList/m:work/m:creation/m:date[1]/@isodate,1,4)
- else if($doc//m:workList/m:work/m:expressionList/m:expression[m:creation/m:date][1]/m:creation/m:date/(@notbefore|@notafter|@startdate|@enddate)!='') then
- concat(substring($doc//m:workList/m:work/m:expressionList/m:expression[m:creation/m:date][1]/m:creation/m:date/@notbefore,1,4),
- substring($doc//m:workList/m:work/m:expressionList/m:expression[m:creation/m:date][1]/m:creation/m:date/@startdate,1,4),
- '-',
- substring($doc//m:workList/m:work/m:expressionList/m:expression[m:creation/m:date][1]/m:creation/m:date/@enddate,1,4),
- substring($doc//m:workList/m:work/m:expressionList/m:expression[m:creation/m:date][1]/m:creation/m:date/@notafter,1,4))
- else
- substring($doc//m:workList/m:work/m:expressionList/m:expression[m:creation/m:date][1]/m:creation/m:date[@isodate][1]/@isodate,1,4)
-
(: for some reason the sort-key function must be called outside the actual searching to have correct work number sorting when searching within all collections :)
let $dummy := loop:sort-key("dummy_collection", $doc, "null")
let $ref :=
- {$doc//m:workList/m:work/m:contributor/m:persName[@role='composer']}
+ {common:get-composers($doc)}
|
- {app:view-document-reference($doc)} |
- {" ",$date_output} |
- {app:get-edition-and-number($doc)} |
-
+ |
+ {common:get-title($doc)}
+
+ |
+ {common:display-date($doc)} |
+ {common:get-edition-and-number($doc)} |
+
+
|
{app:edit-form-reference($doc)} |
{app:copy-document-reference($doc)} |
@@ -132,10 +124,6 @@ declare function local:format-reference(
href="../resources/css/login.css"
type="text/css"/>
-
-
@@ -147,21 +135,34 @@ declare function local:format-reference(