I've written a slightly cleaner Nevow implementation. New features: -- each tag has a 'children' attribute, which is a list of child elements. (As before, tag attributes are contained in a dictionary attribute called 'attribs'.) This makes for cleaner tree-traversal code. # example: Remove empty items from a list for item in mylist.children[:]: if len(item.children) == 0: mylist.children.remove(item) Oscar, since you're interested in tree traversal, could you play with the code, and perhaps improve upon its 'traversal interface'? -- I added a primitive validation mechanism. Example code: >>> from nevow import Tag >>> ul = Tag('ul', contains=['li']) # may only contain LI elements >>> ul.contains = ['li'] # another way to do it. >>> li = Tag('li') # no 'contains' --> no validation >>> p = Tag('p') >>> mylist = ul[li['hello']] # OK: LI inside UL >>> myitem = li[p['bonjour']] # OK: no validation on LI children >>> badlist = ul[p['invalid']] # P in UL not allowed! Traceback: .... nevow.ValidationError:
Note that the HTML tags that I've created in the nevow module do *not* have 'contains' rules; therefore they do no validation. By default, tags will auto-validate their contents when they are initialized, and when they are copied via a __getitem__ call. This behaviour can be turned off by setting the attribute auto_validate=0 on the prototype, or by clearing the contains list of the prototype. See the code for more detail. You can also validate explicitly by calling tag.validate(recurse=bool), where bool defaults to 0 (false). The current validate() method asserts that 'child.name in self.contains' is true for all children, and calls child.validate() if recursion is specified. However, if self.contains is an empty list (the default), then no validation is done, even on children. Note that not all children are Tag objects: some are TextNodes and others may be CallableNodes (lazy-evaluated functions). Neither of these are validated; it's always assumed that text (and callable output) are valid. We might want an expand() method (like a macro expansion) to expand callables in order to validate their output before rendering. I don't claim that this is a great validation scheme, I just hope to get a taste of what one might be like. Andrew, was this kind of what you had in mind w.r.t. validation? -- Graham
""" nevow.py: Basic Nevow-style tags for Quixote (or any other purpose). Nevow is the next generation templating system for Twisted.Web. See http://stewstuff.com/doc/nevow.xhtml for details. This is a clean-room, toy implementation. Not sure about the real Nevow, but this code uses a prototype-based approach. All tag "classes" are really objects, and return modified copies of themselves under certain conditions. Graham Fawcett 2004.01.14 -- first revision. 2004.01.19 -- added 'children' attribute, for tree manips. """ from quixote.html import htmltext class ValidationError(Exception): pass class Tag: def __init__(self, name, children=None, attribs=None, contains=None, auto_validate=1): """ Nevow-like tag objects. name: tag name (e.g. 'html', 'div', ...) children: list of child elements """ self.name = name self.children = children and children or [] self.attribs = attribs and attribs or {} self.contains = contains and contains or [] for element in self.contains: assert isinstance(element, str), \ 'contains attribute must contain string values!' self.auto_validate = auto_validate if auto_validate: self.validate(recurse=0) def __call__(self, *args, **kwargs): if len(args): kwargs.update(args[0]) d = self.attribs.copy() d.update(kwargs) dup = Tag(self.name, None, attribs=d) return dup def __getitem__(self, content): if not isinstance(content, tuple): content = (content,) children = [wrap(child) for child in content] return Tag(self.name, children, attribs=self.attribs, contains=self.contains, auto_validate=self.auto_validate) def validate(self, recurse=1): if self.contains: for child in self.children: if isinstance(child, Tag): if not child.name in self.contains: raise ValidationError, \ '<%s> may not contain <%s>' % (self.name, child.name) if recurse: child.validate(recurse) def render(self): x = [] x.append('<%s' % self.name) if self.attribs: for k, v in self.attribs.items(): x.append(' %s="%s"' % (k, v)) if self.children: x.append('>') for child in self.children: x.append(child.render()) x.append('%s>' % self.name) else: x.append(' />') return ''.join(x) def __repr__(self): return '<%s:%s>' % (self.__class__.__name__, self.name) def __str__(self): return self.render() def htmltext(self): return htmltext(str(self)) class TextNode: def __init__(self, data): self.data = str(data) def render(self): return self.data class CallableNode: def __init__(self, func): self.func = func def render(self): return wrap(self.func()).render() def wrap(element): if isinstance(element, Tag): #print 'wrap returns %s' % children.name return element elif callable(element): return CallableNode(element) elif type(element) in (tuple, list): print 'warning' return [wrap(sub) for sub in element] else: return TextNode(element) #---------------------------------------------------------- # some basic HTML 4 tag names. extend as you please. tagnames = ( 'html head meta script title style link body p h1 h2 h3 h4 h5 h6 ' 'div table tr td tbody ol ul li a hr img object embed ' ) for tagname in tagnames.split(' '): if tagname: cmd = '%s = Tag("%s")' % (tagname, tagname) exec cmd