Templa...:LibXML(3pm)

πŸ“¦
version 2.17 Β· 2 versions
  • 2.17 Β· debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking (latest)
  • 2.17 Β· ubuntu jammy
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

🌸 NAME 🌸

Okay bestie, meet Template::Plugin::XML::LibXML β€” she's the XML::LibXML Template Toolkit Plugin and she is iconic. That's it, that's the whole name section. πŸ’–


✨ SYNOPSIS ✨

[% USE docroot = XML.LibXML("helloworld.xml") %]

The message is: [% docroot.find("/greeting/text") %]

Like… that's the entire vibe. You USE it, you get a docroot, you go get your little bits of XML out. πŸŽ€


πŸ¦‹ DESCRIPTION πŸ¦‹

So this module is a plugin for the XML::LibXML module, and you use it exactly like literally any other Template Toolkit plugin β€” you just drop a USE statement inside a Template. And the way that USE returns you stuff? It hands you back a reference to the root node of the parsed document. Main character behaviour, honestly. πŸ‘‘

πŸ” Specifying a Data Source

The plugin can take a string, a filename, or a filehandle as the source. And that source can be either XML data or HTML data β€” the HTML gets converted to XHTML internally, no cap. ✨

Your USE statement can take one or more arguments to say what XML should actually get processed. If you only pass one argument, the plugin is gonna have to guess what you meant. 🫣

So here's the guessing order when it's forced to guess (and yes, it's a decreasing-priority situation, it checks in this exact order):

  1. First it looks for an open filehandle β€” if it finds one, it assumes it's a filehandle to a file containing XML. πŸ’…
  2. Failing that, it looks for the chars "<?xml" at the start of what you passed β€” and assumes it's an XML string.
  3. Failing that, it looks for a "<html>" tag β€” and assumes it's an HTML string.
  4. Failing that, it looks for a "<" β€” and assumes it's an XML string without a header.
  5. And if literally none of that hit, it assumes what you passed is the filename to a file containing XML. πŸ™ˆ

But in the interests of being explicit (we love a clear communicator), you can just say the type yourself, using the same names as in the XML::LibXML documentation:

# value contains the xml string
[% USE docroot = XML.LibXML(string => value) %]

# value contains the filename of the xml
[% USE docroot = XML.LibXML(file => value) %]

# value contains an open filehandle to some xml
[% USE docroot = XML.LibXML(fh => value) %]

# value contains the html string
[% USE docroot = XML.LibXML(html_string => value) %]

# value contains the filename of the html
[% USE docroot = XML.LibXML(html_file => value) %]

# value contains an open filehandle to some html
[% USE docroot = XML.LibXML(html_fh => value) %]

Or, if you want, you can use names similar to the ones the XML.XPath plugin uses instead:

# value contains the xml string
[% USE docroot = XML.LibXML(xml => value) %] or
[% USE docroot = XML.LibXML(text => value) %]

# value contains the filename of the xml
[% USE docroot = XML.LibXML(filename => value) %]

# value contains the html string
[% USE docroot = XML.LibXML(html => value) %] or
[% USE docroot = XML.LibXML(html_text => value) %]

# value contains the filename of the html
[% USE docroot = XML.LibXML(html_file => value) %]
[% USE docroot = XML.LibXML(html_filename => value) %]

You can also pass extra arguments which get used to set parser options β€” check XML::LibXML for the deets on those. But I'm gonna repeat the following warning because it's so important:

"LibXML options are global (unfortunately this is a limitation of the underlying implementation, not this interface)...Note that even two forked processes will share some of the same options, so be careful out there!"

Fr fr. Be careful out there. 😭

# turn off expanding entities
[% USE docroot = XML.LibXML("file.xml",
                            expand_entities => 0);

🌳 Obtaining Parts of an XML Document

XML::LibXML gives you two simple mechanisms for grabbing sections of the XML document, and both of them are usable from inside the Template Toolkit. 🫢

Mechanism one: XPath statements. Simple values come out with the findvalue routine:

# get the title attribute of the first page node
# (note xpath starts counting from one not zero)
[% docroot.findvalue("/website/section/page[1]/@title"); %]

# get the text contained within a node
[% htmldoc.findvalue("/html/body/h1[1]/text()") %]

And nodes of the xml document come out with the findnodes:

# get all the pages ('pages' is a list of nodes)
[% pages = docroot.findnodes("/website/section/page") %]

# get the first page (as TT folds single elements arrays
# to scalars, 'page1' is the one node that matched)
[% page1 = docroot.findnodes("/website/section/page[1]") %]

Then you can apply even more xpath commands to those nodes in turn:

# get the title attribute of the first page
[% page1.findvalue("@title") %]

Mechanism two: individual method calls to move around the tree. So the stuff above could also be written like this:

# get the text of the h1 node
 [% htmlroot.documentElement
            .getElementsByLocalName("body").first
            .getElementsByLocalName("h1").first
            .textContent %]

# get the title of the first page
[% docroot.documentElement
          .getElementsByLocalName("section").first
          .getElementsByLocalName("page").first
          .getAttribute("title") %]

Use whichever technique makes the most sense in the particular situation. And yes, you can mix them β€” they play nice:

# get the first page title
[% page1 = htmlroot.findnodes("/website/section/page[1]");
   page1.getAttribute("title") %]

There's much more info waiting for you in XML::LibXML::Node. πŸ“š

🎨 Rendering XML

The simplest way to use this plugin is to just… extract each value you wanna print, by hand:

The title of the first page is '[%
 docroot.findvalue("/website/section/page[1]/@title") %]'

or

The title of the first page is '[%
  docroot.documentElement
         .getElementsByLocalName("section").first
         .getElementsByLocalName("page").first
         .getAttribute("title") %]'

You might wanna discard whitespace from text areas. XPath can remove all leading and following whitespace, and condense all multiple spaces in the text down to single spaces:

<p>[% htmlroot.findvalue("normalize-space(
                           /html/body/p[1]/text()
                        )" | html %]</p>

Note that, like above, when we're inserting the values we extracted back into an XML or HTML document we have to be careful to re-encode the attributes we need to escape with something like the html filter. ⚠️

A slightly more advanced technique is to extract a whole node and use the toString method call on it to convert it to a string of XML. This is most useful when you're extracting an existing chunk of XML en mass, because things like <b> and <i> tags will be passed through correctly and entities will be encoded suitably (for example '"' will be turned into '&quot;'):

# get the second paragraph and insert it here as XML
[% htmlroot.findnodes("/html/body/p[2]").toString %]

And the most powerful technique is to use a view (as defined by the VIEW keyword) to recursively render out the XML. By loading this plugin, present methods get created in the XML::LibXML::Node classes and subclasses. Calling present on a node with a VIEW causes it to be rendered by the view block matching the local name of that node (with any non alphanumeric characters turned to underscores). So a <author> tag will be rendered by the 'author' block. Text nodes will call the 'text' block with the text of the node. πŸ’–

Since the blocks can refer back to both the node it was called with and the view, they can choose to recursively render out its children using the view again. To make this technique even better, three extra methods have been added: starttag (recreate a string of the starting tag, including attributes), endtag (recreate a string of the ending tag) and content (when called with a view, will render by calling all the children of that node in turn with that view). 🧚

This is probably best shown with a well commented example:

# create the view
[% VIEW myview notfound => 'passthru' %]

  # default tag that will recreate the tag 'as is' meaning
  # that unknown tags will 'passed though' by the view
  [% BLOCK passthru; item.starttag;
                     item.content(view);
                     item.endtag;
  END %]

  # convert all sections to headed paragraphs
  [% BLOCK section %]
  <h2>[% item.getAttribute("title") %]</h2>
  <p>[% item.content(view) %]</p>
  [% END %]

  # urls link to themselves
  [% BLOCK url %]
  <a href="[% item.content(view) %]">[% item.content(view) %]</a>
  [% END %]

  # email link to themselves with mailtos
  [% BLOCK email %]
  <a href="mailto:[% item.content(view) %]">[% item.content(view) %]</a>
  [% END %]

  # make pod links bold
  [% BLOCK pod %]
  <b>[% item.content(view) %]</b>
  [% END %]

  # render text, re-encoding the attributes as we go
  [% BLOCK text; item | html; END %]

  # render arrays out
  [% BLOCK list; FOREACH i = item; view.print(i); END ; END %]

[% END %]

# use it to render the paragraphs
[% USE doc = XML.LibXML("mydoc.xml") %]
<html>
 <head>
  <title>[% doc.findvalue("/doc/page[1]/@title") %]</title>
 </head>
 <body>
  [% sections = doc.findnodes("/doc/page[1]/section");
     FOREACH section = sections %]
  <!-- next section -->
  [% section.present(myview);
     END %]
 </body>
</html>

Ate. No crumbs. πŸ”₯


πŸ’€ BUGS πŸ’€

So, in order to detect whether a scalar is an open filehandle (which is the thing that gets used when the USE isn't explicit about its data source), this plugin uses the "openhandle" routine from Scalar::Util. If you don't have Scalar::Util installed, or your version of Scalar::Util is old enough that it doesn't support the "openhandle" routine, then a much cruder "defined(fileno $scalar)" check gets employed instead. πŸ’…

Bugs may be reported either via the CPAN RT at http://rt.cpan.org/NoAuth/Bugs.html?Dist=Template-Plugin-XML-LibXML or via the Template Toolkit mailing list: http://www.template-toolkit.org/mailman/listinfo/templates or direct to the author.


πŸ‘‘ AUTHOR πŸ‘‘

Written by Mark Fowler <mark@twoshortplanks.com> 🫢

Copyright Mark Fowler 2002-3, all rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

This module wouldn't have been possible without the wonderful work that has been put into the libxml library by the gnome team, or the equally wonderful work put in by Matt Sergeant and Christian Glahn in creating XML::LibXML. 🌈


πŸŽ€ SEE ALSO πŸŽ€

  • Template
  • Template::Plugin
  • XML::LibXML
  • XML::LibXML::Node

On a similar note, you may want to see Template::Plugin::XML::XPath too. πŸ‘€

The t/test directory in the Template-Plugin-XML-LibXML distribution contains all the example XML files discussed in this documentation. πŸ“


And that's a wrap bestie β€” this page came from perl v5.34.0 and it's dated 2022-07-04. Stan a classic. πŸ’œβœ¨

πŸ“¦ where this page comes from