##################################################
# SPYCE - Python-based HTML Scripting
# Copyright (c) 2002 Rimon Barr.
#
# Refer to spyce.py
# CVS: $Id: toc.py 944 2006-07-22 17:25:58Z ellisj $
##################################################
from spyceModule import spyceModule
import tree
__doc__ = '''
The TOC module provides support for constructing a table contents for a
lengthy document, such as this user documentation. The primary task of the TOC
module is to maintain a document tree, and initiate callbacks at the
appropriate points in the document. Note that this module may automatically
force a secondary processing of the Spyce file to resolve forward references.
<p>
The module provides the following methods to segment the document:
<ul>
<li><b>begin</b>( data, [tag] ): <br> Increase the nesting level and add a
new section. The <b>data</b> is stored in the document tree, and used for
callbacks (see later). An optional <b>tag</b> may be associated with the
node, otherwise one will automatically be generated. The function <b>b</b>()
is equivalent. </li><p>
<li><b>next</b>( data, [tag] ): <br> Add a new section at the same nesting
level. The <b>data</b> is stored in the document tree, and used for
callbacks (see later). An optional <b>tag</b> may be associated with the
node, or one will be automatically generated. The function <b>n</b>() is
equivalent.</li><p>
<li><b>end</b>(): <br> Decrease the nesting level. The function <b>e</b>()
is equivalent.</li><p>
<li><b>anchor</b>( data, [tag] ): <br> Set <b>data</b> and optionally the
<b>tag</b> associated with the root of the document tree. If the tag is
omitted, it defaults to the string <i>'root'</i>. </li><p>
<li><b>level</b>( depth, data, [tag] ): <br> Start a new section at given
<b>depth</b> with given <b>data</b> and optional <b>tag</b>. The necessary
begin(), next() and end() calls are automatically made, based on the current
document depth, so both types of calls can be inter-mixed. </li> <p>
<li><b>l1</b>( data, [tag] ): <br> Start a level 1 section. This
function merely calls <b>level</b>(1, <b>data</b>, <b>tag</b>).
The functions, <b>l2</b>()...<b>l9</b>() are similarly defined. </li> <p>
</ul> <p>
The following methods provide access to document information:
<ul>
<li><b>getTag</b>(): <br> Return the tag of the current document section.
</li><p>
<li><b>getNumbering</b>( [tag] ) <br> Return the numbering of some section
of the document identified by the given <b>tag</b>. If the tag is omitted,
the current document section is assumed. The numbering is an array of
numbers. This function may return 'None' on the first pass through a
document. </li><p>
<li><b>getData</b>( [tag] ) <br> Return the data associated with some
section of the document identified by the given <b>tag</b>. If the tag is
omitted, the current document section is assumed. This function may return
'None' on the first pass through a document. </li><p>
<li><b>getDepth</b>( [tag] ) <br> Return the depth of some section of the
document identified by the given <b>tag</b>. If the tag is omitted, the
current document section is assumed. This function may return 'None' on the
first pass through a document. </li><p>
<li><b>getNextTag</b>( [tag] ) <br> Return the tag of the section following
some section of the document identified by the given <b>tag</b>. If the tag
is omitted, the current document section is assumed. If this is the last
section of the document, then this function will return 'None'. This
function may return 'None' on the first pass through a document. </li><p>
<li><b>getPrevTag</b>( [tag] ) <br> Return the tag of the section before
some section of the document identified by the given <b>tag</b>. If the tag
is omitted, the current document section is assumed. If this is the first
section of the document, then this function will return 'None'. This
function may return 'None' on the first pass through a document. </li><p>
<li><b>getParentTag</b>( [tag] ) <br> Return the tag of the section above
(or containing) some section of the document identified by the given
<b>tag</b>. If the tag is omitted, the current document section is assumed.
If this is the top-most section of the document, then this function will
return 'None'. This function may return 'None' on the first pass through a
document. </li><p>
<li><b>getChildrenTags</b>( [tag] ) <br> Return a list (possibly empty) of
tags of the sections directly contained within some section of the document
identified by the given <b>tag</b>. If the tag is omitted, the current
document section is assumed. This function may return a shorter list than
anticipated or 'None', on the first pass through a document.
</ul> <p>
The TOC modules can make callbacks to handlers that format the document
correctly. The handlers should be defined and registered before the first
section break in the document. The following functions register handlers:
<ul>
<li><b>setDOC_PUSH</b>( f ): <br> Register a function <b>f</b> to be called
when the nesting depth of the document increases. </li><p>
<li><b>setDOC_POP</b>( f ): <br> Register a function <b>f</b> to be called
when the nesting depth of the document decreases. </li><p>
<li><b>setDOC_START</b>( f ): <br> Register a funtion <b>f</b> to be called
at the beginning of a section. </li><p>
<li><b>setDOC_END</b>( f ): <br> Register a function <b>f</b> to be called
at the end of a section. </li><p>
<li><b>setTOC_PUSH</b>( f ): <br> Register a function <b>f</b> to be called
when the nesting depth of the table of contents increases. </li><p>
<li><b>setTOC_POP</b>( f ): <br> Register a function <b>f</b> to be called
when the nesting depth of the table of contents decreases. </li><p>
<li><b>setTOC_ENTRY</b>( f ): <br> Register a function <b>f</b> to be called
for each table of contents entry. </li><p>
</ul><p>
Each callback function should be of the form: <center> <b>f</b>(depth,
tag, numbering, data), </center> where: <b>depth</b> is the nesting depth,
<b>tag</b> is the associated tag, <b>numbering</b> is the position array, and
<b>data</b> is the associated data of the section for which the callback was
made.<p>
The <i>DOC</i> callbacks are made as the sections are encountered. The
<i>TOC</i> callbacks are made while printing the table of contents. If the
modules detects that forward references exist in the document, the document
will be processed twice, and only the second output will be sent. Note that
buffering MUST be turned on for this to function correctly. <p>
To display a table of contents, define the appropriate TOC callback functions
and call:
<ul>
<li><b>showTOC</b>(): Display the table of contents.
</ul>
'''
ROOT_NAME = 'root'
class toc(spyceModule):
def start(self):
if not self._api.getModule('pool').has_key('toc'):
self._api.getModule('pool')['toc'] = {}
try:
self.oldtree, self.oldtags = self._api.getModule('pool')['toc'][self._api.getFilename()]
except (KeyError, TypeError):
self.oldtree = tree.tree( (ROOT_NAME, [], None) )
self.oldtags = {ROOT_NAME: self.oldtree}
# tree data: (tag, numbering, data)
self.tree = tree.tree((ROOT_NAME, [], None))
self.tags = {ROOT_NAME: self.tree}
self.node = self.tree
self.numbering = []
self.autotag = 0
self.tocShown = 0
self.fDOC_PUSH = None
self.fDOC_POP = None
self.fDOC_START = None
self.fDOC_END = None
self.fTOC_PUSH = None
self.fTOC_POP = None
self.fTOC_ENTRY = None
def finish(self, theError):
if self.oldtree is not None:
self.oldtree.delete()
self.oldtree = None
self.oldtags = None
def generate(self):
self.tree.computePreChain()
regenerate = not (self.oldtree == self.tree)
file = self._api.getFilename()
self._api.getModule('pool')['toc'][file] = self.tree, self.tags
if self.tocShown and regenerate:
self._api.getModule('redirect').internal(filename=file)
# set callbacks
def setDOC_PUSH(self, f):
self.fDOC_PUSH = f
def setDOC_POP(self, f):
self.fDOC_POP = f
def setDOC_START(self, f):
self.fDOC_START = f
def setDOC_END(self, f):
self.fDOC_END = f
def setTOC_PUSH(self, f):
self.fTOC_PUSH = f
def setTOC_POP(self, f):
self.fTOC_POP = f
def setTOC_ENTRY(self, f):
self.fTOC_ENTRY = f
# sectioning
def begin(self, data, tag=None, number=1):
self._emit(self.node, self.fDOC_PUSH)
self.numbering = _in(self.numbering)
if number:
self.numbering = _inc(self.numbering)
self.node = self.node.append( (tag, self.numbering, data) )
else:
self.node = self.node.append( (tag, None, data) )
if not tag: tag = self._genTag()
self.tags[tag] = self.node
self._emit(self.node, self.fDOC_START)
def end(self):
self._emit(self.node, self.fDOC_END)
self.numbering = _out(self.numbering)
self.node = self.node.parent
self._emit(self.node, self.fDOC_POP)
def next(self, data, tag=None, number=1):
self._emit(self.node, self.fDOC_END)
self.node = self.node.parent
if number:
self.numbering = _inc(self.numbering)
self.node = self.node.append( (tag, self.numbering, data) )
else:
self.node = self.node.append( (tag, None, data) )
if not tag: tag = self._genTag()
self.tags[tag] = self.node
self._emit(self.node, self.fDOC_START)
def anchor(self, data, tag=ROOT_NAME):
self.tree.data = tag, [], data
self.tags[tag] = self.tree
# shortcuts
b=begin
e=end
n=next
# sectioning by depth
def level(self, depth, data, tag=None):
curdepth = self.getDepth()
if curdepth > depth: # indent
while curdepth > depth:
self.end()
curdepth = self.getDepth()
self.next(data, tag)
elif curdepth < depth: # outdent
while curdepth < depth - 1:
self.begin(None)
curdepth = self.getDepth()
self.begin(data, tag)
else: # next
self.next(data, tag)
def l1(self, data, tag=None):
self.level(1, data, tag)
def l2(self, data, tag=None):
self.level(2, data, tag)
def l3(self, data, tag=None):
self.level(3, data, tag)
def l4(self, data, tag=None):
self.level(4, data, tag)
def l5(self, data, tag=None):
self.level(5, data, tag)
def l6(self, data, tag=None):
self.level(6, data, tag)
def l7(self, data, tag=None):
self.level(7, data, tag)
def l8(self, data, tag=None):
self.level(8, data, tag)
def l9(self, data, tag=None):
self.level(9, data, tag)
# show toc
def showTOC(self):
self.tocShown = 1
self._tocHelper(self.oldtree)
def _tocHelper(self, node):
self._emit(node, self.fTOC_ENTRY)
if node.children:
self._emit(node, self.fTOC_PUSH)
for c in node.children:
self._tocHelper(c)
self._emit(node, self.fTOC_POP)
# current state
def getTag(self, node=None):
self.tocShown = 1
if not node: node = self.node
tag, numbering, data = node.data
return tag
def getNumbering(self, tag=None):
self.tocShown = 1
try:
node = self.node
if tag: node = self.oldtags[tag]
tag, numbering, data = node.data
return numbering
except KeyError:
return None
def getData(self, tag=None):
self.tocShown = 1
try:
node = self.node
if tag: node = self.oldtags[tag]
tag, numbering, data = node.data
return data
except KeyError:
return None
def getDepth(self, tag=None):
self.tocShown = 1
try:
node = self.node
if tag: node = self.tags[tag]
return node.depth
except KeyError:
return None
def getNextTag(self, tag=None):
self.tocShown = 1
try:
if not tag: tag = self.getTag()
tag = self.oldtags[tag].next
if tag==None: return None
return self.getTag(tag)
except KeyError:
return None
def getPrevTag(self, tag=None):
self.tocShown = 1
try:
if not tag: tag = self.getTag()
node = self.oldtags[tag].prev
if node==None: return None
return self.getTag(node)
except KeyError:
return None
def getParentTag(self, tag=None):
self.tocShown = 1
try:
if not tag: tag = self.getTag()
node = self.oldtags[tag].parent
if node==None: return None
return self.getTag(node)
except KeyError:
return None
def getChildrenTags(self, tag=None):
self.tocShown = 1
try:
if not tag: tag = self.getTag()
nodes = self.oldtags[tag].children
return map(self.getTag, nodes)
except KeyError:
return None
# internal helpers
def _genTag(self):
tag = 'auto_'+str(self.autotag)
self.autotag = self.autotag + 1
return tag
def _emit(self, node, f):
tag, numbering, data = node.data
if f: s = f(node.depth, tag, numbering, data)
# hierarchical counting
def _inc(numbering, inc=1):
return numbering[:-1]+[numbering[-1]+inc]
def _in(numbering, start=0):
return numbering+[start]
def _out(numbering):
return numbering[:-1]
def defaultOutput(tag, numbering, data):
return reduce(lambda s, i: '%s%d.' % (s, i), numbering, '') + ' ' + str(data)
|