Next / Previous / Contents / TCC Help System / NM Tech homepage

23. class MonthCell: One table cell

Each instance of this class holds all the information we need to build one month's cell in the index table.

noteweb
# - - - - -   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 ]
    '''

23.1. MonthCell.__init__(): Constructor

noteweb
# - - -   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 ) )

23.2. MonthCell.fileName(): Path to the month's page

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.

noteweb
# - - -   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) )

23.3. MonthCell.writePage(): Render as XHTML

This method creates the page containing one month's notes, provided the page is out of date.

noteweb
# - - -   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.

noteweb
        #-- 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 Previous and Next links can point to them.

noteweb
        #-- 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”.

noteweb
        #-- 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”.

noteweb
        #-- 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.

noteweb
        #-- 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()

23.4. MonthCell.__pageFrame(): Set up a basic page

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”.

noteweb
# - - -   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: Next links to nextURL; Previous links to prevURL; Contents goes to the index page; and Home 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.

noteweb
        #-- 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.

noteweb
        #-- 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.

noteweb
        #-- 3 --
        return tccpage2.TCCPage ( self.title, navList,
            logoImage=ZDP_LOGO, logoLink=ZDP_URL, cssUrl=CSS_URL )

23.5. MonthCell.__renderPage(): Add the notes content

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 “yyyy-dd-mm” date strings as anchor names. We'll have to add a suffix to separate them, e.g., “2008-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.

noteweb
# - - -   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.

noteweb
        #-- 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”.

noteweb
        #-- 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”.

noteweb
        #-- 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] )

23.6. MonthCell.__pageTOC(): Generate page table of contents

This method generates the table of contents for one monthly page. For the XHTML generated, see Section 4.2, “XHTML for the month page”.

noteweb
# - - -   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”.

noteweb
        #-- 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”.

noteweb
        #-- 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.

noteweb
        #-- 3 --
        return anchorMap

23.7. MonthCell.__dayTOC(): Month table of contents entry for one day

This method generates one entry in the table of contents at the top of the monthly page.

noteweb
# - - -   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 “yyyy-mm-dd”). The anchorSet 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.

noteweb
        #-- 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.

noteweb
        #-- 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.

noteweb
        #-- 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”.

noteweb
        #-- 5 --
        # [ if dayNotes contains any notable records ->
        #     li  +:=  a div element containing the bird names
        #         from notable records
        #   else -> I ]
        self.__notablesBlock ( li, dayNotes )

23.8. MonthCell.__notablesBlock(): Display any notable records

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.

noteweb
# - - -   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.

noteweb
        #-- 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.

noteweb
        #-- 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”.

noteweb
        #-- 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 ) )

23.9. MonthCell.__span(): Add a span inline

This service routine is used to add to some parent element a span child with a given class and textual content.

noteweb
# - - -   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

23.10. MonthCell.__dayBlock(): Render one daily note set

Each DayNotes instance is rendered into these XHTML elements:

noteweb
# - - -   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 )

23.11. MonthCell.__dayTitle(): Render the title for one daily block

noteweb
# - - -   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()

23.12. MonthCell.__daySummary(): Render the daily summary block

This method renders the day-summary content for one day-notes.

noteweb
# - - -   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”.

noteweb
        #-- 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”.

noteweb
        #-- 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”.

noteweb
        #-- 4 --
        # [ summaryDiv  +:=  blocks displaying day-annotation
        #       content from dayNotes.daySummary, if any ]
        self.__dayAnnotation ( summaryDiv, dayNotes.daySummary )

23.12.1. Historical note

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.

23.13. MonthCell.__locDef(): Display a locality definition

See Section 4.5, “XHTML for locality definitions”.

noteweb
# - - -   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.

noteweb
        #-- 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.

noteweb
        #-- 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.

noteweb
        #-- 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) )

23.14. MonthCell.__dayAnnotation(): Render day-annotation content

noteweb
# - - -   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”.

noteweb
        #-- 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”.

noteweb
        #-- 5 --
        if daySummary.notes is not None:
            self.__narrative ( parent, daySummary.notes )

23.15. MonthCell.__annoBlock(): Annotation block with a label

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”.

noteweb
# - - -   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”.

noteweb
        #-- 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.

noteweb
        #-- 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 )

23.16. MonthCell.__narrative(): Render a Narrative instance

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.

noteweb
# - - -   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 )

23.17. MonthCell.__paragraph(): Render one paragraph

This method renders a birdnotes.Paragraph instance as an ordinary text paragraph.

noteweb
# - - -   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.

noteweb
        #-- 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”.

noteweb
        #-- 2 --
        # [ div  +:=  div elements containing XHTML rendering
        #             of the content of para ]
        self.__paraContent ( div, para )

23.18. MonthCell.__paraContent(): Content of one paragraph

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 P, method P.genContent() produces a list of (tag, text) tuples.

  • If the tag is None, the text is plain text, not marked up. We use tccpage2.addTextMixed() to add it to the structure wherever the next text string goes.

  • If the tag is not None, its value becomes the class attribute of a span element containing the text.

noteweb
# - - -   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 )

23.19. MonthCell.__birdForm(): Render one BirdForm

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.

noteweb
# - - -   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”.

noteweb
        #-- 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:

noteweb
        #-- 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 )

23.20. MonthCell.__singleSighting(): Single-sighting case

See the notes about overall structure in Section 23.19, “MonthCell.__birdForm(): Render one BirdForm.

noteweb
# - - -   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”.

noteweb
        #-- 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”.

noteweb
        #-- 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”.

noteweb
        #-- 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 )

23.21. MonthCell.__multiSighting(): Multiple-sighting case

See the general remarks in Section 23.19, “MonthCell.__birdForm(): Render one BirdForm.

noteweb
# - - -   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.

noteweb
        #-- 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”.

noteweb
        #-- 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”.

noteweb
        #-- 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 )

23.22. MonthCell.__locGroup(): Render a locality group

For a discussion of how this method generates both inline and block content, see Section 4.10, “XHTML rendering of form data”.

noteweb
# - - -   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”.

noteweb
        #-- 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”.

noteweb
        #-- 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 )

23.23. MonthCell.__sightNotes(): Render a sighting notes group

For an overview of the generated output, see Section 4.11, “XHTML rendering of sighting notes”.

noteweb
# - - -   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.

noteweb
        #-- 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.

noteweb
        #-- 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”.

noteweb
        #-- 6 --
        if sightNotes.notes is not None:
            self.__narrative ( parent, sightNotes.notes )

23.24. MonthCell.__photo(): Generate a photo reference or link

For the XHTML generated here, see Section 4.12, “XHTML rendering of photo links”.

noteweb
# - - -   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.

noteweb
        #-- 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 )

23.25. MonthCell.__floc(): Generate one of multiple sightings

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.

noteweb
# - - -   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”.

noteweb
        #-- 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”.

noteweb
        #-- 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”.

noteweb
        #-- 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 )

23.26. MonthCell.__ageSexGroup(): Render age-sex-group content

For general notes on the rendering of a birdnotes.AgeSexGroup instance, see Section 4.10, “XHTML rendering of form data”.

noteweb
# - - -   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.

noteweb
        #-- 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 (ϕ).

noteweb
        #-- 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 “♂” (&#x2642;) and female symbol “♀” (&#x2640;) 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.

noteweb
        #-- 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, ' ' )

23.27. MonthCell.monthName(): Translate a month key to a month name (static method)

This static method takes a month key of the form 'yyyy-mm' and returns the corresponding text 'monthName, yyyy'. For the mapping from month numbers to month names, see Section 10.1, “MONTH_NAME_MAP: Translate month numbers to month names”.

noteweb
#   @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 )