質問

I have tried XML Simple, but due to the fact that it just reads the XML into a hash, the output is useless when run against the DTD. Learned it the hard way.

So I have adopted XML::LibXML, the funny thing is the requirements that I found most difficult to accomplish with XML::Simple, were the easiest. However I am finding that some of easier things to do in XML::Simple are proving to be impossible (with my lack of understanding of DOM, and some confusing behaviors with XML::LibXML).

So here is a sample of the XML:

    <Metadata>
        <ADI Name="movie" />
        <App_Data Name="Something I don't care about" value="who cares" />
        <App_Data Name="Something I don't care about as well" value="who cares" />
        <App_Data Name="ChangeMe" Value="" />
    </Metadata>
    <Metadata>
        <ADI Name="photo" />
        <App_Data Name="Something I don't care about" value="who cares" />
        <App_Data Name="Something I don't care about as well" value="who cares" />
        <App_Data Name="ChangeMe" Value="" />
    </Metadata>
    <Metadata>
        <ADI Name="poster" />
        <App_Data Name="Something I don't care about" value="who cares" />
        <App_Data Name="Something I don't care about as well" value="who cares" />
        <App_Data Name="ChangeMe" Value="" />
    </Metadata>

Note: I have simplified this for the use in this post.

So basically I have to use the Name field in the <ADI> tag to confirm that I am in the correct area of the DOM to make the change to the Value attribute in the <App_Data> tag that who's Name is ChangeMe.

This is the snippet of code that I have come up with... and failed miserably.

#!/usr/bin/perl

use strict;
use XML::LibXML;

my $xml2 = XML::LibXML->new();
my $data = $xml2->parse_file("adi.xml");
my $movie;
my $photo;
my $poster;

foreach my $test ($data->findnodes('//Metadata')) {
    if ($test->findvalues('./ADI/@Name[.="movie"]')){
        $movie = 1;
        undef $photo;
        undef $poster;
    }
    elsif ($test->findvalues('./ADI/@Name[.="photo"]')){
        undef $movie;
        $photo = 1;
        undef $poster;
    }
    elsif ($test->findvalues('./ADI/@Name[.="poster"]')){
        undef $movie;
        undef $photo;
        $poster = 1;
    }
}

I don't have anything beyond this, because it doesn't work. I get an error something along the lines of

Can't locate object method "findvalues" via package "XML::LibXML::Element"

As a bonus to this question, what if I wanted to completely remove the <Metadata> (and all children) for the ones that contained photo and/or poster?

役に立ちましたか?

解決

Give this a try for starters.

#!/usr/bin/perl

use strict;
use XML::LibXML;

my $xml2 = XML::LibXML->new();
my $data = $xml2->parse_file("adi.xml");

foreach my $test ($data->findnodes('//Metadata')) {
    if ($test->findnodes('./ADI/@Name[.="movie"]')){
        print "movie\n";
    }
    elsif ($test->findnodes('./ADI/@Name[.="photo"]')){
        print "photo\n";
    }
    elsif ($test->findnodes('./ADI/@Name[.="poster"]')){
        print "poster\n";
    }
}

There is no findvalues method. What you want to do is use findnodes, which will return to you a list of nodes matching the XPath expression. Once you have that, you can iterate over the list and extract any of the data you need, much like you're already doing for Metadata.

Also, I'm assuming your XML file has a single root-level element. I used the modified version below to test the above code.

<root>
   <Metadata>
        <ADI Name="movie" />
        <App_Data Name="Something I don't care about" value="who cares" />
        <App_Data Name="Something I don't care about as well" value="who cares" />
        <App_Data Name="ChangeMe" Value="" />
    </Metadata>
    <Metadata>
        <ADI Name="photo" />
        <App_Data Name="Something I don't care about" value="who cares" />
        <App_Data Name="Something I don't care about as well" value="who cares" />
        <App_Data Name="ChangeMe" Value="" />
    </Metadata>
    <Metadata>
        <ADI Name="poster" />
        <App_Data Name="Something I don't care about" value="who cares" />
        <App_Data Name="Something I don't care about as well" value="who cares" />
        <App_Data Name="ChangeMe" Value="" />
    </Metadata>
</root>

I find this cheatsheet useful for Perl's LibXML library.

他のヒント

  • Where did you find findvalues? Docs:

    @nodes = $node->findnodes( $xpath_expression );
    $result = $node->find( $xpath );
    print $node->findvalue( $xpath );
    
  • Why so many uses of .?

    ./ADI/@Name[.="movie"]
    

    should probably be

    ADI[@Name="movie"]
    
  • You have more than one Metadata element, but you set the variables based on the last one alone.

  • You shouldn't be using three different variables to store one piece of information.


#!/usr/bin/perl

use strict;
use warnings;

use XML::LibXML qw( );

my $parser = XML::LibXML->new();
my $doc = $parser->parse_file("adi.xml");

for my $metadata ($doc->findnodes('//Metadata')) {
    my ($adi_type) = $metadata->find('ADI/@Name')
       or next;

    my ($app_data) = $metadata->find('App_Data[@Name="ChangeMe"]');

    if ($adi_type eq 'movie') {
       ...
    }
    elsif ($adi_type eq 'photo') {
       ...
    }
    elsif ($adi_type eq 'poster') {
       ...
    }
}

Or you could even use:

my ($movie_adi) = $doc->findnodes('//Metadata[ADI/@Name="movie"]');
my ($movie_app_data) = $movie_adi->findnodes('App_Data[@Name="ChangeMe"]');
...

my ($photo_adi) = $doc->findnodes('//Metadata[ADI/@Name="photo"]');
my ($photo_app_data) = $photo_adi->findnodes('App_Data[@Name="ChangeMe"]');
...

my ($poster_adi) = $doc->findnodes('//Metadata[ADI/@Name="poster"]');
my ($poster_app_data) = $poster_adi->findnodes('App_Data[@Name="ChangeMe"]');
...

A lot can be done in the XPath expression to find the nodes you're interested in.

This program will do what you ask. I have added a root element <root> to your data to make it into a well-formed XML document.

use strict;
use warnings;

use XML::LibXML;

my $doc = XML::LibXML->load_xml(location => 'adi.xml', no_blanks => 1);

for my $metadata ($doc->findnodes('//Metadata')) {
  if ( $metadata->findnodes('ADI[@Name = "movie" or @Name = "photo"]') ) {
    $metadata->parentNode->removeChild($metadata);
  }
}

print $doc->toString(1);

output

<?xml version="1.0"?>
<root>
  <Metadata>
    <ADI Name="poster"/>
    <App_Data Name="Something I don't care about" value="who cares"/>
    <App_Data Name="Something I don't care about as well" value="who cares"/>
    <App_Data Name="ChangeMe" Value=""/>
  </Metadata>
</root>
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top