Each instance of this class holds all the information we need to build one month's cell in the index table.
# - - - - - c l a s s M o n t h C e l l
class MonthCell:
'''Represents one month's entry in the index table.
Exports:
MonthCell ( yearRow, mm, birdNoteSet ):
[ (yearRow is the containing YearRow instance) and
(mm is the month as a left-zero-filled string 'mm') and
(birdNoteSet is a birdnotes.BirdNoteSet instance
representing that month's data) ->
return a new MonthCell instance with those values ]
.yearRow: [ as passed to constructor, read-only ]
.mm: [ as passed to constructor, read-only ]
.yyyy_mm: [ (yearRow.yyyy)+'-'+(mm)
.title: [ self's 'monthName yyyy' string ]
.birdNoteSet: [ as passed to constructor, read-only ]
.fileName():
[ return the path name of the month's file relative
to the index page ]
.writePage():
[ if self's XHTML page can be created anew ->
that page := self rendered as XHTML ]
'''
# - - - M o n t h C e l l . _ _ i n i t _ _
def __init__ ( self, yearRow, mm, birdNoteSet ):
'''Constructor for MonthCell.
'''
self.yearRow = yearRow
self.mm = mm
self.yyyy_mm = '%s-%s' % (yearRow.yyyy, mm)
self.birdNoteSet = birdNoteSet
self.title = ( "Shipman's field notes, %s" %
MonthCell.monthName ( self.yyyy_mm ) )
This method returns the name of the monthly report file.
The subdirectory is the name of the year, and the
file's name is self.yyyy_mm plus the
standard HTML extension.
# - - - M o n t h C e l l . f i l e N a m e
def fileName ( self ):
'''Return this month's page's relative path.
'''
return ( '%s/%s%s' %
(self.yearRow.yyyy, self.yyyy_mm, HTML_EXT) )
This method creates the page containing one month's notes, provided the page is out of date.
# - - - M o n t h C e l l . w r i t e P a g e
def writePage ( self ):
'''Render self as XHTML.
'''
We don't want to rewrite every HTML file every time, so
we use the self.birdNoteSet.newestTime
timestamp to see if all of the source files are
older than the output file and, if so, return.
#-- 1 --
# [ if (not Args().forceSwitch) and
# (self.fileName() names an existing file whose modification
# time is greater than self.birdNoteSet.newestTime) ->
# return
# else ->
# outFileName := self.fileName() ]
outFileName = self.fileName()
if not Args().forceSwitch:
if os.path.exists ( outFileName ):
status = os.stat ( outFileName )
modTime = status[stat.ST_MTIME]
if ( (self.birdNoteSet.newestTime is not None) and
(modTime > self.birdNoteSet.newestTime) ):
return
Before we can create the page, we need to find the previous and next page so this page's and links can point to them.
#-- 2 --
# [ prev := the 'yyyy-mm' key of the preceding month's
# page, or None if self is the first month
# next := the 'yyyy-mm' key of the following month's
# page, or None if self is the last month ]
prev, next = self.yearRow.yearCollection.neighbors (
self.yyyy_mm )
At this point we call the tccpage2.TCCPage() constructor to set up a basic page structure. See
Section 23.4, “MonthCell.__pageFrame(): Set up a basic
page”.
#-- 3 --
# [ page := a tccpage2.TCCPage instance with
# navigation links (prev, next) ]
page = self.__pageFrame ( prev, next )
For the method that generates the page's variable
content, see Section 23.5, “MonthCell.__renderPage(): Add the
notes content”.
#-- 4 --
# [ page := page with self.birdNoteSet rendered as XHTML ]
self.__renderPage(page)
Open the page's file for writing (if possible) and write the content there.
#-- 5 --
# [ if a file named self.fileName() can be opened new ->
# outFile := that file, so opened
# else ->
# sys.stderr +:= error message
# stop execution ]
try:
outFile = open ( outFileName, 'w' )
except IOError, detail:
print >>sys.stderr, ( "*** Can't create monthly page "
"'%s': %s" % (outFileName, detail) )
raise SystemExit
#-- 6 --
# [ outFile +:= page, rendered as XHTML ]
page.write ( outFile )
outFile.close()
This method sets up a TCCPage instance to
hold the month's content. Links to the documentation for
tccpage2.py are in Section 3, “Overview of the internals”.
# - - - M o n t h C e l l . _ _ p a g e F r a m e
def __pageFrame ( self, prev, next ):
'''Set up an empty tccpage2.TCCPage instance.
[ (prev is the 'yyyy-mm' of the previous month or None) and
[ (next is the 'yyyy-mm' of the next month or None) ->
return a new TCCPage instance with navigation links
previous=(prev's page) and next=(next's page) ]
'''
For the navigational link plan, see Section 4, “The generated XHTML”. We have four navigational features: links to nextURL;
links to prevURL; goes
to the index page; and goes
to Shipman's home page.
To transform the target month numbers into relative URLs,
we can't assume that the referenced pages are in
the same directory. For instance, the predecessor
of '1998-01.xml' might be URL
'../1997/1997-12.xml'. For simplicity,
we'll always go up one level and then back down.
#-- 1 --
# [ if prev is None ->
# prevList := an empty list
# else ->
# prevList := [(name of month prev,URL of prev)] ]
#--
# 0123456
# yyyy-mm <-- Value of 'prev' or 'next'
#--
if prev is None:
prevList = []
else:
prevYYYY = prev[:4]
prevMM = prev[5:]
prevURL = ( '../%s/%s%s' %
(prevYYYY, prev, HTML_EXT) )
prevText = MonthCell.monthName(prev)
prevList = [(prevText, prevURL)]
#-- 2 --
# [ if next is None ->
# nextList := an empty list
# else ->
# nextList := [(name of month next,URL of next)] ]
if next is None:
nextList = []
else:
nextYYYY = next[:4]
nextMM = next[5:]
nextURL = ( '../%s/%s%s' %
(nextYYYY, next, HTML_EXT) )
nextText = MonthCell.monthName(next)
nextList = [(nextText, nextURL)]
Next, build the list of NavLink instances
describing the page's navigational features.
#-- 2 --
# [ navList := a list of tccpage2.NavLink instances
# representing the navigational features ]
navList = (
tccpage2.NavLink ( 'Next', nextList ),
tccpage2.NavLink ( 'Previous', prevList ),
tccpage2.NavLink ( 'Contents',
[("Shipman's Field Notes",
'../%s%s' % (INDEX_PAGE_NAME, HTML_EXT))] ),
tccpage2.NavLink ( 'Home',
[("Shipman's Home Sweet Homepage", HOME_PAGE_URL)] ) )
All that remains is to build and return the TCCPage instance.
#-- 3 --
return tccpage2.TCCPage ( self.title, navList,
logoImage=ZDP_LOGO, logoLink=ZDP_URL, cssUrl=CSS_URL )
Translate self.birdNoteSet to XHTML. The
page argument is a tccpage2.TCCPage instance whose .content attribute is an element under which all
the page content is placed. For an outline of the XHTML
at this level, see Section 4.2, “XHTML for the month page”.
The page starts with a table of links to the daily data
blocks, followed by one data block for each day. This
rendering is driven by the self.birdNoteSet.genDays() method, which
generates the daily blocks as a sequence birdnotes.DayNotes instances, in the order
they occur in the input.
This process is complicated by the need to set up a
system of anchor names so that the page's table of
contents (TOC) can link correctly to the rendering of
each DayNotes instance in the current
month.
Because there may be more than one day-notes element with the same date, we can't simply use the
“” date strings as anchor names. We'll
have to add a suffix to separate them, e.g.,
“yyyy-dd-mm2008-04-11”, “2008-04-11a”, “2008-04-11b”, and so forth.
We'll need two local data structures to manage this process:
A Python set instance named anchorSet will accumulate the anchor strings
that we have used so far. This is needed only during
the process of assigning unique anchor values, which
occurs in Section 23.6, “MonthCell.__pageTOC(): Generate page
table of contents”, so it
is local to that method.
In order to remember which anchors correspond to
which DayNotes instances, we'll use a
rather interesting little Python trick. In Python,
you can use an instance as an index in a dictionary.
We'll keep the anchors as values in a dictionary
named anchorMap, and the keys will be
the DayNotes instances in this month.
That structure is returned by the .__pageTOC method.
# - - - M o n t h C e l l . r e n d e r P a g e
def __renderPage ( self, page ):
'''Render the notes into XHTML.
[ page is a tccpage2.TCCPage instance ->
page := page with self.birdNoteSet rendered as XHTML ]
'''
First we add the short paragraph linking to the documentation so readers can interpret the notes.
#-- 1 --
# [ page.content +:= paragraph linking to documentation ]
intro = et.SubElement ( page.content, 'p' )
introLink = et.SubElement ( intro, 'a',
href=CONVENTIONS_URL )
introLink.text = "How to read Shipman's field notes"
Next comes the page table of contents. See Section 23.6, “MonthCell.__pageTOC(): Generate page
table of contents”.
#-- 2 --
# [ page.content +:= a 'ul' containing the page table
# of contents (TOC)
# anchorMap +:= a dictionary whose keys are the DayNotes
# instances in self.birdNoteSet, and each
# corresponding value is the anchor name used for
# that instance in the generated TOC ]
anchorMap = self.__pageTOC ( page.content )
Finally, we generate a block for each day-notes element in the month. See Section 23.10, “MonthCell.__dayBlock(): Render one
daily note set”.
#-- 3 --
# [ page.content +:= XHTML rendering of the day-notes
# elements inside self.birdNoteSet, using anchorMap
# for the mapping of those elements onto anchors ]
for dayNotes in self.birdNoteSet.genDays():
#-- 3 body --
# [ dayNotes is a birdnotes.DayNotes instance ->
# page.content +:= XHTML rendering of dayNotes
# defining anchor anchorMap[dayNotes] ]
self.__dayBlock ( page.content, dayNotes,
anchorMap[dayNotes] )
This method generates the table of contents for one monthly page. For the XHTML generated, see Section 4.2, “XHTML for the month page”.
# - - - M o n t h C e l l . _ _ p a g e T O C
def __pageTOC ( self, parent ):
'''Generate the monthly page's table of contents.
[ parent is an et.Element ->
parent +:= a 'ul' containing the page table
of contents
return a dictionary whose keys are the DayNotes
instances in self.birdNoteSet, and each
corresponding value is the anchor name used for
that instance in the generated TOC ]
'''
At the top level, a ul (bullet list) wraps
the table of contents. Also, create the anchorSet and anchorMap
structures discussed under Section 23.5, “MonthCell.__renderPage(): Add the
notes content”.
#-- 1 --
# [ parent := parent with a new 'ul' child element added
# ul := that child
# anchorSet := a new, empty set
# anchorMap := a new, empty dictionary ]
ul = et.SubElement ( parent, 'ul' )
anchorSet = set()
anchorMap = {}
For the logic that generates each TOC entry, see Section 23.7, “MonthCell.__dayTOC(): Month table of
contents entry for one day”.
#-- 2 --
# [ ul +:= 'li' elements representing the days in
# self.birdNoteSet
# anchorSet +:= anchors used in the table of contents
# anchorMap +:= entries mapping the DayNotes
# instances in self.birdNoteSet onto the corresponding
# anchors ]
for dayNotes in self.birdNoteSet.genDays():
#-- 2 body --
# [ ul +:= an 'li' element representing dayNotes ]
self.__dayTOC ( ul, dayNotes, anchorSet, anchorMap )
The finished anchorMap is returned to the
caller.
#-- 3 --
return anchorMap
This method generates one entry in the table of contents at the top of the monthly page.
# - - - M o n t h C e l l . _ _ d a y T O C
def __dayTOC ( self, ul, dayNotes, anchorSet, anchorMap ):
'''Generate a link to the day's entry, with notables if any.
[ (ul is an et.Element) and
(dayNotes is a DayNotes instance) and
(anchorSet is a set) and
(anchorMap is a dictionary) ->
ul +:= an 'li' item containing a link to the
anchor for dayNotes.date, plus the English
names for any notable records
anchorSet +:= a new anchor value not in anchorSet
anchorMap +:= an entry whose key is dayNotes
and whose value is that new anchor value ]
'''
First we must create an anchor string that hasn't been
used yet. We'll start with "D" plus the
dayNotes.date string (which has the form
“”). The yyyy-mm-ddanchorSet
argument is a Python set instance
containing all the anchors that have already been used,
so if the simple date is not in that set, we'll use that.
If not, we'll try suffix letters 'a',
'b', and so on, until we get a new one.
In theory, if there are 27 or more daily sets, we might run out of letters. However, the author rarely records multiple sets per day. The worst case is a long drive through multiple states, but to drive through 27 states in one day would be a pretty good trick.
#-- 1 --
# [ newAnchor := a valid HTML anchor name that is not in
# anchorSet ]
newAnchor = "D" + dayNotes.date
suffix = 'a'
while newAnchor in anchorSet:
newAnchor = "D" + dayNotes.date+suffix
suffix = chr(ord(suffix)+1)
Now that we have an anchor string newAnchor that isn't is anchorSet, we know we can
use it as the anchor for dayNotes.
#-- 2 --
# [ anchorSet := union(anchorSet, newAnchor)
# anchorMap +:= an entry whose key is dayNotes and
# whose value is newAnchor ]
anchorSet.add ( newAnchor )
anchorMap[dayNotes] = newAnchor
Next we'll create the li (bullet) for this
date. Inside the bullet is a link to the anchor we just
generated, whose link text is the date, state, and day
locality string from dayNotes.
#-- 3 --
# [ ul +:= a new 'li' element
# li +:= that element ]
li = et.SubElement ( ul, 'li' )
#-- 4 --
# [ li +:= a new 'a' element whose URL is ('#'+newAnchor)
# and whose link text is (dayNotes.date)+': '+
# (dayNotes.regionCode, uppercased)+': '+
# (dayNotes.dayLoc.name) ]
a = et.SubElement ( li, 'a', href=('#'+newAnchor) )
a.text = dayNotes.title()
Next we must scan through all the records inside dayNotes and, if any are notable, generate a
div with all the bird names from notable
records. See Section 23.8, “MonthCell.__notablesBlock(): Display
any notable records”.
#-- 5 --
# [ if dayNotes contains any notable records ->
# li +:= a div element containing the bird names
# from notable records
# else -> I ]
self.__notablesBlock ( li, dayNotes )
This method looks through the records inside a birdnotes.DayNotes instance to see if any of the
records are flagged as notable. If so, it generates a
div element containing a list of their names.
# - - - M o n t h C e l l . _ _ n o t a b l e s B l o c k
def __notablesBlock ( self, parent, dayNotes ):
'''If any records are notable, display their names.
[ (parent is an et.Element) and
(dayNotes is a DayNotes instance) ->
if any records within dayNotes are flagged as notable ->
parent +:= a div element containing the names
from all notable records
else -> I ]
'''
First we'll go through the BirdForm
instances inside dayNotes and accumulate a
list of those flagged as notable. If the list is empty,
we're done.
#-- 1 --
notablesList = [ birdForm
for birdForm in dayNotes.genForms()
if birdForm.notable ]
#-- 2 --
if len(notablesList) == 0:
return
The block is a “div class='loc-child” element. Its first child is a span
class='notable' containing the label
“Notable:”. The bird names
follow, separated by commas.
#-- 3 --
# [ parent +:= a new div element with class=LOC_CHILD_CLASS
# div := that new element ]
div = et.SubElement ( parent, 'div' )
div.attrib['class'] = LOC_CHILD_CLASS
See Section 23.9, “MonthCell.__span(): Add a span inline”.
#-- 4 --
# [ div +:= a new span element with class=NOTABLE_CLASS,
# containing the text 'Notable: ', plus a space ]
self.__span ( div, NOTABLE_CLASS, 'Notable: ' )
tccpage2.addTextMixed ( div, NBSP )
#-- 5 --
# [ div +:= names of the birds in notablesList,
# separated by ', ' ]
nameList = [ str(birdForm.birdId)
for birdForm in notablesList ]
tccpage2.addTextMixed ( div, '; '.join ( nameList ) )
This service routine is used to add to some parent
element a span child with a given class
and textual content.
# - - - M o n t h C e l l . _ _ s p a n
def __span ( self, parent, class_, text ):
'''Add text inside a span inline
[ (parent is an et.Element) and
(class_ is a string) and
(text is a string) ->
parent +:= a new span element with class=(class_)
and text=(text)
return that new span element ]
'''
span = et.SubElement ( parent, 'span' )
span.attrib['class'] = class_
span.text = text
return span
Each DayNotes instance is rendered into
these XHTML elements:
A horizontal rule, hr.
An h2 title that defines the
given anchor. See Section 23.11, “MonthCell.__dayTitle(): Render the
title for one daily block”.
A daily summary block. See Section 23.12, “MonthCell.__daySummary(): Render the
daily summary block”.
Rendering of the BirdForm children.
See Section 23.19, “MonthCell.__birdForm(): Render one
BirdForm”.
# - - - M o n t h C e l l . _ _ d a y B l o c k
def __dayBlock ( self, parent, dayNotes, anchor ):
'''Generate all the output for one DayNotes instance.
'''
#-- 1 --
# [ parent +:= an empty hr element ]
et.SubElement ( parent, 'hr' )
#-- 2 --
# [ parent +:= an h2 heading describing dayNotes
# that has id=anchor ]
self.__dayTitle ( parent, dayNotes, anchor )
#-- 3 --
# [ parent +:= a div class=DAY_SUMMARY_CLASS element
# representing the daily summary for dayNotes ]
self.__daySummary ( parent, dayNotes )
#-- 4 --
# [ parent +:= XHTML for all the BirdForm children
# of dayNotes ]
for birdForm in dayNotes.genForms():
#-- 4 body --
# [ parent +:= XHTML rendering of birdForm ]
self.__birdForm ( parent, birdForm )
# - - - M o n t h C e l l . _ _ d a y T i t l e
def __dayTitle ( self, parent, dayNotes, anchor ):
'''Render the h2 title and define the anchor.
[ (parent is an et.Element) and
(dayNotes is a DayNotes instance) and
(anchor is a valid HTML anchor string) ->
parent +:= an h2 heading describing dayNotes
that has id=anchor ]
'''
#-- 1 --
h2 = et.SubElement ( parent, 'h2', id=anchor )
h2.text = dayNotes.title()
This method renders the day-summary
content for one day-notes.
# - - - M o n t h C e l l . _ _ d a y S u m m a r y
def __daySummary ( self, parent, dayNotes ):
'''Render the daily summary block.
[ (parent is an et.Element) and
(dayNotes is a DayNotes instance) ->
parent +:= a div class=DAY_SUMMARY_CLASS element
representing the daily summary for dayNotes ]
'''
The entire content of this block is wrapped inside div class='day-summary'. If there are any
notable records for the day, they are added just inside
it: see Section 23.8, “MonthCell.__notablesBlock(): Display
any notable records”.
#-- 1 --
# [ parent := parent with a new div element added with
# class-DAY_SUMMARY_CLASS
# summaryDiv := that div element ]
summaryDiv = et.SubElement ( parent, 'div' )
summaryDiv.attrib['class'] = DAY_SUMMARY_CLASS
#-- 2 --
# [ if dayNotes contains any notable records ->
# summaryDiv +:= a div element containing the
# names from all notable records
# else -> I ]
self.__notablesBlock ( summaryDiv, dayNotes )
Next we list the localities for the day; see Section 23.13, “MonthCell.__locDef(): Display a
locality definition”.
#-- 3 --
# [ summaryDiv +:= blocks displaying the localities
# in dayNotes.daySummary ]
for loc in dayNotes.daySummary.genLocs():
self.__locDef ( summaryDiv, loc )
Rounding out the daily summary block are the day-annotation elements; see Section 23.14, “MonthCell.__dayAnnotation(): Render
day-annotation content”.
#-- 4 --
# [ summaryDiv +:= blocks displaying day-annotation
# content from dayNotes.daySummary, if any ]
self.__dayAnnotation ( summaryDiv, dayNotes.daySummary )
In the output of the XSLT script that was the predecessor of noteweb, it was tedious to determine the location code for any given bird form:
The default location code was displayed at the top of the daily summary block, followed by the list of that day's location codes and their definitions.
Under each bird form, the location code was displayed only for records that were not at the default location.
So the reader's process for determining the locality of a record was:
If the bird form record shows no location, find the location code shown as the “Default location,” then look up the code in the table of locations.
If the bird form record shows a location code, look that up in the table of locations.
A better solution is to show the full-length locality name on each bird form record. This means we don't need to show the default location code at all; it's just an artifact of the file encoding (for the convention of the data entry worker).
The only reason for the old, ugly rendering was that
the author found it difficult to implement in straight
XSLT. Have a look at the old birdhtml.xsl if you like. It
was put up as a stopgap before the birdnotes.py package was written.
See Section 4.5, “XHTML for locality definitions”.
# - - - M o n t h C e l l . _ _ l o c D e f
def __locDef ( self, parent, loc ):
'''Display the definition of one locality.
[ (parent is an et.Element) and
(loc is a birdnotes.Loc instance) ->
parent +:= a div element displaying loc ]
'''
All the content generated by this method is wrapped in a
div class=LOC_DEF_CLASS. In each case,
the first content is “@”
followed by the locality's code, a colon, and the locality's
name.
#-- 1 --
# [ parent +:= a div element with class=LOC_DEF_CLASS
# topDiv := that div element ]
topDiv = et.SubElement ( parent, 'div' )
topDiv.attrib['class'] = LOC_DEF_CLASS
#-- 2 --
# [ topDiv +:= '@' + loc.code + ': ' + loc.name ]
topDiv.text = '@%s: %s' % (loc.code, loc.name )
If there is a narrative block for this locality, render
it next inside a div
class=LOC_NARRATIVE_CLASS. The text is just a
string, not an instance of the Narrative
class.
#-- 3 --
# [ if bool(loc.text) ->
# topDiv +:= loc.text, wrapped in a div element
# with class=LOC_NARRATIVE_CLASS
# else -> I ]
if loc.text:
narraDiv = et.SubElement ( topDiv, 'div' )
narraDiv.attrib['class'] = LOC_NARRATIVE_CLASS
narraDiv.text = loc.text
The list of GPS waypoints follows, if there are any.
#-- 4 --
# [ topDiv +:= a sequence of divs with class
# LOC_NARRATIVE_CLASS containing GPS descriptions
# from loc, if any ]
for gps in loc.genGps():
narraDiv = et.SubElement ( topDiv, 'div' )
narraDiv.attrib['class'] = LOC_NARRATIVE_CLASS
narraDiv.text = ( 'GPS: %s %s' %
(gps.waypoint, gps.text) )
# - - - M o n t h C e l l . _ _ d a y A n n o t a t i o n
def __dayAnnotation ( self, parent, daySummary ):
'''Render day-annotation content.
[ (parent is an et.Element) and
(daySummary is a birdnotes.DaySummary instance) ->
parent +:= blocks displaying day-annotation
content from daySummary, if any ]
'''
This method generates the various types of labeled
annotation first in a standard order—route,
weather, missed, and film—followed by unclassified
notes. For the general structure, see Section 4.6, “XHTML for day-annotation elements”. See also Section 23.15, “MonthCell.__annoBlock(): Annotation
block with a label”.
#-- 1 --
# [ if daySummary.route is not None ->
# parent +:= a div labeled with 'route' displaying
# daySummary.route
# else -> I ]
if daySummary.route is not None:
self.__annoBlock ( parent, 'Route', daySummary.route )
#-- 2 --
if daySummary.weather is not None:
self.__annoBlock ( parent, 'Weather', daySummary.weather )
#-- 3 --
if daySummary.missed is not None:
self.__annoBlock ( parent, 'Missed', daySummary.missed )
#-- 4 --
if daySummary.film is not None:
self.__annoBlock ( parent, 'Film', daySummary.film )
The handling of the unlabeled narrative is slightly
different: it is not enclosed in a block with a label, so
it goes straight to Section 23.16, “MonthCell.__narrative(): Render a
Narrative instance”.
#-- 5 --
if daySummary.notes is not None:
self.__narrative ( parent, daySummary.notes )
This method generates one of the blocks displaying
specific kinds of day-annotation data such
as “Route”. For the
generated output, see Section 4.6, “XHTML for day-annotation elements”.
# - - - M o n t h C e l l . _ _ a n n o B l o c k
def __annoBlock ( self, parent, label, narr ):
'''Generate a label block of day annotation.
[ (parent is an et.Element) and
(label is a string) and
(narr is a Narrative instance) ->
parent +:= a div with class=LOC_NARRATIVE_CLASS
containing label and an XHTML rendering of narr ]
'''
#-- 1 --
# [ parent +:= a div element with class=LOC_NARRATIVE_CLASS
# div := that div element ]
div = et.SubElement ( parent, 'div' )
div.attrib['class'] = LOC_NARRATIVE_CLASS
The block's label is wrapped in a span;
see Section 5.2, “Inline markup rules” and Section 23.9, “MonthCell.__span(): Add a span inline”.
#-- 2 --
# [ div +:= a span element with class=LOC_LABEL_CLASS
# containing label ]
self.__span ( div, LOC_LABEL_CLASS, "%s " % label )
The content of the div depends on whether
the given narr has one paragraph or
multiple paragraphs.
If narr has only one child
pararagraph, we go directly to Section 23.18, “MonthCell.__paraContent(): Content of
one paragraph” to avoid getting
another level of div elements around
the content.
In the multi-paragraph case, separate child div elements are generated for each
paragraph by Section 23.16, “MonthCell.__narrative(): Render a
Narrative instance”.
#-- 3 --
# [ div +:= XHTML rendering of narr ]
if len(narr) == 1:
#-- 3.1 --
# [ div +:= contents of the first paragraph of narr ]
self.__paraContent ( div, narr[0] )
else:
#-- 3.2 --
# [ div +:= child div elements containing the
# paragraphs of narr ]
self.__narrative ( div, narr )
The rendering of a Narrative instance is
straightforward: see Section 4.7, “XHTML rendering of narrative elements”.
Each contained Paragraph is rendered
in a div class=PARA_CLASS.
# - - - M o n t h C e l l . _ _ n a r r a t i v e
def __narrative ( self, parent, narr ):
'''Render a birdnotes.Narrative instance into XHTML.
[ (parent is an et.Element) and
(narr is a birdnotes.Narrative instance) ->
parent +:= XHTML rendering of narr ]
'''
#-- 1 --
for para in narr.genParas():
#-- 1 body --
# [ parent +:= a div class=PARA_CLASS element
# containing the XHTML rendering of para ]
self.__paragraph ( parent, para )
This method renders a birdnotes.Paragraph
instance as an ordinary text paragraph.
# - - - M o n t h C e l l . _ _ p a r a g r a p h
def __paragraph ( self, parent, para ):
'''Add one paragraph of narrative.
[ (parent is an et.Element) and
(para is a birdnotes.Paragraph instance) ->
parent +:= a div class=PARA_CLASS containing
the XHTML rendering of para ]
'''
Everything is wrapped in a div
class=PARA_CLASS.
#-- 1 --
# [ parent +:= a div element with class=PARA_CLASS
# div := that div element ]
div = et.SubElement ( parent, 'div' )
div.attrib['class'] = PARA_CLASS
For the translation of the paragraph's content,
see Section 23.18, “MonthCell.__paraContent(): Content of
one paragraph”.
#-- 2 --
# [ div +:= div elements containing XHTML rendering
# of the content of para ]
self.__paraContent ( div, para )
This method translates the “phrase list”
structure inside a Paragraph instance into
XHTML.
Although some text in a paragraph can be marked up (e.g.,
with genus tags), the structure is only
one level deep: the child elements do not themselves have
element children.
However, the rendering of even this shallow structure is
not completely obvious, due to the strange way that the
lxml package handles mixed content: text
after an element is stored in the .tail
attribute of the preceding element, and not associated
with the parent (as it would be in the Document Object
Model).
Fortunately, the tccpage2 module already
contains a function named addTextMixed()
that handles this problem. For details of this function,
see the documentation for tccpage2.
For a Paragraph element , method P
produces a list of P.genContent()( tuples.
tag, text)
If the is tagNone, the is plain text, not
marked up. We use texttccpage2.addTextMixed() to add it to the
structure wherever the next text string goes.
If the is not tagNone, its value becomes the
class attribute of a span element containing the .
text
# - - - M o n t h C e l l . _ _ p a r a C o n t e n t
def __paraContent ( self, parent, para ):
"""Translate the content inside one paragraph.
[ (parent is an et.Element) and
(para is a birdnotes.Paragraph instance) ->
parent +:= XHTML rendering of para's contents ]
"""
#-- 1 --
for tag, s in para.genContent():
#-- 1 body --
# [ (tag is None or a tag string) and
# (s is a string) ->
# if tag is None ->
# parent := parent with (s) added to its text
# else ->
# parent := parent with a new span element
# added, with class=(tag), and text (s) ]
if tag is None:
tccpage2.addTextMixed ( parent, s )
else:
self.__span ( parent, tag, s )
For an overview of the generated output, see Section 4.8, “XHTML for the form element”. A div
class=FORM_CLASS wraps all generated content,
except for notable records, which get class=NOTABLE_FORM_CLASS.
# - - - M o n t h C e l l . _ _ b i r d F o r m
def __birdForm ( self, parent, birdForm ):
'''Output all the data from one BirdForm instance.
[ (parent is an et.Element) and
(birdForm is a birdnotes.BirdForm instance) ->
parent +:= XHTML rendering of birdForm ]
'''
#-- 1 --
# [ parent +:= a new div element with class=FORM_CLASS
# div := that div element ]
div = et.SubElement ( parent, 'div' )
if birdForm.notable:
div.attrib['class'] = NOTABLE_FORM_CLASS
else:
div.attrib['class'] = FORM_CLASS
Next comes the name of this bird form. We'll wrap it in
a span class='notable' if the birdForm.notable flag is true, otherwise we'll
use the regular span class='bird-name'.
The name of the bird form is a birdnotes.BirdId instance in birdForm.birdId; applying the str() function to a BirdId gives the full
name, even for compound forms such as 'Gadwall x
Mallard'. See Section 23.9, “MonthCell.__span(): Add a span inline”.
#-- 2 --
# [ if birdForm.notable ->
# div +:= birdForm.birdId's name, wrapped in a
# span class=NOTABLE_CLASS
# else ->
# div +:= birdForm.birdId's name, wrapped in a
# span class=BIRD_NAME_CLASS ]
if birdForm.notable:
class_ = NOTABLE_CLASS
else:
class_ = BIRD_NAME_CLASS
self.__span ( div, class_, birdForm.birdId.engComma() )
tccpage2.addTextMixed ( div, NBSP )
At this point, the rendering depends on whether this one sighting or multiple sightings:
If a single sighting, we want the age-sex-group content on the same line as
the bird name. If there is loc-group
or sighting-notes content, it should
follow in separate div elements.
See Section 23.20, “MonthCell.__singleSighting():
Single-sighting case”.
For multiple sightings, there may be loc-group or sighting-notes
content attached both at the form
level and at the floc level.
In that case, we must render any loc-group or sighting-notes
content at the form level first.
Then, each floc element is rendered in
the order age-sex-group, loc-detail, and sighting-notes. See Section 23.21, “MonthCell.__multiSighting():
Multiple-sighting case”.
#-- 3 --
# [ if birdForm contains one sighting ->
# div +:= (that sighting's age-sex-group) +
# (that sighting's loc-group, if any) +
# (that sighting's sighting-notes, if any)
# else ->
# div +:= (birdForm's loc-group content, if any) +
# (birdForm's sighting-notes, if any) +
# (birdForm's sightings packaged in separate divs) ]
if len(birdForm) == 1:
self.__singleSighting ( div, birdForm )
else:
self.__multiSighting ( div, birdForm )
See the notes about overall structure in Section 23.19, “MonthCell.__birdForm(): Render one
BirdForm”.
# - - - M o n t h C e l l . _ _ s i n g le S i g h t i n g
def __singleSighting ( self, parent, birdForm ):
'''Render the single-sighting case of a bird form.
[ (parent is an et.element) and
(birdForm is a birdnotes.BirdForm instance with
one sighting) ->
parent +:= (that sighting's age-sex-group) +
(that sighting's loc-group, if any) +
(that sighting's sighting-notes, if any) ]
'''
See Section 23.26, “MonthCell.__ageSexGroup(): Render
age-sex-group content”.
#-- 1 --
# [ sight := the first child sighting of birdForm ]
sight = birdForm[0]
#-- 2 --
# [ if sight.ageSexGroup is not None ->
# parent +:= XHTML rendering of sight.ageSexGroup
# else -> I ]
if sight.ageSexGroup is not None:
self.__ageSexGroup ( parent, sight.ageSexGroup )
See Section 23.22, “MonthCell.__locGroup(): Render a
locality group”.
#-- 3 --
# [ paretn +:= XHTML rendering of sight's effective locality ]
self.__locGroup ( parent, sight.getLocGroup() )
See Section 23.23, “MonthCell.__sightNotes(): Render a
sighting notes group”.
#-- 4 --
# [ if sight.sightNotes is not None ->
# parent +:= XHTML rendering of sight.sightNotes
# else -> I ]
if sight.sightNotes is not None:
self.__sightNotes ( parent, sight.sightNotes )
See the general remarks in Section 23.19, “MonthCell.__birdForm(): Render one
BirdForm”.
# - - - M o n t h C e l l . _ _ m u l t i S i g h t i n g
def __multiSighting ( self, div, birdForm ):
'''Render the multi-sighting case of a bird form.
[ (div is an et.Element) and
(birdForm is a birdnotes.birdForm instance) ->
div +:= (birdForm's loc-group content, if any) +
(birdForm's sighting-notes, if any) +
(birdForm's sightings packaged in separate divs) ]
'''
See Section 23.22, “MonthCell.__locGroup(): Render a
locality group”. We want to
display this only if there is locality data explicitly
attached at this level; if the locality is all inherited,
the children will take care of displaying their
localities.
#-- 1 --
# [ if birdForm.locGroup is not None ->
# div +:= XHTML rendering of birdForm.locGroup
# else -> I ]
if birdForm.locGroup is not None:
self.__locGroup ( div, birdForm.locGroup )
See Section 23.23, “MonthCell.__sightNotes(): Render a
sighting notes group”.
#-- 2 --
# [ if birdForm.sightNotes is not None ->
# div +:= XHTML rendering of birdForm.sightNotes ]
if birdForm.sightNotes:
self.__sightNotes ( div, birdForm.sightNotes )
Everything else we need to generate comes from child
sightings of birdForm. See Section 23.25, “MonthCell.__floc(): Generate one of
multiple sightings”.
#-- 3 --
# [ div +:= birdForm's sightings packaged in separate
# divs ]
for sight in birdForm.genSightings():
#-- 3 body --
# [ div +:= sight packaged in a separate div ]
self.__floc ( div, sight )
For a discussion of how this method generates both inline and block content, see Section 4.10, “XHTML rendering of form data”.
# - - - M o n t h C e l l . _ l o c G r o u p
def __locGroup ( self, parent, locGroup ):
'''Render a LocGroup instance in XHTML.
[ (parent is an et.Element) and
(locGroup is a birdnotes.LocGroup instance) ->
parent +:= XHTML rendering of locGroup ]
'''
The tccpage2.addTextMixed() places the
location name as inline text. For documentation for tccpage2, see Section 3, “Overview of the internals”.
#-- 1 --
# [ if locGroup.loc is not None ->
# parent +:= locGroup.loc.name
# else -> I ]
if locGroup.loc is not None:
tccpage2.addTextMixed ( parent, '@%s' % locGroup.loc.name )
#-- 2 --
# [ if locGroup.gps is not None ->
# parent +:= (' GPS: ')+(locGroup.gps)
# else -> I ]
if locGroup.gps is not None:
tccpage2.addTextMixed ( parent,
' GPS: %s' % locGroup.gps )
If there is loc-detail content, it is a
Narrative instance; see Section 23.16, “MonthCell.__narrative(): Render a
Narrative instance”.
#-- 3 --
# [ if locGroup.locDetail is not None ->
# parent +:= locGroup.locDetail wrapped in a div
# else -> I ]
if locGroup.locDetail is not None:
self.__narrative ( parent, locGroup.locDetail )
For an overview of the generated output, see Section 4.11, “XHTML rendering of sighting notes”.
# - - - M o n t h C e l l . _ _ s i g h t N o t e s
def __sightNotes ( self, parent, sightNotes ):
'''Render sighting-notes content into XHTML.
[ (parent is an et.Element) and
(sightNotes is a birdnotes.SightNotes instance) ->
parent +:= XHTML rendering of birdForm.sightNotes ]
'''
We'll use the method described in Section 23.15, “MonthCell.__annoBlock(): Annotation
block with a label” to generate div elements with run-in initial labels.
#-- 1 --
# [ if sightNotes.desc is not None ->
# parent +:= XHTML rendering of sightNotes.desc
# else -> I ]
if sightNotes.desc is not None:
self.__annoBlock ( parent, 'Description:', sightNotes.desc )
#-- 2 --
# [ simile ]
if sightNotes.behavior is not None:
self.__annoBlock ( parent, 'Behavior:', sightNotes.behavior )
#-- 3 --
if sightNotes.voc is not None:
self.__annoBlock ( parent, 'Vocalizations:',
sightNotes.voc )
#-- 4 --
if sightNotes.breeding is not None:
self.__annoBlock ( parent, 'Breeding:',
sightNotes.breeding )
Next come the photos. If there are any, they are placed
in a child div class='para' below.
#-- 5 --
# [ parent +:= rendering of photos in sightNotes, if any ]
photoList = [ x for x in sightNotes.genPhotos() ]
if len(photoList) > 0:
photoDiv = et.SubElement ( parent, 'div' )
photoDiv.attrib['class'] = PARA_CLASS
for photo in photoList:
self.__photo ( photoDiv, photo )
The sightNotes.notes content, if any, is
not labeled; see Section 23.16, “MonthCell.__narrative(): Render a
Narrative instance”.
#-- 6 --
if sightNotes.notes is not None:
self.__narrative ( parent, sightNotes.notes )
For the XHTML generated here, see Section 4.12, “XHTML rendering of photo links”.
# - - - M o n t h C e l l . _ _ p h o t o
def __photo ( self, parent, photo ):
'''Render one photo instance: a link, or just the catalog number.
[ (parent is an et.Element) and
(photo is a birdnotes.Photo instance) ->
parent +:= XHTML rendering of photo ]
'''
If there is an URL associated with this photo, generate a link to it, otherwise just spit out the catalog number unadorned.
#-- 1 --
# [ if photo.url is None ->
# parent +:= photo.catNo
# return
# else -> I ]
if photo.url is None:
tccpage2.addTextMixed ( parent, "%s " % photo.url )
return
#-- 2 --
# [ parent +:= a new 'a' element with href=photo.url
# a := that new element ]
a = et.SubElement ( parent, 'a', href=photo.url )
#-- 3 --
# [a +:= a new 'img' element with src=(thumbnail for
# photo.catNo) and alt=photo.catNo ]
thumbUrl = '/~shipman/thumb/%s.jpg' % photo.catNo
img = et.SubElement ( a, 'img', alt=photo.catNo,
src=thumbUrl )
When there are multiple sightings of a form, each is
packaged in its own div element with class=FLOC_CLASS. For general notes on
rendering, see Section 23.19, “MonthCell.__birdForm(): Render one
BirdForm”.
# - - - M o n t h C e l l . _ _ f l o c
def __floc ( self, parent, sight ):
'''Render one sighting in the multi-sighting case.
[ (parent is an et.Element) and
(sight is a birdnotes.sighting instance) ->
parent +:= XHTML rendering of sight, packaged in
a separate div ]
'''
#-- 1 --
# [ parent +:= a new div element with class=FLOC_CLASS
# div := that div element ]
div = et.SubElement ( parent, 'div' )
div.attrib['class'] = FLOC_CLASS
See Section 23.26, “MonthCell.__ageSexGroup(): Render
age-sex-group content”.
#-- 2 --
# [ if sight.ageSexGroup is not None ->
# div +:= XHTML rendering of sight.ageSexGroup
# else -> I ]
if sight.ageSexGroup is not None:
self.__ageSexGroup ( div, sight.ageSexGroup )
See Section 23.22, “MonthCell.__locGroup(): Render a
locality group”.
#-- 3 --
# [ div +:= XHTML rendering of sight's effective locality ]
self.__locGroup ( div, sight.getLocGroup() )
See Section 23.23, “MonthCell.__sightNotes(): Render a
sighting notes group”.
#-- 4 --
# [ if sight.sightNotes is not None ->
# div +:= XHTML rendering of sight.sightNotes
# else -> I ]
if sight.sightNotes is not None:
self.__sightNotes ( div, sight.sightNotes )
For general notes on the rendering of a birdnotes.AgeSexGroup instance, see Section 4.10, “XHTML rendering of form data”.
# - - - M o n t h C e l l . _ _ a g e S e x G r o u p
SEX_CODE_MAP = { 'u': u'', 'm': u'\u2642', 'f': u'\u2640' }
def __ageSexGroup ( self, parent, ageSexGroup ):
'''Render a birdnotes.AgeSexGroup instance into XHTML.
[ (parent is an et.Element) and
(ageSexGroup is a birdNotes.AgeSexGroup instance) ->
parent +:= XHTML rendering of sight.ageSexGroup ]
'''
For general notes on the generated output, see Section 4.10, “XHTML rendering of form data”. First we render the count of individuals, if any.
#-- 1 --
# [ if ageSexGroup.count is not None ->
# parent +:= ageSexGroup.count
# else -> I ]
if ageSexGroup.count is not None:
tccpage2.addTextMixed ( parent, ageSexGroup.count )
The age code comes next. Code 'p' is
rendered as Greek letter phi (ϕ).
#-- 2 --
# [ if ageSexGroup.age is None ->
# I
# else if ageSexGroup.age is 'p' ->
# parent +:= Greek letter phi
# else ->
# parent +:= ageSexGroup.age ]
if ageSexGroup.age is not None:
if ageSexGroup.age == 'p':
tccpage2.addTextMixed ( parent, PHI )
else:
tccpage2.addTextMixed ( parent, ageSexGroup.age )
Add the sex code, questionability status, and
fide details. Although
the Unicode entities for the male symbol
“♂” (♂)
and female symbol “♀” (♀) don't currently render correctly
in the PDF for this document, as of May 2011 all the
browsers I checked (Camino, Chrome, Firefox, Opera, and
Safari) PDF for this document do correctly display these
symbols.
#-- 3 --
# [ if ageSexGroup.sex is not None ->
# parent +:= ageSexGroup.sex
# else -> I ]
if ageSexGroup.sex is not None:
tccpage2.addTextMixed ( parent,
self.SEX_CODE_MAP[ageSexGroup.sex] )
#-- 4 --
# [ if ageSexGroup.q is '?' ->
# parent +:= '?'
# else if ageSexGroup.q is '-' ->
# parent +:= ' [uncountable]'
# else -> I ]
if ageSexGroup.q is '?':
tccpage2.addTextMixed ( parent, '?' )
elif ageSexGroup.q is '-':
tccpage2.addTextMixed ( parent, ' [uncountable]' )
#-- 5 --
# [ if ageSexGroup.fide is not None ->
# parent +:= '['+(a span element of class=GENUS_CLASS
# containing 'fide')+' '+ageSexGroup.fide+']'
if ageSexGroup.fide is not None:
tccpage2.addTextMixed ( parent, '[' )
self.__span ( parent, GENUS_CLASS, 'fide' )
tccpage2.addTextMixed ( parent, ' %s]' % ageSexGroup.fide )
#-- 6 --
# [ parent +:= ' ' ]
tccpage2.addTextMixed ( parent, ' ' )
This static method takes a month key of the form ' and returns the corresponding
text yyyy-mm'''. For the
mapping from month numbers to month names, see Section 10.1, “monthName,
yyyyMONTH_NAME_MAP: Translate month
numbers to month names”.
# @staticmethod
def monthName ( yyyy_mm ):
'''Translate a month key to a month name.
[ yyyy_mm is a month key as 'yyyy-mm' ->
return the month name as 'monthName yyyy' ]
'''
#-- 1 --
# [ yyyy := the year part
# mm := the month part ]
#--
# 01234567
# yyyy-mm
#--
yyyy = yyyy_mm[:4]
mm = yyyy_mm[5:7]
#-- 2 --
return '%s %s' % (MONTH_NAME_MAP[mm], yyyy)
monthName = staticmethod ( monthName )