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