Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

XML Namespaces

by simsrw73 (Novice)
on Oct 11, 2017 at 01:15 UTC ( [id://1201132]=perlquestion: print w/replies, xml ) Need Help??

simsrw73 has asked for the wisdom of the Perl Monks concerning the following question:

I've been out of the loop a long time, and I'm having a little trouble wrapping my head around xml parsing & namespaces. I'm trying to use XPathContext as I've read, but I still have to spell out everything in findnodes() and the context doesn't have any effect at all if I comment it out. What am I doing wrong? Is this the correct approach to parsing the document, to extract a single column of words from the table? The code below does work. I’m just not sure it’s the correct way to do it...
use strict; use warnings; use XML::LibXML; use open ':std', ':encoding(UTF-16)'; use constant XML_WORD_COLUMN => 1; my $filename = 'Concordance.xml'; open my $fh, '<', $filename or die "Can't open $filename: $!"; binmode $fh, ':raw'; # drop PerlIO layers on this handle my $doc = XML::LibXML->load_xml(IO => $fh); # ===> This doesn't matter <=== my $xpc = XML::LibXML::XPathContext->new($doc); $xpc->registerNs( o => "urn:schemas-microsoft-com:office:office" + ); $xpc->registerNs( x => "urn:schemas-microsoft-com:office:excel" + ); $xpc->registerNs( ss => "urn:schemas-microsoft-com:office:spreadshee +t" ); $xpc->registerNs( html => "http://www.w3.org/TR/REC-html40" + ); $xpc->registerNs( def => "urn:schemas-microsoft-com:office:spreadshee +t" ); my $table = $xpc->findnodes(q{//ss:Worksheet[@ss:Name='Sheet 1']/ss:Ta +ble/ss:Row}) or die "Can't find table in Worksheet 'Sheet 1': $!"; foreach my $row ($table->get_nodelist) { my $col_index = 1; foreach my $cell ($row->nonBlankChildNodes) { if ($col_index++ == XML_WORD_COLUMN) { my $d = $cell->find('./ss:Data'); print $d->to_literal, "\n"; } } } __END__
<?xml version="1.0" encoding="utf-8"?> <?mso-application progid="Excel.Sheet"?> <Workbook xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="u +rn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsof +t-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40 +" xmlns="urn:schemas-microsoft-com:office:spreadsheet"> <Worksheet ss:Name="Sheet 1"> <Table> <Row> <Cell> <Data ss:Type="String">Word</Data> </Cell> <Cell> <Data ss:Type="String">Count</Data> </Cell> </Row> <Row> <Cell> <Data ss:Type="String">Aaron</Data> </Cell> <Cell> <Data ss:Type="String">330</Data> </Cell> </Row> <Row> <Cell> <Data ss:Type="String">Aaron’s</Data> </Cell> <Cell> <Data ss:Type="String">25</Data> </Cell> </Row> <Row> <Cell> <Data ss:Type="String">Abaddon</Data> </Cell> <Cell> <Data ss:Type="String">7</Data> </Cell> </Row> <!-- Blah Blah Blah --> </Table> <x:WorksheetOptions> <x:FreezePanes /> <x:FrozenNoSplit /> <x:SplitHorizontal>1</x:SplitHorizontal> <x:TopRowBottomPane>1</x:TopRowBottomPane> <x:ActivePane>2</x:ActivePane> </x:WorksheetOptions> </Worksheet> </Workbook>

Replies are listed 'Best First'.
Re: XML Namespaces
by haukex (Archbishop) on Oct 11, 2017 at 07:51 UTC

    The "right" way to go about it is what you're doing, register all the namespaces in the context object, and use all those prefixes in the XPath expression. This way, you don't have to care if the namespace prefixes in the target XML change (however unlikely that may be), as long as the URIs stay the same.

    Note the documentation of XML::LibXML::Node's findnodes: "There are several possible ways to deal with namespaces in XPath: ... The recommended way is to use the XML::LibXML::XPathContext module ... Another possibility is to use prefixes declared in the queried document (if known)."

    When you write $cell->find('./ss:Data'), that's actually not using the context, instead it's using the second aforementioned option of using the prefixes declared in the document. The "right" way is to use the context object for every query: $xpc->find('./ss:Data',$cell). I tested this by changing every one of the prefixes in your XML file, and the find(nodes) still worked fine. This hopefully also answers your question: yes, the context object is necessary in this scenario.

    By the way, if you have the possibility to save this Excel spreadsheet as XLS or XLSX, there's Spreadsheet::Read, which will parse those formats (via Spreadsheet::ParseExcel resp. Spreadsheet::ParseXLSX).

      Thanks, haukex and everyone else that responded. I am stuck with the xml output as it is produced by another application, Logos Bible Software. (concordance of various bible translations that I'm munging, then comparing against MS Office spellchecker to create custom dictionary files.)

      I've made the change re: $xpc->find. Thanks for pointing that out; that makes sense.

      The code works and this isn't a mission critical script so it shouldn't bother me, but it's still a little unclear and I hate that. But I've been writing some test scripts and some of it is starting to sync in. Thanks for confirming that I'm on the right path.

Re: XML Namespaces
by choroba (Cardinal) on Oct 11, 2017 at 08:06 UTC
    See the "NOTE ON NAMESPACES AND XPATH" in XML::LibXML::Document:
    The recommended way is to use the XML::LibXML::XPathContext module
    ...

    Another possibility is to use prefixes declared in the queried document (if known). If the document declares a prefix for the namespace in question (and the context node is in the scope of the declaration), XML::LibXML allows you to use the prefix in the XPath expression.

    So, if you're sure the documents will always use the same prefixes for the same namespaces, you don't need XPathContext.

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: XML Namespaces
by beech (Parson) on Oct 11, 2017 at 02:01 UTC

    Hi

    What happens if you delete the context (or just omit the ss registration), and delete  xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" from the xml file?

      I can omit ALL of the namespace registration in the code as is and it doesn't make a difference. It still works with all the prefix qualifiers in the findnode() method. If I try to remove the part of the line you mention in the xml... I didn't get far as it was requiring a great deal of editing the remaining xml and I didn't get past the errors. Is it requiring all the prefix qualifiers in the call to findnode because the ss namespace is the same uri as the default namespace? I was expecting it to let me omit the ss prefix because of that. That's the heart of my confusion that I have to specify all those prefixes even where xml doesn't include the prefix.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1201132]
Approved by beech
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (4)
As of 2024-04-16 17:52 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found