-
Notifications
You must be signed in to change notification settings - Fork 18
/
petrovich.js
190 lines (162 loc) · 6.39 KB
/
petrovich.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
"use strict";
(function () {
// Predefined values
var predef = {
genders: ['male', 'female', 'androgynous'],
nametypes: ['first', 'middle', 'last'],
cases: ['nominative', 'genitive', 'dative', 'accusative', 'instrumental', 'prepositional']
};
function isEmpty(arr) {
return arr.length === 0;
}
// Auxiliary function: no Array.indexOf owing to IE8
function contains(arr, x) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] === x) return true;
}
return false;
}
// First use means:
// var person = { gender: 'female', first: 'Маша' };
// petrovich(person, 'dative');
var petrovich = function (person, gcase) {
var result = {};
// gender detection
if (person.gender != null) {
if (!contains(predef.genders, person.gender))
throw new Error('Invalid gender: ' + person.gender);
result.gender = person.gender;
} else if (person.middle != null) {
result.gender = petrovich.detect_gender(person.middle);
} else {
throw new Error('Unknown gender');
}
if (!contains(predef.cases, gcase))
throw new Error('Invalid case: ' + gcase);
// look over possible names of properties, inflect them and add to result object
for (var i = 0; i < predef.nametypes.length; i++) {
var nametype = predef.nametypes[i];
if (person[nametype] != null) {
result[nametype] =
inflect(result.gender, person[nametype], gcase, nametype + 'name');
}
}
return result;
};
petrovich.detect_gender = function (middle) {
var ending = middle.toLowerCase().substr(middle.length - 2);
if (ending === 'ич') return 'male';
else if (ending === 'на') return 'female';
else return 'androgynous';
};
// Second use means:
// Build dynamically methods chain like petrovich.male.first.dative(name)
// Isolate scope to reduce polluting scope with temp variables
(function () {
for (var i = 0; i < predef.genders.length; i++) {
var gender = predef.genders[i];
if (!petrovich[gender]) petrovich[gender] = {};
for (var j = 0; j < predef.nametypes.length; j++) {
var nametype = predef.nametypes[j];
if (!petrovich[gender][nametype])
petrovich[gender][nametype] = {};
for (var k = 0; k < predef.cases.length; k++) {
var gcase = predef.cases[k];
// The flower on the mountain peak:
petrovich[gender][nametype][gcase] =
(function (gender, nametype, gcase) {
return function (name) {
return inflect(gender, name, gcase, nametype + 'name');
};
})(gender, nametype, gcase);
}
}
}
})();
// Export for NodeJS or browser
if (typeof module !== "undefined" && module.exports) module.exports = petrovich;
else if (window) window.petrovich = petrovich;
else throw new Error("Unknown environment");
// Key private method, used by all public methods
function inflect(gender, name, gcase, nametype) {
var nametype_rulesets = rules[nametype],
parts = name.split('-'),
result = [];
for (var i = 0; i < parts.length; i++) {
var part = parts[i], first_word = i === 0 && parts.length > 1,
rule = find_rule_global(gender, part,
nametype_rulesets, {first_word: first_word});
if (rule) result.push(apply_rule(part, gcase, rule));
else result.push(part);
}
return result.join('-');
}
// Find groups of rules in exceptions or suffixes of given nametype
function find_rule_global(gender, name, nametype_rulesets, features) {
if (!features) features = {};
var tags = [];
for (var key in features) {
if (features.hasOwnProperty(key)) tags.push(key);
}
if (nametype_rulesets.exceptions) {
var rule = find_rule_local(
gender, name, nametype_rulesets.exceptions, true, tags);
if (rule) return rule;
}
return find_rule_local(
gender, name, nametype_rulesets.suffixes, false, tags);
};
// Local search in rulesets of exceptions or suffixes
function find_rule_local(gender, name, ruleset, match_whole_word, tags) {
for (var i = 0; i < ruleset.length; i++) {
var rule = ruleset[i];
if (rule.tags && ! isEmpty(rule.tags)) {
if (isEmpty(tags)) continue;
var common_tags = [];
for (var j = 0; j < rule.tags.length; j++) {
var tag = rule.tags[j];
if (contains(tags, tag)) common_tags.push(tag);
}
if (isEmpty(common_tags)) continue;
}
if (rule.gender !== 'androgynous' && gender !== rule.gender)
continue;
name = name.toLowerCase();
for (var j = 0; j < rule.test.length; j++) {
var sample = rule.test[j];
var test = match_whole_word ? name :
name.substr(name.length - sample.length);
if (test === sample) return rule;
}
}
return false;
}
// Apply found rule to given name
// Move error throwing from this function to API method
function apply_rule(name, gcase, rule) {
var mod;
if (gcase === 'nominative') mod = '.';
else {
for (var i = 0; i < predef.cases.length; i++) {
if (gcase === predef.cases[i]) {
mod = rule.mods[i - 1];
break;
}
}
}
for (var i = 0; i < mod.length; i++) {
var chr = mod[i];
switch (chr) {
case '.':
break;
case '-':
name = name.substr(0, name.length - 1);
break;
default:
name += chr;
}
}
return name;
}
var rules = null; // grunt: replace rules
})();