read - simplexmlelement php



PHP simpleXML cómo verificar si existe un niño anidado (3)

Tengo que procesar unos 750 archivos xml para generar una buena relación. Probablemente debería haber usado XSLT o usar XPath, pero probablemente sea demasiado tarde para eso. Entonces mi pregunta; para los primeros dos registros todo funciona bien. Parece que hay un par de archivos XML sin los nodos a los que llamo. He intentado usar isset y !== null , que no funciona y me da el mismo error. A saber

Aviso: intentar obtener la propiedad de no objeto en /var/www/overzicht/script.php en la línea 38
Aviso: intentar obtener la propiedad de no objeto en /var/www/overzicht/script.php en la línea 38
Error fatal: Llamar a una función miembro children () en un no objeto en /var/www/overzicht/script.php en la línea 38

Usar lo siguiente probablemente sea incorrecto, ¿verdad?

 if($xml_entry->children('http://www.isotc211.org/2005/gmd')->identificationInfo->MD_DataIdentification->citation->CI_Citation->title->children('http://www.isotc211.org/2005/gco'))

Una pequeña muestra del archivo XML que estoy tratando de analizar es (todo el xml se puede encontrar aquí :

 <gmd:contact>
    <gmd:CI_ResponsibleParty>
      <gmd:individualName>
        <gco:CharacterString>B. Boers</gco:CharacterString>
      </gmd:individualName>
      <gmd:organisationName>
        <gco:CharacterString>Staatsbosbeheer</gco:CharacterString>
      </gmd:organisationName>
      <gmd:positionName>
        <gco:CharacterString>Contactpersoon</gco:CharacterString>
      </gmd:positionName>
    </gmd:CI_ResponsibleParty>
</gmd:contact>

Y mi PHP:

<?php
        $xml_url = "http://www.nationaalgeoregister.nl/geonetwork/srv/dut/q?fast=index&from=1&to=10000&geometry=POLYGON((5.5963%2053.3162%2C5.5963%2053.5766%2C6.9612%2053.5766%2C6.9612%2053.3162%2C5.5963%2053.3162))";
        $xml_single_url = "http://www.nationaalgeoregister.nl/geonetwork/srv/dut/xml.metadata.get?uuid=";
        //Load the XML
        $xml = simplexml_load_file($xml_url);
        $xml_array = array();

        //Loop through all the nodes with 'metadata' and put uuid in the array
        foreach($xml->metadata as $metadata) {
                $xml_array[] = $metadata->children('http://www.fao.org/geonetwork')->children()->uuid;
        }       
        echo "<table>"
        ."<tr>"
        ."<td>Title</td>"
        ."<td>Owner</td>"
        ."<td>Purpose</td>"
        ."<td>Tags</td>"
        ."<td>Url</td>"
        ."<td>Url</td>"     
        ."</tr>";

        $i = 0;
        //For every id in the $xml_array 
        foreach($xml_array as $ar)
        {
            //Just a limit for testing purposes
            $i++;
            if($i == 100)
            {
                break;
            }
            //Loads the xml file
            $xml_entry = simplexml_load_file($xml_single_url .$ar);
            echo "<tr>";

            //Title
            echo "<td>"
            .$xml_entry->children('http://www.isotc211.org/2005/gmd')->identificationInfo->MD_DataIdentification->citation->CI_Citation->title->children('http://www.isotc211.org/2005/gco')->CharacterString
            ."</td>";

            //Owner
            echo "<td>" 
            .$xml_entry->children('http://www.isotc211.org/2005/gmd')->contact->CI_ResponsibleParty->organisationName->children('http://www.isotc211.org/2005/gco')->CharacterString
            ."</td>";

            //Purpose
            echo "<td>" 
            .$xml_entry->children('http://www.isotc211.org/2005/gmd')->identificationInfo->MD_DataIdentification->purpose->children('http://www.isotc211.org/2005/gco')->CharacterString
            ."</td>";

            //Tags      
            //Transfer          
            echo "</tr>";
        }       
        echo "</table>";

?>

Intenté encontrar la solución por mi cuenta, pero parece que no puedo encontrarla.


Answer #1

Parece que debería usar XPath según este enlace .


Answer #2

El problema que tienes es que tienes una larga cadena de operadores -> , y el elemento que falta está en algún lugar de esa cadena . Tan pronto como solicite un elemento que no existe, obtendrá un NULL, y todos los operadores subsiguientes fallarán en algún grado u otro.

Teóricamente, si no tiene idea de cuál de los elementos de la cadena falta (y tal vez lo haga basándose en la estructura conocida / permitida del XML?) Tendría que dividir la cadena en una serie completa de asignaciones intermedias y isset() cheques.

Afortunadamente, PHP le permite salirse con llamadas como null->Property con solo un Notice , por lo que solo es la llamada al método ->children() lo que causará un error fatal. Entonces, podría verificar antes de cada llamada a eso:

 if( ! isset($xml_entry) { return; }
 $temp = $xml_entry->children('http://www.isotc211.org/2005/gmd')->identificationInfo->MD_DataIdentification->citation->CI_Citation->title;
 if( ! isset($temp) { return; }     
 echo $temp->children('http://www.isotc211.org/2005/gco'))->CharacterString;

Sin embargo, el mensaje de error le dice más de lo que puede haberse dado cuenta:

  1. Aviso: intentar obtener la propiedad de no objeto en /var/www/overzicht/script.php en la línea 38
  2. Aviso: intentar obtener la propiedad de no objeto en /var/www/overzicht/script.php en la línea 38
  3. Error fatal: Llamar a una función miembro children () en un no objeto en /var/www/overzicht/script.php en la línea 38

Son dos Notice sobre el acceso a las propiedades y un Fatal error sobre el acceso a un método. Entonces la línea debe romperse así ...

$xml_entry
    ->children('http://www.isotc211.org/2005/gmd')
    ->identificationInfo
    ->MD_DataIdentification
    // OK to here

    ->citation
    // This part didn't complain, but subsequent ones did; <citation> is the missing element

    ->CI_Citation
    // First Notice
    ->title
    // Second Notice
    ->children('http://www.isotc211.org/2005/gco'))
    // Fatal error - processing aborts here

    ->CharacterString

Entonces, lo que necesita verificar es la existencia de una <citation> :

$citation = $xml_entry->children('http://www.isotc211.org/2005/gmd')->identificationInfo->MD_DataIdentification->citation;
if ( isset($citation) )
{
    echo $citation->CI_Citation->title->children('http://www.isotc211.org/2005/gco')->CharacterString;
}

Answer #3

el problema con líneas como estas:

if($xml_entry->children('http://www.isotc211.org/2005/gmd')->identificationInfo->MD_DataIdentification->citation->CI_Citation->title->children('http://www.isotc211.org/2005/gco'))

es que son demasiado largos y propensos a errores. Incluso SimpleXML permite ese tipo de acceso "fácil" aquí, en caso de que no encuentre el elemento en alguna parte, devolverá NULL y luego obtendrá las advertencias e incluso los errores fatales.

Para su caso de uso, es mucho mejor usar una consulta xpath para hacer el trabajo. Como necesita acceder a múltiples propiedades que representan los metadatos, le sugiero que primero incluya esto en una clase de su propio ejemplar SimpleXMLElementXpathObject , el PropertyIterator usado allí se puede encontrar aquí .

Este tipo le permite definir los metadatos que busca con un SimpleXMLElement y una matriz que describe las propiedades asignándolas a las consultas de xpath:

$metaDef = array(
    'title'   => 'gmd:identificationInfo//gmd:CI_Citation/gmd:title/gco:CharacterString',
    'owner'   => 'gmd:contact/gmd:CI_ResponsibleParty/gmd:organisationName/gco:CharacterString',
    'purpose' => 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:purpose/gco:CharacterString',
);

Como puede ver, hay una expresión xpath por cada clave. Las claves se convertirán en propiedades. Esto luego le permite hacer las asignaciones sobre la marcha, por ejemplo:

$meta = new SimpleXMLElementXpathObject($xml, $metaDef);
echo $meta->title, "\n";
echo json_encode($meta, JSON_PRETTY_PRINT), "\n";

Salida:

Natuur - Ecologische verbindingszones
{
    "title": "Natuur - Ecologische verbindingszones",
    "owner": "provincie Frysl\u00e2n",
    "purpose": "Beleidsnota \"ecologische verbindingszones in Frysl\u00e2n\" vastgesteld door Provinciale Staten op 4 oktober 2006. Opgenomen in het Streekplan 2007"
}

En caso de que xpath no devuelva ningún resultado, se proporciona NULL. Eso significa que las propiedades son opcionales, no verá advertencias o incluso errores fatales. Solo para dejarlo en claro: Esto es básicamente usando el método xpath de SimpleXMLElement para que también pueda ejecutar estas consultas por su cuenta.

Un ejemplo más completo:

$query = new GeoNetwork_Query();
$query
    ->setGeometry('POLYGON((5.5963 53.3162,5.5963 53.5766,6.9612 53.5766,6.9612 53.3162,5.5963 53.3162))')
    ->setLimit(10);

$metaObj = function (GeoNetwork_Resource $resource) {
    $metaDef = array(
        'title'   => 'gmd:identificationInfo//gmd:CI_Citation/gmd:title/gco:CharacterString',
        'owner'   => 'gmd:contact/gmd:CI_ResponsibleParty/gmd:organisationName/gco:CharacterString',
        'purpose' => 'gmd:identificationInfo/gmd:MD_DataIdentification/gmd:purpose/gco:CharacterString',
    );

    return new SimpleXMLElementXpathObject($resource->getIterator(), $metaDef);
};

$resources = new GeoNetwork_UuidIterator($query);
$objects   = new DecoratingIterator($resources, $metaObj);
$table     = new HtmlTableIterator($objects, ['Title', 'Owner', 'Purpose']);

echo "<table>\n";
foreach ($table as $row) {
    echo $row, "\n";
}
echo "</table>\n";

He limitado el resultado a 10 para que no cree una lista demasiado larga (para el resultado de la consulta). También puede limitar los $objects envolviéndolos en un LimitIterator . Salida ejemplar del código anterior:

<table>
<tr><td>Title</td><td>Owner</td><td>Purpose</td></tr>
<tr><td>Natuur - Ecologische verbindingszones</td><td>provincie Fryslân</td><td>Beleidsnota "ecologische verbindingszones in Fryslân" vastgesteld door Provinciale Staten op 4 oktober 2006. Opgenomen in het Streekplan 2007</td></tr>
<tr><td>CORINE: Veranderingen in landgebruik in Nederland tussen 1986 en 2000.</td><td>Alterra, Wageningen UR</td><td>Het monitoren van landgebruiksveranderingen op Europese schaal volgens een standaard methode.</td></tr>
<tr><td>Viswaterkaart Sportvisserij</td><td>Sportvisserij Nederland</td><td>Elke sportvisser moet exact weten waar die onder welke (bijz.) voorwaarden mag hengelen.</td></tr>
<tr><td>Veiligheidsafstand vuurwerk</td><td>Interprovinciaal Overleg</td><td>Risicokaart</td></tr>
<tr><td>Weggeg convergenties</td><td>Rijkswaterstaat Data en ICT Dienst (RWS DID)</td><td>Ruimtelijke analyses waarbij ligging van infrastructuur van belang is en bereikbaarheidsberekeningen</td></tr>
<tr><td>Beheerkaart Nat Versie januari 2008</td><td>Rijkswaterstaat Data en ICT Dienst (RWS DID)</td><td>De Beheerkaart Nat wordt door de natte districten van Rijkswaterstaat gebruikt ten behoeve van beheer en onderhoud van zijn beheerobjecten van de watersystemenen. Het NIS gebruikt de gegevens om ondermeer de benodigde budgetten te bepalen voor beheer en onderhoud.</td></tr>
<tr><td>Orthofotomozaieken_project</td><td>Rijkswaterstaat Data en ICT Dienst (RWS DID)</td><td>Gebruik als ondergrond</td></tr>
<tr><td>Knelpunten in LAW-routes</td><td>Stichting Wandelnet</td><td>Inventarisatie van knelpunten in LAW-routes voor provincies</td></tr>
<tr><td>Electronische zeekaarten Ned. Cont. Plat usage Harbour</td><td>Dienst der Hydrografie</td><td>Veilige navigatie</td></tr>
<tr><td>Maatregelzone kernenergie</td><td>Interprovinciaal Overleg</td><td>Risicokaart</td></tr>
</table>

En el código anterior utilicé clases desde aquí: https://gist.github.com/hakre/94a36e4587214a6e9bc9





simplexml