[Zope3-checkins] CVS: Zope3/src/zope/app/content - __init__.py:1.2 configure.zcml:1.2 dtmlpage.py:1.2 file.py:1.2 folder.py:1.2 i18nfile.py:1.2 i18nimage.py:1.2 image.py:1.2 sql.py:1.2 zpt.py:1.2

Jim Fulton jim@zope.com
Wed, 25 Dec 2002 09:13:50 -0500


Update of /cvs-repository/Zope3/src/zope/app/content
In directory cvs.zope.org:/tmp/cvs-serv15352/src/zope/app/content

Added Files:
	__init__.py configure.zcml dtmlpage.py file.py folder.py 
	i18nfile.py i18nimage.py image.py sql.py zpt.py 
Log Message:
Grand renaming:

- Renamed most files (especially python modules) to lower case.

- Moved views and interfaces into separate hierarchies within each
  project, where each top-level directory under the zope package
  is a separate project.

- Moved everything to src from lib/python.

  lib/python will eventually go away. I need access to the cvs
  repository to make this happen, however.

There are probably some bits that are broken. All tests pass
and zope runs, but I haven't tried everything. There are a number
of cleanups I'll work on tomorrow.



=== Zope3/src/zope/app/content/__init__.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:49 2002
+++ Zope3/src/zope/app/content/__init__.py	Wed Dec 25 09:12:48 2002
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.


=== Zope3/src/zope/app/content/configure.zcml 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:49 2002
+++ Zope3/src/zope/app/content/configure.zcml	Wed Dec 25 09:12:48 2002
@@ -0,0 +1,278 @@
+<zopeConfigure
+   xmlns='http://namespaces.zope.org/zope'
+   xmlns:browser='http://namespaces.zope.org/browser'
+>
+
+<!-- Simple Folder Directives -->
+
+<content class="zope.app.content.folder.Folder">
+
+  <implements interface="zope.app.interfaces.container.IContentContainer" />
+
+  <implements
+     interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
+     />
+  
+  <factory
+      id="Folder"
+      permission="zope.ManageContent"
+      title="Folder"
+      description="Minimal folder" />
+
+  <allow
+      interface="zope.app.interfaces.services.service.Read"
+      />
+
+  <require
+      permission="zope.ManageServices"
+      interface="zope.app.interfaces.services.service.Write"
+      />
+
+  <require
+      permission="zope.View"
+      interface="zope.app.interfaces.container.IReadContainer" 
+      />
+
+  <require
+      permission="zope.ManageContent"
+      interface="zope.app.interfaces.container.IWriteContainer"
+      />
+
+</content>
+
+<!-- XXX Do we really need RootFolder? -->
+
+<content class="zope.app.content.folder.RootFolder">
+
+  <implements interface="zope.app.interfaces.container.IContentContainer" />
+
+  <require like_class="zope.app.content.folder.Folder" />
+
+  <implements
+     interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
+     />
+
+</content>
+
+  <permission id="zope.AddImages" title="Add Images" />
+
+  <content class="zope.app.content.image.Image">
+
+    <factory
+        id="Image"
+        permission="zope.ManageContent"
+        title="Image"
+        description="An Image" />
+
+    <require
+        permission="zope.View"
+        interface="zope.app.interfaces.content.file.IReadFile"
+        attributes="getImageSize"
+        />
+
+    <require
+        permission="zope.ManageContent"
+        interface="zope.app.interfaces.content.file.IWriteFile"
+        set_schema="zope.app.interfaces.content.file.IReadFile"
+        />
+
+    <implements
+       interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
+       />
+
+  </content>
+
+  <content class="zope.app.content.i18nimage.I18nImage">
+    <factory
+        id="I18nImage"
+        permission="zope.ManageContent"
+        title="I18n Image"
+        description="An Internationalized Image" />
+    <require
+        permission="zope.View"
+        interface="zope.app.interfaces.content.file.IReadFile"
+        attributes="getImageSize"
+        />
+    <require
+        permission="zope.ManageContent"
+        interface="zope.app.interfaces.content.file.IWriteFile" />
+    <require
+        permission="zope.View"
+        attributes="getDefaultLanguage getAvailableLanguages" />
+    <require
+        permission="zope.ManageContent"
+        attributes="setDefaultLanguage removeLanguage" />
+
+    <implements
+       interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
+       />
+
+  </content>
+
+
+<content class="zope.app.content.zpt.ZPTPage">
+  <factory
+      id="ZPTPage"
+      permission="zope.ManageContent"
+      title="ZPT Page"
+      description="A simple, content-based Page Template" />
+
+  <factory
+      id=".pt"
+      permission="zope.ManageContent"
+      title="ZPT Page" />
+
+  <factory
+      id=".zpt"
+      permission="zope.ManageContent"
+      title="ZPT Page" />
+
+  <require
+      permission="zope.View"
+      attributes="__call__" />
+
+  <require
+      permission="zope.ManageContent"
+      interface="zope.app.content.zpt.IZPTPage"
+      set_attributes="source" />
+
+  <require
+      permission="zope.View"
+      interface="zope.app.content.zpt.IRenderZPTPage" />
+
+  <implements
+      interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+
+  </content>
+
+  <adapter factory="zope.app.content.zpt.SearchableText"
+         provides="zope.app.interfaces.index.text.interfaces.ISearchableText"
+         for="zope.app.content.zpt.IZPTPage" />
+
+
+  <content class="zope.app.content.dtmlpage.DTMLPage">
+
+    <factory
+        id="DTMLPage"
+        permission="zope.ManageContent"
+        title="DTML Page"
+        description="A simple, content-based Page Template" />
+
+    <require permission="zope.View"
+                      attributes="__call__" />
+
+    <require permission="zope.ManageContent"
+                      interface="zope.app.content.dtmlpage.IDTMLPage" />
+
+    <require permission="zope.View"
+                      interface="zope.app.content.dtmlpage.IRenderDTMLPage" />
+
+
+    <implements interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+
+  </content>
+
+
+  <content class="zope.app.content.file.File">
+
+    <factory
+        id="File"
+        permission="zope.ManageContent"
+        title="File"
+        description="A File" />
+
+    <require
+        permission="zope.View"
+        interface="zope.app.interfaces.content.file.IReadFile" />
+
+    <require
+        permission="zope.ManageContent"
+        interface="zope.app.interfaces.content.file.IWriteFile"
+        set_schema="zope.app.interfaces.content.file.IReadFile"
+        />
+
+    <implements
+       interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
+       />
+
+  </content>
+
+  <content class="zope.app.content.i18nfile.I18nFile">
+
+     <factory
+         id="zope.app.content.I18nFile"
+         permission="zope.ManageContent"
+         title="I18n File"
+         description="An Internationalized File" />
+     <require
+         permission="zope.View"
+         interface="zope.app.interfaces.content.file.IReadFile" />
+     <require
+         permission="zope.ManageContent"
+         interface="zope.app.interfaces.content.file.IWriteFile" />
+     <require
+         permission="zope.View"
+         attributes="getDefaultLanguage getAvailableLanguages" />
+     <require
+         permission="zope.ManageContent"
+         attributes="setDefaultLanguage removeLanguage" />
+
+    <implements
+       interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
+       />
+
+   </content>
+
+  <adapter factory="zope.app.content.file.SearchableText"
+           provides="zope.app.interfaces.index.text.interfaces.ISearchableText"
+           for="zope.app.interfaces.content.file.IReadFile" />
+
+  <!-- SQL Script Directives -->
+
+  <permission id="zope.AddSQLScripts" title="Add SQL Scripts" />
+
+  <content class="zope.app.content.sql.SQLScript">
+
+    <factory
+        id="SQLScript"
+        permission="zope.ManageContent"
+        title="SQL Script"
+        description="Dynamic SQL Script" />
+
+    <require
+        permission = "zope.ManageContent"
+        interface = "zope.app.interfaces.content.sql.ISQLScript."
+        set_schema = "zope.app.interfaces.content.sql.ISQLScript."
+        />
+
+    <require
+        permission="zope.ManageContent"
+        interface="zope.app.interfaces.content.file.IFileContent" />
+
+    <implements interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+
+  </content>
+
+
+  <!-- Arguments Directives -->
+
+  <content class="zope.app.content.sql.Arguments">
+    <require
+        permission="zope.ManageContent"
+        interface="zope.interface.common.mapping.IEnumerableMapping" />
+  </content>
+
+
+  <!-- SQL DTML Directives -->
+
+  <content class="zope.app.content.sql.SQLDTML">
+    <require
+        permission="zope.ManageContent"
+        attributes="__call__" />
+  </content>
+
+
+  <!-- Further Directives -->
+
+
+</zopeConfigure>


=== Zope3/src/zope/app/content/dtmlpage.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:49 2002
+++ Zope3/src/zope/app/content/dtmlpage.py	Wed Dec 25 09:12:48 2002
@@ -0,0 +1,96 @@
+##############################################################################
+#
+# 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$
+"""
+import zope.schema
+
+from persistence import Persistent
+
+from zope.interface import Interface, Attribute
+from zope.app.interfaces.annotation import IAnnotatable
+from zope.app.interfaces.content.file import IFileContent
+
+from zope.proxy.context import ContextMethod
+from zope.proxy.context import getWrapperContainer
+from zope.security.proxy import ProxyFactory
+
+from zope.documenttemplate.dt_html import HTML
+
+
+class IDTMLPage(Interface):
+    """DTML Pages are a persistent implementation of DTML."""
+
+    def setSource(text, content_type='text/html'):
+        """Save the source of the page template."""
+
+    def getSource():
+        """Get the source of the page template."""
+
+    source = zope.schema.Bytes(
+        title=u"Source",
+        description=u"""The source od the page template.""",
+        required=True)
+
+
+class IRenderDTMLPage(Interface):
+
+    content_type = Attribute('Content type of generated output')
+
+    def render(request, *args, **kw):
+        """Render the page template.
+
+        The first argument is bound to the top-level 'request'
+        variable. The positional arguments are bound to the 'args'
+        variable and the keyword arguments are bound to the 'options'
+        variable.
+        """
+
+
+class DTMLPage(Persistent):
+
+    # XXX Putting IFileContent at the end gives an error!
+    __implements__ = IFileContent, IDTMLPage, IRenderDTMLPage, IAnnotatable
+
+    def __init__(self, source=''):
+        self.setSource(source)
+
+    def getSource(self):
+        '''See interface IDTMLPage'''
+        return self.template.read()
+
+    def setSource(self, text, content_type='text/html'):
+        '''See interface IDTMLPage'''
+        self.template = HTML(text.encode('utf-8'))
+        self.content_type = content_type
+
+    def render(self, request, *args, **kw):
+        """See interface IDTMLRenderPage"""
+
+        instance = ProxyFactory(getWrapperContainer(self))
+        request = ProxyFactory(request)
+
+        for k in kw:
+            kw[k] = ProxyFactory(kw[k])
+        kw['REQUEST'] = request
+
+        return self.template(instance, request, **kw)
+
+
+    render = ContextMethod(render)
+
+    __call__ = render
+
+    source = property(getSource, setSource, None,
+                      """Source of the DTML Page.""")


=== Zope3/src/zope/app/content/file.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:49 2002
+++ Zope3/src/zope/app/content/file.py	Wed Dec 25 09:12:48 2002
@@ -0,0 +1,228 @@
+##############################################################################
+#
+# 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$
+"""
+import datetime
+zerotime = datetime.datetime.fromtimestamp(0)
+
+from persistence import Persistent
+from transaction import get_transaction
+
+from zope.component import getAdapter
+from zope.publisher.browser import FileUpload
+
+from zope.app.interfaces.dublincore import IZopeDublinCore
+from zope.app.interfaces.content.file import IFile, IReadFile
+
+
+# set the size of the chunks
+MAXCHUNKSIZE = 1 << 16
+
+class File(Persistent):
+    __implements__ = IFile
+
+    def __init__(self, data='', contentType=''):
+        self.data = data
+        self.contentType = contentType
+
+
+    def __len__(self):
+        return self.size
+
+
+    def setContentType(self, contentType):
+        '''See interface IFile'''
+        self._contentType = contentType
+
+
+    def getContentType(self):
+        '''See interface Zope.App.OFS.Content.File.IFile.IFile'''
+        return self._contentType
+
+
+    def edit(self, data, contentType=None):
+        '''See interface IFile'''
+        # XXX This seems broken to me, as setData can override the
+        # content type explicitly passed in.
+
+        if contentType is not None:
+            self._contentType = contentType
+        if hasattr(data, '__class__') and data.__class__ is FileUpload \
+           and not data.filename:
+            data = None          # Ignore empty files
+        if data is not None:
+            self.data = data
+
+
+    def getData(self):
+        '''See interface IFile'''
+        if hasattr(self._data, '__class__') and \
+           self._data.__class__ is FileChunk:
+            return str(self._data)
+        else:
+            return self._data
+
+
+    def setData(self, data):
+        '''See interface IFile'''
+        # Handle case when data is a string
+        if isinstance(data, unicode):
+            data = data.encode('UTF-8')
+
+        if isinstance(data, str):
+            size = len(data)
+            if size < MAXCHUNKSIZE:
+                self._data, self._size = FileChunk(data), size
+                return None
+            self._data, self._size = FileChunk(data), size
+            return None
+
+        # Handle case when data is None
+        if data is None:
+            self._data, self._size = None, 0
+            return None
+
+        # Handle case when data is already a FileChunk
+        if hasattr(data, '__class__') and data.__class__ is FileChunk:
+            size = len(data)
+            self._data, self._size = data, size
+            return None
+
+        # Handle case when data is a file object
+        seek = data.seek
+        read = data.read
+
+        seek(0, 2)
+        size = end = data.tell()
+
+        if size <= 2*MAXCHUNKSIZE:
+            seek(0)
+            if size < MAXCHUNKSIZE:
+                self._data, self._size = read(size), size
+                return None
+            self._data, self._size = FileChunk(read(size)), size
+            return None
+
+        # Make sure we have an _p_jar, even if we are a new object, by
+        # doing a sub-transaction commit.
+        get_transaction().savepoint()
+
+        jar = self._p_jar
+
+        if jar is None:
+            # Ugh
+            seek(0)
+            self._data, self._size = FileChunk(read(size)), size
+            return None
+
+        # Now we're going to build a linked list from back
+        # to front to minimize the number of database updates
+        # and to allow us to get things out of memory as soon as
+        # possible.
+        next = None
+        while end > 0:
+            pos = end - MAXCHUNKSIZE
+            if pos < MAXCHUNKSIZE:
+                pos = 0 # we always want at least MAXCHUNKSIZE bytes
+            seek(pos)
+            data = FileChunk(read(end - pos))
+
+            # Woooop Woooop Woooop! This is a trick.
+            # We stuff the data directly into our jar to reduce the
+            # number of updates necessary.
+            data._p_jar = jar
+
+            # This is needed and has side benefit of getting
+            # the thing registered:
+            data.next = next
+
+            # Now make it get saved in a sub-transaction!
+            get_transaction().savepoint()
+
+            # Now make it a ghost to free the memory.  We
+            # don't need it anymore!
+            data._p_changed = None
+
+            next = data
+            end = pos
+
+        self._data, self._size = next, size
+        return None
+
+
+    def getSize(self):
+        '''See interface IFile'''
+        return self._size
+
+    data = property(getData, setData, None,
+                    """Contains the data of the file.""")
+
+    contentType = property(getContentType, setContentType, None,
+                           """Specifies the content type of the data.""")
+
+    size = property(getSize, None, None,
+                    """Specifies the size of the file in bytes. Read only.""")
+
+
+# Adapter for ISearchableText
+
+from zope.app.interfaces.index.text.interfaces import ISearchableText
+
+class SearchableText:
+
+    __implements__ = ISearchableText
+    __used_for__ = IReadFile
+
+    def __init__(self, file):
+        self.file = file
+
+    def getSearchableText(self):
+        if self.file.contentType == "text/plain":
+            return [unicode(self.file.data)]
+        else:
+            return None
+
+
+class FileChunk(Persistent):
+    # Wrapper for possibly large data
+
+    next = None
+
+    def __init__(self, data):
+        self._data = data
+
+
+    def __getslice__(self, i, j):
+        return self._data[i:j]
+
+
+    def __len__(self):
+        data = str(self)
+        return len(data)
+
+
+    def __str__(self):
+        next = self.next
+        if next is None:
+            return self._data
+
+        result = [self._data]
+        while next is not None:
+            self = next
+            result.append(self._data)
+            next = self.next
+
+        return ''.join(result)


=== Zope3/src/zope/app/content/folder.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:49 2002
+++ Zope3/src/zope/app/content/folder.py	Wed Dec 25 09:12:48 2002
@@ -0,0 +1,100 @@
+##############################################################################
+#
+# 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 persistence import Persistent
+from zodb.btrees.OOBTree import OOBTree
+from zope.app.interfaces.content.folder import IFolder, IRootFolder
+from zope.app.services.service import ServiceManagerContainer
+from zope.app.interfaces.services.service import IServiceManagerContainer
+from zope.exceptions import DuplicationError
+
+
+class Folder(Persistent, ServiceManagerContainer):
+    """The standard Zope Folder implementation."""
+
+    __implements__ = IFolder
+
+    def __init__(self):
+        self.data = OOBTree()
+
+    def keys(self):
+        """Return a sequence-like object containing the names
+           associated with the objects that appear in the folder
+        """
+        return self.data.keys()
+
+    def __iter__(self):
+        return iter(self.data.keys())
+
+    def values(self):
+        """Return a sequence-like object containing the objects that
+           appear in the folder.
+        """
+        return self.data.values()
+
+    def items(self):
+        """Return a sequence-like object containing tuples of the form
+           (name, object) for the objects that appear in the folder.
+        """
+        return self.data.items()
+
+    def __getitem__(self, name):
+        """Return the named object, or the value of the default
+           argument if given and the named object is not found.
+           If no default is given and the object is not found a
+           KeyError is raised.
+        """
+        return self.data[name]
+
+    def get(self, name, default=None):
+        """Return the named object, or the value of the default
+           argument if given and the named object is not found.
+           If no default is given and the object is not found a
+           KeyError is raised.
+        """
+        return self.data.get(name, default)
+
+    def __contains__(self, name):
+        """Return true if the named object appears in the folder."""
+        return self.data.has_key(name)
+
+    def __len__(self):
+        """Return the number of objects in the folder."""
+        return len(self.data)
+
+    def setObject(self, name, object):
+        """Add the given object to the folder under the given name."""
+
+        if not (isinstance(name, str) or isinstance(name, unicode)):
+            raise TypeError("Name must be a string rather than a %s" %
+                            name.__class__.__name__)
+        if not name:
+            raise TypeError("Name must not be empty")
+
+        if name in self.data:
+            raise DuplicationError("name, %s, is already in use" % name)
+
+        self.data[name] = object
+        return name
+
+    def __delitem__(self, name):
+        """Delete the named object from the folder. Raises a KeyError
+           if the object is not found."""
+        del self.data[name]
+
+
+class RootFolder(Folder):
+    """The standard Zope root Folder implementation."""
+
+    __implements__ = Folder.__implements__, IRootFolder


=== Zope3/src/zope/app/content/i18nfile.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:49 2002
+++ Zope3/src/zope/app/content/i18nfile.py	Wed Dec 25 09:12:48 2002
@@ -0,0 +1,145 @@
+##############################################################################
+#
+# 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$
+"""
+
+import persistence
+from zope.app.interfaces.content.i18nfile import II18nFile
+from zope.app.content.file import File
+
+# XXX We shouldn't be dependent on Browser here! Aaaargh.
+from zope.publisher.browser import FileUpload
+
+class I18nFile(persistence.Persistent):
+    """I18n aware file object.  It contains a number of File objects --
+    one for each language.
+    """
+
+    __implements__ = II18nFile
+
+    def __init__(self, data='', contentType=None, defaultLanguage='en'):
+        """ """
+
+        self._data = {}
+        self.defaultLanguage = defaultLanguage
+        self.setData(data, language=defaultLanguage)
+
+        if contentType is None:
+            self.setContentType('')
+        else:
+            self.setContentType(contentType)
+
+
+    def __len__(self):
+        return self.getSize()
+
+
+    def _create(self, data):
+        """Create a new subobject of the appropriate type.  Should be
+        overriden in subclasses.
+        """
+        return File(data)
+
+
+    def _get(self, language):
+        """Helper function -- return a subobject for a given language,
+        and if it does not exist, return a subobject for the default
+        language.
+        """
+        file = self._data.get(language)
+        if not file:
+            file = self._data[self.defaultLanguage]
+        return file
+
+
+    def _get_or_add(self, language, data=''):
+        """Helper function -- return a subobject for a given language,
+        and if it does not exist, create and return a new subobject.
+        """
+        if language is None:
+            language = self.defaultLanguage
+        file = self._data.get(language)
+        if not file:
+            self._data[language] = file = self._create(data)
+            self._p_changed = 1
+        return file
+
+
+    def setContentType(self, contentType):
+        '''See interface IFile'''
+        self._contentType = contentType
+
+
+    def getContentType(self):
+        '''See interface IFile'''
+        return self._contentType
+
+
+    contentType = property(getContentType, setContentType)
+
+    def edit(self, data, contentType=None, language=None):
+        '''See interface IFile'''
+
+        # XXX This seems broken to me, as setData can override the
+        # content type explicitly passed in.
+
+        if contentType is not None:
+            self.setContentType(contentType)
+        if hasattr(data, '__class__') and data.__class__ is FileUpload \
+           and not data.filename:
+            data = None          # Ignore empty files
+        if data is not None:
+            self.setData(data, language)
+
+
+    def getData(self, language=None):
+        '''See interface IFile'''
+        return self._get(language).getData()
+
+
+    def setData(self, data, language=None):
+        '''See interface IFile'''
+        self._get_or_add(language).setData(data)
+
+    data = property(getData, setData)
+
+    def getSize(self, language=None):
+        '''See interface IFile'''
+        return self._get(language).getSize()
+
+    def getDefaultLanguage(self):
+        'See II18nAware'
+        return self.defaultLanguage
+
+    def setDefaultLanguage(self, language):
+        'See II18nAware'
+        if not self._data.has_key(language):
+            raise ValueError, \
+                  'cannot set nonexistent language (%s) as default' % language
+        self.defaultLanguage = language
+
+    def getAvailableLanguages(self):
+        'See II18nAware'
+        return self._data.keys()
+
+    def removeLanguage(self, language):
+        '''See interface II18nFile'''
+
+        if language == self.defaultLanguage:
+            raise ValueError, 'cannot remove default language (%s)' % language
+        if self._data.has_key(language):
+            del self._data[language]
+            self._p_changed = 1


=== Zope3/src/zope/app/content/i18nimage.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:49 2002
+++ Zope3/src/zope/app/content/i18nimage.py	Wed Dec 25 09:12:48 2002
@@ -0,0 +1,50 @@
+##############################################################################
+#
+# 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$
+"""
+
+from zope.app.content.image import IImage, Image, getImageInfo
+from zope.app.content.i18nfile import I18nFile
+from zope.app.interfaces.content.i18nimage import II18nImage
+
+class I18nImage(I18nFile):
+    """An internationalized Image object.  Note that images of all
+    languages share the same content type.
+    """
+
+    __implements__ = II18nImage
+
+
+    def _create(self, data):
+        return Image(data)
+
+
+    def setData(self, data, language=None):
+        '''See interface IFile'''
+        super(I18nImage, self).setData(data, language)
+
+        if language is None or language == self.getDefaultLanguage():
+            # Uploading for the default language only overrides content
+            # type.  Note: do not use the argument data here, it doesn't
+            # work.
+            contentType = getImageInfo(self.getData(language))[0]
+            if contentType:
+                self.setContentType(contentType)
+
+
+    def getImageSize(self, language=None):
+        '''See interface IImage'''
+        return self._get(language).getImageSize()


=== Zope3/src/zope/app/content/image.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:49 2002
+++ Zope3/src/zope/app/content/image.py	Wed Dec 25 09:12:48 2002
@@ -0,0 +1,107 @@
+##############################################################################
+#
+# 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$
+"""
+import struct
+from zope.app.content.file import File
+from cStringIO import StringIO
+from zope.app.interfaces.content.image import IImage
+
+class Image(File):
+    __implements__ = IImage
+
+    def __init__(self, data=None):
+        '''See interface IFile'''
+        self.contentType, self._width, self._height = getImageInfo(data)
+        self.data = data
+
+
+    def setData(self, data):
+
+        super(Image, self).setData(data)
+
+        if data is not None:
+            contentType = None
+            contentType, self._width, self._height = getImageInfo(self.data)
+            if contentType:
+                self.contentType = contentType
+
+
+    def getImageSize(self):
+        '''See interface IImage'''
+        return (self._width, self._height)
+
+    data = property(File.getData, setData, None,
+                    """Contains the data of the file.""")
+
+
+def getImageInfo(data):
+    data = str(data)
+    size = len(data)
+    height = -1
+    width = -1
+    content_type = ''
+
+    # handle GIFs
+    if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'):
+        # Check to see if content_type is correct
+        content_type = 'image/gif'
+        w, h = struct.unpack("<HH", data[6:10])
+        width = int(w)
+        height = int(h)
+
+    # See PNG v1.2 spec (http://www.cdrom.com/pub/png/spec/)
+    # Bytes 0-7 are below, 4-byte chunk length, then 'IHDR'
+    # and finally the 4-byte width, height
+    elif ((size >= 24) and data.startswith('\211PNG\r\n\032\n')
+          and (data[12:16] == 'IHDR')):
+        content_type = 'image/png'
+        w, h = struct.unpack(">LL", data[16:24])
+        width = int(w)
+        height = int(h)
+
+    # Maybe this is for an older PNG version.
+    elif (size >= 16) and data.startswith('\211PNG\r\n\032\n'):
+        # Check to see if we have the right content type
+        content_type = 'image/png'
+        w, h = struct.unpack(">LL", data[8:16])
+        width = int(w)
+        height = int(h)
+
+    # handle JPEGs
+    elif (size >= 2) and data.startswith('\377\330'):
+        content_type = 'image/jpeg'
+        jpeg = StringIO(data)
+        jpeg.read(2)
+        b = jpeg.read(1)
+        try:
+            while (b and ord(b) != 0xDA):
+                while (ord(b) != 0xFF): b = jpeg.read(1)
+                while (ord(b) == 0xFF): b = jpeg.read(1)
+                if (ord(b) >= 0xC0 and ord(b) <= 0xC3):
+                    jpeg.read(3)
+                    h, w = struct.unpack(">HH", jpeg.read(4))
+                    break
+                else:
+                    jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0])-2)
+                b = jpeg.read(1)
+            width = int(w)
+            height = int(h)
+        except struct.error:
+            pass
+        except ValueError:
+            pass
+
+    return content_type, width, height


=== Zope3/src/zope/app/content/sql.py 1.1 => 1.2 === (801/901 lines abridged)
--- /dev/null	Wed Dec 25 09:13:49 2002
+++ Zope3/src/zope/app/content/sql.py	Wed Dec 25 09:12:48 2002
@@ -0,0 +1,898 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""\
+Inserting optional tests with 'sqlgroup'
+
+    It is sometimes useful to make inputs to an SQL statement
+    optinal.  Doing so can be difficult, because not only must the
+    test be inserted conditionally, but SQL boolean operators may or
+    may not need to be inserted depending on whether other, possibly
+    optional, comparisons have been done.  The 'sqlgroup' tag
+    automates the conditional insertion of boolean operators.
+
+    The 'sqlgroup' tag is a block tag that has no attributes. It can
+    have any number of 'and' and 'or' continuation tags.
+
+    Suppose we want to find all people with a given first or nick name
+    and optionally constrain the search by city and minimum and
+    maximum age.  Suppose we want all inputs to be optional.  We can
+    use DTML source like the following::
+
+      <dtml-sqlgroup>
+        <dtml-sqlgroup>
+          <dtml-sqltest name column=nick_name type=nb multiple optional>
+        <dtml-or>
+          <dtml-sqltest name column=first_name type=nb multiple optional>
+        </dtml-sqlgroup>
+      <dtml-and>
+        <dtml-sqltest home_town type=nb optional>
+      <dtml-and>
+        <dtml-if minimum_age>
+           age >= <dtml-sqlvar minimum_age type=int>
+        </dtml-if>
+      <dtml-and>
+        <dtml-if maximum_age>
+           age <= <dtml-sqlvar maximum_age type=int>
+        </dtml-if>

[-=- -=- -=- 801 lines omitted -=- -=- -=-]

+            except:
+                # Okay, the first try failed, so let's try to find the default
+                arg = self._arguments[name]
+                try:
+                    arg_values[name] = arg['default']
+                except:
+                    # Now the argument might be optional anyways; let's check
+                    try:
+                        if not arg['optional']:
+                            missing.append(name)
+                    except:
+                        missing.append(name)
+
+        try:
+            connection = self.getConnection()
+        except AttributeError:
+            raise AttributeError, (
+                "The database connection **%s** cannot be found." % (
+                self.connectionName))
+
+        if connection is None:
+            raise 'Database Error', (
+                '%s is not connected to a database' %'foo')# self.id)
+
+        query = apply(self.template, (), arg_values)
+        cache = getCacheForObj(self)
+        location = getLocationForCache(self)
+        if cache and location:
+            _marker = []
+            result = cache.query(location, {'query': query}, default=_marker)
+            if result is not _marker:
+                return result
+        result = queryForResults(connection, query)
+        if cache and location:
+            cache.set(result, location, {'query': query})
+        return result
+
+    __call__ = ContextMethod(__call__)
+
+
+    # See ISQLScript
+    arguments = property(getArgumentsString, setArguments, None,
+                         "Set the arguments that are used for the SQL Script.")
+    source = property(getSource, setSource, None,
+                      "Set the SQL template source.")
+    connectionName = property(getConnectionName, setConnectionName, None,
+                              "Connection Name for the SQL scripts.")
+
+
+valid_type = {'int':1, 'float':1, 'string':1, 'nb': 1}.has_key


=== Zope3/src/zope/app/content/zpt.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:50 2002
+++ Zope3/src/zope/app/content/zpt.py	Wed Dec 25 09:12:48 2002
@@ -0,0 +1,133 @@
+##############################################################################
+#
+# 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$
+"""
+
+import re
+
+from persistence import Persistent
+
+import zope.schema
+
+from zope.interface import Interface, Attribute
+from zope.proxy.context import ContextMethod
+from zope.proxy.context import getWrapperContainer
+from zope.security.proxy import ProxyFactory
+
+from zope.pagetemplate.pagetemplate import PageTemplate
+from zope.app.pagetemplate.engine import AppPT
+from zope.app.interfaces.index.text.interfaces import ISearchableText
+
+
+class IZPTPage(Interface):
+    """ZPT Pages are a persistent implementation of Page Templates.
+
+       Note: I introduced some new methods whose functionality is
+             actually already covered by some other methods but I
+             want to start enforcing a common coding standard.
+    """
+
+    def setSource(text, content_type='text/html'):
+        """Save the source of the page template.
+
+        'text' must be Unicode.
+        """
+
+    def getSource():
+        """Get the source of the page template."""
+
+    source = zope.schema.Text(
+        title=u"Source",
+        description=u"""The source of the page template.""",
+        required=True)
+
+
+class IRenderZPTPage(Interface):
+
+    content_type = Attribute('Content type of generated output')
+
+    def render(request, *args, **kw):
+        """Render the page template.
+
+        The first argument is bound to the top-level 'request'
+        variable. The positional arguments are bound to the 'args'
+        variable and the keyword arguments are bound to the 'options'
+        variable.
+        """
+
+
+class ZPTPage(AppPT, PageTemplate, Persistent):
+
+    __implements__ = IZPTPage, IRenderZPTPage
+
+    def getSource(self):
+        '''See IZPTPage'''
+        return self.read()
+
+    def setSource(self, text, content_type='text/html'):
+        '''See IZPTPage'''
+        if not isinstance(text, unicode):
+            raise TypeError("source text must be Unicode")
+
+        self.pt_edit(text.encode('utf-8'), content_type)
+
+    def pt_getContext(self, instance, request, **_kw):
+        # instance is a View component
+        namespace = super(ZPTPage, self).pt_getContext(**_kw)
+        namespace['request'] = request
+        namespace['context'] = instance
+        return namespace
+
+    def render(self, request, *args, **keywords):
+        instance = getWrapperContainer(self)
+
+        request = ProxyFactory(request)
+        instance = ProxyFactory(instance)
+        if args: args = ProxyFactory(args)
+        kw = ProxyFactory(keywords)
+
+        namespace = self.pt_getContext(instance, request,
+                                       args=args, options=kw)
+
+        return self.pt_render(namespace)
+
+    render = ContextMethod(render)
+
+    source = property(getSource, setSource, None,
+                      """Source of the Page Template.""")
+
+
+class SearchableText:
+
+    __used_for__ = IZPTPage
+    __implements__ = ISearchableText
+
+    def __init__(self, page):
+        self.page = page
+
+    def getSearchableText(self):
+        text = self.page.getSource()
+        if isinstance(text, str):
+            text = unicode(self.page.source, 'utf-8')
+        # else:
+        #   text was already Unicode, which happens, but unclear how it
+        #   gets converted to Unicode since the ZPTPage stores UTF-8 as
+        #   an 8-bit string.
+
+        if self.page.content_type.startswith('text/html'):
+            tag = re.compile(r"<[^>]+>")
+            text = tag.sub('', text)
+
+        return [text]