# HG changeset patch # User cmlenz # Date 1123409036 0 # Node ID 16d69eb6e047b7fee72e4035b6e2f5cd525c85fa # Parent ecc062d4fd554c84d3ef2db893652d7001526574 Add support for XML fragments to the {{{xmlio}}} module, so that build output and reports don't need to be nested in a meaningless element (such as {{{}}}). diff --git a/bitten/build/ctools.py b/bitten/build/ctools.py --- a/bitten/build/ctools.py +++ b/bitten/build/ctools.py @@ -38,7 +38,7 @@ if target: args.append(target) - log_elem = xmlio.Element('messages') + log_elem = xmlio.Fragment() cmdline = Commandline('make', args) for out, err in cmdline.execute(timeout=100.0): if out: diff --git a/bitten/build/pythontools.py b/bitten/build/pythontools.py --- a/bitten/build/pythontools.py +++ b/bitten/build/pythontools.py @@ -30,7 +30,7 @@ def distutils(ctxt, command='build'): """Execute a `distutils` command.""" cmdline = Commandline('python', ['setup.py', command], cwd=ctxt.basedir) - log_elem = xmlio.Element('messages') + log_elem = xmlio.Fragment() for out, err in cmdline.execute(timeout=100.0): if out: log.info(out) diff --git a/bitten/master.py b/bitten/master.py --- a/bitten/master.py +++ b/bitten/master.py @@ -349,10 +349,9 @@ for log_elem in elem.children('log'): build_log = BuildLog(self.env, build=build.id, step=step.name, type=log_elem.attr.get('type')) - for messages_elem in log_elem.children('messages'): - for message_elem in messages_elem.children('message'): - build_log.messages.append((message_elem.attr['level'], - message_elem.gettext())) + for message_elem in log_elem.children('message'): + build_log.messages.append((message_elem.attr['level'], + message_elem.gettext())) build_log.insert(db=db) def _build_completed(self, db, build, elem): diff --git a/bitten/util/xmlio.py b/bitten/util/xmlio.py --- a/bitten/util/xmlio.py +++ b/bitten/util/xmlio.py @@ -27,8 +27,61 @@ __all__ = ['Element', 'parse'] +def _escape_text(text): + return str(text).replace('&', '&').replace('<', '<') \ + .replace('>', '>') -class Element(object): +def _escape_attr(attr): + return _escape_text(attr).replace('"', '"') + + +class Fragment(object): + """A collection of XML elements.""" + + __slots__ = ['children'] + + def __init__(self, *args, **attr): + """Create an XML fragment.""" + self.children = [] + + def __getitem__(self, nodes): + """Add nodes to the fragment.""" + if not isinstance(nodes, (list, tuple)): + nodes = [nodes] + for node in nodes: + self.append(node) + return self + + def __str__(self): + """Return a string representation of the XML fragment.""" + buf = StringIO() + self.write(buf) + return buf.getvalue() + + def append(self, node): + """Append an element or fragment as child.""" + if isinstance(node, Element): + self.children.append(node) + elif isinstance(node, Fragment): + self.children += node.children + elif node is not None and node != '': + self.children.append(node) + + def write(self, out, newlines=False): + """Serializes the element and writes the XML to the given output + stream. + """ + for child in self.children: + if isinstance(child, Element): + child.write(out, newlines=newlines) + else: + if child[0] == '<': + out.write('') + else: + out.write(_escape_text(child)) + + +class Element(Fragment): """Simple XML output generator based on the builder pattern. Construct XML elements by passing the tag name to the constructor: @@ -76,7 +129,7 @@ >>> print Element('foo')[''] ]]> """ - __slots__ = ['name', 'attr', 'children'] + __slots__ = ['name', 'attr'] def __init__(self, *args, **attr): """Create an XML element using the specified tag name. @@ -84,24 +137,10 @@ The tag name must be supplied as the first positional argument. All keyword arguments following it are handled as attributes of the element. """ + Fragment.__init__(self) self.name = args[0] self.attr = dict([(name, value) for name, value in attr.items() if value is not None]) - self.children = [] - - def __getitem__(self, children): - """Add child nodes to an element.""" - if not isinstance(children, (list, tuple)): - children = [children] - self.children = [child for child in children - if child is not None and child != ''] - return self - - def __str__(self): - """Return a string representation of the XML element.""" - buf = StringIO() - self.write(buf) - return buf.getvalue() def write(self, out, newlines=False): """Serializes the element and writes the XML to the given output @@ -110,36 +149,22 @@ out.write('<') out.write(self.name) for name, value in self.attr.items(): - out.write(' %s="%s"' % (name, self._escape_attr(value))) + out.write(' %s="%s"' % (name, _escape_attr(value))) if self.children: out.write('>') - for child in self.children: - if isinstance(child, Element): - child.write(out, newlines=newlines) - else: - if child[0] == '<': - out.write('') - else: - out.write(self._escape_text(child)) + Fragment.write(self, out, newlines) out.write('') else: out.write('/>') if newlines: out.write(os.linesep) - def _escape_text(self, text): - return str(text).replace('&', '&').replace('<', '<') \ - .replace('>', '>') - - def _escape_attr(self, attr): - return self._escape_text(attr).replace('"', '"') - class SubElement(Element): __slots__ = [] - def __init__(self, *args, **attr): + def __init__(self, parent, name, **attr): """Create an XML element using the specified tag name. The first positional argument is the instance of the parent element that @@ -147,9 +172,8 @@ the name of the tag. All keyword arguments are handled as attributes of the element. """ - assert len(args) == 2 - Element.__init__(self, args[1], **attr) - args[0].children.append(self) + Element.__init__(self, name, **attr) + parent.append(self) def parse(text):