Обновление значения атрибута xml на основе других с помощью Perl

Это мой пример XML-файла

<manifest>
 <default>
    <remote>remote1</remote>
    <revision>rev1</revision>
 </default>
 <project>
    <name>common</name>
    <path>opensource/device</path>
    <revision>sa</revision>
    <x-ship>oss</x-ship>
 </project>
 <project>
   <name>external</name>
   <path>source/tp</path>
   <x-ship>none</x-ship>
 </project>
 <project>
   <name>ws</name>
   <path>opensource/ws</path>
   <remote>nj</remote>
   <revision>myno</revision>
   <x-ship>none</x-ship>
 </project>
</manifest>

В этом мне нужно обновить значение ревизии только тогда, когда <path> содержит строку с открытым исходным кодом.

Я много искал, но не смог найти ничего полезного для этого, я мог бы изменить значение в зависимости от позиции, как показано ниже, может кто-нибудь помочь мне обновить это? Или дайте мне знать, если есть лучшая библиотека Perl для этого.

#!/usr/bin/perl

use strict;
use warnings;

use XML::Simple;

my $xml_file = 'dev.xml';

my $xml = XMLin(
    $xml_file,
    KeepRoot => 1,
    ForceArray => 1,
);

$xml->{manifest}->[0]->{project}->[2]->{revision} = 'kyo';

XMLout(
    $xml,
    KeepRoot => 1,
    NoAttr => 1,
    OutputFile => $xml_file,
);

2 ответа

Решение

Есть определенно кривая обучения, но XML::Twig и синтаксис XPath может справиться с этим довольно хорошо. Следующее демонстрирует, что для изменения поддельных данных, которые вы предоставили.

Обратите внимание, что одна из важных особенностей ветки - это возможность анализировать данные по мере необходимости, вместо того, чтобы загружать чрезвычайно большой XML-файл полностью в память. Это не может быть ограничением в вашем случае, но является важной особенностью для некоторых.

use strict;
use warnings;

use XML::Twig;

my $data = do { local $/; <DATA> };

my $t= XML::Twig->new( 
    twig_handlers => {
        q{project[string(path) =~ /\bopensource\b/]/revision} => \&revision,
    },
    pretty_print => 'indented',
);
$t->parse( $data );
$t->print;

sub revision {
    my ($twig, $rev) = @_;
    $rev->set_text("open source - " . $rev->text());
}

__DATA__
<manifest>
 <default>
    <remote>remote1</remote>
    <revision>rev1</revision>
 </default>
 <project>
    <name>common</name>
    <path>NOTopensource/device</path>
    <revision>sa</revision>
    <x-ship>oss</x-ship>
 </project>
 <project>
   <name>external</name>
   <path>source/tp</path>
   <x-ship>none</x-ship>
 </project>
 <project>
   <name>ws</name>
   <path>opensource/ws</path>
   <remote>nj</remote>
   <revision>myno</revision>
   <x-ship>none</x-ship>
 </project>
</manifest>

Выход:

Вы заметите, что последняя редакция имеет open source - префикс к нему.

<manifest>
  <default>
    <remote>remote1</remote>
    <revision>rev1</revision>
  </default>
  <project>
    <name>common</name>
    <path>NOTopensource/device</path>
    <revision>sa</revision>
    <x-ship>oss</x-ship>
  </project>
  <project>
    <name>external</name>
    <path>source/tp</path>
    <x-ship>none</x-ship>
  </project>
  <project>
    <name>ws</name>
    <path>opensource/ws</path>
    <remote>nj</remote>
    <revision>open source - myno</revision>
    <x-ship>none</x-ship>
  </project>
</manifest>

Приложение о родственных элементах:

Да, в ветке есть методы для перехода к близлежащим элементам xml. Например, если я хочу получить название ревизии, которую я редактировал, и поместить ее в новый текст, я мог бы сделать следующее:

sub revision {
    my ($twig, $rev) = @_;
    my $name = $rev->parent()->first_child("name");
    $rev->set_text("open source - " $name->text() . ' - '. $rev->text());
}

Обратите внимание ws теперь добавлено в отредактированный тег ревизии:

  <project>
    <name>ws</name>
    <path>opensource/ws</path>
    <remote>nj</remote>
    <revision>open source - ws - myno</revision>
    <x-ship>none</x-ship>
  </project>

Этот метод перемещения ветки к соседним элементам часто может быть полезным способом фильтрации. Я легко мог бы сделать то же самое, чтобы заставить эту ветвь иметь путь, содержащий opensource, но установка этого требования в xpath для обработчика удобна, если вы знакомы с синтаксисом xpath.

Также обратите внимание, в моем примере выше, я предполагаю, что есть брат типа name, Обычно я проверял бы перед звонком ->text() или можно получить ошибку.

Приложение об атрибутах:

Что касается вашего крайнего случая с альтернативным форматом:

<project path="opensource" revision="apple" name="platform" x-ship="none"/>

Выше приведены те же данные, что и в других проектах, но вместо значений child элементы, они attributes, Это также особенность XML, но она другая и поэтому должна обрабатываться по-другому.

Ниже приведено редактирование первоначально предложенного сценария, который добавляет новый обработчик для проектов, которые содержат атрибут пути по сравнению с дочерним:

use strict;
use warnings;

use XML::Twig;

my $data = do { local $/; <DATA> };

my $t= XML::Twig->new( 
    twig_handlers => {
        q{project[string(path) =~ /\bopensource\b/]/revision} => \&revision,
        q{project[@path =~ /\bopensource\b/]} => \&project,
    },
    pretty_print => 'indented',
);
$t->parse( $data );
$t->print;

sub revision {
    my ($twig, $rev) = @_;
    $rev->set_text("open source - " . $rev->text());
}

sub project {
    my ($twig, $project) = @_;

    $project->set_att(
        revision => 'open source - ' . $project->{att}{revision},
    );
}

__DATA__
<manifest>
 <default>
    <remote>remote1</remote>
    <revision>rev1</revision>
 </default>
 <project>
    <name>common</name>
    <path>NOTopensource/device</path>
    <revision>sa</revision>
    <x-ship>oss</x-ship>
 </project>
 <project path="opensource" revision="apple" name="platform" x-ship="none"/>
 <project>
   <name>external</name>
   <path>source/tp</path>
   <x-ship>none</x-ship>
 </project>
 <project>
   <name>ws</name>
   <path>opensource/ws</path>
   <remote>nj</remote>
   <revision>myno</revision>
   <x-ship>none</x-ship>
 </project>
</manifest>

И просто для того, чтобы вы могли что-то сравнивать и учиться, вот тот же код, но с фильтрацией, выполненной в обработчике, по сравнению с использованием xpath:

    twig_handlers => {
        q{project[string(path) =~ /\bopensource\b/]/revision} => \&revision,
        q{project} => \&project,
    },

...

sub project {
    my ($twig, $project) = @_;

    if ($project->{att}{path} && $project->{att}{path} =~ /\bopensource\b/) {
        $project->set_att(
            revision => 'open source - ' . $project->{att}{revision},
        );
    }
}

В качестве учебного упражнения я решил продублировать вышеуказанное решение, используя XML::LibXML, Я также использовал этот пост perlmonks как ресурс, чтобы начать работу, так как документы по модулям трудно найти: Stepping up from XML::Simple to XML::LibXML,

use strict;
use warnings;

use XML::LibXML;

my $data = do { local $/; <DATA> };

my $dom = XML::LibXML->load_xml(string => $data);

for my $project ($dom->findnodes('//project')) {
    if (my ($path) = $project->findnodes("./path")) {
        next if $path->textContent() !~ /\bopensource\b/;
        my ($revision) = $project->findnodes("./revision")
            or next;

        my $oldval = $revision->textContent();
        $revision->removeChildNodes();
        $revision->appendText('open source - ' . $oldval);

    } elsif ( my $pathatt = $project->getAttribute('path') ) {
        next if $pathatt !~ /\bopensource\b/;
        $project->setAttribute('revision', 'open source - ' . $project->getAttribute('revision'))
    }
}

print $dom->documentElement()->toString();

__DATA__
<manifest>
 <default>
    <remote>remote1</remote>
    <revision>rev1</revision>
 </default>
 <project>
    <name>common</name>
    <path>NOTopensource/device</path>
    <revision>sa</revision>
    <x-ship>oss</x-ship>
 </project>
 <project path="opensource" revision="apple" name="platform" x-ship="none"/>
 <project>
   <name>external</name>
   <path>source/tp</path>
   <x-ship>none</x-ship>
 </project>
 <project>
   <name>ws</name>
   <path>opensource/ws</path>
   <remote>nj</remote>
   <revision>myno</revision>
   <x-ship>none</x-ship>
 </project>
</manifest>

Результат:

<manifest>
 <default>
    <remote>remote1</remote>
    <revision>rev1</revision>
 </default>
 <project>
    <name>common</name>
    <path>NOTopensource/device</path>
    <revision>sa</revision>
    <x-ship>oss</x-ship>
 </project>
 <project path="opensource" revision="open source - apple" name="platform" x-ship="none"/>
 <project>
   <name>external</name>
   <path>source/tp</path>
   <x-ship>none</x-ship>
 </project>
 <project>
   <name>ws</name>
   <path>opensource/ws</path>
   <remote>nj</remote>
   <revision>open source - myno</revision>
   <x-ship>none</x-ship>
 </project>
</manifest>
Другие вопросы по тегам