File Coverage

blib/lib/Stone.pm
Criterion Covered Total %
statement 98 291 33.6
branch 29 106 27.3
condition 2 31 6.4
subroutine 20 43 46.5
pod 20 27 74.0
total 169 498 33.9


" unless $position; " x ($level-$pos) . ""; " x ($level-$pos+1) . "\n";
line stmt bran cond sub pod time code
1             # ----------------- Stone ---------------
2             # This is basic unit of the boulder stream, and defines a
3             # multi-valued hash array type of structure.
4              
5             package Stone;
6 2     2   12 use strict;
  2         4  
  2         98  
7 2     2   11 use vars qw($VERSION $AUTOLOAD $Fetchlast);
  2         4  
  2         186  
8 2         13 use overload '""' => 'toString',
9 2     2   4370 'fallback' =>' TRUE';
  2         2410  
10              
11             $VERSION = '1.30';
12             require 5.004;
13              
14             =head1 NAME
15              
16             Stone - In-memory storage for hierarchical tag/value data structures
17              
18             =head1 SYNOPSIS
19              
20             use Stone;
21             my $stone = Stone->new( Jim => { First_name => 'James',
22             Last_name => 'Hill',
23             Age => 34,
24             Address => {
25             Street => ['The Manse',
26             '19 Chestnut Ln'],
27             City => 'Garden City',
28             State => 'NY',
29             Zip => 11291 }
30             },
31             Sally => { First_name => 'Sarah',
32             Last_name => 'James',
33             Age => 30,
34             Address => {
35             Street => 'Hickory Street',
36             City => 'Katonah',
37             State => 'NY',
38             Zip => 10578 }
39             }
40             );
41              
42             @tags = $stone->tags; # yields ('James','Sally');
43             $address = $stone->Jim->Address; # gets the address subtree
44             @street = $address->Street; # yeilds ('The Manse','19 Chestnut Ln')
45              
46             $address = $stone->get('Jim')->get('Address'); # same as $stone->Jim->Address
47             $address = $stone->get('Jim.Address'); # another way to express same thing
48              
49             # first Street tag in Jim's address
50             $address = $stone->get('Jim.Address.Street[0]');
51             # second Street tag in Jim's address
52             $address = $stone->get('Jim.Address.Street[1]');
53             # last Street tag in Jim's address
54             $address = $stone->get('Jim.Address.Street[#]');
55              
56             # insert a tag/value pair
57             $stone->insert(Martha => { First_name => 'Martha', Last_name => 'Steward'} );
58              
59             # find the first Address
60             $stone->search('Address');
61              
62             # change an existing subtree
63             $martha = $stone->Martha;
64             $martha->replace(Last_name => 'Stewart'); # replace a value
65              
66             # iterate over the tree with a cursor
67             $cursor = $stone->cursor;
68             while (my ($key,$value) = $cursor->each) {
69             print "$value: Go Bluejays!\n" if $key eq 'State' and $value eq 'Katonah';
70             }
71              
72             # various format conversions
73             print $stone->asTable;
74             print $stone->asString;
75             print $stone->asHTML;
76             print $stone->asXML('Person');
77              
78             =head1 DESCRIPTION
79              
80             A L consists of a series of tag/value pairs. Any given tag may
81             be single-valued or multivalued. A value can be another Stone,
82             allowing nested components. A big Stone can be made up of a lot of
83             little stones (pebbles?). You can obtain a Stone from a
84             L or L persistent database.
85             Alternatively you can build your own Stones bit by bit.
86              
87             Stones can be exported into string, XML and HTML representations. In
88             addition, they are flattened into a linearized representation when
89             reading from or writing to a L or one of its
90             descendents.
91              
92             L was designed for subclassing. You should be able to create
93             subclasses which create or require particular tags and data formats.
94             Currently only L subclasses L.
95              
96             =head1 CONSTRUCTORS
97              
98             Stones are either created by calling the new() method, or by reading
99             them from a L or persistent database.
100              
101             =head2 $stone = Stone->new()
102              
103             This is the main constructor for the Stone class. It can be called
104             without any parameters, in which case it creates an empty Stone object
105             (no tags or values), or it may passed an associative array in order to
106             initialize it with a set of tags. A tag's value may be a scalar, an
107             anonymous array reference (constructed using [] brackets), or a hash
108             references (constructed using {} brackets). In the first case, the
109             tag will be single-valued. In the second, the tag will be
110             multivalued. In the third case, a subsidiary Stone will be generated
111             automatically and placed into the tree at the specified location.
112              
113             Examples:
114              
115             $myStone = new Stone;
116             $myStone = new Stone(Name=>'Fred',Age=>30);
117             $myStone = new Stone(Name=>'Fred',
118             Friend=>['Jill','John','Jerry']);
119             $myStone = new Stone(Name=>'Fred',
120             Friend=>['Jill',
121             'John',
122             'Gerald'
123             ],
124             Attributes => { Hair => 'blonde',
125             Eyes => 'blue' }
126             );
127              
128             In the last example, a Stone with the following structure is created:
129              
130             Name Fred
131             Friend Jill
132             Friend John
133             Friend Gerald
134             Attributes Eyes blue
135             Hair blonde
136              
137             Note that the value corresponding to the tag "Attributes" is itself a
138             Stone with two tags, "Eyes" and "Hair".
139              
140             The XML representation (which could be created with asXML()) looks like this:
141              
142            
143            
144            
145             blue
146             blonde
147            
148             Jill
149             John
150             Gerald
151             Fred
152            
153              
154             More information on Stone initialization is given in the description
155             of the insert() method.
156              
157             =head1 OBJECT METHODS
158              
159             Once a Stone object is created or retrieved, you can manipulate it
160             with the following methods.
161              
162             =head2 $stone->insert(%hash)
163              
164             =head2 $stone->insert(\%hash)
165              
166             This is the main method for adding tags to a Stone. This method
167             expects an associative array as an argument or a reference to one.
168             The contents of the associative array will be inserted into the Stone.
169             If a particular tag is already present in the Stone, the tag's current
170             value will be appended to the list of values for that tag. Several
171             types of values are legal:
172              
173             =over 4
174              
175             =item * A B value
176              
177             The value will be inserted into the C.
178              
179             $stone->insert(name=>Fred,
180             age=>30,
181             sex=>M);
182             $stone->dump;
183            
184             name[0]=Fred
185             age[0]=30
186             sex[0]=M
187              
188             =item * An B reference
189              
190             A multi-valued tag will be created:
191              
192             $stone->insert(name=>Fred,
193             children=>[Tom,Mary,Angelique]);
194             $stone->dump;
195            
196             name[0]=Fred
197             children[0]=Tom
198             children[1]=Mary
199             children[2]=Angelique
200              
201             =item * A B reference
202              
203             A subsidiary C object will be created and inserted into the
204             object as a nested structure.
205              
206             $stone->insert(name=>Fred,
207             wife=>{name=>Agnes,age=>40});
208             $stone->dump;
209              
210             name[0]=Fred
211             wife[0].name[0]=Agnes
212             wife[0].age[0]=40
213              
214             =item * A C object or subclass
215              
216             The C object will be inserted into the object as a nested
217             structure.
218              
219             $wife = new Stone(name=>agnes,
220             age=>40);
221             $husband = new Stone;
222             $husband->insert(name=>fred,
223             wife=>$wife);
224             $husband->dump;
225            
226             name[0]=fred
227             wife[0].name[0]=agnes
228             wife[0].age[0]=40
229              
230             =back
231              
232             =head2 $stone->replace(%hash)
233              
234             =head2 $stone->replace(\%hash)
235              
236             The B method behaves exactly like C with the
237             exception that if the indicated key already exists in the B,
238             its value will be replaced. Use B when you want to enforce
239             a single-valued tag/value relationship.
240              
241             =head2 $stone->insert_list($key,@list)
242             =head2 $stone->insert_hash($key,%hash)
243             =head2 $stone->replace_list($key,@list)
244             =head2 $stone->replace_hash($key,%hash)
245              
246             These are primitives used by the C and C methods.
247             Override them if you need to modify the default behavior.
248              
249             =head2 $stone->delete($tag)
250              
251             This removes the indicated tag from the Stone.
252              
253             =head2 @values = $stone->get($tag [,$index])
254              
255             This returns the value at the indicated tag and optional index. What
256             you get depends on whether it is called in a scalar or list context.
257             In a list context, you will receive all the values for that tag. You
258             may receive a list of scalar values or (for a nested record) or a list
259             of Stone objects. If called in a scalar context, you will either
260             receive the first or the last member of the list of values assigned to
261             the tag. Which one you receive depends on the value of the package
262             variable C<$Stone::Fetchlast>. If undefined, you will receive the
263             first member of the list. If nonzero, you will receive the last
264             member.
265              
266             You may provide an optional index in order to force get() to return a
267             particular member of the list. Provide a 0 to return the first member
268             of the list, or '#' to obtain the last member.
269              
270             If the tag contains a period (.), get() will call index() on your
271             behalf (see below).
272              
273             If the tag begins with an uppercase letter, then you can use the
274             autogenerated method to access it:
275              
276             $stone->Tag_name([$index])
277              
278             This is exactly equivalent to:
279              
280             $stone->get('Teg_name' [,$index])
281              
282             =head2 @values = $stone->search($tag)
283              
284             Searches for the first occurrence of the tag, traversing the tree in a
285             breadth-first manner, and returns it. This allows you to retrieve the
286             value of a tag in a deeply nested structure without worrying about all
287             the intermediate nodes. For example:
288              
289             $myStone = new Stone(Name=>'Fred',
290             Friend=>['Jill',
291             'John',
292             'Gerald'
293             ],
294             Attributes => { Hair => 'blonde',
295             Eyes => 'blue' }
296             );
297              
298             $hair_colour = $stone->search('Hair');
299              
300             The disadvantage of this is that if there is a tag named "Hair" higher
301             in the hierarchy, this tag will be retrieved rather than the lower
302             one. In an array context this method returns the complete list of
303             values from the matching tag. In a scalar context, it returns either
304             the first or the last value of multivalued tags depending as usual on
305             the value of C<$Stone::Fetchlast>.
306              
307             C<$Stone::Fetchlast> is also consulted during the depth-first
308             traversal. If C<$Fetchlast> is set to a true value, multivalued
309             intermediate tags will be searched from the last to the first rather
310             than the first to the last.
311              
312             The Stone object has an AUTOLOAD method that invokes get() when you
313             call a method that is not predefined. This allows a very convenient
314             type of shortcut:
315              
316             $name = $stone->Name;
317             @friends = $stone->Friend;
318             $eye_color = $stone->Attributes->Eyes
319              
320             In the first example, we retrieve the value of the top-level tag Name.
321             In the second example, we retrieve the value of the Friend tag.. In
322             the third example, we retrieve the attributes stone first, then the
323             Eyes value.
324              
325             NOTE: By convention, methods are only autogenerated for tags that
326             begin with capital letters. This is necessary to avoid conflict with
327             hard-coded methods, all of which are lower case.
328              
329             =head2 @values = $stone->index($indexstr)
330              
331             You can access the contents of even deeply-nested B objects
332             with the C method. You provide a B, and receive
333             a value or list of values back.
334              
335             Tag paths look like this:
336              
337             tag1[index1].tag2[index2].tag3[index3]
338              
339             Numbers in square brackets indicate which member of a multivalued tag
340             you're interested in getting. You can leave the square brackets out
341             in order to return just the first or the last tag of that name, in a scalar
342             context (depending on the setting of B<$Stone::Fetchlast>). In an
343             array context, leaving the square brackets out will return B
344             multivalued members for each tag along the path.
345              
346             You will get a scalar value in a scalar context and an array value in
347             an array context following the same rules as B. You can
348             provide an index of '#' in order to get the last member of a list or
349             a [?] to obtain a randomly chosen member of the list (this uses the rand() call,
350             so be sure to call srand() at the beginning of your program in order
351             to get different sequences of pseudorandom numbers. If
352             there is no tag by that name, you will receive undef or an empty list.
353             If the tag points to a subrecord, you will receive a B object.
354              
355             Examples:
356              
357             # Here's what the data structure looks like.
358             $s->insert(person=>{name=>Fred,
359             age=>30,
360             pets=>[Fido,Rex,Lassie],
361             children=>[Tom,Mary]},
362             person=>{name=>Harry,
363             age=>23,
364             pets=>[Rover,Spot]});
365              
366             # Return all of Fred's children
367             @children = $s->index('person[0].children');
368              
369             # Return Harry's last pet
370             $pet = $s->index('person[1].pets[#]');
371              
372             # Return first person's first child
373             $child = $s->index('person.children');
374              
375             # Return children of all person's
376             @children = $s->index('person.children');
377              
378             # Return last person's last pet
379             $Stone::Fetchlast++;
380             $pet = $s->index('person.pets');
381              
382             # Return any pet from any person
383             $pet = $s->index('person[?].pet[?]');
384              
385             I that B may return a B object if the tag path
386             points to a subrecord.
387              
388             =head2 $array = $stone->at($tag)
389              
390             This returns an ARRAY REFERENCE for the tag. It is useful to prevent
391             automatic dereferencing. Use with care. It is equivalent to:
392              
393             $stone->{'tag'}
394              
395             at() will always return an array reference. Single-valued tags will
396             return a reference to an array of size 1.
397              
398             =head2 @tags = $stone->tags()
399              
400             Return all the tags in the Stone. You can then use this list with
401             get() to retrieve values or recursively traverse the stone.
402              
403             =head2 $string = $stone->asTable()
404              
405             Return the data structure as a tab-delimited table suitable for
406             printing.
407              
408             =head2 $string = $stone->asXML([$tagname])
409              
410             Return the data structure in XML format. The entire data structure
411             will be placed inside a top-level tag called . If you wish to
412             change this top-level tag, pass it as an argument to asXML().
413              
414             An example follows:
415              
416             print $stone->asXML('Address_list');
417             # yields:
418            
419              
420            
421            
422            
423             10578
424             Katonah
425             Hickory Street
426             NY
427            
428             Smith
429             30
430             Sarah
431            
432            
433            
434             11291
435             Garden City
436             The Manse
437             19 Chestnut Ln
438             NY
439            
440             Hill
441             34
442             James
443            
444            
445              
446             =head2 $hash = $stone->attributes([$att_name, [$att_value]]])
447              
448             attributes() returns the "attributes" of a tag. Attributes are a
449             series of unique tag/value pairs which are associated with a tag, but
450             are not contained within it. Attributes can only be expressed in the
451             XML representation of a Stone:
452              
453            
454            
455             10578
456             Katonah
457             Hickory Street
458             NY
459            
460            
461              
462             Called with no arguments, attributes() returns the current attributes
463             as a hash ref:
464              
465             my $att = $stone->Address->attributes;
466             my $type = $att->{type};
467              
468             Called with a single argument, attributes() returns the value of the
469             named attribute, or undef if not defined:
470              
471             my $type = $stone->Address->attributes('type');
472              
473             Called with two arguments, attributes() sets the named attribute:
474              
475             my $type = $stone->Address->attributes(type => 'Rural Free Delivery');
476              
477             You may also change all attributes in one fell swoop by passing a hash
478             reference as the single argument:
479              
480             $stone->attributes({id=>'Sally Mae',version=>'2.1'});
481              
482             =head2 $string = $stone->toString()
483              
484             toString() returns a simple version of the Stone that shows just the
485             topmost tags and the number of each type of tag. For example:
486              
487             print $stone->Jim->Address;
488             #yields => Zip(1),City(1),Street(2),State(1)
489              
490             This method is used internally for string interpolation. If you try
491             to print or otherwise manipulate a Stone object as a string, you will
492             obtain this type of string as a result.
493              
494             =head2 $string = $stone->asHTML([\&callback])
495              
496             Return the data structure as a nicely-formatted HTML 3.2 table,
497             suitable for display in a Web browser. You may pass this method a
498             callback routine which will be called for every tag/value pair in the
499             object. It will be passed a two-item list containing the current tag
500             and value. It can make any modifications it likes and return the
501             modified tag and value as a return result. You can use this to modify
502             tags or values on the fly, for example to turn them into HTML links.
503              
504             For example, this code fragment will turn all tags named "Sequence"
505             blue:
506              
507             my $callback = sub {
508             my ($tag,$value) = @_;
509             return ($tag,$value) unless $tag eq 'Sequence';
510             return ( qq($tag),$value );
511             }
512             print $stone->asHTML($callback);
513              
514             =head2 Stone::dump()
515              
516             This is a debugging tool. It iterates through the B object and
517             prints out all the tags and values.
518              
519             Example:
520              
521             $s->dump;
522            
523             person[0].children[0]=Tom
524             person[0].children[1]=Mary
525             person[0].name[0]=Fred
526             person[0].pets[0]=Fido
527             person[0].pets[1]=Rex
528             person[0].pets[2]=Lassie
529             person[0].age[0]=30
530             person[1].name[0]=Harry
531             person[1].pets[0]=Rover
532             person[1].pets[1]=Spot
533             person[1].age[0]=23
534              
535             =head2 $cursor = $stone->cursor()
536              
537             Retrieves an iterator over the object. You can call this several
538             times in order to return independent iterators. The following brief
539             example is described in more detail in L.
540              
541             my $curs = $stone->cursor;
542             while (my($tag,$value) = $curs->next_pair) {
543             print "$tag => $value\n";
544             }
545             # yields:
546             Sally[0].Address[0].Zip[0] => 10578
547             Sally[0].Address[0].City[0] => Katonah
548             Sally[0].Address[0].Street[0] => Hickory Street
549             Sally[0].Address[0].State[0] => NY
550             Sally[0].Last_name[0] => James
551             Sally[0].Age[0] => 30
552             Sally[0].First_name[0] => Sarah
553             Jim[0].Address[0].Zip[0] => 11291
554             Jim[0].Address[0].City[0] => Garden City
555             Jim[0].Address[0].Street[0] => The Manse
556             Jim[0].Address[0].Street[1] => 19 Chestnut Ln
557             Jim[0].Address[0].State[0] => NY
558             Jim[0].Last_name[0] => Hill
559             Jim[0].Age[0] => 34
560             Jim[0].First_name[0] => James
561              
562             =head1 AUTHOR
563              
564             Lincoln D. Stein .
565              
566             =head1 COPYRIGHT
567              
568             Copyright 1997-1999, Cold Spring Harbor Laboratory, Cold Spring Harbor
569             NY. This module can be used and distributed on the same terms as Perl
570             itself.
571              
572             =head1 SEE ALSO
573              
574             L, L, L, L,
575             L, L
576              
577             =cut
578              
579 2     2   3124 use Stone::Cursor;
  2         7  
  2         66  
580 2     2   12 use Carp;
  2         4  
  2         254  
581 2     2   11 use constant DEFAULT_WIDTH=>25; # column width for pretty-printing
  2         5  
  2         13734  
582              
583             # This global controls whether you will get the first or the
584             # last member of a multi-valued attribute when you invoke
585             # get() in a scalar context.
586             $Stone::Fetchlast=0;
587              
588             sub AUTOLOAD {
589 0     0   0 my($pack,$func_name) = $AUTOLOAD=~/(.+)::([^:]+)$/;
590 0         0 my $self = shift;
591 0 0       0 croak "Can't locate object method \"$func_name\" via package \"$pack\". ",
592             "Tag names must begin with a capital letter in order to be called this way"
593             unless $func_name =~ /^[A-Z]/;
594 0         0 return $self->get($func_name,@_);
595             }
596              
597             # Create a new Stone object, filling it with the
598             # provided tag/value pairs, if any
599             sub new {
600 38     38 1 56 my($pack,%initial_values) = @_;
601 38         62 my($self) = bless {},$pack;
602 38 100       86 $self->insert(%initial_values) if %initial_values;
603 38         71 return $self;
604             }
605              
606             # Insert the key->value pairs into the Stone object,
607             # appending to any similarly-named keys that were there before.
608             sub insert {
609 21     21 1 48 my($self,@arg) = @_;
610              
611 21         26 my %hash;
612 21 50 33     48 if (ref $arg[0] and ref $arg[0] eq 'HASH') {
613 0         0 %hash = %{$arg[0]};
  0         0  
614             } else {
615 21         42 %hash = @arg;
616             }
617              
618 21         39 foreach (keys %hash) {
619 25         49 $self->insert_list($_,$hash{$_});
620             }
621             }
622              
623             # Add the key->value pairs to the Stone object,
624             # replacing any similarly-named keys that were there before.
625             sub replace {
626 0     0 1 0 my($self,@arg) = @_;
627              
628 0         0 my %hash;
629 0 0 0     0 if (ref $arg[0] and ref $arg[0] eq 'HASH') {
630 0         0 %hash = %{$arg[0]};
  0         0  
631             } else {
632 0         0 %hash = @arg;
633             }
634            
635 0         0 foreach (keys %hash) {
636 0         0 $self->replace_list($_,$hash{$_});
637             }
638             }
639              
640             # Fetch the value at the specified key. In an array
641             # context, this will return the entire array. In a scalar
642             # context, this will return either the first or the last member
643             # of the array, depending on the value of the global Fetchlast.
644             # You can specify an optional index to index into the resultant
645             # array.
646             # Codes:
647             # digit (12) returns the 12th item
648             # hash sign (#) returns the last item
649             # question mark (?) returns a random item
650             # zero (0) returns the first item
651             sub get {
652 44     44 1 55 my($self,$key,$index) = @_;
653 44 50       98 return $self->index($key) if $key=~/[.\[\]]/;
654              
655 44 50       70 if (defined $index) {
656 0 0 0     0 return $self->get_last($key) if $index eq '#' || $index == -1;
657 0 0       0 if ($index eq '?') {
658 0         0 my $size = scalar(@{$self->{$key}});
  0         0  
659 0         0 return $self->{$key}->[rand($size)];
660             }
661 0 0       0 return $self->{$key}->[$index] if $index ne '';
662             }
663              
664 44 100       75 if (wantarray) {
665 37 50       69 return @{$self->{$key}} if $self->{$key};
  37         107  
666 0         0 return my(@empty);
667             }
668 7 50       22 return $self->get_first($key) unless $Fetchlast;
669 0         0 return $self->get_last($key);
670             }
671              
672             # Returns 1 if the key exists.
673             sub exists {
674 0     0 0 0 my($self,$key,$index) = @_;
675 0 0 0     0 return 1 if defined($self->{$key}) && !$index;
676 0 0       0 return 1 if defined($self->{$key}->[$index]);
677 0         0 return undef;
678             }
679              
680             # return an array reference at indicated tag.
681             # Equivalent to $stone->{'tag'}
682             sub at {
683 0     0 1 0 my $self = shift;
684 0         0 return $self->{$_[0]};
685             }
686             #
687             # Delete the indicated key entirely.
688             sub delete {
689 1     1 1 2 my($self,$key) = @_;
690 1         4 delete $self->{$key};
691 1         2 $self->_fix_cursors;
692             }
693              
694             # Return all the tags in the stone.
695             sub tags {
696 15     15 1 15 my $self = shift;
697 15         12 return grep (!/^\./,keys %{$self});
  15         79  
698             }
699              
700             # Return attributes as a hash reference
701             # (only used by asXML)
702             sub attributes {
703 0     0 1 0 my $self = shift;
704 0         0 my ($tag,$value) = @_;
705 0 0       0 if (defined $tag) {
706 0 0       0 return $self->{'.att'} = $tag if ref $tag eq 'HASH';
707 0 0       0 return $self->{'.att'}{$tag} = $value if defined $value;
708 0         0 return $self->{'.att'}{$tag};
709             }
710 0   0     0 return $self->{'.att'} ||= {};
711             }
712              
713              
714             # Fetch an Iterator on the Stone.
715             sub cursor {
716 0     0 1 0 my $self = shift;
717 0         0 return new Stone::Cursor($self);
718             }
719              
720             # Convert a stone into a straight hash
721             sub to_hash {
722 0     0 0 0 my ($self) = shift;
723 0         0 my ($key,%result);
724 0         0 foreach $key (keys %$self) {
725 0 0       0 next if substr($key,0,1) eq '.';
726 0         0 my ($value,@values);
727 0         0 foreach $value (@{$self->{$key}}) {
  0         0  
728             # NG 00-10-04 changed to convert values with .name into those names
729             # NG 00-10-04 and to convert recursive results to HASH ref
730 0 0       0 push(@values,!ref($value)? $value:
    0          
731             defined ($value->{'.name'})? $value->{'.name'}:
732             {$value->to_hash()});
733             }
734 0 0       0 $result{$key} = @values > 1 ? [@values] : $values[0];
735             }
736 0         0 return %result;
737             }
738              
739             # Search for a particular tag and return it using a breadth-first search
740             sub search {
741 0     0 1 0 my ($self,$tag) = @_;
742 0 0       0 return $self->get($tag) if $self->{$tag};
743 0         0 foreach ($self->tags()) {
744 0         0 my @objects = $self->get($_);
745 0 0       0 @objects = reverse(@objects) if $Fetchlast;
746 0         0 foreach my $obj (@objects) {
747 0 0 0     0 next unless ref($obj) and $obj->isa('Stone');
748 0         0 my @result = $obj->search($tag);
749 0 0       0 return wantarray ? @result : ($Fetchlast ? $result[$#result] : $result[0]);
    0          
750             }
751             }
752 0 0       0 return wantarray ? () : undef;
753             }
754              
755             # Extended indexing, using a compound index that
756             # looks like:
757             # key1[index].key2[index].key3[index]
758             # If indices are left out, then you can get
759             # multiple values out:
760             # 1. In a scalar context, you'll get the first or last
761             # value from each position.
762             # 2. In an array context, you'll get all the values!
763             sub index {
764 4     4 1 7 my($self,$index) = @_;
765 4         14 return &_index($self,split(/\./,$index));
766             }
767              
768             sub _index {
769 8     8   11 my($self,@indices) = @_;
770 8         9 my(@value,$key,$position,$i);
771 0         0 my(@results);
772 8         9 $i = shift @indices;
773              
774 8 50       25 if (($key,$position) = $i=~/(.+)\[([\d\#\?]+)\]/) { # has a position
    100          
775 0         0 @value = $self->get($key,$position); # always a scalar
776             } elsif (wantarray) {
777 6         11 @value = $self->get($i);
778             } else {
779 2         7 @value = scalar($self->get($i));
780             }
781            
782 8         12 foreach (@value) {
783 10 50       19 next unless ref $_;
784 10 100       15 if (@indices) {
785 4 50 33     37 push @results,&_index($_,@indices) if $_->isa('Stone') && !exists($_->{'.name'});
786             } else{
787 6         12 push @results,$_;
788             }
789             }
790 8 100       36 return wantarray ? @results : $results[0];
791             }
792              
793             # Return the data structure as a nicely-formatted tab-delimited table
794             sub asTable {
795 0     0 1 0 my $self = shift;
796 0         0 my $string = '';
797 0         0 $self->_asTable(\$string,0,0);
798 0         0 return $string;
799             }
800              
801             # Return the data structure as a nice string representation (problematic)
802             sub asString {
803 0     0 0 0 my $self = shift;
804 0   0     0 my $MAXWIDTH = shift || DEFAULT_WIDTH;
805 0         0 my $tabs = $self->asTable;
806 0 0       0 return '' unless $tabs;
807 0         0 my(@lines) = split("\n",$tabs);
808 0         0 my($result,@max);
809 0         0 foreach (@lines) {
810 0         0 my(@fields) = split("\t");
811 0         0 for (my $i=0;$i<@fields;$i++) {
812 0 0 0     0 $max[$i] = length($fields[$i]) if
813             !defined($max[$i]) or $max[$i] < length($fields[$i]);
814             }
815             }
816 0 0       0 foreach (@max) { $_ = $MAXWIDTH if $_ > $MAXWIDTH; } # crunch long lines
  0         0  
817 0         0 my $format1 = join(' ',map { "^"."<"x $max[$_] } (0..$#max)) . "\n";
  0         0  
818 0         0 my $format2 = ' ' . join(' ',map { "^"."<"x ($max[$_]-1) } (0..$#max)) . "~~\n";
  0         0  
819 0         0 $^A = '';
820 0         0 foreach (@lines) {
821 0         0 my @data = split("\t");
822 0         0 push(@data,('')x(@max-@data));
823 0         0 formline ($format1,@data);
824 0         0 formline ($format2,@data);
825             }
826 0         0 return ($result = $^A,$^A='')[0];
827             }
828              
829             # Return the data structure as an HTML table
830             sub asHTML {
831 0     0 1 0 my $self = shift;
832 0         0 my $modify = shift;
833 0   0     0 $modify ||= \&_default_modify_html;
834 0         0 my $string = "\n"; \n
835 0         0 $self->_asHTML(\$string,$modify,0,0);
836 0         0 $string .= "
";
837 0         0 return $string;
838             }
839              
840             # Return data structure using XML syntax
841             # Top-level tag is unless otherwise specified
842             sub asXML {
843 0     0 1 0 my $self = shift;
844 0   0     0 my $top = shift || "Stone";
845 0   0     0 my $modify = shift || \&_default_modify_xml;
846 0         0 my $att;
847 0 0       0 if (exists($self->{'.att'})) {
848 0         0 my $a = $self->attributes;
849 0         0 foreach (keys %$a) {
850 0         0 $att .= qq( $_="$a->{$_}");
851             }
852             }
853 0         0 my $string = "<${top}${att}>\n";
854 0         0 $self->_asXML(\$string,$modify,0,1);
855 0         0 $string .="\n";
856 0         0 return $string;
857             }
858              
859             # This is the method used for string interpolation
860             sub toString {
861 44     44 1 89 my $self = shift;
862 44 100       192 return $self->{'.name'} if exists $self->{'.name'};
863 6         13 my @tags = map { my @v = $self->get($_);
  16         29  
864 16         19 my $cnt = scalar @v;
865 16         38 "$_($cnt)"
866             } $self->tags;
867 6 100       17 return '' unless @tags;
868 5         23 return join ',',@tags;
869             }
870              
871              
872             sub _asTable {
873 0     0   0 my $self = shift;
874 0         0 my ($string,$position,$level) = @_;
875 0         0 my $pos = $position;
876 0         0 foreach my $tag ($self->tags) {
877 0         0 my @values = $self->get($tag);
878 0         0 foreach my $value (@values) {
879 0         0 $$string .= "\t" x ($level-$pos) . "$tag\t";
880 0         0 $pos = $level+1;
881 0 0       0 if (exists $value->{'.name'}) {
882 0         0 $$string .= "\t" x ($level-$pos+1) . "$value\n";
883 0         0 $pos=0;
884             } else {
885 0         0 $pos = $value->_asTable($string,$pos,$level+1);
886             }
887             }
888             }
889 0         0 return $pos;
890             }
891              
892             sub _asXML {
893 0     0   0 my $self = shift;
894 0         0 my ($string,$modify,$pos,$level) = @_;
895 0         0 foreach my $tag ($self->tags) {
896 0         0 my @values = $self->get($tag);
897 0         0 foreach my $value (@values) {
898 0 0       0 my($title,$contents) = $modify ? $modify->($tag,$value) : ($tag,$value);
899 0         0 my $att;
900              
901 0 0       0 if (exists $value->{'.att'}) {
902 0         0 my $a = $value->{'.att'};
903 0         0 foreach (keys %$a) {
904 0         0 $att .= qq( $_="$a->{$_}");
905             }
906             }
907              
908 0         0 $$string .= ' ' x ($level-$pos) . "<${title}${att}>";
909 0         0 $pos = $level+1;
910              
911 0 0       0 if (exists $value->{'.name'}) {
912 0         0 $$string .= ' ' x ($level-$pos+1) . "$contents\n";
913 0         0 $pos=0;
914             } else {
915 0         0 $$string .= "\n" . ' ' x ($level+1);
916 0         0 $pos = $value->_asXML($string,$modify,$pos,$level+1);
917 0         0 $$string .= ' ' x ($level-$pos) . "\n";
918             }
919             }
920             }
921 0         0 return $pos;
922             }
923              
924             sub _asHTML {
925 0     0   0 my $self = shift;
926 0         0 my ($string,$modify,$position,$level) = @_;
927 0         0 my $pos = $position;
928 0         0 foreach my $tag ($self->tags) {
929 0         0 my @values = $self->get($tag);
930 0         0 foreach my $value (@values) {
931 0         0 my($title,$contents) = $modify->($tag,$value);
932 0 0       0 $$string .= "
933 0         0 $$string .= "$title
934 0         0 $pos = $level+1;
935 0 0       0 if (exists $value->{'.name'}) {
936 0         0 $$string .= "$contents
937 0         0 $pos=0;
938             } else {
939 0         0 $pos = $value->_asHTML($string,$modify,$pos,$level+1);
940             }
941             }
942             }
943              
944 0         0 return $pos;
945             }
946              
947             sub _default_modify_html {
948 0     0   0 my ($tag,$value) = @_;
949 0         0 return ("$tag",$value);
950             }
951              
952             sub _default_modify_xml {
953 0     0   0 my ($tag,$value) = @_;
954 0         0 $value =~ s/&/&/g;
955 0         0 $value =~ s/>/>/g;
956 0         0 $value =~ s/
957 0         0 ($tag,$value);
958             }
959              
960             # Dump the entire data structure, for debugging purposes
961             sub dump {
962 0     0 1 0 my($self) = shift;
963 0         0 my $i = $self->cursor;
964 0         0 my ($key,$value);
965 0         0 while (($key,$value)=$i->each) {
966 0         0 print "$key=$value\n";
967             }
968             # this has to be done explicitly here or it won't happen.
969 0         0 $i->DESTROY;
970             }
971              
972             # return the name of the Stone
973             sub name {
974 0 0   0 0 0 $_[0]->{'.name'} = $_[1] if defined $_[1];
975 0         0 return $_[0]->{'.name'}
976             }
977              
978              
979             # --------- LOW LEVEL DATA INSERTION ROUTINES ---------
980             # Append a set of values to the key.
981             # One or more values may be other Stones.
982             # You can pass the same value multiple times
983             # to enter multiple values, or alternatively
984             # pass an anonymous array.
985             sub insert_list {
986 33     33 1 52 my($self,$key,@values) = @_;
987              
988 33         40 foreach (@values) {
989 33         36 my $ref = ref($_);
990              
991 33 100       56 if (!$ref) { # Inserting a scalar
992 27         61 my $s = new Stone;
993 27         52 $s->{'.name'} = $_;
994 27         24 push(@{$self->{$key}},$s);
  27         54  
995 27         53 next;
996             }
997              
998 6 100       14 if ($ref=~/Stone/) { # A simple insertion
999 3         3 push(@{$self->{$key}},$_);
  3         5  
1000 3         7 next;
1001             }
1002              
1003 3 50       14 if ($ref eq 'ARRAY') { # A multivalued insertion
1004 0         0 $self->insert_list($key,@{$_}); # Recursive insertion
  0         0  
1005 0         0 next;
1006             }
1007            
1008 3 50       5 if ($ref eq 'HASH') { # Insert a record, potentially recursively
1009 3         4 $self->insert_hash($key,%{$_});
  3         13  
1010 3         6 next;
1011             }
1012              
1013 0         0 warn "Attempting to insert a $ref into a Stone. Be alert.\n";
1014 0         0 push(@{$self->{$key}},$_);
  0         0  
1015              
1016             }
1017 33         61 $self->_fix_cursors;
1018             }
1019              
1020             # Put the values into the key, replacing
1021             # whatever was there before.
1022             sub replace_list {
1023 0     0 1 0 my($self,$key,@values) = @_;
1024 0         0 $self->{$key}=[]; # clear it out
1025 0         0 $self->insert_list($key,@values); # append the values
1026             }
1027              
1028             # Similar to put_record, but doesn't overwrite the
1029             # previous value of the key.
1030             sub insert_hash {
1031 3     3 1 562 my($self,$key,%values) = @_;
1032 3         8 my($newrecord) = $self->new_record($key);
1033 3         7 foreach (keys %values) {
1034 8         18 $newrecord->insert_list($_,$values{$_});
1035             }
1036             }
1037              
1038             # Put a new associative array at the indicated key,
1039             # replacing whatever was there before. Multiple values
1040             # can be represented with an anonymous ARRAY reference.
1041             sub replace_hash {
1042 0     0 1 0 my($self,$key,%values) = @_;
1043 0         0 $self->{$key}=[]; # clear it out
1044 0         0 $self->insert_hash($key,%values);
1045             }
1046              
1047             #------------------- PRIVATE SUBROUTINES-----------
1048             # Create a new record at indicated key
1049             # and return it.
1050             sub new_record {
1051 3     3 0 4 my($self,$key) = @_;
1052 3         4 my $stone = new Stone();
1053 3         4 push(@{$self->{$key}},$stone);
  3         6  
1054 3         4 return $stone;
1055             }
1056              
1057             sub get_first {
1058 7     7 0 8 my($self,$key) = @_;
1059 7         27 return $self->{$key}->[0];
1060             }
1061              
1062             sub get_last {
1063 0     0 0 0 my($self,$key) = @_;
1064 0         0 return $self->{$key}->[$#{$self->{$key}}];
  0         0  
1065             }
1066              
1067             # This is a private subroutine used for registering
1068             # and unregistering cursors
1069             sub _register_cursor {
1070 0     0   0 my($self,$cursor,$register) = @_;
1071 0 0       0 if ($register) {
1072 0         0 $self->{'.cursors'}->{$cursor}=$cursor;
1073             } else {
1074 0         0 delete $self->{'.cursors'}->{$cursor};
1075 0 0       0 delete $self->{'.cursors'} unless %{$self->{'.cursors'}};
  0         0  
1076             }
1077             }
1078              
1079             # This is a private subroutine used to alert cursors that
1080             # our contents have changed.
1081             sub _fix_cursors {
1082 34     34   35 my($self) = @_;
1083 34 50       150 return unless $self->{'.cursors'};
1084 0         0 my($cursor);
1085 0         0 foreach $cursor (values %{$self->{'.cursors'}}) {
  0         0  
1086 0         0 $cursor->reset;
1087             }
1088             }
1089              
1090             # This is a private subroutine. It indexes
1091             # all the way into the structure.
1092             #sub _index {
1093             # my($self,@indices) = @_;
1094             # my $stone = $self;
1095             # my($key,$index,@h);
1096             # while (($key,$index) = splice(@indices,0,2)) {
1097             # unless (defined($index)) {
1098             # return scalar($stone->get($key)) unless wantarray;
1099             # return @h = $stone->get($key) if wantarray;
1100             # } else {
1101             # $stone= ($index eq "\#") ? $stone->get_last($key):
1102             # $stone->get($key,$index);
1103             # last unless ref($stone)=~/Stone/;
1104             # }
1105             # }
1106             # return $stone;
1107             #}
1108              
1109             sub DESTROY {
1110 38     38   51 my $self = shift;
1111 38         35 undef %{$self->{'.cursor'}}; # not really necessary ?
  38         243  
1112             }
1113              
1114              
1115             1;