#! /usr/bin/python from xml.dom.minidom import parse, parseString from xml.dom import Node import copy import sys import types class Plugin: ''' a base class for plugin components which the template can use to process content ''' def process(self, dom, node): pass class PluginAttributes(Plugin): ''' a plugin which supports the setting of attributes against an element. ''' def __init__(self, **kwargs): self.kwargs = kwargs def process(self, dom, node): try: # for each key/val set the attribute accordingly # note that the attribute must already exist in the dom for key, val in self.kwargs.items(): node.attributes[key] = val except TypeError, te: sys.stderr.write('error adding %s = %s\n%s\n' % (key,val,str(node.attributes))) def __str__(self): return 'pluginattributes[%s]' % str(self.kwargs) class PluginAttributesValue(PluginAttributes): ''' a plugin which supports attributes and value content for example:

some content

''' def __init__(self, content, **kwargs): PluginAttributes.__init__(self, **kwargs) self.content = content def process(self, dom, node): PluginAttributes.process(self, dom, node) if node.nodeType == Node.TEXT_NODE: node.nodeValue = self.content elif node.firstChild: node.firstChild.nodeValue = self.content else: node.appendChild(dom.createTextNode(self.content)) def __str__(self): return 'pluginattributesvalue[%s, %s]' % (self.content, str(self.kwargs)) def get(content=None, **kwargs): ''' method to return PluginAttributes or PluginAttributesValue according to parameters ''' if content: return PluginAttributesValue(content, **kwargs) else: return PluginAttributes(**kwargs) class Template(dict): ''' a template containing the xhtml dom and a map of objects to apply to it ''' def __init__(self, filename): ''' parse the xhtml file ''' self.dom = parse(filename) def __str__(self): ''' convert dom back to xhtml ''' self.process(self.dom.documentElement) return self.dom.toxml() def __incr_id(self, node, i): ''' if an id is re-used by a list, we need to make sure it's still unique, so this method appends a counter value to the end of the id ''' if node.attributes and node.attributes.has_key('id'): node.attributes['id'].nodeValue = '%s:%s' % (node.attributes['id'].nodeValue, i) def __process(self, node, content): ''' process content against a specified node ''' # if the content is a template, remove all the child # nodes and then append the root node (and therefore # all its children) of the template if isinstance(content, Template): if node.hasChildNodes(): for n in node.childNodes: node.removeChild(n) n.unlink() node.appendChild(content.dom.documentElement) # if the content is a list, loop through the list # and reprocess either the node, or the child nodes elif type(content) == types.ListType: parent = node.parentNode parent.removeChild(node) idattr = node.attributes['id'].nodeValue i = 0 for elem in content: n = node.cloneNode(True) sys.stderr.write('processing list elem %s, node %s\n' % (elem, n)) i = i + 1 self.__incr_id(n, i) parent.appendChild(n) if n.hasChildNodes() and n.childNodes.item(0).nodeType != Node.TEXT_NODE: for cn in n.childNodes: sys.stderr.write('processing child node %s %s\n' % (cn, elem)) self.__incr_id(cn, i) self.__process(cn, elem) else: self.__process(n, elem) node.unlink() # if the content is a plugin, call the plugin to process the node elif isinstance(content, Plugin): content.process(self.dom, node) # for a string, set the node value to the string elif type(content) == types.StringType: node.firstChild.nodeValue = content def process(self, node): ''' process the template, applying content in the map to the dom ''' idattr = None # ignore text nodes if node.nodeType == Node.TEXT_NODE: return # if the node has the id attribute, get it if node.hasAttributes() and node.attributes.has_key('id'): idattr = node.attributes['id'].nodeValue # if we have that id in our content, call the __process method if idattr and self.has_key(idattr): content = self[idattr] self.__process(node, content) # process all the children of this node for n in node.childNodes: self.process(n) class Engine: ''' the template factory -- use this to get access to templates ''' def __init__(self): self.templates = { } def load(self, filename): ''' create a template if it hasn't already been created, but return a 'deep copy' instead of the original ''' if not self.templates.has_key(filename): self.templates[filename] = Template(filename) return copy.deepcopy(self.templates[filename])