File Coverage

blib/lib/Ace/Object.pm
Criterion Covered Total %
statement 30 355 8.4
branch 0 208 0.0
condition 0 72 0.0
subroutine 10 48 20.8
pod 17 26 65.3
total 57 709 8.0


line stmt bran cond sub pod time code
1             package Ace::Object;
2 4     4   22 use strict;
  4         8  
  4         145  
3 4     4   19 use Carp qw(:DEFAULT cluck);
  4         5  
  4         897  
4              
5             # $Id: Object.pm,v 1.60 2005/04/13 14:26:08 lstein Exp $
6              
7             use overload
8 4         43 '""' => 'name',
9             '==' => 'eq',
10             '!=' => 'ne',
11 4     4   23 'fallback' => 'TRUE';
  4         7  
12 4     4   414 use vars qw($AUTOLOAD $DEFAULT_WIDTH %MO $VERSION);
  4         8  
  4         297  
13 4     4   18 use Ace 1.50 qw(:DEFAULT rearrange);
  4         69  
  4         450  
14              
15             # if set to 1, will conflate tags in XML output
16 4     4   20 use constant XML_COLLAPSE_TAGS => 1;
  4         10  
  4         261  
17 4     4   18 use constant XML_SUPPRESS_CONTENT=>1;
  4         16  
  4         192  
18 4     4   20 use constant XML_SUPPRESS_CLASS=>1;
  4         6  
  4         172  
19 4     4   17 use constant XML_SUPPRESS_VALUE=>0;
  4         7  
  4         158  
20 4     4   24 use constant XML_SUPPRESS_TIMESTAMPS=>0;
  4         12  
  4         31512  
21              
22             require AutoLoader;
23              
24             $DEFAULT_WIDTH=25; # column width for pretty-printing
25             $VERSION = '1.66';
26              
27             # Pseudonyms and deprecated methods.
28             *isClass = \&isObject;
29             *pick = \&fetch;
30             *get = \&search;
31             *add = \&add_row;
32              
33             sub AUTOLOAD {
34 0     0     my($pack,$func_name) = $AUTOLOAD=~/(.+)::([^:]+)$/;
35 0           my $self = $_[0];
36              
37             # This section works with Autoloader
38 0   0       my $presumed_tag = $func_name =~ /^[A-Z]/ && $self->isObject; # initial_cap
39              
40 0 0 0       if ($presumed_tag) {
    0          
41 0 0 0       croak "Invalid object tag \"$func_name\""
      0        
42             if $self->db && $self->model && !$self->model->valid_tag($func_name);
43              
44 0           shift(); # get rid of the object
45 0           my $no_dereference;
46 0 0         if (defined($_[0])) {
47 0 0         if ($_[0] eq '@') {
    0          
48 0           $no_dereference++;
49 0           shift();
50             } elsif ($_[0] =~ /^\d+$/) {
51 0           $no_dereference++;
52             }
53             }
54              
55 0 0 0       $self = $self->fetch if !$no_dereference &&
      0        
56             !$self->isRoot && $self->db; # dereference, if need be
57 0 0         croak "Null object tag \"$func_name\"" unless $self;
58              
59 0 0         return $self->search($func_name,@_) if wantarray;
60 0 0         my ($obj) = @_ ? $self->search($func_name,@_) : $self->search($func_name,1);
61              
62             # these nasty heuristics simulate aql semantics.
63             # undefined return
64 0 0         return unless defined $obj;
65              
66             # don't dereference object if '@' symbol specified
67 0 0         return $obj if $no_dereference;
68              
69             # don't dereference if an offset was explicitly specified
70 0 0 0       return $obj if defined($_[0]) && $_[0] =~ /\d+/;
71              
72             # otherwise dereference if the current thing is an object or we are at a tag
73             # and the thing to the right is an object.
74 0 0 0       return $obj->fetch if $obj->isObject && !$obj->isRoot; # always dereference objects
75              
76             # otherwise return the thing itself
77 0           return $obj;
78             } elsif ($func_name =~ /^[A-Z]/ && $self->isTag) { # follow tag
79 0           return $self->search($func_name);
80             } else {
81 0           $AutoLoader::AUTOLOAD = __PACKAGE__ . "::$func_name";
82 0           goto &AutoLoader::AUTOLOAD;
83             }
84             }
85              
86             sub DESTROY {
87 0     0     my $self = shift;
88              
89 0 0         return unless defined $self->{class}; # avoid working with temp objects from a search()
90 0 0         return if caller() =~ /^(Cache\:\:|DB)/; # prevent recursion in FileCache code
91 0 0         my $db = $self->db or return;
92 0 0         return if $self->{'.nocache'};
93 0 0         return unless $self->isRoot;
94              
95 0 0         if ($self->_dirty) {
96 0 0         warn "Destroy for ",overload::StrVal($self)," ",$self->class,':',$self->name if Ace->debug;
97 0           $self->_dirty(0);
98 0           $db->file_cache_store($self);
99             }
100              
101             # remove our in-memory cache
102             # shouldn't be necessary with weakref
103             # $db->memory_cache_delete($self);
104             }
105              
106             ###################### object constructor #################
107             # IMPORTANT: The _clone subroutine will copy all instance variables that
108             # do NOT begin with a dot (.). If you do not want an instance variable
109             # shared with cloned copies, proceed them with a dot!!!
110             #
111             sub new {
112 0     0 1   my $pack = shift;
113 0           my($class,$name,$db,$isRoot) = rearrange([qw/CLASS NAME/,[qw/DATABASE DB/],'ROOT'],@_);
114 0 0         $pack = ref($pack) if ref($pack);
115 0           my $self = bless { 'name' => $name,
116             'class' => $class
117             },$pack;
118 0 0         $self->db($db) if $self->isObject;
119 0 0 0       $self->{'.root'}++ if defined $isRoot && $isRoot;
120             # $self->_dirty(1) if $isRoot;
121 0           return $self
122             }
123              
124             ######### construct object from serialized input, not usually called directly ########
125             sub newFromText {
126 0     0 0   my ($pack,$text,$db) = @_;
127 0 0         $pack = ref($pack) if ref($pack);
128              
129 0           my @array;
130 0           foreach (split("\n",$text)) {
131 0 0         next unless $_;
132             # this is a hack to fix some txt fields with unescaped tabs
133             # unfortunately it breaks other things
134 0           s/\?txt\?([^?]*?)\t([^?]*?)\?/?txt?$1\\t$2?/g;
135 0           push(@array,[split("\t")]);
136             }
137 0           my $obj = $pack->_fromRaw(\@array,0,0,$#array,$db);
138 0           $obj->_dirty(1);
139 0           $obj;
140             }
141              
142              
143             ################### name of the object #################
144             sub name {
145 0     0 1   my $self = shift;
146 0 0         $self->{'name'} = shift if defined($_[0]);
147 0           my $name = $self->_ace_format($self->{'class'},$self->{'name'});
148 0           $name;
149             }
150              
151             ################### class of the object #################
152             sub class {
153 0     0 1   my $self = shift;
154 0 0         defined($_[0])
155             ? $self->{'class'} = shift
156             : $self->{'class'};
157             }
158              
159             ################### name and class together #################
160             sub id {
161 0     0 0   my $self = shift;
162 0           return "$self->{class}:$self->{name}";
163             }
164              
165             ############## return true if two objects are equivalent ##################
166             # to be equivalent, they must have identical names, classes and databases #
167             # We handle comparisons between objects and numbers ourselves, and let #
168             # Perl handle comparisons between objects and strings #
169             sub eq {
170 0     0 0   my ($a,$b,$rev) = @_;
171 0 0         unless (UNIVERSAL::isa($b,'Ace::Object')) {
172 0           $a = $a->name + 0; # convert to numeric
173 0           return $a == $b; # do a numeric comparison
174             }
175 0 0 0       return 1 if ($a->name eq $b->name)
      0        
176             && ($a->class eq $b->class)
177             && ($a->db eq $b->db);
178 0           return;
179             }
180              
181             sub ne {
182 0     0 0   return !&eq;
183             }
184              
185              
186             ############ returns true if this is a top-level object #######
187             sub isRoot {
188 0     0 1   return exists shift()->{'.root'};
189             }
190              
191             ################### handle to ace database #################
192             sub db {
193 0     0 1   my $self = shift;
194 0 0         if (@_) {
195 0           my $db = shift;
196 0           $self->{db} = "$db"; # store string representation, not object
197             }
198 0           Ace->name2db($self->{db});
199             }
200              
201             ### Return a portion of the tree at the indicated tag path ###
202             #### In a list context returns the column. In an array context ###
203             #### returns a pointer to the subtree ####
204             #### Usually returns what is pointed to by the tag. Will return
205             #### the parent object if you pass a true value as the second argument
206             sub at {
207 0     0 1   my $self = shift;
208 0           my($tag,$pos,$return_parent) = rearrange(['TAG','POS','PARENT'],@_);
209 0 0         return $self->right unless $tag;
210 0           $tag = lc $tag;
211              
212             # Removed a $` here to increase speed -- tim.cutts@incyte.com 2 Sep 1999
213              
214 0 0 0       if (!defined($pos) and $tag=~/(.*?)\[(\d+)\]$/) {
215 0           $pos = $2;
216 0           $tag = $1;
217             }
218              
219 0           my $o = $self;
220 0           my ($parent,$above,$left);
221 0           my (@tags) = $self->_split_tags($tag);
222 0           foreach $tag (@tags) {
223 0           $tag=~s/$;/./g; # unprotect backslashed dots
224 0           my $p = $o;
225 0           ($o,$above,$left) = $o->_at($tag);
226 0 0         return unless defined($o);
227             }
228 0 0 0       return $above || $left if $return_parent;
229 0 0         return defined $pos ? $o->right($pos) : $o unless wantarray;
    0          
230 0           return $o->col($pos);
231             }
232              
233             ### Flatten out part of the tree into an array ####
234             ### along the row. Will not follow object references. ###
235             sub row {
236 0     0 1   my $self = shift;
237 0           my $pos = shift;
238 0           my @r;
239 0 0         my $o = defined $pos ? $self->right($pos) : $self;
240 0           while (defined($o)) {
241 0           push(@r,$o);
242 0           $o = $o->right;
243             }
244 0           return @r;
245             }
246              
247             ### Flatten out part of the tree into an array ####
248             ### along the column. Will not follow object references. ###
249             sub col {
250 0     0 1   my $self = shift;
251 0           my $pos = shift;
252 0 0         $pos = 1 unless defined $pos;
253 0 0         croak "Position must be positive" unless $pos >= 0;
254              
255 0 0         return ($self) unless $pos > 0;
256              
257 0           my @r;
258             # This is for tag[1] semantics
259 0 0         if ($pos == 1) {
260 0           for (my $o=$self->right; defined($o); $o=$o->down) {
261 0           push (@r,$o);
262             }
263             } else {
264             # This is for tag[2] semantics
265 0           for (my $o=$self->right; defined($o); $o=$o->down) {
266 0 0         next unless defined(my $right = $o->right($pos-2));
267 0           push (@r,$right->col);
268             }
269             }
270 0           return @r;
271             }
272              
273             #### Search for a tag, and return the column ####
274             #### Uses a breadth-first search (cols then rows) ####
275             sub search {
276 0     0 1   my $self = shift;
277 0 0         my $tag = shift unless $_[0]=~/^-/;
278 0           my ($subtag,$pos,$filled) = rearrange(['SUBTAG','POS',['FILL','FILLED']],@_);
279 0           my $lctag = lc $tag;
280              
281             # With caching, the old way of following ends up cloning the object
282             # -- which we don't want. So more-or-less emulate the earlier
283             # behavior with an explicit get and fetch
284             # return $self->follow(-tag=>$tag,-filled=>$filled) if $filled;
285 0 0         if ($filled) {
286 0 0         my @node = $self->search($tag) or return; # watch out for recursion!
287 0           my @obj = map {$_->fetch} @node;
  0            
288 0 0         foreach (@obj) {$_->right if defined $_}; # trigger a fill
  0            
289 0 0         return wantarray ? @obj : $obj[0];
290             }
291              
292             TRY: {
293              
294             # look in our tag cache first
295 0 0         if (exists $self->{'.PATHS'}) {
  0            
296              
297             # we've already cached the desired tree
298 0 0         last TRY if exists $self->{'.PATHS'}{$lctag};
299            
300             # not cached, so try parents of tag
301 0           my $m = $self->model;
302 0 0         my @parents = $m->path($lctag) if $m;
303 0           my $tree;
304 0           foreach (@parents) {
305 0 0         ($tree = $self->{'.PATHS'}{lc $_}) && last;
306             }
307 0 0         if ($tree) {
308 0           $self->{'.PATHS'}{$lctag} = $tree->search($tag);
309 0           $self->_dirty(1);
310 0           last TRY;
311             }
312             }
313              
314             # If the object hasn't been filled already, then we can use
315             # acedb's query mechanism to fetch the subobject. This is a
316             # big win for large objects. ...However, we have to disable
317             # this feature if timestamps are active.
318 0 0         unless ($self->filled) {
319 0           my $subobject = $self->newFromText(
320             $self->db->show($self->class,$self->name,$tag),
321             $self->db
322             );
323 0 0         if ($subobject) {
324 0           $subobject->{'.nocache'}++;
325 0           $self->_attach_subtree($lctag => $subobject);
326             } else {
327 0           $self->{'.PATHS'}{$lctag} = undef;
328             }
329 0           $self->_dirty(1);
330 0           last TRY;
331             }
332            
333 0           my @col = $self->col;
334 0           foreach (@col) {
335 0 0         next unless $_->isTag;
336 0 0         if (lc $_ eq $lctag) {
337 0           $self->{'.PATHS'}{$lctag} = $_;
338 0           $self->_dirty(1);
339 0           last TRY;
340             }
341             }
342              
343             # if we get here, we didn't find it in the column,
344             # so we call ourselves recursively to find it
345 0           foreach (@col) {
346 0 0         next unless $_->isTag;
347 0 0         if (my $r = $_->search($tag)) {
348 0           $self->{'.PATHS'}{$lctag} = $r;
349 0           $self->_dirty(1);
350 0           last TRY;
351             }
352             }
353              
354             # If we got here, we just didn't find it. So tag the cache
355             # as empty so that we don't try again
356 0           $self->{'.PATHS'}{$lctag} = undef;
357 0           $self->_dirty(1);
358             }
359              
360 0           my $t = $self->{'.PATHS'}{$lctag};
361 0 0         return unless $t;
362              
363 0 0         if (defined $subtag) {
364 0 0         if ($subtag =~ /^\d+$/) {
365 0           $pos = $subtag;
366             } else { # position on subtag and search again
367 0 0 0       return $t->fetch->search($subtag,$pos)
      0        
368             if $t->isObject || (defined($t->right) and $t->right->isObject);
369 0           return $t->search($subtag,$pos);
370             }
371             }
372              
373 0 0         return defined $pos ? $t->right($pos) : $t unless wantarray;
    0          
374              
375             # We do something verrrry interesting in an array context.
376             # If no position is defined, we return the column to the right.
377             # If a position is defined, we return everything $POS tags
378             # to the right (so-called tag[2] system).
379 0           return $t->col($pos);
380             }
381              
382             # utility routine used in partial tree caching
383             sub _attach_subtree {
384 0     0     my $self = shift;
385 0           my ($tag,$subobject) = @_;
386 0           my $lctag = lc($tag);
387 0           my $obj;
388 0 0         if (lc($subobject->right) eq $lctag) { # new version of aceserver as of 11/30/98
389 0           $obj = $subobject->right;
390             } else { # old version of aceserver
391 0           $obj = $self->new('tag',$tag,$self->db);
392 0           $obj->{'.right'} = $subobject->right;
393             }
394 0           $self->{'.PATHS'}->{$lctag} = $obj;
395             }
396              
397             sub _dirty {
398 0     0     my $self = shift;
399 0 0 0       $self->{'.dirty'} = shift if @_ && $self->isRoot;
400 0           $self->{'.dirty'};
401             }
402              
403             #### return true if tree is populated, without populating it #####
404             sub filled {
405 0     0 0   my $self = shift;
406 0   0       return exists($self->{'.right'}) || exists($self->{'.raw'});
407             }
408              
409             #### return true if you can follow the object in the database (i.e. a class ###
410             sub isPickable {
411 0     0 0   return shift->isObject;
412             }
413              
414             #### Return a string representation of the object subject to Ace escaping rules ###
415             sub escape {
416 0     0 0   my $self = shift;
417 0           my $name = $self->name;
418 0   0       my $needs_escaping = $name=~/[^\w.-]/ || $self->isClass;
419 0 0         return $name unless $needs_escaping;
420 0           $name=~s/\"/\\"/g; #escape quotes"
421 0           return qq/"$name"/;
422             }
423              
424             ############### object on the right of the tree #############
425             sub right {
426 0     0 1   my ($self,$pos) = @_;
427              
428 0           $self->_fill;
429 0           $self->_parse;
430              
431 0 0         return $self->{'.right'} unless defined $pos;
432 0 0         croak "Position must be positive" unless $pos >= 0;
433              
434 0           my $node = $self;
435 0           while ($pos--) {
436 0 0         defined($node = $node->right) || return;
437             }
438 0           $node;
439             }
440              
441             ################# object below on the tree #################
442             sub down {
443 0     0 1   my ($self,$pos) = @_;
444 0           $self->_parse;
445 0 0         return $self->{'.down'} unless defined $pos;
446 0           my $node = $self;
447 0           while ($pos--) {
448 0 0         defined($node = $node->down) || return;
449             }
450 0           $node;
451             }
452              
453             #############################################
454             # fetch current node from the database #
455             sub fetch {
456 0     0 1   my ($self,$tag) = @_;
457 0 0         return $self->search($tag) if defined $tag;
458 0 0 0       my $thing_to_pick = ($self->isTag and defined($self->right)) ? $self->right : $self;
459 0 0         return $thing_to_pick unless $thing_to_pick->isObject;
460 0 0         my $obj = $self->db->get($thing_to_pick->class,$thing_to_pick->name) if $self->db;
461 0           return $obj;
462             }
463              
464             #############################################
465             # follow a tag into the database, returning a
466             # list of followed objects.
467             sub follow {
468 0     0 1   my $self = shift;
469 0           my ($tag,$filled) = rearrange(['TAG','FILLED'],@_);
470              
471 0 0         return unless $self->db;
472 0 0         return $self->fetch() unless $tag;
473 0           my $class = $self->class;
474 0           my $name = Ace->freeprotect($self->name);
475 0           my @options;
476 0 0         if ($filled) {
477 0 0         @options = $filled =~ /^[a-zA-Z]/ ? ('filltag' => $filled) : ('filled'=>1);
478             }
479 0           return $self->db->fetch(-query=>"find $class $name ; follow $tag",@options);
480             }
481              
482             # returns true if the object has a Model, i.e, can be followed into
483             # the database.
484             sub isObject {
485 0     0 0   my $self = shift;
486 0           return _isObject($self->class);
487 0           1;
488             }
489              
490             # returns true if the object is a tag.
491             sub isTag {
492 0     0 1   my $self = shift;
493 0 0         return 1 if $self->class eq 'tag';
494 0           return;
495             }
496              
497             # return the most recent error message
498             sub error {
499 0     0 1   $Ace::Error=~s/\0//g; # get rid of nulls
500 0           return $Ace::Error;
501             }
502              
503             ### Returns the object's model (as an Ace::Model object)
504             sub model {
505 0     0 1   my $self = shift;
506 0 0 0       return unless $self->db && $self->isObject;
507 0           return $self->db->model($self->class);
508             }
509              
510             ### Return the class in which to bless all objects retrieved from
511             # database. Might want to override in other classes
512             sub factory {
513 0     0 1   return __PACKAGE__;
514             }
515              
516             #####################################################################
517             #####################################################################
518             ############### mostly private functions from here down #############
519             #####################################################################
520             #####################################################################
521             # simple clone
522             sub clone {
523 0     0 0   my $self = shift;
524 0           return bless {%$self},ref $self;
525             }
526              
527             # selective clone
528             sub _clone {
529 0     0     my $self = shift;
530 0           my $pack = ref($self);
531 0           my @public_keys = grep {substr($_,0,1) ne '.'} keys %$self;
  0            
532 0           my %newobj;
533 0           @newobj{@public_keys} = @{$self}{@public_keys};
  0            
534              
535             # Turn into a toplevel object
536 0           $newobj{'.root'}++;
537 0           return bless \%newobj,$pack;
538             }
539              
540             sub _fill {
541 0     0     my $self = shift;
542 0 0         return if $self->filled;
543 0 0 0       return unless $self->db && $self->isObject;
544              
545 0           my $data = $self->db->pick($self->class,$self->name);
546 0 0         return unless $data;
547              
548             # temporary object, don't cache it.
549 0           my $new = $self->newFromText($data,$self->db);
550 0           %{$self}=%{$new};
  0            
  0            
551              
552 0           $new->{'.nocache'}++; # this line prevents the thing from being cached
553              
554 0           $self->_dirty(1);
555             }
556              
557             sub _parse {
558 0     0     my $self = shift;
559 0 0         return unless my $raw = $self->{'.raw'};
560 0           my $ts = $self->db->timestamps;
561 0           my $col = $self->{'.col'};
562 0           my $current_obj = $self;
563 0           my $current_row = $self->{'.start_row'};
564 0           my $db = $self->db;
565 0           my $changed;
566              
567 0           for (my $r=$current_row+1; $r<=$self->{'.end_row'}; $r++) {
568 0 0         next unless $raw->[$r][$col] ne '';
569 0           $changed++;
570              
571 0           my $obj_right = $self->_fromRaw($raw,$current_row,$col+1,$r-1,$db);
572              
573             # comment handling
574 0 0         if ( defined($obj_right) ) {
575 0           my ($t,$i);
576 0           my $row = $current_row+1;
577 0           while ($obj_right->isComment) {
578 0 0         $current_obj->comment($obj_right) if $obj_right->isComment;
579 0           $t = $obj_right;
580 0 0         last unless defined ($obj_right = $self->_fromRaw($raw,$row++,$col+1,$r-1,$db));
581             }
582             }
583 0           $current_obj->{'.right'} = $obj_right;
584              
585 0           my ($class,$name,$timestamp) = Ace->split($raw->[$r][$col]);
586 0           my $obj_down = $self->new($class,$name,$db);
587 0 0 0       $obj_down->timestamp($timestamp) if $ts && $timestamp;
588              
589             # comments never occur at down pointers
590 0           $current_obj = $current_obj->{'.down'} = $obj_down;
591 0           $current_row = $r;
592             }
593              
594 0           my $obj_right = $self->_fromRaw($raw,$current_row,$col+1,$self->{'.end_row'},$db);
595              
596             # comment handling
597 0 0         if (defined($obj_right)) {
598 0           my ($t,$i);
599 0           my $row = $current_row + 1;
600 0           while ($obj_right->isComment) {
601 0 0         $current_obj->comment($obj_right) if $obj_right->isComment;
602 0           $t = $obj_right;
603 0 0         last unless defined($obj_right = $self->_fromRaw($raw,$row++,$col+1,$self->{'.end_row'},$db));
604             }
605             }
606 0           $current_obj->{'.right'} = $obj_right;
607 0 0         $self->_dirty(1) if $changed;
608 0           delete @{$self}{qw[.raw .start_row .end_row .col]};
  0            
609             }
610              
611             sub _fromRaw {
612 0     0     my $pack = shift;
613              
614             # this breaks inheritance...
615             # $pack = $pack->factory();
616              
617 0           my ($raw,$start_row,$col,$end_row,$db) = @_;
618 0 0         $db = "$db" if ref $db;
619 0 0         return unless defined $raw->[$start_row][$col];
620              
621             # HACK! Some LongText entries may begin with newlines. This is within the Acedb spec.
622             # Let's purge text entries of leading space and format them appropriate.
623             # This should probably be handled in Freesubs.xs / Ace::split
624 0           my $temp = $raw->[$start_row][$col];
625             # if ($temp =~ /^\?txt\?\s*\n*/) {
626             # $temp =~ s/^\?txt\?(\s*\\n*)/\?txt\?/;
627             # $temp .= '?';
628             # }
629 0           my ($class,$name,$ts) = Ace->split($temp);
630              
631 0   0       my $self = $pack->new($class,$name,$db,!($start_row || $col));
632 0           @{$self}{qw(.raw .start_row .end_row .col db)} = ($raw,$start_row,$end_row,$col,$db);
  0            
633 0 0         $self->{'.timestamp'} = $ts if defined $ts;
634 0           return $self;
635             }
636              
637              
638             # Return partial ace subtree at indicated tag
639             sub _at {
640 0     0     my ($self,$tag) = @_;
641 0           my $pos=0;
642              
643             # Removed a $` here to increase speed -- tim.cutts@incyte.com 2 Sep 1999
644              
645 0 0         if ($tag=~/(.*?)\[(\d+)\]$/) {
646 0           $pos=$2;
647 0           $tag=$1;
648             }
649 0           my $p;
650 0           my $o = $self->right;
651 0           while ($o) {
652 0 0         return ($o->right($pos),$p,$self) if (lc($o) eq lc($tag));
653 0           $p = $o;
654 0           $o = $o->down;
655             }
656 0           return;
657             }
658              
659              
660             # Used to munge special data types. Right now dates are the
661             # only examples.
662             sub _ace_format {
663 0     0     my $self = shift;
664 0           my ($class,$name) = @_;
665 0 0 0       return undef unless defined $class && defined $name;
666 0 0         return $class eq 'date' ? $self->_to_ace_date($name) : $name;
667             }
668              
669             # It's an object unless it is one of these things
670             sub _isObject {
671 0 0   0     return unless defined $_[0];
672 0           $_[0] !~ /^(float|int|date|tag|txt|peptide|dna|scalar|[Tt]ext|comment)$/;
673             }
674              
675             # utility routine used to split a tag path into individual components
676             # allows components to contain dots.
677             sub _split_tags {
678 0     0     my $self = shift;
679 0           my $tag = shift;
680 0           $tag =~ s/\\\./$;/g; # protect backslashed dots
681 0           return map { (my $x=$_)=~s/$;/./g; $x } split(/\./,$tag);
  0            
  0            
682             }
683              
684              
685             1;
686              
687             __END__