# HG changeset patch # User cmlenz # Date 1154899341 0 # Node ID 636e0100fcaf74e06b28358b0a9b4618eff9b11b # Parent df44110ca91dc91f8e91503bf79899d1fafca6f0 Minor performance improvements in serialization. diff --git a/examples/bench/basic.py b/examples/bench/basic.py --- a/examples/bench/basic.py +++ b/examples/bench/basic.py @@ -13,7 +13,7 @@ def render(): ctxt = Context(title='Just a test', user='joe', items=['Number %d' % num for num in range(1, 15)]) - return template.generate(ctxt).render('html') + return template.generate(ctxt).render('xhtml') if verbose: print render() diff --git a/markup/core.py b/markup/core.py --- a/markup/core.py +++ b/markup/core.py @@ -292,7 +292,7 @@ def __new__(cls, text='', *args): if args: - text %= tuple([escape(arg) for arg in args]) + text %= tuple(map(escape, args)) return unicode.__new__(cls, text) def __add__(self, other): @@ -301,8 +301,7 @@ def __mod__(self, args): if not isinstance(args, (list, tuple)): args = [args] - return Markup(unicode.__mod__(self, - tuple([escape(arg) for arg in args]))) + return Markup(unicode.__mod__(self, tuple(map(escape, args)))) def __mul__(self, num): return Markup(unicode(self) * num) @@ -463,11 +462,9 @@ parts = qname.split(u'}', 1) if len(parts) > 1: - self = unicode.__new__(cls, u'{' + qname) - self.namespace = unicode(parts[0]) - self.localname = unicode(parts[1]) + self = unicode.__new__(cls, u'{%s' % qname) + self.namespace, self.localname = map(unicode, parts) else: self = unicode.__new__(cls, qname) - self.namespace = None - self.localname = unicode(qname) + self.namespace, self.localname = None, unicode(qname) return self diff --git a/markup/output.py b/markup/output.py --- a/markup/output.py +++ b/markup/output.py @@ -80,6 +80,7 @@ for filter_ in self.filters: stream = filter_(stream) stream = _PushbackIterator(stream) + pushback = stream.pushback for kind, data, pos in stream: if kind is START: @@ -94,7 +95,7 @@ tagname = '%s:%s' % (prefix, tagname) else: ns_attrib.append((QName('xmlns'), namespace)) - buf = ['<%s' % tagname] + buf = ['<', tagname] for attr, value in attrib + ns_attrib: attrname = attr.localname @@ -102,15 +103,15 @@ prefix = ns_mapping.get(attr.namespace) if prefix: attrname = '%s:%s' % (prefix, attrname) - buf.append(' %s="%s"' % (attrname, escape(value))) + buf += [' ', attrname, '="', escape(value), '"'] ns_attrib = [] kind, data, pos = stream.next() if kind is END: - buf.append('/>') + buf += ['/>'] else: - buf.append('>') - stream.pushback((kind, data, pos)) + buf += ['>'] + pushback((kind, data, pos)) yield Markup(''.join(buf)) @@ -129,19 +130,18 @@ elif kind is COMMENT: yield Markup('' % data) - elif kind is DOCTYPE: - if not have_doctype: - name, pubid, sysid = data - buf = ['\n') - yield Markup(''.join(buf), *filter(None, data)) - have_doctype = True + elif kind is DOCTYPE and not have_doctype: + name, pubid, sysid = data + buf = ['\n'] + yield Markup(''.join(buf), *filter(None, data)) + have_doctype = True elif kind is START_NS: prefix, uri = data @@ -176,47 +176,50 @@ _PRESERVE_SPACE = frozenset([QName('pre'), QName('textarea')]) def __call__(self, stream): + namespace = self.NAMESPACE + ns_mapping = {} + boolean_attrs = self._BOOLEAN_ATTRS + empty_elems = self._EMPTY_ELEMS have_doctype = False - ns_mapping = {} stream = chain(self.preamble, stream) for filter_ in self.filters: stream = filter_(stream) stream = _PushbackIterator(stream) + pushback = stream.pushback for kind, data, pos in stream: if kind is START: tag, attrib = data - if tag.namespace and tag not in self.NAMESPACE: - continue # not in the HTML namespace, so don't emit - buf = ['<', tag.localname] + if not tag.namespace or tag in namespace: + tagname = tag.localname + buf = ['<', tagname] - for attr, value in attrib: - if attr.namespace and attr not in self.NAMESPACE: - continue # not in the HTML namespace, so don't emit - if attr.localname in self._BOOLEAN_ATTRS: - if value: - buf.append(' %s="%s"' % (attr.localname, attr.localname)) + for attr, value in attrib: + if not attr.namespace or attr in namespace: + attrname = attr.localname + if attrname in boolean_attrs: + if value: + buf += [' ', attrname, '="', attrname, '"'] + else: + buf += [' ', attrname, '="', escape(value), '"'] + + if tagname in empty_elems: + kind, data, pos = stream.next() + if kind is END: + buf += [' />'] + else: + buf += ['>'] + pushback((kind, data, pos)) else: - buf.append(' %s="%s"' % (attr.localname, escape(value))) + buf += ['>'] - if tag.localname in self._EMPTY_ELEMS: - kind, data, pos = stream.next() - if kind is END: - buf.append(' />') - else: - buf.append('>') - stream.pushback((kind, data, pos)) - else: - buf.append('>') - - yield Markup(''.join(buf)) + yield Markup(''.join(buf)) elif kind is END: tag = data - if tag.namespace and tag not in self.NAMESPACE: - continue # not in the HTML namespace, so don't emit - yield Markup('' % tag.localname) + if not tag.namespace or tag in namespace: + yield Markup('' % tag.localname) elif kind is TEXT: yield escape(data, quotes=False) @@ -224,24 +227,21 @@ elif kind is COMMENT: yield Markup('' % data) - elif kind is DOCTYPE: - if not have_doctype: - name, pubid, sysid = data - buf = ['\n') - yield Markup(''.join(buf), *filter(None, data)) - have_doctype = True + elif kind is DOCTYPE and not have_doctype: + name, pubid, sysid = data + buf = ['\n'] + yield Markup(''.join(buf), *filter(None, data)) + have_doctype = True - elif kind is START_NS: - prefix, uri = data - if uri not in ns_mapping: - ns_mapping[uri] = prefix + elif kind is START_NS and data[1] not in ns_mapping: + ns_mapping[data[1]] = data[0] elif kind is PI: yield Markup('' % data) @@ -257,8 +257,11 @@ """ def __call__(self, stream): + namespace = self.NAMESPACE + ns_mapping = {} + boolean_attrs = self._BOOLEAN_ATTRS + empty_elems = self._EMPTY_ELEMS have_doctype = False - ns_mapping = {} stream = chain(self.preamble, stream) for filter_ in self.filters: @@ -268,32 +271,33 @@ if kind is START: tag, attrib = data - if tag.namespace and tag not in self.NAMESPACE: - continue # not in the HTML namespace, so don't emit - buf = ['<', tag.localname] + if not tag.namespace or tag in namespace: + tagname = tag.localname + buf = ['<', tagname] - for attr, value in attrib: - if attr.namespace and attr not in self.NAMESPACE \ - or attr.localname.startswith('xml:'): - continue # not in the HTML namespace, so don't emit - if attr.localname in self._BOOLEAN_ATTRS: - if value: - buf.append(' %s' % attr.localname) - else: - buf.append(' %s="%s"' % (attr.localname, escape(value))) + for attr, value in attrib: + attrname = attr.localname + if not attr.namespace and not \ + attrname.startswith('xml:') or \ + attr in namespace: + if attrname in boolean_attrs: + if value: + buf += [' ', attrname] + else: + buf += [' ', attrname, '="', escape(value), '"'] - if tag.localname in self._EMPTY_ELEMS: - kind, data, pos = stream.next() - if kind is not END: - stream.pushback((kind, data, pos)) + if tagname in empty_elems: + kind, data, pos = stream.next() + if kind is not END: + stream.pushback((kind, data, pos)) - yield Markup(''.join(buf + ['>'])) + buf += ['>'] + yield Markup(''.join(buf)) elif kind is END: tag = data - if tag.namespace and tag not in self.NAMESPACE: - continue # not in the HTML namespace, so don't emit - yield Markup('' % tag.localname) + if not tag.namespace or tag in namespace: + yield Markup('' % tag.localname) elif kind is TEXT: yield escape(data, quotes=False) @@ -301,24 +305,21 @@ elif kind is COMMENT: yield Markup('' % data) - elif kind is DOCTYPE: - if not have_doctype: - name, pubid, sysid = data - buf = ['\n') - yield Markup(''.join(buf), *filter(None, data)) - have_doctype = True + elif kind is DOCTYPE and not have_doctype: + name, pubid, sysid = data + buf = ['\n'] + yield Markup(''.join(buf), *filter(None, data)) + have_doctype = True - elif kind is START_NS: - prefix, uri = data - if uri not in ns_mapping: - ns_mapping[uri] = prefix + elif kind is START_NS and data[1] not in ns_mapping: + ns_mapping[data[1]] = data[0] elif kind is PI: yield Markup('' % data) @@ -346,27 +347,31 @@ collapse_lines = self._LINE_COLLAPSE.sub mjoin = Markup('').join preserve = [False] + append_preserve = preserve.append + pop_preserve = preserve.pop textbuf = [] + append_text = textbuf.append + pop_text = textbuf.pop for kind, data, pos in chain(stream, [(None, None, None)]): if kind is TEXT: - textbuf.append(data) + append_text(data) else: if kind is START: - preserve.append(data[0] in self.preserve or + append_preserve(data[0] in self.preserve or data[1].get('xml:space') == 'preserve') if textbuf: if len(textbuf) > 1: text = mjoin(textbuf, escape_quotes=False) del textbuf[:] else: - text = escape(textbuf.pop(), quotes=False) + text = escape(pop_text(), quotes=False) if not preserve[-1]: text = collapse_lines('\n', trim_trailing_space('', text)) yield TEXT, Markup(text), pos if kind is END: - preserve.pop() - if kind is not None: + pop_preserve() + if kind: yield kind, data, pos