def doPagination( page, totalFound, numPerPage, isZeroIndexed = True ):
    """creates the pagination object used by various Helios views"""
    ret = []
    if isZeroIndexed:
        if page < 5:
            startPage = 0
        else:
            startPage = page - 5
        if totalFound % numPerPage:    
            lastPage = (totalFound // numPerPage) + 1
        else:
            lastPage = (totalFound // numPerPage)
        endPage = min( lastPage, page + 5)
        for i in range( startPage, endPage):
            ret.append( { 'selected' : (i == page) , 'start' : ( i * numPerPage) + 1, 'end' : min( totalFound, ( i + 1) * numPerPage), 'page' : i, 'pageLabel' : i+1 } )
        return {'pages' : ret , 'hasPrevious' : (page > 0), 'hasNext' : page < ( lastPage - 1 ), 'previousPage' : page-1, 'nextPage' : page+1 }
    else:    # 1 indexed (used by embedded HIP display)
        page = page + 1     # TODO: deal with this ugly 0be1 wart
        if page < 6:
            startPage = 1
        else:
            startPage = page - 5
        if totalFound % numPerPage:    
            lastPage = (totalFound // numPerPage) + 2
        else:
            lastPage = (totalFound // numPerPage) + 1
        endPage = min( lastPage, page + 5)
        logger.debug( "startPage is %s, endPage is %s, lastPage is %s, page is %s" % ( startPage, endPage, lastPage, page) )
        for i in range( startPage, endPage):
            ret.append( { 'selected' : (i == page) , 'start' : ( i * numPerPage) + 1, 'end' : min( totalFound, ( i + 1) * numPerPage), 'page' : i, 'pageLabel' : i } )
        return {'pages' : ret , 'hasPrevious' : (page > 1), 'hasNext' : page < ( lastPage - 1 ), 'previousPage' : page-1, 'nextPage' : page+1 }
def doPagination(page, totalFound, numPerPage, isZeroIndexed=True):
    """creates the pagination object used by various Helios views"""
    ret = []
    if isZeroIndexed:
        if page < 5:
            startPage = 0
        else:
            startPage = page - 5
        if totalFound % numPerPage:
            lastPage = (totalFound // numPerPage) + 1
        else:
            lastPage = (totalFound // numPerPage)
        endPage = min(lastPage, page + 5)
        for i in range(startPage, endPage):
            ret.append({
                'selected': (i == page),
                'start': (i * numPerPage) + 1,
                'end': min(totalFound, (i + 1) * numPerPage),
                'page': i,
                'pageLabel': i + 1
            })
        return {
            'pages': ret,
            'hasPrevious': (page > 0),
            'hasNext': page < (lastPage - 1),
            'previousPage': page - 1,
            'nextPage': page + 1
        }
    else:  # 1 indexed (used by embedded HIP display)
        page = page + 1  # TODO: deal with this ugly 0be1 wart
        if page < 6:
            startPage = 1
        else:
            startPage = page - 5
        if totalFound % numPerPage:
            lastPage = (totalFound // numPerPage) + 2
        else:
            lastPage = (totalFound // numPerPage) + 1
        endPage = min(lastPage, page + 5)
        logger.debug(
            "startPage is %s, endPage is %s, lastPage is %s, page is %s" %
            (startPage, endPage, lastPage, page))
        for i in range(startPage, endPage):
            ret.append({
                'selected': (i == page),
                'start': (i * numPerPage) + 1,
                'end': min(totalFound, (i + 1) * numPerPage),
                'page': i,
                'pageLabel': i
            })
        return {
            'pages': ret,
            'hasPrevious': (page > 1),
            'hasNext': page < (lastPage - 1),
            'previousPage': page - 1,
            'nextPage': page + 1
        }
def makeSearchString(q, index, limits, handler=None):   # sort, 
    """translates search parameters into Solr query syntax."""
    q = q.replace("_", " ")
    
    # strip out stopwords here... this is necessary due to some 
    # unexpected side-effects of the general keyword search
    # TODO: do NOT strip stopwords if a quoted search, eg. "the the" or "to be or not to be"
    qWords = q.split(" ")
    nonStopWords = []
    for wordOn in qWords:
        if wordOn not in STOPWORDS:    
            nonStopWords.append( wordOn )
    q = " ".join(nonStopWords)
    
    for orig,replacement in SEARCH_CHARACTER_REPLACEMENTS.iteritems():
        q = q.replace(orig,replacement)
    q = urllib.quote( q ).strip()
     
    if not handler or (handler is "standard"):
        if index in allFacetCodes:    
            # then treat as an exact match search; it is a facet, not free text entered by user
            ret = '%s:"%s"' % (index, q)
        else:    
            # then it is a search index, not a facet -- NOT exact match
            ret = '%s:%s' % (index, q)
    else:   
        # if you specify a handler you can't also specify an index...
        ret = '%s' % q
    for limitOn in limits:
        # csdebug: how are these forbidden characters making it this far?
        _limOn = urllib.unquote( limitOn )
        
        
        limitSplit = _limOn.split(":")
        logger.error("limitSplit is %s\n\n\n\n" % limitSplit) # csdebug
        index = limitSplit[0]    # todo error handling
        term = ":".join( limitSplit[1:] )
        for orig,replacement in SEARCH_CHARACTER_REPLACEMENTS.iteritems():
            term = term.replace(orig,replacement)
            logger.error("\n\nterm is now %s\n\n" % term)
        logger.error("\n\nxxterm is now %s\n\n" % term)
        term = term.replace("_", " ")
        #.replace('"', "") 
        term = term.strip()
        logger.error("\n\nyyterm is now %s\n\n" % term)
        # we are going to put exact quotes around the whole thing, so we don't want to double-quote
        term = urllib.quote(term)
        logger.error("\n\n!!!term is now %s\n\n" % term)
        ret = """%s AND %s:"%s\"""" % ( ret, index, term)
    # get rid of any double spaces.
    ret = ret.replace("  ", " ")
    ret = ret.replace(" ", "%20")
    logger.debug( "search string is %s" % ret )  
    return ret.strip()
def parseLocationIPAddressRanges(ranges):
    addressLocationMap = {}
    for k, v in ranges.iteritems():
        if k in LOCATION_NAMES.keys():
            if type(v) in types.StringTypes:
                # individual IP address - - just put in key/value pair
                asLong = IPAddressToInt(v)
                addressLocationMap[asLong] = k
            elif type(v) == types.TupleType:
                logger.debug("on %s" % v)
                startAsLong, endAsLong = IPAddressToInt(v[0]), IPAddressToInt(
                    v[1])
                if startAsLong < endAsLong:
                    logger.debug("adding %s addresses..." %
                                 (endAsLong - startAsLong))
                    for i in range(startAsLong, endAsLong + 1):  # NB fencepost
                        addressLocationMap[i] = k
            elif type(v) == types.ListType:
                for x in v:
                    if type(x) == types.TupleType:
                        logger.debug("on\n%s" % v)
                        startAsLong, endAsLong = IPAddressToInt(
                            x[0]), IPAddressToInt(x[1])
                        if startAsLong < endAsLong:
                            logger.debug("adding %s addresses" %
                                         (endAsLong - startAsLong))
                            for i in range(startAsLong,
                                           endAsLong + 1):  # NB fencepost
                                addressLocationMap[i] = k
    return addressLocationMap
def parseLocationIPAddressRanges( ranges ):
    addressLocationMap = {}
    for k,v in ranges.iteritems():
        if k in LOCATION_NAMES.keys():
            if type(v) in types.StringTypes:
                # individual IP address - - just put in key/value pair
                asLong = IPAddressToInt( v )
                addressLocationMap[asLong] = k
            elif type(v) == types.TupleType:
                logger.debug( "on %s" %  v )
                startAsLong, endAsLong =  IPAddressToInt( v[0] ), IPAddressToInt( v[1] )
                if startAsLong < endAsLong:
                    logger.debug( "adding %s addresses..." % ( endAsLong -startAsLong) )
                    for i in range(startAsLong, endAsLong+1): # NB fencepost
                        addressLocationMap[i] = k
            elif type(v) == types.ListType:
                for x in v:
                    if type(x) == types.TupleType:
                        logger.debug(  "on\n%s" % v )
                        startAsLong, endAsLong = IPAddressToInt(x[0]), IPAddressToInt(x[1])
                        if startAsLong < endAsLong:
                            logger.debug( "adding %s addresses" % ( endAsLong -startAsLong) )
                            for i in range(startAsLong, endAsLong+1): # NB fencepost
                                addressLocationMap[i] = k
    return addressLocationMap                                
def spellCheck( phraseToCheck ):
    """takes the phrase to check and returns a list of potential suggestions."""
    ret = []
    if not config.USE_YAHOO_SPELLING_WEB_SERVICE:
        pass
    else:        
        # TODO: will it handle utf8?
        
        # check cache first.
        cacheKey = "spellcheck~~%s~~" % phraseToCheck
        
        suggestionsFromCache = cache.get( cacheKey )
        if not suggestionsFromCache:
            query = urllib.quote( phraseToCheck )
            
            urlToGet = config.YAHOO_WEB_SERVICE_URL % dict( YAHOO_APPID = config.YAHOO_APPID, query = query )
            logger.debug("fetching URL %s" % urlToGet )
            data = urllib.urlopen( urlToGet ).read()
            try:
                respObject = simplejson.loads( data )
                # note we do this to prevent caching bad data
                cache.set( cacheKey, data, config.YAHOO_SPELLCHECK_CACHE_TIME)
                # TODO: figure out if it will *ever* return multiple suggestions...
            except:
                logger.error( "exception doing spellCheck service" )
        else:
            logger.debug( "got spelling suggestion from cache") 
            respObject = simplejson.loads( suggestionsFromCache )
        #print "respObject is %s, type is %s" % (respObject, type(respObject) )    # csdebug
        if respObject.has_key("ResultSet") and type(respObject['ResultSet']) == types.DictType and respObject['ResultSet'].has_key("Result"):
            ret.append( respObject['ResultSet']['Result'] )
    logger.debug("returning %s" % ret)
    return ret

                
    #return ['pants', 'pantaloons']    # csdebug
    
def processFacets( facetCodes, facetCounts, q, limits, doExtendedTerms = True, doColors=False ):     
    # do postprocessing on facet data --put them in a format that is easier to work
    # with in the templating language, split them up into the basic &extended
    # for toggling, and translate codes where necessary
    logger.debug( "limits is %s" % limits )   
    _facets = []
    _bestBets = []  # best bets are facet terms which contain the search term
    for facetCodeOn in facetCodes:
        facetOn = {'type' : facetCodeOn['type'], 'terms' : [], 'extended_terms' : [], 'code' : facetCodeOn['code'], 
                   'name' : facetCodeOn['name'], 'has_more' : False }
        # check to see if this facet is a cloudy one.  (This value may get set to False later on if there are not enough
        # facets.)
        if facetCodeOn['code'] in allRefineFacetCodes:
            facetOn['show_cloud_link'] = True
        else:
            facetOn['show_cloud_link'] = False
        # colors are used by the cloud view to differentiate facet types.
        if doColors:
            _color = allFacetsByCode[ facetCodeOn['code'] ].get( 'refine_color_class' , 'refine-default')
            facetOn['color'] = _color
        if facetCounts['facet_fields'].has_key( facetCodeOn['code'] ):
            facetCountList = facetCounts['facet_fields'][facetCodeOn['code'] ]    
            # this is a list of alternating facets and counts
            terms, counts = facetCountList[::2], facetCountList[1::2]
            _facetOnTerms = []
            if len(terms) < MIN_TERMS_TO_SHOW_CLOUD_LINK:
                # not enough facets to justify offering a link to the cloud.
                facetOn['show_cloud_link'] = False
            
            for i in range(len(terms)):
                # if there is a translate function associated with this facet, translate the code here.
                if translateFunctionRefs.has_key( facetCodeOn['code'] ):
                    _label = translateFunctionRefs[ facetCodeOn['code'] ]( terms[i] ).strip()
                else:
                    _label = terms[i]
                _websafeTerm = terms[i]
                #_websafeTerm = _websafeTerm.replace("'", "%27").replace("&", "%26" ).replace(" ", "_").replace('"', "%22")
                for orig,replacement in FACET_TERM_REPLACEMENTS.iteritems():
                    _websafeTerm = _websafeTerm.replace(orig,replacement)
                
                
                # TODO: see if this facet is already one of our limits; if so, remove it.
                alreadyApplied = False
                
                for limitOn in limits:
                    # using repr avoids unicode encoding errors.
                    _limitTerm = repr( u"%s:%s" % ( facetCodeOn['code'], _websafeTerm) )
                    _limitOn = repr(limitOn)    
                    if _limitTerm == _limitOn:
                        alreadyApplied = True
                if not alreadyApplied:
                    _facetOnTerms.append( dict( term=_websafeTerm, count=counts[i], label=_label) )

                # check to see if this is a "best bet"
                # nb it's not a best bet if the suggestion is already one of your limits!
                # 'in <string>' comparison requires string as left operand; can't handle Unicode
                # TODO: allow it to handle Unicode here without dying on "in"
                qTerms = [x.strip() for x in q.replace(",", " ").lower().split()]
                if (type(_label) is not types.UnicodeType):
                    numTermsMatched = 0
                    for termOn in qTerms:    
                        # split apart all words and see if each one matches (so order doesn't matter)
                        if _label.lower().find( termOn ) > -1:
                            numTermsMatched += 1
                    if numTermsMatched == len(qTerms): # all words matched 
                        isBestBet = True
                        _facetAsLimit = '%s:"%s"'  %( facetCodeOn['code'] , terms[i] )
                        for limitOn in limits:  
                        # check vs. each limit and see if they're a match -- don't want to suggest a limit already applied
                            if _facetAsLimit in limitOn:
                                isBestBet = False
                        if isBestBet:
                            _bestBets.append( dict( facetTerm=_websafeTerm, facetLabel = _label, facetIndexCode = facetCodeOn['code'], facetIndexLabel = facetCodeOn['name'] ) )           
            if doExtendedTerms and (len( _facetOnTerms ) > MAX_FACET_TERMS_BASIC):
                facetOn['has_more'] = True
                facetOn['terms'] , facetOn['extended_terms'] = _facetOnTerms[:MAX_FACET_TERMS_BASIC], _facetOnTerms[MAX_FACET_TERMS_BASIC: ]
            else:
                facetOn['terms'] = _facetOnTerms 
        
        _facets.append( facetOn )
    return _facets, _bestBets