Ao gerar documentos XML com conteúdo dinâmico é preciso tomar cuidado com a sintaxe XML para que o documento não seja "mal formado". Muita gente conhece a sintaxe básica do XML, que especifica coisas como: é preciso um elemento raiz único, todo elemento que é aberto precisa ser fechado (ou ter o fechamento simplificado com "/>"), todos atributos precisam ter nome e valor, mesmo que o valor seja vazio.
Porém, um detalhe que nem todos sabem é que os elementos que contém texto não podem incluir qualquer tipo de caractere. Neste artigo veremos detalhes sobre isso.
Caracteres Proibidos no XML
Quando escrevi sobre Unicode, comentei que a codificação UTF-8 definia que um símbolo poderia ter de 1 a 4 bytes e, quando possui 1 byte, ele é idêntico à forma ASCII. Também falei que a tabela ASCII possui letras, números, alguns símbolos e alguns caracteres de controle. É justamente com estes caracteres de controle que devemos nos preocupar na hora de montar um XML 100% válido.
Acontece que, pela definição do XML, não podemos utilizar os caracteres de controle, com exceção de: tabulação ("\t" que possui código 9), quebra de linha (o famoso "\n", que possui código 10) e o retorno de carro (o famoso "\r", que possui código 13).
Normalmente, não é possível "digitar" os caracteres de controle (exceto as exceções), mas, então, como é que eles podem "surgir"? Acontece que alguns programas de editor de texto mais sofisticados (como o "MS-Word") utilizam estes caracteres reservados então, ao copiar um trecho de um texto feito nestes editores e colar num formulário web, é possível que estes dados vão para o Banco de Dados e sua base de dados fica "suja" com estes caracteres.
Bom, sabendo que estes caracteres podem existir, como podemos solucionar? Você poderia pensar: usa um bloco <![CDATA[ e ]]>. Mas nem este bloco é capaz de resolver. Você então poderia pensar: usa a função htmlentities ou htmlspecialchars, mas também não resolveria o problema e o documento não seria válido.
A solução é muito simples: não exiba estes caracteres no documento XML. Para isso, é preciso fazer um filtro para sanitizar o conteúdo a ser incluído no XML.
Se o seu documento XML tem codificação ISO-8859-1, você pode usar esta função abaixo para obter apenas os caracteres válidos:
/**
* Limpa caracteres nao pertencentes a codificacao ISO-8859-1
* @param string $texto
* @return string
*/
function sanitizarISO88591($texto) {
return preg_replace('/([\x00-\x08]|[\x0B-\x0C]|[\x0E-\x1F]|[\x7F-\x9F])/', '', $texto);
}
Se o seu documento XML tem codificação UTF-8, então precisa usar uma função um pouco mais sofisticada:
/**
* Limpa caracteres nao pertencentes a codificacao UTF-8
* @param string $texto
* @return string
*/
function sanitizarUTF8($texto) {
$saida = '';
$i = 0;
$len = strlen($texto);
while ($i < $len) {
$char = $texto[$i++];
$ord = ord($char);
// Primeiro byte 0xxxxxxx: simbolo possui 1 byte (ascii)
if (($ord & 0b10000000) == 0b00000000) {
// Caracteres de controle
if (($ord >= 0 && $ord <= 31) || $ord == 127) {
// Excecoes: tab, retorno de carro e quebra de linha
if ($ord == 9 || $ord == 10 || $ord == 13) {
$saida .= $char;
}
} else {
$saida .= $char;
}
// Primeiro byte 110xxxxx: simbolo possui 2 bytes
} elseif (($ord & 0b11100000) == 0b11000000) {
$char2 = $texto[$i++];
$ord2 = ord($char2);
// Segundo byte valido (10xxxxxx)
if (($ord2 & 0b11000000) == 0b10000000) {
$saida .= $char . $char2;
}
// Primeiro byte 1110xxxx: simbolo possui 3 bytes
} elseif (($ord & 0b11110000) == 0b11100000) {
$char2 = $texto[$i++];
$ord2 = ord($char2);
// Segundo byte valido (10xxxxxx)
if (($ord2 & 0b11000000) == 0b10000000) {
$char3 = $texto[$i++];
$ord3 = ord($char3);
// Terceiro byte valido (10xxxxxx)
if (($ord3 & 0b11000000) == 0b10000000) {
$saida .= $char . $char2 . $char3;
}
}
// Primeiro byte 11110xxx: simbolo possui 4 bytes
} elseif (($ord & 0b11111000) == 0b11110000) {
$char2 = $texto[$i++];
$ord2 = ord($char2);
// Segundo byte valido (10xxxxxx)
if (($ord2 & 0b11000000) == 0b10000000) {
$char3 = $texto[$i++];
$ord3 = ord($char3);
// Terceiro byte valido (10xxxxxx)
if (($ord3 & 0b11000000) == 0b10000000) {
$char4 = $texto[$i++];
$ord4 = ord($char4);
// Quarto bytte valido (10xxxxxx)
if (($ord4 & 0b11000000) == 0b10000000) {
$saida .= $char . $char2 . $char3 . $char4;
}
}
}
}
}
return $saida;
}
Observação: este código usa recursos do PHP 5.4. Se você ainda não usa esta versão, avalie a possibilidade de usá-lo, pois é a versão estável do PHP.
2 comentários
Parabéns,
Bem Bacana. Usei algo semelhante lá no meu trabalho.
Parabéns Rubinho
Cara muito bom!
Tinha este problema e não conseguia resolver.
Postar um comentário
Nota: fique a vontade para expressar o que achou deste artigo ou do blog.
Dica: para acompanhar as respostas, acesse com uma conta do Google e marque a opção "Notifique-me".
Atenção: o blogger não permite inclusão de tags nos comentários, por isso, use algum site externo para postar seu código com dúvidas e deixe o link aqui. Exemplo: pastebin.com