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