Как я могу изменить HTML-файлы в Perl? - PullRequest
1 голос
/ 10 октября 2010

У меня есть куча HTML-файлов, и я хочу найти в каждом HTML-файле ключевое слово «From Argumbay» и изменить его на несколько ссылок, которые у меня есть. Сначала я подумал, что это очень просто, поэтому я открыл каждый HTML-файл и загрузил его содержимое в массив (список), затем посмотрел каждое ключевое слово, заменил его на s /// и выгрузил содержимое в файл, в чем проблема? иногда ключевое слово может также появляться в href, что в данном случае я не хочу, чтобы оно было заменено, или оно может появляться внутри некоторых тегов и тому подобного.

ПРИМЕР: http://www.astrosociety.org/education/surf.html

Я бы хотел, чтобы мой сценарий заменял каждое вхождение слова «здесь» на некоторый href, который у меня есть в $ href, но, как вы можете видеть, есть еще один «здесь», который уже есть в href, я не хочу это еще раз. В этом случае здесь нет никаких дополнительных 'кроме как от href, но давайте предположим, что есть.

Я хочу заменить ключевое слово только в том случае, если это просто текст, есть идеи?

BOUUNTY EDIT: Привет, я полагаю, что это простая вещь, но похоже, что он стирает все комментарии, найденные в файле HTML, SHTML (главная проблема заключается в том, что он стирает SSI в SHTML), я попытался использовать: store_comments (1 ) метод в $ html перед вызовом рекурсивной функции, но безрезультатно. Есть идеи, что мне здесь не хватает?

Ответы [ 3 ]

7 голосов
/ 11 октября 2010

Чтобы сделать это с HTML :: TreeBuilder , вы должны прочитать файл, изменить дерево и записать его (в тот же файл или в другой файл). Это довольно сложно, потому что вы пытаетесь преобразовать часть текстового узла в тег, и потому что у вас есть комментарии, которые не могут быть перемещены.

Распространенной идиомой для HTML-дерева является использование рекурсивной функции, которая модифицирует дерево:

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 , чтобы записать файл, если хотите.

3 голосов
/ 10 октября 2010

Если теги имеют значение при поиске и замене, вам необходимо использовать HTML :: Parser .

Этот учебник выглядит немного проще для понимания, чем документация к модулю.

0 голосов
/ 11 октября 2010

Если вы хотите использовать метод типа только для регулярных выражений и готовы принять следующие условия:

  • , это не будет работать правильно в комментариях 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 (поэтому не ставьтеАргумбей "обратно в текст замены).

...