moved query logic to query module; use standard text index instead of TextIndexNG
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1344 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
2d25015202
commit
92f6a06e01
9 changed files with 191 additions and 120 deletions
|
@ -317,8 +317,8 @@ Index attributes adapter
|
||||||
|
|
||||||
>>> from loops.resource import IndexAttributes
|
>>> from loops.resource import IndexAttributes
|
||||||
>>> idx = IndexAttributes(doc1)
|
>>> idx = IndexAttributes(doc1)
|
||||||
>>> idx.text()
|
>>> idx.text() is None
|
||||||
u'doc1 Zope Info'
|
True
|
||||||
|
|
||||||
>>> idx.title()
|
>>> idx.title()
|
||||||
u'doc1 Zope Info'
|
u'doc1 Zope Info'
|
||||||
|
|
|
@ -539,6 +539,15 @@
|
||||||
permission="zope.View"
|
permission="zope.View"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
name="listchildren"
|
||||||
|
for="loops.interfaces.INode
|
||||||
|
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||||
|
provides="zope.interface.Interface"
|
||||||
|
factory="loops.browser.node.ListChildren"
|
||||||
|
permission="zope.View"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- inner HTML views -->
|
<!-- inner HTML views -->
|
||||||
|
|
||||||
<page
|
<page
|
||||||
|
|
|
@ -72,10 +72,14 @@ class NodeView(BaseView):
|
||||||
cm.register('portlet_left', 'navigation', title='Navigation',
|
cm.register('portlet_left', 'navigation', title='Navigation',
|
||||||
subMacro=self.template.macros['menu'])
|
subMacro=self.template.macros['menu'])
|
||||||
if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
|
if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
|
||||||
cm.register('portlet_right', 'actions', title='Actions',
|
cm.register('portlet_right', 'clipboard', title='Clipboard',
|
||||||
subMacro=self.template.macros['actions'])
|
subMacro=self.template.macros['clipboard'])
|
||||||
cm.register('portlet_right', 'personal', title='Personal Items',
|
# this belongs to loops.organize; how to register portlets
|
||||||
subMacro=self.template.macros['personal'])
|
# from sub- (other) packages?
|
||||||
|
# see controller / configurator: use multiple configurators;
|
||||||
|
# register additional configurators (adapters) from within package.
|
||||||
|
cm.register('portlet_right', 'worklist', title='Worklist',
|
||||||
|
subMacro=self.template.macros['worklist'])
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def view(self):
|
def view(self):
|
||||||
|
@ -316,26 +320,27 @@ class InlineEdit(NodeView):
|
||||||
|
|
||||||
# special (named) views for nodes
|
# special (named) views for nodes
|
||||||
|
|
||||||
class ListPages(NodeView):
|
class SpecialNodeView(NodeView):
|
||||||
|
|
||||||
|
macroName = None # to be provided by subclass
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def macro(self):
|
def macro(self):
|
||||||
return self.template.macros['listpages']
|
return self.template.macros[self.macroName]
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def view(self):
|
def view(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class ListResources(NodeView):
|
class ListPages(SpecialNodeView):
|
||||||
|
macroName = 'listpages'
|
||||||
|
|
||||||
@Lazy
|
class ListResources(SpecialNodeView):
|
||||||
def macro(self):
|
macroName = 'listresources'
|
||||||
return self.template.macros['listresources']
|
|
||||||
|
|
||||||
@Lazy
|
class ListChildren(SpecialNodeView):
|
||||||
def view(self):
|
macroName = 'listchildren'
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigureView(NodeView):
|
class ConfigureView(NodeView):
|
||||||
|
|
|
@ -144,6 +144,28 @@
|
||||||
</metal:resources>
|
</metal:resources>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:children define-macro="listchildren"
|
||||||
|
tal:define="target nocall:item/target">
|
||||||
|
<div class="content-1"
|
||||||
|
tal:content="structure item/body"
|
||||||
|
tal:attributes="ondblclick python:
|
||||||
|
target and target.openEditWindow('configure.html')
|
||||||
|
or item.openEditWindow();
|
||||||
|
id string:${view/itemNum}.body;">
|
||||||
|
Listing
|
||||||
|
</div><br />
|
||||||
|
<div tal:attributes="ondblclick python: target.openEditWindow('configure.html')"
|
||||||
|
tal:define="item nocall:item/target"
|
||||||
|
tal:condition="nocall:target">
|
||||||
|
<div tal:repeat="related item/children">
|
||||||
|
<a href="#"
|
||||||
|
tal:attributes="href string:${view/url}/.target${related/uniqueId}"
|
||||||
|
tal:content="related/title">Resource Title</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</metal:children>
|
||||||
|
|
||||||
|
|
||||||
<!-- menu -->
|
<!-- menu -->
|
||||||
|
|
||||||
<metal:menu define-macro="menu"
|
<metal:menu define-macro="menu"
|
||||||
|
@ -171,13 +193,13 @@
|
||||||
|
|
||||||
<!-- portlets -->
|
<!-- portlets -->
|
||||||
|
|
||||||
<metal:actions define-macro="actions">
|
<metal:actions define-macro="clipboard">
|
||||||
<div class="menu-2">Add Resource</div>
|
<div class="menu-2">loops Development</div>
|
||||||
</metal:actions>
|
</metal:actions>
|
||||||
|
|
||||||
|
|
||||||
<metal:actions define-macro="personal">
|
<metal:actions define-macro="worklist">
|
||||||
<div class="menu-2">Clipboard</div>
|
<div class="menu-2">Create Resource...</div>
|
||||||
</metal:actions>
|
</metal:actions>
|
||||||
|
|
||||||
|
|
||||||
|
@ -193,10 +215,6 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- dojoType="Editor"
|
|
||||||
items="formatblock;|;insertunorderedlist;insertorderedlist;|;bold;italic;|;createLink;"
|
|
||||||
-->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- edit and other links -->
|
<!-- edit and other links -->
|
||||||
|
|
||||||
|
|
|
@ -291,9 +291,9 @@ class IndexAttributes(object):
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
# obsolete, use TextIndexNG (with indexableContent() method) instead
|
|
||||||
def text(self):
|
def text(self):
|
||||||
context = self.context
|
context = self.context
|
||||||
|
# TODO: include attributes provided by concept type
|
||||||
return ' '.join((zapi.getName(context), context.title,))
|
return ' '.join((zapi.getName(context), context.title,))
|
||||||
|
|
||||||
def title(self):
|
def title(self):
|
||||||
|
|
100
query.py
100
query.py
|
@ -22,9 +22,11 @@ Query management stuff.
|
||||||
$Id$
|
$Id$
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from zope import schema, component
|
||||||
from zope.interface import Interface, Attribute, implements
|
from zope.interface import Interface, Attribute, implements
|
||||||
|
from zope.app import traversing
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope import schema
|
from zope.app.catalog.interfaces import ICatalog
|
||||||
|
|
||||||
from loops.interfaces import IConcept
|
from loops.interfaces import IConcept
|
||||||
from loops.common import AdapterBase
|
from loops.common import AdapterBase
|
||||||
|
@ -41,6 +43,102 @@ class IQuery(Interface):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class BaseQuery(object):
|
||||||
|
|
||||||
|
implements(IQuery)
|
||||||
|
|
||||||
|
def __init__(self, context):
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def catalog(self):
|
||||||
|
return component.getUtility(ICatalog)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def loopsRoot(self):
|
||||||
|
return self.context.context.getLoopsRoot()
|
||||||
|
|
||||||
|
def queryConcepts(self, title=None, type=None):
|
||||||
|
if type.endswith('*'):
|
||||||
|
start = type[:-1]
|
||||||
|
end = start + '\x7f'
|
||||||
|
else:
|
||||||
|
start = end = type
|
||||||
|
cat = self.catalog
|
||||||
|
if title:
|
||||||
|
result = cat.searchResults(loops_type=(start, end), loops_title=title)
|
||||||
|
else:
|
||||||
|
result = cat.searchResults(loops_type=(start, end))
|
||||||
|
result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def queryConceptsWithChildren(self, title=None, type=None):
|
||||||
|
if title: # there are a few characters that the index doesn't like
|
||||||
|
title = title.replace('(', ' ').replace(')', ' ')
|
||||||
|
if not title and (type is None or '*' in type):
|
||||||
|
return None
|
||||||
|
result = set()
|
||||||
|
queue = list(self.queryConcepts(title=title, type=type))
|
||||||
|
concepts = []
|
||||||
|
while queue:
|
||||||
|
c = queue.pop(0)
|
||||||
|
concepts.append(c)
|
||||||
|
for child in c.getChildren():
|
||||||
|
# TODO: check for tree level, use relevance factors, ...
|
||||||
|
if child not in queue and child not in concepts:
|
||||||
|
queue.append(child)
|
||||||
|
for c in concepts:
|
||||||
|
result.add(c)
|
||||||
|
result.update(c.getResources())
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class FullQuery(BaseQuery):
|
||||||
|
|
||||||
|
def query(self, text=None, type=None, useTitle=True, useFull=False,
|
||||||
|
conceptTitle=None, conceptType=None):
|
||||||
|
result = set()
|
||||||
|
rc = self.queryConceptsWithChildren(title=conceptTitle, type=conceptType)
|
||||||
|
if not rc and not text and '*' in type: # there should be some sort of selection...
|
||||||
|
return result
|
||||||
|
if text or type != 'loops:*': # TODO: this may be highly inefficient!
|
||||||
|
cat = self.catalog
|
||||||
|
if useFull and text:
|
||||||
|
criteria = {'loops_text': text}
|
||||||
|
r1 = set(cat.searchResults(**criteria))
|
||||||
|
else:
|
||||||
|
r1 = set()
|
||||||
|
if type.endswith('*'):
|
||||||
|
start = type[:-1]
|
||||||
|
end = start + '\x7f'
|
||||||
|
else:
|
||||||
|
start = end = type
|
||||||
|
criteria = {'loops_type': (start, end),}
|
||||||
|
if useTitle and text:
|
||||||
|
criteria['loops_title'] = text
|
||||||
|
r2 = set(cat.searchResults(**criteria))
|
||||||
|
result = r1.union(r2)
|
||||||
|
if rc is not None:
|
||||||
|
if result:
|
||||||
|
result = result.intersection(rc)
|
||||||
|
else:
|
||||||
|
result = rc
|
||||||
|
result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class ConceptQuery(BaseQuery):
|
||||||
|
""" Find concepts of type `type` whose title starts with `title`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def query(self, title=None, type=None):
|
||||||
|
if title and not title.endswith('*'):
|
||||||
|
title += '*'
|
||||||
|
return self.queryConcepts(title=title, type=type)
|
||||||
|
|
||||||
|
|
||||||
|
# QueryConcept: concept objects that allow querying the database.
|
||||||
|
|
||||||
class IQueryConcept(Interface):
|
class IQueryConcept(Interface):
|
||||||
""" The schema for the query type.
|
""" The schema for the query type.
|
||||||
"""
|
"""
|
||||||
|
|
10
resource.py
10
resource.py
|
@ -234,10 +234,14 @@ class IndexAttributes(object):
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
# obsolete, use TextIndexNG (with indexableContent() method) instead
|
|
||||||
def text(self):
|
def text(self):
|
||||||
context = self.context
|
context = self.context
|
||||||
return ' '.join((zapi.getName(context), context.title, context.data)).strip()
|
if not context.contentType.startswith('text'):
|
||||||
|
return None
|
||||||
|
data = context.data
|
||||||
|
# TODO: transform to plain text
|
||||||
|
#return ' '.join((zapi.getName(context), context.title, data)).strip()
|
||||||
|
return data
|
||||||
|
|
||||||
def title(self):
|
def title(self):
|
||||||
context = self.context
|
context = self.context
|
||||||
|
@ -245,6 +249,8 @@ class IndexAttributes(object):
|
||||||
|
|
||||||
|
|
||||||
class IndexableResource(object):
|
class IndexableResource(object):
|
||||||
|
""" Used for TextIndexNG.
|
||||||
|
"""
|
||||||
|
|
||||||
implements(IIndexableContent)
|
implements(IIndexableContent)
|
||||||
adapts(IResource)
|
adapts(IResource)
|
||||||
|
|
|
@ -152,8 +152,8 @@ purposes fairly primitive) catalog and a resource we can search for:
|
||||||
Search via related concepts
|
Search via related concepts
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
We first have to prepare some test concepts (topics); we also assign our test
|
We first have to prepare some test concepts (topics); we also assign our test
|
||||||
resource (rplone) from above to one of the topics:
|
resource (rplone) from above to one of the topics:
|
||||||
|
|
||||||
>>> czope = concepts['zope'] = Concept('Zope')
|
>>> czope = concepts['zope'] = Concept('Zope')
|
||||||
>>> czope2 = concepts['zope2'] = Concept('Zope 2')
|
>>> czope2 = concepts['zope2'] = Concept('Zope 2')
|
||||||
|
@ -166,11 +166,11 @@ Search via related concepts
|
||||||
>>> czope2.assignChild(cplone)
|
>>> czope2.assignChild(cplone)
|
||||||
>>> rplone.assignConcept(cplone)
|
>>> rplone.assignConcept(cplone)
|
||||||
|
|
||||||
Now we can fill our search form and execute the query; note that all concepts
|
Now we can fill our search form and execute the query; note that all concepts
|
||||||
found are listed, plus all their children and all resources associated
|
found are listed, plus all their children and all resources associated
|
||||||
with them:
|
with them:
|
||||||
|
|
||||||
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': u'zope'}
|
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text_selected': u'zope'}
|
||||||
>>> request = TestRequest(form=form)
|
>>> request = TestRequest(form=form)
|
||||||
>>> resultsView = SearchResults(page, request)
|
>>> resultsView = SearchResults(page, request)
|
||||||
>>> results = list(resultsView.results)
|
>>> results = list(resultsView.results)
|
||||||
|
@ -179,7 +179,7 @@ Search via related concepts
|
||||||
>>> results[0].context.__name__
|
>>> results[0].context.__name__
|
||||||
u'plone'
|
u'plone'
|
||||||
|
|
||||||
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': u'zope3'}
|
>>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text_selected': u'zope3'}
|
||||||
>>> request = TestRequest(form=form)
|
>>> request = TestRequest(form=form)
|
||||||
>>> resultsView = SearchResults(page, request)
|
>>> resultsView = SearchResults(page, request)
|
||||||
>>> results = list(resultsView.results)
|
>>> results = list(resultsView.results)
|
||||||
|
@ -187,3 +187,11 @@ Search via related concepts
|
||||||
1
|
1
|
||||||
>>> results[0].context.__name__
|
>>> results[0].context.__name__
|
||||||
u'zope3'
|
u'zope3'
|
||||||
|
|
||||||
|
To support easy entry of concepts to search for we can preselect the available
|
||||||
|
concepts (optionally restricted to a certain type) by entering text parts
|
||||||
|
of the concepts' titles:
|
||||||
|
|
||||||
|
TODO...
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,8 @@ loops.search package.
|
||||||
$Id$
|
$Id$
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.app import zapi
|
|
||||||
from zope import interface, component
|
from zope import interface, component
|
||||||
from zope.app.catalog.interfaces import ICatalog
|
from zope.app import traversing
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope.formlib.namedtemplate import NamedTemplate, NamedTemplateImplementation
|
from zope.formlib.namedtemplate import NamedTemplate, NamedTemplateImplementation
|
||||||
|
@ -34,6 +33,7 @@ from zope.i18nmessageid import MessageFactory
|
||||||
from cybertools.ajax import innerHtml
|
from cybertools.ajax import innerHtml
|
||||||
from cybertools.typology.interfaces import ITypeManager
|
from cybertools.typology.interfaces import ITypeManager
|
||||||
from loops.browser.common import BaseView
|
from loops.browser.common import BaseView
|
||||||
|
from loops.query import ConceptQuery, FullQuery
|
||||||
from loops import util
|
from loops import util
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
|
||||||
|
@ -51,10 +51,6 @@ class Search(BaseView):
|
||||||
def macro(self):
|
def macro(self):
|
||||||
return template.macros['search']
|
return template.macros['search']
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def catalog(self):
|
|
||||||
return component.getUtility(ICatalog)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rowNum(self):
|
def rowNum(self):
|
||||||
""" Return the rowNum to be used for identifying the current search
|
""" Return the rowNum to be used for identifying the current search
|
||||||
|
@ -84,19 +80,11 @@ class Search(BaseView):
|
||||||
"""
|
"""
|
||||||
request = self.request
|
request = self.request
|
||||||
request.response.setHeader('Content-Type', 'text/plain; charset=UTF-8')
|
request.response.setHeader('Content-Type', 'text/plain; charset=UTF-8')
|
||||||
text = request.get('searchString', '').replace('(', ' ').replace(')', ' ')
|
title = request.get('searchString', '').replace('(', ' ').replace(')', ' ')
|
||||||
type = request.get('searchType') or 'loops:concept:*'
|
type = request.get('searchType') or 'loops:concept:*'
|
||||||
if type.endswith('*'):
|
result = ConceptQuery(self).query(title=title, type=type)
|
||||||
start = type[:-1]
|
# simple way to provide JSON format:
|
||||||
end = start + '\x7f'
|
return str(sorted([[`o.title`[2:-1], `traversing.api.getName(o)`[2:-1]]
|
||||||
else:
|
|
||||||
start = end = type
|
|
||||||
cat = self.catalog
|
|
||||||
if text:
|
|
||||||
result = cat.searchResults(loops_type=(start, end), loops_text=text+'*')
|
|
||||||
else:
|
|
||||||
result = cat.searchResults(loops_type=(start, end))
|
|
||||||
return str(sorted([[`o.title`[2:-1], `zapi.getName(o)`[2:-1]]
|
|
||||||
for o in result])).replace('\\\\x', '\\x')
|
for o in result])).replace('\\\\x', '\\x')
|
||||||
|
|
||||||
def submitReplacing(self, targetId, formId, view):
|
def submitReplacing(self, targetId, formId, view):
|
||||||
|
@ -116,79 +104,18 @@ class SearchResults(BaseView):
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
return innerHtml(self)
|
return innerHtml(self)
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def catalog(self):
|
|
||||||
return component.getUtility(ICatalog)
|
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def results(self):
|
def results(self):
|
||||||
result = set()
|
|
||||||
request = self.request
|
request = self.request
|
||||||
r3 = self.queryConcepts()
|
|
||||||
type = request.get('search.1.text', 'loops:*')
|
type = request.get('search.1.text', 'loops:*')
|
||||||
text = request.get('search.2.text')
|
text = request.get('search.2.text')
|
||||||
if not r3 and not text and '*' in type: # there should be some sort of selection...
|
useTitle = request.get('search.2.title')
|
||||||
return result
|
useFull = request.get('search.2.full')
|
||||||
#if r3 and type != 'loops:*':
|
conceptType = request.get('search.3.type', 'loops:concept:*')
|
||||||
# typeName = type.split(':')[-1]
|
conceptTitle = request.get('search.3.text_selected')
|
||||||
# r3 = set(o for o in r3 if self.isType(o, typeName))
|
result = FullQuery(self).query(text=text, type=type,
|
||||||
#if text or not '*' in loops:
|
useTitle=useTitle, useFull=useFull,
|
||||||
if text or type != 'loops:*': # TODO: this may be highly inefficient! see above
|
conceptTitle=conceptTitle, conceptType= conceptType)
|
||||||
useTitle = request.get('search.2.title')
|
|
||||||
useFull = request.get('search.2.full')
|
|
||||||
r1 = set()
|
|
||||||
cat = self.catalog
|
|
||||||
if useFull and text and not type.startswith('loops:concept:'):
|
|
||||||
criteria = {'loops_resource_textng': {'query': text},}
|
|
||||||
r1 = set(cat.searchResults(**criteria))
|
|
||||||
if type.endswith('*'):
|
|
||||||
start = type[:-1]
|
|
||||||
end = start + '\x7f'
|
|
||||||
else:
|
|
||||||
start = end = type
|
|
||||||
criteria = {'loops_type': (start, end),}
|
|
||||||
if useTitle and text:
|
|
||||||
criteria['loops_title'] = text
|
|
||||||
r2 = set(cat.searchResults(**criteria))
|
|
||||||
result = r1.union(r2)
|
|
||||||
result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot)
|
|
||||||
if r3 is not None:
|
|
||||||
if result:
|
|
||||||
result = result.intersection(r3)
|
|
||||||
else:
|
|
||||||
result = r3
|
|
||||||
result = sorted(result, key=lambda x: x.title.lower())
|
result = sorted(result, key=lambda x: x.title.lower())
|
||||||
return self.viewIterator(result)
|
return self.viewIterator(result)
|
||||||
|
|
||||||
def queryConcepts(self):
|
|
||||||
result = set()
|
|
||||||
cat = self.catalog
|
|
||||||
request = self.request
|
|
||||||
type = request.get('search.3.type', 'loops:concept:*')
|
|
||||||
text = request.get('search.3.text')
|
|
||||||
if text: # there are a few characters that the index doesn't like
|
|
||||||
text = text.replace('(', ' ').replace(')', ' ')
|
|
||||||
if not text and '*' in type:
|
|
||||||
return None
|
|
||||||
if type.endswith('*'):
|
|
||||||
start = type[:-1]
|
|
||||||
end = start + '\x7f'
|
|
||||||
else:
|
|
||||||
start = end = type
|
|
||||||
criteria = {'loops_type': (start, end),}
|
|
||||||
if text:
|
|
||||||
criteria['loops_title'] = text
|
|
||||||
queue = list(cat.searchResults(**criteria))
|
|
||||||
concepts = []
|
|
||||||
while queue:
|
|
||||||
c = queue.pop(0)
|
|
||||||
concepts.append(c)
|
|
||||||
for child in c.getChildren():
|
|
||||||
# TODO: check for tree level, use relevance factors, ...
|
|
||||||
if child not in queue and child not in concepts:
|
|
||||||
queue.append(child)
|
|
||||||
for c in concepts:
|
|
||||||
result.add(c)
|
|
||||||
result.update(c.getResources())
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue