【问题标题】:Find where an xml element occurs in relation to other elements using PHP使用 PHP 查找 xml 元素相对于其他元素的位置
【发布时间】:2017-06-30 23:10:06
【问题描述】:

我正在使用 PHP 的 simpleXml 将 musicxml 文件显示为屏幕上的乐谱。除非乐谱中某处出现谱号变化,否则一切都运行良好。这是 xml 文件的摘录,显示了小节 27 中的谱号变化:

<measure number="27">
  <note>
    </note>
  <note>
    </note>
  <attributes>
    <clef number="2">
      <sign>F</sign>
      <line>4</line>
      </clef>
    </attributes>
  <note>
    </note>
  <note>
    </note>
  <note>
    </note>
  </measure>

我正在通过 foreach ($measure->note as $note) 浏览每个度量中的注释。我可以通过 if(isset($measure->attributes->clef)) 检测到小节中的谱号变化,但这并不能告诉我 在哪里谱号变化发生(在本例中,在小节的第二个音符之后和最后三个音符之前。)

我查看了 simpleXmlIterator,但它似乎无法处理 simpleXml 对象的摘录(在本例中为 $measure)。我试过了:

$sxi = new SimpleXMLIterator($measure);

这给了我以下警告:

警告: SimpleXMLElement::__construct():实体:第 29 行:解析器错误:开始 应有标记,未找到“

当我var_dump($measure) 时,我看到它将谱号更改放在音符数组的末尾,但为其分配了一个数字,可用于确定其在音符序列中的正确位置:

object(SimpleXMLElement)#21 (4) {
  ["@attributes"]=>
  array(2) {
    ["number"]=>
    string(2) "25"
    ["width"]=>
    string(6) "468.94"
  }
  ["note"]=>
  array(25) {
    ...
    [20]=>
    object(SimpleXMLElement)#43 (5) {
      ["rest"]=>
      object(SimpleXMLElement)#49 (0) {
      }
      ["duration"]=>
      string(1) "4"
    }
    [21]=>
    object(SimpleXMLElement)#45 (7) {
      ["@attributes"]=>
      array(2) {
        ["default-x"]=>
        string(6) "293.75"
        ["default-y"]=>
        string(7) "-255.00"
      }
      ["pitch"]=>
      object(SimpleXMLElement)#49 (2) {
        ["step"]=>
        string(1) "D"
        ["octave"]=>
        string(1) "4"
      }
      ["duration"]=>
      string(1) "2"
    }
  }
  ["attributes"]=>
  object(SimpleXMLElement)#44 (1) {
    ["clef"]=>
    object(SimpleXMLElement)#49 (3) {
      ["@attributes"]=>
      array(1) {
        ["number"]=>
        string(1) "2"
      }
      ["sign"]=>
      string(1) "G"
      ["line"]=>
      string(1) "2"
    }
  }
}

对象#44(谱号变化)应位于对象#43 和#45 的两个音符[实际上是一个休止符和一个音符]之间。所以,如果我能找到一种访问这些“对象编号”的方法,我的问题就可以解决。有谁知道如何做到这一点,或者更好的方法来解决这个问题?

【问题讨论】:

    标签: php xml object simplexml


    【解决方案1】:

    或者,使用preceding-sibling::*following-sibling::* 直接使用PHP 的SimpleXMLElement::xpath 运行XPath。下面创建了一个多维数组,$clefs,由度量编号索引,其中包含 before_notesafter_notes 的两个内部数组:

    示例 XML

    $xml = simplexml_load_string(
    '<score-partwise version="1.0">
      <part-list>
        <score-part id="P1">
          <part-name>Voice</part-name>
        </score-part>
      </part-list>
      <part id="P1">
        <measure number="26">
          <note/>
          <note/>
          <note/>
          <note/>
          <note/>
          <attributes>
            <clef number="2">
              <sign>F</sign>
              <line>4</line>
            </clef>
          </attributes>
        </measure>
        <measure number="27">
          <note/>
          <note/>
          <attributes>
            <clef number="2">
              <sign>F</sign>
              <line>4</line>
            </clef>
          </attributes>
          <note/>
          <note/>
          <note/>
        </measure>
        <measure number="28">
          <note/>
          <attributes>
            <clef number="2">
              <sign>F</sign>
              <line>4</line>
            </clef>
          </attributes>
          <note/>
          <note/>
          <note/>
          <note/>
        </measure>
        <measure number="29">
          <note/>
          <note/>
          <note/>
          <note/>
          <attributes>
              <test>XSLT is cool!</test>
          </attributes>
          <note/>
        </measure>
      </part>
    </score-partwise>');
    

    解析代码

    $clefs = [];    
    
    foreach($xml->xpath('//measure') as $item) { 
    
       $measure_num = (integer)$item->xpath('@number')[0];
    
       $clefs['before_notes'][$measure_num] = count($item->xpath("attributes[clef!='']/preceding-sibling::note"));
       $clefs['after_notes'][$measure_num] = count($item->xpath("attributes[clef!='']/following-sibling::note"));
    
    }
    
    echo var_dump($clefs);
    // array(2) { 
    //   ["before_notes"]=> array(4) { 
    //        [26]=> int(5) [27]=> int(2) [28]=> int(1) [29]=> int(0) 
    //   }
    //   ["after_notes"]=> array(4) { 
    //        [26]=> int(0) [27]=> int(3) [28]=> int(4) [29]=> int(0) 
    //   } 
    // } 
    

    【讨论】:

      【解决方案2】:

      我找到了适合我的解决方案。我意识到 simpleXml() 的返回值是一个对象(而不是 XML 字符串)。所以我不能将它的一部分输入 simpleXmlIterator() 并期望除错误之外的任何内容。

      相反,我最终正确地将 $measure: 视为一个对象,并按如下方式遍历它:

      if(isset($measure->attributes->clef) && $measureNbr > 0) {
          // There is a clef change in this measure!
          $clefNbr = $measure->attributes->clef->attributes()->number;
          $durationBeforeClefChg=0;
          foreach($measure as $property => $value) {
              // Count the unchorded durations before the clef change.
                  if(is_object($value) || is_array($value)) {
                      foreach($value as $p2 => $v2) {
                          if(trim($p2) == 'duration' 
                              && $property == 'note' 
                              && ((!property_exists($value,'staff')) 
                                  || trim($value->staff) == $clefNbr) 
                              && !property_exists($value,'chord')) {
                                  $durationBeforeClefChg+= intval(trim($v2)); 
                          }
                          if(trim($p2) == 'clef' && $property == 'attributes') {
                              $newClef = trim($v2->sign);
                              break 2;
                          }
                      }
                  }
          }
      }
      

      【讨论】:

        【解决方案3】:

        考虑XSLT 解决方案。这种特殊用途的 XML 转换语言可以运行 preceding-sibling::*following-sibling::* XPath 方法来检索新的单独 XML 文件中 &lt;attributes&gt; 之前和之后的 &lt;notes&gt; 元素的计数。

        PHP 可以使用其php-xsl 类运行XSLT 1.0 脚本。下面演示了示例 XML 呈现特定信息的输出。使用这种方法,不需要使用 if 逻辑进行嵌套的 foreach 循环。

        输入 XML (不同的小节编号,属性在不同的位置,一个没有谱号)

        <?xml version="1.0"?>
        <score-partwise version="1.0">
          <part-list>
            <score-part id="P1">
              <part-name>Voice</part-name>
            </score-part>
          </part-list>
          <part id="P1">
            <measure number="26">
              <note/>
              <note/>
              <note/>
              <note/>
              <note/>
              <attributes>
                <clef number="2">
                  <sign>F</sign>
                  <line>4</line>
                </clef>
              </attributes>
            </measure>
            <measure number="27">
              <note/>
              <note/>
              <attributes>
                <clef number="2">
                  <sign>F</sign>
                  <line>4</line>
                </clef>
              </attributes>
              <note/>
              <note/>
              <note/>
            </measure>
            <measure number="28">
              <note/>
              <attributes>
                <clef number="2">
                  <sign>F</sign>
                  <line>4</line>
                </clef>
              </attributes>
              <note/>
              <note/>
              <note/>
              <note/>
            </measure>
            <measure number="29">
              <note/>
              <note/>
              <note/>
              <note/>
              <attributes>
                  <test>XSLT is cool!</test>
              </attributes>
              <note/>
            </measure>
          </part>
        </score-partwise>
        

        XSLT (另存为 .xsl 以便在下面的 PHP 中通过文件或字符串进行解析)

        <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
            <xsl:output method="xml" indent="yes"/>
            <xsl:strip-space elements="*"/>
        
            <xsl:template match="node()|@*">
               <xsl:copy>
                 <xsl:apply-templates select="node()|@*"/>
               </xsl:copy>
            </xsl:template>
        
            <xsl:template match="measure">
               <xsl:copy>
                 <xsl:copy-of select="@*"/>
                 <notes_before_clef><xsl:copy-of select="count(attributes[clef!='']/preceding-sibling::note)"/></notes_before_clef>
                 <notes_after_clef><xsl:copy-of select="count(attributes[clef!='']/following-sibling::note)"/></notes_after_clef>
               </xsl:copy>
            </xsl:template>
        
        </xsl:stylesheet>
        

        PHP (在 .ini 文件中启用 php-xsl 扩展)

        // LOAD XML AND XSLT
        $xml = new DOMDocument;
        $xml->load('Input.xml');         // OR $xml->loadXML($xmlstring);
        
        $xsl = new DOMDocument;
        $xsl->load('XSLT_Script.xsl');   // OR $xsl->loadXML($xslstring);
        
        // INITIALIZE TRANSFORMER
        $proc = new XSLTProcessor;
        $proc->importStyleSheet($xsl);    
        
        // TRANSFORM SOURCE TO NEW TREE
        $newXML = $proc->transformToXML($xml);
        
        // ECHO OUTPUT
        echo $newXML;
        
        // SAVE OUTPUT TO FILE
        file_put_contents('Output.xml', $newXML);
        

        输出 XML (解析此文件以满足最终使用需求)

        <?xml version="1.0"?>
        <score-partwise version="1.0">
          <part-list>
            <score-part id="P1">
              <part-name>Voice</part-name>
            </score-part>
          </part-list>
          <part id="P1">
            <measure number="26">
              <notes_before_clef>5</notes_before_clef>
              <notes_after_clef>0</notes_after_clef>
            </measure>
            <measure number="27">
              <notes_before_clef>2</notes_before_clef>
              <notes_after_clef>3</notes_after_clef>
            </measure>
            <measure number="28">
              <notes_before_clef>1</notes_before_clef>
              <notes_after_clef>4</notes_after_clef>
            </measure>
            <measure number="29">
              <notes_before_clef>0</notes_before_clef>
              <notes_after_clef>0</notes_after_clef>
            </measure>
          </part>
        </score-partwise>
        

        【讨论】:

        • 谢谢,冻糕!这为将 xml 预按摩到更方便我需要的东西开辟了新的可能性。
        猜你喜欢
        • 1970-01-01
        • 2011-03-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-16
        相关资源
        最近更新 更多