如何在Perl中修改HTML文件?

时间:2010-10-10 15:22:52

标签: html perl html-parsing

我有一堆HTML文件,我想要做的是在每个HTML文件中查找关键字'From Argumbay'并使用我拥有的一些href来更改它。 起初我觉得它非常简单,所以我做的是打开每个HTML文件并将其内容加载到数组(列表)中,然后我查找每个关键字并将其替换为s ///,并将内容转储到文件,有什么问题?有时关键字也可以出现在href中,在这种情况下我不希望它被替换,或者它可以出现在某些标签内等等。

示例:http://www.astrosociety.org/education/surf.html

我希望我的脚本用$ href中的一些href替换'here'这个词的每个出现,但正如你所看到的,还有另一个'here'已经被href'ed,我不想要再来一次这个。 在这种情况下除了href之外没有其他'这里有',但我们假设有。

我想只更换关键字,如果它只是文字,任何想法?

BOUUNTY编辑:嗨,我相信它很简单,但似乎它删除了HTML,SHTML文件中发现的所有注释(主要问题是它在SHTML中删除了SSI),我尝试使用:store_comments(1 )在调用递归函数之前对$ html的方法,但无济于事。知道我在这里缺少什么吗?

3 个答案:

答案 0 :(得分:7)

要使用HTML::TreeBuilder执行此操作,您将读取文件,修改树并将其写出(到同一文件或其他文件)。这相当复杂,因为您尝试将文本节点的一部分转换为标记,并且因为您有无法移动的注释。

HTML-Tree的一个常见习惯是使用修改树的递归函数:

use strict;
use warnings;
use 5.008;

use File::Slurp 'read_file';
use HTML::TreeBuilder;

sub replace_keyword
{
  my $elt = shift;

  return if $elt->is_empty;

  $elt->normalize_content;      # Make sure text is contiguous

  my $content = $elt->content_array_ref;

  for (my $i = 0; $i < @$content; ++$i) {
    if (ref $content->[$i]) {
      # It's a child element, process it recursively:
      replace_keyword($content->[$i])
          unless $content->[$i]->tag eq 'a'; # Don't descend into <a>
    } else {
      # It's text:
      if ($content->[$i] =~ /here/) { # your keyword or regexp here
        $elt->splice_content(
          $i, 1, # Replace this text element with...
          substr($content->[$i], 0, $-[0]), # the pre-match text
          # A hyperlink with the keyword itself:
          [ a => { href => 'http://example.com' },
            substr($content->[$i], $-[0], $+[0] - $-[0]) ],
          substr($content->[$i], $+[0])   # the post-match text
        );
      } # end if text contains keyword
    } # end else text
  } # end for $i in content index
} # end replace_keyword


my $content = read_file('foo.shtml');

# Wrap the SHTML fragment so the comments don't move:
my $html = HTML::TreeBuilder->new;
$html->store_comments(1);
$html->parse("<html><body>$content</body></html>");

my $body = $html->look_down(qw(_tag body));
replace_keyword($body);

# Now strip the wrapper to get the SHTML fragment back:
$content = $body->as_HTML;
$content =~ s!^<body>\n?!!;
$content =~ s!</body>\s*\z!!;

print STDOUT $content; # Replace STDOUT with a suitable filehandle

as_HTML的输出将是语法上正确的HTML,但不一定是格式良好的HTML,供人们查看源代码。如果需要,可以使用HTML::PrettyPrinter写出文件。

答案 1 :(得分:3)

如果代码在您的搜索和替换中很重要,则需要使用HTML::Parser

这个tutorial看起来比模块的文档更容易理解。

答案 2 :(得分:0)

如果您想使用正则表达式类型方法,并且您已准备好接受以下条件:

  • 这在HTML评论中无法正常使用
  • 如果在代码中使用<>字符
  • ,则无效
  • 如果使用<>字符且不是标记的一部分,则无法使用
  • 如果标记跨越多行(如果您一次处理一行),这将无效。

如果存在上述任何条件,则必须使用其他答案中概述的HTML / XML解析策略之一。

否则:

my $searchfor = "From Argumbay";
my $replacewith = "<a href='http://google.com/?s=Argumbay'>From_Argumbay</a>";

1 while $html =~ s/
  \A             # beginning of string
  (              # group all non-searchfor text
    (            # sub group non-tag followed by tag
      [^<]*?     # non-tags (non-greedy)
      <[^>]*>    # whole tags
    )*?          # zero or more (non-greedy)
  )
  \Q$searchfor\E # search text
/$1$replacewith/sx;

请注意,如果$searchfor$replacetext匹配,则无法执行此操作(因此请勿将“从Argumbay”添加回替换文本中)。