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 節點保留在記憶體中,從而極大地提高了效能。
然而,這種方法還存在另一個問題。中間節點的大小可能會超過預設的、但可配置的構建節點大小限制。