AcePerl

 view release on metacpan or  search on metacpan

Ace/Object.pm  view on Meta::CPAN

package Ace::Object;
use strict;
use Carp qw(:DEFAULT cluck);

# $Id: Object.pm,v 1.60 2005/04/13 14:26:08 lstein Exp $

use overload 
    '""'       => 'name',
    '=='       => 'eq',
    '!='       => 'ne',
    'fallback' => 'TRUE';
use vars qw($AUTOLOAD $DEFAULT_WIDTH %MO $VERSION);
use Ace 1.50 qw(:DEFAULT rearrange);

# if set to 1, will conflate tags in XML output
use constant XML_COLLAPSE_TAGS => 1;
use constant XML_SUPPRESS_CONTENT=>1;
use constant XML_SUPPRESS_CLASS=>1;
use constant XML_SUPPRESS_VALUE=>0;
use constant XML_SUPPRESS_TIMESTAMPS=>0;

require AutoLoader;

$DEFAULT_WIDTH=25;  # column width for pretty-printing
$VERSION = '1.66';

# Pseudonyms and deprecated methods.
*isClass        =  \&isObject;
*pick           =  \&fetch;
*get            =  \&search;
*add            =  \&add_row;

sub AUTOLOAD {
    my($pack,$func_name) = $AUTOLOAD=~/(.+)::([^:]+)$/;
    my $self = $_[0];

    # This section works with Autoloader
    my $presumed_tag = $func_name =~ /^[A-Z]/ && $self->isObject;  # initial_cap 

    if ($presumed_tag) {
      croak "Invalid object tag \"$func_name\"" 
	if $self->db && $self->model && !$self->model->valid_tag($func_name);

      shift();  # get rid of the object
      my $no_dereference;
      if (defined($_[0])) {
	if ($_[0] eq '@') {
	  $no_dereference++;
	  shift();
	} elsif ($_[0] =~ /^\d+$/) {
	  $no_dereference++;
	}
      }

      $self = $self->fetch if !$no_dereference && 
	!$self->isRoot && $self->db;  # dereference, if need be
      croak "Null object tag \"$func_name\"" unless $self;

      return $self->search($func_name,@_) if wantarray;
      my ($obj) = @_ ? $self->search($func_name,@_) : $self->search($func_name,1);

      # these nasty heuristics simulate aql semantics.
      # undefined return
      return unless defined $obj;

      # don't dereference object if '@' symbol specified
      return $obj if $no_dereference;

      # don't dereference if an offset was explicitly specified
      return $obj if defined($_[0]) && $_[0] =~ /\d+/;

      # otherwise dereference if the current thing is an object or we are at a tag
      # and the thing to the right is an object.
      return $obj->fetch if $obj->isObject && !$obj->isRoot;  # always dereference objects

      # otherwise return the thing itself
      return $obj;
    } elsif ($func_name =~ /^[A-Z]/ && $self->isTag) {  # follow tag
      return $self->search($func_name);
    } else {
      $AutoLoader::AUTOLOAD = __PACKAGE__ . "::$func_name";
      goto &AutoLoader::AUTOLOAD;
    }
}

sub DESTROY {
  my $self = shift;

  return unless defined $self->{class};      # avoid working with temp objects from a search()
  return if caller() =~ /^(Cache\:\:|DB)/;  # prevent recursion in FileCache code
  my $db = $self->db or return;
  return if $self->{'.nocache'};
  return unless $self->isRoot;

  if ($self->_dirty) {
    warn "Destroy for ",overload::StrVal($self)," ",$self->class,':',$self->name if Ace->debug;
    $self->_dirty(0);

Ace/Object.pm  view on Meta::CPAN

    my $self = shift;
    $self->{'name'} = shift if  defined($_[0]);
    my $name = $self->_ace_format($self->{'class'},$self->{'name'});
    $name;
}

################### class of the object #################
sub class {
    my $self = shift;
    defined($_[0])
	? $self->{'class'} = shift
	: $self->{'class'};
}

################### name and class together #################
sub id {
  my $self = shift;
  return "$self->{class}:$self->{name}";
}

############## return true if two objects are equivalent ##################
# to be equivalent, they must have identical names, classes and databases #
# We handle comparisons between objects and numbers ourselves, and let    #
# Perl handle comparisons between objects and strings                     #
sub eq {
    my ($a,$b,$rev) = @_;
    unless (UNIVERSAL::isa($b,'Ace::Object')) {
	$a = $a->name + 0; # convert to numeric
	return $a == $b;  # do a numeric comparison
    }
    return 1 if ($a->name eq $b->name) 
      && ($a->class eq $b->class)
	&& ($a->db eq $b->db);
    return;
}

sub ne { 
    return !&eq;
}


############ returns true if this is a top-level object #######
sub isRoot {
  return exists shift()->{'.root'};
}

################### handle to ace database #################
sub db {
  my $self = shift;
  if (@_) {
    my $db = shift;
    $self->{db} = "$db";  # store string representation, not object
  }
  Ace->name2db($self->{db});
}

### Return a portion of the tree at the indicated tag path     ###
#### In a list context returns the column.  In an array context ###
#### returns a pointer to the subtree ####
#### Usually returns what is pointed to by the tag.  Will return
#### the parent object if you pass a true value as the second argument
sub at {
    my $self = shift;
    my($tag,$pos,$return_parent) = rearrange(['TAG','POS','PARENT'],@_);
    return $self->right unless $tag;
    $tag = lc $tag;

    # Removed a $` here to increase speed -- tim.cutts@incyte.com 2 Sep 1999

    if (!defined($pos) and $tag=~/(.*?)\[(\d+)\]$/) {
      $pos = $2;
      $tag = $1;
    }

    my $o = $self;
    my ($parent,$above,$left);
    my (@tags) = $self->_split_tags($tag);
    foreach $tag (@tags) {
      $tag=~s/$;/./g; # unprotect backslashed dots
      my $p = $o;
      ($o,$above,$left) = $o->_at($tag);
      return unless defined($o);
    }
    return $above || $left if $return_parent;
    return defined $pos ? $o->right($pos) : $o unless wantarray;
    return $o->col($pos);
}

### Flatten out part of the tree into an array ####
### along the row.  Will not follow object references.  ###
sub row {
  my $self = shift;
  my $pos = shift;
  my @r;
  my $o = defined $pos ? $self->right($pos) : $self;
  while (defined($o)) {
    push(@r,$o);
    $o = $o->right;
  }
  return @r;
}

### Flatten out part of the tree into an array ####
### along the column. Will not follow object references. ###
sub col {
  my $self = shift;
  my $pos = shift;
  $pos = 1 unless defined $pos;
  croak "Position must be positive" unless $pos >= 0;

  return ($self) unless $pos > 0;

  my @r;
  # This is for tag[1] semantics
  if ($pos == 1) {
    for (my $o=$self->right; defined($o); $o=$o->down) {
      push (@r,$o);
    }
  } else {
    # This is for tag[2] semantics
    for (my $o=$self->right; defined($o); $o=$o->down) {

Ace/Object.pm  view on Meta::CPAN


Return the class of the object.  The return value may be one of
"float," "int," "date," "tag," "txt," "dna," "peptide," and "scalar."
(The last is used internally by Perl to represent objects created
programatically prior to committing them to the database.)  The class
may also be a user-constructed type such as Sequence, Clone or
Author.  These user-constructed types usually have an initial capital
letter.

=head2 db() method

     $db = $object->db();

Return the database that the object is associated with.

=head2 isClass() method

     $bool = $object->isClass();

Returns true if the object is a class (can be fetched from the
database).

=head2 isTag() method

     $bool = $object->isTag();

Returns true if the object is a tag.

=head2 tags() method

     @tags = $object->tags();

Return all the top-level tags in the object as a list.  In the Author
example above, the returned list would be
('Full_name','Laboratory','Address','Paper').  

You can fetch tags more deeply nested in the structure by navigating
inwards using the methods listed below.

=head2 right() and down() methods

     $subtree = $object->right;
     $subtree = $object->right($position);	
     $subtree = $object->down;
     $subtree = $object->down($position);	

B<right()> and B<down()> provide a low-level way of traversing the
tree structure by following the tree's right and down pointers.
Called without any arguments, these two methods will move one step.
Called with a numeric argument >= 0 they will move the indicated
number of steps (zero indicates no movement).

     $full_name = $object->right->right;
     $full_name = $object->right(2);

     $city = $object->right->down->down->right->right->down->down;
     $city = $object->right->down(2)->right(2)->down(2);

If $object contains the "Thierry-Mieg J" Author object, then the first
series of accesses shown above retrieves the string "Jean
Thierry-Mieg" and the second retrieves "34033 Montpellier."  If the
right or bottom pointers are NULL, these methods will return undef.

In addition to being somewhat awkard, you will probably never need to
use these methods.  A simpler way to retrieve the same information
would be to use the at() method described in the next section.  

The right() and down() methods always walk through the tree of the
current object.  They do not follow object pointers into the database.
Use B<fetch()> (or the deprecated B<pick()> or B<follow()> methods)
instead.

=head2 at() method

    $subtree    = $object->at($tag_path);
    @values     = $object->at($tag_path);

at() is a simple way to fetch the portion of the tree that you are
interested in.  It takes a single argument, a simple tag or a path.  A
simple tag, such as "Full_name", must correspond to a tag in the
column immediately to the right of the root of the tree.  A path such
as "Address.Mail" is a dot-delimited path to the subtree.  Some
examples are given below.

    ($full_name)   = $object->at('Full_name');
    @address_lines = $object->at('Address.Mail');

The second line above is equivalent to:

    @address = $object->at('Address')->at('Mail');

Called without a tag name, at() just dereferences the object,
returning whatever is to the right of it, the same as
$object->right

If a path component already has a dot in it, you may escape the dot
with a backslash, as in:

    $s=$db->fetch('Sequence','M4');
    @homologies = $s->at('Homol.DNA_homol.yk192f7\.3';

This also demonstrates that path components don't necessarily have to
be tags, although in practice they usually are.

at() returns slightly different results depending on the context in
which it is called.  In a list context, it returns the column of
values to the B<right> of the tag.  However, in a scalar context, it
returns the subtree rooted at the tag.  To appreciate the difference,
consider these two cases:

    $name1   = $object->at('Full_name');
    ($name2) = $object->at('Full_name');

After these two statements run, $name1 will be the tag object named
"Full_name", and $name2 will be the text object "Jean Thierry-Mieg",
The relationship between the two is that $name1->right leads to
$name2.  This is a powerful and useful construct, but it can be a trap
for the unwary.  If this behavior drives you crazy, use this
construct:
  
    $name1   = $object->at('Full_name')->at();

For finer control over navigation, path components can include
optional indexes to indicate navigation to the right of the current
path component.  Here is the syntax:

    $object->at('tag1[index1].tag2[index2].tag3[index3]...');

Indexes are zero-based.  An index of [0] indicates no movement
relative to the current component, and is the same as not using an
index at all.  An index of [1] navigates one step to the right, [2]
moves two steps to the right, and so on.  Using the Thierry-Mieg
object as an example again, here are the results of various indexes:

    $object = $db->fetch(Author,"Thierry-Mieg J");
    $a = $object->at('Address[0]')   --> "Address"
    $a = $object->at('Address[1]')   --> "Mail"
    $a = $object->at('Address[2]')   --> "CRBM duCNRS"

In an array context, the last index in the path does something very
interesting.  It returns the entire column of data K steps to the
right of the path, where K is the index.  This is used to implement
so-called "tag[2]" syntax, and is very useful in some circumstances.
For example, here is a fragment of code to return the Thierry-Mieg
object's full address without having to refer to each of the
intervening "Mail", "E_Mail" and "Phone" tags explicitly.

   @address = $object->at('Address[2]');
   --> ('CRBM duCNRS','BP 5051','34033 Montpellier','FRANCE',
        'mieg@kaa.cnrs-mop.fr,'33-67-613324','33-67-521559')

Similarly, "tag[3]" will return the column of data three hops to the
right of the tag.  "tag[1]" is identical to "tag" (with no index), and
will return the column of data to the immediate right.  There is no
special behavior associated with using "tag[0]" in an array context;
it will always return the subtree rooted at the indicated tag.

Internal indices such as "Homol[2].BLASTN", do not have special
behavior in an array context.  They are always treated as if they were
called in a scalar context.

Also see B<col()> and B<get()>.

=head2 get() method

    $subtree    = $object->get($tag);
    @values     = $object->get($tag);
    @values     = $object->get($tag, $position);
    @values     = $object->get($tag => $subtag, $position);

The get() method will perform a breadth-first search through the
object (columns first, followed by rows) for the tag indicated by the
argument, returning the column of the portion of the subtree it points
to.  For example, this code fragment will return the value of the
"Fax" tag.

    ($fax_no) = $object->get('Fax');
         --> "33-67-521559"

The list versus scalar context semantics are the same as in at(), so
if you want to retrieve the scalar value pointed to by the indicated
tag, either use a list context as shown in the example, above, or a
dereference, as in:

     $fax_no = $object->get('Fax');
         --> "Fax"
     $fax_no = $object->get('Fax')->at;
         --> "33-67-521559"

An optional second argument to B<get()>, $position, allows you to
navigate the tree relative to the retrieved subtree.  Like the B<at()>
navigational indexes, $position must be a number greater than or equal
to zero.  In a scalar context, $position moves rightward through the
tree.  In an array context, $position implements "tag[2]" semantics.

For example:

     $fax_no = $object->get('Fax',0);
          --> "Fax"

     $fax_no = $object->get('Fax',1);
          --> "33-67-521559"

     $fax_no = $object->get('Fax',2);
          --> undef  # nothing beyond the fax number

     @address = $object->get('Address',2);
          --> ('CRBM duCNRS','BP 5051','34033 Montpellier','FRANCE',
               'mieg@kaa.cnrs-mop.fr,'33-67-613324','33-67-521559')

It is important to note that B<get()> only traverses tags.  It will
not traverse nodes that aren't tags, such as strings, integers or
objects.  This is in keeping with the behavior of the Ace query
language "show" command.

This restriction can lead to confusing results.  For example, consider
the following object:

 Clone: B0280  Position    Map            Sequence-III  Ends   Left   3569
                                                               Right  3585
                           Pmap           ctg377        -1040  -1024
               Positive    Positive_locus nhr-10
               Sequence    B0280
               Location    RW
               FingerPrint Gel_Number     0
                           Canonical_for  T20H1
                                          K10E5
                           Bands          1354          18


The following attempt to fetch the left and right positions of the
clone will fail, because the search for the "Left" and "Right" tags
cannot traverse "Sequence-III", which is an object, not a tag:

  my $left = $clone->get('Left');    # will NOT work
  my $right = $clone->get('Right');  # neither will this one

You must explicitly step over the non-tag node in order to make this
query work.  This syntax will work:

  my $left = $clone->get('Map',1)->get('Left');   # works
  my $left = $clone->get('Map',1)->get('Right');  # works

Or you might prefer to use the tag[2] syntax here:

  my($left,$right) = $clone->get('Map',1)->at('Ends[2]');

Although not frequently used, there is a form of get() which allows
you to stack subtags:

Ace/Object.pm  view on Meta::CPAN

  $object->get('Address',2);
  $object->Address(2);

Use whatever syntax is most comfortable for you.

In a scalar context, B<col()> returns the number of items in the
column.

=head2 row() method

     @row=$object->row();
     @row=$object->row($position);

B<row()> will return the row of data to the right of the object.  The
first member of the list will be the object itself.  In the case of
the "Thierry-Mieg J" object, the example below will return the list
('Address','Mail','CRBM duCNRS').

     @row = $object->Address->row();

You can provide an optional position to move rightward one or more
places before retrieving the row.  This code fragment will return
('Mail','CRBM duCNRS'):

     @row = $object->Address->row(1);

In a scalar context, B<row()> returns the number of items in the row.

=head2 asString() method

    $object->asString;

asString() returns a pretty-printed ASCII representation of the object
tree.

=head2 asTable() method

    $object->asTable;

asTable() returns the object as a tab-delimited text table.

=head2 asAce() method

    $object->asAce;

asAce() returns the object as a tab-delimited text table in ".ace"
format.

=head2 asHTML() method

   $object->asHTML;
   $object->asHTML(\&tree_traversal_code);

asHTML() returns an HTML 3 table representing the object, suitable for
incorporation into a Web browser page.  The callback routine, if
provided, will have a chance to modify the object representation
before it is incorporated into the table, for example by turning it
into an HREF link.  The callback takes a single argument containing
the object, and must return a string-valued result.  It may also
return a list as its result, in which case the first member of the
list is the string representation of the object, and the second
member is a boolean indicating whether to prune the table at this
level.  For example, you can prune large repetitive lists.

Here's a complete example:

   sub process_cell {
     my $obj = shift;
     return "$obj" unless $obj->isObject || $obj->isTag;

     my @col = $obj->col;
     my $cnt = scalar(@col);
     return ("$obj -- $cnt members",1);  # prune
            if $cnt > 10                 # if subtree to big

     # tags are bold
     return "<B>$obj</B>" if $obj->isTag;  

     # objects are blue
     return qq{<FONT COLOR="blue">$obj</FONT>} if $obj->isObject; 
   }

   $object->asHTML(\&process_cell);

=head2 asXML() method

   $result = $object->asXML;

asXML() returns a well-formed XML representation of the object.  The
particular representation is still under discussion, so this feature
is primarily for demonstration.

=head2 asGIF() method

  ($gif,$boxes) = $object->asGIF();
  ($gif,$boxes) = $object->asGIF(-clicks=>[[$x1,$y1],[$x2,$y2]...]
	                         -dimensions=> [$width,$height],
				 -coords    => [$top,$bottom],
				 -display   => $display_type,
				 -view      => $view_type,
				 -getcoords => $true_or_false
	                         );

asGIF() returns the object as a GIF image.  The contents of the GIF
will be whatever xace would ordinarily display in graphics mode, and
will vary for different object classes.

You can optionally provide asGIF with a B<-clicks> argument to
simulate the action of a user clicking on the image.  The click
coordinates should be formatted as an array reference that contains a
series of two-element subarrays, each corresponding to the X and Y
coordinates of a single mouse click.  There is currently no way to
pass information about middle or right mouse clicks, dragging
operations, or keystrokes.  You may also specify a B<-dimensions> to
control the width and height of the returned GIF.  Since there is no
way of obtaining the preferred size of the image in advance, this is
not usually useful.

The optional B<-display> argument allows you to specify an alternate
display for the object.  For example, Clones can be displayed either
with the PMAP display or with the TREE display.  If not specified, the
default display is used.

The optional B<-view> argument allows you to specify an alternative
view for MAP objects only.  If not specified, you'll get the default
view.

The option B<-coords> argument allows you to provide the top and
bottom of the display for MAP objects only.  These coordinates are in
the map's native coordinate system (cM, bp).  By default, AceDB will
show most (but not necessarily all) of the map according to xace's
display rules.  If you call this method with the B<-getcoords>
argument and a true value, it will return a two-element array
containing the coordinates of the top and bottom of the map.

asGIF() returns a two-element array.  The first element is the GIF
data.  The second element is an array reference that indicates special 
areas of the image called "boxes."  Boxes are rectangular areas that
surround buttons, and certain displayed objects.  Using the contents
of the boxes array, you can turn the GIF image into a client-side
image map.  Unfortunately, not everything that is clickable is
represented as a box.  You still have to pass clicks on unknown image
areas back to the server for processing.

Each box in the array is a hash reference containing the following
keys:

    'coordinates'  => [$left,$top,$right,$bottom]
    'class'        => object class or "BUTTON"
    'name'         => object name, if any
    'comment'      => a text comment of some sort

I<coordinates> points to an array of points indicating the top-left and 
bottom-right corners of the rectangle.  I<class> indicates the class
of the object this rectangle surrounds.  It may be a database object,
or the special word "BUTTON" for one of the display action buttons.
I<name> indicates the name of the object or the button.  I<comment> is 
some piece of information about the object in question.  You can
display it in the status bar of the browser or in a popup window if
your browser provides that facility.

=head2 asDNA() and asPeptide() methods

    $dna = $object->asDNA();
    $peptide = $object->asPeptide();

If you are dealing with a sequence object of some sort, these methods
will return strings corresponding to the DNA or peptide sequence in
FASTA format.

=head2 add_row() method

    $result_code = $object->add_row($tag=>$value);    
    $result_code = $object->add_row($tag=>[list,of,values]);    
    $result_code = $object->add(-path=>$tag,
				-value=>$value);

add_row() updates the tree by adding data to the indicated tag path.  The
example given below adds the value "555-1212" to a new Address entry
named "Pager".  You may call add_row() a second time to add a new value
under this tag, creating multi-valued entries.

 $object->add_row('Address.Pager'=>'555-1212');

You may provide a list of values to add an entire row of data.  For
example:

 $sequence->add_row('Assembly_tags'=>['Finished Left',38949,38952,'AC3']);

Actually, the array reference is not entirely necessary, and if you
prefer you can use this more concise notation:

 $sequence->add_row('Assembly_tags','Finished Left',38949,38952,'AC3');

No check is done against the database model for the correct data type
or tag path.  The update isn't actually performed until you call
commit(), at which time a result code indicates whether the database
update was successful.

You may create objects that reference other objects this way:

    $lab = new Ace::Object('Laboratory','LM',$db);
    $lab->add_row('Full_name','The Laboratory of Medicine');
    $lab->add_row('City','Cincinatti');
    $lab->add_row('Country','USA');

    $author = new Ace::Object('Author','Smith J',$db);
    $author->add_row('Full_name','Joseph M. Smith');
    $author->add_row('Laboratory',$lab);

    $lab->commit();
    $author->commit();

The result code indicates whether the addition was syntactically
correct.  add_row() will fail if you attempt to add a duplicate entry
(that is, one with exactly the same tag and value).  In this case, use
replace() instead.  Currently there is no checking for an attempt to
add multiple values to a single-valued (UNIQUE) tag.  The error will
be detected and reported at commit() time however.

The add() method is an alias for add_row().

See also the Ace->new() method.

=head2 add_tree()

  $result_code = $object->add_tree($tag=>$ace_object);
  $result_code = $object->add_tree(-tag=>$tag,-tree=>$ace_object);

The add_tree() method will insert an entire Ace subtree into the object
to the right of the indicated tag.  This can be used to build up
complex Ace objects, or to copy portions of objects from one database
to another.  The first argument is a tag path, and the second is the
tree that you wish to insert.  As with add_row() the database will
only be updated when you call commit().

When inserting a subtree, you must be careful to remember that
everything to the *right* of the node that you are pointing at will be
inserted; not the node itself.  For example, given this Sequence
object:

  Sequence AC3
    DB_info     Database    EMBL
    Assembly_tags   Finished Left   1   4   AC3
                    Clone left end      1   4   AC3
                    Clone right end     5512    5515    K07C5
                                        38949   38952   AC3
                    Finished Right      38949   38952   AC3

If we use at('Assembly_tags') to fetch the subtree rooted on the
"Assembly_tags" tag, it is the tree to the right of this tag,
beginning with "Finished Left", that will be inserted.

Here is an example of copying the "Assembly_tags" subtree
from one database object to another:

 $remote = Ace->connect(-port=>200005)  || die "can't connect";
 $ac3 = $remote->fetch(Sequence=>'AC3') || die "can't get AC7";
 my $assembly = $ac3->at('Assembly_tags');

 $local = Ace->connect(-path=>'~acedb') || die "can't connect";
 $AC3copy = Ace::Object->new(Sequence=>'AC3copy',$local);
 $AC3copy->add_tree('Assembly_tags'=>$tags);
 $AC3copy->commit || warn $AC3copy->error;

Notice that this syntax will not work the way you think it should:

 $AC3copy->add_tree('Assembly_tags'=>$ac3->at('Assembly_tags'));

This is because call at() in an array context returns the column to
the right of the tag, not the tag itself.

Here's an example of building up a complex structure from scratch
using a combination of add() and add_tree():

 $newObj = Ace::Object->new(Sequence=>'A555',$local);
 my $assembly = Ace::Object->new(tag=>'Assembly_tags');
 $assembly->add('Finished Left'=>[10,20,'ABC']);
 $assembly->add('Clone right end'=>[1000,2000,'DEF']);
 $assembly->add('Clone right end'=>[8000,9876,'FRED']);
 $assembly->add('Finished Right'=>[1000,3000,'ETHEL']);
 $newObj->add_tree('Assembly_tags'=>$assembly);
 $newObj->commit || warn $newObj->error;

=head2 delete() method

    $result_code = $object->delete($tag_path,$value);
    $result_code = $object->delete(-path=>$tag_path,
                                   -value=>$value);

Delete the indicated tag and value from the object.  This example
deletes the address line "FRANCE" from the Author's mailing address:

Ace/Object.pm  view on Meta::CPAN


sub asPeptide {
  return shift()->_special_dump('peptide');
}

sub _special_dump {
  my $self = shift;
  my $dump_format = shift;
  return unless $self->db->count($self->class,$self->name);
  my $result = $self->db->raw_query($dump_format);
  $result =~ s!^//.*!!ms;
  $result;
}

#### As tab-delimited table ####
sub asTable {
    my $self = shift;
    my $string = "$self\t";
    my $right = $self->right;
    $right->_asTable(\$string,1,2) if defined($right);
    return $string . "\n";
}

#### In "ace" format ####
sub asAce {
  my $self = shift;
  my $string = $self->isRoot ? join(' ',$self->class,':',$self->escape) . "\n" : '';
  $self->right->_asAce(\$string,0,[]);
  return "$string\n\n";
}

### Pretty-printed version ###
sub asString {
  my $self = shift;
  my $MAXWIDTH = shift || $DEFAULT_WIDTH;
  my $tabs = $self->asTable;
  return "$self" unless $tabs;
  my(@lines) = split("\n",$tabs);
  my($result,@max);
  foreach (@lines) {
    my(@fields) = split("\t");
    for (my $i=0;$i<@fields;$i++) {
      $max[$i] = length($fields[$i]) if
	!defined($max[$i]) or $max[$i] < length($fields[$i]);
    }
  }
  foreach (@max) { $_ = $MAXWIDTH if $_ > $MAXWIDTH; } # crunch long lines
  my $format1 = join(' ',map { "^"."<"x $max[$_] } (0..$#max)) . "\n";
  my $format2 =   ' ' . join('  ',map { "^"."<"x ($max[$_]-1) } (0..$#max)) . "~~\n";
  $^A = '';
  foreach (@lines) {
    my @data = split("\t");
    push(@data,('')x(@max-@data));
    formline ($format1,@data);
    formline ($format2,@data);
  }
  return ($result = $^A,$^A='')[0];
}

# run a series of GIF commands and return the Gif and the semi-parsed
# "boxes" structure.  Commands is typically a series of mouseclicks
# ($gif,$boxes) = $aceObject->asGif(-clicks=>[[$x1,$y1],[$x2,$y2]...],
#                                   -dimensions=>[$x,$y]);
sub asGif {
  my $self = shift;
  my ($clicks,$dimensions,$display,$view,$coords,$getcoords) = rearrange(['CLICKS',
									  ['DIMENSIONS','DIM'],
									  'DISPLAY',
									  'VIEW',
									  'COORDS',
									  'GETCOORDS',
									  ],@_);
  $display = "-D $display" if $display;
  $view    = "-view $view" if $view;
  my $c;
  if ($coords) {
    $c    =  ref($coords) ? "-coords @$coords" : "-coords $coords";
  }
  my @commands;
  if ($view || $c || $self->class =~ /Map/i) {
      @commands = "gif map \"@{[$self->name]}\" $view $c";
  } else {
      @commands = "gif display $display $view @{[$self->class]} \"@{[$self->name]}\"";
  }
  push(@commands,"Dimensions @$dimensions") if ref($dimensions);
  push(@commands,map { "mouseclick @{$_}" } @$clicks) if ref($clicks);

  if ($getcoords) { # just want the coordinates
    my ($start,$stop);
    my $data = $self->db->raw_query(join(' ; ',@commands));    
    return unless $data =~ /\"[^\"]+\" ([\d.-]+) ([\d.-]+)/;
    ($start,$stop) = ($1,$2);
    return ($start,$stop);
  }

  push(@commands,"gifdump -");

  # do the query
  my $data = $self->db->raw_query(join(' ; ',@commands));

  # A $' has been removed here to improve speed -- tim.cutts@incyte.com 2 Sep 1999

  # did this query succeed?
  my ($bytes, $trim);
  return unless ($bytes, $trim) = $data=~m!^// (\d+) bytes\n\0*(.+)!sm;

  my $gif = substr($trim,0,$bytes);

  # now process the boxes
  my @b;
  my @boxes = split("\n",substr($trim,$bytes));
  foreach (@boxes) {
    last if m!^//!;
    chomp;
    my ($left,$top,$right,$bottom,$class,$name,$comments) = 
      m/^\s*\d*\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\w+):"(.+)"\s*(.*)/;
    next unless defined $left;
    $comments=~s/\s+$//; # sometimes there's extra white space at the end
    my $box = {'coordinates'=>[$left,$top,$right,$bottom],
	       'class'=>$class,
	       'name' =>$name,
	       'comment'=>$comments};
    push (@b,$box);
  }
  return ($gif,\@b);
}

############## timestamp and comment information ############
sub timestamp {
    my $self = shift;
    return $self->{'.timestamp'} = $_[0] if defined $_[0];
    if ($self->db && !$self->{'.timestamp'}) {
      $self->_fill;
      $self->_parse;
    }
    return $self->{'.timestamp'} if $self->{'.timestamp'};
    return unless defined $self->right;
    return $self->{'.timestamp'} = $self->right->timestamp;
}

sub comment {
    my $self = shift;
    return $self->{'.comment'} = $_[0] if defined $_[0];
    if ($self->db && !$self->{'.comment'}) {
      $self->_fill;
      $self->_parse;



( run in 0.575 second using v1.01-cache-2.11-cpan-39bf76dae61 )