django-haystack全文檢索

django-haystack全文檢索

 

前幾天要用Django-haystack來實現搜索功能,網上一搜中文資源少之又少,雖然說有官方文檔,但相信對於咱們這些英語差的同窗來講要看懂真的是一件難事。特別是關於高級部分,特意找了個英語專業的來翻譯,也沒能看出個名堂來,專業性實在是太強了,致使徹底看不懂。。。css

可是,對於一些小站點的開發來講,下面我要給你們講的徹底足夠用了,只不過有時候確實麻煩點。好了,言歸正傳。爲了節約時間,簡單設置部分從網上找了一篇博客,可是這邊文章沒有解釋配置的做用,而我會給大家詳細解釋並拓展。html

轉載部分來自:點擊打開連接python

 

一:使用的工具

  • haystack是django的開源搜索框架,該框架支持Solr,Elasticsearch,Whoosh*Xapian*搜索引擎,不用更改代碼,直接切換引擎,減小代碼量。
  • 搜索引擎使用Whoosh,這是一個由純Python實現的全文搜索引擎,沒有二進制文件等,比較小巧,配置比較簡單,固然性能天然略低。
  • 中文分詞Jieba,因爲Whoosh自帶的是英文分詞,對中文的分詞支持不是太好,故用jieba替換whoosh的分詞組件。
  • 其餘:Python 2.7 or 3.4.4, Django 1.8.3或者以上,Debian 4.2.6_3

二:配置說明

 

如今假設咱們的項目叫作Project,有一個myapp的app,簡略的目錄結構以下。git

  • Project
    • Project
      • settings.py
    • blog
      • models.py
models.py的內容假設以下:

 

[python]  view plain  copy
 
  1. from django.db import models  
  2. from django.contrib.auth.models import User  
  3.   
  4.   
  5. class Note(models.Model):  
  6.     user = models.ForeignKey(User)  
  7.     pub_date = models.DateTimeField()  
  8.     title = models.CharField(max_length=200)  
  9.     body = models.TextField()  
  10.   
  11.     def __str__(self):  
  12.         return self.title  

 

1. 首先安裝各工具

pip install whoosh django-haystack jiebagithub

2. 添加 Haystack 到Django的 INSTALLED_APPS

配置Django項目的settings.py裏面的INSTALLED_APPS添加Haystack,例子:數據庫


 

[python]  view plain  copy
 
  1. INSTALLED_APPS = [   
  2.         'django.contrib.admin',  
  3.         'django.contrib.auth',   
  4.         'django.contrib.contenttypes',   
  5.         'django.contrib.sessions',   
  6.         'django.contrib.sites',   
  7.   
  8.           # Added. haystack先添加,  
  9.           'haystack',   
  10.           # Then your usual apps... 本身的app要寫在haystakc後面  
  11.           'blog',  
  12. ]  

3. 修改 你的 settings.py,以配置引擎

本教程使用的是Whoosh,故配置以下:django

 

[python]  view plain  copy
 
  1. import os  
  2. HAYSTACK_CONNECTIONS = {  
  3.     'default': {  
  4.         'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',  
  5.         'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),  
  6.     },  
  7. }  
其中顧名思義, ENGINE爲使用的引擎必需要有,若是引擎是 Whoosh,則 PATH必需要填寫,其爲Whoosh 索引文件的存放文件夾。
其餘引擎的配置見 官方文檔

 

 

4.建立索引

 

若是你想針對某個app例如mainapp作全文檢索,則必須在mainapp的目錄下面創建search_indexes.py文件,文件名不能修改。內容以下:windows


[python]  view plain  copy
 
  1. import datetime  
  2. from haystack import indexes  
  3. from myapp.models import Note  
  4.   
  5. class NoteIndex(indexes.SearchIndex, indexes.Indexable):     #類名必須爲須要檢索的Model_name+Index,這裏須要檢索Note,因此建立NoteIndex  
  6.     text = indexes.CharField(document=True, use_template=True)  #建立一個text字段  
  7.   
  8.     author = indexes.CharField(model_attr='user')   #建立一個author字段  
  9.   
  10.     pub_date = indexes.DateTimeField(model_attr='pub_date')  #建立一個pub_date字段  
  11.   
  12.     def get_model(self):          #重載get_model方法,必需要有!  
  13.         return Note  
  14.   
  15.     def index_queryset(self, using=None):   #重載index_..函數  
  16.         """Used when the entire index for model is updated."""  
  17.         return self.get_model().objects.filter(pub_date__lte=datetime.datetime.now())  

 

爲何要建立索引?索引就像是一本書的目錄,能夠爲讀者提供更快速的導航與查找。在這裏也是一樣的道理,當數據量很是大的時候,若要從這些數據裏找出全部的知足搜索條件的幾乎是不太可能的,將會給服務器帶來極大的負擔。因此咱們須要爲指定的數據添加一個索引(目錄),在這裏是爲Note建立一個索引,索引的實現細節是咱們不須要關心的,至於爲它的哪些字段建立索引,怎麼指定,正是我要給你們講的,也是網上所未曾提到的。api

 

每一個索引裏面必須有且只能有一個字段爲 document=True,這表明haystack 和搜索引擎將使用此字段的內容做爲索引進行檢索(primary field)。其餘的字段只是附屬的屬性,方便調用,並不做爲檢索數據。直到我本身完成一個搜索器,也沒有用到這些附屬屬性,因此我索性就都刪掉了,你們學習的時候也能夠先註釋掉無論。具體做用我也不明白,反正我沒用上。服務器

注意:若是使用一個字段設置了document=True,則通常約定此字段名爲text,這是在SearchIndex類裏面一向的命名,以防止後臺混亂,固然名字你也能夠隨便改,不過不建議改。

而且,haystack提供了use_template=Truetext字段,這樣就容許咱們使用數據模板去創建搜索引擎索引的文件,說得通俗點就是索引裏面須要存放一些什麼東西,例如 Note 的 title 字段,這樣咱們能夠經過 title 內容來檢索 Note 數據了,舉個例子,假如你搜索 python ,那麼就能夠檢索出含有title含有 python 的Note了,怎麼樣是否是很簡單?數據模板的路徑爲templates/search/indexes/yourapp/note_text.txt(推薦在項目根目錄建立一個templates,並在settings.py裏爲其引入,使得django會從這個templates裏尋找模板,固然,只要放在任何一個你的Django能搜索到的tempaltes下面就好,關於這點我想不屬於咱們討論的範疇),templates/search/indexes/blog/note_text.txt文件名必須爲要索引的類名_text.txt,其內容爲

 

[python]  view plain  copy
 
  1. {{ object.title }}  
  2. {{ object.user.get_full_name }}  
  3. {{ object.body }}  

這個數據模板的做用是對 Note.titleNote.user.get_full_name, Note.body這三個字段創建索引,當檢索的時候會對這三個字段作全文檢索匹配。上面已經解釋清楚了。

 

5.在URL配置中添加SearchView,並配置模板

 

urls.py中配置以下url信息,固然url路由能夠隨意寫。

[python]  view plain  copy
 
  1. (r'^search/', include('haystack.urls')),  

其實 haystack.urls的內容爲

 

[python]  view plain  copy
 
  1. from django.conf.urls import url  
  2. from haystack.views import SearchView  
  3.   
  4. urlpatterns = [  
  5.     url(r'^$', SearchView(), name='haystack_search'),  
  6. ]  

SearchView()視圖函數默認使用的html模板路徑爲 templates/search/search.html(再說一次推薦在根目錄建立templates,並在settings.py裏設置好)
因此須要在 templates/search/下添加 search.html文件,內容爲

 

 

[html]  view plain  copy
 
  1. <h2>Search</h2>  
  2.   
  3. <form method="get" action=".">  
  4.     <table>  
  5.         {{ form.as_table }}  
  6.         <tr>  
  7.             <td</td>  
  8.             <td>  
  9.                 <input type="submit" value="Search">  
  10.             </td>  
  11.         </tr>  
  12.     </table>  
  13.   
  14.     {% if query %}  
  15.         <h3>Results</h3>  
  16.   
  17.         {% for result in page.object_list %}  
  18.             <p>  
  19.                 <href="{{ result.object.get_absolute_url }}">{{ result.object.title }}</a>  
  20.             </p>  
  21.         {% empty %}  
  22.             <p>No results found.</p>  
  23.         {% endfor %}  
  24.   
  25.         {% if page.has_previous or page.has_next %}  
  26.             <div>  
  27.                 {% if page.has_previous %}<href="?q={{ query }}&page={{ page.previous_page_number }}">{% endif %}« Previous{% if page.has_previous %}</a>{% endif %}  
  28.                 |  
  29.                 {% if page.has_next %}<href="?q={{ query }}&page={{ page.next_page_number }}">{% endif %}Next »{% if page.has_next %}</a>{% endif %}  
  30.             </div>  
  31.         {% endif %}  
  32.     {% else %}  
  33.         {# Show some example queries to run, maybe query syntax, something else? #}  
  34.     {% endif %}  
  35. </form>  

很明顯,它自帶了分頁。

 

而後爲你們解釋一下這個文件。首先能夠看到模板裏使用了的變量有 form,query,page 。下面一個個的說一下。

form,很明顯,它和django裏的form類是差很少的,能夠渲染出一個搜索的表單,相信用過Django的Form都知道,因此也很少說了,不明白的能夠去看Django文檔,固然其實我倒最後也沒用上,最後是本身寫了個<form></form>,提供正確的參數如name="seach",method="get"以及你的action地址就OK了。。。若是須要用到更多的搜索功能如過濾的話可能就要自定義Form類了(並且經過上面的例子能夠看到,默認的form也是提供一個簡單的過濾器的,能夠供你選擇哪些model是須要檢索的,若是一個都不勾的話默認所有搜索,固然咱們也是能夠本身利用html來模擬這個form的,因此想要實現model過濾仍是很簡單的,只要模擬一下這個Form的內容就行了),只有這樣haystack纔可以構造出相應的Form對象來進行檢索,其實和django的Form是同樣的,Form有一個自我檢查數據是否合法的功能,haystack也同樣,關於這個此篇文章不作多說,由於我也不太明白(2333)。具體細節去看文檔,並且文檔上關於View&Form那一節仍是比較通俗易懂的,詞彙量要求也不是很高,反正就連我都看懂了一些。。。

query嘛,就是咱們搜索的字符串。

關於page,能夠看到page有object_list屬性,它是一個list,裏面包含了第一頁所要展現的model對象集合,那麼list裏面到底有多少個呢?咱們想要本身控制個數怎麼辦呢?不用擔憂,haystack爲咱們提供了一個接口。咱們只要在settings.py裏設置:

 

[python]  view plain  copy
 
  1. #設置每頁顯示的數目,默認爲20,能夠本身修改  
  2. HAYSTACK_SEARCH_RESULTS_PER_PAGE  =  8  

而後關於分頁的部分,你們看名字應該也能看懂吧。

 

若是想要知道更多的默認context帶的變量,能夠本身看看源碼views.py裏的SearchView類視圖,相信都能看懂。

那麼問題來了。對於一個search頁面來講,咱們確定會須要用到更多自定義的 context 內容,那麼這下該怎麼辦呢?最初我想到的辦法即是修改haystack源碼,爲其添加上更多的 context 內容,大家是否是也有過和我同樣的想法呢?可是這樣作即笨拙又愚蠢,咱們不只須要注意各類環境,依賴關係,並且當服務器主機發生變化時,難道咱們還要把 haystack 也複製過去不成?這樣太愚蠢了!忽然,我想到既然我不能修改源碼,難道我還不能複用源碼嗎?以後,我用看了一下官方文檔,正如我所想的,經過繼承SeachView來實現重載 context 的內容。官方文檔提供了2個版本的SearchView,我最開始用的是新版的,最後出錯了,也懶得去找錯誤是什麼引發的了,直接使用的了舊版本的SearchView,只要你下了haystack,2個版本都是給你安裝好了的。因而咱們在myapp目錄下再建立一個search_views.py 文件,位置名字能夠本身定,用於寫本身的搜索視圖,代碼實例以下:

 

[python]  view plain  copy
 
  1. from haystack.views import SearchView  
  2. from .models import *  
  3.   
  4. class MySeachView(SearchView):  
  5.     def extra_context(self):       #重載extra_context來添加額外的context內容  
  6.         context = super(MySeachView,self).extra_context()  
  7.         side_list = Topic.objects.filter(kind='major').order_by('add_date')[:8]  
  8.         context['side_list'] = side_list  
  9.         return context  

 

 

而後再修改urls.py將search請求映射到MySearchView:

 

[python]  view plain  copy
 
  1. url(r'^search/', search_views.MySeachView(), name='haystack_search'),  

講完了上下文變量,再讓咱們來說一下模板標籤,haystack爲咱們提供了 {% highlight %}和 {% more_like_this %} 2個標籤,這裏我只爲你們詳細講解下 highlight的使用。

 

你是否也想讓本身的檢索和百度搜索同樣,將匹配到的文字也高亮顯示呢? {% highlight %} 爲咱們提供了這個功能(固然不只是這個標籤,貌似還有一個HighLight類,這個本身看文檔去吧,我英語差,看不明白)。

Syntax:

 
   
[python]  view plain  copy
 
  1. {% highlight <text_block> with <query> [css_class "class_name"] [html_tag "span"] [max_length 200] %}  

 

大概意思是爲 text_block 裏的 query 部分添加css_class,html_tag,而max_length 爲最終返回長度,至關於 cut ,我看了一下此標籤實現源碼,默認的html_tag 值爲 span ,css_class 值爲 highlighted,max_length 值爲 200,而後就能夠經過CSS來添加效果。如默認時:

 

[css]  view plain  copy
 
  1. span.highlighted {  
  2.         color: red;  
  3. }  


 

Example:

 

[python]  view plain  copy
 
  1. # 使用默認值  
  2. {% highlight result.summary with query %}  
  3.   
  4. # 這裏咱們爲 {{ result.summary }}裏全部的 {{ query }} 指定了一個<div></div>標籤,而且將class設置爲highlight_me_please,這樣就能夠本身經過CSS爲{{ query }}添加高亮效果了,怎麼樣,是否是很科學呢  
  5. {% highlight result.summary with query html_tag "div" css_class "highlight_me_please" %}  
  6.   
  7. # 這裏能夠限制最終{{ result.summary }}被高亮處理後的長度  
  8. {% highlight result.summary with query max_length 40 %}  


 

好了,到目前爲止,若是你掌握了上面的知識的話,你已經會製做一個比較使人滿意的搜索器了,接下來就是建立index文件了。

 

6.最後一步,重建索引文件

 

使用python manage.py rebuild_index或者使用update_index命令。

好,下面運行項目,進入該url搜索一下試試吧。

每次數據庫更新後都須要更新索引,因此haystack爲你們提供了一個接口,只要在settings.py裏設置:

 

[python]  view plain  copy
 
  1. #自動更新索引  
  2. HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'  


 

三:下面要作的,使用jieba分詞

 
1 將文件 whoosh_backend.py(該文件路徑爲 python路徑/lib/python2.7.5/site-packages/haystack/backends/whoosh_backend.py)拷貝到app下面,並重命名爲 whoosh_cn_backend.py,例如 blog/whoosh_cn_backend.py

修改成以下
 
[python]  view plain  copy
 
  1. from jieba.analyse import ChineseAnalyzer   #在頂部添加  
  2.   
  3. schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=ChineseAnalyzer(),field_boost=field_class.boost, sortable=True)   #注意先找到這個再修改,而不是直接添加  

2 在 settings.py中修改引擎,以下
 
[python]  view plain  copy
 
  1. import os  
  2. HAYSTACK_CONNECTIONS = {  
  3.     'default': {  
  4.         'ENGINE': 'blog.whoosh_cn_backend.WhooshEngine',      #blog.whoosh_cn_backend即是你剛剛添加的文件  
  5.         'PATH': os.path.join(BASE_DIR, 'whoosh_index'  
  6.     },  
  7. }  
 
3 重建索引,在進行搜索中文試試吧。
 
 
 

四.highlight補充

終於寫完了!!!淚奔!!!最後給你們看看個人

怎麼樣,還行吧?眼尖的人會發現,爲何標題裏的高等沒有被替換成...,而段落裏的數學以前的內容卻被替換成了...,標題原本就很短,想象一下,如果高等數學被顯示成了數學,是否是丟失了最重要的信息呢?高等這麼重要的字眼都被省略了,很顯然是不行的,畢竟我是個高等生。那麼怎麼辦呢?我沒有選擇去看文檔,可能文檔的HighLight類就是用來幹這個的吧,可是我選擇了讀highlight 標籤的源碼,最終仍是讓我實現了。

咱們須要作的是複製粘貼源碼,而後進行修改,而不是選擇直接改源碼,建立一個本身的標籤。爲你們奉上。添加myapp/templatetags/my_filters_and_tags.py 文件和 myapp/templatetags/highlighting.py 文件,內容以下(源碼分別位於haystack/templatetags/lighlight.py 和 haystack/utils/lighlighting.py 中):

my_filter_and_tags.py:

 

[python]  view plain  copy
 
  1. # encoding: utf-8  
  2. from __future__ import absolute_import, division, print_function, unicode_literals  
  3.   
  4. from django import template  
  5. from django.conf import settings  
  6. from django.core.exceptions import ImproperlyConfigured  
  7. from django.utils import six  
  8.   
  9. from haystack.utils import importlib  
  10.   
  11. register = template.Library()  
  12.   
  13. class HighlightNode(template.Node):  
  14.     def __init__(self, text_block, query, html_tag=None, css_class=None, max_length=None, start_head=None):  
  15.         self.text_block = template.Variable(text_block)  
  16.         self.query = template.Variable(query)  
  17.         self.html_tag = html_tag  
  18.         self.css_class = css_class  
  19.         self.max_length = max_length  
  20.         self.start_head = start_head  
  21.   
  22.         if html_tag is not None:  
  23.             self.html_tag = template.Variable(html_tag)  
  24.   
  25.         if css_class is not None:  
  26.             self.css_class = template.Variable(css_class)  
  27.   
  28.         if max_length is not None:  
  29.             self.max_length = template.Variable(max_length)  
  30.   
  31.         if start_head is not None:  
  32.             self.start_head = template.Variable(start_head)  
  33.   
  34.     def render(self, context):  
  35.         text_block = self.text_block.resolve(context)  
  36.         query = self.query.resolve(context)  
  37.         kwargs = {}  
  38.   
  39.         if self.html_tag is not None:  
  40.             kwargs['html_tag'] = self.html_tag.resolve(context)  
  41.   
  42.         if self.css_class is not None:  
  43.             kwargs['css_class'] = self.css_class.resolve(context)  
  44.   
  45.         if self.max_length is not None:  
  46.             kwargs['max_length'] = self.max_length.resolve(context)  
  47.   
  48.         if self.start_head is not None:  
  49.             kwargs['start_head'] = self.start_head.resolve(context)  
  50.   
  51.         # Handle a user-defined highlighting function.  
  52.         if hasattr(settings, 'HAYSTACK_CUSTOM_HIGHLIGHTER') and settings.HAYSTACK_CUSTOM_HIGHLIGHTER:  
  53.             # Do the import dance.  
  54.             try:  
  55.                 path_bits = settings.HAYSTACK_CUSTOM_HIGHLIGHTER.split('.')  
  56.                 highlighter_path, highlighter_classname = '.'.join(path_bits[:-1]), path_bits[-1]  
  57.                 highlighter_module = importlib.import_module(highlighter_path)  
  58.                 highlighter_class = getattr(highlighter_module, highlighter_classname)  
  59.             except (ImportError, AttributeError) as e:  
  60.                 raise ImproperlyConfigured("The highlighter '%s' could not be imported: %s" % (settings.HAYSTACK_CUSTOM_HIGHLIGHTER, e))  
  61.         else:  
  62.             from .highlighting import Highlighter  
  63.             highlighter_class = Highlighter  
  64.   
  65.         highlighter = highlighter_class(query, **kwargs)  
  66.         highlighted_text = highlighter.highlight(text_block)  
  67.         return highlighted_text  
  68.  
  69.  
  70. @register.tag  
  71. def myhighlight(parser, token):  
  72.     """ 
  73.     Takes a block of text and highlights words from a provided query within that 
  74.     block of text. Optionally accepts arguments to provide the HTML tag to wrap 
  75.     highlighted word in, a CSS class to use with the tag and a maximum length of 
  76.     the blurb in characters. 
  77.  
  78.     Syntax:: 
  79.  
  80.         {% highlight <text_block> with <query> [css_class "class_name"] [html_tag "span"] [max_length 200] %} 
  81.  
  82.     Example:: 
  83.  
  84.         # Highlight summary with default behavior. 
  85.         {% highlight result.summary with request.query %} 
  86.  
  87.         # Highlight summary but wrap highlighted words with a div and the 
  88.         # following CSS class. 
  89.         {% highlight result.summary with request.query html_tag "div" css_class "highlight_me_please" %} 
  90.  
  91.         # Highlight summary but only show 40 characters. 
  92.         {% highlight result.summary with request.query max_length 40 %} 
  93.     """  
  94.     bits = token.split_contents()  
  95.     tag_name = bits[0]  
  96.   
  97.     if not len(bits) % 2 == 0:  
  98.         raise template.TemplateSyntaxError(u"'%s' tag requires valid pairings arguments." % tag_name)  
  99.   
  100.     text_block = bits[1]  
  101.   
  102.     if len(bits) < 4:  
  103.         raise template.TemplateSyntaxError(u"'%s' tag requires an object and a query provided by 'with'." % tag_name)  
  104.   
  105.     if bits[2] != 'with':  
  106.         raise template.TemplateSyntaxError(u"'%s' tag's second argument should be 'with'." % tag_name)  
  107.   
  108.     query = bits[3]  
  109.   
  110.     arg_bits = iter(bits[4:])  
  111.     kwargs = {}  
  112.   
  113.     for bit in arg_bits:  
  114.         if bit == 'css_class':  
  115.             kwargs['css_class'] = six.next(arg_bits)  
  116.   
  117.         if bit == 'html_tag':  
  118.             kwargs['html_tag'] = six.next(arg_bits)  
  119.   
  120.         if bit == 'max_length':  
  121.             kwargs['max_length'] = six.next(arg_bits)  
  122.   
  123.         if bit == 'start_head':  
  124.             kwargs['start_head'] = six.next(arg_bits)  
  125.   
  126.     return HighlightNode(text_block, query, **kwargs)  
lighlighting.py:

 

 

[python]  view plain  copy
 
  1. # encoding: utf-8  
  2.   
  3. from __future__ import absolute_import, division, print_function, unicode_literals  
  4.   
  5. from django.utils.html import strip_tags  
  6.   
  7.   
  8. class Highlighter(object):  
  9.     #默認值  
  10.     css_class = 'highlighted'  
  11.     html_tag = 'span'  
  12.     max_length = 200  
  13.     start_head = False  
  14.     text_block = ''  
  15.   
  16.     def __init__(self, query, **kwargs):  
  17.         self.query = query  
  18.   
  19.         if 'max_length' in kwargs:  
  20.             self.max_length = int(kwargs['max_length'])  
  21.   
  22.         if 'html_tag' in kwargs:  
  23.             self.html_tag = kwargs['html_tag']  
  24.   
  25.         if 'css_class' in kwargs:  
  26.             self.css_class = kwargs['css_class']  
  27.   
  28.         if 'start_head' in kwargs:  
  29.             self.start_head = kwargs['start_head']  
  30.   
  31.         self.query_words = set([word.lower() for word in self.query.split() if not word.startswith('-')])  
  32.   
  33.     def highlight(self, text_block):  
  34.         self.text_block = strip_tags(text_block)  
  35.         highlight_locations = self.find_highlightable_words()  
  36.         start_offset, end_offset = self.find_window(highlight_locations)  
  37.         return self.render_html(highlight_locations, start_offset, end_offset)  
  38.   
  39.     def find_highlightable_words(self):  
  40.         # Use a set so we only do this once per unique word.  
  41.         word_positions = {}  
  42.   
  43.         # Pre-compute the length.  
  44.         end_offset = len(self.text_block)  
  45.         lower_text_block = self.text_block.lower()  
  46.   
  47.         for word in self.query_words:  
  48.             if not word in word_positions:  
  49.                 word_positions[word] = []  
  50.   
  51.             start_offset = 0  
  52.   
  53.             while start_offset < end_offset:  
  54.                 next_offset = lower_text_block.find(word, start_offset, end_offset)  
  55.   
  56.                 # If we get a -1 out of find, it wasn't found. Bomb out and  
  57.                 # start the next word.  
  58.                 if next_offset == -1:  
  59.                     break  
  60.   
  61.                 word_positions[word].append(next_offset)  
  62.                 start_offset = next_offset + len(word)  
  63.   
  64.         return word_positions  
  65.   
  66.     def find_window(self, highlight_locations):  
  67.         best_start = 0  
  68.         best_end = self.max_length  
  69.   
  70.         # First, make sure we have words.  
  71.         if not len(highlight_locations):  
  72.             return (best_start, best_end)  
  73.   
  74.         words_found = []  
  75.   
  76.         # Next, make sure we found any words at all.  
  77.         for word, offset_list in highlight_locations.items():  
  78.             if len(offset_list):  
  79.                 # Add all of the locations to the list.  
  80.                 words_found.extend(offset_list)  
  81.   
  82.         if not len(words_found):  
  83.             return (best_start, best_end)  
  84.   
  85.         if len(words_found) == 1:  
  86.             return (words_found[0], words_found[0] + self.max_length)  
  87.   
  88.         # Sort the list so it's in ascending order.  
  89.         words_found = sorted(words_found)  
  90.   
  91.         # We now have a denormalized list of all positions were a word was  
  92.         # found. We'll iterate through and find the densest window we can by  
  93.         # counting the number of found offsets (-1 to fit in the window).  
  94.         highest_density = 0  
  95.   
  96.         if words_found[:-1][0] > self.max_length:  
  97.             best_start = words_found[:-1][0]  
  98.             best_end = best_start + self.max_length  
  99.   
  100.         for count, start in enumerate(words_found[:-1]):  
  101.             current_density = 1  
  102.   
  103.             for end in words_found[count + 1:]:  
  104.                 if end - start < self.max_length:  
  105.                     current_density += 1  
  106.                 else:  
  107.                     current_density = 0  
  108.   
  109.                 # Only replace if we have a bigger (not equal density) so we  
  110.                 # give deference to windows earlier in the document.  
  111.                 if current_density > highest_density:  
  112.                     best_start = start  
  113.                     best_end = start + self.max_length  
  114.                     highest_density = current_density  
  115.   
  116.         return (best_start, best_end)  
  117.   
  118.     def render_html(self, highlight_locations=None, start_offset=None, end_offset=None):  
  119.         # Start by chopping the block down to the proper window.  
  120.         #text_block爲內容,start_offset,end_offset分別爲第一個匹配query開始和按長度截斷位置  
  121.         text = self.text_block[start_offset:end_offset]  
  122.   
  123.         # Invert highlight_locations to a location -> term list  
  124.         term_list = []  
  125.   
  126.         for term, locations in highlight_locations.items():  
  127.             term_list += [(loc - start_offset, term) for loc in locations]  
  128.   
  129.         loc_to_term = sorted(term_list)  
  130.   
  131.         # Prepare the highlight template  
  132.         if self.css_class:  
  133.             hl_start = '<%s class="%s">' % (self.html_tag, self.css_class)  
  134.         else:  
  135.             hl_start = '<%s>' % (self.html_tag)  
  136.   
  137.         hl_end = '</%s>' % self.html_tag  
  138.   
  139.         # Copy the part from the start of the string to the first match,  
  140.         # and there replace the match with a highlighted version.  
  141.         #matched_so_far最終求得爲text中最後一個匹配query的結尾  
  142.         highlighted_chunk = ""  
  143.         matched_so_far = 0  
  144.         prev = 0  
  145.         prev_str = ""  
  146.   
  147.         for cur, cur_str in loc_to_term:  
  148.             # This can be in a different case than cur_str  
  149.             actual_term = text[cur:cur + len(cur_str)]  
  150.   
  151.             # Handle incorrect highlight_locations by first checking for the term  
  152.             if actual_term.lower() == cur_str:  
  153.                 if cur < prev + len(prev_str):  
  154.                     continue  
  155.   
  156.                 #分別添上每一個query+其後面的一部分(下一個query的前一個位置)  
  157.                 highlighted_chunk += text[prev + len(prev_str):cur] + hl_start + actual_term + hl_end  
  158.                 prev = cur  
  159.                 prev_str = cur_str  
  160.   
  161.                 # Keep track of how far we've copied so far, for the last step  
  162.                 matched_so_far = cur + len(actual_term)  
  163.   
  164.         # Don't forget the chunk after the last term  
  165.         #加上最後一個匹配的query後面的部分  
  166.         highlighted_chunk += text[matched_so_far:]  
  167.   
  168.         #若是不要開頭not start_head才加點  
  169.         if start_offset > and not self.start_head:  
  170.             highlighted_chunk = '...%s' % highlighted_chunk  
  171.   
  172.         if end_offset < len(self.text_block):  
  173.             highlighted_chunk = '%s...' % highlighted_chunk  
  174.   
  175.         #可見到目前爲止還不包含start_offset前面的,即第一個匹配的前面的部分(text_block[:start_offset]),如需展現(當start_head爲True時)便加上  
  176.         if self.start_head:  
  177.             highlighted_chunk = self.text_block[:start_offset] + highlighted_chunk  
  178.         return highlighted_chunk  


 

添加上這2個文件以後,即可以使用本身的標籤 {% mylighlight %}了,使用時記得Load哦!

 

Syntax:

 
  
[python]  view plain  copy
 
  1. {% myhighlight <text_block> with <query> [css_class "class_name"] [html_tag "span"] [max_length 200] [start_head True] %}  
可見我只是多添加了一個選項 start_head ,默認爲False,若是設置爲True 則不會省略。
相關文章
相關標籤/搜索