view zopeSolr.py @ 5:f695be8f4f34

Incomplete - # 77: Sources: Cloud https://it-dev.mpiwg-berlin.mpg.de/tracs/webpage/ticket/77 added facetFields_limit to search parameters
author dwinter
date Tue, 28 May 2013 11:13:40 +0200
parents 10733d367831
children c97b5cbcda52
line wrap: on
line source

# -*- coding: utf-8 -*-

#Verbindet Zope mit solr. Vorraussetzung ist das Paket sunburnt, @see http://opensource.timetric.com/sunburnt/


from OFS.SimpleItem import SimpleItem
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
import os.path
import sunburnt
from Globals import package_home
import httplib2
import urlparse
import urllib
import re
import xml.etree.ElementTree as ET
import json
   
#Worte die nicht in der Termliste angezeigt werden sollen #TODO: make this configurable

STOPLIST={'main_content':['forward','drucken','history','science','part','publications','projects',
                          'project','new','geschichte','institute','related','boltzmannstraße','14195'],
          
          'title':['bd','10','11','12','18','19','20','abt','ad','di','history','geschichte','science']
          }


PURLSERVER="http://md.mpiwg-berlin.mpg.de/purls/"

#TODO: only needed for getTermsAsJSON, solle irgendwie aus dem sunburnt kommen
SOLRSERVER="/select?q=%s:%s&wt=xml&tv=on&qt=tvrh&fl=%s&tv.tf=true"

def zptFile(self, path, orphaned=False):
    """returns a page template file from the product"""
    if orphaned:
        # unusual case
        pt=PageTemplateFile(os.path.join(package_home(globals()), path))
    else:
      
            pt=PageTemplateFile(os.path.join(package_home(globals()), path)).__of__(self)
    return pt



class ZopeSolr(SimpleItem):
    
    
    meta_type="ZopeSolr"
    
    manage_options= ({'label':'Main Config','action': 'changeMain'},) + SimpleItem.manage_options
    
    def __init__(self,id,title,solrURL):
        self.id=id 
        self.title=title
        self.solrURL=solrURL #URL einer solr instance bzw. einer collection, falls nicht die default collection benutzt werden soll
        
    
    #Verbinde mit der solt Instance
    
    def connect(self):
        self._v_solr=sunburnt.SolrInterface(url=self.solrURL)
    
    
    #erzeuge den link auf die fulltext display version des bildviewers
    def createFullTextLink(self,page,facetSerch,search):
        
        fqs=[]
        for key in facetSerch.keys():
            values = self.getList(facetSerch.get(key))
            for value in values:
                if value!="*":
                    fqs.append("%s=%s"%(key,urllib.quote(value)));
        
        ret="pf=%s"%page 
        ret+="&query=("
        
       
        
        ret+=" OR ".join(fqs)
        #if len(fqs)>0 and len(qs)>0 and qs[0]!="":
        if len(fqs)>0 and len(search)>0: #TODO das muss noch geandert werden, wenn das der normale suchstring nach feldern müssen alle "AND" in "OR" getauscht werden
            #da ja in der volltext-line  die Zeile gedunfen werden soll wenn eines der Worte drin ist, vorher wurd eventuell und über die ganee seite gesucht
            #die worte müssen aber nicht in einer zeile sein und werden jetzt nicht gefunden
            ret+=" OR "
        
        
        
        ret+="("+search+")"
        
        ret+=")&viewLayer=search"
        
        return ret
         
    #erzeuge einen Link, insbesonder für faceted suche
    #@param search: ist suchparameter, wird einfach als search=%s weitergereicht
    #@param facetSearch: bekommt einen hash (feldname, liste der suchworte) 
    
    def generateLink(self,search,facetSearch={},ranges={},dateranges={},args={}):
        ret="?search=%s"%search
       
        for facet in facetSearch.keys():
            
            searchTerms = facetSearch[facet]
            
            if isinstance(searchTerms, basestring):
                searchTerms=[searchTerms]
                
            for searchTerm in searchTerms:  
                try:
                    ret+="&%s_fc=%s"%(facet,searchTerm.encode('utf-8'))
                except:
                    ret+="&%s_fc=%s"%(facet,searchTerm)
                
        for key,values in ranges.items():
            
            if isinstance(values, basestring):
                values=[values]
            
            for value in values:
                ret+="&%s_rg=%s"%(key,value)    

        for key,values in dateranges.items():
            
            if isinstance(values, basestring):
                values=[values]
            
            for value in values:
                ret+="&%s_drg=%s"%(key,value)    

            
        for key,value in args.items():
            
            if isinstance(value, basestring):
                value=[value]
          
            for val in value:
                ret+="&%s=%s"%(key,val)
        return ret
        
    #hilfsmethode erzeuget immer eine liste von einträgen
    def getList(self,param):
        if isinstance(param, basestring):
            param=[param]
         
        return param
        
    #erzeugt analog zu den Ranges in Velocity für ein numerisches Feld die Suche in ranges  
    #@param field:ist der name des Feldes in dem in Ranges gesucht werden soll
    #@param begin anfang der ranges
    #@param end ende der Raanges
    #@param increment größe eines ranges
    #@param date wenn wahr, dann ist field ein Datumsfeld, begin, end,increment sind trotzdem zur Zeit, dan Integer für Jahre !!, d.h. zur Zeit sind nur JAhre als Schritte möglich
    # für alle andere paramter @see prepareSearch
    #@return gibt ein hash: mit range -> anzahl der Treffer in dem Ranage. e.g 1921-1950 -> 21
    
    def ranges(self,field,begin,end,increment,solrQuery="",facetFields=[],start=0,rows=10,facetSearch=None,sortFacets=True,date=False,storeFields=False):
      
        q=self.prepareSearch(solrQuery,facetFields,start,rows,facetSearch,sortFacets)
   
        res={} #speiceher abzahl
        
        fls={} #speichere felder
        
        #for fl in  storeFields: #initialisiere den hash
        #    fls[fl]={}
        
        if not getattr(self,'_v_solr_',None):
            self.connect()
        
        
    
        for x in range(begin,end,increment):
            query={}
            #query["%s__gt"%field]=x
            #TODO __gt scheint nicht zu funktionieren wird zu gte (???)
            
            if not date:
                query["%s__gte"%field]=int(x)+1
                           
                query["%s__lte"%field]=x+increment
                
                key="%s-%s"%(x,x+increment)
            
            else:
                year="%s-01-01T00:00:00Z"
                query["%s__gte"%field]=year%(int(x))
                           
                query["%s__lte"%field]=year%(x+increment)
                
                key="%s@%s"%(year%(int(x)),year%(x+increment))
                
            result = q.query(**query).highlight("text_german").execute()
            
            res[key]=result.result.numFound
            
            if storeFields:
                fls[key] = result.highlighting;
            
            
            
        if storeFields:
            return res,fls
        
        else:
            return res
            
        
    #prepareSearch erzeugt die Suchabfrage
    
    #solrQuery sucht im in schema.xml bzw. solrconfig.xml festgelegt generischen Feld, hierbei werden mit blanks getrennte eintrage in "AND" zerlegt.
    #TODO: erlaube auch suche nach phrasen mit ""
    #facetFields:Liste der Felder, nach denen facitiert werden sollen, wirdn in facet_by in solrburn uebersetzt.
    #start: Erste Eintrag für paginierung, anzahl der Treffer
    #rows: anzahl der Treffer
    #facetSearch: Hash mit Feldnamen: suchwort  oder Feldname: liste von Suchworten, hast wird direkt an query von solrburnt weitergegeben,
    #sortfacets: if true, dann werden die Ergebnisse der facetierten suche alphabetisch sortiert, ACHTUNG: das ist nicht gleich der Funktion in solr die Liste
    #direkt sortiert zurückzubekommen, hier werden die haufigsten Werte genommen (einstellt in solrconfig.xml) und dann nur diese sortiert!
    #ausserdem werden beim sortieren, die stopworte gefiltert!
    #
    #neben den direkten parameter koennen auch parameter fuer die facetierte Suche über FORM im REQUEST übergeben werden, diese Felder müssen dann
    #mit "_fc" enden.
    # gibt als ergebnis den folgenden Hash, so wie in http://opensource.timetric.com/sunburnt/queryingsolr.html#executing-queries-and-interpreting-the-response,
    # http://opensource.timetric.com/sunburnt/queryingsolr.html#highlighting
    # und http://opensource.timetric.com/sunburnt/queryingsolr.html#faceting
    #dokumentiert. Highlighting selbst wird in sorlconfig.xml konfiguriert.
    #return ein Queryobjet, zur eigentlichen Suche muss darauf noch execute ausgeführt werden.
 
    def prepareSearch(self,solrQuery,facetFields=[],start=0,rows=10,facetSearch=None,sortFacets=True,orSearch=None,facetFields_limit=None):
        "search solr"
      
       
        ranges={}
        ## deal with a form   
        if self.REQUEST:    
            constr =  self.REQUEST.form

            for field in constr.keys():
              
                #facetes
                if field.endswith("_fc"):
                    if facetSearch is None:
                        facetSearch={}
                    
                    vals = constr[field]
                    if not isinstance(vals,basestring):
                      
                        vals=[x.decode('utf-8') for x in vals]
                    else:
                        vals=vals.decode('utf-8')
                        
                        
                    facetSearch[field.replace("_fc",'')]=vals
                #ranges form a-b 
                
                
                if field.endswith("_or"):
                    if orSearch is None:
                        orSearch={}
                    
                    vals = constr[field]
                    if not isinstance(vals,basestring):
                       
                        vals=[x.decode('utf-8') for x in vals]
                    else:
                        vals=vals.decode('utf-8')
                        
                        
                    orSearch[field.replace("_or",'')]=vals
                #ranges form a-b 
                
                
               
                if field.endswith("_rg"):
                    
                    
                    splitted = "_".split(field)
                    if len(splitted)==2:
                        #ranges[field.replace("_rg","__gt")]=splitted[0]
                        #TODO __gt scheint nicht zu funktionieren wird zu gte (???)
                        ranges[field.replace("_rg","__gte")]=int(splitted[0])+1
                        ranges[field.replace("_rg","__lte")]=splitted[1]
                
                elif field.endswith("_drg"):
                        splitted = "@".split(field)
                        if len(splitted)==2:
                            #ranges[field.replace("_rg","__gt")]=splitted[0]
                            #TODO __gt scheint nicht zu funktionieren wird zu gte (???)
                            ranges[field.replace("_drg","__gte")]=splitted[0]
                            ranges[field.replace("_drg","__lte")]=splitted[1]
            
        
        #teste verbindung zu solr
        if not getattr(self,'_v_solr_',None):
            self.connect()
        
        
        
        solrQuery = solrQuery.decode('utf-8')
        
        #teile die suche nach " " daraus wird dann eine AND suche
        #TODO: sollte flexibler sein. insbesondere phrasen
        splitted= solrQuery.split(" ")
        
        res = self._v_solr
        
       
        
    
        res=res.query(splitted)
    
        if len(ranges.keys())>0:
            
        
            res=res.query(ranges)
         
    
        #ubergebe alle weiteren feld an die suche.
        if facetSearch:
            for key,vals in facetSearch.items():
              
                if key.endswith("_rg"): #range
                
                    if not isinstance(vals,basestring):
                      
                        vals=[x.decode('utf-8') for x in vals]
                    else:
                        vals=[vals.decode('utf-8')]
                    
                    for val in vals:
                      
                        splitted = val.split("-")

                        if len(splitted)==2:
                            #TODO __gt scheint nicht zu funktionieren wird zu gte (???)
                            facetSearch[key.replace("_rg","__gte")]=int(splitted[0])+1
                            facetSearch[key.replace("_rg","__lte")]=int(splitted[1])

                    del facetSearch[key] # loesche das urspuerngliche feld
                
                elif key.endswith("_drg"): #daterange    
                    
                    if not isinstance(vals,basestring):
                      
                        vals=[x.decode('utf-8') for x in vals]
                    else:
                        vals=[vals.decode('utf-8')]
                    
                    for val in vals:
                      
                        splitted = val.split("@")

                        if len(splitted)==2:
                            #TODO __gt scheint nicht zu funktionieren wird zu gte (???)
                            facetSearch[key.replace("_drg","__gte")]=splitted[0]
                            facetSearch[key.replace("_drg","__lte")]=splitted[1]

                    del facetSearch[key] # loesche das urspuerngliche feld
                    
                    
                else:
                    if not isinstance(vals, basestring):
                        val =  [x for x in vals if x!="*"]  #siehe oben
                    else:
                        val = [vals]
                    facetSearch[key]=val
            
            
            res=res.query(**facetSearch)
            
            
        #felder mit ODER-Suche
        if orSearch:
           
            for key,vals in orSearch.items():
                qr = None
                if not "*" in vals: #dann ohne einschränkung (key:*) sucht nur nach eintragen in denen etwas im feld steht, wir wollen aber alle
                    if  isinstance(vals, basestring):
                        vals = [vals]
                    
                    
                    for val in vals:
                        if not qr:
                            qr=self._v_solr.Q(**{key:val})
                            
                        else:
                            qr=qr|self._v_solr.Q(**{key:val})
                            
                        
                    res=res.query(qr)
                
                        
        
        #wenn facetField existieren dann rufe facetierung auf
        if len(facetFields)>0:
            #for facet in facetFields:
            
            #    res = res.facet_by(facet)
          
          
            if facetFields_limit:
                res = res.facet_by(facetFields,limit=facetFields_limit)
            else:
                res = res.facet_by(facetFields)
                
        #res=res.paginate(start=start, rows=rows).highlight("main_content")
        
        res=res.paginate(start=start, rows=rows)
       
       
        #only highlighting if not searhc for only "*" - avoid max clause error
        if solrQuery=="*":

            
            res=res.paginate(start=start, rows=rows)
        else:
            
            res=res.paginate(start=start, rows=rows).highlight(usePhraseHighlighter=True)
          
        return res
    
    
    def replaceParameter(self,paramsNeu,queryString,ignore=[]):
        params=urlparse.parse_qs(queryString);
     
        for key in paramsNeu.keys():
           
            params[key]=paramsNeu[key]
        
        for key in ignore:
            del params[key]

        retArray = []
        for x in params.keys():
            for y in self.getList(params[x]):
                retArray.append("%s=%s"%(x,urllib.quote(y)))
                                         
        print retArray
        return "&".join(retArray);
    #für die parameter @see prepareSearch
    #erzeugt eine Suchabfrage und führt diese aus.
    #return {"result":response.result, "hl":response.highlighting,"facetFields":facetedFields}   

    def search(self,solrQuery,facetFields=[],start=0,rows=10,facetSearch=None,sortFacets=True,orSearch=None,facetFields_limit=None):
      
        res=self.prepareSearch(solrQuery,facetFields,start,rows,facetSearch,sortFacets,orSearch=orSearch,facetFields_limit=facetFields_limit)
        response= res.execute()
            
            #speichere faceted fields
        if len(facetFields)>0:
            facetedFields=response.facet_counts.facet_fields
      
            if sortFacets:
                facetedFields=self.sortFacetedFields(facetedFields)
              
        else:
            facetedFields={}
        
            
        
            
        return {"result":response.result, "hl":response.highlighting,"facetFields":facetedFields}   
        
     
     
    #hilfsmethode zum sortieren über der ranges
    def sortRanges(self,ranges):
        x=list(ranges)
        x.sort()
        return x
        
    #sortiert die  Werte der FacetedFields 
    #(facetedFields ist ein hast mit feldname -> liste der (wert für das feld, anzahl der treffer für den wert)
    #ausserdem werden die werte gemäß des angegebenen Filter gefiltert.
    
    def sortFacetedFields(self, facetedFields,filter=STOPLIST):
        ret={}
        
        def cmpTuple(x,y):
            return cmp(x[0],y[0])
            
            
        for key in facetedFields.keys():
            ls = facetedFields[key]
            ls.sort(cmpTuple)
            ret[key]=ls
            
            if filter.get(key,None):
                ls2=[]
                for x in ls:
                    if x[0].encode('utf-8') not in filter[key]:
                        ls2.append(x)
                ret[key]=ls2
            
            
            ret[key]=[x for x in ret[key] if x[1]!=0]
        return ret
    
    def changeMain(self,solrURL=None,title=None,REQUEST=None,RESPONSE=None):
        """change main settings"""
        if solrURL:
            self.solrURL=solrURL
            self.title=title  
            self._v_solr=sunburnt.SolrInterface(url=solrURL)
            
            if RESPONSE is not None:
                RESPONSE.redirect('manage_main')
     
     
        else:
            pt=zptFile(self, 'zpt/ChangeZopeSolr.zpt')
            return pt()

    #sucht die je nach einsteillung in solrconfig.xml Werte mit den häufigsten Treffern oder alphabetisch sortiert zu einem 
    #bestimmten eintrag aus solr
    #gedacht ist die methoden für die Anwendung nach dem Harvesten einer Website, es wird daher davon ausgegangen, dass sie hinter der
    #idfield eine url steht. Wir es z.b. bei Nutch passiert. index.htm/index_html als Teil der url wird dabei unterdrückt (analog zu den harbest einstellunge für
    # nutch für zope webseiten.
    #@param @idfield is hierbei der Name des Feldes, das in solrschema als id defniert wurde
    #field der Feldname von dem die Treffer gesurcht wernde sollen
    #url die url des textes
    
    def getTermsAsJSON(self,idfield,field,url):
        """getTerms"""
        
        ret=[]
        h = httplib2.Http()
        url = url.replace("/index.html","").replace("/index_html","")
        if url[-1]=="/":
            url=url[0:-1]
            
        urlq=url.replace(":","\:")
        urlq=urlq.replace("/","\/")
        
        #q ="http://localhost:8983/solr/mpiwgweb/select?q=%s:%s&wt=xml&tv=on&qt=tvrh&fl=%s&tv.tf=true"%(idfield,urlq,field)
        q =self.solrURL+SOLRSERVER%(idfield,urlq,field)
      
        resp, content = h.request(q)
       
        root = ET.fromstring(content)
        #uri = "http://127.0.0.1:18080/www_neu/de/aktuelles/features/feature28"
        
        xpstr = ".//lst[@name='termVectors']/lst[@name='%s']/lst[@name='%s']/lst"%(url,field)
        
       
        tvs = root.findall(xpstr)

        for tv in tvs:
            wd = tv.attrib['name']
            for f in  tv.findall("./int[@name='tf']"):
                fre = f.text
        
            
            if int(fre)>2:
            
                ret.append('{"text":"%s","size":%s}'%(wd,fre))
        
        retStr="["+",".join(ret)+"]"
        
        return retStr    
    
    #tauscht im request die in neewparams angegeben parameter aus.
    def replaceParam(self, newparams):
        x = self.REQUEST.form.copy()
        
        for key,value in newparams.items():
            x[key]=value
        
       
        retls=[]
        for k,v in x.items():
            if not isinstance(v,basestring):
                for y in v:
                    retls.append((k,y))
            else:
                retls.append((k,v))
                
        return "?"+"&".join(["%s=%s"%(k,urllib.quote_plus(v,'/')) for (k, v) in retls])
        
    
    
  
    #ruft @set ranges aus, gibt das ergebnis als json zurück
    def getRangesAsJSON(self,field,begin,end,increment): 
        """ getRangesAsJSON"""
        res = self.ranges(field, int(begin), int(end), int(increment))
        return json.dumps(res)
     
        
    #return only the values of resultList whicht start with startLetter or if starLetterNonAscii all values which
    #start with an non ascii character
    def filter (self,resultList,startLetter=None,startLetterNonAscii=0):
        ls=[]
        if startLetter:
            startLetter=startLetter.lower()
            
            matchStr = "[\[\]'\"]*"+startLetter
            ls = [x for x in resultList if re.match(matchStr,x[0].lower())]
        
        if startLetterNonAscii ==1:
           
            ls = [x for x in resultList if not re.match("[\[\]'\"a-zA-Z].*",x[0])]
        return ls
        

        
def manage_addZopeSolrForm(self):
        """Form for external Links"""
        pt=zptFile(self, 'zpt/AddZopeSolr.zpt')
        return pt()


def manage_addZopeSolr(self,id,title,solrURL,RESPONSE=None):
    """Add an external Link"""

    newObj=ZopeSolr(id,title,solrURL)

    self._setObject(id,newObj)
    
  
    if RESPONSE is not None:
        RESPONSE.redirect('manage_main')