SimpleXML para manipular XML pelo PHP

Artigo que apresenta o SimpleXML, um recurso do PHP para leitura e manipulação de arquivos XML de forma fácil, ágil e rápida.

Introdução

SimpleXML é uma extensão do PHP que permite ler e manipular XML de maneira bastante simples, embora com várias restrições. A principal restrição é que um elemento (tag) só pode conter texto ou outros elementos, nunca texto junto com outros elementos, como ocorre no HTML.

Exemplo de XML que não é corretamente manipulado por SimpleXML:

<?xml version="1.0" ?>
<teste>
  <frase>A linguagem <negrito>PHP<negrito> é legal.</frase>
</teste>

SimpleXML foi feito para manipular XML que representam pacotes de dados sobre determinado registro, diferente do HTML, que é utilizado para formatar um documento, ou seja, são propósitos distintos.

A extensão provê duas funções principais: simplexml_load_string e simplexml_load_file. A primeira carrega um XML a partir de uma string e a segunda carrega um XML a partir do nome de um arquivo. Ambas devolvem um objeto da classe SimpleXMLElement, que é a classe principal da extensão.


Leitura usando SimpleXML

Em geral, a classe SimpleXMLElement representa um elemento dentro do XML, mas também pode representar uma coleção de elementos. Supondo o XML seguinte:

<?xml version="1.0" ?>
<livro>
  <nome>Exemplos de XML</nome>
  <data formato="w3c">2011-02-28</data>
  <edicao>1</edicao>
  <autor>
    <nome>Rubens Takiguti Ribeiro</nome>
    <site>http://rubsphp.blogspot.com</site>
  </autor>
  <comentarios>
    <comentario>
      <autor>Alguém</autor>
      <texto>Gostei muito do livro</texto>
    </comentario>
    <comentario>
      <autor>Outro</autor>
      <texto>Poderia ter mais figuras.</texto>
    </comentario>
  </comentarios>
</livro>

Para obter os dados do livro e do autor, basta acessá-los pela estrutura hierárquica do XML:

$xml = simplexml_load_file('exemplo.xml');

echo strval($xml->nome); // Obtem o nome do livro
echo strval($xml->data); // Obtem a data do livro
echo strval($xml->data['formato']); // Obtem o atributo "formato" da data do livro

echo strval($xml->autor->nome); // Obtem o nome do autor do livro
echo strval($xml->autor->site); // Obtem o endereco do site do autor do livro

Porém, o número de comentários do livro é variado, logo, é preciso usar uma estrutura de repetição:

// Obtendo o nome do autor do primeiro comentario da lista de comentarios
echo strval($xml->comentarios->comentario->autor);

// Forma alternativa para obter autor do primeiro comentario
echo strval($xml->comentarios[0]->comentario[0]->autor[0]);

// Obtendo o nome do autor do segundo comentario
echo strval($xml->comentarios[0]->comentario[1]->autor[0]);

// Percorrendo os elementos "comentario" que estao dentro do primeiro elemento "comentarios"
foreach ($xml->comentarios->comentario as $comentario) {
    echo strval($comentario->autor); // Obtem o nome do autor do comentario
    echo strval($comentario->texto); // Obtem o texto do comentario
}

Observe que a expressão $xml->comentarios->comentario expressa tanto o primeiro elemento "comentario" encontrado dentro do primeiro elemento "comentarios" quanto o array de comentários que estão dentro do elemento "comentarios". O comportamento do objeto depende do contexto em que se encontra. A função strval é utilizada para extrair o valor do elemento na forma de string, caso contrário, seria devolvido outro objeto da classe SimpleXMLElement.

De fato, se uma instrução echo é aplicada a um objeto da classe SimpleXMLElement, é exibido o conteúdo textual do elemento. Porém, para realizar comparações, é importante converter este valor para string e, opcionalmente, para outros tipos depois. Veja o exemplo:

var_dump($xml->nome == $xml->data); // retorna true
var_dump(strval($xml->nome) == strval($xml->data)); // retorna false

No primeiro caso estão sendo comparados dois objetos e no segundo estão sendo comparadas duas strings.

Além disso, a leitura pode ser feita usando XPath através do método xpath, que devolve um array de elementos encontrados.

Finalmente, existem os métodos children e attributes. O primeiro obtem todos os elementos filhos de determinado elemento, e o segundo obtém todos os atributos de determinado elemento. Para obter o nome do elemento ou atributo percorrido, basta usar o método getName.


Escrita usando SimpleXML

A escrita em um documento precisa que o documento seja criado, pelo menos, com uma tag raiz. Em seguida, é possível usar os métodos addChild e addAttribute, respectivamente, para adicionar elementos filhos ou atributos ao elemento. Exemplo:

// Criando um elemento raiz vazio
$xml = new SimpleXMLElement('<?xml version="1.0" ?><livro />');

// Adicionando elementos "nome" e "data"
$xml->addChild('nome', 'Nome do livro');
$elemento_data = $xml->addChild('data', '2011-02-28');

// Incluindo um atributo "formato" no elemento "data"
$elemento_data->addAttribute('formato', 'w3c');

// Exibindo o XML na forma textual
echo $xml->asXML();

Uma forma alternativa, é acessar os elementos e atributos como se eles já existissem. Os elementos filhos são acessados como se fossem atributos do objeto, enquanto os atributos são acessados como se fossem posições de um array (utilizando o operador colchetes):

// Criando um elemento raiz vazio
$xml = new SimpleXMLElement('<?xml version="1.0" ?><livro />');

// Adicionando elementos "nome" e "data"
$xml->nome = 'Nome do livro';
$xml->data = '2011-02-28';

// Incluindo um atributo "formato" no elemento "data"
$xml->data['formato'] = 'w3c';

// Exibindo o XML na forma textual
echo $xml->asXML();

Para remover um elemento, basta aplicar a função unset sobre ele. Por exemplo, para remover o elemento "data":

unset($xml->data);

Salvando o arquivo XML

Ao manipular um objeto SimpleXML, estas alterações ficam apenas na memória RAM. Para persistir estas informações em um arquivo em disco, basta usar o método asXML passando como parâmetro o nome do arquivo que deseja gravar. Neste caso, ao invés de devolver uma string com o conteúdo do arquivo, a função vai jogar o conteúdo em um arquivo e devolver true ou false (indicando sucesso ou não). Exemplo:

// Gravando o conteudo em um arquivo XML
if ($xml->asXML('teste.xml')) {
    echo 'gravou arquivo XML';
}

Observação: o método saveXML é um apelido do método asXML e, neste caso, torna o código mais legível. Afinal, se estamos salvando um arquivo, o método saveXML é auto-explicativo.

// Gravando o conteudo em um arquivo XML
if ($xml->saveXML('teste.xml')) {
    echo 'gravou arquivo XML';
}

Identificando problemas com XML

Caso seu XML tenha algum problema, você pode identificá-los utilizando as funções libxml_use_internal_errors em conjunto com libxml_get_errors, conforme exemplo:

libxml_use_internal_errors(true);
$xml = simplexml_load_string("<?xml version='1.0'><exemplo><quebrado></exemplo>");
if (!$xml) {
    foreach(libxml_get_errors() as $erro) {
        echo $erro->message . "\n";
    }
}

Observações

É possível importar um documento (ou elemento) estruturado através da extensão DOM para a forma mais simples do SimpleXML através da função simplexml_import_dom.

Para ler elementos que possuem "-" (sinal de menos) no nome, basta utilizar a notação:

$xml->{'nome-com-sinal-de-menos'}->nome;

Caso o XML de uma string ou de um arquivo esteja com falhas, ele não será lido corretamente por simplexml_load_string ou simplexml_load_file, mas os erros podem ser recuperados através da função libxml_get_errors, que faz parte da extensão libxml, vem instalada por padrão no PHP (embora possa ser removida).

Com SimpleXML não é possível manipular diretamente comentários, blocos CDATA, o DOCTYPE do documento, entre outras coisas. Caso seja necessário um nível mais aprofundado de manipulação, pode-se usar a extensão DOM, que é mais completa e robusta.

40 comentários

Anônimo disse...

Muito obrigado pelo seu artigo.

No meu arquivo haviam 2 nós comentários e somente conseguia ler o primeiro.

Quando vi isso:

$xml->comentarios[0]->comentario[0]->autor[0]
$xml->comentarios[0]->comentario[1]->autor[0]

percebi na hora que precisava gerar:

$xml->comentarios[1]->comentario[0]->autor[0]
$xml->comentarios[1]->comentario[1]->autor[0]

Seria legal incluir no seu post, menção a se fazer um loop:

for($i=0; $i < count($xml->comentarios); $i++) {
echo strval($xml->comentario[$i]->autor);

Assim gera automaticamente.

Valeu.

rubS (autor do blog) disse...

Olá, Anônimo

Na verdade deixei um exemplo de como percorrer vários comentários, mas usando o "foreach". Só que, pelo exemplo, os elementos "comentario" estão dentro de um elemento chamado "comentarios". Veja o último exemplo da sessão "Leitura usando SimpleXML".

Até

Giovanne Afonso disse...

Muito bom o post! Parabéns, deu pra entender tudo direitinho, estou começando a aprender PHP agora e estou estudando pra caramba, acabei de aprender mais umas coisas legais aqui ^^ OBRIGADO!

Marcelo disse...

Estou tentando ler um XML, mas me retorna um erro por conta dos caracteres especiais. Tentei incluir no header o enconding="UTF-8", mas ainda retorna o seguinte erro:
Warning: simplexml_load_file() [function.simplexml-load-file]: publica/18/600097_05062012_8145.xml:171: parser error : Input is not proper UTF-8, indicate encoding ! Bytes: 0xC7 0x41 0x53 0x3C in /home/mocautomot1/public_html/restrito/importa_autoplus.php on line 23

Marcelo disse...

Estou tentando ler um XML, mas me retorna um erro por conta dos caracteres especiais. Tentei incluir no header o enconding="UTF-8", mas ainda retorna o seguinte erro:
Warning: simplexml_load_file() [function.simplexml-load-file]: publica/18/600097_05062012_8145.xml:171: parser error : Input is not proper UTF-8, indicate encoding ! Bytes: 0xC7 0x41 0x53 0x3C in /home/mocautomot1/public_html/restrito/importa_autoplus.php on line 23

rubS (autor do blog) disse...

Marcelo, veja se este post ajuda:
http://rubsphp.blogspot.com.br/2012/06/cuidados-com-geracao-de-xml.html

Guto Luís disse...

Bom dia,

Muito bom o seu post, eu já havia lido alguns e o seu é que está mais claro.
Porém, estou com uma pequena dúvida em relação a comparação de valores.

Você poderia abordar melhor? ou até mesmo me indicar um post que me explique bem?

Pois eu estou fazendo um programa que deve fazer uma consulta em um site, e a resposta vem em formato de xml, tenho que buscar informações neste arquivo e tenho que comparar alguns.

Ex:

Aqui tenho um trecho do arquivo que recebo na resposta. Nele eu tenho que pegar os dados:
-CompanyName
-CompanyNumber
-DataSet

Esta parte tudo bem, porém em alguns casos eu terei também o item: -SearchMatch
e neste caso preciso pegar o valor e verificar o que tem nele, se for EXACT terei que fazer algo.



4 BUSINESS LIMITED
03782650
LIVE


EXACT


4 BUSINESS CALLS LTD
04822271
LIVE





o código não consigo postar direito, tem que visualizar o código da página para vê-lo.


Obrigado

Rubens Takiguti Ribeiro (autor do blog) disse...

Guto, a questão das comparações é relativamente simples: quando você obtem o valor de um atributo ou o conteúdo de uma tag, ela vem na forma de objeto. Você até consegue imprimir pois o objeto implementa o método __toString, que retorna o valor do objeto numa notação de string.

Para fazer comparações, basta fazer o casting para string manualmente e comparar com o valor desejado. Por exemplo, se você você quer comparar o conteúdo de uma tag:

if (strval($xml->elemento) == 'EXACT') {

Ou então, se for o conteúdo de um atributo:

if (strval($xml->elemento['atributo']) == 'EXACT') {

Acho que é isso.

Anônimo disse...

Gostei muito do seu artigo. Será que com isso consigo manipular uma imagem com extensão .svg, visto que ela é um xml. Preciso disto para construir um mapa dinâmico com esta imagem, manipulando alguns valores que estão em alguns nodos da svg.

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Anônimo

Creio que o SimpleXML sirva perfeitamente para manipular um SVG, visto que é um XML simples.

Talvez, você vai precisar de alguns recursos de XPath para encontrar algumas tags para sem manipuladas. Não citei no artigo, mas é um mecanismo para consultar elementos do XML.

Se ele não for suficiente, recomendo que utilize o DOM.
http://www.php.net/manual/en/book.dom.php

Ricardo Peres disse...

Olá, eu sou iniciante ainda...
eu tenho um arquivo sitemap.xml.. como faço pra gravar um novo campo nele?
acho que não entendi muito bem na parte da gravação... como grava no arquivo, pelo que eu vi aqui na publicação ele só gera os dados do xml mas não grava num arquivo né?

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Ricardo Peres
É isso mesmo que você disse. Ao carregar um arquivo XML para um objeto simpleXML, ele fica apenas em memória e todas as operações neste objeto ficam apenas em memória. Para gerar um novo arquivo XML ou sobrescrever algum existente, é preciso chamar o método asXML passando como parâmetro o nome do arquivo. Esqueci de colocar issso no artigo, então vou atualizá-lo.

Anônimo disse...

Rubens, bom dia tudo tranquilo ? tenho a seguinte dúvida, possuo um xml com a seguinte estrutura:





Saberia me informar como faço para ler os valores dos campos nome, idade, cidade e UF neste estilo de xml ? Tentei de várias maneiras com o SimpleXML mas não obtive exito, li no seu ultimo paragrafo algo a respeito e que talves o SimpleXML não atenda a minha necessidade, poderia me auxiliar ?

Anônimo disse...

estrutura de exemplo:

< xml>
< pessoa nome="Pedro" idade="30" cidade="Curitiba" UF="PR" >
< /xml >

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Anônimo
Como "nome", "idade" e "cidade" são atributos de um elemento do XML, você consegue obter estes valores de forma similar a de um array associativo:

$xml = simplexml_load_file('arquivo.xml');
echo strval($xml->pessoa['nome']);
echo strval($xml->pessoa['idade']);
echo strval($xml->pessoa['cidade']);
echo strval($xml->pessoa['UF']);

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Silvio
Não apareceu sua mensagem corretamente. Provavelmente você colocou sinal de maior e menor, que não são permitidos nos comentários. Mande novamente sua pergunta sem usar estes símbolos, ok?

Anônimo disse...

Otimo artigo... me ajudou bastante, mas estou com problemas em pegar uma id no xml...

Quero pegar o conteudo dessa url -->

< links>
< link url=" www.site....com br ......" type="offer"/ >
< /links>

Tentei fazer assim mas nao deu certo:

children() as $offer){

$a = "Link 3: " .(string) $offer->links->link->Attributes()->url."
";


echo $a;
echo "< hr >

";
}
?>

Alguém poderia me ajudar????? Obrigado

Postei como anonimo pq não tenho conta ativa... mas me chamo André.

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, André

Assim que você abre um XML e joga num objeto, o objeto já representa o próprio elemento raiz. Portanto, não é necessário fazer $offer -> links, pois "$offer" já representa a tag < links >.

Veja alguns exemplos que obtem o campo url:

// Obtem explicitamente o primeiro < link > dentro do XML
// e retorna o atributo "url"
var_dump(strval($xml->link[0]['url']));

// Obtem implicitamente o primeiro < link > dentro do XML,
// depois obtem os atributos e retorna o atributo "url"
var_dump(strval($xml->link->attributes()->url));

Anônimo disse...

Dei um print_r em um arquivo XML e ficou da seguinte forma:

SimpleXMLElement Object ( [numero] => Array ( [0] => 01 [1] => 07 [2] => 30 [3] => 43 [4] => 44 [5] => 54 ) )

Como faço para jogar na tela cada elemento?

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Anônimo

Para mostrar na tela cada elemento, você pode usar o método "asXML", que devolve todo conteúdo do objeto na forma de XML.

Outra possibilidade é percorrer cada elemento, conforme mostrado em "Leitura usando SimpleXML".

Pedro Bráulio Amaral disse...

Olá Rubens, muito bom artigo. Estou iniciando.... e preciso de ajuda.
Tenho o seguinte XML - removi as tags - onde preciso de ler os dados e salvar num DB MySQL:
cartelas
cartela nome="Colecao 15" id="30"
linha id="1"
cor posicao="1" ref="VM321" rgb="D7342B"
cor posicao="2" ref="VM153" rgb="c11236"
cor posicao="3" ref="VM449" rgb="b41e33"
cor posicao="4" ref="BD315" rgb="70113E"
cor posicao="5" ref="BD005" rgb="693F46"
cor posicao="6" ref="BD265" rgb="573138"
/linha
linha id="2"
cor posicao="1" ref="RX719" rgb="973485"
cor posicao="2" ref="RS216" rgb="b82853"
cor posicao="3" ref="RS057" rgb="DB5A92"
cor posicao="4" ref="RS249" rgb="C9256C"
cor posicao="5" ref="RS639" rgb="D5304A"
/linha
/cartela
/cartelas
.... e por aí vai, sendo UMA CARTELAS, onde temos diversas CARTELA, que possui diversas LINHA. Eu até consegui ler e salvar no DB, mas está salvando com dados incorretos.... não correspondem a estrutura do XML
Segue trecho do PHP:
.....
$filename = ($_UP['pasta'] . $nome_final);
$xmlstr = file_get_contents($filename);

// Instancia a classe, e carrega o XML
$dom = new domDocument();
$dom->loadXML($xmlstr);
$xml = simplexml_import_dom($dom);
...
foreach($xml->cartela as $cartela)
{
$id_cartela = $cartela[0]->attributes()->id;
$nome_cartela = $cartela[0]->attributes()->nome;

foreach($xml->cartela->linha as $linha)
{
$id_linha = $linha[0]->attributes()->id;

foreach($xml->cartela->linha->cor as $cores)
{
$posicao_cor = $cores[0]->attributes()->posicao;
$ref_cor = $cores[0]->attributes()->ref;
$rgb_cor = $cores[0]->attributes()->rgb;
$query = mysql_query("INSERT INTO cartela (cartela_id, nome_cartela, linha_id, cor_posicao, referencia, cor_rgb, ultima_alteracao)
VALUES
('$id_cartela', '$nome_cartela', '$id_linha', '$posicao_cor','$ref_cor','$rgb_cor', now())") or die("Erro: " . mysql_error());
}
}
}
..............
Agradeço se puder me ajudar...
Grato.

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Pedro

Ao percorrer os foreaches internos, você não precisa pegar o item desejado a partir da referência da variável $xml. Basta pegar a partir da a variável que você já está percorrendo no foreach superior. Além disso, para pegar os atributos, você pode usar o operador colchetes, ao invés de chamar o método attributes. Fica mais simples.

Veja uma sugestão de melhoria:
http://pastebin.com/qHLLvJJs

Pedro Bráulio Amaral disse...

Nossa... muito bom Rubens. Grato MESMO.. Tão simples né?
.... simplesmente peguei a variável imediatamente superior.. Funcionou NA HORA...
....
foreach($xml->cartela as $cartela)
{
$id_cartela = $cartela[0]->attributes()->id;
$nome_cartela = $cartela[0]->attributes()->nome;

foreach($cartela->linha as $linha)
{
$id_linha = $linha[0]->attributes()->id;

foreach($linha->cor as $cores)
{
$posicao_cor = $cores[0]->attributes()->posicao;
$ref_cor = $cores[0]->attributes()->ref;
$rgb_cor = $cores[0]->attributes()->rgb;

// recarrega os dados
$query = mysql_query("INSERT INTO cartela (cartela_id, nome_cartela, linha_id, cor_posicao, referencia, cor_rgb, ultima_alteracao) VALUES ('$id_cartela', '$nome_cartela', '$id_linha', '$posicao_cor','$ref_cor','$rgb_cor', now())") or die("Erro: " . mysql_error());
}

}
}
.....................
Só mais uma coisa. Esse arq. XML possui diversas tags COR duplicadas... redundantes nas CARTELA. Tentei, mas não consegui. Teria alguma forma de identificar. selecionar e inserir no DB somente uma ocorrência dos registros COR?
.. de qualquer forma, grato novamente, show de bola sua simplicidade.. quanto mais simples, MELHOR.

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Pedro

Para não obter cores repetidas em uma cartela, uma sugestão é criar um array para guardar as cores que já foram inseridas e, antes de inserir uma nova, checar neste array se ela já foi inserida.

http://pastebin.com/dYQx3Xey

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Diego
Acredito que a troca de & por & seja apenas na resposta, que está formatada como HTML e utiliza entities para representar caracteres especiais. Em todo caso, deixo a dica de utilizar http_build_query para montar a query string:
http://pastebin.com/fJzJ2hGA

Você já experimentou acessar a URL que gera o XML diretamente do navegador para ver se ela retorna o XML corretamente?

Passa a mensagem de erro que aí posso ajudar mais fácil.

Até

Douglas Barreto disse...

olá Rubens,
Desculpe estar trazendo esse assunto de volta. estou com um problema aqui.tenho um programinha que gera uma tabela de horário através de um dataset em xml.
Agora eu preciso pegar os dados da tabela e carregar em uma página php. Acontece que quando eu carrego pelo simple_load_file() não retorna nenhum valor, acredito que seja pq os dados não estejam entre " dados " e sim ""...poderia me ajudar, não encontro em lugar algum um post que me ajude nesse assunto.

att,

Douglas Barreto

Douglas Barreto disse...

Olá eu denovo...hehe...
fiz então o exemplo do código que o dataset cria no xml...o meu problema é que ele cria uma tag ROWDATA e dentro dela varias tags ROW seguido do nome do campo da tabela, mas os dados ficam dentro das tags junto com os nomes dos campos...com isso eu não estou conseguindo pegar os dados dos campos de dentro das tags...existe algum modo de eu pegar esses dados?

Grato.

Douglas Barreto disse...

Rubens muito obrigado mesmo, me salvou...ja estava quase refazendo o programinha que gerava os horários por não conseguir pegar eles em php...estou aprendendo ainda por isso a dificuldade...mas vlw mesmo...abraço!

Rubens Takiguti Ribeiro (autor do blog) disse...

Eliézer, se o XML vem de uma API, você usa o simplexml_load_string, e trabalha no objeto retornado normalmente, conforme o artigo mostra. Caso o XML tenha problemas, também é mostrado no artigo como identificar o problema.