如何从Perl中的字符串中删除无效的XML字符?

时间:2009-06-19 08:31:18

标签: xml perl

我正在寻找在将字符串写入XML文件之前从字符串中删除无效字符的标准,批准和强大的方法。我在这里谈论的是包含退格(^ H)和换页字符等的文本块。

作为标准库/模块函数来执行此操作,但我找不到它。

我正在使用XML::LibXML构建一个DOM树,然后我将其序列化为磁盘。

10 个答案:

答案 0 :(得分:7)

删除无效xml-1.0字符的完整正则表达式为:

# #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
$str =~ s/[^\x09\x0A\x0D\x20-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]//go;

对于xml-1.1来说是:

# allowed: [#x1-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
$str =~ s/[^\x01-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]//go;
# restricted:[#x1-#x8][#xB-#xC][#xE-#x1F][#x7F-#x84][#x86-#x9F]
$str =~    s/[\x01-\x08\x0B-\x0C\x0E-\x1F\x7F-\x84\x86-\x9F]//go;

答案 1 :(得分:6)

正如几乎所有人都说的那样,使用正则表达式。说实话,它不够复杂,不值得添加到库中。使用替换预处理文本。

您对上述换行符的评论表明,格式化对您来说非常重要,因此您可能必须准确确定要替换某些字符的内容。

XML规范中明确定义了无效字符列表(例如,此处为http://www.w3.org/TR/REC-xml/#charsets)。不允许的字符是ASCII控制字符栏回车,换行和制表符。所以,你正在看一个29个字符的正则表达式字符类。那肯定不是太糟糕。

类似的东西:

$text =~ s/[\x00-\x08 \x0B \x0C \x0E-\x19]//g;

应该这样做。

答案 2 :(得分:5)

我找到了一个解决方案,但它使用iconv命令而不是perl。

$ iconv -c -f UTF-8 -t UTF-8 invalid.utf8 > valid.utf8

上面基于正则表达式给出的解决方案不起作用!! ,请考虑以下示例:

$ perl -e 'print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root>\x{A0}\x{A0}</root>"' > invalid.xml
$ perl -e 'use XML::Simple; XMLin("invalid.xml")'
invalid.xml:2: parser error : Input is not proper UTF-8, indicate encoding !
Bytes: 0xA0 0xA0 0x3C 0x2F
$ perl -ne 's/[^\x09\x0A\x0D\x20-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]//go; print' invalid.xml > valid.xml
$ perl -e 'use XML::Simple; XMLin("valid.xml")'
invalid.xml:2: parser error : Input is not proper UTF-8, indicate encoding !
Bytes: 0xA0 0xA0 0x3C 0x2F

事实上,两个文件invalid.xmlvalid.xml 相同

问题是“\ x20- \ x {D7FF}”范围匹配那些unicode字符的有效表示,但不是无效的字符序列“\ x {A0} \ x {A0}”。

答案 3 :(得分:4)

翻译比正则表达式替换更快批次。特别是如果您想要删除所有字符。使用newt的集合:

$string_to_clean =~ tr/\x00-\x08\x0B\x0C\x0E-\x19//d;

这样的测试:

cmpthese 1_000_000
       , { translate => sub { 
               my $copy = $text; 
               $copy =~ tr/\x00-\x08\x0B\x0C\x0E-\x19//d; 
           }
           , substitute => sub { 
               my $copy = $text; 
               $copy =~ s/[\x00-\x08\x0B\x0C\x0E-\x19]//g; 
           }
         };

yeilded:

                Rate substitute  translate
substitute  287770/s         --       -86%
translate  2040816/s       609%         --

我需要删除更快的tr所需的字符越多。

答案 4 :(得分:3)

如果您使用XML库来构建XML(而不是字符串连接,简单模板等),那么它应该为您处理。重新发明轮子毫无意义。

答案 5 :(得分:3)

好的,这似乎已经得到了解答,但是嘿。如果要创作XML文档,必须使用XML库。

#!/usr/bin/perl
use strict;
use XML::LibXML;

my $doc = XML::LibXML::Document->createDocument('1.0');
$doc->setURI('http://example.com/myuri');
$doc->setDocumentElement($doc->createElement('root-node'));

$doc->documentElement->appendTextChild('text-node',<<EOT);
    This node contains &, ñ, á, <, >...
EOT

print $doc->toString;

这会产生以下结果:

$ perl test.pl
<?xml version="1.0"?>
<root-node><text-node>    This node contains &amp;, &#x6C821;, &lt;, &gt;...
</text-node></root-node>

编辑:我现在看到您已经在使用XML :: LibXML。这应该可以解决问题。

答案 6 :(得分:0)

您可以使用正则表达式删除控制字符,例如\ cH将匹配\ cL或\ x08和\ x0C分别匹配退格和Formfeed。

答案 7 :(得分:0)

你可以使用一个简单的regex来查找和替换你的文本块中的所有控制字符,用空格替换它们或者完全删除它们 -

# Replace all control characters with a space
$text =~ s/[[:cntrl:]]/ /g;

# or remove them
$text =~ s/[[:cntrl:]]//g;

答案 8 :(得分:0)

我之前没有对包含“无效”字符的XML做过很多工作,但是 在我看来,你在这里有两个完全不同的问题。

首先,您可能不需要数据中的字符。您应该决定它们是什么以及如何删除/替换它们,而不受任何XML限制的影响。例如,您可能有x^H_y^H_z^H_之类的内容,您决定要删除退格和后续字符。或者你可能实际上不想调整你的数据,但是由于需要用XML表示它而感到被迫。

更新:我为后代保留了以下段落,但它们基于一个误解:我认为你可以在XML数据中包含任何字符,只要你正确编码它,但似乎有一些字符是彻头彻尾的verboten, 甚至编码? XML :: LibXML剥离它们(至少当前版本是这样做的),除了nul字符,它将其视为字符串的结尾,丢弃它以及随后的任何内容:(

其次,您可能在数据中包含需要使用XML编码的字符。理想情况下,您使用的任何XML模块都可以为您执行此操作,但如果不是,则应该能够手动执行此操作,例如:

use HTML::Entities "encode_entities_numeric";
$encoded_string = encode_entities_numeric( $string, "\x00-\x08\x0B\x0C\x0E-\x19");

但那真的只是权宜之计。使用适当的XML模块;例如,见this answer

答案 9 :(得分:0)

Axeman关于使用tr的权利,但是他和newt在反转XML规范的法律字符方面犯了一点错误。 http://www.w3.org/TR/REC-xml/#charsets给出了

Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]

由于\x20之前的十六进制数是\x1F(不是\x19!),您应该使用

$string_to_clean =~ tr/\x00-\x08\x0B\x0C\x0E-\x1F//d;