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

8. class TCCPage: The page instance interface

Here's the interface to this class:

tccpage2.py
#================================================================
# 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”.

8.1. TCCPage.__init__(): Constructor

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.

tccpage2.py
# - - -   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.

tccpage2.py
        #-- 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()

8.2. TCCPage.__createHead(): Set up page heading

This method sets up the page's head element and all its content.

tccpage2.py
# - - -   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.

tccpage2.py
        #-- 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.

tccpage2.py
        #-- 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.

tccpage2.py
        #-- 3 --
        # [ self.head  :=  self.head with a stylesheet link added ]
        et.SubElement ( self.head, "link", rel="stylesheet",
                     type="text/css",
                     href=self.cssUrl )

8.3. TCCPage.__createBody(): Set up page body

This method sets up the overall page body structure.

tccpage2.py
# - - -   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:

  1. Top navigational bar.

  2. Title block with logo.

  3. The div element we export as attribute self.content. Here, the caller adds the actual page content.

  4. Horizontal rule and bottom navigational links.

  5. Another horizontal rule and the colophon section.

tccpage2.py
        #-- 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()

8.4. TCCPage.__topNav(): Set up top nav bar

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.

tccpage2.py
# - - -   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.

tccpage2.py
        #-- 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.

tccpage2.py
        #-- 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

8.5. TCCPage.__topNavItem(): Add one top navigational item

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

tccpage2.py
# - - -   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.

tccpage2.py
        #-- 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.

tccpage2.py
        #-- 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 )

8.6. TCCPage.__titleBlock(): Set up page title block

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.

tccpage2.py
# - - -   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 )

8.7. TCCPage.__botNav(): Set up page-bottom navigational links

This method outputs the hr element below the page body, followed by page-bottom navigational links made from self.navList.

tccpage2.py
# - - -   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.

tccpage2.py
        #-- 1 --
        # [ self.body  +:=  an hr element ]
        et.SubElement ( self.body, "hr" )

Then we add the links from self.navList.

tccpage2.py
        #-- 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 )

8.8. TCCPage.__botNavItem(): Generate page-bottom navigational link

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.

tccpage2.py
# - - -   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.

tccpage2.py
        #-- 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.

tccpage2.py
        #-- 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 )

8.9. TCCPage.__botNavShort(): Generate a short-form bottom nav link

This method generates a bottom navigational link in the special case that the link text and the title are the same.

tccpage2.py
# - - -   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

8.10. TCCPage.__botNavGeneral(): Generate a general bottom nav link

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.

tccpage2.py
# - - -   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.

tccpage2.py
        #-- 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.

tccpage2.py
        #-- 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

8.11. TCCPage.__colophon(): Add colophon section

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

tccpage2.py
# - - -   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:

tccpage2.py
        #-- 1 --
        # [ self.body  +:=  an hr element ]
        et.SubElement ( self.body, "hr" )

Next, the address element. We insert self.author if it is nonempty.

tccpage2.py
        #-- 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.

tccpage2.py
        #-- 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.

tccpage2.py
        #-- 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

8.12. TCCPage.write(): Output the finished page

This method sends the XHTML to the output.

tccpage2.py
# - - -   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"))