2015-07-28 6 views
0

В настоящее время я использую Perl-скрипт с LibXML для обработки данного XML-файла. Это идет прилично хорошо, но если у меня есть узел с двумя дочерними узлами и свободным текстом, я начинаю бороться. Пример ввода будет:Заменить узел XML на String в Perl с помощью LibXML

<Errors> 
    <Error> 
     this node works fine 
    </Error> 
    <Error> 
     some text <testTag>with a node</testTag> in between 
    </Error> 
</Errors> 

Ожидаемый результат:

<Errors> 
    <Error> 
     this node works fine 
    </Error> 
    <Error> 
     some text HELLOwith a nodeHELLO in between 
    </Error> 
</Errors> 

Я попытался replaceChild ("HELLO", $ testTagNode); заменить узлы на строку, которую я мог бы (если нужно) продолжить с помощью простого поиска-замены, но я столкнулся с ошибкой «не блаженной ссылки». (Я чувствую, как это было бы очень грязный, если он на самом деле работает именно так.)

Если я пытаюсь запустить простой поиск замены непосредственно на родительский узел, как этот

$error=~s/\</HELLO/g; 

это будет просто никогда триггер (независимо от того, я избегаю < или нет), потому что LibXML, кажется, игнорирует каждый тег, который я специально не запрашиваю; если я пытаюсь распечатать вторую ошибку он также даст мне только

some text with a node in between 

который на самом деле очень хорошая функциональность для остальной части файла, но не в данном случае.

я могу сделать, однако

$error->removeChild($testTagNode); 

, который показывает мне, что он на самом деле делает получить найдены, но не поможет мне в дальнейшем. Я мог бы теоретически удалить узел, сохранить содержимое, а затем просто вставить содержимое обратно в родительский; проблема в том, что она должна быть в том месте, где она была раньше. Единственное, что я, вероятно, мог бы сделать, это прочитать во всем файле как строку, позволить базовому поисковому замещению работать над ним ПЕРЕД доставкой в ​​LibXML, но это может создать довольно большие накладные расходы и на самом деле не очень хорошее решение.

Мне кажется, что я не замечаю что-то существенное, так как это выглядит как довольно простые задачи, но я ничего не могу найти. Возможно, я просто смотрю в неправильном направлении, и есть совершенно другой подход. Любая помощь приветствуется.

+0

Почему вы пытаетесь превратить XML-элемент в виде обычного текста в первую очередь? Это похоже на проблему XY. – Sobrique

ответ

1

Удаление testTag элемента удалит все его дети тоже, так что мы должны двигаться детей каждого testTag элемента в родительский элемент testTag перед удалением элемента testTag. В XML :: Libxml, это делается следующим образом: (испытано)

for my $node ($doc->findnodes('/Errors/Error//testTag')) { 
    my $parent = $node->parentNode(); 

    for my $child_node (
     XML::LibXML::Text->new("HELLO"), 
     $node->childNodes(), 
     XML::LibXML::Text->new("HELLO"), 
    ) { 
     $parent->insertBefore($child_node, $node); 
    } 

    $node->unbindNode(); 
} 

Примечания:

  • Ручка testTag элементов с любым количеством текста и дочерними элементами.
  • Ручки testTag элементы, которые не являются прямыми детьми Error элементы. Даже обрабатывает вложенные элементы testTag. (Используйте /Errors/Error/testTag вместо /Errors/Error//testTag если вы хотите, чтобы справиться с непосредственными потомками Error элементов.)
+0

Хм, так эффективно создавая новый элемент '# text', обертывающий этот дочерний узел? Нейтер, чем мой подход. – Sobrique

+0

@Sobrique, No. Я не обертываю детей текстовым узлом. Это даже не имеет никакого смысла, поскольку текстовые узлы не могут содержать другие узлы. – ikegami

+0

ОК. Мне придется посмотреть на него немного дольше, чтобы понять, что происходит дальше. – Sobrique

1

В XML::XSH2 который просто обертка XML::LibXML, следующий, кажется, работает:

for //testTag/text() { 
    insert text 'HELLO' prepend . ; 
    insert text 'HELLO' append . ; 
    move . replace .. ; 
} 

перевода обратно в XML :: LibXML остается в качестве упражнения для читателя.

+0

Я не уверен, что допустимо считать, что childnres 'testTag' будут только текстовыми узлами. – ikegami

+0

Спасибо за другой подход, но я поеду с теми, которым не нужно больше пакетов, чем у меня уже есть. – LindenRathan

1

Прежде всего - я не думаю, что то, что вы пытаетесь сделать, обязательно особенно полезно. Тем не менее, я буду отмечать - когда вы обрабатываете свои узлы - если у вас есть вложенный узел, как в вашем втором примере, вы фактически получаете 3 'узла', но два из них обозначены как #PCDATA.

Так что вы могли бы сделать что-то вроде этого:

#!/usr/bin/env perl 
use strict; 
use warnings; 
use XML::Twig; 
use Data::Dumper; 

my $twig = XML::Twig->new(pretty_print => 'indented_a')->parse(\*DATA); 
foreach my $error ($twig->get_xpath('//Error')) { 
    my $replace_text; 
    foreach my $child ($error->children) { 
     my $tag = $child->tag; 
     print "Child: $tag ", $child->trimmed_text, "\n"; 
     $tag = '' if $tag eq "#PCDATA"; 
     $replace_text .= $tag . $child->trimmed_text . $tag; 
    } 

    $error->set_text($replace_text); 
    print $error ->trimmed_text, "\n"; 
} 
print $twig->sprint; 

__DATA__ 
<Errors> 
    <Error> 
     this node works fine 
    </Error> 
    <Error> 
     some text <testTag>with a node</testTag> in between 
    </Error> 
</Errors> 

Это превращает его в:

<Errors> 
    <Error>this node works fine</Error> 
    <Error>some texttestTagwith a nodetestTagin between</Error> 
</Errors> 

Очевидно, что вы можете переименовать testTag все, что вы хотите.

(Медведь со мной - я посмотрю, как это сделать в LibXML - к сожалению, он не устанавливается легко на моем ящике Windows).

ОК, так и с XML::LibXML:

#!/usr/bin/env perl 
use strict; 
use warnings; 
use XML::LibXML; 


my $xml = XML::LibXML->load_xml(IO => \*DATA); 
foreach my $error ($xml -> findnodes ('//Error')) { 
    my $replace_text; 
    foreach my $child ($error -> childNodes) { 
     my $tag = $child -> nodeName; 
     $tag = '' if $tag eq '#text'; 
     $replace_text .= $tag . $child -> textContent . $tag; 
     $err -> removeChild($child); 
    } 
    $err -> appendTextNode($replace); 
} 

print $xml -> toString; 

__DATA__ 
<Errors> 
    <Error> 
     this node works fine 
    </Error> 
    <Error> 
     some text <testTag>with a node</testTag> in between 
    </Error> 
</Errors> 
+0

Я не уверен, что приемлемо предположить, что дети testTag будут только текстовыми узлами. – ikegami

+0

Решение LibXML действительно работает, хотя есть 3 пункта, которые необходимо скорректировать в вашем коде: $ err (дважды) должен быть $ error, а в последней строке $ replace должен быть $ replace_text. (Просто оставив это здесь для будущих зрителей.) Кроме этого, работает отлично. Я соглашусь с ответом @ ikegami, хотя он может иметь дело с вложенными тегами. В настоящее время ваше предположение с использованием только текста в узлах testTag остается верным, но, возможно, что изменения и безопасность в будущем - это всегда хорошо. Большое спасибо за Вашу помощь. – LindenRathan

+0

Да, ошибка транскрипции - LibXML не устанавливает красиво на моем ящике Windows. – Sobrique

-1

Это должно работать

$error='<Errors> 
<Error> 
    this node works fine 
</Error> 
<Error> 
    some text <testTag>with a node</testTag> in between 
</Error> 
</Errors>'; 

$error=~ s/<testTag>/HELLO/gs; 
$error=~ s/<\/testTag>/HELLO/gs; 
Смежные вопросы