Here's the interface to this class:
#================================================================
# 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 ]
"""
The navList argument describes a
sequence of standard navigational links that appear on the
bottom of each page, and optionally on the top as well.
This argument is a list of zero or more NavLink instances, each of which describes
one navigational link and the place or places where that
link goes. See Section 9, “class NavLink: Describes
one navigational feature”.
The constructor for this class creates the XHTML page as a
DOM Document tree, fills in the top
and bottom content, and creates self.content as an empty div 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."""
We divide the construction of the page into these parts:
creation of the Document instance;
creation of its head element; and creation
of its body element.
#-- 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()
This method sets up the page's head
element and all its content.
# - - - 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 ]
"""
First we create the actual head
element.
#-- 1 --
# [ self.doc := self.doc with a new head element added
# self.head := that element ]
self.head = et.SubElement ( self.root, "head" )
Next we set up the title element, if any, and set attribute
self.headTitle to point to it.
Unless the value of self.title is an
empty string, we then add the title text.
#-- 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
The stylesheet link is to the URL given by self.cssUrl.
#-- 3 --
# [ self.head := self.head with a stylesheet link added ]
et.SubElement ( self.head, "link", rel="stylesheet",
type="text/css",
href=self.cssUrl )
This method sets up the overall page body structure.
# - - - 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 ]
"""
The principal divisions of the body content, in order, are:
Top navigational bar.
Title block with logo.
The div element we export as
attribute self.content. Here,
the caller adds the actual page content.
Horizontal rule and bottom navigational links.
Another horizontal rule and the colophon section.
#-- 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()
The top navigational bar consists of a series of strings
separated by " / ". The content comes
from the elements of the self.navList
argument, each of which is a NavLink
instance. Each string may be a link or not, depending on
whether the corresponding instance's .destList has any members or not.
# - - - 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 ]
"""
First we create a div element to
hold the pieces of the nav bar.
#-- 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"
Now all that remains is to add one link for each element of
self.navList. The only tricky part
is getting the TOP_NAV_SEP separator
between elements, but not initially or finally. I am
indebted to Dr. Allan M. Stavely for the best (and easiest
to verify) way to do this. We set a variable called
prefix to the empty string
initially. Each time through the loop, we add a copy of
prefix, then we add the new
content, and then we set prefix to
TOP_NAV_SEP.
#-- 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
This method translates one NavLink
instance into a page-top navigational link. If the given
navItem.destList has a URL in it,
the link has link text navItem.shortName and points at that URL.
(This is intended only for links such as
“Next” that have a single URL. If there are
multiple URLs, we use the first one.) If there are no URLs,
the navItem.shortName attribute is
inserted as plain text (not a link).
# - - - 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) ]
"""
First we add the prefix to the parent
element; see Section 7, “addTextMixed(): Generating mixed
content with lxml”.
#-- 1 --
# [ navBar +:= prefix ]
addTextMixed ( navBar, prefix )
The test “if navItem.destList:” succeeds if that list is nonempty. It fails if
navItem.destList is either
None or an empty list.
#-- 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 )
The purpose of this method is to output the small table (one
row, two columns) that positions the main page title
h1 element to the left of the
logo graphic.
Here's the XHTML we're generating:
<table width="100%">
<tr valign="top">
<td align="left"><h1>self.title</h1>
<td align="right">
<a href="self.logoLink">
<img src="self.logoImage"
alt="logo"></a>
</tr>
</table>
Here's the code.
# - - - 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 )
This method outputs the hr element
below the page body, followed by page-bottom navigational
links made from self.navList.
# - - - 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 ]
"""
First we add the separator rule.
#-- 1 --
# [ self.body +:= an hr element ]
et.SubElement ( self.body, "hr" )
Then we add the links from self.navList.
#-- 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 )
For each NavItem instance in
self.navList, there are three
cases:
If the NavItem instance's
.destList is empty, no content
is generated.
For some links, such as “Site map”, the short name is the same as the title. It would be silly to generate the text “Site map: Site map” with the second repetition being the link.
In that case, the .destList
attribute has only one (title, URL) tuple, and the title
is empty. We generate a link containing the boldfaced
.shortName attribute, pointing
at the given URL.
In the general case, we start by generating the
boldfaced .shortName attribute,
but not as a link. This is followed by all links to
each element of .destList,
using the title of the (title, URL) tuple as the link
text and the URL as the destination.
# - - - 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 ]
"""
First we eliminate the case where nothing is generated.
#-- 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" )
Next we check for the special case where the short name is the same as the title.
#-- 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 )
This method generates a bottom navigational link in the special case that the link text and the title are the same.
# - - - 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
This method generates a bottom navigational link in the
general case. The content starts with the boldfaced
short name, then all the elements of navItem.destList separated by BOT_NAV_SEP.
# - - - 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) ]
"""
First we add the boldfaced short name. To get separators
between multiple links, we use the same prefix trick as
prefix to the empty string, then set it to
the separator after each piece is added.
#-- 1 --
# [ div +:= a new "b" child element with text
# (navItem.shortName)
# prefix := "" ]
b = et.SubElement ( div, "b" )
b.text = "%s: " % navItem.shortName
prefix = ""
Since there may be multiple links, we'll loop through the list of destinations, adding a link each time.
#-- 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
The last part of the page to be generated contains a
horizontal rule, the address
element, the time of last update, and the URL (if known).
# - - - 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 ]
"""
First, the horizontal rule:
#-- 1 --
# [ self.body +:= an hr element ]
et.SubElement ( self.body, "hr" )
Next, the address element. We
insert self.author if it is
nonempty.
#-- 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
We package the timestamp in a div element to make it small, since
this is not content most people care about.
#-- 3 --
# [ self.body +:= current time ]
div = et.SubElement ( self.body, "div" )
div.text = ( "Last updated: %s" %
time.strftime ( "%Y-%m-%d %H:%M %Z" ) )
Finally, we add another small heading with the URL, if it is known.
#-- 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
This method sends the XHTML to the output.
# - - - 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"))