=head1 NAME

iPE::XML::Object - A base class for iParameterEstimation for all classes which initialize themselves with XML files.

=head1 DESCRIPTION

This is a base class which provides the functionality for a heirarchical XML parser.  When a new XML::Object is invoked within an XML::Object, the current object is pushed onto a global stack, and popped off once the parse_complete method is called.  Subclassed XML::Objects should implement the handle_subelement() method to take in all the nested elements below the object being created.

=cut

package iPE::XML::Object;
use XML::LibXML;
use iPE::Util::Output;
use strict;

=head1 FUNCTIONS

=over 8

=item new(tag, attributes, data, element)

This method constructs a new XML object.  When subclassing this class, you should call this constructor first.  If you are expecting children elements, you should call handle_children at the end of your constructor.

=cut
sub new {
    my $invocant = shift;
    my $class = ref($invocant) || $invocant;
    my $this = bless {}, $class;
    my ($tag, $att, $data, $element) = @_;

    $this->{xml_element_} = $element;

    return $this;
}

=item init ()

Empty function that should be overridden if any initialization is desired for post-XML parsing cleanup or initialization.

=cut
sub init {}

=item element ()

Returns the XML::LibXML::Element object associated with this XML::Object.

=cut
sub element { shift->{xml_element_} }

=item document ()

Get the owning document of the object.

=cut
sub document { shift->element->getOwner }

=item handle_children (node)

Goes through all subelements and passes them up to the subclass to see if it wants to do anything with it via handle_subelement

=cut
sub handle_children {
    my ($this, $node) = @_;

    for my $element ($node->childNodes) {
        next if $element->nodeType != XML_ELEMENT_NODE;
        my $tag = $element->nodeName;
        my %att;
        for my $attr ($element->attributes) {
            $att{$attr->name} = $attr->value;
        }
        my $data = "";
        for my $node ($element->childNodes) {
            next if ($node->nodeType != XML_TEXT_NODE || $node->data !~ /\S/);
            my $rawData = $node->data;
            $data = "";
            for my $line (split (/\n/, $rawData)) {
                next if ($line !~ /\S/);
                $line =~ s/^\s*(\S)/$1/; chomp($line); 
                $line =~ s/(\S)\s*$/$1/; $line .= "\n";
                $data .= $line;
            }
        }

        $this->handle_subelement($tag, \%att, $data, $element);
    }
}

=item handle_subelement (tag, att, data)

This is a stub definitions in the base class.  Override this method to handle any nested elements within XML tags.  For example, if your element was "cook", and you had the following structure in the document:

<cook name="Meg">
    <helper name="Bernie" can="dance">Right hand man.</helper>
</cook>

handle_subelement would be called with the parameters ("helper", {name => "bernie", can => "dance"}, "Right hand man.")

If the node you were passed contains subelements that you want to be contained in the current object, you may call handle_children with the node you received.

=cut
sub handle_subelement {}

=item removeChildren (node)

Removes the children of a node and returns an an array reference of copies of the children nodes.

=cut
sub removeChildren {
    my ($this, $node) = @_;

    my @clones;
    for my $child ($node->childNodes) {
        push @clones, $child->cloneNode(1);
        $node->removeChild($child);
    }

    return \@clones;
}

=item outputPrepare (out, mode)

Stub function in the root class.  This should be overridden if you have any preparation to do before the XML::Object will be outputted.  If you have a text element (PCData) associated with your object, you should set it with addPCData () or setPCData ().

=cut
sub outputPrepare {}

=item getPCData ()

Gets the parsed character data for this node.

=cut
sub getPCData  { shift->element->textContent }

=item addPCData (string)

Adds parsed character data to the element.  This does not replace any old data but rather adds to the end of the data.

=cut
sub addPCData { $_[0]->element->appendTextNode($_[1]) }

=item setPCData (string)

Sets the parsed character data between the tags of the element.  This will replace any old data that once was in the element.

=cut
sub setPCData {
    my ($this, $str) = @_;
    my $element = $this->element;
    for my $node ($this->element->childNodes) {
        if ($node->nodeType == XML_TEXT_NODE) {
            $element->removeChild($node);
        }
    }
    $element->appendTextNode("\n".$str);
}

=item setAttribute (name, value)

This sets the named attribute to value.   Note that this will create the attribute to this element if it doesn't already exist.

=cut
sub setAttribute {
    my ($this, $name, $value) = @_;
    $this->element->setAttribute($name, $value);
}

=item createSubElement (name, parentElement)

This creates a new element node below the parentElement node named name and returns it.

=cut
sub createSubElement {
    my ($this, $name, $parentElement) = @_;
    my $doc = $parentElement->ownerDocument;
    my $node = $doc->createElement($name);
    $parentElement->addChild($node);
    return $node;
}

=item outputXML (out)

Prints the string associated with the XML of this document.

=cut
sub outputXML { 
    my ($this, $out) = @_;
    $this->outputPrepare($out);
    $out->print($this->element->toString);
}


#private functions
=back

=head1 SEE ALSO

L<XML::DocObject>

=head1 AUTHOR

Bob Zimmermann (rpz@cse.wustl.edu)

=cut

1;
