# Copyright (C) 1998-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # 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. """ from Mailman import Defaults from Mailman import Utils from Mailman import Version from Mailman.configuration import config from Mailman.i18n import _ SPACE = ' ' EMPTYSTRING = '' NL = '\n' # Format an arbitrary object. def HTMLFormatObject(item, indent): "Return a presentation of an object, invoking their Format method if any." if hasattr(item, 'Format'): return item.Format(indent) if isinstance(item, basestring): return item return str(item) def CaseInsensitiveKeyedDict(d): result = {} for (k,v) in d.items(): result[k.lower()] = 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"' % (key.upper(), 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"' % (key.upper(), 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"' % (key.upper(), 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 = '' % SPACE.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 EMPTYSTRING.join(output) class Label(Container): align = 'right' def __init__(self, *items): Container.__init__(self, *items) def Format(self, indent=0): return ('
' % self.align) + \ Container.Format(self, indent) + \ '
' # My own standard document template. YMMV. # something more abstract would be more work to use... class Document(Container): title = None language = None bgcolor = Defaults.WEB_BG_COLOR suppress_head = 0 def set_language(self, lang=None): self.language = lang def set_bgcolor(self, color): self.bgcolor = color def SetTitle(self, title): self.title = title def Format(self, indent=0, **kws): charset = 'us-ascii' if self.language: charset = Utils.GetCharSet(self.language) output = ['Content-Type: text/html; charset=%s\n' % charset] if not self.suppress_head: kws.setdefault('bgcolor', self.bgcolor) tab = ' ' * indent output.extend([tab, '', '' ]) if config.IMAGE_LOGOS: output.append('' % (config.IMAGE_LOGOS + config.SHORTCUT_ICON)) # Hit all the bases output.append('' % charset) if self.title: output.append('%s%s' % (tab, self.title)) output.append('%s' % tab) quals = [] # Default link colors if config.WEB_VLINK_COLOR: kws.setdefault('vlink', config.WEB_VLINK_COLOR) if config.WEB_ALINK_COLOR: kws.setdefault('alink', config.WEB_ALINK_COLOR) if config.WEB_LINK_COLOR: kws.setdefault('link', config.WEB_LINK_COLOR) for k, v in kws.items(): quals.append('%s="%s"' % (k, v)) output.append('%s' % (tab, SPACE.join(quals))) # Always do this... output.append(Container.Format(self, indent)) if not self.suppress_head: output.append('%s' % tab) output.append('%s' % tab) return NL.join(output) def addError(self, errmsg, tag=None): if tag is None: tag = _('Error: ') self.AddItem(Header(3, Bold(FontAttr( _(tag), color=config.WEB_ERROR_COLOR, size='+2')).Format() + Italic(errmsg).Format())) class HeadlessDocument(Document): """Document without head section, for templates that provide their own.""" suppress_head = 1 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.websafe(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 set_action(self, action): self.action = action 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 SPACE.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, value='', size=Defaults.TEXTFIELDWIDTH): InputObj.__init__(self, name, "PASSWORD", value, checked=0, size=size) class TextBox(InputObj): def __init__(self, name, value='', size=Defaults.TEXTFIELDWIDTH): InputObj.__init__(self, name, "TEXT", value, checked=0, size=size) class Hidden(InputObj): def __init__(self, name, value=''): InputObj.__init__(self, name, 'HIDDEN', value, checked=0) class TextArea: def __init__(self, name, text='', rows=None, cols=None, wrap='soft', readonly=0): self.name = name self.text = text self.rows = rows self.cols = cols self.wrap = wrap self.readonly = readonly def Format(self, indent=0): output = '