cjk_index 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8780b3265cb3e02580edd66360721d6a9ea245c3b8465c49e499e0b1bb6d420
4
- data.tar.gz: a9c1233d9e57d170ba90c25e7e0ecf9af6b10e89640584855dbe6fae5b358418
3
+ metadata.gz: 615a0f6d44d6e04e21f66aeb15778e39adf566ec078f11c69ceecf41473e1388
4
+ data.tar.gz: 7a106a769e2cc57e382727c8ada1afbba691fbdb7620c0f487eddbe31610abf8
5
5
  SHA512:
6
- metadata.gz: 205fff662f662ae191a3bb437351531473bcbb8943758d13b7b6593aeabb425814fb8a4cc779f5a028b3d0ff2c6c895ab90efb49ed6d9a6423d31f63f62bb8ed
7
- data.tar.gz: 4a82cf20daf35f34bb5596645e373e4c1902722f5fd91c6541a77c8039f6d97a97d8421c68d20761c29914140d4e8201aa3d5409b883458ff2417deb633dde6b
6
+ metadata.gz: 2783f62f61930fbd740261d552d7b8092b098b1e15a4fe28741b37f766b9670f5e66045c994da3bcff2b16644f28772b681b8f6fd70b28440f8dd58571e0759e
7
+ data.tar.gz: c1faf175e55309bf62568d7215bee743fe7a4f0942d9621e1ad8f846e9082393dd51c38ef2ef141bdada33ce887a253d4ae533e812abe6e07cf59423901e3638
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
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
+
3
13
  ## 1.1.0 (2026-10-28)
4
14
 
5
15
  - `variants` also takes table files of your own: one group per line,
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, katakana → hiragana,
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;
@@ -386,8 +386,10 @@ cjk_index:
386
386
  variants: [ja, _data/itaiji.tsv]
387
387
  ```
388
388
 
389
- Each cell is folded first (NFKC, lowercase, katakana → hiragana), and the
390
- table listed first still picks the form a group folds to. The index records
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
391
393
  the entry as written (`_data/itaiji.tsv`), not the full path, and its digest
392
394
  covers the file's contents, so editing the file means rebuilding the index.
393
395
  To read such an index from another folder in Ruby, pass the normalizer:
@@ -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, katakana ->
9
- # hiragana, character variants from the selected tables, and the iteration
10
- # mark 々 expanded to the preceding character.
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.
@@ -52,6 +53,11 @@ module CJKIndex
52
53
  KATAKANA_LAST = 0x30F6
53
54
  KANA_OFFSET = 0x60
54
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}"
55
61
 
56
62
  def self.default
57
63
  @default ||= new
@@ -121,14 +127,16 @@ module CJKIndex
121
127
  "#{tables.inspect} (an older cjk_index?); rebuild it"
122
128
  end
123
129
 
124
- # Character by character: katakana to hiragana, then the variant tables,
125
- # then 々 to the (folded) character before it. Done as passes over the
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
126
133
  # whole string, which gives the same result as one loop over the
127
134
  # characters (the loop in runtime.js) and is several times faster in Ruby.
128
135
  def normalize(text)
129
136
  return "" if text.nil?
130
137
 
131
138
  s = text.to_s.unicode_normalize(:nfkc).downcase
139
+ s = s.delete(VARIATION_SELECTORS)
132
140
  s = s.tr(KATAKANA, HIRAGANA)
133
141
  # 々 repeats the folded character before it, which may be several
134
142
  # characters (ko): fold one character at a time, as runtime.js does
@@ -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++);
@@ -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], origin[at + needle.length - 1] + 1]
372
+ found << [origin[at], ends[at + needle.length - 1]]
369
373
  from = at + needle.length
370
374
  end
371
375
  found
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CJKIndex
4
- VERSION = "1.1.0"
4
+ VERSION = "1.1.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cjk_index
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Satoru Nakamura