我有html内容片段,其中包含带属性或cdata的自定义xml标记,并且可能包含文本节点。
内容片段格式不正确xml,所以我想我不能使用xml解析器模块。
以下是示例html内容片段:
<p>Hello world, mixed html and xml content</p>
<a href="http://google.com/">google</a>
<fw:blog id="title" content="hellow world" size="30" width="200px" />
<b>First content section</b>
<fw:content id="middle" width="400px" height="300px">Here is the first content section</fw:content>
<b>Second content section</b>
<fw:content id="left-part" width="400px" height="300px"><![[CDATA[ Here is the first content section]]></fw:content>
<b>Attributes may contains single or double quotes, can we skip double quotes in attributes</b>
<fw:blog id="title" content="what's your name, I may"" be cool" size="30" width="200px" />
<fw:lang id="home" />
假设我有名称空间fw
,我需要找到并用每个标记的程序输出替换所有fw
xml标记。
答案 0 :(得分:2)
我为此做了一个非常重要的解决方案。它远非完美,它使用了很多我不想在生产代码中使用的东西,它可能会破坏你的真实数据所具有的一些东西。但它确实适用于该示例。
在查看代码之前,让我们注意一些使XML难以解析的事情:
CDATA
开放是错误的。您正在使用<![[CDATA[
。有一个[
太多了。它应该是<![CDATA[
。我通过使用正则表达式修复它们来修复这些问题。正如我所说,这是非常务实的。我并不认为这是一个非常好的解决方案。
所以这是代码:
use strict; use warnings;
use XML::Simple;
my $html = <<HTML;
<p>Hello world, mixed html and xml content</p>
<a href="http://google.com/">google</a>
<fw:blog id="title" content="hellow world" size="30" width="200px" />
<b>First content section</b>
<fw:content id="middle" width="400px" height="300px">Here is the first content section</fw:content>
<b>Second content section</b>
<fw:content id="left-part" width="400px" height="300px"><![[CDATA[ Here is the first content section]]></fw:content>
<b>Attributes may contains single or double quotes, can we skip double quotes in attributes</b>
<fw:blog id="title" content="what's your name, I may"" be cool" size="30" width="200px" />
<fw:lang id="home" />
HTML
# dispatch table
my %dispatch = (
content => sub {
my ($attr) = @_;
return qq{<div width="$attr->{width}" id="$attr->{id}">Content: $attr->{content}</div>};
},
blog => sub {
my ($attr) = @_;
return qq{<p width="$attr->{width}" id="$attr->{id}">Blog: $attr->{content}</p>};
},
lang => sub {
my ($attr) = @_;
return "<p>FooLanguage</p>";
}
);
# pragmatic repairs based on the example given:
# CDATA only has two brackets, not three, and the closing one is right
$html =~ s/<!\[\[CDATA\[/<![CDATA[/;
# replace tags that do not have a closing tag
$html =~ s{(<fw:[^>]+/>)}{parse($1)}ge;
# replace tags with a closing tag (see http://regex101.com/r/bB0kB5)
$html =~ s{
( # group to $1
<
( # group to $2 and \2
fw: # start with namespace-prefix
[a-zA-z]+ # find tagname
) # end of $2
[^>]* # match everything until the next > (or nothing)
> # end of tag
(?:
[^<]+ # all the stuff before the closing tag
| # or
<!\[CDATA\[.+?\]\]> # a CDATA section
)
</ \2 > # the closing tag is the same as the opening (\2)
)
}
{
parse($1) # dispatch
}gex; # x adds extended readability (i.e. quotes)
print $html;
sub parse {
my ($string) = @_;
# pragmatic repairs based on the example given:
# there can be no unescaped quotes within quotes,
# but there are no empty attributs either
$string =~ s/""/{double-double-quote}/g;
# read with XML::Simple and fetch tagname as well as attributes
my ( $name, $attr ) = each %{ XMLin($string, KeepRoot => 1 ) };
# get rid of the namespace
$name =~ s/^[^:]+://;
# restore quotes
s/{double-double-quote}/""/ for values %$attr;
# dispatch
return $dispatch{$name}->($attr);
}
这是如何运作的?
fw:
命名空间的标记内。content
,blog
和lang
。我不知道他们应该做什么,所以我做了。CDATA
问题。/e
标志,该标志在s///
的替换部分中执行Perl代码。
<foo />
。<foo>...</foo>
并处理内容中的CDATA
。属性中不支持CDATA
!正则表达式使用/x
标志来允许注释和缩进。有关正则表达式的说明,请参阅http://regex101.com/r/bB0kB5。parse()
子获取完整匹配的标记并对其执行操作:
<foo attr="this is "quoted" stuff">
无法正常工作。你必须找到一种处理这些问题的方法。KeepRoot
选项将标记名称作为键,因此我们得到{ foo => { attr1 => 'bar', attr2 => 'baz' }}
。我正在使用each
built-in直接将其分解为键和值。$name
中)。使用params调用coderef的语法是$coderef->($arg)
,但我们使用的是哈希值。我们传递XML :: Simple从属性(和内容)创建的hashref,但它最终像一个名为content
的属性。)我想再次强调,这可能不会对你的真实数据产生影响,但它可能会提供一些关于如何以务实的方式解决问题的想法。