porter2
Version:
Fastest JavaScript implementation of the porter2 stemmer
433 lines (423 loc) • 14.6 kB
JavaScript
// AUTOGENERATED, edit src/index.js instead
Object.defineProperty(exports, '__esModule', { value: true })
// EQ_BEGIN : (chars : string) -> <code (boolean)>
// EQ_END : (offset : number, chars : string) -> <code (boolean)>
// All one-character strings in this file are converted to char codes.
// This is mostly a rewrite of my ocaml implementation (though not published
// at the moment of writing this)
function is_v(char) {
switch (char) {
case 97: case 101: case 105: case 111: case 117: case 121: return true
default: return false
}
}
function is_wxy(char) {
switch (char) {
case 97: case 101: case 105: case 111: case 117: case 121:
case 119: case 120: case 89:
return true
default: return false
}
}
function is_valid_li(char) {
switch (char) {
case 99: case 100: case 101: case 103: case 104: case 107: case 109:
case 110: case 114: case 116:
return true
default: return false
}
}
function is_double(char) {
switch (char) {
case 98: case 100: case 102: case 103: case 109: case 110: case 112:
case 114: case 116:
return true
default: return false
}
}
function is_shortv(w, len) {
// backwardmode: ( non-v_WXY v non-v ) or ( non-v v atlimit )
return len >= 2 && is_v(w[len - 2]) && (
(len === 2 && !is_v(w[len - 1]))
|| (len >= 3 && !is_v(w[len - 3])
&& !is_wxy(w[len - 1]))
)
}
exports.stem = function stem(word) {
if (word.length < 3)
return word
// exception1
if (word.length <= 6) {
switch (word) {
case 'ski': return 'ski'
case 'skies': return 'sky'
case 'dying': return 'die'
case 'lying': return 'lie'
case 'tying': return 'tie'
// special -LY cases
case 'idly': return 'idl'
case 'gently': return 'gentl'
case 'ugly': return 'ugli'
case 'early': return 'earli'
case 'only': return 'onli'
case 'singly': return 'singl'
// invariant forms
case 'sky': case 'news': case 'howe':
// not plural forms
case 'atlas': case 'cosmos': case 'bias': case 'andes':
return word
}
}
var initial_offset = word.charCodeAt(0) === 39 /* ' */ ? 1 : 0
var l = word.length - initial_offset
var w = new Array(l)
var y_found = false
for (var i = 0; i < l; ++i) {
// var ch = word[i + initial_offset]
var ch = word.charCodeAt(i + initial_offset)
if (ch === 121 && (i === 0 || is_v(w[i - 1]))) {
y_found = true
w[i] = 89
continue
}
w[i] = ch
}
if (w[l - 1] === 39)
--l
if (l >= 2 && w[l - 2] === 39 && w[l - 1] === 115)
l -= 2
// mark_regions
var rv = 0;
// rv is the position after the first vowel
while (rv < l && !is_v(w[rv])) ++rv
if (rv < l) ++rv
var r1 = rv
if (l >= 5 && ((w[0] === 103 && w[1] === 101 && w[2] === 110 && w[3] === 101 && w[4] === 114) || (w[0] === 97 && w[1] === 114 && w[2] === 115 && w[3] === 101 && w[4] === 110)))
r1 = 5
else if (l >= 6 && w[0] === 99 && w[1] === 111 && w[2] === 109 && w[3] === 109 && w[4] === 117 && w[5] === 110)
r1 = 6
else {
// > R1 is the region after the first non-vowel following a vowel,
// > or the end of the word if there is no such non-vowel.
while (r1 < l && is_v(w[r1])) ++r1
if (r1 < l) ++r1
}
// > R2 is the region after the first non-vowel following a vowel in R1,
// > or the end of the word if there is no such non-vowel.
var r2 = r1
var found_v = false
while (r2 < l && !is_v(w[r2])) ++r2
while (r2 < l && is_v(w[r2])) ++r2
if (r2 < l) ++r2
// Step_1a
if (l >= 3) {
if (w[l - 1] === 115) {
if (l >= 4 && w[l - 2] === 101 && w[l - 3] === 115 && w[l - 4] === 115)
l -= 2 // sses -> ss
else if (w[l - 2] === 101 && w[l - 3] === 105)
l -= (l >= 5 ? 2 : 1) // ies
else if (w[l - 2] !== 117 && w[l - 2] !== 115 && rv < l - 1)
// us ss -> <nothing>; s -> "delete if the preceding word part
// contains a vowel not immediately before the s"
l -= 1
} else if (w[l - 1] === 100 && w[l - 2] === 101 && w[l - 3] === 105)
l -= (l >= 5 ? 2 : 1) // ied
}
// exception2
if (
(l === 6 && (
(w[0] === 105 && w[1] === 110 && w[2] === 110 && w[3] === 105 && w[4] === 110 && w[5] === 103) || (w[0] === 111 && w[1] === 117 && w[2] === 116 && w[3] === 105 && w[4] === 110 && w[5] === 103) || (w[0] === 101 && w[1] === 120 && w[2] === 99 && w[3] === 101 && w[4] === 101 && w[5] === 100))
) || (l === 7 && (
(w[0] === 99 && w[1] === 97 && w[2] === 110 && w[3] === 110 && w[4] === 105 && w[5] === 110 && w[6] === 103) || (w[0] === 104 && w[1] === 101 && w[2] === 114 && w[3] === 114 && w[4] === 105 && w[5] === 110 && w[6] === 103) || (w[0] === 101 && w[1] === 97 && w[2] === 114 && w[3] === 114 && w[4] === 105 && w[5] === 110 && w[6] === 103)
|| (w[0] === 112 && w[1] === 114 && w[2] === 111 && w[3] === 99 && w[4] === 101 && w[5] === 101 && w[6] === 100) || (w[0] === 115 && w[1] === 117 && w[2] === 99 && w[3] === 99 && w[4] === 101 && w[5] === 101 && w[6] === 100)))
) {
var exp2_out = ''
for (var i = 0; i < l; ++i)
// exp2_out += w[i]
exp2_out += String.fromCharCode(w[i])
return exp2_out
}
// Step_1b
var ll =
// l (length) without the -ly ending
(l >= 2 && w[l - 1] === 121 && w[l - 2] === 108) ? l - 2 : l
if (ll >= 3) {
if (w[ll - 3] === 101 && w[ll - 2] === 101 && w[ll - 1] === 100) {
if (ll >= r1 + 3)
l = ll - 1 // eed eedly -> ee (if in R1)
} else {
// ll without: ed edly ing ingly (-1 if not found)
if (w[ll - 2] === 101 && w[ll - 1] === 100)
ll -= 2
else if (w[ll - 3] === 105 && w[ll - 2] === 110 && w[ll - 1] === 103)
ll -= 3
else ll = -1
if (ll >= 0 && rv <= ll) {
l = ll
if (l >= 2) {
if ((w[l - 1] === 116 && w[l - 2] === 97) || (w[l - 1] === 108 && w[l - 2] === 98) || (w[l - 1] === 122 && w[l - 2] === 105)) {
// at -> ate bl -> ble iz -> ize
w[l] = 101
++l
} else if (w[l - 2] === w[l - 1] && is_double(w[l - 1])) {
--l
} else if (r1 >= l && is_shortv(w, l)) {
// <shortv> -> e
w[l] = 101
++l
}
}
}
}
}
// Step_1c
if (l >= 3 && (w[l - 1] === 89 || w[l - 1] === 121)
&& !is_v(w[l - 2]))
w[l - 1] = 105
// Step_2
if (l >= r1 + 2) {
switch (w[l - 1]) {
case 108:
if (l >= r1 + 6 && w[l - 2] === 97 && w[l - 3] === 110 && w[l - 4] === 111 && w[l - 5] === 105 && w[l - 6] === 116) {
if (l >= 7 && w[l - 7] === 97) {
if (l >= r1 + 7) {
// ational -> ate
l -= 4
w[l - 1] = 101
}
} else {
l -= 2 // tional -> tion
}
}
break
case 110:
if (l >= r1 + 5 && w[l - 2] === 111 && w[l - 3] === 105 && w[l - 4] === 116 && w[l - 5] === 97) {
if (l >= 7 && w[l - 6] === 122 && w[l - 7] === 105) {
if (l >= r1 + 7) {
// ization -> ize
l -= 4
w[l - 1] = 101
}
} else {
// ation -> ate
l -= 2
w[l - 1] = 101
}
}
break
case 114:
if (l >= r1 + 4) {
if (w[l - 2] === 101) {
if (w[l - 3] === 122 && w[l - 4] === 105)
--l // izer -> ize
} else if (w[l - 2] === 111) {
if (w[l - 3] === 116 && w[l - 4] === 97) {
--l
w[l - 1] = 101
}
}
}
break
case 115:
if (l >= r1 + 7 && w[l - 2] === 115 && w[l - 3] === 101 && w[l - 4] === 110 && (
(w[l - 5] === 108 && w[l - 6] === 117 && w[l - 7] === 102) || (w[l - 5] === 115 && w[l - 6] === 117 && w[l - 7] === 111) || (w[l - 5] === 101 && w[l - 6] === 118 && w[l - 7] === 105)
)) {
l -= 4 // fulness -> ful ousness -> ous iveness -> ive
}
break
case 109:
if (l >= r1 + 5 && w[l - 2] === 115 && w[l - 3] === 105 && w[l - 4] === 108 && w[l - 5] === 97)
l -= 3 // alism -> al
break
case 105:
if (w[l - 2] === 99) {
if (l >= r1 + 4 && (w[l - 4] === 101 || w[l - 4] === 97)
&& w[l - 3] === 110) {
w[l - 1] = 101 // enci -> ence anci -> ance
}
} else if (w[l - 2] === 103) {
if (l >= r1 + 3 && l >= 4 && w[l - 2] === 103 && w[l - 3] === 111 && w[l - 4] === 108)
--l // ogi -> og (if preceded by l)
} else if (w[l - 2] === 116) {
if (l >= r1 + 5 && w[l - 3] === 105) {
if (w[l - 4] === 108) {
if (l >= 6 && w[l - 5] === 105 && w[l - 6] === 98) {
if (l >= r1 + 6) {
// biliti -> ble
l -= 3
w[l - 2] = 108
w[l - 1] = 101
}
} else if (w[l - 4] === 108 && w[l - 5] === 97) {
l -= 3 // aliti -> al
}
} else if (w[l - 4] === 118 && w[l - 5] === 105) {
// iviti -> ive
l -= 2
w[l - 1] = 101
}
}
} else if (w[l - 2] === 108 && l >= 3) {
if (w[l - 3] === 98) {
if (l >= 4 && w[l - 4] === 97) {
if (l >= r1 + 4)
w[l - 1] = 101 // abli -> able
} else if (l >= r1 + 3) {
w[l - 1] = 101 // bli -> ble
}
} else {
// Remove li
if (w[l - 3] === 108) {
if (l >= 5 && w[l - 4] === 117 && w[l - 5] === 102) {
if (l >= r1 + 5) l -= 2 // fulli -> ful
} else if (l >= r1 + 4 && w[l - 4] === 97) {
l -= 2 // alli -> al
}
} else if (w[l - 3] === 115) {
if (l >= 6 && w[l - 4] === 115 && w[l - 5] === 101 && w[l - 6] === 108) {
if (l >= r1 + 6)
l -= 2 // lessli -> less
} else if (l >= r1 + 5 && w[l - 4] === 117 && w[l - 5] === 111) {
l -= 2 // ousli -> ous
}
} else if (l >= 5 && w[l - 3] === 116 && w[l - 4] === 110 && w[l - 5] === 101) {
if (l >= r1 + 5)
l -= 2 // entli -> ent
} else if (is_valid_li(w[l - 3])) {
l -= 2
}
}
}
}
}
// Step_3
if (l >= r1 + 3) {
switch (w[l - 1]) {
case 108:
if (w[l - 3] === 99) {
if (l >= r1 + 4 && w[l - 4] === 105 && w[l - 2] === 97)
l -= 2 // ical -> ic
} else if (w[l - 3] === 102) {
if (w[l - 2] === 117)
l -= 3 // ful -> <delete>
} else if (w[l - 3] === 110) {
if (l >= r1 + 6 && w[l - 2] === 97 && w[l - 4] === 111 && w[l - 5] === 105 && w[l - 6] === 116) {
if (l >= 7 && w[l - 7] === 97) {
if (l >= r1 + 7) {
// ational -> ate
l -= 4
w[l - 1] = 101
}
} else {
l -= 2 // tional -> tion
}
}
}
break
case 101:
if (w[l - 2] === 122) {
if (l >= r1 + 5 && w[l - 3] === 105 && w[l - 4] === 108 && w[l - 5] === 97)
l -= 3 // alize -> al
} else if (w[l - 2] === 116) {
if (l >= r1 + 5 && w[l - 3] === 97 && w[l - 4] === 99 && w[l - 5] === 105)
l -= 3 // icate -> ic
} else if (w[l - 2] === 118) {
if (l >= r2 + 5 && w[l - 3] === 105 && w[l - 4] === 116 && w[l - 5] === 97)
l -= 5 // ative -> <delete> (if in R2)
}
break
case 105:
if (l >= r1 + 5 && w[l - 2] === 116 && w[l - 3] === 105 && w[l - 4] === 99 && w[l - 5] === 105)
l -= 3 // iciti -> ic
break
case 115:
if (l >= r1 + 4 && w[l - 2] === 115 && w[l - 3] === 101 && w[l - 4] === 110)
l -= 4 // ness -> <delete>
}
}
// Step_4
if (l >= r2 + 2) {
switch (w[l - 1]) {
case 110:
if (l >= r2 + 3 && w[l - 2] === 111 && w[l - 3] === 105 && (w[l - 4] === 115 || w[l - 4] === 116))
l -= 3 // ion -> <delete> (if preceded by s or t)
break
case 108:
if (w[l - 2] === 97)
l -= 2 // al
break
case 114:
if (w[l - 2] === 101)
l -= 2 // er
break
case 99:
if (w[l - 2] === 105)
l -= 2 // ic
break
case 109:
if (l >= r2 + 3 && w[l - 2] === 115 && w[l - 3] === 105)
l -= 3 // ism
break
case 105:
if (l >= r2 + 3 && w[l - 2] === 116 && w[l - 3] === 105)
l -= 3 // iti
break
case 115:
if (l >= r2 + 3 && w[l - 2] === 117 && w[l - 3] === 111)
l -= 3 // ous
break
case 116:
if (l >= r2 + 3 && w[l - 2] === 110) {
if (w[l - 3] === 97) {
l -= 3 // ant
} else if (w[l - 3] === 101) {
if (l >= 4 && w[l - 4] === 109) {
if (l >= 5 && w[l - 5] === 101) {
if (l >= r2 + 5)
l -= 5 // ement
} else if (l >= r2 + 4) {
l -= 4 // ment
}
} else {
l -= 3 // ent
}
}
}
break
case 101:
if (w[l - 2] === 99) {
if (l >= r2 + 4 && w[l - 3] === 110
&& (w[l - 4] === 97 || w[l - 4] === 101))
l -= 4 // ance ence
} else if (w[l - 2] === 108) {
if (l >= r2 + 4 && w[l - 3] === 98
&& (w[l - 4] === 97 || w[l - 4] === 105))
l -= 4 // able ible
} else if (w[l - 2] === 116) {
if (l >= r2 + 3 && w[l - 3] === 97)
l -= 3 // ate
} else if (l >= r2 + 3
&& (w[l - 2] === 118 || w[l - 2] === 122) && w[l - 3] === 105) {
l -= 3 // ive ize
}
}
}
// Step_5
if (
l >= r1 + 1 && // r1 is >= 1
((l >= r2 + 1 && w[l - 1] === 108 && w[l - 2] === 108)
|| w[l - 1] === 101 && (l >= r2 + 1 || !is_shortv(w, l - 1)))
)
--l
var out = ''
if (y_found) {
for (var i = 0; i < l; ++i) {
out += String.fromCharCode(w[i] === 89 ? 121 : w[i])
}
} else {
for (var i = 0; i < l; ++i)
// out += w[i]
out += String.fromCharCode(w[i])
}
return out
}