# Copyright (C) 1998,1999,2000 by the Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """Library for program-based construction of an HTML documents. Encapsulate HTML formatting directives in classes that act as containers for python and, recursively, for nested HTML formatting objects. """ # Eventually could abstract down to HtmlItem, which outputs an arbitrary html # object given start / end tags, valid options, and a value. Ug, objects # shouldn't be adding their own newlines. The next object should. import types import string import mm_cfg import Utils # Format an arbitrary object. def HTMLFormatObject(item, indent): "Return a presentation of an object, invoking their Format method if any." if type(item) == type(''): return item elif not hasattr(item, "Format"): return `item` else: return item.Format(indent) def CaseInsensitiveKeyedDict(d): result = {} for (k,v) in d.items(): result[string.lower(k)] = v return result # Given references to two dictionaries, copy the second dictionary into the # first one. def DictMerge(destination, fresh_dict): for (key, value) in fresh_dict.items(): destination[key] = value class Table: def __init__(self, **table_opts): self.cells = [] self.cell_info = {} self.row_info = {} self.opts = table_opts def AddOptions(self, opts): DictMerge(self.opts, opts) # Sets all of the cells. It writes over whatever cells you had there # previously. def SetAllCells(self, cells): self.cells = cells # Add a new blank row at the end def NewRow(self): self.cells.append([]) # Add a new blank cell at the end def NewCell(self): self.cells[-1].append('') def AddRow(self, row): self.cells.append(row) def AddCell(self, cell): self.cells[-1].append(cell) def AddCellInfo(self, row, col, **kws): kws = CaseInsensitiveKeyedDict(kws) if not self.cell_info.has_key(row): self.cell_info[row] = { col : kws } elif self.cell_info[row].has_key(col): DictMerge(self.cell_info[row], kws) else: self.cell_info[row][col] = kws def AddRowInfo(self, row, **kws): kws = CaseInsensitiveKeyedDict(kws) if not self.row_info.has_key(row): self.row_info[row] = kws else: DictMerge(self.row_info[row], kws) # What's the index for the row we just put in? def GetCurrentRowIndex(self): return len(self.cells)-1 # What's the index for the col we just put in? def GetCurrentCellIndex(self): return len(self.cells[-1])-1 def ExtractCellInfo(self, info): valid_mods = ['align', 'valign', 'nowrap', 'rowspan', 'colspan', 'bgcolor'] output = '' for (key, val) in info.items(): if not key in valid_mods: continue if key == 'nowrap': output = output + ' NOWRAP' continue else: output = output + ' %s=%s' %(string.upper(key), val) return output def ExtractRowInfo(self, info): valid_mods = ['align', 'valign', 'bgcolor'] output = '' for (key, val) in info.items(): if not key in valid_mods: continue output = output + ' %s=%s' %(string.upper(key), val) return output def ExtractTableInfo(self, info): valid_mods = ['align', 'width', 'border', 'cellspacing', 'cellpadding', 'bgcolor'] output = '' for (key, val) in info.items(): if not key in valid_mods: continue if key == 'border' and val == None: output = output + ' BORDER' continue else: output = output + ' %s=%s' %(string.upper(key), val) return output def FormatCell(self, row, col, indent): try: my_info = self.cell_info[row][col] except: my_info = None output = '\n' + ' '*indent + '' for i in range(len(self.cells[row])): output = output + self.FormatCell(row, i, indent + 2) output = output + '\n' + ' '*indent + '' return output def Format(self, indent=0): output = '\n' + ' '*indent + '' for i in range(len(self.cells)): output = output + self.FormatRow(i, indent + 2) output = output + '\n' + ' '*indent + '\n' return output class Link: def __init__(self, href, text, target=None): self.href = href self.text = text self.target = target def Format(self, indent=0): texpr = "" if self.target != None: texpr = ' target="%s"' % self.target return '%s' % (HTMLFormatObject(self.href, indent), texpr, HTMLFormatObject(self.text, indent)) class FontSize: """FontSize is being deprecated - use FontAttr(..., size="...") instead.""" def __init__(self, size, *items): self.items = list(items) self.size = size def Format(self, indent=0): output = '' % self.size for item in self.items: output = output + HTMLFormatObject(item, indent) output = output + '' return output class FontAttr: """Present arbitrary font attributes.""" def __init__(self, *items, **kw): self.items = list(items) self.attrs = kw def Format(self, indent=0): seq = [] for k, v in self.attrs.items(): seq.append('%s="%s"' % (k, v)) output = '' % string.join(seq, ' ') for item in self.items: output = output + HTMLFormatObject(item, indent) output = output + '' return output class Container: def __init__(self, *items): if not items: self.items = [] else: self.items = items def AddItem(self, obj): self.items.append(obj) def Format(self, indent=0): output = [] for item in self.items: output.append(HTMLFormatObject(item, indent)) return string.join(output, '') # My own standard document template. YMMV. # something more abstract would be more work to use... class Document(Container): title = None def SetTitle(self, title): self.title = title def Format(self, indent=0, **kw): output = 'Content-type: text/html\n\n' spaces = ' ' * indent output = output + spaces output = output + '\n\n' if self.title: output = '%s%s%s\n' % (output, spaces, self.title) output = '%s%s\n%s\n' % string.join(quals, " ") else: output = output + '>\n' output = output + Container.Format(self, indent) output = output + '%s\n' % spaces return output class HeadlessDocument(Document): """Document without head section, for templates that provide their own.""" def Format(self, indent=0, **kw): output = 'Content-type: text/html\n\n' spaces = ' ' * indent output = output + spaces output = output + Container.Format(self, indent) return output class StdContainer(Container): def Format(self, indent=0): # If I don't start a new I ignore indent output = '<%s>' % self.tag output = output + Container.Format(self, indent) output = '%s' % (output, self.tag) return output class QuotedContainer(Container): def Format(self, indent=0): # If I don't start a new I ignore indent output = '<%s>%s' % ( self.tag, Utils.QuoteHyperChars(Container.Format(self, indent)), self.tag) return output class Header(StdContainer): def __init__(self, num, *items): self.items = items self.tag = 'h%d' % num class Address(StdContainer): tag = 'address' class Underline(StdContainer): tag = 'u' class Bold(StdContainer): tag = 'strong' class Italic(StdContainer): tag = 'em' class Preformatted(QuotedContainer): tag = 'pre' class Subscript(StdContainer): tag = 'sub' class Superscript(StdContainer): tag = 'sup' class Strikeout(StdContainer): tag = 'strike' class Center(StdContainer): tag = 'center' class Form(Container): def __init__(self, action='', method='POST', encoding=None, *items): apply(Container.__init__, (self,) + items) self.action = action self.method = method self.encoding = encoding def Format(self, indent=0): spaces = ' ' * indent encoding = '' if self.encoding: encoding = 'enctype="%s"' % self.encoding output = '\n%s
\n' % ( spaces, self.action, self.method, encoding) output = output + Container.Format(self, indent+2) output = '%s\n%s
\n' % (output, spaces) return output class InputObj: def __init__(self, name, ty, value, checked, **kws): self.name = name self.type = ty self.value = value self.checked = checked self.kws = kws def Format(self, indent=0): output = ['') return string.join(output, ' ') class SubmitButton(InputObj): def __init__(self, name, button_text): InputObj.__init__(self, name, "SUBMIT", button_text, checked=0) class PasswordBox(InputObj): def __init__(self, name): InputObj.__init__(self, name, "PASSWORD", '', checked=0) class TextBox(InputObj): def __init__(self, name, value='', size=10): InputObj.__init__(self, name, "TEXT", value, checked=0, size=size) class TextArea: def __init__(self, name, text='', rows=None, cols=None, wrap='soft'): self.name = name self.text = text self.rows = rows self.cols = cols self.wrap = wrap def Format(self, indent=0): output = '' % self.text return output class FileUpload(InputObj): def __init__(self, name, rows=None, cols=None, **kws): apply(InputObj.__init__, (self, name, 'FILE', '', 0), kws) class RadioButton(InputObj): def __init__(self, name, value, checked=0, **kws): apply(InputObj.__init__, (self, name, 'RADIO', value, checked), kws) class CheckBox(InputObj): def __init__(self, name, value, checked=0, **kws): apply(InputObj.__init__, (self, name, "CHECKBOX", value, checked), kws) class VerticalSpacer: def __init__(self, size=10): self.size = size def Format(self, indent=0): output = '' % self.size return output class RadioButtonArray: def __init__(self, name, button_names, checked=None, horizontal=1): # XXX: horizontal doesn't really work, and besides there's no good way # to ask for this in the GetConfigOptions(). -baw self.button_names = button_names self.horizontal = horizontal self.name = name self.checked = checked self.horizontal = horizontal def Format(self, indent=0): t = Table(cellspacing=5) items = [] l = len(self.button_names) for i in range(l): if self.checked == i: items.append(RadioButton(self.name, i, 1)) items.append(self.button_names[i]) else: items.append(RadioButton(self.name, i)) items.append(self.button_names[i]) if self.horizontal: t.AddRow(items) else: for item in items: t.AddRow([item]) return t.Format(indent) class UnorderedList(Container): def Format(self, indent=0): spaces = ' ' * indent output = '\n%s
    \n' % spaces for item in self.items: output = output + '%s
  • %s\n' % \ (spaces, HTMLFormatObject(item, indent + 2)) output = output + '%s
\n' % spaces return output class OrderedList(Container): def Format(self, indent=0): spaces = ' ' * indent output = '\n%s
    \n' % spaces for item in self.items: output = output + '%s
  1. %s\n' % \ (spaces, HTMLFormatObject(item, indent + 2)) output = output + '%s
\n' % spaces return output class DefinitionList(Container): def Format(self, indent=0): spaces = ' ' * indent output = '\n%s
\n' % spaces for dt, dd in self.items: output = output + '%s
%s\n
%s\n' % \ (spaces, HTMLFormatObject(dt, indent+2), HTMLFormatObject(dd, indent+2)) output = output + '%s
\n' % spaces return output # Logo constants # # These are the URLs which the image logos link to. The Mailman home page now # points at the gnu.org site instead of the www.list.org mirror. # from mm_cfg import MAILMAN_URL PYTHON_URL = 'http://www.python.org/' GNU_URL = 'http://www.gnu.org/' # The names of the image logo files. These are concatentated onto # mm_cfg.IMAGE_LOGOS (not urljoined). DELIVERED_BY = 'mailman.jpg' PYTHON_POWERED = 'PythonPowered.png' GNU_HEAD = 'gnu-head-tiny.jpg' def MailmanLogo(): t = Table(border=0, width='100%') if mm_cfg.IMAGE_LOGOS: def logo(file): return mm_cfg.IMAGE_LOGOS + file mmlink = Link(MAILMAN_URL, 'Delivered by Mailman' '
version %s' % (logo(DELIVERED_BY), mm_cfg.VERSION)) pylink = Link(PYTHON_URL, 'Python Powered' % logo(PYTHON_POWERED)) gnulink = Link(GNU_URL, 'GNU\'s Not Unix' % logo(GNU_HEAD)) text = Container(Link(MAILMAN_URL, 'Mailman home page'), '
', Link(PYTHON_URL, 'Python home page'), '
', Link(GNU_URL, 'GNU home page'), ) t.AddRow([mmlink, pylink, gnulink, text]) else: # use only textual links mmlink = Link(MAILMAN_URL, 'Delivered by Mailman
version %s' % mm_cfg.VERSION) pylink = Link(PYTHON_URL, 'Python Powered') gnulink = Link(GNU_URL, "Gnu's Not Unix") t.AddRow([mmlink, pylink, gnulink]) return t