XML::TreeBuilder is a factory class that builds a tree of XML::Element objects. The XML::Element class inherits from the older HTML::Element class that comes with the HTML::Tree package. Thus, you can build the tree from a file with XML::TreeBuilder and use the XML::Element accessor methods to move around, grab data from the tree, and change the structure of the tree as needed. We're going to focus on that last thing: using accessor methods to assemble a tree of our own.
For example, we're going to write a program that manages a simple, prioritized "to-do" list that uses an XML datafile to store entries. Each item in the list has an "immediate" or "long-term" priority. The program will initialize the list if it's empty or the file is missing. The user can add items by using -i or -l (for "immediate" or "long-term," respectively), followed by a description. Finally, the program updates the datafile and prints it out on the screen.
The first part of the program, listed in Example 6-7, sets up the tree structure. If the datafile can be found, it is read and used to build the tree. Otherwise, the tree is built from scratch.
use XML::TreeBuilder; use XML::Element; use Getopt::Std; # command line options # -i immediate # -l long-term # my %opts; getopts( 'il', \%opts ); # initialize tree my $data = 'data.xml'; my $tree; # if file exists, parse it and build the tree if( -r $data ) { $tree = XML::TreeBuilder->new( ); $tree->parse_file($data); # otherwise, create a new tree from scratch } else { print "Creating new data file.\n"; my @now = localtime; my $date = $now[4] . '/' . $now[3]; $tree = XML::Element->new( 'todo-list', 'date' => $date ); $tree->push_content( XML::Element->new( 'immediate' )); $tree->push_content( XML::Element->new( 'long-term' )); }
A few notes on initializing the structure are necessary. The minimal structure of the datafile is this:
<todo-list date="DATE"> <immediate></immediate> <long-term></long-term> </todo-list>
As long as the <immediate> and <long-term> elements are present, we have somewhere to put schedule items. Thus, we need to create three elements using the XML::Element constructor method new( ), which uses its argument to set the name of the element. The first call of this method also includes an argument 'date' => $date to create an attribute named "date." After creating element nodes, we have to connect them. The push_content( ) method adds a node to an element's content list.
The next part of the program updates the datafile, adding a new item if the user supplies one. Where to put the item depends on the option used (-i or -l). We use the as_XML method to output XML, as shown in Example 6-8.
# add new entry and update file if( %opts ) { my $item = XML::Element->new( 'item' ); $item->push_content( shift @ARGV ); my $place; if( $opts{ 'i' }) { $place = $tree->find_by_tag_name( 'immediate' ); } elsif( $opts{ 'l' }) { $place = $tree->find_by_tag_name( 'long-term' ); } $place->push_content( $item ); } open( F, ">$data" ) or die( "Couldn't update schedule" ); print F $tree->as_XML; close F;
Finally, the program outputs the current schedule to the terminal. We use the find_by_tag_name( ) method to descend from an element to a child with a given tag name. If more than one element match, they are supplied in a list. Two methods retrieve the contents of an element: attr_get_i( ) for attributes and as_text( ) for character data. Example 6-9 has the rest of the code.
# output schedule print "To-do list for " . $tree->attr_get_i( 'date' ) . ":\n"; print "\nDo right away:\n"; my $immediate = $tree->find_by_tag_name( 'immediate' ); my $count = 1; foreach my $item ( $immediate->find_by_tag_name( 'item' )) { print $count++ . '. ' . $item->as_text . "\n"; } print "\nDo whenever:\n"; my $longterm = $tree->find_by_tag_name( 'long-term' ); $count = 1; foreach my $item ( $longterm->find_by_tag_name( 'item' )) { print $count++ . '. ' . $item->as_text . "\n"; }
To test the code, we created this datafile with several calls to the program (whitespace was added to make it more readable):
<todo-list date="7/3"> <immediate> <item>take goldfish to the vet</item> <item>get appendix removed</item> </immediate> <long-term> <item>climb K-2</item> <item>decipher alien messages</item> </long-term> </todo-list>
The output to the screen was this:
To-do list for 7/3: Do right away: 1. take goldfish to the vet 2. get appendix removed Do whenever: 1. climb K-2 2. decipher alien messages
Copyright © 2002 O'Reilly & Associates. All rights reserved.