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