forked from apache/lucenenet
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathIndicNormalizer.cs
More file actions
393 lines (368 loc) · 19.8 KB
/
Copy pathIndicNormalizer.cs
File metadata and controls
393 lines (368 loc) · 19.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
// Lucene version compatibility level 4.8.1
using Lucene.Net.Analysis.Util;
using Lucene.Net.Util;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Lucene.Net.Analysis.In
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <summary>
/// Normalizes the Unicode representation of text in Indian languages.
/// <para>
/// Follows guidelines from Unicode 5.2, chapter 6, South Asian Scripts I
/// and graphical decompositions from http://ldc.upenn.edu/myl/IndianScriptsUnicode.html
/// </para>
/// </summary>
public class IndicNormalizer
{
// LUCENENET NOTE: This class was refactored from its Java counterpart,
// favoring the .NET Regex class to determine the "Unicode Block" rather than
// porting over that part of the Java Character class.
// References:
// https://msdn.microsoft.com/en-us/library/20bw873z.aspx#SupportedNamedBlocks
// http://stackoverflow.com/a/11414800/181087
private class ScriptData
{
internal readonly Regex block;
internal readonly UnicodeBlock flag;
internal readonly int @base;
internal OpenBitSet decompMask;
internal ScriptData(Regex block, UnicodeBlock flag, int @base)
{
this.block = block;
this.flag = flag;
this.@base = @base;
}
}
// LUCENENET: scripts moved below declaration of decompositions so it can be populated inline
[Flags]
internal enum UnicodeBlock
{
DEVANAGARI = 1,
BENGALI = 2,
GURMUKHI = 4,
GUJARATI = 8,
ORIYA = 16,
TAMIL = 32,
TELUGU = 64,
KANNADA = 128,
MALAYALAM = 256
}
// LUCENENET: static initialization done inline instead of in constructor
/// <summary>
/// Decompositions according to Unicode 5.2,
/// and http://ldc.upenn.edu/myl/IndianScriptsUnicode.html
/// <para/>
/// Most of these are not handled by unicode normalization anyway.
/// <para/>
/// The numbers here represent offsets into the respective codepages,
/// with -1 representing null and 0xFF representing zero-width joiner.
/// <para/>
/// the columns are: ch1, ch2, ch3, res, flags
/// ch1, ch2, and ch3 are the decomposition
/// res is the composition, and flags are the scripts to which it applies.
/// </summary>
private static readonly int[][] decompositions = new int[][] // LUCENENET: Avoid static constructors (see https://github.com/apache/lucenenet/pull/224#issuecomment-469284006)
{
/* devanagari, gujarati vowel candra O */
new int[] { 0x05, 0x3E, 0x45, 0x11, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GUJARATI },
/* devanagari short O */
new int[] { 0x05, 0x3E, 0x46, 0x12, (int)UnicodeBlock.DEVANAGARI },
/* devanagari, gujarati letter O */
new int[] { 0x05, 0x3E, 0x47, 0x13, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GUJARATI },
/* devanagari letter AI, gujarati letter AU */
new int[] { 0x05, 0x3E, 0x48, 0x14, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GUJARATI },
/* devanagari, bengali, gurmukhi, gujarati, oriya AA */
new int[] { 0x05, 0x3E, -1, 0x06, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.BENGALI | (int)UnicodeBlock.GURMUKHI | (int)UnicodeBlock.GUJARATI | (int)UnicodeBlock.ORIYA },
/* devanagari letter candra A */
new int[] { 0x05, 0x45, -1, 0x72, (int)UnicodeBlock.DEVANAGARI },
/* gujarati vowel candra E */
new int[] { 0x05, 0x45, -1, 0x0D, (int)UnicodeBlock.GUJARATI },
/* devanagari letter short A */
new int[] { 0x05, 0x46, -1, 0x04, (int)UnicodeBlock.DEVANAGARI },
/* gujarati letter E */
new int[] { 0x05, 0x47, -1, 0x0F, (int)UnicodeBlock.GUJARATI },
/* gurmukhi, gujarati letter AI */
new int[] { 0x05, 0x48, -1, 0x10, (int)UnicodeBlock.GURMUKHI | (int)UnicodeBlock.GUJARATI },
/* devanagari, gujarati vowel candra O */
new int[] { 0x05, 0x49, -1, 0x11, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GUJARATI },
/* devanagari short O */
new int[] { 0x05, 0x4A, -1, 0x12, (int)UnicodeBlock.DEVANAGARI },
/* devanagari, gujarati letter O */
new int[] { 0x05, 0x4B, -1, 0x13, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GUJARATI },
/* devanagari letter AI, gurmukhi letter AU, gujarati letter AU */
new int[] { 0x05, 0x4C, -1, 0x14, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GURMUKHI | (int)UnicodeBlock.GUJARATI },
/* devanagari, gujarati vowel candra O */
new int[] { 0x06, 0x45, -1, 0x11, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GUJARATI },
/* devanagari short O */
new int[] { 0x06, 0x46, -1, 0x12, (int)UnicodeBlock.DEVANAGARI },
/* devanagari, gujarati letter O */
new int[] { 0x06, 0x47, -1, 0x13, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GUJARATI },
/* devanagari letter AI, gujarati letter AU */
new int[] { 0x06, 0x48, -1, 0x14, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GUJARATI },
/* malayalam letter II */
new int[] { 0x07, 0x57, -1, 0x08, (int)UnicodeBlock.MALAYALAM },
/* devanagari letter UU */
new int[] { 0x09, 0x41, -1, 0x0A, (int)UnicodeBlock.DEVANAGARI },
/* tamil, malayalam letter UU (some styles) */
new int[] { 0x09, 0x57, -1, 0x0A, (int)UnicodeBlock.TAMIL | (int)UnicodeBlock.MALAYALAM },
/* malayalam letter AI */
new int[] { 0x0E, 0x46, -1, 0x10, (int)UnicodeBlock.MALAYALAM },
/* devanagari candra E */
new int[] { 0x0F, 0x45, -1, 0x0D, (int)UnicodeBlock.DEVANAGARI },
/* devanagari short E */
new int[] { 0x0F, 0x46, -1, 0x0E, (int)UnicodeBlock.DEVANAGARI },
/* devanagari AI */
new int[] { 0x0F, 0x47, -1, 0x10, (int)UnicodeBlock.DEVANAGARI },
/* oriya AI */
new int[] { 0x0F, 0x57, -1, 0x10, (int)UnicodeBlock.ORIYA },
/* malayalam letter OO */
new int[] { 0x12, 0x3E, -1, 0x13, (int)UnicodeBlock.MALAYALAM },
/* telugu, kannada letter AU */
new int[] { 0x12, 0x4C, -1, 0x14, (int)UnicodeBlock.TELUGU | (int)UnicodeBlock.KANNADA },
/* telugu letter OO */
new int[] { 0x12, 0x55, -1, 0x13, (int)UnicodeBlock.TELUGU },
/* tamil, malayalam letter AU */
new int[] { 0x12, 0x57, -1, 0x14, (int)UnicodeBlock.TAMIL | (int)UnicodeBlock.MALAYALAM },
/* oriya letter AU */
new int[] { 0x13, 0x57, -1, 0x14, (int)UnicodeBlock.ORIYA },
/* devanagari qa */
new int[] { 0x15, 0x3C, -1, 0x58, (int)UnicodeBlock.DEVANAGARI },
/* devanagari, gurmukhi khha */
new int[] { 0x16, 0x3C, -1, 0x59, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GURMUKHI },
/* devanagari, gurmukhi ghha */
new int[] { 0x17, 0x3C, -1, 0x5A, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GURMUKHI },
/* devanagari, gurmukhi za */
new int[] { 0x1C, 0x3C, -1, 0x5B, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GURMUKHI },
/* devanagari dddha, bengali, oriya rra */
new int[] { 0x21, 0x3C, -1, 0x5C, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.BENGALI | (int)UnicodeBlock.ORIYA },
/* devanagari, bengali, oriya rha */
new int[] { 0x22, 0x3C, -1, 0x5D, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.BENGALI | (int)UnicodeBlock.ORIYA },
/* malayalam chillu nn */
new int[] { 0x23, 0x4D, 0xFF, 0x7A, (int)UnicodeBlock.MALAYALAM },
/* bengali khanda ta */
new int[] { 0x24, 0x4D, 0xFF, 0x4E, (int)UnicodeBlock.BENGALI },
/* devanagari nnna */
new int[] { 0x28, 0x3C, -1, 0x29, (int)UnicodeBlock.DEVANAGARI },
/* malayalam chillu n */
new int[] { 0x28, 0x4D, 0xFF, 0x7B, (int)UnicodeBlock.MALAYALAM },
/* devanagari, gurmukhi fa */
new int[] { 0x2B, 0x3C, -1, 0x5E, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GURMUKHI },
/* devanagari, bengali yya */
new int[] { 0x2F, 0x3C, -1, 0x5F, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.BENGALI },
/* telugu letter vocalic R */
new int[] { 0x2C, 0x41, 0x41, 0x0B, (int)UnicodeBlock.TELUGU },
/* devanagari rra */
new int[] { 0x30, 0x3C, -1, 0x31, (int)UnicodeBlock.DEVANAGARI },
/* malayalam chillu rr */
new int[] { 0x30, 0x4D, 0xFF, 0x7C, (int)UnicodeBlock.MALAYALAM },
/* malayalam chillu l */
new int[] { 0x32, 0x4D, 0xFF, 0x7D, (int)UnicodeBlock.MALAYALAM },
/* devanagari llla */
new int[] { 0x33, 0x3C, -1, 0x34, (int)UnicodeBlock.DEVANAGARI },
/* malayalam chillu ll */
new int[] { 0x33, 0x4D, 0xFF, 0x7E, (int)UnicodeBlock.MALAYALAM },
/* telugu letter MA */
new int[] { 0x35, 0x41, -1, 0x2E, (int)UnicodeBlock.TELUGU },
/* devanagari, gujarati vowel sign candra O */
new int[] { 0x3E, 0x45, -1, 0x49, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GUJARATI },
/* devanagari vowel sign short O */
new int[] { 0x3E, 0x46, -1, 0x4A, (int)UnicodeBlock.DEVANAGARI },
/* devanagari, gujarati vowel sign O */
new int[] { 0x3E, 0x47, -1, 0x4B, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GUJARATI },
/* devanagari, gujarati vowel sign AU */
new int[] { 0x3E, 0x48, -1, 0x4C, (int)UnicodeBlock.DEVANAGARI | (int)UnicodeBlock.GUJARATI },
/* kannada vowel sign II */
new int[] { 0x3F, 0x55, -1, 0x40, (int)UnicodeBlock.KANNADA },
/* gurmukhi vowel sign UU (when stacking) */
new int[] { 0x41, 0x41, -1, 0x42, (int)UnicodeBlock.GURMUKHI },
/* tamil, malayalam vowel sign O */
new int[] { 0x46, 0x3E, -1, 0x4A, (int)UnicodeBlock.TAMIL | (int)UnicodeBlock.MALAYALAM },
/* kannada vowel sign OO */
new int[] { 0x46, 0x42, 0x55, 0x4B, (int)UnicodeBlock.KANNADA },
/* kannada vowel sign O */
new int[] { 0x46, 0x42, -1, 0x4A, (int)UnicodeBlock.KANNADA },
/* malayalam vowel sign AI (if reordered twice) */
new int[] { 0x46, 0x46, -1, 0x48, (int)UnicodeBlock.MALAYALAM },
/* telugu, kannada vowel sign EE */
new int[] { 0x46, 0x55, -1, 0x47, (int)UnicodeBlock.TELUGU | (int)UnicodeBlock.KANNADA },
/* telugu, kannada vowel sign AI */
new int[] { 0x46, 0x56, -1, 0x48, (int)UnicodeBlock.TELUGU | (int)UnicodeBlock.KANNADA },
/* tamil, malayalam vowel sign AU */
new int[] { 0x46, 0x57, -1, 0x4C, (int)UnicodeBlock.TAMIL | (int)UnicodeBlock.MALAYALAM },
/* bengali, oriya vowel sign O, tamil, malayalam vowel sign OO */
new int[] { 0x47, 0x3E, -1, 0x4B, (int)UnicodeBlock.BENGALI | (int)UnicodeBlock.ORIYA | (int)UnicodeBlock.TAMIL | (int)UnicodeBlock.MALAYALAM },
/* bengali, oriya vowel sign AU */
new int[] { 0x47, 0x57, -1, 0x4C, (int)UnicodeBlock.BENGALI | (int)UnicodeBlock.ORIYA },
/* kannada vowel sign OO */
new int[] { 0x4A, 0x55, -1, 0x4B, (int)UnicodeBlock.KANNADA },
/* gurmukhi letter I */
new int[] { 0x72, 0x3F, -1, 0x07, (int)UnicodeBlock.GURMUKHI },
/* gurmukhi letter II */
new int[] { 0x72, 0x40, -1, 0x08, (int)UnicodeBlock.GURMUKHI },
/* gurmukhi letter EE */
new int[] { 0x72, 0x47, -1, 0x0F, (int)UnicodeBlock.GURMUKHI },
/* gurmukhi letter U */
new int[] { 0x73, 0x41, -1, 0x09, (int)UnicodeBlock.GURMUKHI },
/* gurmukhi letter UU */
new int[] { 0x73, 0x42, -1, 0x0A, (int)UnicodeBlock.GURMUKHI },
/* gurmukhi letter OO */
new int[] { 0x73, 0x4B, -1, 0x13, (int)UnicodeBlock.GURMUKHI }
};
private static readonly IList<ScriptData> scripts = LoadScripts(); // LUCENENET: Avoid static constructors (see https://github.com/apache/lucenenet/pull/224#issuecomment-469284006)
private static IList<ScriptData> LoadScripts()
{
IList<ScriptData> result = new List<ScriptData>(capacity: 9)
{
new ScriptData(new Regex(@"\p{IsDevanagari}", RegexOptions.Compiled), UnicodeBlock.DEVANAGARI, 0x0900),
new ScriptData(new Regex(@"\p{IsBengali}", RegexOptions.Compiled), UnicodeBlock.BENGALI, 0x0980),
new ScriptData(new Regex(@"\p{IsGurmukhi}", RegexOptions.Compiled), UnicodeBlock.GURMUKHI, 0x0A00),
new ScriptData(new Regex(@"\p{IsGujarati}", RegexOptions.Compiled), UnicodeBlock.GUJARATI, 0x0A80),
new ScriptData(new Regex(@"\p{IsOriya}", RegexOptions.Compiled), UnicodeBlock.ORIYA, 0x0B00),
new ScriptData(new Regex(@"\p{IsTamil}", RegexOptions.Compiled), UnicodeBlock.TAMIL, 0x0B80),
new ScriptData(new Regex(@"\p{IsTelugu}", RegexOptions.Compiled), UnicodeBlock.TELUGU, 0x0C00),
new ScriptData(new Regex(@"\p{IsKannada}", RegexOptions.Compiled), UnicodeBlock.KANNADA, 0x0C80),
new ScriptData(new Regex(@"\p{IsMalayalam}", RegexOptions.Compiled), UnicodeBlock.MALAYALAM, 0x0D00),
};
foreach (ScriptData sd in result)
{
sd.decompMask = new OpenBitSet(0x7F);
for (int i = 0; i < decompositions.Length; i++)
{
int ch = decompositions[i][0];
int flags = decompositions[i][4];
if ((flags & (int)sd.flag) != 0)
{
sd.decompMask.Set(ch);
}
}
}
return result;
}
/// <summary>
/// Normalizes input text, and returns the new length.
/// The length will always be less than or equal to the existing length.
/// </summary>
/// <param name="text"> input text </param>
/// <param name="len"> valid length </param>
/// <returns> normalized length </returns>
public virtual int Normalize(char[] text, int len)
{
for (int i = 0; i < len; i++)
{
Regex block;
if ((block = GetBlockForChar(text[i], out ScriptData sd)) != unknownScript)
{
int ch = text[i] - sd.@base;
if (sd.decompMask.Get(ch))
{
len = Compose(ch, block, sd, text, i, len);
}
}
}
return len;
}
/// <summary>
/// Compose into standard form any compositions in the decompositions table.
/// </summary>
private static int Compose(int ch0, Regex block0, ScriptData sd, char[] text, int pos, int len) // LUCENENET: CA1822: Mark members as static
{
if (pos + 1 >= len) // need at least 2 chars!
{
return len;
}
int ch1 = text[pos + 1] - sd.@base;
var block1 = GetBlockForChar(text[pos + 1], out _);
if (block1 != block0) // needs to be the same writing system
{
return len;
}
int ch2 = -1;
if (pos + 2 < len)
{
ch2 = text[pos + 2] - sd.@base;
var block2 = GetBlockForChar(text[pos + 2], out _);
if (text[pos + 2] == '\u200D') // ZWJ
{
ch2 = 0xFF;
}
else if (block2 != block1) // still allow a 2-char match
{
ch2 = -1;
}
}
for (int i = 0; i < decompositions.Length; i++)
{
if (decompositions[i][0] == ch0 && (decompositions[i][4] & (int)sd.flag) != 0)
{
if (decompositions[i][1] == ch1 && (decompositions[i][2] < 0 || decompositions[i][2] == ch2))
{
text[pos] = (char)(sd.@base + decompositions[i][3]);
len = StemmerUtil.Delete(text, pos + 1, len);
if (decompositions[i][2] >= 0)
{
len = StemmerUtil.Delete(text, pos + 1, len);
}
return len;
}
}
}
return len;
}
// LUCENENET: Never matches - we just use this as a placeholder
private static readonly Regex unknownScript = new Regex(@"[^\S\s]", RegexOptions.Compiled);
[ThreadStatic]
private static ScriptData previousScriptData;
/// <summary>
/// LUCENENET: Returns the unicode block for the specified character. Caches the
/// last script and script data used on the current thread to optimize performance
/// when not switching between scripts.
/// </summary>
private static Regex GetBlockForChar(char c, out ScriptData scriptData) // LUCENENET: CA1822: Mark members as static
{
string charAsString = c.ToString();
// Store reference locally to avoid threading issues
ScriptData previousScriptDataLocal = previousScriptData;
Regex previousScript = previousScriptDataLocal?.block;
// Optimize to try the most recent script first.
if (previousScript?.IsMatch(charAsString) ?? false)
{
scriptData = previousScriptDataLocal;
return previousScript;
}
return GetBlockForCharSlow(previousScript, charAsString, out scriptData);
static Regex GetBlockForCharSlow(Regex previousScript, string charAsString, out ScriptData scriptData)
{
foreach (var script in scripts)
{
Regex block = script.block;
if (block != previousScript && block.IsMatch(charAsString))
{
previousScriptData = script;
scriptData = script;
return block;
}
}
scriptData = null;
// return a regex that never matches, nor is in our scripts dictionary
return unknownScript;
}
}
}
}