@valueerror/ltjs-lang-fr 0.0.0-stage → 0.1.0

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.
Files changed (69) hide show
  1. package/LICENSE +232 -0
  2. package/NOTICE +9 -0
  3. package/README.md +25 -2
  4. package/index.js +24 -0
  5. package/licenses/LGPL-2.1.txt +504 -0
  6. package/licenses/french-pos-dict.pom.xml +115 -0
  7. package/licenses/resource_fr_README_lexique.txt +30 -0
  8. package/licenses/resource_fr_hunspell_README_fr.txt +73 -0
  9. package/package.json +60 -4
  10. package/resource-root.js +4 -0
  11. package/resources/META-INF/org/languagetool/language-module.properties +1 -0
  12. package/resources/org/languagetool/resource/fr/README_lexique.txt +30 -0
  13. package/resources/org/languagetool/resource/fr/added.txt +477 -0
  14. package/resources/org/languagetool/resource/fr/added_custom.txt +4 -0
  15. package/resources/org/languagetool/resource/fr/compounds.txt +1443 -0
  16. package/resources/org/languagetool/resource/fr/confusion_sets.txt +76 -0
  17. package/resources/org/languagetool/resource/fr/disambiguation.xml +11391 -0
  18. package/resources/org/languagetool/resource/fr/fr.sor +162 -0
  19. package/resources/org/languagetool/resource/fr/french.dict +0 -0
  20. package/resources/org/languagetool/resource/fr/french.info +20 -0
  21. package/resources/org/languagetool/resource/fr/french_synth.dict +0 -0
  22. package/resources/org/languagetool/resource/fr/french_synth.info +4 -0
  23. package/resources/org/languagetool/resource/fr/french_tags.txt +268 -0
  24. package/resources/org/languagetool/resource/fr/hunspell/README_fr.txt +73 -0
  25. package/resources/org/languagetool/resource/fr/hunspell/ignore.txt +1551 -0
  26. package/resources/org/languagetool/resource/fr/hunspell/prohibit.txt +3 -0
  27. package/resources/org/languagetool/resource/fr/hunspell/spelling.txt +34735 -0
  28. package/resources/org/languagetool/resource/fr/hunspell/spelling_custom.txt +1 -0
  29. package/resources/org/languagetool/resource/fr/hyphenated_words.txt +12864 -0
  30. package/resources/org/languagetool/resource/fr/multiwords.txt +4886 -0
  31. package/resources/org/languagetool/resource/fr/removed.txt +5 -0
  32. package/resources/org/languagetool/resource/fr/removed_custom.txt +4 -0
  33. package/resources/org/languagetool/rules/fr/grammar.xml +151570 -0
  34. package/resources/org/languagetool/rules/fr/remote-rule-filters.xml +820 -0
  35. package/resources/org/languagetool/rules/fr/replace.txt +39 -0
  36. package/resources/org/languagetool/rules/fr/replace_custom.txt +7 -0
  37. package/resources/org/languagetool/rules/fr/style.xml +22790 -0
  38. package/resources/org/languagetool/rules/fr/synonyms.txt +203 -0
  39. package/src/lang/fr/language/BelgianFrench.js +38 -0
  40. package/src/lang/fr/language/CanadianFrench.js +38 -0
  41. package/src/lang/fr/language/French.js +478 -0
  42. package/src/lang/fr/language/SwissFrench.js +38 -0
  43. package/src/lang/fr/language/id2prioFrench.js +102 -0
  44. package/src/lang/fr/rules/AdvancedSynthesizerFilter.js +12 -0
  45. package/src/lang/fr/rules/CompoundRule.js +47 -0
  46. package/src/lang/fr/rules/DMYDateCheckFilter.js +31 -0
  47. package/src/lang/fr/rules/DateCheckFilter.js +35 -0
  48. package/src/lang/fr/rules/DateFilterHelper.js +55 -0
  49. package/src/lang/fr/rules/FindSuggestionsFilter.js +80 -0
  50. package/src/lang/fr/rules/FrenchLazy.js +9 -0
  51. package/src/lang/fr/rules/FrenchMultitokenSpeller.js +15 -0
  52. package/src/lang/fr/rules/FrenchNumberInWordFilter.js +29 -0
  53. package/src/lang/fr/rules/FrenchPartialPosTagFilter.js +29 -0
  54. package/src/lang/fr/rules/FrenchRepeatedWordsRule.js +76 -0
  55. package/src/lang/fr/rules/FrenchSuppressMisspelledSuggestionsFilter.js +12 -0
  56. package/src/lang/fr/rules/InterrogativeVerbFilter.js +159 -0
  57. package/src/lang/fr/rules/MakeContractionsFilter.js +32 -0
  58. package/src/lang/fr/rules/MorfologikFrenchSpellerRule.js +262 -0
  59. package/src/lang/fr/rules/NewYearDateFilter.js +30 -0
  60. package/src/lang/fr/rules/PostponedAdjectiveConcordanceFilter.js +512 -0
  61. package/src/lang/fr/rules/QuestionWhitespaceRule.js +171 -0
  62. package/src/lang/fr/rules/QuestionWhitespaceStrictRule.js +38 -0
  63. package/src/lang/fr/rules/SimpleReplaceRule.js +63 -0
  64. package/src/lang/fr/rules/SuggestionsFilter.js +29 -0
  65. package/src/lang/fr/rules/WordWithDeterminerFilter.js +168 -0
  66. package/src/lang/fr/synthesis/FrenchSynthesizer.js +36 -0
  67. package/src/lang/fr/tagging/FrenchHybridDisambiguator.js +28 -0
  68. package/src/lang/fr/tagging/FrenchTagger.js +176 -0
  69. package/src/lang/fr/tokenizers/FrenchWordTokenizer.js +164 -0
@@ -0,0 +1,512 @@
1
+ // org.languagetool.rules.fr.PostponedAdjectiveConcordanceFilter
2
+ import { registerClass } from '@valueerror/ltjs-core/src/core/resources/ClassBroker.js';
3
+ import { Pattern } from '@valueerror/ltjs-core/src/lib/java/regex/index.js';
4
+ import { arrayGet } from '@valueerror/ltjs-core/src/lib/java/arrays.js';
5
+ import { equalsIgnoreCase, toLowerCase } from '@valueerror/ltjs-core/src/lib/java/strings.js';
6
+ import { RuleFilter } from '@valueerror/ltjs-core/src/core/patterns/RuleFilter.js';
7
+ import { FrenchSynthesizer } from '../synthesis/FrenchSynthesizer.js';
8
+
9
+ const maxLevels = 4; // Java: PostponedAdjectiveConcordanceFilter#maxLevels (instance final field)
10
+
11
+ const NOM = Pattern.compile('[NZ] .*');
12
+ const NOM_MS = Pattern.compile('[NZ] m s');
13
+ const NOM_FS = Pattern.compile('[NZ] f s');
14
+ const NOM_MP = Pattern.compile('[NZ] m p');
15
+ const NOM_MN = Pattern.compile('[NZ] m sp');
16
+ const NOM_FP = Pattern.compile('[NZ] f p');
17
+ const NOM_CS = Pattern.compile('[NZ] e s');
18
+ const NOM_CP = Pattern.compile('[NZ] e sp');
19
+ const NOM_DET = Pattern.compile('[NZ] .*|(P\\+)?D .*');
20
+ const _GN_ = Pattern.compile('_GN_.*');
21
+ const _GN_MS = Pattern.compile('_GN_MS');
22
+ const _GN_FS = Pattern.compile('_GN_FS');
23
+ const _GN_MP = Pattern.compile('_GN_MP');
24
+ const _GN_FP = Pattern.compile('_GN_FP');
25
+ const _GN_CS = Pattern.compile('_GN_[MF]S');
26
+ const _GN_CP = Pattern.compile('_GN_[MF]P');
27
+ const _GN_MN = Pattern.compile('_GN_M[SP]');
28
+ const _GN_FN = Pattern.compile('_GN_F[SP]');
29
+ const DET = Pattern.compile('(P\\+)?D .*');
30
+ const DET_CS = Pattern.compile('(P\\+)?D e s');
31
+ const DET_MS = Pattern.compile('(P\\+)?D m s');
32
+ const DET_FS = Pattern.compile('(P\\+)?D f s');
33
+ const DET_MP = Pattern.compile('(P\\+)?D m p');
34
+ const DET_FP = Pattern.compile('(P\\+)?D f p');
35
+ const DET_CP = Pattern.compile('(P\\+)?D e p');
36
+ const GN_MS = Pattern.compile('[NZ] [me] (s|sp)|J [me] (s|sp)|V ppa m s|(P\\+)?D m (s|sp)');
37
+ const GN_FS = Pattern.compile('[NZ] [fe] (s|sp)|J [fe] (s|sp)|V ppa f s|(P\\+)?D f (s|sp)');
38
+ const GN_MP = Pattern.compile('[NZ] [me] (p|sp)|J [me] (p|sp)|V ppa m p|(P\\+)?D m (p|sp)');
39
+ const GN_FP = Pattern.compile('[NZ] [fe] (p|sp)|J [fe] (p|sp)|V ppa f p|(P\\+)?D f (p|sp)');
40
+ const GN_CP = Pattern.compile('[NZ] [fme] (p|sp)|J [fme] (p|sp)|(P\\+)?D [fme] (p|sp)');
41
+ const GN_CS = Pattern.compile('[NZ] [fme] (s|sp)|J [fme] (s|sp)|(P\\+)?D [fme] (s|sp)');
42
+ const GN_MN = Pattern.compile('[NZ] [me] (s|p|sp)|J [me] (s|p|sp)|(P\\+)?D [me] (s|p|sp)');
43
+ const GN_FN = Pattern.compile('[NZ] [fe] (s|p|sp)|J [fe] (s|p|sp)|(P\\+)?D [fe] (s|p|sp)');
44
+ const ADJECTIU = Pattern.compile('J .*|V ppa .*|PX.*');
45
+ const ADJECTIU_MS = Pattern.compile('J [me] (s|sp)|V ppa m s');
46
+ const ADJECTIU_FS = Pattern.compile('J [fe] (s|sp)|V ppa f s');
47
+ const ADJECTIU_MP = Pattern.compile('J [me] (p|sp)|V ppa m p');
48
+ const ADJECTIU_FP = Pattern.compile('J [fe] (p|sp)|V ppa f p');
49
+ const ADJECTIU_CP = Pattern.compile('J e (p|sp)');
50
+ const ADJECTIU_CS = Pattern.compile('J e (s|sp)');
51
+ const ADJECTIU_MN = Pattern.compile('J m sp');
52
+ const ADJECTIU_FN = Pattern.compile('J f sp');
53
+ const ADJECTIU_S = Pattern.compile('J .* (s|sp)|V ppa . s');
54
+ const ADJECTIU_P = Pattern.compile('J .* (p|sp)|V ppa . p');
55
+ const ADJECTIU_M = Pattern.compile('J [me] .*|V ppa [me] .*');
56
+ const ADJECTIU_F = Pattern.compile('J [fe] .*|V ppa [fe] .*');
57
+ const ADVERBI = Pattern.compile('A');
58
+ const CONJUNCIO = Pattern.compile('C .*');
59
+ const PUNTUACIO = Pattern.compile('_PUNCT');
60
+ const LOC_ADV = Pattern.compile('A');
61
+ const ADVERBIS_ACCEPTATS = Pattern.compile('A');
62
+ const COORDINACIO_IONI = Pattern.compile('et|ou|ni');
63
+ const KEEP_COUNT = Pattern.compile('Y|J .*|N .*|D .*|P.*|V ppa .*|M nonfin|UNKNOWN|Z.*|V.* inf|V ppr');
64
+ const KEEP_COUNT2 = Pattern.compile(',|et|ou|ni');
65
+ const STOP_COUNT = Pattern.compile('[;:\\(\\)\\[\\]–—―‒]');
66
+ const PREPOSICIONS = Pattern.compile('P.*');
67
+ const PREPOSICIO_CANVI_NIVELL = Pattern.compile('d\'|de|des|du|à|au|aux|en|dans|sur|entre|par|pour|avec|sans|contre|comme');
68
+ const VERB = Pattern.compile('V.* (inf|ind|sub|con|ppr|imp).*');
69
+ const INFINITIVE = Pattern.compile('V.* inf');
70
+ const GV = Pattern.compile('_GV_');
71
+
72
+ // Java: PostponedAdjectiveConcordanceFilter (checks if an adjective doesn't agree with the previous noun and with any of the previous words)
73
+ export class PostponedAdjectiveConcordanceFilter extends RuleFilter {
74
+ static FQCN = 'org.languagetool.rules.fr.PostponedAdjectiveConcordanceFilter';
75
+ static JAVA_ABSTRACT = false;
76
+ static JAVA_CONSTRUCTORS = [{ params: [], text: 'public org.languagetool.rules.fr.PostponedAdjectiveConcordanceFilter()' }];
77
+
78
+ adverbAppeared = false;
79
+ conjunctionAppeared = false;
80
+ punctuationAppeared = false;
81
+ infinitiveAppeared = false;
82
+
83
+ // Java: PostponedAdjectiveConcordanceFilter#acceptRuleMatch(RuleMatch,Map,int,AnalyzedTokenReadings[],List)
84
+ acceptRuleMatch(match, arguments_, patternTokenPos) {
85
+ const tokens = match.getSentence().getTokensWithoutWhitespace();
86
+ const i = patternTokenPos;
87
+ let j;
88
+ let isPlural = true;
89
+ let isPrevNoun = false;
90
+ let substPattern = null;
91
+ let gnPattern = null;
92
+ let adjPattern = null;
93
+ let canBeMS = false;
94
+ let canBeFS = false;
95
+ let canBeMP = false;
96
+ let canBeFP = false;
97
+ let canBeP = false;
98
+ /* Count all nouns and determiners before the adjectives */
99
+ // Takes care of acceptable combinations.
100
+ const cNt = new Array(maxLevels).fill(0);
101
+ const cNMS = new Array(maxLevels).fill(0);
102
+ const cNFS = new Array(maxLevels).fill(0);
103
+ const cNMP = new Array(maxLevels).fill(0);
104
+ const cNMN = new Array(maxLevels).fill(0);
105
+ const cNFP = new Array(maxLevels).fill(0);
106
+ const cNCS = new Array(maxLevels).fill(0);
107
+ const cNCP = new Array(maxLevels).fill(0);
108
+ const cDMS = new Array(maxLevels).fill(0);
109
+ const cDFS = new Array(maxLevels).fill(0);
110
+ const cDMP = new Array(maxLevels).fill(0);
111
+ const cDFP = new Array(maxLevels).fill(0);
112
+ const cN = new Array(maxLevels).fill(0);
113
+ const cD = new Array(maxLevels).fill(0);
114
+ let level = 0;
115
+ j = 1;
116
+ this.initializeApparitions();
117
+ while (i - j > 0 && this.keepCounting(tokens[i - j]) && level < maxLevels) {
118
+ if (!isPrevNoun) {
119
+ if (this.matchPostagRegexp(tokens[i - j], NOM) || (
120
+ // adjectiu o participi sense nom, però amb algun determinant davant
121
+ i - j - 1 > 0 && !this.matchPostagRegexp(tokens[i - j], NOM) && this.matchPostagRegexp(tokens[i - j], ADJECTIU)
122
+ && this.matchPostagRegexp(tokens[i - j - 1], DET))) {
123
+ if (this.matchPostagRegexp(tokens[i - j], _GN_MS)) {
124
+ cNMS[level]++;
125
+ canBeMS = true;
126
+ }
127
+ if (this.matchPostagRegexp(tokens[i - j], _GN_FS)) {
128
+ cNFS[level]++;
129
+ canBeFS = true;
130
+ }
131
+ if (this.matchPostagRegexp(tokens[i - j], _GN_MP)) {
132
+ cNMP[level]++;
133
+ canBeMP = true;
134
+ }
135
+ if (this.matchPostagRegexp(tokens[i - j], _GN_FP)) {
136
+ cNFP[level]++;
137
+ canBeFP = true;
138
+ }
139
+ }
140
+ if (!this.matchPostagRegexp(tokens[i - j], _GN_)) {
141
+ if (this.matchPostagRegexp(tokens[i - j], NOM_MS)) {
142
+ cNMS[level]++;
143
+ canBeMS = true;
144
+ } else if (this.matchPostagRegexp(tokens[i - j], NOM_FS)) {
145
+ cNFS[level]++;
146
+ canBeFS = true;
147
+ } else if (this.matchPostagRegexp(tokens[i - j], NOM_MP)) {
148
+ cNMP[level]++;
149
+ canBeMP = true;
150
+ } else if (this.matchPostagRegexp(tokens[i - j], NOM_MN)) {
151
+ cNMN[level]++;
152
+ canBeMS = true;
153
+ canBeMP = true;
154
+ } else if (this.matchPostagRegexp(tokens[i - j], NOM_FP)) {
155
+ cNFP[level]++;
156
+ canBeFP = true;
157
+ } else if (this.matchPostagRegexp(tokens[i - j], NOM_CS)) {
158
+ cNCS[level]++;
159
+ canBeMS = true;
160
+ canBeFS = true;
161
+ } else if (this.matchPostagRegexp(tokens[i - j], NOM_CP)) {
162
+ cNCP[level]++;
163
+ canBeFP = true;
164
+ canBeMP = true;
165
+ }
166
+ }
167
+ }
168
+ // avoid two consecutive nouns
169
+ if (this.matchPostagRegexp(tokens[i - j], NOM)) {
170
+ cNt[level]++;
171
+ isPrevNoun = true;
172
+ } else {
173
+ isPrevNoun = false;
174
+ }
175
+
176
+ if (this.matchPostagRegexp(tokens[i - j], DET_CS)) {
177
+ if (this.matchPostagRegexp(tokens[i - j + 1], NOM_MS)) {
178
+ cDMS[level]++;
179
+ canBeMS = true;
180
+ }
181
+ if (this.matchPostagRegexp(tokens[i - j + 1], NOM_FS)) {
182
+ cDFS[level]++;
183
+ canBeFS = true;
184
+ }
185
+ }
186
+ if (this.matchPostagRegexp(tokens[i - j], DET_CP)) {
187
+ if (this.matchPostagRegexp(tokens[i - j + 1], NOM_MP)) {
188
+ cDMS[level]++;
189
+ canBeMP = true;
190
+ }
191
+ if (this.matchPostagRegexp(tokens[i - j + 1], NOM_FP)) {
192
+ cDFS[level]++;
193
+ canBeFP = true;
194
+ }
195
+ }
196
+ // TODO (Java) DET_CS, DET_CP without noun afterwards
197
+ if (!this.matchPostagRegexp(tokens[i - j], ADVERBI)) {
198
+ if (this.matchPostagRegexp(tokens[i - j], DET_MS)) {
199
+ cDMS[level]++;
200
+ canBeMS = true;
201
+ }
202
+ if (this.matchPostagRegexp(tokens[i - j], DET_FS)) {
203
+ cDFS[level]++;
204
+ canBeFS = true;
205
+ }
206
+ if (this.matchPostagRegexp(tokens[i - j], DET_MP)) {
207
+ cDMP[level]++;
208
+ canBeMP = true;
209
+ }
210
+ if (this.matchPostagRegexp(tokens[i - j], DET_FP)) {
211
+ cDFP[level]++;
212
+ canBeFP = true;
213
+ }
214
+ }
215
+ if (i - j - 1 > 0) {
216
+ if (this.matchRegexp(tokens[i - j].getToken(), PREPOSICIO_CANVI_NIVELL)
217
+ && this.matchPostagRegexp(tokens[i - j], PREPOSICIONS) // exclude "des" when it is only determiner
218
+ && !this.matchPostagRegexp(tokens[i - j], CONJUNCIO) // "com" com a conjunció
219
+ && !this.matchRegexp(tokens[i - j - 1].getToken(), COORDINACIO_IONI)
220
+ && !this.matchPostagRegexp(tokens[i - j + 1], ADVERBI)) {
221
+ level++;
222
+ // exception: d'environ
223
+ } else if (equalsIgnoreCase(tokens[i - j].getToken(), "d'")
224
+ && equalsIgnoreCase(tokens[i - j + 1].getToken(), 'environ')) {
225
+ level++;
226
+ }
227
+ }
228
+ j = this.updateJValue(tokens, i, j, level);
229
+ this.updateApparitions(tokens[i - j]);
230
+ j++;
231
+ }
232
+ level++;
233
+ if (level > maxLevels) {
234
+ level = maxLevels;
235
+ }
236
+ j = 0;
237
+ let cNtotal = 0;
238
+ let cDtotal = 0;
239
+ while (j < level) {
240
+ cN[j] = cNMS[j] + cNFS[j] + cNMP[j] + cNFP[j] + cNCS[j] + cNCP[j] + cNMN[j];
241
+ cD[j] = cDMS[j] + cDFS[j] + cDMP[j] + cDFP[j];
242
+ cNtotal += cN[j];
243
+ cDtotal += cD[j];
244
+
245
+ // exceptions: adjective is plural and there are several nouns before
246
+ if (this.matchPostagRegexp(arrayGet(tokens, i), ADJECTIU_MP) && (cN[j] > 1 || cD[j] > 1)
247
+ && (cNMS[j] + cNMN[j] + cNMP[j] + cNCS[j] + cNCP[j] + cDMS[j] + cDMP[j]) > 0
248
+ && (cNFS[j] + cNFP[j] <= cNt[j])) {
249
+ return null;
250
+ }
251
+ if (this.matchPostagRegexp(arrayGet(tokens, i), ADJECTIU_FP) && (cN[j] > 1 || cD[j] > 1)
252
+ && ((cNMS[j] + cNMP[j] + cNMN[j] + cDMS[j] + cDMP[j]) === 0 || (cNt[j] > 0 && cNFS[j] + cNFP[j] >= cNt[j]))) {
253
+ return null;
254
+ }
255
+ // Adjective can't be singular
256
+ if (cN[j] + cD[j] > 0) { // && level>1
257
+ isPlural = isPlural && cD[j] > 1 && level > 1; // cN[j]>1
258
+ canBeP = canBeP || cN[j] > 1;
259
+ }
260
+ j++;
261
+ }
262
+ // comma + plural noun
263
+ isPlural = isPlural || (i - 2 > 0 && cNMP[0] + cNFP[0] + cNCP[0] > 0 && tokens[i - 2].getToken() === ',');
264
+
265
+ // there is no noun, (no determinant --> && cDtotal==0)
266
+ if (cNtotal === 0 && cDtotal === 0) {
267
+ return null;
268
+ }
269
+
270
+ // patterns according to the analyzed adjective
271
+ if (this.matchPostagRegexp(tokens[i], ADJECTIU_CS)) {
272
+ substPattern = GN_CS;
273
+ adjPattern = ADJECTIU_S;
274
+ gnPattern = _GN_CS;
275
+ } else if (this.matchPostagRegexp(tokens[i], ADJECTIU_CP)) {
276
+ substPattern = GN_CP;
277
+ adjPattern = ADJECTIU_P;
278
+ gnPattern = _GN_CP;
279
+ } else if (this.matchPostagRegexp(tokens[i], ADJECTIU_MN)) {
280
+ substPattern = GN_MN;
281
+ adjPattern = ADJECTIU_M;
282
+ gnPattern = _GN_MN;
283
+ } else if (this.matchPostagRegexp(tokens[i], ADJECTIU_FN)) {
284
+ substPattern = GN_FN;
285
+ adjPattern = ADJECTIU_FN;
286
+ gnPattern = _GN_FN;
287
+ } else if (this.matchPostagRegexp(tokens[i], ADJECTIU_MS)) {
288
+ substPattern = GN_MS;
289
+ adjPattern = ADJECTIU_MS;
290
+ gnPattern = _GN_MS;
291
+ } else if (this.matchPostagRegexp(tokens[i], ADJECTIU_FS)) {
292
+ substPattern = GN_FS;
293
+ adjPattern = ADJECTIU_FS;
294
+ gnPattern = _GN_FS;
295
+ } else if (this.matchPostagRegexp(tokens[i], ADJECTIU_MP)) {
296
+ substPattern = GN_MP;
297
+ adjPattern = ADJECTIU_MP;
298
+ gnPattern = _GN_MP;
299
+ } else if (this.matchPostagRegexp(tokens[i], ADJECTIU_FP)) {
300
+ substPattern = GN_FP;
301
+ adjPattern = ADJECTIU_FP;
302
+ gnPattern = _GN_FP;
303
+ }
304
+
305
+ if (substPattern === null || gnPattern === null || adjPattern === null) {
306
+ return null;
307
+ }
308
+
309
+ // combinations Det/Nom + adv (1,2..) + adj.
310
+ // If there is agreement, the rule doesn't match
311
+ j = 1;
312
+ let keepCount = true;
313
+ while (i - j > 0 && keepCount) {
314
+ if (this.matchPostagRegexp(tokens[i - j], NOM_DET) && this.matchPostagRegexp(tokens[i - j], gnPattern)) {
315
+ return null; // there is a previous agreeing noun
316
+ } else if (!this.matchPostagRegexp(tokens[i - j], _GN_) && this.matchPostagRegexp(tokens[i - j], substPattern)) {
317
+ return null; // there is a previous agreeing noun
318
+ }
319
+ keepCount = !this.matchPostagRegexp(tokens[i - j], NOM_DET);
320
+ j++;
321
+ }
322
+
323
+ // Necessary condition: previous token is a non-agreeing noun
324
+ // or it is adjective or adverb (not preceded by verb)
325
+ if ((this.matchPostagRegexp(arrayGet(tokens, i - 1), NOM) && !this.matchPostagRegexp(tokens[i - 1], substPattern))
326
+ || (this.matchPostagRegexp(tokens[i - 1], _GN_) && !this.matchPostagRegexp(tokens[i - 1], gnPattern))
327
+ || (this.matchPostagRegexp(tokens[i - 1], ADJECTIU) && !this.matchPostagRegexp(tokens[i - 1], adjPattern))
328
+ || (i > 2 && this.matchPostagRegexp(tokens[i - 1], ADVERBIS_ACCEPTATS) && !this.matchPostagRegexp(tokens[i - 2], VERB)
329
+ && !this.matchPostagRegexp(tokens[i - 2], PREPOSICIONS))
330
+ || (i > 3 && this.matchPostagRegexp(tokens[i - 1], LOC_ADV) && this.matchPostagRegexp(tokens[i - 2], LOC_ADV)
331
+ && !this.matchPostagRegexp(tokens[i - 3], VERB) && !this.matchPostagRegexp(tokens[i - 3], PREPOSICIONS))) {
332
+ // continue
333
+ } else {
334
+ return null;
335
+ }
336
+
337
+ // Adjective can't be singular. The rule matches
338
+ if (!(isPlural && this.matchPostagRegexp(tokens[i], ADJECTIU_S))) {
339
+ // look into previous words
340
+ j = 1;
341
+ this.initializeApparitions();
342
+ while (i - j > 0 && this.keepCounting(tokens[i - j]) && (level > 1 || j < 4)) {
343
+ // there is a previous agreeing noun
344
+ if (!this.matchPostagRegexp(tokens[i - j], _GN_) && this.matchPostagRegexp(tokens[i - j], NOM_DET)
345
+ && this.matchPostagRegexp(tokens[i - j], substPattern)) {
346
+ return null;
347
+ // there is a previous agreeing adjective (in a nominal group)
348
+ } else if (this.matchPostagRegexp(tokens[i - j], gnPattern)) {
349
+ return null;
350
+ // if there is no nominal group, it requires noun
351
+ }
352
+ j = this.updateJValue(tokens, i, j, 0);
353
+ this.updateApparitions(tokens[i - j]);
354
+ j++;
355
+ }
356
+ }
357
+
358
+ const synth = FrenchSynthesizer.INSTANCE;
359
+
360
+ // The rule matches
361
+ // Synthesize suggestions
362
+ let suggestions = [];
363
+ let at = this.getAnalyzedToken(tokens[patternTokenPos], ADJECTIU_CS);
364
+ if (at !== null) {
365
+ suggestions.push(...synth.synthesize(at, 'J e p', true));
366
+ }
367
+ if (suggestions.length === 0) {
368
+ at = this.getAnalyzedToken(tokens[patternTokenPos], ADJECTIU_CP);
369
+ if (at !== null) {
370
+ suggestions.push(...synth.synthesize(at, 'J e s', true));
371
+ }
372
+ }
373
+ if (suggestions.length === 0 && isPlural) {
374
+ at = this.getAnalyzedToken(tokens[patternTokenPos], ADJECTIU_P);
375
+ if (at !== null) {
376
+ suggestions.push(...synth.synthesize(at, 'J . p|V ppa . p', true));
377
+ }
378
+ }
379
+ at = this.getAnalyzedToken(tokens[patternTokenPos], ADJECTIU);
380
+ if (at !== null && suggestions.length === 0) {
381
+ if (canBeMS && !isPlural) {
382
+ suggestions.push(...synth.synthesize(at, 'J [me] sp?|V ppa m s', true));
383
+ }
384
+ if (canBeFS && !isPlural) {
385
+ suggestions.push(...synth.synthesize(at, 'J [fe] sp?|V ppa f s', true));
386
+ }
387
+ if (canBeMP) {
388
+ suggestions.push(...synth.synthesize(at, 'J [me] s?p|V ppa m p', true));
389
+ }
390
+ if (canBeFP) {
391
+ suggestions.push(...synth.synthesize(at, 'J [fe] s?p|V ppa f p', true));
392
+ }
393
+ if (canBeMS && (isPlural || canBeP)) {
394
+ suggestions.push(...synth.synthesize(at, 'J [me] s?p|V ppa m p', true));
395
+ }
396
+ if (canBeFS && !canBeMS && (isPlural || canBeP)) {
397
+ suggestions.push(...synth.synthesize(at, 'J [fe] s?p|V ppa f p', true));
398
+ }
399
+ }
400
+
401
+ // set suggestion removing duplicates
402
+ suggestions = [...new Set(suggestions)];
403
+ // avoid the original token as suggestion
404
+ const lowerToken = toLowerCase(tokens[patternTokenPos].getToken());
405
+ if (suggestions.includes(lowerToken)) {
406
+ suggestions.splice(suggestions.indexOf(lowerToken), 1);
407
+ }
408
+ if (suggestions.length === 0) {
409
+ return null;
410
+ }
411
+ match.setSuggestedReplacements(suggestions);
412
+ return match;
413
+ }
414
+
415
+ // Java: PostponedAdjectiveConcordanceFilter#updateJValue(AnalyzedTokenReadings[],int,int,int)
416
+ updateJValue(tokens, i, j) {
417
+ // deux ou plus
418
+ if (this.matchRegexp(tokens[i - j].getToken(), COORDINACIO_IONI)) {
419
+ if (i - j - 1 > 0 && i - j + 1 < tokens.length) {
420
+ if (this.matchPostagRegexp(tokens[i - j - 1], DET) && tokens[i - j + 1].getToken() === 'plus') {
421
+ j = j + 1;
422
+ }
423
+ }
424
+ }
425
+ return j;
426
+ }
427
+
428
+ // Java: PostponedAdjectiveConcordanceFilter#keepCounting(AnalyzedTokenReadings)
429
+ keepCounting(aTr) {
430
+ if (this.matchRegexp(aTr.getToken(), PREPOSICIO_CANVI_NIVELL)) {
431
+ return true;
432
+ }
433
+ if (aTr.getToken() === '.') { // it is not sentence end, but abbreviation
434
+ return true;
435
+ }
436
+ // stop searching if there is some of these combinations:
437
+ // adverb+comma, adverb+conjunction, comma+conjunction,
438
+ // punctuation+punctuation
439
+ if ((this.adverbAppeared && this.conjunctionAppeared) || (this.adverbAppeared && this.punctuationAppeared)
440
+ || (this.conjunctionAppeared && this.punctuationAppeared) || (this.punctuationAppeared && this.matchPostagRegexp(aTr, PUNTUACIO))
441
+ || (this.infinitiveAppeared && this.matchRegexp(aTr.getToken(), COORDINACIO_IONI))
442
+ || (this.infinitiveAppeared && this.adverbAppeared)) {
443
+ return false;
444
+ }
445
+ return (this.matchPostagRegexp(aTr, KEEP_COUNT) || this.matchRegexp(aTr.getToken(), KEEP_COUNT2)
446
+ || this.matchPostagRegexp(aTr, ADVERBIS_ACCEPTATS)) && !this.matchRegexp(aTr.getToken(), STOP_COUNT)
447
+ && (!this.matchPostagRegexp(aTr, GV) || this.matchPostagRegexp(aTr, _GN_));
448
+ }
449
+
450
+ // Java: PostponedAdjectiveConcordanceFilter#initializeApparitions()
451
+ initializeApparitions() {
452
+ this.adverbAppeared = false;
453
+ this.conjunctionAppeared = false;
454
+ this.punctuationAppeared = false;
455
+ this.infinitiveAppeared = false;
456
+ }
457
+
458
+ // Java: PostponedAdjectiveConcordanceFilter#updateApparitions(AnalyzedTokenReadings)
459
+ updateApparitions(aTr) {
460
+ this.conjunctionAppeared ||= this.matchPostagRegexp(aTr, CONJUNCIO);
461
+ if (aTr.getToken() === 'com') {
462
+ return;
463
+ }
464
+ if (this.matchPostagRegexp(aTr, NOM) || this.matchPostagRegexp(aTr, ADJECTIU)) {
465
+ this.initializeApparitions();
466
+ return;
467
+ }
468
+ this.adverbAppeared ||= this.matchPostagRegexp(aTr, ADVERBI);
469
+ this.punctuationAppeared ||= (this.matchPostagRegexp(aTr, PUNTUACIO) || aTr.getToken() === ',');
470
+ this.infinitiveAppeared ||= this.matchPostagRegexp(aTr, INFINITIVE);
471
+ }
472
+
473
+ // Java: PostponedAdjectiveConcordanceFilter#matchPostagRegexp(AnalyzedTokenReadings,Pattern)
474
+ matchPostagRegexp(aToken, pattern) {
475
+ let matches = false;
476
+ for (const analyzedToken of aToken) {
477
+ let posTag = analyzedToken.getPOSTag();
478
+ if (posTag === null) {
479
+ posTag = 'UNKNOWN';
480
+ }
481
+ const m = pattern.matcher(posTag);
482
+ if (m.matches()) {
483
+ matches = true;
484
+ break;
485
+ }
486
+ }
487
+ return matches;
488
+ }
489
+
490
+ // Java: PostponedAdjectiveConcordanceFilter#matchRegexp(String,Pattern)
491
+ matchRegexp(s, pattern) {
492
+ const m = pattern.matcher(s);
493
+ return m.matches();
494
+ }
495
+
496
+ // Java: PostponedAdjectiveConcordanceFilter#getAnalyzedToken(AnalyzedTokenReadings,Pattern)
497
+ getAnalyzedToken(aToken, pattern) {
498
+ for (const analyzedToken of aToken) {
499
+ let posTag = analyzedToken.getPOSTag();
500
+ if (posTag === null) {
501
+ posTag = 'UNKNOWN';
502
+ }
503
+ const m = pattern.matcher(posTag);
504
+ if (m.matches()) {
505
+ return analyzedToken;
506
+ }
507
+ }
508
+ return null;
509
+ }
510
+ }
511
+
512
+ registerClass(PostponedAdjectiveConcordanceFilter.FQCN, PostponedAdjectiveConcordanceFilter);
@@ -0,0 +1,171 @@
1
+ // org.languagetool.rules.fr.QuestionWhitespaceRule
2
+ import { Rule } from '@valueerror/ltjs-core/src/core/rules/model/Rule.js';
3
+ import { RuleMatch } from '@valueerror/ltjs-core/src/core/rules/model/RuleMatch.js';
4
+ import { Categories } from '@valueerror/ltjs-core/src/core/rules/model/Category.js';
5
+ import { PatternTokenBuilder } from '@valueerror/ltjs-core/src/core/patterns/PatternTokenBuilder.js';
6
+ import '@valueerror/ltjs-core/src/core/disambiguation/DisambiguationPatternRule.js';
7
+ import { isEmpty } from '@valueerror/ltjs-core/src/core/text/StringTools.js';
8
+ import { Pattern } from '@valueerror/ltjs-core/src/lib/java/regex/index.js';
9
+
10
+ // Java: QuestionWhitespaceRule#urlPattern
11
+ const urlPattern = Pattern.compile(
12
+ '^(file|s?ftp|finger|git|gopher|hdl|https?|shttp|imap|mailto|mms|nntp|s?news(post|reply)?|prospero|rsync|rtspu|sips?|svn|svn\\+ssh|telnet|wais)$');
13
+
14
+ const ESPACE_FINE_INSECABLE = ' ';
15
+ const NBSP = ' ';
16
+
17
+ let antiPatternList = null; // Java: static ANTI_PATTERNS (built here on first use)
18
+
19
+ // Java: QuestionWhitespaceRule#ANTI_PATTERNS
20
+ function ANTI_PATTERNS() {
21
+ if (antiPatternList === null) {
22
+ antiPatternList = [[
23
+ // ignore smileys, such as :-)
24
+ new PatternTokenBuilder().tokenRegex('[:;]').build(),
25
+ new PatternTokenBuilder().csToken('-').setIsWhiteSpaceBefore(false).build(),
26
+ new PatternTokenBuilder().tokenRegex('[\\(\\)D]').setIsWhiteSpaceBefore(false).build()],
27
+ [ // ignore smileys, such as :)
28
+ new PatternTokenBuilder().tokenRegex('[:;]').build(),
29
+ new PatternTokenBuilder().tokenRegex('[\\(\\)D]').setIsWhiteSpaceBefore(false).build()],
30
+ [ // times like 23:20
31
+ new PatternTokenBuilder().tokenRegex('.*\\d{1,2}').build(), new PatternTokenBuilder().token(':').build(),
32
+ new PatternTokenBuilder().tokenRegex('\\d{1,2}').build()],
33
+ [ // "??"
34
+ new PatternTokenBuilder().tokenRegex('[?!]').build(), new PatternTokenBuilder().tokenRegex('[?!]').build()],
35
+ [ // mac address
36
+ new PatternTokenBuilder().tokenRegex('[a-z0-9]{2}').build(), new PatternTokenBuilder().token(':').build(),
37
+ new PatternTokenBuilder().tokenRegex('[a-z0-9]{2}').build(), new PatternTokenBuilder().token(':').build(),
38
+ new PatternTokenBuilder().tokenRegex('[a-z0-9]{2}').build()],
39
+ [ // csv markup (not sure why we need this, but there were a lot of users ignoring this specific case)
40
+ new PatternTokenBuilder().token(';').build(),
41
+ new PatternTokenBuilder().tokenRegex('.+').setIsWhiteSpaceBefore(false).build(),
42
+ new PatternTokenBuilder().token(';').setIsWhiteSpaceBefore(false).build()],
43
+ [ // csv markup (not sure why we need this, but there were a lot of users ignoring this specific case)
44
+ new PatternTokenBuilder().tokenRegex('.+').setIsWhiteSpaceBefore(false).build(),
45
+ new PatternTokenBuilder().token(';').setIsWhiteSpaceBefore(false).build(),
46
+ new PatternTokenBuilder().tokenRegex('.+').setIsWhiteSpaceBefore(false).build()]];
47
+ }
48
+ return antiPatternList;
49
+ }
50
+
51
+ // Java: QuestionWhitespaceRule
52
+ export class QuestionWhitespaceRule extends Rule {
53
+ static FQCN = 'org.languagetool.rules.fr.QuestionWhitespaceRule';
54
+
55
+ // Java: QuestionWhitespaceRule#getAntiPatterns()
56
+ getAntiPatterns() {
57
+ return this.antiPatterns();
58
+ }
59
+
60
+ // Java: QuestionWhitespaceRule(ResourceBundle,Language)
61
+ constructor(messages, language) {
62
+ super();
63
+ super.setCategory(Categories.MISC.getCategory(messages));
64
+ this.antiPatterns = Rule.cacheAntiPatterns(language, ANTI_PATTERNS());
65
+ }
66
+
67
+ // Java: QuestionWhitespaceRule#getId()
68
+ getId() {
69
+ return 'FRENCH_WHITESPACE';
70
+ }
71
+
72
+ // Java: QuestionWhitespaceRule#getDescription()
73
+ getDescription() {
74
+ return 'Insertion des espaces fines insécables';
75
+ }
76
+
77
+ // Java: QuestionWhitespaceRule#isAllowedWhitespaceChar(AnalyzedTokenReadings[],int)
78
+ isAllowedWhitespaceChar(tokens, i) {
79
+ return i >= 0 ? tokens[i].isWhitespace() : false;
80
+ }
81
+
82
+ // Java: QuestionWhitespaceRule#match(AnalyzedSentence)
83
+ match(sentence) {
84
+ const ruleMatches = [];
85
+ const tokens = this.getSentenceWithImmunization(sentence).getTokens();
86
+ let prevPrevToken = '';
87
+ let prevToken = '';
88
+ for (let i = 1; i < tokens.length; i++) {
89
+ const token = tokens[i].getToken();
90
+ if (tokens[i].isImmunized() || prevToken === '(' || prevToken === '[') {
91
+ prevPrevToken = prevToken;
92
+ prevToken = token;
93
+ continue;
94
+ }
95
+ let msg = null;
96
+ let suggestionText = null;
97
+ let iFrom = i - 1;
98
+ let iTo = i;
99
+ const isPreviousWhitespace = i > 0 ? tokens[i - 1].isWhitespace() : false;
100
+ let prevTokenToChange = prevToken;
101
+ if (isPreviousWhitespace) {
102
+ prevTokenToChange = '';
103
+ }
104
+ if (!this.isAllowedWhitespaceChar(tokens, i - 1)) {
105
+ // Strictly speaking, the character before ?!; should be an "espace fine
106
+ // insécable" (U+202f). In practise, an "espace insécable" (U+00a0) is also
107
+ // often used - or even a common space. Let's accept all - use
108
+ // QuestionWhitespaceStrictRule if this is not strict enough.
109
+ if (token === '?' && prevToken !== '!') {
110
+ msg = "Le point d'interrogation est précédé d'une espace fine insécable.";
111
+ suggestionText = prevTokenToChange + ESPACE_FINE_INSECABLE + '?';
112
+ } else if (token === '!' && prevToken !== '?') {
113
+ msg = "Le point d'exclamation est précédé d'une espace fine insécable.";
114
+ suggestionText = prevTokenToChange + ESPACE_FINE_INSECABLE + '!';
115
+ } else if (token === ';') {
116
+ msg = "Le point-virgule est précédé d'une espace fine insécable.";
117
+ suggestionText = prevTokenToChange + ESPACE_FINE_INSECABLE + ';';
118
+ } else if (token === ':') {
119
+ // Avoid false positive for URL like http://www.languagetool.org.
120
+ const matcherUrl = urlPattern.matcher(prevToken);
121
+ if (!matcherUrl.find()) {
122
+ msg = "Les deux-points sont précédés d'une espace insécable.";
123
+ suggestionText = prevTokenToChange + NBSP + ':';
124
+ }
125
+ } else if (token === '»') {
126
+ if (prevPrevToken === '«') {
127
+ msg = 'Les guillemets sont toujours accompagnés d\'une espace insécable.';
128
+ suggestionText = '«' + NBSP + prevTokenToChange + NBSP + '»';
129
+ iFrom = i - 2;
130
+ } else {
131
+ msg = 'Le guillemet fermant est précédé d\'une espace insécable.';
132
+ suggestionText = prevTokenToChange + NBSP + '»';
133
+ }
134
+ }
135
+ }
136
+
137
+ if (prevToken === '«') {
138
+ if (isEmpty(token)) {
139
+ msg = "Le guillemet ouvrant est suivi d'une espace insécable.";
140
+ suggestionText = '«' + NBSP;
141
+ iTo = i - 1;
142
+ } else if (!this.isAllowedWhitespaceChar(tokens, i)) {
143
+ let nextToken = '';
144
+ if (i + 1 < tokens.length) {
145
+ nextToken = tokens[i + 1].getToken();
146
+ }
147
+ if (nextToken !== '»') {
148
+ msg = "Le guillemet ouvrant est suivi d'une espace insécable.";
149
+ if (!tokens[i].isWhitespace()) {
150
+ suggestionText = '«' + NBSP + token;
151
+ } else {
152
+ suggestionText = '«' + NBSP;
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ if (msg !== null) {
159
+ const fromPos = tokens[iFrom].getStartPos();
160
+ const toPos = tokens[iTo].getEndPos();
161
+ const ruleMatch = new RuleMatch(this, sentence, fromPos, toPos, msg, 'Insérer une espace insécable');
162
+ ruleMatch.setSuggestedReplacement(suggestionText);
163
+ ruleMatches.push(ruleMatch);
164
+ }
165
+ prevPrevToken = prevToken;
166
+ prevToken = token;
167
+ }
168
+
169
+ return this.toRuleMatchArray(ruleMatches);
170
+ }
171
+ }