Processing XML with PHP

Using SimpleXML and DOM

Tutorial onde se descreve a implementação de uma pequena aplicação Web para gestão de logs e se usam os vários serviços desta aplicação para demonstrar a utilização das duas classes PHP para processamento de XML: SimpleXML e DOM.


@Copyright José Carlos Ramalho - DI/UM - Nov. 2012

Pretende-se desenvolver uma pequena aplicação Web para gerir os logs de uma qualquer outra aplicação de grande dimensão.

O Log Manager deverá ter 3 serviços:

Reset:
limpa o registo de logs;
Add Log:
acrescenta um registo de log;
List Logs:
lista os logs registados numa tabela HTML.

Primeiro há que desenhar a estrutura do registo de logs: o logbook. Optou-se por especificar um XML Schema que se apresenta a seguir.

Schema do logbook

Depois cria-se uma instância que servirá de base a testes.

<?xml version="1.0" encoding="ISO-8859-1"?>
<logs>
  <log>
    <tipo>MMM</tipo>
    <data>1234-11-02</data>
    <utilizador>jcr</utilizador>
    <operacao>Criação</operacao>
  </log>
  <log>
    <tipo>PPPP</tipo>
    <data>1956</data>
    <utilizador>maria</utilizador>
    <operacao>remoção</operacao>
  </log>
</logs>

Pretende-se obter como resultado desta operação um logbook vazio. Falta definir qual a representação para um logbook vazio.

Para o nosso exercício vamos considerar um documento XML com um elemento logs vazio como se ilustra a seguir:

<?xml version="1.0" encoding="ISO-8859-1"?>
<logs/>

Uma estratégia possível será a de criar um documento XML apenas com a declaração XML e o elemento principal vazio. A classe SimpleXML não pode ser usada na implementação desta estratégia uma vez que só permite manipular o elemento principal, não é possível acrescentar a declaração XML ou outras instruções de processamento que serão necessárias mais à frente. Assim resta-nos a classe DOM como se exemplifica a seguir.

<?php

$dom = new DOMDocument('1.0', 'ISO-8859-1');

$element = $dom->createElement('logs');
$dom->appendChild($element);

$dom->save('logbook.xml');

echo ">p>Reset feito com sucesso.>/p>";
echo ">a href='log-manager.html'>Voltar ao log-manager.>/a>";

?>

De início, cria-se uma instância de uma árvore abstracta, apenas com o nodo raíz e um filho que é a declaração XML com os parámetros passados como argumento: new DOMDocument.

A seguir cria-se um elemento e acrescenta-se este à árvore: os métodos createElement e appendChild devem usar-se aos pares.

Por fim, grava-se a estrutura no ficheiro de registo: save(...).


Este serviço é constituído por duas partes. Um formulário que recolhe a informação do registo e uma script que processa a informação recebida do formulário. Optou-se por usar o método get permitindo que a informação, alternativamente ao formulário, possa ser enviada diretamente pelo URL.

A seguir apresenta-se o formulário para recolha de informação.

1ª parte: o formulário

<h2>Add Log</h2>
<form action="add-log.php" method="get">
    <fieldset>
         <legend>Registo de um Log:</legend>
         Tipo: <input name="tipo" type="text" size="20"/><br/> 
         Data: <input name="data" type="text" size="10"/><br/> 
         Utilizador: <input name="utilizador" type="text" size="20"/><br/>    
         Op: <input name="operacao" type="text" size="60"/><br/>
         <input type="submit" value="Enviar"/>
    </fieldset>               
</form>

Com a utilização de umas stylesheets CSS este formulário terá o seguinte aspecto:

Se não pretendermos usar o formulário e registar diretamente uma entrada podemos tirar partido do método get que nos permite fazer a operação invocando apenas a script de atendimento com os parâmetros apropriados (este mecanismo poderá ser usado, por exemplo, para integrar este serviço numa aplicação Web).

http://servidor.uminho.pt/app/add-log.php?tipo=MMM&data=2012-11-08&utilizador=jcr&operacao=teste

Note que:

  • após o nome da script o carácter ? inicia a lista de parâmetros;
  • cada parâmetro é constituído por um par: nome e valor;
  • cada parâmetro é separado do seguinte pelo carácter &.

2ª parte: a script de atendimento

Para processar os dados do formulário é preciso desenvolver uma script que tanto poderá ser escrita usando a classe DOM como a SimpleXML.

Comecemos pela script em DOM.

<?php
$logs = new DOMDocument(); 
$logs->load('logs.xml');

$log = $logs->createElement('log');
$tipo = $logs->createElement('tipo',$_GET['tipo']);
$data = $logs->createElement('data',$_GET['data']);
$utilizador = $logs->createElement('utilizador',$_GET['utilizador']);
$operacao = $logs->createElement('operacao',$_GET['operacao']);

$log->appendChild($tipo);
$log->appendChild($data);
$log->appendChild($utilizador);
$log->appendChild($operacao);

$logs->documentElement->appendChild($log);

$logs->formatOutput = true;
$logs->save("logs.xml");

header("Location: log-manager.html");
?>

Notas:

  • Nas linhas 2 e 3, cria-se o objeto DOM e carrega-se o documento XML;
  • Nas linhas 5 a 9, criam-se os nodos do tipo Element necessários, repare na utilização do array associativo $_GET, que corresponde ao método usado no formulário;
  • Nas linhas 11 a 14 e 16, colocam-se os nodos criados no local apropriado da árvore documental abstrata;
  • Na linha 19, reescreve-se tudo no ficheiro XML;
  • Por fim, a linha 21 indica ao browser que deve regressar à página de administração da aplicação.

A mesma script, agora usando a classe SimpleXML teria o seguinte aspeto:

<?php

  $logbook = simplexml_load_file("logs.xml");
  
  $log = $logbook->addChild('log');
  $log->addChild('tipo', $_GET['tipo']);
  $log->addChild('data', $_GET['data']);
  $log->addChild('utilizador', $_GET['utilizador']);
  $log->addChild('operacao', $_GET['operacao']);
  
  $logbook->asXML("logs.xml");
  
  header("Location: log-manager.html");
?>
            

A diferença principal é que enquanto em DOM temos duas instruções para colocar um nodo na árvore (criar o nodo e inseri-lo no sítio certo), em SimpleXML podemos fazê-lo de uma só vez.


Este serviço irá listar numa tabela Hyper Text Markup Language a lista de logs armazenados. Pode ser feito de várias maneiras:

  1. Com PHP usando a classe SimpleXML;
  2. Com PHP usando a classe DOM;
  3. Com PHP e XSL usando processador de XSL do PHP;
  4. Apenas com XSL, associando a stylesheet ao documento XML e deixando ao browser o trabalho de aplicar a stylesheet ao documento.

1. PHP com SimpleXML

  $logs = simplexml_load_file("logs.xml");
  
  print
  "<html>
    <head>
      <title>Listagem de Logs</title>
    </head>
    <body>
      <table id=\"logs\" border=\"1\">
        <tr>
          <th>Tipo</th><th>Data</th><th>Utilizador</th><th>Operação</th>
        </tr>
  ";
  
  foreach ($logs->log as $l)
    {
      print "<tr>
               <td>".(string)$l->tipo."</td>".
               "<td>". (string)$l->data."</td>".
               "<td>".(string)$l->utilizador."</td>".
               "<td>".(string)$l->operacao."</td>".
             "</tr>
            ";
    }
  print "</table>";
  print "[<a href=\"log-manager.html\">Voltar ao incio</a>]
        </body>
      </html>";

Notas:

  • Carrega-se o documento XML: simplexml_load_file;
  • Com a instrução cíclica foreach percorre-se um a um cada log;
  • Na expressão do foreach podíamos usar alternativamente a expressão: $logs->xpath('//log') que é mais genérica uma vez que os elementos a processar são os constantes na lista devolvida pela expressão XPath.

2. PHP com DOM

O modelo de dados do DOM é mais granular e a respectiva classe oferece métodos de mais baixo nível o que tem como implicações: ser possível fazer tudo sobre a estrutura e algumas operações são mais complicadas desmembrando-se em várias pequenas ações.

O parser que cria o DOM não deixa nada de fora, nem os caracteres brancos, o que tem um impacto sobre a estrutura. Se o documento tiver sido criado num IDE ou mesmo pelos métodos do DOM, foi gerado espaço branco entre os elementos. Este espaço branco não é descartado mas sim armazenado em nodos no modelo abstracto baptizados com o nome #text.

Observe a seguinte rotina PHP que faz uma listagem dos nodos da estrutura abstracta após carregar o documento logs.xml.

<?php
$xmlDoc = new DOMDocument();
$xmlDoc->load("logs.xml");

$x = $xmlDoc->documentElement;
foreach ($x->childNodes as $item)
  {
  print $item->nodeName . " = " . $item->nodeValue . "<br />";
  }
?>

Notas:

  • Cria-se um objecto DOMDocument;
  • Carrega-se neste objecto o documento XML: load;
  • Seleciona-se o elemento principal: $x = $xmlDoc->documentElement;
  • Com a instrução cíclica foreach percorre-se um a um cada filho do elemento principal que corresponde a um log;
  • Usa-se o método nodeName para listar o nome do nodo;
  • Usa-se o método nodeValue para listar o conteúdo do nodo.

Se aplicarmos esta script ao nosso registo de logs obtemos o seguinte resultado:

#text =
log = MMM 1234-11-02 jcr Criação
#text =
log = PPPP 1956 maria remoção
#text =

Como se pode ver pelo exemplo, vai ser preciso "fintar" os nodos #text extra que aparecem no documento.

Podemos então implementar a listagem da seguinte forma:

<?php
$logs = new DOMDocument(); 
  $logs->load('logs.xml');
  
  print
  "<html>
    <head>
      <title>Listagem de Logs</title>
    </head>
    <body>
      <table id=\"logs\" border=\"1\">
        <tr>
          <th>Tipo</th><th>Data</th><th>Utilizador</th><th>Operação</th>
        </tr>
  ";
  
  $loglist = $logs->getElementsByTagName('log');
  foreach ($loglist as $l) 
    {
      print "<tr>";
      foreach($l->childNodes as $log) 
        {
          if($log->nodeName != "#text")
            {
              print "<td>".$log->nodeValue."</td>";
            }
        }
       print "</tr>";
    }
  print "</table>";
  print "[<a href=\"log-manager.html\">Voltar ao incio</a>]
        </body>
      </html>";
?>

3. PHP e XSL (com o PHP a processar o XSL)

Nesta e na opção seguinte vai-se utilizar uma stylesheet XSL para transformar o documento XML numa tabela em HTML. O que vai diferir é a maneira como iremos aplicar a transformação ao documento XML.

A stylesheet XSL tem o seguinte aspeto.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
    exclude-result-prefixes="xs xd"
    version="2.0">
    
    <xsl:output method="html" indent="yes" encoding="ISO-8859-1"/>
    
    <xsl:template match="/">
        <table border="1">
            <tr><th>Tipo</th><th>Data</th><th>Utilizador</th><th>Operação</th></tr>
            <xsl:apply-templates select="logs/*"/>
        </table>
    </xsl:template>
    
    <xsl:template match="log">
        <tr>
            <td>
                <xsl:value-of select="tipo"/>
            </td>
            <td>
                <xsl:value-of select="data"/>
            </td>
            <td>
                <xsl:value-of select="utilizador"/>
            </td>
            <td>
                <xsl:value-of select="operacao"/>
            </td>
        </tr>
    </xsl:template>
</xsl:stylesheet>

A stylesheet apresentada é muito simples porque o objetivo aqui é mostrar como as coisas se fazem, não havendo grandes preocupações com o resultado final que poderá ser apurado individualmente nas aplicações a serem desenvolvidas.

Para aplicar esta stylesheet na transformação do documento XML em PHP é preciso carregar ambos em memória e utilizar o processador do XSL que vem com o PHP para fazer a transformação. A script seguinte mostra uma possível maneira de o fazer.

<?php
# LOAD XML FILE
$logs = new DOMDocument(); 
$logs->load('logs.xml');

# START XSLT
$xslt = new XSLTProcessor();
$XSL = new DOMDocument();
$XSL->load( 'logs.xsl', LIBXML_NOCDATA);
$xslt->importStylesheet( $XSL );
#PRINT
print $xslt->transformToXML( $logs );
?>

Notas:

  • Cria-se um DOMDocument com a representação abstrata do documento XML (linhas 3 e 4);
  • Cria-se outro DOMDocument com a stylesheet (linhas 8 e 9);
  • Cria-se uma instância do processador de XSL (linha 7);
  • Carrega-se o objeto com a stylesheet no processador (linha 10);
  • Ordena-se a transformação do documento com a stylesheet (linha 12).

4. XSL

Nesta alternativa, depois de termos a stylesheet XSL basta associarmos esta ao documento XML com a seguinte instrução de processamento:

<?xml-stylesheet type="text/xsl" href="logs.xsl"?>

O documento XML ficará com a seguinte estrutura:

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="logs.xsl"?>
<logs>
    ...
</logs>

Na página de administração coloca-se agora o link do List logs como sendo o documento XML. O browser ao carregar o documento vai detetar a indicação da stylesheet e irá usar o seu processador para transformar o documento antes de o apresentar.

Se optarmos por esta alternativa temos de ter consciência de que o documento XML terá de ser criado sempre com a associação à stylesheet. No caso desta aplição, isto significaria que teríamos de acrescentar a inclusão daquela instrução de processamento na operação de Reset como se mostra a seguir.

<?php
$dom = new DOMDocument('1.0', 'ISO-8859-1');

$xslt = $dom->createProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="logs.xsl"');
$dom->appendChild($xslt);

$element = $dom->createElement('logs');
$dom->appendChild($element);

$dom->save('logs.xml');

echo "<p>Reset feito com sucesso.</p>";
echo "<a href='log-manager.html'>Voltar ao log-manager.</a>";
?>

Acrescentaram-se as linhas 4 e 5.


Fica aqui terminado este tutorial onde se pretenderam demonstrar as funcionalidades básicas do processamento de XML com PHP. Este conjunto de ideias e scripts pode ser facilmente adaptado para trabalhar com muitas outras aplicações.