You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

simple.go 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  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. package simple
  15. import (
  16. "unicode/utf8"
  17. "github.com/blevesearch/bleve/registry"
  18. "github.com/blevesearch/bleve/search/highlight"
  19. )
  20. const Name = "simple"
  21. const defaultFragmentSize = 200
  22. type Fragmenter struct {
  23. fragmentSize int
  24. }
  25. func NewFragmenter(fragmentSize int) *Fragmenter {
  26. return &Fragmenter{
  27. fragmentSize: fragmentSize,
  28. }
  29. }
  30. func (s *Fragmenter) Fragment(orig []byte, ot highlight.TermLocations) []*highlight.Fragment {
  31. var rv []*highlight.Fragment
  32. maxbegin := 0
  33. OUTER:
  34. for currTermIndex, termLocation := range ot {
  35. // start with this
  36. // it should be the highest scoring fragment with this term first
  37. start := termLocation.Start
  38. end := start
  39. used := 0
  40. for end < len(orig) && used < s.fragmentSize {
  41. r, size := utf8.DecodeRune(orig[end:])
  42. if r == utf8.RuneError {
  43. continue OUTER // bail
  44. }
  45. end += size
  46. used++
  47. }
  48. // if we still have more characters available to us
  49. // push back towards beginning
  50. // without cross maxbegin
  51. for start > 0 && used < s.fragmentSize {
  52. if start > len(orig) {
  53. // bail if out of bounds, possibly due to token replacement
  54. // e.g with a regexp replacement
  55. continue OUTER
  56. }
  57. r, size := utf8.DecodeLastRune(orig[0:start])
  58. if r == utf8.RuneError {
  59. continue OUTER // bail
  60. }
  61. if start-size >= maxbegin {
  62. start -= size
  63. used++
  64. } else {
  65. break
  66. }
  67. }
  68. // however, we'd rather have the tokens centered more in the frag
  69. // lets try to do that as best we can, without affecting the score
  70. // find the end of the last term in this fragment
  71. minend := end
  72. for _, innerTermLocation := range ot[currTermIndex:] {
  73. if innerTermLocation.End > end {
  74. break
  75. }
  76. minend = innerTermLocation.End
  77. }
  78. // find the smaller of the two rooms to move
  79. roomToMove := utf8.RuneCount(orig[minend:end])
  80. roomToMoveStart := 0
  81. if start >= maxbegin {
  82. roomToMoveStart = utf8.RuneCount(orig[maxbegin:start])
  83. }
  84. if roomToMoveStart < roomToMove {
  85. roomToMove = roomToMoveStart
  86. }
  87. offset := roomToMove / 2
  88. for offset > 0 {
  89. r, size := utf8.DecodeLastRune(orig[0:start])
  90. if r == utf8.RuneError {
  91. continue OUTER // bail
  92. }
  93. start -= size
  94. r, size = utf8.DecodeLastRune(orig[0:end])
  95. if r == utf8.RuneError {
  96. continue OUTER // bail
  97. }
  98. end -= size
  99. offset--
  100. }
  101. rv = append(rv, &highlight.Fragment{Orig: orig, Start: start - offset, End: end - offset})
  102. // set maxbegin to the end of the current term location
  103. // so that next one won't back up to include it
  104. maxbegin = termLocation.End
  105. }
  106. if len(ot) == 0 {
  107. // if there were no terms to highlight
  108. // produce a single fragment from the beginning
  109. start := 0
  110. end := start + s.fragmentSize
  111. if end > len(orig) {
  112. end = len(orig)
  113. }
  114. rv = append(rv, &highlight.Fragment{Orig: orig, Start: start, End: end})
  115. }
  116. return rv
  117. }
  118. func Constructor(config map[string]interface{}, cache *registry.Cache) (highlight.Fragmenter, error) {
  119. size := defaultFragmentSize
  120. sizeVal, ok := config["size"].(float64)
  121. if ok {
  122. size = int(sizeVal)
  123. }
  124. return NewFragmenter(size), nil
  125. }
  126. func init() {
  127. registry.RegisterFragmenter(Name, Constructor)
  128. }