使用Perl更新基于其他的xml属性值 [英] Updating xml attribute value based on other with Perl

查看:94
本文介绍了使用Perl更新基于其他的xml属性值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我的示例xml文件

This is my sample xml file

<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>中包含开源"字符串时,我才需要更新修订版的值.

In this I need to update the value of revision only when <path> has "opensource" string in it.

我进行了很多搜索,但找不到任何有用的方法来实现此目的,我可以根据以下位置修改值,有人可以帮助我进行更新吗?或者让我知道是否有更好的Perl库可以做到这一点.

I searched a lot but couldn't find anything useful to achieve this, I could modify the value based on the position as below, can anyone help me updating this? Or let me know if there is a better Perl library to do this.

#!/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,
);

推荐答案

肯定有一个学习曲线,但是

There is definitely a learning curve, but XML::Twig and XPath syntax can handle this quite well. The following demonstrates that for a variation of the fake data that you provided.

请注意,Twig的一大特色是能够在您进行数据时解析数据,而不必将一个非常大的XML文件完全加载到内存中.对于您的情况,这可能不是一个限制,但是对于某些人来说,这是一个重要的功能.

Please note that one of the big features to twig is the ability to parse data as you go instead of having to load an extremely large XML file entirely into memory. That may not be a limitation in your case, but is an important feature for some.

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 -前缀.

You'll notice that the last revision has open source - prefixed to it.

<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元素.例如,如果我想拉出我正在编辑的修订的名称,并将其放置在新文本中,则可以执行以下操作:

Yes, there are methods to traverse within a twig to nearby xml elements. For example, if I wanted to pull the name of the revision I was editting, and place that in the new text, I could do the following:

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

请注意,现在已将ws添加到已编辑的修订标签中:

Note the ws now added to the edited revision tag:

  <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中为处理程序设置该要求很方便.

This method of traversing the twig to nearby elements can often be a useful way of filtering. I easily could've done the same to enforce this branch be one with a path that contained opensource, but setting that requirement in the xpath for the handler is convenient if one is familiar with the xpath syntax.

还要注意,在上面的示例中,我假设存在类型为name的同级.通常,我会在调用->text()之前进行检查,以确保可能会出错.

Also note, in my above example, I assume there is a sibling of type name. Normally I would check to make sure before calling ->text() or one might get an error.

关于属性的附录

关于使用其他格式的特殊情况:

Concerning your edge case with an alternative format:

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

上面包含与其他项目相同的数据,但是值不是child元素,而是attributes.这也是XML的功能,但是它是不同的,因此必须以不同的方式进行处理.

The above contains the same data as other projects, but instead of the values being child elements, they are attributes. This is also a feature of XML, but it is different and so must be handled in a different way.

以下是对最初建议的脚本的编辑,该脚本为包含path属性而不是child的项目添加了新的处理程序:

The following is an edit of the originally suggested script that adds a new handler for projects that contain a path attribute versus a child:

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相比:

And just to give you something to compare and learn from, here is the same code but with the filtering done in the handler versus using 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},
        );
    }
}

这篇关于使用Perl更新基于其他的xml属性值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆