#!/usr/bin/env python #================================================================ # noteweb: Render XML bird field notes into HTML # For documentation, see: # http://www.nmt.edu/~shipman/aba/doc/noteweb/ #---------------------------------------------------------------- #================================================================ # Imports #---------------------------------------------------------------- import sys, os, stat, re import birdnotes import txny as taxonomy import lxml.etree as et import tccpage2 #================================================================ # Manifest constants #---------------------------------------------------------------- MONTH_NAME_MAP = { '01': 'January', '02': 'February', '03': 'March', '04': 'April', '05': 'May', '06': 'June', '07': 'July', '08': 'August', '09': 'September', '10': 'October', '11': 'November', '12': 'December' } MONTH_SEASON_MAP = { '01': 'winter', '02': 'winter', '03': 'spring', '04': 'spring', '05': 'spring', '06': 'summer', '07': 'summer', '08': 'fall', '09': 'fall', '10': 'fall', '11': 'fall', '12': 'winter' } YEAR_PAT = re.compile ( r'[12]' # First digit must be 1 or 2 r'\d{3}' # Followed by three more digits r'$' ) # Insure that the entire string was matched YYYY_MM_XML_PAT = re.compile ( r'[12]\d{3}' # Matches 1000-2999 r'\-' # Matches '-' r'[01]\d' # Matches 00-19 r'\.' # Matches '.' r'xml' # Matches 'xml' r'$' ) # End-of line anchor: insure a complete match HTML_EXT = '.html' INDEX_PAGE_NAME = 'field' INDEX_PAGE_TITLE = "John W. Shipman's Field Notes" HOME_PAGE_URL = 'http://www.nmt.edu/~shipman/' CONVENTIONS_URL = '/~shipman/aba/' SEASONS_CLASS = 'seasons' YEAR_GROUP_FREQUENCY = 5 YEAR_GROUP_CLASS = 'year-group' YEAR_ROW_CLASS = 'year-row' ROW_LABEL_CLASS = 'row-label' NBSP = u'\u00a0' PHI = u'\u03d5' CSS_URL = 'http://www.nmt.edu/~shipman/aba/doc/noteweb/birdnotes.css' ZDP_LOGO = 'http://www.nmt.edu/~shipman/zdplogo.png' ZDP_URL = 'http://www.nmt.edu/~shipman/z' LOC_CHILD_CLASS = 'loc-child' NOTABLE_CLASS = 'notable' DAY_SUMMARY_CLASS = 'day-summary' LOC_DEF_CLASS = 'loc-def' LOC_NARRATIVE_CLASS = 'loc-narrative' LOC_LABEL_CLASS = 'loc-label' PARA_CLASS = 'para' FORM_CLASS = 'form' NOTABLE_FORM_CLASS = 'notable-form' BIRD_NAME_CLASS = 'bird-name' FLOC_CLASS = 'floc' GENUS_CLASS = 'genus' # - - - m a i n def main(): '''Main program. [ file 'aou.xml' is a readable, valid taxonomy file -> if all monthly files under the current directory are valid against birdnotes.rnc -> monthly web pages := rendering of those monthly files into XHTML, in the same directory, with the names changed from .xml to .html index page := table of links to monthly web pages else -> sys.stderr +:= error message(s) (monthly web pages, index page) := (anything) ] ''' #-- 1 -- # [ if there is a readable aou.xml in the current directory # that is valid against txny.rnc -> # txny := a Txny instance representing that file # else -> # sys.stderr +:= error message(s) # stop execution ] try: txny = taxonomy.Txny() except IOError, detail: print >>sys.stderr, ( "*** Can't read the taxonomy file: %s" % detail ) raise SystemExit #-- 2 -- # [ yearCollection := a new, empty YearCollection instance ] yearCollection = YearCollection() #-- 3 -- # [ yearCollection := yearCollection with years added # corresponding to subdirectories of '.' with year # names 1000-2999 ] # sys.stderr +:= error message(s) from invalid monthly XML # files in those subdirectories findYears ( txny, yearCollection ) #-- 4 -- # [ monthly web pages := XHTML renderings of BirdNoteSet # instances from yearCollection ] for yearRow in yearCollection.genYearsRev(): yearRow.writeMonthPages() #-- 5 -- # [ index page := table of links to monthly web pages # from yearCollection ] buildIndex ( yearCollection ) # - - - f i n d Y e a r s def findYears ( txny, yearCollection ): '''Build the list of years. [ let year-dirs == (subdirectories of '.' with four-digit names in the range 1000-2999) in: (txny is a taxonomy.Txny instance) and (yearCollection is a YearCollection instance) -> sys.stderr +:= error messages from invalid monthly XML files in year-dirs yearCollection := yearCollection with years added corresponding to year-dirs ] ''' #-- 1 -- # [ yyyyList := subdirectories of '.' that match YEAR_PAT ] yyyyList = [ dirName for dirName in os.listdir('.') if YEAR_PAT.match(dirName) is not None ] #-- 2 -- # [ yyyyList := yyyyList sorted in ascending order ] yyyyList.sort() #-- 3 -- # [ yearCollection := yearCollection with valid monthly # data added from XML files in its members' subdirectories # sys.stderr +:= error message(s) about invalid monthly # XML files in that set, if any ] for yyyy in yyyyList: #-- 3.1 -- # [ yearCollection := yearCollection with a new YearRow # added for year=yyyy # yearRow := that new YearRow instance ] yearRow = yearCollection.addYear ( txny, yyyy ) #-- 3.2 -- # [ yearRow := yearRow with MonthCell instances added # for XML notes files in directory yearRow.yyyy # valid against birdnotes.rnc and txny # sys.stderr +:= error message(s) for invalid XML # notes files in that directory, if any ] yearRow.readAllMonths() # - - - b u i l d I n d e x def buildIndex ( yearCollection ): '''Build the index page. [ yearCollection is a YearCollection instance -> index page +:= table of links to monthly web pages made from yearCollection ] ''' #-- 1 -- # [ page := a new tccpage2.TCCPage instance with navigation # set up for the index page ] page = pageFrame() #-- 2 -- # [ page.content +:= boilerplate content for the index page ] indexBoilerplate ( page.content ) #-- 3 -- # [ page.content +:= the index table made from yearCollection ] indexTable ( page.content, yearCollection ) #-- 4 -- # [ page.content +:= a link to this document ] p = et.SubElement ( page.content, 'p' ) p.text = "These pages are generated automatically; see " docLink = et.SubElement ( p, 'a', href='http://www.nmt.edu/~shipman/aba/doc/noteweb/' ) docLink.text = "the documentation" docLink.tail = "." #-- 5 -- # [ INDEX_PAGE_NAME+HTML_EXT can be created new -> # that file := XHTML serialization of page ] try: indexName = INDEX_PAGE_NAME + HTML_EXT indexFile = open ( indexName, 'w' ) page.write ( indexFile ) indexFile.close() except IOError, detail: print >>sys.stderr, ( "*** Can't write the index page " "'%s'." % indexName ) # - - - P a g e F r a m e def pageFrame(): '''Sets up the tccpage2.TCCPage instance for the index page. [ return a new tccpage2.TCCPage instance with navigation set up for the index page ] ''' #-- 1 -- # [ navList := a list of tccpage2.NavLink instances # representing the navigational features ] navList = ( tccpage2.NavLink ( 'Next', [] ), tccpage2.NavLink ( 'Previous', [] ), tccpage2.NavLink ( 'Contents', [] ), tccpage2.NavLink ( 'Home', [("Shipman's Home Sweet Homepage", HOME_PAGE_URL)] ) ) #-- 2 -- return tccpage2.TCCPage ( INDEX_PAGE_TITLE, navList, logoImage=ZDP_LOGO, logoLink=ZDP_URL, cssUrl=CSS_URL ) # - - - i n d e x B o i l e r P l a t e def indexBoilerplate ( parent ): '''Add the fixed content to the index page. [ parent is an et.Element -> parent +:= fixed content for the index page ] ''' #-- 1 -- # [ parent +:= paragraph about seasons ] p1 = et.SubElement ( parent, 'p' ) p1.text = ( 'Notes are grouped according to the reporting ' 'seasons for ' ) cite = et.SubElement (p1, 'cite' ) cite.text = 'Audubon Field Notes' cite.tail = '.' #-- 2 -- # [ parent +:= paragraph linking to conventions page ] p2 = et.SubElement ( parent, 'p' ) p2.text = 'See ' conLink = et.SubElement ( p2, 'a', href=CONVENTIONS_URL ) conLink.text = "How to read Shipman's field notes" conLink.tail = ( ' for notational conventions and the ' "author's contact information." ) # - - - i n d e x T a b l e def indexTable ( parent, yearCollection ): '''Build the table of yearly rows and monthly link cells. [ (parent is an et.Element) and (yearCollection is a YearCollection instance) -> parent +:= the index table made from yearCollection ] ''' #-- 1 -- # [ parent +:= a new table element containing column # definitions, column headings, and an empty tbody # element # tbody := that tbody element ] tbody = indexTableFrame ( parent ) #-- 2 - # [ tbody +:= table rows made from the YearRow instances # in YearCollection, in reverse chronological order ] for yearRow in yearCollection.genYearsRev(): #-- 2 body -- # [ yearRow is a YearRow instance -> # tbody +:= a tr element made from yearRow ] buildRow ( tbody, yearRow ) # - - - i n d e x T a b l e F r a m e def indexTableFrame ( parent ): '''Build an empty table. [ parent is an et.Element -> parent +:= a new table element containing column definitions, column headings, and an empty tbody element return that tbody element ] ''' #-- 1 -- # [ parent +:= a new table element with class=SEASONS_CLASS # table := that element ] table = et.SubElement ( parent, 'table', border='5', cellpadding='5', width='100%' ) table.attrib['class'] = SEASONS_CLASS #-- 2 -- # [ table +:= new col elements declaring the column # properties ] et.SubElement ( table, 'colgroup', align='right' ) et.SubElement ( table, 'colgroup', span='12', align='left', width='*' ) #-- 3 -- # [ table +:= a thead element containing the column # labels ] thead = et.SubElement ( table, 'thead' ) headRow = et.SubElement ( thead, 'tr' ) headRow.attrib['class'] = YEAR_ROW_CLASS headLabel = et.SubElement ( headRow, 'th' ) headLabel.text = 'Year' winter1 = et.SubElement ( headRow, 'th', colspan='2' ) winter1.attrib['class'] = 'winter' winter1.text = 'Winter' spring = et.SubElement ( headRow, 'th', colspan='3' ) spring.attrib['class'] = 'spring' spring.text = 'Spring' summer = et.SubElement ( headRow, 'th', colspan='2' ) summer.attrib['class'] = 'summer' summer.text = 'Summer' fall = et.SubElement ( headRow, 'th', colspan='4' ) fall.attrib['class'] = 'fall' fall.text = 'Fall' winter2 = et.SubElement ( headRow, 'th' ) winter2.attrib['class'] = 'winter' winter2.text = 'Winter' #-- 4 -- # [ table +:= a tbody element # tbody := that element ] tbody = et.SubElement ( table, 'tbody' ) #-- 5 -- return tbody # - - - b u i l d R o w def buildRow ( tbody, yearRow ): '''Builds one row of the index table, holding one year. [ (tbody is an et.Element) and (yearRow is a YearRow instance) -> tbody +:= a tr element made from yearRow ] ''' #-- 1 -- # [ tbody := a new 'tr' element # tr := that element ] tr = et.SubElement ( tbody, 'tr' ) #-- 2 -- # [ if int(yearRow.yyyy) is divisible by YEAR_GROUP_FREQUENCY -> # tr +:= a class=YEAR_GROUP_CLASS attribute # else -> # tr +:= a class=YEAR_ROW_CLASS attribute ] if ( int(yearRow.yyyy) % YEAR_GROUP_FREQUENCY ) == 0: tr.attrib['class'] = YEAR_GROUP_CLASS else: tr.attrib['class'] = YEAR_ROW_CLASS #-- 3 -- # [ tr +:= a th element with class=ROW_LABEL_CLASS # containing yearRow.yyyy ] rowLabel = et.SubElement ( tr, 'th' ) rowLabel.attrib['class'] = ROW_LABEL_CLASS rowLabel.text = yearRow.yyyy #-- 4 -- # [ tr +:= twelve cells containing links to the elements # of yearRow corresponding to the twelve months, or # non-breaking spaces where months are absent ] for monthKey in [ '%02d' % x for x in range(1,13) ]: #-- 4 body -- # [ monthKey is in the range ['01', '02', ..., '12'] -> # if yearRow[monthKey] exists -> # tr +:= a td element containing a link to # the month page for yearRow[monthKey] # else -> # tr +:= a td element containing a non-breaking # space ] buildMonthCell ( tr, yearRow, monthKey ) # - - - b u i l d M o n t h C e l l def buildMonthCell ( tr, yearRow, monthKey ): '''Add one month to the index table: a link or a non-breaking space. [ (tr is an et.Element) and (yearRow is a YearRow instance) and (monthKey is in the sequence '01', '02', ... '12') -> if yearRow[monthKey] exists -> tr +:= a td element containing a link to the month page for yearRow[monthKey] else -> tr +:= a td element containing a non-breaking space ] ''' #-- 1 -- # [ tr +:= a new td element # td := that element ] td = et.SubElement ( tr, 'td' ) #-- 2 -- # [ td +:= a 'class' attribute equal to the season for # the month whose number is monthKey ] td.attrib['class'] = MONTH_SEASON_MAP[monthKey] #-- 3 -- # [ if yearRow[monthKey] exists -> # monthCell := the corresponding value # else -> # td.text := a non-breaking space # return ] try: monthCell = yearRow[monthKey] except KeyError: td.text = NBSP return #-- 4 -- # [ td +:= a link to URL monthCell.fileName(), with the link # text the name month number (monthKey) ] monthFileName = monthCell.fileName() a = et.SubElement ( td, 'a', href=monthFileName ) a.text = MONTH_NAME_MAP[monthKey] # - - - - - c l a s s Y e a r C o l l e c t i o n class YearCollection: '''Represents the entire set of years and months. Exports: YearCollection(): [ return a new, empty YearCollection instance ] .addYear ( yyyy ): [ yyyy is a year number as a four-digit string -> self := self with a new, empty YearRow added for year=yyyy return that YearRow ] .__getitem__(self, yyyy): [ yyyy is a year number as a four-digit string -> if yyyy is contained in self -> return the corresponding YearRow instance else -> raise KeyError ] .genYearsRev(): [ generate self's contained YearRow instances in reverse chronological order ] .neighbors ( yyyy_mm ): [ yyyy_mm is a month string as 'yyyy-mm' -> let: prev == the month in self prior to yyyy_mm in chronological sequence as a 'yyyy-mm' string, or None if first next == the month in self after yyyy_mm in chronological sequence as a 'yyyy-mm' string, or None if last in: return (prev,next) ] .findMonth ( yyyy_mm ): [ yyyy_mm is a month key string as 'yyyy-mm' -> if self has a month with that key -> return the corresponding MonthCell else -> raise KeyError ] State/Invariants: .__yearMap: [ a dictionary whose keys are the 'yyyy' strings of years in self, and each corresponding value is a YearRow instance for that year ] ''' # - - - Y e a r C o l l e c t i o n . _ _ i n i t _ _ def __init__ ( self ): '''Constructor for YearCollection. ''' self.__yearMap = {} # - - - Y e a r C o l l e c t i o n . a d d Y e a r def addYear ( self, txny, yyyy ): '''Add a new year row ''' yearRow = YearRow ( self, txny, yyyy ) self.__yearMap[yyyy] = yearRow return yearRow # - - - Y e a r C o l l e c t i o n . _ _ g e t i t e m _ _ def __getitem__ ( self, yyyy ): '''Implements self[yyyy] ''' return self.__yearMap[yyyy] # - - - Y e a r C o l l e c t i o n . g e n Y e a r s R e v def genYearsRev ( self ): '''Generate years in reverse chronological order ''' #-- 1 -- # [ yyyyList := keys of self.__yearMap in descending # order ] yyyyList = self.__yearMap.keys() yyyyList.sort() yyyyList.reverse() #-- 2 -- # [ generate the YearRow instances in self in order by # the elements of yyyyList ] for yyyy in yyyyList: yield self[yyyy] #-- 3 -- raise StopIteration # - - - Y e a r C o l l e c t i o n . n e i g h b o r s def neighbors ( self, yyyy_mm ): '''Find the previous/next months to yyyy_mm in sequence ''' #-- 1 -- # [ if yyyy_mm has a predecessor in self -> # prev := that predecessor's 'yyyy-mm' month key # else -> # prev := None ] prev = self.__findPrev ( yyyy_mm ) #-- 2 -- # [ if yyyy_mm has a successor in self -> # next := that successor's 'yyyy-mm' month key # else -> # next := None ] next = self.__findNext ( yyyy_mm ) #-- 3 -- return (prev, next ) # - - - Y e a r C o l l e c t i o n . _ _ f i n d P r e v def __findPrev ( self, yyyy_mm ): '''Find the month preceding yyyy_mm. [ yyyy_mm is a month in self with key string 'yyyy-mm' -> if self has any months before yyyy_mm -> return the month key of the immediate predecessor else -> return None ] ''' #-- 1 -- # [ yyyy := year part of yyyy_mm # mm := month part of yyyy_mm ] yyyy, mm = yyyy_mm.split('-') #-- 2 -- # [ yyyy is a year in self -> # yearRow := YearRow instance for year yyyy ] yearRow = self[yyyy] #-- 3 -- # [ if yearRow has a predecessor to month mm -> # return that month's key as a string 'mm' # else -> I ] try: prev = yearRow.predecessor ( mm ) return '%s-%s' % (yyyy, prev) except KeyError: pass #-- 4 -- # [ yyyyList := all year key strings in self, sorted # in descending order ] yyyyList = self.__yearMap.keys() yyyyList.sort() yyyyList.reverse() #-- 5 -- # [ yyyy is an element of yyyyList -> # pos := yyyy's position in yyyyList ] pos = yyyyList.index ( yyyy ) #-- 6 -- # [ if any year with a key in yyyyList[pos+1:] has at # least one month in it -> # return the yyyy-mm key of the last month in the # first such year # else -> I ] for prevYear in yyyyList[pos+1:]: prevRow = self[prevYear] if len(prevRow) > 0: lastMM = prevRow.lastMonth() return '%s-%s' % (prevYear, lastMM) #-- 7 -- return None # - - - Y e a r C o l l e c t i o n . _ _ f i n d N e x t def __findNext ( self, yyyy_mm ): '''Find the next month chronologically after yyyy_mm. [ yyyy_mm is a month in self with key string 'yyyy-mm' -> if self has any months after yyyy_mm -> return the month key of the immediate successor else -> return None ] ''' #-- 1 -- # [ yyyy := year part of yyyy_mm # mm := month part of yyyy_mm ] yyyy, mm = yyyy_mm.split('-') #-- 2 -- # [ yyyy is a year in self -> # yearRow := YearRow instance for year yyyy ] yearRow = self[yyyy] #-- 3 -- # [ if yearRow has a successor to month mm -> # return that month's key as a string 'mm' # else -> I ] try: next = yearRow.successor ( mm ) return '%s-%s' % (yyyy, next) except KeyError: pass #-- 4 -- # [ yyyyList := all year key strings in self, sorted # in ascending order ] yyyyList = self.__yearMap.keys() yyyyList.sort() #-- 5 -- # [ yyyy is an element of yyyyList -> # pos := yyyy's position in yyyyList ] pos = yyyyList.index ( yyyy ) #-- 6 -- # [ if any year with a key in yyyyList[pos+1:] has at # least one month in it -> # return the yyyy-mm key of the first month in # the first such year # else -> I ] for nextYear in yyyyList[pos+1:]: nextRow = self[nextYear] if len(nextRow) > 0: firstMM = nextRow.firstMonth() return '%s-%s' % (nextYear, firstMM) #-- 7 -- return None # - - - - - c l a s s Y e a r R o w class YearRow: '''Represents one year's line in the index table. Exports: YearRow ( yearCollection, txny, yyyy ): [ (yearCollection is the parent YearCollection) and (txny is a taxonomy.Txny instance) and (yyyy is a four-digit year as a string) -> return a new, empty YearRow with year=yyyy ] .yearCollection: [ as passed to constructor, read-only ] .txny: [ as passed to constructor, read-only ] .yyyy: [ as passed to constructor, read-only ] .__len__(self): [ return the number of months in self ] .__getitem(self, mm): [ mm is a month key as 'mm' -> if self has a month mm -> return the corresponding MonthCell else -> raise KeyError ] .firstMonth(): [ if self has any months -> return the month key of the first as 'mm' else -> raise KeyError ] .lastMonth(): [ if self has any months -> return the month key of the last as 'mm' else -> raise KeyError ] .predecessor ( mm ): [ if 'mm' is a month key in self -> if self has any months before mm -> return the last such month key as 'mm' else -> raise KeyError ] .successor ( mm ): [ if 'mm' is a month key in self -> if self has any months after mm -> return the first such month key as 'mm' else -> raise KeyError ] .readAllMonths(): [ self := self with MonthCell instances added for XML notes files in directory yyyy valid against birdnotes.rnc and txny sys.stderr +:= error message(s) for invalid XML notes files in that directory, if any ] State/Invariants: .__monthMap: [ a dictionary whose keys are two-digit, zero-filled month numbers, and each corresponding value is the MonthCell instance representing that month's field notes ] ''' # - - - Y e a r R o w . _ _ i n i t _ _ def __init__ ( self, yearCollection, txny, yyyy ): '''Constructor ''' self.yearCollection = yearCollection self.txny = txny self.yyyy = yyyy self.__monthMap = {} # - - - Y e a r R o w . _ _ l e n _ _ def __len__ ( self ): '''Return the number of contained months. ''' return len(self.__monthMap) # - - - Y e a r R o w . _ _ g e t i t e m _ _ def __getitem__ ( self, mm ): '''Retrieve one month ''' return self.__monthMap[mm] # - - - Y e a r R o w . f i r s t M o n t h def firstMonth(self): '''Retrieve self's first month number, if any. ''' #-- 1 -- if len(self) == 0: raise KeyError #-- 2 -- # [ monthKeyList := month keys in self in ascending order ] monthKeyList = self.__monthMap.keys() monthKeyList.sort() #-- 3 -- # [ return the month number from the first element # of monthKeyList ] return self[monthKeyList[0]].mm # - - - Y e a r R o w . l a s t M o n t h def lastMonth(self): '''Retrieve self's last month, if any. ''' #-- 1 -- if len(self) == 0: raise KeyError #-- 2 -- # [ monthKeyList := month keys in self in ascending order ] monthKeyList = self.__monthMap.keys() monthKeyList.sort() #-- 3 -- # [ return the month number from the last element # of monthKeyList ] return self[monthKeyList[-1]].mm # - - - Y e a r R o w . p r e d e c e s s o r def predecessor ( self, mm ): '''Find the month preceding month mm, if any ''' #-- 1 -- # [ mmList := keys of self.__monthMap in descending order ] mmList = self.__monthMap.keys() mmList.sort() mmList.reverse() #-- 2 -- # [ mm is a member of mmList -> # pos := position of mm in mmList ] pos = mmList.index ( mm ) #-- 3 -- # [ if pos+1 >= len(mmList) -> # raise KeyError # else -> # return mmList[pos+1] ] if pos+1 >= len(mmList): raise KeyError else: return mmList[pos+1] # - - - Y e a r R o w . s u c c e s s o r def successor ( self, mm ): '''Find the following month. ''' #-- 1 -- # [ mmList := keys of self.__monthMap in ascending order ] mmList = self.__monthMap.keys() mmList.sort() #-- 2 -- # [ mm is a member of mmList -> # pos := position of mm in mmList ] pos = mmList.index ( mm ) #-- 3 -- # [ if pos+1 >= len(mmList) -> # raise KeyError # else -> # return mmList[pos+1] ] if pos+1 >= len(mmList): raise KeyError else: return mmList[pos+1] # - - - Y e a r R o w . r e a d A l l M o n t h s def readAllMonths ( self ): '''Find and read all monthly files for this year. ''' #-- 1 -- # [ monthFileNameList := names from directory # ('./'+self.yyyy) that match YYYY_MM_XML_PAT ] monthFileNameList = [ fileName for fileName in os.listdir ( self.yyyy ) if YYYY_MM_XML_PAT.match(fileName) is not None ] #-- 2 -- # [ self := self with MonthCell instances added # representing files in monthFileNameList # valid against birdnotes.rnc and txny # sys.stderr +:= error message(s) for invalid # files, if any ] for monthFileName in monthFileNameList: #-- 2 body -- # [ if monthFileName names a readable file # valid against birdnotes.rnc and txny -> # self.__monthMap +:= an entry whose key is the # month number and whose value is a MonthCell # representing that file # else -> # sys.stderr +:= error message(s) ] self.readOneMonth ( monthFileName ) # - - - Y e a r R o w . r e a d O n e M o n t h def readOneMonth ( self, monthFileName ): '''Try to read one month's field data. [ monthFileName is a string matching YYYY_MM_XML_PAT -> if monthFileName names a readable file valid against birdnotes.rnc and txny -> self.__monthMap +:= an entry whose key is the month number and whose value is a MonthCell representing that file else -> sys.stderr +:= error message(s) ] ''' #-- 1 -- # [ mm := month number from monthFileName # monthPath := self.yyyy + '/' + monthFileName ] #-- # 0 1 # 01234567890 # yyyy-mm.xml #-- mm = monthFileName[5:7] monthPath = '%s/%s' % (self.yyyy, monthFileName) #-- 2 -- # [ birdNoteSet := a new birdnotes.BirdNoteSet instance # with taxonomy self.txny ] birdNoteSet = birdnotes.BirdNoteSet ( self.txny ) #-- 3 -- # [ if monthPath names a readable file valid against # birdnotes.rnc -> # birdNoteSet := birdNoteSet with data added # from that file # else -> # sys.stderr +:= error message(s) # return ] try: birdNoteSet.readFile ( monthPath ) except IOError, detail: print >>sys.stderr, ( "*** Invalid monthly file " "'%s': %s" % (monthFileName, detail) ) return #-- 4 -- # [ self.__monthMap[mm] := a new MonthCell instance # with self as the parent, month=mm, and # birdNoteSet=birdNoteSet ] self.__monthMap[mm] = MonthCell ( self, mm, birdNoteSet ) # - - - Y e a r R o w . w r i t e M o n t h P a g e s def writeMonthPages ( self ): '''Generate all monthly pages for this year. [ monthly web pages := rendering of valid monthly XML files under directory self.year, if any ] ''' #-- 1 -- for mm in self.__monthMap: monthCell = self.__monthMap[mm] monthCell.writePage() # - - - - - 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 ) ) # - - - 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) ) # - - - 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. ''' #-- 1 -- # [ if self.fileName() names an existing file whose # modification time is greater than # self.birdNoteSet.newestTime -> # return # else -> # outFileName := self.fileName() ] outFileName = self.fileName() 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 #-- 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 ) #-- 3 -- # [ page := a tccpage2.TCCPage instance with # navigation links (prev, next) ] page = self.__pageFrame ( prev, next ) #-- 4 -- # [ page := page with self.birdNoteSet rendered as XHTML ] self.__renderPage(page) #-- 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() # - - - 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) ] ''' #-- 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)] #-- 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)] ) ) #-- 3 -- return tccpage2.TCCPage ( self.title, navList, logoImage=ZDP_LOGO, logoLink=ZDP_URL, cssUrl=CSS_URL ) # - - - 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 ] ''' #-- 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" #-- 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 ) #-- 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] ) # - - - 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 ] ''' #-- 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 = {} #-- 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 ) #-- 3 -- return anchorMap # - - - 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 ] ''' #-- 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) #-- 2 -- # [ anchorSet := union(anchorSet, newAnchor) # anchorMap +:= an entry whose key is dayNotes and # whose value is newAnchor ] anchorSet.add ( newAnchor ) anchorMap[dayNotes] = newAnchor #-- 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() #-- 5 -- # [ if dayNotes contains any notable records -> # li +:= a div element containing the bird names # from notable records # else -> I ] self.__notablesBlock ( li, dayNotes ) # - - - 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 ] ''' #-- 1 -- notablesList = [ birdForm for birdForm in dayNotes.genForms() if birdForm.notable ] #-- 2 -- if len(notablesList) == 0: return #-- 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 #-- 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 ) ) # - - - 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 # - - - 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() # - - - 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 ] ''' #-- 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 ) #-- 3 -- # [ summaryDiv +:= blocks displaying the localities # in dayNotes.daySummary ] for loc in dayNotes.daySummary.genLocs(): self.__locDef ( summaryDiv, loc ) #-- 4 -- # [ summaryDiv +:= blocks displaying day-annotation # content from dayNotes.daySummary, if any ] self.__dayAnnotation ( summaryDiv, dayNotes.daySummary ) # - - - 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 ] ''' #-- 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 ) #-- 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 #-- 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 ] ''' #-- 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 ) #-- 5 -- if daySummary.notes is not None: self.__narrative ( parent, daySummary.notes ) # - - - 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 #-- 2 -- # [ div +:= a span element with class=LOC_LABEL_CLASS # containing label ] self.__span ( div, LOC_LABEL_CLASS, "%s " % label ) #-- 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 ) # - - - 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 ) # - - - 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 ] ''' #-- 1 -- # [ parent +:= a div element with class=PARA_CLASS # div := that div element ] div = et.SubElement ( parent, 'div' ) div.attrib['class'] = PARA_CLASS #-- 2 -- # [ div +:= div elements containing XHTML rendering # of the content of para ] self.__paraContent ( div, para ) # - - - 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 ) # - - - 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 #-- 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 ) #-- 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 ) # - - - 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) ] ''' #-- 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 ) #-- 3 -- # [ paretn +:= XHTML rendering of sight's effective locality ] self.__locGroup ( parent, sight.getLocGroup() ) #-- 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 ) # - - - 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) ] ''' #-- 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 ) #-- 2 -- # [ if birdForm.sightNotes is not None -> # div +:= XHTML rendering of birdForm.sightNotes ] if birdForm.sightNotes: self.__sightNotes ( div, birdForm.sightNotes ) #-- 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 ) # - - - 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 ] ''' #-- 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 ) #-- 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 ) # - - - 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 ] ''' #-- 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 ) #-- 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 ) #-- 6 -- if sightNotes.notes is not None: self.__narrative ( parent, sightNotes.notes ) # - - - 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 ] ''' #-- 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 ) # - - - 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 #-- 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 ) #-- 3 -- # [ div +:= XHTML rendering of sight's effective locality ] self.__locGroup ( div, sight.getLocGroup() ) #-- 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 ) # - - - M o n t h C e l l . _ _ a g e S e x G r o u p 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 ] ''' #-- 1 -- # [ if ageSexGroup.count is not None -> # parent +:= ageSexGroup.count # else -> I ] if ageSexGroup.count is not None: tccpage2.addTextMixed ( parent, ageSexGroup.count ) #-- 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 ) #-- 3 -- # [ if ageSexGroup.sex is not None -> # parent +:= ageSexGroup.sex # else -> I ] if ageSexGroup.sex is not None: tccpage2.addTextMixed ( parent, 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, ' ' ) # @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 ) #================================================================ # Epilogue #---------------------------------------------------------------- if __name__ == '__main__': main()