cjk_index 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +33 -2
- data/lib/cjk_index/bridgetown.rb +2 -2
- data/lib/cjk_index/builder.rb +6 -4
- data/lib/cjk_index/cli.rb +2 -2
- data/lib/cjk_index/jekyll.rb +2 -2
- data/lib/cjk_index/normalizer.rb +57 -12
- data/lib/cjk_index/runtime.js +10 -0
- data/lib/cjk_index/searcher.rb +7 -3
- data/lib/cjk_index/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 615a0f6d44d6e04e21f66aeb15778e39adf566ec078f11c69ceecf41473e1388
|
|
4
|
+
data.tar.gz: 7a106a769e2cc57e382727c8ada1afbba691fbdb7620c0f487eddbe31610abf8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2783f62f61930fbd740261d552d7b8092b098b1e15a4fe28741b37f766b9670f5e66045c994da3bcff2b16644f28772b681b8f6fd70b28440f8dd58571e0759e
|
|
7
|
+
data.tar.gz: c1faf175e55309bf62568d7215bee743fe7a4f0942d9621e1ad8f846e9082393dd51c38ef2ef141bdada33ce887a253d4ae533e812abe6e07cf59423901e3638
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.1 (2026-10-28)
|
|
4
|
+
|
|
5
|
+
- Variation selectors (U+FE00–U+FE0F and the ideographic U+E0100–U+E01EF)
|
|
6
|
+
are dropped when folding: 「珊」 followed by an ideographic variation
|
|
7
|
+
selector is 「珊」. Before, the selector broke a CJK run in two, so
|
|
8
|
+
「珊瑚」 did not find 「珊\u{E0100}瑚」, and a table file of your own with
|
|
9
|
+
such a cell was rejected as "not one character". `highlight` and
|
|
10
|
+
`excerpt` keep the selector inside the mark with its character. Rebuild
|
|
11
|
+
indexes whose text has variation selectors.
|
|
12
|
+
|
|
13
|
+
## 1.1.0 (2026-10-28)
|
|
14
|
+
|
|
15
|
+
- `variants` also takes table files of your own: one group per line,
|
|
16
|
+
tab-separated, the form to fold to first (the format of the itaiji lists
|
|
17
|
+
that collection databases keep). Paths are relative to the site's folder
|
|
18
|
+
(Jekyll, Bridgetown) or the current folder; the index records the path as
|
|
19
|
+
written and a digest of the contents. `Builder.load` and `Builder.read`
|
|
20
|
+
take `normalizer:` to read such an index from another folder.
|
|
21
|
+
- `Searcher.new(builder)` folds with the builder's normalizer.
|
|
22
|
+
|
|
3
23
|
## 1.0.0 (2026-10-28)
|
|
4
24
|
|
|
5
25
|
Indexes built with 0.x should be rebuilt: the `ja` table changed, and the
|
data/README.md
CHANGED
|
@@ -24,8 +24,8 @@ even when it is an exact title. cjk_index instead:
|
|
|
24
24
|
stores their positions, so a query matches only where its bigrams are
|
|
25
25
|
adjacent and in order: an exact substring match, with no dictionary or
|
|
26
26
|
native extension (the same idea as [kensaku](https://github.com/whispcat/kensaku));
|
|
27
|
-
- folds variants on both sides: NFKC, lowercase,
|
|
28
|
-
old-form kanji → modern form (「圖」→「図」), 々 expanded (「佐々木」→「佐佐木」),
|
|
27
|
+
- folds variants on both sides: NFKC, lowercase, variation selectors
|
|
28
|
+
dropped, katakana → hiragana, old-form kanji → modern form (「圖」→「図」), 々 expanded (「佐々木」→「佐佐木」),
|
|
29
29
|
and optionally Traditional ↔ Simplified Chinese (from OpenCC) and old
|
|
30
30
|
Hangul in the Private Use Area;
|
|
31
31
|
- ranks with BM25 per field, with field boosts;
|
|
@@ -366,6 +366,37 @@ by Unicode normalization. `ko` is for old Hangul stored in the Private Use
|
|
|
366
366
|
Area by HWP and the Hanyang fonts; a query typed in standard jamo then finds
|
|
367
367
|
and highlights it.
|
|
368
368
|
|
|
369
|
+
### A table of your own
|
|
370
|
+
|
|
371
|
+
Any entry of `variants` that is not one of the names above is a table file
|
|
372
|
+
of your own, relative to the site's folder (Jekyll, Bridgetown) or the
|
|
373
|
+
current folder (CLI, Rake, Ruby). One group per line, tab-separated: the
|
|
374
|
+
form to fold to, then its variants, one character each. This is the format
|
|
375
|
+
of the itaiji lists that collection databases often keep already.
|
|
376
|
+
|
|
377
|
+
```text
|
|
378
|
+
# itaiji.tsv
|
|
379
|
+
与 與
|
|
380
|
+
宴 醼 讌
|
|
381
|
+
嶋 嶌
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
```yaml
|
|
385
|
+
cjk_index:
|
|
386
|
+
variants: [ja, _data/itaiji.tsv]
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Each cell is folded first (NFKC, lowercase, variation selectors dropped,
|
|
390
|
+
katakana → hiragana), so a cell written as an ideographic variation sequence
|
|
391
|
+
(珊 + U+E0100) is the character itself, and the table listed first still
|
|
392
|
+
picks the form a group folds to. The index records
|
|
393
|
+
the entry as written (`_data/itaiji.tsv`), not the full path, and its digest
|
|
394
|
+
covers the file's contents, so editing the file means rebuilding the index.
|
|
395
|
+
To read such an index from another folder in Ruby, pass the normalizer:
|
|
396
|
+
`Builder.load(json, normalizer: CJKIndex::Normalizer.new(tables: [...], base: dir))`.
|
|
397
|
+
Wide lists merge more than spelling: a list that puts 燕 with 宴 makes
|
|
398
|
+
a search for 燕 (swallow) find 宴 (banquet). Check a list against your own material first.
|
|
399
|
+
|
|
369
400
|
Browser script size, gzipped: no table 6.3 KB, `ja` 9.0 KB, `ja, zh-safe`
|
|
370
401
|
24.1 KB, `ja, zh` 25.9 KB, `ja, ko` 36.1 KB.
|
|
371
402
|
|
data/lib/cjk_index/bridgetown.rb
CHANGED
|
@@ -11,7 +11,7 @@ module CJKIndex
|
|
|
11
11
|
# Bridgetown.configure do |config|
|
|
12
12
|
# init :cjk_index,
|
|
13
13
|
# output: "cjk-index", # folder inside the output (default)
|
|
14
|
-
# variants: ["ja"], # variant tables: ja (default), zh
|
|
14
|
+
# variants: ["ja"], # variant tables: ja (default), zh-safe, zh, ko, or a file
|
|
15
15
|
# indexes: [
|
|
16
16
|
# { name: "posts", # -> output/cjk-index/posts.json
|
|
17
17
|
# collection: "posts", # a collection (or a list of them), or...
|
|
@@ -37,7 +37,7 @@ module CJKIndex
|
|
|
37
37
|
def write(site, config)
|
|
38
38
|
config = stringify(config)
|
|
39
39
|
output = config.fetch("output", DEFAULT_OUTPUT).to_s.gsub(%r{\A/+|/+\z}, "")
|
|
40
|
-
normalizer = Normalizer.new(tables: Array(config.fetch("variants", Normalizer::DEFAULT_TABLES)))
|
|
40
|
+
normalizer = Normalizer.new(tables: Array(config.fetch("variants", Normalizer::DEFAULT_TABLES)), base: site.root_dir)
|
|
41
41
|
put(site, output, RUNTIME_NAME, Runtime.source(normalizer: normalizer))
|
|
42
42
|
Array(config["indexes"]).each do |spec|
|
|
43
43
|
builder = build(site, stringify(spec), normalizer)
|
data/lib/cjk_index/builder.rb
CHANGED
|
@@ -129,7 +129,9 @@ module CJKIndex
|
|
|
129
129
|
# Reads an index written by to_json (String) or to_h back, to update it.
|
|
130
130
|
# Reads formats 2, 3 and 4; a split index is read with read.
|
|
131
131
|
# A value filter keeps the form of each value that the index stored.
|
|
132
|
-
|
|
132
|
+
# normalizer: needed when the index uses a table file of your own and
|
|
133
|
+
# the current folder is not the one it was built from.
|
|
134
|
+
def self.load(index, normalizer: nil)
|
|
133
135
|
data = index.is_a?(String) ? JSON.parse(index) : index
|
|
134
136
|
unless data.is_a?(Hash) && FORMATS.include?(data["format"])
|
|
135
137
|
raise ArgumentError, "cjk_index: unsupported index format #{data.is_a?(Hash) ? data['format'].inspect : data.class}"
|
|
@@ -137,7 +139,7 @@ module CJKIndex
|
|
|
137
139
|
raise ArgumentError, "cjk_index: a split index is read with Builder.read(directory)" unless data["tokens"]
|
|
138
140
|
|
|
139
141
|
filters = (data["filters"] || {}).transform_values { |f| f.slice("kind", "separator") }
|
|
140
|
-
normalizer
|
|
142
|
+
normalizer ||= Normalizer.new(tables: data["variants"])
|
|
141
143
|
normalizer.check!(data)
|
|
142
144
|
builder = new(fields: data["fields"], boosts: data["fields"].zip(data["boosts"]).to_h,
|
|
143
145
|
normalizer: normalizer, filters: filters)
|
|
@@ -147,8 +149,8 @@ module CJKIndex
|
|
|
147
149
|
|
|
148
150
|
# Reads an index from a file (written by write or to_json) or from the
|
|
149
151
|
# directory of a split index (written by write_split), to update it.
|
|
150
|
-
def self.read(path)
|
|
151
|
-
load(Builder.read_data(path))
|
|
152
|
+
def self.read(path, normalizer: nil)
|
|
153
|
+
load(Builder.read_data(path), normalizer: normalizer)
|
|
152
154
|
end
|
|
153
155
|
|
|
154
156
|
# The index at path as one Hash with "tokens", for a file or a split
|
data/lib/cjk_index/cli.rb
CHANGED
|
@@ -73,7 +73,7 @@ module CJKIndex
|
|
|
73
73
|
name, spec = parse_filter(v)
|
|
74
74
|
opts[:filters][name] = spec
|
|
75
75
|
end
|
|
76
|
-
o.on("--variants LIST", Array, "variant tables: ja (default), zh-safe, zh, ko") { |v| opts[:variants] = v }
|
|
76
|
+
o.on("--variants LIST", Array, "variant tables: ja (default), zh-safe, zh, ko, or table files") { |v| opts[:variants] = v }
|
|
77
77
|
o.on("-o", "--output PATH", "index file (default: standard output)") { |v| output = v }
|
|
78
78
|
o.on("--runtime PATH", "also write the browser script for these variants") { |v| runtime = v }
|
|
79
79
|
end
|
|
@@ -196,7 +196,7 @@ module CJKIndex
|
|
|
196
196
|
output = nil
|
|
197
197
|
OptionParser.new do |o|
|
|
198
198
|
o.banner = "Usage: cjk_index runtime [options]"
|
|
199
|
-
o.on("--variants LIST", Array, "variant tables: ja (default), zh-safe, zh, ko; must match the index") { |v| variants = v }
|
|
199
|
+
o.on("--variants LIST", Array, "variant tables: ja (default), zh-safe, zh, ko, or table files; must match the index") { |v| variants = v }
|
|
200
200
|
o.on("-o", "--output PATH", "script file (default: standard output)") { |v| output = v }
|
|
201
201
|
end.parse!(argv)
|
|
202
202
|
raise Error, "too many arguments: #{argv.join(' ')}" unless argv.empty?
|
data/lib/cjk_index/jekyll.rb
CHANGED
|
@@ -9,7 +9,7 @@ module CJKIndex
|
|
|
9
9
|
#
|
|
10
10
|
# cjk_index:
|
|
11
11
|
# output: assets/cjk-index # where files are written (default)
|
|
12
|
-
# variants: [ja] # variant tables: ja (default), zh
|
|
12
|
+
# variants: [ja] # variant tables: ja (default), zh-safe, zh, ko, or a file of your own
|
|
13
13
|
# indexes:
|
|
14
14
|
# - name: posts # -> assets/cjk-index/posts.json
|
|
15
15
|
# collection: posts # a Jekyll collection, or...
|
|
@@ -64,7 +64,7 @@ module CJKIndex
|
|
|
64
64
|
return unless config.is_a?(Hash)
|
|
65
65
|
|
|
66
66
|
output = config.fetch("output", DEFAULT_OUTPUT).sub(%r{\A/+}, "").sub(%r{/+\z}, "")
|
|
67
|
-
normalizer = Normalizer.new(tables: Array(config.fetch("variants", Normalizer::DEFAULT_TABLES)))
|
|
67
|
+
normalizer = Normalizer.new(tables: Array(config.fetch("variants", Normalizer::DEFAULT_TABLES)), base: site.source)
|
|
68
68
|
site.static_files << GeneratedFile.new(site, output, RUNTIME_NAME, Runtime.source(normalizer: normalizer))
|
|
69
69
|
Array(config["indexes"]).each do |spec|
|
|
70
70
|
builder = build(site, spec, normalizer)
|
data/lib/cjk_index/normalizer.rb
CHANGED
|
@@ -5,9 +5,10 @@ require "json"
|
|
|
5
5
|
|
|
6
6
|
module CJKIndex
|
|
7
7
|
# Folds text so that spelling variants a reader treats as "the same" compare
|
|
8
|
-
# equal: NFKC (full-width -> half-width etc.), lowercase,
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
# equal: NFKC (full-width -> half-width etc.), lowercase, variation
|
|
9
|
+
# selectors dropped (珊 followed by an ideographic variation selector is 珊),
|
|
10
|
+
# katakana -> hiragana, character variants from the selected tables, and
|
|
11
|
+
# the iteration mark 々 expanded to the preceding character.
|
|
11
12
|
#
|
|
12
13
|
# The same rules run in the browser (see Runtime), so an index built here
|
|
13
14
|
# and a query typed there are folded identically.
|
|
@@ -24,10 +25,19 @@ module CJKIndex
|
|
|
24
25
|
# With several tables, characters linked by any table fold to one form,
|
|
25
26
|
# preferring the target of the table listed first: with [ja, zh],
|
|
26
27
|
# 圖, 図 and 图 all become 図.
|
|
28
|
+
# Any other entry names a table file of your own, relative to base (the
|
|
29
|
+
# site's folder for Jekyll and Bridgetown, the current folder otherwise).
|
|
30
|
+
# The index records the entry as written, not the resolved path.
|
|
27
31
|
#
|
|
28
32
|
# Table format: UTF-8 lines "variant<TAB>folded", optionally followed by
|
|
29
33
|
# another tab and a comment column; lines starting with "#" are comments.
|
|
30
34
|
# "folded" may be several characters.
|
|
35
|
+
#
|
|
36
|
+
# Your own table is one group per line, tab-separated: the form to fold to,
|
|
37
|
+
# then its variants, one character each (the format of the itaiji lists
|
|
38
|
+
# that collection databases keep). Each cell is folded first (NFKC,
|
|
39
|
+
# lowercase, katakana -> hiragana), so a compatibility ideograph such as
|
|
40
|
+
# U+F900 joins the group of the character NFKC makes of it.
|
|
31
41
|
class Normalizer
|
|
32
42
|
DATA_DIR = File.expand_path("../../data", __dir__)
|
|
33
43
|
TABLES = {
|
|
@@ -43,16 +53,48 @@ module CJKIndex
|
|
|
43
53
|
KATAKANA_LAST = 0x30F6
|
|
44
54
|
KANA_OFFSET = 0x60
|
|
45
55
|
ITERATION_MARK = "々"
|
|
56
|
+
# Variation selectors (VS1-VS16 and the ideographic VS17-VS256) only pick
|
|
57
|
+
# a glyph of the character before them: 珊 with or without U+E0100 is the
|
|
58
|
+
# same character to a reader, so they are dropped, not kept as a run of
|
|
59
|
+
# their own between two CJK characters. In String#delete form.
|
|
60
|
+
VARIATION_SELECTORS = "\u{FE00}-\u{FE0F}\u{E0100}-\u{E01EF}"
|
|
46
61
|
|
|
47
62
|
def self.default
|
|
48
63
|
@default ||= new
|
|
49
64
|
end
|
|
50
65
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
# Pairs [variant, folded] of a built-in table, or of a table file of your
|
|
67
|
+
# own (a name that is not built in, resolved against base).
|
|
68
|
+
def self.read_table(name, base: Dir.pwd)
|
|
69
|
+
return read_groups(name, File.expand_path(name, base)) unless TABLES.key?(name)
|
|
70
|
+
|
|
71
|
+
lines(File.join(DATA_DIR, TABLES[name])).map { |_, line| line.split("\t")[0, 2] }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.lines(path) # :nodoc:
|
|
75
|
+
File.readlines(path, chomp: true, encoding: "UTF-8").each_with_index
|
|
76
|
+
.reject { |line, _| line.strip.empty? || line.start_with?("#") }
|
|
77
|
+
.map { |line, i| [i + 1, line.delete_prefix("\uFEFF")] }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.read_groups(name, path) # :nodoc:
|
|
81
|
+
unless File.file?(path)
|
|
82
|
+
raise ArgumentError, "unknown variant table #{name.inspect}: not one of #{TABLES.keys.join(', ')}, " \
|
|
83
|
+
"and no file at #{path}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
plain = new(tables: [])
|
|
87
|
+
lines(path).flat_map do |number, line|
|
|
88
|
+
target, *variants = line.split("\t").map(&:strip).reject(&:empty?).map { |cell| plain.normalize(cell) }
|
|
89
|
+
variants = variants.uniq.reject { |v| v == target } # NFKC may already fold a cell (㍿)
|
|
90
|
+
variants.each do |v|
|
|
91
|
+
next if v.length == 1
|
|
92
|
+
|
|
93
|
+
raise ArgumentError, "#{name}:#{number}: #{v.inspect} is not one character; " \
|
|
94
|
+
"only the first cell (the form to fold to) may be longer"
|
|
95
|
+
end
|
|
96
|
+
variants.map { |v| [v, target] }
|
|
97
|
+
end
|
|
56
98
|
end
|
|
57
99
|
|
|
58
100
|
attr_reader :tables, :variants, :digest
|
|
@@ -60,8 +102,9 @@ module CJKIndex
|
|
|
60
102
|
KATAKANA = "#{KATAKANA_FIRST.chr(Encoding::UTF_8)}-#{KATAKANA_LAST.chr(Encoding::UTF_8)}"
|
|
61
103
|
HIRAGANA = "#{(KATAKANA_FIRST - KANA_OFFSET).chr(Encoding::UTF_8)}-#{(KATAKANA_LAST - KANA_OFFSET).chr(Encoding::UTF_8)}"
|
|
62
104
|
|
|
63
|
-
def initialize(tables: DEFAULT_TABLES)
|
|
105
|
+
def initialize(tables: DEFAULT_TABLES, base: Dir.pwd)
|
|
64
106
|
@tables = tables.map(&:to_s).uniq.freeze
|
|
107
|
+
@base = base
|
|
65
108
|
@variants = build_variants.freeze
|
|
66
109
|
keys = @variants.keys.select { |k| k.length == 1 }
|
|
67
110
|
@variant_chars = keys.empty? ? nil : Regexp.new("[#{keys.map { |k| format('\\u{%x}', k.ord) }.join}]")
|
|
@@ -84,14 +127,16 @@ module CJKIndex
|
|
|
84
127
|
"#{tables.inspect} (an older cjk_index?); rebuild it"
|
|
85
128
|
end
|
|
86
129
|
|
|
87
|
-
# Character by character:
|
|
88
|
-
# then 々 to the (folded) character
|
|
130
|
+
# Character by character: variation selectors dropped, katakana to
|
|
131
|
+
# hiragana, then the variant tables, then 々 to the (folded) character
|
|
132
|
+
# before it. Done as passes over the
|
|
89
133
|
# whole string, which gives the same result as one loop over the
|
|
90
134
|
# characters (the loop in runtime.js) and is several times faster in Ruby.
|
|
91
135
|
def normalize(text)
|
|
92
136
|
return "" if text.nil?
|
|
93
137
|
|
|
94
138
|
s = text.to_s.unicode_normalize(:nfkc).downcase
|
|
139
|
+
s = s.delete(VARIATION_SELECTORS)
|
|
95
140
|
s = s.tr(KATAKANA, HIRAGANA)
|
|
96
141
|
# 々 repeats the folded character before it, which may be several
|
|
97
142
|
# characters (ko): fold one character at a time, as runtime.js does
|
|
@@ -122,7 +167,7 @@ module CJKIndex
|
|
|
122
167
|
end
|
|
123
168
|
rank = {}
|
|
124
169
|
@tables.each_with_index do |name, priority|
|
|
125
|
-
self.class.read_table(name).each do |from, to|
|
|
170
|
+
self.class.read_table(name, base: @base).each do |from, to|
|
|
126
171
|
parent[find.call(from)] = find.call(to)
|
|
127
172
|
rank[to] = [rank.fetch(to, [priority, to.ord]), [priority, to.ord]].min
|
|
128
173
|
end
|
data/lib/cjk_index/runtime.js
CHANGED
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
// Score multiplier for matches found by prefix or typo expansion.
|
|
60
60
|
var EXPANDED = 0.6;
|
|
61
61
|
|
|
62
|
+
// Variation selectors (VS1-VS16, VS17-VS256): dropped, as
|
|
63
|
+
// CJKIndex::Normalizer::VARIATION_SELECTORS in Ruby.
|
|
64
|
+
var SELECTOR = /^[\ufe00-\ufe0f\u{e0100}-\u{e01ef}]$/u;
|
|
65
|
+
|
|
62
66
|
// Folds one character; prev is the previous folded character (for 々).
|
|
63
67
|
function foldChar(ch, prev) {
|
|
64
68
|
var c = ch.codePointAt(0);
|
|
@@ -73,6 +77,7 @@
|
|
|
73
77
|
var out = '';
|
|
74
78
|
var prev = null;
|
|
75
79
|
for (var ch of String(text).normalize('NFKC').toLowerCase()) {
|
|
80
|
+
if (SELECTOR.test(ch)) continue;
|
|
76
81
|
prev = foldChar(ch, prev);
|
|
77
82
|
out += prev;
|
|
78
83
|
}
|
|
@@ -534,11 +539,16 @@
|
|
|
534
539
|
// Each folded code point remembers which original characters it came
|
|
535
540
|
// from. A character may fold to several (ko: one Private Use Area
|
|
536
541
|
// character -> a jamo sequence), and a cluster may compose into fewer.
|
|
542
|
+
// A character that folds to nothing (a variation selector) belongs to
|
|
543
|
+
// the one before it: a match on 珊 marks its selector too.
|
|
537
544
|
function emit(str, from, to) {
|
|
545
|
+
var before = norm.length;
|
|
538
546
|
for (var c of str.normalize('NFKC').toLowerCase()) {
|
|
547
|
+
if (SELECTOR.test(c)) continue;
|
|
539
548
|
prev = foldChar(c, prev);
|
|
540
549
|
for (var p of prev) { norm.push(p); start.push(from); end.push(to); }
|
|
541
550
|
}
|
|
551
|
+
if (norm.length === before && end.length) end[end.length - 1] = to;
|
|
542
552
|
}
|
|
543
553
|
for (var i = 0, j; i < chars.length; i = j) {
|
|
544
554
|
for (j = i + 1; j < chars.length && JOINS_PREVIOUS.test(chars[j]); j++);
|
data/lib/cjk_index/searcher.rb
CHANGED
|
@@ -57,7 +57,7 @@ module CJKIndex
|
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
@normalizer = normalizer || Normalizer.new(tables: data["variants"])
|
|
60
|
+
@normalizer = normalizer || @builder&.normalizer || Normalizer.new(tables: data["variants"])
|
|
61
61
|
@normalizer.check!(data)
|
|
62
62
|
|
|
63
63
|
if data["shards"] && !@load_shard
|
|
@@ -353,19 +353,23 @@ module CJKIndex
|
|
|
353
353
|
chars = text.to_s.chars
|
|
354
354
|
norm = +""
|
|
355
355
|
origin = []
|
|
356
|
+
ends = []
|
|
356
357
|
chars.each_with_index do |ch, i|
|
|
357
358
|
folded = normalizer.normalize(ch)
|
|
358
359
|
# 々 depends on the previous character, so fold it in context.
|
|
359
360
|
folded = norm[-1] if folded == Normalizer::ITERATION_MARK && !norm.empty?
|
|
361
|
+
# A character that folds to nothing (a variation selector) belongs to
|
|
362
|
+
# the one before it: a match on 珊 marks its selector too.
|
|
363
|
+
ends[-1] = i + 1 if folded.empty? && !ends.empty?
|
|
360
364
|
norm << folded
|
|
361
|
-
folded.length.times { origin << i }
|
|
365
|
+
folded.length.times { origin << i; ends << i + 1 }
|
|
362
366
|
end
|
|
363
367
|
needles = normalizer.normalize(query).split(Tokenizer::SEPARATOR).reject(&:empty?)
|
|
364
368
|
ranges = needles.flat_map do |needle|
|
|
365
369
|
found = []
|
|
366
370
|
from = 0
|
|
367
371
|
while (at = norm.index(needle, from))
|
|
368
|
-
found << [origin[at],
|
|
372
|
+
found << [origin[at], ends[at + needle.length - 1]]
|
|
369
373
|
from = at + needle.length
|
|
370
374
|
end
|
|
371
375
|
found
|
data/lib/cjk_index/version.rb
CHANGED