#! /usr/bin/python # Copyright (c) 1999-2000 by Alex Schroeder """HTML-TOC USAGE html-toc [-dv] toc-file OPTIONS -d debuggin -v verbose EXAMPLES creater master toc and link tags in all HTML files listed in the TOC file: html-toc FILES The TOC file lists the HTML files that make up a web site in the correct order. Every file can have one of the following attributes: chapter, section, subsection, contents, index. All files with the section attribute will be indented one level in the TOC (table of contents); all files with the subsection attribute will be indented two levels in the TOC. Example: contents index.html index site-index.html chapter awww.html chapter emacs.html chapter atlantis/index.html section atlantis/juenger.html The file with the contents attribute will have an HTML TOC block updated. The HTML TOC block has an entry for each HTML file in the TOC file. Each entry consists of a link to the HTML file, the name of the HTML file and a description of the HTML file. The name is taken from the first H1 header found in the HTML file (stripping all HTML tags). The description is taken from the relevant META tag. Here is an example HTML fragment: Alex: Emacs

Emacs

The name of the HTML will be "Emacs" and not "Alex: Emacs". The content of the TITLE tag is intended to be used in bookmark files of visitors; it is not supposed to appear in the TOC. The HTML TOC block replaced in the HTML file with the contents attribute is recognized by the following starting fragment (could be any heading level from 1 to 6):

...

... RESULT Every HTML file will have an HTML LINK block updated. An HTML LINK block consists of several consecutive lines of LINK tags. It contains a link rev="chapter" and a link rel="contents" tag to the file with the contents attribute, a link rel="index" tag to the file with the index attribute, a link rel="next" and a link rel="prev" tag to the files before and after itself in the TOC, and a link rel="up" tag to the last file with a smaller indentation level (or to the file with the contents attribute if there is no such file). Example: """ import getopt import sys import string import re import os verbose = 0 def main(): # must be declared global in order to change it global verbose try: opts, args = getopt.getopt(sys.argv[1:], 'vd') except getopt.error, msg: sys.stdout = sys.stderr print msg print __doc__%globals() sys.exit(2) for o, a in opts: if o == '-v': verbose = verbose + 1 if o == '-d': verbose = verbose + 2 if len(args) != 1: sys.exit("A TOC file is required") Toc(args[0]).update() class Toc: def __init__(self, filename): try: f = open(filename) except IOError, msg: sys.exit(msg) self.toc = [] for line in f.readlines(): line = string.strip(line) (attr, filename) = string.split(line,None,1) entry = Entry(filename,attr) self.toc.append(entry) if verbose >= 2: print entry f.close() if len(self.toc) == 0: sys.exit("%s: No pages listed" % filename) def update(self): self.update_toc() self.update_files() self.update_contents(self.master_toc()) def update_toc(self): "Complete the datastructure." # determine the contents and index pages self.find_special_pages() # set all the links up_links = [] level = self.starting_level() pagecount = len(self.toc) for i in range(pagecount): # set previous link if not on the first page if i > 0: self.toc[i].prev = self.toc[i-1] # set next link if not on the last page if i+1 < pagecount: self.toc[i].next = self.toc[i+1] # if on the same level, use the same up link as the previous page. if self.toc[i].level == level and self.toc[i].prev: self.toc[i].up = self.toc[i].prev.up # if on a higher level, use the previous page as an up # link and add the current link to the list of up links. while self.toc[i].level > level: self.toc[i].up = self.toc[i].prev up_links.append(self.toc[i]) level = level + 1 # if on a lower level, pop an up link from the list of up links. while self.toc[i].level < level: self.toc[i].up = up_links.pop().prev.up level = level - 1 # set contents and index link self.toc[i].contents = self.contents self.toc[i].index = self.index if verbose > 1: print "%d %s prev: %s, next: %s, up: %s, contents: %s, index: %s" % ( self.toc[i].level, self.toc[i], self.toc[i].prev_link(), self.toc[i].next_link(), self.toc[i].up_link(), self.toc[i].contents_link(), self.toc[i].index_link()) def find_special_pages(self): self.contents = None self.index = None for page in self.toc: if page.attr == 'contents': self.contents = page if page.attr == 'index': self.index = page def starting_level(self): level = self.toc[0].level if level != 1: sys.exit("First page ('%s') has type '%s' which is not on level 1" % (self.toc[0].filename, self.toc[0].attr)) return level def update_files(self): "Replace LINK tags in the HTML files." for page in self.toc: links = '' # This used to contain at least one LINK REV links but doesn't anymore... :) if page.attr != 'contents' and page.contents: links = links + '\n' % ( page.contents_link(), page.contents.name ) if page.attr != 'index' and page.index: links = links + '\n' % ( page.index_link(), page.index.name ) if page.next: links = links + '\n' % ( page.next_link(), page.next.name ) if page.prev: links = links + '\n' % ( page.prev_link(), page.prev.name ) if page.up: links = links + '\n' % ( page.up_link(), page.up.name ) self.update_links(page.filename, links) links_regexp = re.compile( r"(\s*)+", re.IGNORECASE | re.DOTALL ) def update_links(self, filename, links): "Open FILENAME and replace the link block with the LINKS string." try: f = open(filename) except IOError, msg: sys.exit(msg) text = f.read() replacement = self.links_regexp.sub(links, text, 1) f.close() if verbose > 1: print filename print links print "old:" print text print "new:" print replacement if text != replacement: os.rename(filename, filename + "~") self.write_file(filename, replacement) else: if verbose > 0: print "No changes in %s" % filename def write_file(self, filename, text): "Write TEXT into FILENAME." try: f = open(filename, "w") except IOError, msg: sys.exit(msg) f.write(text) f.close() def master_toc(self): "Return an HTML TOC block." level = 0 toc_block = '' for page in self.toc: while page.level > level: toc_block = toc_block + "\n" level = level - 1 toc_block = toc_block + "
  • %s: %s\n" % ( self.contents.link_to(page), page.name, page.desc ) while level > 0: toc_block = toc_block + "\n" level = level - 1 return toc_block toc_regexp = re.compile( r"(.*?).*?\n\n", re.IGNORECASE | re.DOTALL | re.MULTILINE ) def update_contents(self, toc_block): "Replace HTML TOC block in the contents file." try: f = open(self.contents.filename) except IOError, msg: sys.exit(msg) text = f.read() match = self.toc_regexp.search(text) title = match.group(1) + "\n" replacement = self.toc_regexp.sub(title + toc_block + "\n", text, 1) f.close() if text != replacement: os.rename(self.contents.filename, self.contents.filename + "~") self.write_file(self.contents.filename, replacement) else: if verbose > 0: print "No TOC changes in %s" % self.contents.filename class Entry: name_regexp = re.compile( r"(.*?)", re.IGNORECASE | re.DOTALL ) desc_regexp = re.compile( r"", re.IGNORECASE | re.DOTALL ) tag_regexp = re.compile( r"<.*?>", re.DOTALL ) level_equiv = { 'chapter': 1, 'section': 2, 'subsection': 3, 'index' : 1, 'contents': 1 } def __init__(self, filename, attr): self.filename = filename self.attr = attr try: self.level = self.level_equiv[attr] except KeyError: sys.exit("TOC lists %s with unknown attribute '%s'" % (filename, attr)) self.name = '' self.desc = '' self.parse() # Links; there are no methods to set these, set them directly. # These contain references to Entry objects. # Use the *_link() methods to retrieve the relative links. self.prev = None self.next = None self.up = None self.index = None self.contents = None def should_have_desc(self): "Return true for all entries except for the contents entry." return self.attr != "contents" def parse(self): "Open file and determine name and description of the page. Set self.name and self.desc." try: f = open(self.filename) except IOError, msg: sys.exit(msg) text = f.read() self.name = self.find_name(text) if self.should_have_desc(): self.desc = self.find_desc(text) f.close() def find_name(self, text): "Find the name of the page in the file by looking at the H1 tag." match = self.name_regexp.search(text) if match: return self.strip_html(match.group(1)) else: sys.exit("%s: No H1 tag to determine the name of the page\n" % self.filename) def find_desc(self, text): "Find the description of the page in the file by looking at the corresponding META tag." match = self.desc_regexp.search(text) if match: return self.strip_html(match.group(2)) else: sys.exit("%s: No META tag to determine the description of the page\n" % self.filename) def strip_html(self, fragm): "Strip a fragment of HTML code of all tags and collapse whitespace." # Remove tags fragm = self.tag_regexp.sub('', fragm) # Collapse whitespace. fragm = string.join(string.split(fragm)) # Remove leading and trailing space, if any. fragm = string.strip(fragm) return fragm def __str__(self): if verbose > 3: return "%s (%s: %d): %s -- %s..." % (self.filename, self.attr, self.level, self.name, self.desc[:15]) else: return self.filename def prev_link(self): return self.link_to(self.prev) def next_link(self): return self.link_to(self.next) def up_link(self): return self.link_to(self.up) def contents_link(self): return self.link_to(self.contents) def index_link(self): return self.link_to(self.index) def link_to(self, other): "Return the relativ link to the other entry." # When creating a forward link between two absolute filenames, # you have to keep all excess directories in the to-file, or # you must prefix the to-file with ../ for each excess # directory in the from-file. if other == None: return None src = string.split(self.filename, '/') dest = string.split(other.filename, '/') link = '' # If the to-file file is in a directory ABOVE the from-file, # eg. the from-file is 'a/b/c/d/source.html' and the to-file # is 'a/b/c/target.html', the link is '../target.html'. # If the to-file file is in a directory BELOW the from-file, # eg. the from-file is 'a/b/c/source.html' and the to-file is # 'a/b/c/d/target.html', the link is 'd/target.html'. # Start by stripping directories that are equal. while src[0] == dest[0] and len(src) > 1 and len(dest) > 1: src = src[1:] dest = dest[1:] # If src has more elements than just the filename, the target is # ABOVE: Replace each directory from src with '../'. for i in src[:-1]: link = link + '../' # If dest has more than the filename, the target is BELOW: Add # all the directories from dest, including the filename. link = link + string.join(dest,'/') return link if __name__ == '__main__': main()