"""body.py: Objects to represent input files for PyStyler
$Revision: 1.29 $ $Date: 2001/01/17 00:27:57 $
Exports:
class Body: Represents one page input (body) file
class LinkVar: Represents one variation of link text used in
an ... construct
class Target: Represents someplace a link can point
"""
import sys
import string # Standard string functions
import re # Standard regular expression package
from sgmltag import * # My SGML parsing stuff
from log import * # Error logging routine from library
from scan import * # File scanning routines from library
#================================================================
# Verification functions
#----------------------------------------------------------------
# effective-next(body, plan) ==
# if body contains a tag -> None
# else if body contains a tag ->
# a target representing shortname foo
# else if the body's topic has a right-hand sibling in plan ->
# a target representing that sibling
# else -> None
#--
# NB: If there are multiple tags, the last one wins.
#----------------------------------------------------------------
# effective-prev(body, plan) ==
# if body contains a tag -> None
# else if body contains a tag ->
# a target representing shortname foo
# else if the body's topic has a left-hand sibling in the plan ->
# a target representing that sibling
# else -> None
#--
# NB: If there are multiple tags, the last one wins.
#----------------------------------------------------------------
# effective-see-list(body, plan) ==
# a list consisting of:
# (1) a target representing the parent topic of this body's
# topic, unless a occurs in the body file; plus
# (2) any targets mentioned in tags (after the
# last if any)
#----------------------------------------------------------------
# effective-template(body, plan) ==
# if the body file contains a tag and
# that fileName names a valid template file ->
# a Template object representing fileName
# else ->
# the default template from body's topic
#----------------------------------------------------------------
# our-tag-list ==
# { , , , , , , , ,
# , }
#----------------------------------------------------------------
# single-tag-list ==
# { , , , , , , }
#----------------------------------------------------------------
# paired-tag-list == { , }
#----------------------------------------------------------------
# header-tag == { , , , , ,
# }
#--
# Note: and tags are basically ignored. In
# practice, a header-tag can occur anywhere. See the
# Body.__parseHeadTag() method for more comments on the treatment
# of and .
#----------------------------------------------------------------
# - - - - - c l a s s B o d y - - - - -
class Body:
"""Represents one input (body) file.
Exports:
Body ( topic, fileName ):
[ if (topic is a Topic object)
and (fileName is a string) ->
if fileName names a readable, valid body file ->
return a new Body object representing that fileName
as that topic
else ->
Log() +:= error message(s)
raise IOError ]
.topic: [ as passed to constructor ]
.author:
[ if self contains an tag ->
the content of that tag as a string
else -> None ]
.seeList:
[ effective-see-list(self, self.topic.plan) ]
.enext:
[ effective-next(self, self.topic.plan) ]
.eprev:
[ effective-prev(self, self.topic.plan) ]
.etemplate:
[ effective-template(self, self.topic.plan) ]
.text:
[ if self is valid ->
self's expanded body text as a list of strings
else -> None ]
.updated:
[ if self contains an tag ->
the value of the DATE attribute as a string
else -> None ]
"""
# - - - B o d y . _ _ i n i t _ _ - - -
def __init__ ( self, topic, fileName ):
"""Constructor for the Body object.
"""
#-- 1 --
self.topic = topic
self.seeList = []
self.etemplate = topic.template
self.author = None
self.updated = None
#-- 2 --
# [ self.enext := a Target object representing self.topic's
# right-hand sibling in topic.plan, if any,
# else None
# self.eprev := a Target object representing self.topic's
# left-hand sibling in topic.plan, if any,
# else None
# self.seeList := self.seeList + (a Target object
# representing self.topic's parent, if any) ]
self.__findSiblings ( )
#-- 3 --
# [ if fileName names a readable, valid body file ->
# self := self modified to reflect the contents
# of that file
# self.topic.plan := self.topic.plan with link variants
# added from that file
# else ->
# Log() +:= error message(s)
# self.topic.plan := self.topic.plan with link variants
# added from that file, if any
# raise IOError ]
self.__read ( fileName )
# - - - B o d y . _ _ f i n d S i b l i n g s - - -
def __findSiblings ( self ):
"""Find the previous, next, and up topics relative to self.
[ self.enext := a Target object representing self.topic's
right-hand sibling in topic.plan, if any,
else None
self.eprev := a Target object representing self.topic's
left-hand sibling in topic.plan, if any,
else None
self.seeList := self.seeList + (a Target object
representing self.topic's parent, if any) ]
"""
#-- 1 --
self.enext = None
self.eprev = None
#-- 2 --
# [ if self.topic has no parent ->
# return
# else ->
# parent := parent of self.topic
# self.seeList +:= a Target representing parent of self.topic ]
parent = self.topic.parent
if parent is None:
return
self.seeList.append ( Target ( parent ) )
#-- 3 --
# [ if self.topic has no left sibling -> I
# else ->
# self.eprev := a Target object for self.topic's left sibling ]
leftSibNo = self.topic.birthOrder() - 1
if leftSibNo >= 0:
self.eprev = Target ( parent.nthChild ( leftSibNo ) )
#-- 4 --
# [ if self.topic has no right sibling -> I
# else ->
# self.enext := a Target object for self.topic's right sibling ]
rightSibNo = leftSibNo + 2
if rightSibNo < parent.nChildren():
self.enext = Target ( parent.nthChild ( rightSibNo ) )
# - - - B o d y . _ _ r e a d - - -
def __read ( self, fileName ):
"""Reads one input body file.
[ if fileName names a readable, valid body file ->
self := self modified to reflect the contents
of that file
self.topic.plan := self.topic.plan with link variants
added from that file
else ->
Log() +:= error message(s)
self.topic.plan := self.topic.plan with link variants
added from that file, if any
raise IOError ]
"""
#-- 1 --
# [ errCount := err count from Log()
# self.text := "" ]
errCount = Log().count()
self.text = []
#-- 2 --
# [ if fileName names a readable file ->
# scan := a new Scan object positioned at the start of
# that file
# else ->
# Log() +:= error message
# raise IOError ]
try:
scan = Scan ( fileName )
except IOError, detail:
Log().error ( "Can't open input file `%s'" % fileName )
raise IOError, detail
#-- 3 --
# [ if scan contains a valid body file ->
# self := self modified to reflect scan's contents
# self.topic.plan := self.topic.plan with link variants added
# from scan
# else ->
# Log() +:= error message(s)
# self := self modified to reflect valid parts
# of scan, if any
# self.topic.plan := self.topic.plan with valid link variants
# added from scan, if any ]
while not scan.atEndFile:
#-- 3.1 --
# [ if scan starts with a badly formed tag ->
# scan := scan advanced past the tag or to EOF if it
# is unclosed, at least one character
# Log() +:= error message(s)
# else if scan starts with one of our tags ->
# scan := scan advanced past the valid part
# (at least one character) or to the
# end of the tag if it is valid
# self := self modified to reflect that tag
# Log() +:= error messages from expansion, if any
# self.topic.plan := self.topic.plan with link variants
# added from scan, if any
# else if scan starts with a valid tag ->
# scan := scan advanced past that tag
# self.text := self.text + (the equivalent of that tag)
# else ->
# scan := scan advanced up to the next tag, or EOF,
# whichever comes first
# self.text := self.text + (characters from scan up to
# the next tag, or EOF, whichever comes first) ]
self.__readToken ( scan )
#-- 4 --
# [ if errCount < (count of errors from Log()) ->
# raise IOError
# else -> return None ]
scan.close()
if errCount < Log().count():
raise IOError, "Errors in log file `%s'" % fileName
else:
return None
# - - - B o d y . _ _ r e a d T o k e n - - -
def __readToken ( self, scan ):
"""Read the input file up to the next tag of ours, or EOF
[ if scan starts with a badly formed tag ->
scan := scan advanced past the tag or to EOF if it
is unclosed, at least one character
Log() +:= error message(s)
else if scan starts with one of our tags ->
scan := scan advanced past that tag, and
past its closing tag if paired
self := self modified to reflect that tag
Log() +:= error messages from expansion, if any
self.topic.plan := self.topic.plan with link variants
added from scan, if any
else if scan starts with a valid tag ->
scan := scan advanced past that tag
self.text := self.text + (the equivalent of that tag)
else ->
scan := scan advanced up to the next tag, or EOF,
whichever comes first
self.text := self.text + (characters from scan up to
the next tag, or EOF, whichever comes first) ]
"""
#-- 1 --
# [ if scan starts with "<" but that begins no valid SGML tag ->
# scan := scan advanced past the "<" and then after the
# next ">" or to EOF, whichever comes first
# Log() +:= error message(s)
# else if input is a "<" starting a syntactically valid tag
# that is one of ours ->
# scan := scan advanced past the tag, and past its closing
# tag if paired
# self := self modified to reflect that tag
# self.topic.plan := self.topic.plan with link variants added
# from the tag, if any
# Log() +:= error message from expansion, if any
# else if input starts with a valid tag that is not ours ->
# scan := scan advanced past the tag
# self.text +:= the equivalent of that tag
# else ->
# scan := scan advanced to the next "<" or to EOF, whichever
# comes first
# self.text +:= characters from scan up to the next "<" or
# EOF, whichever comes first ]
if scan.match("<"):
#-- 1.1 --
# [ if scan starts with "<" but that begins no valid SGML tag ->
# scan := scan advanced after the next ">" or to EOF,
# whichever comes first
# Log() +:= error message(s)
# else if input is a "<" starting a valid tag that is
# one of ours ->
# scan := scan advanced past the tag, and past its
# closing tag if paired
# self := self modified to reflect that tag and its
# content if paired
# Log() +:= error messages from expansion, if any
# else if scan starts with a valid tag ->
# scan := scan advanced past that tag
# self.text := self.text + (the equivalent of that tag) ]
self.__readTag ( scan )
else:
#-- 1.2 --
# [ scan := scan advanced to the next "<" or to EOF,
# whichever comes first
# self.text +:= characters from scan up to the next "<" or
# EOF, whichever comes first ]
self.__readNonTag ( scan )
# - - - B o d y . _ _ r e a d N o n T a g - - -
def __readNonTag ( self, scan ):
"""Process text up to the next tag, or to EOF
[ scan := scan advanced to the next "<" or to EOF,
whichever comes first
self.text +:= characters from scan up to the next "<" or
EOF, whichever comes first ]
"""
#-- 1 --
while not scan.atEndFile:
#-- 1 loop --
# [ if there is a "<" on this line ->
# scan := scan advanced up to the "<"
# self.text +:= text from scan up to the "<"
# return
# else if this is the last line in scan ->
# scan := scan advanced to EOF
# self.text +:= remainder of line in scan + "\n"
# else ->
# scan := scan advanced to the next line
# self.text +:= remainder of line in scan + "\n" ]
tagStart = scan.find ( "<" )
if tagStart is not None:
self.text.append ( scan.tab ( tagStart ) )
return
self.text.append ( scan.tab(-1) )
self.text.append ( "\n" )
scan.nextLine()
# - - - B o d y . _ _ r e a d T a g - - -
def __readTag ( self, scan ):
"""Read an SGML tag, or pair of tags if it's ours
[ if scan starts with "<" but that begins no valid SGML tag ->
scan := scan advanced after the next ">" or to EOF,
whichever comes first
Log() +:= error message(s)
else if input is a "<" starting a valid tag that is
one of ours ->
scan := scan advanced past the tag, and past its
closing tag if paired
self := self modified to reflect that tag and its
content if paired
Log() +:= error messages about semantics, if any
else if scan starts with a valid tag ->
scan := scan advanced past that tag
self.text := self.text + (the equivalent of that tag) ]
"""
#-- 1 --
# [ if scan starts with a syntactically valid HTML tag ->
# scan := scan advanced past the tag
# tag := an SGMLTag object representing that tag with
# the tag and attribute names lowercased
# else ->
# scan := scan advanced after the next ">" or to EOF,
# whichever comes first
# Log() +:= error message(s)
# return ]
tag = SGML_Tag_Scan ( scan )
if tag is None:
return
#-- 2 --
# [ if tag is not one of ours ->
# self.text +:= source equivalent of tag
# else if tag is ours but invalid ->
# scan := scan advanced (if paired) past the closing tag
# if any, or to EOF
# Log() +:= error message(s)
# else if tag is valid and, if it is a paired tag,
# scan contains a closing tag ->
# scan := scan advanced past the closing tag, if any, else
# unchanged
# self.text +:= expansion of the tag
# self.topic.plan +:= link variant from the tag, if any
# self := self modified to reflect any header information
# in the tag ]
self.__dispatchTag ( scan, tag )
# - - - B o d y . _ _ d i s p a t c h T a g - - -
#----------------------------------------------------------------
# Generic calling sequence for a `dispatch-routine' that handles
# one of our tags:
# f ( scan, tag )
# Generic intendend function:
# [ if (scan is a Scan object)
# and (tag is an SGMLTag object) ->
# if tag is unpaired and invalid ->
# Log() +:= error message(s)
# if tag is unpaired and valid ->
# self.text +:= expansion of tag, if any
# self := self modified to reflect any header
# information from tag
# if tag is paired and invalid ->
# scan := scan advanced past the closing tag, if present,
# or EOF if it is missing
# Log() +:= error message(s)
# if tag is paired and valid ->
# scan := scan advanced past the closing tag
# self.text := expansion of tag, its contents, and its close,
# if any
# self.plan +:= link variants from tag, if any
# self := self modified to reflect any header
# information from the tag ]
#----------------------------------------------------------------
# The `tagDispatchMap', defined after all the dispatch-routines,
# maps each of our lowercased tag names |-> the corresponding
# dispatch-routine.
#----------------------------------------------------------------
def __dispatchTag ( self, scan, tag ):
"""Process a tag, one of ours (possibly paired) or a pass-through
[ if (tag is an SGMLTag object)
and (self.tagDispatchMap is a dictionary mapping each of our
lowercased tag names |-> the dispatch-routine for that tag) ->
if tag is not one of ours ->
self.text +:= source equivalent of tag
else if tag is ours but invalid ->
scan := scan advanced (if paired) past the closing tag
if any, or to EOF
Log() +:= error message(s)
else if tag is valid and, if it is a paired tag,
scan contains a closing tag ->
scan := scan advanced past the closing tag, if any,
else unchanged
self.text +:= expansion of the tag
self.topic.plan +:= link variant from the tag, if any
self := self modified to reflect any header information
in the tag ]
"""
#-- 1 --
lowGI = string.lower(tag.gi)
#-- 2 --
# [ if self.tagDispatchMap has a key for lowGI ->
# dispatch := corresponding value from self.tagDispatchMap
# else ->
# self.text +:= source equivalent of tag
# return ]
try:
dispatch = self.tagDispatchMap [ lowGI ]
except KeyError:
self.text.append ( str(tag) )
return
#-- 3 --
# [ if tag is a closing tag and lowGI is not "head" ->
# Log() +:= error message
# return
# else -> I ]
if ( ( tag.isClose ) and ( lowGI != "head" ) ):
scan.error ( "There is no opening tag corresponding to this "
"closing tag." )
return
#-- 4 --
# [ if dispatch(scan, tag) finds tag invalid ->
# scan := scan advanced past the content and closing tag,
# if expected and present, else to EOF
# Log() +:= error(s)
# if dispatch(scan, tag) finds tag valid ->
# scan := scan advanced past the content and closing tag,
# if any, else unchanged
# self.text +:= expansion of tag, if any
# self.topic.plan +:= link variant from tag and content, if any
# self := self modified to reflect any header information
# from the tag and content ]
dispatch ( self, scan, tag )
# - - - B o d y . _ _ p a r s e A u t h o r T a g - - -
def __parseAuthorTag ( self, scan, tag ):
"""Process the ... tag.
[ if scan contains a valid tag somewhere before EOF ->
scan := scan advanced past the first such tag
self.author := text from scan up to the tag
else ->
scan := scan advanced past tag or to EOF
Log() +:= error message ]
"""
#-- 1 --
# [ if scan contains a valid tag before EOF ->
# scan := scan advanced past the first such tag
# content := text from scan up to tag
# else ->
# scan := scan advanced to EOF
# content := None
# Log() +:= error message ]
content = self.__parseToClosingTag ( scan, tag.gi )
#-- 2 --
# [ if content is None -> I
# else if tag has any attributes ->
# Log() +:= error message
# else ->
# self.author := content ]
if content is not None:
self.author = content
if len(tag.attrList) > 0:
scan.error ( "No attributes are allowed on the tag." )
# - - - B o d y . _ _ p a r s e G T a g - - -
def __parseGTag ( self, scan, tag ):
"""Process the tag.
[ if tag is a valid tag ->
self.text +:= (link to target topic of tag) +
(title of target topic of tag) + ""
else ->
Log() +:= error message ]
"""
#-- 1 --
# [ if tag has one ID attribute that names a valid short name
# in self.topic.plan ->
# target := a Target object representing that short name
# else ->
# Log() +:= error message(s)
# return ]
target = self.__findTarget ( scan, tag )
if target is None:
return
#-- 2 --
# [ self.text +:= (link to target) + (title of target) + "" ]
self.__makeLink ( self.topic.relPath ( target ),
target.topic.title )
# - - - B o d y . _ _ p a r s e H e a d T a g - - -
def __parseHeadTag ( self, scan, tag ):
"""Check or tags.
This routine does nothing, because we ignore ...
tags. Although we encourage header-type tags to be placed
at the top of the file between ..., in actual
practice, header tags can occur anywhere.
"""
pass
# - - - B o d y . _ _ p a r s e N e x t T a g - - -
def __parseNextTag ( self, scan, tag ):
"""Process the tag.
[ if tag is a valid tag of the form ->
self.enext := a Target object representing foo
else if tag has the form ->
self.enext := None
else ->
Log() +:= error message ]
"""
#-- 1 --
# [ if tag has one ID attribute whose value is a short name
# found in self.topic.plan ->
# self.enext := a Target object representing that short name
# else if tag has one NONE attribute with no value ->
# self.enext := None
# else ->
# Log() +:= error message
# return ]
try:
self.enext = self.__findEffectiveTarget ( scan, tag )
except ValueError:
return
# - - - B o d y . _ _ p a r s e P r e v T a g - - -
def __parsePrevTag ( self, scan, tag ):
"""Process the tag.
[ if tag is a valid tag of the form ->
self.eprev := a Target object representing foo
else if tag has the form ->
self.eprev := None
else ->
Log() +:= error message ]
"""
#-- 1 --
# [ if tag has one ID attribute whose value is a short name
# found in self.topic.plan ->
# self.eprev := a Target object representing that short name
# else if tag has one NONE attribute with no value ->
# self.eprev := None
# else ->
# Log() +:= error message
# return ]
try:
self.eprev = self.__findEffectiveTarget ( scan, tag )
except ValueError:
return
# - - - B o d y . _ _ p a r s e R T a g - - -
def __parseRTag ( self, scan, tag ):
"""Process the tag.
[ if tag is a valid tag of the form ->
self.text +:= (link to foo) + "`" + (title of foo) + "'"
else ->
Log() +:= error message ]
"""
#-- 1 --
# [ if tag has one ID attribute that names a valid short name
# in self.topic.plan ->
# target := a Target object representing that short name
# else ->
# Log() +:= error message
# return ]
target = self.__findTarget ( scan, tag )
if target is None:
return
#-- 2 --
# [ self.text +:= (link to target) + "`" + (title of target)
# + "'" ]
self.__makeLink ( self.topic.relPath ( target ),
"`%s'" % target.topic.title )
# - - - B o d y . _ _ p a r s e R R T a g - - -
def __parseRRTag ( self, scan, tag ):
"""Process the ... tags.
[ if tag does not have the form where foo is a short
name defined in self.topic.plan ->
Log() +:= error message
scan := scan advanced past next tag, or to EOF
else if scan does not contain a tag before EOF ->
scan := scan advanced to EOF
Log() +:= error message
else ->
scan := scan advanced past the next tag
self.text +:= (link to foo) + (text from scan up to next
tag) + ""
self.topic.plan := self.topic.plan with a LinkVar added
with .text=(text from scan up to next tag),
.shortName=self.topic.shortName, and .lineNo=scan.lineNo ]
"""
#-- 1 --
# [ lineNo := current line number from scan ]
lineNo = scan.lineNo
#-- 2 --
# [ if scan contains an tag before EOF ->
# scan := scan advanced past the next tag
# linkText := text from scan up to next tag
# else ->
# scan := scan advanced to EOF
# Log() +:= error message
# return ]
linkText = self.__parseToClosingTag ( scan, tag.gi )
if linkText is None:
return
#-- 3 --
# [ if tag has one ID attribute that names a short name found in
# self.topic.plan ->
# target := a Target object representing that short name
# else ->
# Log() +:= error message
# return ]
target = self.__findTarget ( scan, tag )
if target is None:
return
#-- 4 --
# [ self.text +:= (link to target) + (linkText) + "" ]
self.__makeLink ( self.topic.relPath ( target ), linkText )
#-- 5 --
# [ self.topic.plan := self.plan with a LinkVar added with .text=
# (text from scan up to next tag),
# .shortName=self.topic.shortName, and
# .lineNo=scan.lineNo ]
linkVar = LinkVar ( linkText, self.topic.shortName, lineNo )
target.topic.addLinkVar ( linkVar )
# - - - B o d y . _ _ p a r s e S e e T a g - - -
def __parseSeeTag ( self, scan, tag ):
"""Process the tag.
[ if tag has the form where foo is a short name
found in self.topic.plan ->
self.seeList +:= a Target object representing foo
else if tag has the form ->
self.seeList := []
else ->
Log() +:= error message ]
"""
#-- 1 --
# [ if tag has one ID attribute whose value is a short name found
# in self.topic.plan ->
# target := a Target object representing that short name
# else if tag has one NONE attribute with no value ->
# target := None
# else ->
# Log() +:= error message
# return ]
try:
target = self.__findEffectiveTarget ( scan, tag )
except ValueError:
return
#-- 2 --
if target is None:
self.seeList = []
else:
self.seeList.append ( target )
# - - - B o d y . _ _ p a r s e T e m p l a t e T a g - - -
def __parseTemplateTag ( self, scan, tag ):
"""Process the tag.
[ if tag has one ID attribute that names a valid template
file, relative to the starting directory ->
self := self with its effective template changed to
that template as a Template object
else ->
Log() +:= error message ]
"""
#-- 1 --
# [ if tag has one attribute ->
# tagAttr := a TagAttr object representing that attribute
# else ->
# Log() +:= error message
# return ]
if len ( tag.attrList ) != 1:
scan.error ( "This tag must have exactly one ID attribute." )
return
else:
tagAttr = tag.attrList[0]
#-- 2 --
# [ if tagAttr has name "id" and a value ->
# path := that value
# else ->
# Log() +:= error message
# return ]
if tagAttr.name != "id":
scan.error ( "The attribute name of this tag must be `ID'." )
return
if tagAttr.value is None:
scan.error ( "The ID attribute must have the template file name, "
"in quotes, as a value." )
return
path = tagAttr.value
#-- 3 --
# [ if path names a valid template file relative to the starting
# directory ->
# template := a Template object representing that file
# else ->
# Log() +:= error message
# return ]
template = self.topic.plan.lookupTemplate ( path )
if template is None:
return
#-- 4 --
self.etemplate = template
# - - - B o d y . _ _ p a r s e U p d a t e d T a g - - -
datePat = re.compile ( r'^' # Start anchor
r'\$' # Start of RCS tag
r'Date' # Date keyword
r'(.*)' # Contents
r'\$' ) # End of RCS tag
def __parseUpdatedTag ( self, scan, tag ):
"""Process the tag.
[ if tag has one ID attribute whose value is a valid RCS date ->
self.updated := the timestamp from that tag
else ->
Log() +:= error message ]
"""
#-- 1 --
# [ if tag has exactly one attribute ->
# tagAttr := a TagAttr representing that attribute
# else ->
# Log() +:= error message
# return ]
if len ( tag.attrList ) != 1:
scan.error ( "This tag should have the form `'." )
return
tagAttr = tag.attrList[0]
#-- 2 --
# [ if tagAttr.name is not "date" ->
# Log() +:= error message
# return
# else -> I ]
if tagAttr.name != "date":
scan.error ( "This tag should have the form `'." )
return
#-- 3 --
# [ if tagAttr.value looks like a checked-in file's timestamp,
# with "Date: " followed later by "$" ->
# timestamp := everything between "Date" and "$" from
# tagAttr.value
# else ->
# Log() +:= error message
# return ]
m = self.datePat.match ( tagAttr.value )
if m is None:
scan.error ( "This tag should have the form `'." )
return
timestamp = string.rstrip ( m.group ( 1 ) )
if ( ( len ( timestamp ) < 2 ) or
( timestamp[0:2] != ": " ) ):
scan.error ( "This file has never been checked in." )
return
#-- 4 --
self.updated = timestamp[2:]
# - - - B o d y . t a g D i s p a t c h M a p - - -
#--
# Tag dispatch table: this enumerates all of `our' tags and
# the routines that process them. Note that the elements of
# this table are class methods, so they must be called with
# `self' as the first argument.
#--
tagDispatchMap = { "author": __parseAuthorTag,
"g": __parseGTag,
"head": __parseHeadTag,
"next": __parseNextTag,
"prev": __parsePrevTag,
"r": __parseRTag,
"rr": __parseRRTag,
"see": __parseSeeTag,
"template": __parseTemplateTag,
"updated": __parseUpdatedTag }
#================================================================
# SERVICE METHODS
#----------------------------------------------------------------
# - - - B o d y . _ _ f i n d T a r g e t - - -
def __findTarget ( self, scan, tag ):
"""Find the effective target of a tag, ID=shortname
[ if (tag is an SGMLTag object)
and (scan is a Scan object) ->
if tag has one ID attribute that names a valid short name
(with optional anchor) in self.topic.plan ->
return a Target object representing that short name
and optional anchor
else ->
Log() +:= error message(s)
return ]
"""
#-- 1 --
# [ if tag has one attribute ->
# tagAttr := a TagAttr object representing that attribute
# else ->
# Log() +:= error message
# return ]
if len ( tag.attrList ) != 1:
scan.error ( "This tag must have exactly one ID attribute." )
return
else:
tagAttr = tag.attrList[0]
#-- 2 --
# [ if tagAttr has name "id" and a value ->
# value := that value
# else ->
# Log() +:= error message
# return ]
if tagAttr.name != "id":
scan.error ( "The attribute name of this tag must be `ID'." )
return
if tagAttr.value is None:
scan.error ( "The ID attribute must have a quoted string value." )
return
else:
value = tagAttr.value
#-- 3 --
# [ if value, up to its first "#" if it contains one, is a valid
# short name in self.topic.plan ->
# target := a Target object for that short name, with an
# anchor from value if it contains "#"
# else ->
# Log() +:= error message
# return ]
try:
target = self.topic.plan.lookupShortName ( value )
except KeyError:
scan.error ( "Unknown short reference: `%s'" % value )
return
#-- 4 --
return target
# - - - B o d y . _ _ f i n d E f f e c t i v e T a r g e t - - -
def __findEffectiveTarget ( self, scan, tag ):
"""Find the effective target of a tag: ID=foo or NONE
[ if (tag is an SGMLTag object) ->
if tag has one ID attribute whose value is a short name
found in self.topic.plan, with optional anchor ->
return a Target object representing that short name
and optional anchor
else if tag has one NONE attribute ->
return None
else ->
Log() +:= error message
raise ValueError ]
"""
#-- 1 --
# [ if number of tag's attributes is not one ->
# Log() +:= error message
# return
# else ->
# tagAttr := a TagAttr repr. tag's first attribute ]
if len ( tag.attrList ) != 1:
scan.error
else:
tagAttr = tag.attrList[0]
#-- 2 --
# [ if tagAttr's name is "none" ->
# return None
# else -> I ]
if tagAttr.name == "none":
return None
#-- 3 --
# [ if tagAttr has name "id" and a value ->
# value := that value
# else ->
# Log() +:= error message
# return ]
if tagAttr.name != "id":
scan.error ( "The attribute name of this tag must be `ID'." )
raise ValueError, "Expecting an ID attribute."
if tagAttr.value is None:
scan.error ( "The ID attribute must have a quoted string value." )
raise ValueError, "Expecting ID=value attribute."
else:
value = tagAttr.value
#-- 4 --
# [ if value, up to its first "#" if it contains one, is a valid
# short name in self.topic.plan ->
# target := a Target object for that short name, with an
# anchor from value if it contains "#"
# else ->
# Log() +:= error message
# raise ValueError ]
try:
target = self.topic.plan.lookupShortName ( value )
except KeyError:
scan.error ( "Unknown short reference: `%s'" % value )
raise ValueError, "Unknown short reference."
#-- 5 --
return target
# - - - B o d y . _ _ p a r s e T o C l o s i n g T a g - - -
def __parseToClosingTag ( self, scan, gi ):
"""Find a closing tag with identifier gi in scan.
[ if (scan is a Scan object)
and (gi is a tag identifier) ->
if scan contains a closing tag with identifier gi ->
scan := scan advanced past the first such closing tag
return text from scan up to the closing tag
else ->
scan := scan advanced past a malformed tag or
to EOF, whichever is first
Log() +:= error message
return None ]
"""
#-- 1 --
# [ errCount := count of errors in the Log() object
# L := an empty list
# done := 0
# lowTarget := gi, lowercased ]
errCount = Log().count()
L = []
done = 0
#-- 2 --
# [ if scan contains optional text and/or tags followed by
# a closing tag with identifier gi ->
# scan := scan advanced past the first such closing tag
# L +:= (text from scan up to the closing tag)
# else ->
# scan := scan advanced past the first malformed tag or EOF
# Log() +:= error message(s)
# L +:= text and valid tags up to the first malformed
# tag or EOF ]
done = 0
while not done:
done = self.__gropeForCloser ( scan, L, gi )
#-- 3 --
# [ if errCount < (count of errors from Log() ) ->
# return None
# else ->
# return elements of L concatenated ]
if errCount < Log().count():
return None
else:
return string.join ( L, "" )
# - - - B o d y . _ _ g r o p e F o r C l o s e r - - -
def __gropeForCloser ( self, scan, L, gi ):
"""Nibble off something until EOF or a tag
Note: The value returned is 1 for done, 0 to signal keep going.
[ if scan is at EOF ->
return 1
else if scan starts with a valid close tag of type (gi) ->
scan := scan advanced past that tag
return 1
else if scan starts with a valid tag ->
scan := scan advanced past that tag
L +:= text of that tag, or a functional equivalent
return 0
else if scan starts with a malformed tag ->
scan := scan advanced past that tag or to EOF
Log() +:= error message(s)
return 0
else if the line in scan has an SGML_TAG_OPEN anywhere in it ->
scan := scan advanced up to the next SGML_TAG_OPEN
L +:= text up to the next SGML_TAG_OPEN
return 0
else if line in scan is the last in the file ->
scan := scan advanced to EOF
L +:= text from scan up to EOF
return 1
else ->
scan := scan advanced to the start of the next line
L +:= (remainder of line in scan) + "\n"
return 0 ]
"""
#-- 1 --
# [ if scan is at EOF -> return 1
# else -> I ]
if scan.atEndFile:
return 1
#-- 2 --
# [ if scan starts with SGML_TAG_OPEN ->
# pastOpen := position past that character
# else ->
# pastOpen := None ]
pastOpen = scan.match ( SGML_TAG_OPEN )
#-- 3 --
if pastOpen is None:
#-- 3.1 --
# [ if there is an SGML_TAG_OPEN anywhere on the current line
# of scan ->
# scan := scan advanced up to the first SGML_TAG_OPEN
# L +:= text up the first SGML_TAG_OPEN
# return 0
# else if line in scan is last in file ->
# scan := scan advanced to EOF
# L +:= (text up to EOF) + "\n"
# return 1
# else ->
# scan := scan advanced to the start of the next line
# L +:= (text up to end of line) + "\n"
# return 0 ]
result = self.__gropeNonTag ( scan, L )
return result
else:
#-- 3.2 --
# [ if scan starts with SGML_TAG_OPEN ->
# if scan starts with a valid closing tag of type gi ->
# scan := scan advanced past that tag
# return 1
# else if scan starts with a badly formed tag ->
# scan := scan advanced past that tag or to EOF
# Log() +:= error message(s)
# return 1
# else ->
# scan := scan advanced past the end of the tag
# L +:= text of the tag, or equivalent
# return 0 ]
result = self.__gropeTag ( scan, L, gi )
return result
# - - - B o d y . _ _ g r o p e N o n T a g - - -
def __gropeNonTag ( self, scan, L ):
"""Bite off text up to the next tag, end of line, or end of file.
[ if (scan is a Scan object) and (L is a list) ->
if there is an SGML_TAG_OPEN anywhere on the current line
of scan ->
scan := scan advanced up to the first SGML_TAG_OPEN
L +:= text up the first SGML_TAG_OPEN
return 0
else if line in scan is last in file ->
scan := scan advanced to EOF
L +:= (text up to EOF) + "\n"
return 1
else ->
scan := scan advanced to the start of the next line
L +:= (text up to end of line) + "\n"
return 0 ]
"""
#-- 1 --
# [ if there is an SGML_TAG_OPEN anywhere in the line in scan ->
# endp := position at the start of the first SGML_TAG_OPEN
# else ->
# endp := None ]
endp = scan.find ( SGML_TAG_OPEN )
#-- 2 --
if endp is None:
#-- 2.1 --
# [ scan := scan advanced to the next line, or EOF
# L +:= (text up to end of line in scan) + "\n" ]
text = scan.tab(-1)
scan.nextLine()
L.append ( text )
L.append ( "\n" )
else:
#-- 2.2 --
# [ scan := scan advanced to endp
# L +:= (text up to endp) ]
text = scan.tab ( endp )
L.append ( text )
#-- 3 --
if scan.atEndFile: return 1
else: return 0
# - - - B o d y . _ _ g r o p e T a g - - -
def __gropeTag ( self, scan, L, gi ):
"""Process a tag, signaling success if it's a close tag of type gi.
[ if (scan is a Scan object)
and (L is a list)
and (gi is a lowercased SGML tag name)
and (scan starts with SGML_TAG_OPEN) ->
if scan starts with a valid closing tag of type gi ->
scan := scan advanced past that tag
return 1
else if scan starts with a badly formed tag ->
scan := scan advanced past that tag or to EOF
Log() +:= error message(s)
return 1
else ->
scan := scan advanced past the end of the tag
L +:= text of the tag, or equivalent
return 0 ]
"""
#-- 1 --
# [ if scan starts with a valid SGML tag ->
# scan := scan advanced past that tag
# tag := an SGMLTag object representing that tag
# else if scan looks like a tag but it's badly formed ->
# scan := scan advanced past the tag or to EOF
# Log() +:= error message(s)
# tag := None ]
tag = SGML_Tag_Scan ( scan )
#-- 2 --
# [ if tag is None ->
# return 1
# else if tag is a closing tag of type gi ->
# return 1
# else ->
# L +:= text of tag, or equivalent
# return 0 ]
if tag is None:
return 1
if ( ( string.lower(gi) == string.lower(tag.gi) ) and
tag.isClose ):
return 1
else:
L.append ( str(tag) )
return 0
# - - - B o d y . _ _ m a k e L i n k - - -
def __makeLink ( self, path, linkText ):
"""Build a link: linkText
[ self.text +:= (link to path) + (linkText) + "" ]
"""
self.text.append ( '%s' % ( path, linkText ) )
# - - - - - c l a s s L i n k V a r - - - - -
class LinkVar:
"""An instance represents link text used in an ... construct.
Exports:
LinkVar ( text, shortName, lineNo ):
[ if (text is the content of the ... construct as a string)
and (shortName is the short name of the body file containing
the ... construct)
and (lineNo is the line number of the in the body file) ->
return a new LinkVar object with these values ]
.text [ as passed to constructor ]
.shortName [ as passed to constructor ]
.lineNo [ as passed to constructor ]
cmp() [ orders LinkVars by (text, short name, line no.) ]
"""
def __init__ ( self, text, shortName, lineNo ):
"""Constructor for a LinkVar object
"""
self.text = text
self.shortName = shortName
self.lineNo = lineNo
def __cmp__ ( self, other ):
"""Compares two LinkVar objects
"""
return cmp ( ( self.text, self.shortName, self.lineNo ),
( other.text, other.shortName, other.lineNo ) )
# - - - - - c l a s s T a r g e t - - - - -
class Target:
"""Represents one place a link can point.
Exports:
Target ( topic, anchor=None ):
[ if (topic is a Topic object)
and (anchor is a string starting with "#", or None) ->
return a new Target object with those values ]
.topic: [ as passed to constructor ]
.anchor: [ as passed to constructor ]
"""
def __init__ ( self, topic, anchor=None ):
"""Constructor for Target
"""
self.topic = topic
self.anchor = anchor
def __str__ ( self ):
"""Debug display for Target
"""
if not self.anchor:
return "T<%s>" % str(self.topic)
else:
return "T<%s%s>" % (str(self.topic), self.anchor)