[Zope-Checkins] CVS: Packages/ZConfig - cfgparser.py:1.2 datatypes.py:1.2 info.py:1.2 loader.py:1.2 matcher.py:1.2 schema.py:1.2 substitution.py:1.2 url.py:1.2 Config.py:1.15 Context.py:1.18 __init__.py:1.4 ApacheStyle.py:NONE Exceptions.py:NONE Substitution.py:NONE

Fred L. Drake, Jr. fred@zope.com
Fri, 3 Jan 2003 16:06:26 -0500


Update of /cvs-repository/Packages/ZConfig
In directory cvs.zope.org:/tmp/cvs-serv11713

Modified Files:
	Config.py Context.py __init__.py 
Added Files:
	cfgparser.py datatypes.py info.py loader.py matcher.py 
	schema.py substitution.py url.py 
Removed Files:
	ApacheStyle.py Exceptions.py Substitution.py 
Log Message:
Merge the zconfig-schema-devel-branch into the trunk for the ZConfig package.
Copyright notices get 2003 added as well.

The zconfig-schema-devel-branch should no longer be used.


=== Packages/ZConfig/cfgparser.py 1.1 => 1.2 ===
--- /dev/null	Fri Jan  3 16:06:25 2003
+++ Packages/ZConfig/cfgparser.py	Fri Jan  3 16:05:51 2003
@@ -0,0 +1,178 @@
+##############################################################################
+#
+# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Configuration parser."""
+
+from ZConfig import ConfigurationError, ConfigurationSyntaxError
+from ZConfig.substitution import isname, substitute
+from ZConfig.url import urljoin
+
+try:
+    True
+except NameError:
+    True = 1
+    False = 0
+
+
+class ZConfigParser:
+    def __init__(self, resource, context):
+        self.resource = resource
+        self.context = context
+        self.file = resource.file
+        self.url = resource.url
+        self.lineno = 0
+        self.stack = []   # [(type, name, delegatename, prevmatcher), ...]
+        self.defs = {}
+
+    def nextline(self):
+        line = self.file.readline()
+        if line:
+            self.lineno += 1
+            return False, line.strip()
+        else:
+            return True, None
+
+    def parse(self, section):
+        done, line = self.nextline()
+        while not done:
+            if line[:1] in ("", "#"):
+                # blank line or comment
+                pass
+
+            elif line[:2] == "</":
+                # section end
+                if line[-1] != ">":
+                    self.error("malformed section end")
+                section = self.end_section(section, line[2:-1])
+
+            elif line[0] == "<":
+                # section start
+                if line[-1] != ">":
+                    self.error("malformed section start")
+                section = self.start_section(section, line[1:-1])
+
+            elif line[0] == "%":
+                self.handle_directive(section, line[1:])
+
+            else:
+                self.handle_key_value(section, line)
+
+            done, line = self.nextline()
+
+        if self.stack:
+            self.error("unclosed sections not allowed")
+
+    def start_section(self, section, rest):
+        isempty = rest[-1:] == "/"
+        if isempty:
+            text = rest[:-1].rstrip()
+        else:
+            text = rest.rstrip()
+        # parse section start stuff here
+        m = _section_start_rx.match(text)
+        if not m:
+            self.error("malformed section header")
+        type, name, delegatename = m.group('type', 'name', 'delegatename')
+        type = type.lower()
+        if name:
+            name = name.lower()
+        try:
+            newsect = self.context.startSection(section, type, name,
+                                                delegatename)
+        except ConfigurationError, e:
+            self.error(e[0])
+
+        if isempty:
+            self.context.endSection(section, type, name, delegatename, newsect)
+            return section
+        else:
+            self.stack.append((type, name, delegatename, section))
+            return newsect
+
+    def end_section(self, section, rest):
+        if not self.stack:
+            self.error("unexpected section end")
+        type = rest.rstrip().lower()
+        opentype, name, delegatename, prevsection = self.stack.pop()
+        if type != opentype:
+            self.error("unbalanced section end")
+        try:
+            self.context.endSection(
+                prevsection, type, name, delegatename, section)
+        except ConfigurationError, e:
+            self.error(e[0])
+        return prevsection
+
+    def handle_key_value(self, section, rest):
+        m = _keyvalue_rx.match(rest)
+        if not m:
+            self.error("malformed configuration data")
+        key, value = m.group('key', 'value')
+        if not value:
+            value = ''
+        else:
+            value = substitute(value, self.defs)
+        try:
+            section.addValue(key, value)
+        except ConfigurationError, e:
+            self.error(e[0])
+
+    def handle_directive(self, section, rest):
+        m = _keyvalue_rx.match(rest)
+        if not m:
+            self.error("missing or unrecognized directive")
+        name, arg = m.group('key', 'value')
+        if name not in ("define", "include"):
+            self.error("unknown directive: " + `name`)
+        if not arg:
+            self.error("missing argument to %%%s directive" % name)
+        if name == "include":
+            self.handle_include(section, arg)
+        elif name == "define":
+            self.handle_define(section, arg)
+        else:
+            assert 0, "unexpected directive for " + `"%" + rest`
+
+    def handle_include(self, section, rest):
+        newurl = urljoin(self.url, rest)
+        self.context.includeConfiguration(section, newurl)
+
+    def handle_define(self, section, rest):
+        parts = rest.split(None, 1)
+        defname = parts[0].lower()
+        defvalue = ''
+        if len(parts) == 2:
+            defvalue = parts[1]
+        if self.defs.has_key(defname):
+            self.error("cannot redefine " + `defname`)
+        if not isname(defname):
+            self.error("not a substitution legal name: " + `defname`)
+        self.defs[defname] = defvalue
+
+    def error(self, message):
+        raise ConfigurationSyntaxError(message, self.url, self.lineno)
+
+
+import re
+# _name_re cannot allow "(" or ")" since we need to be able to tell if
+# a section has a name or not: <section (name)> would be ambiguous if
+# parentheses were allowed in names.
+_name_re = r"[^\s()]+"
+_keyvalue_rx = re.compile(r"(?P<key>%s)\s*(?P<value>[^\s].*)?$"
+                          % _name_re)
+_section_start_rx = re.compile(r"(?P<type>%s)"
+                               r"(?:\s+(?P<name>%s))?"
+                               r"(?:\s*[(](?P<delegatename>%s)[)])?"
+                               r"$"
+                               % (_name_re, _name_re, _name_re))
+del re


=== Packages/ZConfig/datatypes.py 1.1 => 1.2 ===
--- /dev/null	Fri Jan  3 16:06:25 2003
+++ Packages/ZConfig/datatypes.py	Fri Jan  3 16:05:51 2003
@@ -0,0 +1,372 @@
+##############################################################################
+#
+# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Selection of standard datatypes for ZConfig."""
+
+import re
+import sys
+import os
+
+try:
+    True
+except NameError:
+    True = 1
+    False = 0
+
+
+class MemoizedConversion:
+    """Conversion helper that caches the results of expensive conversions."""
+
+    def __init__(self, conversion):
+        self._memo = {}
+        self._conversion = conversion
+
+    def __call__(self, value):
+        try:
+            return self._memo[value]
+        except KeyError:
+            v = self._conversion(value)
+            self._memo[value] = v
+            return v
+
+
+class RangeCheckedConversion:
+    """Conversion helper that range checks another conversion."""
+
+    def __init__(self, conversion, min=None, max=None):
+        self._min = min
+        self._max = max
+        self._conversion = conversion
+
+    def __call__(self, value):
+        v = self._conversion(value)
+        if self._min is not None and v < self._min:
+            raise ValueError("%s is below lower bound (%s)"
+                             % (`v`, `self._min`))
+        if self._max is not None and v > self._max:
+            raise ValueError("%s is above upper bound (%s)"
+                             % (`v`, `self._max`))
+        return v
+
+
+class RegularExpressionConversion:
+    def __init__(self, regex):
+        self._rx = re.compile(regex)
+
+    def __call__(self, value):
+        m = self._rx.match(value)
+        if m and m.group() == value:
+            return value
+        else:
+            raise ValueError("value did not match regular expression: "
+                             + `value`)
+
+
+def check_locale(value):
+    import locale
+    prev = locale.setlocale(locale.LC_ALL)
+    try:
+        try:
+            locale.setlocale(locale.LC_ALL, value)
+        finally:
+            locale.setlocale(locale.LC_ALL, prev)
+    except locale.Error:
+        raise ValueError(
+            'The specified locale "%s" is not supported by your system.\n'
+            'See your operating system documentation for more\n'
+            'information on locale support.' % value)
+    else:
+        return value
+
+
+class BasicKeyConversion(RegularExpressionConversion):
+    def __init__(self):
+        RegularExpressionConversion.__init__(self, "[a-zA-Z][-._a-zA-Z0-9]*")
+
+    def __call__(self, value):
+        value = str(value)
+        return RegularExpressionConversion.__call__(self, value).lower()
+
+
+class IdentifierConversion(RegularExpressionConversion):
+    def __init__(self):
+        RegularExpressionConversion.__init__(self, "[_a-zA-Z][_a-zA-Z0-9]*")
+
+
+class LogLevelConversion:
+    # This uses the 'logging' package conventions; only makes sense
+    # for Zope 2.7 (and newer) and Zope 3.  Not sure what the
+    # compatibility should be.
+
+    _levels = {
+        "critical": 50,
+        "fatal": 50,
+        "error": 40,
+        "warn": 30,
+        "info": 20,
+        "debug": 10,
+        "all": 0,
+        }
+
+    def __call__(self, value):
+        s = str(value).lower()
+        if self._levels.has_key(s):
+            return self._levels[s]
+        else:
+            v = int(s)
+            if v < 0 or v > 50:
+                raise ValueError("log level not in range: " + `v`)
+            return v
+
+
+if sys.version[:3] < "2.3":
+    def integer(value):
+        try:
+            return int(value)
+        except ValueError:
+            return long(value)
+else:
+    integer = int
+
+
+def null_conversion(value):
+    return value
+
+
+def asBoolean(s):
+    """Convert a string value to a boolean value."""
+    ss = str(s).lower()
+    if ss in ('yes', 'true', 'on'):
+        return True
+    elif ss in ('no', 'false', 'off'):
+        return False
+    else:
+        raise ValueError("not a valid boolean value: " + repr(s))
+
+
+port_number = RangeCheckedConversion(integer, min=1, max=0xffff).__call__
+
+
+def inet_address(s):
+    # returns (host, port) tuple
+    host = ''
+    port = None
+    if ":" in s:
+        host, s = s.split(":", 1)
+        if s:
+            port = port_number(s)
+        host = host.lower()
+    else:
+        try:
+            port = port_number(s)
+        except ValueError:
+            host = s.lower()
+    return host, port
+
+
+def socket_address(s):
+    # returns (family, address) tuple
+    import socket
+    if "/" in s:
+        if hasattr(socket, "AF_UNIX"):
+            return socket.AF_UNIX, s
+        else:
+            raise ValueError(
+                "AF_UNIX sockets are not supported on this platform")
+    else:
+        return socket.AF_INET, inet_address(s)
+
+def float_conversion(v):
+    if isinstance(v, type('')) or isinstance(v, type(u'')):
+        if v.lower() in ["inf", "-inf", "nan"]:
+            raise ValueError(`v` + " is not a portable float representation")
+    return float(v)
+
+class IpaddrOrHostname(RegularExpressionConversion):
+    def __init__(self):
+        # IP address regex from the Perl Cookbook, Recipe 6.23 (revised ed.)
+        # We allow underscores in hostnames although this is considered
+        # illegal according to RFC1034.
+        expr = (r"(^(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." #ipaddr
+                r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." #ipaddr cont'd
+                r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])\." #ipaddr cont'd
+                r"(\d|[01]?\d\d|2[0-4]\d|25[0-5])$)" #ipaddr cont'd
+                r"|([^0-9][A-Za-z0-9-_.]+)") # or hostname
+        RegularExpressionConversion.__init__(self, expr)
+
+def existing_directory(v):
+    if os.path.isdir(v):
+        return v
+    raise ValueError, '%s is not an existing directory' % v
+
+def existing_path(v):
+    if os.path.exists(v):
+        return v
+    raise ValueError, '%s is not an existing path' % v
+
+def existing_file(v):
+    if os.path.exists(v):
+        return v
+    raise ValueError, '%s is not an existing file' % v
+
+def existing_dirpath(v):
+    if not os.path.split(v)[0]:
+        # relative pathname
+        return v
+    dir = os.path.dirname(v)
+    if os.path.isdir(dir):
+        return v
+    raise ValueError, ('The directory named as part of the path %s '
+                       'does not exist.' % v)
+
+def parse_constructor(v):
+    parenmsg = (
+        'Invalid constructor (unbalanced parenthesis in "%s")' % v
+        )
+    openparen = v.find('(')
+    if openparen < 0:
+        raise ValueError(parenmsg)
+    klass = v[:openparen]
+    if not v.endswith(')'):
+        raise ValueError(parenmsg)
+    arglist = v[openparen+1:-1]
+    return klass, arglist
+
+def get_arglist(s):
+    # someone could do a better job at this.
+    pos = []
+    kw = {}
+    args = s.split(',')
+    args = filter(None, args)
+    while args:
+        arg = args.pop(0)
+        try:
+            if '=' in arg:
+                k,v=arg.split('=', 1)
+                k = k.strip()
+                v = v.strip()
+                kw[k] = eval(v)
+            else:
+                arg = arg.strip()
+                pos.append(eval(arg))
+        except SyntaxError:
+            if not args:
+                raise
+            args[0] = '%s, %s' % (arg, args[0])
+    return pos, kw
+
+def constructor_conversion(v):
+    klass, arglist = parse_constructor(v)
+    pos, kw = get_arglist(arglist)
+    return klass, pos, kw
+
+def space_sep_key_value_conversion(v):
+    l = v.split(' ', 1)
+    if len(l) < 2:
+        l.append('')
+    return l
+
+
+class SuffixMultiplier:
+    # d is a dictionary of suffixes to integer multipliers.  If no suffixes
+    # match, default is the multiplier.  Matches are case insensitive.  Return
+    # values are in the fundamental unit.
+    def __init__(self, d, default=1):
+        self._d = d
+        self._default = default
+        # all keys must be the same size
+        self._keysz = None
+        for k in d.keys():
+            if self._keysz is None:
+                self._keysz = len(k)
+            else:
+                assert self._keysz == len(k)
+
+    def __call__(self, v):
+        v = v.lower()
+        for s, m in self._d.items():
+            if v[-self._keysz:] == s:
+                return int(v[:-self._keysz]) * m
+        return int(v) * self._default
+
+stock_datatypes = {
+    "boolean":           asBoolean,
+    "identifier":        IdentifierConversion(),
+    "integer":           integer,
+    "float":             float_conversion,
+    "string":            str,
+    "null":              null_conversion,
+    "locale":            MemoizedConversion(check_locale),
+    "port-number":       port_number,
+    "basic-key":         BasicKeyConversion(),
+    "logging-level":     LogLevelConversion(),
+    "inet-address":      inet_address,
+    "socket-address":    socket_address,
+    "ipaddr-or-hostname":IpaddrOrHostname(),
+    "existing-directory":existing_directory,
+    "existing-path":     existing_path,
+    "existing-file":     existing_file,
+    "existing-dirpath":  existing_dirpath,
+    "constructor":       constructor_conversion,
+    "key-value":         space_sep_key_value_conversion,
+    "byte-size":         SuffixMultiplier({'kb': 1024,
+                                           'mb': 1024*1024,
+                                           'gb': 1024*1024*1024L,
+                                           }),
+    "time-interval":     SuffixMultiplier({'s': 1,
+                                           'm': 60,
+                                           'h': 60*60,
+                                           'd': 60*60*24,
+                                           }),
+    }
+
+class Registry:
+    def __init__(self, stock=None):
+        if stock is None:
+            stock = stock_datatypes.copy()
+        self._stock = stock
+        self._other = {}
+
+    def get(self, name):
+        t = self._stock.get(name)
+        if t is None:
+            t = self._other.get(name)
+            if t is None:
+                t = self.search(name)
+        return t
+
+    def register(self, name, conversion):
+        if self._stock.has_key(name):
+            raise ValueError("datatype name conflicts with built-in type: "
+                             + `name`)
+        if self._other.has_key(name):
+            raise ValueError("datatype name already registered:" + `name`)
+        self._other[name] = conversion
+
+    def search(self, name):
+        if not "." in name:
+            raise ValueError("unloadable datatype name: " + `name`)
+        components = name.split('.')
+        start = components[0]
+        g = globals()
+        package = __import__(start, g, g)
+        modulenames = [start]
+        for component in components[1:]:
+            modulenames.append(component)
+            try:
+                package = getattr(package, component)
+            except AttributeError:
+                n = '.'.join(modulenames)
+                package = __import__(n, g, g, component)
+        self._other[name] = package
+        return package


=== Packages/ZConfig/info.py 1.1 => 1.2 ===
--- /dev/null	Fri Jan  3 16:06:25 2003
+++ Packages/ZConfig/info.py	Fri Jan  3 16:05:51 2003
@@ -0,0 +1,363 @@
+##############################################################################
+#
+# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Objects that can describe a ZConfig schema."""
+
+import ZConfig
+
+
+try:
+    True
+except NameError:
+    True = 1
+    False = 0
+
+
+class UnboundedThing:
+    def __lt__(self, other):
+        return False
+
+    def __le__(self, other):
+        return isinstance(other, self.__class__)
+
+    def __gt__(self, other):
+        return True
+
+    def __ge__(self, other):
+        return True
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__)
+
+    def __ne__(self, other):
+        return not isinstance(other, self.__class__)
+
+    def __repr__(self):
+        return "<Unbounded>"
+
+Unbounded = UnboundedThing()
+
+
+class BaseInfo:
+    """Information about a single configuration key."""
+
+    description = None
+    example = None
+    metadefault = None
+
+    def __init__(self, name, datatype, minOccurs, maxOccurs, handler,
+                 attribute):
+        if maxOccurs is not None and maxOccurs < 1:
+            if maxOccurs < 1:
+                raise ZConfig.SchemaError(
+                    "maxOccurs must be at least 1")
+            if minOccurs is not None and minOccurs < maxOccurs:
+                raise ZConfig.SchemaError(
+                    "minOccurs must be at least maxOccurs")
+        self.name = name
+        self.datatype = datatype
+        self.minOccurs = minOccurs
+        self.maxOccurs = maxOccurs
+        self.handler = handler
+        self.attribute = attribute
+
+    def __repr__(self):
+        clsname = self.__class__.__name__
+        return "<%s for %s>" % (clsname, `self.name`)
+
+    def istypegroup(self):
+        return False
+
+    def ismulti(self):
+        return self.maxOccurs > 1
+
+    def issection(self):
+        return False
+
+
+class KeyInfo(BaseInfo):
+    def __init__(self, name, datatype, minOccurs, maxOccurs, handler,
+                 attribute):
+        assert minOccurs is not None
+        BaseInfo.__init__(self, name, datatype, minOccurs, maxOccurs,
+                          handler, attribute)
+        self._finished = False
+        self._default = None
+
+    def finish(self):
+        if self._finished:
+            raise ZConfig.SchemaError(
+                "cannot finish KeyInfo more than once")
+        self._finished = True
+
+    def adddefault(self, value):
+        if self._finished:
+            raise ZConfig.SchemaError(
+                "cannot add default values to finished KeyInfo")
+        if self.maxOccurs > 1:
+            if self._default is None:
+                self._default = [value]
+            else:
+                self._default.append(value)
+        elif self._default is not None:
+            raise ZConfig.SchemaError(
+                "cannot set more than one default to key with maxOccurs == 1")
+        else:
+            self._default = value
+
+    def getdefault(self):
+        if not self._finished:
+            raise ZConfig.SchemaError(
+                "cannot get default value of key before KeyInfo"
+                " has been completely initialized")
+        if self._default is None and self.maxOccurs > 1:
+            return []
+        else:
+            return self._default
+
+
+class SectionInfo(BaseInfo):
+    def __init__(self, name, sectiontype, minOccurs, maxOccurs, handler,
+                 attribute):
+        # name        - name of the section; one of '*', '+', or name1
+        # sectiontype - SectionType instance
+        # minOccurs   - minimum number of occurances of the section
+        # maxOccurs   - maximum number of occurances; if > 1, name
+        #               must be '*' or '+'
+        # handler     - handler name called when value(s) must take effect,
+        #               or None
+        # attribute   - name of the attribute on the SectionValue object
+        if maxOccurs > 1:
+            if name not in ('*', '+'):
+                raise ZConfig.SchemaError(
+                    "sections which can occur more than once must"
+                    " use a name of '*' or '+'")
+            if not attribute:
+                raise ZConfig.SchemaError(
+                    "sections which can occur more than once must"
+                    " specify a target attribute name")
+        if sectiontype.istypegroup():
+            datatype = None
+        else:
+            datatype = sectiontype.datatype
+        BaseInfo.__init__(self, name, datatype,
+                          minOccurs, maxOccurs, handler, attribute)
+        self.sectiontype = sectiontype
+
+    def __repr__(self):
+        clsname = self.__class__.__name__
+        return "<%s for %s (%s)>" % (
+            clsname, self.sectiontype.name, `self.name`)
+
+    def issection(self):
+        return True
+
+    def allowUnnamed(self):
+        return self.name == "*"
+
+    def isAllowedName(self, name):
+        if name == "*" or name == "+":
+            return False
+        elif self.name == "+":
+            return name and True or False
+        elif not name:
+            return self.name == "*"
+        else:
+            return name == self.name
+
+    def getdefault(self):
+        # sections cannot have defaults
+        if self.maxOccurs > 1:
+            return []
+        else:
+            return None
+
+class TypeContainer:
+    def __init__(self):
+        self._types = {}
+
+    def addtype(self, typeinfo):
+        n = typeinfo.name.lower()
+        if self._types.has_key(n):
+            raise ZConfig.SchemaError("type name cannot be redefined: "
+                                             + `typeinfo.name`)
+        self._types[n] = typeinfo
+
+    def gettype(self, name):
+        n = name.lower()
+        try:
+            return self._types[n]
+        except KeyError:
+            raise ZConfig.SchemaError("unknown type name: " + `name`)
+
+    def gettypenames(self):
+        return self._types.keys()
+
+
+class GroupType(TypeContainer):
+    def __init__(self, name):
+        TypeContainer.__init__(self)
+        self.name = name
+
+    def istypegroup(self):
+        return True
+
+
+class SectionType:
+    def __init__(self, name, keytype, valuetype, datatype):
+        # name      - name of the section, or '*' or '+'
+        # datatype  - type for the section itself
+        # keytype   - type for the keys themselves
+        # valuetype - default type for key values
+        self.name = name
+        self.datatype = datatype
+        self.keytype = keytype
+        self.valuetype = valuetype
+        self._children = []    # [(key, info), ...]
+        self._attrmap = {}     # {attribute: index, ...}
+        self._keymap = {}      # {key: index, ...}
+
+    def __len__(self):
+        return len(self._children)
+
+    def __getitem__(self, index):
+        return self._children[index]
+
+    def _add_child(self, key, info):
+        # check naming constraints
+        assert key or info.attribute
+        if key and self._keymap.has_key(key):
+            raise ZConfig.SchemaError(
+                "child name %s already used" % key)
+        if info.attribute and self._attrmap.has_key(info.attribute):
+            raise ZConfig.SchemaError(
+                "child attribute name %s already used" % info.attribute)
+        # a-ok, add the item to the appropriate maps
+        if info.attribute:
+            self._attrmap[info.attribute] = len(self._children)
+        if key:
+            self._keymap[key] = len(self._children)
+        self._children.append((key, info))
+
+    def addkey(self, keyinfo):
+        self._add_child(keyinfo.name, keyinfo)
+
+    def addsection(self, name, sectinfo):
+        assert name not in ("*", "+")
+        self._add_child(name, sectinfo)
+
+    def getinfo(self, key):
+        if not key:
+            raise ZConfig.ConfigurationError(
+                "cannot match a key without a name")
+        index = self._keymap.get(key)
+        if index is None:
+            raise ZConfig.ConfigurationError("no key matching " + `key`)
+        else:
+            return self._children[index][1]
+
+    def getchildnames(self):
+        return [key for (key, info) in self._children]
+
+    def getrequiredtypes(self):
+        d = {}
+        if self.name:
+            d[self.name] = 1
+        stack = [self]
+        while stack:
+            info = stack.pop()
+            for key, ci in info._children:
+                if ci.issection():
+                    t = ci.sectiontype
+                    if not d.has_key(t.name):
+                        d[t.name] = 1
+                        stack.append(t)
+        return d.keys()
+
+    def getsectionindex(self, type, name):
+        index = -1
+        for key, info in self._children:
+            index += 1
+            if key:
+                if key == name:
+                    if not info.issection():
+                        raise ZConfig.ConfigurationError(
+                            "section name %s already in use for key" % key)
+                    st = info.sectiontype
+                    if st.istypegroup():
+                        try:
+                            st = st.gettype(type)
+                        except ZConfig.ConfigurationError:
+                            raise ZConfig.ConfigurationError(
+                                "section type %s not allowed for name %s"
+                                % (`type`, `key`))
+                    if not st.name == type:
+                        raise ZConfig.ConfigurationError(
+                            "name %s must be used for a %s section"
+                            % (`name`, `st.name`))
+                    return index
+            # else must be a section or a sectiongroup:
+            elif info.sectiontype.name == type:
+                if not (name or info.allowUnnamed()):
+                    raise ZConfig.ConfigurationError(
+                        `type` + " sections must be named")
+                return index
+            elif info.sectiontype.istypegroup():
+                st = info.sectiontype
+                if st.name == type:
+                    raise ZConfig.ConfigurationError(
+                        "cannot define section with a sectiongroup type")
+                try:
+                    st = st.gettype(type)
+                except ZConfig.ConfigurationError:
+                    # not this one; maybe a different one
+                    pass
+                else:
+                    return index
+        raise ZConfig.ConfigurationError("no matching section defined")
+
+    def getsectioninfo(self, type, name):
+        i = self.getsectionindex(type, name)
+        st = self._children[i][1]
+        if st.istypegroup():
+            st = st.gettype(type)
+        return st
+
+    def istypegroup(self):
+        return False
+
+
+class SchemaType(TypeContainer, SectionType):
+    def __init__(self, name, keytype, valuetype, datatype, handler, url):
+        SectionType.__init__(self, name, keytype, valuetype, datatype)
+        TypeContainer.__init__(self)
+        self.handler = handler
+        self.url = url
+
+    def allowUnnamed(self):
+        return True
+
+    def isAllowedName(self, name):
+        return False
+
+    def issection(self):
+        return True
+
+    def getunusedtypes(self):
+        alltypes = self.gettypenames()
+        reqtypes = self.getrequiredtypes()
+        for n in reqtypes:
+            alltypes.remove(n)
+        if self.name and self.name in alltypes:
+            alltypes.remove(self.name)
+        return alltypes


=== Packages/ZConfig/loader.py 1.1 => 1.2 ===
--- /dev/null	Fri Jan  3 16:06:25 2003
+++ Packages/ZConfig/loader.py	Fri Jan  3 16:05:51 2003
@@ -0,0 +1,242 @@
+##############################################################################
+#
+# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Schema loader utility."""
+
+import os.path
+import urllib
+import urllib2
+
+import ZConfig
+
+from ZConfig import datatypes
+from ZConfig import matcher
+from ZConfig.url import urlnormalize, urldefrag, urljoin, urlsplit, urlunsplit
+
+try:
+    True
+except NameError:
+    True = 1
+    False = 0
+
+
+RESOURCE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+                            "resources")
+
+
+def loadSchema(url):
+    return SchemaLoader().loadURL(url)
+
+def loadSchemaFile(file, url=None):
+    return SchemaLoader().loadFile(file, url)
+
+def loadConfig(schema, url):
+    return ConfigLoader(schema).loadURL(url)
+
+def loadConfigFile(schema, file, url=None):
+    return ConfigLoader(schema).loadFile(file, url)
+
+
+class BaseLoader:
+    def __init__(self):
+        pass
+
+    def createResource(self, file, url, fragment=None):
+        return Resource(file, url, fragment)
+
+    def loadURL(self, url):
+        url = self.normalizeURL(url)
+        r = self.openResource(url)
+        try:
+            return self.loadResource(r)
+        finally:
+            r.close()
+
+    def loadFile(self, file, url=None):
+        if not url:
+            url = _url_from_file(file)
+        r = self.createResource(file, url)
+        return self.loadResource(r)
+
+    # utilities
+
+    def loadResource(self, resource):
+        raise NotImpementedError(
+            "BaseLoader.loadResource() must be overridden by a subclass")
+
+    def openResource(self, url):
+        # XXX This should be replaced to use a local cache for remote
+        # resources.  The policy needs to support both re-retrieve on
+        # change and provide the cached resource when the remote
+        # resource is not accessible.
+        parts = list(urlsplit(url))
+        fragment = parts[-1]
+        if fragment:
+            parts[-1] = ''
+            url = urlunsplit(tuple(parts))
+        if parts[0] == 'zconfig':
+            file = _open_resource_file(url)
+        else:
+            file = urllib2.urlopen(url)
+        return self.createResource(file, url, fragment or None)
+
+    def normalizeURL(self, url):
+        if os.path.exists(url):
+            url = "file://" + urllib.pathname2url(os.path.abspath(url))
+        else:
+            url = urlnormalize(url)
+        if url and not self.allowFragments():
+            url, fragment = urldefrag(url)
+            if fragment:
+                raise ZConfig.ConfigurationError(
+                    "fragment identifiers are not supported")
+        return url
+
+    def allowFragments(self):
+        return False
+
+
+def _url_from_file(file):
+    name = getattr(file, "name", None)
+    if name and name[0] != "<" and name[-1] != ">":
+        return "file://" + urllib.pathname2url(os.path.abspath(name))
+    else:
+        return None
+
+
+def _open_resource_file(url):
+    parts = urlsplit(url)
+    assert parts[0] == 'zconfig'
+    fn = os.path.join(RESOURCE_DIR, parts[2])
+    if not os.path.isfile(fn):
+        raise ValueError("no such resource: " + `parts[2]`)
+    return open(fn)
+
+
+class SchemaLoader(BaseLoader):
+    def __init__(self, registry=None):
+        if registry is None:
+            registry = datatypes.Registry()
+        self.registry = registry
+        self._cache = {}
+        BaseLoader.__init__(self)
+
+    def loadResource(self, resource):
+        if resource.url and self._cache.has_key(resource.url):
+            schema = self._cache[resource.url]
+        else:
+            from ZConfig.schema import parseResource
+            schema = parseResource(resource, self.registry, self)
+            self._cache[resource.url] = schema
+        if resource.fragment:
+            type = self.registry.get("basic-key")(resource.fragment)
+            schema = schema.gettype(type)
+        return schema
+
+    def allowFragments(self):
+        return True
+
+
+class ConfigLoader(BaseLoader):
+    def __init__(self, schema):
+        if schema.istypegroup():
+            raise ZConfig.SchemaError(
+                "cannot check a configuration an abstract type")
+        BaseLoader.__init__(self)
+        self.schema = schema
+
+    def loadResource(self, resource):
+        self.handlers = []
+        sm = matcher.SchemaMatcher(self.schema, self.handlers)
+        self._parse_resource(sm, resource)
+        return sm.finish(), CompositeHandler(self.handlers, self.schema)
+
+    # parser support API
+
+    def startSection(self, parent, type, name, delegatename):
+        if delegatename:
+            raise NotImpementedError("section delegation is not yet supported")
+        t = self.schema.gettype(type)
+        if t.istypegroup():
+            raise ZConfig.ConfigurationError(
+                "concrete sections cannot match abstract section types;"
+                " found abstract type " + `type`)
+        ci = parent.type.getsectioninfo(type, name)
+        assert not ci.istypegroup()
+        if not ci.isAllowedName(name):
+            raise ZConfig.ConfigurationError(
+                "%s is not an allowed name for %s sections"
+                % (`name`, `ci.sectiontype.name`))
+        return matcher.SectionMatcher(ci, t, name, self.handlers)
+
+    def endSection(self, parent, type, name, delegatename, matcher):
+        assert not delegatename
+        sectvalue = matcher.finish()
+        parent.addSection(type, name, sectvalue)
+
+    def includeConfiguration(self, section, url):
+        r = self.openResource(url)
+        self._parse_resource(section, r)
+
+    # internal helper
+
+    def _parse_resource(self, matcher, resource):
+        from ZConfig.cfgparser import ZConfigParser
+        parser = ZConfigParser(resource, self)
+        parser.parse(matcher)
+
+
+class CompositeHandler:
+    def __init__(self, handlers, schema):
+        self._handlers = handlers
+        self._convert = schema.registry.get("basic-key")
+
+    def __call__(self, handlermap):
+        d = {}
+        for name, callback in handlermap.items():
+            n = self._convert(name)
+            if d.has_key(n):
+                raise ZConfig.ConfigurationError(
+                    "handler name not unique when converted to a basic-key: "
+                    + `name`)
+            d[n] = callback
+        L = []
+        for handler, value in self._handlers:
+            if not d.has_key(handler):
+                L.append(handler)
+        if L:
+            raise ZConfig.ConfigurationError(
+                "undefined handlers: " + ", ".join(L))
+        for handler, value in self._handlers:
+            f = d[handler]
+            if f is not None:
+                f(value)
+
+    def __len__(self):
+        return len(self._handlers)
+
+
+class Resource:
+    def __init__(self, file, url, fragment=None):
+        self.file = file
+        self.url = url
+        self.fragment = fragment
+
+    def close(self):
+        if self.file is not None:
+            self.file.close()
+            self.file = None
+            self.closed = True
+
+    def __getattr__(self, name):
+        return getattr(self.file, name)


=== Packages/ZConfig/matcher.py 1.1 => 1.2 ===
--- /dev/null	Fri Jan  3 16:06:25 2003
+++ Packages/ZConfig/matcher.py	Fri Jan  3 16:05:51 2003
@@ -0,0 +1,284 @@
+##############################################################################
+#
+# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Utility that manages the binding of configuration data to a section."""
+
+import types
+
+import ZConfig
+
+
+class BaseMatcher:
+    def __init__(self, info, type, handlers):
+        self.info = info
+        self.type = type
+        self._values = [None] * len(type)
+        self._sectionnames = {}
+        if handlers is None:
+            handlers = []
+        self._handlers = handlers
+
+    def __repr__(self):
+        clsname = self.__class__.__name__
+        extra = "type " + `self.type.name`
+        return "<%s for %s>" % (clsname, extra)
+
+    def addSection(self, type, name, sectvalue):
+        if name:
+            if self._sectionnames.has_key(name):
+                raise ZConfig.ConfigurationError(
+                    "section names must not be re-used within the"
+                    " same container:" + `name`)
+            self._sectionnames[name] = name
+        i = self.type.getsectionindex(type, name)
+        ci = self.type.getsectioninfo(type, name)
+        v = self._values[i]
+        if v is None:
+            if ci.ismulti():
+                self._values[i] = [sectvalue]
+            else:
+                self._values[i] = sectvalue
+        elif ci.ismulti():
+            v.append(sectvalue)
+        else:
+            raise ZConfig.ConfigurationError(
+                "too many instances of %s section" % `ci.sectiontype.name`)
+
+    def addValue(self, key, value):
+        length = len(self.type)
+        arbkey_info = None
+        for i in range(length):
+            k, ci = self.type[i]
+            if k == key:
+                break
+            if ci.name == "+" and not ci.issection():
+                arbkey_info = i, k, ci
+        else:
+            if arbkey_info is None:
+                raise ZConfig.ConfigurationError(
+                    `key` + " is not a known key name")
+            i, k, ci = arbkey_info
+        if ci.issection():
+            if ci.name:
+                extra = " in %s sections" % `self.type.name`
+            else:
+                extra = ""
+            raise ZConfig.ConfigurationError(
+                "%s is not a valid key name%s" % (`key`, extra))
+
+        ismulti = ci.ismulti()
+        v = self._values[i]
+        if v is None:
+            if k == '+':
+                v = {}
+            elif ismulti:
+                v = []
+            self._values[i] = v
+        elif not ismulti:
+            if k != '+':
+                raise ZConfig.ConfigurationError(
+                    `key` + " does not support multiple values")
+        elif len(v) == ci.maxOccurs:
+            raise ZConfig.ConfigurationError(
+                "too many values for " + `name`)
+
+        if k == '+':
+            if ismulti:
+                if v.has_key(key):
+                    v[key].append(value)
+                else:
+                    v[key] = [value]
+            else:
+                if v.has_key(key):
+                    raise ZConfig.ConfigurationError("too many")
+                v[key] = value
+        elif ismulti:
+            v.append(value)
+        else:
+            self._values[i] = value
+
+    def finish(self):
+        """Check the constraints of the section and convert to an application
+        object."""
+        length = len(self.type)
+        values = self._values
+        attrnames = [None] * length
+        for i in range(length):
+            key, ci = self.type[i]
+            attrnames[i] = ci.attribute or key
+            v = values[i]
+            if v is None and ci.name == '+' and not ci.issection():
+                if ci.minOccurs > 0:
+                    raise ZConfig.ConfigurationError(
+                        "no keys defined for the %s key/value map; at least %d"
+                        " must be specified" % (ci.attribute, ci.minOccurs))
+                v = {}
+                values[i] = v
+            if v is None and ci.minOccurs:
+                default = ci.getdefault()
+                if default is None:
+                    if key:
+                        s = `key`
+                    else:
+                        s = "section type " + `ci.typename`
+                    raise ZConfig.ConfigurationError(
+                        "no values for %s; %s required" % (s, ci.minOccurs))
+                else:
+                    v = values[i] = default[:]
+            if ci.ismulti():
+                if v is None:
+                    v = values[i] = ci.getdefault()[:]
+                if len(v) < ci.minOccurs:
+                    raise ZConfig.ConfigurationError(
+                        "not enough values for %s; %d found, %d required"
+                        % (`key`, len(v), ci.minOccurs))
+            if v is None and not ci.issection():
+                if ci.ismulti():
+                    v = ci.getdefault()[:]
+                else:
+                    v = ci.getdefault()
+                values[i] = v
+        return self.constuct(attrnames)
+
+    def constuct(self, attrnames):
+        values = self._values
+        for i in range(len(values)):
+            name, ci = self.type[i]
+            if ci.ismulti():
+                if ci.issection():
+                    v = []
+                    for s in values[i]:
+                        if s is not None:
+                            v.append(s._type.datatype(s))
+                        else:
+                            v.append(None)
+                elif ci.name == '+':
+                    v = values[i]
+                    for key, val in v.items():
+                        v[key] = [ci.datatype(s) for s in val]
+                else:
+                    v = [ci.datatype(s) for s in values[i]]
+            elif ci.issection():
+                if values[i] is not None:
+                    v = values[i]._type.datatype(values[i])
+                else:
+                    v = None
+            elif name == '+':
+                v = values[i]
+                for key, val in v.items():
+                    v[key] = ci.datatype(val)
+            else:
+                v = values[i]
+                if v is not None:
+                    v = ci.datatype(v)
+            values[i] = v
+            if ci.handler is not None:
+                self._handlers.append((ci.handler, v))
+        return self.createValue(attrnames)
+
+    def createValue(self, attrnames):
+        return SectionValue(attrnames, self._values, None, self.type)
+
+
+class SectionMatcher(BaseMatcher):
+    def __init__(self, info, type, name, handlers):
+        if name or info.allowUnnamed():
+            self.name = name
+        else:
+            raise ZConfig.ConfigurationError(
+                `type.name` + " sections may not be unnamed")
+        BaseMatcher.__init__(self, info, type, handlers)
+
+    def createValue(self, attrnames):
+        return SectionValue(attrnames, self._values, self.name, self.type)
+
+
+class SchemaMatcher(BaseMatcher):
+    def __init__(self, info, handlers=None):
+        BaseMatcher.__init__(self, info, info, handlers)
+
+    def finish(self):
+        # Since there's no outer container to call datatype()
+        # for the schema, we convert on the way out.
+        v = BaseMatcher.finish(self)
+        v = self.type.datatype(v)
+        if self.type.handler is not None:
+            self._handlers.append((self.type.handler, v))
+        return v
+
+
+class SectionValue:
+    """Generic 'bag-of-values' object for a section.
+
+    Derived classes should always call the SectionValue constructor
+    before attempting to modify self.
+    """
+
+    def __init__(self, attrnames, values, name, type):
+        d = self.__dict__
+        d['_attrnames'] = attrnames
+        d['_values'] = values
+        d['_name'] = name
+        d['_type'] = type
+
+    def __repr__(self):
+        if self._name:
+            # probably unique for a given config file; more readable than id()
+            name = `self._name`
+        else:
+            # identify uniquely
+            name = "at %#x" % id(self)
+        clsname = self.__class__.__name__
+        return "<%s for %s %s>" % (clsname, self._type.name, name)
+
+    def __len__(self):
+        return len(self._values)
+
+    def __getitem__(self, index):
+        if isinstance(index, types.SliceType):
+            raise TypeError("SectionValue does not support slicing")
+        return self._values[index]
+
+    def __setitem__(self, index, value):
+        if isinstance(index, types.SliceType):
+            raise TypeError("SectionValue does not support slicing")
+        self._values[index] = value
+
+    def __getattr__(self, name):
+        try:
+            i = self._attrnames.index(name)
+        except ValueError:
+            raise AttributeError, name
+        return self._values[i]
+
+    def __setattr__(self, name, value):
+        try:
+            i = self._attrnames.index(name)
+        except ValueError:
+            self.__dict__[name] = value
+        else:
+            self._values[i] = value
+
+    def __str__(self):
+        l = []
+        for i in range(len(self._attrnames)):
+            k = self._attrnames[i]
+            v = self._values[i]
+            l.append('%-40s: %s' % (k, v))
+        return '\n'.join(l)
+
+    def getSectionName(self):
+        return self._name
+
+    def getSectionType(self):
+        return self._type.name


=== Packages/ZConfig/schema.py 1.1 => 1.2 ===
--- /dev/null	Fri Jan  3 16:06:25 2003
+++ Packages/ZConfig/schema.py	Fri Jan  3 16:05:51 2003
@@ -0,0 +1,393 @@
+##############################################################################
+#
+# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Parser for ZConfig schemas."""
+
+import xml.sax
+import xml.sax.saxutils
+
+import ZConfig
+
+from ZConfig import info
+from ZConfig import url
+
+
+try:
+    True
+except NameError:
+    True = 1
+    False = 0
+
+try:
+    dict
+except NameError:
+    def dict(mapping):
+        d = {}
+        for k, v in mapping.items():
+            d[k] = v
+        return d
+
+
+def parseResource(resource, registry, loader):
+    parser = SchemaParser(registry, loader, resource.url)
+    xml.sax.parse(resource.file, parser)
+    return parser._schema
+
+
+class SchemaParser(xml.sax.ContentHandler):
+
+    _cdata_tags = "description", "metadefault", "example", "default"
+    _handled_tags = ("schema", "import", "sectiongroup", "sectiontype",
+                     "key", "multikey", "section", "multisection")
+
+    def __init__(self, registry, loader, url):
+        self._registry = registry
+        self._loader = loader
+        self._basic_key = registry.get("basic-key")
+        self._identifier = registry.get("identifier")
+        self._cdata = None
+        self._locator = None
+        self._prefixes = []
+        self._schema = None
+        self._stack = []
+        self._group = None
+        self._url = url
+
+    # SAX 2 ContentHandler methods
+
+    def setDocumentLocator(self, locator):
+        self._locator = locator
+
+    def startElement(self, name, attrs):
+        attrs = dict(attrs)
+        if name == "schema":
+            if self._schema is not None:
+                self.error("schema element improperly nested")
+            self.start_schema(attrs)
+        elif name in self._handled_tags:
+            if self._schema is None:
+                self.error(name + " element outside of schema")
+            getattr(self, "start_" + name)(attrs)
+        elif name in self._cdata_tags:
+            if self._schema is None:
+                self.error(name + " element outside of schema")
+            if self._cdata is not None:
+                self.error(name + " element improperly nested")
+            self._cdata = []
+        else:
+            self.error("Unknown tag " + name)
+
+    def characters(self, data):
+        if self._cdata is not None:
+            self._cdata.append(data)
+        elif data.strip():
+            self.error("unexpected non-blank character data: "
+                       + `data.strip()`)
+
+    def endElement(self, name):
+        if name in self._handled_tags:
+            getattr(self, "end_" + name)()
+        else:
+            data = ''.join(self._cdata).strip()
+            self._cdata = None
+            if name == "default":
+                # value for a key
+                self._stack[-1].adddefault(data)
+            else:
+                setattr(self._stack[-1], name, data)
+
+    def endDocument(self):
+        if self._schema is None:
+            self.error("no schema found")
+
+    # schema loading logic
+
+    def start_import(self, attrs):
+        src = attrs.get("src", "").strip()
+        pkg = attrs.get("package", "").strip()
+        if not (src or pkg):
+            self.error("import must specify either src or package")
+        if src and pkg:
+            self.error("import may only specify one of src or package")
+        if src:
+            src = url.urljoin(self._url, src)
+            src, fragment = url.urldefrag(src)
+            if fragment:
+                self.error("import src many not include a fragment identifier")
+            urls = [src]
+        else:
+            raise NotImpementedError(
+                "<import package='...'/> not yet implemented")
+        for s in urls:
+            schema = self._loader.loadURL(s)
+            for n in schema.gettypenames():
+                self._schema.addtype(schema.gettype(n))
+
+    def end_import(self):
+        pass
+
+    def get_handler(self, attrs):
+        v = attrs.get("handler")
+        if v is None:
+            return v
+        else:
+            return self.basic_key(v)
+
+    def push_prefix(self, attrs):
+        name = attrs.get("prefix")
+        if name:
+            if name.startswith(".") and self._prefixes:
+                prefix = self._prefixes[-1] + name
+            elif name.startswith("."):
+                self.error("prefix may not begin with '.'")
+            else:
+                prefix = name
+        elif self._prefixes:
+            prefix = self._prefixes[-1]
+        else:
+            prefix = ''
+        self._prefixes.append(prefix)
+
+    def get_classname(self, name):
+        if name.startswith("."):
+            return self._prefixes[-1] + name
+        else:
+            return name
+
+    def get_datatype(self, attrs, attrkey, default):
+        if attrs.has_key(attrkey):
+            dtname = self.get_classname(attrs[attrkey])
+        else:
+            dtname = default
+
+        try:
+            return self._registry.get(dtname)
+        except ValueError, e:
+            self.error(e[0])
+
+    def get_sect_typeinfo(self, attrs):
+        keytype = self.get_datatype(attrs, "keytype", "basic-key")
+        valuetype = self.get_datatype(attrs, "valuetype", "string")
+        datatype = self.get_datatype(attrs, "datatype", "null")
+        return keytype, valuetype, datatype
+
+    def start_schema(self, attrs):
+        self.push_prefix(attrs)
+        handler = self.get_handler(attrs)
+        keytype, valuetype, datatype = self.get_sect_typeinfo(attrs)
+        name = attrs.get("type")
+        if name is not None:
+            name = self.basic_key(name)
+        self._schema = info.SchemaType(name, keytype, valuetype, datatype,
+                                       handler, self._url)
+        if name is not None:
+            # XXX circular reference
+            self._schema.addtype(self._schema)
+        self._schema.registry = self._registry
+        self._stack = [self._schema]
+
+    def end_schema(self):
+        del self._prefixes[-1]
+        assert not self._prefixes
+
+    def start_sectiontype(self, attrs):
+        name = attrs.get("type")
+        if not name:
+            self.error("sectiontype type must not be omitted or empty")
+        name = self.basic_key(name)
+        self.push_prefix(attrs)
+        keytype, valuetype, datatype = self.get_sect_typeinfo(attrs)
+        sectinfo = info.SectionType(name, keytype, valuetype, datatype)
+        self._schema.addtype(sectinfo)
+        if self._group is not None:
+            if attrs.has_key("group"):
+                self.error("sectiontype cannot specify group"
+                           " if nested in a sectiongroup")
+            self._group.addtype(sectinfo)
+        elif attrs.has_key("group"):
+            groupname = self.basic_key(attrs["group"])
+            group = self._schema.gettype(groupname)
+            if not group.istypegroup():
+                self.error("type specified as group is not a sectiongroup")
+            group.addtype(sectinfo)
+        self._stack.append(sectinfo)
+
+    def end_sectiontype(self):
+        del self._prefixes[-1]
+        self._stack.pop()
+
+    def get_required(self, attrs):
+        if attrs.has_key("required"):
+            v = attrs["required"]
+            if v == "yes":
+                return True
+            elif v == "no":
+                return False
+            self.error("value for 'required' must be 'yes' or 'no'")
+        else:
+            return False
+
+    def get_ordinality(self, attrs):
+        min, max = 0, info.Unbounded
+        if self.get_required(attrs):
+            min = 1
+        return min, max
+
+    def get_sectiontype(self, attrs):
+        type = attrs.get("type")
+        if not type:
+            self.error("section must specify type")
+        return self._schema.gettype(type)
+
+    def start_section(self, attrs):
+        sectiontype = self.get_sectiontype(attrs)
+        handler = self.get_handler(attrs)
+        min = self.get_required(attrs) and 1 or 0
+        any, name, attribute = self.get_name_info(attrs, "section")
+        if any and not attribute:
+            self.error(
+                "attribute must be specified if section name is '*' or '+'")
+        section = info.SectionInfo(any or name, sectiontype,
+                                   min, 1, handler, attribute)
+        self._stack[-1].addsection(name, section)
+        self._stack.append(section)
+
+    def end_section(self):
+        self._stack.pop()
+
+    def start_multisection(self, attrs):
+        sectiontype = self.get_sectiontype(attrs)
+        min, max = self.get_ordinality(attrs)
+        any, name, attribute = self.get_name_info(attrs, "multisection")
+        if any not in ("*", "+"):
+            self.error("multisection must specify '*' or '+' for the name")
+        handler = self.get_handler(attrs)
+        section = info.SectionInfo(any or name, sectiontype,
+                                   min, max, handler, attribute)
+        self._stack[-1].addsection(name, section)
+        self._stack.append(section)
+
+    def end_multisection(self):
+        self._stack.pop()
+
+    def start_sectiongroup(self, attrs):
+        if self._group is not None:
+            self.error("sectiongroup elements cannot be nested")
+        self.push_prefix(attrs)
+        name = attrs.get("type")
+        if not name:
+            self.error("sectiongroup must be named")
+        name = self.basic_key(name)
+        self._group = info.GroupType(name)
+        self._schema.addtype(self._group)
+        self._stack.append(self._group)
+
+    def end_sectiongroup(self):
+        del self._prefixes[-1]
+        self._group = None
+        self._stack.pop()
+
+    def get_key_info(self, attrs, element):
+        any, name, attribute = self.get_name_info(attrs, element)
+        if any == '*':
+            self.error(element + " may not specify '*' for name")
+        if not name and any != '+':
+            self.error(element + " name may not be omitted or empty")
+        datatype = self.get_datatype(attrs, "datatype", "string")
+        handler = self.get_handler(attrs)
+        return name or any, datatype, handler, attribute
+
+    def start_key(self, attrs):
+        name, datatype, handler, attribute = self.get_key_info(attrs, "key")
+        min = self.get_required(attrs) and 1 or 0
+        key = info.KeyInfo(name, datatype, min, 1, handler, attribute)
+        if attrs.has_key("default"):
+            if min:
+                self.error("required key cannot have a default value")
+            key.adddefault(str(attrs["default"]).strip())
+        key.finish()
+        self._stack[-1].addkey(key)
+        self._stack.append(key)
+
+    def end_key(self):
+        self._stack.pop()
+
+    def start_multikey(self, attrs):
+        if attrs.has_key("default"):
+            self.error("default values for multikey must be given using"
+                       " 'default' elements")
+        name, datatype, handler, attribute = self.get_key_info(attrs,
+                                                               "multikey")
+        min, max = self.get_ordinality(attrs)
+        key = info.KeyInfo(name, datatype, min, max, handler, attribute)
+        self._stack[-1].addkey(key)
+        self._stack.append(key)
+
+    def end_multikey(self):
+        self._stack.pop().finish()
+
+    def get_name_info(self, attrs, element):
+        name = attrs.get("name")
+        if not name:
+            self.error(element + " name must be specified and non-empty")
+        aname = attrs.get("attribute")
+        if aname:
+            aname = self.identifier(aname)
+            if aname.startswith("getSection"):
+                # reserved; used for SectionValue methods to get meta-info
+                self.error("attribute names may not start with 'getSection'")
+        if name in ("*", "+"):
+            if not aname:
+                self.error(
+                    "container attribute must be specified and non-empty"
+                    " when using '*' or '+' for a section name")
+            return name, None, aname
+        else:
+            # run the keytype converter to make sure this is a valid key
+            name = self._stack[-1].keytype(name)
+            if not aname:
+                aname = self.basic_key(name)
+                aname = self.identifier(aname.replace('-', '_'))
+            return None, self.basic_key(name), aname
+
+    # datatype conversion wrappers
+
+    def basic_key(self, s):
+        try:
+            return self._basic_key(s)
+        except ValueError, e:
+            self.error(e[0])
+        except ZConfig.SchemaError, e:
+            self.initerror(e)
+            raise
+
+    def identifier(self, s):
+        try:
+            return self._identifier(s)
+        except ValueError, e:
+            self.error(e[0])
+        except ZConfig.SchemaError, e:
+            self.initerror(e)
+            raise
+
+    # exception setup helpers
+
+    def initerror(self, e):
+        if self._locator is not None:
+            e.colno = self._locator.getColumnNumber()
+            e.lineno = self._locator.getLineNumber()
+            e.url = self._locator.getSystemId()
+        return e
+
+    def error(self, message):
+        raise self.initerror(ZConfig.SchemaError(message))


=== Packages/ZConfig/substitution.py 1.1 => 1.2 ===
--- /dev/null	Fri Jan  3 16:06:25 2003
+++ Packages/ZConfig/substitution.py	Fri Jan  3 16:05:51 2003
@@ -0,0 +1,91 @@
+##############################################################################
+#
+# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Substitution support for ZConfig values."""
+
+import ZConfig
+
+try:
+    True
+except NameError:
+    True = 1
+    False = 0
+
+
+def substitute(s, mapping):
+    """Interpolate variables from `section` into `s`."""
+    if "$" in s:
+        result = ''
+        rest = s
+        while rest:
+            p, name, rest = _split(rest)
+            result += p
+            if name:
+                v = mapping.get(name)
+                if v is None:
+                    raise ZConfig.SubstitutionReplacementError(s, name)
+                result += v
+        return result
+    else:
+        return s
+
+
+def isname(s):
+    """Return True iff s is a valid substitution name."""
+    m = _name_match(s)
+    if m:
+        return m.group() == s
+    else:
+        return False
+
+
+def _split(s):
+    # Return a triple:  prefix, name, suffix
+    # - prefix is text that can be used literally in the result (may be '')
+    # - name is a referenced name, or None
+    # - suffix is trailling text that may contain additional references
+    #   (may be '' or None)
+    if "$" in s:
+        i = s.find("$")
+        c = s[i+1:i+2]
+        if c == "":
+            raise ZConfig.SubstitutionSyntaxError(
+                "illegal lone '$' at end of source")
+        if c == "$":
+            return s[:i+1], None, s[i+2:]
+        prefix = s[:i]
+        if c == "{":
+            m = _name_match(s, i + 2)
+            if not m:
+                raise ZConfig.SubstitutionSyntaxError(
+                    "'${' not followed by name")
+            name = m.group(0)
+            i = m.end() + 1
+            if not s.startswith("}", i - 1):
+                raise ZConfig.SubstitutionSyntaxError(
+                    "'${%s' not followed by '}'" % name)
+        else:
+            m = _name_match(s, i+1)
+            if not m:
+                raise ZConfig.SubstitutionSyntaxError(
+                    "'$' not followed by '$' or name")
+            name = m.group(0)
+            i = m.end()
+        return prefix, name.lower(), s[i:]
+    else:
+        return s, None, None
+
+
+import re
+_name_match = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*").match
+del re


=== Packages/ZConfig/url.py 1.1 => 1.2 ===
--- /dev/null	Fri Jan  3 16:06:25 2003
+++ Packages/ZConfig/url.py	Fri Jan  3 16:05:51 2003
@@ -0,0 +1,94 @@
+"""urlparse-like helpers that support the zconfig scheme."""
+
+import posixpath as _posixpath
+import urlparse as _urlparse
+
+from urllib import splittype as _splittype
+
+
+def urlnormalize(url):
+    parts = urlsplit(url)
+    if not parts[0]:
+        raise ValueError("invalid URL, or file does not exist:\n"
+                         + repr(url))
+    url = urlunsplit(parts)
+    if url.startswith("file:/") and not url.startswith("file:///"):
+        url = "file://" + url[5:]
+    return url
+
+
+def urlunsplit(parts):
+    url = _urlparse.urlunsplit(parts)
+    if (  parts[0] == "file"
+          and url.startswith("file:/")
+          and not url.startswith("file:///")):
+        url = "file://" + url[5:]
+    return url
+
+
+def urldefrag(url):
+    parts = urlsplit(url)
+    if parts[0] == "zconfig":
+        return "zconfig:" + parts[2], parts[4]
+    else:
+        url, fragment = _urlparse.urldefrag(url)
+        return urlnormalize(url), fragment
+
+
+def urljoin(base, relurl):
+    scheme = _splittype(base)[0]
+    if scheme != "zconfig":
+        url = _urlparse.urljoin(base, relurl)
+        if url.startswith("file:/") and not url.startswith("file:///"):
+            url = "file://" + url[5:]
+        return url
+    relscheme = _splittype(relurl)[0]
+    if relscheme and relscheme != "zconfig":
+        return _urlparse.urljoin(base, relurl)
+    baseparts = urlsplit(base)
+    relparts = urlsplit(relurl, "zconfig")
+    if relparts[2]:
+        d = _posixpath.dirname(baseparts[2])
+        if d:
+            d += "/"
+        path = _posixpath.normpath(_posixpath.join(d, relparts[2]))
+    else:
+        path = baseparts[2]
+    parts = path.split('/')
+    if '..' in parts:
+        raise ValueError("zconfig URIs cannot include '..' references: "
+                         + `path`)
+    s = "zconfig:" + path
+    if relparts[4]:
+        s += "#" + relparts[4]
+    return s
+
+
+def urlsplit(url, scheme=''):
+    stated_scheme = _splittype(url)[0]
+    scheme = stated_scheme or scheme
+    parts = _urlparse.urlsplit(url, scheme=scheme)
+    if scheme == "zconfig":
+        path = parts[2]
+        if stated_scheme:
+            # These constraints only apply to absolute zconfig: URLs
+            if not path:
+                # Require a non-empty path; empty path is ok for
+                # relative URL ("#frag").
+                raise ValueError(
+                    "zconfig URIs require a non-empty path component")
+            if '..' in path.split('/'):
+                raise ValueError(
+                    "zconfig URIs cannot include '..' references: " + `url`)
+        # Split the fragment ourselves since the urlparse module
+        # doesn't know about the zconfig: scheme.
+        if '#' in path:
+            path, fragment = path.split('#', 1)
+            parts = "zconfig", '', path, '', fragment
+        if path[:1] == '/':
+            raise ValueError(
+                "path component of zconfig: URIs may not start with '/'")
+        if '?' in path:
+            raise ValueError("zconfig: URIs may not contain queries: "
+                             + `url`)
+    return parts


=== Packages/ZConfig/Config.py 1.14 => 1.15 ===
--- Packages/ZConfig/Config.py:1.14	Thu Dec  5 00:17:45 2002
+++ Packages/ZConfig/Config.py	Fri Jan  3 16:05:51 2003
@@ -1,12 +1,22 @@
+##############################################################################
+#
+# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
 """Configuration data structure."""
 
 import ZConfig
 
-try:
-    True
-except NameError:
-    True = 1
-    False = 0
+from ZConfig.datatypes import asBoolean
+
 
 class Configuration:
     def __init__(self, container, type, name, url):
@@ -103,12 +113,10 @@
 
     def has_key(self, key):
         key = key.lower()
-        if self._data.has_key(key):
-            return True
-        elif self.delegate:
-            return self.delegate.has_key(key)
-        else:
-            return False
+        have = self._data.has_key(key)
+        if self.delegate and not have:
+            have = self.delegate.has_key(key)
+        return have
 
     def items(self):
         """Returns a list of key-value pairs for this section.
@@ -190,32 +198,3 @@
             return default
         else:
             return s.split()
-
-
-class ImportingConfiguration(Configuration):
-    def __init__(self, url):
-        self._imports = []
-        Configuration.__init__(self, None, None, None, url)
-
-    def addImport(self, section):
-        self._imports.append(section)
-
-    def get(self, key, default=None):
-        s = Configuration.get(self, key, default)
-        if s is default:
-            for config in self._imports:
-                s = config.get(key, default)
-                if s is not default:
-                    break
-        return s
-
-
-def asBoolean(s):
-    """Convert a string value to a boolean value."""
-    ss = str(s).lower()
-    if ss in ('yes', 'true', 'on'):
-        return True
-    elif ss in ('no', 'false', 'off'):
-        return False
-    else:
-        raise ValueError("not a valid boolean value: " + repr(s))


=== Packages/ZConfig/Context.py 1.17 => 1.18 ===
--- Packages/ZConfig/Context.py:1.17	Thu Jan  2 13:29:55 2003
+++ Packages/ZConfig/Context.py	Fri Jan  3 16:05:51 2003
@@ -1,40 +1,41 @@
+##############################################################################
+#
+# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
 """Top-level configuration handle."""
 
-import os
-import urllib
-import urllib2
 import urlparse
 
 import ZConfig
 
-from Config import Configuration, ImportingConfiguration
-from Substitution import isname, substitute
+from ZConfig import loader
+from ZConfig.Config import Configuration
 
 
-class Context:
+class Context(loader.BaseLoader):
 
     def __init__(self):
-        self._imports = []         # URL  -> Configuration
+        loader.BaseLoader.__init__(self)
         self._named_sections = {}  # name -> Configuration
         self._needed_names = {}    # name -> [needy Configuration, ...]
-        self._current_imports = []
         self._all_sections = []
 
     # subclass-support API
 
-    def createImportedSection(self, section, url):
-        return ImportingConfiguration(url)
-
     def createNestedSection(self, section, type, name, delegatename):
-        if name:
-            name = name.lower()
-        return Configuration(section, type.lower(), name, section.url)
+        return Configuration(section, type, name, section.url)
 
     def createToplevelSection(self, url):
-        return ImportingConfiguration(url)
-
-    def createResource(self, file, url):
-        return Resource(file, url)
+        return Configuration(None, None, None, url)
 
     def getDelegateType(self, type):
         # Applications must provide delegation typing information by
@@ -42,78 +43,26 @@
         return type.lower()
 
     def parse(self, resource, section):
-        from ApacheStyle import Parse
-        Parse(resource, self, section)
+        from ZConfig.cfgparser import ZConfigParser
+        ZConfigParser(resource, self).parse(section)
 
-    def _normalize_url(self, url):
-        if os.path.exists(url):
-            url = "file://" + urllib.pathname2url(os.path.abspath(url))
-        else:
-            parts = urlparse.urlparse(url)
-            if not parts[0]:
-                raise ValueError("invalid URL, or file does not exist:\n"
-                                 + repr(url))
-        return url
-
-    # public API
-
-    def load(self, url):
-        """Load a resource from a URL or pathname."""
-        url = self._normalize_url(url)
-        top = self.createToplevelSection(url)
+    def loadResource(self, resource):
+        top = self.createToplevelSection(resource.url)
         self._all_sections.append(top)
-        self._imports = [top]
-        self._parse_url(url, top)
+        self.parse(resource, top)
         self._finish()
         return top
 
-    loadURL = load # Forward-compatible alias
-
-    def loadfile(self, file, url=None):
-        if not url:
-            name = getattr(file, "name", None)
-            if name and name[0] != "<" and name[-1] != ">":
-                url = "file://" + urllib.pathname2url(os.path.abspath(name))
-        top = self.createToplevelSection(url)
-        self._all_sections.append(top)
-        self._imports = [top]
-        self._current_imports.append(top)
-        r = self.createResource(file, url)
-        try:
-            self.parse(r, top)
-        finally:
-            del self._current_imports[-1]
-        self._finish()
-        return top
-
-    loadFile = loadfile # Forward-compatible alias
-
-
     # interface for parser
 
-    def importConfiguration(self, section, url):
-        for config in self._imports:
-            if config.url == url:
-                return config
-        newsect = self.createImportedSection(section, url)
-        self._all_sections.append(newsect)
-        self._imports.append(newsect)
-        section.addImport(newsect)
-        self._parse_url(url, newsect)
-
     def includeConfiguration(self, section, url):
-        # XXX we always re-parse, unlike import
-        file = urllib2.urlopen(url)
-        r = self.createResource(file, url)
+        r = self.openResource(url)
         try:
             self.parse(r, section)
         finally:
-            file.close()
+            r.close()
 
-    def nestSection(self, section, type, name, delegatename):
-        if name:
-            name = name.lower()
-        type = type.lower()
+    def startSection(self, section, type, name, delegatename):
         if name and self._named_sections.has_key(name):
             # Make sure sections of the same name are not defined
             # twice in the same resource, and that once a name has
@@ -139,30 +88,12 @@
         section.addChildSection(newsect)
         if name:
             self._named_sections[name] = newsect
-            current = self._current_imports[-1]
-            if section is not current:
-                current.addNamedSection(newsect)
-            for config in self._current_imports[:-1]:
-                # XXX seems very painful
-                if not config._sections_by_name.has_key((type, name)):
-                    config.addNamedSection(newsect)
         return newsect
 
-    # internal helpers
+    def endSection(self, parent, type, name, delegatename, section):
+        section.finish()
 
-    def _parse_url(self, url, section):
-        url, fragment = urlparse.urldefrag(url)
-        if fragment:
-            raise ZConfig.ConfigurationError(
-                "fragment identifiers are not currently supported")
-        file = urllib2.urlopen(url)
-        self._current_imports.append(section)
-        r = self.createResource(file, url)
-        try:
-            self.parse(r, section)
-        finally:
-            del self._current_imports[-1]
-            file.close()
+    # internal helpers
 
     def _finish(self):
         # Resolve section delegations
@@ -199,24 +130,3 @@
             if sect.delegate is not None:
                 sect.finish()
         self._all_sections = None
-
-
-class Resource:
-    def __init__(self, file, url):
-        self.file = file
-        self.url = url
-        self._definitions = {}
-
-    def define(self, name, value):
-        key = name.lower()
-        if self._definitions.has_key(key):
-            raise ZConfig.ConfigurationError("cannot redefine " + `name`)
-        if not isname(name):
-            raise ZConfig.ConfigurationError(
-                "not a substitution legal name: " + `name`)
-        self._definitions[key] = value
-
-    def substitute(self, s):
-        # XXX  I don't really like calling this substitute(),
-        # XXX  but it will do for now.
-        return substitute(s, self._definitions)


=== Packages/ZConfig/__init__.py 1.3 => 1.4 ===
--- Packages/ZConfig/__init__.py:1.3	Thu Dec  5 00:17:45 2002
+++ Packages/ZConfig/__init__.py	Fri Jan  3 16:05:51 2003
@@ -1,6 +1,6 @@
 ##############################################################################
 #
-# Copyright (c) 2002 Zope Corporation and Contributors.
+# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
 # All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
@@ -16,12 +16,100 @@
 $Id$
 """
 
-from ZConfig.Exceptions import *
+from ZConfig.loader import loadConfig, loadConfigFile
+from ZConfig.loader import loadSchema, loadSchemaFile
 
-def load(url):
+
+def loadURL(url):
     import Context
-    return Context.Context().load(url)
+    return Context.Context().loadURL(url)
 
-def loadfile(file, url=None):
+def loadFile(file, url=None):
     import Context
-    return Context.Context().loadfile(file, url)
+    return Context.Context().loadFile(file, url)
+
+
+class ConfigurationError(Exception):
+    def __init__(self, msg):
+        self.message = msg
+        Exception.__init__(self, msg)
+
+    def __str__(self):
+        return self.message
+
+
+class _ParseError(ConfigurationError):
+    def __init__(self, msg, url, lineno, colno=None):
+        self.url = url
+        self.lineno = lineno
+        self.colno = colno
+        ConfigurationError.__init__(self, msg)
+
+    def __str__(self):
+        s = self.message
+        if self.url:
+            s += "\n("
+        elif (self.lineno, self.colno) != (None, None):
+            s += " ("
+        if self.lineno:
+            s += "line %d" % self.lineno
+            if self.colno is not None:
+                s += ", column %d" % self.colno
+            if self.url:
+                s += " in %s)" % self.url
+            else:
+                s += ")"
+        elif self.url:
+            s += self.url + ")"
+        return s
+
+
+class SchemaError(_ParseError):
+    """Raised when there's an error in the schema itself."""
+
+    def __init__(self, msg, url=None, lineno=None, colno=None):
+        _ParseError.__init__(self, msg, url, lineno, colno)
+
+
+class ConfigurationMissingSectionError(ConfigurationError):
+    def __init__(self, type, name=None):
+        self.type = type
+        self.name = name
+        details = 'Missing section (type: %s' % type
+        if name is not None:
+            details += ', name: %s' % name
+        ConfigurationError.__init__(self, details + ')')
+
+
+class ConfigurationConflictingSectionError(ConfigurationError):
+    def __init__(self, type, name=None):
+        self.type = type
+        self.name = name
+        details = 'Conflicting sections (type: %s' % type
+        if name is not None:
+            details += ', name: %s' % name
+        ConfigurationError.__init__(self, details + ')')
+
+
+class ConfigurationSyntaxError(_ParseError):
+    """Raised when there's a syntax error in a configuration file."""
+
+
+class ConfigurationTypeError(ConfigurationError):
+    def __init__(self, msg, found, expected):
+        self.found = found
+        self.expected = expected
+        ConfigurationError.__init__(self, msg)
+
+
+class SubstitutionSyntaxError(ConfigurationError):
+    """Raised when interpolation source text contains syntactical errors."""
+
+
+class SubstitutionReplacementError(ConfigurationError, LookupError):
+    """Raised when no replacement is available for a reference."""
+
+    def __init__(self, source, name):
+        self.source = source
+        self.name = name
+        ConfigurationError.__init__(self, "no replacement for " + `name`)

=== Removed File Packages/ZConfig/ApacheStyle.py ===

=== Removed File Packages/ZConfig/Exceptions.py ===

=== Removed File Packages/ZConfig/Substitution.py ===