XML::Smart(3pm)

πŸ“¦
version 1.78 Β· 5 versions
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

πŸ’– XML::Smart(3pm) β€” part 1 πŸ’–

Okay bestie, this is the XML::Smart(3pm) manual page, part 1, and we are going to get through it together. Grab a beverage. πŸ’…

🌸 NAME 🌸

XML::Smart β€” a smart, easy and powerful way to access or create XML from fiels, data and URLs. Yes, fiels is exactly what it says; I am preserving the typo like a museum artifact. πŸŽ€

πŸŽ€ VERSION πŸŽ€

This is Version 1.78 πŸ’–

✨ SYNOPSIS ✨

Okay so XML::Smart gives you an easy way to access/create XML data. It’s based on a HASH tree created from the XML data, and it enables dynamic access to that tree through the standard Perl syntax for Hash and Array β€” without you necessarily caring about which one you’re working with. In other words: each point in the tree works as a Hash and an Array at the same time! It’s giving dual-threat main character energy, fr fr. πŸ˜­πŸ‘‘

This module additionally provides special resources such as: search for nodes by attribute, select an attribute value in each multiple node, change the returned format, and so on. Very β€œI have options and I’m not afraid to use them.” πŸ¦‹

The module also automatically handles binary data (encoding/decoding to/from base64), CDATA (like contents with <tags>) and Unicode. It can be used to create XML files, load XML from the Web (just by using an URL as the file path) and has an easy way to send XML data through sockets β€” just adding the length of the data in the <?xml?> header. Obsessed. πŸ’–

You can use XML::Smart with XML::Parser, or with the 2 standard parsers of XML::Smart:

  • XML::Smart::Parser
  • XML::Smart::HTMLParser

XML::Smart::HTMLParser can be used to load/parse wild/bad XML data, or HTML tags. Iconic for chaotic data. πŸ’…

πŸŽ€ Tutorial and F.A.Q. πŸŽ€

You can find some extra documents about XML::Smart at:

  • XML::Smart::Tutorial β€” Tutorial and examples for XML::Smart.
  • XML::Smart::FAQ β€” Frequently Asked Questions about XML::Smart.

🌈 USAGE 🌈

Here’s the group-chat cheat sheet, bestie. Copy these exactly; they’re literally the whole vibe:

         ## Create the object and load the file:
         my $XML = XML::Smart->new('file.xml') ;

         ## Force the use of the parser 'XML::Smart::Parser'.
         my $XML = XML::Smart->new('file.xml' , 'XML::Smart::Parser') ;

         ## Get from the web:
         my $XML = XML::Smart->new('http://www.perlmonks.org/index.pl?node_id=16046') ;

         ## Cut the root:
         $XML = $XML->cut_root ;

         ## Or change the root:
         $XML = $XML->{hosts} ;

         ## Get the address [0] of server [0]:
         my $srv0_addr0 = $XML->{server}[0]{address}[0] ;
         ## ...or...
         my $srv0_addr0 = $XML->{server}{address} ;

         ## Get the server where the attibute 'type' eq 'suse':
         my $server = $XML->{server}('type','eq','suse') ;

         ## Get the address again:
         my $addr1 = $server->{address}[1] ;
         ## ...or...
         my $addr1 = $XML->{server}('type','eq','suse'){address}[1] ;

         ## Get all the addresses of a server:
         my @addrs = @{$XML->{server}{address}} ;
         ## ...or...
         my @addrs = $XML->{server}{address}('@') ;

         ## Get a list of types of all the servers:
         my @types = $XML->{server}('[@]','type') ;

         ## Add a new server node:
         my $newsrv = {
         os      => 'Linux' ,
         type    => 'Mandrake' ,
         version => 8.9 ,
         address => [qw(192.168.3.201 192.168.3.202)]
         } ;

         push(@{$XML->{server}} , $newsrv) ;

         ## Get/rebuild the XML data:
         my $xmldata = $XML->data ;

         ## Save in some file:
         $XML->save('newfile.xml') ;

         ## Send through a socket:
         print $socket $XML->data(length => 1) ; ## show the 'length' in the XML header to the
                                                 ## socket know the amount of data to read.

         __DATA__
         <?xml version="1.0" encoding="iso-8859-1"?>
         <hosts>
           <server os="linux" type="redhat" version="8.0">
             <address>192.168.0.1</address>
             <address>192.168.0.2</address>
           </server>
           <server os="linux" type="suse" version="7.0">
             <address>192.168.1.10</address>
             <address>192.168.1.20</address>
           </server>
           <server address="192.168.2.100" os="linux" type="conectiva" version="9.0"/>
         </hosts>

πŸ‘‘ METHODS πŸ‘‘

Okay, now the actual methods. Stay with me; this is where the magic happens. ✨

🧚 new (FILE|DATA|URL , PARSER , OPTIONS)

Create a XML object. Yes, β€œa XML object”; grammar is not the assignment. πŸ’…

πŸ“ Arguments πŸ“

🌸 FILE|DATA|URL 🌸

The first argument can be:

  • XML data as string.
  • File path.
  • File Handle (GLOB).
  • URL (Need LWP::UserAgent).

If not passed, a null XML tree is started, where you should create your own XML data, than build/save/send it. πŸŽ€

🌈 PARSER (optional) 🌈

Set the XML parser to use. Options:

  • XML::Parser
  • XML::Smart::Parser
  • XML::Smart::HTMLParser

XML::Smart::Parser can only handle basic XML data (not supported PCDATA, and any header like: ENTITY, NOTATION, etc...), but is a good choice when you don’t want to install big modules to parse XML, since it comes with the main module. But it still can handle CDATA and binary data. πŸ¦‹

** See "PARSING HTML as XML" for XML::Smart::HTMLParser.

Aliases for the options:

  • SMART|REGEXP => XML::Smart::Parser
  • HTML => XML::Smart::HTMLParser

Default:

If not set it will look for XML::Parser and load it. If XML::Parser can't be loaded it will use XML::Smart::Parser, which is actually a clone of XML::Parser::Lite with some fixes. πŸ‘‘

✨ OPTIONS ✨

You can force the uper case and lower case for tags (nodes) and arguments (attributes), and other extra things.

  • lowtag β€” Make the tags lower case.
  • lowarg β€” Make the arguments lower case.
  • upertag β€” Make the tags uper case.
  • uperarg β€” Make the arguments uper case.
  • arg_single β€” Set the value of arguments to 1 when they have a undef value.
** This option will work only when the XML is parsed by XML::Smart::HTMLParser, since it accept arguments without values:
                             my $xml = new XML::Smart(
                             '<root><foo arg1="" flag></root>' ,
                             'XML::Smart::HTMLParser' ,
                             arg_single => 1 ,
                             ) ;

In this example the option "arg_single" was used, what will define flag to 1, but arg1 will still have a null string value ("").

Here's the tree of the example above:

                             'root' => {
                                         'foo' => {
                                                    'flag' => 1,
                                                    'arg1' => ''
                                                  },
                                       },
  • use_spaces β€” Accept contents that have only spaces.
  • on_start (CODE) *optional β€” Code/sub to call on start a tag.
** This will be called after XML::Smart parse the tag, should be used only if you want to change the tree.
  • on_char (CODE) *optional β€” Code/sub to call on content.
** This will be called after XML::Smart parse the tag, should be used only if you want to change the tree.
  • on_end (CODE) *optional β€” Code/sub to call on end a tag.
** This will be called after XML::Smart parse the tag, should be used only if you want to change the tree.

** This options are applied when the XML data is loaded. For XML generation see data() OPTIONS.

πŸ’– Examples of use πŸ’–

         my $xml_from_url = XML::Smart->new("http://www.perlmonks.org/index.pl?node_id=16046") ;

         ...

         my $xml_from_str = XML::Smart->new(q`<?xml version="1.0" encoding="iso-8859-1" ?>
         <root>
           <foo arg="xyz"/>
         </root>
         `) ;

         ...

         my $null_xml = XML::Smart->new() ;

         ...

         my $xml_from_html = XML::Smart->new($html_data , 'html' ,
         lowtag => 1 ,
         lowarg => 1 ,
         on_char => sub {
                      my ( $tag , $pointer , $pointer_back , $cont) = @_ ;
                      $pointer->{extra_arg} = 123 ; ## add an extrar argument.
                      $pointer_back->{$tag}{extra_arg} = 123 ; ## Same, but using the previous pointer.
                      $$cont .= "\n" ; ## append data to the content.
                    }
         ) ;

πŸͺ„ apply_dtd (DTD , OPTIONS)

Apply the DTD to the XML tree.

DTD can be a source, file, GLOB or URL.

This method is useful if you need to have the XML generated by data() formated in a specific DTD, so, elements will be nodes automatically, attributes will be checked, required elements and attributes will be created, the element order will be set, etc...

πŸ’œ OPTIONS πŸ’œ

  • no_delete BOOL β€” If TRUE tells that not defined elements and attributes in the DTD won't be deleted from the XML tree.

πŸŽ€ Example of use πŸŽ€

         $xml->apply_dtd(q`
         <!DOCTYPE cds [
         <!ELEMENT cds (album+)>
         <!ATTLIST cds
                   creator  CDATA
                   date     CDATA #REQUIRED
                   type     (a|b|c) #REQUIRED "a"
         >
         <!ELEMENT album (#PCDATA)>
         ]>
         ` ,
         no_delete => 1 ,
         );

πŸ¦‹ args() πŸ¦‹

Return the arguments names (not nodes).

πŸ¦‹ args_values() πŸ¦‹

Return the arguments values (not nodes).

πŸ¦‹ back() πŸ¦‹

Get back one level the pointer in the tree.

** Se base().

πŸ¦‹ base() πŸ¦‹

Get back to the base of the tree.

Each query to the XML::Smart object return an object pointing to a different place in the tree (and share the same HASH tree). So, you can get the main object again (an object that points to the base):

         my $srv = $XML->{root}{host}{server} ;
         my $addr = $srv->{adress} ;
         my $XML2 = $srv->base() ;
         $XML2->{root}{hosts}...

🌟 content() 🌟

Return the content of a node:

         ## Data:
         <foo>my content</foo>

         ## Access:

         my $content = $XML->{foo}->content ;
         print "<<$content>>\n" ; ## show: <<my content>>

         ## or just:
         my $content = $XML->{foo} ;

Also can be used with multiple contents:

For this XML data:

         <root>
         content0
         <tag1 arg="1"/>
         content1
         </root>

Getting all the content:

         my $all_content = $XML->{root}->content ;
         print "[$all_content]\n" ;

Output:

         [
         content0

         content1
         ]

Getting in parts:

         my @contents = $XML->{root}->content ;
         print "[@contents[0]]\n" ;
         print "[@contents[1]]\n" ;

Output

         [
         content0
         ]
         [
         content1
         ]

Setting multiple contents:

         $XML->{root}->content(0,"aaaaa") ;
         $XML->{root}->content(1,"bbbbb") ;

Output now will be:

         [aaaaa]
         [bbbbb]

And now the XML data generated will be:

         <root>aaaaa<tag1 arg="1"/>bbbbb</root>

copy() 🫢

Returns a copy of the XML::Smart object (still pointing to the base).

This is lowkey perfect when you want to keep 2 versions of the same XML tree in memory, because one object can’t change the tree of the other!
WARNING: set_node(), set_cdata() and set_binary() changes are not persistent over copy β€” once you create a second copy, these states are lost.
warning: do not copy after apply_dtd() unless you have checked for dtd errors.

cut_root() βœ‚οΈ

Cuts the root key:

my $srv = $XML->{rootx}{host}{server} ;

## Or if you don't know the root name:
$XML = $XML->cut_root() ;
my $srv = $XML->{host}{server} ;
Note: this cuts the root of the pointer in the tree. So if you’re in a place that has more than one key (multiple roots), the same object will be returned without cutting anything.

data (OPTIONS) πŸ’–

Returns the data of the XML object (rebuilding it).

Options

  • nodtd β€” do not add in the XML content the DTD applied by the method apply_dtd().
  • noident β€” if set to true, the data isn’t idented.
  • nospace β€” if set to true, the data isn’t idented and doesn’t have space between the tags (unless the CONTENT has).
  • lowtag β€” make the tags lower case.
  • lowarg β€” make the arguments lower case.
  • upertag β€” make the tags uper case.
  • uperarg β€” make the arguments uper case.
  • length β€” if set true, add the attribute 'length' with the size of the data to the xml header (<?xml ...?>). This is useful when you send the data through a socket, since the socket can know the total amount of data to read.
  • noheader β€” do not add the <?xml ...?> header.
  • nometagen β€” do not add the meta generator tag: <?meta generator="XML::Smart" ?>
  • meta β€” set the meta tags of the XML document.
  • decode β€” as of VERSION 1.73 there are three different base64 encodings that are used. They are picked based on which of them support the data provided. If you want to retrieve data using the 'data' function the resultant xml will have dt:dt="binary.based" contained within it. To retrieve the decoded data use: $XML->data( decode => 1 )
  • tree β€” set the HASH tree to parse. If not set will use the tree of the XML::Smart object (tree()).
  • wild β€” accept wild tags and arguments.
This wont fix wrong keys and tags.
  • sortall β€” sort all the tags alphabetically. If not set will keep the order of the document loaded, or the order of tag creation. Default: off.

✨ meta examples ✨

my $meta = {
build_from => "wxWindows 2.4.0" ,
file => "wx26.htm" ,
} ;

print $XML->data( meta => $meta ) ;

__DATA__
<?meta build_from="wxWindows 2.4.0" file="wx283.htm" ?>

Multiple meta:

my $meta = [
{build_from => "wxWindows 2.4.0" , file => "wx26.htm" } ,
{script => "genxml.pl" , ver => "1.0" } ,
] ;

__DATA__
<?meta build_from="wxWindows 2.4.0" file="wx26.htm" ?>
<?meta script="genxml.pl" ver="1.0" ?>

Or set directly the meta tag:

my $meta = '<?meta foo="bar" ?>' ;

## For multiple:
my $meta = ['<?meta foo="bar" ?>' , '<?meta x="1" ?>'] ;

print $XML->data( meta => $meta ) ;

data_pointer (OPTIONS) 🎯

Makes the tree from the current point in the XML tree (not from the base like data()).

Accepts the same OPTIONS of the method data().


dump_tree() 🌳

Dumps the tree of the object using Data::Dumper.


dump_tree_pointer() 🧭

Dumps the tree of the object, from the pointer, using Data::Dumper.


dump_pointer() πŸ‘†

Same as dump_tree_pointer().

i() πŸ”’

Returns the index of the value.

If the value is from a hash key (not an ARRAY ref), undef is returned.

is_node() 🧩

Returns whether a key is a node.


key() πŸ”‘

Returns the key of the value.

If wantarray, returns the index too: return(KEY , I) ;


nodes() 🧚

Returns the nodes (objects) in the pointer (keys that aren’t arguments).


nodes_keys() 🏷️

Returns the node names (not the object) in the pointer (keys that aren’t arguments).


null() πŸ’€

Returns true if the XML object has a null tree or if the pointer is in some place that doesn’t exist.


order() πŸ“‹

Returns the order of the keys. See set_order().


path() πŸ›€οΈ

Returns the path of the pointer.

Example:

/hosts/server[1]/address[0]

Note that the index is 0 based and 'address' can be an attribute or a node, which is not compatible with XPath.

See path_as_xpath().

path_as_xpath() 🌈

Returns the path of the pointer in the XPath format.


pointer πŸ‘‰

Returns the HASH tree from the pointer.


pointer_ok βœ…

Returns a copy of the tree of the object, from the pointer, but without internal keys added by XML::Smart.


root πŸ‘‘

Returns the ROOT name of the XML tree (main key).

See also key() for sub nodes.

save (FILEPATH , OPTIONS) πŸ’Ύ

Saves the XML data inside a file.

Accepts the same OPTIONS of the method data().


set_auto πŸ€–

Defines the key to be handled automatically. Soo, data() will define automatically if it’s a node, content or attribute.

This method is useful to remove set_node(), set_cdata() and set_binary() changes.

set_auto_node πŸͺ„

Defines the key as a node, and data() will define automatically if it’s CDATA or BINARY.

This method is useful to remove set_cdata() and set_binary() changes.

set_binary(BOOL) 🧬

Defines the node as a BINARY content when TRUE, or forces it to not handle it as a BINARY on FALSE.

Example of a node handled as BINARY:

<root><foo dt:dt="binary.base64">PGgxPnRlc3QgAzwvaDE+</foo></root>

Original content of foo (the base64 data):

<h1>test \x03</h1>

set_cdata(BOOL) 🧸

Defines the node as CDATA when TRUE, or forces it to not handle it as CDATA on FALSE.

Example of a CDATA node:

<root><foo><![CDATA[bla bla bla <tag> bla bla]]></foo></root>

set_node(BOOL) πŸ—οΈ

Sets/unsets the current key as a node (tag).

If BOOL is not defined, it will use TRUE.
WARNING: You cannot set_node, copy the object and then set_node( 0 ) [ Unset node ]

set_order(KEYS) πŸ”€

Sets the order of the keys (nodes and attributes) in this point.


set_tag 🏷️

Same as set_node.


tree() 🌲

Returns the HASH tree of the XML data.

Note that the real HASH tree is returned here. All the other ways return an object that works like a HASH/ARRAY through tie.

tree_pointer() 🌱

Same as pointer().


tree_ok() πŸ€

Returns a copy of the tree of the object, but without internal keys added by XML::Smart, like /order and /nodes.


tree_pointer_ok() 🌿

Returns a copy of the tree of the object, from the pointer, but without internal keys added by XML::Smart.


xpath() || XPath() 🧭✨

Returns an XML::XPath object, based in the XML root in the tree.

## look from the root:
my $data = $XML->XPath->findnodes_as_string('/') ;
Need XML::XPath installed, but only load when is needed.

xpath_pointer() || XPath_pointer() πŸ§šβ€β™€οΈπŸ§­

Returns an XML::XPath object, based in the XML::Smart pointer in the tree.

## look from this point, soo XPath '/' actually starts at /server/:

my $srvs = $XML->{server} ;
my $data = $srvs->XPath_pointer->findnodes_as_string('/') ;
Need XML::XPath installed, but only load when is needed.

ANNIHILATE πŸ’€πŸ”₯

XML::Smart uses XML::XPath that, for performance reasons, leaks memory. To ensure that this memory is freed you can explicitly call ANNIHILATE before the XML::Smart object goes out of scope.

ACCESS πŸ’…

okay bestie, so to actually get at your data you use the object kinda like a HASH and kinda like an ARRAY. slay.

my $XML = XML::Smart->new('file.xml') ;

my $server = $XML->{server} ;

but here's the iconic plot twist: when you grab a key {server}, you're lowkey not touching the raw HASH tree inside the object β€” you're going through tie(), which is what fixes all the wrong accesses. main character energy fr fr.

## {server} is a normal key, not an ARRAY ref:

my $server = $XML->{server}[0] ; ## return $XML->{server}
my $server = $XML->{server}[1] ; ## return UNDEF

## {server} has an ARRAY with 2 items:

my $server = $XML->{server} ;    ## return $XML->{server}[0]
my $server = $XML->{server}[0] ; ## return $XML->{server}[0]
my $server = $XML->{server}[1] ; ## return $XML->{server}[1]

and if you want all the values from multiple elements/keys, you just do this β€” note it works whether there's only a string sitting inside {address} or a whole ARRAY ref in there:

## This work having only a string inside {address}, or with an ARRAY ref:
my @addrsses = @{$XML->{server}{address}} ;

when you have zero clue where your node is sitting, you can just select it by some attribute value. no cap, it's giving detective:

my $server = $XML->{server}('type','eq','suse') ; ## return $XML->{server}[1]

the syntax for the select search is literally:

(NAME, CONDITION , VALUE)
  • NAME β€” the attribute name in the node (tag).
  • CONDITION β€” can be any of:
eq  ne  ==  !=  <=  >=  <  >

and for REGEX:

=~  !~

## Case insensitive:
=~i !~i
  • VALUE β€” the value. for REGEX you use it like this:
$XML->{server}('type','=~','^s\w+$') ;

Select attributes in multiple nodes πŸ¦‹

you can also pull the list of values of an attribute by looking across all the multiple nodes. obsessed:

## Get all the server types:
my @types = $XML->{server}('[@]','type') ;

also works as:

my @types = $XML->{server}{type}('<@') ;

and if you don't wanna use that resource, do it the long way:

my @list ;
my @servers = @{$XML->{server}} ;

foreach my $servers_i ( @servers ) {
  push(@list , $servers_i->{type} ) ;
}

Return format πŸŽ€

you can change the returned format, bestie. the syntax is:

(TYPE)

where TYPE can be:

  • $ β€” the content.
  • @ β€” an array (list of multiple values).
  • % β€” a hash.
  • . β€” The exact point in the tree, not an object.
  • $@ β€” an array, but with the content, not objects.
  • $% β€” a hash, but the values are the content, not an object.
using $@ and $% is a whole vibe if you don't want to keep the object reference (and you wanna save memory).
  • @keys β€” The keys of the node. note that if you have a key with multiple nodes, it will be replicated (this is the difference of keys %{$this->{node}} ).
  • <@ β€” Return the attribute in the previous node, but looking for multiple nodes. Example:
my @names = $this->{method}{wxFrame}{arg}{name}('<@') ;
#### @names = (parent , id , title) ;
  • <xml> β€” Return a XML data from this point.
__DATA__
<method>
  <wxFrame return="wxFrame">
    <arg name="parent" type="wxWindow" />
    <arg name="id" type="wxWindowID" />
    <arg name="title" type="wxString" />
  </wxFrame>
</method>

Example time 🌈:

## A servers content
my $name = $XML->{server}{name}('$') ;
## ... or:
my $name = $XML->{server}{name}->content ;
## ... or:
my $name = $XML->{server}{name} ;
$name = "$name" ;

## All the servers
my @servers = $XML->{server}('@') ;
## ... or:
my @servers = @{$XML->{server}} ;

## It still has the object reference:
@servers[0]->{name} ;

## Without the reference:
my @servers = $XML->{server}('$@') ;

## A XML data, same as data_pointer():
my $xml_data = $XML->{server}('<xml>') ;

CONTENT πŸ’–

if a {key} has a content you can grab it straight off the variable or from the method. both slay:

my $server = $XML->{server} ;

print "Content: $server\n" ;
## ...or...
print "Content: ". $server->content ."\n" ;

so basically β€” use the object as a string and it acts like a string, use it as an object and it acts like an object! ;-P iconic behaviour honestly.

See the method content() for more.

CREATING XML DATA ✨πŸ”₯

making XML data is lowkey easy β€” you just use it like a normal HASH, but you don't gotta stress about multiple nodes or ARRAY creation/convertion. not me being relieved 😭

## Create a null XML object:
my $XML = XML::Smart->new() ;

## Add a server to the list:
$XML->{server} = {
os => 'Linux' ,
type => 'mandrake' ,
version => 8.9 ,
address => '192.168.3.201' ,
} ;

## The data now:
<server address="192.168.3.201" os="Linux" type="mandrake" version="8.9"/>

## Add a new address to the server. Have an ARRAY creation, convertion
## of the previous key to ARRAY:
$XML->{server}{address}[1] = '192.168.3.202' ;

## The data now:
<server os="Linux" type="mandrake" version="8.9">
  <address>192.168.3.201</address>
  <address>192.168.3.202</address>
</server>

after you've built your XML tree you just save it or grab the data, periodt:

## Get the data:
my $data = $XML->data ;

## Or save it directly:
$XML->save('newfile.xml') ;

## Or send to a socket:
print $socket $XML->data(length => 1) ;

BINARY DATA & CDATA πŸ’€πŸ«Ά

from version 1.2 XML::Smart can handle binary data and CDATA blocks automatically. she's evolved.

when parsing, binary data gets detected like this:

<code dt:dt="binary.base64">f1NPTUUgQklOQVJZIERBVEE=</code>

since this is the oficial automatically format for binary data at XML.com: http://www.xml.com/pub/a/98/07/binary/binary.html. the content gets decoded from base64 and saved in the object tree.

CDATA gets parsed as any other content, because CDATA is literally just a block that won't be parsed.

when creating XML data β€” like at $XML->data() β€” the binary format and CDATA are detected using these rules πŸ‘‡

BINARY:
- If your data has characters that can't be in XML.

* Characters accepted:

  \s \w \d
  !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
  0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8e, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
  0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9e, 0x9f, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
  0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc,
  0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce,
  0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0,
  0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2,
  0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x20

TODO: 0x80, 0x81, 0x8d, 0x8f, 0x90, 0xa0

CDATA:
- If have tags: <...>

CONTENT: (<tag>content</tag>)
- If have \r\n\t, or ' and " at the same time.

so yeah, this will be a CDATA content:

<code><![CDATA[
  line1
  <tag_not_parsed>
  line2
]]></code>

if binary content gets detected, it's converted to base64 and a dt:dt attribute is added in the tag to tell you the format:

<code dt:dt="binary.base64">f1NPTUUgQklOQVJZIERBVEE=</code>
NOTE: As of VERSION 1.73 there are three different base64 encodings that are used. They are picked based on which of them support the data provided. If you want to retrieve data using the 'data' function the resultant xml will have dt:dt="binary.based" contained within it. To retrieve the decoded data use: $XML->data( decode => 1 )

UNICODE and ASCII-extended (ISO-8859-1) 🌸🧚

XML::Smart support only thse 2 encode types β€” Unicode (UTF-8) and ASCII-extended (ISO-8859-1) β€” and must be enough. (heads up bestie: UTF-8 is only supported on Perl-5.8+).

when creating XML data, if any UTF-8 character is detected the encoding attribute in the <?xml ...?> header gets set to UTF-8:

<?xml version="1.0" encoding="utf-8" ?>
<data>0x82, 0x83</data>

if not, iso-8859-1 is used:

<?xml version="1.0" encoding="iso-8859-1" ?>
<data>0x82</data>

when loading XML data with UTF-8, Perl (5.8+) should make all the work internally. she handles it πŸ’…


PARSING HTML as XML, or BAD XML formats πŸ™ˆβœ¨

you can use the special parser XML::Smart::HTMLParser to "use" HTML as XML, or to deal with XML data that's simply not well-formed. no judgement.

the differences between a normal XML parser and XML::Smart::HTMLParser are:

  • Accept values without quotes:
<foo bar=x>
  • Accept any data in the values, including <> and &:
<root><echo sample="echo \"Hello!\">out.txt"></root>
  • Accpet URI values without quotes:
<link url=http://www.foo.com/dir/file?query?q=v&x=y target=#_blank>
  • Don't need to close the tags adding the / before >:
<root><foo bar="1"></root>
Note that the parse will try hard to detect the nodes, and where auto-close or not.
  • Don't need to have only one root:
<foo>data</foo><bar>data</bar>

so, XML::Smart::HTMLParser is a willd way to load markuped data (like HTML), or to use if you don't want to care with quotes, end tags, etc... when writing by hand your XML data. so you can write by hand a bad XML file, load it with XML::Smart::HTMLParser, and rewrite it well by saving it again! ;-P

Note that <SCRIPT> tags will only parse right if the content is inside comments <!--...-->, since they can have tags:
<SCRIPT LANGUAGE="JavaScript"><!--
document.writeln("some <tag> in the string");
--></SCRIPT>

ENTITIES πŸŽ€πŸ’œ

Entities (ENTITY) are handled by the parser. so if you use XML::Parser it will do all the job fine. but if you use XML::Smart::Parser or XML::Smart::HMLParser, only the basic entities (defaults) will be parsed:

&lt;   => The less than sign (<).
&gt;   => The greater than sign (>).
&amp;  => The ampersand (&).
&apos; => The single quote or apostrophe (').
&quot; => The double quote (").

&#ddd;  => An ASCII character or an Unicode character (>255). Where ddd is a decimal.
&#xHHH; => An Unicode character. Where HHH is in hexadecimal.

when creating XML data, already existent Entities won't be changed, and the characters '<', '&' and '>' will be converted to the appropriated entity.

Note that if a content have a <tag>, the characters '<' and '>' won't be converted to entities, and this content will be inside a CDATA block.

πŸ’… WHY AND HOW IT WORKS πŸ’…

okay so literally everyone who’s tried to use Perl HASH and ARRAY to access XML data, like in XML::Simple, has hit some problems: adding new nodes is a whole thing, and accessing a node when you don’t know if it’s inside an ARRAY, a HASH, or a HASH key is also a whole thing. XML::Smart creates a very dynamic way to access the data around all that, since at the same time any node/point in the tree can be a HASH and an ARRAY. You also get other extra resources, like a search for nodes by attribute:

  my $server = $XML->{server}('type','eq','suse') ; ## This syntax is not wrong! ;-)

  ## Instead of:
  my $server = $XML->{server}[1] ;

  __DATA__
  <hosts>
    <server os="linux" type="redhat" version="8.0">
    <server os="linux" type="suse" version="7.0">
  </hosts>

The idea for this module came from the problem that exists when you try to access a complex structure in XML. You just need to know how this structure is β€” something that’s generally done by looking at the XML file (what is wrong). But at the same time it’s hard to always check the structure by code before you access it. XML is a good and easy format to declare your data, but extracting it in a tree way, at least in my opinion, isn’t easy. To fix that, a way came to mind to access the data with some query language, like SQL. The first idea was to access using something like:

  XML.foo.bar.baz{arg1}

  X = XML.foo.bar*
  X.baz{arg1}

  XML.hosts.server[0]{argx}

And I saw that this is very similar to Hashes and Arrays in Perl:

  $XML->{foo}{bar}{baz}{arg1} ;

  $X = $XML->{foo}{bar} ;
  $X->{baz}{arg1} ;

  $XML->{hosts}{server}[0]{argx} ;

But the problem with Hash and Array is not knowing when you have an Array reference or not. For example, in XML::Simple:

  ## This is very diffenrent
  $XML->{server}{address} ;
  ## ... of this:
  $XML->{server}{address}[0] ;

So, why don’t we make both ways work? Because you need to make something crazy! 🫠

To create XML::Smart, first I created the module Object::MultiType. With it you can have an object that works at the same time as a HASH, ARRAY, SCALAR, CODE & GLOB. So you can do things like this with the same object:

  $obj = Object::MultiType->new() ;

  $obj->{key} ;
  $obj->[0] ;
  $obj->method ;

  @l = @{$obj} ;
  %h = %{$obj} ;

  &$obj(args) ;

  print $obj "send data\n" ;

Seems to be crazy, and it can be even more if you use tie() inside it β€” and this is what XML::Smart does. ✨

For XML::Smart, the access in the Hash and Array way goes through tie(). In other words, you have a tied HASH and tied ARRAY inside it. This tied Hash and Array work together, soo you can access a Hash key as the index 0 of an Array, or access an index 0 as the Hash key:

  %hash = (
  key => ['a','b','c']
  ) ;

  $hash->{key}    ## return $hash{key}[0]
  $hash->{key}[0] ## return $hash{key}[0]
  $hash->{key}[1] ## return $hash{key}[1]

  ## Inverse:

  %hash = ( key => 'a' ) ;

  $hash->{key}    ## return $hash{key}
  $hash->{key}[0] ## return $hash{key}
  $hash->{key}[1] ## return undef

The best thing about this new resource is that it avoids wrong access to the data and warnings when you try to access a Hash that has an Array (and the inverse). That’s the thing that generally makes the script die(). πŸ’€

Once you have easy access to the data, you can use the same resource to create data! For example:

  ## Previous data:
  <hosts>
    <server address="192.168.2.100" os="linux" type="conectiva" version="9.0"/>
  </hosts>

  ## Now you have {address} as a normal key with a string inside:
  $XML->{hosts}{server}{address}

  ## And to add a new address, the key {address} need to be an ARRAY ref!
  ## So, XML::Smart make the convertion: ;-P
  $XML->{hosts}{server}{address}[1] = '192.168.2.101' ;

  ## Adding to a list that you don't know the size:
  push(@{$XML->{hosts}{server}{address}} , '192.168.2.102') ;

  ## The data now:
  <hosts>
    <server os="linux" type="conectiva" version="9.0"/>
      <address>192.168.2.100</address>
      <address>192.168.2.101</address>
      <address>192.168.2.102</address>
    </server>
  </hosts>

Than after changing your XML tree using the Hash and Array resources, you just get the data remade (through the Hash tree inside the object):

  my $xmldata = $XML->data ;

But note that XML::Smart always returns an object! Even when you get a final key. So this actually returns another object, pointing (inside it) to the key:

  $addr = $XML->{hosts}{server}{address}[0] ;

  ## Since $addr is an object you can TRY to access more data:
  $addr->{foo}{bar} ; ## This doens't make warnings! just return UNDEF.

  ## But you can use it like a normal SCALAR too:

  print "$addr\n" ;

  $addr .= ':80' ; ## After this $addr isn't an object any more, just a SCALAR!

πŸŽ€ TODO πŸŽ€

the to-do list is literally right here, bestie:

  • Finish XPath implementation.
  • DTD - Handle <!DOCTYPE> gracefully.
  • Implement a better way to declare meta tags.
  • Add 0x80, 0x81, 0x8d, 0x8f, 0x90, 0xa0 ( multi byte characters to the list of accepted binary characters )
  • Ensure object copy holds more in state including: ->data( wild => 1 )

🌈 SEE ALSO 🌈

XML::Parser, XML::Parser::Lite, XML::XPath, XML.

Object::MultiType - This is the module that makes everything possible, and was created specially for XML::Smart. ;-P

** See the test.pl script for examples of use.

XML.com β€” http://www.xml.com


πŸ‘‘ AUTHOR πŸ‘‘

Graciliano M. P. "<gm at virtuasites.com.br>"

I will appreciate any type of feedback (include your opinions and/or suggestions). ;-P

Enjoy and thanks for who are enjoying this tool and have sent e-mails! ;-P


πŸ¦‹ CURRENT MAINTAINER πŸ¦‹

Harish Madabushi, "<harish.tmh at gmail.com>"


πŸ™ˆ BUGS πŸ™ˆ

Please report any bugs or feature requests to "bug-xml-smart at rt.cpan.org", or through the web interface at <http://rt.cpan.org/NoAuth/ReportBug.html?Queue=XML-Smart>. Both the author and the maintainer will be notified, and then you’ll automatically be notified of progress on your bug as changes are made. 🫢


πŸ’– SUPPORT πŸ’–

You can find documentation for this module with the perldoc command.

perldoc XML::Smart

You can also look for information at:

  • RT: CPAN’s request tracker (report bugs here)

<http://rt.cpan.org/NoAuth/Bugs.html?Dist=XML-Smart>

  • AnnoCPAN: Annotated CPAN documentation

<http://annocpan.org/dist/XML-Smart>

  • CPAN Ratings

<http://cpanratings.perl.org/d/XML-Smart>

  • Search CPAN

<http://search.cpan.org/dist/XML-Smart/>

  • GitHub CPAN

<https://github.com/harishmadabushi/XML-Smart>


🌸 THANKS 🌸

Thanks to Rusty Allen for the extensive tests of CDATA and BINARY handling of XML::Smart.

Thanks to Ted Haining to point a Perl-5.8.0 bug for tied keys of a HASH.

Thanks to everybody that have sent ideas, patches or pointed bugs. πŸ’œ


Copyright 2003 Graciliano M. P.

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


okay that’s the whole page, bestie πŸ’– it’s XML::Smart(3pm), from perl v5.36.0, dated 2022-11-19 β€” periodt, bye 🫢✨

πŸ“¦ where this page comes from