diff --git a/src/DocumentLibrary/CVS/Entries b/src/DocumentLibrary/CVS/Entries
new file mode 100644
index 0000000..3c2c98e
--- /dev/null
+++ b/src/DocumentLibrary/CVS/Entries
@@ -0,0 +1,21 @@
+/CatalogPlus.py/1.1.1.1/Fri Jan 18 04:38:36 2002//
+/DLGlobals.py/1.1.1.1/Fri Jan 18 04:38:39 2002//
+/Document.py/1.4/Sun Jun 23 13:08:54 2002//
+/DocumentLibrary.py/1.2/Sun Jun 23 11:51:05 2002//
+/DocumentStore.py/1.1.1.1/Fri Jan 18 04:38:37 2002//
+/INSTALL.txt/1.1.1.1/Fri Jan 18 04:38:32 2002//
+/IconImage.py/1.1.1.1/Fri Jan 18 04:38:39 2002//
+/LICENSE.txt/1.1.1.1/Fri Jan 18 04:38:36 2002//
+/README.txt/1.2/Sun Jun 23 12:00:33 2002//
+/TODO.txt/1.1.1.1/Fri Jan 18 04:38:39 2002//
+/TopicIndex.py/1.2/Sun Jun 23 13:01:12 2002//
+/__init__.py/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/refresh.txt/1.1.1.1/Fri Jan 18 04:38:37 2002//
+/text.c.patch/1.1.1.1/Fri Jan 18 04:38:33 2002//
+/version.txt/1.2/Sun Jun 23 13:20:49 2002//
+D/FileConverters////
+D/document_icons////
+D/dtml////
+D/instance////
+D/www////
+/HISTORY.txt/1.6/Sun Jun 23 09:41:07 2002//
diff --git a/src/DocumentLibrary/CVS/Repository b/src/DocumentLibrary/CVS/Repository
new file mode 100644
index 0000000..57ff92d
--- /dev/null
+++ b/src/DocumentLibrary/CVS/Repository
@@ -0,0 +1 @@
+DocumentLibrary
diff --git a/src/DocumentLibrary/CVS/Root b/src/DocumentLibrary/CVS/Root
new file mode 100644
index 0000000..2084eca
--- /dev/null
+++ b/src/DocumentLibrary/CVS/Root
@@ -0,0 +1 @@
+:pserver:anonymous@cvs.nlada-library.sourceforge.net:/cvsroot/nlada-library
diff --git a/src/DocumentLibrary/CatalogPlus.py b/src/DocumentLibrary/CatalogPlus.py
new file mode 100644
index 0000000..d7f142f
--- /dev/null
+++ b/src/DocumentLibrary/CatalogPlus.py
@@ -0,0 +1,165 @@
+##############################################################################
+#
+# Zope Public License (ZPL) Version 1.0
+# -------------------------------------
+#
+# Copyright (c) Digital Creations. All rights reserved.
+#
+# This license has been certified as Open Source(tm).
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions in source code must retain the above copyright
+# notice, this list of conditions, and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions, and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# 3. Digital Creations requests that attribution be given to Zope
+# in any manner possible. Zope includes a "Powered by Zope"
+# button that is installed by default. While it is not a license
+# violation to remove this button, it is requested that the
+# attribution remain. A significant investment has been put
+# into Zope, and this effort will continue if the Zope community
+# continues to grow. This is one way to assure that growth.
+#
+# 4. All advertising materials and documentation mentioning
+# features derived from or use of this software must display
+# the following acknowledgement:
+#
+# "This product includes software developed by Digital Creations
+# for use in the Z Object Publishing Environment
+# (http://www.zope.org/)."
+#
+# In the event that the product being advertised includes an
+# intact Zope distribution (with copyright and license included)
+# then this clause is waived.
+#
+# 5. Names associated with Zope or Digital Creations must not be used to
+# endorse or promote products derived from this software without
+# prior written permission from Digital Creations.
+#
+# 6. Modified redistributions of any form whatsoever must retain
+# the following acknowledgment:
+#
+# "This product includes software developed by Digital Creations
+# for use in the Z Object Publishing Environment
+# (http://www.zope.org/)."
+#
+# Intact (re-)distributions of any official Zope release do not
+# require an external acknowledgement.
+#
+# 7. Modifications are encouraged but must be packaged separately as
+# patches to official Zope releases. Distributions that do not
+# clearly separate the patches from the original work must be clearly
+# labeled as unofficial distributions. Modifications which do not
+# carry the name Zope may be packaged in any form, as long as they
+# conform to all of the clauses above.
+#
+#
+# Disclaimer
+#
+# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
+# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#
+# This software consists of contributions made by Digital Creations and
+# many individuals on behalf of Digital Creations. Specific
+# attributions are listed in the accompanying credits file.
+#
+##############################################################################
+
+from Products.ZCatalog.Catalog import Catalog
+
+try:
+ # Try to import PluginIndexes (Zope 2.4 or higher)
+ from Products.PluginIndexes.FieldIndex.FieldIndex import FieldIndex
+ from Products.PluginIndexes.TextIndex.TextIndex import TextIndex
+ from Products.PluginIndexes.KeywordIndex.KeywordIndex import KeywordIndex
+except:
+ # Otherwise use Index names (Zope up to 2.3)
+ def FieldIndex(id): return 'FieldIndex'
+ def TextIndex(id, lexicon=None): return 'TextIndex'
+ def KeywordIndex(id): return 'KeywordIndex'
+
+
+class CatalogPlus(Catalog):
+ """ A slightly enhanced subclass of Catalog that plays well with
+ CatalogAware and does subtransactions without the full weight of a ZCatalog"""
+
+ threshold = 10000
+ _v_total = 0
+ _v_transaction = None
+
+ def catalog_object(self, obj, uid=None):
+ """ wrapper around catalog """
+
+ if uid is None:
+ try: uid = obj.getPhysicalPath
+ except AttributeError:
+ raise CatalogError(
+ "A cataloged object must support the 'getPhysicalPath' "
+ "method if no unique id is provided when cataloging"
+ )
+ else: uid=string.join(uid(), '/')
+ elif type(uid) is not type(''):
+ raise CatalogError('The object unique id must be a string.')
+
+ self.catalogObject(obj, uid, None)
+ # None passed in to catalogObject as third argument indicates
+ # that we shouldn't try to commit subtransactions within any
+ # indexing code. We throw away the result of the call to
+ # catalogObject (which is a word count), because it's
+ # worthless to us here.
+
+ if self.threshold is not None:
+ # figure out whether or not to commit a subtransaction.
+ t = id(get_transaction())
+ if t != self._v_transaction:
+ self._v_total = 0
+ self._v_transaction = t
+ self._v_total = self._v_total + 1
+ # increment the _v_total counter for this thread only and get
+ # a reference to the current transaction.
+ # the _v_total counter is zeroed if we notice that we're in
+ # a different transaction than the last one that came by.
+ # self.threshold represents the number of times that
+ # catalog_object needs to be called in order for the catalog
+ # to commit a subtransaction. The semantics here mean that
+ # we should commit a subtransaction if our threshhold is
+ # exceeded within the boundaries of the current transaction.
+ if self._v_total > self.threshold:
+ get_transaction().commit(1)
+ self._p_jar.cacheFullSweep(3)
+ self._v_total = 0
+
+ def uncatalog_object(self, uid):
+ """ For CatalogAware compatibilty """
+ self.uncatalogObject(uid)
+
+ def getLexicon(self):
+ lexicon = self.lexicon
+
+ if type(lexicon) == type(''):
+ vocabulary = getattr(self, lexicon, None)
+ if vocabulary:
+ return vocabulary.getLexicon()
+ else:
+ return None
+ else:
+ return lexicon
+
diff --git a/src/DocumentLibrary/DLGlobals.py b/src/DocumentLibrary/DLGlobals.py
new file mode 100644
index 0000000..c535ba6
--- /dev/null
+++ b/src/DocumentLibrary/DLGlobals.py
@@ -0,0 +1,48 @@
+##################################################################################
+#
+# Kaivo Public Software License
+#
+# Copyright (c) 2001, Kaivo, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# o Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# o Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#
+# o Neither Kaivo nor the names of its contributors may be used to endorse
+# or promote products derived from this software without specific prior
+# written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL KAIVO OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+##################################################################################
+
+__doc__ = """Document Library Global Declarations"""
+__version__ = '1.0b2'
+
+# Document Library by Casey Duncan (cduncan@kaivo.com)
+# Kaivo, Inc. (http://www.kaivo.com)
+
+from DateTime import DateTime
+
+# Special flag values
+document_unreviewed_date = DateTime('1970/1/1 12:00pm')
+document_min_reviewed_date = DateTime('1980/1/1 12:00pm')
+document_rejected_date = DateTime('1970/2/1 12:00pm')
+document_no_creation_date = DateTime('1970/1/1 12:00pm')
diff --git a/src/DocumentLibrary/Document.py b/src/DocumentLibrary/Document.py
new file mode 100644
index 0000000..230e7cf
--- /dev/null
+++ b/src/DocumentLibrary/Document.py
@@ -0,0 +1,604 @@
+##################################################################################
+#
+# Kaivo Public Software License
+#
+# Copyright (c) 2001, Kaivo, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# o Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# o Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#
+# o Neither Kaivo nor the names of its contributors may be used to endorse
+# or promote products derived from this software without specific prior
+# written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL KAIVO OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+##################################################################################
+
+__doc__ = """Document Library Document"""
+__version__ = '1.0b2'
+
+# Document by Casey Duncan (cduncan@kaivo.com)
+# Kaivo, Inc. (http://www.kaivo.com)
+
+from string import split, join, strip, find, rfind, replace
+from time import time
+from os import path, listdir
+from tempfile import mktemp
+import urllib
+from StringIO import StringIO
+from Globals import DTMLFile, package_home
+from DateTime import DateTime
+from OFS.Image import *
+from OFS.PropertySheets import FixedSchema
+from App.ImageFile import ImageFile
+import DLGlobals
+import Acquisition
+from FileConverters import FileConverters
+try:
+ from Products.ZCatalog.CatalogPathAwareness import CatalogAware
+except ImportError:
+ from Products.ZCatalog.CatalogAwareness import CatalogAware
+
+manage_addDocumentFileForm = DTMLFile('dtml/AddDocumentForm', globals())
+
+def check_content_type(context, content_type):
+ """Verify that the type of file is allowed"""
+
+ if hasattr(context, 'deny_content_types') and content_type:
+ return not (content_type in context.deny_content_types)
+
+ return 1
+
+def manage_addDocumentFile(self,id='',file='',title='',precondition='', content_type='',
+ REQUEST=None):
+ """Creates a new Library Document File"""
+
+ id=str(id)
+ title=str(title)
+ content_type=str(content_type)
+ precondition=str(precondition)
+
+ self=self.this()
+ doc = Document(id, title, '', content_type, precondition, container=self)
+ self._setObject(doc.getId(), doc)
+ doc = self._getOb(doc.getId())
+ # Now we "upload" the data. By doing this in two steps, we
+ # can use a database trick to make the upload more efficient.
+ doc.manage_upload(file)
+
+ if content_type:
+ doc.content_type=content_type
+
+ if not check_content_type(self, doc.content_type):
+ doc.manage_delObjects(doc.getId())
+ raise "IllegalFileType", \
+ "That file type cannot be uploaded into the library."
+
+ if REQUEST is not None:
+ REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
+ else:
+ return doc
+
+class Document(CatalogAware, File):
+ """Document library document"""
+
+ meta_type = 'Document File'
+
+ __ac_permissions__ = File.__ac_permissions__ + (
+ ('Access contents information', ('format', 'source', 'identifier', 'subject',
+ 'content_description', 'content_size', 'is_accepted',
+ 'owner_id', 'authenticated_user_is_owner')),
+ ('View', ('text_content', 'PrincipiaSearchSource', 'read', 'super_topics',
+ 'topicMap', 'all_searchable_text')),
+ ('Manage properties', ('acceptDocument', 'rejectDocument', 'setIcons', 'showTopics')),
+ )
+
+ manage_options = File.manage_options + (
+ { 'label': 'View Plain Text', 'action': 'text_content' },
+ )
+
+ _properties = ()
+
+ # Set default values for important properties
+ review_date = DLGlobals.document_unreviewed_date
+
+ def __init__(self, id='', title='', file='', content_type='', precondition='', container=None):
+ if not check_content_type(self, content_type):
+ raise "IllegalFileType", \
+ "That file type cannot be uploaded into the library."
+
+ if not id and container:
+ # Create a randomized id if none was provided
+ container = getattr(container, 'aq_base', container)
+ while not id or hasattr(container, id):
+ id = str(time())
+
+ File.__init__(self, id, title, file, content_type, precondition)
+
+ if not hasattr(self, 'icon'): # We aren't an acquisition wrapper yet
+ self.setIcons()
+
+ def __getattr__(self, name):
+ """Allow access to index_html via the filename and
+ default property values"""
+
+ if name == self.__dict__.get('filename', None):
+ return self.index_html
+ elif name[0] != '_' and name[:3] != 'aq_' and self.hasProperty(name):
+ # Make default property values act like attributes
+ for p in self._properties:
+ if p['id'] == name: return p['default']
+ raise AttributeError, name
+ else:
+ raise AttributeError, name
+
+ def acceptDocument(self, REQUEST=None, RESPONSE=None):
+ """Mark the document as accepted by setting its review_date to the current date"""
+ self.review_date = DateTime()
+ self.reindex_object()
+ self.showTopics()
+
+ if REQUEST and RESPONSE and REQUEST.has_key('last_url'):
+ RESPONSE.redirect(REQUEST.last_url + '?' + REQUEST.get('last_query',''))
+
+ def rejectDocument(self, REQUEST=None, RESPONSE=None):
+ """Mark the document as rejected by resetting its review_date"""
+ self.review_date = DLGlobals.document_rejected_date
+ self.reindex_object()
+
+ if REQUEST and RESPONSE and REQUEST.has_key('last_url'):
+ RESPONSE.redirect(REQUEST.last_url + '?' + REQUEST.get('last_query',''))
+
+ def is_accepted(self):
+ """Returns true if the document is indexed and has been reviewed and accepted"""
+ return hasattr(self.aq_base, 'topics') and self.topics \
+ and self.getProperty('review_date') != DLGlobals.document_rejected_date
+
+ #
+ # Content and conversion handling methods
+ #
+
+ def read(self):
+ """Return raw data of document file
+ This can be memory intensive for large files.
+ Use the _writeToTempFile method to get the data to external programs"""
+ data=self.data
+ if type(data) is type(''): return data
+
+ r = StringIO()
+ while data is not None:
+ r.write(data.data)
+ data=data.next
+
+ return r.getvalue()
+
+ def _writeToTempFile(self):
+ """Writes the contents of the document file to a temporary file and
+ returns the file name. Used by file converters"""
+ tmp_file = mktemp()
+ f = open(tmp_file, 'wb')
+
+ data=self.data
+ if type(data) is type(''):
+ f.write(data)
+ else:
+ while data is not None:
+ f.write(data.data)
+ data=data.next
+
+ f.close()
+ return tmp_file
+
+ def PrincipiaSearchSource(self):
+ """Return the raw text representation of the document if possible"""
+
+ # Check for a cached copy of the full text first
+ if hasattr(self, '_v_raw_text'):
+ return self._v_raw_text
+
+ format = self.format()
+
+ if FileConverters.has_key(format):
+ # use the installed converter
+ convert = FileConverters[format].convert
+ try:
+ # Convert the document data to text and cache the result
+ self._v_raw_text = convert(self)
+ return self._v_raw_text
+ except:
+ return ''
+ elif format[:5] == 'text/':
+ # This appears to be a raw text type, so return the data as is
+ return self.read()
+ else:
+ # Can't do it captain
+ return ''
+
+ def text_content(self, RESPONSE=None):
+ """The file converted to text with proper HTTP headers set
+ for viewing in a browser"""
+ t = self.PrincipiaSearchSource()
+
+ if RESPONSE is not None:
+ RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime))
+ RESPONSE.setHeader('Content-Type', 'text/plain')
+ RESPONSE.setHeader('Content-Length', len(t))
+
+ return t
+
+ def content_description(self):
+ """Returns a friendly description of the document content"""
+
+ format = self.format()
+
+ if format[:5] == 'text/':
+ d = 'Text Document (%s)' % format[5:]
+ else:
+ sep = find(format,'/') + 1
+ d = 'Document (%s)' % format[sep:]
+
+ if FileConverters.has_key(format):
+ return getattr(FileConverters[format], 'content_description', d)
+ else:
+ return d
+
+ def content_size(self):
+ """Returns a qualified document size in Kb or Mb"""
+ s = self.get_size()
+
+ if s == 0:
+ return '0 bytes'
+ elif s <= 1024:
+ return '1 Kb'
+ elif s < 1048576:
+ return '%d Kb' % (s / 1024)
+ else:
+ return '%d Mb' % (s / 1048576)
+
+ def topicMap(self):
+ """Returns a tuple of id,title mappings for each topic this
+ document belongs to"""
+
+ r = []
+
+ if hasattr(self, 'topics') and hasattr(self.aq_parent, 'Index'):
+ indexQuery = self.aq_parent.Index.query # Acquire the index's query method
+
+ # Iterate the topic ids assigned to this document and
+ # search the topic index to find the titles of the topics
+ for topic_id in self.topics:
+ topic_info = indexQuery(id=topic_id)
+ if topic_info: r.append({ 'id': topic_id, 'title': topic_info[0].title })
+
+ return tuple(r)
+
+ def super_topics(self):
+ """The list of all topics this document implicitly resides in including
+ all parent topics up to but not including the root"""
+
+ r = {} # Use a dict to filter out duplicates
+ if hasattr(self, 'topics') and hasattr(self.aq_parent, 'Index'):
+ indexQuery = self.aq_parent.Index.query # Acquire the index's query method
+
+ # Iterate the topic ids assigned to this document and
+ # search the topic index to find the parents of these topics
+ for topic_id in self.topics:
+ for topic_info in indexQuery(id=topic_id):
+ topic = topic_info.getObject()
+
+ if topic:
+ for parent in topic.topicParents()[1:]:
+ r[parent.id] = 1
+ return r.keys()
+
+
+ def all_searchable_text(self):
+ """All searchable text data concatenated together"""
+
+ r = []
+
+ # Add all string and text properties not marked hidden
+ for p in self.propertyMap():
+ if p.get('type', '') in ['string', 'text'] and not p.get('hidden', 0):
+ v = self.getProperty(p['id'])
+ if v: r.append(v)
+
+ # Add the titles of the topics assigned to this document
+ for t in self.topicMap():
+ r.append(t['title'])
+
+ # Add the full text content of the document
+ v = self.PrincipiaSearchSource()
+ if v: r.append(v)
+
+ return join(r, '\n')
+
+ def owner_id(self):
+ """The user id of the user who owns this document"""
+ owner = self.owner_info()
+ if owner and owner.has_key('id'):
+ return owner['id']
+ else:
+ return None
+
+ def authenticated_user_is_owner(self, REQUEST=None):
+ """Returns true if the currently authenticated user owns
+ the document object"""
+ if not REQUEST: REQUEST = self.REQUEST
+ return self.owner_id() == REQUEST.AUTHENTICATED_USER.getUserName()
+
+ #
+ # Dublin core compliance methods
+ #
+
+ creator = '' # Override Zope creator method
+
+ def format(self):
+ """Return the MIME content-type of the document"""
+ if hasattr(self, 'aq_base'): self = self.aq_base
+ c_type = getattr(self, 'content_type', None)
+ if c_type: c_type = split(c_type, ';')[0]
+ return c_type
+
+ def source(self):
+ """Return the URI of the source document if it was
+ submitted via URI"""
+ if hasattr(self, 'aq_base'): self = self.aq_base
+ return getattr(self, 'data_url', None)
+
+ def identifier(self):
+ """Unambiguous document identifier"""
+ if not hasattr(self, 'aq_base'): return self.getId()
+ if hasattr(self.aq_base, 'filename'):
+ return self.absolute_url() + '/' + urllib.quote(self.filename)
+ else:
+ return self.absolute_url()
+
+ def subject(self):
+ """Return the subject as a comma separated list of
+ the topic titles this document resides in"""
+ r = map(lambda i: i['title'], self.topicMap())
+ return join(r, ', ')
+
+ #
+ # Property handling methods
+ #
+
+ def _getDocumentProperties(self):
+ """Acquire the property information from the document store"""
+ try:
+ if self._properties != self.aq_parent._documentProperties:
+ self._properties = self.aq_parent._documentProperties
+ except:
+ pass
+
+ return self._properties
+
+ def propdict(self):
+ dict={}
+ for p in self._getDocumentProperties():
+ dict[p['id']]=p
+ return dict
+
+ def propertyMap(self):
+ """Override the default property mapping for File objects.
+ Documents use a single property definition in the store instead"""
+
+ # Return a tuple of mappings, giving meta-data for document properties.
+ r=[]
+ for d in self._getDocumentProperties():
+ mode=d.get('mode', 'wd')
+ if 'd' in mode:
+ dd={}
+ dd.update(d)
+ d=dd
+ d['mode']=filter(lambda c: c != 'd', mode)
+ r.append(d)
+
+ return tuple(r)
+
+ def hasProperty(self, id):
+ """Return true if object has a property 'id'"""
+ for p in self._getDocumentProperties():
+ if id==p['id']:
+ return 1
+ return 0
+
+ def property_extensible_schema__(self):
+ """Document files have fixed property schemas if they are in
+ a document store which describes their properties"""
+ return not hasattr(self.aq_parent, '_documentProperties')
+
+ def manage_edit(self, title, content_type, precondition='', REQUEST=None):
+ """Update object and reindex"""
+ r = File.manage_edit(self, title, content_type, precondition,
+ REQUEST=REQUEST)
+ self.setIcons()
+ self.reindex_object()
+ return r
+
+ def _updateProperty(self, name, value):
+ """Update property value by name. Includes special parsing for
+ topic list properties"""
+
+ if name == 'topics':
+ # Filter the topic code list to get the topic codes alone
+ topics = []
+ for topic in value:
+ if strip(topic): topics.append(split(topic)[0])
+ r = File._updateProperty(self, name, topics)
+ if hasattr(self.aq_base, 'review_date') and self.review_date != DLGlobals.document_unreviewed_date:
+ try: self.showTopics()
+ except: pass
+ else:
+ r = File._updateProperty(self, name, value)
+
+ def manage_changeProperties(self, REQUEST=None, reindex=1, **kw):
+ """Update properties and reindex"""
+ r = File.manage_changeProperties(self, REQUEST, kw=kw)
+ if reindex: self.reindex_object()
+ return r
+
+ def manage_editProperties(self, REQUEST, reindex=1):
+ """Edit Properties and reindex"""
+ r = File.manage_editProperties(self, REQUEST)
+ if reindex: self.reindex_object()
+ return r
+
+ #
+ # Document data handlers
+ #
+
+ def update_data(self, data, content_type=None, size=None):
+ """Update file data and reindex"""
+ r = File.update_data(self, data, content_type, size)
+ self.setIcons()
+ # Clear any cached file data
+ if hasattr(self, '_v_raw_text'): del self._v_raw_text
+ # reindex the document in the Catalog
+ self.reindex_object()
+ return r
+
+ def manage_upload(self, file, REQUEST=None, remote=0):
+ """Upload new file data into the document. Extended to support
+ uploading data from remote servers"""
+
+ if remote and type(file) == type(''):
+ url = file
+
+ # make sure the url uses an acceptable protocol (not file or mailto)
+ p = find(url, ':')
+ if p > -1:
+ if url[:p] not in ('http', 'ftp', 'gopher'):
+ raise "URLError", \
+ ('The URL specified: '
+ '%s '
+ 'is invalid.') % (url, url)
+ else:
+ # use HTTP if no protocol was specified
+ url = 'http://' + url
+
+ self.data_url = url
+ # Attempt to connect and retrieve the remote document
+ try:
+ remote_file = urllib.urlopen(url)
+ data = remote_file.read()
+ if remote_file.headers.type == 'text/html':
+ data = unlocalizeHTML(url, data)
+ file = Pdata(data) # blob wrapper object from OFS.Image
+ file.headers = remote_file.info().dict
+ file.filename = 'index.html'
+ remote_file.close()
+ except IOError, error:
+ raise "URLError", \
+ ('
An error occurred while '
+ 'attempting to connect to '
+ '%s
'
+ '
Error Message: %s
') % (url, url, error)
+ else:
+ if hasattr(self.aq_base, 'data_url'): del self.data_url
+
+ # Set the filename
+ filename = getattr(file, 'filename', 'untitled')
+
+ # Strip off the file path (if any)
+ for path_sep in ['\\', '/', ':']:
+ sep = rfind(filename, path_sep) + 1
+ if sep:
+ filename = filename[sep:]
+ break
+
+ while hasattr(self, filename):
+ # Make sure the filename is not the same name as existing attrs
+ filename = 'doc_' + filename
+
+ self.filename = filename
+
+ # Upload the data
+ return File.manage_upload(self, file, REQUEST)
+
+ def setIcons(self):
+ """set the document icons based on the content-type"""
+ format = self.format()
+ sep = find(format,'/') + 1
+ type = format[sep:]
+ sep = find(type, '-') + 1
+ if sep: type = type[sep:]
+ large_icon_path = 'document_icons/%s_large.gif' % type
+ if path.exists(path.join(package_home(globals()), large_icon_path)):
+ self.large_icon = ImageFile(large_icon_path, globals())
+ else:
+ self.large_icon = ImageFile('document_icons/generic_large.gif', globals())
+
+ small_icon_path = 'document_icons/%s_small.gif' % type
+ if path.exists(path.join(package_home(globals()), small_icon_path)):
+ self.small_icon = ImageFile(small_icon_path, globals())
+ else:
+ self.small_icon = ImageFile('document_icons/generic_small.gif', globals())
+
+ self.icon = join(self.getPhysicalPath()[1:],'/') + '/small_icon'
+
+ def showTopics(self):
+ """Make the document topics visible"""
+ for topic_id in self.topics:
+ topics = self.aq_parent.Index.query(id=topic_id)
+ for topic in topics:
+ topic = topic.getObject()
+ if topic: topic.showTopic() # Sometime getObject() returns None, gaack!
+
+
+Globals.InitializeClass(Document)
+
+class DocURLopener(urllib.FancyURLopener):
+ """Extend the urllib FancyURLopener to raise an exception on 404 not found errors."""
+
+ def http_error_404(self, url, fp, errcode, errmsg, headers):
+ raise "NotFoundError", \
+ ('
The document specified at: '
+ 'http:%s '
+ 'was not found. '
+ 'Please double check this URL and '
+ 'submit again.
') % (url, url)
+
+urllib._urlopener = DocURLopener() # Tell urllib to use our urlopener class
+
+def unlocalizeHTML(url, data):
+ """Add a declaration to documents that lack it to compensate for
+ relocating them into the library"""
+
+ p = find(data, '')
+ if p > 0:
+ return replace(data, '', '' % base_url, 1)
+ elif p == -1:
+ p = find(data, '')
+ if p > 0:
+ return replace(data, '', '' % base_url, 1)
+ return data
diff --git a/src/DocumentLibrary/DocumentLibrary.py b/src/DocumentLibrary/DocumentLibrary.py
new file mode 100644
index 0000000..77e8414
--- /dev/null
+++ b/src/DocumentLibrary/DocumentLibrary.py
@@ -0,0 +1,180 @@
+##################################################################################
+#
+# Kaivo Public Software License
+#
+# Copyright (c) 2001, Kaivo, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# o Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# o Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#
+# o Neither Kaivo nor the names of its contributors may be used to endorse
+# or promote products derived from this software without specific prior
+# written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL KAIVO OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+##################################################################################
+
+__doc__ = """A Zope Document Library"""
+__version__ = '1.0b2'
+
+# Document Library by Casey Duncan (cduncan@kaivo.com)
+# Kaivo, Inc. (http://www.kaivo.com)
+
+import os
+from string import strip, upper
+import Globals
+from Globals import DTMLFile
+from OFS.Folder import Folder
+from OFS.DTMLMethod import DTMLMethod
+from DateTime import DateTime
+from Products.ZCatalog.Vocabulary import Vocabulary
+import TopicIndex
+import DLGlobals
+import DocumentStore
+
+manage_addDocumentLibraryForm = DTMLFile('dtml/AddLibraryForm', globals())
+
+def manage_addDocumentLibrary(self, id, title='', vocab_id='', REQUEST=None):
+ """Construct a Document Library object"""
+
+ self._setObject(id, DocumentLibrary(id, title, vocab_id, self))
+
+ if REQUEST is not None:
+ return self.manage_main(self, REQUEST, update_menu=1)
+
+
+class DocumentLibrary(Folder):
+ """Document Library Zope Product"""
+
+ meta_type = 'Document Library'
+
+ __ac_permissions__ = Folder.__ac_permissions__ + (
+ ('View', ('setFlag', 'getFlag', 'clearFlag')),
+ ('Add Document Libraries', ('manage_addLibraryTopicIndexRootForm',
+ 'manage_addLibraryTopicIndexRoot',
+ 'manage_addLibraryDocumentStoreForm',
+ 'manage_addLibraryDocumentStore')),
+ )
+
+ # Allow DocumentStores and TopicIndexRoots to be created in us
+ meta_types = ({'name': TopicIndex.TopicIndexRoot.meta_type,
+ 'action': 'manage_addLibraryTopicIndexRootForm'},
+ {'name': DocumentStore.DocumentStore.meta_type,
+ 'action': 'manage_addLibraryDocumentStoreForm'})
+
+ manage_addLibraryTopicIndexRootForm = TopicIndex.manage_addLibraryTopicIndexRootForm
+ manage_addLibraryTopicIndexRoot = TopicIndex.manage_addLibraryTopicIndexRoot
+ manage_addLibraryDocumentStoreForm = DocumentStore.manage_addLibraryDocumentStoreForm
+ manage_addLibraryDocumentStore = DocumentStore.manage_addLibraryDocumentStore
+
+ #manage_options = (
+ # {'label': 'Contents', 'action': 'manage_main'},
+ #)
+
+ _properties = (
+ { 'id': 'title', 'type': 'string', 'mode': 'w' },
+ { 'id': 'hide_empty_topics', 'type': 'boolean', 'mode': 'w'},
+ { 'id': 'document_types', 'type': 'lines', 'mode': 'wd' },
+ { 'id': 'deny_content_types', 'type': 'lines', 'mode': 'wd' }
+ )
+
+ hide_empty_topics = 0 # for backward compatibility with existing libraries
+
+ # Export the special values to the namespace for better portability/readability
+ document_unreviewed_date = DLGlobals.document_unreviewed_date
+ document_rejected_date = DLGlobals.document_rejected_date
+ document_min_reviewed_date = DLGlobals.document_min_reviewed_date
+ document_no_creation_date = DLGlobals.document_no_creation_date
+
+ def __init__(self, id, title='', vocab_id='', container=None):
+ self.id = id
+ self.title = title
+ self.document_types = ['None Defined']
+ self.hide_empty_topics = 0
+
+ # MIME types not allowed to be uploaded. Here we are blanket restricting all
+ # executable and unknown types by default.
+ self.deny_content_types = ['application/octet-stream']
+
+ if not vocab_id:
+ # Add a global vocabulary
+ vocab_id = 'Vocabulary'
+ ob = Vocabulary(vocab_id, '', globbing=1)
+ self._setObject(vocab_id, ob)
+ else:
+ # Make ourselves into an acquisition wrapper to acquire the
+ # Vocabulary
+ if container:
+ self = self.__of__(container)
+ else:
+ raise AttributeError, ("You cannot specify a vocab_id without "
+ "also specifying a container.")
+ # Add the document storage
+ ob = DocumentStore.DocumentStore('Documents', '', vocab_id, container=self)
+ self._setObject('Documents', ob)
+
+ # Add the index topic root
+ ob = TopicIndex.TopicIndexRoot('Index', 'Top of Index', vocab_id, container=self)
+ self._setObject('Index', ob)
+
+ # Add the DTML instance methods
+ path = os.path.join(Globals.package_home(globals()), 'instance', 'methods')
+ for id in os.listdir(path):
+ file = os.path.join(path, id)
+ if not os.path.isdir(file):
+ file = open(file)
+ ob = DTMLMethod(file.read(), __name__=id)
+ self._setObject(id, ob)
+
+ def setFlag(self, flag_collection, flag_name, REQUEST=None):
+ """Sets a flag on REQUEST in the specified flag_collection, creating the
+ collection if it doesn't exist"""
+
+ if REQUEST is None:
+ REQUEST = self.REQUEST
+
+ flags = REQUEST.get(flag_collection, {})
+ flags[flag_name] = 1
+ REQUEST.set(flag_collection, flags)
+
+ def clearFlag(self, flag_collection, flag_name, REQUEST=None):
+ """Clears a flag on REQUEST in the specified flag_collection, creating the
+ collection if it doesn't exist"""
+
+ if REQUEST is None:
+ REQUEST = self.REQUEST
+
+ flags = REQUEST.get(flag_collection, {})
+ flags[flag_name] = 0
+ REQUEST.set(flag_collection, flags)
+
+ def getFlag(self, flag_collection, flag_name, REQUEST=None):
+ """Returns the value for a flag of the specified flag_collection. If the
+ flag does not exist, false is returned"""
+
+ if REQUEST is None:
+ REQUEST = self.REQUEST
+
+ flags = REQUEST.get(flag_collection, {})
+ return flags.has_key(flag_name) and flags[flag_name]
+
+Globals.InitializeClass(DocumentLibrary)
diff --git a/src/DocumentLibrary/DocumentStore.py b/src/DocumentLibrary/DocumentStore.py
new file mode 100644
index 0000000..2e77f94
--- /dev/null
+++ b/src/DocumentLibrary/DocumentStore.py
@@ -0,0 +1,480 @@
+##################################################################################
+#
+# Kaivo Public Software License
+#
+# Copyright (c) 2001, Kaivo, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# o Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# o Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#
+# o Neither Kaivo nor the names of its contributors may be used to endorse
+# or promote products derived from this software without specific prior
+# written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL KAIVO OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+##################################################################################
+
+__doc__ = """Document Library Document Storage"""
+__version__ = '1.0b2'
+
+# Document Store by Casey Duncan (cduncan@kaivo.com)
+# Kaivo, Inc. (http://www.kaivo.com)
+
+# This initial implementation uses a BTreeFolder to store the documents
+# in the ZODB. Later implementations will abstract this to allow the
+# option of storing the documents in a separate file system.
+
+from string import strip, split, join, find
+from StringIO import StringIO
+import Globals
+from Globals import DTMLFile
+from App.ImageFile import ImageFile
+from Products.BTreeFolder.BTreeFolder import BTreeFolder
+from CatalogPlus import CatalogPlus, FieldIndex, TextIndex, KeywordIndex
+from DateTime.DateTime import DateTime
+from ZPublisher.Converters import type_converters
+import DLGlobals
+import Document
+
+manage_addLibraryDocumentStoreForm = DTMLFile('dtml/AddDocumentStoreForm', globals())
+
+def manage_addLibraryDocumentStore(self, id, title='', vocab_id = None,
+ REQUEST=None):
+ """Add a document store instance to a library"""
+ ob = DocumentStore(id, title, vocab_id, self)
+ self._setObject(id, ob)
+
+ if REQUEST is not None:
+ REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
+
+
+class DocumentStore(BTreeFolder):
+ """Document library document storage class"""
+
+ meta_type="Document Store"
+ all_meta_types=({'name': 'Document File', 'action': 'manage_addDocumentFileForm'},)
+
+ manage_options = (
+ {'label': 'Contents', 'action': 'manage_main'},
+ {'label': 'Document Properties', 'action': 'manage_documentProperties'},
+ {'label': 'Catalog Indexes', 'action': 'manage_catalogIndexes'},
+ {'label': 'Catalog Metadata', 'action': 'manage_catalogColumns'},
+ {'label': 'Catalog Maintenance', 'action': 'manage_catalogMaintenance'},
+ {'label': 'Security', 'action': 'manage_access'},
+ {'label': 'Undo', 'action': 'manage_UndoForm'},
+ {'label': 'Owner', 'action': 'manage_owner'},
+ {'label': 'Find', 'action': 'manage_findForm'},
+ )
+
+ __ac_permissions__ = BTreeFolder.__ac_permissions__ + (
+ ('Add Document Library Files', ('manage_addDocumentFileForm', 'manage_addDocumentFile',
+ 'addDocumentFile', 'validateDocumentProperties')),
+ ('Add Document Library Files Without Review', ()),
+ ('Manage properties', ('manage_documentProperties',)),
+ ('Delete objects', ('deleteDocumentFile',)),
+ ('Search ZCatalog', ('query', 'getpath', 'getCatalogIndexes', 'getCatalogColumns')),
+ ('Manage ZCatalog Entries', ('manage_reinitCatalog', 'manage_catalogColumns',
+ 'manage_delCatalogColumns', 'manage_addCatalogColumn',
+ 'manage_catalogIndexes', 'manage_delCatalogIndexes',
+ 'manage_addCatalogIndex','manage_catalogMaintenance')),
+ )
+
+ _documentProperties = (
+ { 'id': 'title', 'type': 'string', 'required': 1, 'mode': 'w' },
+ { 'id': 'content_type', 'type': 'string', 'hidden': 1, 'mode': 'w' },
+ { 'id': 'filename', 'type': 'string', 'hidden': 1, 'default': '', 'mode': 'w'},
+ { 'id': 'type', 'type': 'selection', 'select_variable': 'document_types', 'default':'', 'required': 1 },
+ { 'id': 'description', 'type': 'text', 'default': '', 'required': 1 },
+ { 'id': 'topics', 'type': 'lines', 'default': [], 'required': 1, 'mode': 'w' },
+ { 'id': 'creator', 'type': 'string', 'default': '' },
+ { 'id': 'date', 'type': 'date',
+ 'default': DLGlobals.document_no_creation_date, 'mode': 'w' },
+ { 'id': 'review_date', 'type': 'date',
+ 'default': DLGlobals.document_unreviewed_date, 'mode': 'w' },
+ )
+
+ _defaultCatalogColumns = ( 'id', 'title', 'identifier', 'date', 'description' )
+ _defaultCatalogIndexes = (
+ ('id', FieldIndex),
+ ('title', TextIndex),
+ ('type', FieldIndex),
+ ('description', TextIndex),
+ ('topics', KeywordIndex),
+ ('super_topics', KeywordIndex),
+ ('creator', TextIndex),
+ ('date', FieldIndex),
+ ('review_date', FieldIndex),
+ ('all_searchable_text', TextIndex),
+ ('owner_id', FieldIndex),
+ )
+
+ def __init__(self, id, title='', vocab_id=None, container=None):
+ self.id = id
+ self.title = title
+
+ if vocab_id is not None and container is None:
+ raise AttributeError, 'You cannot specify a vocab_id without' \
+ 'also specifying a container.'
+ if container is not None:
+ self=self.__of__(container)
+
+ # Setup the document store schema and indexes
+ self.Catalog = CatalogPlus(vocab_id)
+ lexicon = self.Catalog.getLexicon()
+
+ for name in self._defaultCatalogColumns:
+ self.Catalog.addColumn(name)
+
+ for name, index_class in self._defaultCatalogIndexes:
+ if index_class is TextIndex:
+ self.Catalog.addIndex(name, index_class(name, lexicon=lexicon))
+ else:
+ self.Catalog.addIndex(name, index_class(name))
+
+ try: # Be flexible with the BTreeFolder implementation
+ BTreeFolder.__init__(self)
+ except:
+ BTreeFolder.__init__(self, id)
+
+ DocumentStore_icon = ImageFile('www/DocumentStore_icon.gif', globals())
+ def icon(self):
+ """Managment icon"""
+ return join(self.getPhysicalPath()[1:],'/') + '/DocumentStore_icon'
+
+ def lex(self):
+ """debug"""
+ return self.Catalog.lexicon
+
+ #
+ # Document property methods
+ #
+
+ def defaultPropertyValue(self, propertyId, d=None):
+ """Return the default value for a document property"""
+ for p in self._documentProperties:
+ if p['id'] == propertyId:
+ try:
+ return p['default']
+ except KeyError:
+ return d
+ return d
+
+ def documentPropertyMap(self):
+ """Return mapping of document property schema"""
+ return self._documentProperties
+
+ def documentPropertyDict(self):
+ """Return a dictionary of property info keyed on id"""
+ propdict = {}
+
+ for prop in self._documentProperties:
+ propdict[prop['id']] = prop
+
+ return propdict
+
+ def _setError(self, field, message, REQUEST, error_template):
+ """Set a validation error into REQUEST"""
+ REQUEST.set('ERROR_' + field, error_template % message)
+
+ def validateDocumentProperties(self, REQUEST,
+ error_template='
%s
'):
+ """Validates document properties passed on the REQUEST object
+ raises a ValidationError and puts error messages into REQUEST if
+ errors are found"""
+ errors = 0
+
+ for p in self._documentProperties:
+ field = p['id']
+ ftype = p['type']
+ value = REQUEST.get(field, None)
+
+ if p.has_key('required') and p['required'] and not value \
+ or (ftype in ('lines','list','tokens') and value == ['']):
+ self._setError(field, 'Required Field', REQUEST, error_template)
+ errors = errors + 1
+ elif ftype == 'date' and value:
+ try:
+ DateTime(value)
+ except:
+ self._setError(field, 'Invalid Date', REQUEST, error_template)
+ errors = errors + 1
+ elif ftype == 'int' and value:
+ try:
+ int(value)
+ except:
+ self._setError(field, 'Invalid Integer Value', REQUEST, error_template)
+ errors = errors + 1
+ elif ftype == 'float' and value:
+ try:
+ float(v)
+ except:
+ self._setError(field, 'Invalid Real Number', REQUEST, error_template)
+ errors = errors + 1
+
+ if REQUEST.has_key('file') \
+ and not (REQUEST.file.filename or REQUEST.get('url','http://') != 'http://'):
+ self._setError('file', 'Specify a File or a URL to Submit',
+ REQUEST, error_template)
+ errors = errors + 1
+
+ if errors == 1:
+ message = ('1 error was found on the form. '
+ 'Please correct the noted field '
+ 'and resubmit the form.')
+ elif errors > 1:
+ message = ('%d errors were found on the form. '
+ 'Please correct the noted fields '
+ 'and resubmit the form.') % errors
+
+ if errors:
+ if REQUEST.has_key('file'):
+ message = message + (' Note: Due to browser limitations, '
+ 'you may need to specify the file you are '
+ 'submitting again.')
+ REQUEST.set('ERROR_FORM', error_template % message)
+ raise 'ValidationError', '%d Form Field Error(s) Found' % errors
+
+ manage_documentProperties = DTMLFile('dtml/EditDocumentProperties', globals())
+
+ def manage_editDocumentProperties(self, REQUEST, RESPONSE=None):
+ """Modify the document properties' default values"""
+
+ editedProperties = []
+
+ for prop in self._documentProperties:
+ propvalue = REQUEST.get(prop['id'], '')
+ proptype = prop.get('type', 'string')
+
+ if type_converters.has_key(proptype):
+ propvalue = type_converters[proptype](propvalue)
+
+ prop['default'] = propvalue
+
+ required_flag = prop['id'] + '_required_'
+
+ if REQUEST.has_key(required_flag) and REQUEST[required_flag]:
+ prop['required'] = 1
+ else:
+ if prop.has_key('required'): del prop['required']
+
+ editedProperties.append(prop)
+
+ self._documentProperties = tuple(editedProperties)
+
+ if REQUEST and RESPONSE:
+ RESPONSE.redirect(REQUEST.URL1 + ('/manage_documentProperties?'
+ 'manage_tabs_message=Properties+Updated'))
+
+ def manage_delDocumentProperties(self, ids=None, REQUEST=None, RESPONSE=None):
+ """Delete one or more document properties specified by 'ids'."""
+ if not ids:
+ raise 'BadRequest', "No properties were specified"
+
+ propdict=self.documentPropertyDict()
+ nd=self._reserved_names
+ for id in ids:
+ if not propdict.has_key(id):
+ raise 'BadRequest', (
+ 'The property %s does not exist' % id)
+ if (not 'd' in propdict[id].get('mode', 'wd')) or (id in nd):
+ raise 'BadRequest', (
+ 'The property %s cannot be deleted.' % id)
+ del propdict[id]
+
+ self._documentProperties = tuple(map(lambda i: i[1], propdict.items()))
+
+ if REQUEST and RESPONSE:
+ RESPONSE.redirect(REQUEST.URL1 + ('/manage_documentProperties'
+ '?manage_tabs_message=Properties+Deleted'))
+
+ def manage_addDocumentProperty(self, id, default_value, type, required=0,
+ REQUEST=None, RESPONSE=None):
+ """Add a document property"""
+ # Raise an error if an value is wrapped.
+ if hasattr(default_value, 'aq_base'):
+ raise ValueError, 'Invalid property value: wrapped object'
+
+ # Check that the property id is valid
+ docpropnames = map(lambda p: p['id'], self._documentProperties)
+
+ if not id or id[:1]=='_' or (id[:3]=='aq_') \
+ or (' ' in id) or hasattr(self.aq_base, id) or (id in docpropnames):
+ raise 'Bad Request', 'Invalid or duplicate property id'
+
+ # Convert the value to the proper type
+ if type_converters.has_key(type):
+ default_value = type_converters[type](default_value)
+
+ prop = {}
+ prop['id'] = id
+ prop['type'] = type
+
+ if not type in ('selection', 'multiple selection'):
+ prop['default'] = default_value
+ else:
+ prop['select_variable'] = default_value
+ prop['default'] = ''
+
+ if required: prop['required'] = 1
+ self._documentProperties = self._documentProperties + (prop,)
+
+ if REQUEST and RESPONSE:
+ RESPONSE.redirect('manage_documentProperties?manage_tabs_message=Property+Added')
+
+ #
+ # Document management methods
+ #
+
+ manage_addDocumentFileForm = Document.manage_addDocumentFileForm
+ manage_addDocumentFile = Document.manage_addDocumentFile
+
+ def addDocumentFile(self, REQUEST):
+ """Adds a new document and sets its properties from a form based submission"""
+
+ self=self.this()
+ doc = Document.Document(title=REQUEST.title, container=self)
+ self._setObject(doc.getId(), doc)
+ doc = self._getOb(doc.getId())
+
+ # See if the user can skip the document review
+ if REQUEST.AUTHENTICATED_USER.has_permission('Add Document Library Files Without Review',
+ self):
+ REQUEST.set('review_date', DateTime())
+ else:
+ REQUEST.set('review_date', DLGlobals.document_unreviewed_date)
+
+ # Set the properties, but don't index yet
+ doc.manage_changeProperties(REQUEST, reindex=0)
+ # See if a file or url was specified
+ if not REQUEST.file.filename and REQUEST.get('url', 'http://') != 'http://':
+ doc.manage_upload(file=REQUEST.url, remote=1)
+ else:
+ doc.manage_upload(REQUEST.file)
+
+ if not Document.check_content_type(self, doc.content_type):
+ self.manage_delObjects(doc.getId())
+ raise "IllegalFileType", \
+ "That file type cannot be uploaded into the library."
+
+ return doc
+
+ def deleteDocumentFile(self, REQUEST=None, RESPONSE=None):
+ """Deletes a document from the document store"""
+ self.manage_delObjects(REQUEST.id)
+
+ if REQUEST and RESPONSE and REQUEST.has_key('last_url'):
+ RESPONSE.redirect(REQUEST.last_url + '?' + REQUEST.get('last_query',''))
+
+ #
+ # Catalog query support methods
+ #
+
+ def query(self, REQUEST=None, **kw):
+ """Query the document store for matching documents"""
+ return apply(self.Catalog.searchResults, (REQUEST,), kw)
+
+ def getpath(self, rid):
+ """
+ Return the path to a cataloged object given a 'data_record_id_'
+ Used by the Catalog brains
+ """
+ return self.Catalog.paths[rid]
+
+ #
+ # Catalog management methods
+ #
+
+ manage_catalogColumns = DTMLFile('dtml/catalogColumns', globals())
+
+ def getCatalogColumns(self):
+ """Returns the metadata columns in the catalog"""
+ return self.Catalog.schema.keys()
+
+ def manage_delCatalogColumns(self, names, REQUEST=None, RESPONSE=None):
+ """Delete Catalog metadata column"""
+ if type(names) == type(''):
+ names = [names]
+
+ for name in names:
+ self.Catalog.delColumn(name)
+
+ if REQUEST and RESPONSE:
+ RESPONSE.redirect('manage_catalogColumns?manage_tabs_message=Catalog+Metadata+Deleted')
+
+ def manage_addCatalogColumn(self, name, REQUEST=None, RESPONSE=None):
+ """Add Catalog metdata column"""
+ self.Catalog.addColumn(name)
+
+ if REQUEST and RESPONSE:
+ RESPONSE.redirect('manage_catalogColumns?manage_tabs_message=Catalog+Metadata+Column+Added')
+
+ manage_catalogIndexes = DTMLFile('dtml/catalogIndexes', globals())
+
+ def getCatalogIndexes(self):
+ """Returns the indexes in the catalog"""
+ return self.Catalog.indexes.values()
+
+ def manage_delCatalogIndexes(self, names, REQUEST=None, RESPONSE=None):
+ """Delete Catalog index"""
+ if type(names) == type(''):
+ names = [names]
+
+ for name in names:
+ self.Catalog.delIndex(name)
+
+ if REQUEST and RESPONSE:
+ RESPONSE.redirect('manage_catalogIndexes?manage_tabs_message=Catalog+Index+Deleted')
+
+ def manage_addCatalogIndex(self, name, type, REQUEST=None, RESPONSE=None):
+ """Add Catalog index"""
+ index_class={'FieldIndex': FieldIndex,
+ 'TextIndex': TextIndex,
+ 'KeywordIndex': KeywordIndex}[type]
+
+ if index_class is TextIndex:
+ self.Catalog.addIndex(name,
+ index_class(name, lexicon=self.Catalog.getLexicon()))
+ else:
+ self.Catalog.addIndex(name, index_class(name))
+
+ if REQUEST and RESPONSE:
+ RESPONSE.redirect('manage_catalogIndexes?manage_tabs_message=Catalog+Index+Added')
+
+ manage_catalogMaintenance = DTMLFile('dtml/catalogMaintenance', globals())
+
+ def manage_reinitCatalog(self, REQUEST=None, RESPONSE=None):
+ """Reinitialize the document store catalog"""
+
+ self.Catalog.clear()
+
+ for doc in self.objectValues():
+ try:
+ doc.setIcons()
+ if doc.is_accepted(): doc.showTopics()
+ except:
+ pass
+ doc.index_object()
+
+ if REQUEST and RESPONSE:
+ RESPONSE.redirect('manage_catalogMaintenance?manage_tabs_message=Catalog+Reinitialized')
+
+Globals.InitializeClass(DocumentStore)
diff --git a/src/DocumentLibrary/FileConverters/CVS/Entries b/src/DocumentLibrary/FileConverters/CVS/Entries
new file mode 100644
index 0000000..9e39766
--- /dev/null
+++ b/src/DocumentLibrary/FileConverters/CVS/Entries
@@ -0,0 +1,9 @@
+/__init__.py/1.1.1.1/Fri Jan 18 04:38:43 2002//
+/doc.py/1.1.1.1/Fri Jan 18 04:38:43 2002//
+/html.py/1.1.1.1/Fri Jan 18 04:38:43 2002//
+/pdf.py/1.1.1.1/Fri Jan 18 04:38:43 2002//
+/ppt.py/1.1.1.1/Fri Jan 18 04:38:43 2002//
+/ps.py/1.1.1.1/Fri Jan 18 04:38:43 2002//
+/wvText.xml/1.1.1.1/Fri Jan 18 04:38:43 2002//
+/xls.py/1.1.1.1/Fri Jan 18 04:38:43 2002//
+D
diff --git a/src/DocumentLibrary/FileConverters/CVS/Repository b/src/DocumentLibrary/FileConverters/CVS/Repository
new file mode 100644
index 0000000..05376e6
--- /dev/null
+++ b/src/DocumentLibrary/FileConverters/CVS/Repository
@@ -0,0 +1 @@
+DocumentLibrary/FileConverters
diff --git a/src/DocumentLibrary/FileConverters/CVS/Root b/src/DocumentLibrary/FileConverters/CVS/Root
new file mode 100644
index 0000000..2084eca
--- /dev/null
+++ b/src/DocumentLibrary/FileConverters/CVS/Root
@@ -0,0 +1 @@
+:pserver:anonymous@cvs.nlada-library.sourceforge.net:/cvsroot/nlada-library
diff --git a/src/DocumentLibrary/FileConverters/__init__.py b/src/DocumentLibrary/FileConverters/__init__.py
new file mode 100644
index 0000000..6c67436
--- /dev/null
+++ b/src/DocumentLibrary/FileConverters/__init__.py
@@ -0,0 +1,29 @@
+"""File converters for the document library product"""
+
+from os import listdir
+
+def getFileConverters():
+ """Import converter modules from this directory
+ and return them as a dictionary keyed by content_type.
+
+ Each converter module should define an attribute content_type and a function
+ def convert(data) which should return text given data in the
+ format the converter understands. content_type may be a string or a
+ sequence of strings if several content_types are equivilant"""
+
+ cvtr_dict = {}
+
+ for cvtr_name in listdir(__path__[0]):
+ if cvtr_name[0] != '_' and cvtr_name[-3:] == '.py':
+ cvtr = __import__(cvtr_name[:-3], globals(), globals(), __path__)
+
+ if hasattr(cvtr, 'content_type') and hasattr(cvtr, 'convert'):
+ if type(cvtr.content_type) == type(''):
+ cvtr_dict[cvtr.content_type] = cvtr
+ else:
+ for c_type in cvtr.content_type:
+ cvtr_dict[c_type] = cvtr
+
+ return cvtr_dict
+
+FileConverters = getFileConverters()
diff --git a/src/DocumentLibrary/FileConverters/doc.py b/src/DocumentLibrary/FileConverters/doc.py
new file mode 100644
index 0000000..dfed561
--- /dev/null
+++ b/src/DocumentLibrary/FileConverters/doc.py
@@ -0,0 +1,26 @@
+"""MSWord doc to text file converter for Document Library"""
+
+from os import popen, remove, path
+from Globals import package_home
+
+content_type = ('application/msword', 'application/ms-word', 'application/vnd.ms-word')
+
+content_description = """Microsoft Word [
+Download MS Word Viewer]"""
+
+wvConf_file = path.join(package_home(globals()), 'wvText.xml')
+
+# Uses the wvWare, and assumes it is in the path
+# Requires write access to /tmp
+
+def convert(documentFile):
+ """Convert MSWord data to raw text"""
+
+ tmp_name = documentFile._writeToTempFile()
+ text = popen('wvWare -x %s %s 2> /dev/null' % (wvConf_file, tmp_name)).read()
+ remove(tmp_name)
+
+ return text
+
+# For increased speed, comment out line 400 of text.c in the wv source
diff --git a/src/DocumentLibrary/FileConverters/html.py b/src/DocumentLibrary/FileConverters/html.py
new file mode 100644
index 0000000..84305e7
--- /dev/null
+++ b/src/DocumentLibrary/FileConverters/html.py
@@ -0,0 +1,46 @@
+"""HTML to text file converter for Document Library"""
+
+from sgmllib import SGMLParser
+from string import join
+
+content_type = ('text/html', 'text/xml', 'text/sgml')
+
+content_description = 'Web Page (HTML)'
+
+# Code taken from Dieter Maurer's CatalogSupport Module
+# http://www.handshake.de/~dieter/pyprojects/zope
+# Thank you!
+
+class _StripTagParser(SGMLParser):
+ '''SGML Parser removing any tags and translating HTML entities.'''
+
+ from htmlentitydefs import entitydefs
+
+ data= None
+
+ def handle_data(self,data):
+ if self.data is None: self.data=[]
+ self.data.append(data)
+
+ def __str__(self):
+ if self.data is None: return ''
+ return join(self.data,'')
+
+
+def convert(doc):
+ """Convert html data to raw text"""
+
+ p = _StripTagParser()
+
+ try:
+ data = doc.data
+ if type(data) == type(''):
+ p.feed(data)
+ else:
+ while data is not None:
+ p.feed(data.data)
+ data = data.next
+ p.close()
+ return str(p)
+ except:
+ return ''
diff --git a/src/DocumentLibrary/FileConverters/pdf.py b/src/DocumentLibrary/FileConverters/pdf.py
new file mode 100644
index 0000000..12707e0
--- /dev/null
+++ b/src/DocumentLibrary/FileConverters/pdf.py
@@ -0,0 +1,19 @@
+"""PDF to text file converter for Document Library"""
+
+from os import popen, remove
+
+content_type = 'application/pdf'
+
+content_description = """Adobe Acrobat PDF [Download Acrobat Reader]"""
+
+# Uses the built-in linux pdftotext command, and assumes it is in the path
+# Requires write access to /tmp
+
+def convert(documentFile):
+ """Convert pdf data to raw text"""
+
+ tmp_name = documentFile._writeToTempFile()
+ text = popen('pdftotext %s -' % tmp_name).read()
+ remove(tmp_name)
+
+ return text
diff --git a/src/DocumentLibrary/FileConverters/ppt.py b/src/DocumentLibrary/FileConverters/ppt.py
new file mode 100644
index 0000000..11f2b03
--- /dev/null
+++ b/src/DocumentLibrary/FileConverters/ppt.py
@@ -0,0 +1,47 @@
+"""MSPowerpoint doc to text file converter for Document Library"""
+
+from os import popen, remove
+from sgmllib import SGMLParser
+from string import join
+
+content_type = ('application/mspowerpoint', 'application/ms-powerpoint',
+ 'application/vnd.ms-powerpoint')
+
+content_description = """Microsoft Powerpoint [Download Powerpoint Viewer]"""
+
+#uses pptHTML http://www.xlhtml.org
+# Requires write access to /tmp
+
+class _StripTagParser(SGMLParser):
+ '''SGML Parser removing any tags and translating HTML entities.'''
+
+ from htmlentitydefs import entitydefs
+
+ data= None
+
+ def handle_data(self,data):
+ if self.data is None: self.data=[]
+ self.data.append(data)
+
+ def __str__(self):
+ if self.data is None: return ''
+ return join(self.data,'')
+
+
+def convert(documentFile):
+ """Convert MS-Powerpoint data to raw text"""
+
+ tmp_name = documentFile._writeToTempFile()
+ text = popen('pptHtml %s 2> /dev/null' % (tmp_name)).read()
+ remove(tmp_name)
+
+ p = _StripTagParser()
+
+ try:
+ p.feed(text)
+ p.close()
+ return str(p)
+ except:
+ return ''
diff --git a/src/DocumentLibrary/FileConverters/ps.py b/src/DocumentLibrary/FileConverters/ps.py
new file mode 100644
index 0000000..aec0c62
--- /dev/null
+++ b/src/DocumentLibrary/FileConverters/ps.py
@@ -0,0 +1,22 @@
+"""Postscript to text file converter for Document Library"""
+
+from os import popen, remove, rename
+
+content_type = 'application/postscript'
+
+content_description = 'Adobe Postscript Document'
+
+# Uses the prescript program available at:
+# http://www.nzdl.org/html/prescript.html
+# Requires write access to /tmp
+
+def convert(documentFile):
+ """Convert postscript data to raw text"""
+
+ tmp_name = documentFile._writeToTempFile()
+ rename(tmp_name, tmp_name + '.ps')
+ tmp_name = tmp_name + '.ps'
+ text = popen('prescript plain %s - 2> /dev/null' % tmp_name).read()
+ remove(tmp_name)
+
+ return text
diff --git a/src/DocumentLibrary/FileConverters/wvText.xml b/src/DocumentLibrary/FileConverters/wvText.xml
new file mode 100644
index 0000000..e05d9fb
--- /dev/null
+++ b/src/DocumentLibrary/FileConverters/wvText.xml
@@ -0,0 +1,355 @@
+
+
+ABW
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DocumentLibrary/FileConverters/xls.py b/src/DocumentLibrary/FileConverters/xls.py
new file mode 100644
index 0000000..867d380
--- /dev/null
+++ b/src/DocumentLibrary/FileConverters/xls.py
@@ -0,0 +1,48 @@
+"""MSExcel doc to text file converter for Document Library
+ kedai@kedai.com.my"""
+
+
+from os import popen, remove, path
+from sgmllib import SGMLParser
+from string import join
+
+content_type = ('application/msexcel', 'application/ms-excel', 'application/vnd.ms-excel')
+
+content_description = """Microsoft Excel [Download MS Excel Viewer]"""
+
+#uses xlHTML http://www.xlhtml.org
+# Requires write access to /tmp
+
+class _StripTagParser(SGMLParser):
+ '''SGML Parser removing any tags and translating HTML entities.'''
+
+ from htmlentitydefs import entitydefs
+
+ data= None
+
+ def handle_data(self,data):
+ if self.data is None: self.data=[]
+ self.data.append(data)
+
+ def __str__(self):
+ if self.data is None: return ''
+ return join(self.data,'')
+
+
+def convert(documentFile):
+ """Convert MS-Excel data to raw text"""
+
+ tmp_name = documentFile._writeToTempFile()
+ text = popen('xlHtml -nc %s 2> /dev/null' % (tmp_name)).read()
+ remove(tmp_name)
+
+ p = _StripTagParser()
+
+ try:
+ p.feed(text)
+ p.close()
+ return str(p)
+ except:
+ return ''
diff --git a/src/DocumentLibrary/HISTORY.txt b/src/DocumentLibrary/HISTORY.txt
new file mode 100644
index 0000000..913283f
--- /dev/null
+++ b/src/DocumentLibrary/HISTORY.txt
@@ -0,0 +1,217 @@
+DocumentLibrary Product change history
+
+1.0rc1 - 6/23/02
+
+ - Fixed file name bug when uploading documents by URL with a query string.
+
+ - Now uses CatalogPathAwareness for virtual hosting compatibility
+
+ - Fixed unpickleable error on file info changes in Zope 2.5+
+
+ - Minor doc updates.
+
+ - Fixed instantiation bug when working with cvs checkout of DL.
+
+1.0b3 - 10/30/01
+
+ - Fixed a bug where changes to the document property "required" flag were
+ not being properly committed.
+
+ - Document file icons will now be properly updated when you change the
+ content-type without uploading a new file.
+
+ - All the various MIME types that are used for MS Office files are now
+ included. Why they need 3 different type strings for every single format
+ I'll never know. 8^)
+
+ - A Postscript text converter has been added, allows Postscript documents
+ to be indexed.
+
+ - The code to create the link to open the document info window has been
+ simplified down to one line.
+
+ - Fixed a bug that prevented instantiating a library in Zope 2.4.2.
+ Thanks go to Heimo Laukkanen for reporting this.
+
+ - Fixed a bug that prevented document_browse and index_chooser from
+ working in Zope 2.4.2. Thanks again to Heimo Laukkanen.
+
+ - The document_edit form now allows you to upload a new document or URL
+ to replace the existing one.
+
+ - Fixed some misspellings in the search_tips page.
+
+1.0b2 - 7/31/01
+
+ - A minor JavaScript buglet in the index_chooser has been fixed.
+ Thanks to Jussi Talaskivi for tracking this down.
+
+ - A missing semi-colon in the document_batch_links has been added.
+ Thanks to Dave Bukove and Kedai for finding this one.
+
+ - The filename submitted from Internet Explorer is now properly
+ stripped of the file path. Thanks again to Jussi Talaskivi for
+ pointing this out.
+
+ - The document_edit method now properly redirects when you
+ are reviewing documents. Thanks again to Dave Bukove for pointing
+ this out.
+
+ - An MS Powerpoint text converter has been added, rounding out
+ the MSOffice indexing support. Also, icons for Excel and Powerpoint
+ files are now included.
+
+ - A bug that prevented permission changes on the DocumentStore has
+ been fixed.
+
+ - A bug that prevented the document review link from appearing properly
+ has been fixed.
+
+ - The catalog text indexes in Zope 2.4 properly acquire and use the
+ global (or user specified) vocabulary.
+
+1.0b1 - 7/16/01
+
+ - It was not possible to restrict access to index topics by changing
+ their security permissions. Doing so would make it impossible for
+ users to browse the library. The browsing code has now been updated
+ to skip unauthorized index topics. Thanks to Sedat Yilmazer for
+ pointing this out.
+
+ - The document text converters have been simplified and made more
+ cross-platform friendly. They also consume less server memory for
+ large document file data.
+
+ - There is now a delete button on the document_edit screen if you
+ have rights to delete the document. This is something I had added
+ to my own library instances, but never made it to release. Thanks to
+ Larry Prikockis for pointing out this omission.
+
+ - A typo in the permissions declarations of DocumentStore has been
+ corrected.
+
+ - The product now uses the PluginIndexes interface for its Catalogs
+ and therefore is now compatible with Zope 2.4. Yea!!
+
+ - Form validation has been added to the document_submit and
+ document_edit forms.
+
+ - Friendly error messages have been created for invalid file and
+ URL submissions.
+
+ - A message now appears informing the user that a submission
+ succeeded. It also includes a link to the document so that the
+ user can edit it right after submission. For this to work, the
+ owner role should have the permission "Manage properties" for
+ the Library.
+
+0.5a - 6/27/01
+
+ - The AddDocumentFile method of the Document Store now returns the
+ document object that was added so that it can be further
+ manipulated or accessed immediately after adding it.
+
+ - Fixed a major bug in the Document class that was causing the
+ text conversion to fail for large documents because their raw data
+ was truncated when it was passed to the converter.
+
+ - Added a text converter for MS Excel. Thanks go out to Kendai for
+ contributing this code.
+
+0.4a - 6/19/01
+
+ - Added a new permission "Add Document Library Files Without Review"
+ which allows selected roles to do just that.
+
+ - A bug in the document_submit and document_edit methods that prevented
+ the creator document field from being saved has been fixed. Thanks go
+ to Rogerio Atem for pointing this one out.
+
+ - A bug where cached full-text of document files was not properly flushed
+ out when a new file was uploaded has been fixed.
+
+ - The document_edit DTML form has a pragma: no-cache header set to prevent
+ caching of stale document data on the client-side.
+
+ - URL to download MSWord viewer has been updated.
+
+ - Default document property values set in the document store now act
+ like real attribute values of documents.
+
+ - You can now add selection and multiple selection property types
+ to the document properties without blowing things up. Thanks go to
+ David Jacobs for finding this bug.
+
+0.3a - 5/25/01
+
+ - Fixed MIME type handling to properly deal with browsers submitting
+ extra data in the Content-Type header. Thanks to Dieter Stubler
+ for pointing this out.
+
+ - wvWare errors (which can be numerous, if superfluous) are now
+ redirected to /dev/null to enhance performance and avoid filling
+ log files with gibberish.
+
+ - The file name of an uploaded file or URL target is now stored in the
+ filename property of its document file object. This file name also
+ becomes an attribute of the file which allows you to use the file
+ name as the target of the url for a better document downloading
+ experience. For instance, the following document:
+
+ http://Zope/Library/Documents/6789564.678
+
+ can now be accessed as:
+
+ http://Zope/Library/Documents/6789564.678/myfile.doc
+
+ assuming its filename is "myfile.doc". This way the client can
+ download the file under its original name.
+
+ - The document identifier attribute (from Dublin Core) now returns the
+ full URL including the filename (as above). The default
+ DTML methods now use the identifier instead of absolute_url() to make
+ links to the documents.
+
+ - Tightened up security a bit.
+
+ - Added Document property management tab to document store. Yea!!
+
+ - Added Catalog management tabs to document store. Double Yea!!
+
+ - Made search forms repopulate explicitly from REQUEST to avoid various
+ namespace acquisition problems. Thanks to Joseph Schlesinger for
+ pointing this out.
+
+ - You can now instantiate new Document Store and Topic Index Root
+ objects inside a Document Library.
+
+ - Changed global date constants for greater Windows compatibility
+ (Still no Windows testing has been done by me, however)
+
+0.2a - 5/18/01
+
+ - Removed broken symlink from product directory
+
+ - Fixed documentation typos
+
+ - Added the ability to specify an external vocabulary when creating a
+ library.
+
+ - Removed some code that was not longer used.
+
+ - Switched pathing of wvWare config files in the MSWord file converter
+ to make sure the ones supplied with the product were used, rather than
+ relying on them being installed in their default places.
+
+ - Various DTML instance method buglets and snafus were resolved.
+
+ - Added a hide_empty_topics property to the library, which enables a
+ thinned down browser interface for use with thinly populated topic
+ indexes. Great when you have 100 topics and 5 documents.
+
+ - Fixed a bug which caused cross-referenced topic indexes assigned to
+ documents to multiply when the document was edited.
+
+0.1a - 5/4/01
+
+ - Initial Release
diff --git a/src/DocumentLibrary/INSTALL.txt b/src/DocumentLibrary/INSTALL.txt
new file mode 100644
index 0000000..6dfbdc0
--- /dev/null
+++ b/src/DocumentLibrary/INSTALL.txt
@@ -0,0 +1,56 @@
+Kaivo DocumentLibrary Product Installation
+
+ Compatibility
+
+ At present the DocumentLibrary product runs on Unix
+ systems only. It has been tested on RedHat Linux version 7.x.
+
+ Win32 compatibility is something that I will add (see TODO.txt)
+ in the future. In fact, everything except the PDF and Word
+ document converters should work. I have done zero testing
+ on this, however.
+
+ Installation Procedure
+
+ Extract the package into your Zope products directory.
+ (Zope/lib/python/Products).
+
+ For MSWord document text indexing support, you must install
+ wvWare, an open source MSWord document conversion utility.
+ You can download wvWare from: http://www.wvWare.com/
+
+ Once wvWare is installed, make sure you can execute
+ the wvWare command as the Zope user. If you cannot, adjust
+ your PATH environment variable accordingly.
+
+ The PDF converter requires the utility pdftotext which is
+ part of the open source Xpdf package. My RedHat
+ installation included this, but you can download it and
+ install it yourself from: http://www.foolabs.com/xpdf/
+
+ Like wvWare, pdftotext must be in your Zope user's path
+ for the converter to function.
+
+ The Excel and Powerpoint converters requires the open source
+ xlHtml converter available at: http://www.xlhtml.org
+
+ The Postscript converter requires the open source Prescript
+ program available at: http://www.nzdl.org/html/prescript.html
+
+ In order for any of the above converters to work properly, the
+ zope process must have write access to the system temporary
+ directory (i.e. /tmp)
+
+ Testing the Converters
+
+ Once you get everything installed, you can easily test the
+ converters.
+
+ In Zope, add a Document Library object. Inside it will
+ be a folder called Documents. In there, click on the
+ "Add Document File" button. Upload a document of each
+ type you wish to test.
+
+ Select each document one at a time and click on the "Edit"
+ button. Then click on the "View Plain Text" tab. If all
+ goes well, the text should appear.
diff --git a/src/DocumentLibrary/IconImage.py b/src/DocumentLibrary/IconImage.py
new file mode 100644
index 0000000..4b2321b
--- /dev/null
+++ b/src/DocumentLibrary/IconImage.py
@@ -0,0 +1,167 @@
+##############################################################################
+#
+# Zope Public License (ZPL) Version 1.0
+# -------------------------------------
+#
+# Copyright (c) Digital Creations. All rights reserved.
+#
+# This license has been certified as Open Source(tm).
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions in source code must retain the above copyright
+# notice, this list of conditions, and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions, and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# 3. Digital Creations requests that attribution be given to Zope
+# in any manner possible. Zope includes a "Powered by Zope"
+# button that is installed by default. While it is not a license
+# violation to remove this button, it is requested that the
+# attribution remain. A significant investment has been put
+# into Zope, and this effort will continue if the Zope community
+# continues to grow. This is one way to assure that growth.
+#
+# 4. All advertising materials and documentation mentioning
+# features derived from or use of this software must display
+# the following acknowledgement:
+#
+# "This product includes software developed by Digital Creations
+# for use in the Z Object Publishing Environment
+# (http://www.zope.org/)."
+#
+# In the event that the product being advertised includes an
+# intact Zope distribution (with copyright and license included)
+# then this clause is waived.
+#
+# 5. Names associated with Zope or Digital Creations must not be used to
+# endorse or promote products derived from this software without
+# prior written permission from Digital Creations.
+#
+# 6. Modified redistributions of any form whatsoever must retain
+# the following acknowledgment:
+#
+# "This product includes software developed by Digital Creations
+# for use in the Z Object Publishing Environment
+# (http://www.zope.org/)."
+#
+# Intact (re-)distributions of any official Zope release do not
+# require an external acknowledgement.
+#
+# 7. Modifications are encouraged but must be packaged separately as
+# patches to official Zope releases. Distributions that do not
+# clearly separate the patches from the original work must be clearly
+# labeled as unofficial distributions. Modifications which do not
+# carry the name Zope may be packaged in any form, as long as they
+# conform to all of the clauses above.
+#
+#
+# Disclaimer
+#
+# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
+# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#
+# This software consists of contributions made by Digital Creations and
+# many individuals on behalf of Digital Creations. Specific
+# attributions are listed in the accompanying credits file.
+#
+##############################################################################
+"""Image object uploaded via the web"""
+
+__version__='0.4a'
+
+from OFS.content_types import guess_content_type
+from App.Common import rfc1123_date
+from string import rfind, split
+from DateTime import DateTime
+from time import time
+from OFS.Traversable import Traversable
+from OFS.SimpleItem import Item_w__name__
+from Acquisition import Implicit
+from Globals import Persistent
+import string
+
+
+class IconImage(Persistent, Implicit, Item_w__name__):
+ """Simple icon image objects uploaded via the web"""
+
+ def __init__(self, id, file):
+ self.data=data=file.read()
+
+ if file.headers.has_key('Content-Type'):
+ content_type = file.headers['Content-Type']
+ else:
+ content_type, enc = guess_content_type(file.filename, data)
+
+ if content_type:
+ self.content_type=content_type
+ else:
+ self.content_type = 'image/%s' % file.filename[rfind(file.filename,'.')+1:]
+ self.__name__ = id
+ self.lmt=time()
+ self.lmh=rfc1123_date(self.lmt)
+
+
+ def index_html(self, REQUEST, RESPONSE):
+ """Default document"""
+ # HTTP If-Modified-Since header handling. This is duplicated
+ # from OFS.Image.Image - it really should be consolidated
+ # somewhere...
+ header=REQUEST.get_header('If-Modified-Since', None)
+ if header is not None:
+ header=string.split(header, ';')[0]
+ # Some proxies seem to send invalid date strings for this
+ # header. If the date string is not valid, we ignore it
+ # rather than raise an error to be generally consistent
+ # with common servers such as Apache (which can usually
+ # understand the screwy date string as a lucky side effect
+ # of the way they parse it).
+ try: mod_since=long(DateTime(header).timeTime())
+ except: mod_since=None
+ if mod_since is not None:
+ if getattr(self, 'lmt', None):
+ last_mod = long(self.lmt)
+ else:
+ last_mod = long(0)
+ if last_mod > 0 and last_mod <= mod_since:
+ RESPONSE.setStatus(304)
+ return ''
+
+ RESPONSE.setHeader('Content-Type', self.content_type)
+ RESPONSE.setHeader('Last-Modified', self.lmh)
+ return self.data
+
+ def __str__(self):
+ """Returns the source path for the image"""
+ return string.join(self.getPhysicalPath()[1:], '/')
+
+ __repr__ = __str__
+
+ HEAD__roles__=None
+ def HEAD(self, REQUEST, RESPONSE):
+ """ """
+ RESPONSE.setHeader('Content-Type', self.content_type)
+ RESPONSE.setHeader('Last-Modified', self.lmh)
+ return ''
+
+ def __len__(self):
+ # This is bogus and needed because of the way Python tests truth.
+ return 1
+
+
diff --git a/src/DocumentLibrary/LICENSE.txt b/src/DocumentLibrary/LICENSE.txt
new file mode 100644
index 0000000..42887ce
--- /dev/null
+++ b/src/DocumentLibrary/LICENSE.txt
@@ -0,0 +1,37 @@
+##################################################################################
+#
+# Kaivo Public Software License
+#
+# Copyright (c) 2001, Kaivo, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# o Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# o Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#
+# o Neither Kaivo nor the names of its contributors may be used to endorse
+# or promote products derived from this software without specific prior
+# written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL KAIVO OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+##################################################################################
+
+This product includes software developed by Digital Creations
+for use in the Z Object Publishing Environment (http://www.zope.org/).
diff --git a/src/DocumentLibrary/README.txt b/src/DocumentLibrary/README.txt
new file mode 100644
index 0000000..dc50377
--- /dev/null
+++ b/src/DocumentLibrary/README.txt
@@ -0,0 +1,130 @@
+DocumentLibrary Product
+
+ The DocumentLibrary is designed to be a drop-in Zope product
+ that allows you to create full-text searchable and browsable
+ document libraries. It requires Zope 2.3.2 or higher.
+ It also requires the BTreeFolder product version 0.2, which
+ is included in the package.
+
+ Document Storage
+
+ This product comes with a special document file class that
+ allows you to store and index several file formats
+ in the library. Since they are Zope objects, you can
+ also associate arbitrary metadata with your documents.
+ The default installation includes a subset of the
+ Dublin Core document metadata standard:
+
+ - identifier:* Absolute URL of the document
+
+ - title: User specified title
+
+ - creator: Original author
+
+ - description: Document abstract
+
+ - date: Creation or revision date
+
+ - type: General category
+
+ - format:* MIME format of the document data
+
+ - source:* URI of original document (if any)
+
+ - subject:* Topic index titles assigned to document.
+
+ *Indicates attributes that are derived programmatically.
+
+ In addition, the following properties are available:
+
+ - review_date: Date the document was reviewed.
+
+ - topics: Python list of topic index ids assigned.
+
+ - filename: The original file name of the file submitted.
+
+ Documents stored in the library have fixed property sheets
+ that are managed centrally. This allows you to modify the
+ property metadata schema for all documents at once.
+
+ The documents are indexed when they are submitted to the
+ library. Currently full-text indexing is supported for
+ the following file formats:
+
+ - Plain Text
+
+ - HTML/XML
+
+ - PDF
+
+ - Postscript
+
+ - Microsoft Word (6.0/95, 97 & 2000 formats)
+
+ - Microsoft Excel
+
+ - Microsoft Powerpoint
+
+ A plug-in architecture for full-text converters has been
+ implemented to make adding support for new file formats
+ as simple as possible.
+
+ Document file objects can also be used independently of the
+ library if you want to use their text indexing facilities
+ separately.
+
+ Topic Index
+
+ The library supports a hierarchical indexing system to
+ categorize documents. A single document can be assigned to
+ as many different topic indexes as desired.
+
+ To enhance the look and feel of your library interface,
+ Topic Indexes can be assigned custom icons from within the
+ Zope management interface.
+
+ The default user interface is a hierarchical drill-down
+ through the index. It is essentially a simplified tree
+ interface. This can be completely customized for your needs
+ by modifying the DTML methods in the library.
+
+ An entire topic index hierarchy can be imported at once
+ from a text file containing a tabbed outline. A sample
+ legal services topic index is available for download.
+
+ Searching
+
+ The library includes two interfaces for users to use for
+ queries: a simple search that searches all document
+ meta-data and text simultaneously and an advanced search
+ that allows more refined searches.
+
+ The search machinery uses Zope Catalogs, and so has simple
+ boolean search support ("and", "or", "and not" and near "...")
+ for textual content and meta-data.
+
+ Localized searching is also supported to allow users to
+ perform searches on only those documents under a specific
+ topic index.
+
+ Document Submission and Review
+
+ Documents can be submitted to the library by any users you
+ choose to grant this permissions to, including anonymous users.
+ Documents are submitted by uploading a file from the user's
+ computer or by specifying a URL where the document resides.
+ If a URL is specified, the file data is retrieved and stored
+ in the document file at upload time.
+
+ To guard against inappropriate submissions, a review feature
+ is implemented to allow one or more reviewers to approve or
+ reject submissions. To allow the delegation of this review
+ process to several people, each reviewer can be assigned a
+ separate topic index that review documents for.
+
+ A further safeguard is a list of file types that are not
+ allowed in the library. This can be modified to restrict
+ certain unsafe file types. By default, all unknown binary
+ files (such as executables) cannot be uploaded into the
+ library. This can be modified by going to the Properties tab
+ of a document library object.
diff --git a/src/DocumentLibrary/TODO.txt b/src/DocumentLibrary/TODO.txt
new file mode 100644
index 0000000..d7f5e07
--- /dev/null
+++ b/src/DocumentLibrary/TODO.txt
@@ -0,0 +1,42 @@
+Things To Do for the DocumentLibrary Product
+
+_Please note_: that offers of money or free food and beverages
+may be effective in changing this prioritization.
+
+ Near Term
+
+ - Create interface files for the help system, add help system
+ docs, and clean up to better conform to guidelines.
+
+ - Write API docs.
+
+ - Implement support for indexing additional file formats such
+ as WordPerfect, Excel, etc.
+
+ - Support cross-format conversions such as Word to PDF, PDF
+ to HTML, etc.
+
+ - Swill malted/caffeinated beverages. <==repeat as needed
+
+ In a While
+
+ - Support multiple file formats per document.
+
+ - Support Win32. This is mostly a function of getting cross-
+ platform file converters and taking time to test.
+
+ Long Term
+
+ - Support storage of document data in file system.
+
+ - Support file histories for revision management.
+
+ - Multiple language support.
+
+ - Implement some reporting functionality.
+
+ - Implement XML/RDF publication of document info.
+
+ Once I Retire
+
+ - Implement clever WebDAV support for intranet libraries.
diff --git a/src/DocumentLibrary/TopicIndex.py b/src/DocumentLibrary/TopicIndex.py
new file mode 100644
index 0000000..3771842
--- /dev/null
+++ b/src/DocumentLibrary/TopicIndex.py
@@ -0,0 +1,401 @@
+##################################################################################
+#
+# Kaivo Public Software License
+#
+# Copyright (c) 2001, Kaivo, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# o Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# o Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#
+# o Neither Kaivo nor the names of its contributors may be used to endorse
+# or promote products derived from this software without specific prior
+# written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL KAIVO OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+##################################################################################
+
+__doc__ = """Document Library Topic Index"""
+__version__ = '1.0b2'
+
+from os import path
+from string import join, find, strip
+import Globals
+from Globals import DTMLFile
+from App.ImageFile import ImageFile
+from OFS.Folder import Folder
+from Acquisition import aq_base
+from CatalogPlus import CatalogPlus, FieldIndex, TextIndex, KeywordIndex
+from IconImage import IconImage
+try:
+ from Products.ZCatalog.CatalogPathAwareness import CatalogAware
+except ImportError:
+ from Products.ZCatalog.CatalogAwareness import CatalogAware
+
+class TopicIndexBase(Folder):
+ """Document Library Index Topic Base Class"""
+
+ all_meta_types = ({'name': 'Topic Index', 'action': 'manage_addTopicIndexForm'},)
+
+ __ac_permissions__ = Folder.__ac_permissions__ + (
+ ('Add Library Topic Indexes', ('manage_addTopicIndexForm', 'manage_addTopicIndex')),
+ ('View', ('topicParents', 'topicPath', 'getTopicId')),
+ )
+
+ isLibraryTopicIndex = 1
+
+ def __init__(self, id, title=''):
+ self.id = id
+ self.title = title
+ self.documents = []
+
+ manage_addTopicIndexForm = DTMLFile('dtml/AddIndexForm', globals())
+
+ def manage_addTopicIndex(self, id, title='', REQUEST=None):
+ """Construct a Topic Index object"""
+
+ self._setObject(id, TopicIndex(id, title))
+
+ if REQUEST is not None:
+ return self.manage_main(self, REQUEST, update_menu=1)
+
+ def topicParents(self):
+ """Return a list of topic indexes from the topic index root down to this topic"""
+
+ r = [self]
+ parent = self.aq_parent
+
+ while hasattr(parent.aq_explicit, 'isLibraryTopicIndex'):
+ r.append(parent)
+ parent = parent.aq_parent
+
+ r.reverse()
+ return r
+
+ def topicPath(self):
+ """Returns the path from the index root to this topic index"""
+ parent_ids = map(lambda ob: ob.getId(), self.topicParents())
+ return join(parent_ids[1:],'/')
+
+ def getTopicId(self):
+ """Returns the topic id of the current index topic.
+ Used to pass the id without namespace collisions"""
+ if self.meta_type != TopicIndexRoot.meta_type:
+ return self.id
+ else:
+ return None
+
+ TopicIndexBase_icon = ImageFile('www/DefaultTopicIndex_icon.gif', globals())
+ def icon(self):
+ root = self.topicParents()[0]
+ return join(root.getPhysicalPath()[1:],'/') + '/TopicIndexBase_icon'
+
+
+class TopicIndex(CatalogAware, TopicIndexBase):
+ """Document Library Index Topic"""
+
+ meta_type = 'Topic Index'
+
+ _properties = TopicIndexBase._properties + (
+ { 'id': 'visible', 'type': 'boolean', 'mode': 'w' },
+ )
+
+ manage_options = TopicIndexBase.manage_options + (
+ { 'label': 'Icon', 'action': 'manage_changeIconForm' },
+ )
+
+ visible = 0
+
+ # Icon management stuff
+
+ manage_changeIconForm = DTMLFile('dtml/IndexIconForm', globals())
+
+ def manage_changeIcon(self, file, REQUEST=None, RESPONSE=None):
+ """Change topic index icon"""
+ self.topic_icon = IconImage('topic_icon', file)
+
+ if REQUEST is not None and RESPONSE is not None:
+ return self.manage_changeIconForm(REQUEST, RESPONSE)
+
+ def icon(self):
+ if hasattr(self.aq_base, 'topic_icon'):
+ return self.topic_icon
+ elif hasattr(self, 'default_icon'):
+ return self.default_icon
+ else:
+ return TopicIndexBase.icon(self)
+
+ def manage_changeProperties(self, REQUEST=None, **kw):
+ """Update properties and reindex"""
+ r = TopicIndexBase.manage_changeProperties(self, REQUEST, kw=kw)
+
+ # Check for afterEditTopicIndex hook method and call it
+ if hasattr(self, 'afterEditTopicIndex'):
+ self.afterEditTopicIndex()
+
+ self.reindex_object()
+ return r
+
+ def manage_editProperties(self, REQUEST):
+ """Edit Properties and reindex"""
+ r = TopicIndexBase.manage_editProperties(self, REQUEST)
+
+ # Check for afterEditTopicIndex hook method and call it
+ if hasattr(self, 'afterEditTopicIndex'):
+ self.afterEditTopicIndex()
+
+ self.reindex_object()
+ return r
+
+ def _setPropValue(self, id, value):
+ """Override this private method from PropertyManager to make sure
+ setting the visible property propagates up though the parents"""
+
+ if id == 'visible' and value:
+ # Make sure the visible value is set to 1 as a value for indexing purposes
+ TopicIndexBase._setPropValue(self, 'visible', 1)
+ self.showTopic()
+ else:
+ TopicIndexBase._setPropValue(self, id, value)
+
+ def manage_renameObject(self, id, new_id, REQUEST=None):
+ """Rename object and reindex"""
+ # Check for beforeDeleteTopicIndex hook method and call it
+ if hasattr(self, 'beforeDeleteTopicIndex'):
+ self.beforeDeleteTopicIndex()
+
+ r = TopicIndexBase.manage_renameObject(self, id, new_id, REQUEST)
+
+ self.reindex_object()
+ return r
+
+ def manage_afterAdd(self, item, container):
+ """Call afterAddTopicIndex hook method if any"""
+ if hasattr(self, 'afterAddTopicIndex'):
+ self.afterAddTopicIndex()
+
+ self.index_object()
+
+ def manage_beforeDelete(self, item, container):
+ """Call beforeDeleteTopicIndex hook method if any"""
+ if hasattr(self, 'beforeDeleteTopicIndex'):
+ self.beforeDeleteTopicIndex()
+
+ self.unindex_object()
+
+ def manage_afterClone(self, item):
+ """Call afterAddTopicIndex hook method if any"""
+ if hasattr(self, 'afterAddTopicIndex'):
+ self.afterAddTopicIndex()
+
+ self.index_object()
+
+ def showTopic(self):
+ """Make this topic and parent topics visible"""
+ ob = self
+
+ while ob.meta_type == self.meta_type:
+ ob.visible = 1
+ ob.reindex_object()
+ ob = ob.aq_parent
+
+ def hideTopic(self):
+ """Make this topic invisible"""
+ self.visible = 0
+ self.reindex_object()
+
+
+Globals.InitializeClass(TopicIndex)
+
+manage_addLibraryTopicIndexRootForm = DTMLFile('dtml/AddTopicIndexRootForm', globals())
+
+def manage_addLibraryTopicIndexRoot(self, id, title='', vocab_id = None,
+ REQUEST=None):
+ """Add a document store instance to a library"""
+ ob = TopicIndexRoot(id, title, vocab_id, self)
+ self._setObject(id, ob)
+
+ if REQUEST is not None:
+ REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
+
+
+class TopicIndexRoot(TopicIndexBase):
+ """Document Library Topic Index Root"""
+
+ meta_type = 'Topic Index Root'
+
+ manage_options = TopicIndexBase.manage_options + (
+ { 'label': 'Import Topic Index', 'action': 'manage_importIndexForm' },
+ { 'label': 'Default Topic Icon', 'action': 'manage_changeIconForm' }
+ )
+
+ __ac_permissions__ = TopicIndexBase.__ac_permissions__ + (
+ ('Manage properties', ('manage_changeIconForm', 'manage_changeIcon')),
+ ('Add Document Library Topics', ('manage_importIndexForm', 'manage_importIndex')),
+ ('Delete objects', ('manage_importIndexForm', 'manage_importIndex')),
+ ('Search ZCatalog', ('query', 'getpath')),
+ )
+
+ manage_changeIconForm = DTMLFile('dtml/IndexIconForm', globals())
+
+ def manage_changeIcon(self, file, REQUEST=None, RESPONSE=None):
+ """Change topic index icon"""
+ self.default_icon = IconImage('default_icon', file)
+
+ if REQUEST is not None and RESPONSE is not None:
+ return self.manage_changeIconForm(REQUEST, RESPONSE)
+
+ TopicIndexRoot_icon = ImageFile('www/TopicIndexRoot_icon.gif', globals())
+ def icon(self):
+ """Management icon"""
+ return join(self.getPhysicalPath()[1:],'/') + '/TopicIndexRoot_icon'
+
+ def _initCatalog(self, vocab_id):
+ """Setup the index root catalog"""
+ self.Catalog = CatalogPlus(vocab_id)
+ lexicon = self.Catalog.getLexicon()
+
+ self.Catalog.addColumn('id')
+ self.Catalog.addIndex('id', FieldIndex('id'))
+ self.Catalog.addColumn('title')
+ self.Catalog.addIndex('title', TextIndex('title', lexicon=lexicon))
+ self.Catalog.addIndex('visible', FieldIndex('visible'))
+
+ def __init__(self, id, title='', vocab_id=None, container=None):
+ if vocab_id is not None and container is None:
+ raise AttributeError, ("You cannot specify a vocab_id without "
+ "also specifying a container.")
+ if container is not None:
+ self=self.__of__(container)
+
+ self._initCatalog(vocab_id)
+ TopicIndexBase.__init__(aq_base(self), id, title)
+
+ manage_importIndexForm = DTMLFile('dtml/ImportIndexForm', globals())
+
+ def manage_importIndex(self, file, clear=1, delimiter=';', RESPONSE=None):
+ """Create a topics from a text outline file"""
+
+ if delimiter == '\\t': delimiter = '\t'
+
+ if clear:
+ # Clear existing topics
+ self.manage_delObjects(self.objectIds(TopicIndex.meta_type))
+ # We shouldn't need to reinit the catalog, but...
+ self._initCatalog(self.Catalog.lexicon)
+
+ # Execute clearIndexTopics hook method if any
+ if hasattr(self, 'clearIndexTopics'):
+ self.clearIndexTopics()
+
+ topic_id_seq=0
+ indent=0
+ new_index=None
+ line_num = 0
+ drill = [self]
+
+ for line in file.readlines():
+ line_num = line_num + 1
+
+ # figure out the indent of this line
+ last_indent = indent
+ indent = 0
+ while line[indent] == '\t' and indent < len(line):
+ indent = indent + 1
+
+ if indent - last_indent > 1:
+ raise 'IndentError', 'Indentation error at line %d of index file' % line_num
+
+ if indent > last_indent:
+ if new_index is not None:
+ drill.append(new_index)
+ else:
+ raise 'IndentError', 'Indentation error at line %d of index file' % line_num
+ elif indent < last_indent:
+ drill = drill[:indent + 1]
+
+ id, title = getTopicInfo(line[indent:], delimiter)
+
+ if title:
+ index = drill[-1]
+ if not id:
+ id = nextTopicId(index, topic_id_seq)
+ topic_id_seq = int(id) + 1
+ new_index = TopicIndex(id, title)
+ index._setObject(id, new_index)
+ new_index = index._getOb(id)
+ new_index.index_object()
+
+ if RESPONSE is not None:
+ RESPONSE.redirect('manage_main')
+
+ #
+ # Catalog query support methods
+ #
+
+ def query(self, REQUEST=None, **kw):
+ """Query the Index for matching topics"""
+ return apply(self.Catalog.searchResults, (REQUEST,), kw)
+
+ def getpath(self, rid):
+ """
+ Return the path to a cataloged object given a 'data_record_id_'
+ Used by the Catalog brains
+ """
+ return self.Catalog.paths[rid]
+
+ def getTopicIds(self, title):
+ """Return a list of topic ids that match the title query string"""
+ topics = self.query(title=title)
+ if topics:
+ return map(lambda t: t.id, topics)
+ else:
+ return []
+
+Globals.InitializeClass(TopicIndexRoot)
+
+def getTopicInfo(line, delimiter):
+ """Return the title and id from the topic import file line"""
+
+ d = find(line, delimiter)
+ l = len(delimiter)
+
+ if d > 0:
+ title = strip(line[:d])
+ id = strip(line[d + l:])
+ else:
+ id = ''
+ title = strip(line)
+
+ return (id, title)
+
+def nextTopicId(index, seq, pad='000000'):
+ """Returns the next topic id string and increments the sequence for import"""
+
+ i=index.aq_base
+ id =''
+
+ while not id or hasattr(i, id):
+ id = str(seq)
+ if len(id) < len(pad): id = pad[len(id):] + id
+ seq = seq + 1
+
+ return id
diff --git a/src/DocumentLibrary/__init__.py b/src/DocumentLibrary/__init__.py
new file mode 100644
index 0000000..0e6f23f
--- /dev/null
+++ b/src/DocumentLibrary/__init__.py
@@ -0,0 +1,26 @@
+import DocumentLibrary
+import Document
+import DocumentStore
+import TopicIndex
+
+def initialize(context):
+ """Initialize Document Library Product"""
+
+ context.registerClass(
+ DocumentLibrary.DocumentLibrary,
+ constructors = (
+ DocumentLibrary.manage_addDocumentLibraryForm,
+ DocumentLibrary.manage_addDocumentLibrary
+ ),
+ permission = 'Add Document Libraries',
+ icon = 'www/DocumentLibrary_icon.gif'
+ )
+
+ context.registerClass(
+ Document.Document,
+ constructors = (
+ Document.manage_addDocumentFileForm,
+ Document.manage_addDocumentFile
+ ),
+ permission = 'Add Document Library Files'
+ )
diff --git a/src/DocumentLibrary/document_icons/CVS/Entries b/src/DocumentLibrary/document_icons/CVS/Entries
new file mode 100644
index 0000000..3de68ee
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/CVS/Entries
@@ -0,0 +1,13 @@
+/excel_large.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/excel_small.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/generic_large.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/generic_small.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/html_large.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/html_small.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/msword_large.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/msword_small.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/pdf_large.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/pdf_small.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/powerpoint_large.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/powerpoint_small.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+D
diff --git a/src/DocumentLibrary/document_icons/CVS/Repository b/src/DocumentLibrary/document_icons/CVS/Repository
new file mode 100644
index 0000000..fdea3fb
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/CVS/Repository
@@ -0,0 +1 @@
+DocumentLibrary/document_icons
diff --git a/src/DocumentLibrary/document_icons/CVS/Root b/src/DocumentLibrary/document_icons/CVS/Root
new file mode 100644
index 0000000..2084eca
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/CVS/Root
@@ -0,0 +1 @@
+:pserver:anonymous@cvs.nlada-library.sourceforge.net:/cvsroot/nlada-library
diff --git a/src/DocumentLibrary/document_icons/excel_large.gif b/src/DocumentLibrary/document_icons/excel_large.gif
new file mode 100644
index 0000000..403e175
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/excel_large.gif
Binary files differ
diff --git a/src/DocumentLibrary/document_icons/excel_small.gif b/src/DocumentLibrary/document_icons/excel_small.gif
new file mode 100644
index 0000000..99e4022
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/excel_small.gif
Binary files differ
diff --git a/src/DocumentLibrary/document_icons/generic_large.gif b/src/DocumentLibrary/document_icons/generic_large.gif
new file mode 100644
index 0000000..4b2b6fb
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/generic_large.gif
Binary files differ
diff --git a/src/DocumentLibrary/document_icons/generic_small.gif b/src/DocumentLibrary/document_icons/generic_small.gif
new file mode 100644
index 0000000..831dea0
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/generic_small.gif
Binary files differ
diff --git a/src/DocumentLibrary/document_icons/html_large.gif b/src/DocumentLibrary/document_icons/html_large.gif
new file mode 100644
index 0000000..6386df8
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/html_large.gif
Binary files differ
diff --git a/src/DocumentLibrary/document_icons/html_small.gif b/src/DocumentLibrary/document_icons/html_small.gif
new file mode 100644
index 0000000..2f87585
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/html_small.gif
Binary files differ
diff --git a/src/DocumentLibrary/document_icons/msword_large.gif b/src/DocumentLibrary/document_icons/msword_large.gif
new file mode 100644
index 0000000..03ef2dc
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/msword_large.gif
Binary files differ
diff --git a/src/DocumentLibrary/document_icons/msword_small.gif b/src/DocumentLibrary/document_icons/msword_small.gif
new file mode 100644
index 0000000..b04162f
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/msword_small.gif
Binary files differ
diff --git a/src/DocumentLibrary/document_icons/pdf_large.gif b/src/DocumentLibrary/document_icons/pdf_large.gif
new file mode 100644
index 0000000..ff58b01
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/pdf_large.gif
Binary files differ
diff --git a/src/DocumentLibrary/document_icons/pdf_small.gif b/src/DocumentLibrary/document_icons/pdf_small.gif
new file mode 100644
index 0000000..20bcf99
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/pdf_small.gif
Binary files differ
diff --git a/src/DocumentLibrary/document_icons/powerpoint_large.gif b/src/DocumentLibrary/document_icons/powerpoint_large.gif
new file mode 100644
index 0000000..1921256
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/powerpoint_large.gif
Binary files differ
diff --git a/src/DocumentLibrary/document_icons/powerpoint_small.gif b/src/DocumentLibrary/document_icons/powerpoint_small.gif
new file mode 100644
index 0000000..c19f363
--- /dev/null
+++ b/src/DocumentLibrary/document_icons/powerpoint_small.gif
Binary files differ
diff --git a/src/DocumentLibrary/dtml/AddDocumentForm.dtml b/src/DocumentLibrary/dtml/AddDocumentForm.dtml
new file mode 100755
index 0000000..0ef1012
--- /dev/null
+++ b/src/DocumentLibrary/dtml/AddDocumentForm.dtml
@@ -0,0 +1,66 @@
+
+
+
+
+
+A Document File is an enhanced file object that allows full text
+searchability of file formats such as MSWord, PDF and HTML. It
+also contains enhanced support for the
+Dublin Core Metadata Initiative.
+
+
+Select a file to upload from your local computer by clicking the
+Browse button.
+
+A document library stores document files in a topic hierarchy.
+It provides functionality for users to browse, search and add documents
+through a web interface.
+
+A library topic index root is the container of a library's
+topic index hierarchy. In it you can create library topic indexes.
+It also provides support for topic index searching.
+
+
+
+
diff --git a/src/DocumentLibrary/dtml/CVS/Entries b/src/DocumentLibrary/dtml/CVS/Entries
new file mode 100644
index 0000000..98e2664
--- /dev/null
+++ b/src/DocumentLibrary/dtml/CVS/Entries
@@ -0,0 +1,13 @@
+/AddDocumentForm.dtml/1.1.1.1/Fri Jan 18 04:38:42 2002//
+/AddDocumentStoreForm.dtml/1.1.1.1/Fri Jan 18 04:38:41 2002//
+/AddIndexForm.dtml/1.1.1.1/Fri Jan 18 04:38:41 2002//
+/AddLibraryForm.dtml/1.1.1.1/Fri Jan 18 04:38:41 2002//
+/AddTopicIndexRootForm.dtml/1.1.1.1/Fri Jan 18 04:38:42 2002//
+/EditDocumentProperties.dtml/1.1.1.1/Fri Jan 18 04:38:42 2002//
+/ImportIndexForm.dtml/1.1.1.1/Fri Jan 18 04:38:41 2002//
+/IndexIconForm.dtml/1.1.1.1/Fri Jan 18 04:38:42 2002//
+/ManagerMain.dtml/1.1.1.1/Fri Jan 18 04:38:42 2002//
+/catalogColumns.dtml/1.1.1.1/Fri Jan 18 04:38:42 2002//
+/catalogIndexes.dtml/1.1.1.1/Fri Jan 18 04:38:42 2002//
+/catalogMaintenance.dtml/1.1.1.1/Fri Jan 18 04:38:43 2002//
+D
diff --git a/src/DocumentLibrary/dtml/CVS/Repository b/src/DocumentLibrary/dtml/CVS/Repository
new file mode 100644
index 0000000..6b5599e
--- /dev/null
+++ b/src/DocumentLibrary/dtml/CVS/Repository
@@ -0,0 +1 @@
+DocumentLibrary/dtml
diff --git a/src/DocumentLibrary/dtml/CVS/Root b/src/DocumentLibrary/dtml/CVS/Root
new file mode 100644
index 0000000..2084eca
--- /dev/null
+++ b/src/DocumentLibrary/dtml/CVS/Root
@@ -0,0 +1 @@
+:pserver:anonymous@cvs.nlada-library.sourceforge.net:/cvsroot/nlada-library
diff --git a/src/DocumentLibrary/dtml/EditDocumentProperties.dtml b/src/DocumentLibrary/dtml/EditDocumentProperties.dtml
new file mode 100755
index 0000000..e7f41be
--- /dev/null
+++ b/src/DocumentLibrary/dtml/EditDocumentProperties.dtml
@@ -0,0 +1,252 @@
+
+
+
+
+
+Creates a topic index from a text file outline. The tabbed indentation
+of each line determines the level of each index topic created. The title and
+id
+of each topic can be specified by delimiting the two on a given line: title;id.
+The default delimiter is a semicolon (;). If no delimiter is used, the
+text of the line becomes the topic's title and the id is assigned in sequence.
+
+
+WARNING:
+The existing index topics will be cleared!
+
+
+Select the index file to upload from your local computer by clicking the
+Browse button.
+
+This list defines the search indexes available for documents stored
+here.
+
+
+Text Indexes break text up into individual words, and
+are often referred to as full-text indexes. Text indexes
+sort results by score meaning they return hits in order
+from the most relevant to the lest relevant.
+
+
+
+Field Indexes treat the value of an objects attributes
+atomically.
+
+
+
+Keyword Indexes index a sequence of objects that act as
+'keywords' for an object. A Keyword Index will return any objects
+that have one or more keywords specified in a search query.
+
Reinitializing the document catalog will
+ completely clear the catalog, rebuild the indexes and metadata
+ elements and reindex all of the documents contained here. This
+ can take a very long time to complete if you have many documents
+ in your library.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Subtransactions
+
+
+
+
+
+
+
Subtransactions allow Zope to commit small
+ parts of a transaction over a period of time instead of all at
+ once. For ZCatalog, this means using subtransactions can
+ signficantly reduce the memory requirements needed to index huge
+ amounts of text all at once. Currently, subtransactions are only
+ applied to text indexes.
+
+
If enabled, subtransactions will reduce the memory
+ requirements of ZCatalog, but at the expense of speed.
+ If you choose to enable subtransactions, you can adjust how often
+ ZCatalog commits a subtransactions by adjusting the
+ threshold below.
+
+
If you are using ZCatalog and ZSQL Methods
+ in the same transaction, you must disable
+ subtransactions, they are not compatible with ZSQL Methods.
+
+
+
+
+
+
Subtransactions are
+
+ Enabled
+
+ Disabled
+
+
+
+
+
+
+
+
+
The Subtransaction threshold is the number of
+ objects cataloged
+ in the context of a single transaction that the catalog
+ will index before it commits a subtransaction. If this number
+ is low, the Catalog will take longer to index but consume less
+ memory. If this number is higher, the Catalog will index
+ quickly but consume much more memory.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DocumentLibrary/instance/CVS/Entries b/src/DocumentLibrary/instance/CVS/Entries
new file mode 100644
index 0000000..9c832b9
--- /dev/null
+++ b/src/DocumentLibrary/instance/CVS/Entries
@@ -0,0 +1 @@
+D/methods////
diff --git a/src/DocumentLibrary/instance/CVS/Repository b/src/DocumentLibrary/instance/CVS/Repository
new file mode 100644
index 0000000..97e539f
--- /dev/null
+++ b/src/DocumentLibrary/instance/CVS/Repository
@@ -0,0 +1 @@
+DocumentLibrary/instance
diff --git a/src/DocumentLibrary/instance/CVS/Root b/src/DocumentLibrary/instance/CVS/Root
new file mode 100644
index 0000000..2084eca
--- /dev/null
+++ b/src/DocumentLibrary/instance/CVS/Root
@@ -0,0 +1 @@
+:pserver:anonymous@cvs.nlada-library.sourceforge.net:/cvsroot/nlada-library
diff --git a/src/DocumentLibrary/instance/methods/--README-- b/src/DocumentLibrary/instance/methods/--README--
new file mode 100755
index 0000000..20fc88d
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/--README--
@@ -0,0 +1,147 @@
+Document Library Product Setup:
+
+If you are reading this, you are well on your way to getting
+your document library online. There are still a few things
+left to do before you start using it:
+
+Style Sheet:
+
+ A sample style sheet is included with this product. It illustrates
+ the different CSS classes used throughout and gives them some
+ basic presentation. To use the included style sheet, include the
+ following code in the element of your standard_html_header:
+
+
+
+ If you use the ZStyleSheet product, simply save the style sheet
+ to a file and import it in.
+
+Topic Index:
+
+ In the library there is an Index object which holds the topic index
+ for your library. Inside you can create a hierarchy of topics with as
+ many levels as desired. This can be done using the Zope managment
+ interface or by importing a tab-indented outline from a text file.
+ The format is as follows:
+
+ - The number of leading tabs on each line denotes the topic index level.
+ a line with no tabs is a top level topic index.
+
+ - Text following the leading tabs (if any) specifies the title of the
+ topic index.
+
+ - Optionally you may specify an id for each topic index by adding a
+ delimiter character of your choosing followed by the id string. ids
+ for topics residing in the same parent topic must be unique. However,
+ you can cross-reference topic indices under different parents by
+ giving them the same id string.
+
+ - If an id is not specified for a topic index, an arbitrary one is
+ assigned to it as part of a unique sequence.
+
+ Below is an example of a topic index file, which uses a semicolon (;)
+ to delimit the title and id. You can using any character as a delimiter
+ excluding newline.
+
+ Bread;100
+ White;100.1
+ Butter Top;100.1.1
+ French;100.1.2
+ Italian;100.1.3
+ Wheat;100.2
+ Rye;100.3
+ Marble;100.3.1
+ Caraway;100.3.2
+ Pumpernickel;100.4
+ Cold Cuts;101
+ Beef;101.1
+ Roast Beef;101.1.1
+ Corned Beef;101.1.2
+ Pastrami;101.1.3
+ Ham;101.2
+ Baked Ham;101.2.1
+ Honey Ham;101.2.2
+ Turkey;101.3
+ Pastrami;101.1.3
+ Condiments;102
+ Mayo;102.1
+ Mustard;102.2
+ Yellow;102.2.1
+ Dijon;102.2.2
+ Brown;102.2.3
+ Ketchup;102.3
+
+ You get the idea. In the above example, Pastrami is cross-referenced
+ under Beef and Turkey. A document placed under one is automatically
+ placed under the other. They are actually the same topic as far as
+ the library is concerned.
+
+ To import an index from a file, click on the Index object and use
+ the "Import Topic Index" tab.
+
+ A complete legal services index is available for download as a
+ functional example.
+
+Library Properties:
+
+ The library object's property sheet contains three properties that
+ you should modify to suit your needs:
+
+ Document Types
+
+ A more general categorization of documents is made using the
+ type property. You can define the set of types for the documents
+ in your library by adding them to the document_types lines
+ propery of the library object. The items added here will be
+ presented as options to the user when using the advanced search
+ or submitting a document.
+
+ Forbidden Content Types
+
+ You can forbid certain MIME file types from being submitted
+ by modifying the deny_content_types property of the library.
+ By default, application/octet-stream is forbidden so that
+ executables and generic binary files cannot be submitted.
+
+ Hide Empty Topic Indexes
+
+ If you have an expansive index and few documents, it can be
+ beneficial to hide empty topics when browsing the library. To
+ enable this feature, set the library property hide_empty_topics
+ to true. By default, all topics are hidden. When a document
+ is approved on the review screen, any topics assigned to that
+ document and their parents become visible. Topic indexes are
+ only hidden when browsing, when assigning topics to documents
+ using the topic chooser, all topics are shown.
+
+Permissions:
+
+ Below are the permissions needed for users to be able to perform
+ specific tasks in the library:
+
+ Task Object Permissions
+ ---------------------------------------------------------------------
+ Browse & Search Document library Access Content Information
+ View
+ Query Vocabulary
+ Search ZCatalog
+
+ Submit Documents Documents Folder Add Document Library Files
+
+ Review Submitted Documents Folder Manage Properties
+ Documents
+
+ Manage Topic Indices Index Add Library Topic Indexes
+ Manage Properites
+ Delete Objects
+
+For additional information on this product, read the text files in the
+DocumentLibrary directory in your Zope Products directory.
+
+I hope you find the document library product useful. If you have
+questions, comments or want to report a bug, email me at:
+cduncan@kaivo.com
+
+--------------------------------------------------------------------------
+
+Copyright (c) 2001 Kaivo, Inc. http://www.kaivo.com
diff --git a/src/DocumentLibrary/instance/methods/CVS/Entries b/src/DocumentLibrary/instance/methods/CVS/Entries
new file mode 100644
index 0000000..5f75027
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/CVS/Entries
@@ -0,0 +1,22 @@
+/--README--/1.1.1.1/Fri Jan 18 04:38:46 2002//
+/advanced_search_form/1.2/Sun Jun 23 12:09:34 2002//
+/document_batch_links/1.2/Sun Jun 23 12:09:34 2002//
+/document_browse/1.2/Sun Jun 23 12:09:34 2002//
+/document_edit/1.2/Sun Jun 23 12:09:34 2002//
+/document_info/1.2/Sun Jun 23 12:09:34 2002//
+/document_list/1.2/Sun Jun 23 12:09:34 2002//
+/document_list_item/1.2/Sun Jun 23 12:09:34 2002//
+/document_review/1.2/Sun Jun 23 12:09:34 2002//
+/document_search/1.2/Sun Jun 23 12:09:34 2002//
+/document_submit/1.2/Sun Jun 23 12:09:34 2002//
+/document_submit_file_error/1.2/Sun Jun 23 12:09:34 2002//
+/document_submit_message/1.2/Sun Jun 23 12:09:34 2002//
+/document_submit_url_error/1.2/Sun Jun 23 12:09:34 2002//
+/index_chooser/1.2/Sun Jun 23 12:09:34 2002//
+/index_html/1.2/Sun Jun 23 12:09:34 2002//
+/library_style_sheet/1.2/Sun Jun 23 12:09:34 2002//
+/nav_links/1.2/Sun Jun 23 12:09:34 2002//
+/search_tips/1.2/Sun Jun 23 12:09:34 2002//
+/show_hide_topics/1.2/Sun Jun 23 12:09:34 2002//
+/simple_search_form/1.2/Sun Jun 23 12:09:34 2002//
+D
diff --git a/src/DocumentLibrary/instance/methods/CVS/Repository b/src/DocumentLibrary/instance/methods/CVS/Repository
new file mode 100644
index 0000000..f0cbe91
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/CVS/Repository
@@ -0,0 +1 @@
+DocumentLibrary/instance/methods
diff --git a/src/DocumentLibrary/instance/methods/CVS/Root b/src/DocumentLibrary/instance/methods/CVS/Root
new file mode 100644
index 0000000..2084eca
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/CVS/Root
@@ -0,0 +1 @@
+:pserver:anonymous@cvs.nlada-library.sourceforge.net:/cvsroot/nlada-library
diff --git a/src/DocumentLibrary/instance/methods/advanced_search_form b/src/DocumentLibrary/instance/methods/advanced_search_form
new file mode 100755
index 0000000..caa3bf1
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/advanced_search_form
@@ -0,0 +1,97 @@
+
+
+advanced_search_form method
+
+ User interface for advanced library queries
+
+ Author: Casey Duncan (casey_duncan@yahoo.com)
+
+ Dependencies:
+ document_search method
+ nav_links method
+
+ Arguments:
+ None
+
+ CSS classes used:
+ document-library
+ help-text
+
+
+
+
+
+
+
+
Advanced Document Search
+
+ Enter search criteria in as many document data fields as desired. Entering more
+ criteria will result in a more refined search.
+ Visit the search tips page for help creating your
+ search criteria.
+
+ For easier searching, use the simple search.
+
+
+
+
diff --git a/src/DocumentLibrary/instance/methods/document_batch_links b/src/DocumentLibrary/instance/methods/document_batch_links
new file mode 100755
index 0000000..9fcde26
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/document_batch_links
@@ -0,0 +1,32 @@
+
+
+document_batch_links method
+
+ Displays links to the next and previous batches in the
+ document list.
+
+ Author: Casey Duncan (casey_duncan@yahoo.com)
+
+ Arguments:
+ start Batch start
+ documents Document list sequence
+
+ CSS classes used:
+ batch-links
+
+
+
+
diff --git a/src/DocumentLibrary/instance/methods/document_browse b/src/DocumentLibrary/instance/methods/document_browse
new file mode 100755
index 0000000..077ebf4
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/document_browse
@@ -0,0 +1,85 @@
+
+
+document_browse method
+
+ Displays a hierarchy of linked topic indexes allowing the user to navigate around the
+ index and find documents.
+
+ Author: Casey Duncan (casey_duncan@yahoo.com)
+
+ Dependencies:
+ document_list method
+
+ CSS classes used:
+ title Topic index title
+
+
+
+
+
+ If we are executed outside of the context of the Index, then
+ call ourself again inside the context of the Index.
+
+
+
+
+
+
+
+
+
+
+
+ Create indented backlinks to higher level topics
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ List any subtopics in this topic
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ List any documents in this topic
+
+
+
+
+
+ Close backlink lists
+
+
+
+
+
+
+
+
diff --git a/src/DocumentLibrary/instance/methods/document_edit b/src/DocumentLibrary/instance/methods/document_edit
new file mode 100755
index 0000000..00a42f7
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/document_edit
@@ -0,0 +1,177 @@
+
+
+document_edit method
+
+ Form for users to edit document properties. Requires
+ "Manage properties" rights on the Documents folder.
+
+ Author: Casey Duncan (casey_duncan@yahoo.com)
+
+ Arguments:
+ last_url (optional) URL to return to
+ last_query (optional) query string from last_url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Edit Document File
+
+
+(* Denotes required fields)
+
+
+
+
+
diff --git a/src/DocumentLibrary/instance/methods/document_info b/src/DocumentLibrary/instance/methods/document_info
new file mode 100755
index 0000000..cdfe9c0
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/document_info
@@ -0,0 +1,102 @@
+
+
+document_info method
+
+ Displays document properties to the user.
+
+ Author: Casey Duncan (casey_duncan@yahoo.com)
+
+ Dependencies:
+ document_list method
+
+ CSS classes used:
+ document-library Entire document topic hierarchy
+ title Topic index title
+ format
+ size
+ type
+ date
+ author
+ subject
+ description
+
+
+
+
+
+ &dtml-title;
+
+
+
+
+
+
+
+
+ To submit a document to the library, fill out this form.
+
+ (* Denotes required fields)
+
+
+
+
+
diff --git a/src/DocumentLibrary/instance/methods/document_submit_file_error b/src/DocumentLibrary/instance/methods/document_submit_file_error
new file mode 100644
index 0000000..be4006a
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/document_submit_file_error
@@ -0,0 +1,35 @@
+
+
+document_submit_file_error method
+
+ Error message and help for invalid file submissions.
+
+ Author: Casey Duncan (casey_duncan@yahoo.com)
+
+
+
+
Document Submission Error
+
+Sorry, the file you specified could not be submitted to the library.
+It either could not be opened or it is not a valid file format. To correct
+this problem try one of the following:
+
+
+
Make sure the file is not open in another program on your computer.
+ If it is, close the file and try again.
+
Make sure the file is not an executable program file, these
+ file types (commonly with the extension ".exe" or ".com")
+ cannot be submitted to the library.
+
Make sure the file name has the proper file extension in its file
+ name, for instance: ".doc" for MS Word documents, ".pdf" for
+ PDF documents, etc. Note: the extension may not be visible
+ when you browse for the file. If the file appears with the proper
+ icon in the browse window, then the extension is correct.
+
Make sure that you have the proper rights to open the file on
+ your computer. Try opening it on your computer using the appropriate
+ program. If you cannot access the file, see your system
+ administrator for help.
+
+
+To return to the form to resubmit your document, click on the
+Back button on your browser's toolbar.
Your document
+ was submitted sucessfully.
+
+ Your document will be reviewed and will become available in the library
+ when it is approved.
+
+
+ This is commented out until I can track down a somewhat silly
+ security issue. Uncomment it to see what I mean...
+
+ Edit the document data.
+
+
+
+
diff --git a/src/DocumentLibrary/instance/methods/document_submit_url_error b/src/DocumentLibrary/instance/methods/document_submit_url_error
new file mode 100644
index 0000000..ebd73e2
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/document_submit_url_error
@@ -0,0 +1,24 @@
+
+
+document_submit_url_error method
+
+ Error message and help for invalid URL submissions.
+
+ Author: Casey Duncan (casey_duncan@yahoo.com)
+
+
+
+
Document Submission Error
+
The URL you specified for the document could not be submitted because:
+
+
To correct this problem try the following:
+
+
Confirm that you can access the URL as entered using your web browser.
+ Click on the URL link above to test it in your browser
+
Make sure you URL uses the http, ftp or gopher protocols. This is denoted
+ at the front of the URL (ie http://). Other protocols (such as file: or mailto:)
+ are not allowed for submitting documents. If the protocol is omitted,
+ http is assumed.
+
+
To return to the form to resubmit your document, click on the
+Back button on your browser's toolbar.
diff --git a/src/DocumentLibrary/instance/methods/index_chooser b/src/DocumentLibrary/instance/methods/index_chooser
new file mode 100755
index 0000000..3c7a068
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/index_chooser
@@ -0,0 +1,193 @@
+
+
+index_chooser method
+
+ Popup index chooser that allows user to select on (or more) topics.
+ Uses JavaScript to populate a topic list on the parent form.
+
+ Author: Casey Duncan (casey_duncan@yahoo.com)
+
+ Arguments:
+ topic_form Name of parent form on parent window
+ topic_field Name of field on parent form (optional, defaults to "topics")
+ single Flag to specify whether on a single topic can be selected (option, defaults to false)
+
+ CSS classes used:
+ advocate-library Entire document topic hierarchy
+ title Topic index title
+ code Topic index code
+
+
+
+
+
+ If we are executed outside of the context of the Index, then
+ call ourself again inside the context of the Index.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Display a intro blurb at the top only when the user first enters the library
+
+
+
+
+
+
+
+
+
+
+
+ Welcome to the document library. You can use this page to
+ browse and
+ search
+ the documents here.
+
+ If you have a document that you would like to add to the library, you can
+ submit it here.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DocumentLibrary/instance/methods/library_style_sheet b/src/DocumentLibrary/instance/methods/library_style_sheet
new file mode 100755
index 0000000..23cfd69
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/library_style_sheet
@@ -0,0 +1,87 @@
+
+
+library_style_sheet method
+
+ Style sheet for use with the document library.
+ Insert inline or link to it in standard_html_header
+
+ Author: Casey Duncan (casey_duncan@yahoo.com)
+
+
+
+
+
diff --git a/src/DocumentLibrary/instance/methods/nav_links b/src/DocumentLibrary/instance/methods/nav_links
new file mode 100755
index 0000000..39e5d28
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/nav_links
@@ -0,0 +1,68 @@
+
+
+nav_links method
+
+ Presents appropriate links to the user dynamically.
+
+ Author: Casey Duncan (casey_duncan@yahoo.com)
+
+ CSS classes used:
+ nav-links Navigation Links
+
+
+
+
+ When searching for documents, there are several techniques you
+ can use to improve the accuracy of your search results. These terms allow you
+ to formulate partial word, boolean and phrase matching search queries. Below are examples of
+ the query language that is used to perform searches in the document
+ library. You can also combine the techniques below in a single search to
+ further refine your query.
+
+
+
+
Simple word searches
+
If you enter multiple words for your search query the
+ results will be
+ documents containing one or more of the words in your query. Queries are
+ not case sensitive, so they will match the word(s) only based on spelling.
+ Documents matching more of the words will appear first in the search results.
+ Because a document only need to match one of the words you specify,
+ you may find that your search results contain irrelevant documents.
+
Example: housing landlord rent
+ This search query would find all documents containing one or more of the
+ words specified.
+
+
Refining your results to match multiple words
+
Using the and search
+ term between the
+ words you are searching for will limit the search results only to documents
+ containing all of the specified words.
+
Example: housing and landlord and rent
+ This search query would find only documents containing all of the
+ words specified.
+
+
Explicitly omitting results
+
You might also want to omit documents containing certain words.
+ Using the and not search term between the
+ words you are searching for will omit any documents containing the word
+ you specify.
+
Example: housing and landlord and not discrimination
+ This search query would find documents containing the first two words
+ and omit any documents also containing the last word.
+
+
Searching for phrases
+
If the word combination you are searching for always occurs
+ in a particular phrase,
+ you will get better search results using a phrase matching query. To match a particular
+ phrase, place it in double quotation marks. In order for a document to match, the
+ words in the phrase must occur in the document in the same order as they appear in the query.
+
Example: "fair housing"
+ This search query would find only documents containing the phrase specified.
+
+
Matching partial words
+
You can match multiple word forms simultaneously using
+ partial word matching. If, for
+ instance you want to guarantee that all documents containing any form of a word
+ (plural, singular, past tense, etc.) are found, you can use the asteriks (*) and
+ question mark (?) wildcard characters in your search queries. The question
+ mark matches any single character and the asterik matches zero or more characters.
+
Example: discriminat*
+ This query would find documents containing the words "discriminate", "discriminated",
+ "discrimination", etc.
+
+
+
+
diff --git a/src/DocumentLibrary/instance/methods/show_hide_topics b/src/DocumentLibrary/instance/methods/show_hide_topics
new file mode 100755
index 0000000..6b72f70
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/show_hide_topics
@@ -0,0 +1,19 @@
+
+show_hide_topics method
+
+ Toggles a cookie flag to indicate whether empty topics are shown.
+ To enable this feature, set the hide_empty_topics property of the
+ document library object to true.
+
+ Author: Casey Duncan (casey_duncan@yahoo.com)
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DocumentLibrary/instance/methods/simple_search_form b/src/DocumentLibrary/instance/methods/simple_search_form
new file mode 100755
index 0000000..af75e34
--- /dev/null
+++ b/src/DocumentLibrary/instance/methods/simple_search_form
@@ -0,0 +1,36 @@
+
+
+simple_search_form method
+
+ HTML interface to search all textual indexes simultaneously
+
+ Author: Casey Duncan (casey_duncan@yahoo.com)
+
+ CSS classes used:
+ simple-search Simple search form class
+ search-note Scope note for search form
+
+
+
+
+
+
diff --git a/src/DocumentLibrary/refresh.txt b/src/DocumentLibrary/refresh.txt
new file mode 100644
index 0000000..6e7380b
--- /dev/null
+++ b/src/DocumentLibrary/refresh.txt
@@ -0,0 +1 @@
+Refresh enabled
diff --git a/src/DocumentLibrary/text.c.patch b/src/DocumentLibrary/text.c.patch
new file mode 100644
index 0000000..dd7d7a5
--- /dev/null
+++ b/src/DocumentLibrary/text.c.patch
@@ -0,0 +1,19 @@
+*** wv/text.c Sun Jan 21 16:54:18 2001
+--- text.c Thu Mar 15 15:23:03 2001
+***************
+*** 397,403 ****
+
+ if(wv_iconv(iconv_handle, &ibuf, &ibuflen, &obuf, &obuflen) == (size_t)-1)
+ {
+! wvError(("iconv failed errno: %d, to:%s from:%s\n",errno, t_code, f_code));
+ /* I'm torn here - do i just announce the failure, continue, or copy over to the other buffer? */
+
+ /* errno is usually 84 (illegal byte sequence)
+--- 397,403 ----
+
+ if(wv_iconv(iconv_handle, &ibuf, &ibuflen, &obuf, &obuflen) == (size_t)-1)
+ {
+! /*wvError(("iconv failed errno: %d, to:%s from:%s\n",errno, t_code, f_code));*/
+ /* I'm torn here - do i just announce the failure, continue, or copy over to the other buffer? */
+
+ /* errno is usually 84 (illegal byte sequence)
diff --git a/src/DocumentLibrary/version.txt b/src/DocumentLibrary/version.txt
new file mode 100644
index 0000000..0f82de4
--- /dev/null
+++ b/src/DocumentLibrary/version.txt
@@ -0,0 +1 @@
+1.0rc1
diff --git a/src/DocumentLibrary/www/CVS/Entries b/src/DocumentLibrary/www/CVS/Entries
new file mode 100644
index 0000000..da35356
--- /dev/null
+++ b/src/DocumentLibrary/www/CVS/Entries
@@ -0,0 +1,7 @@
+/DefaultTopicIndex_icon.gif/1.1.1.1/Fri Jan 18 04:38:41 2002//
+/DocumentLibrary_icon.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/DocumentStore_icon.gif/1.1.1.1/Fri Jan 18 04:38:41 2002//
+/TopicIndexRoot_icon.gif/1.1.1.1/Fri Jan 18 04:38:41 2002//
+/Topic_icon.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+/folder_icon_lg.gif/1.1.1.1/Fri Jan 18 04:38:40 2002//
+D
diff --git a/src/DocumentLibrary/www/CVS/Repository b/src/DocumentLibrary/www/CVS/Repository
new file mode 100644
index 0000000..6840bd2
--- /dev/null
+++ b/src/DocumentLibrary/www/CVS/Repository
@@ -0,0 +1 @@
+DocumentLibrary/www
diff --git a/src/DocumentLibrary/www/CVS/Root b/src/DocumentLibrary/www/CVS/Root
new file mode 100644
index 0000000..2084eca
--- /dev/null
+++ b/src/DocumentLibrary/www/CVS/Root
@@ -0,0 +1 @@
+:pserver:anonymous@cvs.nlada-library.sourceforge.net:/cvsroot/nlada-library
diff --git a/src/DocumentLibrary/www/DefaultTopicIndex_icon.gif b/src/DocumentLibrary/www/DefaultTopicIndex_icon.gif
new file mode 100755
index 0000000..1a4a118
--- /dev/null
+++ b/src/DocumentLibrary/www/DefaultTopicIndex_icon.gif
Binary files differ
diff --git a/src/DocumentLibrary/www/DocumentLibrary_icon.gif b/src/DocumentLibrary/www/DocumentLibrary_icon.gif
new file mode 100755
index 0000000..7c15fbb
--- /dev/null
+++ b/src/DocumentLibrary/www/DocumentLibrary_icon.gif
Binary files differ
diff --git a/src/DocumentLibrary/www/DocumentStore_icon.gif b/src/DocumentLibrary/www/DocumentStore_icon.gif
new file mode 100755
index 0000000..71bedb1
--- /dev/null
+++ b/src/DocumentLibrary/www/DocumentStore_icon.gif
Binary files differ
diff --git a/src/DocumentLibrary/www/TopicIndexRoot_icon.gif b/src/DocumentLibrary/www/TopicIndexRoot_icon.gif
new file mode 100755
index 0000000..8d31c10
--- /dev/null
+++ b/src/DocumentLibrary/www/TopicIndexRoot_icon.gif
Binary files differ
diff --git a/src/DocumentLibrary/www/Topic_icon.gif b/src/DocumentLibrary/www/Topic_icon.gif
new file mode 100755
index 0000000..415c01a
--- /dev/null
+++ b/src/DocumentLibrary/www/Topic_icon.gif
Binary files differ
diff --git a/src/DocumentLibrary/www/folder_icon_lg.gif b/src/DocumentLibrary/www/folder_icon_lg.gif
new file mode 100755
index 0000000..eee205a
--- /dev/null
+++ b/src/DocumentLibrary/www/folder_icon_lg.gif
Binary files differ