Это немного сложнее, но XML :: Twig предназначен (и я активно его использую) для такого рода обработки. Таким образом, существует специальный метод, называемый mark
, который принимает регулярное выражение и помечает совпадения.
В этом случае регулярное выражение, вероятно, будет довольно большим. Я использовал Regexp :: Assempble для его сборки, чтобы он был оптимизирован. Тогда другая проблема заключается в том, что mark
не позволяет вам использовать текст соответствия для установки атрибута (я мог бы поработать над этим в следующей версии модуля, что было бы полезно), поэтому я должен отметить сначала , затем вернитесь назад и установите атрибут href
во втором проходе (в любом случае второй проход необходим для «отмены связывания» слов, которые уже были связаны).
И последнее слово: я почти отказался от написания решения, потому что в данных вашего примера есть несколько опечаток. Нет ничего хуже, чем правильно составить код, просто увидеть, что тест по-прежнему не работает, потому что вы используете «словарь» в коде и «определения» в данных, или «furtykurtle», «furtikurty» и «furtijurty», где все должно быть быть тем же словом. Поэтому, пожалуйста, перед публикацией убедитесь, что ваши данные верны. К счастью, я писал код в качестве теста.
#!/usr/bin/perl
use strict;
use warnings;
use XML::Twig;
use Regexp::Assemble;
use Test::More tests => 1;
use autodie qw(open);
my %dictionary = (
frobnitz => 'definitions.html#frobnitz',
crulps => 'definitions.html#crulps',
furtikurty => 'definitions.html#furtikurty',
);
my $match_defs= Regexp::Assemble->new()
->add( keys %dictionary)
->anchor_word
->as_string;
# I am not familiar enough with Regexp::Assemble to know a cleaner
# way to get get the capturing braces in the regexp
$match_defs= qr/($match_defs)/;
my $in = data_para();
my $expected = data_para();
my $out;
open( my $out_fh, '>', \$out);
XML::Twig->new( twig_roots => { 'description' => sub { tag_defs( @_, $out_fh, $match_defs, \%dictionary); } },
twig_print_outside_roots => $out_fh,
)
->parse( $in);
is( $out, $expected, 'base test');
exit;
sub tag_defs
{ my( $t, $description, $out_fh, $match_defs, $dictionary)= @_;
my @a= $description->mark( $match_defs, 'a' );
# word => 1 when already used in this description
# this might need to have a different scope if you need to tag
# only the first time the word appears in a section or whatever
my $tagged_in_description;
foreach my $a (@a)
{ my $word= $a->text;
warn "checking a: ", $a->sprint, "\n";
if( $tagged_in_description->{$word})
{ $a->erase; } # we did not need to tag it after all
else
{ $a->set_att( href => $dictionary->{$word}); }
$tagged_in_description->{$word}++;
}
$t->flush( $out_fh); }
sub def_href
{ my( $word)= @_;
return $dictionary{word};
}
sub data_para
{ local $/="\n\n";
my $para= <DATA>;
return $para;
}
__DATA__
<article>
<author>Smith</author>
<date>2011-10-10</date>
<description>Article about <b>frobnitz</b>, crulps and furtikurty's. Mainly frobnitz</description>
</article>
<article>
<author>Smith</author>
<date>2011-10-10</date>
<description>Article about <b><a href="definitions.html#frobnitz">frobnitz</a></b>, <a href="definitions.html#crulps">crulps</a> and <a href="definitions.html#furtikurty">furtikurty</a>'s. Mainly frobnitz</description>
</article>