跳轉到內容

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

[編輯 | 編輯原始碼]

將音符轉換為 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
} ;

中間 XML

[編輯 | 編輯原始碼]

原始指令碼需要重複訪問原始 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 節點保留在記憶體中,從而極大地提高了效能。

然而,這種方法還存在另一個問題。中間節點的大小可能會超過預設的、但可配置的構建節點大小限制。

華夏公益教科書