Django-Haystack 全文检索之highlight 标签和自定义Highlighter
highlight在全文检索起到的作用是:用户在浏览器,搜索关键字,服务器返回相关结果,然后haystack把该关键字高亮起来的过程。
haystack highlight 标签的用法
{% highlight <text_block> with <query> [css_class "class_name"] [html_tag "span"] [max_length 200] %}
实例
{% highlight result.summary with query max_length 40 css_class "my_css_class"%}
highlight-标签名
text_block-用户显示的文本,通常为model.attr
query-关键字
css_class-显示的样式 - 用户自己的css文件
html_tag-使用哪个HTML标签,默认是span
max_length-返回的文本最大长度(含高亮的搜索词)
highlight 标签的实现逻辑
用户在浏览器,搜索关键字,服务器返回结果,并把该关键字高亮起来
前端使用到了highlight标签,所以haystack会调用haystack\templatestags\highlight.py 里的hightlight方法(被haystack注册成标签) 该方法返回的是一个HighlightNode对象
def highlight(parser, token):
...
这部分代码的作用是拆分模板里的 {% highlight result.summary with query max_length 40 css_class "my_css_class"%} 标签代码
text_block: result.summary
css_class: my_css_class 定义在css文件里
html_tag: 最终展示搜索词的HTML标签, 默认是span
...
...
...
# kwargs字典分别有:kwargs['css_class']
# kwargs['html_tag']
# kwargs['max_length']
return HighlightNode(text_block, query, **kwargs)
HighlightNode的render()方法会被调用(还没看实现代码),然后通过haystack\templatestags\highlighting.py Highlighter类的highlight() 方法获得highlighted_text, 然后返回highlighted_text。
而这个highlighted_text,就是我们前端看到的高亮效果的HTML。highlighted_text如下图所示。
class HighlightNode(template.Node):
def render(self, context):
...
...
...
这时候这个方法会去找一下setting.py文件是否有用户自定义的Highlighter类,如果有就使用用户的否则就使用默认的Highlighter
if hasattr(settings, 'HAYSTACK_CUSTOM_HIGHLIGHTER') and settings.HAYSTACK_CUSTOM_HIGHLIGHTER:
...
highlighter = highlighter_class(query, **kwargs) # highlighter_class的赋值是,到底使用哪个Highlighter类,默认使用haystack\templatestags\highlight.py的Highlighter类
highlighted_text = highlighter.highlight(text_block) # 调用highlight方法,获得highlighted_text
return highlighted_text # 如上图所示,format类似:plain text +<span class="my_css_class">query</span> + plain text
class Highlighter(object):
def highlight(self, text_block):
self.text_block = strip_tags(text_block) # 过滤HTML标签
highlight_locations = self.find_highlightable_words() # 找到需要高亮的搜索词query处于整个文本(在这个例子里是 result.summary)的位置offset, format: [(0, "your_query"), (14, "your_query")]
start_offset, end_offset = self.find_window(highlight_locations) # 由于用户给定了搜索结果的max_length,言下之意,搜索的结果长度小于等于result.summary的长度,所以需要找到在既定长度
# 也就是max_length下,result.summary的什么区间存在最多的query,然后返回该区间的起止位置, 这里有个不太满足需求的地方,
# 下面会带出
return self.render_html(highlight_locations, start_offset, end_offset) # 在获得需要高亮的结果的位置,搜索结果起止位置,然后组装html文本
highlight tag的不足
Django提供的{% highlight %} 标签, 可以达到高亮query的效果。 但同时,也存在其不足。当query 为单个查询关键词的时候, haystack返回的文本是从找到该关键词为起点start_offset, 到start_offset+max_length。
但当这个query,处于整个搜索文本的最末尾的时候, 返回结果是“…”+query(示例如下)。( "…"是在Highlighter的render_html()里添加的 ) 这样的结果显示相当不友好。效果如下图。
haystack\templatestags\highlighting.py Highlighter类的find_window()方法
if len(words_found) == 1:
return (words_found[0], words_found[0] + self.max_length)
自定义Highlighter
所以在理解highlight标签实现的过程后,个人的做法是继承Highlighter类,然后重写find_window方法。因为find_window决定了搜索结果处于整个文本result.summary的区间(起止位置 )。
from haystack.utils import Highlighter
class MyHighLighter(Highlighter):
def find_window(self, highlight_locations):
...
...
# 只修改了if len(words_found) == 1 的代码段
if len(words_found) == 1:
# return (words_found[0], words_found[0] + self.max_length) #
# new added
best_start = words_found[0]
best_end = words_found[0] + self.max_length
if best_end > len(self.text_block):
current_length = len(self.text_block) - best_start
move_forward_steps = self.max_length - current_length
best_start = best_start - move_forward_steps
# 逻辑如下图, 实现的方式各有不同,按自己理解和需求就好
# 这里的逻辑是因为搜索词query处于text_block的末尾,所以用max_length-query的长度=剩下的长度(move_forward_steps)
if best_start < 0:
best_start = 0
return (best_start, best_end)
HAYSTACK_CUSTOM_HIGHLIGHTER = "你的路径.utils.MyHighLighter"
xxx.html
{% load highlight %}
{% highlight goods.object.g_title with query max_length 16 css_class "highlight"%}
xxx.css
/*for highlight*/
.highlight{color:red;}
到此,自定义的highlighter类就修改好了。