From 991559357f6a03c3a5b70c053c8c2554aa8d5ee4 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Wed, 25 Aug 2010 10:26:40 -0700 Subject: [PATCH] Implement initial Python bindings for Open vSwitch database. These initial bindings pass a few hundred of the corresponding tests for C implementations of various bits of the Open vSwitch library API. The poorest part of them is actually the Python IDL interface in ovs.db.idl, which has not received enough attention yet. It appears to work, but it doesn't yet support writes (transactions) and it is difficult to use. I hope to improve it as it becomes clear what semantics Python applications actually want from an IDL. --- Makefile.am | 2 +- lib/automake.mk | 3 +- ovsdb/OVSDB.py | 510 ----------------------------- ovsdb/automake.mk | 7 +- ovsdb/ovsdb-doc.in | 27 +- ovsdb/ovsdb-dot.in | 7 +- ovsdb/ovsdb-idlc.in | 127 +++----- python/ovs/__init__.py | 1 + python/ovs/automake.mk | 42 +++ python/ovs/daemon.py | 431 +++++++++++++++++++++++++ python/ovs/db/__init__.py | 1 + python/ovs/db/data.py | 433 +++++++++++++++++++++++++ python/ovs/db/error.py | 34 ++ python/ovs/db/idl.py | 305 ++++++++++++++++++ python/ovs/db/parser.py | 105 ++++++ python/ovs/db/schema.py | 159 ++++++++++ python/ovs/db/types.py | 545 +++++++++++++++++++++++++++++++ python/ovs/dirs.py | 7 + python/ovs/fatal_signal.py | 121 +++++++ python/ovs/json.py | 528 +++++++++++++++++++++++++++++++ python/ovs/jsonrpc.py | 526 ++++++++++++++++++++++++++++++ python/ovs/ovsuuid.py | 72 +++++ python/ovs/poller.py | 123 +++++++ python/ovs/process.py | 39 +++ python/ovs/reconnect.py | 563 +++++++++++++++++++++++++++++++++ python/ovs/socket_util.py | 143 +++++++++ python/ovs/stream.py | 316 ++++++++++++++++++ python/ovs/timeval.py | 24 ++ python/ovs/util.py | 43 +++ tests/atlocal.in | 3 + tests/automake.mk | 11 + tests/daemon-py.at | 185 +++++++++++ tests/daemon.at | 2 +- tests/json.at | 45 ++- tests/jsonrpc-py.at | 48 +++ tests/jsonrpc.at | 2 +- tests/ovsdb-column.at | 6 +- tests/ovsdb-data.at | 104 +++--- tests/ovsdb-idl-py.at | 149 +++++++++ tests/ovsdb-schema.at | 6 +- tests/ovsdb-table.at | 14 +- tests/ovsdb-types.at | 108 +++---- tests/ovsdb.at | 57 ++++ tests/reconnect.at | 18 +- tests/test-daemon.py | 66 ++++ tests/test-json.py | 92 ++++++ tests/test-jsonrpc.c | 2 +- tests/test-jsonrpc.py | 221 +++++++++++++ tests/test-ovsdb.py | 372 ++++++++++++++++++++++ tests/test-reconnect.py | 195 ++++++++++++ tests/testsuite.at | 2 + xenserver/README | 6 + xenserver/automake.mk | 3 +- xenserver/openvswitch-xen.spec | 32 +- xenserver/uuid.py | 541 +++++++++++++++++++++++++++++++ 55 files changed, 6776 insertions(+), 758 deletions(-) delete mode 100644 ovsdb/OVSDB.py create mode 100644 python/ovs/__init__.py create mode 100644 python/ovs/automake.mk create mode 100644 python/ovs/daemon.py create mode 100644 python/ovs/db/__init__.py create mode 100644 python/ovs/db/data.py create mode 100644 python/ovs/db/error.py create mode 100644 python/ovs/db/idl.py create mode 100644 python/ovs/db/parser.py create mode 100644 python/ovs/db/schema.py create mode 100644 python/ovs/db/types.py create mode 100644 python/ovs/dirs.py create mode 100644 python/ovs/fatal_signal.py create mode 100644 python/ovs/json.py create mode 100644 python/ovs/jsonrpc.py create mode 100644 python/ovs/ovsuuid.py create mode 100644 python/ovs/poller.py create mode 100644 python/ovs/process.py create mode 100644 python/ovs/reconnect.py create mode 100644 python/ovs/socket_util.py create mode 100644 python/ovs/stream.py create mode 100644 python/ovs/timeval.py create mode 100644 python/ovs/util.py create mode 100644 tests/daemon-py.at create mode 100644 tests/jsonrpc-py.at create mode 100644 tests/ovsdb-idl-py.at create mode 100644 tests/test-daemon.py create mode 100644 tests/test-json.py create mode 100644 tests/test-jsonrpc.py create mode 100644 tests/test-ovsdb.py create mode 100644 tests/test-reconnect.py create mode 100644 xenserver/uuid.py diff --git a/Makefile.am b/Makefile.am index eb7f4f78..da4c1b33 100644 --- a/Makefile.am +++ b/Makefile.am @@ -130,4 +130,4 @@ include debian/automake.mk include vswitchd/automake.mk include ovsdb/automake.mk include xenserver/automake.mk - +include python/ovs/automake.mk diff --git a/lib/automake.mk b/lib/automake.mk index 801be72d..efb84c5a 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -234,7 +234,8 @@ lib/dirs.c: Makefile echo 'const char ovs_bindir[] = "$(bindir)";') > lib/dirs.c.tmp mv lib/dirs.c.tmp lib/dirs.c -install-data-local: +install-data-local: lib-install-data-local +lib-install-data-local: $(MKDIR_P) $(DESTDIR)$(RUNDIR) $(MKDIR_P) $(DESTDIR)$(PKIDIR) $(MKDIR_P) $(DESTDIR)$(LOGDIR) diff --git a/ovsdb/OVSDB.py b/ovsdb/OVSDB.py deleted file mode 100644 index 3acc7b87..00000000 --- a/ovsdb/OVSDB.py +++ /dev/null @@ -1,510 +0,0 @@ -import re - -class Error(Exception): - def __init__(self, msg): - Exception.__init__(self) - self.msg = msg - -def getMember(json, name, validTypes, description, default=None): - if name in json: - member = json[name] - if len(validTypes) and type(member) not in validTypes: - raise Error("%s: type mismatch for '%s' member" - % (description, name)) - return member - return default - -def mustGetMember(json, name, expectedType, description): - member = getMember(json, name, expectedType, description) - if member == None: - raise Error("%s: missing '%s' member" % (description, name)) - return member - -class DbSchema: - def __init__(self, name, tables): - self.name = name - self.tables = tables - - @staticmethod - def fromJson(json): - name = mustGetMember(json, 'name', [unicode], 'database') - tablesJson = mustGetMember(json, 'tables', [dict], 'database') - tables = {} - for tableName, tableJson in tablesJson.iteritems(): - tables[tableName] = TableSchema.fromJson(tableJson, - "%s table" % tableName) - return DbSchema(name, tables) - -class IdlSchema(DbSchema): - def __init__(self, name, tables, idlPrefix, idlHeader): - DbSchema.__init__(self, name, tables) - self.idlPrefix = idlPrefix - self.idlHeader = idlHeader - - @staticmethod - def fromJson(json): - schema = DbSchema.fromJson(json) - idlPrefix = mustGetMember(json, 'idlPrefix', [unicode], 'database') - idlHeader = mustGetMember(json, 'idlHeader', [unicode], 'database') - return IdlSchema(schema.name, schema.tables, idlPrefix, idlHeader) - -class TableSchema: - def __init__(self, columns): - self.columns = columns - - @staticmethod - def fromJson(json, description): - columnsJson = mustGetMember(json, 'columns', [dict], description) - columns = {} - for name, json in columnsJson.iteritems(): - columns[name] = ColumnSchema.fromJson( - json, "column %s in %s" % (name, description)) - return TableSchema(columns) - -class ColumnSchema: - def __init__(self, type, persistent): - self.type = type - self.persistent = persistent - - @staticmethod - def fromJson(json, description): - type = Type.fromJson(mustGetMember(json, 'type', [dict, unicode], - description), - 'type of %s' % description) - ephemeral = getMember(json, 'ephemeral', [bool], description) - persistent = ephemeral != True - return ColumnSchema(type, persistent) - -def escapeCString(src): - dst = "" - for c in src: - if c in "\\\"": - dst += "\\" + c - elif ord(c) < 32: - if c == '\n': - dst += '\\n' - elif c == '\r': - dst += '\\r' - elif c == '\a': - dst += '\\a' - elif c == '\b': - dst += '\\b' - elif c == '\f': - dst += '\\f' - elif c == '\t': - dst += '\\t' - elif c == '\v': - dst += '\\v' - else: - dst += '\\%03o' % ord(c) - else: - dst += c - return dst - -def returnUnchanged(x): - return x - -class UUID: - x = "[0-9a-fA-f]" - uuidRE = re.compile("^(%s{8})-(%s{4})-(%s{4})-(%s{4})-(%s{4})(%s{8})$" - % (x, x, x, x, x, x)) - - def __init__(self, value): - self.value = value - - @staticmethod - def fromString(s): - if not uuidRE.match(s): - raise Error("%s is not a valid UUID" % s) - return UUID(s) - - @staticmethod - def fromJson(json): - if UUID.isValidJson(json): - return UUID(json[1]) - else: - raise Error("%s is not valid JSON for a UUID" % json) - - @staticmethod - def isValidJson(json): - return len(json) == 2 and json[0] == "uuid" and uuidRE.match(json[1]) - - def toJson(self): - return ["uuid", self.value] - - def cInitUUID(self, var): - m = re.match(self.value) - return ["%s.parts[0] = 0x%s;" % (var, m.group(1)), - "%s.parts[1] = 0x%s%s;" % (var, m.group(2), m.group(3)), - "%s.parts[2] = 0x%s%s;" % (var, m.group(4), m.group(5)), - "%s.parts[3] = 0x%s;" % (var, m.group(6))] - -class Atom: - def __init__(self, type, value): - self.type = type - self.value = value - - @staticmethod - def fromJson(type_, json): - if ((type_ == 'integer' and type(json) in [int, long]) - or (type_ == 'real' and type(json) in [int, long, float]) - or (type_ == 'boolean' and json in [True, False]) - or (type_ == 'string' and type(json) in [str, unicode])): - return Atom(type_, json) - elif type_ == 'uuid': - return UUID.fromJson(json) - else: - raise Error("%s is not valid JSON for type %s" % (json, type_)) - - def toJson(self): - if self.type == 'uuid': - return self.value.toString() - else: - return self.value - - def cInitAtom(self, var): - if self.type == 'integer': - return ['%s.integer = %d;' % (var, self.value)] - elif self.type == 'real': - return ['%s.real = %.15g;' % (var, self.value)] - elif self.type == 'boolean': - if self.value: - return ['%s.boolean = true;'] - else: - return ['%s.boolean = false;'] - elif self.type == 'string': - return ['%s.string = xstrdup("%s");' - % (var, escapeCString(self.value))] - elif self.type == 'uuid': - return self.value.cInitUUID(var) - - def toEnglish(self, escapeLiteral=returnUnchanged): - if self.type == 'integer': - return '%d' % self.value - elif self.type == 'real': - return '%.15g' % self.value - elif self.type == 'boolean': - if self.value: - return 'true' - else: - return 'false' - elif self.type == 'string': - return escapeLiteral(self.value) - elif self.type == 'uuid': - return self.value.value - -# Returns integer x formatted in decimal with thousands set off by commas. -def commafy(x): - return _commafy("%d" % x) -def _commafy(s): - if s.startswith('-'): - return '-' + _commafy(s[1:]) - elif len(s) <= 3: - return s - else: - return _commafy(s[:-3]) + ',' + _commafy(s[-3:]) - -class BaseType: - def __init__(self, type, - enum=None, - refTable=None, refType="strong", - minInteger=None, maxInteger=None, - minReal=None, maxReal=None, - minLength=None, maxLength=None): - self.type = type - self.enum = enum - self.refTable = refTable - self.refType = refType - self.minInteger = minInteger - self.maxInteger = maxInteger - self.minReal = minReal - self.maxReal = maxReal - self.minLength = minLength - self.maxLength = maxLength - - @staticmethod - def fromJson(json, description): - if type(json) == unicode: - return BaseType(json) - else: - atomicType = mustGetMember(json, 'type', [unicode], description) - enum = getMember(json, 'enum', [], description) - if enum: - enumType = Type(atomicType, None, 0, 'unlimited') - enum = Datum.fromJson(enumType, enum) - refTable = getMember(json, 'refTable', [unicode], description) - refType = getMember(json, 'refType', [unicode], description) - if refType == None: - refType = "strong" - minInteger = getMember(json, 'minInteger', [int, long], description) - maxInteger = getMember(json, 'maxInteger', [int, long], description) - minReal = getMember(json, 'minReal', [int, long, float], description) - maxReal = getMember(json, 'maxReal', [int, long, float], description) - minLength = getMember(json, 'minLength', [int], description) - maxLength = getMember(json, 'minLength', [int], description) - return BaseType(atomicType, enum, refTable, refType, minInteger, maxInteger, minReal, maxReal, minLength, maxLength) - - def toEnglish(self, escapeLiteral=returnUnchanged): - if self.type == 'uuid' and self.refTable: - s = escapeLiteral(self.refTable) - if self.refType == 'weak': - s = "weak reference to " + s - return s - else: - return self.type - - def constraintsToEnglish(self, escapeLiteral=returnUnchanged): - if self.enum: - literals = [value.toEnglish(escapeLiteral) - for value in self.enum.values] - if len(literals) == 2: - return 'either %s or %s' % (literals[0], literals[1]) - else: - return 'one of %s, %s, or %s' % (literals[0], - ', '.join(literals[1:-1]), - literals[-1]) - elif self.minInteger != None and self.maxInteger != None: - return 'in range %s to %s' % (commafy(self.minInteger), - commafy(self.maxInteger)) - elif self.minInteger != None: - return 'at least %s' % commafy(self.minInteger) - elif self.maxInteger != None: - return 'at most %s' % commafy(self.maxInteger) - elif self.minReal != None and self.maxReal != None: - return 'in range %g to %g' % (self.minReal, self.maxReal) - elif self.minReal != None: - return 'at least %g' % self.minReal - elif self.maxReal != None: - return 'at most %g' % self.maxReal - elif self.minLength != None and self.maxLength != None: - if self.minLength == self.maxLength: - return 'exactly %d characters long' % (self.minLength) - else: - return 'between %d and %d characters long' % (self.minLength, self.maxLength) - elif self.minLength != None: - return 'at least %d characters long' % self.minLength - elif self.maxLength != None: - return 'at most %d characters long' % self.maxLength - else: - return '' - - def toCType(self, prefix): - if self.refTable: - return "struct %s%s *" % (prefix, self.refTable.lower()) - else: - return {'integer': 'int64_t ', - 'real': 'double ', - 'uuid': 'struct uuid ', - 'boolean': 'bool ', - 'string': 'char *'}[self.type] - - def toAtomicType(self): - return "OVSDB_TYPE_%s" % self.type.upper() - - def copyCValue(self, dst, src): - args = {'dst': dst, 'src': src} - if self.refTable: - return ("%(dst)s = %(src)s->header_.uuid;") % args - elif self.type == 'string': - return "%(dst)s = xstrdup(%(src)s);" % args - else: - return "%(dst)s = %(src)s;" % args - - def initCDefault(self, var, isOptional): - if self.refTable: - return "%s = NULL;" % var - elif self.type == 'string' and not isOptional: - return "%s = \"\";" % var - else: - return {'integer': '%s = 0;', - 'real': '%s = 0.0;', - 'uuid': 'uuid_zero(&%s);', - 'boolean': '%s = false;', - 'string': '%s = NULL;'}[self.type] % var - - def cInitBaseType(self, indent, var): - stmts = [] - stmts.append('ovsdb_base_type_init(&%s, OVSDB_TYPE_%s);' % ( - var, self.type.upper()),) - if self.enum: - stmts.append("%s.enum_ = xmalloc(sizeof *%s.enum_);" - % (var, var)) - stmts += self.enum.cInitDatum("%s.enum_" % var) - if self.type == 'integer': - if self.minInteger != None: - stmts.append('%s.u.integer.min = INT64_C(%d);' % (var, self.minInteger)) - if self.maxInteger != None: - stmts.append('%s.u.integer.max = INT64_C(%d);' % (var, self.maxInteger)) - elif self.type == 'real': - if self.minReal != None: - stmts.append('%s.u.real.min = %d;' % (var, self.minReal)) - if self.maxReal != None: - stmts.append('%s.u.real.max = %d;' % (var, self.maxReal)) - elif self.type == 'string': - if self.minLength != None: - stmts.append('%s.u.string.minLen = %d;' % (var, self.minLength)) - if self.maxLength != None: - stmts.append('%s.u.string.maxLen = %d;' % (var, self.maxLength)) - elif self.type == 'uuid': - if self.refTable != None: - stmts.append('%s.u.uuid.refTableName = "%s";' % (var, escapeCString(self.refTable))) - return '\n'.join([indent + stmt for stmt in stmts]) - -class Type: - def __init__(self, key, value=None, min=1, max=1): - self.key = key - self.value = value - self.min = min - self.max = max - - @staticmethod - def fromJson(json, description): - if type(json) == unicode: - return Type(BaseType(json)) - else: - keyJson = mustGetMember(json, 'key', [dict, unicode], description) - key = BaseType.fromJson(keyJson, 'key in %s' % description) - - valueJson = getMember(json, 'value', [dict, unicode], description) - if valueJson: - value = BaseType.fromJson(valueJson, - 'value in %s' % description) - else: - value = None - - min = getMember(json, 'min', [int], description, 1) - max = getMember(json, 'max', [int, unicode], description, 1) - return Type(key, value, min, max) - - def isScalar(self): - return self.min == 1 and self.max == 1 and not self.value - - def isOptional(self): - return self.min == 0 and self.max == 1 - - def isOptionalPointer(self): - return (self.min == 0 and self.max == 1 and not self.value - and (self.key.type == 'string' or self.key.refTable)) - - def toEnglish(self, escapeLiteral=returnUnchanged): - keyName = self.key.toEnglish(escapeLiteral) - if self.value: - valueName = self.value.toEnglish(escapeLiteral) - - if self.isScalar(): - return keyName - elif self.isOptional(): - if self.value: - return "optional %s-%s pair" % (keyName, valueName) - else: - return "optional %s" % keyName - else: - if self.max == "unlimited": - if self.min: - quantity = "%d or more " % self.min - else: - quantity = "" - elif self.min: - quantity = "%d to %d " % (self.min, self.max) - else: - quantity = "up to %d " % self.max - - if self.value: - return "map of %s%s-%s pairs" % (quantity, keyName, valueName) - else: - if keyName.endswith('s'): - plural = keyName + "es" - else: - plural = keyName + "s" - return "set of %s%s" % (quantity, plural) - - def constraintsToEnglish(self, escapeLiteral=returnUnchanged): - s = "" - - constraints = [] - keyConstraints = self.key.constraintsToEnglish(escapeLiteral) - if keyConstraints: - if self.value: - constraints += ['key ' + keyConstraints] - else: - constraints += [keyConstraints] - - if self.value: - valueConstraints = self.value.constraintsToEnglish(escapeLiteral) - if valueConstraints: - constraints += ['value ' + valueConstraints] - - return ', '.join(constraints) - - def cDeclComment(self): - if self.min == 1 and self.max == 1 and self.key.type == "string": - return "\t/* Always nonnull. */" - else: - return "" - - def cInitType(self, indent, var): - initKey = self.key.cInitBaseType(indent, "%s.key" % var) - if self.value: - initValue = self.value.cInitBaseType(indent, "%s.value" % var) - else: - initValue = ('%sovsdb_base_type_init(&%s.value, ' - 'OVSDB_TYPE_VOID);' % (indent, var)) - initMin = "%s%s.n_min = %s;" % (indent, var, self.min) - if self.max == "unlimited": - max = "UINT_MAX" - else: - max = self.max - initMax = "%s%s.n_max = %s;" % (indent, var, max) - return "\n".join((initKey, initValue, initMin, initMax)) - -class Datum: - def __init__(self, type, values): - self.type = type - self.values = values - - @staticmethod - def fromJson(type_, json): - if not type_.value: - if len(json) == 2 and json[0] == "set": - values = [] - for atomJson in json[1]: - values += [Atom.fromJson(type_.key, atomJson)] - else: - values = [Atom.fromJson(type_.key, json)] - else: - if len(json) != 2 or json[0] != "map": - raise Error("%s is not valid JSON for a map" % json) - values = [] - for pairJson in json[1]: - values += [(Atom.fromJson(type_.key, pairJson[0]), - Atom.fromJson(type_.value, pairJson[1]))] - return Datum(type_, values) - - def cInitDatum(self, var): - if len(self.values) == 0: - return ["ovsdb_datum_init_empty(%s);" % var] - - s = ["%s->n = %d;" % (var, len(self.values))] - s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);" - % (var, len(self.values), var)] - - for i in range(len(self.values)): - key = self.values[i] - if self.type.value: - key = key[0] - s += key.cInitAtom("%s->keys[%d]" % (var, i)) - - if self.type.value: - s += ["%s->values = xmalloc(%d * sizeof *%s->values);" - % (var, len(self.values), var)] - for i in range(len(self.values)): - value = self.values[i][1] - s += key.cInitAtom("%s->values[%d]" % (var, i)) - else: - s += ["%s->values = NULL;" % var] - - if len(self.values) > 1: - s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);" - % (var, self.type.key.upper())] - - return s diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk index 5745697a..5b46e168 100644 --- a/ovsdb/automake.mk +++ b/ovsdb/automake.mk @@ -60,7 +60,6 @@ EXTRA_DIST += ovsdb/ovsdb-server.1.in # ovsdb-idlc EXTRA_DIST += \ - ovsdb/OVSDB.py \ ovsdb/SPECS \ ovsdb/simplejson/__init__.py \ ovsdb/simplejson/_speedups.c \ @@ -90,7 +89,7 @@ EXTRA_DIST += \ ovsdb/ovsdb-idlc.1 DISTCLEANFILES += ovsdb/ovsdb-idlc SUFFIXES += .ovsidl -OVSDB_IDLC = $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in +OVSDB_IDLC = $(run_python) $(srcdir)/ovsdb/ovsdb-idlc.in .ovsidl.c: $(OVSDB_IDLC) c-idl-source $< > $@.tmp mv $@.tmp $@ @@ -115,12 +114,12 @@ $(OVSIDL_BUILT): ovsdb/ovsdb-idlc.in EXTRA_DIST += ovsdb/ovsdb-doc.in noinst_SCRIPTS += ovsdb/ovsdb-doc DISTCLEANFILES += ovsdb/ovsdb-doc -OVSDB_DOC = $(PYTHON) $(srcdir)/ovsdb/ovsdb-doc.in +OVSDB_DOC = $(run_python) $(srcdir)/ovsdb/ovsdb-doc.in # ovsdb-dot EXTRA_DIST += ovsdb/ovsdb-dot.in noinst_SCRIPTS += ovsdb/ovsdb-dot DISTCLEANFILES += ovsdb/ovsdb-dot -OVSDB_DOT = $(PYTHON) $(srcdir)/ovsdb/ovsdb-dot.in +OVSDB_DOT = $(run_python) $(srcdir)/ovsdb/ovsdb-dot.in include ovsdb/ovsdbmonitor/automake.mk diff --git a/ovsdb/ovsdb-doc.in b/ovsdb/ovsdb-doc.in index 4950e47e..90de4521 100755 --- a/ovsdb/ovsdb-doc.in +++ b/ovsdb/ovsdb-doc.in @@ -7,10 +7,9 @@ import re import sys import xml.dom.minidom -sys.path.insert(0, "@abs_top_srcdir@/ovsdb") -import simplejson as json - -from OVSDB import * +import ovs.json +from ovs.db import error +import ovs.db.schema argv0 = sys.argv[0] @@ -29,7 +28,7 @@ def textToNroff(s, font=r'\fR'): elif c == "'": return r'\(cq' else: - raise Error("bad escape") + raise error.Error("bad escape") # Escape - \ " ' as needed by nroff. s = re.sub('([-"\'\\\\])', escape, s) @@ -58,7 +57,7 @@ def inlineXmlToNroff(node, font): elif node.hasAttribute('group'): s += node.attributes['group'].nodeValue else: - raise Error("'ref' lacks column and table attributes") + raise error.Error("'ref' lacks column and table attributes") return s + font elif node.tagName == 'var': s = r'\fI' @@ -66,9 +65,9 @@ def inlineXmlToNroff(node, font): s += inlineXmlToNroff(child, r'\fI') return s + font else: - raise Error("element <%s> unknown or invalid here" % node.tagName) + raise error.Error("element <%s> unknown or invalid here" % node.tagName) else: - raise Error("unknown node %s in inline xml" % node) + raise error.Error("unknown node %s in inline xml" % node) def blockXmlToNroff(nodes, para='.PP'): s = '' @@ -87,7 +86,7 @@ def blockXmlToNroff(nodes, para='.PP'): s += ".IP \\(bu\n" + blockXmlToNroff(liNode.childNodes, ".IP") elif (liNode.nodeType != node.TEXT_NODE or not liNode.data.isspace()): - raise Error("