PageSourceSearch

https://feminizator.github.io/js/feminitives-functions.js

js feminizator.github.io collected 2026-10-03 09:11:22 UTC 13,415 bytes, 419 lines download raw bytes

1//
2// Copyright (C) 2016-2019, Maxim Lihachev, <[email protected]>
3//
4// This program is free software: you can redistribute it and/or modify it
5// under the terms of the GNU General Public License as published by the Free
6// Software Foundation, version 3.
7//
8// This program is distributed in the hope that it will be useful, but WITHOUT
9// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11// more details.
12//
13// You should have received a copy of the GNU General Public License along with
14// this program. If not, see <https://www.gnu.org/licenses/>.
15//
16
17//------------------------------------------------------------------------------
18
19'use strict';
20
21//------------------------------------------------------------------------------
22
23//Иерархия элементов на странице
24var HTML = {
25	container: "",
26	_select:    function(element) { return document.getElementById(this.container + "-" + element); },
27	input:      function() { return this._select("word"); },
28	button:     function() { return this._select("convert"); },
29	dict:       function() { return this._select("dict"); },
30	content:    function() { return this._select("content"); },
31	full:       function() { return this._select("full"); },
32	help:       function() { return this._select("help"); },
33	image:      function() { return this._select("image"); },
34};
35
36//Вывод справки с примерами использования
37function show_help() {
38	HTML.vis(HTML.help());
39	HTML.vis(HTML.content());
40}
41
42//Смена названия по клику
43function swap_title(id) {
44	var e = document.getElementById(id);
45	if(e.style.display == 'inline') {
46		e.style.display = 'none';
47	} else {
48		e.style.display = 'inline';
49	}
50}
51
52HTML.vis = function(e, v) {
53	if (v !== undefined) {
54		e.style.visibility = v;
55	} else {
56		e.style.visibility = e.style.visibility === "visible" ? "hidden" : "visible";
57	}
58}
59
60//Инициализация документа
61HTML.init = function(root) {
62	//Задание базового id для всех элементов
63	this.container = root;
64
65	//Конвертирование по нажатию <Enter>
66	this.input().addEventListener('keyup', event => {
67		event.preventDefault();
68		event.keyCode == 13 && tr();
69	});
70
71	//Конвертирование по нажатию кнопки
72	this.button().onclick = () => tr();
73};
74
75//------------------------------------------------------------------------------
76
77FEM.words.convert = function(string) {
78	for (var fem_w in this) {
79		string =  string.replace(new RegExp('(^|\\s)+' + fem_w, "ig"), '$1' + this[fem_w])
80				.replace(/(.)/, s => s.toUpperCase());
81	}
82	return string;
83};
84
85//------------------------------------------------------------------------------
86
87//Первый элемент списка - окончание (в виде регулярного выражения)
88let ending = tuple => new RegExp("^.*" + tuple[0] + "$", "i");
89
90//Второй элемент списка - смещение
91let offset = tuple => tuple[1];
92
93//Случайный элемент списка
94let random_word = wordlist => wordlist[Math.floor(Math.random() * wordlist.length)];
95
96//Оборачивание в <span> с указанным классом
97let html_wrap = (str, cl) => `<span class="${cl}">${str}</span>`;
98
99//Цветовое выделение текста
100let css_end = ending => html_wrap(ending, "ending");
101
102//Символ gender gap
103let css_gender_gap = html_wrap(' \u26A7 ', "queer");
104
105//------------------------------------------------------------------------------
106
107//Конструирование феминитива с gender_gap
108function construct_feminitive(stem, ending, gap) {
109	return gap ? stem + css_gender_gap + css_end(ending) : stem + "_" + ending;
110}
111
112//Длина шаблона, подпадающего под регулярное выражение r
113function regex_len(r) {
114	return r.replace(/\[[^\[\]]*\]/g, "x").length;
115}
116
117//Отправка адреса страницы в vk.com
118function share_page() {
119	let vk_url = "http://vk.com/share.php"
120			+ "?url="         + URL.href
121			+ "&title="       + URL.title
122			+ "&description=" + URL.description.text;
123
124	let new_tab = window.open(vk_url,'_blank');
125	new_tab.focus();
126}
127
128//Сохранение изображения с феминитивом
129function download_image() {
130	html2canvas(HTML.image(), {
131		onrendered: canvas => {
132			let a = document.createElement('a');
133			a.href = canvas.toDataURL();
134			a.download = HTML.content().innerHTML + '.png';
135			a.click();
136		}
137	});
138}
139
140//Создание феминитива
141function make_feminitives(word, visibleGaps) {
142	//Обрабатываем только слова длиннее трёх символов
143	if (word.length < 3) return [word, word];
144
145	if (FEM.words[word] != undefined) {
146		let wrd = visibleGaps ? FEM.words[word] : FEM.words[word].replace("_", "");
147
148		return [wrd, wrd];
149	}
150
151	var stem           = "";             //Основа слова
152	// var current_ending = word.slice(-2); //Текущее окончание
153	var current_endings = [word.slice(-4), word.slice(-3), word.slice(-2)];
154	var feminitives    = [];             //Массив феминитивов
155	var femicards      = [];             //Массив феминитивов для карточки
156
157	var found = false;
158
159	current_endings.forEach(current_ending => {
160		if (!found) {
161			for (let fem_ending in FEM.endings) {
162				FEM.endings[fem_ending].forEach(end => {
163					if (regex_len(end[0]) === current_ending.length
164					&&  ending(end).test(current_ending)) {
165						//Удаление лишних букв из основы
166						stem = offset(end) === 0 ? word : word.slice(0, -offset(end));
167
168						let rule = fem_ending.split("+");
169
170						let prefix, all_fem_endings;
171
172						if (rule.length > 1) {
173							prefix = rule[0];
174							all_fem_endings = rule[1];
175						} else {
176							prefix = "";
177							all_fem_endings = fem_ending;
178						}
179
180						all_fem_endings.split("|").forEach(e => {
181							//Добавление фем-варианта слова в массив
182							feminitives.push(construct_feminitive(stem + prefix, e, 1, visibleGaps));
183							femicards.push(construct_feminitive(stem + prefix, e, 0, visibleGaps));
184
185						});
186
187						found = true;
188					}
189				});
190			}
191		}
192	});
193
194	//При отсутствии феминитивов считать корректным исходное слово
195	return [FEM.random_word(femicards) || word, feminitives];
196}
197
198//Создание феминитива
199function make_feminitives_old(word) {
200	//Обрабатываем только слова длиннее трёх символов
201	if (word.length < 3) return [word, word];
202
203	var stem           = "";             //Основа слова
204	var current_ending = word.slice(-2); //Текущее окончание
205	var feminitives    = [];             //Массив феминитивов
206	var femicards      = [];             //Массив феминитивов для карточки
207
208	for (let fem_ending in FEM.endings) {
209		FEM.endings[fem_ending].forEach(end => {
210			if (ending(end).test(current_ending)) {
211				//Удаление лишних букв из основы
212				stem = offset(end) === 0 ? word : word.slice(0, -offset(end));
213
214				//Добавление фем-варианта слова в массив
215				feminitives.push(construct_feminitive(stem, fem_ending, 1));
216				femicards.push(construct_feminitive(stem, fem_ending, 0));
217			}
218		});
219	}
220	//При отсутствии феминитивов считать корректным исходное слово
221	return [random_word(femicards) || word, feminitives];
222}
223
224//Поиск и феминизация дефиниции в викистранице
225function parseWikiPage(page) {
226	var wiki = page.split('\n');
227	var definition = "";
228
229	console.log(wiki)
230
231	wiki.some((line, n) => {
232		if (line.match(/^.*==== Значение ====.*$/)) {
233			let definition_line = n + 1;
234
235			//После заголовка возможна пустая строка или помета (например, {{воен.}})
236			if (wiki[n+1].trim().length === 0
237			||  wiki[n+1].trim().match(/^{{[^}]*}}$/)) {
238				definition_line = n + 2;
239			}
240
241			definition = wiki[definition_line]
242			//# дефиниция
243			.replace(/^# ?/, "")
244
245			//[[1]]
246			.replace(/\[{2}([^\]|]*)\]{2}/g, "$1")
247
248			//[[1|2]]
249			.replace(/\[{2}[^|]*\|([^\]]*)\]{2}/g, "$1")
250
251			//{{сокр.|слово<!--ком.-->.*$
252			.replace(/\{{2}[а-яА-Я]+\.[^{}]*\|[^{}]*\}{2}\s*/g, "")
253
254			//~ : возможна вложенность
255			.replace(/\{{2}[а-яА-Я]+\.[^{}]*\|[^{}]*\}{2}\s*/g, "")
256
257			//{{-}}$
258			.replace(/\{\{-\}\}/g, "")
259
260			//{{помета|...}}$
261			.replace(/\{{2}помета\s*\|[^}]+\}{2}/g, "")
262
263			//{{действие|(глагол)#...lang=LC}}$
264			.replace(/\{{2}(действие)\s*\|([^#|]+)([#|].*lang=\w{2})?\}{2}/g, "$1 «$2»")
265
266			//{{пример/семантика|.*$ или {{пример/семантика}}
267			.replace(/\{{2}(пример|семантика)(\}{2}|\s*\|.*$)/g, "")
268
269			//{{1|слово<!--комментарий-->.*$
270			.replace(/\{{2}[^{}]*\|(lang=\w{2})?/g, "")
271
272			//{{1}}
273			.replace(/\{{2}([^\]|]*)\}{2}/g, "$1")
274
275			//'''ударение'''
276			.replace(/'''([^']*)'''/g, "«$1»")
277
278			//<!--комментарий-->
279			.replace(/<!--[^>]*-->/g, "")
280
281			//}}
282			.replace(/\}{2}/g, "")
283
284			//ссылки [n]
285			.replace(/\[[0-9]{1,}\]/g, "")
286
287			//^,; ...
288			.replace(/^\s*[,;]\s*/g, "")
289
290			//{{.*$
291			.replace(/\{{2}[^}]*$/g, "")
292
293			//HTML
294			.replace(/(<([^>]+)>)/ig, "")
295
296			//Неразрывный пробел
297			.replace(/&nbsp;/g, " ")
298
299			//Точка в конце предложения
300			.replace(/ ?$/,".");
301
302			return true;
303		}
304	});
305
306	//Разделение дефиниции на массив слов и знаков препинания и феминизация слов
307	var tokens =  definition.match(/[\wа-яА-Яё]+|\d+| +|[.;,]|[^ \w\d\t.;,]+/ig) || [];
308	console.log(tokens);
309
310	//Замена местоимений, предлогов и проч.
311	HTML.full().innerHTML = FEM.words.convert(tokens.map(w => make_feminitives(w)[0]).join(""));
312
313	//DEBUG
314	console.log(definition);
315	console.log(tokens);
316}
317
318//Запрос значения слова в викисловаре
319function get_wiktionary(term) {
320	var cors_url = "https://api.allorigins.win/get?url=";
321	var wiki_url = cors_url + encodeURIComponent("https://ru.wiktionary.org/w/index.php?action=raw&title=" + term);
322
323	var xmlhttp = window.XMLHttpRequest
324		? new XMLHttpRequest()
325		: new ActiveXObject("Microsoft.XMLHTTP");
326
327	xmlhttp.onreadystatechange = function() {
328		if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
329			console.log(JSON.parse(xmlhttp.response));
330			parseWikiPage(JSON.parse(xmlhttp.responseText).contents);
331		}
332	};
333
334	xmlhttp.open("GET", wiki_url, true);
335	xmlhttp.send();
336}
337
338//Создание и вывод феминитива
339function tr(word) {
340	//Исходное слово
341	var wd = word || HTML.input().value.trim().toLowerCase().replace(/<\/?[^>]+(>|$)/g, "").split(" ")[0];
342	var feminitives = "";
343
344	//Состояние по умолчанию
345	HTML.content().innerHTML = "Введите слово";
346	HTML.dict().innerHTML    = "";
347	HTML.full().innerHTML    = "";
348
349	HTML.vis(HTML.help(),    "hidden");
350	HTML.vis(HTML.content(), "visible");
351
352	//Изменение адреса
353	URL.set(wd);
354
355	//Вывод информации
356	if (!wd) {
357		show_help();
358
359		return;
360	} else if (FEM.exceptions.contains(wd)) {
361		HTML.full().innerHTML = FEM.exceptions.definition(wd);
362		feminitives = FEM.exceptions.feminitives(wd);
363	} else {
364		get_wiktionary(wd);
365		feminitives = make_feminitives(wd);
366	}
367	//Вывод информации
368	HTML.input().value = wd;
369	HTML.content().innerHTML = feminitives[0].replace(/(.)/, s => s.toUpperCase());
370	HTML.dict().innerHTML    = feminitives[1].join(" | ")
371				|| "Это слово и так прекрасно. Оставим его как есть.";
372}
373
374//------------------------------------------------------------------------------
375
376//Параметры URL
377var URL = {
378	opt: {},
379	description: {
380		clear: function()   { this.text = "Как феминистки пишут разные слова."; },
381		set:   function(wd) { this.text = 'Как феминистки пишут слово "' +wd+ '".'; },
382	},
383	set: function(wd) {
384		if (!wd) {
385			window.history.pushState({}, null, window.location.href.split('?')[0]);
386			this.description.clear();
387		} else {
388			window.history.pushState({}, null, window.location.href.split('?')[0]+'?word='+wd);
389			this.description.set(wd);
390		}
391		this.href = encodeURIComponent(window.location.href);
392	}
393};
394
395//Разбор параметров URL
396URL.parse = function() {
397	var gy = window.location.search.substring(1).split("&");
398	gy.forEach(arg => {
399		let ft = arg.split("=");
400		this.opt[ft[0]] = this.opt[ft[0]] || decodeURIComponent(ft[1]);
401	});
402
403	URL.description.clear();
404	this.title = document.title;
405	this.opt.href  = encodeURIComponent(window.location.href);
406};
407
408//Инициализация с разбором адресной строки
409function init(container) {
410	HTML.init(container);
411	URL.parse();
412
413	if (URL.opt.word) {
414		HTML.input().value = URL.opt.word.replace(/\+/g," ");
415		tr();
416	} else {
417		show_help();
418	}
419}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.