use strict;
package XML::XBEL;

use base qw (XML::XBEL::item
	     XML::XBEL::container);

# $Id: XBEL.pm,v 1.7 2004/07/03 06:17:50 asc Exp $

=head1 NAME 

XML::XBEL - OOP for reading and writing XBEL documents.

=head1 SYNOPSIS

 # creating an XBEL document

 use XML::XBEL;
 use XML::XBEL::Folder;
 use XML::XBEL::Bookmark;

 my $xbel = XML::XBEL->new();
 $xbel->new_document({title=>"My Bookmarks"});

 $xbel->add_bookmark({href  => "http://foo.com",
	 	      title => "foo",
		      desc  => "bar"});

 my $folder1 = XML::XBEL::Folder->new({title => "comp"});
 my $folder2 = XML::XBEL::Folder->new({title => "lang"});
 my $folder3 = XML::XBEL::Folder->new({title => "perl"});

 my $bm = XML::XBEL::Bookmark->new({"title=>"misc"});
 $bm->href("http://groups.google.com/groups?q=comp.lang.perl.misc");

 $folder3->add_bookmark($bm);
 $folder2->add_folder($folder3);
 $folder1->add_folder($folder2);

 $xbel->add_folder($folder1);

 print $xbel->toString();

 # parsing an XBEL document

 use XML::XBEL;

 my $xbel = XML::XBEL->new();
 $xbel->parse_file($file);
 
 foreach my $bm ($xbel->bookmarks()) {

     print sprintf("%s points to %s\n",
		   $_->title(),
		   $_->href());
 } 

=head1 DESCRIPTION

OOP for reading and writing XBEL files.

=cut

$XML::XBEL::VERSION = '1.2';

use XML::LibXML;

=head1 PACKAGE METHODS

=cut

=head2 __PACKAGE__->new()

Returns an I<XML::XBEL> object.

=cut

sub new {
    my $pkg  = shift;

    return bless {'__doc'  => undef,
		  '__root' => undef } , $pkg;
}

=head1 OBJECT METHODS

=cut

=head2 $self->parse_file($file)

Returns true or false.

=cut

sub parse_file {
    my $self = shift;
    my $file = shift;

    my $parser = XML::LibXML->new();
    my $doc = $parser->parse_file($file);

    return $self->_parse($doc);
}

=head2 $self->parse_string($string)

Returns true or false.

=cut

sub parse_string {
    my $self = shift;
    my $str  = shift;

    my $parser = XML::LibXML->new();
    my $doc = $parser->parse_string($str);

    return $self->_parse($doc);
}

sub _parse {
    my $self = shift;
    my $doc  = shift;

    $self->{'__doc'}  = $doc;
    $self->{'__root'} = $doc->documentElement();

    return 1;
}

=head2 $obj->new_document(\%args)

Valid arguments are :

=over 4

=item * B<title>

String.

=item * B<desc>

String.

=item * B<info>

Hash ref, with the following key/value pairs :

=over 6

=item * I<owner>

Array ref.

=back

=back

Returns true or false.

=cut

sub new_document {
    my $self = shift;
    my $args = shift;

    my $doc = XML::LibXML::Document->new();

    if ($args->{encoding}) {
	$doc->setEncoding($args->{encoding});
    }

    my $root = XML::LibXML::Element->new("xbel");
    $doc->setDocumentElement($root);

    $self->{'__doc'}  = $doc;
    $self->{'__root'} = $root;

    foreach my $el ("title","desc","info") {

	if (! exists($args->{$el})) {
	    next;
	}

	$self->$el($args->{$el});
    }

    return 1;
}

=head2 $obj->title($title)

Get/set the title for an XBEL document.

Returns a string when called with no arguments;
otherwise returns true or false.

=cut

# Defined in XML::XBEL::item

=head2 $obj->desc($description)

Get/set the description for an XBEL document.

Returns a string when called with no arguments;
otherwise returns true or false.

=cut

# Defined in XML::XBEL::item

=head2 $obj->info(\%args)

Get/set the metadata for an XBEL document.

Valid args are :

=over 4

=item * B<owner>

Array reference

=back

Returns an array reference when called with no arguments;
otherwise returns true or false.

=cut

# Defined in XML::XBEL::info

=head2 $obj->bookmarks($recursive)

Returns a list of child I<XML::XBEL::Bookmark> objects.

Where I<$recursive> is a boolean indicating whether to
return all the bookmarks in an XBEL document or only its
immediate children.

=cut

# Defined in XML::XBEL::container

=head2 $obj->folders($recursive)

Returns a list of child I<XML::XBEL::Folder> objects.

Where I<$recursive> is a boolean indicating whether to
return all the folders in an XBEL document or only its
immediate children.

=cut

# Defined in XML::XBEL::container

=head2 $obj->aliases($recursive)

Returns a list of child I<XML::XBEL::Alias> objects.

Where I<$recursive> is a boolean indicating whether to
return all the aliases in an XBEL document or only its
immediate children.

=cut

# Defined in XML::XBEL::container

=head2 $obj->find_by_id($id)

Returns an I<XML::XBEL::Bookmark> or I<XML::XBEL::Folder>
object whose id attribute matches $id.

=cut

sub find_by_id {
    my $self = shift;
    my $id   = shift;

    my $node = ($self->{'__root'}->findnodes("//child::*[\@id='$id']"))[0];

    # print sprintf("%s %s\n",$node,$node->nodeName());

    if (! $node) {
	return undef;
    }

    elsif ($node->nodeName() eq "folder") {
	require XML::XBEL::Folder;
	return XML::XBEL::Folder->build_node($node);
    }

    elsif ($node->nodeName() eq "bookmark") {
	require XML::XBEL::Bookmark;
	return XML::XBEL::Bookmark->build_node($node);
    }

    else {
	return undef;
    }
}

=head2 $obj->find_by_href($href)

Returns a list of I<XML::XBEL::Bookmark> objects whose 
href attribute matches $href.

=cut

sub find_by_href {
    my $self = shift;
    my $href = shift;

    my @nodes = $self->{'__root'}->findnodes("//child::*[name()='bookmark' and \@href='$href']");

    if (! @nodes) {
	return undef;
    }

    require XML::XBEL::Bookmark;
	
    return map { 
	XML::XBEL::Bookmark->build_node($_);
    } @nodes
}

=head2 $obj->add_bookmark((XML::XBEL::Bookmark || \%args))

Add a new bookmark to an XBEL document.

If passed a hash ref, valid arguments are the same as those
defined for the I<XML::XBEL::Bookmark> object constructor.

=cut

# Defined in XML::XBEL::container

=head2 $obj->add_folder((XML::XBEL::Folder || \%args))

Add a new folder to an XBEL document.

If passed a hash ref, valid arguments are the same as those
defined for the I<XML::XBEL::Folder> object constructor.

=cut

# Defined in XML::XBEL::container

=head2 $obj->add_alias((XML::XBEL::Alias || \%args))

Add a new alias to an XBEL document.

If passed a hash ref, valid arguments are the same as those
defined for the I<XML::XBEL::Alias> object constructor.

=cut

# Defined in XML::XBEL::container

=head2 $obj->add_separator()

Add a new separator to an XBEL document.

=cut

# Defined in XML::XBEL::container

=head2 $obj->toString($format)

=cut

sub toString {
    my $self = shift;
    $self->{'__doc'}->toString(@_);
}

=head2 $obj->toFile($filename,$format)

=cut

sub toFile {
    my $self = shift;
    $self->{'__doc'}->toString(@_);
}

=head2 $obj->toFH(\*$fh,$format)

=cut

sub toFH {
    my $self = shift;
    $self->{'__doc'}->toString(@_);
}

=head2 $obj->toSAX(A::SAX::Handler)

Generate SAX events for the XBEL object.

=cut

sub toSAX {
    my $self    = shift;
    my $handler = shift;

    require XML::LibXML::SAX::Parser;
    my $gen = XML::LibXML::SAX::Parser->new(Handler => $handler);
    $gen->generate($self->{'__doc'});
}

=head1 VERSION

1.2

=head1 DATE

$Date: 2004/07/03 06:17:50 $

=head1 AUTHOR

Aaron Straup Cope E<lt>ascope@cpan.orgE<gt>

=head1 SEE ALSO

L<XML::XBEL::Folder>

L<XML::XBEL::Bookmark>

L<XML::XBEL::Alias>

L<XML::XBEL::Separator>

=head1 BUGS

It's possible. Please report all bugs via http://rt.cpan.org

=head1 LICENSE

Copyright (c) 2004 Aaron Straup Cope. All rights reserved.

This is free software, you may use it and distribute it under the
same terms as Perl itself.

=cut

return 1;
