1 # $Id: HIVQueryHelper.pm 231 2008-12-11 14:32:00Z maj $
3 # BioPerl module for Bio::DB::HIV::HIVQueryHelper
5 # Please direct questions and support issues to <bioperl-l@bioperl.org>
7 # Cared for by Mark A. Jensen <maj@fortinbras.us>
9 # Copyright Mark A. Jensen
11 # You may distribute this module under the same terms as perl itself
13 # POD documentation - main docs before the code
17 Bio::DB::HIV::HIVQueryHelper - Routines and packages used by Bio::DB::HIV and
18 Bio::DB::Query::HIVQuery
22 Used in Bio::DB::Query::HIVQuery. No need to use directly.
26 C<Bio::DB::HIV::HIVQueryHelper> contains a number of packages for use
27 by L<Bio::DB::Query::HIVQuery>. Package C<HIVSchema> parses the
28 C<lanl-schema.xml> file, and allows access to it in the context of the
29 relational database it represents (see APPENDIX for excruciating
30 detail). Packages C<QRY>, C<R>, and C<Q> together create the query
31 string parser that enables NCBI-like queries to be understood by
32 C<Bio::DB::Query::HIVQuery>. They provide objects and operators to
33 perform and simplify logical expressions involving C<AND>, C<OR>, and
34 C<()> and return hash structures that can be handled by
35 C<Bio::DB::Query::HIVQuery> routines.
41 User feedback is an integral part of the evolution of this and other
42 Bioperl modules. Send your comments and suggestions preferably to
43 the Bioperl mailing list. Your participation is much appreciated.
45 bioperl-l@bioperl.org - General discussion
46 http://bioperl.org/wiki/Mailing_lists - About the mailing lists
50 Please direct usage questions or support issues to the mailing list:
52 I<bioperl-l@bioperl.org>
54 rather than to the module maintainer directly. Many experienced and
55 reponsive experts will be able look at the problem and quickly
56 address it. Please include a thorough description of the problem
57 with code and data examples if at all possible.
61 Report bugs to the Bioperl bug tracking system to help us keep track
62 of the bugs and their resolution. Bug reports can be submitted via
65 https://github.com/bioperl/bioperl-live/issues
67 =head1 AUTHOR - Mark A. Jensen
69 Email maj@fortinbras.us
77 The rest of the documentation details each of the contained packages.
78 Internal methods are usually preceded with a _
82 # Let the code begin...
84 package Bio
::DB
::HIV
::HIVQueryHelper
;
91 @Bio::QueryStringSyntax
::Exception
::ISA
= qw( Bio::Root::Exception);
96 =head2 HIVSchema - objects/methods to manipulate a version of the LANL HIV DB schema
98 =head3 HIVSchema SYNOPSIS
100 $schema = new HIVSchema( 'lanl-schema.xml' );
101 @tables = $schema->tables;
102 @validFields = $schema->fields;
103 @validAliases = $schema->aliases;
104 @query_aliases_for_coreceptor = $schema->aliases( 'SEQ_SAMple.SSAM_second_receptor' );
105 $pk_for_SequenceEntry = $schema->primarykey('SequenceEntry'); # returns 'SequenceEntry.SE_id'
106 $fk_for_SEQ_SAMple_to_SequenceEntry =
107 $schema->foreignkey('SEQ_SAMple', 'SequenceEntry'); # returns 'SEQ_SAMple.SSAM_SE_id'
109 $table = $schema->tablepart('SEQ_SAMple.SSAM_badseq'); # returns 'SEQ_SAMple'
110 $column = $schema->columnpart('SEQ_SAMple.SSAM_badseq'); # returns 'SSAM_badseq'
112 =head3 HIVSchema DESCRIPTION
114 HIVSchema methods are used in L<Bio::DB::Query::HIVQuery> for table,
115 column, primary/foreign key manipulations based on the observed Los
116 Alamos HIV Sequence Database (LANL DB) naming conventions for their
117 CGI parameters. The schema is contained in an XML file
118 (C<lanl-schema.xml>) which is read into an HIVSchema object, in turn a
119 property of the HIVQuery object. HIVSchema methods are used to build
120 correct cgi queries in a way that attempts to preserve the context of
121 the relational database the query parameters represent.
125 package # hide from PAUSE
127 # objects/methods to manipulate a version of the LANL HIV DB schema
135 =head3 HIVSchema CONSTRUCTOR
137 =head4 HIVSchema::new
140 Usage : $schema = new HIVSchema( "lanl-schema.xml ");
143 Returns : an HIVSchema object
153 $self->{schema_ref
} = loadHIVSchema
($args[0]);
155 bless($self, $class);
161 =head3 HIVSchema INSTANCE METHODS
163 =head4 HIVSchema tables
166 Usage : $schema->tables()
167 Function: get all table names in schema
169 Returns : array of table names
175 # return array of all tables in schema
178 my $sref = $self->{schema_ref
};
179 Bio
::Root
::Root
->throw("schema not initialized") unless $sref;
180 my @k = grep(/\./, keys %$sref);
190 =head4 HIVSchema columns
193 Usage : $schema->columns( [$tablename] );
194 Function: return array of columns for specified table, or all columns in
195 schema, if called w/o args
198 Args : tablename or fieldname string
203 # return array of columns for specified table
204 # all columns in schema, if called w/o args
208 my $sref = $self->{schema_ref
};
209 Bio
::Root
::Root
->throw("schema not initialized") unless $sref;
212 # check if table exists
213 return () unless grep(/^$tbl$/i, $self->tables);
214 my @k = sort keys %$sref;
215 @k = grep (/^$tbl\./i, @k);
222 =head4 HIVSchema fields
225 Usage : $schema->fields();
226 Function: return array of all fields in schema, in format "table.column"
228 Returns : array of all fields
234 # return array of all fields (Table.Column format) in schema
236 my $sref = $self->{schema_ref
};
237 Bio
::Root
::Root
->throw("schema not initialized") unless $sref;
238 my @k = sort keys %{$sref};
242 =head4 HIVSchema options
245 Usage : $schema->options(@fieldnames)
246 Function: get array of options (i.e., valid match data strings) available
249 Returns : array of match data strings
250 Args : [array of] fieldname string[s] in "table.column" format
255 # return array of options available to specified field
258 my $sref = $self->{schema_ref
};
259 Bio
::Root
::Root
->throw("schema not initialized") unless $sref;
260 return $$sref{$sfield}{option
} ? @
{$$sref{$sfield}{option
}} : ();
263 =head4 HIVSchema aliases
266 Usage : $schema->aliases(@fieldnames)
267 Function: get array of aliases to specified field[s]
269 Returns : array of valid query aliases for fields as spec'd in XML file
270 Args : [an array of] fieldname[s] in "table.column" format
275 # return array of aliases to specified field
278 my $sref = $self->{schema_ref
};
280 Bio
::Root
::Root
->throw("schema not initialized") unless $sref;
282 return $$sref{$sfield}{alias
} ? @
{$$sref{$sfield}{alias
}} : ();
284 else { # all valid aliases
285 map {push @ret, @
{$$sref{$_}{alias
}} if $$sref{$_}{alias
}} $self->fields;
290 =head4 HIVSchema ankh
292 Title : ankh (annotation key hash)
293 Usage : $schema->ankh(@fieldnames)
294 Function: return a hash translating fields to annotation keys for the
296 (Annotation keys are used for parsing the tab-delimited response
297 to Bio::DB::Query::HIVQuery::_do_lanl_request.)
300 Args : [an array of] fieldname[s] in "table.column" format
305 # return hash translating sfields to annotation keys for specified sfield(s)
309 my $sref = $self->{schema_ref
};
310 Bio
::Root
::Root
->throw("schema not initialized") unless $sref;
312 next unless $$sref{$_}{ankey
};
313 $ret{$_} = {'ankey'=>$$sref{$_}{ankey
},'antype'=>$$sref{$_}{antype
}};
318 =head4 HIVSchema tablepart
320 Title : tablepart (alias: tbl)
321 Usage : $schema->tbl(@fieldnames)
322 Function: return the portion of the fieldname[s] that refer to the
324 Example : $schema->tbl('SequenceEntry.SE_id'); # returns 'SequenceEntry'
325 Returns : table name as string
326 Args : [an array of] fieldname[s] in "table.column" format
331 # return the 'Table' part of the specified field(s)
334 Bio
::Root
::Root
->throw("schema not initialized") unless $self->{schema_ref
};
335 my ($squish,@ret, %ret);
336 if ($sfields[0] eq '-s') {
337 # squish : remove duplicates from the returned array
342 push @ret, /^(.*)\./;
345 # arg order is clobbered
349 return (wantarray ?
@ret : $ret[0]);
354 shift->tablepart(@_);
357 =head4 HIVSchema columnpart
359 Title : columnpart (alias: col)
360 Usage : $schema->col(@fieldnames)
361 Function: return the portion of the fieldname[s] that refer to the
363 Example : $schema->col('SequenceEntry.SE_id'); # returns 'SE_id'
364 Returns : column name as string
365 Args : [an array of] fieldname[s] in "table.column" format
370 # return the 'Column' part of the specified field(s)
373 Bio
::Root
::Root
->throw("schema not initialized") unless $self->{schema_ref
};
376 push @ret, /\.(.*)$/;
378 return (wantarray ?
@ret : $ret[0]);
383 shift->columnpart(@_);
386 =head4 HIVSchema primarykey
388 Title : primarykey [alias: pk]
389 Usage : $schema->pk(@tablenames);
390 Function: return the primary key of the specified table[s], as judged by
391 the syntax of the table's[s'] fieldnames
392 Example : $schema->pk('SequenceEntry') # returns 'SequenceEntry.SE_id'
393 Returns : primary key fieldname[s] in "table.column" format, or null if
395 Args : [an array of] table name[s] (fieldnames are ok, table part used)
400 # return the primary key (in Table.Column format) of specified table(s)
404 Bio
::Root
::Root
->throw("schema not initialized") unless $self->{schema_ref
};
405 foreach my $tbl (@tbl) {
408 grep(/^$tbl$/i, $self->tables) ?
409 push(@ret, grep(/\.[0-9a-zA-Z]+_id/, grep(/$tbl/i,$self->fields))) :
412 return (wantarray ?
@ret : $ret[0]);
417 shift->primarykey(@_);
420 =head4 HIVSchema foreignkey
422 Title : foreignkey [alias: fk]
423 Usage : $schema->fk($intable [, $totable])
424 Function: return foreign key fieldname in table $intable referring to
425 table $totable, or all foreign keys in $intable if $totable
427 Example : $schema->fk('AUthor', 'SequenceEntry'); # returns 'AUthor_AU_SE_id'
428 Returns : foreign key fieldname[s] in "table.column" format
429 Args : tablename [, optional foreign table name] (fieldnames are ok,
435 # return foreign key in in-table ($intbl) to to-table ($totbl)
436 # or all foreign keys in in-table if to-table not specified
437 # keys returned in Table.Column format
439 my ($intbl, $totbl) = @_;
440 Bio
::Root
::Root
->throw("schema not initialized") unless $self->{schema_ref
};
443 $totbl =~ s/\..*$// if $totbl;
444 # check if in-table exists
445 return () unless grep( /^$intbl/i, $self->tables);
446 my @ret = grep( /$intbl\.(?:[0-9a-zA-Z]+_){2,}id/i, $self->fields);
448 my $tpk = $self->primarykey($totbl);
449 return (wantarray ?
() : "") unless grep( /^$totbl/i, $self->tables) && $tpk;
450 ($tpk) = ($tpk =~ /\.(.*)$/);
451 @ret = grep( /$tpk$/, @ret);
452 return (wantarray ?
@ret : $ret[0]);
455 # return all foreign keys in in-table
462 shift->foreignkey(@_);
465 =head4 HIVSchema foreigntable
467 Title : foreigntable [alias ftbl]
468 Usage : $schema->ftbl( @foreign_key_fieldnames );
469 Function: return tablename of table that foreign keys points to
470 Example : $schema->ftbl( 'AUthor.AU_SE_id' ); # returns 'SequenceEntry'
472 Args : [an array of] fieldname[s] in "table.column" format
477 # return table name that foreign key(s) point(s) to
481 Bio
::Root
::Root
->throw("schema not initialized") unless $self->{schema_ref
};
483 my ($mnem, $fmnem) = /\.([0-9a-zA-Z]+)_([0-9a-zA-Z]+)_.*$/;
484 next unless $mnem && $fmnem;
485 # lookup based on Table.Column format of fields
486 my $sf = [grep( /^[0-9a-zA-Z]+\.$fmnem\_/, $self->fields )]->[0];
488 ($sf) = ($sf =~ /^([0-9a-zA-Z]+)\./);
491 return (wantarray ?
@ret : $ret[0]);
496 shift->foreigntable(@_);
499 =head4 HIVSchema find_join
502 Usage : $sch->find_join('Table1', 'Table2')
503 Function: Retrieves a set of foreign and primary keys (in table.column
504 format) that represents a join path from Table1 to Table2
506 Returns : an array of keys (as table.column strings) -or- an empty
507 array if Table1 == Table2 -or- undef if no path exists
508 Args : two table names as strings
514 my ($tgt, $tbl) = @_;
515 my ($stack, $revstack, $found, $revcut) = ([],[], 0, 4);
516 $self->_find_join_guts($tgt, $tbl, $stack, \
$found);
518 if (@
$stack > $revcut) {
519 # reverse order of tables, see if a shorter path emerges
521 $self->_find_join_guts($tgt, $tbl, $revstack, \
$found, 1);
522 return (@
$stack <= @
$revstack ? @
$stack : @
$revstack);
531 =head4 HIVSchema _find_join_guts
533 Title : _find_join_guts
534 Usage : $sch->_find_join_guts($table1, $table2, $stackref, \$found, $reverse)
535 (call with $stackref = [], $found=0)
536 Function: recursive guts of find_join
538 Returns : if a path is found, $found==1 and @$stackref contains the keys
539 in table.column format representing the path; if a path is not
540 found, $found == 0 and @$stackref contains garbage
541 Args : $table1, $table2 : table names as strings
542 $stackref : an arrayref to an empty array
543 \$found : a scalar ref to the value 0
544 $rev : if $rev==1, the arrays of table names will be reversed;
545 this can give a shorter path if cycles exist in the
550 sub _find_join_guts
{
552 my ($tbl, $tgt, $stack, $found, $rev) = @_;
553 return () if $tbl eq $tgt;
554 my $k = $self->pk($tbl);
556 # all fks pointing to pk
558 $self->fk($_, $k) || ()
559 } ($rev ?
reverse $self->tables : $self->tables);
560 # skip keys already on stack
562 (@
$stack == 1) && do {
563 @fk2pk = grep (!/$$stack[0]/, @fk2pk);
565 (@
$stack > 1 ) && do {
566 @fk2pk = map { my $f=$_; grep(/$f/, @
$stack) ?
() : $f } @fk2pk;
569 foreach my $f2p (@fk2pk) { # tables with fks pointing to pk
571 if ($self->tbl($f2p) eq $tgt) { # this fk's table is the target
578 $self->_find_join_guts($self->tbl($f2p), $tgt, $stack, $found, $rev);
584 my @fks = ($rev ?
reverse $self->fk($tbl) : $self->fk($tbl));
585 #skip keys already on stack
587 (@
$stack == 1) && do {
588 @fks = grep(!/$$stack[0]/, @fks);
590 (@
$stack > 1) && do {
591 @fks = map { my $f=$_; grep(/$f/, @
$stack) ?
() : $f } @fks;
598 if ($self->ftbl($f) eq $tgt) { #found it
603 $self->_find_join_guts($self->ftbl($f), $tgt, $stack, $found, $rev);
604 $$found ?
return : pop @
$stack;
614 =head4 HIVSchema loadSchema
616 Title : loadHIVSchema [alias: loadSchema]
617 Usage : $schema->loadSchema( $XMLfilename )
618 Function: read (LANL DB) schema spec from XML
619 Example : $schema->loadSchema('lanl-schema.xml');
620 Returns : hashref to schema data
621 Keys are fieldnames in "table.column" format.
622 Each value is a hashref with the following properties:
623 {name} : HIVWEB 'table.column' format fieldname,
624 can be used directly in the cgi query
625 {aliases} : ref to array containing valid aliases/shortcuts for
626 {name}; can be used in routines creating the HTML query
627 {options} : ref to array containing valid matchdata for this field
628 can be used directly in the HTML query
629 {ankey} : contains the annotation key for this field used with
630 Bioperl annotation objects
631 {..attr..}: ..value_of_attr.. for this field (app-specific metadata)
638 Bio
::Root
::Root
->throw("loadHIVSchema: schema file not found") unless -e
$fn;
639 my $q = XML
::Simple
->new(ContentKey
=>'name',NormalizeSpace
=>2,ForceArray
=>1);
641 my $ref = $q->XMLin($fn);
642 my @sf = keys %{$$ref{sfield
}};
644 my $h = $$ref{sfield
}{$_};
646 foreach my $ptr ($$h{option
}, $$h{alias
}) {
648 # kludge for XMLin: appears to convert to arrays, if there
649 # exists a tag without content, but to convert to hashes
650 # with content as key, if all tags possess content
651 if (ref($ptr) eq 'HASH') {
652 my @k = keys %{$ptr};
653 if (grep /desc/, keys %{$ptr->{$k[0]}}) {
655 $$h{desc
} = [ map { $$ptr{$_}->{desc
} } @k ];
657 # now overwrite with keys (descs in same order...)
660 elsif (ref($ptr) eq 'ARRAY') {
661 $ptr = [map { ref eq 'HASH' ?
$_->{name
} : $_ } @
{$ptr}]
668 for my $ptr ($$h{ankey
}) {
670 my $ank = [keys %{$ptr}]->[0];
675 $h->{antype
} = $ptr->{$ank}{antype
};
685 $self->{schema_ref
} = loadHIVSchema
(shift);
690 =head4 HIVSchema _sfieldh
693 Usage : $schema->_sfieldh($fieldname)
694 Function: get hashref to the specified field hash
697 Args : fieldname in "table.column" format
702 # return reference to the specified field hash
705 return ${$self->{schema_ref
}}{$sfield};
710 =head2 Class QRY - a query algebra for HIVQuery
716 new Q('coreceptor', 'CXCR4'),
717 new Q('country', 'ZA')
720 QRY::Eq(QRY::And($Q, $Q), $Q); # returns 1
721 QRY::Eq(QRY::Or($Q, $Q), $Q); # returns 1
725 new Q( 'coreceptor', 'CCR5' ),
726 new Q( 'country', 'ZA')
729 (QRY::And($Q, $Q2))->isnull; # returns 1
730 $Q3 = QRY::Or($Q, $Q2);
731 print $Q3->A; # prints '(CCR5 CXCR4)[coreceptor] (ZA)[country]'
733 =head3 QRY DESCRIPTION
735 The QRY package provides a query parser for
736 L<Bio::DB::Query::HIVQuery>. Currently, the parser supports AND, OR,
737 and () operations. The structure of the LANL cgi makes it tricky to
738 perform NOTs, though this could be implemented if the desire were
741 Two class methods do the work. C<QRY::_parse_q> does a first-pass
742 parse of the query string. C<QRY::_make_q> interprets the parse tree
743 as returned by C<QRY::_parse_q> and produces an array of hash
744 structures that can be used directly by C<Bio::DB::Query::HIVQuery>
745 query execution methods. Validation of query fields and options is
746 performed at the C<Bio::DB::Query::HIVQuery> level, not here.
748 C<QRY> objects are collections of C<R> (or request) objects, which are
749 in turn collections of C<Q> (or atomic query) objects. C<Q> objects
750 represent a query on a single field, with match data options C<OR>ed
751 together, e.g. C<(A B)[subtype]>. C<R> objects collect C<Q> objects
752 that could be processed in a single HTTP request; i.e., a set of
753 atomic queries each having different fields C<AND>ed together, such as
755 (A B)[subtype] AND ('CCR5')[coreceptor] AND (US CA)[country]
757 The C<QRY> object collects C<R>s that cannot be reduced (through
758 logical operations) to a single HTTP request, e.g.
760 ((C)[subtype] AND (SI)[phenotype]) OR ( (D)[subtype] AND (NSI)[phenotype] ),
762 which cannot be got in one go through the current LANL cgi
763 implementation (as far as I can tell). The parser will simplify
766 ((C)[subtype] AND (SI)[phenotype]) OR ((C)[subtype] AND (NSI)[phenotype])
768 to the single request
770 (C)[subtype] AND (NSI SI)[phenotype]
774 The operators C<&> and C<|> are overloaded to C<QRY::And> and
775 C<QRY::Or>, to get Perl precedence and grouping for free. C<bool> is
776 overloaded to get symbolic tests such as C<if ($QRY) {stuff}>. C<==>
777 is overloaded with C<QRY::Eq> for convenience. No overloading is done
782 # a query algebra for HIVQuery
784 # Each Q object is an 'atomic' query, written as (data)[field]
785 # (a b ...)[X] equals (a)[X] | (b)[X] | ...
786 # Each R object represents a single HTTP request to the db
787 # contains an array of Q (atomic) objects (q1, q2, ...)
788 # the R object is interpreted as q1 & q2 & ...
789 # Each QRY object represents a series of HTTP requests to the db
790 # contains an array of R (request) objects (R1, R2, ...)
791 # the QRY object is interpreted as R1 | R2 | ...
793 # & and | operations are specified for each type
795 package # hide from PAUSE
798 $QRY::NULL
= new QRY
();
808 # query language emulator
809 # supports only AND and OR, any groupings
812 # query atom: bareword [field] OR (bareword ...) [field]
813 # only single bareword allowed between []
814 # annotation fields in {} (only bareword lists allowed between {})
815 # () can group query atoms joined by operators (AND or OR)
816 # () containing only barewords MUST be followed by a field descriptor [field]
817 # empty [] not allowed
818 # query atoms joined with AND by default
819 # barewords are associated (ORed within) the next field descriptor in the line
821 # follow the parse tree, creating new QRY objects as needed in @q, and
822 # construct a logical expression using & and | symbols.
823 # These are overloaded for doing ands and ors on QRY objects;
824 # to get the final QRY object, eval the resulting expression $q_expr.
825 # QRY object will be translated into (possibly multiple) hashes
826 # conforming to HIVQuery parameter requirements.
831 Usage : QRY::_make_q($parsetree)
832 Function: creates hash structures suitable for HIVQuery from parse tree
833 returned by QRY::_parse_q
835 Returns : array of hashrefs of query specs
842 my ($q_expr, @q, @an, $query, @dbq);
843 _make_q_guts
($ptree, \
$q_expr, \
@q, \
@an);
844 $query = eval $q_expr;
845 throw Bio
::Root
::Root
(-class=>'Bio::Root::Exception',
847 -value
=>$q_expr) if $@
;
848 return {} if $query->isnull;
849 foreach my $rq ($query->requests) {
850 my $h = {'query'=>{}};
851 foreach ($rq->atoms) {
852 my @d = split(/\s+/, $_->dta);
854 $d =~ s/[+]/ /g; ###! _ to [+]
857 $h->{'query'}{$_->fld} = (@d == 1) ?
$d[0] : [@d];
859 $h->{'annot'} = [@an] if @an;
865 =head4 QRY _make_q_guts
867 Title : _make_q_guts (Internal class method)
868 Usage : _make_q_guts($ptree, $q_expr, $qarry, $anarry)
869 Function: traverses the parse tree returned from QRY::_parse_q, checking
870 syntax and creating HIVQuery-compliant query structures
873 Args : $parse_tree (hashref), $query_expression (scalar string ref),
874 $query_array (array ref : stack for returning query structures),
875 $annotation_array (array ref : stack for returning annotation
881 my ($ptree, $q_expr, $qarry, $anarry) = @_;
884 foreach (@
{$ptree->{cont
}}) {
894 for my $dl ($_->{delim
}) {
895 ($dl =~ m{\(}) && do {
896 if (grep /^HASH/, @
{$_->{cont
}}) {
897 $$q_expr .= "&" unless !$$q_expr || !length($$q_expr) || (substr($$q_expr, -1, 1) =~ /[&|(]/);
899 _make_q_guts
($_,$q_expr,$qarry,$anarry);
904 my $c = join(' ',@
{$_->{cont
}});
906 Bio
::Root
::Root
->throw("query syntax error: unmatched ['\"]") if (@c = ($c =~ /(['"])/g)) % 2;
907 @c = split(/\s*(['"])\s*/, $c);
911 $c = join('', ($c, shift @c, shift @c));
912 $c =~ s/\s+/+/g; ###! _ to +
916 push @words, split(/\s+/,$c);
922 ($dl =~ m{\[}) && do {
923 Bio
::Root
::Root
->throw("syntax error: empty field descriptor") unless @
{$_->{cont
}};
924 Bio
::Root
::Root
->throw("syntax error: more than one field descriptor in square brackets") unless @
{$_->{cont
}} == 1;
926 push @
{$qarry}, new QRY
( new R
( new Q
( $_->{cont
}->[0], @words)));
927 # add default operation if nec
928 $$q_expr .= "&" unless !$$q_expr || !length($$q_expr) || (substr($$q_expr, -1, 1) =~ /[&|(]/);
929 $$q_expr .= "\$q[".$#$qarry."]";
933 ($dl =~ m{\{}) && do {
934 foreach my $an (@
{$_->{cont
}}) {
935 ($an =~ /^HASH/) && do {
936 if ($an->{delim
} eq '[') {
937 push @
$anarry, @
{$an->{cont
}};
940 Bio
::Root
::Root
->throw("query syntax error: only field descriptors (with or without square brackets) allowed in annotation spec");
957 do { # else, bareword
959 $words[-1] .= "+$_"; ####! _ to +
964 m/['"]/ && ($o = !$o);
967 Bio
::Root
::Root
->throw("query syntax error: no search fields specified")
968 unless $$q_expr =~ /q\[[0-9]+\]/;
971 throw Bio
::Root
::Root
(-class=>'Bio::QueryStringSyntax::Exception',
980 Usage : QRY::_parse_q($query_string)
981 Function: perform first pass parse of a query string with some syntax
982 checking, return a parse tree suitable for QRY::_make_q
983 Example : QRY::_parse_q(" to[be] OR (not to)[be] ");
989 # parse qry string into a branching tree structure
990 # each branch tagged by the opening delimiter ( key 'delim' )
991 # content (tokens and subbranch hashes) placed in l2r order in
996 my $illegal = qr/[^a-zA-Z0-9-_<>=,\.\(\[\{\}\]\)\s'"]/;
997 my $pdlm = qr/[\{\[\(\)\]\}]/;
998 my %md = ('('=>')', '['=>']','{'=>'}');
999 my @tok = grep !/^\s*$/, split /($pdlm)/, $qstr;
1000 return {} unless @tok;
1006 Bio
::Root
::Root
->throw("query syntax error: illegal character") if $qstr =~ /$illegal/;
1008 $ptree = $p = {'delim'=>'*'};
1014 my $new = {'delim'=>$_};
1015 $p->{cont
} = [] unless $p->{cont
};
1016 push @
{$p->{cont
}}, $new;
1023 my $d = pop @dstack;
1024 if ($md{$d} eq $_) {
1026 Bio
::Root
::Root
->throw("query syntax error: unmatched \"$_\"") unless $p;
1029 Bio
::Root
::Root
->throw("query syntax error: saw \"$_\" before matching \"$md{$d}\"");
1034 $p->{cont
} = [] unless $p->{cont
};
1035 push @
{$p->{cont
}}, split(/\s+/);
1040 throw Bio
::Root
::Root
(-class=>'Bio::QueryStringSyntax::Exception',
1048 =head3 QRY CONSTRUCTOR
1050 =head4 QRY Constructor
1052 Title : QRY constructor
1053 Usage : $QRY = new QRY()
1057 Args : array of R objects, optional
1065 $self->{requests
} = [];
1066 bless($self, $class);
1067 $self->put_requests(@args) if @args;
1071 ## QRY instance methods
1073 =head3 QRY INSTANCE METHODS
1078 Usage : $QRY->requests
1079 Function: get/set array of requests comprising this QRY object
1082 Args : array of class R objects
1088 $self->put_requests(@_) if @_;
1089 return @
{$self->{'requests'}};
1092 =head4 QRY put_requests
1094 Title : put_requests
1095 Usage : $QRY->put_request(@R)
1096 Function: add object of class R to $QRY
1099 Args : [an array of] of class R object[s]
1107 Bio
::Root
::Root
->throw('requires type R (request)') unless ref && $_->isa('R');
1108 push @
{$self->{requests
}}, $_;
1116 Usage : $QRY->isnull
1117 Function: test if QRY object is null
1119 Returns : 1 if null, 0 otherwise
1126 return ($self->requests) ?
0 : 1;
1132 Usage : print $QRY->A
1133 Function: get a string representation of QRY object
1135 Returns : string scalar
1142 return join( "\n", map {$_->A} $self->requests );
1149 Function: get number of class R objects contained by QRY object
1158 return scalar @
{$self->{'requests'}};
1164 Usage : $QRY2 = $QRY1->clone;
1165 Function: create and return a clone of the object
1167 Returns : object of class QRY
1175 my $ret = QRY
->new();
1176 foreach ($self->requests) {
1177 $ret->put_requests($_->clone);
1182 ## QRY class methods
1184 =head3 QRY CLASS METHODS
1189 Usage : $QRY3 = QRY::Or($QRY1, $QRY2)
1190 Function: logical OR for QRY objects
1192 Returns : a QRY object
1193 Args : two class QRY objects
1199 my ($q, $r, $rev_f) = @_;
1200 Bio
::Root
::Root
->throw('requires type QRY') unless ref($q) && $q->isa('QRY');
1201 Bio
::Root
::Root
->throw('requires type QRY') unless ref($r) && $r->isa('QRY');
1205 elsif ($r->isnull) {
1208 do {my $qq = $q; $q=$r; $r=$qq} if ($q->len > $r->len);
1209 my @rq_r = $r->requests;
1210 my @rq_q = $q->requests;
1211 my (@cand_rq, @ret_rq);
1212 # search for simplifications
1217 while (my $rq = pop @now) {
1218 my @result = R
::Or
($rq, $_);
1220 push @cand_rq, $result[0]->clone;
1228 push @cand_rq, $_->clone unless ($found);
1229 # @now becomes unexamined @rq_q's plus failed @rq_q's
1230 @now = (@now, @nxt);
1232 push @cand_rq, map {$_->clone} @now; # add all failed @rq_q's
1233 # squeeze out redundant requests
1234 while (my $rq = pop @cand_rq) {
1235 push @ret_rq, $rq unless @cand_rq && grep {R
::Eq
($rq, $_)} @cand_rq;
1237 return new QRY
( @ret_rq );
1243 Usage : $QRY3 = QRY::And($QRY1, $QRY2)
1244 Function: logical AND for QRY objects
1246 Returns : a QRY object
1247 Args : two class QRY objects
1252 my ($q, $r, $rev_f) = @_;
1253 Bio
::Root
::Root
->throw('requires type QRY') unless ref($q) && $q->isa('QRY');
1254 Bio
::Root
::Root
->throw('requires type QRY') unless ref($r) && $r->isa('QRY');
1255 return ($QRY::NULL
) if ($q->isnull || $r->isnull);
1256 my (@cand_rq, @ret_rq);
1257 foreach my $rq_r ($r->requests) {
1258 foreach my $rq_q ($q->requests) {
1259 my ($rq) = R
::And
($rq_r, $rq_q);
1260 push @cand_rq, $rq unless $rq->isnull;
1263 return $QRY::NULL
unless @cand_rq;
1264 # squeeze out redundant requests
1265 while (my $rq = pop @cand_rq) {
1266 push @ret_rq, $rq unless @cand_rq && grep {R
::Eq
($rq, $_)} @cand_rq;
1268 return new QRY
( @ret_rq );
1274 Usage : QRY::Bool($QRY1)
1275 Function: allows symbolic testing of QRY object when bool overloaded
1276 Example : do {stuff} if $QRY1 *same as* do {stuff} if !$QRY1->isnull
1278 Args : a class QRY object
1284 Bio
::Root
::Root
->throw('requires type QRY') unless ref($q) && $q->isa('QRY');
1285 return $q->isnull ?
0 : 1;
1291 Usage : QRY::Eq($QRY1, $QRY2)
1292 Function: test if R objects in two QRY objects are the same
1293 (irrespective of order)
1295 Returns : 1 if equal, 0 otherwise
1296 Args : two class QRY objects
1301 my ($q, $r, $rev_f) = @_;
1302 Bio
::Root
::Root
->throw('requires type QRY') unless ref($q) && $q->isa('QRY');
1303 Bio
::Root
::Root
->throw('requires type QRY') unless ref($r) && $r->isa('QRY');
1304 return 0 unless $q->len == $r->len;
1305 foreach my $rq_q ($q->requests) {
1307 foreach my $rq_r ($r->requests) {
1308 if (R
::Eq
($rq_q,$rq_r)) {
1313 return 0 unless $found;
1320 =head2 Class R - request objects for QRY algebra
1324 $R = new R( $q1, $q2 );
1326 $R->del_atoms('coreceptor', 'phenotype');
1328 $R1 = new R( new Q('subtype', 'B') );
1329 $R2 = new R( new Q('subtype', 'B C'),
1330 new Q('country', 'US') );
1331 R::Eq( (R::And($R1, $R2))[0],
1332 new R( new Q('subtype', 'B' ),
1333 new Q('country', 'US') )); # returns 1
1334 QRY::Eq( new QRY(R::Or($R1, $R2)), new QRY($R1, $R2) ); # returns 1
1335 R::In( (R::And($R1, $R2))[0], $R1 ); # returns 1
1337 =head3 R DESCRIPTION
1339 Class R objects contain a list of atomic queries (class Q
1340 objects). Each class R object represents a single HTTP request to the
1341 LANL DB. When converted to a DB query, the class Q objects contained
1342 by an R object are effectively C<AND>ed.
1346 package # hide from PAUSE
1349 $R::NULL
= R
->new();
1354 =head3 R CONSTRUCTOR
1356 =head4 R constructor
1358 Title : R constructor
1359 Usage : $R = new R()
1360 Function: create a new R (request) object
1362 Returns : class R (request) object
1363 Args : optional, array of class Q objects
1371 $self->{atoms
} = {};
1372 bless($self, $class);
1373 $self->put_atoms(@args) if @args;
1377 ## R instance methods
1379 =head3 R INSTANCE METHODS
1385 Function: get number of class Q objects contained in R object
1394 return scalar @
{[keys %{$self->{'atoms'}}]};
1400 Usage : $R->atoms( [optional $field])
1401 Function: get array of class Q (atomic query) objects in class R object
1402 Example : $R->atoms(); $R->atoms('coreceptor')
1403 Returns : array of class Q objects (all Qs or those corresponding to $field
1405 Args : optional, scalar string
1411 # returns an array of atoms
1412 # no arg: all atoms;
1413 # args: atoms with specified fields
1415 my @flds = (@_ ?
@_ : keys %{$self->{'atoms'}});
1416 return wantarray ?
map { $self->{'atoms'}->{$_} } @flds : $self->{'atoms'}->{$flds[0]};
1423 Function: get array of fields of all Q objects contained in $R
1425 Returns : array of scalars
1432 return keys %{$self->{'atoms'}};
1438 Usage : $R->put_atoms( @q )
1439 Function: AND an atomic query (class Q object) to the class R object's list
1442 Args : an [array of] class Q object[s]
1447 # AND this atom to the request
1452 Bio
::Root
::Root
->throw('requires type Q (atom)') unless ref && $_->isa('Q');
1453 if ($self->atoms($_->fld)) {
1454 my $a = Q
::qand
( $self->atoms($_->fld), $_ );
1456 delete $self->{'atoms'}->{$_->fld};
1459 $self->{atoms
}->{$_->fld} = $a->clone;
1463 $self->{atoms
}->{$_->fld} = $_->clone;
1472 Usage : $R->del_atoms( @qfields )
1473 Function: removes class Q objects from R object's list according to the
1474 field names given in arguments
1476 Returns : the class Q objects deleted
1477 Args : scalar array of field names
1482 # remove atoms by field from request
1486 return () unless @args;
1489 push @ret, delete $self->{'atoms'}->{$_};
1498 Function: test if class R object is null
1500 Returns : 1 if null, 0 otherwise
1507 return ($self->len) ?
0 : 1;
1514 Function: get a string representation of class R object
1516 Returns : string scalar
1523 my @a = sort {$a->fld cmp $b->fld} $self->atoms;
1524 return join(" ", map {$_->A} @a);
1530 Usage : $R2 = $R1->clone;
1531 Function: create and return a clone of the object
1533 Returns : object of class R
1542 foreach ($self->atoms) {
1543 $ret->put_atoms($_->clone);
1550 =head3 R CLASS METHODS
1555 Usage : R::In($R1, $R2)
1556 Function: tests whether the query represented by $R1 would return a subset
1557 of items returned by the query represented by $R2
1558 Example : print "R2 gets those and more" if R::In($R1, $R2);
1559 Returns : 1 if R1 is subset of R2, 0 otherwise
1560 Args : two class R objects
1567 Bio
::Root
::Root
->throw('requires type R (request)') unless ref($s) && $s->isa('R');
1568 Bio
::Root
::Root
->throw('requires type R (request)') unless ref($t) && $t->isa('R');
1569 return 1 if ($s->isnull);
1571 my @cf = grep {defined} map {my $f=$_; grep /^$f$/,$s->fields} $t->fields;
1572 return 0 unless @cf==$t->len;
1574 my @sd = split(/\s+/, $s->atoms($_)->dta);
1575 my @td = split(/\s+/, $t->atoms($_)->dta);
1576 my @cd = grep {defined} map {my $d=$_; grep /^$d$/, @td} @sd;
1577 return 0 unless @cd==@sd;
1585 Usage : @Rresult = R::And($R1, $R2)
1586 Function: logical AND for R objects
1588 Returns : an array containing class R objects
1589 Args : two class R objects
1596 Bio
::Root
::Root
->throw('requires type R (request)') unless ref($s) && $s->isa('R');
1597 Bio
::Root
::Root
->throw('requires type R (request)') unless ref($t) && $t->isa('R');
1598 return ($R::NULL
) if ($s->isnull || $t->isnull);
1600 do { my $ss = $s; $s = $t; $t = $ss } if ( $s->len > $t->len );
1601 # $t has at least as many fields defined than $s ($t is more restrictive)
1604 my @cf = grep {defined} map {my $sf = $_; grep /$sf/, $t->fields } $s->fields;
1611 # And the atoms with identical fields
1614 my ($a) = Q
::qand
($s->atoms($_), $t->atoms($_));
1619 $ret->put_atoms($a);
1622 # put the private atoms
1623 $ret->put_atoms($u->atoms, $v->atoms);
1631 Usage : @Rresult = R::Or($R1, $R2)
1632 Function: logical OR for R objects
1634 Returns : an array containing class R objects
1635 Args : two class R objects
1642 Bio
::Root
::Root
->throw('requires type R (request)') unless ref($s) && $s->isa('R');
1643 Bio
::Root
::Root
->throw('requires type R (request)') unless ref($t) && $t->isa('R');
1647 elsif ($t->isnull) {
1650 return $s->clone if (R
::In
($t, $s));
1651 return $t->clone if (R
::In
($s, $t));
1654 do { my $ss = $s; $s = $t; $t = $ss } if ( $s->len > $t->len );
1656 my @cf = grep {defined} map {my $sf = $_; grep /$sf/, $t->fields } $s->fields;
1658 if ($t->len == @cf) {
1659 # all atoms equal within fields but one? If yes, simplify...
1660 my @df = grep {!Q
::qeq
($s->atoms($_), $t->atoms($_))} @cf;
1662 my ($a) = Q
::qor
($s->atoms($df[0]), $t->atoms($df[0]));
1663 my $ret = $s->clone;
1664 $ret->del_atoms($df[0]);
1665 $ret->put_atoms($a);
1670 # neither request contains the other, and the requests cannot be
1671 # simplified; reflect back (clones of) the input...
1672 return ($s->clone, $t->clone);
1679 Usage : R::Eq($R1, $R2)
1680 Function: test if class Q objects in two R objects are the same
1681 (irrespective of order)
1683 Returns : 1 if equal, 0 otherwise
1684 Args : two class R objects
1691 Bio
::Root
::Root
->throw('requires type R (request)') unless ref($s) && $s->isa('R');
1692 Bio
::Root
::Root
->throw('requires type R (request)') unless ref($t) && $t->isa('R');
1693 my @sf = $s->fields;
1694 my @tf = $t->fields;
1695 return 0 unless @sf==@tf;
1696 my @cf = grep {defined} map {my $f=$_; grep /^$f$/,@sf} @tf;
1697 return 0 unless @cf==@tf;
1699 return 0 unless Q
::qeq
($s->atoms($_), $t->atoms($_));
1705 =head2 Class Q - atomic query objects for QRY algebra
1709 $q = new Q('coreceptor', 'CXCR4 CCR5');
1710 $u = new Q('coreceptor', 'CXCR4');
1711 $q->fld; # returns 'coreceptor'
1712 $q->dta; # returns 'CXCR4 CCR5'
1713 print $q->A; # prints '(CXCR4 CCR5)[coreceptor]
1714 Q::qeq($q, $u); # returns 0
1715 Q::qeq( Q::qor($q, $q), $q ); # returns 1
1716 Q::qin($u, $q) # returns 1
1717 Q::qeq(Q::qand($u, $q), $u ); # returns 1
1719 =head3 Q DESCRIPTION
1721 Class Q objects represent atomic queries, that can be described by a
1722 single LANL cgi parameter=value pair. Class R objects (requests) are
1723 built from class Qs. The logical operations at the higher levels
1724 (C<QRY, R>) ultimately depend on the lower level operations on Qs:
1725 C<qeq, qin, qand, qor>.
1729 package # hide from PAUSE
1732 $Q::NULL
= Q
->new();
1736 =head3 Q CONSTRUCTOR
1738 =head4 Q constructor
1740 Title : Q constructor
1741 Usage : $q = new Q($field, $data)
1742 Function: create a new Q (atomic query) object
1744 Returns : class Q object
1745 Args : optional $field, $data strings
1751 my ($class,@args) = @_;
1753 foreach (@args) { s/^\s+//; s/\s+$//; }
1754 my ($fld, @dta) = @args;
1756 $self->{dta
}=join(" ", @dta);
1757 bless($self, $class);
1761 ## Q instance methods
1763 =head3 Q INSTANCE METHODS
1769 Function: test if class Q object is null
1771 Returns : 1 if null, 0 otherwise
1778 Bio
::Root
::Root
->throw("requires type Q (atom)") unless ref($self) && $self->isa('Q');
1779 return 1 unless (($self->fld && length($self->fld)) || ($self->dta && length($self->dta)));
1786 Usage : $q->fld($field)
1787 Function: get/set fld (field name) property
1796 Bio
::Root
::Root
->throw("requires type Q (atom)") unless ref($self) && $self->isa('Q');
1801 return $self->{fld
}=$f;
1803 return $self->{fld
};
1810 Usage : $q->dta($data)
1811 Function: get/set dta (whsp-separated data string) property
1820 Bio
::Root
::Root
->throw("requires type Q (atom)") unless ref($self) && $self->isa('Q');
1821 my $d = join(" ", @_);
1825 return $self->{dta
} = $d;
1827 return $self->{dta
};
1834 Function: get a string representation of class Q object
1836 Returns : string scalar
1843 Bio
::Root
::Root
->throw("requires type Q (atom)") unless ref($self) && $self->isa('Q');
1844 my @a = split(/\s+/, $self->dta);
1846 return "(".join(' ', sort {$a cmp $b} @a).")[".$self->fld."]";
1852 Usage : $q2 = $q1->clone;
1853 Function: create and return a clone of the object
1855 Returns : object of class Q
1862 Bio
::Root
::Root
->throw("requires type Q (atom)") unless ref($self) && $self->isa('Q');
1863 my $ret = Q
->new($self->fld, $self->dta);
1869 =head3 Q CLASS METHODS
1874 Usage : Q::qin($q1, $q2)
1875 Function: tests whether the query represented by $q1 would return a subset
1876 of items returned by the query represented by $q2
1877 Example : print "q2 gets those and more" if Q::qin($q1, $q2);
1878 Returns : 1 if q1 is subset of q2, 0 otherwise
1879 Args : two class Q objects
1885 Bio
::Root
::Root
->throw('requires type Q (atom)') unless (ref $a) && $a->isa('Q') && (ref $b) && $b->isa('Q');
1886 return 0 unless $a->fld eq $b->fld;
1887 return Q
::qeq
( $b, Q
::qor
($a, $b) );
1893 Usage : Q::qeq($q1, $q2)
1894 Function: test if fld and dta properties in two class Q objects are the same
1895 (irrespective of order)
1897 Returns : 1 if equal, 0 otherwise
1898 Args : two class Q objects
1905 Bio
::Root
::Root
->throw('requires type Q (atom)') unless (ref $a) && $a->isa('Q') && (ref $b) && $b->isa('Q');
1906 return 0 unless $a->fld eq $b->fld;
1907 my @ad = unique
(split(/\s+/,$a->dta));
1908 my @bd = unique
(split(/\s+/,$b->dta));
1909 return 0 unless @ad==@bd;
1910 my @cd = grep {defined} map {my $f = $_; grep /^$f$/, @ad} @bd;
1917 Usage : @qresult = Q::qor($q1, $q2)
1918 Function: logical OR for Q objects
1920 Returns : an array of class Q objects
1921 Args : two class Q objects
1929 Bio
::Root
::Root
->throw("requires type Q (atom)") unless ref && $_->isa('Q');
1933 @a = grep {!$_->isnull} @a;
1934 return ($Q::NULL
) unless @a > 0;
1935 # list of unique flds
1936 @f = unique
(map {$_->fld} @a);
1937 foreach my $f (@f) {
1938 my @fobjs = grep {$_->fld eq $f} @a;
1939 my @d = unique
(map {split(/\s/, $_->dta)} @fobjs );
1940 my $r = Q
->new($f, @d);
1949 Usage : @qresult = Q::And($q1, $q2)
1950 Function: logical AND for R objects
1952 Returns : an array of class Q objects
1953 Args : two class Q objects
1960 Bio
::Root
::Root
->throw('requires type Q (atom)') unless (ref $a) && $a->isa('Q') && (ref $b) && $b->isa('Q');
1962 if (ref $a eq 'ARRAY') {
1963 foreach my $ea (@
$a) {
1964 push @ret, qand
( $ea, $b );
1966 return qor
(@ret); # simplify
1968 elsif (ref $b eq 'ARRAY') {
1969 foreach my $eb (@
$b) {
1970 push @ret, qand
( $a, $eb);
1973 return qor
(@ret); # simplify
1976 return ($Q::NULL
) if ($a->isnull || $b->isnull);
1977 if ($a->fld eq $b->fld) {
1978 # find intersection of data
1980 @ad = split(/\s+/, $a->dta);
1981 @ad{@ad} = (1) x
@ad;
1982 @bd = split(/\s+/, $b->dta);
1986 my $r = Q
->new($a->fld,
1988 map {$ad{$_} == 2 ?
$_ : undef} keys %ad);
1989 return (length($r->dta) > 0) ?
($r) : ($Q::NULL
);
2002 Usage : @ua = unique(@a)
2003 Function: return contents of @a with duplicates removed
2019 =head2 Additional tools for Bio::AnnotationCollectionI
2021 =head3 Bio::AnnotationCollectionI SYNOPSIS (additional methods)
2023 $seq->annotation->put_value('patient_id', 1401)
2024 $seq->annotation->get_value('patient_ids') # returns 1401
2025 $seq->annotation->put_value('patient_group', 'MassGenH')
2026 $seq->annotation->put_value(['clinical', 'cd4count'], 503);
2027 $seq->annotation->put_value(['clinical', 'virus_load'], 150805);
2028 foreach ( qw( cd4count virus_load ) ) {
2029 $blood_readings{$_} = $seq->annonation->get_value(['clinical', $_]);
2032 =head3 Bio::AnnotationCollectionI DESCRIPTION (additional methods)
2034 C<get_value()> and C<put_value> allow easy creation of and access to an
2035 annotation collection tree with nodes of L<Bio::Annotation::SimpleValue>. These
2036 methods obiviate direct accession of the SimpleValue objects.
2040 package Bio
::AnnotationCollectionI
;
2042 use Bio
::Annotation
::SimpleValue
;
2047 Usage : $ac->get_value($tagname) -or-
2048 $ac->get_value( $tag_level1, $tag_level2,... )
2049 Function: access the annotation value associated with the given tags
2052 Args : an array of tagnames that descend into the annotation tree
2061 return "" unless @_;
2062 while ($_ = shift @args) {
2063 @h = $self->get_Annotations($_);
2064 if (ref($h[0]->{value
})) {
2065 $self = $h[0]->{value
}; # must be another Bio::AnnotationCollectionI
2071 return $h[0] && $h[0]->{value
} ; # now the last value.
2077 Usage : $ac->put_value($tagname, $value) -or-
2078 $ac->put_value([$tag_level1, $tag_level2, ...], $value) -or-
2079 $ac->put_value( [$tag_level1, $tag_level2, ...] )
2080 Function: create a node in an annotation tree, and assign a scalar value to it
2081 if a value is specified
2083 Returns : scalar or a Bio::AnnotationCollection object
2084 Args : $tagname, $value scalars (can be specified as -KEYS=>$tagname,
2085 -VALUE=>$value) -or-
2086 \@tagnames, $value (or as -KEYS=>\@tagnames, -VALUE=>$value )
2087 Note : If intervening nodes do not exist, put_value creates them, replacing
2088 existing nodes. So if $ac->put_value('x', 10) was done, then later,
2089 $ac->put_value(['x', 'y'], 20), the original value of 'x' is trashed,
2090 and $ac->get_value('x') will now return the annotation collection
2099 my ($keys, $value) = $self->_rearrange([qw( KEYS VALUE )], @args);
2100 my (@keys, $lastkey);
2101 # $value ||= new Bio::Annotation::Collection;
2102 @keys = (ref($keys) eq 'ARRAY') ? @
$keys : ($keys);
2103 $lastkey = pop @keys;
2105 my $a = $self->get_value($_);
2106 if (ref($a) && $a->isa('Bio::Annotation::Collection')) {
2110 # replace an old value
2111 $self->remove_Annotations($_) if $a;
2112 my $ac = Bio
::Annotation
::Collection
->new();
2113 $self->add_Annotation(Bio
::Annotation
::SimpleValue
->new(
2121 if ($self->get_value($lastkey)) {
2122 # replace existing value
2123 ($self->get_Annotations($lastkey))[0]->{value
} = $value;
2126 $self->add_Annotation(Bio
::Annotation
::SimpleValue
->new(
2137 Usage : $ac->get_keys($tagname_level_1, $tagname_level_2,...)
2138 Function: Get an array of tagnames underneath the named tag nodes
2139 Example : # prints the values of the members of Category 1...
2140 print map { $ac->get_value($_) } $ac->get_keys('Category 1') ;
2141 Returns : array of tagnames or empty list if the arguments represent a leaf
2142 Args : [array of] tagname[s]
2150 my $a = $self->get_value($_);
2151 if (ref($a) && $a->isa('Bio::Annotation::Collection')) {
2158 return $self->get_all_annotation_keys();