【问题标题】:Updating xml attribute value based on other with Perl使用 Perl 基于其他更新 xml 属性值
【发布时间】:2014-05-25 13:16:36
【问题描述】:

这是我的示例 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>

只有当&lt;path&gt; 包含“opensource”字符串时,我才需要更新修订的值。

我搜索了很多,但找不到任何有用的东西来实现这一点,我可以根据以下位置修改值,有人可以帮我更新吗?或者让我知道是否有更好的 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,
);

【问题讨论】:

    标签: perl xml-twig xml-simple


    【解决方案1】:

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

    请注意,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 类型的兄弟姐妹。通常我会在致电-&gt;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},
            );
        }
    }
    

    【讨论】:

    • 非常感谢,它是一个很好的解决方案,还有像 get_text 这样的东西来读取值(就像你使用 $rev->set_text 一样),因为我需要从一个 xml 读取并将值写入“opensource/blah/blah/ 与 b/w 两个匹配的其他 xml。再次感谢!
    • 阅读我的附录,了解如何将树枝遍历到附近的兄弟元素。
    • 我的其他 xml 文件在单行中有项目: 和上面的脚本没有'无法使用这种风格的 xml,是否有任何指针可以解决此问题?感谢所有的帮助。
    • 我创建了第二个附录来讨论您的极端情况以及如何使用 xml 元素属性。
    • 非常感谢米勒,帮了大忙。
    【解决方案2】:

    作为学习练习,我决定使用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>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-01
      • 2017-05-03
      • 1970-01-01
      • 2021-01-22
      • 1970-01-01
      相关资源
      最近更新 更多