1. Livro: Pro PHP XML and Web Services
Apresentando o XML Canonical
A especificação XML da Canonical (http://www.w3.org/TR/xml-c14n) estabelece um método
para determinar se dois documentos são idênticos. O problema subjacente é que podem ser criados
documentos que signifiquem a mesma coisa e que tenham o mesmo conteúdo com
diferentes representações físicas. Isso dificulta determinar se os dois documentos são realmente
idênticos. Com o XML canônico, dois documentos com o mesmo forma canônica
são consideradas idênticas, mesmo que suas representações físicas não sejam idênticas.
Antes de prosseguir, você deve entender qual é a forma canônica. A especificação
define forma canônica de um documento para a representação física do documento
criado usando os seguintes métodos:
• O documento está codificado em UTF-8.
• Quebras de linha normalizam para #xA na entrada, antes da análise.
• Valores de atributos são normalizados, como se fosse um processador de validação.
• As referências de entidades analisadas e de caractere são substituídas.
• As seções CDATA são substituídas pelo conteúdo de seus caracteres.
• A declaração XML e a DTD são removidas.
• Elementos vazios são convertidos em pares de tag de início e fim.
• O espaço em branco fora do elemento do documento e dentro das tags de início e fim é
normalizado.
• Todo espaço em branco no conteúdo do personagem é retido (excluindo caracteres removidos
durante
normalização de alimentação de linha).
• Os delimitadores de valores de atributos são definidos entre aspas (aspas duplas).
• Caracteres especiais em valores de atributos e conteúdo de caracteres são substituídos por
caracteres
referências.
Declarações de espaço de nomes supérfluas são removidas de cada elemento.
• Os atributos padrão são adicionados a cada elemento.
• A ordem lexicográfica é imposta às declarações e atributos do namespace de cada
elemento.
Se você olhar através desta lista, você verá alguns itens que tornam as coisas difíceis quando
usando PHP para criar XML canônico. Você pode resolver muitos desses problemas usando o
analisador opções ao carregar um documento. Usando LIBXML_NOENT, LIBXML_DTDLOAD,
LIBXML_DTDATTR, e LIBXML_NOCDATA em combinação irá substituir referências de
entidades com seu conteúdo, carregar DTD para garantir que os IDs sejam manipulados, padronize
todos os atributos para que eles sejam fisicamente árvore e converta todos os CDATA em conteúdo
de texto. Isso deixa alguns itens que não são executados automaticamente.
Você poderia manipular o DTD de duas maneiras. Crie um novo documento e faça
uma cópia profunda ou importe o elemento do documento para o novo documento. Em seguida,
imprima o documento usando o elemento do documento como o contexto ou use XSL. Você
também pode lidar com
Declaração XML de duas maneiras. Usando o DOM, você pode imprimir o documento usando o
elemento documento como o contexto. Ou, usando XSL, supondo que você usou o analisador já
mencionado opções, você pode executar uma transformação no documento onde a folha de estilo
retorna elemento do documento para a árvore de resultados e omite a declaração XML se estiver
sendo serializado. Para exemplo:
2. <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
Se isso for usado para retornar um objeto DOMDocument, a declaração DTD e XML terá sido
removido ao serializar usando o elemento document como contexto ao serializar:
$root = $doc->documentElement;
print $doc->saveXML($root);
Nota: A serialização de um documento usando a API do DOM para remover a declaração DTD e
XML pode ser feito somente ao serializar para uma string. Serializar para um arquivo não suporta
um nó de conteúdo.
Modelo de dados
Se você decidir ler as especificações de canonização XML, você notará que tudo
é definido em termos de conjuntos de nós XPath, em que o conjunto de nós contém os nós a serem
convertidos em forma canônica. PHP não possui suporte nativo para canonização para XML,
portanto, usando Xpath depende de como você decide implementar alguma forma de canonização.
Baseado no Modelo de dados XPath, os tipos de nós que são significativos para criar a forma
canônica são raiz, nós de elemento, comentário, PI, texto, atributo e namespace.
Isso não significa que você precisa lidar com outros tipos de nós. Como você já viu, os atributos
precisam ser padronizados, as seções CDATA precisam ser convertidas em nós de texto e as
referências de entidades analisadas e de caractere precisam ser resolvidas. Você pode lidar com eles,
no entanto, enquanto o documento está sendo carregado:
$dom = new DOMDocument();
$dom->loadXML($xmlstring, LIBXML_NOENT | LIBXML_DTDLOAD | LIBXML_DTDATTR |
LIBXML_NOCDATA);
O documento resultante ainda pode conter nós irrelevantes, como uma declaração de tipo de
documento, mas você poderá descartá-los ao criar o formulário canônico.
A ordenação de nós também é importante ao criar a forma canônica. Felizmente, se estiver usando
Xpath, conjuntos de nós já devem estar na ordem do documento, mas para produzir a forma
canônica correta, você precisa lidar com mais alguns problemas:
• Os nós de espaço de nomes vêm antes dos nós de atributo.
• Os nós de namespace são classificados lexicograficamente com base em seus nomes locais, onde
um namespace padrão sempre viria primeiro na lista.
• Os nós de atributo também são classificados lexicograficamente, mas são classificados com base
em seus URIs de namespace e, em seguida, com base em seus nomes locais. Atributos não dentro de
um namespace tem um URI vazio e viria antes de qualquer atributo com namespace.
Isso evita outro desafio que você precisa lidar com a codificação. Se XSL está sendo
usado para criar a saída, o uso criativo de modelos pode ajudar você a conseguir isso.
Você também pode executar isso usando a API do DOM. Embora os atributos não sejam ordenados,
3. quando serializados, os namespaces são sempre serializados antes dos atributos. Além disso, ambos
são serializados em ordem em que eles são definidos em um elemento. Por exemplo, o primeiro
atributo adicionado ao elemento é o primeiro atributo serializado. O mesmo vale para os
namespaces. O seguinte elemento:
<node a:attr="a-attr" b:attr="b-attr" attr2="attr2" attr="attr"
xmlns:b="http://www.example.com/b"
xmlns:a="http://www.example.com/a"
xmlns="http://www.example.com" />
seria serializado automaticamente para o seguinte:
<node xmlns:b="http://www.example.com/b" xmlns:a="http://www.example.com/a"
xmlns="http://www.example.com" a:attr="a-attr" b:attr="b-attr"
attr2="attr2" attr="attr"/>
Isso ainda não é o que você realmente precisa. As declarações de namespace não são classificadas e
os atributos não são classificados. Usar a API DOM significa recriar a árvore usando apenas
nós apropriados e aplicando todas as regras apropriadas. Neste caso, quando o novo elemento
está sendo criado, namespaces e atributos precisam ser criados na ordem correta. O código
na Listagem 12-1 é apenas um exemplo de como fazer isso. Não é otimizado e dividido
em várias etapas para ilustrar o que precisa acontecer. A variável $ node usada no código
refere-se a um objeto DOMElement que faz referência ao elemento do nó anterior.
Listing12 - 1.SortingNamespaces and Attributes
/* Generic Attribute Sorting And Appending Function */
function sortAndAddAttrs($element, $arAtts)
{
$newAtts = array();
foreach($arAtts AS $attnode) {
$newAtts[$attnode->nodeName] = $attnode;
}
ksort($newAtts);
foreach($newAtts as $attnode) {
$element->setAttribute($attnode->nodeName, $attnode->nodeValue);
}
}
$dom2 = new DOMDocument();
$element = $dom2->createElementNS("http://www.example.com", "node");
$dom2→appendChild($root);
/* Create DOMXPath based on original document $dom */
$xPath = new DOMXPath($dom);
$nsnode = $xPath->query('namespace::*', $node);
/* Add namespace nodes */
foreach($arNS AS $nsnode) {
/* Skip default namespace because it was already added with element node
Skip xml namespace because it is automatic for document */
if ($nsnode->prefix != "" && $nsnode->prefix != "xml") {
$element->setAttributeNS("http://www.w3.org/2000/xmlns/",
4. "xmlns:" . $nsnode->prefix, $nsnode->namespaceURI);
}
}
/* Get attributes not in a namespace, and then sort and add them */
$arAtts = $xPath->query('attribute::*[namespace-uri(.) = ""]', $node);
sortAndAddAttrs($element, $arAtts);
/* Get namespaced attributes */
$arAtts = $xPath->query('attribute::*[namespace-uri(.) != ""]', $node);
/* Create an array with namespace URIs as keys, and sort them */
$arNS = array();
foreach($arAtts AS $attnode) {
$arNS[$attnode->namespaceURI] = 1;
}
ksort($arNS);
/* Loop through the URIs, and then sort and add attributes within that namespace
*/
foreach($arNS as $nsURI => $val) {
$arAtts = $xPath->query('attribute::*[namespace-uri(.) = "' . $nsURI .
'"]', $node);
sortAndAddAttrs($element, $arAtts);
}
Após a serialização, onde o elemento acabou de ser criado como elemento do documento de
$ dom2, a saída mostra os namespaces e os atributos corretamente ordenados:
<node xmlns="http://www.example.com" xmlns:a="http://www.example.com/a"
xmlns:b="http://www.example.com/b" attr="attr" attr2="attr2"
a:attr="a-attr" b:attr="b-attr"/>
Tradução: Diogo Rocha