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.
Files changed (117) hide show
  1. package/.editorconfig +7 -0
  2. package/.npmignore +4 -11
  3. package/README.md +190 -50
  4. package/bayes_classifier.json +1 -0
  5. package/lib/natural/analyzers/sentence_analyzer.js +8 -5
  6. package/lib/natural/brill_pos_tagger/data/Dutch/README.txt +2 -0
  7. package/lib/natural/brill_pos_tagger/data/Dutch/brill_CONTEXTRULES.jg +285 -0
  8. package/lib/natural/brill_pos_tagger/data/Dutch/brill_LEXICON.jg +11699 -0
  9. package/lib/natural/brill_pos_tagger/data/English/README.txt +8 -0
  10. package/lib/natural/brill_pos_tagger/data/English/lexicon_from_posjs.json +297827 -0
  11. package/lib/natural/brill_pos_tagger/data/English/tr_from_brill_paper.txt +10 -0
  12. package/lib/natural/brill_pos_tagger/data/English/tr_from_posjs.txt +18 -0
  13. package/lib/natural/brill_pos_tagger/lib/Brill_POS_Tagger.js +136 -0
  14. package/lib/natural/brill_pos_tagger/lib/Predicate.js +410 -0
  15. package/lib/natural/brill_pos_tagger/lib/TF_Parser.js +717 -0
  16. package/lib/natural/brill_pos_tagger/lib/TF_Parser.pegjs +55 -0
  17. package/lib/natural/brill_pos_tagger/lib/TransformationRule.js +49 -0
  18. package/lib/natural/classifiers/bayes_classifier.js +6 -5
  19. package/lib/natural/classifiers/classifier.js +3 -2
  20. package/lib/natural/classifiers/logistic_regression_classifier.js +9 -5
  21. package/lib/natural/distance/dice_coefficient.js +22 -12
  22. package/lib/natural/distance/jaro-winkler_distance.js +13 -8
  23. package/lib/natural/index.js +6 -0
  24. package/lib/natural/inflectors/present_verb_inflector.js +1 -0
  25. package/lib/natural/ngrams/ngrams.js +6 -0
  26. package/lib/natural/normalizers/normalizer_ja.js +26 -8
  27. package/lib/natural/phonetics/double_metaphone.js +9 -4
  28. package/lib/natural/phonetics/metaphone.js +4 -0
  29. package/lib/natural/phonetics/soundex.js +11 -7
  30. package/lib/natural/spellcheck/spellcheck.js +80 -0
  31. package/lib/natural/stemmers/porter_stemmer.js +90 -62
  32. package/lib/natural/stemmers/porter_stemmer_fr.js +8 -3
  33. package/lib/natural/stemmers/porter_stemmer_pt.js +302 -0
  34. package/lib/natural/stemmers/stemmer.js +31 -6
  35. package/lib/natural/stemmers/stemmer_pt.js +65 -0
  36. package/lib/natural/stemmers/token.js +163 -0
  37. package/lib/natural/tfidf/tfidf.js +49 -18
  38. package/lib/natural/tokenizers/regexp_tokenizer.js +3 -3
  39. package/{examples/inflection/noun.js → lib/natural/tokenizers/sentence_tokenizer.js} +14 -10
  40. package/lib/natural/tokenizers/tokenizer_case.js +58 -0
  41. package/lib/natural/util/bag.js +1 -0
  42. package/lib/natural/util/stopwords_pt.js +136 -0
  43. package/lib/natural/wordnet/wordnet.js +2 -2
  44. package/lib/natural/wordnet/wordnet_file.js +1 -1
  45. package/lr_classifier.json +1 -0
  46. package/package.json +7 -4
  47. package/package.json~ +66 -0
  48. package/benchmarks/index.js +0 -32
  49. package/benchmarks/metaphone.js +0 -27
  50. package/benchmarks/soundex.js +0 -27
  51. package/examples/classification/basic.js +0 -35
  52. package/examples/classification/recall.js +0 -28
  53. package/examples/classification/store.js +0 -37
  54. package/examples/inflection/count.js +0 -35
  55. package/examples/inflection/noun_attach.js +0 -33
  56. package/examples/phonetics/compare.js +0 -39
  57. package/examples/phonetics/sounds_like.js +0 -40
  58. package/examples/phonetics/tokenize_and_phoneticize.js +0 -54
  59. package/examples/stemming/stem_corpus.js +0 -29
  60. package/examples/stemming/stem_word.js +0 -26
  61. package/examples/tfidf/array_example.js +0 -34
  62. package/examples/tfidf/multiple_terms.js +0 -33
  63. package/examples/tfidf/tfidf_example.js +0 -44
  64. package/io_spec/test_data/tfidf/one +0 -1
  65. package/io_spec/test_data/tfidf/two +0 -1
  66. package/io_spec/tfidf_spec.js +0 -50
  67. package/io_spec/wordnet_spec.js +0 -81
  68. package/spec/aggressive_tokenizer_es_spec.js +0 -36
  69. package/spec/aggressive_tokenizer_fr_spec.js +0 -101
  70. package/spec/aggressive_tokenizer_nl_spec.js +0 -36
  71. package/spec/aggressive_tokenizer_pt_spec.js +0 -56
  72. package/spec/aggressive_tokenizer_spec.js +0 -56
  73. package/spec/bayes_classifier_spec.js +0 -173
  74. package/spec/classifier_spec.js +0 -91
  75. package/spec/count_inflector_fr_spec.js +0 -44
  76. package/spec/count_inflector_spec.js +0 -73
  77. package/spec/dice_coefficient_spec.js +0 -26
  78. package/spec/double_metaphone_spec.js +0 -743
  79. package/spec/jaro-winkler_spec.js +0 -53
  80. package/spec/lancaster_stemmer_spec.js +0 -73
  81. package/spec/levenshtein_spec.js +0 -66
  82. package/spec/logistic_regression_classifier_spec.js +0 -122
  83. package/spec/longest_path_tree_spec.js +0 -102
  84. package/spec/metaphone_spec.js +0 -247
  85. package/spec/ngram_spec.js +0 -130
  86. package/spec/ngram_zh_spec.js +0 -122
  87. package/spec/normalizer_ja_spec.js +0 -154
  88. package/spec/normalizer_no_spec.js +0 -111
  89. package/spec/normalizer_spec.js +0 -118
  90. package/spec/noun_inflector_fr_spec.js +0 -476
  91. package/spec/noun_inflector_ja_spec.js +0 -112
  92. package/spec/noun_inflector_spec.js +0 -270
  93. package/spec/porter_stemmer_fr_spec.js +0 -101
  94. package/spec/porter_stemmer_it_spec.js +0 -44
  95. package/spec/porter_stemmer_no_spec.js +0 -143
  96. package/spec/porter_stemmer_ru_spec.js +0 -63
  97. package/spec/porter_stemmer_spec.js +0 -137
  98. package/spec/present_verb_inflector_spec.js +0 -125
  99. package/spec/sentence_analyzer_spec.js +0 -29
  100. package/spec/shortest_path_tree_spec.js +0 -132
  101. package/spec/soundex_spec.js +0 -154
  102. package/spec/stemmer_ja_spec.js +0 -66
  103. package/spec/test_data/snowball_fr.txt +0 -20402
  104. package/spec/test_data/snowball_it.txt +0 -35482
  105. package/spec/test_data/snowball_no.txt +0 -20628
  106. package/spec/test_data/tfdif_testcorpus.txt +0 -7
  107. package/spec/test_data/tfidf_document1.txt +0 -1
  108. package/spec/test_data/tfidf_document2.txt +0 -1
  109. package/spec/test_data/tfidf_document3.txt +0 -1
  110. package/spec/test_data/tfidf_document4.txt +0 -1
  111. package/spec/test_data/tfidf_teststopwords.txt +0 -1
  112. package/spec/test_data/wordnet_mini.sql +0 -95
  113. package/spec/tfidf_spec.js +0 -198
  114. package/spec/tokenizer_ja_spec.js +0 -58
  115. package/spec/transliterator_ja_spec.js +0 -103
  116. package/spec/treebank_word_tokenizer_spec.js +0 -36
  117. 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, 'C').replace(/[aeiouy]+/g, 'V');
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(/[^aeiou]/g, 'C').replace(/[aeiouy]/g, 'V');
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
- return -1;
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 = null;
70
+ var replacement = token;
71
71
 
72
- for(var i = 0; i < replacements.length; i++) {
73
- if(measureThreshold == null || measure(attemptReplace(token, replacements[i][0], '')) > measureThreshold)
74
- replacement = attemptReplace(token, replacements[i][0], replacements[i][1]);
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
- var result = attemptReplacePatterns(token, replacements, measureThreshold);
87
- token = result == null ? token : result;
88
-
89
- return token;
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
- if(token.substr(-1) == 's' && token.substr(-2, 1) != 's' && token.length > 3)
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
- if(measure(token.substr(0, token.length - 3)) > 0)
123
+ if(measure(token.substr(0, token.length - 3)) > 0)
107
124
  return token.replace(/eed$/, 'ee');
108
125
  } else {
109
- var result = attemptReplace(token, /ed|ing$/, '', function(token) {
110
- if(categorizeGroups(token).indexOf('V') >= 0) {
111
- var result = attemptReplacePatterns(token, [['at', 'ate'], ['bl', 'ble'], ['iz', 'ize']]);
112
- if(result)
113
- return result;
114
- else {
115
- if(endsWithDoublCons(token) && token.match(/[^lsz]$/))
116
- return token.replace(/([^aeiou])\1$/, '$1');
117
-
118
- if(measure(token) == 1 && categorizeChars(token).substr(-3) == 'CVC' && token.match(/[^wxy]$/))
119
- return token + 'e';
120
- }
121
-
122
- return token;
123
- }
124
-
125
- return null;
126
- });
127
-
128
- if(result)
129
- return result;
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
- if(categorizeGroups(token).substr(-2, 1) == 'V') {
138
- if(token.substr(-1) == 'y')
139
- return token.replace(/y$/, 'i');
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
- return replacePatterns(token, [['ational', 'ate'], ['tional', 'tion'], ['enci', 'ence'], ['anci', 'ance'],
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 replacePatterns(token, [['al', ''], ['ance', ''], ['ence', ''], ['er', ''],
163
- ['ic', ''], ['able', ''], ['ible', ''], ['ant', ''],
164
- ['ement', ''], ['ment', ''], ['ent', ''], [/([st])ion/, '$1'], ['ou', ''], ['ism', ''],
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
- if(token.length > 3 && ((m > 1 && token.substr(-1) == 'e') || (m == 1 && !(categorizeChars(token).substr(-4, 3) == 'CVC' && token.match(/[^wxy].$/)))))
174
- return token.replace(/e$/, '');
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
- if(endsWithDoublCons(token) && token.substr(-2) == 'll')
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
- //console.log('regions:', regs)
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 = r2 = rv = len = token.length;
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(text).forEach(function(token) {
45
- if(keepStops || stopwords.words.indexOf(token) == -1)
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
  };