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.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + p + Trio + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/testing/ant-tests.xml b/testing/ant-tests.xml index 2a67eabdd..03bdf40a0 100644 --- a/testing/ant-tests.xml +++ b/testing/ant-tests.xml @@ -32,7 +32,7 @@ - +