[Zope-Checkins] CVS: Zope/lib/python/ZConfig - cfgparser.py:1.1.4.1 datatypes.py:1.1.4.1 info.py:1.1.4.1 loader.py:1.1.4.1 matcher.py:1.1.4.1 schema.py:1.1.4.1 substitution.py:1.1.4.1 url.py:1.1.4.1 BRANCHES.txt:1.1.4.2 Config.py:1.2.4.7 Context.py:1.1.4.7 __init__.py:1.1.4.9

Chris McDonough chrism@zope.com
Fri, 3 Jan 2003 10:52:31 -0500


Update of /cvs-repository/Zope/lib/python/ZConfig
In directory cvs.zope.org:/tmp/cvs-serv2937

Modified Files:
      Tag: chrism-install-branch
	BRANCHES.txt Config.py Context.py __init__.py 
Added Files:
      Tag: chrism-install-branch
	cfgparser.py datatypes.py info.py loader.py matcher.py 
	schema.py substitution.py url.py 
Log Message:
Merge zconfig-schema-devel-branch into chrism-install-branch.  Why not? ;-)


=== Added File Zope/lib/python/ZConfig/cfgparser.py ===
##############################################################################
#
# Copyright (c) 2002 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


=== Added File Zope/lib/python/ZConfig/datatypes.py ===
##############################################################################
#
# Copyright (c) 2002 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

stock_datatypes = {
    "boolean":       asBoolean,
    "identifier":    IdentifierConversion().__call__,
    "integer":       integer,
    "float":         float_conversion,
    "string":        str,
    "null":          null_conversion,
    "locale":        MemoizedConversion(check_locale).__call__,
    "port-number":   port_number,
    "basic-key":     BasicKeyConversion().__call__,
    "logging-level": LogLevelConversion().__call__,
    "inet-address":  inet_address,
    "socket-address":socket_address,
    "ipaddr-or-hostname":IpaddrOrHostname().__call__,
    "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,
    }

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


=== Added File Zope/lib/python/ZConfig/info.py ===
##############################################################################
#
# Copyright (c) 2002 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()

    def finish(self):
        if not self._types:
            raise ZConfig.SchemaError(
                "sectiongroup must contain at least on sectiontype")


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


=== Added File Zope/lib/python/ZConfig/loader.py ===
##############################################################################
#
# Copyright (c) 2002 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):
        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`)
        info = parent.info
        info = getattr(info, "sectiontype", info)
        ci = info.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)


=== Added File Zope/lib/python/ZConfig/matcher.py ===
##############################################################################
#
# Copyright (c) 2002 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__
        return "<%s for >" % (clsname, )

    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


=== Added File Zope/lib/python/ZConfig/schema.py ===
##############################################################################
#
# Copyright (c) 2002 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()
        if not src:
            self.error("import src may not be omitted or empty")
        src = url.urljoin(self._url, src)
        src, fragment = url.urldefrag(src)
        if fragment:
            self.error("import src many not include a fragment identifier")
        schema = self._loader.loadURL(src)
        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:
            self._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")
        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().finish()

    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))


=== Added File Zope/lib/python/ZConfig/substitution.py ===
##############################################################################
#
# Copyright (c) 2002 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


=== Added File Zope/lib/python/ZConfig/url.py ===
"""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


=== Zope/lib/python/ZConfig/BRANCHES.txt 1.1.4.1 => 1.1.4.2 ===
--- Zope/lib/python/ZConfig/BRANCHES.txt:1.1.4.1	Thu Oct 10 14:29:12 2002
+++ Zope/lib/python/ZConfig/BRANCHES.txt	Fri Jan  3 10:51:55 2003
@@ -6,3 +6,9 @@
     An example of an alternate syntax for ZConfig.  This syntax was
     developed while trying the package initially, but was rejected.
     It is saved on a branch to avoid losing historical information.
+
+zconfig-schema-devel-branch
+    Development branch for schema support in ZConfig.  The branch is
+    based on the ZConfig trunk, but the development is strongly based
+    on the work Chris McDonough started in the chrism-install-branch
+    for Zope 2.


=== Zope/lib/python/ZConfig/Config.py 1.2.4.6 => 1.2.4.7 ===
--- Zope/lib/python/ZConfig/Config.py:1.2.4.6	Fri Dec  6 11:06:23 2002
+++ Zope/lib/python/ZConfig/Config.py	Fri Jan  3 10:51:55 2003
@@ -1,12 +1,22 @@
+##############################################################################
+#
+# Copyright (c) 2002 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,21 +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
-
-    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.
@@ -199,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))


=== Zope/lib/python/ZConfig/Context.py 1.1.4.6 => 1.1.4.7 ===
--- Zope/lib/python/ZConfig/Context.py:1.1.4.6	Fri Dec  6 11:06:23 2002
+++ Zope/lib/python/ZConfig/Context.py	Fri Jan  3 10:51:55 2003
@@ -1,40 +1,41 @@
+##############################################################################
+#
+# Copyright (c) 2002 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,74 +43,27 @@
         return type.lower()
 
     def parse(self, resource, section):
-        from ApacheStyle import Parse
-        Parse(resource, self, 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)
-        self._all_sections.append(top)
-        self._imports = [top]
-        self._parse_url(url, top)
-        self._finish()
-        return top
+        from ZConfig.cfgparser import ZConfigParser
+        ZConfigParser(resource, self).parse(section)
 
-    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)
+    def loadResource(self, resource):
+        top = self.createToplevelSection(resource.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.parse(resource, top)
         self._finish()
         return top
 
 
     # 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
@@ -135,30 +89,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
@@ -195,24 +131,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)


=== Zope/lib/python/ZConfig/__init__.py 1.1.4.8 => 1.1.4.9 ===
--- Zope/lib/python/ZConfig/__init__.py:1.1.4.8	Fri Dec  6 11:06:23 2002
+++ Zope/lib/python/ZConfig/__init__.py	Fri Jan  3 10:51:55 2003
@@ -16,20 +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 loadschema(url, schema):
-    from SchemaParser import SchemaContext
-    return SchemaContext().load(url, schema)
-
-def loadschemafile(file, schema, url=None):
-    from SchemaParser import SchemaContext
-    return SchemaContext().load(file, url, schema)
+    def __init__(self, source, name):
+        self.source = source
+        self.name = name
+        ConfigurationError.__init__(self, "no replacement for " + `name`)