PHP XML Expat
Aprenda o parser XML Expat do PHP: modo orientado a eventos, streaming, callbacks, leitura de atributos, verificação de erros e exemplos práticos.
O PHP inclui um parser XML integrado baseado na biblioteca Expat. Ao contrário de parsers baseados em árvore, como o SimpleXML ou o DOM, o Expat é um parser orientado a eventos (estilo "SAX"): em vez de carregar o documento inteiro na memória como uma árvore, ele percorre o XML em streaming e aciona suas funções de callback ao encontrar cada tag de início, tag de fim e trecho de texto.
Esta página explica como o Expat funciona, quando você deve utilizá-lo e apresenta um exemplo completo e executável com tratamento de atributos e verificação de erros.
O que é o parser Expat?
O Expat é um parser XML rápido, leve e não validador escrito em C. A extensão PHP xml o envolve por meio da família de funções xml_parser_*. Suas características principais:
- Orientado a eventos — você registra callbacks de handler; o parser os chama conforme realiza a leitura.
- Streaming — o XML pode ser alimentado em partes (
xml_parse()pode ser chamado repetidamente), mantendo o uso de memória baixo mesmo para arquivos grandes. - Não validador — verifica se o documento está bem formado, mas não valida contra um DTD ou esquema.
Esse é o trade-off oposto ao de um parser em árvore. Um parser em árvore é conveniente (você pode navegar pelo documento inteiro com $xml->note->message), mas mantém tudo na memória. O Expat mantém a memória plana ao custo de forçar você a rastrear o estado conforme os eventos chegam.
Quando usar o Expat?
- Documentos grandes — feeds ou exportações com vários megabytes, nos quais construir uma árvore DOM completa seria custoso.
- Fontes em streaming — dados chegando por um socket ou em partes, onde não é possível aguardar o documento inteiro.
- Extrair e descartar — você precisa apenas de alguns valores e não quer o overhead de uma árvore.
Para documentos pequenos que você simplesmente quer ler, o SimpleXML requer muito menos código. Consulte Tipos de parsers XML do PHP para uma comparação lado a lado.
Configurando a extensão
A extensão xml é fornecida com o PHP e habilitada por padrão na maioria das compilações. Se as funções do parser estiverem ausentes, habilite-a no php.ini:
extension=xml ; Linux/macOS
extension=php_xml.dll ; WindowsVocê pode confirmar se está disponível em tempo de execução:
<?php
var_dump(function_exists('xml_parser_create')); // bool(true)Um exemplo completo com Expat
O exemplo abaixo faz o parse de uma string XML e imprime cada evento. Ele registra três handlers — para tags de início, tags de fim e texto — e finaliza com verificação de erros adequada. A leitura a partir de uma string o torna autocontido; a variante baseada em arquivo vem a seguir.
<?php
$xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<note importance="high">
<to>User</to>
<from>System</from>
<message>Hello from Expat</message>
</note>
XML;
// 1. Create the parser.
$parser = xml_parser_create();
// Keep tag names in their original case (Expat upper-cases them by default).
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
// 2. Register handlers for start/end tags and for character data.
xml_set_element_handler(
$parser,
function ($parser, $name, $attrs) {
echo "Start: $name";
foreach ($attrs as $key => $value) {
echo " [$key=$value]";
}
echo "\n";
},
function ($parser, $name) {
echo "End: $name\n";
}
);
xml_set_character_data_handler($parser, function ($parser, $data) {
$data = trim($data); // text between tags includes whitespace
if ($data !== '') {
echo "Text: $data\n";
}
});
// 3. Feed the document to the parser (true = this is the final chunk).
if (!xml_parse($parser, $xml, true)) {
$code = xml_get_error_code($parser);
die(sprintf(
"XML error: %s at line %d\n",
xml_error_string($code),
xml_get_current_line_number($parser)
));
}
// 4. Release the parser.
xml_parser_free($parser);Saída:
Start: note [importance=high]
Start: to
Text: User
End: to
Start: from
Text: System
End: from
Start: message
Text: Hello from Expat
End: message
End: noteComo funciona. xml_parser_create() cria o parser. xml_set_element_handler() (referência) registra os callbacks de tag de início e fim, e xml_set_character_data_handler() (referência) registra o callback de texto. xml_parse() então conduz todo o processo, chamando essas funções na ordem do documento. O handler de início recebe um array $attrs, portanto ler um atributo como importance é simplesmente $attrs['importance'].
Dois detalhes que costumam confundir as pessoas:
- Espaços em branco são dados de caractere. As quebras de linha e a indentação entre tags também acionam o handler de dados de caractere — por isso o exemplo chama
trim()e pula strings vazias. - Case folding. Por padrão, o Expat converte os nomes dos elementos para maiúsculas. Definir
XML_OPTION_CASE_FOLDINGcomofalsemantém a capitalização original, que é quase sempre o que você deseja.
Fazendo o parse de um arquivo em partes
A verdadeira força do Expat é o streaming. Leia o arquivo um bloco de cada vez e alimente cada bloco em xml_parse(), passando true apenas no último bloco (detectado com feof()):
<?php
$parser = xml_parser_create();
xml_set_element_handler($parser, 'startElement', 'endElement');
xml_set_character_data_handler($parser, 'characterData');
$fp = fopen('example.xml', 'r') or die("Could not open file\n");
while ($data = fread($fp, 4096)) {
if (!xml_parse($parser, $data, feof($fp))) {
$code = xml_get_error_code($parser);
die(sprintf(
"XML error: %s at line %d\n",
xml_error_string($code),
xml_get_current_line_number($parser)
));
}
}
fclose($fp);
xml_parser_free($parser);
function startElement($parser, $name, $attrs) { echo "Start: $name\n"; }
function endElement($parser, $name) { echo "End: $name\n"; }
function characterData($parser, $data) {
$data = trim($data);
if ($data !== '') echo "Text: $data\n";
}Como o documento nunca é totalmente mantido na memória, esse padrão lida com arquivos muito maiores do que o limite de memória. Os handlers podem ser nomes de funções simples (como aqui), closures ou callables [$object, 'method'] via xml_set_object().
Tratamento de erros
Sempre verifique o valor de retorno de xml_parse() — ele retorna false em um documento malformado. O código de erro de xml_get_error_code() pode ser convertido em uma mensagem legível com xml_error_string(), e você pode identificar a localização com xml_get_current_line_number() e xml_get_current_column_number(). Ignorar essa verificação significa que um feed corrompido falha silenciosamente.
Vantagens do Expat
- Baixo uso de memória — faz streaming do documento em vez de construir uma árvore, mantendo a memória plana independentemente do tamanho do arquivo.
- Rápido — o parser C subjacente é altamente otimizado.
- Multiplataforma — incluído com o PHP em todos os sistemas operacionais suportados.
- Controle granular — você decide exatamente o que fazer em cada evento, ignorando tudo que não precisa.
O trade-off: como não há árvore, você deve rastrear o contexto (em qual elemento está) por conta própria. Se esse controle de estado se tornar pesado, um parser em árvore como SimpleXML ou DOM é a escolha mais adequada.
Conclusão
O parser XML baseado em Expat oferece ao PHP uma forma rápida, eficiente em memória e orientada a eventos para ler XML. Registre handlers para os eventos de seu interesse, alimente o documento com xml_parse(), verifique erros e libere o parser ao concluir. Use-o para documentos grandes ou em streaming; para documentos pequenos, o SimpleXML costuma ser a escolha mais simples.