"""tccpage2.py: For dynamic generation of web pages in TCC style. For documentation, see: http://www.nmt.edu/tcc/projects/tccpage/ """ #================================================================ # Imports #---------------------------------------------------------------- import sys import time from lxml import etree as et XHTML_DOCTYPE = ( '\n' ) XHTML_NS = "http://www.w3.org/1999/xhtml" CSS_URL = "http://www.nmt.edu/tcc/style.css" TOP_NAV_SEP = " / " TCC_MAIN_URL = "http://www.nmt.edu/tcc/" TCC_LOGO_URL = "http://www.nmt.edu/tcc/images/logo.png" BOT_NAV_SEP = "; " # - - - a d d T e x t M i x e d def addTextMixed ( parent, s ): """Add text s inside a parent element. [ (parent is an et.Element instance) and (s is a string) -> if parent has no element children -> parent.text +:= s else -> (last element child of parent).tail +:= s ] """ #-- 1 -- if len(parent) == 0: #-- 1.1 -- # [ if bool(parent.text) -> # parent.text +:= s # else -> # parent.text := s ] parent.text = (parent.text or "") + s else: #-- 1.2 -- # [ let # youngest == (last element child of parent) # in # if bool(youngest.tail) -> # youngest.tail +:= s # else -> # youngest.tail := s ] youngest = parent[-1] youngest.tail = (youngest.tail or "") + s #================================================================ # Functions and classes #---------------------------------------------------------------- # - - - - - c l a s s T C C P a g e - - - - - class TCCPage: """Represents one page in the TCC style. Exports: TCCPage ( title=None, navList=None, author=None, url=None, logoImage=None, logoLink=None, cssUrl=None ): [ (title is the page's title as a string, default empty) and (navList describes the page's standard set of navigational links as NavLink instances, default empty) and (author is the page's author credit string, default empty) and (url is the page's URL as a string, default empty) and (logoImage is the URL of the logo image, default TCC_LOGO_URL) and (logoLink is the URL the logo links to, default TCC_MAIN_URL) and (cssURL is the stylesheet URL, default CSS_URL) -> return a new, empty TCCPage instance representing those values ] .root: [ self's "html" element as an et.Element ] .headTitle: [ self's head's title as an et.Element, initially the title argument, read/write ] .bodyTitle: [ self's body title as an h1 et.Element, initially the title argument, read/write ] .content: [ a div et.Element in self's body between the top and bottom nav links ] .address: [ self's address as an address et.Element, initially the author argument, read/write ] .url: [ as passed to constructor, read-only ] .write ( outFile=None ): [ outFile is a writeable file, defaulting to sys.stdout -> outFile +:= an XHTML representation of self ] State/Invariants: .doc: [ self's Document instance ] .head: [ self's head element ] .body: [ self's body element ] """ # - - - T C C P a g e . _ _ i n i t _ _ - - - def __init__ ( self, title=None, navList=None, author=None, url=None, logoImage=None, logoLink=None, cssUrl=None ): """Constructor for a TCCPage.""" #-- 1 -- self.title = title or "" self.navList = navList or [] self.author = author or "" self.url = url or "" self.logoImage = logoImage or TCC_LOGO_URL self.logoLink = logoLink or TCC_MAIN_URL self.cssUrl = cssUrl or CSS_URL #-- 2 -- # [ self.doc := a new ElementTree with root node "html" # [ self.root := that root node ] self.root = et.Element ( "html", xmlns=XHTML_NS ) self.doc = et.ElementTree ( self.root ) #-- 3 -- # [ self.doc := self.doc with a new head element added # containing self's head content # self.head := that element ] self.__createHead() #-- 4 -- # [ self.doc := self.doc with a new body element added # containing self's body content # self.body := that element # self.bodyTitle := the h1 element containing self.title # self.content := a div element for the body content # self.address := the address element in the colophon ] self.__createBody() # - - - T C C P a g e . _ _ c r e a t e H e a d - - - def __createHead ( self ): """Set up the page head element. [ self.doc := self.doc with a new head element added containing self's head content self.head := that element ] """ #-- 1 -- # [ self.doc := self.doc with a new head element added # self.head := that element ] self.head = et.SubElement ( self.root, "head" ) #-- 2 -- # [ self.head := self.head with a new title element # added containing self.title (if nonempty) # self.headTitle := that element ] self.headTitle = et.SubElement ( self.head, "title" ) if self.title: self.headTitle.text = self.title #-- 3 -- # [ self.head := self.head with a stylesheet link added ] et.SubElement ( self.head, "link", rel="stylesheet", type="text/css", href=self.cssUrl ) # - - - T C C P a g e . _ _ c r e a t e B o d y - - - def __createBody ( self ): """Set up the page body. [ self.doc := self.doc with a new body element added containing self's body content self.body := that element self.bodyTitle := the h1 element containing self.title self.content := a div element for the body content self.address := the address element in the colophon ] """ #-- 1 -- # [ self.doc := self.doc with a new body element added # self.body := that element ] self.body = et.SubElement ( self.root, "body" ) #-- 2 -- # [ self.body := self.body with the top nav bar added ] self.__topNav() #-- 3 -- # [ self.body := self.body with a title block added # self.bodyTitle := the h1 within that title block ] self.__titleBlock() #-- 4 -- # [ self.body := self.body with a new div element added # self.content := that div element ] self.content = et.SubElement ( self.body, "div" ) #-- 5 -- # [ self.body := self.body with bottom nav links added ] self.__botNav() #-- 6 -- # [ self.body := self.body with the colophon added # self.address := the address element within that # colophon ] self.__colophon() # - - - T C C P a g e . _ _ t o p N a v - - - def __topNav ( self ): """Build the row of navigational links across the page top. [ (self.navList as invariant) and (self.body as invariant) -> self.body := self.body with the top nav bar added ] """ #-- 1 -- # [ self.body := self.body with a new div element added # navBar := that element ] navBar = et.SubElement ( self.body, "div" ) navBar.attrib["class"] = "top-nav" #-- 2 -- prefix = "" #-- 3 -- # [ navBar +:= (prefix) + (navBar with top elements of # self.navList added, separated by TOP_NAV_SEP # prefix := TOP_NAV_SEP ] for navItem in self.navList: #-- 3 body -- # [ navItem is a NavLink instance -> # if navItem.noTop -> # I # else -> # navBar +:= (prefix) + (navItem as a top nav link) # prefix := TOP_NAV_SEP ] if not navItem.noTop: #-- 3.1 -- # [ navBar +:= (prefix) + (navItem as a top nav link) self.__topNavItem ( navBar, navItem, prefix ) #-- 3.2 -- prefix = TOP_NAV_SEP # - - - T C C P a g e . _ _ t o p N a v I t e m - - - def __topNavItem ( self, navBar, navItem, prefix ): """Add one item to the top navigational bar. [ (navBar is an et.Element) and (navItem is a NavLink) -> navBar +:= (prefix) + (navItem as a top nav link) ] """ #-- 1 -- # [ navBar +:= prefix ] addTextMixed ( navBar, prefix ) #-- 2 -- if navItem.destList: #-- 2.1 -- # [ navBar +:= a link to navItem.destList[0] with text # navItem.shortName ] title, url = navItem.destList[0] a = et.SubElement ( navBar, "a", href=url ) a.text = navItem.shortName else: # [ navBar +:= navItem.shortName as text ] addTextMixed ( navBar, navItem.shortName ) # - - - T C C P a g e . _ _ t i t l e B l o c k - - - def __titleBlock ( self ): """Set up the main page title block. [ self.title as invariant -> self.body := self.body with a title block added self.bodyTitle := the h1 within that title block ] """ #-- 1 -- # [ self.body := self.body with a new table element added # table := that table element ] table = et.SubElement ( self.body, "table", width="100%" ) #-- 2 -- # [ table := table with a new tr element added # row := that tr element ] row = et.SubElement ( table, "tr", valign="top" ) #-- 3 -- # [ row := row with a new cell added containing self.title # inside an h1 element # self.bodyTitle := that h1 element ] td = et.SubElement ( row, "td", align="left" ) self.bodyTitle = et.SubElement ( td, "h1" ) if self.title: self.bodyTitle.text = self.title #-- 4 -- # [ row := row with a new cell added containing the # TCC logo as a link to TCC_MAIN_URL ] td = et.SubElement ( row, "td", align="right" ) a = et.SubElement ( td, "a", href=self.logoLink ) img = et.SubElement ( a, "img", alt="logo", src=self.logoImage ) # - - - T C C P a g e . _ _ b o t N a v - - - def __botNav ( self ): """Add the page-bottom navigational links section. [ (self.body as invariant) and (self.navList as invariant) -> self.body := self.body with bottom nav links added from self.navList ] """ #-- 1 -- # [ self.body +:= an hr element ] et.SubElement ( self.body, "hr" ) #-- 2 -- # [ self.body +:= bottom nav links made from self.navList ] for navItem in self.navList: #-- 2 body -- # [ navItem is a NavLink -> # self.body +:= a bottom nav link made from navItem ] self.__botNavItem ( navItem ) # - - - T C C P a g e . _ _ b o t N a v I t e m - - - def __botNavItem ( self, navItem ): """Generate one page-bottom navigational link. [ navItem is a NavLink instance -> self.body +:= a bottom nav link made from navItem ] """ #-- 1 -- # [ if navItem.destList is empty or None -> # return # else -> # self.body +:= a new, empty div element # div := that div element ] if not navItem.destList: return else: div = et.SubElement ( self.body, "div" ) #-- 2 -- title, url = navItem.destList[0] #-- 3 -- # [ if bool(title) is false -> # div +:= a link to url with link text navItem.shortName # else -> # div +:= (navItem.shortName, boldfaced) + # (elements of navItem.destList as links, # separated by BOT_NAV_SEP) ] if not title: #-- 3.1 -- # [ div +:= a link to url with link text # navItem.shortName ] self.__botNavShort ( div, url, navItem.shortName ) else: #-- 3.2 -- # [ div +:= (navItem.shortName, boldfaced) + # (elements of navItem.destList as links, # separated by BOT_NAV_SEP) ] self.__botNavGeneral ( div, navItem ) # - - - T C C P a g e . _ _ b o t N a v S h o r t def __botNavShort ( self, div, url, shortName ): """Generate a short-form navigational link. [ (div is an et.Element) and (url is a URL as a string) and (shortName is a string) -> div +:= a link to url with link text shortName ] """ #-- 1 -- # [ div +:= a new "a" child element with href=url # a := that new child element ] a = et.SubElement ( div, "a", href=url ) #-- 2 -- # [ a +:= a new "b" child element with text shortName ] b = et.SubElement ( a, "b" ) b.text = shortName # - - - T C C P a g e . _ _ b o t N a v G e n e r a l def __botNavGeneral ( self, div, navItem ): """Generate a bottom navigational link in the general case. [ (div is an et.Element) and (navItem is a NavItem instance) -> div +:= (navItem.shortName, boldfaced) + (elements of navItem.destList as links, separated by BOT_NAV_SEP) ] """ #-- 1 -- # [ div +:= a new "b" child element with text # (navItem.shortName) # prefix := "" ] b = et.SubElement ( div, "b" ) b.text = "%s: " % navItem.shortName prefix = "" #-- 2 -- # [ div +:= (prefix) + (elements of navItem.destList, # made into links, separated by BOT_NAV_SEP) # prefix := BOT_NAV_SEP ] for title, url in navItem.destList: #-- 2 body -- # [ div +:= (prefix) + (a link to url with link # text=title) # prefix := BOT_NAV_SEP ] #-- 2.1 -- # [ if bool(prefix) -> # div +:= prefix # else -> I ] if prefix: addTextMixed ( div, prefix ) #-- 2.2 -- # [ div +:= (link to url with link text (title)) ] a = et.SubElement ( div, "a", href=url ) a.text = title #-- 2.3 -- prefix = BOT_NAV_SEP # - - - T C C P a g e . _ _ c o l o p h o n - - - def __colophon ( self ): """Add the colophon section to the page. [ (self.author as invariant) and (self.url as invariant) -> self.body := self.body with the colophon added self.address := the address element within that colophon ] """ #-- 1 -- # [ self.body +:= an hr element ] et.SubElement ( self.body, "hr" ) #-- 2 -- # [ self.body +:= a new address element containing # self.author # self.address := that element ] self.address = et.SubElement ( self.body, "address" ) if self.author: self.address.text = self.author #-- 3 -- # [ self.body +:= current time ] div = et.SubElement ( self.body, "div" ) div.text = ( "Last updated: %s" % time.strftime ( "%Y-%m-%d %H:%M %Z" ) ) #-- 4 -- # [ if self.url -> # self.body +:= self.url ] if self.url: div = et.SubElement ( self.body, "div" ) div.text = "URL: " tt = et.SubElement ( div, "tt" ) tt.text = self.url # - - - T C C P a g e . w r i t e - - - def write ( self, outFile=None ): """Write the page as XHTML.""" #-- 1 -- # [ if outFile is None -> # out := sys.stdout # else -> # out := outFile ] out = outFile or sys.stdout #-- 2 -- # [ out +:= DOCTYPE for XHTML 1.1 ] out.write ( XHTML_DOCTYPE ) #-- 3 -- # [ out +:= XHTML rendering of self.doc ] out.write(et.tostring(self.root, pretty_print=True, encoding="utf-8")) # - - - - - c l a s s N a v L i n k - - - - - class NavLink: """Represents one navigational feature with zero or more destinations. Exports: NavLink ( shortName, destList=None, noTop=0 ): [ (shortName is the short name of this feature) and (destList is a list of (title, url) tuples representing places this link should point, defaulting to none) and (noTop is true iff this feature should be omitted from the top nav bar) -> return a new NavLink instance representing those values ] .shortName: [ as passed to constructor, read-only ] .destList: [ as passed to constructor, read-only ] .noTop: [ as passed to constructor, read-only ] """ def __init__ ( self, shortName, destList=None, noTop=0 ): """Constructor for NavLink.""" self.shortName = shortName self.destList = destList self.noTop = noTop