Хмм ... Я бы предложил использовать для этого Algorithm :: Diff . По сути, если вы взяли двухстороннюю разность двух текстов, вы должны получить что-то вроде этого:
[+<div2>+]
[+<head>+]Tit[-e-]l[+e</head>+]
[+<p>+]Here is some normali[-s-][+z+]ed sample text[-e-].[+</p>+]
[+<p>+]The ort[+h+]ograph[-e-] has been changed.[+</p>+]
[+</div2>+]
Вы заметите, что некоторые вставки тегов XML чередуются с текстовыми изменениями. Теперь, если вы просто взяли теги из версии +
и текст из версии -
, вы должны получить требуемый комбинированный текст.
Для достижения наилучшего эффекта я бы рекомендовал использовать интеллектуальный токенизатор, который обрабатывает теги XML как отдельные токены, например, <p>foo</p>
будет разделен на <p>
, f
, o
, o
, </p>
. Это не только ускоряет процесс сравнения и упрощает синтаксический анализ выходных данных, но также позволяет избежать риска того, что алгоритм сравнения может разбить тег на несколько частей или спутать его с текстом.
Вот пример кода:
sub merge_tags {
my ($orig, $tagged) = @_;
# tokenize strings into tags and chars (could use a real XML parser here)
$_ = [/\G(<(?:[^>"']|"[^"]*"|'[^']*')*>|.)/sg] for $orig, $tagged;
require Algorithm::Diff;
my $diff = Algorithm::Diff->new( $orig, $tagged );
my @output;
while ($diff->Next) {
if ($diff->Diff) {
my @text = grep !/^<.*>$/s, $diff->Items(1);
my @tags = grep /^<.*>$/s, $diff->Items(2);
# kluge: output opening tags first
push @output, shift @tags while @tags and $tags[0] !~ /^<\//;
push @output, @text, @tags;
}
else {
push @output, $diff->Same;
}
}
return join "", @output;
}
Я уверен, что этот код может быть улучшен (например, он может быть разумнее в отношении вложенности тегов), но, по крайней мере, он работает для примера ввода.