Commit 733b0fbd authored by 's avatar

added some z3 interfaces for catalog related classes

parent d971242c
......@@ -26,6 +26,8 @@ Zope Changes
Features Added
- PluginIndexes, ZCTextIndex and ZCatalog: Added some z3 interfaces.
- Verbose security exception reporting has been folded into Zope,
removing the need for the VerboseSecurity product. See the
documentation for the "verbose-security" option in zope.conf.
......
......@@ -7,28 +7,31 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Date index.
"""$Id$
$Id$
"""
import time
from datetime import date, datetime
from datetime import tzinfo, timedelta
from types import StringType, FloatType, IntType
from BTrees.IIBTree import IISet, union, intersection, multiunion
from BTrees.IOBTree import IOBTree
from BTrees.OIBTree import OIBTree
from DateTime.DateTime import DateTime
from Globals import DTMLFile
from OFS.PropertyManager import PropertyManager
from datetime import date, datetime
from Products.PluginIndexes import PluggableIndex
from zope.interface import implements
from Products.PluginIndexes.common import safe_callable
from Products.PluginIndexes.common.UnIndex import UnIndex
from Products.PluginIndexes.common.util import parseIndexRequest
from Products.PluginIndexes.common import safe_callable
from Globals import DTMLFile
from BTrees.IOBTree import IOBTree
from BTrees.OIBTree import OIBTree
from BTrees.IIBTree import IISet, union, intersection, multiunion
import time
from Products.PluginIndexes.interfaces import IDateIndex
_marker = []
......@@ -73,17 +76,20 @@ class LocalTimezone(tzinfo):
Local = LocalTimezone()
###############################################################################
class DateIndex(UnIndex, PropertyManager):
""" Index for Dates """
__implements__ = (PluggableIndex.UniqueValueIndex,
PluggableIndex.SortIndex)
"""Index for dates.
"""
__implements__ = UnIndex.__implements__
implements(IDateIndex)
meta_type = 'DateIndex'
query_options = ['query', 'range']
index_naive_time_as_local = True # False means index as UTC
_properties=({'id':'index_naive_time_as_local',
_properties=({'id':'index_naive_time_as_local',
'type':'boolean',
'mode':'w'},)
......@@ -138,7 +144,6 @@ class DateIndex(UnIndex, PropertyManager):
return returnStatus
def _apply_index( self, request, cid='', type=type ):
"""Apply the index to query parameters given in the argument
......@@ -220,7 +225,6 @@ class DateIndex(UnIndex, PropertyManager):
else:
return r, (self.id,)
def _convert( self, value, default=None ):
"""Convert Date/Time value to our internal representation"""
# XXX: Code patched 20/May/2003 by Kiran Jonnalagadda to
......
......@@ -7,18 +7,27 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""DateIndex unit tests.
$Id$
"""
import Zope2
import unittest
import Testing
import Zope2
Zope2.startup()
from DateTime import DateTime
from datetime import date, datetime, tzinfo, timedelta
from Products.PluginIndexes.DateIndex.DateIndex import DateIndex, Local
from types import IntType, FloatType
import time
from types import IntType, FloatType
from DateTime import DateTime
from Products.PluginIndexes.DateIndex.DateIndex import DateIndex, Local
class Dummy:
......@@ -96,6 +105,7 @@ class USTimeZone(tzinfo):
Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
###############################################################################
class DI_Tests(unittest.TestCase):
def setUp(self):
self._values = (
......@@ -109,13 +119,13 @@ class DI_Tests(unittest.TestCase):
(7, Dummy('f', 1072742900)), # 1073545928
(8, Dummy('g', date(2034,2,5))), # 1073599200
(9, Dummy('h', datetime(2034,2,5,15,20,5))), # (varies)
(10, Dummy('i', datetime(2034,2,5,10,17,5,
(10, Dummy('i', datetime(2034,2,5,10,17,5,
tzinfo=Eastern))), # 1073600117
)
self._index = DateIndex('date')
self._noop_req = {'bar': 123}
self._request = {'date': DateTime(0)}
self._min_req = {'date': {'query': DateTime('2032-05-08 15:16:17'),
self._min_req = {'date': {'query': DateTime('2032-05-08 15:16:17'),
'range': 'min'}}
self._max_req = {'date': {'query': DateTime('2032-05-08 15:16:17'),
'range': 'max'}}
......@@ -154,6 +164,18 @@ class DI_Tests(unittest.TestCase):
yr, mo, dy, hr, mn = dt.toZone('UTC').parts()[:5]
return (((yr * 12 + mo) * 31 + dy) * 24 + hr) * 60 + mn
def test_z3interfaces(self):
from Products.PluginIndexes.interfaces import IDateIndex
from Products.PluginIndexes.interfaces import IPluggableIndex
from Products.PluginIndexes.interfaces import ISortIndex
from Products.PluginIndexes.interfaces import IUniqueValueIndex
from zope.interface.verify import verifyClass
verifyClass(IDateIndex, DateIndex)
verifyClass(IPluggableIndex, DateIndex)
verifyClass(ISortIndex, DateIndex)
verifyClass(IUniqueValueIndex, DateIndex)
def test_empty(self):
empty = self._index
......@@ -204,28 +226,24 @@ class DI_Tests(unittest.TestCase):
self._checkApply(self._range_req, values[2:] )
self._checkApply(self._float_req, [values[6]] )
self._checkApply(self._int_req, [values[7]] )
def test_naive_convert_to_utc(self):
values = self._values
index = self._index
index.index_naive_time_as_local = False
self._populateIndex()
for k, v in values[9:]:
for k, v in values[9:]:
# assert that the timezone is effectively UTC for item 9,
# and still correct for item 10
yr, mo, dy, hr, mn = v.date().utctimetuple()[:5]
val = (((yr * 12 + mo) * 31 + dy) * 24 + hr) * 60 + mn
self.failUnlessEqual(self._index.getEntryForObject(k), val)
def test_suite():
suite = unittest.TestSuite()
suite.addTest( unittest.makeSuite( DI_Tests ) )
return suite
def run():
unittest.TextTestRunner().run(test_suite())
if __name__ == '__main__':
run()
unittest.main(defaultTest='test_suite')
......@@ -7,56 +7,58 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Date range index.
"""$Id$
$Id$
"""
import os
from Products.PluginIndexes import PluggableIndex
from Products.PluginIndexes.common.UnIndex import UnIndex
from Products.PluginIndexes.common.util import parseIndexRequest
from Products.PluginIndexes.common import safe_callable
from BTrees.IOBTree import IOBTree
from AccessControl import ClassSecurityInfo
from BTrees.IIBTree import IISet, IITreeSet, union, intersection, multiunion
from BTrees.IOBTree import IOBTree
import BTrees.Length
from Globals import package_home, DTMLFile, InitializeClass
from AccessControl import ClassSecurityInfo
from DateTime.DateTime import DateTime
from Globals import package_home, DTMLFile, InitializeClass
from zope.interface import implements
from Products.PluginIndexes.common import safe_callable
from Products.PluginIndexes.common.UnIndex import UnIndex
from Products.PluginIndexes.common.util import parseIndexRequest
from Products.PluginIndexes.interfaces import IDateRangeIndex
_dtmldir = os.path.join( package_home( globals() ), 'dtml' )
VIEW_PERMISSION = 'View'
INDEX_MGMT_PERMISSION = 'Manage ZCatalogIndex Entries'
class DateRangeIndex(UnIndex):
"""
Index a date range, such as the canonical "effective-expiration"
range in the CMF. Any object may return None for either the
start or the end date: for the start date, this should be
the logical equivalent of "since the beginning of time"; for the
end date, "until the end of time".
Therefore, divide the space of indexed objects into four containers:
"""Index for date ranges, such as the "effective-expiration" range in CMF.
- Objects which always match ( i.e., they returned None for both );
Any object may return None for either the start or the end date: for the
start date, this should be the logical equivalent of "since the beginning
of time"; for the end date, "until the end of time".
- Objects which match after a given time ( i.e., they returned None
for the end date );
Therefore, divide the space of indexed objects into four containers:
- Objects which match until a given time ( i.e., they returned None
for the start date );
- Objects which always match (i.e., they returned None for both);
- Objects which match only during a specific interval.
- Objects which match after a given time (i.e., they returned None for the
end date);
- Objects which match until a given time (i.e., they returned None for the
start date);
- Objects which match only during a specific interval.
"""
__implements__ = (PluggableIndex.UniqueValueIndex,
PluggableIndex.SortIndex)
__implements__ = UnIndex.__implements__
implements(IDateRangeIndex)
security = ClassSecurityInfo()
......@@ -83,27 +85,21 @@ class DateRangeIndex(UnIndex):
self._edit(since_field, until_field)
self.clear()
security.declareProtected( VIEW_PERMISSION
, 'getSinceField'
)
def getSinceField( self ):
"""
security.declareProtected(VIEW_PERMISSION, 'getSinceField')
def getSinceField(self):
"""Get the name of the attribute indexed as start date.
"""
return self._since_field
security.declareProtected( VIEW_PERMISSION
, 'getUntilField'
)
def getUntilField( self ):
"""
security.declareProtected(VIEW_PERMISSION, 'getUntilField')
def getUntilField(self):
"""Get the name of the attribute indexed as end date.
"""
return self._until_field
manage_indexProperties = DTMLFile( 'manageDateRangeIndex', _dtmldir )
security.declareProtected( INDEX_MGMT_PERMISSION
, 'manage_edit'
)
security.declareProtected(INDEX_MGMT_PERMISSION, 'manage_edit')
def manage_edit( self, since_field, until_field, REQUEST ):
"""
"""
......@@ -113,7 +109,7 @@ class DateRangeIndex(UnIndex):
% REQUEST.get('URL2')
)
security.declarePrivate( '_edit' )
security.declarePrivate('_edit')
def _edit( self, since_field, until_field ):
"""
Update the fields used to compute the range.
......@@ -121,10 +117,7 @@ class DateRangeIndex(UnIndex):
self._since_field = since_field
self._until_field = until_field
security.declareProtected( INDEX_MGMT_PERMISSION
, 'clear'
)
security.declareProtected(INDEX_MGMT_PERMISSION, 'clear')
def clear( self ):
"""
Start over fresh.
......@@ -308,7 +301,7 @@ class DateRangeIndex(UnIndex):
#
# ZCatalog needs this, although it isn't (yet) part of the interface.
#
security.declareProtected( VIEW_PERMISSION , 'numObjects' )
security.declareProtected(VIEW_PERMISSION , 'numObjects')
def numObjects( self ):
""" """
return len( self._unindex )
......
......@@ -7,16 +7,24 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""DateRangeIndex unit tests.
$Id$
"""
import Zope2
import unittest
import Testing
import Zope2
Zope2.startup()
import sys
from Products.PluginIndexes.DateRangeIndex.DateRangeIndex import DateRangeIndex
class Dummy:
def __init__( self, name, start, stop ):
......@@ -63,6 +71,7 @@ def matchingDummies( value ):
return result
class DRI_Tests( unittest.TestCase ):
def setUp( self ):
......@@ -71,6 +80,18 @@ class DRI_Tests( unittest.TestCase ):
def tearDown( self ):
pass
def test_z3interfaces(self):
from Products.PluginIndexes.interfaces import IDateRangeIndex
from Products.PluginIndexes.interfaces import IPluggableIndex
from Products.PluginIndexes.interfaces import ISortIndex
from Products.PluginIndexes.interfaces import IUniqueValueIndex
from zope.interface.verify import verifyClass
verifyClass(IDateRangeIndex, DateRangeIndex)
verifyClass(IPluggableIndex, DateRangeIndex)
verifyClass(ISortIndex, DateRangeIndex)
verifyClass(IUniqueValueIndex, DateRangeIndex)
def test_empty( self ):
empty = DateRangeIndex( 'empty' )
......@@ -120,13 +141,11 @@ class DRI_Tests( unittest.TestCase ):
bad = Dummy( 'bad', long(sys.maxint) + 1, long(sys.maxint) + 1 )
work.index_object( 0, bad )
def test_suite():
suite = unittest.TestSuite()
suite.addTest( unittest.makeSuite( DRI_Tests ) )
return suite
def run():
unittest.TextTestRunner().run(test_suite())
if __name__ == '__main__':
run()
unittest.main(defaultTest='test_suite')
#############################################################################
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
......@@ -7,24 +7,25 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Simple column indices.
"""Simple column indices
$Id$
"""
from Products.PluginIndexes import PluggableIndex
from Globals import DTMLFile
from Products.PluginIndexes.common.UnIndex import UnIndex
from Globals import DTMLFile
class FieldIndex(UnIndex):
"""Field Indexes"""
__implements__ = (PluggableIndex.UniqueValueIndex,
PluggableIndex.SortIndex)
"""Index for simple fields.
"""
__implements__ = UnIndex.__implements__
meta_type="FieldIndex"
......
......@@ -7,14 +7,22 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""FieldIndex unit tests.
$Id$
"""
import unittest
import Testing
import Zope2
Zope2.startup()
import os, sys, unittest
import ZODB
from Products.PluginIndexes.FieldIndex.FieldIndex import FieldIndex
class Dummy:
def __init__( self, foo ):
......@@ -28,9 +36,9 @@ class Dummy:
__repr__ = __str__
class TestCase( unittest.TestCase ):
"""
Test FieldIndex objects.
class FieldIndexTests(unittest.TestCase):
"""Test FieldIndex objects.
"""
def setUp( self ):
......@@ -68,7 +76,6 @@ class TestCase( unittest.TestCase ):
self._zero_req = { 'foo': 0 }
self._none_req = { 'foo': None }
def tearDown( self ):
"""
"""
......@@ -87,6 +94,16 @@ class TestCase( unittest.TestCase ):
for k, v in expectedValues:
assert k in result
def test_z3interfaces(self):
from Products.PluginIndexes.interfaces import IPluggableIndex
from Products.PluginIndexes.interfaces import ISortIndex
from Products.PluginIndexes.interfaces import IUniqueValueIndex
from zope.interface.verify import verifyClass
verifyClass(IPluggableIndex, FieldIndex)
verifyClass(ISortIndex, FieldIndex)
verifyClass(IUniqueValueIndex, FieldIndex)
def testEmpty( self ):
"Test an empty FieldIndex."
......@@ -205,11 +222,11 @@ class TestCase( unittest.TestCase ):
assert r2 == r
def test_suite():
return unittest.makeSuite( TestCase )
def main():
unittest.TextTestRunner().run(test_suite())
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(FieldIndexTests),
))
if __name__ == '__main__':
main()
unittest.main(defaultTest='test_suite')
......@@ -7,26 +7,36 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Keyword index.
$Id$
"""
from types import StringType, UnicodeType
from logging import getLogger
from BTrees.OOBTree import OOSet, difference
from BTrees.OOBTree import OOSet, difference
from Globals import DTMLFile
from Products.PluginIndexes import PluggableIndex
from Products.PluginIndexes.common.UnIndex import UnIndex
from Products.PluginIndexes.common import safe_callable
from Products.PluginIndexes.common.UnIndex import UnIndex
LOG = getLogger('Zope.KeywordIndex')
class KeywordIndex(UnIndex):
__implements__ = (PluggableIndex.UniqueValueIndex,
PluggableIndex.SortIndex)
"""Like an UnIndex only it indexes sequences of items.
Searches match any keyword.
This should have an _apply_index that returns a relevance score
"""
__implements__ = UnIndex.__implements__
meta_type="KeywordIndex"
......@@ -41,14 +51,6 @@ class KeywordIndex(UnIndex):
query_options = ("query","operator", "range")
"""Like an UnIndex only it indexes sequences of items
Searches match any keyword.
This should have an _apply_index that returns a relevance score
"""
def _index_object(self, documentId, obj, threshold=None, attr=''):
""" index an object 'obj' with integer id 'i'
......@@ -128,13 +130,11 @@ class KeywordIndex(UnIndex):
LOG.error('Attempt to unindex nonexistent'
' document id %s' % documentId)
index_html = DTMLFile('dtml/index', globals())
manage_workspace = DTMLFile('dtml/manageKeywordIndex', globals())
manage_browse = DTMLFile('../dtml/browseIndex', globals())
manage_addKeywordIndexForm = DTMLFile('dtml/addKeywordIndex', globals())
def manage_addKeywordIndex(self, id, extra=None,
......
......@@ -7,13 +7,24 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
import os, sys, unittest, zLOG
"""KeywordIndex unit tests.
$Id$
"""
import unittest
import Testing
import Zope2
Zope2.startup()
import zLOG
from Products.PluginIndexes.KeywordIndex.KeywordIndex import KeywordIndex
class Dummy:
def __init__( self, foo ):
......@@ -35,6 +46,7 @@ def sortedUnique(seq):
unique.sort()
return unique
class TestKeywordIndex( unittest.TestCase ):
"""
Test KeywordIndex objects.
......@@ -102,6 +114,16 @@ class TestKeywordIndex( unittest.TestCase ):
for k, v in expectedValues:
assert k in result
def test_z3interfaces(self):
from Products.PluginIndexes.interfaces import IPluggableIndex
from Products.PluginIndexes.interfaces import ISortIndex
from Products.PluginIndexes.interfaces import IUniqueValueIndex
from zope.interface.verify import verifyClass
verifyClass(IPluggableIndex, KeywordIndex)
verifyClass(ISortIndex, KeywordIndex)
verifyClass(IUniqueValueIndex, KeywordIndex)
def testAddObjectWOKeywords(self):
self._catch_log_errors()
......@@ -212,7 +234,7 @@ class TestKeywordIndex( unittest.TestCase ):
#
record[ 'foo' ][ 'range' ] = 'min:max'
self._checkApply( record, self._values[6:7] )
def testDuplicateKeywords(self):
self._catch_log_errors()
try:
......@@ -248,14 +270,11 @@ class TestKeywordIndex( unittest.TestCase ):
}
self._checkApply(record, values[5:7])
def test_suite():
suite = unittest.TestSuite()
suite.addTest( unittest.makeSuite( TestKeywordIndex ) )
return suite
def main():
unittest.main(defaultTest='test_suite')
if __name__ == '__main__':
main()
unittest.main(defaultTest='test_suite')
......@@ -7,11 +7,13 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Path index.
__version__ = '$Id$'
$Id$
"""
from types import StringType, ListType, TupleType
from logging import getLogger
......@@ -22,17 +24,23 @@ from BTrees.IOBTree import IOBTree
from BTrees.OOBTree import OOBTree
from BTrees.IIBTree import IITreeSet, IISet, intersection, union
from BTrees.Length import Length
from zope.interface import implements
from Products.PluginIndexes import PluggableIndex
from Products.PluginIndexes.common.util import parseIndexRequest
from Products.PluginIndexes.common import safe_callable
from Products.PluginIndexes.common.util import parseIndexRequest
from Products.PluginIndexes.interfaces import IPathIndex
from Products.PluginIndexes.interfaces import IUniqueValueIndex
_marker = []
LOG = getLogger('Zope.PathIndex')
class PathIndex(Persistent, SimpleItem):
""" A path index stores all path components of the physical
path of an object:
"""Index for paths returned by getPhysicalPath.
A path index stores all path components of the physical path of an object.
Internal datastructure:
......@@ -42,10 +50,10 @@ class PathIndex(Persistent, SimpleItem):
- the value is a mapping 'level of the path component' to
'all docids with this path component on this level'
"""
__implements__ = (PluggableIndex.UniqueValueIndex,)
implements(IPathIndex, IUniqueValueIndex)
meta_type="PathIndex"
......@@ -72,7 +80,7 @@ class PathIndex(Persistent, SimpleItem):
def insertEntry(self, comp, id, level):
"""Insert an entry.
comp is a path component
comp is a path component
id is the docid
level is the level of the component inside the path
"""
......@@ -111,7 +119,7 @@ class PathIndex(Persistent, SimpleItem):
if isinstance(path, (ListType, TupleType)):
path = '/'+ '/'.join(path[1:])
comps = filter(None, path.split('/'))
if not self._unindex.has_key(docid):
self._length.change(1)
......@@ -250,8 +258,8 @@ class PathIndex(Persistent, SimpleItem):
return ('getPhysicalPath', )
def getEntryForObject(self, docid, default=_marker):
""" Takes a document ID and returns all the information
we have on that specific object.
""" Takes a document ID and returns all the information
we have on that specific object.
"""
try:
return self._unindex[docid]
......
......@@ -7,14 +7,22 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""PathIndex unit tests.
$Id$
"""
import unittest
import Testing
import Zope2
Zope2.startup()
from Products.PluginIndexes.PathIndex.PathIndex import PathIndex
class Dummy:
meta_type="foo"
......@@ -30,7 +38,8 @@ class Dummy:
__repr__ = __str__
class TestCase( unittest.TestCase ):
class PathIndexTests(unittest.TestCase):
""" Test PathIndex objects """
def setUp(self):
......@@ -60,6 +69,14 @@ class TestCase( unittest.TestCase ):
for k, v in self._values.items():
self._index.index_object( k, v )
def test_z3interfaces(self):
from Products.PluginIndexes.interfaces import IPathIndex
from Products.PluginIndexes.interfaces import IUniqueValueIndex
from zope.interface.verify import verifyClass
verifyClass(IPathIndex, PathIndex)
verifyClass(IUniqueValueIndex, PathIndex)
def testEmpty(self):
self.assertEqual(self._index.numObjects() ,0)
self.assertEqual(self._index.getEntryForObject(1234), None)
......@@ -87,7 +104,6 @@ class TestCase( unittest.TestCase ):
self._index.index_object(19, o)
self.assertEqual(self._index.numObjects(), 19)
def testUnIndexError(self):
self._populateIndex()
# this should not raise an error
......@@ -135,7 +151,6 @@ class TestCase( unittest.TestCase ):
lst = list(res[0].keys())
self.assertEqual(lst,results)
def testSimpleTests(self):
self._populateIndex()
......@@ -201,11 +216,11 @@ class TestCase( unittest.TestCase ):
lst = list(res[0].keys())
self.assertEqual(lst,results)
def test_suite():
return unittest.makeSuite( TestCase )
def main():
unittest.TextTestRunner().run(test_suite())
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(PathIndexTests),
))
if __name__ == '__main__':
main()
unittest.main(defaultTest='test_suite')
......@@ -7,36 +7,38 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Deprecated text index. Please use ZCTextIndex instead.
"""Text Index
$Id$
"""
__version__ = '$Revision: 1.36 $'[11:-2]
import operator, warnings
import re
from cgi import escape
from types import *
import re
import operator,warnings
from Globals import Persistent,DTMLFile
from Globals import Persistent, DTMLFile
from zLOG import LOG, ERROR
from Acquisition import Implicit
from Products.PluginIndexes.common.ResultList import ResultList
from Products.PluginIndexes import PluggableIndex
from Products.PluginIndexes.common.util import parseIndexRequest
from OFS.SimpleItem import SimpleItem
from BTrees.IOBTree import IOBTree
from BTrees.OIBTree import OIBTree
from BTrees.IIBTree import IIBTree, IIBucket, IISet, IITreeSet
from BTrees.IIBTree import IIBTree, IIBucket, IISet
from BTrees.IIBTree import difference, weightedIntersection
from BTrees.OIBTree import OIBTree
from zope.interface import implements
from Products.PluginIndexes import PluggableIndex
from Products.PluginIndexes.common import safe_callable
from Products.PluginIndexes.common.ResultList import ResultList
from Products.PluginIndexes.common.util import parseIndexRequest
from Products.PluginIndexes.interfaces import IPluggableIndex
from Products.PluginIndexes.interfaces import ITextIndex
from Lexicon import Lexicon
from types import *
from cgi import escape
class Op:
def __init__(self, name):
......@@ -54,7 +56,9 @@ operator_dict = {'andnot': AndNot, 'and': And, 'or': Or,
'...': Near, 'near': Near,
AndNot: AndNot, And: And, Or: Or, Near: Near}
class TextIndex(Persistent, Implicit, SimpleItem):
"""Full-text index.
There is a ZCatalog UML model that sheds some light on what is
......@@ -64,16 +68,17 @@ class TextIndex(Persistent, Implicit, SimpleItem):
{'bob' : {1 : 5, 2 : 3, 42 : 9}}
{'uncle' : {1 : 1}}
The '_unindex' attribute is a mapping from document id to word
ids. This mapping allows the catalog to unindex an object:
{42 : ('bob', 'is', 'your', 'uncle')
This isn't exactly how things are represented in memory, many
optimizations happen along the way."""
optimizations happen along the way.
"""
__implements__ = (PluggableIndex.PluggableIndexInterface,)
implements(ITextIndex, IPluggableIndex)
meta_type='TextIndex'
......@@ -114,11 +119,9 @@ class TextIndex(Persistent, Implicit, SimpleItem):
self.call_methods = call_methods
self.catalog = caller
# Default text index operator (should be visible to ZMI)
self.useOperator = 'or'
if extra: self.vocabulary_id = extra.vocabulary
else: self.vocabulary_id = "Vocabulary"
......@@ -132,11 +135,12 @@ class TextIndex(Persistent, Implicit, SimpleItem):
self._lexicon = lexicon
self.vocabulary_id = '__userdefined__'
def getId(self): return self.id
def getId(self):
return self.id
def getLexicon(self, vocab_id=None):
"""Return the Lexicon in use. Removed lots of stinking code"""
"""Get the Lexicon in use.
"""
if self._lexicon is None:
## if no lexicon is provided, create a default one
......@@ -152,12 +156,9 @@ class TextIndex(Persistent, Implicit, SimpleItem):
return self._lexicon
def __nonzero__(self):
return not not self._unindex
def clear(self):
"""Reinitialize the text index."""
self._index = IOBTree()
......@@ -186,7 +187,6 @@ class TextIndex(Persistent, Implicit, SimpleItem):
scores=IIBTree(scores)
return scores
convert(_index, self._index, threshold, convertScores)
_unindex=self._unindex
......@@ -205,7 +205,6 @@ class TextIndex(Persistent, Implicit, SimpleItem):
return histogram
def getEntryForObject(self, rid, default=None):
"""Get all information contained for a specific object.
......@@ -219,7 +218,6 @@ class TextIndex(Persistent, Implicit, SimpleItem):
return tuple(map(self.getLexicon().getWord,
results))
def insertForwardIndexEntry(self, entry, documentId, score=1):
"""Uses the information provided to update the indexes.
......@@ -303,7 +301,6 @@ class TextIndex(Persistent, Implicit, SimpleItem):
except (AttributeError, TypeError):
encoding = 'latin1'
lexicon = self.getLexicon()
splitter = lexicon.Splitter
......@@ -444,7 +441,6 @@ class TextIndex(Persistent, Implicit, SimpleItem):
return r
def _apply_index(self, request, cid=''):
""" Apply the index to query parameters given in the argument,
request
......@@ -500,7 +496,6 @@ class TextIndex(Persistent, Implicit, SimpleItem):
return (IIBucket(), (self.id,))
def positions(self, docid, words,
# This was never tested: obj
):
......@@ -513,14 +508,12 @@ class TextIndex(Persistent, Implicit, SimpleItem):
# The code below here is broken and requires an API change to fix
# it. Waaaaa.
if self._schema is None:
f = getattr
else:
f = operator.__getitem__
id = self._schema[self.id]
if self.call_methods:
doc = str(f(obj, self.id)())
else:
......@@ -531,8 +524,6 @@ class TextIndex(Persistent, Implicit, SimpleItem):
r = r+self.getLexicon().Splitter(doc).indexes(word)
return r
def query(self, s, default_operator=Or):
""" Evaluate a query string.
......@@ -566,7 +557,6 @@ class TextIndex(Persistent, Implicit, SimpleItem):
# evalute the final 'expression'
return self.evaluate(q)
def get_operands(self, q, i):
"""Evaluate and return the left and right operands for an operator"""
try:
......@@ -593,8 +583,6 @@ class TextIndex(Persistent, Implicit, SimpleItem):
return (left, right)
def evaluate(self, query):
"""Evaluate a parsed query"""
# Strip off meaningless layers
......@@ -649,17 +637,14 @@ class TextIndex(Persistent, Implicit, SimpleItem):
""" return name of indexed attributes """
return (self.id, )
def numObjects(self):
""" return number of index objects """
return len(self._index)
def manage_setPreferences(self,vocabulary,
REQUEST=None,RESPONSE=None,URL2=None):
""" preferences of TextIndex """
if self.vocabulary_id != vocabulary:
self.clear()
self.vocabulary_id = vocabulary
......@@ -667,10 +652,10 @@ class TextIndex(Persistent, Implicit, SimpleItem):
if RESPONSE:
RESPONSE.redirect(URL2 + '/manage_main?manage_tabs_message=Preferences%20saved')
manage_workspace = DTMLFile("dtml/manageTextIndex",globals())
manage_vocabulary = DTMLFile("dtml/manageVocabulary",globals())
def parse(s):
"""Parse parentheses and quotes"""
l = []
......@@ -713,7 +698,6 @@ def parse2(q, default_operator, operator_dict=operator_dict):
return q
def parens(s, parens_re=re.compile('[()]').search):
mo = parens_re(s)
if mo is None:
......@@ -737,7 +721,6 @@ def parens(s, parens_re=re.compile('[()]').search):
raise QueryError, "Mismatched parentheses"
def quotes(s):
if '"' not in s:
......@@ -766,7 +749,6 @@ def quotes(s):
return filter(None, splitted)
manage_addTextIndexForm = DTMLFile('dtml/addTextIndex', globals())
def manage_addTextIndex(self, id, extra=None, REQUEST=None, RESPONSE=None, URL3=None):
......
......@@ -7,16 +7,22 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""ZCatalog product"""
"""Vocabulary for deprecated text index.
$Id$
"""
from Globals import DTMLFile, MessageDialog
import Globals, AccessControl.Role
from Acquisition import Implicit
from Persistence import Persistent
from OFS.SimpleItem import Item
from zope.interface import implements
from Products.PluginIndexes.interfaces import IVocabulary
from Products.PluginIndexes.TextIndex import Lexicon, GlobbingLexicon
from Products.PluginIndexes.TextIndex.Lexicon import stop_word_dict
from Products.PluginIndexes.TextIndex import Splitter
......@@ -39,18 +45,16 @@ def manage_addVocabulary(self, id, title, globbing=None, extra=None,
class _extra: pass
class Vocabulary(Item, Persistent, Implicit,
AccessControl.Role.RoleManager,
):
"""
A Vocabulary is a user-managable realization of a Lexicon object.
class Vocabulary(Item, Persistent, Implicit, AccessControl.Role.RoleManager):
"""A Vocabulary is a user-managable realization of a Lexicon object.
"""
implements(IVocabulary)
meta_type = "Vocabulary"
_isAVocabulary = 1
manage_options=(
(
{'label': 'Vocabulary', 'action': 'manage_main',
......@@ -73,8 +77,6 @@ class Vocabulary(Item, Persistent, Implicit,
['Anonymous', 'Manager']),
)
manage_main = DTMLFile('dtml/manage_vocab', globals())
manage_query = DTMLFile('dtml/vocab_query', globals())
......@@ -115,7 +117,6 @@ class Vocabulary(Item, Persistent, Implicit,
return str(result)
def manage_insert(self, word='', URL1=None, RESPONSE=None):
""" doc string """
self.insert(word)
......
......@@ -7,18 +7,25 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""TextIndex unit tests.
$Id$
"""
import unittest
import Testing
import Zope2
Zope2.startup()
import sys, os, unittest
import zLOG
def log_write(subsystem, severity, summary, detail, error):
if severity >= zLOG.PROBLEM:
assert 0, "%s(%s): %s" % (subsystem, severity, summary)
import ZODB
from ZODB.MappingStorage import MappingStorage
import transaction
......@@ -26,6 +33,7 @@ import transaction
from Products.PluginIndexes.TextIndex import TextIndex
from Products.PluginIndexes.TextIndex import GlobbingLexicon
class Dummy:
def __init__( self, text ):
......@@ -51,7 +59,6 @@ class Tests(unittest.TestCase):
self.old_log_write = zLOG.log_write
zLOG.log_write=log_write
def dbopen(self):
if self.db is None:
s = MappingStorage()
......@@ -79,14 +86,23 @@ class Tests(unittest.TestCase):
self.db = None
zLOG.log_write=self.old_log_write
def checkSimpleAddDelete(self):
def test_z3interfaces(self):
from Products.PluginIndexes.interfaces import IPluggableIndex
from Products.PluginIndexes.interfaces import ITextIndex
from Products.PluginIndexes.TextIndex.TextIndex import TextIndex
from zope.interface.verify import verifyClass
verifyClass(IPluggableIndex, TextIndex)
verifyClass(ITextIndex, TextIndex)
def test_SimpleAddDelete(self):
self.index.index_object(0, self.doc)
self.index.index_object(1, self.doc)
self.doc.text='spam is good, spam is fine, span span span'
self.index.index_object(0, self.doc)
self.index.unindex_object(0)
def checkPersistentUpdate1(self):
def test_PersistentUpdate1(self):
# Check simple persistent indexing
index=self.dbopen()
......@@ -112,7 +128,7 @@ class Tests(unittest.TestCase):
r=list(r[0].keys())
assert r == [0,1], r
def checkPersistentUpdate2(self):
def test_PersistentUpdate2(self):
# Check less simple persistent indexing
index=self.dbopen()
......@@ -146,8 +162,6 @@ class Tests(unittest.TestCase):
r=list(r[0].keys())
assert r == [0,1,2], r
sample_texts = [
"""This is the time for all good men to come to
the aid of their country""",
......@@ -178,95 +192,95 @@ class Tests(unittest.TestCase):
assert r == rlist, r
return index._apply_index
def checkStarQuery(self):
def test_StarQuery(self):
self.globTest({'text':'m*n'}, [0,2])
def checkAndQuery(self):
def test_AndQuery(self):
self.globTest({'text':'time and country'}, [0,])
def checkOrQuery(self):
def test_OrQuery(self):
self.globTest({'text':'time or country'}, [0,1,6])
def checkDefaultOrQuery(self):
def test_DefaultOrQuery(self):
self.globTest({'text':'time country'}, [0,1,6])
def checkNearQuery(self):
def test_NearQuery(self):
# Check a NEAR query.. (NOTE:ACTUALLY AN 'AND' TEST!!)
# NEAR never worked, so Zopes post-2.3.1b3 define near to mean AND
self.globTest({'text':'time ... country'}, [0,])
def checkQuotesQuery(self):
def test_QuotesQuery(self):
ai = self.globTest({'text':'"This is the time"'}, [0,])
r = list(ai({'text':'"now is the time"'})[0].keys())
assert r == [], r
def checkAndNotQuery(self):
def test_AndNotQuery(self):
self.globTest({'text':'time and not country'}, [6,])
def checkParenMatchingQuery(self):
def test_ParenMatchingQuery(self):
ai = self.globTest({'text':'(time and country) men'}, [0,])
r = list(ai({'text':'(time and not country) or men'})[0].keys())
assert r == [0, 6], r
def checkTextIndexOperatorQuery(self):
def test_TextIndexOperatorQuery(self):
self.globTest({'text': {'query': 'time men', 'operator':'and'}}, [0,])
def checkNonExistentWord(self):
def test_NonExistentWord(self):
self.globTest({'text':'zop'}, [])
def checkComplexQuery1(self):
def test_ComplexQuery1(self):
self.globTest({'text':'((?ount* or get) and not wait) '
'"been *ert*"'}, [0, 1, 5, 6])
# same tests, unicode strings
def checkStarQueryUnicode(self):
def test_StarQueryUnicode(self):
self.globTest({'text':u'm*n'}, [0,2])
def checkAndQueryUnicode(self):
def test_AndQueryUnicode(self):
self.globTest({'text':u'time and country'}, [0,])
def checkOrQueryUnicode(self):
def test_OrQueryUnicode(self):
self.globTest({'text':u'time or country'}, [0,1,6])
def checkDefaultOrQueryUnicode(self):
def test_DefaultOrQueryUnicode(self):
self.globTest({'text':u'time country'}, [0,1,6])
def checkNearQueryUnicode(self):
def test_NearQueryUnicode(self):
# Check a NEAR query.. (NOTE:ACTUALLY AN 'AND' TEST!!) (unicode)
# NEAR never worked, so Zopes post-2.3.1b3 define near to mean AND
self.globTest({'text':u'time ... country'}, [0,])
def checkQuotesQueryUnicode(self):
def test_QuotesQueryUnicode(self):
ai = self.globTest({'text':u'"This is the time"'}, [0,])
r = list(ai({'text':'"now is the time"'})[0].keys())
assert r == [], r
def checkAndNotQueryUnicode(self):
def test_AndNotQueryUnicode(self):
self.globTest({'text':u'time and not country'}, [6,])
def checkParenMatchingQueryUnicode(self):
def test_ParenMatchingQueryUnicode(self):
ai = self.globTest({'text':u'(time and country) men'}, [0,])
r = list(ai({'text':u'(time and not country) or men'})[0].keys())
assert r == [0, 6], r
def checkTextIndexOperatorQueryUnicode(self):
def test_TextIndexOperatorQueryUnicode(self):
self.globTest({'text': {u'query': u'time men', 'operator':'and'}},
[0,])
def checkNonExistentWordUnicode(self):
def test_NonExistentWordUnicode(self):
self.globTest({'text':u'zop'}, [])
def checkComplexQuery1Unicode(self):
def test_ComplexQuery1Unicode(self):
self.globTest({'text':u'((?ount* or get) and not wait) '
'"been *ert*"'}, [0, 1, 5, 6])
def test_suite():
return unittest.makeSuite(Tests, 'check')
return unittest.makeSuite(Tests)
if __name__=='__main__':
unittest.main(defaultTest='test_suite')
......@@ -7,11 +7,13 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Filtered set.
__version__ = '$Id$'
$Id$
"""
import sys
from logging import getLogger
......@@ -19,12 +21,18 @@ from logging import getLogger
from ZODB.POSException import ConflictError
from BTrees.IIBTree import IITreeSet
from Persistence import Persistent
from RestrictedPython.Eval import RestrictionCapableEval
from zope.interface import implements
from Products.PluginIndexes.interfaces import IFilteredSet
LOG = getLogger('Zope.TopicIndex.FilteredSet')
class FilteredSetBase(Persistent):
# A pre-calculated result list based on an expression.
implements(IFilteredSet)
def __init__(self, id, expr):
self.id = id
......@@ -43,17 +51,21 @@ class FilteredSetBase(Persistent):
def getId(self):
return self.id
def getExpression(self):
# Get the expression.
return self.expr
def getIds(self):
# Get the IDs of all objects for which the expression is True.
return self.ids
def getType(self):
return self.meta_type
def setExpression(self, expr): self.expr = expr
def setExpression(self, expr):
# Set the expression.
self.expr = expr
def __repr__(self):
return '%s: (%s) %s' % (self.id,self.expr,map(None,self.ids))
......@@ -67,7 +79,7 @@ class PythonFilteredSet(FilteredSetBase):
def index_object(self, documentId, o):
try:
if RestrictionCapableEval(self.expr).eval({'o': o}):
if RestrictionCapableEval(self.expr).eval({'o': o}):
self.ids.insert(documentId)
else:
try:
......
......@@ -7,11 +7,13 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Topic index.
__version__ = '$Id$'
$Id$
"""
from logging import getLogger
......@@ -19,23 +21,29 @@ from Globals import Persistent, DTMLFile
from OFS.SimpleItem import SimpleItem
from BTrees.OOBTree import OOBTree
from BTrees.IIBTree import IITreeSet,intersection,union
from zope.interface import implements
import FilteredSet
from Products.PluginIndexes import PluggableIndex
from Products.PluginIndexes.common.util import parseIndexRequest
from Products.PluginIndexes.interfaces import IPluggableIndex
from Products.PluginIndexes.interfaces import ITopicIndex
import FilteredSet
_marker = []
LOG = getLogger('Zope.TopicIndex')
class TopicIndex(Persistent, SimpleItem):
""" A TopicIndex maintains a set of FilteredSet objects.
Every FilteredSet object consists of an expression and
and IISet with all Ids of indexed objects that eval with
this expression to 1.
"""A TopicIndex maintains a set of FilteredSet objects.
Every FilteredSet object consists of an expression and and IISet with all
Ids of indexed objects that eval with this expression to 1.
"""
__implements__ = (PluggableIndex.PluggableIndexInterface,)
implements(ITopicIndex, IPluggableIndex)
meta_type="TopicIndex"
query_options = ('query','operator')
......@@ -52,7 +60,8 @@ class TopicIndex(Persistent, SimpleItem):
self.operators = ('or','and')
self.defaultOperator = 'or'
def getId(self): return self.id
def getId(self):
return self.id
def clear(self):
for fs in self.filteredSets.values():
......@@ -111,25 +120,27 @@ class TopicIndex(Persistent, SimpleItem):
def getEntryForObject(self,docid, default=_marker):
""" Takes a document ID and returns all the information we have
on that specific object.
on that specific object.
"""
return self.filteredSets.keys()
def addFilteredSet(self, filter_id, typeFilteredSet, expr):
# Add a FilteredSet object.
if self.filteredSets.has_key(filter_id):
raise KeyError,\
'A FilteredSet with this name already exists: %s' % filter_id
self.filteredSets[filter_id] = \
FilteredSet.factory(filter_id, typeFilteredSet, expr)
def delFilteredSet(self,filter_id):
def delFilteredSet(self, filter_id):
# Delete the FilteredSet object specified by 'filter_id'.
if not self.filteredSets.has_key(filter_id):
raise KeyError,\
'no such FilteredSet: %s' % filter_id
del self.filteredSets[filter_id]
def clearFilteredSet(self,filter_id):
def clearFilteredSet(self, filter_id):
# Clear the FilteredSet object specified by 'filter_id'.
if not self.filteredSets.has_key(filter_id):
raise KeyError,\
'no such FilteredSet: %s' % filter_id
......@@ -184,7 +195,6 @@ class TopicIndex(Persistent, SimpleItem):
RESPONSE.redirect(URL1+'/manage_workspace?'
'manage_tabs_message=FilteredSet(s)%20cleared')
index_html = DTMLFile('dtml/index', globals())
manage_workspace = DTMLFile('dtml/manageTopicIndex',globals())
editFilteredSet = DTMLFile('dtml/editFilteredSet',globals())
......
......@@ -7,15 +7,22 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""TopicIndex unit tests.
$Id$
"""
import unittest
import Testing
import Zope2
Zope2.startup()
import ZODB
from Products.PluginIndexes.TopicIndex.TopicIndex import TopicIndex
class Obj:
def __init__(self,id,meta_type=''):
......@@ -25,6 +32,7 @@ class Obj:
def getId(self): return self.id
def getPhysicalPath(self): return self.id
class TestBase(unittest.TestCase):
def _searchAnd(self,query,expected):
......@@ -41,6 +49,7 @@ class TestBase(unittest.TestCase):
self.assertEqual(rows,expected,query)
return rows
class TestTopicIndex(TestBase):
def setUp(self):
......@@ -56,6 +65,13 @@ class TestTopicIndex(TestBase):
self.TI.index_object(5 , Obj('5','doc3'))
self.TI.index_object(6 , Obj('6','doc3'))
def test_z3interfaces(self):
from Products.PluginIndexes.interfaces import ITopicIndex
from Products.PluginIndexes.interfaces import IPluggableIndex
from zope.interface.verify import verifyClass
verifyClass(ITopicIndex, TopicIndex)
verifyClass(IPluggableIndex, TopicIndex)
def testOr(self):
self._searchOr('doc1',[1,2])
......@@ -64,28 +80,23 @@ class TestTopicIndex(TestBase):
self._searchOr(['doc2'],[3,4])
self._searchOr(['doc1','doc2'], [1,2,3,4])
def testAnd(self):
self._searchAnd('doc1',[1,2])
self._searchAnd(['doc1'],[1,2])
self._searchAnd('doc2',[3,4])
self._searchAnd(['doc2'],[3,4])
self._searchAnd(['doc1','doc2'],[])
def testRemoval(self):
self.TI.index_object(1, Obj('1','doc2'))
self._searchOr('doc1',[2])
self._searchOr('doc2', [1,3,4])
def test_suite():
return unittest.TestSuite( (
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(TestTopicIndex),
))
def main():
unittest.TextTestRunner().run(test_suite())
if __name__ == '__main__':
main()
unittest.main(defaultTest='test_suite')
......@@ -4,33 +4,47 @@
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Base for bi-directional indexes
"""Base for bi-directional indexes.
$Id$"""
$Id$
"""
import sys
from cgi import escape
from logging import getLogger
from BTrees.OOBTree import OOBTree
from BTrees.IOBTree import IOBTree
from BTrees.IIBTree import IITreeSet, IISet, union, intersection
from OFS.SimpleItem import SimpleItem
from BTrees.IOBTree import IOBTree
import BTrees.Length
from BTrees.OOBTree import OOBTree
from OFS.SimpleItem import SimpleItem
from zope.interface import implements
from Products.PluginIndexes.common.util import parseIndexRequest
from Products.PluginIndexes import PluggableIndex
from Products.PluginIndexes.common import safe_callable
from Products.PluginIndexes.common.util import parseIndexRequest
from Products.PluginIndexes.interfaces import IPluggableIndex
from Products.PluginIndexes.interfaces import ISortIndex
from Products.PluginIndexes.interfaces import IUniqueValueIndex
_marker = []
LOG = getLogger('Zope.UnIndex')
class UnIndex(SimpleItem):
"""Simple forward and reverse index"""
"""Simple forward and reverse index.
"""
__implements__ = (PluggableIndex.UniqueValueIndex,
PluggableIndex.SortIndex)
implements(IPluggableIndex, IUniqueValueIndex, ISortIndex)
def __init__(
self, id, ignore_ex=None, call_methods=None, extra=None, caller=None):
......@@ -93,7 +107,8 @@ class UnIndex(SimpleItem):
self.indexed_attrs = ia.split(',')
else:
self.indexed_attrs = list(ia)
self.indexed_attrs = [ attr.strip() for attr in self.indexed_attrs if attr ]
self.indexed_attrs = [ attr.strip()
for attr in self.indexed_attrs if attr ]
if not self.indexed_attrs:
self.indexed_attrs = [id]
......@@ -133,7 +148,6 @@ class UnIndex(SimpleItem):
"""Generate a list of IDs for which we have referenced objects."""
return self._unindex.keys()
def getEntryForObject(self, documentId, default=_marker):
"""Takes a document ID and returns all the information we have
on that specific object.
......@@ -143,7 +157,6 @@ class UnIndex(SimpleItem):
else:
return self._unindex.get(documentId, default)
def removeForwardIndexEntry(self, entry, documentId):
"""Take the entry provided and remove any reference to documentId
in its entry in the index.
......@@ -173,7 +186,6 @@ class UnIndex(SimpleItem):
'should not happen.' % (self.__class__.__name__,
repr(entry), str(self.id)))
def insertForwardIndexEntry(self, entry, documentId):
"""Take the entry provided and put it in the correct place
in the forward index.
......@@ -194,7 +206,6 @@ class UnIndex(SimpleItem):
indexRow=IITreeSet((indexRow, documentId))
self._index[entry] = indexRow
def index_object(self, documentId, obj, threshold=None):
""" wrapper to handle indexing of multiple attributes """
......@@ -208,7 +219,6 @@ class UnIndex(SimpleItem):
return res > 0
def _index_object(self, documentId, obj, threshold=None, attr=''):
""" index and object 'obj' with integer id 'documentId'"""
returnStatus = 0
......@@ -319,7 +329,6 @@ class UnIndex(SimpleItem):
r = None
opr = None
# experimental code for specifing the operator
operator = record.get('operator',self.useOperator)
if not operator in self.operators :
......@@ -339,13 +348,11 @@ class UnIndex(SimpleItem):
if range_parm.find("max")>-1:
opr_args.append("max")
if record.get('usage',None):
# see if any usage params are sent to field
opr = record.usage.lower().split(':')
opr, opr_args=opr[0], opr[1:]
if opr=="range": # range search
if 'min' in opr_args: lo = min(record.keys)
else: lo = None
......
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""PluginIndexes z3 interfaces.
$Id$
"""
from zope.interface import Interface
from zope.schema import Bool
# create IPluggableIndex, IUniqueValueIndex, ISortIndex
from Products.Five.fiveconfigure import createZope2Bridge
from common.PluggableIndex import PluggableIndexInterface
from common.PluggableIndex import SortIndex
from common.PluggableIndex import UniqueValueIndex
import interfaces
createZope2Bridge(PluggableIndexInterface, interfaces, 'IPluggableIndex')
createZope2Bridge(SortIndex, interfaces, 'ISortIndex')
createZope2Bridge(UniqueValueIndex, interfaces, 'IUniqueValueIndex')
del createZope2Bridge
del PluggableIndexInterface
del SortIndex
del UniqueValueIndex
del interfaces
class IDateIndex(Interface):
"""Index for dates.
"""
index_naive_time_as_local = Bool(title=u'Index naive time as local?')
class IDateRangeIndex(Interface):
"""Index for date ranges, such as the "effective-expiration" range in CMF.
Any object may return None for either the start or the end date: for the
start date, this should be the logical equivalent of "since the beginning
of time"; for the end date, "until the end of time".
Therefore, divide the space of indexed objects into four containers:
- Objects which always match (i.e., they returned None for both);
- Objects which match after a given time (i.e., they returned None for the
end date);
- Objects which match until a given time (i.e., they returned None for the
start date);
- Objects which match only during a specific interval.
"""
def getSinceField():
"""Get the name of the attribute indexed as start date.
"""
def getUntilField():
"""Get the name of the attribute indexed as end date.
"""
class IPathIndex(Interface):
"""Index for paths returned by getPhysicalPath.
A path index stores all path components of the physical path of an object.
Internal datastructure:
- a physical path of an object is split into its components
- every component is kept as a key of a OOBTree in self._indexes
- the value is a mapping 'level of the path component' to
'all docids with this path component on this level'
"""
class IVocabulary(Interface):
"""A Vocabulary is a user-managable realization of a Lexicon object.
"""
class ITextIndex(Interface):
"""Full-text index.
There is a ZCatalog UML model that sheds some light on what is
going on here. '_index' is a BTree which maps word ids to mapping
from document id to score. Something like:
{'bob' : {1 : 5, 2 : 3, 42 : 9}}
{'uncle' : {1 : 1}}
The '_unindex' attribute is a mapping from document id to word
ids. This mapping allows the catalog to unindex an object:
{42 : ('bob', 'is', 'your', 'uncle')
This isn't exactly how things are represented in memory, many
optimizations happen along the way.
"""
def getLexicon(vocab_id=None):
"""Get the Lexicon in use.
"""
class IFilteredSet(Interface):
"""A pre-calculated result list based on an expression.
"""
def getExpression():
"""Get the expression.
"""
def getIds():
"""Get the IDs of all objects for which the expression is True.
"""
def setExpression(expr):
"""Set the expression.
"""
class ITopicIndex(Interface):
"""A TopicIndex maintains a set of FilteredSet objects.
Every FilteredSet object consists of an expression and and IISet with all
Ids of indexed objects that eval with this expression to 1.
"""
def addFilteredSet(filter_id, typeFilteredSet, expr):
"""Add a FilteredSet object.
"""
def delFilteredSet(filter_id):
"""Delete the FilteredSet object specified by 'filter_id'.
"""
def clearFilteredSet(filter_id):
"""Clear the FilteredSet object specified by 'filter_id'.
"""
......@@ -8,29 +8,30 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Plug in text index for ZCatalog with relevance ranking.
"""Plug in text index for ZCatalog with relevance ranking."""
$Id$
"""
from cgi import escape
from types import TupleType
import ZODB
from Persistence import Persistent
import Acquisition
from Acquisition import aq_base, aq_inner, aq_parent
from OFS.SimpleItem import SimpleItem
from Globals import DTMLFile, InitializeClass
from AccessControl.SecurityInfo import ClassSecurityInfo
from AccessControl.Permissions import manage_zcatalog_indexes, search_zcatalog
from zope.interface import implements
from Products.PluginIndexes.common.PluggableIndex import \
PluggableIndexInterface
from Products.PluginIndexes.common.util import parseIndexRequest
from Products.PluginIndexes.common import safe_callable
from Products.PluginIndexes.interfaces import IPluggableIndex
from Products.ZCTextIndex.ILexicon import ILexicon
from Products.ZCTextIndex.Lexicon import \
......@@ -38,16 +39,23 @@ from Products.ZCTextIndex.Lexicon import \
from Products.ZCTextIndex.NBest import NBest
from Products.ZCTextIndex.QueryParser import QueryParser
from PipelineFactory import element_factory
from interfaces import IZCLexicon
from interfaces import IZCTextIndex
from Products.ZCTextIndex.CosineIndex import CosineIndex
from Products.ZCTextIndex.OkapiIndex import OkapiIndex
index_types = {'Okapi BM25 Rank':OkapiIndex,
'Cosine Measure':CosineIndex}
class ZCTextIndex(Persistent, Acquisition.Implicit, SimpleItem):
"""Persistent TextIndex"""
"""Persistent text index.
"""
__implements__ = PluggableIndexInterface
implements(IZCTextIndex, IPluggableIndex)
## Magic class attributes ##
......@@ -72,7 +80,8 @@ class ZCTextIndex(Persistent, Acquisition.Implicit, SimpleItem):
# via the silly "extra" record.
self._fieldname = field_name or getattr(extra, 'doc_attr', '') or id
self._indexed_attrs = self._fieldname.split(',')
self._indexed_attrs = [ attr.strip() for attr in self._indexed_attrs if attr ]
self._indexed_attrs = [ attr.strip()
for attr in self._indexed_attrs if attr ]
lexicon_id = lexicon_id or getattr(extra, 'lexicon_id', '')
lexicon = getattr(caller, lexicon_id, None)
......@@ -170,7 +179,7 @@ class ZCTextIndex(Persistent, Acquisition.Implicit, SimpleItem):
continue
all_texts.append(text)
if all_texts:
if all_texts:
return self.index.index_doc(documentId, ' '.join(all_texts))
else:
return 0
......@@ -237,7 +246,7 @@ class ZCTextIndex(Persistent, Acquisition.Implicit, SimpleItem):
def getIndexSourceNames(self):
"""Return sequence of names of indexed attributes"""
try:
return self._indexed_attrs
return self._indexed_attrs
except:
return [self._fieldname]
......@@ -253,7 +262,6 @@ class ZCTextIndex(Persistent, Acquisition.Implicit, SimpleItem):
return None
else:
return lex.absolute_url()
InitializeClass(ZCTextIndex)
......@@ -297,8 +305,13 @@ def manage_addLexicon(self, id, title='', elements=[], REQUEST=None):
LexiconQueryPerm = 'Query Vocabulary'
LexiconMgmtPerm = 'Manage Vocabulary'
class PLexicon(Lexicon, Acquisition.Implicit, SimpleItem):
"""Lexicon for ZCTextIndex"""
"""Lexicon for ZCTextIndex.
"""
implements(IZCLexicon)
meta_type = 'ZCTextIndex Lexicon'
......
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""ZCTextIndex z3 interfaces.
$Id$
"""
from zope.interface import Interface
class IZCTextIndex(Interface):
"""Persistent text index.
"""
class IZCLexicon(Interface):
"""Lexicon for ZCTextIndex.
"""
......@@ -11,14 +11,21 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""ZCTextIndex unit tests.
$Id$
"""
import unittest
import Testing
import Zope2
Zope2.startup()
import re
from Interface.Verify import verifyClass
import Acquisition
from zExceptions import NotFound
from Products.PluginIndexes.common.PluggableIndex import \
PluggableIndexInterface
from Products.ZCTextIndex.ZCTextIndex import ZCTextIndex, PLexicon
from Products.ZCTextIndex.tests import \
testIndex, testQueryEngine, testQueryParser
......@@ -32,8 +39,6 @@ from Products.ZCTextIndex.QueryParser import QueryParser
from Products.ZCTextIndex.StopDict import get_stopdict
from Products.ZCTextIndex.ParseTree import ParseError
import re
import unittest
class Indexable:
def __init__(self, text):
......@@ -227,9 +232,21 @@ class CosineIndexTests(ZCIndexTestsBase, testIndex.CosineIndexTest):
# Gigabytes, pp. 180-188. This test peeks into many internals of the
# cosine indexer.
def testInterface(self):
def test_z2interfaces(self):
from Interface.Verify import verifyClass
from Products.PluginIndexes.common.PluggableIndex \
import PluggableIndexInterface
verifyClass(PluggableIndexInterface, ZCTextIndex)
def test_z3interfaces(self):
from Products.PluginIndexes.interfaces import IPluggableIndex
from Products.ZCTextIndex.interfaces import IZCTextIndex
from zope.interface.verify import verifyClass
verifyClass(IPluggableIndex, ZCTextIndex)
verifyClass(IZCTextIndex, ZCTextIndex)
def testRanking(self):
self.words = ["cold", "days", "eat", "hot", "lot", "nine", "old",
"pease", "porridge", "pot"]
......@@ -525,18 +542,28 @@ class QueryTestsBase(testQueryEngine.TestQueryEngine,
dictkeys.sort()
self.assertEqual(setkeys, dictkeys)
class CosineQueryTests(QueryTestsBase):
IndexFactory = CosineIndex
class OkapiQueryTests(QueryTestsBase):
IndexFactory = OkapiIndex
############################################################################
class PLexiconTests(unittest.TestCase):
def test_z3interfaces(self):
from Products.ZCTextIndex.interfaces import IZCLexicon
from zope.interface.verify import verifyClass
verifyClass(IZCLexicon, PLexicon)
def test_suite():
s = unittest.TestSuite()
for klass in (CosineIndexTests, OkapiIndexTests,
CosineQueryTests, OkapiQueryTests):
CosineQueryTests, OkapiQueryTests, PLexiconTests):
s.addTest(unittest.makeSuite(klass))
return s
......
......@@ -16,11 +16,10 @@ $Id$
"""
from warnings import warn
import urllib, time, sys, string,logging
import urllib, time, sys, string, logging
from Globals import DTMLFile, MessageDialog
import Globals
from OFS.Folder import Folder
from OFS.ObjectManager import ObjectManager
from DateTime import DateTime
......@@ -29,19 +28,23 @@ from Persistence import Persistent
from DocumentTemplate.DT_Util import InstanceDict, TemplateDict
from DocumentTemplate.DT_Util import Eval
from AccessControl.Permission import name_trans
from Catalog import Catalog, CatalogError
from AccessControl.DTML import RestrictedDTML
from AccessControl.Permissions import \
manage_zcatalog_entries, manage_zcatalog_indexes, search_zcatalog
from ZCatalogIndexes import ZCatalogIndexes
from ZODB.POSException import ConflictError
import transaction
from Products.PluginIndexes.common.PluggableIndex \
import PluggableIndexInterface
from Products.PluginIndexes.TextIndex import Splitter
from IZCatalog import IZCatalog
from zLOG import LOG
from zope.interface import implements
from Catalog import Catalog, CatalogError
from interfaces import IZCatalog as z3IZCatalog
from IZCatalog import IZCatalog as z2IZCatalog
from ProgressHandler import ZLogHandler
from zLOG import LOG, INFO
from ZCatalogIndexes import ZCatalogIndexes
LOG = logging.getLogger('Zope.ZCatalog')
......@@ -79,7 +82,8 @@ class ZCatalog(Folder, Persistent, Implicit):
Python program to catalog objects.
"""
__implements__ = IZCatalog
__implements__ = z2IZCatalog
implements(z3IZCatalog)
meta_type = "ZCatalog"
icon='misc_/ZCatalog/ZCatalog.gif'
......
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""ZCatalog z3 interfaces.
$Id$
"""
# create IZCatalog
from Products.Five.fiveconfigure import createZope2Bridge
from IZCatalog import IZCatalog as z2IZCatalog
import interfaces
createZope2Bridge(z2IZCatalog, interfaces, 'IZCatalog')
del createZope2Bridge
del z2IZCatalog
del interfaces
......@@ -12,14 +12,13 @@
##############################################################################
""" Unittests for Catalog.
$Id:$
$Id$
"""
import unittest
import Testing
import Zope2
Zope2.startup()
from Interface.Verify import verifyClass
from itertools import chain
import random
......@@ -162,6 +161,7 @@ class zdummyFalse(zdummy):
class TestZCatalog(unittest.TestCase):
def setUp(self):
from Products.ZCatalog.ZCatalog import ZCatalog
self._catalog = ZCatalog('Catalog')
......@@ -181,6 +181,20 @@ class TestZCatalog(unittest.TestCase):
def _resolve_num(self, num):
return self.d[num]
def test_z2interfaces(self):
from Interface.Verify import verifyClass
from Products.ZCatalog.IZCatalog import IZCatalog
from Products.ZCatalog.ZCatalog import ZCatalog
verifyClass(IZCatalog, ZCatalog)
def test_z3interfaces(self):
from Products.ZCatalog.interfaces import IZCatalog
from Products.ZCatalog.ZCatalog import ZCatalog
from zope.interface.verify import verifyClass
verifyClass(IZCatalog, ZCatalog)
def testGetMetadataForUID(self):
testNum = str(self.upper - 3) # as good as any..
data = self._catalog.getMetadataForUID(testNum)
......@@ -232,12 +246,6 @@ class TestZCatalog(unittest.TestCase):
result = self._catalog(title='9999')
self.assertEquals(1, len(result))
def test_interface(self):
from Products.ZCatalog.IZCatalog import IZCatalog
from Products.ZCatalog.ZCatalog import ZCatalog
verifyClass(IZCatalog, ZCatalog)
class dummy(ExtensionClass.Base):
att1 = 'att1'
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment