File Coverage

blib/lib/Bio/Phylo/NeXML/Writable.pm
Criterion Covered Total %
statement 204 325 62.7
branch 55 120 45.8
condition 13 34 38.2
subroutine 43 54 79.6
pod 35 35 100.0
total 350 568 61.6


line stmt bran cond sub pod time code
1             package Bio::Phylo::NeXML::Writable;
2 51     51   340 use strict;
  51         977  
  51         1237  
3 51     51   226 use warnings;
  51         92  
  51         1120  
4 51     51   222 use base 'Bio::Phylo';
  51         92  
  51         8524  
5 51     51   4965 use Bio::Phylo::IO 'unparse';
  51         106  
  51         2467  
6 51     51   5214 use Bio::Phylo::Factory;
  51         108  
  51         288  
7 51     51   13962 use Bio::Phylo::NeXML::DOM;
  51         122  
  51         357  
8 51     51   14331 use Bio::Phylo::NeXML::Entities '/entities/';
  51         196  
  51         7492  
9 51     51   376 use Bio::Phylo::Util::Exceptions 'throw';
  51         95  
  51         2211  
10 51     51   280 use Bio::Phylo::Util::CONSTANT qw'/looks_like/ :namespaces :objecttypes';
  51         94  
  51         25259  
11             {
12             my $logger = __PACKAGE__->get_logger;
13             my $fac = Bio::Phylo::Factory->new;
14             my $DICTIONARY_CONSTANT = _DICTIONARY_;
15             my $META_CONSTANT = _META_;
16             my %namespaces = (
17             'nex' => _NS_NEXML_,
18             'xml' => _NS_XML_,
19             'xsi' => _NS_XSI_,
20             'rdf' => _NS_RDF_,
21             'xsd' => _NS_XSD_,
22             'map' => _NS_PHYLOMAP_,
23             );
24             my @fields =
25             \( my ( %tag, %id, %attributes, %identifiable, %suppress_ns, %meta, %url ) );
26              
27             =head1 NAME
28              
29             Bio::Phylo::NeXML::Writable - Superclass for objects that serialize to NeXML
30              
31             =head1 SYNOPSIS
32              
33             # no direct usage
34              
35             =head1 DESCRIPTION
36              
37             This is the superclass for all objects that can be serialized to NeXML
38             (L<http://www.nexml.org>).
39              
40             =head1 METHODS
41              
42             =head2 MUTATORS
43              
44             =over
45              
46             =item set_namespaces()
47              
48             Type : Mutator
49             Title : set_namespaces
50             Usage : $obj->set_namespaces( 'dwc' => 'http://www.namespaceTBD.org/darwin2' );
51             Function: Adds one or more prefix/namespace pairs
52             Returns : $self
53             Args : One or more prefix/namespace pairs, as even-sized list,
54             or as a hash reference, i.e.:
55             $obj->set_namespaces( 'dwc' => 'http://www.namespaceTBD.org/darwin2' );
56             or
57             $obj->set_namespaces( { 'dwc' => 'http://www.namespaceTBD.org/darwin2' } );
58             Notes : This is a global for the XMLWritable class, so that in a recursive
59             to_xml call the outermost element contains the namespace definitions.
60             This method can also be called as a static class method, i.e.
61             Bio::Phylo::NeXML::Writable->set_namespaces(
62             'dwc' => 'http://www.namespaceTBD.org/darwin2');
63              
64             =cut
65              
66             sub set_namespaces {
67 243     243 1 339 my $self = shift;
68 243 100 66     806 if ( scalar(@_) == 1 and ref( $_[0] ) eq 'HASH' ) {
    50          
69 25         41 my $hash = shift;
70 25         34 for my $key ( keys %{$hash} ) {
  25         74  
71 25         80 $namespaces{$key} = $hash->{$key};
72             }
73             }
74             elsif ( my %hash = looks_like_hash @_ ) {
75 218         510 for my $key ( keys %hash ) {
76 218         657 $namespaces{$key} = $hash{$key};
77             }
78             }
79             }
80              
81             =item set_suppress_ns()
82              
83             Type : Mutator
84             Title : set_suppress_ns
85             Usage : $obj->set_suppress_ns();
86             Function: Tell this object not to write namespace attributes
87             Returns :
88             Args : none
89              
90             =cut
91              
92             sub set_suppress_ns : Clonable {
93 110     110 1 140 my $self = shift;
94 110         179 my $id = $self->get_id;
95 110         261 $suppress_ns{$id} = 1;
96 51     51   382 }
  51         104  
  51         319  
97              
98             =item clear_suppress_ns()
99              
100             Type : Mutator
101             Title : clear_suppress_ns
102             Usage : $obj->clear_suppress_ns();
103             Function: Tell this object to write namespace attributes
104             Returns :
105             Args : none
106              
107             =cut
108              
109             sub clear_suppress_ns {
110 0     0 1 0 my $self = shift;
111 0         0 my $id = $self->get_id;
112 0         0 $suppress_ns{$id} = 0;
113             }
114              
115             =item add_meta()
116              
117             Type : Mutator
118             Title : add_meta
119             Usage : $obj->add_meta($meta);
120             Function: Adds a metadata attachment to the object
121             Returns : $self
122             Args : A Bio::Phylo::NeXML::Meta object
123              
124             =cut
125              
126             sub add_meta {
127 1443     1443 1 2427 my ( $self, $meta_obj ) = @_;
128 1443 50       2921 if ( looks_like_object $meta_obj, $META_CONSTANT ) {
129 1443         2791 my $id = $self->get_id;
130 1443 100       3131 if ( not $meta{$id} ) {
131 321         671 $meta{$id} = [];
132             }
133 1443         1766 push @{ $meta{$id} }, $meta_obj;
  1443         2725  
134 1443 50       2665 if ( $self->is_identifiable ) {
135 0         0 $self->set_attributes( 'about' => '#' . $self->get_xml_id );
136             }
137             }
138 1443         2830 return $self;
139             }
140              
141             =item remove_all_meta()
142              
143             Type : Mutator
144             Title : remove_all_meta
145             Usage : $obj->remove_all_meta();
146             Function: Removes all metadata attachments from the object
147             Returns : $self
148             Args : None
149              
150             =cut
151              
152             sub remove_all_meta {
153 0     0 1 0 my $self = shift;
154 0         0 $meta{$self->get_id} = [];
155 0         0 return $self;
156             }
157              
158             =item remove_meta()
159              
160             Type : Mutator
161             Title : remove_meta
162             Usage : $obj->remove_meta($meta);
163             Function: Removes a metadata attachment from the object
164             Returns : $self
165             Args : Bio::Phylo::NeXML::Meta
166              
167             =cut
168              
169             sub remove_meta {
170 0     0 1 0 my ( $self, $meta ) = @_;
171 0         0 my $id = $self->get_id;
172 0         0 my $meta_id = $meta->get_id;
173 0 0       0 if ( $meta{$id} ) {
174 0         0 DICT: for my $i ( 0 .. $#{ $meta{$id} } ) {
  0         0  
175 0 0       0 if ( $meta{$id}->[$i]->get_id == $meta_id ) {
176 0         0 splice @{ $meta{$id} }, $i, 1;
  0         0  
177 0         0 last DICT;
178             }
179             }
180             }
181 0 0 0     0 if ( not $meta{$id} or not @{ $meta{$id} } ) {
  0         0  
182 0         0 $self->unset_attribute('about');
183             }
184 0         0 return $self;
185             }
186              
187             =item set_meta_object()
188              
189             Type : Mutator
190             Title : set_meta_object
191             Usage : $obj->set_meta_object($predicate => $object);
192             Function: Attaches a $predicate => $object pair to the invocant
193             Returns : $self
194             Args : $predicate => (a valid curie of a known namespace)
195             $object => (an object value)
196              
197             =cut
198              
199             sub set_meta_object {
200 800     800 1 1371 my ( $self, $predicate, $object ) = @_;
201 800 100       988 if ( my ($meta) = @{ $self->get_meta($predicate) } ) {
  800         1494  
202 71         197 $meta->set_triple( $predicate => $object );
203             }
204             else {
205 729         4036 $self->add_meta( $fac->create_meta( '-triple' => { $predicate => $object } ) );
206             }
207 800         2736 return $self;
208             }
209              
210             =item set_meta()
211              
212             Type : Mutator
213             Title : set_meta
214             Usage : $obj->set_meta([ $m1, $m2, $m3 ]);
215             Function: Assigns all metadata objects
216             Returns : $self
217             Args : An array ref of metadata objects
218              
219             =cut
220            
221             sub set_meta : Clonable {
222 109     109 1 166 my ( $self, $meta ) = @_;
223 109 50 33     198 if ( $meta && @{ $meta } ) {
  109         238  
224 0         0 $meta{$self->get_id} = $meta;
225 0         0 $self->set_attributes( 'about' => '#' . $self->get_xml_id );
226             }
227             else {
228 109         231 $meta{$self->get_id} = [];
229 109         281 $self->unset_attribute( 'about' );
230             }
231 109         215 return $self;
232 51     51   30291 }
  51         105  
  51         251  
233            
234             =item set_identifiable()
235              
236             By default, all XMLWritable objects are identifiable when serialized,
237             i.e. they have a unique id attribute. However, in some cases a serialized
238             object may not have an id attribute (governed by the nexml schema). For
239             such objects, id generation can be explicitly disabled using this method.
240             Typically, this is done internally - you will probably never use this method.
241              
242             Type : Mutator
243             Title : set_identifiable
244             Usage : $obj->set_identifiable(0);
245             Function: Enables/disables id generation
246             Returns : $self
247             Args : BOOLEAN
248              
249             =cut
250              
251             sub set_identifiable : Clonable {
252 110     110 1 137 my $self = shift;
253 110         190 $identifiable{ $self->get_id } = shift;
254 110         215 return $self;
255 51     51   9576 }
  51         118  
  51         209  
256              
257             =item set_tag()
258              
259             This method is usually only used internally, to define or alter the
260             name of the tag into which the object is serialized. For example,
261             for a Bio::Phylo::Forest::Node object, this method would be called
262             with the 'node' argument, so that the object is serialized into an
263             xml element structure called <node/>
264              
265             Type : Mutator
266             Title : set_tag
267             Usage : $obj->set_tag('node');
268             Function: Sets the tag name
269             Returns : $self
270             Args : A tag name (must be a valid xml element name)
271              
272             =cut
273              
274             sub set_tag : Clonable {
275 110     110 1 172 my ( $self, $tag ) = @_;
276              
277             # _ is ok; see http://www.w3.org/TR/2004/REC-xml-20040204/#NT-NameChar
278 110 50       747 if ( $tag =~ qr/^[a-zA-Z]+\:?[a-zA-Z_]*$/ ) {
279 110         265 $tag{ $self->get_id } = $tag;
280 110         311 return $self;
281             }
282             else {
283 0         0 throw 'BadString' => "'$tag' is not valid for xml";
284             }
285 51     51   12316 }
  51         106  
  51         196  
286              
287             =item set_name()
288              
289             Sets invocant name.
290              
291             Type : Mutator
292             Title : set_name
293             Usage : $obj->set_name($name);
294             Function: Assigns an object's name.
295             Returns : Modified object.
296             Args : Argument must be a string. Ensure that this string is safe to use for
297             whatever output format you want to use (this differs between xml and
298             nexus, for example).
299              
300             =cut
301              
302             sub set_name : Clonable {
303 9962     9962 1 19765 my ( $self, $name ) = @_;
304 9962 100       18045 if ( defined $name ) {
305 9858         22200 return $self->set_attributes( 'label' => $name );
306             }
307             else {
308 104         230 return $self;
309             }
310 51     51   9704 }
  51         110  
  51         187  
311              
312             =item set_attributes()
313              
314             Assigns attributes for the element.
315              
316             Type : Mutator
317             Title : set_attributes
318             Usage : $obj->set_attributes( 'foo' => 'bar' )
319             Function: Sets the xml attributes for the object;
320             Returns : $self
321             Args : key/value pairs or a hash ref
322              
323             =cut
324              
325             sub set_attributes {
326 14343     14343 1 20698 my $self = shift;
327 14343         30557 my $id = $self->get_id;
328 14343         20244 my %attrs;
329 14343 50 33     45274 if ( scalar @_ == 1 and ref $_[0] eq 'HASH' ) {
    50          
330 0         0 %attrs = %{ $_[0] };
  0         0  
331             }
332             elsif ( scalar @_ % 2 == 0 ) {
333 14343         35936 %attrs = @_;
334             }
335             else {
336 0         0 throw 'OddHash' => 'Arguments are not even key/value pairs';
337             }
338 14343   100     47081 my $hash = $attributes{$id} || {};
339 14343         46156 my $fully_qualified_attribute_regex = qr/^(.+?):(.+)/;
340 14343         35901 for my $key ( keys %attrs ) {
341 15838 100       64813 if ( $key =~ $fully_qualified_attribute_regex ) {
342 1496         3607 my ( $prefix, $attribute ) = ( $1, $2 );
343 1496 50 33     4966 if ( $prefix ne 'xmlns' and not exists $namespaces{$prefix} ) {
344 0         0 $logger->warn("Unbound attribute prefix '${prefix}'");
345             }
346             }
347 15838         39438 $hash->{$key} = $attrs{$key};
348             }
349 14343         25381 $attributes{$id} = $hash;
350 14343         44789 return $self;
351             }
352              
353             =item set_xml_id()
354              
355             This method is usually only used internally, to store the xml id
356             of an object as it is parsed out of a nexml file - this is for
357             the purpose of round-tripping nexml info sets.
358              
359             Type : Mutator
360             Title : set_xml_id
361             Usage : $obj->set_xml_id('node345');
362             Function: Sets the xml id
363             Returns : $self
364             Args : An xml id (must be a valid xml NCName)
365              
366             =cut
367              
368             sub set_xml_id {
369 1     1 1 3 my ( $self, $id ) = @_;
370 1 50       8 if ( $id =~ qr/^[a-zA-Z][a-zA-Z0-9\-_\.]*$/ ) {
371 1         3 $id{ $self->get_id } = $id;
372 1         6 $self->set_attributes( 'id' => $id, 'about' => "#$id" );
373 1         2 return $self;
374             }
375             else {
376 0         0 throw 'BadString' => "'$id' is not a valid xml NCName for $self";
377             }
378             }
379              
380             =item set_base_uri()
381              
382             This utility method can be used to set the xml:base attribute, i.e. to specify
383             a location for the object's XML serialization that potentially differs from
384             the physical location of the containing document.
385              
386             Type : Mutator
387             Title : set_base_uri
388             Usage : $obj->set_base_uri('http://example.org');
389             Function: Sets the xml:base attribute
390             Returns : $self
391             Args : A URI string
392              
393             =cut
394              
395             sub set_base_uri : Clonable {
396 110     110 1 161 my ( $self, $uri ) = @_;
397 110 100       174 if ( $uri ) {
398 2         5 $self->set_attributes( 'xml:base' => $uri );
399             }
400 110         203 return $self;
401 51     51   24154 }
  51         110  
  51         207  
402              
403             =item set_link()
404              
405             This sets a clickable link, i.e. a url, for the object. This has no relation to
406             the xml:base attribute, it is solely intended for serializations that
407             allow clickable links, such as SVG or RSS.
408              
409             Type : Mutator
410             Title : set_link
411             Usage : $node->set_link($url);
412             Function: Sets clickable link
413             Returns : $self
414             Args : url
415              
416             =cut
417              
418             sub set_link : Clonable {
419 117     117 1 183 my ( $self, $url ) = @_;
420 117 100       203 if ( $url ) {
421 9         18 my $id = $self->get_id;
422 9         20 $url{$id} = $url;
423             }
424 117         183 return $self;
425 51     51   9794 }
  51         110  
  51         181  
426              
427             =item unset_attribute()
428              
429             Removes specified attribute
430              
431             Type : Mutator
432             Title : unset_attribute
433             Usage : $obj->unset_attribute( 'foo' )
434             Function: Removes the specified xml attribute for the object
435             Returns : $self
436             Args : an attribute name
437              
438             =cut
439              
440             sub unset_attribute {
441 109     109 1 135 my $self = shift;
442 109         188 my $attrs = $attributes{ $self->get_id };
443 109 100 66     256 if ( $attrs and looks_like_instance( $attrs, 'HASH' ) ) {
444 29         74 delete $attrs->{$_} for @_;
445             }
446 109         158 return $self;
447             }
448              
449             =back
450              
451             =head2 ACCESSORS
452              
453             =over
454              
455             =item get_namespaces()
456              
457             Type : Accessor
458             Title : get_namespaces
459             Usage : my %ns = %{ $obj->get_namespaces };
460             Function: Retrieves the known namespaces
461             Returns : A hash of prefix/namespace key/value pairs, or
462             a single namespace if a single, optional
463             prefix was provided as argument
464             Args : Optional - a namespace prefix
465              
466             =cut
467              
468             sub get_namespaces {
469 17790     17790 1 25441 my ( $self, $prefix ) = @_;
470 17790 100       26172 if ($prefix) {
471 2776         7492 return $namespaces{$prefix};
472             }
473             else {
474 15014         53186 my %tmp_namespaces = %namespaces;
475 15014         31769 return \%tmp_namespaces;
476             }
477             }
478              
479             =item get_prefix_for_namespace()
480              
481             Type : Accessor
482             Title : get_prefix_for_namespace
483             Usage : my $prefix = $obj->get_prefix_for_namespace('http://example.org/')
484             Function: Retrieves the prefix for the argument namespace
485             Returns : A prefix string
486             Args : A namespace URI
487              
488             =cut
489            
490             sub get_prefix_for_namespace {
491 0     0 1 0 my ( $self, $ns_uri ) = @_;
492            
493             # check argument
494 0 0       0 if ( not $ns_uri ) {
495 0         0 throw 'BadArgs' => "Need namespaces URI argument";
496             }
497            
498             # iterate over namespace/prefix pairs
499 0         0 my $namespaces = $self->get_namespaces;
500 0         0 for my $prefix ( keys %{ $namespaces } ) {
  0         0  
501 0 0       0 if ( $namespaces->{$prefix} eq $ns_uri ) {
502 0         0 return $prefix;
503             }
504             }
505            
506             # warn user
507 0         0 $logger->warn("No prefix for namespace $ns_uri");
508 0         0 return undef;
509             }
510              
511             =item get_meta()
512              
513             Retrieves the metadata for the element.
514              
515             Type : Accessor
516             Title : get_meta
517             Usage : my @meta = @{ $obj->get_meta };
518             Function: Retrieves the metadata for the element.
519             Returns : An array ref of Bio::Phylo::NeXML::Meta objects
520             Args : Optional: a list of CURIE predicates, in which case
521             the returned objects will be those matching these
522             predicates
523              
524             =cut
525              
526             sub get_meta {
527 67852     67852 1 79825 my $self = shift;
528 67852   100     118166 my $metas = $meta{ $self->get_id } || [];
529 67852 100       142951 if ( @_ ) {
530 52459         78313 my %predicates = map { $_ => 1 } @_;
  52459         129116  
531 52459         72470 my @matches = grep { $predicates{$_->get_predicate} } @{ $metas };
  12450         27825  
  52459         80013  
532 52459         135985 return \@matches;
533             }
534 15393         42377 return $metas;
535             }
536              
537             =item get_meta_object()
538              
539             Retrieves the metadata annotation object for the provided predicate
540              
541             Type : Accessor
542             Title : get_meta_object
543             Usage : my $title = $obj->get_meta_object('dc:title');
544             Function: Retrieves the metadata annotation value for the object.
545             Returns : An annotation value, i.e. the object of a triple
546             Args : Required: a CURIE predicate for which the annotation
547             value is returned
548             Note : This method returns the object for the first annotation
549             with the provided predicate. Keep this in mind when dealing
550             with an object that has multiple annotations with the same
551             predicate.
552              
553             =cut
554            
555             sub get_meta_object {
556 51659     51659 1 97548 my ( $self, $predicate ) = @_;
557 51659 50       82579 throw 'BadArgs' => "No CURIE provided" unless $predicate;
558 51659         65095 my ( $meta ) = @{ $self->get_meta($predicate) };
  51659         84113  
559 51659 100       92423 if ( $meta ) {
560 432         998 return $meta->get_object;
561             }
562             else {
563 51227         125751 return undef;
564             }
565             }
566              
567             =item get_tag()
568              
569             Retrieves tag name for the element.
570              
571             Type : Accessor
572             Title : get_tag
573             Usage : my $tag = $obj->get_tag;
574             Function: Gets the xml tag name for the object;
575             Returns : A tag name
576             Args : None.
577              
578             =cut
579              
580             sub get_tag {
581 1535     1535 1 2129 my $self = shift;
582 1535 100       2386 if ( my $tagstring = $tag{ $self->get_id } ) {
    50          
583 4         16 return $tagstring;
584             }
585             elsif ( looks_like_implementor $self, '_tag' ) {
586 1531         2833 return $self->_tag;
587             }
588             else {
589 0         0 return '';
590             }
591             }
592              
593             =item get_name()
594              
595             Gets invocant's name.
596              
597             Type : Accessor
598             Title : get_name
599             Usage : my $name = $obj->get_name;
600             Function: Returns the object's name.
601             Returns : A string
602             Args : None
603              
604             =cut
605              
606             sub get_name {
607 26216     26216 1 101775 my $self = shift;
608 26216         40486 my $id = $self->get_id;
609 26216 100       58388 if ( !$attributes{$id} ) {
610 213         520 $attributes{$id} = {};
611             }
612 26216 100       47601 if ( defined $attributes{$id}->{'label'} ) {
613 25719         77060 return $attributes{$id}->{'label'};
614             }
615             else {
616 497         1383 return '';
617             }
618             }
619              
620             =item get_xml_tag()
621              
622             Retrieves tag string
623              
624             Type : Accessor
625             Title : get_xml_tag
626             Usage : my $str = $obj->get_xml_tag;
627             Function: Gets the xml tag for the object;
628             Returns : A tag, i.e. pointy brackets
629             Args : Optional: a true value, to close an empty tag
630              
631             =cut
632              
633             sub get_xml_tag {
634 0     0 1 0 my ( $self, $closeme ) = @_;
635 0         0 my %attrs = %{ $self->get_attributes };
  0         0  
636 0         0 my $tag = $self->get_tag;
637 0         0 my $xml = '<' . $tag;
638 0         0 for my $key ( keys %attrs ) {
639 0         0 $xml .= ' ' . $key . '="' . encode_entities($attrs{$key}) . '"';
640             }
641 0         0 my $has_contents = 0;
642 0         0 my $meta = $self->get_meta;
643 0 0       0 if ( @{$meta} ) {
  0         0  
644 0         0 $xml .= '>'; # if not @{ $dictionaries };
645 0         0 $xml .= $_->to_xml for @{$meta};
  0         0  
646 0         0 $has_contents++;
647             }
648 0 0       0 if ($has_contents) {
649 0 0       0 $xml .= "</$tag>" if $closeme;
650             }
651             else {
652 0 0       0 $xml .= $closeme ? '/>' : '>';
653             }
654 0         0 return $xml;
655             }
656              
657             =item get_attributes()
658              
659             Retrieves attributes for the element.
660              
661             Type : Accessor
662             Title : get_attributes
663             Usage : my %attrs = %{ $obj->get_attributes };
664             Function: Gets the xml attributes for the object;
665             Returns : A hash reference
666             Args : None.
667             Comments: throws ObjectMismatch if no linked taxa object
668             can be found
669              
670             =cut
671              
672             my $add_namespaces_to_attributes = sub {
673             my ( $self, $attrs ) = @_;
674             my $i = 0;
675             my $inside_to_xml_recursion = 0;
676             CHECK_RECURSE: while ( my @frame = caller($i) ) {
677             if ( $frame[3] =~ m/::to_xml$/ ) {
678             $inside_to_xml_recursion++;
679             last CHECK_RECURSE if $inside_to_xml_recursion > 1;
680             }
681             $i++;
682             }
683             if ( $inside_to_xml_recursion <= 1 ) {
684             my $tmp_namespaces = get_namespaces();
685             for my $ns ( keys %{$tmp_namespaces} ) {
686             $attrs->{ 'xmlns:' . $ns } = $tmp_namespaces->{$ns};
687             }
688             }
689             return $attrs;
690             };
691             my $flatten_attributes = sub {
692             my $self = shift;
693             my $tempattrs = $attributes{ $self->get_id };
694             my $attrs;
695             if ($tempattrs) {
696             my %deref = %{$tempattrs};
697             $attrs = \%deref;
698             }
699             else {
700             $attrs = {};
701             }
702             return $attrs;
703             };
704              
705             sub get_attributes {
706 15014     15014 1 22445 my ( $self, $arg ) = @_;
707 15014         21710 my $attrs = $flatten_attributes->($self);
708            
709             # process the 'label' attribute: encode if there's anything there,
710             # otherwise delete the attribute
711 15014 50       24183 if ( $attrs->{'label'} ) {
712 0         0 $attrs->{'label'} = encode_entities($attrs->{'label'});
713             }
714             else {
715 15014         18248 delete $attrs->{'label'};
716             }
717            
718             # process the id attribute: if it's not there, autogenerate it, unless
719             # the object is explicitly not identifiable, in which case delete the
720             # attribute
721 15014 50       23966 if ( not $attrs->{'id'} ) {
722 15014         22923 $attrs->{'id'} = $self->get_xml_id;
723             }
724 15014 50 33     25407 if ( defined $self->is_identifiable and not $self->is_identifiable ) {
725 0         0 delete $attrs->{'id'};
726             }
727            
728             # process the about attribute
729 15014 50 33     19279 if ( not @{ $self->get_meta } and $attrs->{'about'} ) {
  15014         23200  
730 0         0 delete $attrs->{'about'};
731             }
732            
733             # set the otus attribute
734 15014 50       38864 if ( $self->can('get_taxa') ) {
735 0 0       0 if ( my $taxa = $self->get_taxa ) {
736 0 0       0 $attrs->{'otus'} = $taxa->get_xml_id
737             if looks_like_instance( $taxa, 'Bio::Phylo' );
738             }
739             else {
740 0         0 $logger->error("$self can link to a taxa element, but doesn't");
741             }
742             }
743            
744             # set the otu attribute
745 15014 50       28981 if ( $self->can('get_taxon') ) {
746 0 0       0 if ( my $taxon = $self->get_taxon ) {
747 0         0 $attrs->{'otu'} = $taxon->get_xml_id;
748             }
749             else {
750 0         0 $logger->info("No linked taxon found");
751 0         0 delete $attrs->{'otu'};
752             }
753             }
754            
755             # add the namespace attributes unless explicitly supressed
756 15014 50       22353 if ( not $self->is_ns_suppressed ) {
757 15014         22232 $attrs = $add_namespaces_to_attributes->( $self, $attrs )
758             }
759            
760             # now either return the whole hash or just one value if a
761             # key/attribute name was provided
762 15014 50       55855 return $arg ? $attrs->{$arg} : $attrs;
763             }
764              
765             =item get_xml_id()
766              
767             Retrieves xml id for the element.
768              
769             Type : Accessor
770             Title : get_xml_id
771             Usage : my $id = $obj->get_xml_id;
772             Function: Gets the xml id for the object;
773             Returns : An xml id
774             Args : None.
775              
776             =cut
777              
778             sub get_xml_id {
779 15016     15016 1 18369 my $self = shift;
780 15016 100       24500 if ( my $id = $id{ $self->get_id } ) {
781 13592         27515 return $id;
782             }
783             else {
784 1424         2409 my $xml_id = $self->get_tag;
785 1424         2537 my $obj_id = $self->get_id;
786 1424         9169 $xml_id =~ s/^(.).+(.)$/$1$2$obj_id/;
787 1424         5240 return $id{$obj_id} = $xml_id;
788             }
789             }
790              
791             =item get_base_uri()
792              
793             This utility method can be used to get the xml:base attribute, which specifies
794             a location for the object's XML serialization that potentially differs from
795             the physical location of the containing document.
796              
797             If no xml:base attribute has been defined on the focal object, this method
798             moves on, recursively, to containing objects (e.g. from node to tree to forest)
799             until such time that a base URI has been found.
800              
801             Type : Mutator
802             Title : get_base_uri
803             Usage : my $base = $obj->get_base_uri;
804             Function: Gets the xml:base attribute
805             Returns : A URI string
806             Args : None
807              
808             =cut
809              
810             sub get_base_uri {
811 111     111 1 139 my $self = shift;
812 111         212 while ( $self ) {
813 154         246 my $attrs = $flatten_attributes->($self);
814 154 100       299 if ( my $base = $attrs->{'xml:base'} ) {
815 3         15 $logger->info("Found xml:base attribute on $self: $base");
816 3         11 return $base;
817             }
818            
819 151         573 $logger->info("Traversing up to $self to locate xml:base");
820             # we do this because node objects are contained inside their
821             # parents, recursively, but node nexml elements aren't. it
822             # would be inefficient to traverse all the parent nodes when,
823             # logically, none of them could have an xml:base attribute
824             # that could apply to the original invocant. in fact, doing
825             # so could yield spurious results.
826 151 100       378 if ( $self->_type == _NODE_ ) {
827 2         6 $self = $self->get_tree;
828             }
829             else {
830 149         336 $self = $self->_get_container;
831             }
832             }
833 108         289 $logger->info("No xml:base attribute was found anywhere");
834 108         188 return undef;
835             }
836              
837             =item get_link()
838              
839             This returns a clickable link for the object. This has no relation to
840             the xml:base attribute, it is solely intended for serializations that
841             allow clickable links, such as SVG or RSS.
842              
843             Type : Accessor
844             Title : get_link
845             Usage : my $link = $obj->get_link();
846             Function: Returns a clickable link
847             Returns : url
848             Args : NONE
849              
850             =cut
851              
852 150     150 1 322 sub get_link { $url{ shift->get_id } }
853              
854             =item get_dom_elt()
855              
856             Type : Serializer
857             Title : get_dom_elt
858             Usage : $obj->get_dom_elt
859             Function: Generates a DOM element from the invocant
860             Returns : a DOM element object (default XML::Twig)
861             Args : DOM factory object
862              
863             =cut
864              
865             sub get_dom_elt {
866 0     0 1 0 my ( $self, $dom ) = @_;
867 0   0     0 $dom ||= Bio::Phylo::NeXML::DOM->get_dom;
868 0 0       0 unless ( looks_like_object $dom, _DOMCREATOR_ ) {
869 0         0 throw 'BadArgs' => 'DOM factory object not provided';
870             }
871 0         0 my $elt = $dom->create_element( '-tag' => $self->get_tag );
872 0         0 my %attrs = %{ $self->get_attributes };
  0         0  
873 0         0 for my $key ( keys %attrs ) {
874 0         0 $elt->set_attributes( $key => $attrs{$key} );
875             }
876 0         0 for my $meta ( @{ $self->get_meta } ) {
  0         0  
877 0         0 $elt->set_child( $meta->to_dom($dom) );
878             }
879              
880             #my $dictionaries = $self->get_dictionaries;
881             #if ( @{ $dictionaries } ) {
882             # $elt->set_child( $_->to_dom($dom) ) for @{ $dictionaries };
883             #}
884 0 0       0 if ( looks_like_implementor $self, 'get_sets' ) {
885 0         0 my $sets = $self->get_sets;
886 0         0 $elt->set_child( $_->to_dom($dom) ) for @{$sets};
  0         0  
887             }
888 0         0 return $elt;
889             }
890              
891             =back
892              
893             =head2 TESTS
894              
895             =over
896              
897             =item is_identifiable()
898              
899             By default, all XMLWritable objects are identifiable when serialized,
900             i.e. they have a unique id attribute. However, in some cases a serialized
901             object may not have an id attribute (governed by the nexml schema). This
902             method indicates whether that is the case.
903              
904             Type : Test
905             Title : is_identifiable
906             Usage : if ( $obj->is_identifiable ) { ... }
907             Function: Indicates whether IDs are generated
908             Returns : BOOLEAN
909             Args : NONE
910              
911             =cut
912              
913             sub is_identifiable {
914 16568     16568 1 20031 my $self = shift;
915 16568         26285 return $identifiable{ $self->get_id };
916             }
917             *get_identifiable = \&is_identifiable;
918              
919             =item is_ns_suppressed()
920              
921             Type : Test
922             Title : is_ns_suppressed
923             Usage : if ( $obj->is_ns_suppressed ) { ... }
924             Function: Indicates whether namespace attributes should not
925             be written on XML serialization
926             Returns : BOOLEAN
927             Args : NONE
928              
929             =cut
930              
931             sub is_ns_suppressed {
932 15125     15125 1 26360 return $suppress_ns{ shift->get_id };
933             }
934             *get_suppress_ns = \&is_ns_suppressed;
935            
936             =item is_equal()
937              
938             Tests whether the invocant and the argument are the same. Normally this is done
939             by comparing object identifiers, but if the argument is not an object but a string
940             then the string is taken to be a name with which to compare, e.g.
941             $taxon->is_equal('Homo sapiens')
942              
943             Type : Test
944             Title : is_equal
945             Usage : if ( $obj->is_equal($other) ) { ... }
946             Function: Tests whether the invocant and the argument are the same
947             Returns : BOOLEAN
948             Args : Object to compare with, or a string representing a
949             name to compare with the invocant's name
950              
951             =cut
952            
953             sub is_equal {
954 3489     3489 1 4605 my ($self,$other) = @_;
955 3489 50       5582 return ref $other ? $self->SUPER::is_equal($other) : $self->get_name eq $other;
956             }
957              
958             =back
959              
960             =head2 SERIALIZERS
961              
962             =over
963              
964             =item to_xml()
965              
966             Serializes invocant to XML.
967              
968             Type : XML serializer
969             Title : to_xml
970             Usage : my $xml = $obj->to_xml;
971             Function: Serializes $obj to xml
972             Returns : An xml string
973             Args : None
974              
975             =cut
976              
977             sub to_xml {
978 0     0 1   my $self = shift;
979 0           my $xml = '';
980 0 0         if ( $self->can('get_entities') ) {
981 0           for my $ent ( @{ $self->get_entities } ) {
  0            
982 0 0         if ( looks_like_implementor $ent, 'to_xml' ) {
983 0           $xml .= "\n" . $ent->to_xml;
984             }
985             }
986 0           $xml .= $self->sets_to_xml;
987             }
988 0 0         if ($xml) {
989 0           $xml = $self->get_xml_tag . $xml . sprintf('</%s>', $self->get_tag);
990             }
991             else {
992 0           $xml = $self->get_xml_tag(1);
993             }
994 0           return $xml;
995             }
996              
997             =item to_dom()
998              
999             Type : Serializer
1000             Title : to_dom
1001             Usage : $obj->to_dom
1002             Function: Generates a DOM subtree from the invocant and
1003             its contained objects
1004             Returns : a DOM element object (default: XML::Twig flavor)
1005             Args : DOM factory object
1006             Note : This is the generic function. It is redefined in the
1007             classes below.
1008              
1009             =cut
1010              
1011             sub to_dom {
1012 0     0 1   my ( $self, $dom ) = @_;
1013 0   0       $dom ||= Bio::Phylo::NeXML::DOM->get_dom;
1014 0 0         if ( looks_like_object $dom, _DOMCREATOR_ ) {
1015 0           my $elt = $self->get_dom_elt($dom);
1016 0 0         if ( $self->can('get_entities') ) {
1017 0           for my $ent ( @{ $self->get_entities } ) {
  0            
1018 0 0         if ( looks_like_implementor $ent, 'to_dom' ) {
1019 0           $elt->set_child( $ent->to_dom($dom) );
1020             }
1021             }
1022             }
1023 0           return $elt;
1024             }
1025             else {
1026 0           throw 'BadArgs' => 'DOM factory object not provided';
1027             }
1028             }
1029            
1030             =item to_json()
1031              
1032             Serializes object to JSON string
1033              
1034             Type : Serializer
1035             Title : to_json()
1036             Usage : print $obj->to_json();
1037             Function: Serializes object to JSON string
1038             Returns : String
1039             Args : None
1040             Comments:
1041              
1042             =cut
1043              
1044             sub to_json {
1045 0     0 1   looks_like_class('Bio::Phylo::NeXML::XML2JSON')->new->convert( shift->to_xml );
1046             }
1047              
1048             sub _json_data {
1049 0     0     my $self = shift;
1050 0           my %meta = map { $_->get_predicate => $_->get_object } @{ $self->get_meta };
  0            
  0            
1051 0           my %result = %{ $self->SUPER::_json_data };
  0            
1052 0           $result{$_} = $meta{$_} for keys %meta;
1053 0 0         $result{'name'} = $self->get_name if $self->get_name;
1054 0 0         $result{'link'} = $self->get_link if $self->get_link;
1055 0           return \%result;
1056             }
1057              
1058             =item to_cdao()
1059              
1060             Serializes object to CDAO RDF/XML string
1061              
1062             Type : Serializer
1063             Title : to_cdao()
1064             Usage : print $obj->to_cdao();
1065             Function: Serializes object to CDAO RDF/XML string
1066             Returns : String
1067             Args : None
1068             Comments:
1069              
1070             =cut
1071            
1072             sub to_cdao {
1073 0     0 1   return unparse(
1074             '-phylo' => shift,
1075             '-format' => 'cdao',
1076             );
1077             }
1078              
1079             sub _cleanup : Destructor {
1080 25926     25926   31812 my $self = shift;
1081            
1082             # this deserves an explanation. the issue is as follows: for the package
1083             # bio-phylo-megatree we have node objects that are persisted in a database
1084             # and accessed through an object-relational mapping provided by DBIx::Class.
1085             # these node objects are created and destroyed on the fly as a set of node
1086             # records (i.e. a tree) is traversed. this is the whole point of the package,
1087             # because it means large trees don't ever have to be kept in memory. however,
1088             # as a consequence, every time one of those ORM-backed nodes goes out of scope,
1089             # this destructor is called and all the @fields are cleaned up again. this
1090             # precludes computation and caching of node coordinates (or any other semantic
1091             # annotation) on such ORM-backed objects. the terrible, terrible fix for now is
1092             # to just assume that i) these annotations need to stay alive ii) we're not going
1093             # to have ID clashes (!!!!!), so iii) we just don't clean up after ourselves.
1094             # as a note to my future self: it would be a good idea to have a triple store-like
1095             # table to store the annotations, so they are persisted in the same way as the
1096             # node objects, bypassing this malarkey.
1097 25926 50       70884 if ( not $self->isa('DBIx::Class::Core') ) {
1098 25926         43353 my $id = $self->get_id;
1099 25926         39523 for my $field (@fields) {
1100 181482         250083 delete $field->{$id};
1101             }
1102             }
1103 51     51   102342 }
  51         119  
  51         248  
1104              
1105             =back
1106              
1107             =cut
1108              
1109             # podinherit_insert_token
1110              
1111             =head1 SEE ALSO
1112              
1113             There is a mailing list at L<https://groups.google.com/forum/#!forum/bio-phylo>
1114             for any user or developer questions and discussions.
1115              
1116             Also see the manual: L<Bio::Phylo::Manual> and L<http://rutgervos.blogspot.com>.
1117              
1118             =head1 CITATION
1119              
1120             If you use Bio::Phylo in published research, please cite it:
1121              
1122             B<Rutger A Vos>, B<Jason Caravas>, B<Klaas Hartmann>, B<Mark A Jensen>
1123             and B<Chase Miller>, 2011. Bio::Phylo - phyloinformatic analysis using Perl.
1124             I<BMC Bioinformatics> B<12>:63.
1125             L<http://dx.doi.org/10.1186/1471-2105-12-63>
1126              
1127             =cut
1128              
1129             }
1130             1;