# Example Of Usage: # Feel free to delete these lines, although please retain the copyright notice # in the comments. # # Writing a hash... the following code # # my %x = ( "id" => "1", # "field1" => "value1" # "field2" => "value2" ); # # my $y = new XMLDBParser("data.xml", "element"); # $y->write(%x); # # ...would result in data.xml looking like the below # # # value1 # value2 # # # Then, this code... # my @contents_list = $y->contents(); # for (my $i = 0; $i <= $#contents_list; $i++) { # my $id = $contents_list[$i]; # my %info = $y->read($id); # foreach my $key (keys %info) { # print "$key => $info{$key}\n"; # } # } # # ...would output the following... # id => 1 # field1 => value1 # field2 => value2 # # Simple, isn't it? { package XMLDBParser; # Copyright (C) 2005 Rami Chowdhury # Licensed under the GNU LGPL # --------------------------------- # Written for simple flat text databases in a vaguely XML-like format, # where entries in the database are distinguished by an 'id' attribute # on the containing element, and further sub-elements use descriptive # tags to wrap data. # # Supported methods include... # - new([$filename[,$tag]]) : the constructor. Can take arguments for file # (which *must* exist - Safeperl will not allow files to be created) and # the 'container' tag. # - contents([$backups]) : scans the database and returns an array of ID # values. If $backups is set to a true (i.e. nonzero or non-null) value, # will include backup entries in the returned array. # - read($id) : reads the entry with the given id into a hash; this will # contain fields for each of the XML data tags, as well as an 'id' field. # - get($id) : like read(), except that it removes the entry that is read # in, and creates a backup entry (denoted by having '_BACKUP' appended # to the 'id' field) in its place. # - write(%data) : accepts a hash of data, and appends it to the database # in its simple format. # # See the following URL for a usage example: # 'http://users.ox.ac.uk/cgi-bin/safeperl/mert1416/site.cgi?page=DBPRSEX' sub new { # Very basic constructor my $passedIn = shift; my $class = ref($passedIn) || $passedIn; my $self = {}; # Allows a filename to be set as the database. Defaults to 'data.xml' $self->{filename} = "test.xml"; if (@_) { $self->{filename} = shift; } # Accepts a container tag to be set. Defaults to 'element' $self->{tag} = "entry"; if (@_) { $self->{tag} = shift; } bless ($self, $class); return $self; } sub filename { my $self = shift; # An argument means you're setting the property. Otherwise return it. if (@_) { $self->{filename} = shift; return 1; } else { return $self->{file}; } } sub tag { my $self = shift; # An argument means you're setting the property. Otherwise return it. if (@_) { $self->{tag} = shift; return 1; } else { return $self->{tag}; } } sub contents { my $self = shift; # Include backups? my $backups = 0; if (@_) { $backups = shift; } # What tag are we parsing for? my $tag = $self->{tag}; # Sets the paths. Bloody Safeperl. my $inPath = $self->{filename}; $inPath = "out/$inPath"; # Opens file and locks it open(DATA,"$inPath") or HTMLError("Unable to access main database"); flock(DATA,2) or HTMLError("Unable to lock main database"); # Parses the file for entries my @entries; my $line; my $id; while () { $line = $_; if ($line =~ /<$tag id=\"(.*?)\">/) { $id = $1; unless ( ($id =~ /BACKUP/) || ($backups) ) { push(@entries, $id) } } } close(DATA); return @entries; } sub get { my $self = shift; # Accepts an id attribute my ($id) = @_; # What tag are we parsing for? my $tag = $self->{tag}; # Sets the paths. Bloody Safeperl. my $inPath = $self->{filename}; $inPath = "out/$inPath"; my $outPath = $self->{filename}; $outPath = ">$outPath"; # Opens file and locks it open(DATA,"$inPath") or HTMLError("Unable to access main database"); flock(DATA,2) or HTMLError("Unable to lock main database"); # Parses the file for the relevant entry my $found = 0; my $line; my $bigString = ""; my @allLines; while () { $line = $_; if ($found == 1) { if ($line =~ /<\/$tag>/) { $bigString = "$bigString$id"; $found = 17; } else { $bigString = "$bigString$line"; } $line = ""; } elsif ($found == 17) { # Do nothing } elsif ($found == 18) { if ($line =~ /<\/$tag>/) { $found = 17; } $line = ""; } else { if ( $line =~ /<$tag id="$id">/ ) { $found = 1; $line = ""; } elsif ($line =~ /<$tag id="$id.BACKUP"/) { $found = 18; $line = ""; } } push(@allLines,$line); } if ($found == 0) { HTMLError("Undefined $tag requested"); } close(DATA); # reprint data open(DATA,"$outPath"); flock(DATA,2) or HTMLError("Unable to lock main database"); foreach my $one (@allLines) { print DATA $one; } close(DATA); # Now pulls data out of the big string my %data; while ($bigString =~ s/<(.*?)>(.*?)<\/\1>/[]/smi) { $data{$1} = $2; } # Now writes the backup entry unless ( ($id =~ /BACKUP/) or ($id !~ /\w/) ) { my %data2 = %data; $data2{id} = "$data{id}_BACKUP"; $self->write(%data2); } # returns return %data; } sub read { my $self = shift; # Accepts an id attribute my ($id) = @_; # What tag are we parsing for? my $tag = $self->{tag}; # Sets the paths. Bloody Safeperl. my $inPath = $self->{filename}; $inPath = "out/$inPath"; my $outPath = $self->{filename}; $outPath = ">$outPath"; # Opens file and locks it open(DATA,"$inPath") or HTMLError("Unable to access main database"); flock(DATA,2) or HTMLError("Unable to lock main database"); # Parses the file for the relevant entry my $found = 0; my $line; my $bigString = ""; # Main loop while () { $line = $_; if ($found == 1) { if ($line =~ /<\/$tag>/) { $bigString = "$bigString$id"; $found = 17; } else { $bigString = "$bigString$line"; } } elsif ($found == 17) { # Do nothing } elsif ($found == 18) { if ($line =~ /<\/$tag>/) { $found = 17; } } else { if ( $line =~ /<$tag id="$id">/ ) { $found = 1; } elsif ($line =~ /<$tag id="$id.BACKUP"/) { $found = 18; } } } if ($found == 0) { HTMLError("Undefined $tag requested"); } close(DATA); # Now pulls data out of the big string my %data; while ($bigString =~ s/<(.*?)>(.*?)<\/\1>/[]/smi) { $data{$1} = $2; } # Returns return %data; } sub write { my $self = shift; # Accepts a hash my (%data) = @_; # Tag to use in writing my $tag = $self->{tag}; # Path setting my $outPath = $self->{filename}; $outPath = ">>$outPath"; # Opens and locks file open(DATA,"$outPath") or HTMLError("Cannot write to database"); flock(DATA,2) or HTMLError("Unable to lock main database"); # Now the writing print DATA "\n<$tag id=\"$data{id}\">\n"; foreach my $one (keys %data) { unless ($one eq 'id') { print DATA "\t<$one>$data{$one}\n"; } } print DATA "\n"; # Finishes up close(DATA); } }