diff --git a/add/data/xql/getMeasurePage.xql b/add/data/xql/getMeasurePage.xql
index 16a9f39b2..36c91879d 100644
--- a/add/data/xql/getMeasurePage.xql
+++ b/add/data/xql/getMeasurePage.xql
@@ -26,6 +26,7 @@ declare function local:findMeasure($mei, $movementId, $measureIdName) as element
else
(($mei/id($movementId)//mei:measure[@label eq $measureIdName], $mei/id($movementId)//mei:measure[@n eq $measureIdName])[1])
};
+
declare function local:getMeasure($mei, $measure, $movementId) as map(*) {
let $measureId := $measure/string(@xml:id)
let $zoneId := substring-after($measure/string(@facs), '#')
diff --git a/add/data/xql/getMeasuresOnPage.xql b/add/data/xql/getMeasuresOnPage.xql
index 699ad37b7..5b14047a0 100644
--- a/add/data/xql/getMeasuresOnPage.xql
+++ b/add/data/xql/getMeasuresOnPage.xql
@@ -4,11 +4,15 @@ xquery version "3.1";
:)
(:~
- Returns a JSON sequence with all measures on a specific page.
-
- @author Daniel Röwenstrunk
+ Returns a JSON array with all measures on a specific page.
:)
+(: IMPORTS ================================================================= :)
+
+import module namespace eutil="http://www.edirom.de/xquery/eutil" at "/db/apps/Edirom-Online/data/xqm/eutil.xqm";
+
+import module namespace measure = "http://www.edirom.de/xquery/measure" at "/db/apps/Edirom-Online/data/xqm/measure.xqm";
+
(: NAMESPACE DECLARATIONS ================================================== :)
declare namespace mei = "http://www.music-encoding.org/ns/mei";
@@ -22,72 +26,17 @@ declare namespace xmldb = "http://exist-db.org/xquery/xmldb";
declare option output:method "json";
declare option output:media-type "application/json";
-(: FUNCTION DECLARATIONS =================================================== :)
-
-(:~
- Finds all measures on a page.
-
- @param $mei The sourcefile
- @param $surface The surface to look at
- @returns A list of json objects with measure information
-:)
-declare function local:getMeasures($mei as node(), $surface as node()) as map(*)* {
-
- for $zone in $surface/mei:zone[@type = 'measure']
- let $zoneRef := concat('#', $zone/@xml:id)
- (:
- The first predicate with `contains` is just a rough estimate to narrow down the result set.
- It uses the index and is fast while the second (exact) predicate is generally too slow
- :)
- let $measures := $mei//mei:measure[contains(@facs, $zoneRef)][$zoneRef = tokenize(@facs, '\s+')]
- return
- for $measure in $measures
- let $measureLabel :=
- if ($measure/@label) then
- ($measure/string(@label))
- else
- ($measure/string(@n))
- let $measureLabel :=
- if ($measure//mei:multiRest) then
- ($measureLabel || '–' || number($measureLabel) + number($measure//mei:multiRest/@num) - 1)
- else
- ($measureLabel)
- return
- map {
- 'zoneId': $zone/string(@xml:id),
- 'ulx': $zone/string(@ulx),
- 'uly': $zone/string(@uly),
- 'lrx': $zone/string(@lrx),
- 'lry': $zone/string(@lry),
- 'id': $measure/string(@xml:id),
- 'name': $measureLabel,
- 'type': $measure/string(@type),(: where is measure type being used :)
- 'rest': local:getMRest($measure)
- }
-};
-
-declare function local:getMRest($measure) {
-
- if ($measure//mei:mRest) then
- (string('1'))
- else if ($measure//mei:multiRest) then
- ($measure//mei:multiRest/string(@num))
- else
- (string('0'))
-};
-
(: QUERY BODY ============================================================== :)
let $uri := request:get-parameter('uri', '')
let $surfaceId := request:get-parameter('pageId', '')
-let $mei := doc($uri)/root()
+let $mei := eutil:getDoc($uri)
let $surface := $mei/id($surfaceId)
return
array {
- (: TODO: überlegen, wie die Staff-spezifischen Ausschnitte angezeigt werden sollen :)
- local:getMeasures($mei, $surface)
+ measure:getMeasuresOnPage($mei, $surface)
}
diff --git a/add/data/xqm/eutil.xqm b/add/data/xqm/eutil.xqm
index 1d0cf76e5..367f31451 100644
--- a/add/data/xqm/eutil.xqm
+++ b/add/data/xqm/eutil.xqm
@@ -204,6 +204,7 @@ declare function eutil:getDocumentLabel($doc as xs:string, $edition as xs:string
declare function eutil:getPartLabel($measureOrPerfRes as node(), $type as xs:string) as xs:string {
(: request:get-parameter('lang', '') doesn't work?? [DeRi]:)
+ (: replace with eutil:getLanguage($edition) or similar:)
let $lang :=
if(request:get-parameter('lang', '') = '') then
@@ -214,12 +215,17 @@ declare function eutil:getPartLabel($measureOrPerfRes as node(), $type as xs:str
let $part := $measureOrPerfRes/ancestor::mei:part
let $voiceRef := $part//mei:staffDef/@decls
let $voiceID := substring-after($voiceRef, '#')
+ let $voiceElem := $measureOrPerfRes/ancestor::mei:mei/id($voiceID)
let $perfResLabel :=
- if($type eq 'measure') then
- ($measureOrPerfRes/ancestor::mei:mei/id($voiceID)/@label)
- else
+ if($type eq 'measure' and $voiceElem[@label]) then
+ ($voiceElem/@label)
+ else if($type eq 'measure' and $voiceElem[@codedval]) then
+ ($voiceElem/@codedval)
+ else if ($measureOrPerfRes[@label]) then
($measureOrPerfRes/@label)
+ else
+ ($measureOrPerfRes/@codedval)
let $dictKey := 'perfMedium.perfRes.' || functx:substring-before-if-contains($perfResLabel,'.')
@@ -266,7 +272,9 @@ declare function eutil:getLanguageString($key as xs:string, $values as xs:string
let $base := system:get-module-load-path()
let $file := eutil:getDoc(concat($base, '/../locale/edirom-lang-', $lang, '.xml'))
- let $string := $file//entry[@key = $key]/string(@value)
+ let $string := if($file//entry[@key = $key]) then (
+ $file//entry[@key = $key]/string(@value)
+ ) else('noValueFound')
let $string := functx:replace-multi($string, for $i in (0 to (count($values) - 1)) return concat('\{',$i,'\}'), $values)
return
diff --git a/add/data/xqm/measure.xqm b/add/data/xqm/measure.xqm
new file mode 100644
index 000000000..56230e49b
--- /dev/null
+++ b/add/data/xqm/measure.xqm
@@ -0,0 +1,236 @@
+xquery version "3.1";
+(:
+ : For LICENSE-Details please refer to the LICENSE file in the root directory of this repository.
+ :)
+
+(:~
+ : This module provides library functions for Sources
+ :
+ : @author Dennis Ried
+ :)
+module namespace measure = "http://www.edirom.de/xquery/measure";
+
+(: IMPORTS ================================================================= :)
+
+import module namespace functx="http://www.functx.com";
+
+import module namespace eutil="http://www.edirom.de/xquery/eutil" at "/db/apps/Edirom-Online/data/xqm/eutil.xqm";
+
+(: NAMESPACE DECLARATIONS ================================================== :)
+
+declare namespace mei="http://www.music-encoding.org/ns/mei";
+declare namespace xhtml="http://www.w3.org/1999/xhtml";
+
+(: FUNCTION DECLARATIONS =================================================== :)
+
+(:~
+ : Returns a label for a measure, taken either from the `@label` attribute
+ : if present or the `@n` attribute otherwise.
+ :
+ : @param $measure The measure to be processed
+ : @return the string value of the `@label` or the `@n` attribute if present, the empty sequence otherwise.
+ :)
+declare function measure:getMeasureLabelAttr($measure as element(mei:measure)) as xs:string? {
+
+ if(exists($measure[@label])) then (
+ $measure/@label => data()
+ ) else (
+ $measure/@n => data()
+ )
+};
+
+(:~
+ : Adds diacritical characters
+ :
+ : @param $measure The measure to be processed
+ : @param $measureLabel The label of the measure
+ : @return A span containing the label
+ :)
+declare function measure:makeMeasureLabelCritical($measure as node(), $measureLabel as xs:string) as element(xhtml:span) {
+
+ let $measureParentElem := local-name($measure/parent::node())
+ return
+ if($measureParentElem = 'supplied') then (
+ {'[' || $measureLabel || ']'}
+ ) else if($measureParentElem = 'del') then (
+ {$measureLabel}
+ ) else (
+ {$measureLabel}
+ )
+};
+
+(:~
+ : Returns a label for a measure (range)
+ :
+ : @param $measure The measure to be processed
+ : @return A span containing the label
+ :)
+declare function measure:getMeasureLabel($measure as element(mei:measure)) as element(xhtml:span) {
+
+ let $measureLabel := measure:getMeasureLabelAttr($measure)
+ let $measureID := $measure/@xml:id
+ let $measureFacs := $measure/@facs
+ let $measuresZoneRef := $measure/ancestor::mei:mdiv//mei:measure[@facs = $measureFacs]
+ let $measureZoneRefCount := count($measuresZoneRef)
+
+ let $measureLabels :=
+ if(not($measure/parent::mei:reg)) then (
+ if(($measureZoneRefCount gt 1) and ($measure//mei:multiRest)) then (
+ for $measure in $measuresZoneRef
+ let $measureLabel := measure:getMeasureLabelAttr($measure)
+ let $measureLabel := measure:makeMeasureLabelCritical($measure, $measureLabel)
+ let $measureLabel := ($measureLabel || '–' || number($measureLabel) + number($measure//mei:multiRest/@num) - 1)
+ return
+ {$measureLabel}
+ ) else if ($measureZoneRefCount gt 1) then (
+ for $label in $measuresZoneRef
+ return
+ measure:makeMeasureLabelCritical($label, $label/string(@label))
+ ) else if ($measure//mei:multiRest) then (
+ {$measureLabel || '–' || (number($measureLabel) + number($measure//mei:multiRest/@num) - 1)}
+ ) else (
+ measure:makeMeasureLabelCritical($measure, $measureLabel)
+ )
+ ) else if($measure/parent::mei:reg) then (
+ for $measure in $measuresZoneRef
+ return
+ measure:getRegMeasureLabel($measure/parent::mei:reg)
+ ) else (
+ noLabel
+ )
+
+ return
+ measure:joinMeasureLabels($measureLabels)
+};
+
+(:~
+ : Returns a label for a measure (range)
+ :
+ : @param $labels The measure to be processed
+ : @return A span containing the joined label
+ :)
+declare function measure:joinMeasureLabels($labels as node()*) as element(xhtml:span) {
+
+ {
+ for $label at $pos in functx:distinct-deep($labels)
+ return
+ if($pos = 1) then (
+ $label
+ ) else(
+ '/',$label
+ )
+ }
+};
+
+(:~
+ : Returns a label for a regularized measure (range)
+ :
+ : @param $reg The reg element (containing measure) to be processed
+ : @return The label as string
+ :)
+declare function measure:getRegMeasureLabel($reg as node()) as element(xhtml:span) {
+
+ let $measures := $reg/mei:measure
+ let $measuresCount := count($measures)
+ let $measureStart := $measures[1]
+ let $measureStop := $measures[$measuresCount]
+ let $measureLabel := measure:getMeasureLabelAttr($measureStart) || '–' || measure:getMeasureLabelAttr($measureStop)
+ return
+ {$measureLabel}
+};
+
+(:~
+ : Returns the value of a (multi) measure rest
+ :
+ : @param $measure The measure to be processed
+ : @return The value of the rest as string
+ :)
+declare function measure:getMRest($measure as element(mei:measure)) as xs:string {
+
+ if($measure//mei:mRest) then (
+ string('1')
+ ) else if($measure//mei:multiRest) then(
+ $measure//mei:multiRest/string(@num)
+ ) else (
+ string('0')
+ )
+};
+
+(:~
+ : Returns resolved labels
+ : E.g., for a measure with the label "3-5" this function will return "3,4,5";
+ : for a measure with the n attribute "4a" this function will return "4a".
+ :
+ : @param $measure The measure to be processed
+ : @return A sequence of measure numbers or labels
+ :)
+declare function measure:analyzeLabel($measure as element(mei:measure)) as xs:string* {
+ if($measure/@label)
+ then (
+ if (matches($measure/@label, '^\d+[\-–]\d+$'))
+ then (
+ let $first := functx:substring-before-match($measure/@label, '[\-–]') => number()
+ let $last := functx:substring-after-match($measure/@label, '[\-–]') => number()
+ let $steps := ($last - $first + 1) => xs:integer()
+ for $i in 1 to $steps
+ return
+ string($first + $i - 1)
+ )
+ else $measure/@label => data()
+ )
+ else $measure/@n => data()
+};
+
+(:~
+ : Resolvs multi-measure rests
+ :
+ : @param $mdiv The mdiv to be processed
+ : @param $measureN The number of the measure to be resolved
+ : @return The measures covered by the multi-measure rest
+ :)
+(: This does not work, see discussion at https://github.com/Edirom/Edirom-Online/pull/302 :)
+(:
+declare function measure:resolveMultiMeasureRests($mdiv as node(), $measureN as xs:string) as node()* {
+ let $measureNNumber := number($measureN)
+ return
+ if ($mdiv//mei:measure[.//mei:multiRest][@label]) then (
+ $mdiv//mei:measure[.//mei:multiRest][number(substring-before(@label, '–')) <= $measureNNumber][.//mei:multiRest/number(@num) gt number($measureNNumber - substring-before(@label, '–'))]
+ ) else (
+ $mdiv//mei:measure[.//mei:multiRest][number(@n) lt $measureNNumber][.//mei:multiRest/number(@num) gt ($measureNNumber - number(@n))]
+ )
+};
+:)
+
+(:~
+ : Finds all measures on a page.
+ :
+ : @param $mei The sourcefile
+ : @param $surface The surface to look at
+ : @returns A sequence of map objects with measure information
+ :)
+declare function measure:getMeasuresOnPage($mei as document-node()?, $surface as element(mei:surface)) as map(*)* {
+
+ for $zone in $surface/mei:zone[@type='measure'][@xml:id]
+ (: do we need to compute an id for zones without @xml:id? :)
+ let $zoneRef := concat('#', $zone/@xml:id)
+ (:
+ : The first predicate with `contains` is just a rough estimate to narrow down the result set.
+ : It uses the index and is fast while the second (exact) predicate is generally too slow
+ :)
+ let $measures := $mei//mei:measure[contains(@facs, $zoneRef)][$zoneRef = tokenize(@facs, '\s+')]
+ return
+ for $measure in $measures
+ let $measureLabel := measure:getMeasureLabel($measure)
+ return
+ map {
+ 'zoneId': $zone/string(@xml:id),
+ 'ulx': $zone/string(@ulx),
+ 'uly': $zone/string(@uly),
+ 'lrx': $zone/string(@lrx),
+ 'lry': $zone/string(@lry),
+ 'id': $measure/string(@xml:id),
+ 'name': $measureLabel,
+ 'type': $measure/string(@type),
+ 'rest': measure:getMRest($measure)
+ }
+};
diff --git a/packages/eoTheme/sass/etc/facsimile.scss b/packages/eoTheme/sass/etc/facsimile.scss
index d4e02e232..02a78d8fb 100644
--- a/packages/eoTheme/sass/etc/facsimile.scss
+++ b/packages/eoTheme/sass/etc/facsimile.scss
@@ -17,6 +17,10 @@
@include background-image(radial-gradient(center , rgba(100, 100, 100, 0.3), rgba(0, 0, 0, 0) 5px));
}
+ .del {
+ text-decoration: line-through;
+ }
+
.measure.highlighted {
box-shadow: 0 0 4px rgba(0, 0, 0, 0.6) inset;
}
diff --git a/testing/XQSuite/measure-tests.xqm b/testing/XQSuite/measure-tests.xqm
new file mode 100644
index 000000000..f886b1048
--- /dev/null
+++ b/testing/XQSuite/measure-tests.xqm
@@ -0,0 +1,55 @@
+xquery version "3.1";
+
+module namespace measuretests = "http://www.edirom.de/xquery/xqsuite/measure-tests";
+
+import module namespace measure = "http://www.edirom.de/xquery/measure" at "xmldb:exist:///db/apps/Edirom-Online/data/xqm/measure.xqm";
+
+declare namespace mei="http://www.music-encoding.org/ns/mei";
+declare namespace tei="http://www.tei-c.org/ns/1.0";
+declare namespace test="http://exist-db.org/xquery/xqsuite";
+declare namespace xhtml="http://www.w3.org/1999/xhtml";
+
+declare
+ %test:args("some content") %test:assertEquals("1")
+ %test:args("some content") %test:assertEquals("foo")
+ %test:args("some content") %test:assertEquals("bar")
+ %test:args("some content") %test:assertEquals("bar")
+ function measuretests:getMeasureLabelAttr($node as element()) {
+ measure:getMeasureLabelAttr($node)
+};
+
+(: The function `measure:resolveMultiMeasureRests#2` is currently commented out :)
+(:
+declare
+ %test:args("4") %test:assertEmpty
+ %test:args("foo") %test:assertEmpty
+ %test:args("108") %test:assertExists
+ function measuretests:resolveMultiMeasureRests($measureN as xs:string) as node()* {
+ doc("testdata/multiMeasureRests.xml")/mei:mdiv =>
+ measure:resolveMultiMeasureRests($measureN)
+};
+:)
+
+declare
+ %test:args("xacea1fff-da53-42b4-bb5e-e814e1ca653a") %test:assertXPath("/xhtml:span[parent::xhtml:span][.='107']")
+ %test:args("x9cafb006-025b-4019-9430-936ab85a62db") %test:assertXPath("/xhtml:span[parent::xhtml:span][.='108']")
+ %test:args("xf48dfa96-2cf1-43e5-8fbf-ec8d1dca7501") %test:assertXPath("/xhtml:span[parent::xhtml:span][.='110']")
+ function measuretests:getMeasureLabel($measureID as xs:string) as element(xhtml:span) {
+ doc("testdata/multiMeasureRests.xml")/id($measureID) =>
+ measure:getMeasureLabel()
+};
+
+declare
+ %test:args("xacea1fff-da53-42b4-bb5e-e814e1ca653a") %test:assertEquals("107")
+ %test:args("x9cafb006-025b-4019-9430-936ab85a62db") %test:assertEquals("108","109")
+ %test:args("xf48dfa96-2cf1-43e5-8fbf-ec8d1dca7501") %test:assertEquals("110a")
+ %test:args("x10f96702-291d-4d4e-962a-755a4eed2507") %test:assertEquals("104","105","106","107")
+ %test:args("foo") %test:assertEmpty
+ function measuretests:analyzeLabel($measureID as xs:string) as xs:string* {
+ doc("testdata/multiMeasureRests.xml")/id($measureID) =>
+ measure:analyzeLabel()
+};
diff --git a/testing/XQSuite/run-unit-tests.xq b/testing/XQSuite/run-unit-tests.xq
index 6b11c1a37..709b51c98 100644
--- a/testing/XQSuite/run-unit-tests.xq
+++ b/testing/XQSuite/run-unit-tests.xq
@@ -9,8 +9,10 @@ xquery version "3.1";
import module namespace test="http://exist-db.org/xquery/xqsuite" at "resource:org/exist/xquery/lib/xqsuite/xqsuite.xql";
import module namespace eut="http://www.edirom.de/xquery/xqsuite/eutil-tests" at "eutil-tests.xqm";
+import module namespace measuretests = "http://www.edirom.de/xquery/xqsuite/measure-tests" at "measure-tests.xqm";
(: the test:suite() function will run all the test-annotated functions in the module whose namespace URI you provide :)
test:suite((
- util:list-functions("http://www.edirom.de/xquery/xqsuite/eutil-tests")
+ util:list-functions("http://www.edirom.de/xquery/xqsuite/eutil-tests"),
+ util:list-functions("http://www.edirom.de/xquery/xqsuite/measure-tests")
))
diff --git a/testing/XQSuite/testdata/multiMeasureRests.xml b/testing/XQSuite/testdata/multiMeasureRests.xml
new file mode 100644
index 000000000..8491b13ec
--- /dev/null
+++ b/testing/XQSuite/testdata/multiMeasureRests.xml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (Fine)
+
+
Der Stichfehler in der Cl (zwei statt vier Takte Pause nach T. 105) wurde in ED₂
+ nicht korrigiert und hat daher Eingang in weitere Ausgaben gefunden.