natural 0.5.4 → 0.6.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.
- package/.dockerignore +2 -0
- package/.npmignore +1 -0
- package/.travis.yml +1 -1
- package/README.md +412 -89
- package/bayes_classifier.json +1 -1
- package/classifier.json +418 -0
- package/data/kata-dasar.txt +29933 -0
- package/lib/natural/brill_pos_tagger/lib/Brill_POS_Tagger.js +8 -9
- package/lib/natural/brill_pos_tagger/lib/Brill_POS_Tester.js +5 -5
- package/lib/natural/brill_pos_tagger/lib/Brill_POS_Trainer.js +10 -6
- package/lib/natural/brill_pos_tagger/lib/Corpus.js +21 -16
- package/lib/natural/brill_pos_tagger/lib/Lexicon.js +22 -13
- package/lib/natural/brill_pos_tagger/lib/Predicate.js +2 -2
- package/lib/natural/brill_pos_tagger/lib/RuleTemplate.js +2 -2
- package/lib/natural/brill_pos_tagger/lib/RuleTemplates.js +132 -146
- package/lib/natural/brill_pos_tagger/lib/Sentence.js +41 -0
- package/lib/natural/brill_pos_tagger/lib/TF_Parser.js +83 -76
- package/lib/natural/brill_pos_tagger/lib/TF_Parser.pegjs +19 -4
- package/lib/natural/brill_pos_tagger/lib/TransformationRule.js +10 -11
- package/lib/natural/classifiers/classifier.js +15 -8
- package/lib/natural/classifiers/maxent/Classifier.js +125 -0
- package/lib/natural/classifiers/maxent/Context.js +33 -0
- package/lib/natural/classifiers/maxent/Distribution.js +145 -0
- package/lib/natural/classifiers/maxent/Element.js +34 -0
- package/lib/natural/classifiers/maxent/Feature.js +88 -0
- package/lib/natural/classifiers/maxent/FeatureSet.js +67 -0
- package/lib/natural/classifiers/maxent/GISScaler.js +148 -0
- package/lib/natural/classifiers/maxent/POS/ME_Corpus.js +56 -0
- package/lib/natural/classifiers/maxent/POS/ME_Sentence.js +73 -0
- package/lib/natural/classifiers/maxent/POS/POS_Element.js +172 -0
- package/lib/natural/classifiers/maxent/Sample.js +141 -0
- package/lib/natural/classifiers/maxent/SimpleExample/SE_Element.js +49 -0
- package/lib/natural/distance/hamming_distance.js +52 -0
- package/lib/natural/distance/jaro-winkler_distance.js +16 -6
- package/lib/natural/distance/levenshtein_distance.js +164 -11
- package/lib/natural/index.js +26 -5
- package/lib/natural/inflectors/noun_inflector.js +15 -13
- package/lib/natural/ngrams/ngrams.js +83 -25
- package/lib/natural/normalizers/normalizer_sv.js +41 -0
- package/lib/natural/sentiment/Basque/senticon_eu.json +21557 -0
- package/lib/natural/sentiment/Catalan/senticon_ca.json +36352 -0
- package/lib/natural/sentiment/Dutch/negations_du.json +3 -0
- package/lib/natural/sentiment/Dutch/pattern-sentiment-nl.json +39650 -0
- package/lib/natural/sentiment/English/negations_en.json +3 -0
- package/lib/natural/sentiment/English/pattern-sentiment-en.json +16024 -0
- package/lib/natural/sentiment/English/senticon_en.json +124197 -0
- package/lib/natural/sentiment/French/pattern-sentiment-fr.json +40906 -0
- package/lib/natural/sentiment/Galician/senticon_gl.json +24427 -0
- package/lib/natural/sentiment/Italian/pattern-sentiment-it.json +24522 -0
- package/lib/natural/sentiment/SentimentAnalyzer.js +116 -0
- package/lib/natural/sentiment/Spanish/afinnShortSortedSpanish.json +1655 -0
- package/lib/natural/sentiment/Spanish/negations_es.json +3 -0
- package/lib/natural/sentiment/Spanish/senticon_es.json +56722 -0
- package/lib/natural/sentiment/tools/README.md +31 -0
- package/lib/natural/sentiment/tools/XmlParser4PatternData.js +31 -0
- package/lib/natural/sentiment/tools/sentimentXmlParser.js +30 -0
- package/lib/natural/stemmers/indonesian/base_stemmer_id.js +86 -0
- package/lib/natural/stemmers/indonesian/prefix_rules.js +1027 -0
- package/lib/natural/stemmers/indonesian/removal.js +49 -0
- package/lib/natural/stemmers/indonesian/stemmer_id.js +307 -0
- package/lib/natural/stemmers/indonesian/suffix_rules.js +75 -0
- package/lib/natural/stemmers/porter_stemmer_es.js +265 -199
- package/lib/natural/stemmers/porter_stemmer_fr.js +1 -1
- package/lib/natural/stemmers/porter_stemmer_nl.js +362 -0
- package/lib/natural/stemmers/porter_stemmer_sv.js +99 -0
- package/lib/natural/stemmers/stemmer_es.js +18 -12
- package/lib/natural/stemmers/stemmer_nl.js +64 -0
- package/lib/natural/stemmers/stemmer_sv.js +61 -0
- package/lib/natural/tfidf/tfidf.js +27 -4
- package/lib/natural/tokenizers/aggressive_tokenizer_id.js +44 -0
- package/lib/natural/tokenizers/aggressive_tokenizer_sv.js +40 -0
- package/lib/natural/tokenizers/aggressive_tokenizer_vi.js +37 -0
- package/lib/natural/tokenizers/orthography_matchers.js +33 -0
- package/lib/natural/tokenizers/regexp_tokenizer.js +23 -3
- package/lib/natural/tokenizers/sentence_tokenizer.js +1 -1
- package/lib/natural/util/stopwords_id.js +119 -0
- package/lib/natural/util/stopwords_no.js +1 -1
- package/lib/natural/util/stopwords_sv.js +70 -0
- package/lib/natural/wordnet/wordnet_file.js +7 -1
- package/lr_classifier.json +1 -1
- package/package-lock.json +378 -0
- package/package.json +29 -21
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2018, Hugo W.L. ter Doest
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
|
12
|
+
all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
20
|
+
THE SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* Spec for the Dutch Porter Stemmer can be found at:
|
|
25
|
+
* http://snowball.tartarus.org/algorithms/dutch/stemmer.html
|
|
26
|
+
*/
|
|
27
|
+
'use strict'
|
|
28
|
+
|
|
29
|
+
var Stemmer = require('./stemmer_nl');
|
|
30
|
+
|
|
31
|
+
const DEBUG = false;
|
|
32
|
+
const vowels = "aeiouèy";
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
function isVowel(x) {
|
|
36
|
+
return vowels.indexOf(x) > -1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
// * Return longest matching suffixes for a token or '' if no suffix match
|
|
41
|
+
String.prototype.endsinArr = function(suffixes) {
|
|
42
|
+
var i, longest = '';
|
|
43
|
+
for (i = 0; i < suffixes.length; i++) {
|
|
44
|
+
if (this.endsin(suffixes[i]) && suffixes[i].length > longest.length)
|
|
45
|
+
longest = suffixes[i];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (DEBUG && longest != "") {
|
|
49
|
+
console.log("Matched suffix: " + longest);
|
|
50
|
+
}
|
|
51
|
+
return longest;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
// Returns true if token has suffix
|
|
56
|
+
String.prototype.endsin = function(suffix) {
|
|
57
|
+
if (this.length < suffix.length) return false;
|
|
58
|
+
return (this.slice(-suffix.length) == suffix);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
// Removes a suffix of len characters and returns the string
|
|
63
|
+
String.prototype.removeSuffix = function(len) {
|
|
64
|
+
return this.substr(0, this.length - len);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
// Define undoubling the ending as removing the last letter if the word ends kk, dd or tt.
|
|
69
|
+
String.prototype.undoubleEnding = function() {
|
|
70
|
+
if (this.substr(-2) == "kk" || this.substr(-2) == "tt" || this.substr(-2) == "dd") {
|
|
71
|
+
return this.substr(0, this.length - 1);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class PorterStemmer extends Stemmer {
|
|
80
|
+
constructor() {
|
|
81
|
+
super();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
replaceAccentedCharacters(word) {
|
|
86
|
+
var accentedCharactersMapping = {
|
|
87
|
+
"ä": "a",
|
|
88
|
+
"ë": "e",
|
|
89
|
+
"ï": "i",
|
|
90
|
+
"ö": "o",
|
|
91
|
+
"ü": "u",
|
|
92
|
+
"á": "a",
|
|
93
|
+
"é": "e",
|
|
94
|
+
"í": "i",
|
|
95
|
+
"ó": "o",
|
|
96
|
+
"ú": "u"
|
|
97
|
+
}
|
|
98
|
+
var result = word;
|
|
99
|
+
for (var x in accentedCharactersMapping) {
|
|
100
|
+
result = result.replace(new RegExp(x, "g"), accentedCharactersMapping[x]);
|
|
101
|
+
}
|
|
102
|
+
if (DEBUG) {
|
|
103
|
+
console.log("replaceAccentedCharacters: " + result);
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
//Put initial y, y after a vowel, and i between vowels into upper case.
|
|
110
|
+
handleYI(word) {
|
|
111
|
+
// Initial y
|
|
112
|
+
var result = word.replace(/^y/, "Y");
|
|
113
|
+
if (DEBUG) {
|
|
114
|
+
console.log("handleYI: initial y: " + result);
|
|
115
|
+
}
|
|
116
|
+
// y after vowel
|
|
117
|
+
result = result.replace(/([aeioué])y/g, "$1Y");
|
|
118
|
+
if (DEBUG) {
|
|
119
|
+
console.log("handleYI: y after vowel: " + result);
|
|
120
|
+
}
|
|
121
|
+
// i between vowels
|
|
122
|
+
var result = result.replace(/([aeioué])i([aeioué])/g, "$1I$2");
|
|
123
|
+
if (DEBUG) {
|
|
124
|
+
console.log("handleYI: i between vowels:" + result);
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
// Determines R1 and R2; adapted from the French Porter Stemmer
|
|
131
|
+
markRegions(token) {
|
|
132
|
+
var r1, r2, len;
|
|
133
|
+
|
|
134
|
+
r1 = r2 = len = token.length;
|
|
135
|
+
|
|
136
|
+
// R1 is the region after the first non-vowel following a vowel,
|
|
137
|
+
for (var i = 0; i < len - 1 && r1 == len; i++) {
|
|
138
|
+
if (isVowel(token[i]) && !isVowel(token[i + 1])) {
|
|
139
|
+
r1 = i + 2;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Or is the null region at the end of the word if there is no such non-vowel.
|
|
143
|
+
|
|
144
|
+
// R1 is adjusted such that the region before it contains at least 3 characters
|
|
145
|
+
if (r1 != len) {
|
|
146
|
+
// R1 is not null
|
|
147
|
+
if (r1 < 3) {
|
|
148
|
+
// Region before does not contain at least 3 characters
|
|
149
|
+
if (len > 3) {
|
|
150
|
+
r1 = 3;
|
|
151
|
+
// Now R1 contains at least 3 characters
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// It is not possible to make the region before long enough
|
|
155
|
+
r1 = len;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// R2 is the region after the first non-vowel following a vowel in R1
|
|
161
|
+
for (i = r1; i < len - 1 && r2 == len; i++) {
|
|
162
|
+
if (isVowel(token[i]) && !isVowel(token[i + 1])) {
|
|
163
|
+
r2 = i + 2;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Or is the null region at the end of the word if there is no such non-vowel.
|
|
167
|
+
|
|
168
|
+
if (DEBUG) {
|
|
169
|
+
console.log("Regions r1 = " + r1 + " r2 = " + r2);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.r1 = r1;
|
|
173
|
+
this.r2 = r2;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
prelude(word) {
|
|
178
|
+
var result = this.replaceAccentedCharacters(word);
|
|
179
|
+
result = this.handleYI(result);
|
|
180
|
+
this.markRegions(result);
|
|
181
|
+
if (DEBUG) {
|
|
182
|
+
console.log("Prelude: " + result);
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
// (1b) en ene => delete if in R1 and preceded by a valid en-ending, and then undouble the ending
|
|
189
|
+
// Define a valid en-ending as a non-vowel, and not gem.
|
|
190
|
+
// Define undoubling the ending as removing the last letter if the word ends kk, dd or tt.
|
|
191
|
+
step1b(word, suffixes) {
|
|
192
|
+
var result = word;
|
|
193
|
+
|
|
194
|
+
var match = result.endsinArr(suffixes);
|
|
195
|
+
if (match != "") {
|
|
196
|
+
var pos = result.length - match.length;
|
|
197
|
+
if (pos >= this.r1) {
|
|
198
|
+
// check the character before the matched en/ene AND check for gem
|
|
199
|
+
if (!isVowel(result[pos - 1]) && result.substr(pos - 3, 3) !== "gem") {
|
|
200
|
+
// delete
|
|
201
|
+
result = result.removeSuffix(match.length);
|
|
202
|
+
// Undouble the ending
|
|
203
|
+
result = result.undoubleEnding();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (DEBUG) {
|
|
208
|
+
console.log("step 1b: " + result);
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
step1(word) {
|
|
215
|
+
var result = word;
|
|
216
|
+
// (1a) heden => replace with heid if in R1
|
|
217
|
+
if (result.endsin("heden") && result.length - 5 >= this.r1) {
|
|
218
|
+
result = result.removeSuffix(5);
|
|
219
|
+
result += "heid";
|
|
220
|
+
}
|
|
221
|
+
if (DEBUG) {
|
|
222
|
+
console.log("step 1a: " + result);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
result = this.step1b(result, ["en", "ene"]);
|
|
226
|
+
|
|
227
|
+
// (1c) s se => delete if in R1 and preceded by a valid s-ending
|
|
228
|
+
// Define a valid s-ending as a non-vowel other than j.
|
|
229
|
+
var match = result.endsinArr(["se", "s"]);
|
|
230
|
+
if (match != "") {
|
|
231
|
+
var pos = result.length - match.length;
|
|
232
|
+
if (pos >= this.r1) {
|
|
233
|
+
// check the character before the matched s/se
|
|
234
|
+
// HtD: if there is a s before the s/se the suffix should stay
|
|
235
|
+
//if (!isVowel(result[pos - 1]) && result[pos - 1] != "j") {
|
|
236
|
+
if (!isVowel(result[pos - 1]) && !result.match(/[js]se?$/)) {
|
|
237
|
+
result = result.removeSuffix(match.length);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (DEBUG) {
|
|
242
|
+
console.log("step 1c: " + result);
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
// Delete suffix e if in R1 and preceded by a non-vowel, and then undouble the ending
|
|
249
|
+
step2(word) {
|
|
250
|
+
var result = word;
|
|
251
|
+
if (result.endsin("e") && this.r1 <= result.length) {
|
|
252
|
+
if (result.length > 1 && !isVowel(result[result.length - 2])) {
|
|
253
|
+
// Delete
|
|
254
|
+
result = result.removeSuffix(1);
|
|
255
|
+
this.suffixeRemoved = true;
|
|
256
|
+
// Undouble the ending
|
|
257
|
+
result = result.undoubleEnding();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
if (DEBUG) {
|
|
263
|
+
console.log("step2: " + result);
|
|
264
|
+
}
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
// Step 3a: heid => delete heid if in R2 and not preceded by c, and treat a preceding en as in step 1(b)
|
|
270
|
+
step3a(word) {
|
|
271
|
+
var result = word;
|
|
272
|
+
if (result.endsin("heid") && result.length - 4 >= this.r2 && result[result.length - 5] != "c") {
|
|
273
|
+
// Delete
|
|
274
|
+
result = result.removeSuffix(4);
|
|
275
|
+
// Treat a preceding en as in step 1b
|
|
276
|
+
result = this.step1b(result, ["en"]);
|
|
277
|
+
}
|
|
278
|
+
if (DEBUG) {
|
|
279
|
+
console.log("step 3a: " + result);
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
// d suffixes: Search for the longest among the following suffixes, and perform the action indicated.
|
|
286
|
+
step3b(word) {
|
|
287
|
+
var result = word;
|
|
288
|
+
|
|
289
|
+
// end ing => delete if in R2; if preceded by ig, delete if in R2 and not preceded by e, otherwise undouble the ending
|
|
290
|
+
var suf = "";
|
|
291
|
+
if (suf = result.endsinArr(["end", "ing"])) {
|
|
292
|
+
if ((result.length - 3) >= this.r2) {
|
|
293
|
+
// Delete suffix
|
|
294
|
+
result = result.removeSuffix(3);
|
|
295
|
+
//this.regions(result);
|
|
296
|
+
if (result.endsin("ig") && (result.length - 2 >= this.r2) && result[result.length - 3] != "e") {
|
|
297
|
+
// Delete suffix
|
|
298
|
+
result = result.removeSuffix(2);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
result = result.undoubleEnding();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ig => delete if in R2 and not preceded by e
|
|
307
|
+
if (result.endsin("ig") && this.r2 <= result.length - 2 && result[result.length - 3] != "e") {
|
|
308
|
+
result = result.removeSuffix(2);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// lijk => delete if in R2, and then repeat step 2
|
|
312
|
+
if (result.endsin("lijk") && this.r2 <= result.length - 4) {
|
|
313
|
+
result = result.removeSuffix(4);
|
|
314
|
+
// repeat step 2
|
|
315
|
+
result = this.step2(result);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// baar => delete if in R2
|
|
319
|
+
if (result.endsin("baar") && this.r2 <= result.length - 4) {
|
|
320
|
+
result = result.removeSuffix(4);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// bar => delete if in R2 and if step 2 actually removed an e
|
|
324
|
+
if (result.endsin("bar") && this.r2 <= result.length - 3 && this.suffixeRemoved) {
|
|
325
|
+
result = result.removeSuffix(3);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (DEBUG) {
|
|
329
|
+
console.log("step 3b: " + result);
|
|
330
|
+
}
|
|
331
|
+
return result;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
// undouble vowel => If the words ends CVD, where C is a non-vowel,
|
|
336
|
+
// D is a non-vowel other than I, and V is double a, e, o or u,
|
|
337
|
+
// remove one of the vowels from V (for example, maan -> man, brood -> brod)
|
|
338
|
+
step4(word) {
|
|
339
|
+
var result = word;
|
|
340
|
+
|
|
341
|
+
if (result.match(/[bcdfghjklmnpqrstvwxz](aa|ee|oo|uu)[bcdfghjklmnpqrstvwxz]$/)) {
|
|
342
|
+
result = result.substr(0, result.length - 2) + result[result.length - 1];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (DEBUG) {
|
|
346
|
+
console.log("step4: " + result);
|
|
347
|
+
}
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Turn I and Y back into lower case.
|
|
352
|
+
postlude(word) {
|
|
353
|
+
return word.toLowerCase();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
stem(word) {
|
|
357
|
+
return this.postlude(this.step4(this.step3b(this.step3a(this.step2(this.step1(this.prelude(word)))))));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
module.exports = new PorterStemmer();
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2017, Dogan Yazar
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
|
12
|
+
all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
20
|
+
THE SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
var Stemmer = require('./stemmer_sv')
|
|
24
|
+
|
|
25
|
+
//Get R1 region
|
|
26
|
+
function getRegions(str) {
|
|
27
|
+
const match = str.match(/[aeiouyäåö][^aeiouyäåö]([a-zåäö]+)/)
|
|
28
|
+
let r1 = ''
|
|
29
|
+
if (match && match[1]) {
|
|
30
|
+
r1 = match[1]
|
|
31
|
+
if (match.index + 2 < 3) { //Not clear why we need this! Algorithm does not describe this part!
|
|
32
|
+
r1 = str.slice(3)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
r1,
|
|
37
|
+
rest: str.slice(0, str.length - r1.length)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function step1a(str, regions = getRegions(str)) {
|
|
42
|
+
const r1 = regions.r1
|
|
43
|
+
if (!r1) {
|
|
44
|
+
return str
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const regEx = /(heterna|hetens|anden|andes|andet|arens|arnas|ernas|heten|heter|ornas|ande|ades|aren|arna|arne|aste|erna|erns|orna|ade|are|ast|ens|ern|het|ad|ar|as|at|en|er|es|or|a|e)$/
|
|
48
|
+
const match = r1.match(regEx)
|
|
49
|
+
return match ? regions.rest + r1.slice(0, match.index) : str
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function step1b(str, regions = getRegions(str)) {
|
|
53
|
+
if (regions.r1 && str.match(/(b|c|d|f|g|h|j|k|l|m|n|o|p|r|t|v|y)s$/)) {
|
|
54
|
+
return str.slice(0, -1)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return str
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function step1(str) {
|
|
61
|
+
const regions = getRegions(str)
|
|
62
|
+
const resA = step1a(str, regions)
|
|
63
|
+
const resB = step1b(str, regions)
|
|
64
|
+
|
|
65
|
+
return resA.length < resB.length ? resA : resB
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function step2(str, regions = getRegions(str)) {
|
|
69
|
+
const r1 = regions.r1
|
|
70
|
+
if (r1 && r1.match(/(dd|gd|nn|dt|gt|kt|tt)$/)) {
|
|
71
|
+
return str.slice(0, -1)
|
|
72
|
+
}
|
|
73
|
+
return str
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function step3(str, regions = getRegions(str)) {
|
|
77
|
+
const r1 = regions.r1
|
|
78
|
+
if (r1) {
|
|
79
|
+
if (r1.match(/(lös|full)t$/)) {
|
|
80
|
+
return str.slice(0, -1)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const match = r1.match(/(lig|ig|els)$/)
|
|
84
|
+
return match ? regions.rest + r1.slice(0, match.index) : str
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return str
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function stem(_str) {
|
|
91
|
+
const str = _str.toLowerCase()
|
|
92
|
+
return step3(step2(step1(str)))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
var PorterStemmer = new Stemmer()
|
|
96
|
+
module.exports = PorterStemmer
|
|
97
|
+
|
|
98
|
+
// perform full stemming algorithm on a single word
|
|
99
|
+
PorterStemmer.stem = stem
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Copyright (c) 2012, David Przybilla, Chris Umbel
|
|
2
|
+
Copyright (c) 2012, 2018 David Przybilla, Chris Umbel, Hugo W.L. ter Doest
|
|
3
3
|
|
|
4
4
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
5
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -23,21 +23,24 @@ THE SOFTWARE.
|
|
|
23
23
|
var stopwords = require('../util/stopwords_es');
|
|
24
24
|
var Tokenizer = require('../tokenizers/aggressive_tokenizer_es');
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
class Stemmer {
|
|
27
|
+
constructor() {
|
|
28
|
+
|
|
29
|
+
}
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
stem(token) {
|
|
30
32
|
return token;
|
|
31
33
|
};
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
tokenizeAndStem(text, keepStops) {
|
|
34
36
|
var stemmedTokens = [];
|
|
35
37
|
|
|
38
|
+
var that = this;
|
|
36
39
|
new Tokenizer().tokenize(text).forEach(function(token) {
|
|
37
40
|
if (keepStops || stopwords.words.indexOf(token) == -1) {
|
|
38
41
|
var resultToken = token.toLowerCase();
|
|
39
42
|
if (resultToken.match(new RegExp('[a-záéíóúüñ0-9]+', 'gi'))) {
|
|
40
|
-
resultToken =
|
|
43
|
+
resultToken = that.stem(resultToken);
|
|
41
44
|
}
|
|
42
45
|
stemmedTokens.push(resultToken);
|
|
43
46
|
}
|
|
@@ -46,13 +49,16 @@ module.exports = function() {
|
|
|
46
49
|
return stemmedTokens;
|
|
47
50
|
};
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
attach() {
|
|
53
|
+
var that = this;
|
|
54
|
+
String.prototype.stem = function() {
|
|
55
|
+
return that.stem(this);
|
|
52
56
|
};
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
String.prototype.tokenizeAndStem = function(keepStops) {
|
|
59
|
+
return that.tokenizeAndStem(this, keepStops);
|
|
60
|
+
};
|
|
57
61
|
};
|
|
58
62
|
}
|
|
63
|
+
|
|
64
|
+
module.exports = Stemmer;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2018 Hugo W.L. ter Doest
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
|
12
|
+
all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
20
|
+
THE SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
var stopwords = require('../util/stopwords_nl');
|
|
24
|
+
var Tokenizer = require('../tokenizers/aggressive_tokenizer');
|
|
25
|
+
|
|
26
|
+
class Stemmer {
|
|
27
|
+
constructor() {
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
stem(token) {
|
|
32
|
+
return token;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
tokenizeAndStem(text, keepStops) {
|
|
36
|
+
var stemmedTokens = [];
|
|
37
|
+
|
|
38
|
+
var that = this;
|
|
39
|
+
new Tokenizer().tokenize(text).forEach(function(token) {
|
|
40
|
+
if (keepStops || stopwords.words.indexOf(token) == -1) {
|
|
41
|
+
var resultToken = token.toLowerCase();
|
|
42
|
+
if (resultToken.match(new RegExp('[a-zäëïöüáéíóúè0-9]+', 'gi'))) {
|
|
43
|
+
resultToken = that.stem(resultToken);
|
|
44
|
+
}
|
|
45
|
+
stemmedTokens.push(resultToken);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return stemmedTokens;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
attach() {
|
|
53
|
+
var that = this;
|
|
54
|
+
String.prototype.stem = function() {
|
|
55
|
+
return that.stem(this);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
String.prototype.tokenizeAndStem = function(keepStops) {
|
|
59
|
+
return that.tokenizeAndStem(this, keepStops);
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = Stemmer;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2017, Dogan Yazar
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
|
12
|
+
all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
20
|
+
THE SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
var stopwords = require('../util/stopwords_sv');
|
|
24
|
+
var Tokenizer = require('../tokenizers/aggressive_tokenizer_sv');
|
|
25
|
+
|
|
26
|
+
module.exports = function() {
|
|
27
|
+
var stemmer = this;
|
|
28
|
+
|
|
29
|
+
stemmer.stem = function(token) {
|
|
30
|
+
return token;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
stemmer.addStopWord = function(stopWord) {
|
|
34
|
+
stopwords.words.push(stopWord);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
stemmer.addStopWords = function(moreStopWords) {
|
|
38
|
+
stopwords.words = stopwords.words.concat(moreStopWords);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
stemmer.tokenizeAndStem = function(text, keepStops) {
|
|
42
|
+
var stemmedTokens = [];
|
|
43
|
+
|
|
44
|
+
new Tokenizer().tokenize(text).forEach(function(token) {
|
|
45
|
+
if(keepStops || stopwords.words.indexOf(token.toLowerCase()) == -1)
|
|
46
|
+
stemmedTokens.push(stemmer.stem(token));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return stemmedTokens;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
stemmer.attach = function() {
|
|
53
|
+
String.prototype.stem = function() {
|
|
54
|
+
return stemmer.stem(this);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
String.prototype.tokenizeAndStem = function(keepStops) {
|
|
58
|
+
return stemmer.tokenizeAndStem(this, keepStops);
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -151,8 +151,9 @@ TfIdf.prototype.addFileSync = function(path, encoding, key, restoreCache) {
|
|
|
151
151
|
TfIdf.prototype.tfidf = function(terms, d) {
|
|
152
152
|
var _this = this;
|
|
153
153
|
|
|
154
|
-
if(!_.isArray(terms))
|
|
154
|
+
if(!_.isArray(terms)) {
|
|
155
155
|
terms = tokenizer.tokenize(terms.toString().toLowerCase());
|
|
156
|
+
}
|
|
156
157
|
|
|
157
158
|
return terms.reduce(function(value, term) {
|
|
158
159
|
var idf = _this.idf(term);
|
|
@@ -163,10 +164,16 @@ TfIdf.prototype.tfidf = function(terms, d) {
|
|
|
163
164
|
|
|
164
165
|
TfIdf.prototype.listTerms = function(d) {
|
|
165
166
|
var terms = [];
|
|
166
|
-
|
|
167
|
+
var _this = this;
|
|
167
168
|
for(var term in this.documents[d]) {
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
if (this.documents[d]) {
|
|
170
|
+
if(term != '__key') {
|
|
171
|
+
terms.push({"term": term,
|
|
172
|
+
"tf": tf(term, _this.documents[d]),
|
|
173
|
+
"idf": _this.idf(term),
|
|
174
|
+
"tfidf": _this.tfidf(term, d)});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
170
177
|
}
|
|
171
178
|
|
|
172
179
|
return terms.sort(function(x, y) { return y.tfidf - x.tfidf; });
|
|
@@ -191,3 +198,19 @@ TfIdf.prototype.setTokenizer = function(t) {
|
|
|
191
198
|
throw new Error('Expected a valid Tokenizer');
|
|
192
199
|
tokenizer = t;
|
|
193
200
|
};
|
|
201
|
+
|
|
202
|
+
// Define a stopwords other than the default
|
|
203
|
+
TfIdf.prototype.setStopwords = function(customStopwords) {
|
|
204
|
+
|
|
205
|
+
if (!Array.isArray(customStopwords))
|
|
206
|
+
return false;
|
|
207
|
+
|
|
208
|
+
customStopwords.forEach(stopword => {
|
|
209
|
+
if ((typeof stopword) != 'string')
|
|
210
|
+
return false;
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
stopwords = customStopwords;
|
|
214
|
+
return true;
|
|
215
|
+
|
|
216
|
+
}
|