Each instance of this class is a container for all the photos that depict members of a specific taxon. Instances are arrange in the form of a tree. The root node is a container for the entire catalog, and represents the root taxon of birds, Class Aves.
Here is the interface. The constructor takes one argument: the taxon related to this node.
# - - - - - c l a s s T a x o n P h o t o S e t - - - - -
class TaxonPhotoSet:
"""Represents all photos contained in a specific taxon.
Exports:
TaxonPhotoSet ( taxon ):
[ taxon is a txny.Taxon instance ->
return a new, empty TaxonPhotoSet for that taxon ]
.taxon: [ as passed to constructor; read-only ]
.nContained: [ number of photos in self's subtree ]
.nReferred: [ number of photos referred to self's taxon ]
The .addArchImage() method is used to load
catalog entries into the tree. For each photo, this
method is called once for each kind of bird in the photo.
Note the precondition that the specified birdId must be referred to a taxon that is either
self or a descendant of self. It simplifies the logic of
adding a new photo to know that the photo goes somewhere in
the correct subtree. It is no burden on the caller who
adds the photo to the root of the tree, since all birds are
in class Aves. See Section 11.2, “TaxonPhotoSet.addArchImage(): Add one
photo”.
.addArchImage ( birdId, archImage ):
[ birdId is referred to a taxon contained in self.taxon ->
(birdId is a composite bird form code as an
abbr.BirdId object) and
(archImage is an image catalog entry as a
archindex.ArchImage instance) ->
self := self with archImage added under birdId ]
The .walk() method walks the tree, starting
at the given node, and then recursively visiting its
descendants. This method is called to generate the various
taxonomic headings on the index page. See Section 11.5, “TaxonPhotoSet.walk(): Walk the tree”.
.walk():
[ generate self, followed by the descendants of self,
in preorder traversal ]
Because the presentation of a taxon with only one form is
different than the presentation of a multi-form taxon, the
.nForms() method allows the caller to
determine the number of forms. See Section 11.6, “TaxonPhotoSet.nForms(): Number of
contained forms”.
.nForms(): [ returns the number of forms in self ]
The .genForms() method generates the
contained FormPhotoSet objects in order.
See Section 11.7, “TaxonPhotoSet.genForms(): Generate
referred FormPhotoSet objects”.
.genForms():
[ generate the FormPhotoSet objects in self, in
ascending order by inverted English name ]
Here are the internals of the instance. Each node
maintains two counters. The .nContained
attribute counts all the photos ever added to this node's
subtree. The .nReferred attribute is a
count of only those photos referred to this node's taxon.
Each TaxonPhotoSet instance is a container
for two kinds of objects: child taxa, and FormPhotoSet instances for any photos that may be
referred to this taxon.
One of the author's favorite standard Python tricks serves nicely to structure both these container relationships: we keep the contained values in a dictionary, and access them using a key that happens to sort in the desired access order.
The standard Python trick is: use the dictionary's .keys() method to extract a list of keys; sort
that list; and then go through the dictionary in order
using that list.
Dictionary .childMap contains the child
TaxonPhotoSet elements as values. In order
to access the children in taxonomic order, we use the txKey attribute of the taxon as the key, which is
a string of digits that orders taxa in phylogenetic order.
State/Internals:
.__childMap:
[ a dictionary whose values are the child TaxonPhotoSet
nodes of self, and each corresponding key is the
.txKey attribute of self's taxon ]
The desired order of form names within a taxon is alphabetical order by the inverted English name.
.__formMap:
[ a dictionary whose values are the FormPhotoSet
objects holding photos referred to self.taxon,
and each corresponding key is the inverted
English name for that form ]
"""
The constructor does not do much. It stores the
specified taxon, zeroes the counts of
contained photos, and sets up the internal data
structures.
# - - - T a x o n P h o t o S e t . _ _ i n i t _ _ - - -
def __init__ ( self, taxon ):
"""Constructor for TaxonPhotoSet"""
self.taxon = taxon
self.nContained = 0
self.nReferred = 0
self.__childMap = {}
self.__formMap = {}
Because multiple forms of birds may appear in one photo,
each photo may be added under multiple names. In that
case, the .addArchImage() method is called
once for each name.
Hence, this method operates on not just a catalog entry
(as an ArchImage object from the archindex.py module), but also on a BirdId object (from the abbr.py module) that represents the kind of
bird.
# - - - T a x o n P h o t o S e t . a d d A r c h I m a g e - - -
def addArchImage ( self, birdId, archImage ):
"""Add one photo to the report under a given birdId
"""
Adding a new photo to the structure always starts at the
root node, by calling its .addArchImage()
method. In general, this operation is recursive. It may
involve the addition of multiple new taxa to the tree,
starting at the top and terminating at the taxon to which
the photo is referred.
Depending on whether the given birdId is
referred to self's taxon or not, there are two cases.
Basis case: if the birdId is referred
to self's taxon, no new child nodes need to be added
under this one. First, make sure that self has a
FormPhotoSet for this bird name, then
add the photo to that FormPhotoSet.
Recursive case: if the birdId is
referred to a descendant of self, first insure that
nodes are added to self's subtree down to the
referred taxon's node, then add the photo to that
node.
The first order of business is to separate the two
cases. The taxon attribute of a BirdId object is the taxon to which that form is
referred. The comparison function (.__cmp__()) in the Taxon class
compares instances according to their
txKey attribute, so we can use the
ordinary Python “==”
comparison to see if they are the same taxon.
For the internal methods that handle the two cases, see
Section 11.3, “TaxonPhotoSet.__addReferred(): Basis case” and Section 11.4, “TaxonPhotoSet.__addContained():
Recursive case”.
#-- 1 --
# [ if self.taxon is the same as birdId.taxon ->
# self.nReferred +:= 1
# self.__formMap := self.__formMap with archImage added
# under the name from birdId
# else ->
# self.__childMap := self.__childMap with new descendant
# nodes added as necessary down to birdId.taxon,
# and archImage added under the name from birdId to
# the node for birdId.taxon ]
if self.taxon == birdId.taxon:
#-- 1.1 --
self.__addReferred ( birdId, archImage )
self.nReferred += 1
else:
#-- 1.2 --
self.__addContained ( birdId, archImage )
The final step is bookkeeping: counting the number of photos in this subtree.
#-- 2 --
self.nContained += 1
This method adds the new photo to the forms in self.
# - - - T a x o n P h o t o S e t . _ _ a d d R e f e r r e d - - -
def __addReferred ( self, birdId, archImage ):
"""Add a catalog entry to one of the FormPhotoSets in self.
[ (birdId is a BirdId referred to self.taxon) and
(archImage is a catalog entry as an archindex.ArchImage) ->
self.__formMap := self.__formMap with archImage added
under the name from birdId ]
"""
The first step is to make sure there is a FormPhotoSet in self.__formMap
under the name used by birdId. See
Section 12, “class FormPhotoSet: All photos for one
name”.
#-- 1 --
# [ formName := inverted English name from birdId ]
formName = birdId.engComma()
#-- 2 --
# [ if self.__formMap has a key formName ->
# formSet := the corresponding value
# else ->
# self.__formMap := a new FormPhotoSet instance
# for formName
# formSet := that new FormPhotoSet instance ]
try:
formSet = self.__formMap[formName]
except KeyError:
formSet = FormPhotoSet ( birdId )
self.__formMap[formName] = formSet
At this point, formSet is the FormPhotoSet instance to which the new photo is
to be added. See Section 12.2, “FormPhotoSet.addArchImage(): Add one
photo”.
#-- 3 --
# [ formSet := formSet with archImage added ]
formSet.addArchImage ( archImage )
This method is used when the form name to be added is not referred to self's taxon. It recursively adds new nodes to self's subtree down to the level where the form name is referred, at which point the basis case adds the photo at that level.
# - - - T a x o n P h o t o S e t . _ _ a d d C o n t a i n e d - - -
def __addContained ( self, birdId, archImage ):
"""Recursive case: add a photo to a descendant taxon.
[ (birdId is a BirdId referred to a descendant of
self.taxon) and
(archImage is a catalog entry as an archindex.ArchImage) ->
self.__childMap := self.__childMap with new descendant
nodes added as necessary down to birdId.taxon,
and archImage added under the name from birdId to
the node for birdId.taxon ]
"""
Each recursive call first ensures that the tree has a node for the child of self containing the new form name, and then it adds the new catalog entry to the child.
The first task is to determine which child taxon contains the new form name. It might be an actual child of self, or it might be an ancestor further down the tree.
#-- 1 --
newTaxon = birdId.taxon
#-- 2 --
# [ newTaxon is a descendant of self.taxon ->
# childTaxon := the child of self.taxon that
# contains newTaxon ]
childTaxon = self.taxon.childContaining ( newTaxon )
Next we make sure that self.childMap has a
child node for this child.
#-- 3 --
# [ if self.__childMap has a key for childTaxon.txKey ->
# childNode := the corresponding value
# else ->
# self.__childMap [ childTaxon.txKey ] := a new
# TaxonPhotoSet for childTaxon
# childNode := that same TaxonPhotoSet ]
try:
childNode = self.__childMap [ childTaxon.txKey ]
except KeyError:
childNode = TaxonPhotoSet ( childTaxon )
self.__childMap [ childTaxon.txKey ] = childNode
A recursive call to the child node's .addArchImage() method will add the new photo to
the correct subtree. See Section 11.2, “TaxonPhotoSet.addArchImage(): Add one
photo”.
#-- 4 --
# [ childNode := childNode with archImage added under
# birdId ]
childNode.addArchImage ( birdId, archImage )
This method recursively walks the tree, yielding the current node, followed by its subtrees.
# - - - T a x o n P h o t o S e t . w a l k - - -
def walk ( self ):
"""Recursive tree walker, yielding nodes in pre-order.
"""
The current node is generated first.
#-- 1 --
yield self
This method specifies that the children are generated in
taxonomic order. Since the keys in the self.__childMap dictionary are in phylogenetic
order, we extract the key set, sort it, and then
recursively generate the subtrees in order by that set.
#-- 2 --
# [ childKeys := a list containing the keys of
# self.__childMap in ascendind order ]
childKeys = self.__childMap.keys()
childKeys.sort()
#-- 3 --
# [ childKeys is a list of keys in self.__childMap ->
# generate the nodes from subtrees rooted in the nodes
# that are values of self.__childMap, in childKeys order ]
for txKey in childKeys:
childNode = self.__childMap[txKey]
for node in childNode.walk():
yield node
Raising StopIteration terminates the
generator.
#-- 4 --
raise StopIteration
This method returns the number of forms referred to self. Since the dictionary self.__formMap has one entry for each referred
form, we need only return the cardinality of that
dictionary.
# - - - T a x o n P h o t o S e t . n F o r m s - - -
def nForms ( self ):
"""How many form names are referred to self?
"""
return len ( self.__formMap )
This method generates the FormPhotoSet
objects contained in self, in ascending
order by their inverted English name. The code is
straightforward: extract the English names that are the
keys in the .__formMap dictionary, sort
them, and generate the values from the dictionary in
that order.
# - - - T a x o n P h o t o S e t . g e n F o r m s - - -
def genForms ( self ):
"""Generate self's referred forms.
"""
#-- 1 --
# [ keyList := the keys from self.__formMap, sorted ]
keyList = self.__formMap.keys()
keyList.sort()
#-- 2 --
# [ keyList is a list of keys in self.__formMap ->
# generate the values from self.__formMap in the
# order specified by keyList ]
for key in keyList:
yield self.__formMap[key]
#-- 3 --
raise StopIteration
This method is called to render one taxon's content on the index page, and also to build all form pages (and links to them) for forms referred to this taxon.
# - - - T a x o n P h o t o S e t . b u i l d P a g e - - -
def buildPage ( self, parent ):
"""Add one taxon to the index page, and build all its form pages
[ parent is an xc.Element node ->
parent := parent with XHTML child nodes added to
represent self
form pages for forms referred to self's taxon :=
content displaying thumbnails and catalog info
for forms in self ]
"""
In one case, this method builds nothing at all: if it is a genus-level taxon that has no forms referred to it. See Section 3.1, “The index page” for remarks on this case.
#-- 1 --
# [ if self is a genus-level taxon with no forms referred
# to it ->
# return
# else -> I ]
if ( ( self.taxon.rank.depth ==
self.taxon.txny.hier.genusRank().depth ) and
( len(self.__formMap) == 0 ) ):
return
All the content for this taxon will be wrapped in a div element of class head-, where R is the code for the taxonomic
rank of self's taxon.
R
#-- 2 --
# [ parent := parent with a div added, tagged with
# the heading class for self's taxonomic rank
# div := that new div element ]
div = xc.Element ( parent, "div" )
div["class"] = "head-%s" % self.taxon.rank.code
Here we divide the logic into three cases.
If no form is referred to this taxon, the name is
added directly to the div.
If there is only one form referred to this taxon, the name is a link to the form page.
If there are multiple forms referred to this taxon, the taxon's name is not a link, but it is followed by lines linking to the various form pages.
If there is a
single form in this taxon, the content is the taxonomic
heading as a link to the form page for that form. If
there are multiple forms, the content starts with the
taxonomic heading, followed by one additional div element for each form's name as a link to
the corresponding form page.
#-- 3 --
# [ if len(self.__formMap) == 0 ->
# div := div + (self's inverted name)
# else if len(self.__formMap) <= 1 ->
# div := div + (link to form page containing self's
# inverted name, and the form name if
# different than self's inverted name
# form page for self.__formMap's value := content
# displaying thumbnails and catalog info for
# self.__formMap's value
# else ->
# div := div + (self's inverted name) +
# (links to form pages for forms in self.__formMap)
# form pages for self.__formMap values := content
# displaying thumbnails and catalog info for
# values in self.__formMap ]
if len(self.__formMap) == 0:
self.__webName ( div )
elif len(self.__formMap) == 1:
self.__buildSingleForm ( div )
else:
self.__buildMultiForms ( div )
This method adds the content for one taxon to the index page when there is only one form referred to that taxon. It also builds the form page for that form.
# - - - T a x o n P h o t o S e t . _ _ b u i l d S i n g l e F o r m
def __buildSingleForm ( self, div ):
"""Build index and form content for the single-form case.
[ (self.__formMap has only one entry) and
(div is an xc.Element) ->
div := div + (link to form page containing self's
inverted name, and the form name if
different than self's inverted name
form page for self.__formMap's value := content
displaying thumbnails and catalog info for
self.__formMap's value ]
"""
To review the XHTML build in this case, refer to Section 4.2, “XHTML for the index page”. The subtree we generate here
is rooted in an “a”
(hyperlink) element whose href attribute
points at the form page. The link text is the scientific
name, followed by the inverted English name. If the
inverted English name of the sole form differs from the
taxon's standard English name, we add the content
“(as otherName)”.
First, build the link element.
#-- 1 --
# [ div := div + (a new "a" element)
# link := that "a" element ]
link = xc.Element ( div, "a" )
Next we extract the sole entry in self.__formMap. The .keys()
method on a dictionary produces a list of keys, a
singleton in our case.
#-- 2 --
# [ formSet := the sole value in self.__formMap ]
formKey = self.__formMap.keys()[0]
formSet = self.__formMap[formKey]
At this point, formSet is a FormPhotoSet instance, and its .pathName() method returns the pathname to its
form page (which hasn't been built yet).
#-- 3 --
# [ formSet is a FormPhotoSet ->
# link := link with an href attribute added whose
# value is the path name to formSet's form page ]
link [ "href" ] = formSet.pathName()
Add, as link text, the scientific and English names for
self's taxon. Then, if the form's inverted English name
is different, add the “(as otherName)” content.
#-- 4 --
# [ link := link + (text of self's names) ]
self.__webName ( link )
#-- 5 --
# [ if self.taxon's inverted English name differs from
# formSet's inverted English name ->
# link := link + "(as " + (formSet's inverted
# English name) + ")"
# else -> I ]
formEng = formSet.birdId.engComma()
if formEng != self.taxon.engComma:
xc.Text ( link, "(as %s)" % formEng )
All that remains is to generate formSet's
form page.
#-- 6 --
# [ form page for formSet := content displaying
# thumbnails and catalog data for formSet ]
formSet.buildPage()
This method builds the XHTML for the taxon's scientific name and, if defined, its inverted English name. For design notes, see Section 4.2, “XHTML for the index page”.
# - - - T a x o n P h o t o S e t . _ _ w e b N a m e - - -
def __webName ( self, parent ):
"""Add the scientific and English names to a Web page.
[ parent is an xc.Element ->
parent := parent + (self.taxon's scientific name) +
(self.taxon's English name, if known) ]
"""
The principal complication is that we must italicize
genus-level and lower-rank names by wrapping them in a
span class="genus" element; see Section 5, “Styling with CSS”. The depth
attribute of a taxonomic rank is 0 for class, 1 for
order, and so on, so we can test the rank depth of self's
taxon against the depth of the genus rank to determine
whether to italicize.
#-- 1 --
# [ selfDepth := taxonomic rank depth of self.taxon
# genusDepth := taxonomic rank depth of the genus
# rank of the taxonomy used by self.taxon ]
selfDepth = self.taxon.rank.depth
genusDepth = self.taxon.txny.hier.genusRank().depth
If this rank is genus or deeper, we now add the span element, otherwise we don't. In either
case we set the variable textParent to
point to the node to which the name text is to be added.
#-- 2 --
# [ if selfDepth >= genusDepth ->
# parent := parent + (a new span element with
# class="genus")
# textParent := that new span element
# else ->
# textParent := parent ]
if selfDepth >= genusDepth:
textParent = xc.Element ( parent, "span",
class_="genus" )
else:
textParent = parent
Next, the scientific name is added to textParent. We add the rank name to ranks above
genus level (e.g., “Family Fringillidae”),
but omit it for genera and deeper because the
italicization tells the reader it's a scientific name.
#-- 3 --
# [ textParent := textParent + (self.taxon's scientific
# name) ]
if selfDepth < genusDepth:
xc.Text ( textParent, "%s " % self.taxon.rank.name )
xc.Text ( textParent, self.taxon.sci )
Finally, display the English name if there is one. The
txny.py module sets the .eng attribute to the same as the .sci
attribute by default, so in that case, don't redundantly
display it. This text is added to parent,
not textParent, so that it will be outside
the span if present.
#-- 4 --
# [ if self.taxon.eng != self.taxon.sci ->
# parent := textParent + ": " + self.taxon.engComma
if self.taxon.eng != self.taxon.sci:
xc.Text ( parent, ": %s" % self.taxon.engComma )
This method generates content for one taxon on the index page for the case where more than one form name is referred to that taxon.
# - - - T a x o n P h o t o S e t . _ _ b u i l d M u l t i F o r m s
def __buildMultiForms ( self, parent ):
"""Build index and form content for the multi-form case.
[ parent is an xc.Element ->
parent := parent + (self's inverted name) +
(links to form pages for forms in self.__formMap)
form pages for self.__formMap values := content
displaying thumbnails and catalog info for
values in self.__formMap ]
"""
First we add the form name to the parent div.
#-- 1 --
# [ parent := parent with self's name added as text ]
self.__webName ( parent )
The forms referred to this taxon will be displayed in ascending order by inverted English name. This is achieved using the standard Python trick of keeping instances as values in a dictionary, and using a key that sorts in the desired order—in this case, the inverted English name.
#-- 2 --
# [ nameList := keys of self.__formMap, sorted ]
nameList = self.__formMap.keys()
nameList.sort()
For each key in nameList, the
corresponding value is a FormPhotoSet
instance. The generation of the index page content, and
corresponding photo page, for each form is done by
Section 11.12, “TaxonPhotoSet.__buildFormLine():
Build one form link and form page”.
#-- 3 --
# [ parent := parent with divs added containing the
# names from self.__formMap in order by nameList,
# each as a link to the corresponding value from
# self.__formMap
# form pages for values in self.__formMap := content
# from those values ]
for formName in nameList:
#-- 3 body --
# [ let formSet == self.__formMap[formName]
# in
# parent := parent with a div added containing the
# name from formSet and a link to formSet's form page
# form page for formSet := content from formSet ]
self.__buildFormLine ( parent, self.__formMap[formName] )
This method builds the div on the index
page containing the link to a form page. It also creates
the corresponding form page.
# - - - T a x o n P h o t o S e t . _ _ b u i l d F o r m L i n e
def __buildFormLine ( self, parent, formSet ):
"""Build one form page link and the corresponding form page.
[ (parent is an xc.Element) and
(formSet is a FormPhotoSet) ->
parent := parent with a div added containing the
name from formSet and a link to formSet's form page
form page for formSet := content from formSet ]
"""
The XHTML generated here is described in Section 4.2, “XHTML for the index page”. The only child added to parent is the div element, with
class="name-line",
#-- 1 --
# [ parent := parent with a new div element added
# div := that new div element ]
div = xc.Element ( parent, "div", class_="name-line" )
The only child of the div is the link
(“a”) element that points to
the form page.
#-- 2 --
# [ div := div with a new 'a' element added whose href
# attribute is the relative URL of formSet's page
# link := that 'a' element ]
link = xc.Element ( div, 'a', href=formSet.pathName() )
The link text is the form's inverted English name.
#-- 3 --
# [ link := link with formSet's inverted English name
# added as link text ]
xc.Text ( link, formSet.birdId.engComma() )
Having finished generating all the index page content for this form, now generate the actual form page.
#-- 4 --
# [ form page for formSet := content from formSet ]
formSet.buildPage()