2

这是我的示例 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,
);
4

2 回答 2

4

肯定有一个学习曲线,但是XML::TwigXPath 语法可以很好地处理这个问题。以下演示了您提供的假数据的变体。

请注意,twig 的一大特点是能够随时解析数据,而不必将非常大的 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},
        );
    }
}
于 2014-03-20T20:27:08.817 回答
0

作为学习练习,我决定使用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>
于 2014-03-22T08:10:29.673 回答