使用PHP查找xml元素相对于其他元素的位置

时间:2017-06-30 23:10:06

标签: php xml object simplexml

我正在使用PHP的simpleXml将screenxml文件显示为屏幕上的乐谱。除非在乐谱的某处改变谱号,否则一切都运作良好。以下是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-&gt;注释为$ note)来查看每个度量中的注释。我可以通过 if(isset($ measure-&gt; attributes-&gt; clef))来检测音量中的谱号变化,但这并没有告诉我哪里谱号发生变化(在测量的第二个音符之后和最后三个音符之前,在此示例中。)

我查看了simpleXmlIterator,但它似乎没有处理simpleXml对象的摘录(在这种情况下是$ measure)。我试过了:

$sxi = new SimpleXMLIterator($measure);

这给了我以下警告:

  

警告:   SimpleXMLElement :: __ construct():实体:第29行:解析器错误:开始   标签预期,'&lt;'找不到

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

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的两个音符[实际上,休息和音符]之间。所以,如果我能找到一种方法来访问那些“对象号”,我的问题就可以解决了。有谁知道如何做到这一点,或者更好的方法来解决这个问题?

3 个答案:

答案 0 :(得分:0)

我找到了一个适合我的解决方案。我意识到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;
                    }
                }
            }
    }
}

答案 1 :(得分:0)

考虑一个XSLT解决方案。这种特殊用途的XML转换语言可以运行preceding-sibling::*following-sibling::* XPath方法,以在新的单独XML文件中检索<notes>之前和之后的<attributes>个元素的数量。 / p>

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

输入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>

答案 2 :(得分:0)

或者,使用preceding-sibling::*following-sibling::*直接使用PHP的SimpleXMLElement::xpath运行XPath。下面创建一个多维数组, $ clefs ,用度量编号索引,带有两个内部数组,用于 before_notes after_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) 
//   } 
// }