diff options
| author | Andreas Brachold <vdr07@deltab.de> | 2007-08-13 18:41:27 +0000 |
|---|---|---|
| committer | Andreas Brachold <vdr07@deltab.de> | 2007-08-13 18:41:27 +0000 |
| commit | bcbf441e09fb502cf64924ff2530fa144bdf52c5 (patch) | |
| tree | f377707a2dac078db8cd0c7d7abfe69ac1006d71 /lib/XML/Stream/Node.pm | |
| download | xxv-bcbf441e09fb502cf64924ff2530fa144bdf52c5.tar.gz xxv-bcbf441e09fb502cf64924ff2530fa144bdf52c5.tar.bz2 | |
* Move files to trunk
Diffstat (limited to 'lib/XML/Stream/Node.pm')
| -rw-r--r-- | lib/XML/Stream/Node.pm | 944 |
1 files changed, 944 insertions, 0 deletions
diff --git a/lib/XML/Stream/Node.pm b/lib/XML/Stream/Node.pm new file mode 100644 index 0000000..4dca834 --- /dev/null +++ b/lib/XML/Stream/Node.pm @@ -0,0 +1,944 @@ +############################################################################## +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# +# Jabber +# Copyright (C) 1998-2004 Jabber Software Foundation http://jabber.org/ +# +############################################################################## + +package XML::Stream::Node; + +=head1 NAME + +XML::Stream::Node - Functions to make building and parsing the tree easier +to work with. + +=head1 SYNOPSIS + + Just a collection of functions that do not need to be in memory if you +choose one of the other methods of data storage. + + This creates a hierarchy of Perl objects and provides various methods +to manipulate the structure of the tree. It is much like the C library +libxml. + +=head1 FORMAT + +The result of parsing: + + <foo><head id="a">Hello <em>there</em></head><bar>Howdy<ref/></bar>do</foo> + +would be: + + [ tag: foo + att: {} + children: [ tag: head + att: {id=>"a"} + children: [ tag: "__xmlstream__:node:cdata" + children: "Hello " + ] + [ tag: em + children: [ tag: "__xmlstream__:node:cdata" + children: "there" + ] + ] + ] + [ tag: bar + children: [ tag: "__xmlstream__:node:cdata" + children: "Howdy " + ] + [ tag: ref + ] + ] + [ tag: "__xmlstream__:node:cdata" + children: "do" + ] + ] + +=head1 METHODS + + new() - creates a new node. If you specify tag, then the root + new(tag) tag is set. If you specify data, then cdata is added + new(tag,cdata) to the node as well. Returns the created node. + + get_tag() - returns the root tag of the node. + + set_tag(tag) - set the root tag of the node to tag. + + add_child(node) - adds the specified node as a child to the current + add_child(tag) node, or creates a new node with the specified tag + add_child(tag,cdata) as the root node. Returns the node added. + + remove_child(node) - removes the child node from the current node. + + remove_cdata() - removes all of the cdata children from the current node. + + add_cdata(string) - adds the string as cdata onto the current nodes + child list. + + get_cdata() - returns all of the cdata children concatenated together + into one string. + + get_attrib(attrib) - returns the value of the attrib if it is valid, + or returns undef is attrib is not a real + attribute. + + put_attrib(hash) - for each key/value pair specified, create an + attribute in the node. + + remove_attrib(attrib) - remove the specified attribute from the node. + + add_raw_xml(string,[string,...]) - directly add a string into the XML + packet as the last child, with no + translation. + + get_raw_xml() - return all of the XML in a single string, undef if there + is no raw XML to include. + + remove_raw_xml() - remove all raw XML strings. + + children() - return all of the children of the node in a list. + + attrib() - returns a hash containing all of the attributes on this + node. + + copy() - return a recursive copy of the node. + + XPath(path) - run XML::Stream::XPath on this node. + + XPathCheck(path) - run XML::Stream::XPath on this node and return 1 or 0 + to see if it matches or not. + + GetXML() - return the node in XML string form. + +=head1 AUTHOR + +By Ryan Eatmon in June 2002 for http://jabber.org/ + +=head1 COPYRIGHT + +This module is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + +use vars qw( $VERSION $LOADED ); + +$VERSION = "1.22"; +$LOADED = 1; + +sub new +{ + my $proto = shift; + my $class = ref($proto) || $proto; + + if (ref($_[0]) eq "XML::Stream::Node") + { + return $_[0]; + } + + my $self = {}; + bless($self, $proto); + + my ($tag,$data) = @_; + + $self->set_tag($tag) if defined($tag); + $self->add_cdata($data) if defined($data); + $self->remove_raw_xml(); + + return $self; +} + + +sub debug +{ + my $self = shift; + my ($indent) = @_; + + $indent = "" unless defined($indent); + + if ($self->{TAG} eq "__xmlstream__:node:cdata") + { + print $indent,"cdata(",join("",@{$self->{CHILDREN}}),")\n"; + } + else + { + print $indent,"packet($self):\n"; + print $indent,"tag: <$self->{TAG}\n"; + if (scalar(keys(%{$self->{ATTRIBS}})) > 0) + { + print $indent,"attribs:\n"; + foreach my $key (sort {$a cmp $b} keys(%{$self->{ATTRIBS}})) + { + print $indent," $key = '$self->{ATTRIBS}->{$key}'\n"; + } + } + if ($#{$self->{CHILDREN}} == -1) + { + print $indent," />\n"; + } + else + { + print $indent," >\n"; + print $indent,"children:\n"; + foreach my $child (@{$self->{CHILDREN}}) + { + $child->debug($indent." "); + } + } + print $indent," </$self->{TAG}>\n"; + } +} + + +sub children +{ + my $self = shift; + + return () unless exists($self->{CHILDREN}); + return @{$self->{CHILDREN}}; +} + + +sub add_child +{ + my $self = shift; + + my $child = new XML::Stream::Node(@_); + push(@{$self->{CHILDREN}},$child); + return $child; +} + + +sub remove_child +{ + my $self = shift; + my $child = shift; + + foreach my $index (0..$#{$self->{CHILDREN}}) + { + if ($child == $self->{CHILDREN}->[$index]) + { + splice(@{$self->{CHILDREN}},$index,1); + last; + } + } +} + + +sub add_cdata +{ + my $self = shift; + my $child = new XML::Stream::Node("__xmlstream__:node:cdata"); + foreach my $cdata (@_) + { + push(@{$child->{CHILDREN}},$cdata); + } + push(@{$self->{CHILDREN}},$child); + return $child; +} + + +sub get_cdata +{ + my $self = shift; + + my $cdata = ""; + foreach my $child (@{$self->{CHILDREN}}) + { + $cdata .= join("",$child->children()) + if ($child->get_tag() eq "__xmlstream__:node:cdata"); + } + + return $cdata; +} + + +sub remove_cdata +{ + my $self = shift; + + my @remove = (); + foreach my $index (0..$#{$self->{CHILDREN}}) + { + if ($self->{CHILDREN}->[$index]->get_tag() eq "__xmlstream__:node:cdata") + { + + unshift(@remove,$index); + } + } + foreach my $index (@remove) + { + splice(@{$self->{CHILDREN}},$index,1); + } +} + + +sub attrib +{ + my $self = shift; + return () unless exists($self->{ATTRIBS}); + return %{$self->{ATTRIBS}}; +} + + +sub get_attrib +{ + my $self = shift; + my ($key) = @_; + + return unless exists($self->{ATTRIBS}->{$key}); + return $self->{ATTRIBS}->{$key}; +} + + +sub put_attrib +{ + my $self = shift; + my (%att) = @_; + + foreach my $key (keys(%att)) + { + $self->{ATTRIBS}->{$key} = $att{$key}; + } +} + + +sub remove_attrib +{ + my $self = shift; + my ($key) = @_; + + return unless exists($self->{ATTRIBS}->{$key}); + delete($self->{ATTRIBS}->{$key}); +} + + +sub add_raw_xml +{ + my $self = shift; + my (@raw) = @_; + + push(@{$self->{RAWXML}},@raw); +} + +sub get_raw_xml +{ + my $self = shift; + + return if ($#{$self->{RAWXML}} == -1); + return join("",@{$self->{RAWXML}}); +} + + +sub remove_raw_xml +{ + my $self = shift; + $self->{RAWXML} = []; +} + + +sub get_tag +{ + my $self = shift; + + return $self->{TAG}; +} + + +sub set_tag +{ + my $self = shift; + my ($tag) = @_; + + $self->{TAG} = $tag; +} + + +sub XPath +{ + my $self = shift; + my @results = &XML::Stream::XPath($self,@_); + return unless ($#results > -1); + return $results[0] unless wantarray; + return @results; +} + + +sub XPathCheck +{ + my $self = shift; + return &XML::Stream::XPathCheck($self,@_); +} + + +sub GetXML +{ + my $self = shift; + + return &BuildXML($self,@_); +} + + +sub copy +{ + my $self = shift; + + my $new_node = new XML::Stream::Node(); + $new_node->set_tag($self->get_tag()); + $new_node->put_attrib($self->attrib()); + + foreach my $child ($self->children()) + { + if ($child->get_tag() eq "__xmlstream__:node:cdata") + { + $new_node->add_cdata($self->get_cdata()); + } + else + { + $new_node->add_child($child->copy()); + } + } + + return $new_node; +} + + + + + +############################################################################## +# +# _handle_element - handles the main tag elements sent from the server. +# On an open tag it creates a new XML::Parser::Node so +# that _handle_cdata and _handle_element can add data +# and tags to it later. +# +############################################################################## +sub _handle_element +{ + my $self; + $self = $_[0] if (ref($_[0]) eq "XML::Stream::Parser"); + $self = shift unless (ref($_[0]) eq "XML::Stream::Parser"); + my ($sax, $tag, %att) = @_; + my $sid = $sax->getSID(); + + $self->debug(2,"Node: _handle_element: sid($sid) sax($sax) tag($tag) att(",%att,")"); + + my $node = new XML::Stream::Node($tag); + $node->put_attrib(%att); + + $self->debug(2,"Node: _handle_element: check(",$#{$self->{SIDS}->{$sid}->{node}},")"); + + if ($#{$self->{SIDS}->{$sid}->{node}} >= 0) + { + $self->{SIDS}->{$sid}->{node}->[$#{$self->{SIDS}->{$sid}->{node}}]-> + add_child($node); + } + + push(@{$self->{SIDS}->{$sid}->{node}},$node); +} + + +############################################################################## +# +# _handle_cdata - handles the CDATA that is encountered. Also, in the +# spirit of XML::Parser::Node it combines any sequential +# CDATA into one tag. +# +############################################################################## +sub _handle_cdata +{ + my $self; + $self = $_[0] if (ref($_[0]) eq "XML::Stream::Parser"); + $self = shift unless (ref($_[0]) eq "XML::Stream::Parser"); + my ($sax, $cdata) = @_; + my $sid = $sax->getSID(); + + $self->debug(2,"Node: _handle_cdata: sid($sid) sax($sax) cdata($cdata)"); + + return if ($#{$self->{SIDS}->{$sid}->{node}} == -1); + + $self->debug(2,"Node: _handle_cdata: sax($sax) cdata($cdata)"); + + $self->{SIDS}->{$sid}->{node}->[$#{$self->{SIDS}->{$sid}->{node}}]-> + add_cdata($cdata); +} + + +############################################################################## +# +# _handle_close - when we see a close tag we need to pop the last element +# from the list and push it onto the end of the previous +# element. This is how we build our hierarchy. +# +############################################################################## +sub _handle_close +{ + my $self; + $self = $_[0] if (ref($_[0]) eq "XML::Stream::Parser"); + $self = shift unless (ref($_[0]) eq "XML::Stream::Parser"); + my ($sax, $tag) = @_; + my $sid = $sax->getSID(); + + $self->debug(2,"Node: _handle_close: sid($sid) sax($sax) tag($tag)"); + + $self->debug(2,"Node: _handle_close: check(",$#{$self->{SIDS}->{$sid}->{node}},")"); + + if ($#{$self->{SIDS}->{$sid}->{node}} == -1) + { + $self->debug(2,"Node: _handle_close: rootTag($self->{SIDS}->{$sid}->{rootTag}) tag($tag)"); + if ($self->{SIDS}->{$sid}->{rootTag} ne $tag) + { + $self->{SIDS}->{$sid}->{streamerror} = "Root tag mis-match: <$self->{SIDS}->{$sid}->{rootTag}> ... </$tag>\n"; + } + return; + } + + my $CLOSED = pop @{$self->{SIDS}->{$sid}->{node}}; + + $self->debug(2,"Node: _handle_close: check2(",$#{$self->{SIDS}->{$sid}->{node}},")"); + + if($#{$self->{SIDS}->{$sid}->{node}} == -1) + { + push @{$self->{SIDS}->{$sid}->{node}}, $CLOSED; + + if (ref($self) ne "XML::Stream::Parser") + { + my $stream_prefix = $self->StreamPrefix($sid); + + if(defined($self->{SIDS}->{$sid}->{node}->[0]) && + ($self->{SIDS}->{$sid}->{node}->[0]->get_tag() =~ /^${stream_prefix}\:/)) + { + my $node = $self->{SIDS}->{$sid}->{node}->[0]; + $self->{SIDS}->{$sid}->{node} = []; + $self->ProcessStreamPacket($sid,$node); + } + else + { + my $node = $self->{SIDS}->{$sid}->{node}->[0]; + $self->{SIDS}->{$sid}->{node} = []; + + my @special = + &XML::Stream::XPath( + $node, + '[@xmlns="'.&XML::Stream::ConstXMLNS("xmpp-sasl").'" or @xmlns="'.&XML::Stream::ConstXMLNS("xmpp-tls").'"]' + ); + if ($#special > -1) + { + my $xmlns = $node->get_attrib("xmlns"); + + $self->ProcessSASLPacket($sid,$node) + if ($xmlns eq &XML::Stream::ConstXMLNS("xmpp-sasl")); + $self->ProcessTLSPacket($sid,$node) + if ($xmlns eq &XML::Stream::ConstXMLNS("xmpp-tls")); + } + else + { + &{$self->{CB}->{node}}($sid,$node); + } + } + } + } +} + + +############################################################################## +# +# SetXMLData - takes a host of arguments and sets a portion of the specified +# XML::Parser::Node object with that data. The function works +# in two modes "single" or "multiple". "single" denotes that +# the function should locate the current tag that matches this +# data and overwrite it's contents with data passed in. +# "multiple" denotes that a new tag should be created even if +# others exist. +# +# type - single or multiple +# XMLTree - pointer to XML::Stream Node object +# tag - name of tag to create/modify (if blank assumes +# working with top level tag) +# data - CDATA to set for tag +# attribs - attributes to ADD to tag +# +############################################################################## +sub SetXMLData +{ + my ($type,$XMLTree,$tag,$data,$attribs) = @_; + + if ($tag ne "") + { + if ($type eq "single") + { + foreach my $child ($XMLTree->children()) + { + if ($$XMLTree[1]->[$child] eq $tag) + { + $XMLTree->remove_child($child); + + my $newChild = $XMLTree->add_child($tag); + $newChild->put_attrib(%{$attribs}); + $newChild->add_cdata($data) if ($data ne ""); + return; + } + } + } + my $newChild = $XMLTree->add_child($tag); + $newChild->put_attrib(%{$attribs}); + $newChild->add_cdata($data) if ($data ne ""); + } + else + { + $XMLTree->put_attrib(%{$attribs}); + $XMLTree->add_cdata($data) if ($data ne ""); + } +} + + +############################################################################## +# +# GetXMLData - takes a host of arguments and returns various data structures +# that match them. +# +# type - "existence" - returns 1 or 0 if the tag exists in the +# top level. +# "value" - returns either the CDATA of the tag, or the +# value of the attribute depending on which is +# sought. This ignores any mark ups to the data +# and just returns the raw CDATA. +# "value array" - returns an array of strings representing +# all of the CDATA in the specified tag. +# This ignores any mark ups to the data +# and just returns the raw CDATA. +# "tree" - returns an XML::Parser::Node object with the +# specified tag as the root tag. +# "tree array" - returns an array of XML::Parser::Node +# objects each with the specified tag as +# the root tag. +# "child array" - returns a list of all children nodes +# not including CDATA nodes. +# "attribs" - returns a hash with the attributes, and +# their values, for the things that match +# the parameters +# "count" - returns the number of things that match +# the arguments +# "tag" - returns the root tag of this tree +# XMLTree - pointer to XML::Parser::Node object +# tag - tag to pull data from. If blank then the top level +# tag is accessed. +# attrib - attribute value to retrieve. Ignored for types +# "value array", "tree", "tree array". If paired +# with value can be used to filter tags based on +# attributes and values. +# value - only valid if an attribute is supplied. Used to +# filter for tags that only contain this attribute. +# Useful to search through multiple tags that all +# reference different name spaces. +# +############################################################################## +sub GetXMLData +{ + my ($type,$XMLTree,$tag,$attrib,$value) = @_; + + $tag = "" if !defined($tag); + $attrib = "" if !defined($attrib); + $value = "" if !defined($value); + + my $skipthis = 0; + + #------------------------------------------------------------------------- + # Check if a child tag in the root tag is being requested. + #------------------------------------------------------------------------- + if ($tag ne "") + { + my $count = 0; + my @array; + foreach my $child ($XMLTree->children()) + { + if (($child->get_tag() eq $tag) || ($tag eq "*")) + { + #------------------------------------------------------------- + # Filter out tags that do not contain the attribute and value. + #------------------------------------------------------------- + next if (($value ne "") && ($attrib ne "") && $child->get_attrib($attrib) && ($XMLTree->get_attrib($attrib) ne $value)); + next if (($attrib ne "") && !$child->get_attrib($attrib)); + + #------------------------------------------------------------- + # Check for existence + #------------------------------------------------------------- + if ($type eq "existence") + { + return 1; + } + #------------------------------------------------------------- + # Return the raw CDATA value without mark ups, or the value of + # the requested attribute. + #------------------------------------------------------------- + if ($type eq "value") + { + if ($attrib eq "") + { + my $str = $child->get_cdata(); + return $str; + } + return $XMLTree->get_attrib($attrib) + if defined($XMLTree->get_attrib($attrib)); + } + #------------------------------------------------------------- + # Return an array of values that represent the raw CDATA without + # mark up tags for the requested tags. + #------------------------------------------------------------- + if ($type eq "value array") + { + if ($attrib eq "") + { + my $str = $child->get_cdata(); + push(@array,$str); + } + else + { + push(@array, $XMLTree->get_attrib($attrib)) + if defined($XMLTree->get_attrib($attrib)); + } + } + #------------------------------------------------------------- + # Return a pointer to a new XML::Parser::Tree object that has + # the requested tag as the root tag. + #------------------------------------------------------------- + if ($type eq "tree") + { + return $child; + } + #------------------------------------------------------------- + # Return an array of pointers to XML::Parser::Tree objects + # that have the requested tag as the root tags. + #------------------------------------------------------------- + if ($type eq "tree array") + { + push(@array,$child); + } + #------------------------------------------------------------- + # Return an array of pointers to XML::Parser::Tree objects + # that have the requested tag as the root tags. + #------------------------------------------------------------- + if ($type eq "child array") + { + push(@array,$child) if ($child->get_tag() ne "__xmlstream__:node:cdata"); + } + #------------------------------------------------------------- + # Return a count of the number of tags that match + #------------------------------------------------------------- + if ($type eq "count") + { + $count++; + } + #------------------------------------------------------------- + # Return the attribute hash that matches this tag + #------------------------------------------------------------- + if ($type eq "attribs") + { + return $XMLTree->attrib(); + } + } + } + #--------------------------------------------------------------------- + # If we are returning arrays then return array. + #--------------------------------------------------------------------- + if (($type eq "tree array") || ($type eq "value array") || + ($type eq "child array")) + { + return @array; + } + + #--------------------------------------------------------------------- + # If we are returning then count, then do so + #--------------------------------------------------------------------- + if ($type eq "count") + { + return $count; + } + } + else + { + #--------------------------------------------------------------------- + # This is the root tag, so handle things a level up. + #--------------------------------------------------------------------- + + #--------------------------------------------------------------------- + # Return the raw CDATA value without mark ups, or the value of the + # requested attribute. + #--------------------------------------------------------------------- + if ($type eq "value") + { + if ($attrib eq "") + { + my $str = $XMLTree->get_cdata(); + return $str; + } + return $XMLTree->get_attrib($attrib) + if $XMLTree->get_attrib($attrib); + } + #--------------------------------------------------------------------- + # Return a pointer to a new XML::Parser::Tree object that has the + # requested tag as the root tag. + #--------------------------------------------------------------------- + if ($type eq "tree") + { + return $XMLTree; + } + + #--------------------------------------------------------------------- + # Return the 1 if the specified attribute exists in the root tag. + #--------------------------------------------------------------------- + if ($type eq "existence") + { + if ($attrib ne "") + { + return ($XMLTree->get_attrib($attrib) eq $value) if ($value ne ""); + return defined($XMLTree->get_attrib($attrib)); + } + return 0; + } + + #--------------------------------------------------------------------- + # Return the attribute hash that matches this tag + #--------------------------------------------------------------------- + if ($type eq "attribs") + { + return $XMLTree->attrib(); + } + #--------------------------------------------------------------------- + # Return the tag of this node + #--------------------------------------------------------------------- + if ($type eq "tag") + { + return $XMLTree->get_tag(); + } + } + #------------------------------------------------------------------------- + # Return 0 if this was a request for existence, or "" if a request for + # a "value", or [] for "tree", "value array", and "tree array". + #------------------------------------------------------------------------- + return 0 if ($type eq "existence"); + return "" if ($type eq "value"); + return []; +} + + +############################################################################## +# +# BuildXML - takes an XML::Parser::Tree object and builds the XML string +# that it represents. +# +############################################################################## +sub BuildXML +{ + my ($node,$rawXML) = @_; + + my $str = "<".$node->get_tag(); + + my %attrib = $node->attrib(); + + foreach my $att (sort {$a cmp $b} keys(%attrib)) + { + $str .= " ".$att."='".&XML::Stream::EscapeXML($attrib{$att})."'"; + } + + my @children = $node->children(); + if (($#children > -1) || + (defined($rawXML) && ($rawXML ne "")) || + (defined($node->get_raw_xml()) && ($node->get_raw_xml() ne "")) + ) + { + $str .= ">"; + foreach my $child (@children) + { + if ($child->get_tag() eq "__xmlstream__:node:cdata") + { + $str .= &XML::Stream::EscapeXML(join("",$child->children())); + } + else + { + $str .= &XML::Stream::Node::BuildXML($child); + } + } + $str .= $node->get_raw_xml() + if (defined($node->get_raw_xml()) && + ($node->get_raw_xml() ne "") + ); + $str .= $rawXML if (defined($rawXML) && ($rawXML ne "")); + $str .= "</".$node->get_tag().">"; + } + else + { + $str .= "/>"; + } + + return $str; +} + + +############################################################################## +# +# XML2Config - takes an XML data tree and turns it into a hash of hashes. +# This only works for certain kinds of XML trees like this: +# +# <foo> +# <bar>1</bar> +# <x> +# <y>foo</y> +# </x> +# <z>5</z> +# </foo> +# +# The resulting hash would be: +# +# $hash{bar} = 1; +# $hash{x}->{y} = "foo"; +# $hash{z} = 5; +# +# Good for config files. +# +############################################################################## +sub XML2Config +{ + my ($XMLTree) = @_; + + my %hash; + foreach my $tree (&XML::Stream::GetXMLData("tree array",$XMLTree,"*")) + { + if ($tree->get_tag() eq "__xmlstream__:node:cdata") + { + my $str = join("",$tree->children()); + return $str unless ($str =~ /^\s*$/); + } + else + { + if (&XML::Stream::GetXMLData("count",$XMLTree,$tree->get_tag()) > 1) + { + push(@{$hash{$tree->get_tag()}},&XML::Stream::XML2Config($tree)); + } + else + { + $hash{$tree->get_tag()} = &XML::Stream::XML2Config($tree); + } + } + } + return \%hash; +} + + +1; |
