aboutsummaryrefslogtreecommitdiffstats
path: root/Mailman/htmlformat.py
diff options
context:
space:
mode:
author <>2003-01-02 05:25:50 +0000
committer <>2003-01-02 05:25:50 +0000
commitb132a73f15e432eaf43310fce9196ca0c0651465 (patch)
treec15f816ba7c4de99fef510e3bd75af0890d47441 /Mailman/htmlformat.py
downloadmailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.gz
mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.xz
mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.zip
This commit was manufactured by cvs2svn to create branch
'Release_2_1-maint'.
Diffstat (limited to '')
-rw-r--r--Mailman/htmlformat.py678
1 files changed, 678 insertions, 0 deletions
diff --git a/Mailman/htmlformat.py b/Mailman/htmlformat.py
new file mode 100644
index 00000000..0175bccb
--- /dev/null
+++ b/Mailman/htmlformat.py
@@ -0,0 +1,678 @@
+# Copyright (C) 1998,1999,2000,2001,2002 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
+
+from Mailman import mm_cfg
+from Mailman import Utils
+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 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[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 + '<td'
+ if my_info:
+ output = output + self.ExtractCellInfo(my_info)
+ item = self.cells[row][col]
+ item_format = HTMLFormatObject(item, indent+4)
+ output = '%s>%s</td>' % (output, item_format)
+ return output
+
+ def FormatRow(self, row, indent):
+ try:
+ my_info = self.row_info[row]
+ except:
+ my_info = None
+
+ output = '\n' + ' '*indent + '<tr'
+ if my_info:
+ output = output + self.ExtractRowInfo(my_info)
+ output = output + '>'
+
+ for i in range(len(self.cells[row])):
+ output = output + self.FormatCell(row, i, indent + 2)
+
+ output = output + '\n' + ' '*indent + '</tr>'
+
+ return output
+
+ def Format(self, indent=0):
+ output = '\n' + ' '*indent + '<table'
+ output = output + self.ExtractTableInfo(self.opts)
+ output = output + '>'
+
+ for i in range(len(self.cells)):
+ output = output + self.FormatRow(i, indent + 2)
+
+ output = output + '\n' + ' '*indent + '</table>\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 '<a href="%s"%s>%s</a>' % (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 = '<font size="%s">' % self.size
+ for item in self.items:
+ output = output + HTMLFormatObject(item, indent)
+ output = output + '</font>'
+ 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 = '<font %s>' % SPACE.join(seq)
+ for item in self.items:
+ output = output + HTMLFormatObject(item, indent)
+ output = output + '</font>'
+ 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 ('<div align="%s">' % self.align) + \
+ Container.Format(self, indent) + \
+ '</div>'
+
+
+# My own standard document template. YMMV.
+# something more abstract would be more work to use...
+
+class Document(Container):
+ title = None
+ language = None
+ bgcolor = mm_cfg.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,
+ '<HTML>',
+ '<HEAD>'
+ ])
+ if mm_cfg.IMAGE_LOGOS:
+ output.append('<LINK REL="SHORTCUT ICON" HREF="%s">' %
+ (mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON))
+ # Hit all the bases
+ output.append('<META http-equiv="Content-Type" '
+ 'content="text/html; charset=%s">' % charset)
+ if self.title:
+ output.append('%s<TITLE>%s</TITLE>' % (tab, self.title))
+ output.append('%s</HEAD>' % tab)
+ quals = []
+ # Default link colors
+ if mm_cfg.WEB_VLINK_COLOR:
+ kws.setdefault('vlink', mm_cfg.WEB_VLINK_COLOR)
+ if mm_cfg.WEB_ALINK_COLOR:
+ kws.setdefault('alink', mm_cfg.WEB_ALINK_COLOR)
+ if mm_cfg.WEB_LINK_COLOR:
+ kws.setdefault('alink', mm_cfg.WEB_LINK_COLOR)
+ for k, v in kws.items():
+ quals.append('%s="%s"' % (k, v))
+ output.append('%s<BODY %s>' % (tab, SPACE.join(quals)))
+ # Always do this...
+ output.append(Container.Format(self, indent))
+ if not self.suppress_head:
+ output.append('%s</BODY>' % tab)
+ output.append('%s</HTML>' % tab)
+ return NL.join(output)
+
+ def addError(self, errmsg, tag=None, *args):
+ if tag is None:
+ tag = _('Error: ')
+ self.AddItem(Header(3, Bold(FontAttr(
+ _(tag), color=mm_cfg.WEB_ERROR_COLOR, size='+2')).Format() +
+ Italic(errmsg % args).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</%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</%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<FORM action="%s" method="%s" %s>\n' % (
+ spaces, self.action, self.method, encoding)
+ output = output + Container.Format(self, indent+2)
+ output = '%s\n%s</FORM>\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 = ['<INPUT name="%s" type="%s" value="%s"' %
+ (self.name, self.type, self.value)]
+ for item in self.kws.items():
+ output.append('%s="%s"' % item)
+ if self.checked:
+ output.append('CHECKED')
+ output.append('>')
+ 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=mm_cfg.TEXTFIELDWIDTH):
+ InputObj.__init__(self, name, "PASSWORD", value, checked=0, size=size)
+
+class TextBox(InputObj):
+ def __init__(self, name, value='', size=mm_cfg.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 = '<TEXTAREA NAME=%s' % self.name
+ if self.rows:
+ output += ' ROWS=%s' % self.rows
+ if self.cols:
+ output += ' COLS=%s' % self.cols
+ if self.wrap:
+ output += ' WRAP=%s' % self.wrap
+ if self.readonly:
+ output += ' READONLY'
+ output += '>%s</TEXTAREA>' % 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 = '<spacer type="vertical" height="%d">' % self.size
+ return output
+
+class WidgetArray:
+ Widget = None
+
+ def __init__(self, name, button_names, checked, horizontal, values):
+ self.name = name
+ self.button_names = button_names
+ self.checked = checked
+ self.horizontal = horizontal
+ self.values = values
+ assert len(values) == len(button_names)
+ # Don't assert `checked' because for RadioButtons it is a scalar while
+ # for CheckedBoxes it is a vector. Subclasses will assert length.
+
+ def ischecked(self, i):
+ raise NotImplemented
+
+ def Format(self, indent=0):
+ t = Table(cellspacing=5)
+ items = []
+ for i, name, value in zip(range(len(self.button_names)),
+ self.button_names,
+ self.values):
+ ischecked = (self.ischecked(i))
+ item = self.Widget(self.name, value, ischecked).Format() + name
+ items.append(item)
+ if not self.horizontal:
+ t.AddRow(items)
+ items = []
+ if self.horizontal:
+ t.AddRow(items)
+ return t.Format(indent)
+
+class RadioButtonArray(WidgetArray):
+ Widget = RadioButton
+
+ def __init__(self, name, button_names, checked=None, horizontal=1,
+ values=None):
+ if values is None:
+ values = range(len(button_names))
+ # BAW: assert checked is a scalar...
+ WidgetArray.__init__(self, name, button_names, checked, horizontal,
+ values)
+
+ def ischecked(self, i):
+ return self.checked == i
+
+class CheckBoxArray(WidgetArray):
+ Widget = CheckBox
+
+ def __init__(self, name, button_names, checked=None, horizontal=0,
+ values=None):
+ if checked is None:
+ checked = [0] * len(button_names)
+ else:
+ assert len(checked) == len(button_names)
+ if values is None:
+ values = range(len(button_names))
+ WidgetArray.__init__(self, name, button_names, checked, horizontal,
+ values)
+
+ def ischecked(self, i):
+ return self.checked[i]
+
+class UnorderedList(Container):
+ def Format(self, indent=0):
+ spaces = ' ' * indent
+ output = '\n%s<ul>\n' % spaces
+ for item in self.items:
+ output = output + '%s<li>%s\n' % \
+ (spaces, HTMLFormatObject(item, indent + 2))
+ output = output + '%s</ul>\n' % spaces
+ return output
+
+class OrderedList(Container):
+ def Format(self, indent=0):
+ spaces = ' ' * indent
+ output = '\n%s<ol>\n' % spaces
+ for item in self.items:
+ output = output + '%s<li>%s\n' % \
+ (spaces, HTMLFormatObject(item, indent + 2))
+ output = output + '%s</ol>\n' % spaces
+ return output
+
+class DefinitionList(Container):
+ def Format(self, indent=0):
+ spaces = ' ' * indent
+ output = '\n%s<dl>\n' % spaces
+ for dt, dd in self.items:
+ output = output + '%s<dt>%s\n<dd>%s\n' % \
+ (spaces, HTMLFormatObject(dt, indent+2),
+ HTMLFormatObject(dd, indent+2))
+ output = output + '%s</dl>\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 = '<img src="%s" alt="Delivered by Mailman" border=0>' \
+ '<br>version %s' % (logo(DELIVERED_BY), mm_cfg.VERSION)
+ pylink = '<img src="%s" alt="Python Powered" border=0>' % \
+ logo(PYTHON_POWERED)
+ gnulink = '<img src="%s" alt="GNU\'s Not Unix" border=0>' % \
+ logo(GNU_HEAD)
+ t.AddRow([mmlink, pylink, gnulink])
+ else:
+ # use only textual links
+ version = mm_cfg.VERSION
+ mmlink = Link(MAILMAN_URL,
+ _('Delivered by Mailman<br>version %(version)s'))
+ pylink = Link(PYTHON_URL, _('Python Powered'))
+ gnulink = Link(GNU_URL, _("Gnu's Not Unix"))
+ t.AddRow([mmlink, pylink, gnulink])
+ return t
+
+
+class SelectOptions:
+ def __init__(self, varname, values, legend,
+ selected=0, size=1, multiple=None):
+ self.varname = varname
+ self.values = values
+ self.legend = legend
+ self.size = size
+ self.multiple = multiple
+ # we convert any type to tuple, commas are needed
+ if not multiple:
+ if type(selected) == types.IntType:
+ self.selected = (selected,)
+ elif type(selected) == types.TupleType:
+ self.selected = (selected[0],)
+ elif type(selected) == types.ListType:
+ self.selected = (selected[0],)
+ else:
+ self.selected = (0,)
+
+ def Format(self, indent=0):
+ spaces = " " * indent
+ items = min( len(self.values), len(self.legend) )
+
+ # jcrey: If there is no argument, we return nothing to avoid errors
+ if items == 0:
+ return ""
+
+ text = "\n" + spaces + "<Select name=\"%s\"" % self.varname
+ if self.size > 1:
+ text = text + " size=%d" % self.size
+ if self.multiple:
+ text = text + " multiple"
+ text = text + ">\n"
+
+ for i in range(items):
+ if i in self.selected:
+ checked = " Selected"
+ else:
+ checked = ""
+
+ opt = " <option value=\"%s\"%s> %s </option>" % (
+ self.values[i], checked, self.legend[i])
+ text = text + spaces + opt + "\n"
+
+ return text + spaces + '</Select>'