Package Bio :: Package Phylo :: Module PhyloXMLIO
[hide private]
[frames] | no frames]

Source Code for Module Bio.Phylo.PhyloXMLIO

  1  # Copyright (C) 2009 by Eric Talevich (eric.talevich@gmail.com) 
  2  # This code is part of the Biopython distribution and governed by its 
  3  # license. Please see the LICENSE file that should have been included 
  4  # as part of this package. 
  5   
  6  """PhyloXML reader/parser, writer, and associated functions. 
  7   
  8  Instantiates tree elements from a parsed PhyloXML file, and constructs an XML 
  9  file from a `Bio.Phylo.PhyloXML` object. 
 10   
 11  About capitalization: 
 12   
 13  - phyloXML means the file format specification 
 14  - PhyloXML means the Biopython module `Bio.Phylo.PhyloXML` and its classes 
 15  - Phyloxml means the top-level class used by `PhyloXMLIO.read` (but not 
 16    `Bio.Phylo.read`!), containing a list of Phylogenies (objects derived from 
 17    `BaseTree.Tree`) 
 18  """ 
 19  __docformat__ = "restructuredtext en" 
 20   
 21  import sys 
 22   
 23  from Bio._py3k import basestring 
 24  from Bio._py3k import unicode 
 25   
 26  from Bio.Phylo import PhyloXML as PX 
 27   
 28  # For speed try to use cElementTree rather than ElementTree 
 29  try: 
 30      if (3, 0) <= sys.version_info[:2] <= (3, 1): 
 31          # Workaround for bug in python 3.0 and 3.1, 
 32          # see http://bugs.python.org/issue9257 
 33          from xml.etree import ElementTree as ElementTree 
 34      else: 
 35          from xml.etree import cElementTree as ElementTree 
 36  except ImportError: 
 37      from xml.etree import ElementTree as ElementTree 
 38   
 39  # Recognize the phyloXML namespace when parsing 
 40  # See http://effbot.org/zone/element-namespaces.htm 
 41  NAMESPACES = { 
 42      'phy': 'http://www.phyloxml.org', 
 43  } 
 44   
 45  try: 
 46      register_namespace = ElementTree.register_namespace 
 47  except AttributeError: 
 48      if not hasattr(ElementTree, '_namespace_map'): 
 49          # cElementTree needs the pure-Python xml.etree.ElementTree 
 50          from xml.etree import ElementTree as ET_py 
 51          ElementTree._namespace_map = ET_py._namespace_map 
 52   
53 - def register_namespace(prefix, uri):
54 ElementTree._namespace_map[uri] = prefix
55 56 for prefix, uri in NAMESPACES.items(): 57 register_namespace(prefix, uri) 58 59 # Tell ElementTree how to write to text handles 60 DEFAULT_ENCODING = ("unicode" if sys.version_info[0] >= 3 else "utf-8") 61 62
63 -class PhyloXMLError(Exception):
64 """Exception raised when PhyloXML object construction cannot continue. 65 66 XML syntax errors will be found and raised by the underlying ElementTree 67 module; this exception is for valid XML that breaks the phyloXML 68 specification. 69 """ 70 pass
71 72 73 # --------------------------------------------------------- 74 # Public API 75
76 -def read(file):
77 """Parse a phyloXML file or stream and build a tree of Biopython objects. 78 79 The children of the root node are phylogenies and possibly other arbitrary 80 (non-phyloXML) objects. 81 82 :returns: a single `Bio.Phylo.PhyloXML.Phyloxml` object. 83 """ 84 return Parser(file).read()
85 86
87 -def parse(file):
88 """Iterate over the phylogenetic trees in a phyloXML file. 89 90 This ignores any additional data stored at the top level, but may be more 91 memory-efficient than the `read` function. 92 93 :returns: a generator of `Bio.Phylo.PhyloXML.Phylogeny` objects. 94 """ 95 return Parser(file).parse()
96 97
98 -def write(obj, file, encoding=DEFAULT_ENCODING, indent=True):
99 """Write a phyloXML file. 100 101 :Parameters: 102 obj 103 an instance of `Phyloxml`, `Phylogeny` or `BaseTree.Tree`, or an 104 iterable of either of the latter two. The object will be converted 105 to a Phyloxml object before serialization. 106 file 107 either an open handle or a file name. 108 """ 109 def fix_single(tree): 110 if isinstance(tree, PX.Phylogeny): 111 return tree 112 if isinstance(tree, PX.Clade): 113 return tree.to_phylogeny() 114 if isinstance(tree, PX.BaseTree.Tree): 115 return PX.Phylogeny.from_tree(tree) 116 if isinstance(tree, PX.BaseTree.Clade): 117 return PX.Phylogeny.from_tree(PX.BaseTree.Tree(root=tree)) 118 else: 119 raise ValueError("iterable must contain Tree or Clade types")
120 121 if isinstance(obj, PX.Phyloxml): 122 pass 123 elif (isinstance(obj, PX.BaseTree.Tree) or 124 isinstance(obj, PX.BaseTree.Clade)): 125 obj = fix_single(obj).to_phyloxml() 126 elif hasattr(obj, '__iter__'): 127 obj = PX.Phyloxml({}, phylogenies=(fix_single(t) for t in obj)) 128 else: 129 raise ValueError("First argument must be a Phyloxml, Phylogeny, " 130 "Tree, or iterable of Trees or Phylogenies.") 131 return Writer(obj).write(file, encoding=encoding, indent=indent) 132 133 134 # --------------------------------------------------------- 135 # Functions I wish ElementTree had 136
137 -def _local(tag):
138 """Extract the local tag from a namespaced tag name.""" 139 if tag[0] == '{': 140 return tag[tag.index('}') + 1:] 141 return tag
142 143
144 -def _split_namespace(tag):
145 """Split a tag into namespace and local tag strings.""" 146 try: 147 return tag[1:].split('}', 1) 148 except: 149 return ('', tag)
150 151
152 -def _ns(tag, namespace=NAMESPACES['phy']):
153 """Format an XML tag with the given namespace.""" 154 return '{%s}%s' % (namespace, tag)
155 156
157 -def _get_child_as(parent, tag, construct):
158 """Find a child node by tag, and pass it through a constructor. 159 160 Returns None if no matching child is found. 161 """ 162 child = parent.find(_ns(tag)) 163 if child is not None: 164 return construct(child)
165 166
167 -def _get_child_text(parent, tag, construct=unicode):
168 """Find a child node by tag; pass its text through a constructor. 169 170 Returns None if no matching child is found. 171 """ 172 child = parent.find(_ns(tag)) 173 if child is not None and child.text: 174 return construct(child.text)
175 176
177 -def _get_children_as(parent, tag, construct):
178 """Find child nodes by tag; pass each through a constructor. 179 180 Returns an empty list if no matching child is found. 181 """ 182 return [construct(child) for child in 183 parent.findall(_ns(tag))]
184 185
186 -def _get_children_text(parent, tag, construct=unicode):
187 """Find child nodes by tag; pass each node's text through a constructor. 188 189 Returns an empty list if no matching child is found. 190 """ 191 return [construct(child.text) for child in 192 parent.findall(_ns(tag)) 193 if child.text]
194 195
196 -def _indent(elem, level=0):
197 """Add line breaks and indentation to ElementTree in-place. 198 199 Sources: 200 201 - http://effbot.org/zone/element-lib.htm#prettyprint 202 - http://infix.se/2007/02/06/gentlemen-indent-your-xml 203 """ 204 i = "\n" + level * " " 205 if len(elem): 206 if not elem.text or not elem.text.strip(): 207 elem.text = i + " " 208 for e in elem: 209 _indent(e, level + 1) 210 if not e.tail or not e.tail.strip(): 211 e.tail = i + " " 212 if not e.tail or not e.tail.strip(): 213 e.tail = i 214 else: 215 if level and (not elem.tail or not elem.tail.strip()): 216 elem.tail = i
217 218 # --------------------------------------------------------- 219 # INPUT 220 # --------------------------------------------------------- 221 222
223 -def _str2bool(text):
224 if text == 'true' or text == '1': 225 return True 226 if text == 'false' or text == '0': 227 return False 228 raise ValueError('String could not be converted to boolean: ' + text)
229 230
231 -def _dict_str2bool(dct, keys):
232 out = dct.copy() 233 for key in keys: 234 if key in out: 235 out[key] = _str2bool(out[key]) 236 return out
237 238
239 -def _int(text):
240 if text is not None: 241 try: 242 return int(text) 243 except Exception: 244 return None
245 246
247 -def _float(text):
248 if text is not None: 249 try: 250 return float(text) 251 except Exception: 252 return None
253 254
255 -def _collapse_wspace(text):
256 """Replace all spans of whitespace with a single space character. 257 258 Also remove leading and trailing whitespace. See "Collapse Whitespace 259 Policy" in the phyloXML spec glossary: 260 http://phyloxml.org/documentation/version_100/phyloxml.xsd.html#Glossary 261 """ 262 if text is not None: 263 return ' '.join(text.split())
264 265 266 # NB: Not currently used
267 -def _replace_wspace(text):
268 """Replace tab, LF and CR characters with spaces, but don't collapse. 269 270 See "Replace Whitespace Policy" in the phyloXML spec glossary: 271 http://phyloxml.org/documentation/version_100/phyloxml.xsd.html#Glossary 272 """ 273 for char in ('\t', '\n', '\r'): 274 if char in text: 275 text = text.replace(char, ' ') 276 return text
277 278
279 -class Parser(object):
280 """Methods for parsing all phyloXML nodes from an XML stream. 281 282 To minimize memory use, the tree of ElementTree parsing events is cleared 283 after completing each phylogeny, clade, and top-level 'other' element. 284 Elements below the clade level are kept in memory until parsing of the 285 current clade is finished -- this shouldn't be a problem because clade is 286 the only recursive element, and non-clade nodes below this level are of 287 bounded size. 288 """ 289
290 - def __init__(self, file):
291 # Get an iterable context for XML parsing events 292 context = iter(ElementTree.iterparse(file, events=('start', 'end'))) 293 event, root = next(context) 294 self.root = root 295 self.context = context
296
297 - def read(self):
298 """Parse the phyloXML file and create a single Phyloxml object.""" 299 phyloxml = PX.Phyloxml(dict((_local(key), val) 300 for key, val in self.root.items())) 301 other_depth = 0 302 for event, elem in self.context: 303 namespace, localtag = _split_namespace(elem.tag) 304 if event == 'start': 305 if namespace != NAMESPACES['phy']: 306 other_depth += 1 307 continue 308 if localtag == 'phylogeny': 309 phylogeny = self._parse_phylogeny(elem) 310 phyloxml.phylogenies.append(phylogeny) 311 if event == 'end' and namespace != NAMESPACES['phy']: 312 # Deal with items not specified by phyloXML 313 other_depth -= 1 314 if other_depth == 0: 315 # We're directly under the root node -- evaluate 316 otr = self.other(elem, namespace, localtag) 317 phyloxml.other.append(otr) 318 self.root.clear() 319 return phyloxml
320
321 - def parse(self):
322 """Parse the phyloXML file incrementally and return each phylogeny.""" 323 phytag = _ns('phylogeny') 324 for event, elem in self.context: 325 if event == 'start' and elem.tag == phytag: 326 yield self._parse_phylogeny(elem)
327 328 # Special parsing cases -- incremental, using self.context 329
330 - def _parse_phylogeny(self, parent):
331 """Parse a single phylogeny within the phyloXML tree. 332 333 Recursively builds a phylogenetic tree with help from parse_clade, then 334 clears the XML event history for the phylogeny element and returns 335 control to the top-level parsing function. 336 """ 337 phylogeny = PX.Phylogeny(**_dict_str2bool(parent.attrib, 338 ['rooted', 'rerootable'])) 339 list_types = { 340 # XML tag, plural attribute 341 'confidence': 'confidences', 342 'property': 'properties', 343 'clade_relation': 'clade_relations', 344 'sequence_relation': 'sequence_relations', 345 } 346 for event, elem in self.context: 347 namespace, tag = _split_namespace(elem.tag) 348 if event == 'start' and tag == 'clade': 349 assert phylogeny.root is None, \ 350 "Phylogeny object should only have 1 clade" 351 phylogeny.root = self._parse_clade(elem) 352 continue 353 if event == 'end': 354 if tag == 'phylogeny': 355 parent.clear() 356 break 357 # Handle the other non-recursive children 358 if tag in list_types: 359 getattr(phylogeny, list_types[tag]).append( 360 getattr(self, tag)(elem)) 361 # Complex types 362 elif tag in ('date', 'id'): 363 setattr(phylogeny, tag, getattr(self, tag)(elem)) 364 # Simple types 365 elif tag in ('name', 'description'): 366 setattr(phylogeny, tag, _collapse_wspace(elem.text)) 367 # Unknown tags 368 elif namespace != NAMESPACES['phy']: 369 phylogeny.other.append(self.other(elem, namespace, tag)) 370 parent.clear() 371 else: 372 # NB: This shouldn't happen in valid files 373 raise PhyloXMLError('Misidentified tag: ' + tag) 374 return phylogeny
375 376 _clade_complex_types = ['color', 'events', 'binary_characters', 'date'] 377 _clade_list_types = { 378 'confidence': 'confidences', 379 'distribution': 'distributions', 380 'reference': 'references', 381 'property': 'properties', 382 } 383 _clade_tracked_tags = set(_clade_complex_types).union(_clade_list_types.keys()).union( 384 ['branch_length', 'name', 'node_id', 'width']) 385
386 - def _parse_clade(self, parent):
387 """Parse a Clade node and its children, recursively.""" 388 clade = PX.Clade(**parent.attrib) 389 if clade.branch_length is not None: 390 clade.branch_length = float(clade.branch_length) 391 # NB: Only evaluate nodes at the current level 392 tag_stack = [] 393 for event, elem in self.context: 394 namespace, tag = _split_namespace(elem.tag) 395 if event == 'start': 396 if tag == 'clade': 397 clade.clades.append(self._parse_clade(elem)) 398 continue 399 if tag == 'taxonomy': 400 clade.taxonomies.append(self._parse_taxonomy(elem)) 401 continue 402 if tag == 'sequence': 403 clade.sequences.append(self._parse_sequence(elem)) 404 continue 405 if tag in self._clade_tracked_tags: 406 tag_stack.append(tag) 407 if event == 'end': 408 if tag == 'clade': 409 elem.clear() 410 break 411 if tag != tag_stack[-1]: 412 continue 413 tag_stack.pop() 414 # Handle the other non-recursive children 415 if tag in self._clade_list_types: 416 getattr(clade, self._clade_list_types[tag]).append( 417 getattr(self, tag)(elem)) 418 elif tag in self._clade_complex_types: 419 setattr(clade, tag, getattr(self, tag)(elem)) 420 elif tag == 'branch_length': 421 # NB: possible collision with the attribute 422 if clade.branch_length is not None: 423 raise PhyloXMLError( 424 'Attribute branch_length was already set ' 425 'for this Clade.') 426 clade.branch_length = _float(elem.text) 427 elif tag == 'width': 428 clade.width = _float(elem.text) 429 elif tag == 'name': 430 clade.name = _collapse_wspace(elem.text) 431 elif tag == 'node_id': 432 clade.node_id = PX.Id(elem.text.strip(), 433 elem.attrib.get('provider')) 434 elif namespace != NAMESPACES['phy']: 435 clade.other.append(self.other(elem, namespace, tag)) 436 elem.clear() 437 else: 438 raise PhyloXMLError('Misidentified tag: ' + tag) 439 return clade
440
441 - def _parse_sequence(self, parent):
442 sequence = PX.Sequence(**parent.attrib) 443 for event, elem in self.context: 444 namespace, tag = _split_namespace(elem.tag) 445 if event == 'end': 446 if tag == 'sequence': 447 parent.clear() 448 break 449 if tag in ('accession', 'mol_seq', 'uri', 450 'domain_architecture'): 451 setattr(sequence, tag, getattr(self, tag)(elem)) 452 elif tag == 'annotation': 453 sequence.annotations.append(self.annotation(elem)) 454 elif tag == 'name': 455 sequence.name = _collapse_wspace(elem.text) 456 elif tag in ('symbol', 'location'): 457 setattr(sequence, tag, elem.text) 458 elif namespace != NAMESPACES['phy']: 459 sequence.other.append(self.other(elem, namespace, tag)) 460 parent.clear() 461 return sequence
462
463 - def _parse_taxonomy(self, parent):
464 taxonomy = PX.Taxonomy(**parent.attrib) 465 for event, elem in self.context: 466 namespace, tag = _split_namespace(elem.tag) 467 if event == 'end': 468 if tag == 'taxonomy': 469 parent.clear() 470 break 471 if tag in ('id', 'uri'): 472 setattr(taxonomy, tag, getattr(self, tag)(elem)) 473 elif tag == 'common_name': 474 taxonomy.common_names.append(_collapse_wspace(elem.text)) 475 elif tag == 'synonym': 476 taxonomy.synonyms.append(elem.text) 477 elif tag in ('code', 'scientific_name', 'authority', 'rank'): 478 # ENH: check_str on rank 479 setattr(taxonomy, tag, elem.text) 480 elif namespace != NAMESPACES['phy']: 481 taxonomy.other.append(self.other(elem, namespace, tag)) 482 parent.clear() 483 return taxonomy
484
485 - def other(self, elem, namespace, localtag):
486 return PX.Other(localtag, namespace, elem.attrib, 487 value=elem.text and elem.text.strip() or None, 488 children=[self.other(child, *_split_namespace(child.tag)) 489 for child in elem])
490 491 # Complex types 492
493 - def accession(self, elem):
494 return PX.Accession(elem.text.strip(), elem.get('source'))
495
496 - def annotation(self, elem):
497 return PX.Annotation( 498 desc=_collapse_wspace(_get_child_text(elem, 'desc')), 499 confidence=_get_child_as(elem, 'confidence', self.confidence), 500 properties=_get_children_as(elem, 'property', self.property), 501 uri=_get_child_as(elem, 'uri', self.uri), 502 **elem.attrib)
503
504 - def binary_characters(self, elem):
505 def bc_getter(elem): 506 return _get_children_text(elem, 'bc')
507 return PX.BinaryCharacters( 508 type=elem.get('type'), 509 gained_count=_int(elem.get('gained_count')), 510 lost_count=_int(elem.get('lost_count')), 511 present_count=_int(elem.get('present_count')), 512 absent_count=_int(elem.get('absent_count')), 513 # Flatten BinaryCharacterList sub-nodes into lists of strings 514 gained=_get_child_as(elem, 'gained', bc_getter), 515 lost=_get_child_as(elem, 'lost', bc_getter), 516 present=_get_child_as(elem, 'present', bc_getter), 517 absent=_get_child_as(elem, 'absent', bc_getter))
518
519 - def clade_relation(self, elem):
520 return PX.CladeRelation( 521 elem.get('type'), elem.get('id_ref_0'), elem.get('id_ref_1'), 522 distance=elem.get('distance'), 523 confidence=_get_child_as(elem, 'confidence', self.confidence))
524
525 - def color(self, elem):
526 red, green, blue = (_get_child_text(elem, color, int) for color in 527 ('red', 'green', 'blue')) 528 return PX.BranchColor(red, green, blue)
529
530 - def confidence(self, elem):
531 return PX.Confidence( 532 _float(elem.text), 533 elem.get('type'))
534
535 - def date(self, elem):
536 return PX.Date( 537 unit=elem.get('unit'), 538 desc=_collapse_wspace(_get_child_text(elem, 'desc')), 539 value=_get_child_text(elem, 'value', float), 540 minimum=_get_child_text(elem, 'minimum', float), 541 maximum=_get_child_text(elem, 'maximum', float), 542 )
543
544 - def distribution(self, elem):
545 return PX.Distribution( 546 desc=_collapse_wspace(_get_child_text(elem, 'desc')), 547 points=_get_children_as(elem, 'point', self.point), 548 polygons=_get_children_as(elem, 'polygon', self.polygon))
549
550 - def domain(self, elem):
551 return PX.ProteinDomain(elem.text.strip(), 552 int(elem.get('from')) - 1, 553 int(elem.get('to')), 554 confidence=_float(elem.get('confidence')), 555 id=elem.get('id'))
556
557 - def domain_architecture(self, elem):
558 return PX.DomainArchitecture( 559 length=int(elem.get('length')), 560 domains=_get_children_as(elem, 'domain', self.domain))
561
562 - def events(self, elem):
563 return PX.Events( 564 type=_get_child_text(elem, 'type'), 565 duplications=_get_child_text(elem, 'duplications', int), 566 speciations=_get_child_text(elem, 'speciations', int), 567 losses=_get_child_text(elem, 'losses', int), 568 confidence=_get_child_as(elem, 'confidence', self.confidence))
569
570 - def id(self, elem):
571 provider = elem.get('provider') or elem.get('type') 572 return PX.Id(elem.text.strip(), provider)
573
574 - def mol_seq(self, elem):
575 is_aligned = elem.get('is_aligned') 576 if is_aligned is not None: 577 is_aligned = _str2bool(is_aligned) 578 return PX.MolSeq(elem.text.strip(), is_aligned=is_aligned)
579
580 - def point(self, elem):
581 return PX.Point( 582 elem.get('geodetic_datum'), 583 _get_child_text(elem, 'lat', float), 584 _get_child_text(elem, 'long', float), 585 alt=_get_child_text(elem, 'alt', float), 586 alt_unit=elem.get('alt_unit'))
587
588 - def polygon(self, elem):
589 return PX.Polygon( 590 points=_get_children_as(elem, 'point', self.point))
591
592 - def property(self, elem):
593 return PX.Property(elem.text.strip(), 594 elem.get('ref'), 595 elem.get('applies_to'), 596 elem.get('datatype'), 597 unit=elem.get('unit'), 598 id_ref=elem.get('id_ref'))
599
600 - def reference(self, elem):
601 return PX.Reference( 602 doi=elem.get('doi'), 603 desc=_get_child_text(elem, 'desc'))
604
605 - def sequence_relation(self, elem):
606 return PX.SequenceRelation( 607 elem.get('type'), elem.get('id_ref_0'), elem.get('id_ref_1'), 608 distance=_float(elem.get('distance')), 609 confidence=_get_child_as(elem, 'confidence', self.confidence))
610
611 - def uri(self, elem):
612 return PX.Uri(elem.text.strip(), 613 desc=_collapse_wspace(elem.get('desc')), 614 type=elem.get('type'))
615 616 617 # --------------------------------------------------------- 618 # OUTPUT 619 # --------------------------------------------------------- 620
621 -def _serialize(value):
622 """Convert a Python primitive to a phyloXML-compatible Unicode string.""" 623 if isinstance(value, float): 624 return unicode(value).upper() 625 elif isinstance(value, bool): 626 return unicode(value).lower() 627 return unicode(value)
628 629
630 -def _clean_attrib(obj, attrs):
631 """Create a dictionary from an object's specified, non-None attributes.""" 632 out = {} 633 for key in attrs: 634 val = getattr(obj, key) 635 if val is not None: 636 out[key] = _serialize(val) 637 return out
638 639
640 -def _handle_complex(tag, attribs, subnodes, has_text=False):
641 def wrapped(self, obj): 642 elem = ElementTree.Element(tag, _clean_attrib(obj, attribs)) 643 for subn in subnodes: 644 if isinstance(subn, basestring): 645 # singular object: method and attribute names are the same 646 if getattr(obj, subn) is not None: 647 elem.append(getattr(self, subn)(getattr(obj, subn))) 648 else: 649 # list: singular method, pluralized attribute name 650 method, plural = subn 651 for item in getattr(obj, plural): 652 elem.append(getattr(self, method)(item)) 653 if has_text: 654 elem.text = _serialize(obj.value) 655 return elem
656 wrapped.__doc__ = "Serialize a %s and its subnodes, in order." % tag 657 return wrapped 658 659
660 -def _handle_simple(tag):
661 def wrapped(self, obj): 662 elem = ElementTree.Element(tag) 663 elem.text = _serialize(obj) 664 return elem
665 wrapped.__doc__ = "Serialize a simple %s node." % tag 666 return wrapped 667 668
669 -class Writer(object):
670 """Methods for serializing a PhyloXML object to XML.""" 671
672 - def __init__(self, phyloxml):
673 """Build an ElementTree from a PhyloXML object.""" 674 assert isinstance(phyloxml, PX.Phyloxml), "Not a Phyloxml object" 675 self._tree = ElementTree.ElementTree(self.phyloxml(phyloxml))
676
677 - def write(self, file, encoding=DEFAULT_ENCODING, indent=True):
678 if indent: 679 _indent(self._tree.getroot()) 680 self._tree.write(file, encoding) 681 return len(self._tree.getroot())
682 683 # Convert classes to ETree elements 684
685 - def phyloxml(self, obj):
686 elem = ElementTree.Element('phyloxml', obj.attributes) # Namespaces 687 for tree in obj.phylogenies: 688 elem.append(self.phylogeny(tree)) 689 for otr in obj.other: 690 elem.append(self.other(otr)) 691 return elem
692
693 - def other(self, obj):
694 elem = ElementTree.Element(_ns(obj.tag, obj.namespace), obj.attributes) 695 elem.text = obj.value 696 for child in obj.children: 697 elem.append(self.other(child)) 698 return elem
699 700 phylogeny = _handle_complex('phylogeny', 701 ('rooted', 'rerootable', 702 'branch_length_unit', 'type'), 703 ('name', 704 'id', 705 'description', 706 'date', 707 ('confidence', 'confidences'), 708 'clade', 709 ('clade_relation', 'clade_relations'), 710 ('sequence_relation', 711 'sequence_relations'), 712 ('property', 'properties'), 713 ('other', 'other'), 714 )) 715 716 clade = _handle_complex('clade', ('id_source',), 717 ('name', 718 'branch_length', 719 ('confidence', 'confidences'), 720 'width', 721 'color', 722 'node_id', 723 ('taxonomy', 'taxonomies'), 724 ('sequence', 'sequences'), 725 'events', 726 'binary_characters', 727 ('distribution', 'distributions'), 728 'date', 729 ('reference', 'references'), 730 ('property', 'properties'), 731 ('clade', 'clades'), 732 ('other', 'other'), 733 )) 734 735 accession = _handle_complex('accession', ('source',), 736 (), has_text=True) 737 738 annotation = _handle_complex('annotation', 739 ('ref', 'source', 'evidence', 'type'), 740 ('desc', 741 'confidence', 742 ('property', 'properties'), 743 'uri', 744 )) 745
746 - def binary_characters(self, obj):
747 """Serialize a binary_characters node and its subnodes.""" 748 elem = ElementTree.Element('binary_characters', 749 _clean_attrib(obj, 750 ('type', 'gained_count', 'lost_count', 751 'present_count', 'absent_count'))) 752 for subn in ('gained', 'lost', 'present', 'absent'): 753 subelem = ElementTree.Element(subn) 754 for token in getattr(obj, subn): 755 subelem.append(self.bc(token)) 756 elem.append(subelem) 757 return elem
758 759 clade_relation = _handle_complex('clade_relation', 760 ('id_ref_0', 'id_ref_1', 761 'distance', 'type'), 762 ('confidence',)) 763 764 color = _handle_complex('color', (), ('red', 'green', 'blue')) 765 766 confidence = _handle_complex('confidence', ('type',), 767 (), has_text=True) 768 769 date = _handle_complex('date', ('unit',), 770 ('desc', 'value', 'minimum', 'maximum')) 771 772 distribution = _handle_complex('distribution', (), 773 ('desc', 774 ('point', 'points'), 775 ('polygon', 'polygons'), 776 )) 777
778 - def domain(self, obj):
779 """Serialize a domain node.""" 780 elem = ElementTree.Element('domain', 781 {'from': str(obj.start + 1), 'to': str(obj.end)}) 782 if obj.confidence is not None: 783 elem.set('confidence', _serialize(obj.confidence)) 784 if obj.id is not None: 785 elem.set('id', obj.id) 786 elem.text = _serialize(obj.value) 787 return elem
788 789 domain_architecture = _handle_complex('domain_architecture', 790 ('length',), 791 (('domain', 'domains'),)) 792 793 events = _handle_complex('events', (), 794 ('type', 795 'duplications', 796 'speciations', 797 'losses', 798 'confidence', 799 )) 800 801 id = _handle_complex('id', ('provider',), (), has_text=True) 802 803 mol_seq = _handle_complex('mol_seq', ('is_aligned',), 804 (), has_text=True) 805 806 node_id = _handle_complex('node_id', ('provider',), (), has_text=True) 807 808 point = _handle_complex('point', ('geodetic_datum', 'alt_unit'), 809 ('lat', 'long', 'alt')) 810 811 polygon = _handle_complex('polygon', (), (('point', 'points'),)) 812 813 property = _handle_complex('property', 814 ('ref', 'unit', 'datatype', 815 'applies_to', 'id_ref'), 816 (), has_text=True) 817 818 reference = _handle_complex('reference', ('doi',), ('desc',)) 819 820 sequence = _handle_complex('sequence', 821 ('type', 'id_ref', 'id_source'), 822 ('symbol', 823 'accession', 824 'name', 825 'location', 826 'mol_seq', 827 'uri', 828 ('annotation', 'annotations'), 829 'domain_architecture', 830 ('other', 'other'), 831 )) 832 833 sequence_relation = _handle_complex('sequence_relation', 834 ('id_ref_0', 'id_ref_1', 835 'distance', 'type'), 836 ('confidence',)) 837 838 taxonomy = _handle_complex('taxonomy', 839 ('id_source',), 840 ('id', 841 'code', 842 'scientific_name', 843 'authority', 844 ('common_name', 'common_names'), 845 ('synonym', 'synonyms'), 846 'rank', 847 'uri', 848 ('other', 'other'), 849 )) 850 851 uri = _handle_complex('uri', ('desc', 'type'), (), has_text=True) 852 853 # Primitive types 854 855 # Floating point 856 alt = _handle_simple('alt') 857 branch_length = _handle_simple('branch_length') 858 lat = _handle_simple('lat') 859 long = _handle_simple('long') 860 maximum = _handle_simple('maximum') 861 minimum = _handle_simple('minimum') 862 value = _handle_simple('value') 863 width = _handle_simple('width') 864 865 # Integers 866 blue = _handle_simple('blue') 867 duplications = _handle_simple('duplications') 868 green = _handle_simple('green') 869 losses = _handle_simple('losses') 870 red = _handle_simple('red') 871 speciations = _handle_simple('speciations') 872 873 # Strings 874 bc = _handle_simple('bc') 875 code = _handle_simple('code') 876 common_name = _handle_simple('common_name') 877 desc = _handle_simple('desc') 878 description = _handle_simple('description') 879 location = _handle_simple('location') 880 name = _handle_simple('name') 881 rank = _handle_simple('rank') 882 scientific_name = _handle_simple('scientific_name') 883 symbol = _handle_simple('symbol') 884 synonym = _handle_simple('synonym') 885 type = _handle_simple('type')
886