Perl使用正则表达式手动解析xml标记

时间:2014-03-02 03:57:21

标签: html xml regex perl

我有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标记。

1 个答案:

答案 0 :(得分:2)

我为此做了一个非常重要的解决方案。它远非完美,它使用了很多我不想在生产代码中使用的东西,它可能会破坏你的真实数据所具有的一些东西。但它确实适用于该示例。

在查看代码之前,让我们注意一些使XML难以解析的事情:

  • 您的CDATA开放是错误的。您正在使用<![[CDATA[。有一个[太多了。它应该是<![CDATA[
  • 属性中的双引号中断XML解析器

我通过使用正则表达式修复它们来修复这些问题。正如我所说,这是非常务实的。我并不认为这是一个非常好的解决方案。

所以这是代码:

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:命名空间的标记内。
  • 示例中有三种类型的说明:contentbloglang。我不知道他们应该做什么,所以我做了。
  • 我创建了一个调度表。这是一个散列,其中指令作为键,代码作为值。 Mark Jason Dominus的书Higher Order Perl就是一个非常好的资源。
  • 我在HTML / XML字符串中全局修复了CDATA问题。
  • 有两个正则表达式负责用实际内容替换指令。他们正在使用/e标志,该标志在s///的替换部分中执行Perl代码。
    • 第一个找到没有结束标记的所有标记,即<foo />
    • 第二个更复杂。它处理<foo>...</foo>并处理内容中的CDATA。属性中不支持CDATA!正则表达式使用/x标志来允许注释和缩进。有关正则表达式的说明,请参阅http://regex101.com/r/bB0kB5
  • 我的parse()子获取完整匹配的标记并对其执行操作:
    • 用占位符替换双引号。如果属性中存在引用内容的真实实例,它将会中断! <foo attr="this is "quoted" stuff"> 无法正常工作。你必须找到一种处理这些问题的方法。
    • 它使用XML::Simple将标记分解为带有属性的hashref。 KeepRoot选项将标记名称作为键,因此我们得到{ foo => { attr1 => 'bar', attr2 => 'baz' }}。我正在使用each built-in直接将其分解为键和值。
    • 将已转义的双引号替换回来。
    • 通过调度表发送指令(位于$name中)。使用params调用coderef的语法是$coderef->($arg),但我们使用的是哈希值。我们传递XML :: Simple从属性(和内容)创建的hashref,但它最终像一个名为content的属性。)

我想再次强调,这可能不会对你的真实数据产生影响,但它可能会提供一些关于如何以务实的方式解决问题的想法。

相关问题