2015-08-21 3 views
3

Я пытаюсь добавить дочерний элемент к узлу в XML с помощью XML: Twig. мой XML являетсяКак добавить дочерний элемент к узлу в XMl с помощью XML: Perl

<Install> 
<version > 
<number>6.0</number> 
<build>1037124</build> 
<path>path</path> 
<kind>kind</kind> 
</version> 
<version > 
<number>7.0</number> 
<build>1037124</build> 
<path>path</path> 
<kind>kind</kind> 
</version> 
</Install> 

и выход я хочу это: -

<Install> 
<version > 
<number>6.0</number> 
<build>1037124</build> 
<path>path/path> 
<kind>kind</kind> 
<patch>patch</patch> 
<patchv>patchv</patchv> 
</version> 
<version > 
<number>7.0</number> 
<build>1037124</build> 
<path>path</path> 
<kind>kind</kind> 
</version> 
</Install> 

Я хочу добавить два новых дочернего узла к родительскому версии, где число 6,0

Я попробовал этот код

my $version = "6.0";  
    my $file_loc = "data.xml"; 
    my $twig = XML::Twig->new(pretty_print => 'indented_a')->parsefile($file_loc); 

for my $number ($twig->findnodes('/Install/version/number')) { 
    $number->parent->last_child->insert(
       patch => { version =>'patch' }, 
       patchversion => { version =>'patchv' }, 
     ) if $number->trimmed_text eq $version; 
} 

но выход, который я получаю, не является тем, что я хочу. выход: -

<Install> 
    <version> 
    <number>6.0</number> 
    <build>1037124</build> 
    <path>path</path> 
    <kind> 
     <patch version="patch"> 
    <patchversion version="patchv">kind</patchversion> 
     </patch> 
    </kind> 
    </version> 
    <version > 
    <number>7.0</number> 
    <build>1037124</build> 
    <path>path</path> 
    <kind>kind</kind> 
    </version> 
    </Install> 

Я попытался это с другим способом, также с помощью пасты следующим образом: -

my $version = "6.0";  
    my $file_loc = "data.xml"; 


     my $myXML = <<"XML"; 
<patch> 
<build></build> 
<version></version> 
</patch> 
XML 
my $t = XML::Twig->new(
    twig_handlers => { 
    Install => sub { add_version($myXML, @_); }, } 
)->parsefile($file_loc) or die $!; 

$t->set_pretty_print('indented_c'); 
open my $xml_fh, '>', "$file_loc" or die $!; 
$t->print($xml_fh); 
sub add_version { 
    my ($xml, $t, $install) = @_; 
    my $new_elt = XML::Twig::Elt->parse($xml); 
    for my $number ($t->findnodes('/Install/version/number')) { 
    if($number->trimmed_text eq $version) 
    { 
    $new_elt->paste( $number->parent->last_child->paste => $install); 
    } 
    } 
} 

здесь я получаю следующее сообщение об ошибке

cannot paste an element that belongs to a tree at 

можно достичь этого используя только вставку или мне нужно использовать пасту? Как это сделать в любом случае?

+0

Ваш XML не является корректным. Существует '<' missing ni ваш вход. Хотя общий вопрос очень хороший, пожалуйста, по-прежнему убедитесь, что вы публикуете действительный код и XML (если не задан вопрос «почему это не компилируется»). Благодарю. :) – simbabque

+0

@simbabque Я пропустил «<», возможно, при написании этого вопроса, мой оригинальный файл в порядке :) все еще ищут ответ, хотя я пробовал много вещей, которые не работали. – user3649361

+0

Вы хотите, чтобы я объяснил, почему ваша вторая попытка не делает то, что вы хотите? – simbabque

ответ

2

Вы были на правильном пути с первой попытки, но вам нужно использовать insert_new_element. insert заменяет содержимое элемента и будет создавать атрибуты с выбранным вами синтаксисом.

Вы можете поместить все это в очень простой twig_handler. Захватите все элементы <version>, проверите их текстовый узел <number>, а затем добавьте новый элемент с текстовым узлом внутри и для исправления и patchv значения.

Элемент last_child => '' помещает новый элемент в конец родительского элемента. Это the $optional_position that is documented with paste.

use strict; 
use warnings; 
use XML::Twig; 

my $twig = XML::Twig->new(
    pretty_print => 'indented_a', 
    twig_handlers => { 
     'version' => sub { 
      if ($_->first_child('number')->text() eq '6.0') { 
       $_->insert_new_elt(last_child => 'patch')->set_text('patch'); 
       $_->insert_new_elt(last_child => 'patchv')->set_text('patchv'); 
      } 
     } 
    } 
)->parse(\*DATA); 

$twig->print; 

Выход:

<Install> 
    <version> 
    <number>6.0</number> 
    <build>1037124</build> 
    <path>path</path> 
    <kind>kind</kind> 
    <patch>patch</patch> 
    <patchv>patchv</patchv> 
    </version> 
    <version> 
    <number>7.0</number> 
    <build>1037124</build> 
    <path>path</path> 
    <kind>kind</kind> 
    </version> 
</Install> 

Ваша вторая попытка не работает, по крайней мере по двум причинам:

# this elem get's pasted (1) 
# |    on this positon (2)        
# |    |          in that elem (3) 
$new_elt->paste( $number->parent->last_child->paste => $install); 

В (2) оно должно иметь место, который является строкой. Это что-то вроде first_child, last_child и так далее. They are listed in the docs of paste.

Таким образом, он вычислил возвращаемое значение $number->parent->last_child->paste и использовал его как строку позиции. Но такой возвратной ценности нет, так что это не сработает.

Но это не так далеко.Сообщение об ошибке не может вставить элемент, принадлежащий дереву, из paste, который у вас есть (2), потому что вы вызываете paste() по адресу $number->parent->last_child, и это уже находится в дереве. Это не разрешено. Если вы удалите эту часть, код скомпилируется.

if ($number->trimmed_text eq $version) { 
    $new_elt->paste($install); 
} 

Выход (пропущено):

<Install> 
    <patch> 
    <build></build> 
    <version></version> 
    </patch> 
    <version> 
    <number>6.0</number> 
    <build>1037124</build> 
    <path>path</path> 
    <kind>kind</kind> 
    </version> 

Это еще не то, что мы хотим, потому что <patch> закончилась внутри <Install>, а не внутри <version>. Но у нас уже есть $number из цикла. Это узел <number> внутри узла <version>, поэтому мы можем получить его родительский элемент и использовать его в нашем (3).

$new_elt->paste($number->parent); 

Теперь мы получаем это:

<Install> 
    <version> 
    <patch> 
     <build></build> 
     <version></version> 
    </patch> 
    <number>6.0</number> 
    <build>1037124</build> 
    <path>path</path> 
    <kind>kind</kind> 
    </version> 

Это лучше. Это как-то в нужном месте. Если вы добавите позицию last_child, она заканчивается в конце.

$new_elt->paste(last_child => $number->parent); 

Теперь здесь:

<Install> 
    <version> 
    <number>6.0</number> 
    <build>1037124</build> 
    <path>path</path> 
    <kind>kind</kind> 
    <patch> 
     <build></build> 
     <version></version> 
    </patch> 
    </version> 

Теперь это просто неправильный материал мы наклеивать, потому что мы хотели два элемента <patch> и <patchv>, как с текстом. Это потому, что у вас есть таковые в $myXML.

У вас не может быть двух элементов верхнего уровня. Это недопустимый синтаксис XML.

my $myXML = <<"XML"; 
<patch>patch</patch> 
<patchv>patchv</patchv> 
XML 

Это будет завершаться мусором после элемента документа в строке 2, столбец 0, байты 21 в .... Таким образом, вам придется либо иметь две переменные, либо дважды вызвать paste, либо обернуть их и извлечь дочерние узлы нового корневого элемента.

Но, конечно, это очень сложно. Первый подход, описанный выше, является более чистым.

+0

спасибо за ответ, можете ли вы объяснить, почему второй подход не работает. – user3649361

+0

Также для вашего подхода завернутый 'add_version ($ myXML, @_)' проверить [curry] (https://metacpan.org/pod/curry), что упрощает его. Я объяснил, что [в ответе здесь] (http://stackoverflow.com/a/31728383/1331451). Но я все еще думаю, что вам это не нужно в этой ситуации. – simbabque

+0

yes Я столкнулся с обеими проблемами, о которых вы говорили выше, поэтому я изменил свой XML-файл ввода. Спасибо за хорошее и простое объяснение :) – user3649361

Смежные вопросы