1 #$Id: CommonSubs.pm,v 1.72 2005/11/21 12:19:35 pieter Exp $
5 MOBY::CommonSubs.pm - a set of exportable subroutines that are
6 useful in clients and services to deal with the input/output from
11 CommonSubs are used to do various manipulations of MOBY Messages. It is useful
12 both Client and Service side to construct and parse MOBY Messages, and ensure
13 that the message structure is valid as per the API.
15 It DOES NOT connect to MOBY Central for any of its functions, though it does
16 contact the ontology server, so it will require a network connection.
20 =head2 Client Side Paradigm
24 =head2 Service-Side Paradigm
26 The following is a generalized architecture for *all*
27 BioMOBY services showing how to parse incoming messages
28 using the subroutines provided in CommonSubs
31 my ($caller, $data) = @_;
32 my $MOBY_RESPONSE; # holds the response raw XML
34 # genericServiceInputParser
35 # unpacks incoming message into an array of arrarefs.
36 # Each element of the array is a queryInput block, or a mobyData block
37 # the arrayref has the following structure:
38 # [SIMPLE, $queryID, $simple]
39 # the first element is an exported constant SIMPLE, COLLECTION, SECONDARY
40 # the second element is the queryID (required for enumerating the responses)
41 # the third element is the XML::LibXML for the Simple, Collection, or Parameter block
42 my (@inputs)= genericServiceInputParser($data);
43 # or fail properly with an empty response
44 return SOAP::Data->type('base64' => responseHeader("my.authURI.com") . responseFooter()) unless (scalar(@inputs));
46 # you only need to do this if you are intending to be namespace aware
47 # some services might not care what namespace the data is in, so long
49 my @validNS_LSID = validateNamespaces("NCBI_gi"); # returns LSID's for each human-readable
52 my ($articleType, $qID, $input) = @{$_};
53 unless (($articleType == SIMPLE) && ($input)){
54 # in this example, we are only accepting SIMPLE types as input
55 # so write back an empty response block and move on to the next
56 $MOBY_RESPONSE .= simpleResponse("", "", $qID) ;
59 # now take the namespace and ID from our input article
60 # (see pod docs for other possibilities)
61 my $namespace = getSimpleArticleNamespaceURI($input); # get namespace
62 my ($identifier) = getSimpleArticleIDs($input); # get ID (note array output! see pod)
64 # here is where you do whatever manipulation you need to do
65 # for your particular service.
66 # you will be building an XML document into $MOBY_RESPONSE
69 return SOAP::Data->type('base64' => (responseHeader("illuminae.com") . $MOBY_RESPONSE . responseFooter));
76 A COMPLETE EXAMPLE OF AN EASY MOBY SERVICE
78 This is a service that:
80 CONSUMES: base Object in the GO namespace
82 PRODUCES: GO_Term (in the GO namespace)
85 # this subroutine is called from your dispatch_with line
90 my ($caller, $message) = @_;
92 my (@inputs)= genericServiceInputParser($message); # ([SIMPLE, $queryID, $simple],...)
93 return SOAP::Data->type('base64' => responseHeader('my.authURI.com') . responseFooter()) unless (scalar(@inputs));
95 my @validNS = validateNamespaces("GO"); # ONLY do this if you are intending to be namespace aware!
97 my $dbh = _connectToGoDatabase();
98 return SOAP::Data->type('base64' => responseHeader('my.authURI.com') . responseFooter()) unless $dbh;
99 my $sth = $dbh->prepare(q{
100 select name, term_definition
101 from term, term_definition
102 where term.id = term_definition.term_id
106 my ($articleType, $ID, $input) = @{$_};
107 unless ($articleType == SIMPLE){
108 $MOBY_RESPONSE .= simpleResponse("", "", $ID);
111 my $ns = getSimpleArticleNamespaceURI($input);
112 (($MOBY_RESPONSE .= simpleResponse("", "", $ID)) && (next))
113 unless validateThisNamespace($ns, @validNS); # only do this if you are truly validating namespaces
114 my ($accession) = defined(getSimpleArticleIDs($ns, [$input]))?getSimpleArticleIDs($ns,[$input]):undef;
115 unless (defined($accession)){
116 $MOBY_RESPONSE .= simpleResponse("", "", $ID);
119 unless ($accession =~/^GO:/){
120 $accession = "GO:$accession"; # we still haven't decided on whether id's should include the prefix...
122 $sth->execute($accession);
123 my ($term, $def) = $sth->fetchrow_array;
125 $MOBY_RESPONSE .= simpleResponse("
126 <moby:GO_Term namespace='GO' id='$accession'>
127 <moby:String namespace='' id='' articleName='Term'>$term</moby:String>
128 <moby:String namespace='' id='' articleName='Definition'>$def</moby:String>
129 </moby:GO_Term>", "GO_Term_From_ID", $ID)
131 $MOBY_RESPONSE .= simpleResponse("", "", $ID)
136 return SOAP::Data->type('base64' => (responseHeader("my.authURI.com") . $MOBY_RESPONSE . responseFooter));
142 Mark Wilkinson (markw at illuminae dot com)
144 BioMOBY Project: http://www.biomoby.org
150 package MOBY
::CommonSubs
;
153 use MOBY
::CrossReference
;
154 use MOBY
::Client
::OntologyServer
;
157 use MOBY
::Client
::SimpleArticle
;
158 use MOBY
::Client
::CollectionArticle
;
159 use MOBY
::Client
::SecondaryArticle
;
160 use MOBY
::MobyXMLConstants
;
161 use constant COLLECTION
=> 1;
162 use constant SIMPLE
=> 2;
163 use constant SECONDARY
=> 3;
164 use constant PARAMETER
=> 3; # be friendly in case they use this instead
165 use constant BE_NICE
=> 1;
166 use constant BE_STRICT
=> 0;
167 our @ISA = qw(Exporter);
168 our @EXPORT = qw(COLLECTION SIMPLE SECONDARY PARAMETER BE_NICE BE_STRICT);
173 getSimpleArticleNamespaceURI
179 getNodeContentWithArticle
182 validateThisNamespace
186 extractResponseArticles
189 genericServiceInputParser
190 genericServiceInputParserAsObject
191 complexServiceInputParser
192 whichDeepestParentObject
210 our @EXPORT_OK = (@
{$EXPORT_TAGS{'all'}});
212 =head2 genericServiceInputParser
214 B<function:> For the MOST SIMPLE SERVICES that take single Simple or
215 Collection inputs and no Secondaries/Parameters this routine takes the
216 MOBY message and breaks the objects out of it in a useful way
219 my @inputs = genericServiceInputParser($MOBY_mssage));
221 B<args:> C<$message> - this is the SOAP payload; i.e. the XML document containing the MOBY message
223 B<returns:> C<@inputs> - the structure of @inputs is a list of listrefs.
225 Each listref has three components:
230 COLLECTION|SIMPLE (i.e. constants 1, 2)
236 $data - the data takes several forms
241 $article XML::LibXML node for Simples <mobyData...>...</mobyData>
244 \@article XML:LibXML nodes for Collections
250 For example, the input message:
252 <mobyData queryID = '1'>
254 <Object namespace=blah id=blah/>
257 <mobyData queryID = '2'>
259 <Object namespace=blah id=blah/>
263 (note that SIMPLE, COLLECTION, and SECONDARY are exported constants from this module)
265 @inputs = ([SIMPLE, 1, $DOM], [SIMPLE, 2, $DOM]) # the <Simple> block
267 For example, the input message:
269 <mobyData queryID = '1'>
272 <Object namespace=blah id=blah/>
275 <Object namespace=blah id=blah/>
282 @inputs = ( [COLLECTION, 1, [$DOM, $DOM]] ) # the <Simple> block
286 sub genericServiceInputParser
{
287 my ( $message ) = @_; # get the incoming MOBY query XML
288 my @inputs; # set empty response
289 my @queries = getInputs
( $message ); # returns XML::LibXML nodes <mobyData>...</mobyData>
290 foreach my $query ( @queries ) {
291 my $queryID = getInputID
( $query ); # get the queryID attribute of the mobyData
293 getArticles
( $query )
294 ; # get the Simple/Collection/Secondary articles making up this query <Simple>...</Simple> or <Collection>...</Collection> or <Parameter>...</Parameter>
295 foreach my $input ( @input_articles ) { # input is a listref
296 my ( $articleName, $article ) = @
{$input}; # get the named article
297 if ( isCollectionArticle
( $article ) ) {
298 my @simples = getCollectedSimples
( $article );
299 push @inputs, [ COLLECTION
, $queryID, \
@simples ];
300 } elsif ( isSimpleArticle
( $article ) ) {
301 push @inputs, [ SIMPLE
, $queryID, $article ];
302 } elsif ( isSecondaryArticle
( $article ) )
303 { # should never happen in a generic service parser!
304 push @inputs, [ SECONDARY
, $queryID, $article ];
311 =head2 serviceInputParser
315 B<function:> to take a MOBY message and break the objects out of it.
316 This is identical to the genericServiceInputParser method above,
317 except that it returns the data as Objects rather than XML::LibXML
318 nodes. This is an improvement!
320 B<usage:> C<my @inputs = serviceInputParser($MOBY_mssage));>
322 B<args:> C<$message> - this is the SOAP payload; i.e. the XML document containing the MOBY message
324 B<returns:> C<@inputs> - the structure of @inputs is a list of listrefs.
326 Each listref has three components:
328 1. COLLECTION|SIMPLE|SECONDARY (i.e. constants 1, 2, 3)
329 2. queryID (undef for Secondary parameters)
330 3. $data - either MOBY::Client::SimpleArticle, CollectionArticle, or SecondaryArticle
334 sub serviceInputParser
{
335 my ( $message ) = @_; # get the incoming MOBY query XML
336 my @inputs; # set empty response
337 my @queries = getInputs
( $message ); # returns XML::LibXML nodes <mobyData>...</mobyData>
339 # mark, this doesn't work for complex services. We need to allow more than one input per invocation
340 foreach my $query ( @queries ) {
341 my $queryID = getInputID
( $query ); # get the queryID attribute of the mobyData
342 # get the Simple/Collection articles making up this query
343 # <Simple>...</Simple> or <Collection>...</Collection>
344 # or <Parameter>...</Parameter
345 my @input_articles = getArticlesAsObjects
( $query );
346 foreach my $article ( @input_articles ) { # input is a listref
347 if ( $article->isCollection ) {
348 my @simples = getCollectedSimples
( $article->XML );
349 push @inputs, [ COLLECTION
, $queryID, \
@simples ];
350 } elsif ( $article->isSimple ) {
351 push @inputs, [ SIMPLE
, $queryID, $article ];
352 } elsif ( $article->isSecondary ) {
353 push @inputs, [ SECONDARY
, $queryID, $article ];
360 =head2 complexServiceInputParser
362 B<function:> For more complex services that have multiple articles for
363 each input and/or accept parameters, this routine will take a MOBY
364 message and extract the Simple/Collection/Parameter objects out of it
367 B<usage:> C<my $inputs = complexServiceInputParser($MOBY_mssage));>
369 B<args:> C<$message> - this is the SOAP payload; i.e. the XML document containing the MOBY message
371 B<returns:> C<$inputs> is a hashref with the following structure:
373 $inputs->{$queryID} = [ [TYPE, $DOM], [TYPE, $DOM], [TYPE, $DOM] ]
377 For example, the input message:
379 <mobyData queryID = '1'>
380 <Simple articleName='name1'>
381 <Object namespace=blah id=blah/>
383 <Parameter articleName='cutoff'>
388 will become: (note that SIMPLE, COLLECTION, and SECONDARY are exported constants from this module)
390 $inputs->{1} = [ [SIMPLE, $DOM_name1], # the <Simple> block
391 [SECONDARY, $DOM_cutoff] # $DOM_cutoff= <Parameter> block
394 Please see the XML::LibXML pod documentation for information about how to parse XML DOM objects.
398 With inputs that have collections these are presented as a listref of
399 Simple article DOM's. So for the following message:
402 <Collection articleName='name1'>
404 <Object namespace=blah id=blah/>
407 <Object namespace=blah id=blah/>
410 <Parameter articleName='cutoff'>
417 $inputs->{1} = [ [COLLECTION, [$DOM, $DOM] ], # $DOM is the <Simple> Block!
418 [SECONDARY, $DOM_cutoff] # $DOM_cutoff = <Parameter> Block
421 Please see the XML::LibXML pod documentation for information about how to parse XML DOM objects.
425 sub complexServiceInputParser
{
426 my ( $message ) = @_; # get the incoming MOBY query XML
427 my @inputs; # set empty response
428 my @queries = getInputs
( $message ); # returns XML::LibXML nodes <mobyData>...</mobyData>
429 my %input_parameters; # $input_parameters{$queryID} = [
430 foreach my $query ( @queries ) {
431 my $queryID = getInputID
( $query ); # get the queryID attribute of the mobyData
433 getArticles
( $query )
434 ; # get the Simple/Collection/Secondary articles making up this query <Simple>...</Simple> or <Collection>...</Collection> or <Parameter>...</Parameter>
435 foreach my $input ( @input_articles ) { # input is a listref
436 my ( $articleName, $article ) = @
{$input}; # get the named article
437 if ( isCollectionArticle
( $article ) ) {
438 my @simples = getCollectedSimples
( $article );
439 push @
{ $input_parameters{$queryID} },
440 [ COLLECTION
, \
@simples ];
441 } elsif ( isSimpleArticle
( $article ) ) {
442 push @
{ $input_parameters{$queryID} }, [ SIMPLE
, $article ];
443 } elsif ( isSecondaryArticle
( $article ) ) {
444 push @
{ $input_parameters{$queryID} }, [ SECONDARY
, $article ];
448 return \
%input_parameters;
453 B<function:> get the Simple/Collection/Parameter articles for a single mobyData
455 B<usage:> C<@articles = getArticles($XML)>
457 B<args:> raw XML or XML::LibXML of a queryInput, mobyData, or queryResponse block (e.g. from getInputs)
459 B<returns:> a list of listrefs; each listref is one component of the
460 queryInput or mobyData block a single block may consist of one or more
461 named or unnamed simple, collection, or parameter articles. The
462 listref structure is thus C<[name, $ARTICLE_DOM]>:
464 e.g.: @articles = ['name1', $SIMPLE_DOM]
466 generated from the following sample XML:
469 <Simple articleName='name1'>
470 <Object namespace=blah id=blah/>
474 or : @articles = ['name1', $COLL_DOM], ['paramname1', $PARAM_DOM]
476 generated from the following sample XML:
479 <Collection articleName='name1'>
481 <Object namespace=blah id=blah/>
484 <Object namespace=blah id=blah/>
487 <Parameter articleName='e value cutoff'>
488 <default>10</default>
496 $moby = _string_to_DOM
($moby);
498 unless ( ($moby->nodeType == ELEMENT_NODE
)
499 && ( $moby->nodeName =~ /^(moby:|)(queryInput|queryResponse|mobyData)$/ ) );
501 foreach my $child ( $moby->childNodes )
502 { # there may be more than one Simple/Collection per input; iterate over them
503 next unless ( ($child->nodeType == ELEMENT_NODE
) # ignore whitespace
504 && ( $child->nodeName =~ /^(moby:|)(Simple|Collection|Parameter)$/ ) );
505 my $articleName = _moby_getAttribute
($child, 'articleName' );
506 # push the named child DOM elements (which are <Simple> or <Collection>, <Parameter>)
507 push @articles, [ $articleName, $child ];
509 return @articles; # return them.
511 #################################################
512 ##################################
513 ##################################
514 # COMMON SUBROUTINES for Clients and Services
515 ##################################
516 ##################################
517 #################################################
519 =head2 getSimpleArticleIDs
521 B<function:> to get the IDs of simple articles that are in the given namespace
525 my @ids = getSimpleArticleIDs("NCBI_gi", \@SimpleArticles);
526 my @ids = getSimpleArticleIDs(\@SimpleArticles);
530 C<$Namespace> - (optional) a namespace stringfrom the MOBY namespace ontology, or undef if you don't care
532 C<\@Simples> - (required) a listref of Simple XML::LibXML nodes i.e. the XML::LibXML representing an XML structure like this:
535 <Object namespace="NCBI_gi" id="163483"/>
538 Note : If you provide a namespace, it will return *only* the ids that
539 are in the given namespace, but will return 'undef' for any articles
540 in the WRONG namespace so that you get an equivalent number of outputs
543 Note that if you call this with a single argument, this is assumed to
544 be C<\@Articles>, so you will get ALL id's regardless of namespace!
549 sub getSimpleArticleIDs
{
550 my ( $desired_namespace, $input_nodes ) = @_;
551 if ( $desired_namespace && !$input_nodes )
552 { # if called with ONE argument, then these are the input nodes!
553 $input_nodes = $desired_namespace;
554 $desired_namespace = undef;
556 $input_nodes = [$input_nodes]
557 unless ref( $input_nodes ) eq 'ARRAY'; # be flexible!
558 return undef unless scalar @
{$input_nodes};
559 my @input_nodes = @
{$input_nodes};
560 my $OS = MOBY
::Client
::OntologyServer
->new;
561 my ( $s, $m, $namespace_lsid );
562 if ( $desired_namespace ) {
563 ( $s, $m, $namespace_lsid ) =
564 $OS->namespaceExists( term
=> $desired_namespace ); # returns (success, message, lsid)
565 unless ( $s ) { # bail if not successful
566 # Printing to STDERR is not very helpful - we should probably return something that can be dealt iwth programatically....
567 die("MOBY::CommonSubs: the namespace '$desired_namespace' "
568 . "does not exist in the MOBY ontology, "
569 . "and does not have a valid LSID");
572 $desired_namespace = $namespace_lsid; # Replace namespace with fully-qualified LSID
575 foreach my $in ( @input_nodes ) {
577 #$in = "<Simple><Object namespace='' id=''/></Simple>"
578 next unless $in->nodeName =~ /^(moby:|)Simple$/; # only allow simples
579 my @simples = $in->childNodes;
580 foreach ( @simples ) { # $_ = <Object namespace='' id=''/>
581 next unless $_->nodeType == ELEMENT_NODE
;
582 if ( $desired_namespace ) {
583 my $ns = _moby_getAttributeNode
($_, 'namespace' ); # get the namespace DOM node
584 unless ( $ns ) { # if we don't get it at all, then move on to the next input
585 push @ids, undef; # but push an undef onto teh stack in order
588 $ns = $ns->getValue; # if we have a namespace, then get its value
589 ( $s, $m, $ns ) = $OS->namespaceExists( term
=> $ns );
590 # A bad namespace will return 'undef' which makes for a bad comparison (Perl warning).
591 # Better to check directly for success ($s), THEN check that namespace is the one we wanted.
592 unless ( $s && $ns eq $desired_namespace )
593 { # we are registering as working in a particular namespace, so check this
594 push @ids, undef; # and push undef onto the stack if it isn't
599 # Now do the same thing for ID's
600 my $id = _moby_getAttributeNode
($_, 'id' );
606 unless ( defined $id ) { # it has to have a hope in hell of retrieving something...
607 push @ids, undef; # otherwise push undef onto the stack if it isn't
616 =head2 getSimpleArticleNamespaceURI
618 B<function:> to get the namespace of a simple article
620 B<usage:> C<my $ns = getSimpleArticleNamespaceURI($SimpleArticle);>
622 B<args:> C<$Simple> - (required) a single XML::LibXML node
623 representing a Simple Article i.e. the XML::LibXML representing an XML
627 <Object namespace="NCBI_gi" id="163483"/>
633 sub getSimpleArticleNamespaceURI
{
635 # pass me a <SIMPLE> input node and I will give you the lsid of the namespace of that input object
636 my ( $input_node ) = @_;
637 return undef unless $input_node;
638 my $OS = MOBY
::Client
::OntologyServer
->new;
640 #$input_node = "<Simple><Object namespace='' id=''/></Simple>"
641 my @simples = $input_node->childNodes;
643 { # $_ = <Object namespace='' id=''/> # should be just one, so I will return at will from this routine
644 next unless $_->nodeType == ELEMENT_NODE
;
645 my $ns = _moby_getAttributeNode
($_, 'namespace' ); # get the namespace DOM node
646 return undef unless ( $ns ); # if we don't get it at all, then move on to the next input
647 my ( $s, $m, $lsid ) =
648 $OS->namespaceExists( term
=> $ns->getValue ); # if we have a namespace, then get its value
649 return undef unless $s;
655 # Convert string to DOM.
656 # If DOM passed in, just return it (i.e., this should be idempotent)
657 # By Frank Gibbons, Aug. 2005
658 # Utility subroutine, not for external use (no export), widely used in this package.
661 return $XML if ( ref($XML) =~ /^XML\:\:LibXML/ );
663 my $parser = XML
::LibXML
->new();
665 eval { $doc = $parser->parse_string( $XML ) };
666 die("CommonSubs couldn't parse XML '$XML' because\n\t$@") if $@
;
667 return $doc->getDocumentElement();
672 B<function:> get the mobyData block(s) as XML::LibXML nodes
674 B<usage:> C<@queryInputs = getInputArticles($XML)>
676 B<args:> the raw XML of a <MOBY> query, or an XML::LibXML document
678 B<returns:> a list of XML::LibXML::Node's, each is a queryInput or mobyData block.
680 B< Note:> Remember that these blocks are enumerated! This is what you
681 pass as the third argument to the simpleResponse or collectionResponse
682 subroutine to associate the numbered input to the numbered response
688 my $moby = _string_to_DOM
($XML);
690 foreach my $querytag qw( queryInput moby:queryInput mobyData moby:mobyData )
692 my $x = $moby->getElementsByTagName( $querytag ); # get the mobyData block
693 for ( 1 .. $x->size() ) { # there may be more than one mobyData per message
694 push @queries, $x->get_node( $_ );
697 return @queries; # return them in the order that they were discovered.
702 B<function:> get the value of the queryID element
704 B<usage:> C<@queryInputs = getInputID($XML)>
706 B<args:> the raw XML or XML::LibXML of a queryInput or mobyData block (e.g. from getInputs)
708 B<returns:> integer, or ''
710 B< Note:> Inputs and Responses are coordinately enumerated! The
711 integer you get here is what you pass as the third argument to the
712 simpleResponse or collectionResponse subroutine to associate the
713 numbered input to the numbered response
719 my $moby = _string_to_DOM
($XML);
720 return '' unless ( $moby->nodeName =~ /^(moby:|)queryInput|mobyData$/ );
721 my $qid = _moby_getAttribute
($moby, 'queryID' );
722 return defined( $qid ) ?
$qid : '';
725 =head2 getArticlesAsObjects
729 B<function:> get the Simple/Collection articles for a single mobyData
730 or queryResponse node, rethrning them as SimpleArticle,
731 SecondaryArticle, or ServiceInstance objects
733 B<usage:> C<@articles = getArticles($XML)>
735 B<args:> raw XML or XML::LibXML of a moby:mobyData block
741 sub getArticlesAsObjects
{
743 $moby = _string_to_DOM
($moby);
744 return undef unless $moby->nodeType == ELEMENT_NODE
;
746 unless ($moby->nodeName =~ /^(moby:|)(queryInput|queryResponse|mobyData)$/);
748 foreach my $child ( $moby->childNodes )
749 { # there may be more than one Simple/Collection per input; iterate over them
750 next unless $child->nodeType == ELEMENT_NODE
; # ignore whitespace
752 unless ( $child->nodeName =~ /^(moby:|)(Simple|Collection|Parameter)$/ );
754 if ( $child->nodeName =~ /^(moby:|)Simple$/ ) {
755 $object = MOBY
::Client
::SimpleArticle
->new( XML_DOM
=> $child );
756 } elsif ( $child->nodeName =~ /^(moby:|)Collection$/ ) {
757 $object = MOBY
::Client
::CollectionArticle
->new( XML_DOM
=> $child );
758 } elsif ( $child->nodeName =~ /^(moby:|)Parameter$/ ) {
759 $object = MOBY
::Client
::SecondaryArticle
->new( XML_DOM
=> $child );
762 push @articles, $object; # take the child elements, which are <Simple/> or <Collection/>
764 return @articles; # return them.
767 =head2 getCollectedSimples
769 B<function:> get the Simple articles collected in a moby:Collection block
771 B<usage:> C<@Simples = getCollectedSimples($XML)>
773 B<args:> raw XML or XML::LibXML of a moby:Collection block
775 B<returns:> a list of XML::LibXML nodes, each of which is a moby:Simple block
779 sub getCollectedSimples
{
781 $moby = _string_to_DOM
($moby);
782 return undef unless $moby->nodeType == ELEMENT_NODE
;
783 return undef unless ( $moby->nodeName =~ /^(moby\:|)Collection$/ );
785 foreach my $child ( $moby->childNodes )
786 { # there may be more than one Simple/Collection per input; iterate over them
787 next unless $child->nodeType == ELEMENT_NODE
; # ignore whitespace
788 next unless ( $child->nodeName =~ /^(moby\:|)Simple$/ );
789 push @articles, $child; # take the child elements, which are <Simple/> or <Collection/>
791 return @articles; # return them.
794 =head2 getInputArticles
797 B<function:> get the Simple/Collection articles for each input query, in order
799 B<usage:> C<@queries = getInputArticles($XML)>
801 B<args:> the raw XML of a moby:MOBY query
803 B<returns:> a list of listrefs, each listref is the input to a single
804 query. Remember that the input to a single query may be one or more
805 Simple and/or Collection articles. These are provided as XML::LibXML
808 i.e.: @queries = ([$SIMPLE_DOM_NODE], [$SIMPLE_DOM_NODE2])
809 or : @queries = ([$COLLECTION_DOM_NODE], [$COLLECTION_DOM_NODE2])
811 The former is generated from the following XML:
817 <Object namespace=blah id=blah/>
822 <Object namespace=blah id=blah/>
830 sub getInputArticles
{
832 $moby = _string_to_DOM
($moby);
834 foreach ( 'queryInput', 'moby:queryInput', 'mobyData', 'moby:mobyData' ) {
835 $x = $moby->getElementsByTagName( $_ ); # get the mobyData block
836 last if $x->get_node( 1 );
838 return undef unless $x->get_node( 1 ); # in case there was no match at all
840 for ( 1 .. $x->size() ) { # there may be more than one mobyData per message
842 foreach my $child ( $x->get_node( $_ )->childNodes )
843 { # there may be more than one Simple/Collection per input; iterate over them
844 next unless $child->nodeType == ELEMENT_NODE
; # ignore whitespace
845 push @this_query, $child; # take the child elements, which are <Simple/> or <Collection/>
847 push @queries, \
@this_query;
849 return @queries; # return them in the order that they were discovered.
852 =head2 extractRawContent
854 B<function:> pass me an article (Simple, or Collection) and I'll give
855 you the content AS A STRING - i.e. the raw XML of the contained MOBY
858 B<usage:> C<extractRawContent($simple)>
860 B<input:> the one element of the output from getArticles
866 sub extractRawContent
{
867 my ( $article ) = @_;
868 return "" unless ( $article || (ref( $article ) =~ /XML\:\:LibXML/) );
870 foreach ( $article->childNodes ) {
871 $response .= $_->toString;
873 # print STDERR "RESPONSE = $response\n";
877 =head2 getNodeContentWithArticle
879 B<function:> a very flexible way to get the stringified content of a
880 node that has the correct element and article name or get the value of
883 B<usage:> C<@strings = getNodeContentWithArticle($node, $tagname, $articleName)>
887 C<$node> - an XML::LibXML node, or straight XML. It may even be the entire mobyData block.
888 C<$tagname> - the tagname (effectively from the Object type ontology), or "Parameter" if you are trying to get secondaries
889 C<$articleName> - the articleName that we are searching for. to get the content of the primary object, leave this field blank!
892 B<returns:> an ARRAY of the stringified text content for each
893 node that matched the tagname/articleName specified; one
894 array element for each matching node. Newlines are NOT considered
895 new nodes (as they are in normal XML).
897 B<notes:> This was written for the purpose of getting the values of
898 String, Integer, Float, Date_Time, and other such primitives.
900 For example, in the following XML:
906 <Sequence namespace=blah id=blah>
907 <Integer namespace='' id='' articleName="Length">3</Integer>
908 <String namespace='' id='' articleName="SequenceString">ATG</String>
915 would be analysed as follows:
917 # get $input - e.g. from genericServiceInputParser or complexServiceInputParser
918 @sequences = getNodeContentWithArticle($input, "String", "SequenceString");
920 For Parameters, such as the following
926 <Sequence namespace=blah id=blah>
927 <Integer namespace='' id='' articleName="Length">3</Integer>
928 <String namespace='' id='' articleName="SequenceString">ATG</String>
931 <Parameter articleName='cutoff'>
938 You would parse it as follows:
940 # get $input - e.g. from genericServiceInputParser or complexServiceInputParser
941 @sequences = getNodeContentWithArticle($input, "String", "SequenceString");
942 @cutoffs = getNodeContentWithArticle($input, "Parameter", "cutoff");
946 my $inputs = complexServiceInputParser($MOBY_mssage));
947 # $inputs->{$queryID} = [ [TYPE, $DOM], [TYPE, $DOM], [TYPE, $DOM] ]
948 my (@enumerated) = keys %{$inputs};
949 foreach $no (@enumerated){
950 my @articles = @{$inputs->{$no}};
951 foreach my $article(@articles){
952 my ($type, $DOM) = @{$article};
953 if ($type == SECONDARY){
954 ($cutoff) = getNodeContentsWithArticle($DOM, "Parameter", "cutoff");
956 @sequences = getNodeContentWithArticle($DOM, "String", "SequenceString");
963 sub getNodeContentWithArticle
{
964 # give me a DOM, a TagName, an articleName and I will return you the content
965 # of that node **as a string** (beware if there are additional XML tags in there!)
966 # this is meant for MOBYesque PRIMITIVES - things like:
967 # <String articleName="SequenceString">TAGCTGATCGAGCTGATGCTGA</String>
968 # call _getNodeContentsWithAttribute($DOM_NODE, "String", "SequenceString")
969 # and I will return "TACGATGCTAGCTAGCGATCGG"
970 # Caveat Emptor - I will NOT chop off leading and trailing whitespace or
971 # carriage returns, as these might be meaningful!
972 my ( $node, $element, $articleName ) = @_;
974 return () unless ( (ref( $node ) =~ /XML\:\:LibXML/) && $element);
977 my $nodes = $node->getElementsByTagName( $element );
978 unless ( $nodes->get_node( 1 ) ) {
979 $nodes = $node->getElementsByTagName("moby:$element");
981 $node = $nodes->get_node(1); # this routine should only ever be called if there is only one possible answer, so this is safe
983 unless ($articleName){ # the request is for root node if no articleName
985 foreach my $child($node->childNodes){
986 next unless ($child->nodeType == TEXT_NODE
987 || $child->nodeType == CDATA_SECTION_NODE
);
988 $resp .= $child->nodeValue;
990 push @contents, $resp;
994 # if there is an articleName, then get that specific node
995 for ( 1 .. $nodes->size() ) {
996 my $child = $nodes->get_node( $_ );
997 if ( _moby_getAttribute
($child, "articleName")
998 && ( $child->getAttribute("articleName") eq $articleName )
1001 # now we have a valid child, get the content... stringified... regardless of what it is
1002 if ( isSecondaryArticle
( $child ) ) {
1004 my $valuenodes = $child->getElementsByTagName('Value');
1005 unless ( $valuenodes->get_node( 1 ) ) {
1006 $valuenodes = $child->getElementsByTagName("moby:Value");
1008 for ( 1 .. $valuenodes->size() ) {
1009 my $valuenode = $valuenodes->get_node( $_ );
1010 foreach my $amount ( $valuenode->childNodes ) {
1011 next unless ($amount->nodeType == TEXT_NODE
1012 || $amount->nodeType == CDATA_SECTION_NODE
);
1013 $resp .= $amount->nodeValue;
1016 push @contents, $resp;
1019 foreach ( $child->childNodes ) {
1020 next unless ($_->nodeType == TEXT_NODE
1021 || $_->nodeType == CDATA_SECTION_NODE
);
1022 $resp .= $_->nodeValue;
1024 push @contents, $resp;
1031 *getResponseArticles
= \
&extractResponseArticles
;
1032 *getResponseArticles
= \
&extractResponseArticles
;
1034 =head2 getResponseArticles (a.k.a. extractResponseArticles)
1036 B<function:> get the DOM nodes corresponding to individual Simple or Collection outputs from a MOBY Response
1038 B<usage:> C<($collections, $simples) = getResponseArticles($node)>
1040 B<args:> C<$node> - either raw XML or an XML::LibXML::Document to be searched
1042 B<returns:> an array-ref of Collection article XML::LibXML::Node's or an array-ref of Simple article XML::LibXML::Node's
1046 sub extractResponseArticles
{
1047 my ( $result ) = @_;
1048 return ( [], [] ) unless $result;
1050 unless ( ref( $result ) =~ /XML\:\:LibXML/ ) {
1051 my $parser = XML
::LibXML
->new();
1052 my $doc = $parser->parse_string( $result );
1053 $moby = $doc->getDocumentElement();
1055 $moby = $result->getDocumentElement();
1061 foreach my $which ( 'moby:queryResponse', 'queryResponse',
1062 'mobyData', 'moby:mobyData' )
1064 my $responses = $moby->getElementsByTagName( $which );
1065 next unless $responses;
1066 foreach my $n ( 1 .. ( $responses->size() ) ) {
1067 my $resp = $responses->get_node( $n );
1068 foreach my $response_component ( $resp->childNodes ) {
1069 next unless $response_component->nodeType == ELEMENT_NODE
;
1070 if ( $response_component->nodeName =~ /^(moby:|)Simple$/ )
1072 foreach my $Object ( $response_component->childNodes ) {
1073 next unless $Object->nodeType == ELEMENT_NODE
;
1075 push @objects, $Object;
1077 } elsif ( $response_component->nodeName =~ /^(moby:|)Collection$/ )
1080 foreach my $simple ( $response_component->childNodes ) {
1081 next unless $simple->nodeType == ELEMENT_NODE
;
1082 next unless ( $simple->nodeName =~ /^(moby:|)Simple$/ );
1083 foreach my $Object ( $simple->childNodes ) {
1084 next unless $Object->nodeType == ELEMENT_NODE
;
1086 push @objects, $Object;
1089 push @collections, \
@objects
1090 ; #I'm not using collections yet, so we just use Simples.
1095 return ( \
@collections, \
@objects );
1100 =head1 IDENTITY AND VALIDATION
1102 This section describes functionality associated with identifying parts of a message,
1103 and checking that it is valid.
1105 =head2 isSimpleArticle, isCollectionArticle, isSecondaryArticle
1107 B<function:> tests XML (text) or an XML DOM node to see if it represents a Simple, Collection, or Secondary article
1111 if (isSimpleArticle($node)){do something to it}
1115 if (isCollectionArticle($node)){do something to it}
1119 if (isSecondaryArticle($node)){do something to it}
1121 B< input :> an XML::LibXML node, an XML::LibXML::Document or straight XML
1127 sub isSimpleArticle
{
1129 eval { $DOM = _string_to_DOM
($DOM) };
1131 $DOM = $DOM->getDocumentElement if ( $DOM->isa( "XML::LibXML::Document" ) );
1132 return ($DOM->nodeName =~ /^(moby:|)Simple$/) ?
1 : 0; #Optional 'moby:' namespace prefix
1135 sub isCollectionArticle
{
1137 eval {$DOM = _string_to_DOM
($DOM) };
1139 $DOM = $DOM->getDocumentElement if ( $DOM->isa( "XML::LibXML::Document" ) );
1140 return ( $DOM->nodeName =~ /^(moby\:|)Collection$/ ) ?
1 : 0; #Optional 'moby:' prefix
1143 sub isSecondaryArticle
{
1146 eval {$DOM = _string_to_DOM
($XML)} ;
1148 $DOM = $DOM->getDocumentElement if ( $DOM->isa( "XML::LibXML::Document" ) );
1149 return ($DOM->nodeName =~ /^(moby\:|)Parameter$/) ?
1 : 0; #Optional 'moby:' prefix
1153 =head2 validateNamespaces
1155 B<function:> checks the namespace ontology for the namespace lsid
1157 B<usage:> C<@LSIDs = validateNamespaces(@namespaces)>
1159 B<args:> ordered list of either human-readable or lsid presumptive namespaces
1161 B<returns:> ordered list of the LSID's corresponding to those
1162 presumptive namespaces; undef for each namespace that was invalid
1166 sub validateNamespaces
{
1167 # give me a list of namespaces and I will return the LSID's in order
1168 # I return undef in that list position if the namespace is invalid
1169 my ( @namespaces ) = @_;
1170 my $OS = MOBY
::Client
::OntologyServer
->new;
1172 foreach ( @namespaces ) {
1173 my ( $s, $m, $LSID ) = $OS->namespaceExists( term
=> $_ );
1174 push @lsids, $s ?
$LSID : undef;
1179 =head2 validateThisNamespace
1181 B<function:> checks a given namespace against a list of valid namespaces
1183 B<usage:> C<$valid = validateThisNamespace($ns, @validNS);>
1185 B<args:> ordered list of the namespace of interest and the list of valid NS's
1191 sub validateThisNamespace
{
1192 my ( $ns, @namespaces ) = @_;
1193 return 1 unless scalar @namespaces; # if you don't give me a list, I assume everything is valid...
1194 @namespaces = @
{$namespaces[0]} # if you send me an arrayref I should be kind... DWIM!
1195 if ( ref $namespaces[0] eq 'ARRAY' );
1196 return grep /$ns/, @namespaces;
1200 =head1 ANCILIARY ELEMENTS
1202 This section contains subroutines that handle processing of optional message elements containing
1203 meta-data. Examples are the ServiceNotes, and CrossReference blocks.
1205 =head2 getServiceNotes
1207 B<function:> to get the content of the Service Notes block of the MOBY message
1209 B<usage:> C<getServiceNotes($message)>
1211 B<args:> C<$message> is either the XML::LibXML of the MOBY message, or plain XML
1213 B<returns:> String content of the ServiceNotes block of the MOBY Message
1217 sub getServiceNotes
{
1218 my ( $result ) = @_;
1219 return ( "" ) unless $result;
1220 my $moby = _string_to_DOM
($result);
1222 my $responses = $moby->getElementsByTagName( 'moby:serviceNotes' )
1223 || $moby->getElementsByTagName( 'serviceNotes' );
1225 foreach my $n ( 1 .. ( $responses->size() ) ) {
1226 my $resp = $responses->get_node( $n );
1227 foreach my $response_component ( $resp->childNodes ) {
1228 # $content .= $response_component->toString;
1229 $content .= $response_component->nodeValue
1230 if ( $response_component->nodeType == TEXT_NODE
);
1231 $content .= $response_component->nodeValue
1232 if ( $response_component->nodeType == CDATA_SECTION_NODE
);
1235 return ( $content );
1238 =head2 getCrossReferences
1240 B<function:> to get the cross-references for a Simple article
1242 B<usage:> C<@xrefs = getCrossReferences($XML)>
1244 B<args:> C<$XML> is either a SIMPLE article (<Simple>...</Simple>) or an
1245 object (the payload of a Simple article), and may be either raw XML or
1246 an XML::LibXML node.
1248 B<returns:> an array of MOBY::CrossReference objects
1252 my (($colls, $simps) = getResponseArticles($query); # returns DOM nodes
1253 foreach (@{$simps}){
1254 my @xrefs = getCrossReferences($_);
1255 foreach my $xref(@xrefs){
1256 print "Cross-ref type: ",$xref->type,"\n";
1257 print "namespace: ",$xref->namespace,"\n";
1258 print "id: ",$xref->id,"\n";
1259 if ($xref->type eq "Xref"){
1260 print "Cross-ref relationship: ", $xref->xref_type,"\n";
1267 sub getCrossReferences
{
1269 $XML = _string_to_DOM
($XML);
1272 return () if ( $XML->nodeName =~ /^(moby:|)Collection$/ );
1273 if ( $XML->nodeName =~ /^(moby:|)Simple$/ ) {
1274 foreach my $child ( $XML->childNodes ) {
1275 next unless $child->nodeType == ELEMENT_NODE
;
1277 last; # enforce proper MOBY message structure
1280 foreach ( $XML->childNodes ) {
1281 next unless (($_->nodeType == ELEMENT_NODE
)
1282 || ($_->nodeName =~ /^(moby:|)CrossReference$/) );
1283 foreach my $xref ( $_->childNodes ) {
1284 next unless ( ($xref->nodeType == ELEMENT_NODE
)
1285 || ($xref->nodeName =~ /^(moby:|)(Xref|Object)$/) );
1289 foreach ( @xrefs ) {
1291 if ($_->nodeName =~ /^(moby:|)Xref$/) { $x = _makeXrefType
( $_ ) }
1292 elsif ($_->nodeName =~ /^(moby:|)Object$/) { $x = _makeObjectType
( $_ ) }
1293 push @XREFS, $x if $x;
1299 =head1 CONSTRUCTING OUTPUT
1301 This section describes how to construct output, in response to an
1302 incoming message. Responses come in three varieties:
1307 Simple - Only simple article(s)
1310 Collection - Only collection(s) of simples
1313 Complex - Any combination of simple and/or collection and/or secondary articles.
1317 =head2 simpleResponse
1319 B<function:> wraps a simple article in the appropriate (mobyData) structure.
1320 Works only for simple articles. If you need to mix simples with collections and/or
1321 secondaries use complexReponse instead.
1323 B<usage:> C<$responseBody = simpleResponse($object, $ArticleName, $queryID);>
1326 C<$object> - (optional) a MOBY Object as raw XML.
1327 C<$articleName> - (optional) an article name for this article.
1328 C<$queryID> - (optional, but strongly recommended) the query ID value for the mobyData block to which you are responding.
1330 B<notes:> As required by the API you must return a response for every
1331 input. If one of the inputs was invalid, you return a valid (empty)
1332 MOBY response by calling simpleResponse(undef, undef, $queryID) with
1337 sub simpleResponse
{
1338 my ( $data, $articleName, $qID ) = @_; # articleName optional
1339 $qID = _getQueryID
( $qID )
1340 if ref( $qID ) =~ /XML\:\:LibXML/; # in case they send the DOM instead of the ID
1341 $data ||= ''; # initialize to avoid uninit value errors
1342 $articleName ||= "";
1344 if ( $articleName || $data) { # Linebreaks in XML make it easier for human debuggers to read!
1346 <moby:mobyData moby:queryID='$qID'>
1347 <moby:Simple moby:articleName='$articleName'>$data</moby:Simple>
1352 <moby:mobyData moby:queryID='$qID'/>
1358 =head2 collectionResponse
1360 B<function:> wraps a set of articles in the appropriate mobyData structure.
1361 Works only for collection articles. If you need to mix collections with simples and/or
1362 secondaries use complexReponse instead.
1364 B<usage:> C<$responseBody = collectionResponse(\@objects, $articleName, $queryID);>
1367 C<\@objects> - (optional) a listref of MOBY Objects as raw XML.
1368 C<$articleName> - (optional) an artice name for this article.
1369 C<$queryID> - (optional, but strongly recommended) the ID of the query to which you are responding.
1371 B<notes:> as required by the API you must return a response for every
1372 input. If one of the inputs was invalid, you return a valid (empty)
1373 MOBY response by calling collectionResponse(undef, undef, $queryID).
1377 sub collectionResponse
{
1378 my ( $data, $articleName, $qID ) = @_; # articleName optional
1382 # The response should only be completely empty when the input $data is completely empty.
1383 # Testing just the first element is incorrect.
1384 my $not_completely_empty = 0;
1385 foreach (@
{$data}) { $not_completely_empty += defined $_ }
1386 unless ( ( ref($data) eq 'ARRAY' ) && $not_completely_empty )
1387 { # we're expecting an arrayref as input data, and it must not be empty
1388 return "<moby:mobyData moby:queryID='$qID'/>";
1390 foreach ( @
{$data} ) { # Newlines are for ease of human reading (pretty-printing).
1391 # It's really hard to keep this kind of thing in sync with itself, but for what it's worth, let's leave it in.
1393 $content .= "<moby:Simple>$_</moby:Simple>\n";
1395 $content .= "<moby:Simple/>\n";
1398 if ( $articleName ) {
1400 <moby:mobyData moby:queryID='$qID'>
1401 <moby:Collection moby:articleName='$articleName'>
1408 <moby:mobyData moby:queryID='$qID'>
1409 <moby:Collection moby:articleName='$articleName'>$content</moby:Collection>
1415 =head2 complexResponse
1417 B<function:> wraps articles in the appropriate (mobyData) structure.
1418 Can be used to send any combination of the three BioMOBY article types -
1419 simple, collection and secondary - back to a client.
1421 B<usage:> C<$responseBody = complexResponse(\@articles, $queryID);>
1425 C<\@articles> - (optional) a listref of arrays. Each element of @articles is
1426 itself a listref of [$articleName, $article], where $article is either
1427 the article's raw XML for simples and secondaries or a reference to an array containing
1428 [$articleName, $simpleXML] elements for a collection of simples.
1430 C<$queryID> - (optional, but strongly recommended) the queryID value for
1431 the mobyData block to which you are responding
1433 B<notes:> as required by the API you must return a response for every
1434 input. If one of the inputs was invalid, you return a valid (empty)
1435 MOBY response by calling complexResponse(undef, $queryID) with no
1440 sub complexResponse
{
1441 my ( $data, $qID ) = @_;
1442 #return 'ERROR: expected listref [element1, element2, ...] for data' unless ( ref( $data ) =~ /array/i );
1443 return "<moby:mobyData moby:queryID='$qID'/>\n"
1444 unless ( ref( $data ) eq 'ARRAY' );
1445 $qID = _getQueryID
( $qID )
1446 if ref( $qID ) =~ /XML\:\:LibXML/; # in case they send the DOM instead of the ID
1447 my @inputs = @
{$data};
1448 my $output = "<moby:mobyData queryID='$qID'>";
1449 foreach ( @inputs ) {
1450 #return 'ERROR: expected listref [articleName, XML] for data element' unless ( ref( $_ ) =~ /array/i );
1451 return "<moby:mobyData moby:queryID='$qID'/>\n"
1452 unless ( ref($_) eq 'ARRAY' );
1453 while ( my ( $articleName, $XML ) = splice( @
{$_}, 0, 2 ) ) {
1454 if ( ref($XML) ne 'ARRAY' ) {
1455 $articleName ||= "";
1457 if ( $XML =~ /\<(moby:|)Value\>/ ) {
1459 "<moby:Parameter moby:articleName='$articleName'>$XML</moby:Parameter>\n";
1462 "<moby:Simple moby:articleName='$articleName'>\n$XML\n</moby:Simple>\n";
1464 # Need to do this for collections also!!!!!!
1467 $output .= "<moby:Collection moby:articleName='$articleName'>\n";
1469 $output .= "<moby:Simple>$_</moby:Simple>\n";
1471 $output .= "</moby:Collection>\n";
1475 $output .= "</moby:mobyData>\n";
1479 =head2 responseHeader
1481 B<function:> print the XML string of a MOBY response header +/- serviceNotes
1485 responseHeader('illuminae.com')
1488 -authority => 'illuminae.com',
1489 -note => 'here is some data from the service provider')
1491 B<args:> a string representing the service providers authority URI, OR
1492 a set of named arguments with the authority and the service provision
1497 B<notes:> returns everything required up to the response articles themselves. i.e. something like:
1499 <?xml version='1.0' encoding='UTF-8'?>
1500 <moby:MOBY xmlns:moby='http://www.biomoby.org/moby'>
1501 <moby:Response moby:authority='http://www.illuminae.com'>
1505 sub responseHeader
{
1506 use HTML
::Entities
();
1507 my ( $auth, $notes ) = _rearrange
( [qw
[AUTHORITY NOTE
]], @_ );
1508 $auth ||= "not_provided";
1511 "<?xml version='1.0' encoding='UTF-8'?>"
1512 . "<moby:MOBY xmlns:moby='http://www.biomoby.org/moby' xmlns='http://www.biomoby.org/moby'>"
1513 . "<moby:mobyContent moby:authority='$auth'>";
1515 my $encodednotes = HTML
::Entities
::encode
( $notes );
1516 $xml .= "<moby:serviceNotes>$encodednotes</moby:serviceNotes>";
1521 =head2 responseFooter
1523 B<function:> print the XML string of a MOBY response footer
1527 return responseHeader('illuminae.com') . $DATA . responseFooter;
1529 B<notes:> returns everything required after the response articles themselves i.e. something like:
1536 sub responseFooter
{
1537 return "</moby:mobyContent></moby:MOBY>";
1542 =head1 MISCELLANEOUS
1544 This section contains routines that didn't quite seem to fit anywhere else.
1548 =head2 _moby_getAttributeNode, _moby_getAttribute
1550 B<function:> Perform the same task as the DOM routine
1551 getAttribute(Node), but check for both the prefixed and un-prefixed
1552 attribute name (the prefix in question being, of course,
1557 $id = _moby_getAttribute($xml_libxml, "id");
1559 where C<id> is an attribute in the XML block given as C<$xml_libxml>
1561 B<notes:> This function is intended for use internal to this package
1562 only. It's not exported.
1566 sub _moby_getAttributeNode
{
1567 # Mimics behavior of XML::LibXML method getAttributeNode, but if the unqualified attribute cannot be found,
1568 # we qualify it with "moby:" and try again.
1569 # We do this so often this module, it's worth having a separate subroutine to do this.
1570 my ($xref, $attr) = @_;
1571 my ($package, $filename, $line) = caller;
1572 if ( !(ref($xref) =~ "^XML\:\:LibXML") ) {
1573 warn "_moby_getAttributeNode: Looking for attribute '$attr'"
1574 . "Can't parse non-XML argument '$xref',\n"
1575 . " called from line $line";
1578 if (!defined $attr) {
1579 warn "_moby_getAttributeNode: Non-empty attribute is required"
1580 . "\n called from line $line";
1583 return ( $xref->getAttributeNode($attr) || $xref->getAttributeNode( "moby:$attr" ) );
1586 sub _moby_getAttribute
{
1587 # Mimics behavior of XML::LibXML method getAttribute, but if the unqualified attribute cannot be found,
1588 # we qualify it with "moby:" and try again.
1589 # We do this so often this module, it's worth having a separate subroutine to do this.
1590 my ($xref, $attr) = @_;
1591 my ($package, $filename, $line) = caller;
1592 if ( !(ref($xref) =~ "^XML\:\:LibXML")) {
1593 warn "_moby_getAttribute: Looking for attribute '$attr', "
1594 ."can't parse non-XML argument '$xref'\n"
1595 . "_moby_getAttribute called from line $line";
1598 if (!defined $attr) {
1599 warn "_moby_getAttribute: Non-empty attribute is required"
1600 . "\n called from line $line";
1603 return ( $xref->getAttribute($attr) || $xref->getAttribute("moby:$attr") );
1606 =head2 whichDeepestParentObject
1608 B<function:> select the parent node from nodeList that is closest to the querynode
1612 ($term, $lsid) = whichDeepestParentObject($CENTRAL, $queryTerm, \@termList)
1616 C<$CENTRAL> - your MOBY::Client::Central object
1618 C<$queryTerm> - the object type I am interested in
1620 C<\@termlist> - the list of object types that I know about
1622 B<returns:> an ontology term and LSID as a scalar, or undef if there is
1623 no parent of this node in the nodelist. note that it will only return
1624 the term if you give it term names in the @termList. If you give it
1625 LSID's in the termList, then both the parameters returned will be
1626 LSID's - it doesn't back-translate...)
1630 sub whichDeepestParentObject
{
1631 my ( $CENTRAL, $queryTerm, $termlist ) = @_;
1632 return ( undef, undef )
1633 unless ( $CENTRAL && $queryTerm
1634 && $termlist && ( ref( $termlist ) eq 'ARRAY' ) );
1636 my $queryLSID = $CENTRAL->ObjLSID( $queryTerm );
1637 foreach ( @
$termlist ) { # get list of known LSIDs
1638 my $lsid = $CENTRAL->ObjLSID( $_ );
1639 return ( $_, $lsid )
1640 if ( $lsid eq $queryLSID ); # of course, if we find it in the list, then return it right away!
1641 $nodeLSIDs{$lsid} = $_;
1643 return ( undef, undef ) unless keys( %nodeLSIDs );
1645 $CENTRAL->ISA( $queryTerm, 'Object' )
1646 ; # set the complete parentage in the cache if it isn't already
1647 return ( undef, undef )
1648 unless $isa; # this should return true or we are in BIIIG trouble!
1650 $CENTRAL->ISA_CACHE( $queryTerm )
1651 ; # returns **LSIDs** in order, so we can shift our way back to root
1652 while ( my $thislsid = shift @ISAlsids ) { # @isas are lsid's
1653 return ( $nodeLSIDs{$thislsid}, $thislsid ) if $nodeLSIDs{$thislsid};
1655 return ( undef, undef );
1661 my $ns = _moby_getAttributeNode
($xref, 'namespace' );
1662 return undef unless $ns;
1663 my $id = _moby_getAttributeNode
($xref, 'id' );
1664 return undef unless $id;
1665 my $xr = _moby_getAttributeNode
($xref, 'xref_type' );
1666 return undef unless $xr;
1667 my $ec = _moby_getAttributeNode
($xref, 'evidence_code' );
1668 return undef unless $ec;
1669 my $au = _moby_getAttributeNode
($xref, 'authURI' );
1670 return undef unless $au;
1671 my $sn = _moby_getAttributeNode
($xref, 'serviceName' );
1672 return undef unless $sn;
1673 my $XREF = MOBY
::CrossReference
->new(
1675 namespace
=> $ns->getValue,
1676 id
=> $id->getValue,
1677 authURI
=> $au->getValue,
1678 serviceName
=> $sn->getValue,
1679 evidence_code
=> $ec->getValue,
1680 xref_type
=> $xr->getValue
1686 sub _makeObjectType
{
1688 my $ns = _moby_getAttributeNode
($xref, 'namespace' );
1689 return undef unless $ns;
1690 my $id = _moby_getAttributeNode
($xref, 'id');
1691 return undef unless $id;
1692 my $XREF = MOBY
::CrossReference
->new(
1694 namespace
=> $ns->getValue,
1695 id
=> $id->getValue,
1699 =head2 _rearrange (stolen from BioPerl ;-) )
1702 $object->_rearrange( array_ref, list_of_arguments)
1704 B<Purpose :> Rearranges named parameters to requested order.
1707 $self->_rearrange([qw(SEQUENCE ID DESC)],@param);
1708 Where C<@param = (-sequence => $s, -desc => $d, -id => $i);>
1710 B<returns:> C<@params> - an array of parameters in the requested order.
1712 The above example would return ($s, $i, $d).
1713 Unspecified parameters will return undef. For example, if
1714 C<@param = (-sequence => $s);>
1715 the above _rearrange call would return ($s, undef, undef)
1717 B<Argument:> C<$order> : a reference to an array which describes the desired order of the named parameters.
1719 C<@param :> an array of parameters, either as a list (in which case the function
1720 simply returns the list), or as an associative array with hyphenated
1721 tags (in which case the function sorts the values according to
1722 @{$order} and returns that new array.) The tags can be upper, lower,
1723 or mixed case but they must start with a hyphen (at least the first
1724 one should be hyphenated.)
1726 B< Source:> This function was taken from CGI.pm, written by
1727 Dr. Lincoln Stein, and adapted for use in Bio::Seq by Richard Resnick
1728 and then adapted for use in Bio::Root::Object.pm by Steve Chervitz,
1729 then migrated into Bio::Root::RootI.pm by Ewan Birney.
1732 Uppercase tags are the norm, (SAC) This method may not be appropriate
1733 for method calls that are within in an inner loop if efficiency is a
1736 Parameters can be specified using any of these formats:
1737 @param = (-name=>'me', -color=>'blue');
1738 @param = (-NAME=>'me', -COLOR=>'blue');
1739 @param = (-Name=>'me', -Color=>'blue');
1740 @param = ('me', 'blue');
1742 A leading hyphenated argument is used by this function to indicate
1743 that named parameters are being used. Therefore, the ('me', 'blue')
1744 list will be returned as-is.
1746 Note that Perl will confuse unquoted, hyphenated tags as function
1747 calls if there is a function of the same name in the current
1748 namespace: C<-name => 'foo'> is interpreted as C<-&name => 'foo'>
1750 For ultimate safety, put single quotes around the tag: C<('-name'=>'me', '-color' =>'blue');>
1752 This can be a bit cumbersome and I find not as readable as using all
1753 uppercase, which is also fairly safe:C<(-NAME=>'me', -COLOR =>'blue');>
1755 Personal note (SAC): I have found all uppercase tags to be more
1756 managable: it involves less single-quoting, the key names stand out
1757 better, and there are no method naming conflicts. The drawbacks are
1758 that it's not as easy to type as lowercase, and lots of uppercase can
1759 be hard to read. Regardless of the style, it greatly helps to line the parameters up
1760 vertically for long/complex lists.
1765 # my $dummy = shift;
1767 return @_ unless ( substr( $_[0] || '', 0, 1 ) eq '-' );
1768 push @_, undef unless $#_ % 2;
1771 ( my $key = shift ) =~ tr/a-z\055/A-Z/d; #deletes all dashes!
1772 $param{$key} = shift;
1774 map { $_ = uc( $_ ) } @
$order; # for bug #1343, but is there perf hit here?
1775 return @param{@
$order};
1780 $query = _string_to_XML
($query);
1781 return '' unless ( $query->nodeName =~ /^(moby:|)(queryInput|mobyData)$/ ); #Eddie - unsure
1782 return _moby_getAttribute
($query, 'queryID' );