/* Copyright (C) 2021-2024 Alik Aslanyan This file is part of Gadgetbridge. Gadgetbridge is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gadgetbridge is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.util.language.impl; import nodomain.freeyourgadget.gadgetbridge.util.language.Transliterator; import org.ahocorasick.trie.Emit; import org.ahocorasick.trie.Trie; import org.apache.commons.lang3.text.WordUtils; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; public class ArmenianTransliterator implements Transliterator { // Transliteration map ordered by priority // Armenian has some rules regarding reading of 'ո' in the middle of the word it reads as english O // But if word starts with it's read as sound of 'vo' // Or if it has 'ւ' symbol after it, then we should read it as 'u' (as double o in booze) private static final Map transliterateMap = new LinkedHashMap() { { // Simple substitutions Map simpleSubstitions = new HashMap() { { put("ա","a"); put("բ","b"); put("գ","g"); put("դ","d"); put("ե","e"); put("զ","z"); put("է","e"); put("ը","y"); put("թ","t"); put("ժ","j"); put("ի","i"); put("լ","l"); put("խ","x"); put("ծ","c"); put("կ","k"); put("հ","h"); put("ձ","dz"); put("ղ","x"); put("ճ","c"); put("մ","m"); put("յ","y"); put("ն","n"); put("շ","sh"); put("ո", "vo"); put("չ","ch"); put("պ","p"); put("ջ","j"); put("ռ","r"); put("ս","s"); put("վ","v"); put("տ","t"); put("ր","r"); put("ց","c"); put("փ","p"); put("ք","q"); put("օ","o"); put("և","ev"); put("ֆ","f"); put("՝", "`"); put("՞", "?"); put("։", ":"); put("․", "."); } }; // Capitalize existing simple substitutions here for (final Entry entry : new ArrayList>(simpleSubstitions.entrySet())) { String capitalKey = entry.getKey().toUpperCase(); if (!capitalKey.equals(entry.getKey())) { simpleSubstitions.put(capitalKey, entry.getValue().toUpperCase()); } } // Letter + 'ու' final String[] letterMapU = { "ա", "բ", "գ", "դ", "ե", "զ", "է", "ը", "թ", "ժ", "ի", "լ", "խ", "ծ", "կ", "հ", "ձ", "ղ", "ճ", "մ", "յ", "ն", "շ", "չ", "պ", "ջ", "ռ", "ս", "վ", "տ", "ր", "ց", "փ", "ք", "օ", "և", "ֆ", "ո" }; for (final String letter : letterMapU) { final String capitalLetter = letter.toUpperCase(); final String transliteratedLetter = Objects.requireNonNull(simpleSubstitions.get(letter), letter); final String transliteratedCapitalLetter = Objects.requireNonNull(simpleSubstitions.get(capitalLetter), capitalLetter); put(letter + "ու", transliteratedLetter + "u"); put(capitalLetter + "ու", transliteratedCapitalLetter + "u"); put(letter + "ՈՒ", transliteratedLetter + "U"); put(capitalLetter + "ՈՒ", transliteratedCapitalLetter + "U"); put(letter + "Ու", transliteratedLetter + "U"); put(capitalLetter + "Ու", transliteratedCapitalLetter + "U"); put(letter + "ոՒ", transliteratedLetter + "U"); put(capitalLetter + "ոՒ", transliteratedCapitalLetter + "U"); } put("ու","u"); put("Ու","U"); put("ոՒ","U"); put("ՈՒ","U"); // Letter + 'ո' final String[] letterMapVo = { "բ", "գ", "դ", "զ", "թ", "ժ", "լ", "խ", "ծ", "կ", "հ", "ձ", "ղ", "ճ", "մ", "յ", "ն", "շ", "ո", // ո + ո should be voo "չ", "պ", "ջ", "ռ", "ս", "վ", "տ", "ր", "ց", "փ", "ք", "և", "ֆ" }; for (String letter : letterMapVo) { String capitalLetter = letter.toUpperCase(); final String transliteratedLetter = Objects.requireNonNull(simpleSubstitions.get(letter)); final String transliteratedCapitalLetter = Objects.requireNonNull(simpleSubstitions.get(capitalLetter)); put(letter + "ո", transliteratedLetter + "o"); put(capitalLetter + "ո", transliteratedCapitalLetter + "o"); put(letter + "Ո", transliteratedLetter + "Օ"); put(capitalLetter + "Ո", transliteratedCapitalLetter + "Օ"); } put("ո","vo"); put("Ո","VO"); // Two different ways to write, we support all. put("եւ","ev"); put("եվ","ev"); put("Եւ","Ev"); put("Եվ","Ev"); put("ԵՒ","EV"); put("ԵՎ","EV"); // If this symbol wasn't used in the combination with others, then it's meaningless put("ւ",""); put("Ւ",""); // Simple substitutions have last priority for (final Map.Entry entry : simpleSubstitions.entrySet()) { put(entry.getKey(), entry.getValue()); put(entry.getKey().toUpperCase(), entry.getValue().toUpperCase()); } }}; private static final Map transliterationPriorityMap = new HashMap() {{ int priority = 0; for (final String key : transliterateMap.keySet()) { put(key, priority++); } }}; // Aho-Corasick trie private static final Trie transliterationTrie; static { final Trie.TrieBuilder builder = Trie.builder(); for (final String key : ArmenianTransliterator.transliterateMap.keySet()) { builder.addKeyword(key); } transliterationTrie = builder.build(); } private static String ahoCorasick(final String text) { // Create a buffer sufficiently large that re-allocations are minimized. final StringBuilder sb = new StringBuilder(text.length() * 10 / 12); // The complexity of the Aho-Corasick algorithm O(N + L + Z) // Where N is the length of the text, L is the length of keywords and the Z is a number of matches. // This algorithm allows us to do fast substring search final List emits = new ArrayList(transliterationTrie.parseText(text)); // Sort collection first by starting position, then by priority. Collections.sort(emits, new Comparator() { @Override public int compare(Emit a, Emit b) { int cmp = Integer.compare(a.getStart(), b.getStart()); if (cmp != 0) { return cmp; } int priorityA = transliterationPriorityMap.get(a.getKeyword()); int priorityB = transliterationPriorityMap.get(b.getKeyword()); return Integer.compare(priorityA, priorityB); } }); int prevIndex = 0; for (final Emit emit : emits) { final int matchIndex = emit.getStart(); // Skip if we already substituted this part if (matchIndex < prevIndex) { continue; } // Add part which shouldn't be substituted sb.append(text.substring(prevIndex, matchIndex)); // Substitute and append to the builder sb.append(Objects.requireNonNull(ArmenianTransliterator.transliterateMap.get(emit.getKeyword()))); prevIndex = emit.getEnd() + 1; } // Add the remainder of the string (contains no more matches). sb.append(text.substring(prevIndex)); return sb.toString(); } @Override public String transliterate(String txt) { if (txt == null || txt.isEmpty()) { return txt; } return ahoCorasick(txt); } }