# $Header$
#
# FILE:    Element.pm
# AUTHORS: Erik Nygren (nygren@mit.edu)
#
# XML Element object.  Can be parsed out of a XML document
# and may contain attributes, PCDATA, and other elements.
# This allows the parsing of generic XML documents into 
# a large tree of elements.
#
# Eventually it may make sense to allow for subclasses to be
# automagically created as appropriate based on the tags.
#

require 5.002;

use strict;

BEGIN {
    require XML::Parser;
}

package XML::Element;

use vars qw/ $pstk $peltype $thetree /;

$pstk = "XML::Element.stack";
$peltype = "XML::Element.type";

sub new {
    my ($type, $tag) = @_;
    my $self = {};
    $self->{"tag"} = $tag;	# this element's tag
    $self->{"Kids"} = [];	# a reference to an array containing elements
				# and strings (for cdata)
    $self->{"attrib"} = [];	# a hash of attributes for this element
    return bless $self, $type;
}

# Parses in a file into an XML tree.  The root
# of the tree is an Element that contains all of the other
# root elements in the document.
# Note that XML::Parser also has an "Objects" style which does this,
# but this is a little simpler for this particular application.
sub parseFromFile {
    my ($type, $filename) = @_;

    my $parser = new XML::Parser(ErrorContext => 2,
				 Handlers => {
	Start => \&xmlparse_start,
	End => \&xmlparse_end,
	Char => \&xmlparse_char
	});

    $parser->{$peltype}  = $type;
    
    # We keep track of the parsing state on a stack.
    # The top of the stack contains the current element
    # we're having fun with...  :)
    $parser->{$pstk} = [];
    unshift @{$parser->{$pstk}}, $type->new("FILEROOT");

    $parser->parsefile($filename);

    return (pop @{$parser->{$pstk}});
}

# Handles start tags...
# Basically we create a new element and add it to the current 
# element's contents and then push it onto the stack.
sub xmlparse_start {
    my ($p, $tag, @attrib) = @_;
    my $el = $p->{$peltype}->new($tag);
    $el->{"attrib"} = \@attrib;
    #printf "Start <%s> (%s)\n", $tag, $el;
    push @{$p->{$pstk}->[0]->{"Kids"}}, $el;
    unshift @{$p->{$pstk}}, $el;
}

# Handles end tags...
# Basically we pop the top element off of the stack after checking.
sub xmlparse_end {
    my ($p, $tag) = @_;
    #printf "End </%s>\n", $tag;
    my $el = $p->{$pstk}->[0];
    if ($el->getTag ne $tag) {
	printf "Warning: end tag </%s> doesn't match start tag <%s>\n",
	            $tag, $el->getTag;
    }
    shift @{$p->{$pstk}};
}

# Handles CDATA...
# Push the text onto the element at the top of the stack.
sub xmlparse_char {
    my ($p, $text) = @_;
    #printf "CDATA [%s]\n", $text;
    my $el = $p->{$pstk}->[0];
    push @{$el->{"Kids"}}, $text;
}

sub getTag {
    my ($this) = @_;
    return $this->{"tag"};
}

sub getKids {
    my ($this) = @_;
    return @{$this->{"Kids"}};
}

# Gets the text out of an element and its sub elements and strips off
# leading and trailing whitespace
sub getText {
    my ($this, $withwhitespace) = @_;
    my $s = "";
    for (@{$this->{"Kids"}}) {
	if (ref $_) {
	    $s .= $_->getText(1);
	} else {
	    $s .= $_;
	}
    }
    if ($withwhitespace != 1) {
	$s =~ s/^\s*//;
	$s =~ s/\s*$//;
    }
    return $s;
}

# Returns the first sub-element with a particular tag, if one exists.
sub getEl {
    my ($this, $tag) = @_;
    for (@{$this->{"Kids"}}) {
	if ((ref $_) && ($_->{"tag"} eq $tag)) {
	    return $_;
	}
    }
    return 0;
}

# Returns the text in an sub-element with a tag
sub getElText {
    my ($this, $tag) = @_;
    my $el = $this->getEl($tag);
    if (!$el) {
	die "couldn't find element with tag $tag\n";
    }
    return $el->getText();
}

# Returns a list of elements a particular tag.
sub getEls {
    my ($this, $tag) = @_;
    my @els;
    for (@{$this->{"Kids"}}) {
	if ((ref $_) && ($_->{"tag"} eq $tag)) {
	    push @els, $_;
	}
    }
    return @els;
}

sub printDebug {
    my ($this) = @_;
    
    printf "<%s", $this->getTag;
#    my ($key, $val);
#    foreach ($key,$val) in (@{$this->{"attribs"}}) {
#	printf " %s=%s", $key, $val;
#    }
    print ">\n";
    foreach (@{$this->{"Kids"}}) {
	if (ref $_) {
	    $_->printDebug;
	} else {
	    print $_;
	}
    }
    printf "</%s>\n", $this->getTag;
}



#$thetree = XML::Element->parseFromFile($ARGV[0]);
#print "\n\n=============================================\n\n\n";
#$thetree->printDebug;
