Extracting data from an XML document that uses namespaces
Asked Answered
P

2

9

I have some XML files where I want to use some information from them. I have written a code that reads those files and then looks for some conditions.

The problem is that these XML file begins with

   <SquishReport version="2.1" xmlns="http://www.froglogic.com/XML2">

and Perl could not read them (at least in my code!). But When I am appending these lines in the first line of XML file

   <?xml version="1.0" encoding="UTF-8"?>
   <?xml-stylesheet type="text/xsl"?>

works very well.

Some lines from my XML file test.xml:

<SquishReport version="2.1" xmlns="http://www.froglogic.com/XML2">
   <test name="TEST">
      <prolog time="2015-10-01T03:45:22+02:00"/>
      <test name="tst_start_app">
          <prolog time="2015-02-01T03:45:23+02:00"/>
          <message line="38" type="LOG" file="C:\squish\test\sources.py" time="2015-02-01T03:45:23+02:00">
              <description>
                <![CDATA[>>  >>  >> start: init (global) - testcase C:\squish\test\tst_start_app]]></description>
          </message>
       </test>
   </test>
</SquishReport>

and the Perl code for reading the XML file is:

use strict;
use warnings;
use feature 'say';
use XML::LibXML;

# Parse the XML
my $xml = XML::LibXML->load_xml(location => 'test.xml');

# Iterate the entries
for my $entry ($xml->findnodes('/SquishReport/test/test')) {
    my $key = $entry->findvalue('@name');
    say "$key";
}
Philoprogenitive answered 27/10, 2015 at 14:31 Comment(2)
Possible duplicate of Why does XML::LibXML find no nodes for this xpath query when using a namespaceDamned
Please don't close as duplicate of that question. That question's XML is illegal, complicating the issue, and making the solution to that question irrelevant to this question. I'd like to have this question available as a clean example.Spook
S
12

The root node of that document is an element which has name SquishReport in the http://www.froglogic.com/XML2 namespace. Concisely, we can say the root node is a

{http://www.froglogic.com/XML2}SquishReport


When one uses SquishReport (as opposed to prefix:SquishReport) in an XPath, that tries to match an element which has name SquishReport in the null namespace. Concisely, we can say it attempts to match a

{}SquishReport


To specify the namespace, one uses prefixes defined in a context, as follows:

use strict;
use warnings;
use feature qw( say );

use XML::LibXML               qw( );
use XML::LibXML::XPathContext qw( );

my $xpc = XML::LibXML::XPathContext->new();
$xpc->registerNs(sr => 'http://www.froglogic.com/XML2');

my $doc = XML::LibXML->load_xml( location => 'test.xml' );
for my $entry ($xpc->findnodes('/sr:SquishReport/sr:test/sr:test', $doc)) {
    my $key = $entry->findvalue('@name');
    say $key;
}


Note: The prefix used in the XPath have no relation to the prefixes used in the XML document (if any). You are expected to know the namespace in which resides the elements for which you are searching, but not the prefixes used by a given document.

Spook answered 27/10, 2015 at 14:35 Comment(2)
Thanks a lot for great answer!!Philoprogenitive
This is really clean and clear! I tend to use modules built out of XML::LibXML but you are making it look easy here. XML is never going to go away and perl has some really powerful tools for dealing with it.Belligerent
B
0

Perl has so many excellent XML tools - thanks to all the module developers and libxml2, XML almost seems easy. One of those tools is XML::Dataset - a convenience "scaffolding" module that builds on XML::LibXML and uses a "profile" markup language to grab data from XML sources (NB: The profile mark-up is sensitive to whitespace and line endings).

e.g.:

use XML::Dataset;
use DDP;

my $xml = "Squish.xml" ; 
open my $fh, "<", $xml or die "aiiieee!";
my $test_data = do { local $/; <$fh> };

# describe the data using XML::Dataset simplified markup:
my $data_profile
    = q(
          SquishReport
            test
              test
                 name = dataset:name);

# parse it with XML::Dataset profile
my $parsed_data = parse_using_profile($test_data, $data_profile);

# view the element with Data::Printer
foreach my $element ( $parsed_data->{name}){
     p $element ;
};

Squish.xml:

<SquishReport version="2.1" xmlns="http://www.froglogic.com/XML2">
   <test name="TEST">
      <prolog time="2015-10-01T03:45:22+02:00"/>
      <test name="tst_start_app">
          <prolog time="2015-02-01T03:45:23+02:00"/>
          <message line="38" type="LOG" file="C:\squish\test\sources.py" time="2015-02-01T03:45:23+02:00">
              <description>
                <![CDATA[>>  >>  >> start: init (global) - testcase C:\squish\test\tst_start_app]]></description>
          </message>
       </test>
   </test>
</SquishReport>

Output:

\ [
    [0] {
        name   "tst_start_app"
    }
]
Belligerent answered 27/10, 2015 at 16:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.