| 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__ |