[Zope3-checkins] CVS: Zope3/src/zope/app/index/text - __init__.py:1.1.2.1 configure.zcml:1.1.2.1 control.pt:1.1.2.1 control.py:1.1.2.1 index.py:1.1.2.1 processors.py:1.1.2.1 queries.py:1.1.2.1

Jim Fulton jim@zope.com
Mon, 23 Dec 2002 14:31:41 -0500


Update of /cvs-repository/Zope3/src/zope/app/index/text
In directory cvs.zope.org:/tmp/cvs-serv19908/zope/app/index/text

Added Files:
      Tag: NameGeddon-branch
	__init__.py configure.zcml control.pt control.py index.py 
	processors.py queries.py 
Log Message:
Initial renaming before debugging

=== Added File Zope3/src/zope/app/index/text/__init__.py ===
#
# This file is necessary to make this directory a package.


=== Added File Zope3/src/zope/app/index/text/configure.zcml ===
<zopeConfigure
   xmlns='http://namespaces.zope.org/zope'
   xmlns:browser='http://namespaces.zope.org/browser'
>

  <content class="zope.app.index.text.index.TextIndex">

    <require
      permission="Zope.ManageServices"
      interface="zope.app.interfaces.index.text.interfaces.IUITextIndex"
      attributes="query"
      />

    <factory
      id="Zope.App.index.text.factory"
      permission="Zope.ManageServices"
      />

  </content>

  <browser:menuItem
    menu="add_component"
    for="zope.app.interfaces.container.IAdding"
    action="Zope.App.index.text.factory"
    title="Text Index"
    description="An index to support full-text search"
    />

  <browser:defaultView
    for="zope.textindex.textindexinterfaces.IStatistics"
    name="control.html" />

  <browser:view
    for="zope.textindex.textindexinterfaces.IStatistics"
    permission="Zope.ManageServices"
    name="control.html"
    factory="zope.app.index.text.control.ControlView">

    <browser:page name="index.html" template="control.pt" />

  </browser:view>

</zopeConfigure>


=== Added File Zope3/src/zope/app/index/text/control.pt ===
<html metal:use-macro="views/standard_macros/page">

  <head>
    <title>TextIndex Control Page</title>
  </head>

  <body>

  <div metal:fill-slot="body">

    <h1><br>TextIndex</h1>

    <p class="form-help">
        This page lets you control a text index, which is used to
        provide a full-text searching facility.  The search box here is
        only for debugging.  Subscription status: A "subscribed" index
        will update itself whenever objects are added, deleted or
        modified; an "unsubscribed" index will retain the indexing
        information but not update itself further.
    </p>

    <span tal:condition="request/callSubscribe|nothing" tal:omit-tag="">
        <span tal:define="dummy context/subscribe" tal:omit-tag=""/>
        Successfully subscribed.
    </span>
    <span tal:condition="request/callUnsubscribe|nothing" tal:omit-tag="">
        <span tal:define="dummy context/unsubscribe" tal:omit-tag=""/>
        Successfully unsubscribed.
    </span>

    <div>
        Documents: <span tal:replace="context/documentCount" />
    </div>

    <div>
        Words: <span tal:replace="context/wordCount" />
    </div>

    <form method="POST">
       <span tal:condition="context/isSubscribed" tal:omit-tag="">
           Subscription state: ON
           <input type="submit" value="Unsubscribe" name="callUnsubscribe" />
       </span>
       <span tal:condition="not:context/isSubscribed" tal:omit-tag="">
           Subscription state: OFF
           <input type="submit" value="Subscribe" name="callSubscribe" />
       </span>
       <input type="hidden" name="queryText" value=""
              tal:attributes="value request/queryText|nothing" />
    </form>

    <form method="GET">
        <input type="text" name="queryText" value=""
               tal:attributes="value request/queryText|nothing" />
        <input type="submit" value="Query" />
    </form>

    <div tal:condition="request/queryText|nothing" tal:omit-tag="">
        <div tal:define="result view/query" tal:omit-tag="">
	    <div tal:condition="not:result/total">
	        No hits.  Please try another query.
	    </div>
	    <div tal:condition="result/total">
		<div tal:content="
		string:Hits ${result/first}-${result/last} of ${result/total}:"
		/>
		<div tal:repeat="info result/results">
	            title=<span tal:replace="info/title" />;
		    url=<a href="location"
		            tal:attributes="href info/location"
                            tal:content="info/location">url</a>;
		    score=<span tal:replace="info/scoreLabel" />
		</div>
	    </div>
	    <span tal:condition="exists:result/prev">
                <a href="next"
                   tal:attributes="href
                       string:?queryText=${request/queryText}&start=${view/prevBatch}">&lt;-- PREVIOUS BATCH</a>
	    </span>
	    <span tal:condition="exists:result/next">
                <a href="next"
                   tal:attributes="href
                       string:?queryText=${request/queryText}&start=${view/nextBatch}">NEXT BATCH --&gt;</a>
	    </span>
        </div>
    </div>

  </div>

  </body>

</html>


=== Added File Zope3/src/zope/app/index/text/control.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Control view for the text index.

XXX longer description goes here.

$Id: control.py,v 1.1.2.1 2002/12/23 19:31:39 jim Exp $
"""

from __future__ import generators

from zope.component import getService, queryAdapter
from zope.exceptions import NotFoundError
from zope.publisher.interfaces.browser import IBrowserView
from zope.publisher.browser import BrowserView

from Zope.App.Traversing import locationAsUnicode
from zope.app.interfaces.dublincore import IZopeDublinCore
from zope.app.interfaces.index.text.interfaces import IQueryView

class ControlView(BrowserView):

    __implements__ = BrowserView.__implements__, IQueryView

    default_start = 0 # Don't change -- always start at first batch
    default_count = 2 # Default batch size -- tune as you please

    def __init__(self, context, request):
        super(ControlView, self).__init__(context, request)
        self.hub = getService(context, "ObjectHub")

    def nextBatch(self):
        start = int(self.request.get('start', self.default_start))
        count = int(self.request.get('count', self.default_count))
        return start + count

    def prevBatch(self):
        start = int(self.request.get('start', self.default_start))
        count = int(self.request.get('count', self.default_count))
        return start - count

    def query(self, start=None):
        queryText = self.request.get('queryText', '')
        if start is None:
            start = int(self.request.get('start', self.default_start))
        count = int(self.request.get('count', self.default_count))
        results, total = self.context.query(queryText, start, count)
        nresults = len(results)
        first = start + 1
        last = first + len(results) - 1
        result = {
            'results': list(self._resultIterator(results)),
            'nresults': nresults,
            'first': first,
            'last': last,
            'total': total,
            }
        if start > 0:
            prev = max(0, start - count)
            result['prev'] = prev
        if last < total:
            next = start + count
            result['next'] = next
        return result

    def _resultIterator(self, results):
        for hubid, score in results:
            yield self._cookInfo(hubid, score)

    def _cookInfo(self, hubid, score):
        location = locationAsUnicode(self.hub.getLocation(hubid))
        scoreLabel = "%.1f%%" % (100.0 * score)
        result = {
            'location': location,
            'score': score,
            'scoreLabel': scoreLabel,
            }
        try:
            object = self.hub.getObject(hubid)
        except NotFoundError:
            pass
        else:
            dc = queryAdapter(object, IZopeDublinCore, context=self.context)
            if dc is not None:
                title = dc.title
                result['title'] = title
        return result


=== Added File Zope3/src/zope/app/index/text/index.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""This is a text index which can be subscribed to an event service.

Events related to object creation and deletion are translated into
index_doc() and unindex_doc() calls.

In addition, this implements TTW subscription management.

$Id: index.py,v 1.1.2.1 2002/12/23 19:31:39 jim Exp $
"""

from zope.component import getService, queryAdapter
from zope.proxy.context import ContextMethod
from zope.interfaces.event import ISubscriber
from zope.exceptions import NotFoundError
from zope.textindex.textindexwrapper import TextIndexWrapper

from zope.app.interfaces.dublincore import IZopeDublinCore
from zope.app.interfaces.services.hub import \
     IRegistrationHubEvent, \
     IObjectRegisteredHubEvent, \
     IObjectUnregisteredHubEvent, \
     IObjectModifiedHubEvent
from Zope.App.Traversing import locationAsUnicode
from zope.app.interfaces.index.text.interfaces import ISearchableText, IUITextIndex

class TextIndex(TextIndexWrapper):

    __implements__ = (TextIndexWrapper.__implements__,
                      ISubscriber, IUITextIndex)

    def notify(wrapped_self, event):
        """An event occurred.  Index or unindex the object in response."""
        if (IObjectRegisteredHubEvent.isImplementedBy(event) or
            IObjectModifiedHubEvent.isImplementedBy(event)):
            texts = wrapped_self._getTexts(event.object)
            if texts is not None:
                wrapped_self.index_doc(event.hubid, texts)
        elif IObjectUnregisteredHubEvent.isImplementedBy(event):
            try:
                wrapped_self.unindex_doc(event.hubid)
            except KeyError:
                pass
    notify = ContextMethod(notify)

    def _getTexts(wrapped_self, object):
        adapted = queryAdapter(object, ISearchableText, context=wrapped_self)
        if adapted is None:
            return None
        return adapted.getSearchableText()
    _getTexts = ContextMethod(_getTexts)

    currentlySubscribed = False # Default subscription state

    def subscribe(wrapped_self, channel=None, update=True):
        if wrapped_self.currentlySubscribed:
            raise RuntimeError, "already subscribed; please unsubscribe first"
        channel = wrapped_self._getChannel(channel)
        channel.subscribe(wrapped_self, IRegistrationHubEvent)
        channel.subscribe(wrapped_self, IObjectModifiedHubEvent)
        if update:
            wrapped_self._update(channel.iterObjectRegistrations())
        wrapped_self.currentlySubscribed = True
    subscribe = ContextMethod(subscribe)

    def unsubscribe(wrapped_self, channel=None):
        if not wrapped_self.currentlySubscribed:
            raise RuntimeError, "not subscribed; please subscribe first"
        channel = wrapped_self._getChannel(channel)
        channel.unsubscribe(wrapped_self, IObjectModifiedHubEvent)
        channel.unsubscribe(wrapped_self, IRegistrationHubEvent)
        wrapped_self.currentlySubscribed = False
    unsubscribe = ContextMethod(unsubscribe)

    def isSubscribed(self):
        return self.currentlySubscribed

    def _getChannel(wrapped_self, channel):
        if channel is None:
            channel = getService(wrapped_self, "ObjectHub")
        return channel
    _getChannel = ContextMethod(_getChannel)

    def _update(wrapped_self, registrations):
        for location, hubid, wrapped_object in registrations:
            texts = wrapped_self._getTexts(wrapped_object)
            if texts is not None:
                wrapped_self.index_doc(hubid, texts)
    _update = ContextMethod(_update)


=== Added File Zope3/src/zope/app/index/text/processors.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""A query processor to the TextIndex that supports batching and ranking.

$Id: processors.py,v 1.1.2.1 2002/12/23 19:31:39 jim Exp $
"""

from zope.component import getAdapter

from zope.textindex.textindexinterfaces import IQuerying
from zope.app.interfaces.index.interfaces import \
    IBatchedResult, IRankedHubIdList, IBatchedTextIndexQuery
from zope.app.interfaces.services.query import \
    IQueryProcessor
from zope.app.index.text.queries import BatchedTextIndexQuery
from zope.app.index.queries import BatchedRankedResult

class IBatchedRankedProcessor(IQueryProcessor):
    # XXX until named adapters are there
    pass

class BatchedRankedProcessor:

    __implements__ = IBatchedRankedProcessor
    __used_for__ = IQuerying

    input_interface = IBatchedTextIndexQuery
    output_interface = (IRankedHubIdList, IBatchedResult)

    def __init__(self, textindex):
        self.__textindex = textindex

    def __call__(self, query):
        query = getAdapter(query, IBatchedTextIndexQuery)
        resultlist, totalresults = self.__textindex.query(query.textIndexQuery, \
                    query.startPosition, query.batchSize)
                    
        # XXX do we need some wrapping here?
        result = BatchedRankedResult(resultlist, query.startPosition, \
                    query.batchSize, totalresults)
                    
        return result


=== Added File Zope3/src/zope/app/index/text/queries.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""XXX short summary goes here.

$Id: queries.py,v 1.1.2.1 2002/12/23 19:31:39 jim Exp $
"""

from zope.app.interfaces.index.interfaces import \
    IBatchedTextIndexQuery, IBatchedResult, IRankedHubIdList

class BatchedTextIndexQuery:

    __implements__ = IBatchedTextIndexQuery

    def __init__(self, query, startposition, batchsize):
        
        self.textIndexQuery = query
        self.startPosition = startposition
        self.batchSize = batchsize