XQuery/使用中间文档
处理 XML 通常涉及为后续处理创建中间 XML 片段。以下是如何使用两种方法的示例,一种是对相同数据进行多次传递,另一种是构建数据的中间视图。
MusicXML 是用于记录乐谱的 XML 应用程序。有一系列软件可以生成和使用 MusicXML。
有两种类型的 MusicXML 以及两个相关的模式,一种是将小节包含在部分中(partwise),另一种是将部分包含在小节中(timewise)。
MusicXML partwise 乐谱的示例是 莫扎特的 A 大调钢琴奏鸣曲,作品编号 331
以下是一个音符的示例定义
<note>
<pitch>
<step>A</step>
<octave>3</octave>
</pitch>
<duration>2</duration>
<voice>3</voice>
<type>eighth</type>
<stem>down</stem>
<staff>2</staff>
<beam number="1">begin</beam>
<notations>
<slur type="stop" number="1"/>
</notations>
</note>
Recordare 网站提供了一些示例代码来演示使用 XQuery 处理 MusicXML [1]。第一个脚本找到乐谱中的最低和最高音符。网站上显示的脚本不符合当前的 XQuery 标准,但只需进行一些细微的更改即可使其更新。
declare function local:MidiNote($thispitch as element(pitch) ) as xs:integer
{
let $step := $thispitch/step
let $alter :=
if (empty($thispitch/alter)) then 0
else xs:integer($thispitch/alter)
let $octave := xs:integer($thispitch/octave)
let $pitchstep :=
if ($step = "C") then 0
else if ($step = "D") then 2
else if ($step = "E") then 4
else if ($step = "F") then 5
else if ($step = "G") then 7
else if ($step = "A") then 9
else if ($step = "B") then 11
else 0
return 12 * ($octave + 1) + $pitchstep + $alter
} ;
let $doc := doc("/db/Wiki/Music/examples/MozartPianoSonata.xml")
let $part := $doc//part[./@id = "P1"]
let $highnote := max(for $pitch in $part//pitch return local:MidiNote($pitch))
let $lownote := min(for $pitch in $part//pitch return local:MidiNote($pitch))
let $highpitch := $part//pitch[local:MidiNote(.) = $highnote]
let $lowpitch := $part//pitch[local:MidiNote(.) = $lownote]
let $highmeas := string($highpitch[1]/../../@number)
let $lowmeas := string($lowpitch[1]/../../@number)
return
<result>
<low-note>{$lowpitch[1]}
<measure>{$lowmeas}</measure>
</low-note>
<high-note>{$highpitch[1]}
<measure>{$highmeas}</measure>
</high-note>
</result>
输出
<result>
<low-note>
<pitch>
<step>D</step>
<octave>2</octave>
</pitch>
<measure>3</measure>
</low-note>
<high-note>
<pitch>
<step>E</step>
<octave>6</octave>
</pitch>
<measure>5</measure>
</high-note>
</result>
音符所在小节的路径
let $highmeas := string($highpitch[1]/../../@number)
使用一组固定的步骤向上遍历层次结构。这限制了此脚本对 MusicXML 模式类型的应用,因为小节在层次结构中的位置在两种模式中是不同的。在编写脚本时,祖先轴尚不支持,但现在已经支持,因此这些行可以用更通用的方式表示为
let $highmeas := string($highpitch/ancestor::measure/@number)
将音符转换为 MIDI 编号的函数使用嵌套的 if-then-else 表达式。XQuery 缺少可能使用的 switch 表达式,但更清晰的方法是使用查找表,该查找表在脚本中定义或存储在数据库中。
这里,音符序列被创建为查找表。它绑定到一个全局变量,该变量在修订后的音符到 MIDI 函数中使用
declare variable $NOTESTEP :=
(
<note name="C" stepNo="0"/>,
<note name="D" stepNo="2"/>,
<note name="E" stepNo="4"/>,
<note name="F" stepNo="5"/>,
<note name="G" stepNo="7"/>,
<note name="A" stepNo="9"/>,
<note name="B" stepNo="11"/>
);
declare function local:MidiNote($thispitch as element(pitch) ) as xs:integer
{
let $alter := xs:integer(($thispitch/alter,0)[1])
let $octave := xs:integer($thispitch/octave)
let $pitchstepNo := xs:integer($NOTESTEP[@name = $thispitch/step]/@stepNo)
return 12 * ($octave + 1) + $pitchstepNo + $alter
} ;
原始脚本需要重复访问原始 MusicXML 源。另一种方法是创建一个中间结构来保存 MIDI 音符,并在后续分析中使用它。该结构是原始音符的计算视图,并添加了派生数据 - MIDI 音符和小节。
let $midiNotes :=
for $pitch in $part//pitch
return
<pitch>
{$pitch/*}
<midi>{local:MidiNote($pitch)}</midi>
<measure>{string($pitch/../../@number)}</measure>
</pitch>
然后使用此视图来定位最高和最低音符以及它们在乐谱中的位置
let $highnote := max($midiNotes/midi)
let $lownote := min($midiNotes/midi)
let $highpitch := $midiNotes[midi = $highnote]
let $lowpitch := $midiNotes[midi = $lownote]
declare variable $NOTESTEP :=
(
<note name="C" step="0"/>,
<note name="D" step="2"/>,
<note name="E" step="4"/>,
<note name="F" step="5"/>,
<note name="G" step="7"/>,
<note name="A" step="9"/>,
<note name="B" step="11"/>
);
declare function local:MidiNote($thispitch as element(pitch) ) as xs:integer
{
let $alter := xs:integer(($thispitch/alter,0)[1])
let $octave := xs:integer($thispitch/octave)
let $name := $thispitch/step
let $pitchstep := xs:integer($NOTESTEP[@name = $name]/@step)
return 12 * ($octave + 1) + $pitchstep + $alter
} ;
let $doc := doc("/db/Wiki/Music/examples/MozartPianoSonata.xml")
let $part := $doc//part[./@id = "P1"]
let $midiNotes :=
for $pitch in $part//pitch
return
<pitch>
{$pitch/*}
<midi>{local:MidiNote($pitch)}</midi>
<measure>{string($pitch/ancestor::measure/@number)}</measure>
</pitch>
let $highnote := max($midiNotes/midi)
let $lownote := min($midiNotes/midi)
return
<result>
<low-note>
{$midiNotes[midi = $lownote]}
</low-note>
<high-note>
{ $midiNotes[midi = $highnote]}
</high-note>
</result>
尽管可以说第二种脚本的设计更简洁、更直接,但它依赖于构建临时 XML 节点,然后这些节点是 XPath 表达式的主题。不同实现对这些临时 XML 节点的处理方式不同。在 eXist 的早期版本中,每个节点都写入数据库中的临时文档,这会导致性能开销和垃圾收集问题。在 1.3 版本中,中间 XML 节点保留在内存中,从而极大地提高了性能。
然而,这种方法还存在另一个问题。中间节点的大小可能会超过预设的、但可配置的构建节点大小限制。