#!/usr/local/bin/python #-- # addr-web.py: Render an XML address book conforming to address.dtd. # $Revision: 1.7 $ $Date: 2002/11/15 06:57:49 $ #-- # Documentation: # http://www.nmt.edu/tcc/help/xml/addr-web.html #================================================================ # Overall intended function: # [ if (command line arguments aren't valid) -> # sys.stderr +:= error message # else if the filename command line argument names a readable # file conforming to address.dtd -> # sys.stdout +:= a page of HTML displaying that file # according to the command line switches ] #---------------------------------------------------------------- # Contents: # Imports # Manifest constants # Classes: # class Args: Represents the command line arguments # class View: Represents the address book in viewing order # class Block: Base class for displayed entries # class OrgBlock(Block): Displayed by organization name # class PersonBlock(Block): Displayed by personal name # class XrefBlock(Block): Displayed by cross-reference text # Main #---------------------------------------------------------------- #================================================================ # Imports #---------------------------------------------------------------- import sys # Standard system interface import cgi # For cgi.escape() function from sysargs import * # My command line argument digester from foldifier import * # My XML line folder object from gen_xml import * # My XML generator package from fo_helpers import * # My flow object helpers from address import * # Address book objects #================================================================ # Manifest constants #---------------------------------------------------------------- UNLISTED_SWITCH = "u" # -u: Show unlisted entries/phone #s PRINT_SWITCH = "p" # -p: Print in 3x5 format FILENAME_ARG = "filename" # Name of the filename argument PRINT_SIZE = "3x5" # Page layout key in fo_helpers.pageDimSet PAGE_TITLE = "Addresses" # Generated Web page title PHONE_COL_WIDE = "0.75in" # Width of FOT phone number column MAIN_COL_WIDE = "1.75in" # Width of FOT other-data column #-- # Stylesheet classes used in address.css #-- INTRO_DATE_STYLE = "intro-date" # Dateline in intro paragraph PHONE_STYLE = "phone" # Phone numbers INDEX_TERM_STYLE = "index-term" # Indexed identifier (sort key) ORG_STYLE = "org" # Organization name CONTACT_STYLE = "contact" # Organization contact person PERSON_STYLE = "person" # Person's name LOCATION_STYLE = "loc" # Location (street/mailing address) DIRECTIONS_STYLE = "dir" # Driving directions NOTES_STYLE = "notes" # Miscellaneous notes #-- # Attribute dictionaries for font and other markup #-- NORMAL_SIZE = "7pt" # Common font size INDEX_SIZE = "7pt" # For index term HEADING_SIZE = "9pt" # For headers and footers RUNNING_HEAD_FONT = { # For running page header "font-family": "sans-serif", "font-size": HEADING_SIZE } INTRO_FONT = { # For introductory text "font-family": "serif", "font-size": NORMAL_SIZE } INDEX_FONT = { # For the index term (sort key) "font-family": "sans-serif", "font-size": INDEX_SIZE, "font-weight": "bold" } NORMAL_FONT = { # Phone numbers, non-index data "font-family": "serif", "font-size": NORMAL_SIZE } THIN_BORDER = { # Narrow border around a box "border-style": "solid", "border-width": "0.5pt", "padding-left": "1pt", "padding-right": "1pt", "margin-bottom": "2pt" } # - - - - - c l a s s A r g s - - - - - class Args: """Represents the command line arguments. Args(): [ if sys.argv represents valid command line arguments for this script -> return a new Args object representing those arguments else -> sys.stderr +:= error message stop execution ] .showUnlisted: [ if sys.argv contained an UNLISTED_SWITCH switch -> 1 else -> 0 ] .fot: [ if sys.argv contained a PRINT_SWITCH switch -> 1 else -> 0 ] .fileName: [ the FILENAME_ARG argument from sys.argv ] .switchList: [ a list of SwitchArg objects representing our valid command line switches ] .posList: [ a list of PosArg objects representing our valid command line positional arguments ] """ # - - - A r g s . _ _ i n i t _ _ - - - def __init__ ( self ): "Constructor for the Args object" #-- 1 -- # [ switchList := a list of SwitchArg objects representing # our valid command line switches # posList := a list of PosArg objects representing # our valid command line positional arguments ] self.switchList = [ SwitchArg ( UNLISTED_SWITCH, [ "Show unlisted entries and phone numbers." ] ), SwitchArg ( PRINT_SWITCH, [ 'Format for 3"x5" print size using XSL-FO.' ] ) ] self.posList = [ PosArg ( FILENAME_ARG, [ "Name of the input file, conforming to DTD", "address.dtd." ] ) ] #-- 2 -- # [ if sys.argv contains only switches in self.switchList and # positional arguments in self.posList -> # sysArgs := a new SysArgs object representing those # switches and positional arguments # else -> # sys.stderr +:= error message # stop execution ] sysArgs = SysArgs ( self.switchList, self.posList ) #-- 3 -- # [ if sysArgs has an UNLISTED_SWITCH switch -> # self.showUnlisted := 1 # else -> # self.showUnlisted := 0 ] self.showUnlisted = sysArgs.switchMap[UNLISTED_SWITCH] #-- 4 -- # [ if sysArgs has a PRINT_SWITCH switch -> # self.fot := 1 # else -> # self.fot := 0 ] self.fot = sysArgs.switchMap[PRINT_SWITCH] #-- 5 -- # [ self.fileName := the FILENAME_ARG positional from sysArgs ] self.fileName = sysArgs.posMap[FILENAME_ARG] # - - - - - c l a s s V i e w - - - - - class View: """Represents an AddressBook digested into a format for viewing Exports: View ( book, unlisted=0 ): [ if (book is an AddressBook object) and (unlisted is true iff unlisted entries and phone numbers should be displayed) -> return a new View object representing book ] .book: [ as passed to constructor ] .unlisted: [ as passed to constructor ] .html(f): [ if f is a Foldifier object -> f := f with an HTML rendering of self appended ] .fot(f): [ if (f is a Foldifier object) and ("3x5" is a key in fo_helpers.pageDimSet) -> f := f with an XSL-FO rendering of self appended ] State/Invariants: .__sortedList: [ a list of Block objects representing the entries from self.book in presentation order ] """ # - - - V i e w . _ _ i n i t _ _ - - - def __init__ ( self, book, unlisted=0 ): "Constructor for the View object." #-- 1 -- self.book = book self.unlisted = unlisted self.__sortedList = [] #-- 2 -- # [ self.__sortedList +:= Block objects representing # the entries from book, each appearing at the location # of each of its xref, org, and person keys ] for entry in self.book.addrList: #-- 2 body -- if ( ( self.unlisted or entry.hide is None) and ( entry.status is None ) ): self.__makeBlock ( entry ) #-- 3 -- # [ self.__makeBlocks := self.__makeBlocks, sorted ] self.__sortedList.sort() # - - - V i e w . _ _ m a k e B l o c k s - - - def __makeBlock ( self, entry ): """Generate all the Block-type objects for one entry [ if self.unlisted: self.__sortedList +:= Block objects representing entry, appearing at the location of each of its xref, org, and person keys else -> self.__sortedList +:= non-hidden Block objects representing all entries from book minus any unlisted phone numbers, appearing at the location of each of its xref, org, and person keys ] """ #-- 1 -- # [ self.__sortedList +:= entries for all xrefs of entry ] for xref in entry.xrefList: self.__sortedList.append ( XrefBlock ( xref, entry, self.unlisted ) ) #-- 2 -- # [ self.__sortedList +:= entries for all org names in entry ] for org in entry.orgList: self.__sortedList.append ( OrgBlock ( org, entry, self.unlisted ) ) #-- 3 -- # [ self.__sortedList +:= entries for all person names in entry ] for person in entry.personList: self.__sortedList.append ( PersonBlock ( person, entry, self.unlisted ) ) # - - - V i e w . h t m l - - - def html ( self, f ): "Ravel self out as HTML to Foldifier object f" #-- 1 -- # [ doc := a Document object with a basic, empty Web page # headTag := the tag on that page # bodyTag := the tag on that page ] doc = Document() doc.root = BlockTag ( None, "html" ) headTag = BlockTag ( doc.root, "head" ) bodyTag = BlockTag ( doc.root, "body" ) #-- 2 -- # [ headTag := headTag with title and stylesheet links added ] self.__buildHeadTag ( headTag ) #-- 3 -- # [ bodyTag := bodyTag with heading and introductory material ] self.__buildProlog ( bodyTag ) #-- 4 -- # [ bodyTag +:= to contain entries # tableTag := that
element ] tableTag = BlockTag ( bodyTag, "table", border="2", cellpadding="2", cellspacing="2" ) #-- 5 -- # [ tableTag := tabletag with one row added per element of # self.__sortedList ] for block in self.__sortedList: #-- 5 body -- # [ tableTag +:= a row added displaying block ] self.__addRow ( tableTag, block ) #-- 6 -- # [ f := f + (XML rendering of doc) ] doc.str ( f ) # - - - V i e w . _ _ b u i l d H e a d T a g - - - def __buildHeadTag ( self, headTag ): """Add the content to the section of the web page [ if headTag is a gen_xml Tag object -> headTag := headTag with title and stylesheet links added ] """ #-- 1 -- # [ headTag +:= a element ] titleTag = BlockTag ( headTag, "title" ) titleTag.add ( PAGE_TITLE ) #-- 2 -- # [ headTag +:= a link to stylesheet "address.css" ] BlockTag ( headTag, "link", rel="stylesheet", href="address.css" ) # - - - V i e w . _ _ b u i l d P r o l o g - - - def __buildProlog ( self, bodyTag ): """Generate the web page heading and introductory text [ if bodyTag is a gen_xml Tag object -> bodyTag := bodyTag with heading and introductory material ] """ #-- 1 -- # [ bodyTag +:= page heading ] titleTag = BlockTag ( bodyTag, "h2" ) titleTag.add ( PAGE_TITLE ) #-- 2 -- # [ bodyTag +:= paragraph containing self.book.headingText # p := that paragraph element ] p = BlockTag ( bodyTag, "p" ) p.add ( self.book.headingText ) #-- 3 -- # [ p +:= self.book.date, styled as a date ] dateSpanTag = InlineTag ( p, "span", _class=INTRO_DATE_STYLE ) dateSpanTag.add ( " [", self.book.date, "]" ) # - - - V i e w . _ _ a d d R o w - - - def __addRow ( self, tableTag, block ): """Add a Block object to the table [ if (tableTag is a gen_xml Tag object) and (block is a Block object) -> tableTag := a row added displaying block ] """ #-- 1 -- # [ tableTag +:= a <tr> element containing two <td> elements # rowTag := that <tr> element # leftCell := the first <td> element # rightCell := the second <td> element ] rowTag = BlockTag ( tableTag, "tr" ) leftCell = BlockTag ( rowTag, "td", align="right" ) rightCell = BlockTag ( rowTag, "td", align="justify" ) #-- 2 -- # [ leftCell +:= all phone numbers from block ] block.htmlPhoneNos ( leftCell ) #-- 3 -- # [ rightCell +:= all non-phone data from block, with # key-related item in class "index-term" ] block.htmlNonPhone ( rightCell ) # - - - V i e w . f o t - - - def fot ( self, f ): "Generate an XSL Flow Objects tree from self." #-- 1 -- # [ if PRINT_SIZE is a key in pageDimSet -> # pageDim := the corresponding PageDim object # else -> # sys.stderr +:= (usage message) # stop execution ] try: pageDim = pageDimSet[PRINT_SIZE] except KeyError: text = ( "-%s option requires `%s' page layout in " "fo_helpers.py" % ( PRINT_SWITCH, PRINT_SIZE ) ) usage ( args.switchList, args.posList, text ) #-- 2 -- # [ fot := a new FlowTree object with layout specified by # pageDim and containing one fo:page-sequence element mastered # to MASTER_NAME # pageSequence := that fo:page-sequence element as a Tag object fot = FlowTree ( pageDim ) pageSequence = BlockTag ( fot.root, "fo:page-sequence" ) pageSequence.addAttr ( "master-reference", MASTER_NAME ) #-- 3 -- # [ pageSequence +:= running header and footer content ] self.__addStatic ( pageSequence ) #-- 4 -- # [ pageSequence +:= an fo:flow containing self, rendered ] self.__addFlow ( pageSequence ) #-- 5 -- # [ f +:= fot as XSL-FO ] fot.xml ( f ) # - - - V i e w . _ _ a d d S t a t i c - - - def __addStatic ( self, pageSequence ): """Add the header and footer content to an fo:page-sequence. [ if pageSequence is an fo:page-sequence element as a Tag -> pageSequence +:= running header and footer content ] """ #-- 1 -- # [ pageSequence +:= an fo:static-content element # referring to xsl-region-before and containing a # running head as an fo:block with text PAGE_TITLE # and attributes from RUNNING_HEAD_FONT ] staticHead = BlockTag ( pageSequence, "fo:static-content" ) staticHead.addAttr ( "flow-name", "xsl-region-before" ) headBlock = BlockTag ( staticHead, "fo:block" ) headBlock.addAttrDict ( RUNNING_HEAD_FONT ) headBlock.addAttrDict ( THIN_BORDER ) headBlock.add ( PAGE_TITLE ) #-- 2 -- # [ pageSequence +:= an fo:static-content element # referring to xsl-region-after and containing a # centered page number using style RUNNING_HEAD_FONT ] staticFoot = BlockTag ( pageSequence, "fo:static-content" ) staticFoot.addAttr ( "flow-name", "xsl-region-after" ) footBlock = BlockTag ( staticFoot, "fo:block" ) footBlock.addAttrDict ( RUNNING_HEAD_FONT ) footBlock.addAttr ( "text-align-last", "center" ) footBlock.addAttrDict ( THIN_BORDER ) EmptyTag ( footBlock, "fo:page-number" ) # - - - V i e w . _ _ a d d F l o w - - - def __addFlow ( self, pageSequence ): """Add the address book as an fo:flow child of pageSequence. [ if pageSequence is an fo:page-sequence element as a Tag -> pageSequence +:= an fo:flow containing self, rendered ] """ #-- 1 -- # [ pageSequence +:= an fo:flow element keyed to xsl-region-body # flow := that element ] flow = BlockTag ( pageSequence, "fo:flow" ) flow.addAttr ( "flow-name", "xsl-region-body" ) #-- 2 -- # [ flow +:= a block showing self.book.headingText and # self.book.date ] self.__fotProlog ( flow ) #-- 3 -- # [ flow +:= a table displaying the elements of # self.__sortedList in the same order ] self.__fotTable ( flow ) # - - - V i e w . _ _ f o t P r o l o g - - - def __fotProlog ( self, flow ): """Add self.book.{headingText,date} to flow as a block [ if flow is an fo:flow element as a Tag -> flow +:= a block showing self.book.HeadingText and self.book.date ] """ #-- 1 -- # [ flow +:= a new fo:block with no content, using # font INTRO_FONT # block := that new fo:block ] block = BlockTag ( flow, "fo:block" ) block.addAttrDict ( INTRO_FONT ) block.addAttr ( "padding-after", "0.1in" ) #-- 2 -- # [ block +:= self.book.headingText ] block.add ( cgi.escape ( self.book.headingText ) ) #-- 3 -- # [ block +:= self.book.date ] block.add ( " [%s]" % self.book.date ) # - - - V i e w . _ _ f o t T a b l e - - - def __fotTable ( self, flow ): """Add self.__sortedList to flow as a table [ if flow is an fo:flow element as a Tag -> flow +:= a table displaying the elements of self.__sortedList in the same order ] """ #-- 1 -- # [ flow +:= a new fo:table element with two columns of # width PHONE_COL_WIDE and MAIN_COL_WIDE and # an empty fo:table-body # table := that fo:table element # tableBody := that fo:table-body element ] table = BlockTag ( flow, "fo:table" ) table.addAttr ( "table-layout", "fixed" ) col1 = EmptyTag ( table, "fo:table-column" ) col1.addAttr ( "column-width", PHONE_COL_WIDE ) col2 = EmptyTag ( table, "fo:table-column" ) col2.addAttr ( "column-width", MAIN_COL_WIDE ) tableBody = BlockTag ( table, "fo:table-body" ) #-- 2 -- # [ tableBody +:= rows representing elements of # self.__sortedList in the same order ] for block in self.__sortedList: #-- 2 body -- # [ tableBody +:= a row representing block ] self.__fotRow ( tableBody, block ) # - - - V i e w . _ _ f o t R o w - - - def __fotRow ( self, tableBody, block ): """Add one row to tableBody representing block [ if (tableBody is an fo:table-body element) and (block is a Block object) -> tableBody +:= a row representing block ] """ #-- 1 -- if ( ( not self.unlisted ) and ( block.entry.hide ) ): return #-- 2 -- # [ tableBody +:= a new fo:table-row element with # two fo:table-cell children # row := that fo:table-row element # phoneCell := the first fo:table-cell # mainCell := the second fo:table-cell ] row = BlockTag ( tableBody, "fo:table-row" ) phoneCell = BlockTag ( row, "fo:table-cell" ) mainCell = BlockTag ( row, "fo:table-cell" ) mainCell.addAttr ( "padding-start", "3pt" ) #-- 3 -- # [ if self.unlisted -> # phoneCell +:= all phone numbers from block.entry # else -> # phoneCell +:= all listed phone numbers from block.entry ] block.fotPhoneNos ( phoneCell ) #-- 4 -- # [ mainCell +:= all non-phone data from block ] block.fotNonPhone ( mainCell ) # - - - - - c l a s s B l o c k - - - - - class Block: """Base class for all types of viewed entries. Exports: Block ( key, entry, unlisted=0 ): [ if (key is a string) and (entry is an Entry object) and (unlisted is true iff unlisted entries and phone numbers should be displayed) -> return a Block object with those values ] .key: [ as passed to constructor ] .entry: [ as passed to constructor ] .unlisted: [ as passed to constructor ] .__cmp__(): [ returns the usual Python comparison value based on self.key ] .htmlPhoneNos ( parentTag ): [ if parentTag is a gen_xml Tag object -> if not self.unlisted -> parent +:= content representing listed phone numbers from entry else -> parent +:= content representing all phone numbers from entry ] .htmlNonPhone ( parentTag ): # VIRTUAL METHOD [ if parentTag is a gen_xml Tag object -> parentTag +:= content representing all non-phone data ] .htmlNonPhoneSkip ( parentTag, id ): [ if (parentTag is a gen_xml Tag object) and (id is a Person or Org object) -> parentTag +:= content representing all non-phone data except for any person or org content matching id ] .htmlOrg ( parentTag, org ): [ if (parentTag is a gen_xml Tag object) and (org is an Org object) -> parentTag +:= content representing org ] .htmlPerson ( parentTag, person ): [ if (parentTag is a gen_xml Tag object) and (person is a Person object) -> parentTag +:= content representing person ] .htmlEmail ( parentTag, email ): [ if (parentTag is a gen_xml Tag object) and (email is an Email object) -> parentTag +:= content representing email ] .htmlLocation ( parentTag, location ): [ if (parentTag is a gen_xml Tag object) and (location is a Location object) -> parentTag +:= content representing location ] .htmlDirections ( parentTag ): [ if (parentTag is a gen_xml Tag object) -> parentTag +:= content representing self.entry's directions ] .htmlNotes ( parentTag ): [ if (parentTag is a gen_xml Tag object) -> parentTag +:= content representing self.entry's notes ] .fotPhoneNos ( cell ): [ if cell is an fo:table-cell element as a Tag -> if self.unlisted -> cell +:= all phone numbers from block.entry as block-level elements else -> cell +:= all listed phone numbers from block.entry as block-level elements ] NB: The requirement that the children of an fo:table-cell be block-level elements is a limitation of Apache FOP 0.20.4 .fotNonPhone ( cell ): # VIRTUAL METHOD [ if cell is an fo:table-cell element as a Tag -> cell +:= all non-phone data from self as block-level elements ] .fotNonPhoneSkip ( fob, id ): [ if (fob is an fo:block) and (id is a Person or Org object) -> fob +:= content representing all non-phone data except for any person or org content matching id ] .fotOrg ( fob, org ): [ if (fob is an fo:block element) and (org is an Org object) -> fob +:= content representing org ] .fotPerson ( fob, person ): [ if (fob is an fo:block element) and (person is a Person object) -> fob +:= content representing person ] .fotEmail ( fob, email ): [ if (fob is an fo:block element) and (email is an Email object) -> fob +:= content representing email ] .fotLocation ( fob, location ): [ if (fob is an fo:block element) and (location is a Location object) -> fob +:= content representing location ] .fotDirections ( fob ): [ if (fob is an fo:block element) -> fob +:= content representing self.entry's directions ] .fotNotes ( fob ): [ if (fob is an fo:block element) -> fob +:= content representing self.entry's notes ] """ # - - - B l o c k . _ _ i n i t _ _ - - - def __init__ ( self, key, entry, unlisted=0 ): "Constructor for the Block object." #-- 1 -- self.key = key self.entry = entry self.unlisted = unlisted # - - - B l o c k . _ _ c m p _ _ - - - def __cmp__ ( self, other ): "Compares two Block objects." return cmp ( self.key.upper(), other.key.upper() ) # - - - B l o c k . h t m l P h o n e N o s - - - def htmlPhoneNos ( self, parentTag ): "Generate the phone number cell of an address book entry." #-- 1 -- # [ parentTag +:= a <span> element with class=PHONE_STYLE # spanTag := that <span> element ] spanTag = InlineTag ( parentTag, "span", _class=PHONE_STYLE ) #-- 2 -- # [ if self.unlisted -> # spanTag +:= all phone numbers from self.entry # else -> # spanTag +:= all listed phone numbers from self.entry ] for phonex in range ( len ( self.entry.phoneList ) ): #-- 2 body -- # [ if (not self.unlisted) # and (self.entry.phoneList[phonex] is unlisted -> # I # else -> # spanTag +:= content representing # self.entry.phoneList[phonex] ] if phonex > 0: # Add line breaks between entries EmptyTag ( spanTag, "br" ) self.__formatPhone ( spanTag, self.entry.phoneList[phonex] ) # - - - B l o c k . _ _ f o r m a t P h o n e - - - def __formatPhone ( self, parentTag, phone ): """Add a phone number to the Web page. [ if (spanTag is a gen_xml Tag object) and (phone is a Phone object) -> if (not self.unlisted) and (phone is unlisted) -> I else -> parentTag +:= content representing phone ] """ #-- 1 -- if ( ( not self.unlisted ) and ( phone.unlisted ) ): return #-- 2 -- # [ if phone.kind is not None -> # parentTag +:= phone.kind # else -> I ] if phone.kind is not None: parentTag.add ( "%s: " % phone.kind ) #-- 3 -- if phone.unlisted: parentTag.add ( "[U] " ) #-- 4 -- # [ parentTag +:= phone.text ] parentTag.add ( phone.text ) # - - - B l o c k . h t m l N o n P h o n e - - - def htmlNonPhone ( self, parentTag ): "Virtual method" raise NotImplementedError, ( "Block.htmlNonPhone() is " "a virtual method." ) # - - - B l o c k . h t m l N o n P h o n e S k i p - - - def htmlNonPhoneSkip ( self, parentTag, id ): "Like htmlNonPhone(), but assumes primary index term done elsewhere." #-- 1 -- # [ parentTag +:= all Org elements in self.entry, except # the one matching id if any ] for org in self.entry.orgList: if org is not id: self.htmlOrg ( parentTag, org ) #-- 2 -- # [ parentTag +:= all Person elements in self.entry, except # the one matching id, if any ] for person in self.entry.personList: if person is not id: self.htmlPerson ( parentTag, person ) #-- 3 -- NB: We are not showing e-mails here! # [ parentTag +:= all Location elements in self.entry ] for location in self.entry.locationList: self.htmlLocation ( parentTag, location ) #-- 4 -- # [ parentTag +:= self.entry.directions, if any ] self.htmlDirections ( parentTag ) #-- 5 -- # [ parentTag +:= self.entry.notes, if any ] self.htmlNotes ( parentTag ) # - - - B l o c k . h t m l O r g - - - def htmlOrg ( self, parentTag, org ): "Add content identifying an organization." #-- 1 -- # [ parentTag +:= a new <span> element in class "id" # spanTag := that <span> ] spanTag = InlineTag ( parentTag, "span", _class=ORG_STYLE ) #-- 2 -- # [ spanTag +:= org.name ] spanTag.add ( cgi.escape ( org.name ) ) #-- 3 -- # [ if org.contact is not None -> # spanTag +:= a new <span> with class CONTACT_STYLE, # containing org.contact ] if org.contact is not None: span2 = InlineTag ( spanTag, "span", _class=CONTACT_STYLE ) span2.add ( " (", org.contact, ")" ) # - - - B l o c k . h t m l P e r s o n - - - def htmlPerson ( self, parentTag, person ): "Display a person's name" #-- 1 -- # [ parentTag +:= a new <span> element with class PERSON_STYLE # spanTag := that <span> ] spanTag = BlockTag ( parentTag, "span", _class=PERSON_STYLE ) #-- 2 -- # [ spanTag +:= person's name ] spanTag.add ( cgi.escape ( person.lastFirst() ) ) # - - - B l o c k . h t m l E m a i l - - - def htmlEmail ( self, parentTag, email ): "Display an e-mail address" #-- 1 -- # [ parentTag +:= a new <span> element with class "email" # spanTag := that <span> ] spanTag = BlockTag ( parentTag, "span", _class="email" ) #-- 2 -- # [ if email.kind is not None -> # spanTag +:= email.kind # else -> I ] if email.kind is not None: spanTag.add ( "%s: " % email.kind ) #-- 3 -- # [ spanTag +:= email.text ] spanTag.add ( email.text ) # - - - h t m l L o c a t i o n - - - def htmlLocation ( self, parentTag, location ): "Add a location to the web page" #-- 1 -- # [ parentTag +:= a new <span> element with class=LOCATION_STYLE # spanTag := that <span> ] spanTag = InlineTag ( parentTag, "span", _class=LOCATION_STYLE ) #-- 2 -- if location.mail is not None: spanTag.add ( "(USPS) " ) #-- 3 -- # [ spanTag +:= strings from location.aList, separated # by slashes ] spanTag.add ( cgi.escape ( " / ".join ( location.aList ) ), " " ) #-- 4 -- if location.city is not None: spanTag.add ( cgi.escape ( location.city), " " ) #-- 5 -- if location.state is not None: spanTag.add ( location.state, " " ) #-- 6 -- if location.zip is not None: spanTag.add ( location.zip, " " ) #-- 7 -- if location.nation is not None: spanTag.add ( location.nation, " " ) # - - - B l o c k . h t m l D i r e c t i o n s - - - def htmlDirections ( self, parentTag ): "Add self.entry.directions to a web page" #-- 1 -- # [ if self.entry.dimensions is None -> # return # else -> # directions := self.entry.directions ] directions = self.entry.directions if directions is None: return #-- 2 -- # [ parentTag +:= a new <span> with class=DIRECTIONS_STYLE # spanTag := that <span> # chunks := [] ] spanTag = InlineTag ( parentTag, "span", _class=DIRECTIONS_STYLE ) chunks = [] #-- 3 -- # [ chunks +:= directions.legList with appropriate punctuation ] chunks.append ( "[" ) for leg in directions.legList: chunks.append ( "( " ) chunks.append ( leg.on ) chunks.append ( " / " ) chunks.append ( leg.when ) chunks.append ( " / " ) chunks.append ( leg.then ) chunks.append ( " )" ) chunks.append ( "]" ) #-- 4 -- # [ spanTag +:= elements of chunks, concatenated ] spanTag.add ( cgi.escape ( "".join ( chunks ) ) ) # - - - B l o c k . h t m l N o t e s - - - def htmlNotes ( self, parentTag ): "Append self.entry.notes to the web page" #-- 1 -- if self.entry.notes is None: return else: notes = self.entry.notes #-- 2 -- # [ parentTag +:= a new <span> element with class=NOTES_STYLE # and content (notes) # spanTag := that <span> ] spanTag = InlineTag ( parentTag, "span", _class=NOTES_STYLE ) spanTag.add ( cgi.escape ( notes ) ) # - - - B l o c k . f o t P h o n e N o s - - - def fotPhoneNos ( self, cell ): """Format phone numbers as XSL-FO. NB: We want to stack the phone numbers in a relatively narrow column, so each gets its own fo:block.""" #-- 1 -- for phone in self.entry.phoneList: #-- 1 body -- # [ if ( ( not self.unlisted ) and ( phone.unlisted ) ) -> # I # else -> # cell +:= an fo:block containing phone ] self.__fotPhone ( phone, cell ) # - - - B l o c k . _ _ f o t P h o n e - - - def __fotPhone ( self, phone, cell ): """Format one phone number. [ if (phone is a Phone object) and (cell is an fo:table-cell element as a Tag) -> if ( ( not self.unlisted ) and ( phone.unlisted ) ) -> I else -> cell +:= an fo:block containing phone ] """ #-- 1 -- if ( ( not self.unlisted ) and ( phone.unlisted ) ): return #-- 2 -- # [ cell +:= an fo:block element with NORMAL_FONT applied # block := that element ] block = BlockTag ( cell, "fo:block" ) block.addAttrDict ( NORMAL_FONT ) block.addAttr ( "text-align", "end" ) # Right-justify phone nos. #-- 3 -- # [ if phone.kind is not None -> # parentTag +:= phone.kind # else -> I ] if phone.kind is not None: block.add ( "%s: " % phone.kind ) #-- 4 -- if phone.unlisted: block.add ( "[U] " ) #-- 5 -- # [ block +:= phone.text ] block.add ( phone.text ) # - - - B l o c k . f o t N o n P h o n e - - - def fotNonPhone ( self, cell ): "Format all data except phone numbers as XSL-FO (virtual method)" raise NotImplementedError, ( "Block.fotNonPhone() is a " "virtual method." ) # - - - B l o c k . f o t N o n P h o n e S k i p - - - def fotNonPhoneSkip ( self, fob, id ): "Format all non-phone data, but skip id (the index term)" #-- 1 -- # [ fob +:= all Org elements in self.entry, except the # one matching id, if any ] for org in self.entry.orgList: if org is not id: self.fotOrg ( fob, org ) #-- 2 -- # [ fob +:= all Person elements in self.entry, except # the one matching id, if any ] for person in self.entry.personList: if person is not id: self.fotPerson ( fob, person ) #-- 3 -- # [ fob +:= self.entry.location ] for location in self.entry.locationList: self.fotLocation ( fob, location ) #-- 4 -- # [ fob +:= self.entry.directions, if any ] self.fotDirections ( fob ) #-- 5 -- # [ fob +:= self.entry.notes, if any ] self.fotNotes ( fob ) # - - - B l o c k . f o t O r g - - - def fotOrg ( self, fob, org ): "Add an Org to an fo:block" #-- 1 -- # [ fob +:= an fo:inline element # inline := that element ] inline = BlockTag ( fob, "fo:inline" ) inline.addAttrDict ( NORMAL_FONT ) #-- 2 -- # [ inline +:= org.name ] inline.add ( cgi.escape ( org.name ) ) #-- 3 -- # [ if org.contact is not None -> # inline +:= org.contact # else -> I ] if org.contact is not None: inline.add ( "(", cgi.escape ( org.contact ), ")" ) inline.add ( " / " ) # - - - B l o c k . f o t P e r s o n - - - def fotPerson ( self, fob, person ): "Add a Person to an fo:block" #-- 1 -- # [ fob +:= an fo:inline element # inline := that element ] inline = BlockTag ( fob, "fo:inline" ) inline.addAttrDict ( NORMAL_FONT ) #-- 2 -- # [ inline +:= person.lastFirst() ] inline.add ( cgi.escape ( person.lastFirst() ), " / " ) # - - - B l o c k . f o t E m a i l - - - def fotEmail ( self, fob, email ): "Add an Email to an fo:block" raise NotImplementedError, "Block.fotEmail() not written yet." # - - - B l o c k . f o t L o c a t i o n - - - def fotLocation ( self, fob, location ): "Add a Location to an fo:block" #-- 1 -- if location.mail is not None: fob.add ( "(USPS) " ) #-- 2 -- # [ fob +:= strings from location.aList, separated by slashes ] fob.add ( cgi.escape ( " / ".join ( location.aList ) ), " / " ) #-- 3 -- if location.city is not None: fob.add ( cgi.escape ( location.city ), " " ) #-- 4 -- if location.state is not None: fob.add ( location.state, " " ) #-- 5 -- if location.zip is not None: fob.add ( location.zip, " " ) #-- 6 -- if location.nation is not None: fob.add ( location.nation ) #-- 7 -- if ( ( location.city is not None ) or ( location.state is not None ) or ( location.zip is not None ) or ( location.nation is not None ) ): fob.add ( " / " ) # - - - B l o c k . f o t D i r e c t i o n s - - - def fotDirections ( self, fob ): "Add self.entry.directions to an fo:block" #-- 1 -- # [ if self.entry.directions is None -> # return # else -> # directions := self.entry.directions ] directions = self.entry.directions if directions is None: return #-- 2 -- chunks = [] #-- 3 -- # [ chunks +:= directions.legList with appropriate punctuation ] chunks.append ( "[" ) for leg in directions.legList: chunks.append ( "( " ) chunks.append ( leg.on ) chunks.append ( " / " ) chunks.append ( leg.when ) chunks.append ( " / " ) chunks.append ( leg.then ) chunks.append ( " )" ) chunks.append ( "]" ) #-- 4 -- # [ fob +:= elements of chunks, concatenated ] fob.add ( cgi.escape ( "".join ( chunks ) ) ) # - - - B l o c k . f o t N o t e s - - - def fotNotes ( self, fob ): "Add self.entry.notes to an fo:block" #-- 1 -- if self.entry.notes is None: return else: notes = self.entry.notes #-- 2 -- fob.add ( cgi.escape ( notes ) ) # - - - - - c l a s s X r e f B l o c k - - - - - class XrefBlock(Block): "Represents an entry for a cross-reference." def __init__ ( self, key, entry, unlisted=0 ): Block.__init__ ( self, key, entry, unlisted ) # - - - X r e f B l o c k . h t m l N o n P h o n e - - - def htmlNonPhone ( self, parentTag ): "Generate non-phone HTML for a cross-reference." #-- 1 -- # [ parentTag +:= a new <span> with class=INDEX_TERM_STYLE # spanTag := that <span> ] spanTag = BlockTag ( parentTag, "span", _class=INDEX_TERM_STYLE ) #-- 2 -- # [ spanTag +:= self.key ] spanTag.add ( cgi.escape ( self.key ) ) #-- 3 -- # [ parentTag +:= content representing all non-phone data ] self.htmlNonPhoneSkip ( parentTag, None ) # - - - X r e f B l o c k . f o t N o n P h o n e - - - def fotNonPhone ( self, cell ): "Add an xref entry to an fo:table-cell as a block-level element." #-- 1 -- # [ cell +:= an fo:block element with NORMAL_FONT applied, # containing an fo:inline element with INDEX_FONT # applied # fob := that fo:block element # inline := that fo:inline element ] fob = BlockTag ( cell, "fo:block" ) fob.addAttrDict ( NORMAL_FONT ) inline = BlockTag ( fob, "fo:inline" ) inline.addAttrDict ( INDEX_FONT ) #-- 2 -- # [ inline +:= self.key ] inline.add ( cgi.escape ( self.key ), " " ) #-- 3 -- # [ fob +:= content representing all non-phone data ] self.fotNonPhoneSkip ( fob, None ) # - - - - - c l a s s O r g B l o c k - - - - - class OrgBlock(Block): """Represents an entry appearing under an organization name. Exports: OrgBlock ( org, entry, unlisted ): [ if (org is an Org object in entry) and (entry is an Entry object) and (unlisted is true iff unlisted entries are to be shown) -> return a new OrgBlock representing a view of entry with org highlighted ] State/Invariants: .org: [ as passed to constructor ] """ def __init__ ( self, org, entry, unlisted ): Block.__init__ ( self, org.key, entry, unlisted ) self.org = org # - - - O r g B l o c k . h t m l N o n P h o n e - - - def htmlNonPhone ( self, parentTag ): "Display self with self.org highlighted" #-- 1 -- # [ parentTag +:= a new <span> with class=INDEX_TERM_STYLE # spanTag := that <span> ] spanTag = InlineTag ( parentTag, "span", _class=INDEX_TERM_STYLE ) #-- 2 -- # [ spanTag +:= org.name ] spanTag.add ( cgi.escape ( self.org.name ) ) #-- 3 -- # [ if org.contact is not None -> # spanTag +:= org.contact, in style CONTACT_STYLE # else -> I ] if self.org.contact is not None: span2 = InlineTag ( spanTag, "span", _class=CONTACT_STYLE ) span2.add ( " (", cgi.escape ( self.org.contact ), ")" ) #-- 4 -- # [ parentTag +:= content representing all non-phone data # except self.org ] self.htmlNonPhoneSkip ( parentTag, self.org ) # - - - O r g B l o c k . f o t N o n P h o n e - - - def fotNonPhone ( self, cell ): "Add all non-phone data to an fo:table-cell." #-- 1 -- # [ cell +:= an fo:block element with NORMAL_FONT applied, # containing an fo:inline element with INDEX_FONT # applied # fob := that fo:block element # inline := that fo:inline element ] fob = BlockTag ( cell, "fo:block" ) fob.addAttrDict ( NORMAL_FONT ) inline = BlockTag ( fob, "fo:inline" ) inline.addAttrDict ( INDEX_FONT ) #-- 2 -- inline.add ( cgi.escape ( self.org.name ), " " ) #-- 3 -- # [ if self.org.contact is not None -> # fob +:= self.org.contact # else -> I ] if self.org.contact is not None: fob.add ( " (", cgi.escape ( self.org.contact ), ")" ) #-- 4 -- # [ fob +:= content representing all non-phone data # except self.org ] self.fotNonPhoneSkip ( fob, self.org ) # - - - - - c l a s s P e r s o n B l o c k - - - - - class PersonBlock(Block): """Represents an entry appearing under an organization name. Exports: PersonBlock ( person, entry, unlisted ): [ if (person is an Person object in entry) and (entry is an Entry object) and (unlisted is true iff unlisted entries are to be shown) -> return a new PersonBlock representing a view of entry with person highlighted ] State/Invariants: .person: [ as passed to constructor ] """ def __init__ ( self, person, entry, unlisted ): Block.__init__ ( self, person.key, entry, unlisted ) self.person = person # - - - P e r s o n B l o c k . h t m l N o n P h o n e - - - def htmlNonPhone ( self, parentTag ): "Display self with self.person highlighted" #-- 1 -- # [ parentTag +:= a new <span> with class=INDEX_TERM_STYLE # spanTag := that <span> ] spanTag = InlineTag ( parentTag, "span", _class=INDEX_TERM_STYLE ) #-- 2 -- # [ spanTag +:= person.lastFirst() ] spanTag.add ( cgi.escape ( self.person.lastFirst() ), " " ) #-- 3 -- # [ parentTag +:= content representing all non-phone data # except self.person ] self.htmlNonPhoneSkip ( parentTag, self.person ) # - - - P e r s o n B l o c k . f o t N o n P h o n e - - - def fotNonPhone ( self, cell ): "Add all non-phone data to an fo:table-cell" #-- 1 -- # [ cell +:= an fo:block element with NORMAL_FONT applied, # containing an fo:inline with INDEX_FONT applied # fob := that fo:block element # inline := that fo:inline element ] fob = BlockTag ( cell, "fo:block" ) fob.addAttrDict ( NORMAL_FONT ) inline = BlockTag ( fob, "fo:inline" ) inline.addAttrDict ( INDEX_FONT ) #-- 2 -- # [ inline +:= self.person.lastFirst() ] inline.add ( cgi.escape ( self.person.lastFirst() ), " " ) #-- 3 -- # [ fob +:= content representing all non-phone data # from self except self.person ] self.fotNonPhoneSkip ( fob, self.person ) # - - - - - a d d r - w e b - - m a i n - - - - - #-- 1 -- # [ if sys.argv is valid for this program -> # args := an Args object representing those arguments # else -> # sys.stderr +:= error message # stop execution ] args = Args () #-- 2 -- # [ if args.fileName names a readable file conforming to address.dtd -> # book := an AddressBook object representing that file # else -> # sys.stderr +:= error message # stop execution ] book = AddressBook ( args.fileName ) #-- 3 -- # [ view := a View object representing a view of book # according to the options from args # fold := a Foldifier object folding 80-character lines # to sys.stdout ] view = View ( book, unlisted=args.showUnlisted ) fold = Foldifier ( sys.stdout ) #-- 4 -- # [ if args.fot -> # fold +:= an XSL-FO rendering of view # else -> # fold +:= an HTML rendering of view ] if args.fot: view.fot ( fold ) else: view.html ( fold )