This is an automated email from the ASF dual-hosted git repository.

astitcher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-proton.git


The following commit(s) were added to refs/heads/main by this push:
     new 7c08f80ed PROTON-2844: [Python tooling] Eliminate use of mllib
7c08f80ed is described below

commit 7c08f80ed69745a4504b0c3e6325a4ae6baf2b3c
Author: Andrew Stitcher <[email protected]>
AuthorDate: Fri Aug 2 11:52:07 2024 -0400

    PROTON-2844: [Python tooling] Eliminate use of mllib
    
    Python has good enough suppport for xml in the standard library
    so there is no longer any need to use a home built solution.
---
 c/src/encodings.h.py             |  21 +--
 c/src/protocol.h.py              |  14 +-
 c/src/protocol.py                |  48 +++---
 tools/python/mllib/__init__.py   |  76 ---------
 tools/python/mllib/dom.py        | 331 ---------------------------------------
 tools/python/mllib/parsers.py    | 105 -------------
 tools/python/mllib/transforms.py | 171 --------------------
 7 files changed, 40 insertions(+), 726 deletions(-)

diff --git a/c/src/encodings.h.py b/c/src/encodings.h.py
index f03f3a288..33e9e04b3 100644
--- a/c/src/encodings.h.py
+++ b/c/src/encodings.h.py
@@ -18,13 +18,13 @@
 # under the License.
 #
 
-import mllib
-import optparse
 import os
-import sys
+import xml.etree.ElementTree as ET
 
+ns = {'amqp': 'http://www.amqp.org/schema/amqp.xsd'}
 xml = os.path.join(os.path.dirname(__file__), "types.xml")
-doc = mllib.xml_parse(xml)
+doc = ET.parse(xml).getroot()
+
 
 print("/* generated from %s */" % xml)
 print("#ifndef _PROTON_ENCODINGS_H")
@@ -32,13 +32,14 @@ print("#define _PROTON_ENCODINGS_H 1")
 print()
 print("#define PNE_DESCRIPTOR          (0x00)")
 
-for enc in doc.query["amqp/section/type/encoding"]:
-    name = enc["@name"] or enc.parent["@name"]
-    # XXX: a bit hacky
-    if name == "ieee-754":
-        name = enc.parent["@name"]
+types = doc.findall('./amqp:section/amqp:type', ns)
+encodings = [(t.attrib['name'], e) for t in types for e in 
t.findall('./amqp:encoding', ns)]
+for parentname, enc in encodings:
+    name = enc.attrib.get("name")
+    if not name or name == "ieee-754":
+        name = parentname
     cname = "PNE_" + name.replace("-", "_").upper()
-    print("#define %s%s(%s)" % (cname, " " * (20 - len(cname)), enc["@code"]))
+    print("#define %s%s(%s)" % (cname, " " * (20 - len(cname)), 
enc.attrib["code"]))
 
 print()
 print("#endif /* encodings.h */")
diff --git a/c/src/protocol.h.py b/c/src/protocol.h.py
index a6a789e70..620d72917 100644
--- a/c/src/protocol.h.py
+++ b/c/src/protocol.h.py
@@ -30,10 +30,10 @@ fields = {}
 
 for type in TYPES:
     fidx = 0
-    for f in type.query["field"]:
+    for f in type.findall("amqp:field", ns):
         print("#define %s_%s (%s)" % (field_kw(type), field_kw(f), fidx))
         fidx += 1
-        d = f["@default"]
+        d = f.attrib.get("default")
         if d:
             ft = ftype(f)
             # Don't bother to emit a boolean default that is False
@@ -51,13 +51,13 @@ for type in TYPES:
 idx = 0
 
 for type in TYPES:
-    desc = type["descriptor"]
-    name = type["@name"].upper().replace("-", "_")
-    print("#define %s_SYM (\"%s\")" % (name, desc["@name"]))
-    hi, lo = [int(x, 0) for x in desc["@code"].split(":")]
+    desc = type.find("./amqp:descriptor", ns)
+    name = type.attrib["name"].upper().replace("-", "_")
+    print("#define %s_SYM (\"%s\")" % (name, desc.attrib["name"]))
+    hi, lo = [int(x, 0) for x in desc.attrib["code"].split(":")]
     code = (hi << 32) + lo
     print("#define %s ((uint64_t) %s)" % (name, code))
-    fields[code] = (type["@name"], [f["@name"] for f in type.query["field"]])
+    fields[code] = (type.attrib["name"], [f.attrib["name"] for f in 
type.findall("amqp:field", ns)])
     idx += 1
 
 print("""
diff --git a/c/src/protocol.py b/c/src/protocol.py
index b112b0060..da3773150 100644
--- a/c/src/protocol.py
+++ b/c/src/protocol.py
@@ -16,25 +16,21 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-import mllib
 import os
-import sys
+import xml.etree.ElementTree as ET
 
-doc = mllib.xml_parse(os.path.join(os.path.dirname(__file__), "transport.xml"))
-mdoc = mllib.xml_parse(os.path.join(os.path.dirname(__file__), 
"messaging.xml"))
-tdoc = mllib.xml_parse(os.path.join(os.path.dirname(__file__), 
"transactions.xml"))
-sdoc = mllib.xml_parse(os.path.join(os.path.dirname(__file__), "security.xml"))
+ns = {'amqp': 'http://www.amqp.org/schema/amqp.xsd'}
+doc = ET.parse(os.path.join(os.path.dirname(__file__), 
"transport.xml")).getroot()
+mdoc = ET.parse(os.path.join(os.path.dirname(__file__), 
"messaging.xml")).getroot()
+tdoc = ET.parse(os.path.join(os.path.dirname(__file__), 
"transactions.xml")).getroot()
+sdoc = ET.parse(os.path.join(os.path.dirname(__file__), 
"security.xml")).getroot()
 
 
-def eq(attr, value):
-    return lambda nd: nd[attr] == value
-
-
-TYPEStmp = doc.query["amqp/section/type", eq("@class", "composite")] + \
-    mdoc.query["amqp/section/type", eq("@class", "composite")] + \
-    tdoc.query["amqp/section/type", eq("@class", "composite")] + \
-    sdoc.query["amqp/section/type", eq("@class", "composite")] + \
-    mdoc.query["amqp/section/type", eq("@provides", "section")]
+TYPEStmp = doc.findall("./amqp:section/amqp:type/[@class='composite']", ns) + \
+    mdoc.findall("./amqp:section/amqp:type/[@class='composite']", ns) + \
+    tdoc.findall("./amqp:section/amqp:type/[@class='composite']", ns) + \
+    sdoc.findall("./amqp:section/amqp:type/[@class='composite']", ns) + \
+    mdoc.findall("./amqp:section/amqp:type/[@provides='section']", ns)
 TYPES = []
 for ty in TYPEStmp:
     if ty not in TYPES:
@@ -42,14 +38,14 @@ for ty in TYPEStmp:
 RESTRICTIONS = {}
 COMPOSITES = {}
 
-for type in doc.query["amqp/section/type"] + mdoc.query["amqp/section/type"] + 
\
-        sdoc.query["amqp/section/type"] + tdoc.query["amqp/section/type"]:
+for type in doc.findall("./amqp:section/amqp:type", ns) + 
mdoc.findall("./amqp:section/amqp:type", ns) + \
+        sdoc.findall("./amqp:section/amqp:type", ns) + 
tdoc.findall("./amqp:section/amqp:type", ns):
 
-    source = type["@source"]
+    source = type.attrib["source"]
     if source:
-        RESTRICTIONS[type["@name"]] = source
-    if type["@class"] == "composite":
-        COMPOSITES[type["@name"]] = type
+        RESTRICTIONS[type.attrib["name"]] = source
+    if type.attrib["class"] == "composite":
+        COMPOSITES[type.attrib["name"]] = type
 
 
 def resolve(name):
@@ -90,24 +86,24 @@ NULLABLE = set(["string", "symbol"])
 
 
 def fname(field):
-    return field["@name"].replace("-", "_")
+    return field.attrib["name"].replace("-", "_")
 
 
 def tname(t):
-    return t["@name"].replace("-", "_")
+    return t.attrib["name"].replace("-", "_")
 
 
 def multi(f):
-    return f["@multiple"] == "true"
+    return f.attrib.get("multiple") == "true"
 
 
 def ftype(field):
     if multi(field):
         return "list"
-    elif field["@type"] in COMPOSITES:
+    elif field.attrib["type"] in COMPOSITES:
         return "box"
     else:
-        return resolve(field["@type"]).replace("-", "_")
+        return resolve(field.attrib["type"]).replace("-", "_")
 
 
 def fconstruct(field, expr):
diff --git a/tools/python/mllib/__init__.py b/tools/python/mllib/__init__.py
deleted file mode 100644
index 20daa3f9f..000000000
--- a/tools/python/mllib/__init__.py
+++ /dev/null
@@ -1,76 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-"""
-This module provides document parsing and transformation utilities for XML.
-"""
-
-import os
-import sys
-import xml.sax
-import types
-from xml.sax.handler import ErrorHandler
-from xml.sax.xmlreader import InputSource
-
-from io import StringIO
-
-from . import dom
-from . import transforms
-from . import parsers
-
-CLASS_TYPES = (type,)
-
-def transform(node, *args):
-    result = node
-    for t in args:
-        if isinstance(t, CLASS_TYPES):
-            t = t()
-        result = result.dispatch(t)
-    return result
-
-
-class Resolver:
-
-    def __init__(self, path):
-        self.path = path
-
-    def resolveEntity(self, publicId, systemId):
-        for p in self.path:
-            fname = os.path.join(p, systemId)
-            if os.path.exists(fname):
-                source = InputSource(systemId)
-                source.setByteStream(open(fname))
-                return source
-        return InputSource(systemId)
-
-
-def xml_parse(filename, path=()):
-    h = parsers.XMLParser()
-    p = xml.sax.make_parser()
-    p.setContentHandler(h)
-    p.setErrorHandler(ErrorHandler())
-    p.setEntityResolver(Resolver(path))
-    p.parse(filename)
-    return h.parser.tree
-
-
-def sexp(node):
-    s = transforms.Sexp()
-    node.dispatch(s)
-    return s.out
diff --git a/tools/python/mllib/dom.py b/tools/python/mllib/dom.py
deleted file mode 100644
index b99174057..000000000
--- a/tools/python/mllib/dom.py
+++ /dev/null
@@ -1,331 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-"""
-Simple DOM for both SGML and XML documents.
-"""
-
-import sys
-STRING_TYPES = (str,)
-
-
-class Container:
-
-    def __init__(self):
-        self.children = []
-
-    def add(self, child):
-        child.parent = self
-        self.children.append(child)
-
-    def extend(self, children):
-        for child in children:
-            child.parent = self
-            self.children.append(child)
-
-
-class Component:
-
-    def __init__(self):
-        self.parent = None
-
-    def index(self):
-        if self.parent:
-            return self.parent.children.index(self)
-        else:
-            return 0
-
-    def _line(self, file, line, column):
-        self.file = file
-        self.line = line
-        self.column = column
-
-
-class DispatchError(Exception):
-
-    def __init__(self, scope, f):
-        msg = "no such attribute"
-
-
-class Dispatcher:
-
-    def is_type(self, type):
-        cls = self
-        while cls is not None:
-            if cls.type == type:
-                return True
-            cls = cls.base
-        return False
-
-    def dispatch(self, f, attrs=""):
-        cls = self
-        while cls is not None:
-            if hasattr(f, cls.type):
-                return getattr(f, cls.type)(self)
-            else:
-                cls = cls.base
-
-        cls = self
-        while cls is not None:
-            if attrs:
-                sep = ", "
-                if cls.base is None:
-                    sep += "or "
-            else:
-                sep = ""
-            attrs += "%s'%s'" % (sep, cls.type)
-            cls = cls.base
-
-        raise AttributeError("'%s' object has no attribute %s" %
-                             (f.__class__.__name__, attrs))
-
-
-class Node(Container, Component, Dispatcher):
-
-    type = "node"
-    base = None
-
-    def __init__(self):
-        Container.__init__(self)
-        Component.__init__(self)
-        self.query = Query([self])
-
-    def __getitem__(self, name):
-        for nd in self.query[name]:
-            return nd
-
-    def text(self):
-        from . import transforms
-        return self.dispatch(transforms.Text())
-
-    def tag(self, name, *attrs, **kwargs):
-        t = Tag(name, *attrs, **kwargs)
-        self.add(t)
-        return t
-
-    def data(self, s):
-        d = Data(s)
-        self.add(d)
-        return d
-
-    def entity(self, s):
-        e = Entity(s)
-        self.add(e)
-        return e
-
-
-class Tree(Node):
-
-    type = "tree"
-    base = Node
-
-
-class Tag(Node):
-
-    type = "tag"
-    base = Node
-
-    def __init__(self, _name, *attrs, **kwargs):
-        Node.__init__(self)
-        self.name = _name
-        self.attrs = list(attrs)
-        self.attrs.extend(kwargs.items())
-        self.singleton = False
-
-    def get_attr(self, name):
-        for k, v in self.attrs:
-            if name == k:
-                return v
-
-    def _idx(self, attr):
-        idx = 0
-        for k, v in self.attrs:
-            if k == attr:
-                return idx
-            idx += 1
-        return None
-
-    def set_attr(self, name, value):
-        idx = self._idx(name)
-        if idx is None:
-            self.attrs.append((name, value))
-        else:
-            self.attrs[idx] = (name, value)
-
-    def dispatch(self, f):
-        try:
-            attr = "do_" + self.name
-            method = getattr(f, attr)
-        except AttributeError:
-            return Dispatcher.dispatch(self, f, "'%s'" % attr)
-        return method(self)
-
-
-class Leaf(Component, Dispatcher):
-
-    type = "leaf"
-    base = None
-
-    def __init__(self, data):
-        assert isinstance(data, STRING_TYPES)
-        self.data = data
-
-
-class Data(Leaf):
-    type = "data"
-    base = Leaf
-
-
-class Entity(Leaf):
-    type = "entity"
-    base = Leaf
-
-
-class Character(Leaf):
-    type = "character"
-    base = Leaf
-
-
-class Comment(Leaf):
-    type = "comment"
-    base = Leaf
-
-###################
-## Query Classes ##
-###########################################################################
-
-
-class Adder:
-
-    def __add__(self, other):
-        return Sum(self, other)
-
-
-class Sum(Adder):
-
-    def __init__(self, left, right):
-        self.left = left
-        self.right = right
-
-    def __iter__(self):
-        for x in self.left:
-            yield x
-        for x in self.right:
-            yield x
-
-
-class View(Adder):
-
-    def __init__(self, source):
-        self.source = source
-
-
-class Filter(View):
-
-    def __init__(self, predicate, source):
-        View.__init__(self, source)
-        self.predicate = predicate
-
-    def __iter__(self):
-        for nd in self.source:
-            if self.predicate(nd):
-                yield nd
-
-
-class Flatten(View):
-
-    def __iter__(self):
-        sources = [iter(self.source)]
-        while sources:
-            try:
-                nd = next(sources[-1])
-                if isinstance(nd, Tree):
-                    sources.append(iter(nd.children))
-                else:
-                    yield nd
-            except StopIteration:
-                sources.pop()
-
-
-class Children(View):
-
-    def __iter__(self):
-        for nd in self.source:
-            for child in nd.children:
-                yield child
-
-
-class Attributes(View):
-
-    def __iter__(self):
-        for nd in self.source:
-            for a in nd.attrs:
-                yield a
-
-
-class Values(View):
-
-    def __iter__(self):
-        for name, value in self.source:
-            yield value
-
-
-def flatten_path(path):
-    if isinstance(path, STRING_TYPES):
-        for part in path.split("/"):
-            yield part
-    elif callable(path):
-        yield path
-    else:
-        for p in path:
-            for fp in flatten_path(p):
-                yield fp
-
-
-class Query(View):
-
-    def __iter__(self):
-        for nd in self.source:
-            yield nd
-
-    def __getitem__(self, path):
-        query = self.source
-        for p in flatten_path(path):
-            if callable(p):
-                select = Query
-                pred = p
-                source = query
-            elif isinstance(p, STRING_TYPES):
-                if p[0] == "@":
-                    select = Values
-                    pred = lambda x, n=p[1:]: x[0] == n
-                    source = Attributes(query)
-                elif p[0] == "#":
-                    select = Query
-                    pred = lambda x, t=p[1:]: x.is_type(t)
-                    source = Children(query)
-                else:
-                    select = Query
-                    def pred(x, n=p): return isinstance(x, Tag) and x.name == n
-                    source = Flatten(Children(query))
-            else:
-                raise ValueError(p)
-            query = select(Filter(pred, source))
-
-        return query
diff --git a/tools/python/mllib/parsers.py b/tools/python/mllib/parsers.py
deleted file mode 100644
index 9904b8831..000000000
--- a/tools/python/mllib/parsers.py
+++ /dev/null
@@ -1,105 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-"""
-Parsers for XML to dom.
-"""
-import xml.sax.handler
-from .dom import *
-
-
-class Parser:
-
-    def __init__(self):
-        self.tree = Tree()
-        self.node = self.tree
-        self.nodes = []
-
-    def line(self, id, lineno, colno):
-        while self.nodes:
-            n = self.nodes.pop()
-            n._line(id, lineno, colno)
-
-    def add(self, node):
-        self.node.add(node)
-        self.nodes.append(node)
-
-    def start(self, name, attrs):
-        tag = Tag(name, *attrs)
-        self.add(tag)
-        self.node = tag
-
-    def end(self, name):
-        self.balance(name)
-        self.node = self.node.parent
-
-    def data(self, data):
-        children = self.node.children
-        if children and isinstance(children[-1], Data):
-            children[-1].data += data
-        else:
-            self.add(Data(data))
-
-    def comment(self, comment):
-        self.add(Comment(comment))
-
-    def entity(self, ref):
-        self.add(Entity(ref))
-
-    def character(self, ref):
-        self.add(Character(ref))
-
-    def balance(self, name=None):
-        while self.node != self.tree and name != self.node.name:
-            self.node.parent.extend(self.node.children)
-            del self.node.children[:]
-            self.node.singleton = True
-            self.node = self.node.parent
-
-
-class XMLParser(xml.sax.handler.ContentHandler):
-
-    def __init__(self):
-        self.parser = Parser()
-        self.locator = None
-
-    def line(self):
-        if self.locator is not None:
-            self.parser.line(self.locator.getSystemId(),
-                             self.locator.getLineNumber(),
-                             self.locator.getColumnNumber())
-
-    def setDocumentLocator(self, locator):
-        self.locator = locator
-
-    def startElement(self, name, attrs):
-        self.parser.start(name, attrs.items())
-        self.line()
-
-    def endElement(self, name):
-        self.parser.end(name)
-        self.line()
-
-    def characters(self, content):
-        self.parser.data(content)
-        self.line()
-
-    def skippedEntity(self, name):
-        self.parser.entity(name)
-        self.line()
diff --git a/tools/python/mllib/transforms.py b/tools/python/mllib/transforms.py
deleted file mode 100644
index b84c7f65d..000000000
--- a/tools/python/mllib/transforms.py
+++ /dev/null
@@ -1,171 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-"""
-Useful transforms for dom objects.
-"""
-from . import dom
-from io import StringIO
-
-
-class Visitor:
-
-    def descend(self, node):
-        for child in node.children:
-            child.dispatch(self)
-
-    def node(self, node):
-        self.descend(node)
-
-    def leaf(self, leaf):
-        pass
-
-
-class Identity:
-
-    def descend(self, node):
-        result = []
-        for child in node.children:
-            result.append(child.dispatch(self))
-        return result
-
-    def default(self, tag):
-        result = dom.Tag(tag.name, *tag.attrs)
-        result.extend(self.descend(tag))
-        return result
-
-    def tree(self, tree):
-        result = dom.Tree()
-        result.extend(self.descend(tree))
-        return result
-
-    def tag(self, tag):
-        return self.default(tag)
-
-    def leaf(self, leaf):
-        return leaf.__class__(leaf.data)
-
-
-class Sexp(Identity):
-
-    def __init__(self):
-        self.stack = []
-        self.level = 0
-        self.out = ""
-
-    def open(self, s):
-        self.out += "(%s" % s
-        self.level += len(s) + 1
-        self.stack.append(s)
-
-    def line(self, s=""):
-        self.out = self.out.rstrip()
-        self.out += "\n" + " " * self.level + s
-
-    def close(self):
-        s = self.stack.pop()
-        self.level -= len(s) + 1
-        self.out = self.out.rstrip()
-        self.out += ")"
-
-    def tree(self, tree):
-        self.open("+ ")
-        for child in tree.children:
-            self.line()
-            child.dispatch(self)
-        self.close()
-
-    def tag(self, tag):
-        self.open("Node(%s) " % tag.name)
-        for child in tag.children:
-            self.line()
-            child.dispatch(self)
-        self.close()
-
-    def leaf(self, leaf):
-        self.line("%s(%s)" % (leaf.__class__.__name__, leaf.data))
-
-
-class Output:
-
-    def descend(self, node):
-        out = StringIO()
-        for child in node.children:
-            out.write(child.dispatch(self))
-        return out.getvalue()
-
-    def default(self, tag):
-        out = StringIO()
-        out.write("<%s" % tag.name)
-        for k, v in tag.attrs:
-            out.write(' %s="%s"' % (k, v))
-        out.write(">")
-        out.write(self.descend(tag))
-        if not tag.singleton:
-            out.write("</%s>" % tag.name)
-        return out.getvalue()
-
-    def tree(self, tree):
-        return self.descend(tree)
-
-    def tag(self, tag):
-        return self.default(tag)
-
-    def data(self, leaf):
-        return leaf.data
-
-    def entity(self, leaf):
-        return "&%s;" % leaf.data
-
-    def character(self, leaf):
-        raise Exception("TODO")
-
-    def comment(self, leaf):
-        return "<!-- %s -->" % leaf.data
-
-
-class Empty(Output):
-
-    def tag(self, tag):
-        return self.descend(tag)
-
-    def data(self, leaf):
-        return ""
-
-    def entity(self, leaf):
-        return ""
-
-    def character(self, leaf):
-        return ""
-
-    def comment(self, leaf):
-        return ""
-
-
-class Text(Empty):
-
-    def data(self, leaf):
-        return leaf.data
-
-    def entity(self, leaf):
-        return "&%s;" % leaf.data
-
-    def character(self, leaf):
-        # XXX: is this right?
-        return "&#%s;" % leaf.data


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to