natural 0.1.28 → 0.4.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.
- package/.editorconfig +7 -0
- package/.npmignore +4 -11
- package/README.md +190 -50
- package/bayes_classifier.json +1 -0
- package/lib/natural/analyzers/sentence_analyzer.js +8 -5
- package/lib/natural/brill_pos_tagger/data/Dutch/README.txt +2 -0
- package/lib/natural/brill_pos_tagger/data/Dutch/brill_CONTEXTRULES.jg +285 -0
- package/lib/natural/brill_pos_tagger/data/Dutch/brill_LEXICON.jg +11699 -0
- package/lib/natural/brill_pos_tagger/data/English/README.txt +8 -0
- package/lib/natural/brill_pos_tagger/data/English/lexicon_from_posjs.json +297827 -0
- package/lib/natural/brill_pos_tagger/data/English/tr_from_brill_paper.txt +10 -0
- package/lib/natural/brill_pos_tagger/data/English/tr_from_posjs.txt +18 -0
- package/lib/natural/brill_pos_tagger/lib/Brill_POS_Tagger.js +136 -0
- package/lib/natural/brill_pos_tagger/lib/Predicate.js +410 -0
- package/lib/natural/brill_pos_tagger/lib/TF_Parser.js +717 -0
- package/lib/natural/brill_pos_tagger/lib/TF_Parser.pegjs +55 -0
- package/lib/natural/brill_pos_tagger/lib/TransformationRule.js +49 -0
- package/lib/natural/classifiers/bayes_classifier.js +6 -5
- package/lib/natural/classifiers/classifier.js +3 -2
- package/lib/natural/classifiers/logistic_regression_classifier.js +9 -5
- package/lib/natural/distance/dice_coefficient.js +22 -12
- package/lib/natural/distance/jaro-winkler_distance.js +13 -8
- package/lib/natural/index.js +6 -0
- package/lib/natural/inflectors/present_verb_inflector.js +1 -0
- package/lib/natural/ngrams/ngrams.js +6 -0
- package/lib/natural/normalizers/normalizer_ja.js +26 -8
- package/lib/natural/phonetics/double_metaphone.js +9 -4
- package/lib/natural/phonetics/metaphone.js +4 -0
- package/lib/natural/phonetics/soundex.js +11 -7
- package/lib/natural/spellcheck/spellcheck.js +80 -0
- package/lib/natural/stemmers/porter_stemmer.js +90 -62
- package/lib/natural/stemmers/porter_stemmer_fr.js +8 -3
- package/lib/natural/stemmers/porter_stemmer_pt.js +302 -0
- package/lib/natural/stemmers/stemmer.js +31 -6
- package/lib/natural/stemmers/stemmer_pt.js +65 -0
- package/lib/natural/stemmers/token.js +163 -0
- package/lib/natural/tfidf/tfidf.js +49 -18
- package/lib/natural/tokenizers/regexp_tokenizer.js +3 -3
- package/{examples/inflection/noun.js → lib/natural/tokenizers/sentence_tokenizer.js} +14 -10
- package/lib/natural/tokenizers/tokenizer_case.js +58 -0
- package/lib/natural/util/bag.js +1 -0
- package/lib/natural/util/stopwords_pt.js +136 -0
- package/lib/natural/wordnet/wordnet.js +2 -2
- package/lib/natural/wordnet/wordnet_file.js +1 -1
- package/lr_classifier.json +1 -0
- package/package.json +7 -4
- package/package.json~ +66 -0
- package/benchmarks/index.js +0 -32
- package/benchmarks/metaphone.js +0 -27
- package/benchmarks/soundex.js +0 -27
- package/examples/classification/basic.js +0 -35
- package/examples/classification/recall.js +0 -28
- package/examples/classification/store.js +0 -37
- package/examples/inflection/count.js +0 -35
- package/examples/inflection/noun_attach.js +0 -33
- package/examples/phonetics/compare.js +0 -39
- package/examples/phonetics/sounds_like.js +0 -40
- package/examples/phonetics/tokenize_and_phoneticize.js +0 -54
- package/examples/stemming/stem_corpus.js +0 -29
- package/examples/stemming/stem_word.js +0 -26
- package/examples/tfidf/array_example.js +0 -34
- package/examples/tfidf/multiple_terms.js +0 -33
- package/examples/tfidf/tfidf_example.js +0 -44
- package/io_spec/test_data/tfidf/one +0 -1
- package/io_spec/test_data/tfidf/two +0 -1
- package/io_spec/tfidf_spec.js +0 -50
- package/io_spec/wordnet_spec.js +0 -81
- package/spec/aggressive_tokenizer_es_spec.js +0 -36
- package/spec/aggressive_tokenizer_fr_spec.js +0 -101
- package/spec/aggressive_tokenizer_nl_spec.js +0 -36
- package/spec/aggressive_tokenizer_pt_spec.js +0 -56
- package/spec/aggressive_tokenizer_spec.js +0 -56
- package/spec/bayes_classifier_spec.js +0 -173
- package/spec/classifier_spec.js +0 -91
- package/spec/count_inflector_fr_spec.js +0 -44
- package/spec/count_inflector_spec.js +0 -73
- package/spec/dice_coefficient_spec.js +0 -26
- package/spec/double_metaphone_spec.js +0 -743
- package/spec/jaro-winkler_spec.js +0 -53
- package/spec/lancaster_stemmer_spec.js +0 -73
- package/spec/levenshtein_spec.js +0 -66
- package/spec/logistic_regression_classifier_spec.js +0 -122
- package/spec/longest_path_tree_spec.js +0 -102
- package/spec/metaphone_spec.js +0 -247
- package/spec/ngram_spec.js +0 -130
- package/spec/ngram_zh_spec.js +0 -122
- package/spec/normalizer_ja_spec.js +0 -154
- package/spec/normalizer_no_spec.js +0 -111
- package/spec/normalizer_spec.js +0 -118
- package/spec/noun_inflector_fr_spec.js +0 -476
- package/spec/noun_inflector_ja_spec.js +0 -112
- package/spec/noun_inflector_spec.js +0 -270
- package/spec/porter_stemmer_fr_spec.js +0 -101
- package/spec/porter_stemmer_it_spec.js +0 -44
- package/spec/porter_stemmer_no_spec.js +0 -143
- package/spec/porter_stemmer_ru_spec.js +0 -63
- package/spec/porter_stemmer_spec.js +0 -137
- package/spec/present_verb_inflector_spec.js +0 -125
- package/spec/sentence_analyzer_spec.js +0 -29
- package/spec/shortest_path_tree_spec.js +0 -132
- package/spec/soundex_spec.js +0 -154
- package/spec/stemmer_ja_spec.js +0 -66
- package/spec/test_data/snowball_fr.txt +0 -20402
- package/spec/test_data/snowball_it.txt +0 -35482
- package/spec/test_data/snowball_no.txt +0 -20628
- package/spec/test_data/tfdif_testcorpus.txt +0 -7
- package/spec/test_data/tfidf_document1.txt +0 -1
- package/spec/test_data/tfidf_document2.txt +0 -1
- package/spec/test_data/tfidf_document3.txt +0 -1
- package/spec/test_data/tfidf_document4.txt +0 -1
- package/spec/test_data/tfidf_teststopwords.txt +0 -1
- package/spec/test_data/wordnet_mini.sql +0 -95
- package/spec/tfidf_spec.js +0 -198
- package/spec/tokenizer_ja_spec.js +0 -58
- package/spec/transliterator_ja_spec.js +0 -103
- package/spec/treebank_word_tokenizer_spec.js +0 -36
- package/spec/trie_spec.js +0 -206
|
@@ -25,19 +25,19 @@ var Stemmer = require('./stemmer');
|
|
|
25
25
|
// denote groups of consecutive consonants with a C and consecutive vowels
|
|
26
26
|
// with a V.
|
|
27
27
|
function categorizeGroups(token) {
|
|
28
|
-
return token.replace(/[^aeiou]+/g, '
|
|
28
|
+
return token.replace(/[^aeiouy]+y/g, 'CV').replace(/[aeiou]+/g, 'V').replace(/[^V]+/g, 'C');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// denote single consonants with a C and single vowels with a V
|
|
32
32
|
function categorizeChars(token) {
|
|
33
|
-
return token.replace(/[^
|
|
33
|
+
return token.replace(/[^aeiouy]y/g, 'CV').replace(/[aeiou]/g, 'V').replace(/[^V]/g, 'C');
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
// calculate the "measure" M of a word. M is the count of VC sequences dropping
|
|
37
37
|
// an initial C if it exists and a trailing V if it exists.
|
|
38
38
|
function measure(token) {
|
|
39
39
|
if(!token)
|
|
40
|
-
|
|
40
|
+
return -1;
|
|
41
41
|
|
|
42
42
|
return categorizeGroups(token).replace(/^C/, '').replace(/V$/, '').length / 2;
|
|
43
43
|
}
|
|
@@ -67,14 +67,12 @@ function attemptReplace(token, pattern, replacement, callback) {
|
|
|
67
67
|
// attempt to replace a list of patterns/replacements on a token for a minimum
|
|
68
68
|
// measure M.
|
|
69
69
|
function attemptReplacePatterns(token, replacements, measureThreshold) {
|
|
70
|
-
var replacement =
|
|
70
|
+
var replacement = token;
|
|
71
71
|
|
|
72
|
-
for(var i = 0; i < replacements.length; i++) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if(replacement)
|
|
77
|
-
break;
|
|
72
|
+
for(var i = 0; i < replacements.length; i++) {
|
|
73
|
+
if(measureThreshold == null || measure(attemptReplace(token, replacements[i][0], replacements[i][1])) > measureThreshold) {
|
|
74
|
+
replacement = attemptReplace(replacement, replacements[i][0], replacements[i][2]) || replacement;
|
|
75
|
+
}
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
return replacement;
|
|
@@ -83,50 +81,73 @@ function attemptReplacePatterns(token, replacements, measureThreshold) {
|
|
|
83
81
|
// replace a list of patterns/replacements on a word. if no match is made return
|
|
84
82
|
// the original token.
|
|
85
83
|
function replacePatterns(token, replacements, measureThreshold) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
return attemptReplacePatterns(token, replacements, measureThreshold) || token;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// TODO: this should replace all of the messy replacement stuff above
|
|
88
|
+
function replaceRegex(token, regex, includeParts, minimumMeasure) {
|
|
89
|
+
var parts;
|
|
90
|
+
var result = '';
|
|
91
|
+
|
|
92
|
+
if(regex.test(token)) {
|
|
93
|
+
parts = regex.exec(token);
|
|
94
|
+
|
|
95
|
+
includeParts.forEach(function(i) {
|
|
96
|
+
result += parts[i];
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if(measure(result) > minimumMeasure) {
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return null;
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
// step 1a as defined for the porter stemmer algorithm.
|
|
93
108
|
function step1a(token) {
|
|
94
|
-
if(token.match(/(ss|i)es$/))
|
|
109
|
+
if(token.match(/(ss|i)es$/)) {
|
|
95
110
|
return token.replace(/(ss|i)es$/, '$1');
|
|
96
|
-
|
|
97
|
-
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if(token.substr(-1) == 's' && token.substr(-2, 1) != 's' && token.length > 2) {
|
|
98
114
|
return token.replace(/s?$/, '');
|
|
99
|
-
|
|
115
|
+
}
|
|
116
|
+
|
|
100
117
|
return token;
|
|
101
118
|
}
|
|
102
119
|
|
|
103
120
|
// step 1b as defined for the porter stemmer algorithm.
|
|
104
121
|
function step1b(token) {
|
|
105
122
|
if(token.substr(-3) == 'eed') {
|
|
106
|
-
|
|
123
|
+
if(measure(token.substr(0, token.length - 3)) > 0)
|
|
107
124
|
return token.replace(/eed$/, 'ee');
|
|
108
125
|
} else {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
var result = attemptReplace(token, /(ed|ing)$/, '', function(token) {
|
|
127
|
+
if(categorizeGroups(token).indexOf('V') >= 0) {
|
|
128
|
+
result = attemptReplacePatterns(token, [['at', '', 'ate'], ['bl', '', 'ble'], ['iz', '', 'ize']]);
|
|
129
|
+
|
|
130
|
+
if(result != token) {
|
|
131
|
+
return result;
|
|
132
|
+
} else {
|
|
133
|
+
if(endsWithDoublCons(token) && token.match(/[^lsz]$/)) {
|
|
134
|
+
return token.replace(/([^aeiou])\1$/, '$1');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if(measure(token) == 1 && categorizeChars(token).substr(-3) == 'CVC' && token.match(/[^wxy]$/)) {
|
|
138
|
+
return token + 'e';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return token;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return null;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if(result) {
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
130
151
|
}
|
|
131
152
|
|
|
132
153
|
return token;
|
|
@@ -134,44 +155,48 @@ function step1b(token) {
|
|
|
134
155
|
|
|
135
156
|
// step 1c as defined for the porter stemmer algorithm.
|
|
136
157
|
function step1c(token) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
158
|
+
var categorizedGroups = categorizeGroups(token);
|
|
159
|
+
|
|
160
|
+
if(token.substr(-1) == 'y' && categorizedGroups.substr(0, categorizedGroups.length - 1).indexOf('V') > -1) {
|
|
161
|
+
return token.replace(/y$/, 'i');
|
|
140
162
|
}
|
|
141
|
-
|
|
163
|
+
|
|
142
164
|
return token;
|
|
143
165
|
}
|
|
144
166
|
|
|
145
167
|
// step 2 as defined for the porter stemmer algorithm.
|
|
146
168
|
function step2(token) {
|
|
147
|
-
|
|
148
|
-
['izer', 'ize'], ['abli', 'able'], ['alli', 'al'], ['entli', 'ent'], ['eli', 'e'],
|
|
149
|
-
['ousli', 'ous'], ['ization', 'ize'], ['ation', 'ate'], ['ator', 'ate'],['alism', 'al'],
|
|
150
|
-
['iveness', 'ive'], ['fulness', 'ful'], ['ousness', 'ous'], ['aliti', 'al'],
|
|
151
|
-
['iviti', 'ive'], ['biliti', 'ble']], 0);
|
|
169
|
+
token = replacePatterns(token, [['ational', '', 'ate'], ['tional', '', 'tion'], ['enci', '', 'ence'], ['anci', '', 'ance'],
|
|
170
|
+
['izer', '', 'ize'], ['abli', '', 'able'], ['bli', '', 'ble'], ['alli', '', 'al'], ['entli', '', 'ent'], ['eli', '', 'e'],
|
|
171
|
+
['ousli', '', 'ous'], ['ization', '', 'ize'], ['ation', '', 'ate'], ['ator', '', 'ate'],['alism', '', 'al'],
|
|
172
|
+
['iveness', '', 'ive'], ['fulness', '', 'ful'], ['ousness', '', 'ous'], ['aliti', '', 'al'],
|
|
173
|
+
['iviti', '', 'ive'], ['biliti', '', 'ble'], ['logi', '', 'log']], 0);
|
|
174
|
+
|
|
175
|
+
return token;
|
|
152
176
|
}
|
|
153
177
|
|
|
154
178
|
// step 3 as defined for the porter stemmer algorithm.
|
|
155
179
|
function step3(token) {
|
|
156
|
-
return replacePatterns(token, [['icate', 'ic'], ['ative', ''], ['alize', 'al'],
|
|
157
|
-
['iciti', 'ic'], ['ical', 'ic'], ['ful', ''], ['ness', '']], 0);
|
|
180
|
+
return replacePatterns(token, [['icate', '', 'ic'], ['ative', '', ''], ['alize', '', 'al'],
|
|
181
|
+
['iciti', '', 'ic'], ['ical', '', 'ic'], ['ful', '', ''], ['ness', '', '']], 0);
|
|
158
182
|
}
|
|
159
183
|
|
|
160
184
|
// step 4 as defined for the porter stemmer algorithm.
|
|
161
185
|
function step4(token) {
|
|
162
|
-
return
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
['ate', ''], ['iti', ''], ['ous', ''], ['ive', ''],
|
|
166
|
-
['ize', '']], 1);
|
|
186
|
+
return replaceRegex(token, /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/, [1], 1) ||
|
|
187
|
+
replaceRegex(token, /^(.+?)(s|t)(ion)$/, [1, 2], 1) ||
|
|
188
|
+
token;
|
|
167
189
|
}
|
|
168
190
|
|
|
169
191
|
// step 5a as defined for the porter stemmer algorithm.
|
|
170
192
|
function step5a(token) {
|
|
171
|
-
var m = measure(token);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
193
|
+
var m = measure(token.replace(/e$/, ''));
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if(m > 1 || (m == 1 && !(categorizeChars(token).substr(-4, 3) == 'CVC' && token.match(/[^wxy].$/)))) {
|
|
198
|
+
token = token.replace(/e$/, '');
|
|
199
|
+
}
|
|
175
200
|
|
|
176
201
|
return token;
|
|
177
202
|
}
|
|
@@ -179,8 +204,7 @@ function step5a(token) {
|
|
|
179
204
|
// step 5b as defined for the porter stemmer algorithm.
|
|
180
205
|
function step5b(token) {
|
|
181
206
|
if(measure(token) > 1) {
|
|
182
|
-
|
|
183
|
-
return token.replace(/ll$/, 'l');
|
|
207
|
+
return token.replace(/ll$/, 'l');
|
|
184
208
|
}
|
|
185
209
|
|
|
186
210
|
return token;
|
|
@@ -189,12 +213,16 @@ function step5b(token) {
|
|
|
189
213
|
var PorterStemmer = new Stemmer();
|
|
190
214
|
module.exports = PorterStemmer;
|
|
191
215
|
|
|
216
|
+
|
|
192
217
|
// perform full stemming algorithm on a single word
|
|
193
218
|
PorterStemmer.stem = function(token) {
|
|
219
|
+
if(token.length < 3) return token;
|
|
194
220
|
return step5b(step5a(step4(step3(step2(step1c(step1b(step1a(token.toLowerCase())))))))).toString();
|
|
195
221
|
};
|
|
196
222
|
|
|
197
223
|
//exports for tests
|
|
224
|
+
PorterStemmer.categorizeGroups = categorizeGroups;
|
|
225
|
+
PorterStemmer.measure = measure;
|
|
198
226
|
PorterStemmer.step1a = step1a;
|
|
199
227
|
PorterStemmer.step1b = step1b;
|
|
200
228
|
PorterStemmer.step1c = step1c;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
/*
|
|
2
4
|
Copyright (c) 2014, Ismaël Héry
|
|
3
5
|
|
|
@@ -50,7 +52,8 @@ function stem(token) {
|
|
|
50
52
|
return token;
|
|
51
53
|
|
|
52
54
|
var regs = regions(token);
|
|
53
|
-
|
|
55
|
+
|
|
56
|
+
var r1_txt, r2_txt, rv_txt;
|
|
54
57
|
r1_txt = token.substring(regs.r1);
|
|
55
58
|
r2_txt = token.substring(regs.r2);
|
|
56
59
|
rv_txt = token.substring(regs.rv);
|
|
@@ -269,9 +272,11 @@ function stem(token) {
|
|
|
269
272
|
* @return {Object} Regions r1, r2, rv as offsets from the begining of the word
|
|
270
273
|
*/
|
|
271
274
|
function regions(token) {
|
|
272
|
-
var r1
|
|
275
|
+
var r1, r2, rv, len;
|
|
273
276
|
var i;
|
|
274
277
|
|
|
278
|
+
r1 = r2 = rv = len = token.length;
|
|
279
|
+
|
|
275
280
|
// R1 is the region after the first non-vowel following a vowel,
|
|
276
281
|
for (var i = 0; i < len - 1 && r1 == len; i++) {
|
|
277
282
|
if (isVowel(token[i]) && !isVowel(token[i + 1])) {
|
|
@@ -369,4 +374,4 @@ function isVowel(letter) {
|
|
|
369
374
|
function endsin(token, suffix) {
|
|
370
375
|
if (token.length < suffix.length) return false;
|
|
371
376
|
return (token.slice(-suffix.length) == suffix);
|
|
372
|
-
};
|
|
377
|
+
};
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2015, Luís Rodrigues
|
|
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
|
+
module.exports = (function () {
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
var Stemmer = require('./stemmer_pt'),
|
|
27
|
+
Token = require('./token'),
|
|
28
|
+
PorterStemmer = new Stemmer();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Marks a region after the first non-vowel following a vowel, or the
|
|
32
|
+
* null region at the end of the word if there is no such non-vowel.
|
|
33
|
+
*
|
|
34
|
+
* @param {Object} token Token to stem.
|
|
35
|
+
* @param {Number} start Start index (defaults to 0).
|
|
36
|
+
* @param {Number} Region start index.
|
|
37
|
+
*/
|
|
38
|
+
var markRegionN = function (start) {
|
|
39
|
+
var index = start || 0,
|
|
40
|
+
length = this.string.length,
|
|
41
|
+
region = length;
|
|
42
|
+
|
|
43
|
+
while (index < length - 1 && region === length) {
|
|
44
|
+
if (this.hasVowelAtIndex(index) && !this.hasVowelAtIndex(index + 1)) {
|
|
45
|
+
region = index + 2;
|
|
46
|
+
}
|
|
47
|
+
index++;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return region;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Mark RV.
|
|
55
|
+
*
|
|
56
|
+
* @param {Object} token Token to stem.
|
|
57
|
+
* @return {Number} Region start index.
|
|
58
|
+
*/
|
|
59
|
+
var markRegionV = function () {
|
|
60
|
+
var rv = this.string.length;
|
|
61
|
+
|
|
62
|
+
if (rv > 3) {
|
|
63
|
+
if (!this.hasVowelAtIndex(1)) {
|
|
64
|
+
rv = this.nextVowelIndex(2) + 1;
|
|
65
|
+
|
|
66
|
+
} else if (this.hasVowelAtIndex(0) && this.hasVowelAtIndex(1)) {
|
|
67
|
+
rv = this.nextConsonantIndex(2) + 1;
|
|
68
|
+
|
|
69
|
+
} else {
|
|
70
|
+
rv = 3;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return rv;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Prelude.
|
|
79
|
+
*
|
|
80
|
+
* Nasalised vowel forms should be treated as a vowel followed by a consonant.
|
|
81
|
+
*
|
|
82
|
+
* @param {String} token Word to stem.
|
|
83
|
+
* @return {String} Stemmed token.
|
|
84
|
+
*/
|
|
85
|
+
function prelude (token) {
|
|
86
|
+
return token
|
|
87
|
+
.replaceAll('ã', 'a~')
|
|
88
|
+
.replaceAll('õ', 'o~');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Step 1: Standard suffix removal.
|
|
93
|
+
*
|
|
94
|
+
* This step should always be performed.
|
|
95
|
+
*
|
|
96
|
+
* @param {Token} token Word to stem.
|
|
97
|
+
* @return {Token} Stemmed token.
|
|
98
|
+
*/
|
|
99
|
+
function standardSuffix (token) {
|
|
100
|
+
|
|
101
|
+
token.replaceSuffixInRegion([
|
|
102
|
+
'amentos', 'imentos', 'aço~es', 'adoras', 'adores', 'amento', 'imento',
|
|
103
|
+
|
|
104
|
+
'aça~o', 'adora', 'ância', 'antes', 'ismos', 'istas',
|
|
105
|
+
|
|
106
|
+
'ador', 'ante', 'ável', 'ezas', 'icas', 'icos', 'ismo', 'ista', 'ível',
|
|
107
|
+
'osas', 'osos',
|
|
108
|
+
|
|
109
|
+
'eza', 'ica', 'ico', 'osa', 'oso'
|
|
110
|
+
|
|
111
|
+
], '', 'r2');
|
|
112
|
+
|
|
113
|
+
token.replaceSuffixInRegion(['logias', 'logia'], 'log', 'r2');
|
|
114
|
+
|
|
115
|
+
// token.replaceSuffixInRegion(['uço~es', 'uça~o'], 'u', 'r1');
|
|
116
|
+
|
|
117
|
+
token.replaceSuffixInRegion(['ências', 'ência'], 'ente', 'r2');
|
|
118
|
+
|
|
119
|
+
token.replaceSuffixInRegion([
|
|
120
|
+
'ativamente', 'icamente', 'ivamente', 'osamente', 'adamente'
|
|
121
|
+
], '', 'r2');
|
|
122
|
+
|
|
123
|
+
token.replaceSuffixInRegion('amente', '', 'r1');
|
|
124
|
+
|
|
125
|
+
token.replaceSuffixInRegion([
|
|
126
|
+
'antemente', 'avelmente', 'ivelmente', 'mente'
|
|
127
|
+
], '', 'r2');
|
|
128
|
+
|
|
129
|
+
token.replaceSuffixInRegion([
|
|
130
|
+
'abilidades', 'abilidade',
|
|
131
|
+
'icidades', 'icidade',
|
|
132
|
+
'ividades', 'ividade',
|
|
133
|
+
'idades', 'idade'
|
|
134
|
+
], '', 'r2');
|
|
135
|
+
|
|
136
|
+
token.replaceSuffixInRegion([
|
|
137
|
+
'ativas', 'ativos', 'ativa', 'ativo',
|
|
138
|
+
'ivas', 'ivos', 'iva', 'ivo'
|
|
139
|
+
], '', 'r2');
|
|
140
|
+
|
|
141
|
+
if (token.hasSuffix('eiras') || token.hasSuffix('eira')) {
|
|
142
|
+
token.replaceSuffixInRegion(['iras', 'ira'], 'ir', 'rv');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return token;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Step 2: Verb suffix removal.
|
|
150
|
+
*
|
|
151
|
+
* Perform this step if no ending was removed in step 1.
|
|
152
|
+
*
|
|
153
|
+
* @param {Token} token Token to stem.
|
|
154
|
+
* @return {Token} Stemmed token.
|
|
155
|
+
*/
|
|
156
|
+
function verbSuffix (token) {
|
|
157
|
+
|
|
158
|
+
token.replaceSuffixInRegion([
|
|
159
|
+
'aríamos', 'ássemos', 'eríamos', 'êssemos', 'iríamos', 'íssemos',
|
|
160
|
+
|
|
161
|
+
'áramos', 'aremos', 'aríeis', 'ásseis', 'ávamos', 'éramos', 'eremos',
|
|
162
|
+
'eríeis', 'ésseis', 'íramos', 'iremos', 'iríeis', 'ísseis',
|
|
163
|
+
|
|
164
|
+
'ara~o', 'ardes', 'areis', 'áreis', 'ariam', 'arias', 'armos', 'assem',
|
|
165
|
+
'asses', 'astes', 'áveis', 'era~o', 'erdes', 'ereis', 'éreis', 'eriam',
|
|
166
|
+
'erias', 'ermos', 'essem', 'esses', 'estes', 'íamos', 'ira~o', 'irdes',
|
|
167
|
+
'ireis', 'íreis', 'iriam', 'irias', 'irmos', 'issem', 'isses', 'istes',
|
|
168
|
+
|
|
169
|
+
'adas', 'ados', 'amos', 'ámos', 'ando', 'aram', 'aras', 'arás', 'arei',
|
|
170
|
+
'arem', 'ares', 'aria', 'asse', 'aste', 'avam', 'avas', 'emos', 'endo',
|
|
171
|
+
'eram', 'eras', 'erás', 'erei', 'erem', 'eres', 'eria', 'esse', 'este',
|
|
172
|
+
'idas', 'idos', 'íeis', 'imos', 'indo', 'iram', 'iras', 'irás', 'irei',
|
|
173
|
+
'irem', 'ires', 'iria', 'isse', 'iste',
|
|
174
|
+
|
|
175
|
+
'ada', 'ado', 'ais', 'ara', 'ará', 'ava', 'eis', 'era', 'erá', 'iam',
|
|
176
|
+
'ias', 'ida', 'ido', 'ira', 'irá',
|
|
177
|
+
|
|
178
|
+
'am', 'ar', 'as', 'ei', 'em', 'er', 'es', 'eu', 'ia', 'ir', 'is', 'iu', 'ou'
|
|
179
|
+
|
|
180
|
+
], '', 'rv');
|
|
181
|
+
|
|
182
|
+
return token;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Step 3: Delete suffix i.
|
|
187
|
+
*
|
|
188
|
+
* Perform this step if the word was changed, in RV and preceded by c.
|
|
189
|
+
*
|
|
190
|
+
* @param {Token} token Token to stem.
|
|
191
|
+
* @return {Token} Stemmed token.
|
|
192
|
+
*/
|
|
193
|
+
function iPrecededByCSuffix (token) {
|
|
194
|
+
|
|
195
|
+
if (token.hasSuffix('ci')) {
|
|
196
|
+
token.replaceSuffixInRegion('i', '', 'rv');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return token;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Step 4: Residual suffix.
|
|
204
|
+
*
|
|
205
|
+
* Perform this step if steps 1 and 2 did not alter the word.
|
|
206
|
+
*
|
|
207
|
+
* @param {Token} token Token to stem.
|
|
208
|
+
* @return {Token} Stemmed token.
|
|
209
|
+
*/
|
|
210
|
+
function residualSuffix (token) {
|
|
211
|
+
|
|
212
|
+
token.replaceSuffixInRegion(['os', 'a', 'i', 'o', 'á', 'í', 'ó'], '', 'rv');
|
|
213
|
+
|
|
214
|
+
return token;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Step 5: Residual form.
|
|
219
|
+
*
|
|
220
|
+
* This step should always be performed.
|
|
221
|
+
*
|
|
222
|
+
* @param {Token} token Token to stem.
|
|
223
|
+
* @return {Token} Stemmed token.
|
|
224
|
+
*/
|
|
225
|
+
function residualForm (token) {
|
|
226
|
+
|
|
227
|
+
var tokenString = token.string;
|
|
228
|
+
|
|
229
|
+
if (token.hasSuffix('gue') || token.hasSuffix('gué') || token.hasSuffix('guê')) {
|
|
230
|
+
token.replaceSuffixInRegion(['ue', 'ué', 'uê'], '', 'rv');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (token.hasSuffix('cie') || token.hasSuffix('cié') || token.hasSuffix('ciê')) {
|
|
234
|
+
token.replaceSuffixInRegion(['ie', 'ié', 'iê'], '', 'rv');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (tokenString === token.string) {
|
|
238
|
+
token.replaceSuffixInRegion(['e', 'é', 'ê'], '', 'rv');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
token.replaceSuffixInRegion('ç', 'c', 'all');
|
|
242
|
+
|
|
243
|
+
return token;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Postlude.
|
|
248
|
+
*
|
|
249
|
+
* Turns a~, o~ back into ã, õ.
|
|
250
|
+
*
|
|
251
|
+
* @param {String} token Word to stem.
|
|
252
|
+
* @return {String} Stemmed token.
|
|
253
|
+
*/
|
|
254
|
+
function postlude (token) {
|
|
255
|
+
return token
|
|
256
|
+
.replaceAll('a~', 'ã')
|
|
257
|
+
.replaceAll('o~', 'õ');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Stems a word using a Porter stemmer algorithm.
|
|
262
|
+
*
|
|
263
|
+
* @param {String} word Word to stem.
|
|
264
|
+
* @return {String} Stemmed token.
|
|
265
|
+
*/
|
|
266
|
+
PorterStemmer.stem = function (word) {
|
|
267
|
+
var token = new Token(word.toLowerCase()),
|
|
268
|
+
original;
|
|
269
|
+
|
|
270
|
+
token = prelude(token);
|
|
271
|
+
|
|
272
|
+
token.usingVowels('aeiouáéíóúâêôàãõ')
|
|
273
|
+
.markRegion('all', 0)
|
|
274
|
+
.markRegion('r1', null, markRegionN)
|
|
275
|
+
.markRegion('r2', token.regions.r1, markRegionN)
|
|
276
|
+
.markRegion('rv', null, markRegionV);
|
|
277
|
+
|
|
278
|
+
original = token.string;
|
|
279
|
+
|
|
280
|
+
// Always do step 1.
|
|
281
|
+
token = standardSuffix(token);
|
|
282
|
+
|
|
283
|
+
// Do step 2 if no ending was removed by step 1.
|
|
284
|
+
if (token.string === original) {
|
|
285
|
+
token = verbSuffix(token);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// If the last step to be obeyed — either step 1 or 2 — altered the word,
|
|
289
|
+
// do step 3. Alternatively, if neither steps 1 nor 2 altered the word, do
|
|
290
|
+
// step 4.
|
|
291
|
+
token = token.string !== original ? iPrecededByCSuffix(token) : residualSuffix(token);
|
|
292
|
+
|
|
293
|
+
// Always do step 5.
|
|
294
|
+
token = residualForm(token);
|
|
295
|
+
|
|
296
|
+
token = postlude(token);
|
|
297
|
+
|
|
298
|
+
return token.string;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
return PorterStemmer;
|
|
302
|
+
})();
|
|
@@ -38,14 +38,39 @@ module.exports = function() {
|
|
|
38
38
|
stopwords.words = stopwords.words.concat(moreStopWords);
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
stemmer.removeStopWord = function(stopWord) {
|
|
42
|
+
this.removeStopWords([stopWord])
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
stemmer.removeStopWords = function(moreStopWords) {
|
|
46
|
+
moreStopWords.forEach(function(stopWord){
|
|
47
|
+
var idx = stopwords.words.indexOf(stopWord);
|
|
48
|
+
if (idx >= 0) {
|
|
49
|
+
stopwords.words.splice(idx, 1);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
|
|
41
56
|
stemmer.tokenizeAndStem = function(text, keepStops) {
|
|
42
57
|
var stemmedTokens = [];
|
|
43
|
-
|
|
44
|
-
new Tokenizer().tokenize(
|
|
45
|
-
|
|
58
|
+
var lowercaseText = text.toLowerCase();
|
|
59
|
+
var tokens = new Tokenizer().tokenize(lowercaseText);
|
|
60
|
+
|
|
61
|
+
if (keepStops) {
|
|
62
|
+
tokens.forEach(function(token) {
|
|
46
63
|
stemmedTokens.push(stemmer.stem(token));
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
else {
|
|
68
|
+
tokens.forEach(function(token) {
|
|
69
|
+
if (stopwords.words.indexOf(token) == -1)
|
|
70
|
+
stemmedTokens.push(stemmer.stem(token));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
49
74
|
return stemmedTokens;
|
|
50
75
|
};
|
|
51
76
|
|
|
@@ -53,7 +78,7 @@ module.exports = function() {
|
|
|
53
78
|
String.prototype.stem = function() {
|
|
54
79
|
return stemmer.stem(this);
|
|
55
80
|
};
|
|
56
|
-
|
|
81
|
+
|
|
57
82
|
String.prototype.tokenizeAndStem = function(keepStops) {
|
|
58
83
|
return stemmer.tokenizeAndStem(this, keepStops);
|
|
59
84
|
};
|