Обновление значения атрибута 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>