summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJim Fulton <jim@zope.com>2002-06-10 23:29:49 +0000
committerJim Fulton <jim@zope.com>2002-06-10 23:29:49 +0000
commit9477e5b0825355d9bfe242b00e6b1a56060183f4 (patch)
tree9815b7bb6b07472017d7f2e201fdb112ca75b905
downloadzope-traversing-9477e5b0825355d9bfe242b00e6b1a56060183f4.tar.gz
Merged Zope-3x-branch into newly forked Zope3 CVS Tree.
-rw-r--r--AcquireNamespace.py57
-rw-r--r--AttrItemNamespaces.py49
-rw-r--r--CreateNamespace.py35
-rw-r--r--DefaultTraversable.py39
-rw-r--r--EtcNamespace.py54
-rw-r--r--Exceptions.py22
-rw-r--r--INamespaceHandler.py34
-rw-r--r--ITraversable.py32
-rw-r--r--ITraverser.py53
-rw-r--r--Namespaces.py44
-rw-r--r--ParameterParsing.py46
-rw-r--r--PresentationNamespaces.py45
-rw-r--r--Traverser.py124
-rw-r--r--__init__.py18
-rw-r--r--tests/__init__.py14
-rw-r--r--tests/testAcquire.py63
-rw-r--r--tests/testAtterItem.py48
-rw-r--r--tests/testEtc.py49
-rw-r--r--tests/testNamespaceTrversal.py52
-rw-r--r--tests/testPresentation.py73
-rw-r--r--tests/testTraverser.py240
-rw-r--r--traversing.zcml19
22 files changed, 1210 insertions, 0 deletions
diff --git a/AcquireNamespace.py b/AcquireNamespace.py
new file mode 100644
index 0000000..6d1c5c3
--- /dev/null
+++ b/AcquireNamespace.py
@@ -0,0 +1,57 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+#
+##############################################################################
+"""
+
+$Id: AcquireNamespace.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+from Namespaces import provideNamespaceHandler
+from Exceptions import UnexpectedParameters
+from Zope.Exceptions import NotFoundError
+from Zope.ComponentArchitecture import queryAdapter
+from Zope.Proxy.ContextWrapper import ContextWrapper, getWrapperContext
+from ITraversable import ITraversable
+
+class ExcessiveWrapping(NotFoundError):
+ """Too many levels of acquisition wrapping. We don't believe them."""
+
+def acquire(name, parameters, pname, ob, request):
+ if parameters:
+ raise UnexpectedParameters(parameters)
+
+ i = 0
+ origOb = ob
+ while i < 200:
+ i += 1
+ traversable = queryAdapter(ob, ITraversable, None)
+ if traversable is not None:
+
+ try:
+ # XXX what do we do if the path gets bigger?
+ path = []
+ next = traversable.traverse(name, parameters, pname, path)
+ if path: continue
+ except NotFoundError:
+ pass
+ else:
+ return ContextWrapper(next, ob, name=name)
+
+ ob = getWrapperContext(ob)
+ if ob is None:
+ raise NotFoundError(origOb, pname)
+
+ raise ExcessiveWrapping(origOb, pname)
+
+provideNamespaceHandler('acquire', acquire)
+
diff --git a/AttrItemNamespaces.py b/AttrItemNamespaces.py
new file mode 100644
index 0000000..e602804
--- /dev/null
+++ b/AttrItemNamespaces.py
@@ -0,0 +1,49 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+#
+##############################################################################
+"""
+
+$Id: AttrItemNamespaces.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+from Namespaces import provideNamespaceHandler
+from Exceptions import UnexpectedParameters
+
+def attr(name, parameters, pname, ob, request):
+ if parameters:
+ raise UnexpectedParameters(parameters)
+ return getattr(ob, name)
+
+provideNamespaceHandler('attribute', attr)
+
+def item(name, parameters, pname, ob, request):
+ if parameters:
+ raise UnexpectedParameters(parameters)
+ return ob[name]
+
+provideNamespaceHandler('item', item)
+
+
+# YAGNI
+#
+# def accessor(name, parameters, ob, request):
+# if parameters:
+# raise UnexpectedParameters(parameters)
+#
+# method = getattr(ob, name, None)
+# if method is None:
+# raise NotFound(ob, name, request)
+#
+# return method()
+#
+# provideNamespaceHandler('accessor', accessor)
diff --git a/CreateNamespace.py b/CreateNamespace.py
new file mode 100644
index 0000000..383dad6
--- /dev/null
+++ b/CreateNamespace.py
@@ -0,0 +1,35 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+
+$Id: CreateNamespace.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+from Zope.ComponentArchitecture import getService
+from Namespaces import provideNamespaceHandler
+from Exceptions import UnexpectedParameters
+from Zope.Exceptions import NotFoundError
+from Zope.Proxy.ContextWrapper import ContextWrapper
+
+def create(name, parameters, pname, ob, request):
+ if parameters:
+ raise UnexpectedParameters(parameters)
+
+ for addable in getService(ob, 'AddableContent').getAddables(ob):
+ if addable.id == name:
+ return ContextWrapper(addable, ob, name=name)
+
+ raise NotFoundError(ob, pname)
+
+provideNamespaceHandler('create', create)
diff --git a/DefaultTraversable.py b/DefaultTraversable.py
new file mode 100644
index 0000000..e972122
--- /dev/null
+++ b/DefaultTraversable.py
@@ -0,0 +1,39 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+#
+##############################################################################
+from ITraversable import ITraversable
+from Zope.Exceptions import NotFoundError
+from Exceptions import UnexpectedParameters
+
+class DefaultTraversable:
+ """Traverses objects via attribute and item lookup"""
+
+ __implements__ = ITraversable
+
+ def __init__(self, subject):
+ self._subject = subject
+
+ def traverse(self, name, parameters, pname, furtherPath):
+ if parameters:
+ raise UnexpectedParameters(parameters)
+ subject = self._subject
+ r = getattr(subject, name, self) # self used as marker
+ if r is not self:
+ return r
+
+ if hasattr(subject, '__getitem__'):
+ # Let exceptions propagate.
+ return self._subject[name]
+ else:
+ raise NotFoundError(self._subject, name)
+
diff --git a/EtcNamespace.py b/EtcNamespace.py
new file mode 100644
index 0000000..1c77801
--- /dev/null
+++ b/EtcNamespace.py
@@ -0,0 +1,54 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+#
+##############################################################################
+"""
+
+$Id: EtcNamespace.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+from Zope.App.OFS.ApplicationControl.ApplicationControl \
+ import ApplicationController
+from Namespaces import provideNamespaceHandler
+from Exceptions import UnexpectedParameters
+from Zope.Exceptions import NotFoundError
+
+def etc(name, parameters, pname, ob, request):
+ # XXX
+
+ # This is here now to allow us to get service managers from a
+ # separate namespace from the content. We add and etc
+ # namespace to allow us to handle misc objects. We'll apply
+ # YAGNI for now and hard code this. We'll want something more
+ # general later. We were thinking of just calling "get"
+ # methods, but this is probably too magic. In particular, we
+ # will treat returned objects as sub-objects wrt security and
+ # not all get methods may satisfy this assumption. It might be
+ # best to introduce some sort of etc registry.
+
+ if parameters:
+ raise UnexpectedParameters(parameters)
+
+ if name == 'ApplicationController' and ob is None:
+ return ApplicationController
+
+ if name != 'Services':
+
+ raise NotFoundError(ob, pname, request)
+
+ method_name = "getServiceManager"
+ method = getattr(ob, method_name, None)
+ if method is None:
+ raise NotFound(ob, pname, request)
+
+ return method()
+
+provideNamespaceHandler('etc', etc)
diff --git a/Exceptions.py b/Exceptions.py
new file mode 100644
index 0000000..09a64e3
--- /dev/null
+++ b/Exceptions.py
@@ -0,0 +1,22 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+#
+##############################################################################
+"""
+
+$Id: Exceptions.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+from Zope.Exceptions import NotFoundError
+
+class UnexpectedParameters(NotFoundError):
+ """Unexpected namespace parameters were provided.
+ """
diff --git a/INamespaceHandler.py b/INamespaceHandler.py
new file mode 100644
index 0000000..bd7b553
--- /dev/null
+++ b/INamespaceHandler.py
@@ -0,0 +1,34 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+#
+##############################################################################
+"""
+
+$Id: INamespaceHandler.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+from Interface import Interface
+
+class INamespaceHandler(Interface):
+
+ def __call__(name, parameters, pname, object, request):
+ """Access a name in a namespace
+
+ The name lookup usually depends on an object and/or a
+ request. If an object or request is unavailable, None will be passed.
+
+ The parameters provided, are passed as a sequence of
+ name, value items. The 'pname' argument has the original name
+ before parameters were removed.
+
+ It is not the respoonsibility of the handler to wrap the return value.
+ """
diff --git a/ITraversable.py b/ITraversable.py
new file mode 100644
index 0000000..b81305f
--- /dev/null
+++ b/ITraversable.py
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+#
+##############################################################################
+import Interface
+
+class ITraversable(Interface.Base):
+ """To traverse an object, this interface must be provided"""
+
+ def traverse(name, parameters, pname, furtherPath):
+ """Get the next item on the path
+
+ Should return the item corresponding to 'name' or raise
+ Zope.Exceptions.NotFoundError where appropriate.
+
+ The parameters provided, are passed as a sequence of
+ name, value items. The 'pname' argument has the original name
+ before parameters were removed.
+
+ furtherPath is a list of names still to be traversed. This method is
+ allowed to change the contents of furtherPath.
+
+ """
diff --git a/ITraverser.py b/ITraverser.py
new file mode 100644
index 0000000..f02d72b
--- /dev/null
+++ b/ITraverser.py
@@ -0,0 +1,53 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+#
+##############################################################################
+"""
+
+$Id: ITraverser.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+import Interface
+
+_RAISE_KEYERROR=[]
+
+class ITraverser(Interface.Base):
+ """Provide traverse features"""
+
+ def getPhysicalRoot():
+ """
+ Returns the top-level Application object, or the base object if it is
+ unwrapped.
+ """
+
+ def getPhysicalPath():
+ """
+ Returns a path (an immutable sequence of strings) from the root.
+
+ This path can be used to access this object again later, for example
+ in a copy/paste operation. Returns an empty tuple if the base object
+ is not wrapped.
+ """
+
+ def traverse(path, default=_RAISE_KEYERROR):
+ """
+ Return an object given a path.
+
+ Path is either an immutable sequence of strings or a slash ('/')
+ delimited string.
+
+ If the first string in the path sequence is an empty string,
+ or the path begins with a '/', start at the root. Otherwise the path
+ is relative to the current context.
+
+ If the object is not found, return 'default' argument.
+ """
diff --git a/Namespaces.py b/Namespaces.py
new file mode 100644
index 0000000..8ad5960
--- /dev/null
+++ b/Namespaces.py
@@ -0,0 +1,44 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+#
+##############################################################################
+"""
+
+$Id: Namespaces.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+from Zope.Exceptions import NotFoundError
+from Zope.Proxy.ContextWrapper import ContextWrapper
+
+_namespace_handlers = {}
+
+def provideNamespaceHandler(ns, handler):
+ _namespace_handlers[ns] = handler
+
+def namespaceLookup(name, ns, qname, parameters, object, request=None):
+ """Lookup a value from a namespace
+
+ name -- the original name
+ ns -- The namespace
+ qname -- The name without any parameters
+ """
+
+ handler = _namespace_handlers.get(ns)
+ if handler is None:
+ raise NotFoundError(name)
+ new = ContextWrapper(handler(qname, parameters, name, object, request),
+ object, name=name)
+ return new
+
+# Register the etc, view, and resource namespaces
+import EtcNamespace, PresentationNamespaces, AttrItemNamespaces
+import CreateNamespace, AcquireNamespace
diff --git a/ParameterParsing.py b/ParameterParsing.py
new file mode 100644
index 0000000..ff624a2
--- /dev/null
+++ b/ParameterParsing.py
@@ -0,0 +1,46 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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
+#
+##############################################################################
+"""
+
+Revision information:
+$Id: ParameterParsing.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+import re
+
+namespace_pattern = re.compile('[+][+]([a-zA-Z0-9_]+)[+][+]')
+
+def parameterizedNameParse(name):
+ """Parse a name with parameters, including namespace parameters.
+
+ Return:
+
+ - namespace, or None if there isn't one.
+
+ - unparameterized name
+
+ - sequence of parameters, as name-value pairs.
+ """
+
+ ns = ''
+ if name[:2] == '@@':
+ ns = 'view'
+ name = name[2:]
+ else:
+ match = namespace_pattern.match(name)
+ if match:
+ prefix, ns = match.group(0, 1)
+ name = name[len(prefix):]
+
+ return ns, name, ()
diff --git a/PresentationNamespaces.py b/PresentationNamespaces.py
new file mode 100644
index 0000000..99dd451
--- /dev/null
+++ b/PresentationNamespaces.py
@@ -0,0 +1,45 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+
+$Id: PresentationNamespaces.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+from Zope.ComponentArchitecture import getView, getResource
+from Namespaces import provideNamespaceHandler
+from Exceptions import UnexpectedParameters
+from Zope.Exceptions import NotFoundError
+
+class NoRequest(NotFoundError):
+ """Atempt to access a presentation component outside of a request context
+ """
+
+def view(name, parameters, pname, ob, request):
+ if parameters:
+ raise UnexpectedParameters(parameters)
+ if not request:
+ raise NoRequest(pname)
+ return getView(ob, name, request)
+
+provideNamespaceHandler('view', view)
+
+def resource(name, parameters, pname, ob, request):
+ if parameters:
+ raise UnexpectedParameters(parameters)
+ if not request:
+ raise NoRequest(pname)
+ return getResource(ob, name, request)
+
+provideNamespaceHandler('resource', resource)
+
diff --git a/Traverser.py b/Traverser.py
new file mode 100644
index 0000000..011d7c2
--- /dev/null
+++ b/Traverser.py
@@ -0,0 +1,124 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+#
+##############################################################################
+"""Default implementation of ITraverser.
+
+$Id: Traverser.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+from ITraverser import ITraverser
+from ITraversable import ITraversable
+from Zope.ContextWrapper.IWrapper import IWrapper
+from Zope.Proxy.ContextWrapper import getWrapperContext, getWrapperData
+from Zope.Proxy.ContextWrapper import ContextWrapper
+from Zope.ComponentArchitecture import queryAdapter
+from Zope.Exceptions import NotFoundError, Unauthorized
+from Namespaces import namespaceLookup
+from ParameterParsing import parameterizedNameParse
+from Zope.Security.SecurityManagement import getSecurityManager
+
+from types import StringTypes
+
+from __future__ import generators
+
+# A chain generator; let's us walk the wrapper chain down to the root
+def WrapperChain(w):
+ while w is not None:
+ yield w
+ w = getWrapperContext(w)
+
+_marker = object()
+
+class Traverser:
+ """Provide traverse features"""
+
+ __implements__ = ITraverser
+
+ # This adapter can be used for any object.
+
+ def __init__(self, wrapper):
+ self._wrapper = wrapper
+
+ def getPhysicalRoot(self):
+ # Loop over all wrappers until the last one, which is the root.
+ for w in WrapperChain(self._wrapper):
+ pass
+ return w
+
+ def getPhysicalPath(self):
+ path = []
+
+ for w in WrapperChain(self._wrapper):
+ d = getWrapperData(w)
+ if d:
+ path.insert(0, d['name'])
+
+ return tuple(path)
+
+ def traverse(self, path, default=_marker, request=None):
+ if not path:
+ return self._wrapper
+
+ if isinstance(path, StringTypes):
+ path = path.split('/')
+ if len(path) > 1 and not path[-1]:
+ # Remove trailing slash
+ path.pop()
+ else:
+ path = list(path)
+
+ path.reverse()
+ pop = path.pop
+
+ curr = self._wrapper
+ if not path[-1]:
+ # Start at the root
+ pop()
+ curr = self.getPhysicalRoot()
+ try:
+ while path:
+ name = pop()
+
+ if name == '.':
+ continue
+
+ if name == '..':
+ curr = getWrapperContext(curr) or curr
+ continue
+
+
+ if name and name[:1] in '@+':
+ ns, nm, parms = parameterizedNameParse(name)
+ if ns:
+ curr = namespaceLookup(name, ns, nm, parms,
+ curr, request)
+ continue
+ else:
+ parms = ()
+ nm = name
+
+ traversable = queryAdapter(curr, ITraversable, None)
+ if traversable is None:
+ raise NotFoundError, 'No traversable adapter found'
+
+ next = traversable.traverse(nm, parms, name, path)
+ curr = ContextWrapper(next, curr, name=name)
+
+ return curr
+
+ except:
+ if default == _marker:
+ raise
+ return default
+
+
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..656c898
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,18 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+#
+##############################################################################
+"""
+Traversing the object tree.
+"""
+
+
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..cd904c8
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,14 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+#
+##############################################################################
+"""App tests"""
diff --git a/tests/testAcquire.py b/tests/testAcquire.py
new file mode 100644
index 0000000..0beb6ab
--- /dev/null
+++ b/tests/testAcquire.py
@@ -0,0 +1,63 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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
+#
+##############################################################################
+"""
+
+Revision information:
+$Id: testAcquire.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+from unittest import TestCase, TestSuite, main, makeSuite
+from Zope.ComponentArchitecture.tests.PlacelessSetup import PlacelessSetup
+from Zope.App.Traversing.ITraversable import ITraversable
+from Zope.App.Traversing.DefaultTraversable import DefaultTraversable
+from Zope.ComponentArchitecture.GlobalAdapterService import provideAdapter
+from Zope.Proxy.ContextWrapper import ContextWrapper, getWrapperContext
+from Zope.App.Traversing.AcquireNamespace import acquire
+from Zope.Exceptions import NotFoundError
+
+class Test(PlacelessSetup, TestCase):
+
+ def test(self):
+ provideAdapter(None, ITraversable, DefaultTraversable)
+
+ class C:
+ def __init__(self, name):
+ self.name = name
+
+ a = C('a')
+ a.a1 = C('a1')
+ a.a2 = C('a2')
+ a.a2.a21 = C('a21')
+ a.a2.a21.a211 = C('a211')
+
+ a2 = ContextWrapper(a.a2, a)
+ a21 = ContextWrapper(a.a2.a21, a2)
+ a211 = ContextWrapper(a.a2.a21.a211, a21)
+
+ acquired = acquire('a1', (), 'a1;acquire', a211, None)
+
+ self.assertEqual(acquired.name, 'a1')
+
+ self.assertRaises(NotFoundError,
+ acquire, 'a3', (), 'a1;acquire', a211, None)
+
+
+
+def test_suite():
+ return TestSuite((
+ makeSuite(Test),
+ ))
+
+if __name__=='__main__':
+ main(defaultTest='test_suite')
diff --git a/tests/testAtterItem.py b/tests/testAtterItem.py
new file mode 100644
index 0000000..70eb072
--- /dev/null
+++ b/tests/testAtterItem.py
@@ -0,0 +1,48 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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
+#
+##############################################################################
+"""
+
+Revision information:
+$Id: testAtterItem.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+from unittest import TestCase, TestSuite, main, makeSuite
+from Zope.Testing.CleanUp import CleanUp # Base class w registry cleanup
+
+
+class C:
+ a = 1
+ def __getitem__(self, key): return key+'value'
+c=C()
+
+
+class Test(CleanUp, TestCase):
+
+ def testAttr(self):
+ from Zope.App.Traversing.AttrItemNamespaces import attr
+ self.assertEqual(attr('a', (), 'a;attribute', c, None), 1)
+
+ def testItem(self):
+ from Zope.App.Traversing.AttrItemNamespaces import item
+ self.assertEqual(item('a', (), 'a;item', c, None), 'avalue')
+
+
+
+def test_suite():
+ return TestSuite((
+ makeSuite(Test),
+ ))
+
+if __name__=='__main__':
+ main(defaultTest='test_suite')
diff --git a/tests/testEtc.py b/tests/testEtc.py
new file mode 100644
index 0000000..8080e1f
--- /dev/null
+++ b/tests/testEtc.py
@@ -0,0 +1,49 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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
+#
+##############################################################################
+"""
+
+Revision information:
+$Id: testEtc.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+from unittest import TestCase, TestSuite, main, makeSuite
+from Zope.Testing.CleanUp import CleanUp # Base class w registry cleanup
+
+class Test(CleanUp, TestCase):
+
+ def testApplicationControl(self):
+ from Zope.App.Traversing.EtcNamespace import etc
+ from Zope.App.OFS.ApplicationControl.ApplicationControl \
+ import ApplicationController
+
+ self.assertEqual(
+ etc('ApplicationController', (), '++etc++Services', None, None),
+ ApplicationController)
+
+ def testServices(self):
+ from Zope.App.Traversing.EtcNamespace import etc
+ class C:
+ def getServiceManager(self): return 42
+
+ self.assertEqual(etc('Services', (), 'etc:Services', C(), None), 42)
+
+
+
+def test_suite():
+ return TestSuite((
+ makeSuite(Test),
+ ))
+
+if __name__=='__main__':
+ main(defaultTest='test_suite')
diff --git a/tests/testNamespaceTrversal.py b/tests/testNamespaceTrversal.py
new file mode 100644
index 0000000..aaeb28c
--- /dev/null
+++ b/tests/testNamespaceTrversal.py
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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
+#
+##############################################################################
+"""
+
+Revision information:
+$Id: testNamespaceTrversal.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+from unittest import TestCase, TestSuite, main, makeSuite
+from Zope.Testing.CleanUp import CleanUp # Base class w registry cleanup
+
+class C:
+ a = 1
+ def __getitem__(self, key): return key+'value'
+
+c=C()
+
+
+class Test(CleanUp, TestCase):
+
+ def testAttr(self):
+ from Zope.App.Traversing.Traverser import Traverser
+ traverser = Traverser(c)
+ v = traverser.traverse('++attribute++a')
+ self.assertEqual(v, 1)
+
+ def testItem(self):
+ from Zope.App.Traversing.Traverser import Traverser
+ traverser = Traverser(c)
+ v = traverser.traverse('++item++a')
+ self.assertEqual(v, 'avalue')
+
+
+
+def test_suite():
+ return TestSuite((
+ makeSuite(Test),
+ ))
+
+if __name__=='__main__':
+ main(defaultTest='test_suite')
diff --git a/tests/testPresentation.py b/tests/testPresentation.py
new file mode 100644
index 0000000..26c69ed
--- /dev/null
+++ b/tests/testPresentation.py
@@ -0,0 +1,73 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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
+#
+##############################################################################
+"""
+
+Revision information:
+$Id: testPresentation.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+from unittest import TestCase, TestSuite, main, makeSuite
+from Zope.ComponentArchitecture.tests.PlacelessSetup import PlacelessSetup
+from Zope.ComponentArchitecture.GlobalViewService import provideView
+from Zope.ComponentArchitecture.GlobalResourceService import provideResource
+from Zope.App.Traversing.PresentationNamespaces import view, resource
+from Zope.Exceptions import NotFoundError
+from Interface import Interface
+
+class IContent(Interface): pass
+class IPresentationType(Interface): pass
+
+class Content: __implements__ = IContent
+
+class Resource:
+
+ def __init__(self, request): pass
+ __implements__ = IPresentationType
+
+class View:
+ __implements__ = IPresentationType
+
+ def __init__(self, content, request):
+ self.content = content
+
+class Request:
+
+ def getPresentationType(self): return IPresentationType
+ def getPresentationSkin(self): return ''
+
+
+class Test(PlacelessSetup, TestCase):
+
+ def testView(self):
+ provideView(IContent, 'foo', IPresentationType, [View])
+
+ ob = Content()
+ v = view('foo', (), '@@foo', ob, Request())
+ self.assertEqual(v.__class__, View)
+
+ def testResource(self):
+ provideResource('foo', IPresentationType, Resource)
+
+ ob = Content()
+ r = resource('foo', (), '++resource++foo', ob, Request())
+ self.assertEqual(r.__class__, Resource)
+
+
+def test_suite():
+ return TestSuite((
+ makeSuite(Test),
+ ))
+
+if __name__=='__main__':
+ main(defaultTest='test_suite')
diff --git a/tests/testTraverser.py b/tests/testTraverser.py
new file mode 100644
index 0000000..0cfdf5e
--- /dev/null
+++ b/tests/testTraverser.py
@@ -0,0 +1,240 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+#
+##############################################################################
+"""
+
+$Id: testTraverser.py,v 1.2 2002/06/10 23:28:17 jim Exp $
+"""
+
+import unittest
+from Zope.App.Traversing.ITraverser import ITraverser
+from Zope.App.Traversing.ITraversable import ITraversable
+from Zope.App.Traversing.Traverser import Traverser
+from Zope.App.Traversing.DefaultTraversable import DefaultTraversable
+from Zope.Proxy.ContextWrapper import ContextWrapper
+from Zope.Exceptions import NotFoundError, Unauthorized
+from Zope.ComponentArchitecture import getService
+from Zope.Security.SecurityManagement import setSecurityPolicy, \
+ noSecurityManager
+
+from Interface.Verify import verifyClass
+from Interface.Implements import instancesOfObjectImplements
+from Zope.App.OFS.Services.ServiceManager.tests.PlacefulSetup import PlacefulSetup
+from Zope.Security.Checker \
+ import ProxyFactory, defineChecker, NamesChecker, CheckerPublic, Checker
+from Zope.Security.SecurityManagement import newSecurityManager
+
+class C:
+ def __init__(self, name):
+ self.name = name
+
+class TraverserTests(PlacefulSetup, unittest.TestCase):
+
+ def setUp(self):
+ PlacefulSetup.setUp(self)
+ # Build up a wrapper chain
+ self.root = ContextWrapper(C('root'), None, name='')
+ self.folder = ContextWrapper(C('folder'), self.root, name='folder')
+ self.item = ContextWrapper(C('item'), self.folder, name='item')
+ self.tr = Traverser(self.item)
+
+ def testImplementsITraverser(self):
+ self.failUnless(ITraverser.isImplementedBy(self.tr))
+
+ def testVerifyInterfaces(self):
+ for interface in instancesOfObjectImplements(Traverser):
+ verifyClass(interface, Traverser)
+
+ def testPhysicalRoot(self):
+ self.failUnless(self.tr.getPhysicalRoot() is self.root)
+
+ def testPhysicalPath(self):
+ self.assertEquals(self.tr.getPhysicalPath(), ('', 'folder', 'item'))
+
+ def testUnwrapped(self):
+ # The feature should do the right thing for unwrapped objects too
+ unwrapped = C('unwrapped')
+ tr = Traverser(unwrapped)
+ self.assertEquals(tr.getPhysicalPath(), ())
+ self.failUnless(tr.getPhysicalRoot() is unwrapped)
+
+class UnrestrictedNoTraverseTests(unittest.TestCase):
+ def setUp(self):
+ self.root = root = C('root')
+ self.folder = folder = C('folder')
+ self.item = item = C('item')
+
+ root.folder = folder
+ folder.item = item
+
+ self.tr = Traverser(root)
+
+ def testNoTraversable(self):
+ self.assertRaises(NotFoundError, self.tr.traverse,
+ 'folder')
+
+class UnrestrictedTraverseTests(PlacefulSetup, unittest.TestCase):
+ def setUp(self):
+ PlacefulSetup.setUp(self)
+ # Build up a wrapper chain
+
+ getService(None,"Adapters").provideAdapter(
+ None, ITraversable, DefaultTraversable)
+
+ self.root = root = C('root')
+ self.folder = folder = C('folder')
+ self.item = item = C('item')
+
+ root.folder = folder
+ folder.item = item
+
+ self.tr = Traverser(root)
+
+ def testSimplePathString(self):
+ tr = self.tr
+ item = self.item
+
+ self.assertEquals(tr.traverse('/folder/item'), item)
+ self.assertEquals(tr.traverse('folder/item'), item)
+ self.assertEquals(tr.traverse('/folder/item/'), item)
+
+ def testSimplePathUnicode(self):
+ tr = self.tr
+ item = self.item
+
+ self.assertEquals(tr.traverse(u'/folder/item'), item)
+ self.assertEquals(tr.traverse(u'folder/item'), item)
+ self.assertEquals(tr.traverse(u'/folder/item/'), item)
+
+ def testSimplePathTuple(self):
+ tr = self.tr
+ item = self.item
+
+ self.assertEquals(tr.traverse(('', 'folder', 'item')),
+ item)
+ self.assertEquals(tr.traverse(('folder', 'item')), item)
+
+ def testComplexPathString(self):
+ tr = self.tr
+ item = self.item
+
+ self.assertEquals(tr.traverse('/folder/../folder/./item'),
+ item)
+ self.assertEquals(tr.traverse(
+ '/../folder/../../folder/item'), item)
+ self.assertEquals(tr.traverse('../../folder/item'), item)
+
+ def testNotFoundDefault(self):
+ self.assertEquals(self.tr.traverse('foo', 'notFound'),
+ 'notFound')
+
+ def testNotFoundNoDefault(self):
+ self.assertRaises(NotFoundError, self.tr.traverse, 'foo')
+
+def Denied(*names):
+
+ def check(name):
+ if name in names:
+ return 'Waaaa'
+ return CheckerPublic
+
+ return Checker(check)
+
+class RestrictedTraverseTests(PlacefulSetup, unittest.TestCase):
+ _oldPolicy = None
+ _deniedNames = ()
+
+ def setUp(self):
+ PlacefulSetup.setUp(self)
+
+ getService(None,"Adapters").provideAdapter(
+ None, ITraversable, DefaultTraversable)
+
+ self.root = root = C('root')
+ self.folder = folder = C('folder')
+ self.item = item = C('item')
+
+ root.folder = folder
+ folder.item = item
+
+ self.tr = Traverser(ProxyFactory(root))
+
+ def testAllAllowed(self):
+ defineChecker(C, Checker(lambda name: CheckerPublic))
+ tr = Traverser(ProxyFactory(self.root))
+ item = self.item
+
+ self.assertEquals(tr.traverse(('', 'folder', 'item')), item)
+ self.assertEquals(tr.traverse(('folder', 'item')), item)
+
+ def testItemDenied(self):
+ newSecurityManager('no one')
+ defineChecker(C, Denied('item'))
+ tr = Traverser(ProxyFactory(self.root))
+ folder = self.folder
+
+ self.assertRaises(Unauthorized, tr.traverse,
+ ('', 'folder', 'item'))
+ self.assertRaises(Unauthorized, tr.traverse,
+ ('folder', 'item'))
+ self.assertEquals(tr.traverse(('', 'folder')), folder)
+ self.assertEquals(tr.traverse(('folder', '..', 'folder')),
+ folder)
+ self.assertEquals(tr.traverse(('folder',)), folder)
+
+class DefaultTraversableTests(unittest.TestCase):
+ def testImplementsITraversable(self):
+ self.failUnless(ITraversable.isImplementedBy(DefaultTraversable(None)))
+
+ def testVerifyInterfaces(self):
+ for interface in instancesOfObjectImplements(DefaultTraversable):
+ verifyClass(interface, DefaultTraversable)
+
+ def testAttributeTraverse(self):
+ root = C('root')
+ item = C('item')
+ root.item = item
+ df = DefaultTraversable(root)
+
+ further = []
+ next = df.traverse('item', (), 'item', further)
+ self.failUnless(next is item)
+ self.assertEquals(further, [])
+
+ def testDictionaryTraverse(self):
+ dict = {}
+ foo = C('foo')
+ dict['foo'] = foo
+ df = DefaultTraversable(dict)
+
+ further = []
+ next = df.traverse('foo', (), 'foo', further)
+ self.failUnless(next is foo)
+ self.assertEquals(further, [])
+
+ def testNotFound(self):
+ df = DefaultTraversable(C('dummy'))
+
+ self.assertRaises(NotFoundError, df.traverse, 'bar', (), 'bar', [])
+
+def test_suite():
+ loader = unittest.TestLoader()
+ suite = loader.loadTestsFromTestCase(TraverserTests)
+ suite.addTest(loader.loadTestsFromTestCase(DefaultTraversableTests))
+ suite.addTest(loader.loadTestsFromTestCase(UnrestrictedNoTraverseTests))
+ suite.addTest(loader.loadTestsFromTestCase(UnrestrictedTraverseTests))
+ suite.addTest(loader.loadTestsFromTestCase(RestrictedTraverseTests))
+ return suite
+
+if __name__=='__main__':
+ unittest.TextTestRunner().run(test_suite())
diff --git a/traversing.zcml b/traversing.zcml
new file mode 100644
index 0000000..29b8d77
--- /dev/null
+++ b/traversing.zcml
@@ -0,0 +1,19 @@
+<zopeConfigure
+ xmlns='http://namespaces.zope.org/zope'
+ xmlns:security='http://namespaces.zope.org/security'
+ xmlns:zmi='http://namespaces.zope.org/zmi'
+ xmlns:browser='http://namespaces.zope.org/browser'
+>
+
+<adapter factory="Zope.App.Traversing.Traverser."
+ provides="Zope.App.Traversing.ITraverser."
+ />
+ <!-- Ultimately, this should be registered only for IWrapper, but that
+ won't work like that just now.
+ for="Zope.ContextWrapper.IWrapper." /> -->
+
+<adapter factory="Zope.App.Traversing.DefaultTraversable."
+ provides="Zope.App.Traversing.ITraversable." />
+
+
+</zopeConfigure>