Estoy usando PHP simpleXml para mostrar un archivo musicxml como una partitura musical en la pantalla. Todo funciona bien a menos que haya un cambio de clave en algún punto de la partitura. Aquí hay un extracto del archivo xml que muestra un cambio de clave en la medida 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>

Estoy revisando las notas en cada compás mediante foreach ($ medida-> nota como $ nota) . Puedo detectar un cambio de clave en un compás mediante if (isset ($ medida-> atributos-> clave)) pero esto no me dice dónde ocurre el cambio de clave (después de la segunda nota del compás y antes de las últimas tres notas, en este ejemplo).

Miré en simpleXmlIterator, pero no parece manejar un extracto de un objeto simpleXml ($ measure en este caso). Intenté:

$sxi = new SimpleXMLIterator($measure);

Esto me dio la siguiente advertencia:

Advertencia: SimpleXMLElement :: __ construct (): Entidad: línea 29: error del analizador: Etiqueta de inicio esperada, '<' no encontrado

Cuando var_dump($measure) veo que coloca el cambio de clave al final de la matriz de notas, pero le asigna un número que podría usarse para determinar su lugar apropiado en la secuencia de notas:

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"
    }
  }
}

El objeto # 44 (el cambio de clave) debe aparecer entre las dos notas [en realidad, un descanso y una nota] que son los objetos # 43 y # 45. Entonces, si pudiera encontrar una manera de acceder a esos "números de objeto", mi problema podría resolverse. ¿Alguien sabe cómo hacer esto, o una mejor manera de resolver este problema?

0
smmcroberts 1 jul. 2017 a las 02:10

3 respuestas

La mejor respuesta

Alternativamente, ejecute XPath directamente con PHP SimpleXMLElement :: xpath usando {{X0} } y following-sibling::*. A continuación se crea una matriz multidimensional, $ clefs , indexada por número de medida con dos matrices internas para before_notes y after_notes :

XML de muestra

$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>');

Código de análisis

$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) 
//   } 
// } 
0
Parfait 1 jul. 2017 a las 20:00

Encontré una solución que funcionó para mí. Me di cuenta de que el valor de retorno de simpleXml () es un objeto (no una cadena XML). Por lo tanto, no puedo alimentar una parte de él en simpleXmlIterator () y esperar todo menos errores.

En cambio, terminé tratando $ measure correctamente: como un objeto, e iteré a través de él de la siguiente manera:

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;
                    }
                }
            }
    }
}
0
smmcroberts 1 jul. 2017 a las 17:39

Considere una solución XSLT. Este lenguaje de transformación XML de propósito especial puede ejecutar los métodos preceding-sibling::* y following-sibling::* XPath para recuperar el recuento de elementos <notes> antes y después de <attributes> en un nuevo archivo XML separado.

PHP puede ejecutar scripts XSLT 1.0 con su php-xsl clase. A continuación se muestra con XML de muestra que representa una salida de información específica. Con este enfoque, no se necesita un bucle foreach anidado con la lógica if.

XML de entrada (diferentes números de medida con atributos en varias posiciones y uno sin clave)

<?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 (guardar como .xsl para que se analice en PHP a continuación, ya sea por archivo o cadena)

<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 (habilitar la extensión php-xsl en el archivo .ini)

// 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 de salida (analizar este archivo para las necesidades de uso final)

<?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>
0
Parfait 1 jul. 2017 a las 18:36