Boa hora do dia!
O escopo do formato XML é bastante extenso. Juntamente com CSV, JSON e outros, XML é uma das maneiras mais comuns de apresentar dados para troca entre diferentes serviços, programas e sites. Como exemplo, podemos citar o formato CommerceML para trocar mercadorias e pedidos entre 1C "Trade Management" e uma loja online.
Portanto, quase todo mundo que cria serviços da Web de tempos em tempos precisa lidar com a necessidade de analisar documentos XML. No meu post, proponho um dos métodos sobre como fazer isso da maneira mais clara e transparente possível, usando o XMLReader.
O PHP oferece várias maneiras de trabalhar com o formato XML. Sem entrar em detalhes, direi que, em princípio, eles podem ser divididos em dois grupos:
- Carregando o documento XML inteiro na memória como um objeto e trabalhando com este objeto
- Leitura passo a passo de uma sequência XML no nível de tags, atributos e conteúdo de texto
A primeira maneira é mais intuitiva, o código parece mais transparente. Este método funciona bem para arquivos pequenos.
O segundo método é uma abordagem de nível inferior, que nos oferece várias vantagens e, ao mesmo tempo, ofusca a vida. Vamos insistir nisso com mais detalhes. Prós:
- Velocidade de análise. Você pode ler mais detalhadamente aqui .
- Consumo de menos RAM. Não armazenamos todos os dados na forma de um objeto que é muito caro na memória.
Mas: sacrificamos a legibilidade do código. Se o objetivo de nossa análise é, por exemplo, calcular a soma dos valores em determinados lugares dentro do XML com uma estrutura simples, não há problemas.
No entanto, se a estrutura do arquivo é complexa, mesmo trabalhando com dados depende do caminho completo para esses dados, e o resultado deve incluir muitos parâmetros, então chegamos a um código bastante caótico.
Então, escrevi uma aula que posteriormente facilitou minha vida. Seu uso simplifica a escrita de regras e melhora muito a legibilidade dos programas, seu tamanho se torna muitas vezes menor e o código é mais bonito.
A idéia principal é a seguinte: armazenaremos o esquema do nosso XML e como trabalhar com ele em uma única matriz que repete a hierarquia apenas das tags necessárias. Além disso, para qualquer uma das tags na mesma matriz, poderemos registrar as funções necessárias para manipular a abertura de uma tag, fechá-la, ler atributos ou ler texto ou todos juntos. Assim, armazenamos nossa estrutura XML e manipuladores em um único local. Uma simples olhada em nossa estrutura de processamento será suficiente para entender o que estamos fazendo com nosso arquivo XML. Farei uma reserva de que, em tarefas simples (como nos exemplos abaixo), a vantagem na legibilidade é pequena, mas será óbvio ao trabalhar com arquivos de uma estrutura relativamente complexa - por exemplo, um formato de troca com 1C.
Agora os detalhes. Aqui está a nossa classe:
Versão de depuração (com o parâmetro $ debug):
Classe XMLReaderStruct - clique para expandirclass XMLReaderStruct extends XMLReader { public function xmlStruct($xml, $structure, $encoding = null, $options = 0, $debug = false) { $this->xml($xml, $encoding, $options); $stack = array(); $node = &$structure; $skipToDepth = false; while ($this->read()) { switch ($this->nodeType) { case self::ELEMENT: if ($skipToDepth === false) {
Versão de lançamento (sem o parâmetro $ debug e comentários):
Classe XMLReaderStruct - clique para expandir class XMLReaderStruct extends XMLReader { public function xmlStruct($xml, $structure, $encoding = null, $options = 0) { $this->xml($xml, $encoding, $options); $stack = array(); $node = &$structure; $skipToDepth = false; while ($this->read()) { switch ($this->nodeType) { case self::ELEMENT: if ($skipToDepth === false) { if (isset($node[$this->name])) { $stack[$this->depth] = &$node; $node = &$node[$this->name]; if (isset($node["__open"]) && (false === $node["__open"]())) return false; if (isset($node["__attrs"])) { $attrs = array(); if ($this->hasAttributes) while ($this->moveToNextAttribute()) $attrs[$this->name] = $this->value; if (false === $node["__attrs"]($attrs)) return false; } if ($this->isEmptyElement) { if (isset($node["__close"]) && (false === $node["__close"]())) return false; $node = &$stack[$this->depth]; } } else { $skipToDepth = $this->depth; } } break; case self::TEXT: if ($skipToDepth === false) { if (isset($node["__text"]) && (false === $node["__text"]($this->value))) return false; } break; case self::END_ELEMENT: if ($skipToDepth === false) { if (isset($node["__close"]) && (false === $node["__close"]())) return false; $node = &$stack[$this->depth]; } elseif ($this->depth === $skipToDepth) { $skipToDepth = false; } break; } } return true; } }
Como você pode ver, nossa classe estende os recursos da classe XMLReader padrão, à qual adicionamos um método:
xmlStruct($xml, $structure, $encoding = null, $options = 0, $debug = false)
Parâmetros:
- $ xml, $ encoding, $ options : como em XMLReader :: xml ()
- $ structure : uma matriz associativa que descreve completamente como devemos trabalhar com nosso arquivo. Entende-se que sua aparência é conhecida antecipadamente e sabemos exatamente quais tags e o que devemos fazer.
- $ debug : (somente para a versão Debug) se deve ou não gerar informações de depuração (por padrão - desativado).
O argumento
$ structure .
Essa é uma matriz associativa, cuja estrutura repete a hierarquia de tags do arquivo XML e, se necessário, cada um dos elementos da estrutura pode ter funções de manipulador (definidas como campos com a chave correspondente):
- "__open" - função ao abrir uma tag - function ()
- "__attrs" - função para processar atributos de tags (se houver) - função ($ assocArray)
- "__text" - função na presença do valor de texto da tag - função ($ text)
- "__close" - função ao fechar uma tag - function ()
Se algum dos manipuladores retornar false, a análise será abortada e a função xmlStruct () retornará false. Os exemplos a seguir mostram como construir o argumento $ structure:
Exemplo 1 mostrando a ordem na qual os manipuladores são chamadosSuponha que haja um arquivo XML:
<?xml version="1.0" encoding="UTF-8"?> <root> <a attr_1="123" attr_2="456">Abc</a> <b> <x>This is node x inside b</x> </b> <c></c> <d> <x>This is node x inside d</x> </d> <e></e> </root>
$structure = array( 'root' => array( 'a' => array( "__attrs" => function($array) { echo "ATTR ARRAY IS ",json_encode($array),"\r\n"; }, "__text" => function($text) use (&$a) { echo "TEXT a {$text}\r\n"; } ), 'b' => array( "__open" => function() { echo "OPEN b\r\n"; }, "__close" => function() { echo "CLOSE b\r\n"; }, 'x' => array( "__open" => function() { echo "OPEN x\r\n"; }, "__text" => function($text) { echo "TEXT x {$text}\r\n"; }, "__close" => function() { echo "CLOSE x\r\n"; } ) ) ) ); $xmlReaderStruct->xmlStruct($xml, $structure);
Os manipuladores serão chamados (em ordem cronológica):
atributos raiz-> a
raiz do campo de texto-> a
raiz de abertura-> b
raiz de abertura-> b-> x
raiz de texto-> b-> x
raiz de fechamento-> b-> x
raiz de fechamento-> b
Os campos restantes não serão processados (incluindo raiz-> d-> x será ignorado, porque está fora da estrutura)
Exemplo 2 ilustrando uma tarefa prática simplesSuponha que haja um arquivo XML:
<?xml version="1.0" encoding="UTF-8"?> <shop> <record> <id>0</id> <type>product</type> <name>Some product name. ID:0</name> <qty>0</qty> <price>0</price> </record> <record> <id>1</id> <type>service</type> <name>Some product name. ID:1</name> <qty>1</qty> <price>15</price> </record> <record> <id>2</id> <type>product</type> <name>Some product name. ID:2</name> <qty>2</qty> <price>30</price> </record> <record> <id>3</id> <type>service</type> <name>Some product name. ID:3</name> <qty>3</qty> <price>45</price> </record> <record> <id>4</id> <type>product</type> <name>Some product name. ID:4</name> <qty>4</qty> <price>60</price> </record> <record> <id>5</id> <type>service</type> <name>Some product name. ID:5</name> <qty>5</qty> <price>75</price> </record> </shop>
Este é um cheque administrativo com bens e serviços.
Cada registro de verificação contém o identificador, tipo (produto "produto" ou serviço "serviço"), nome, quantidade e preço.
Tarefa: calcule o valor da verificação, mas separadamente para bens e serviços.
include_once "xmlreaderstruct.class.php"; $x = new XMLReaderStruct(); $productsSum = 0; $servicesSum = 0; $structure = array( 'shop' => array( 'record' => array( 'type' => array( "__text" => function($text) use (&$currentRecord) { $currentRecord['isService'] = $text === 'service'; } ), 'qty' => array( "__text" => function($text) use (&$currentRecord) { $currentRecord['qty'] = (int)$text; } ), 'price' => array( "__text" => function($text) use (&$currentRecord) { $currentRecord['price'] = (int)$text; } ), '__open' => function() use (&$currentRecord) { $currentRecord = array(); }, '__close' => function() use (&$currentRecord, &$productsSum, &$servicesSum) { $money = $currentRecord['qty'] * $currentRecord['price']; if ($currentRecord['isService']) $servicesSum += $money; else $productsSum += $money; } ) ) ); $x->xmlStruct(file_get_contents('example.xml'), $structure); echo 'Overal products price: ', $productsSum, ', Overal services price: ', $servicesSum;