~ubuntu-branches/debian/sid/golang-github-blevesearch-bleve/sid

« back to all changes in this revision

Viewing changes to search/highlight/highlighter/simple/highlighter_simple.go

  • Committer: Package Import Robot
  • Author(s): Michael Lustfield
  • Date: 2017-03-30 16:06:03 UTC
  • Revision ID: package-import@ubuntu.com-20170330160603-0oogmb960l7918jx
Tags: upstream-0.5.0+git20170324.202.4702785f
Import upstream version 0.5.0+git20170324.202.4702785f

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
//  Copyright (c) 2014 Couchbase, Inc.
 
2
//
 
3
// Licensed under the Apache License, Version 2.0 (the "License");
 
4
// you may not use this file except in compliance with the License.
 
5
// You may obtain a copy of the License at
 
6
//
 
7
//              http://www.apache.org/licenses/LICENSE-2.0
 
8
//
 
9
// Unless required by applicable law or agreed to in writing, software
 
10
// distributed under the License is distributed on an "AS IS" BASIS,
 
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
12
// See the License for the specific language governing permissions and
 
13
// limitations under the License.
 
14
 
 
15
package simple
 
16
 
 
17
import (
 
18
        "container/heap"
 
19
        "fmt"
 
20
 
 
21
        "github.com/blevesearch/bleve/document"
 
22
        "github.com/blevesearch/bleve/registry"
 
23
        "github.com/blevesearch/bleve/search"
 
24
        "github.com/blevesearch/bleve/search/highlight"
 
25
)
 
26
 
 
27
const Name = "simple"
 
28
const DefaultSeparator = "…"
 
29
 
 
30
type Highlighter struct {
 
31
        fragmenter highlight.Fragmenter
 
32
        formatter  highlight.FragmentFormatter
 
33
        sep        string
 
34
}
 
35
 
 
36
func NewHighlighter(fragmenter highlight.Fragmenter, formatter highlight.FragmentFormatter, separator string) *Highlighter {
 
37
        return &Highlighter{
 
38
                fragmenter: fragmenter,
 
39
                formatter:  formatter,
 
40
                sep:        separator,
 
41
        }
 
42
}
 
43
 
 
44
func (s *Highlighter) Fragmenter() highlight.Fragmenter {
 
45
        return s.fragmenter
 
46
}
 
47
 
 
48
func (s *Highlighter) SetFragmenter(f highlight.Fragmenter) {
 
49
        s.fragmenter = f
 
50
}
 
51
 
 
52
func (s *Highlighter) FragmentFormatter() highlight.FragmentFormatter {
 
53
        return s.formatter
 
54
}
 
55
 
 
56
func (s *Highlighter) SetFragmentFormatter(f highlight.FragmentFormatter) {
 
57
        s.formatter = f
 
58
}
 
59
 
 
60
func (s *Highlighter) Separator() string {
 
61
        return s.sep
 
62
}
 
63
 
 
64
func (s *Highlighter) SetSeparator(sep string) {
 
65
        s.sep = sep
 
66
}
 
67
 
 
68
func (s *Highlighter) BestFragmentInField(dm *search.DocumentMatch, doc *document.Document, field string) string {
 
69
        fragments := s.BestFragmentsInField(dm, doc, field, 1)
 
70
        if len(fragments) > 0 {
 
71
                return fragments[0]
 
72
        }
 
73
        return ""
 
74
}
 
75
 
 
76
func (s *Highlighter) BestFragmentsInField(dm *search.DocumentMatch, doc *document.Document, field string, num int) []string {
 
77
        tlm := dm.Locations[field]
 
78
        orderedTermLocations := highlight.OrderTermLocations(tlm)
 
79
        scorer := NewFragmentScorer(tlm)
 
80
 
 
81
        // score the fragments and put them into a priority queue ordered by score
 
82
        fq := make(FragmentQueue, 0)
 
83
        heap.Init(&fq)
 
84
        for _, f := range doc.Fields {
 
85
                if f.Name() == field {
 
86
                        _, ok := f.(*document.TextField)
 
87
                        if ok {
 
88
                                termLocationsSameArrayPosition := make(highlight.TermLocations, 0)
 
89
                                for _, otl := range orderedTermLocations {
 
90
                                        if otl.ArrayPositions.Equals(f.ArrayPositions()) {
 
91
                                                termLocationsSameArrayPosition = append(termLocationsSameArrayPosition, otl)
 
92
                                        }
 
93
                                }
 
94
 
 
95
                                fieldData := f.Value()
 
96
                                fragments := s.fragmenter.Fragment(fieldData, termLocationsSameArrayPosition)
 
97
                                for _, fragment := range fragments {
 
98
                                        fragment.ArrayPositions = f.ArrayPositions()
 
99
                                        scorer.Score(fragment)
 
100
                                        heap.Push(&fq, fragment)
 
101
                                }
 
102
                        }
 
103
                }
 
104
        }
 
105
 
 
106
        // now find the N best non-overlapping fragments
 
107
        var bestFragments []*highlight.Fragment
 
108
        if len(fq) > 0 {
 
109
                candidate := heap.Pop(&fq)
 
110
        OUTER:
 
111
                for candidate != nil && len(bestFragments) < num {
 
112
                        // see if this overlaps with any of the best already identified
 
113
                        if len(bestFragments) > 0 {
 
114
                                for _, frag := range bestFragments {
 
115
                                        if candidate.(*highlight.Fragment).Overlaps(frag) {
 
116
                                                if len(fq) < 1 {
 
117
                                                        break OUTER
 
118
                                                }
 
119
                                                candidate = heap.Pop(&fq)
 
120
                                                continue OUTER
 
121
                                        }
 
122
                                }
 
123
                                bestFragments = append(bestFragments, candidate.(*highlight.Fragment))
 
124
                        } else {
 
125
                                bestFragments = append(bestFragments, candidate.(*highlight.Fragment))
 
126
                        }
 
127
 
 
128
                        if len(fq) < 1 {
 
129
                                break
 
130
                        }
 
131
                        candidate = heap.Pop(&fq)
 
132
                }
 
133
        }
 
134
 
 
135
        // now that we have the best fragments, we can format them
 
136
        orderedTermLocations.MergeOverlapping()
 
137
        formattedFragments := make([]string, len(bestFragments))
 
138
        for i, fragment := range bestFragments {
 
139
                formattedFragments[i] = ""
 
140
                if fragment.Start != 0 {
 
141
                        formattedFragments[i] += s.sep
 
142
                }
 
143
                formattedFragments[i] += s.formatter.Format(fragment, orderedTermLocations)
 
144
                if fragment.End != len(fragment.Orig) {
 
145
                        formattedFragments[i] += s.sep
 
146
                }
 
147
        }
 
148
 
 
149
        if dm.Fragments == nil {
 
150
                dm.Fragments = make(search.FieldFragmentMap, 0)
 
151
        }
 
152
        if len(formattedFragments) > 0 {
 
153
                dm.Fragments[field] = formattedFragments
 
154
        }
 
155
 
 
156
        return formattedFragments
 
157
}
 
158
 
 
159
// FragmentQueue implements heap.Interface and holds Items.
 
160
type FragmentQueue []*highlight.Fragment
 
161
 
 
162
func (fq FragmentQueue) Len() int { return len(fq) }
 
163
 
 
164
func (fq FragmentQueue) Less(i, j int) bool {
 
165
        // We want Pop to give us the highest, not lowest, priority so we use greater-than here.
 
166
        return fq[i].Score > fq[j].Score
 
167
}
 
168
 
 
169
func (fq FragmentQueue) Swap(i, j int) {
 
170
        fq[i], fq[j] = fq[j], fq[i]
 
171
        fq[i].Index = i
 
172
        fq[j].Index = j
 
173
}
 
174
 
 
175
func (fq *FragmentQueue) Push(x interface{}) {
 
176
        n := len(*fq)
 
177
        item := x.(*highlight.Fragment)
 
178
        item.Index = n
 
179
        *fq = append(*fq, item)
 
180
}
 
181
 
 
182
func (fq *FragmentQueue) Pop() interface{} {
 
183
        old := *fq
 
184
        n := len(old)
 
185
        item := old[n-1]
 
186
        item.Index = -1 // for safety
 
187
        *fq = old[0 : n-1]
 
188
        return item
 
189
}
 
190
 
 
191
func Constructor(config map[string]interface{}, cache *registry.Cache) (highlight.Highlighter, error) {
 
192
        separator := DefaultSeparator
 
193
        separatorVal, ok := config["separator"].(string)
 
194
        if ok {
 
195
                separator = separatorVal
 
196
        }
 
197
 
 
198
        fragmenterName, ok := config["fragmenter"].(string)
 
199
        if !ok {
 
200
                return nil, fmt.Errorf("must specify fragmenter")
 
201
        }
 
202
        fragmenter, err := cache.FragmenterNamed(fragmenterName)
 
203
        if err != nil {
 
204
                return nil, fmt.Errorf("error building fragmenter: %v", err)
 
205
        }
 
206
 
 
207
        formatterName, ok := config["formatter"].(string)
 
208
        if !ok {
 
209
                return nil, fmt.Errorf("must specify formatter")
 
210
        }
 
211
        formatter, err := cache.FragmentFormatterNamed(formatterName)
 
212
        if err != nil {
 
213
                return nil, fmt.Errorf("error building fragment formatter: %v", err)
 
214
        }
 
215
 
 
216
        return NewHighlighter(fragmenter, formatter, separator), nil
 
217
}
 
218
 
 
219
func init() {
 
220
        registry.RegisterHighlighter(Name, Constructor)
 
221
}