diff --git a/CHANGELOG.md b/CHANGELOG.md index 2853844..381850b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +1.4.0 +-- +част. снятие омонимии на морф. анализе; испр. нач.формы глагола; подд. е вместо ё; расширение словаря + 1.2.16 -- diff --git a/pom.xml b/pom.xml index 34d4965..c022733 100644 --- a/pom.xml +++ b/pom.xml @@ -12,9 +12,13 @@ ru.textanalysis.tawt tawt-parent - 0.0.5 + 0.1.0 + + 1.14.2 + + @@ -49,6 +53,13 @@ lombok provided + + + + org.jsoup + jsoup + ${jsoup.version} + diff --git a/src/main/java/ru/textanalysis/tawt/ms/constant/Const.java b/src/main/java/ru/textanalysis/tawt/ms/constant/Const.java index 2ec5d28..b936106 100644 --- a/src/main/java/ru/textanalysis/tawt/ms/constant/Const.java +++ b/src/main/java/ru/textanalysis/tawt/ms/constant/Const.java @@ -6,6 +6,10 @@ public interface Const { int BUFFER_SIZE_FOR_INSERT = 10_000; String START_INSERT = "INSERT INTO 'Form' ('id','StringForm') VALUES "; String CONTINUED_INSERT = "(%d, '%s')"; + + String TAB_SEPARATOR = "\t"; String COMMA_SEPARATOR = ","; String SEMICOLON_SEPARATOR = ";"; + + String TAB_AND_COMMA_REGEX = "[,;]"; } diff --git a/src/main/java/ru/textanalysis/tawt/ms/constant/TypeOfSpeechs.java b/src/main/java/ru/textanalysis/tawt/ms/constant/TypeOfSpeechs.java new file mode 100644 index 0000000..8276221 --- /dev/null +++ b/src/main/java/ru/textanalysis/tawt/ms/constant/TypeOfSpeechs.java @@ -0,0 +1,12 @@ +package ru.textanalysis.tawt.ms.constant; + +public interface TypeOfSpeechs { + + String VERB = "VERB"; + String NUMR = "NUMR"; + String ADJF = "ADJF"; + String ADJS = "ADJS"; + String PRTF = "PRTF"; + String PRTS = "PRTS"; + String INFN = "INFN"; +} diff --git a/src/main/java/ru/textanalysis/tawt/ms/conversion/dictionary/ConversionDictionary.java b/src/main/java/ru/textanalysis/tawt/ms/conversion/dictionary/ConversionDictionary.java index a49aac6..3b031fa 100644 --- a/src/main/java/ru/textanalysis/tawt/ms/conversion/dictionary/ConversionDictionary.java +++ b/src/main/java/ru/textanalysis/tawt/ms/conversion/dictionary/ConversionDictionary.java @@ -38,22 +38,24 @@ package ru.textanalysis.tawt.ms.conversion.dictionary; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import ru.textanalysis.tawt.ms.loader.DatabaseFactory; import ru.textanalysis.tawt.ms.loader.DatabaseLemmas; import ru.textanalysis.tawt.ms.loader.DatabaseStrings; -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.File; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.regex.Pattern; +import java.util.*; + +import static ru.textanalysis.tawt.ms.constant.Const.COMMA_SEPARATOR; +import static ru.textanalysis.tawt.ms.constant.Const.TAB_SEPARATOR; +import static ru.textanalysis.tawt.ms.constant.TypeOfSpeechs.INFN; +import static ru.textanalysis.tawt.ms.constant.TypeOfSpeechs.VERB; @Slf4j public class ConversionDictionary { @@ -63,12 +65,11 @@ public class ConversionDictionary { public static void main(String[] args) { ConversionDictionary conversionDictionary = new ConversionDictionary(); - conversionDictionary.conversionDictionary("dict.opcorpora.txt", StandardCharsets.UTF_8); + conversionDictionary.conversionDictionary("dict.opcorpora.xml", StandardCharsets.UTF_8); } - // todo: йо - public void conversionDictionary(String sourceDictionaryPath, Charset encoding) { - List> lemmas = convertLemmasFromInitDictionary(sourceDictionaryPath, encoding); + public void conversionDictionary(String sourceDictionaryPath, Charset encoding, String... additionalDictionaryPaths) { + List> lemmas = convertLemmasFromInitDictionary(sourceDictionaryPath, encoding, additionalDictionaryPaths); databaseLemmas.recreate(lemmas); databaseStrings.recreate(lemmas); @@ -76,38 +77,166 @@ public void conversionDictionary(String sourceDictionaryPath, Charset encoding) databaseLemmas.compression(); } - private List> convertLemmasFromInitDictionary(String sourceDictionaryPath, Charset encoding) { + private List> convertLemmasFromInitDictionary(String sourceDictionaryPath, Charset encoding, String... additionalDictionaryPaths) { + List dictionaryPaths = new ArrayList<>(); + dictionaryPaths.add(sourceDictionaryPath); + dictionaryPaths.addAll(Arrays.asList(additionalDictionaryPaths)); List> lemmas = new ArrayList<>(); - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(sourceDictionaryPath), encoding))) { - while (bufferedReader.ready()) { - String initForm = bufferedReader.readLine(); - if (StringUtils.isNotBlank(initForm) && !Pattern.matches("\\d+", initForm)) { //todo - List lemma = new LinkedList<>(); - FormForConversion initialForm = createForm(initForm, true); - lemma.add(initialForm); - while (bufferedReader.ready()) { - String derivativeForm = bufferedReader.readLine(); - if (StringUtils.isBlank(derivativeForm)) { - break; + HashMap> lemmasMap = new HashMap<>(); + HashMap> verbs = new HashMap<>(); + + try { + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + for (String dictionaryPath : dictionaryPaths) { + Document document = documentBuilder.parse(new File(dictionaryPath).toURI().toString()); + + Node dictionary = document.getDocumentElement(); + NodeList dictionaryProps = dictionary.getChildNodes(); + for (int i = 0; i < dictionaryProps.getLength(); i++) { + Node node = dictionaryProps.item(i); + if (node.getNodeType() != Node.TEXT_NODE && (node.getNodeName().equals("grammemes") || node.getNodeName().equals("restrictions") || node.getNodeName().equals("link_types"))) { + while (node.hasChildNodes()) { + node.removeChild(node.getFirstChild()); + } + } + } + for (int i = 0; i < dictionaryProps.getLength(); i++) { + Node lemmata = dictionaryProps.item(i); + if (lemmata.getNodeType() != Node.TEXT_NODE && lemmata.getNodeName().equals("lemmata")) { + NodeList lemmataProps = lemmata.getChildNodes(); + for (int j = 0; j < lemmataProps.getLength(); j++) { + Node lemma = lemmataProps.item(j); + if (lemma.getNodeType() != Node.TEXT_NODE && lemma.getNodeName().equals("lemma")) { + String commonCharacteristics = ""; + int formNumber = 0; + boolean isVerb = false; + List verbInfn = new ArrayList<>(); + List wordLemma = new LinkedList<>(); + NodeList lemmaProps = lemma.getChildNodes(); + for (int k = 0; k < lemmaProps.getLength(); k++) { + Node value = lemmaProps.item(k); + if (value.getNodeType() != Node.TEXT_NODE && value.getNodeName().equals("l")) { + NodeList valueProps = value.getChildNodes(); + for (int m = 0; m < valueProps.getLength(); m++) { + Node characteristic = valueProps.item(m); + if (characteristic.getNodeType() != Node.TEXT_NODE && characteristic.getNodeName().equals("g")) { + commonCharacteristics += characteristic.getAttributes().getNamedItem("v").getNodeValue(); + commonCharacteristics += COMMA_SEPARATOR; + } + } + if (commonCharacteristics.length() > 0) { + commonCharacteristics = commonCharacteristics.substring(0, commonCharacteristics.length() - 1); + } + } else if (value.getNodeType() != Node.TEXT_NODE && value.getNodeName().equals("f")) { + StringBuilder formCharacteristics = new StringBuilder(value.getAttributes().getNamedItem("t").getNodeValue()); + formCharacteristics.append(TAB_SEPARATOR); + formCharacteristics.append(commonCharacteristics); + NodeList valueProps = value.getChildNodes(); + for (int m = 0; m < valueProps.getLength(); m++) { + Node characteristic = valueProps.item(m); + if (characteristic.getNodeType() != Node.TEXT_NODE && characteristic.getNodeName().equals("g")) { + formCharacteristics.append(COMMA_SEPARATOR); + formCharacteristics.append(characteristic.getAttributes().getNamedItem("v").getNodeValue()); + } + } + if (formCharacteristics.toString().contains(INFN) || formCharacteristics.toString().contains(VERB)) { + isVerb = true; + verbInfn.add(formCharacteristics.toString()); + } else { + if (formNumber == 0) { + FormForConversion initialForm = createForm(formCharacteristics.toString(), true); + wordLemma.add(initialForm); + } else { + wordLemma.add(createForm(formCharacteristics.toString(), false)); + } + formNumber++; + } + } + } + if (isVerb) { + verbs.put(Integer.valueOf(lemma.getAttributes().getNamedItem("id").getNodeValue()), verbInfn); + } else { + lemmasMap.put(Integer.valueOf(lemma.getAttributes().getNamedItem("id").getNodeValue()), wordLemma); + } + while (lemma.hasChildNodes()) { + lemma.removeChild(lemma.getFirstChild()); + } + } + } + } else if (lemmata.getNodeType() != Node.TEXT_NODE && lemmata.getNodeName().equals("links")) { + NodeList lemmataProps = lemmata.getChildNodes(); + for (int j = 0; j < lemmataProps.getLength(); j++) { + Node lemma = lemmataProps.item(j); + if (lemma.getNodeType() != Node.TEXT_NODE && lemma.getNodeName().equals("link")) { + if (Objects.equals(lemma.getAttributes().getNamedItem("type").getNodeValue(), "3")) { + List wordLemma = new LinkedList<>(); + List infn = verbs.get(Integer.parseInt(lemma.getAttributes().getNamedItem("from").getNodeValue())); + FormForConversion initialForm = createForm(infn.get(0).replaceAll(INFN, VERB), true); + wordLemma.add(initialForm); + List verb = verbs.get(Integer.parseInt(lemma.getAttributes().getNamedItem("to").getNodeValue())); + verb.forEach(form -> { + FormForConversion derivativeForm = createForm(form, false); + wordLemma.add(derivativeForm); + }); + lemmasMap.put(Integer.valueOf(lemma.getAttributes().getNamedItem("from").getNodeValue()), wordLemma); + } else if (!Objects.equals(lemma.getAttributes().getNamedItem("type").getNodeValue(), "11")) { + if (lemmasMap.containsKey(Integer.parseInt(lemma.getAttributes().getNamedItem("to").getNodeValue()))) { + lemmasMap.get(Integer.parseInt(lemma.getAttributes().getNamedItem("to").getNodeValue())).forEach(formLemma -> { + if (lemmasMap.containsKey(Integer.parseInt(lemma.getAttributes().getNamedItem("from").getNodeValue()))) { + formLemma.setLink(lemmasMap.get(Integer.parseInt(lemma.getAttributes().getNamedItem("from").getNodeValue())).get(0).hashCode(), + lemmasMap.get(Integer.parseInt(lemma.getAttributes().getNamedItem("from").getNodeValue())).get(0).getKey()); + } + }); + } + } + while (lemma.hasChildNodes()) { + lemma.removeChild(lemma.getFirstChild()); + } + } } - lemma.add(createForm(derivativeForm, false)); } - lemmas.add(lemma); } } - } catch (IOException ex) { + lemmas = new ArrayList<>(lemmasMap.values()); + lemmasMap.clear(); + verbs.clear(); + lemmas.add(addSupportYo(lemmas)); + } catch (Exception ex) { log.error("Ошибка при чтении файла. Файл: {}", sourceDictionaryPath, ex); } return lemmas; } + private List addSupportYo(List> lemmas) { + boolean firstYo = true; + List yoLemma = new LinkedList<>(); + for (List lemma : lemmas) { + for (FormForConversion formForConversion : lemma) { + if (formForConversion.getStringName().contains("ё")) { + String curLemmaString = formForConversion.getStringName().split(TAB_SEPARATOR)[0]; + if (firstYo) { + FormForConversion yoForm = createForm(curLemmaString.replaceAll("ё", "е"), true); + yoForm.setLink(formForConversion.hashCode(), formForConversion.getKey()); + yoLemma.add(yoForm); + firstYo = false; + } else { + FormForConversion yoForm = createForm(curLemmaString.replaceAll("ё", "е"), false); + yoForm.setLink(formForConversion.hashCode(), formForConversion.getKey()); + yoLemma.add(yoForm); + } + } + } + } + return yoLemma; + } + private FormForConversion createForm(String line, boolean isInitialForm) { - String[] parameters = line.toLowerCase(Locale.ROOT).split("\t"); + String[] parameters = line.toLowerCase(Locale.ROOT).split(TAB_SEPARATOR); FormForConversion form = new FormForConversion(parameters[0], isInitialForm); if (parameters.length > 1) { form.setCharacteristics(parameters[1].split("[, ]")); } else { - System.out.println("error"); //todo + form.setCharacteristics(new String[0]); } return form; } diff --git a/src/main/java/ru/textanalysis/tawt/ms/conversion/dictionary/FormForConversion.java b/src/main/java/ru/textanalysis/tawt/ms/conversion/dictionary/FormForConversion.java index 1b81246..1fedae6 100644 --- a/src/main/java/ru/textanalysis/tawt/ms/conversion/dictionary/FormForConversion.java +++ b/src/main/java/ru/textanalysis/tawt/ms/conversion/dictionary/FormForConversion.java @@ -23,11 +23,14 @@ public class FormForConversion { private final int key; private byte partOfSpeech; private byte[] morfCharacteristics; + private byte[] Link; private boolean isFirstKey; protected FormForConversion(String stringName, boolean isInitialForm) { this.stringName = stringName.toLowerCase(Locale.ROOT); key = createKey(isInitialForm); + long link = 0; + this.Link = getBytes(link); } public String getStringName() { @@ -35,15 +38,28 @@ public String getStringName() { } protected void setCharacteristics(String[] characteristics) { - List parameters = new ArrayList<>(Arrays.asList(characteristics)); - setPartOfSpeech(conversionPartOfSpeech(parameters)); - setMorfCharacteristics(getBytes(conversionMorfCharacteristics(parameters))); + if (characteristics.length > 0) { + List parameters = new ArrayList<>(Arrays.asList(characteristics)); + setPartOfSpeech(conversionPartOfSpeech(parameters)); + setMorfCharacteristics(getBytes(conversionMorfCharacteristics(parameters))); + } else { + setPartOfSpeech((byte) 0); + long chars = 0; + setMorfCharacteristics(getBytes(chars)); + } } private void setPartOfSpeech(byte partOfSpeech) { this.partOfSpeech = partOfSpeech; } + protected void setLink(int hash, int key) { + long morphLink = hash; + morphLink = morphLink << 32; + morphLink += key; + this.Link = getBytes(morphLink); + } + private byte getPartOfSpeech() { return partOfSpeech; } @@ -114,6 +130,10 @@ private byte[] getMorfCharacteristics() { return morfCharacteristics; } + private byte[] getLink() { + return Link; + } + public int getKey() { return key; } @@ -157,6 +177,7 @@ public byte[] getByteFileFormat() { bytesFormat = plusByte(bytesFormat, getPartOfSpeech()); } bytesFormat = plusByte(bytesFormat, getMorfCharacteristics()); + bytesFormat = plusByte(bytesFormat, getLink()); return bytesFormat; } diff --git a/src/main/java/ru/textanalysis/tawt/ms/dictionary/convertor/WiktionaryTagsData.java b/src/main/java/ru/textanalysis/tawt/ms/dictionary/convertor/WiktionaryTagsData.java new file mode 100644 index 0000000..fc85fa7 --- /dev/null +++ b/src/main/java/ru/textanalysis/tawt/ms/dictionary/convertor/WiktionaryTagsData.java @@ -0,0 +1,118 @@ +package ru.textanalysis.tawt.ms.dictionary.convertor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static ru.textanalysis.tawt.ms.constant.TypeOfSpeechs.*; + +/** + * Хранение преобразованания тегов с Wiktionary в стандарт OpenCorpora + */ +public class WiktionaryTagsData { + + private final Map tagsDictionary; + private final List toSDictionary; + + public WiktionaryTagsData() { + tagsDictionary = new HashMap<>(); + tagsDictionary.put("прилагательное", ADJF); + tagsDictionary.put("качественное", "Qual"); + tagsDictionary.put("ед. ч.", "sing"); + tagsDictionary.put("мн. ч.", "plur"); + tagsDictionary.put("муж. р.", "masc"); + tagsDictionary.put("ср. р.", "neut"); + tagsDictionary.put("жен. р.", "femn"); + tagsDictionary.put("существительное", "NOUN"); + tagsDictionary.put("притяжательное прилагательное", "ADJF,Poss"); + tagsDictionary.put("прилагательное (притяжательное)", "ADJF,Poss"); + tagsDictionary.put("прилагательное (относительное)", ADJF); + tagsDictionary.put("относительное прилагательное", ADJF); + tagsDictionary.put("местоименное прилагательное", "ADJF,Apro"); + tagsDictionary.put("притяжательное местоимение (местоименное прилагательное)", "NPRO,Poss"); + tagsDictionary.put("определительное местоимение (местоименное прилагательное)", "ADJF,Apro"); + tagsDictionary.put("общий род (может согласовываться с другими частями речи как мужского, так и женского рода)", "ms-f"); + tagsDictionary.put("им.", "nomn"); + tagsDictionary.put("р.", "gent"); + tagsDictionary.put("д.", "datv"); + tagsDictionary.put("в.", "accs"); + tagsDictionary.put("т.", "ablt"); + tagsDictionary.put("тв.", "ablt"); + tagsDictionary.put("п.", "loct"); + tagsDictionary.put("пр.", "loct"); + tagsDictionary.put("одуш.", "anim"); + tagsDictionary.put("неод.", "inan"); + tagsDictionary.put("глагол", VERB); + tagsDictionary.put("одушевлённое", "anim"); + tagsDictionary.put("неодушевлённое", "inan"); + tagsDictionary.put("совершенный вид", "perf"); + tagsDictionary.put("переходный", "tran"); + tagsDictionary.put("несовершенный вид", "impf"); + tagsDictionary.put("непереходный", "intr"); + tagsDictionary.put("будущ.", "futr"); + tagsDictionary.put("прош.", "past"); + tagsDictionary.put("наст.", "pres"); + tagsDictionary.put("повелит.", "impr"); + tagsDictionary.put("средний род", "neut"); + tagsDictionary.put("мужской род", "masc"); + tagsDictionary.put("женский род", "femn"); + tagsDictionary.put("действительное причастие", "PRTF,actv"); + tagsDictionary.put("страдательное причастие", "PRTF,pssv"); + tagsDictionary.put("совершенного вида", "perf"); + tagsDictionary.put("несовершенного вида", "impf"); + tagsDictionary.put("прошедшего времени", "past"); + tagsDictionary.put("настоящего времени", "pres"); + tagsDictionary.put("будущего времени", "futr"); + tagsDictionary.put("невозвратное деепричастие", "GRND"); + tagsDictionary.put("возвратное деепричастие", "GRND,Refl"); + tagsDictionary.put("возвратный", "Refl"); + tagsDictionary.put("возвратное причастие", "PRTF,Refl"); + tagsDictionary.put("числительное", NUMR); + tagsDictionary.put("порядковое", "Anum"); + tagsDictionary.put("вводное слово", "CONJ,Prnt"); + tagsDictionary.put("также вводное слово", "Prnt"); + tagsDictionary.put("наречие", "ADVB"); + tagsDictionary.put("предлог", "PREP"); + tagsDictionary.put("союз", "CONJ"); + tagsDictionary.put("частица", "PRCL"); + tagsDictionary.put("междометие", "INTJ"); + tagsDictionary.put("я", "1per"); + tagsDictionary.put("мы", "1per"); + tagsDictionary.put("ты", "2per"); + tagsDictionary.put("вы", "2per"); + tagsDictionary.put("он она оно", "3per"); + tagsDictionary.put("они", "3per"); + tagsDictionary.put("местоимение (притяжательное)", "ADJF,Apro"); + tagsDictionary.put("предикатив", "PRED"); + tagsDictionary.put("порядковое числительное (счётное прилагательное)", "ADJF,Anum"); + tagsDictionary.put("порядковое числительное", "ADJF,Anum"); + tagsDictionary.put("счётное прилагательное", "ADJF,Anum"); + + toSDictionary = new ArrayList<>(); + toSDictionary.add(ADJF); + toSDictionary.add(ADJS); + toSDictionary.add("NOUN"); + toSDictionary.add("PRED"); + toSDictionary.add("INTJ"); + toSDictionary.add("PRCL"); + toSDictionary.add("CONJ"); + toSDictionary.add("PREP"); + toSDictionary.add("ADVB"); + toSDictionary.add(NUMR); + toSDictionary.add(PRTF); + toSDictionary.add(PRTS); + toSDictionary.add("GRND"); + toSDictionary.add(VERB); + toSDictionary.add(INFN); + toSDictionary.add("NPRO"); + } + + public Map getTags() { + return tagsDictionary; + } + + public List getToS() { + return toSDictionary; + } +} \ No newline at end of file diff --git a/src/main/java/ru/textanalysis/tawt/ms/dictionary/convertor/WiktionaryToOpenCorporaConverter.java b/src/main/java/ru/textanalysis/tawt/ms/dictionary/convertor/WiktionaryToOpenCorporaConverter.java new file mode 100644 index 0000000..dce6849 --- /dev/null +++ b/src/main/java/ru/textanalysis/tawt/ms/dictionary/convertor/WiktionaryToOpenCorporaConverter.java @@ -0,0 +1,80 @@ +package ru.textanalysis.tawt.ms.dictionary.convertor; + +import static ru.textanalysis.tawt.ms.constant.Const.*; +import static ru.textanalysis.tawt.ms.constant.TypeOfSpeechs.*; + +/** + * Преобразование тегов с Wiktionary в стандарт OpenCorpora + */ +public class WiktionaryToOpenCorporaConverter { + + private final WiktionaryTagsData wiktionaryTagsData; + + /** + * Instantiates a new Tags to word form converter. + * + * @param wiktionaryTagsData объект с информацией о преобразовании тегов + */ + public WiktionaryToOpenCorporaConverter(WiktionaryTagsData wiktionaryTagsData) { + this.wiktionaryTagsData = wiktionaryTagsData; + } + + /** + * Преобразовывает неизменяемые части речи из + * стандарта Wiktionary в стандарт OpenCorpora + * + * @param token слово и его теги, разделённые '\t' + * @return WordForm с леммой в стандарте OpenCorpora + */ + public WordFormForConverter convertImmutableToWordForm(String token) { + String[] tokens = token.split(TAB_SEPARATOR); + String[] tags = tokens[1].split(TAB_AND_COMMA_REGEX); + StringBuilder builder = new StringBuilder(tokens[0]).append(TAB_SEPARATOR); + for (String tag : tags) { + tag = tag.trim(); + if (wiktionaryTagsData.getTags().containsKey(tag)) { + builder.append(wiktionaryTagsData.getTags().get(tag)).append(COMMA_SEPARATOR); + } + } + String result = builder.toString(); + if (result.contains(COMMA_SEPARATOR)) { + result = result.substring(0, result.length() - 1).replaceAll(VERB, INFN); + } + return new WordFormForConverter(result); + } + + /** + * Преобразовывает изменяемые части речи из + * стандарте Wiktionary в стандарт OpenCorpora + * Задействуется таблица словоформ + * + * @param token слово и его теги, разделённые '\t' + * @return WordForm с леммой в стандарте OpenCorpora + */ + public WordFormForConverter convertTableFormToWordForm(String token) { + String[] tokens = token.split(TAB_SEPARATOR); + String[] tags = tokens[1].split(TAB_AND_COMMA_REGEX); + StringBuilder builder = new StringBuilder(tokens[0]).append(TAB_SEPARATOR); + for (String tag : tags) { + tag = tag.trim(); + if (wiktionaryTagsData.getTags().containsKey(tag) && builder.indexOf(wiktionaryTagsData.getTags().get(tag)) == -1) { + builder.append(wiktionaryTagsData.getTags().get(tag)).append(COMMA_SEPARATOR); + } + } + String result = builder.toString(); + if (result.contains(COMMA_SEPARATOR)) { + result = result.substring(0, result.length() - 1); + } + if (token.contains("порядковое")) { + result = result.replaceAll(NUMR, ADJF); + } + if (token.contains("кратк. форма")) { + if (result.contains(ADJF)) { + result = result.replaceAll(ADJF, ADJS); + } else if (result.contains(PRTF)) { + result = result.replaceAll(PRTF, PRTS); + } + } + return new WordFormForConverter(result); + } +} \ No newline at end of file diff --git a/src/main/java/ru/textanalysis/tawt/ms/dictionary/convertor/WordFormForConverter.java b/src/main/java/ru/textanalysis/tawt/ms/dictionary/convertor/WordFormForConverter.java new file mode 100644 index 0000000..7fd7344 --- /dev/null +++ b/src/main/java/ru/textanalysis/tawt/ms/dictionary/convertor/WordFormForConverter.java @@ -0,0 +1,77 @@ +package ru.textanalysis.tawt.ms.dictionary.convertor; + +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static ru.textanalysis.tawt.ms.constant.Const.COMMA_SEPARATOR; +import static ru.textanalysis.tawt.ms.constant.Const.TAB_SEPARATOR; + +/** + * Хранение слова и его тегов + */ +@Slf4j +public class WordFormForConverter { + + private String word; + private List tags; + + /** + * Instantiates a new Word form. + * + * @param OpenCorporaWordForm слово и его теги в стандарте OpenCorpora + */ + public WordFormForConverter(String OpenCorporaWordForm) { + try { + if (OpenCorporaWordForm.length() == 0) { + throw new Exception("Неверный формат словоформы"); + } + String[] wordTags = OpenCorporaWordForm.split(TAB_SEPARATOR); + if (wordTags.length != 2) { + throw new Exception("Неверный формат словоформы"); + } + this.word = wordTags[0]; + String[] tags = wordTags[1].split(COMMA_SEPARATOR); + this.tags = new ArrayList<>(); + this.tags.addAll(Arrays.asList(tags)); + } catch (Exception ex) { + log.info(ex.getMessage(), ex); + } + } + + public String getWord() { + return word; + } + + public List getTags() { + return tags; + } + + /** + * проверка, содержится в WordForm необходимый тег + * + * @param str проверяемое значение + * + * @return the boolean + */ + public boolean contains(String str) { + return this.toString().contains(str); + } + + @Override + public String toString() { + try { + StringBuilder result = new StringBuilder(this.word + TAB_SEPARATOR + this.tags.get(0)); + for (int i = 1; i < this.tags.size(); i++) { + result.append(COMMA_SEPARATOR); + result.append(this.tags.get(i)); + } + return result.toString(); + } catch (Exception ex) { + log.error("Не удалось получить токен.", ex); + return ""; + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/textanalysis/tawt/ms/dictionary/open/corpora/OpenCorporaXmlDocument.java b/src/main/java/ru/textanalysis/tawt/ms/dictionary/open/corpora/OpenCorporaXmlDocument.java new file mode 100644 index 0000000..ae4b1f9 --- /dev/null +++ b/src/main/java/ru/textanalysis/tawt/ms/dictionary/open/corpora/OpenCorporaXmlDocument.java @@ -0,0 +1,163 @@ +package ru.textanalysis.tawt.ms.dictionary.open.corpora; + +import lombok.extern.slf4j.Slf4j; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.management.modelmbean.XMLParseException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.File; +import java.util.List; +import java.util.Objects; + +/** + * Предоставляет создание нового xml файла, + * открытие существующего xml файла в стандарте OpenCorpora, + * добавление новых лемм в xml файлом + */ +@Slf4j +public class OpenCorporaXmlDocument { + + private Document doc; + private org.w3c.dom.Element lemmataElement; + private org.w3c.dom.Element linksElement; + private int lemmaId; + + /** + * Instantiates a new Open corpora xml document. + */ + public OpenCorporaXmlDocument() { + try { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + this.doc = dBuilder.newDocument(); + + org.w3c.dom.Element rootElement = doc.createElement("dictionary"); + doc.appendChild(rootElement); + org.w3c.dom.Element grammemesElement = doc.createElement("grammemes"); + rootElement.appendChild(grammemesElement); + org.w3c.dom.Element restrictionsElement = doc.createElement("restrictions"); + rootElement.appendChild(restrictionsElement); + this.lemmataElement = doc.createElement("lemmata"); + rootElement.appendChild(lemmataElement); + org.w3c.dom.Element linkTypesElement = doc.createElement("link_types"); + rootElement.appendChild(linkTypesElement); + this.linksElement = doc.createElement("links"); + rootElement.appendChild(linksElement); + + lemmaId = 100000001; + } catch (ParserConfigurationException e) { + log.warn(e.getMessage(), e); + } + } + + /** + * Instantiates a new Open corpora xml document. + * + * @param filePath путь до файла xml в стандарте OpenCorpora + * @param lemmas List в котором хранятся уже добавленные леммы, для предотвращения дублирования + */ + public OpenCorporaXmlDocument(String filePath, List lemmas) { + try { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + dbFactory.setIgnoringElementContentWhitespace(true); + dbFactory.setNamespaceAware(true); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + this.doc = dBuilder.parse(new File(filePath)); + doc.getDocumentElement().normalize(); + + org.w3c.dom.Element rootElement = doc.getDocumentElement(); + deleteEmptyNodes(rootElement); + if (Objects.equals(rootElement.getNodeName(), "dictionary")) { + NodeList rootElementChilds = rootElement.getChildNodes(); + for (int i = 0; i < rootElementChilds.getLength(); i++) { + Node nNode = rootElementChilds.item(i); + if (nNode.getNodeType() == Node.ELEMENT_NODE) { + org.w3c.dom.Element eElement = (org.w3c.dom.Element) nNode; + if (eElement.getTagName().equals("lemmata")) { + this.lemmataElement = eElement; + NodeList lemmataElementChilds = lemmataElement.getChildNodes(); + for (int j = 0; j < lemmataElementChilds.getLength(); j++) { + Node lNode = lemmataElementChilds.item(j); + if (lNode.getNodeType() == Node.ELEMENT_NODE) { + org.w3c.dom.Element lElement = (org.w3c.dom.Element) lNode; + if (lElement.getTagName().equals("lemma")) { + if (this.lemmaId < Integer.parseInt(lElement.getAttributeNode("id").getValue())) { + this.lemmaId = Integer.parseInt(lElement.getAttributeNode("id").getValue()); + } + NodeList lemmaElementChilds = lElement.getChildNodes(); + for (int k = 0; k < lemmaElementChilds.getLength(); k++) { + Node tNode = lemmaElementChilds.item(k); + if (tNode.getNodeType() == Node.ELEMENT_NODE) { + org.w3c.dom.Element tElement = (org.w3c.dom.Element) tNode; + if (tElement.getTagName().equals("l")) { + synchronized (lemmas) { + lemmas.add(tElement.getAttributeNode("t").getValue()); + } + } + } + } + } + } + } + this.lemmaId++; + } else if (eElement.getTagName().equals("links")) { + this.linksElement = eElement; + } + } + } + } else { + throw new XMLParseException(); + } + if (this.lemmataElement == null || this.linksElement == null) { + throw new XMLParseException(); + } + if (lemmaId == 0) { + lemmaId = 100000001; + } + } catch (XMLParseException ex) { + log.error("Неправильный формат xml файла.", ex); + } catch (Exception ex) { + ex.printStackTrace(); + log.error(ex.getMessage(), ex); + } + } + + public Document getDoc() { + return doc; + } + + public Element getLemmataElement() { + return lemmataElement; + } + + public Element getLinksElement() { + return linksElement; + } + + public int getLemmaId() { + return lemmaId; + } + + public void setLemmaId(int lemmaId) { + this.lemmaId = lemmaId; + } + + private void deleteEmptyNodes(org.w3c.dom.Element element) { + if (element.hasChildNodes()) { + NodeList nodelist = element.getChildNodes(); + for (int i = nodelist.getLength() - 1; i >= 0; i--) { + if (nodelist.item(i).getNodeType() != Node.ELEMENT_NODE) { + element.removeChild(nodelist.item(i)); + } + } + for (int i = 0; i < element.getChildNodes().getLength(); i++) { + deleteEmptyNodes((org.w3c.dom.Element) element.getChildNodes().item(i)); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/textanalysis/tawt/ms/dictionary/open/corpora/XmlBuilder.java b/src/main/java/ru/textanalysis/tawt/ms/dictionary/open/corpora/XmlBuilder.java new file mode 100644 index 0000000..0b24573 --- /dev/null +++ b/src/main/java/ru/textanalysis/tawt/ms/dictionary/open/corpora/XmlBuilder.java @@ -0,0 +1,103 @@ +package ru.textanalysis.tawt.ms.dictionary.open.corpora; + +import lombok.extern.slf4j.Slf4j; +import ru.textanalysis.tawt.ms.dictionary.convertor.WiktionaryTagsData; +import ru.textanalysis.tawt.ms.dictionary.convertor.WordFormForConverter; +import ru.textanalysis.tawt.ms.dictionary.wiktionary.WiktionaryInfoParser; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Основной класс + * Создание нового xml файла или открытие существующего + * Получение словоформ в стандарте OpenCorpora из Wiktionary + * Добавление новых лемм в xml файл + * Запись информации в xml файл + */ +@Slf4j +public class XmlBuilder { + + private final OpenCorporaXmlDocument xmlDocument; + private final XmlDocumentLemmaCreator lemmaCreator; + private final WiktionaryTagsData wiktionaryTagsData; + private final WiktionaryInfoParser wiktionaryInfoParser; + private final List lemmas; + + /** + * Instantiates a new Xml builder. + */ + public XmlBuilder() { + this.lemmas = Collections.synchronizedList(new ArrayList<>()); + xmlDocument = new OpenCorporaXmlDocument(); + wiktionaryTagsData = new WiktionaryTagsData(); + lemmaCreator = new XmlDocumentLemmaCreator(wiktionaryTagsData, xmlDocument); + wiktionaryInfoParser = new WiktionaryInfoParser(wiktionaryTagsData); + } + + /** + * Instantiates a new Xml builder. + * + * @param filePath путь до файла xml в формате OpenCorpora + */ + public XmlBuilder(String filePath) { + this.lemmas = Collections.synchronizedList(new ArrayList<>()); + xmlDocument = new OpenCorporaXmlDocument(filePath, this.lemmas); + wiktionaryTagsData = new WiktionaryTagsData(); + lemmaCreator = new XmlDocumentLemmaCreator(wiktionaryTagsData, xmlDocument); + wiktionaryInfoParser = new WiktionaryInfoParser(wiktionaryTagsData); + } + + /** + * Получение информации о слове и его тегах в стандарте OpenCorpora + * + * @param word исследуемое слово + * @param sleepTime время между запросами + * @param requestTimeOut время ожидания подключения + * @return the words tags + */ + public List> getWordsTags(String word, int sleepTime, int requestTimeOut) { + return wiktionaryInfoParser.getWordsTags(word, sleepTime, requestTimeOut, lemmas); + } + + /** + * Добавление новой формы в xml файл + * + * @param forms список форм в стандарте OpenCorpora + */ + public void addLemmaForms(List> forms) { + synchronized (lemmaCreator) { + lemmaCreator.addLemmaForms(forms); + } + } + + /** + * Записывает информацию в xml файл + * + * @param filePath путь до файла, в который будет идти запись + */ + public synchronized void recordInFile(String filePath) { + try { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + + DOMSource domSource = new DOMSource(xmlDocument.getDoc()); + StreamResult streamResult = new StreamResult(new File(filePath)); + + transformer.transform(domSource, streamResult); + } catch (TransformerException ex) { + log.error("Ошибка записи в файл.", ex); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/textanalysis/tawt/ms/dictionary/open/corpora/XmlDocumentLemmaCreator.java b/src/main/java/ru/textanalysis/tawt/ms/dictionary/open/corpora/XmlDocumentLemmaCreator.java new file mode 100644 index 0000000..4bb6a0a --- /dev/null +++ b/src/main/java/ru/textanalysis/tawt/ms/dictionary/open/corpora/XmlDocumentLemmaCreator.java @@ -0,0 +1,138 @@ +package ru.textanalysis.tawt.ms.dictionary.open.corpora; + +import lombok.extern.slf4j.Slf4j; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import ru.textanalysis.tawt.ms.dictionary.convertor.WiktionaryTagsData; +import ru.textanalysis.tawt.ms.dictionary.convertor.WordFormForConverter; + +import java.util.ArrayList; +import java.util.List; + +import static ru.textanalysis.tawt.ms.constant.Const.TAB_SEPARATOR; +import static ru.textanalysis.tawt.ms.constant.TypeOfSpeechs.*; + +/** + * Добавление в xml файл новых лемм + */ +@Slf4j +public class XmlDocumentLemmaCreator { + + private final WiktionaryTagsData wiktionaryTagsData; + private final OpenCorporaXmlDocument xmlDocument; + + /** + * Instantiates a new Xml document lemma creator. + * + * @param wiktionaryTagsData объект с информацией о преобразовании тегов + */ + public XmlDocumentLemmaCreator(WiktionaryTagsData wiktionaryTagsData, OpenCorporaXmlDocument xmlDocument) { + this.wiktionaryTagsData = wiktionaryTagsData; + this.xmlDocument = xmlDocument; + } + + /** + * Добавление новых лемм в xml файл + * + * @param forms добавляемые формы + */ + public void addLemmaForms(List> forms) { + for (int i = 0; i < forms.size(); i++) { + if (forms.get(i).get(0).contains(VERB)) { + continue; + } + if (forms.get(i).get(0).contains(INFN) && i > 0 && forms.get(i - 1).get(0).contains(VERB)) { + synchronized (xmlDocument) { + addLemma(forms.get(i - 1)); + addLemma(forms.get(i)); + Element linkElement = createNewElement(xmlDocument.getLinksElement(), xmlDocument.getLemmaId() - 1, xmlDocument.getLemmaId() - 2, 3); + } + } else { + synchronized (xmlDocument) { + addLemma(forms.get(i)); + } + } + if (forms.get(i).get(0).contains(ADJS) && i > 0 && forms.get(i - 1).get(0).contains(ADJF)) { + synchronized (xmlDocument) { + Element linkElement = createNewElement(xmlDocument.getLinksElement(), xmlDocument.getLemmaId() - 2, xmlDocument.getLemmaId() - 1, 1); + } + } else if (forms.get(i).get(0).contains(PRTS) && i > 0 && forms.get(i - 1).get(0).contains(PRTF)) { + synchronized (xmlDocument) { + Element linkElement = createNewElement(xmlDocument.getLinksElement(), xmlDocument.getLemmaId() - 2, xmlDocument.getLemmaId() - 1, 6); + } + } + } + } + + private Element createNewElement(Element parentElement, String tagName, String attributeName, String attributeValue) { + Element newElement = xmlDocument.getDoc().createElement(tagName); + parentElement.appendChild(newElement); + Attr attr = xmlDocument.getDoc().createAttribute(attributeName); + attr.setValue(attributeValue); + newElement.setAttributeNode(attr); + + return newElement; + } + + private Element createNewElement(Element parentElement, int fromIndex, int toIndex, int linkType) { + Element newElement = xmlDocument.getDoc().createElement("link"); + parentElement.appendChild(newElement); + Attr firstAttr = xmlDocument.getDoc().createAttribute("from"); + firstAttr.setValue(String.valueOf(fromIndex)); + newElement.setAttributeNode(firstAttr); + Attr secondAttr = xmlDocument.getDoc().createAttribute("to"); + secondAttr.setValue(String.valueOf(toIndex)); + newElement.setAttributeNode(secondAttr); + Attr thirdAttr = xmlDocument.getDoc().createAttribute("type"); + thirdAttr.setValue(String.valueOf(linkType)); + newElement.setAttributeNode(thirdAttr); + + return newElement; + } + + private void addLemma(List forms) { + try { + if (forms != null && forms.size() > 0) { + if (!wiktionaryTagsData.getToS().contains(forms.get(0).getTags().get(0))) { + throw new Exception("Неверный формат токена."); + } + + Element lemmaElement = createNewElement(xmlDocument.getLemmataElement(), "lemma", "id", String.valueOf(xmlDocument.getLemmaId())); + + Element lElement = createNewElement(lemmaElement, "l", "t", forms.get(0).getWord()); + + List overallTags; + if (forms.get(0).getTags().size() > 0) { + overallTags = new ArrayList<>(forms.get(0).getTags()); + } else { + throw new Exception("Пустой токен."); + } + + for (int i = 1; i < forms.size(); i++) { + List derivitiveTags = new ArrayList<>(forms.get(i).getTags()); + overallTags.retainAll(derivitiveTags); + } + + Element gElement; + for (String overallTag : overallTags) { + gElement = createNewElement(lElement, "g", "v", overallTag); + } + + for (WordFormForConverter form : forms) { + Element fElement = createNewElement(lemmaElement, "f", "t", form.toString().split(TAB_SEPARATOR)[0]); + List derivitiveTags = new ArrayList<>(form.getTags()); + derivitiveTags.removeAll(overallTags); + for (String derivitiveTag : derivitiveTags) { + gElement = createNewElement(fElement, "g", "v", derivitiveTag); + } + } + + xmlDocument.setLemmaId(xmlDocument.getLemmaId() + 1); + } + } catch (NullPointerException ex) { + log.error("Неверный формат токена.", ex); + } catch (Exception ex) { + log.error(ex.getMessage(), ex); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/textanalysis/tawt/ms/dictionary/wiktionary/WiktionaryInfoParser.java b/src/main/java/ru/textanalysis/tawt/ms/dictionary/wiktionary/WiktionaryInfoParser.java new file mode 100644 index 0000000..d54960a --- /dev/null +++ b/src/main/java/ru/textanalysis/tawt/ms/dictionary/wiktionary/WiktionaryInfoParser.java @@ -0,0 +1,271 @@ +package ru.textanalysis.tawt.ms.dictionary.wiktionary; + +import lombok.extern.slf4j.Slf4j; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import ru.textanalysis.tawt.ms.dictionary.convertor.WiktionaryTagsData; +import ru.textanalysis.tawt.ms.dictionary.convertor.WiktionaryToOpenCorporaConverter; +import ru.textanalysis.tawt.ms.dictionary.convertor.WordFormForConverter; + +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +import static java.lang.Thread.sleep; +import static ru.textanalysis.tawt.ms.constant.Const.COMMA_SEPARATOR; +import static ru.textanalysis.tawt.ms.constant.Const.TAB_SEPARATOR; + +/** + * Парсер информации о тегах слова из Wiktionary + */ +@Slf4j +public class WiktionaryInfoParser { + + private final WiktionaryToOpenCorporaConverter wiktionaryToOpenCorporaConverter; + private WiktionaryParsabilityChecker parsabilityChecker; + + /** + * Instantiates a new Wiktionary info parser. + * + * @param wiktionaryTagsData объект с информацией о преобразовании тегов + */ + public WiktionaryInfoParser(WiktionaryTagsData wiktionaryTagsData) { + this.wiktionaryToOpenCorporaConverter = new WiktionaryToOpenCorporaConverter(wiktionaryTagsData); + } + + /** + * Получение информации о слове и его тегах с Wiktionary + * + * @param word исследуемое слово + * @param sleepTime время между запросами + * @param requestTimeOut время ожидания подключения + * @param lemmas List в котором хранятся уже добавленные леммы, для предотвращения дублирования + * @return the words tags + */ + public List> getWordsTags(String word, int sleepTime, int requestTimeOut, List lemmas) { + List> lexems = new ArrayList<>(); + if (lemmas.contains(word.toLowerCase(Locale.ROOT))) { + return new ArrayList<>(); + } + if (word.isEmpty()) { + return new ArrayList<>(); + } + if (!word.matches("[а-яА-ЯёЁ-]+")) { + return new ArrayList<>(); + } + try { + sleep(sleepTime); + parsabilityChecker = new WiktionaryParsabilityChecker(word.toLowerCase(Locale.ROOT), requestTimeOut); + if (parsabilityChecker.getDoc() == null) { + sleep(sleepTime / 2); + boolean isSuccessful = parsabilityChecker.tryRepeatConnection(word.toLowerCase(Locale.ROOT), 2 * requestTimeOut); + if (!isSuccessful) { + throw new SocketTimeoutException("Неудачное повторное соединение."); + } + } + if (lemmas.contains(parsabilityChecker.getInitialForm().text().toLowerCase(Locale.ROOT))) { + return new ArrayList<>(); + } + if (!parsabilityChecker.checkParsability(lemmas)) { + return new ArrayList<>(); + } + + Element table = null; + boolean isFoundRussianBlock = false; + boolean isFoundWordTags = false; + StringBuilder tagsStr = new StringBuilder(); + Element mainElement = parsabilityChecker.getDoc().getElementsByClass("mw-parser-output").first(); + Elements mainElements = mainElement != null ? mainElement.getAllElements() : null; + for (int i = 0; i < Objects.requireNonNull(mainElements).size(); i++) { + if (mainElements.get(i).tagName().equals("h1")) { + if (isFoundRussianBlock) { + break; + } + if (Objects.requireNonNull(mainElements.get(i).getElementsByClass("mw-headline").first()).text().equals("Русский")) { + isFoundRussianBlock = true; + continue; + } + } + if (isFoundRussianBlock) { + if (mainElements.get(i).tagName().equals("h3")) { + if (Objects.requireNonNull(mainElements.get(i).getElementsByClass("mw-headline").first()).text().equals("Морфологические и синтаксические свойства")) { + isFoundWordTags = true; + continue; + } + } + if (isFoundWordTags) { + if (mainElements.get(i).tagName().equals("table")) { + table = mainElements.get(i); + } else if (mainElements.get(i).tagName().equals("p")) { + tagsStr.append(mainElements.get(i).text().toLowerCase(Locale.ROOT)); + tagsStr.append(COMMA_SEPARATOR); + } else if (mainElements.get(i).tagName().equals("h3") || mainElements.get(i).tagName().equals("h4")) { + List> info = parseInfo(table, tagsStr.toString(), word.toLowerCase(Locale.ROOT), lemmas); + isFoundWordTags = false; + table = null; + tagsStr = new StringBuilder(); + lexems.addAll(info); + } + } + } + } + + lexems.removeIf(lexem -> lemmas.contains(lexem.get(0).getWord())); + + if (!tryAdd(parsabilityChecker.getInitialForm().text().toLowerCase(Locale.ROOT), lemmas)) { + return new ArrayList<>(); + } + + return lexems; + } catch (SocketTimeoutException exc) { + String messages; + if (Objects.equals(exc.getMessage(), "Неудачное повторное соединение.")) { + messages = "https://ru.wiktionary.org/wiki/" + word.toLowerCase(Locale.ROOT) + ". Неудачное повторное соединение."; + } else { + messages = "https://ru.wiktionary.org/wiki/" + word.toLowerCase(Locale.ROOT) + ". Не удалось установить соединение."; + } + log.error(messages, exc); + return null; + } catch (NullPointerException ex) { + log.error("Не удалось разобрать страницу.", ex); + return new ArrayList<>(); + } catch (Exception ex) { + log.error(ex.getMessage(), ex); + return new ArrayList<>(); + } + } + + private List> parseInfo(Element table, String tagsStr, String word, List lemmas) { + List lemma = new ArrayList<>(); + List additionalLemma = new ArrayList<>(); + List> lexems = new ArrayList<>(); + if (table == null) { + if (tagsStr.contains("наречие") || tagsStr.contains("союз") || tagsStr.contains("предлог") + || tagsStr.contains("частица") || tagsStr.contains("междометие") || tagsStr.contains("неизменяем")) { + lemma.add(wiktionaryToOpenCorporaConverter.convertImmutableToWordForm(word.toLowerCase(Locale.ROOT) + TAB_SEPARATOR + tagsStr)); + } + } else { + Elements tags = table.getElementsByTag("tr"); + + if (tagsStr.contains("глагол")) { + additionalLemma.add(wiktionaryToOpenCorporaConverter.convertImmutableToWordForm(parsabilityChecker.getInitialForm().text().toLowerCase(Locale.ROOT) + TAB_SEPARATOR + tagsStr)); + } + List tokenTags = new ArrayList<>(); + for (int tag = 0; tag < tags.size(); tag++) { + Elements child = tags.get(tag).children(); + int counter = 0; + int columnNumber = 0; + StringBuilder rowTokenTags = new StringBuilder(); + boolean repeatingForm = false; + if (child.size() > tokenTags.size() || child.get(0).tagName().equals("th")) { + for (int j = 0; j < child.size(); j++) { + if (tag == 0 && j == 0 && child.get(j).tagName().equals("th")) { + continue; + } + int colSpan; + if (child.get(j).tagName().equals("th")) { + if (child.get(j).hasAttr("colspan")) { + colSpan = Integer.parseInt(child.get(j).attr("colspan")); + } else { + colSpan = 1; + } + if (tag == 0) { + for (int k = 0; k < colSpan; k++) { + tokenTags.add(child.get(j).text().toLowerCase(Locale.ROOT)); + } + } else { + for (int k = 0; k < colSpan; k++) { + tokenTags.set(counter, tokenTags.get(counter) + COMMA_SEPARATOR + child.get(counter).text().toLowerCase(Locale.ROOT)); + counter++; + } + } + } else if (child.get(j).tagName().equals("td")) { + if (!child.get(j).children().isEmpty() && !child.get(j).children().get(0).tagName().equals("br")) { + if (rowTokenTags.length() == 0) { + rowTokenTags = new StringBuilder(child.get(j).getElementsByTag("a").text().toLowerCase(Locale.ROOT)); + } else { + rowTokenTags.append(COMMA_SEPARATOR); + rowTokenTags.append(child.get(j).getElementsByTag("a").text().toLowerCase(Locale.ROOT)); + } + } else { + String[] tex = child.get(j).text().split(" "); + for (int p = 0; p < tex.length; p++) { + if (tex.length > 1 && changeSpecialCharacter(tex[0]).equals(changeSpecialCharacter(tex[1]))) { + repeatingForm = true; + } + if (!(p > 0 && repeatingForm)) { + tex[p] = changeSpecialCharacter(tex[p]); + if (!tex[p].equals("—")) { + String token = tex[p] + TAB_SEPARATOR + tagsStr + COMMA_SEPARATOR + tokenTags.get(columnNumber) + COMMA_SEPARATOR + rowTokenTags; + if (token.contains("он она оно") && tex.length > 1) { + if (p == 0) { + token += ",муж. р."; + } + if (p == 1) { + token += ",жен. р."; + } + if (p == 2) { + token += ",ср. р."; + } + } + if (token.contains(",м.")) { + continue; + } + if ((rowTokenTags.toString().contains("я") || rowTokenTags.toString().contains("ты") || rowTokenTags.toString().contains("вы") + || rowTokenTags.toString().contains("они")) && tokenTags.get(columnNumber).contains("прош.")) { + continue; + } else if ((rowTokenTags.toString().contains("он она оно") || rowTokenTags.toString().contains("мы")) + && tokenTags.get(columnNumber).contains("прош.")) { + token = token.replaceAll("он она оно", "").replaceAll(",мы", ""); + } + if (token.contains("кратк. форма")) { + additionalLemma.add(wiktionaryToOpenCorporaConverter.convertTableFormToWordForm(token.toLowerCase(Locale.ROOT))); + } else { + lemma.add(wiktionaryToOpenCorporaConverter.convertTableFormToWordForm(token.toLowerCase(Locale.ROOT))); + } + } + } + } + columnNumber++; + } + } + } + } + } + } + + if (lemma.size() > 0) { + lexems.add(lemma); + if (additionalLemma.size() > 0) { + lexems.add(additionalLemma); + } + } + + return lexems; + } + + private boolean tryAdd(String initialForm, List lemmas) { + synchronized (lemmas) { + if (!lemmas.contains(initialForm)) { + lemmas.add(initialForm); + return true; + } + return false; + } + } + + private String changeSpecialCharacter(String s) { + s = s.replaceAll("́", "").replaceAll("́ѐ", "е").replaceAll("́о̀", "о") + .replaceAll("а̀", "а").replaceAll("ѐ", "е") + .replaceAll("ѝ", "и").replaceAll("о̀", "о") + .replaceAll("у̀", "у").replaceAll("ё̀", "ё") + .replaceAll("э̀", "э").replaceAll(COMMA_SEPARATOR, "") + .replaceAll("ѝ", "и").replaceAll("я̀", "я") + .replaceAll("о̀", "о").replaceAll("о̀", "о") + .replaceAll("у̀", "у").replaceAll("я̀", "я") + .replaceAll("ы̀", "ы").replaceAll("ю̀", "ю"); + return s; + } +} \ No newline at end of file diff --git a/src/main/java/ru/textanalysis/tawt/ms/dictionary/wiktionary/WiktionaryParsabilityChecker.java b/src/main/java/ru/textanalysis/tawt/ms/dictionary/wiktionary/WiktionaryParsabilityChecker.java new file mode 100644 index 0000000..b6aa8e2 --- /dev/null +++ b/src/main/java/ru/textanalysis/tawt/ms/dictionary/wiktionary/WiktionaryParsabilityChecker.java @@ -0,0 +1,160 @@ +package ru.textanalysis.tawt.ms.dictionary.wiktionary; + +import lombok.extern.slf4j.Slf4j; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import static ru.textanalysis.tawt.ms.constant.Const.COMMA_SEPARATOR; + +/** + * Установка соединение с Wiktionary, + * получение html страницы с информацией о словах + */ +@Slf4j +public class WiktionaryParsabilityChecker { + + public final String WIKTIONARY_URL = "https://ru.wiktionary.org/wiki/"; + public final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0"; + public final String REFERRER = "https://www.yandex.ru"; + public final boolean IGNORE_HTTP_ERRORS = Boolean.TRUE; + public final boolean FOLLOW_REDIRECTS = Boolean.TRUE; + public final boolean IGNORE_CONTENT_TYPE = Boolean.TRUE; + public final String ELEMENTS_BY_TAG_H1 = "h1"; + + private Document doc; + private Element initialForm; + + /** + * получение html страницы для необходимого слова + * + * @param word слово, для которого получается страница + * @param requestTimeOut ождание время подключения в мс + */ + public WiktionaryParsabilityChecker(String word, int requestTimeOut) { + String url = WIKTIONARY_URL + word; + try { + this.doc = Jsoup.connect(url) + .ignoreHttpErrors(IGNORE_HTTP_ERRORS) + .timeout(requestTimeOut) + .userAgent(USER_AGENT) + .referrer(REFERRER) + .followRedirects(FOLLOW_REDIRECTS) + .ignoreContentType(IGNORE_CONTENT_TYPE) + .get(); + this.initialForm = doc.getElementsByTag(ELEMENTS_BY_TAG_H1).first(); + } catch (IOException ex) { + log.warn(url + " Не удалось установить соединение.", ex); + } + } + + /** + * Повтор подключения к странице Wiktionary + * если страница уже была получена - сразу возвращает true + * + * @param word слово, для которого получается страница + * @param requestTimeOut ожидание время подключения в мс + * @return true если попытка удалась, иначе false + */ + public boolean tryRepeatConnection(String word, int requestTimeOut) { + String url = WIKTIONARY_URL + word; + try { + if (this.doc == null) { + this.doc = Jsoup.connect(url) + .ignoreHttpErrors(IGNORE_HTTP_ERRORS) + .timeout(requestTimeOut) + .userAgent(USER_AGENT) + .referrer(REFERRER) + .followRedirects(FOLLOW_REDIRECTS) + .ignoreContentType(IGNORE_CONTENT_TYPE) + .get(); + this.initialForm = doc.getElementsByTag(ELEMENTS_BY_TAG_H1).first(); + } + return true; + } catch (IOException ex) { + log.warn(url + " Не удалось установить соединение.", ex); + return false; + } + } + + /** + * Проверка возможности получения информации о слове + * существует ли страница с данным словом, + * было ли оно уже добавлено в словарь, + * есть ли блок русского языка + * + * @param lemmas List в котором хранятся уже добавленные леммы, для предотвращения дублирования + * @return true если попытка удалась, иначе false + */ + public boolean checkParsability(List lemmas) { + if (initialForm != null && !initialForm.text().isEmpty()) { + Element notFound = doc.getElementsByClass("noarticletext mw-content-ltr").first(); + if (notFound != null) { + return false; + } + if (initialForm.text().toLowerCase(Locale.ROOT).contains("инкубатор")) { + return false; + } + List matchingElements = checkExistingForms(initialForm.text().toLowerCase(Locale.ROOT), lemmas); + if (!matchingElements.isEmpty()) { + return false; + } + } else { + return false; + } + Element content = doc.select("div.mw-parser-output").first(); + AtomicBoolean isParsable = new AtomicBoolean(Boolean.FALSE); + if (content != null) { + Elements headLines = content.getElementsByTag(ELEMENTS_BY_TAG_H1); + headLines.forEach(headLine -> { + Elements head = headLine.select("span.mw-headline"); + head.forEach(line -> { + if (line.text().equals("Русский")) { + isParsable.set(true); + } + }); + }); + } + if (!isParsable.get()) { + return false; + } + + StringBuilder tags = new StringBuilder(); + Elements wordProperties = Objects.requireNonNull(doc.getElementsByClass("mw-parser-output").first()).getElementsByTag("p"); + if (!wordProperties.isEmpty()) { + tags.append(wordProperties.get(0).text().toLowerCase(Locale.ROOT)).append(COMMA_SEPARATOR); + if (wordProperties.size() > 1) { + tags.append(wordProperties.get(1).text().toLowerCase(Locale.ROOT)).append(COMMA_SEPARATOR); + } + if (wordProperties.size() > 2) { + tags.append(wordProperties.get(2).text().toLowerCase(Locale.ROOT)).append(COMMA_SEPARATOR); + } + } + + return !tags.toString().contains("викисловарь:к удалению"); + } + + public Document getDoc() { + return doc; + } + + public Element getInitialForm() { + return initialForm; + } + + private List checkExistingForms(String initialForm, List lemmas) { + synchronized (lemmas) { + return lemmas.stream() + .filter(str -> str.trim().equals(initialForm)) + .collect(Collectors.toList()); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/textanalysis/tawt/ms/loader/DatabaseLemmas.java b/src/main/java/ru/textanalysis/tawt/ms/loader/DatabaseLemmas.java index 6da5318..82027f8 100644 --- a/src/main/java/ru/textanalysis/tawt/ms/loader/DatabaseLemmas.java +++ b/src/main/java/ru/textanalysis/tawt/ms/loader/DatabaseLemmas.java @@ -47,7 +47,7 @@ public void recreate(List> lemmas) { public void decompressDd() { boolean needDecompress; if (file.exists()) { - //todo проверка, что версия старая + //todo проверка, что версия старая. Проверка еще не реализована needDecompress = false; } else { needDecompress = true; diff --git a/src/main/java/ru/textanalysis/tawt/ms/loader/DatabaseStrings.java b/src/main/java/ru/textanalysis/tawt/ms/loader/DatabaseStrings.java index 253ee18..660030d 100644 --- a/src/main/java/ru/textanalysis/tawt/ms/loader/DatabaseStrings.java +++ b/src/main/java/ru/textanalysis/tawt/ms/loader/DatabaseStrings.java @@ -75,12 +75,8 @@ private String getLiteralById(int idKey, boolean isInitialForm) { public void decompressDd(File file) { boolean needDecompress; - if (file.exists()) { - //todo проверка, что версия старая - needDecompress = false; - } else { - needDecompress = true; - } + //todo проверка, что версия старая + needDecompress = !file.exists(); if (needDecompress) { System.out.println("Decompress DB. Please wait a few minutes"); File dir = file.getParentFile(); @@ -110,7 +106,7 @@ public void recreate(List> forms) { .filter(form -> !form.isFirstKey()) .forEach(form -> { int id = form.getKey(); - String name = form.getStringName(); + String name = form.getStringName().replace("'", ""); String query = String.format(CONTINUED_INSERT, id, name); if (form.isInitialForm()) { insert(multipleInsertInitial, query, dbInitialFormString); diff --git a/src/main/java/ru/textanalysis/tawt/ms/model/Property.java b/src/main/java/ru/textanalysis/tawt/ms/model/Property.java index 0b2f259..86cc567 100644 --- a/src/main/java/ru/textanalysis/tawt/ms/model/Property.java +++ b/src/main/java/ru/textanalysis/tawt/ms/model/Property.java @@ -53,6 +53,7 @@ public final class Property { public final static String NAME_BD_DERIVATIVE_FORM = "dictionary.derivativeFormString.db"; public final static String NAME_BD_INITIAL_FORM = "dictionary.initialFormString.db"; public final static String NAME_HASH_AND_MORF_CHARACTERISTICS = "dictionary.format.morfCharacteristic"; + public final static String OMO_FORM_STAT = "dictionary.format.omoFormStat"; public final static int CONTROL_VALUE = -1; public final static int KEY_OFFSET = 8; public final static int START_ID_INITIAL_FORM = 1 << KEY_OFFSET; diff --git a/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/DerivativeForm.java b/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/DerivativeForm.java index 6eb5e41..a92442e 100644 --- a/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/DerivativeForm.java +++ b/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/DerivativeForm.java @@ -7,8 +7,8 @@ public final class DerivativeForm extends Form { private InitialForm initialForm; - public DerivativeForm(int formKey, long morfCharacteristics) { - super(formKey, morfCharacteristics); + public DerivativeForm(int formKey, long morfCharacteristics, long link) { + super(formKey, morfCharacteristics, link); } @Override diff --git a/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/Form.java b/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/Form.java index daecc7f..edaaa1c 100644 --- a/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/Form.java +++ b/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/Form.java @@ -4,6 +4,7 @@ import ru.textanalysis.tawt.ms.loader.DatabaseFactory; import ru.textanalysis.tawt.ms.loader.DatabaseStrings; +import static ru.textanalysis.tawt.ms.constant.Const.COMMA_SEPARATOR; import static ru.textanalysis.tawt.ms.loader.LoadHelper.getControlHashCode; import static ru.textanalysis.tawt.ms.loader.LoadHelper.getControlValue; @@ -16,11 +17,13 @@ public abstract class Form { protected static int formCount = 0; protected final long morphCharacteristics; + protected final long link; protected final int formKeyInBD; protected final int order; - protected Form(int formKey, long morphCharacteristics) { + protected Form(int formKey, long morphCharacteristics, long link) { this.morphCharacteristics = morphCharacteristics; + this.link = link; this.formKeyInBD = formKey; formCount++; order = formCount; @@ -30,6 +33,10 @@ public int getMyFormKey() { return formKeyInBD; } + public long getLink() { + return link; + } + public String getMyString() { return DATABASE_FORM_STRINGS.getLiteralById(getMyFormKey()); } @@ -97,11 +104,11 @@ public int hashCode() { @Override public String toString() { return "{" + - "TF=" + getTypeForm() + "," + - "isInit=" + isInitialForm() + "," + - "hash=" + hashCode() + "," + + "TF=" + getTypeForm() + COMMA_SEPARATOR + + "isInit=" + isInitialForm() + COMMA_SEPARATOR + + "hash=" + hashCode() + COMMA_SEPARATOR + "str='" + getMyString() + "'," + - "ToS=" + getTypeOfSpeech() + "," + + "ToS=" + getTypeOfSpeech() + COMMA_SEPARATOR + "morf=" + morphCharacteristics + "}"; } diff --git a/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/InitialForm.java b/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/InitialForm.java index 5032cae..c50054f 100644 --- a/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/InitialForm.java +++ b/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/InitialForm.java @@ -14,8 +14,8 @@ public class InitialForm extends Form { private final List derivativeForms; @Builder - protected InitialForm(int formKey, byte typeOfSpeech, long morfCharacteristics, List derivativeForms) { - super(formKey, morfCharacteristics); + protected InitialForm(int formKey, byte typeOfSpeech, long morfCharacteristics, long link, List derivativeForms) { + super(formKey, morfCharacteristics, link); this.typeOfSpeech = typeOfSpeech; this.derivativeForms = Collections.unmodifiableList(derivativeForms); derivativeForms.forEach(form -> form.setInitialForm(this)); diff --git a/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/NumberForm.java b/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/NumberForm.java index 2dd1628..dc9a541 100644 --- a/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/NumberForm.java +++ b/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/NumberForm.java @@ -12,7 +12,7 @@ public class NumberForm extends InitialForm { private final String textNumber; public NumberForm(String textNumber) { - super(textNumber.hashCode(), TypeOfSpeech.NUMERAL, 0, List.of()); + super(textNumber.hashCode(), TypeOfSpeech.NUMERAL, 0, 0, List.of()); this.textNumber = textNumber; } diff --git a/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/UnfamiliarForm.java b/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/UnfamiliarForm.java index 30d45e1..3caadf2 100644 --- a/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/UnfamiliarForm.java +++ b/src/main/java/ru/textanalysis/tawt/ms/model/jmorfsdk/UnfamiliarForm.java @@ -12,7 +12,7 @@ public final class UnfamiliarForm extends InitialForm { private final String literal; public UnfamiliarForm(String literal) { - super(literal.hashCode(), MorfologyParameters.TypeOfSpeech.UNFAMILIAR, 0, List.of()); + super(literal.hashCode(), MorfologyParameters.TypeOfSpeech.UNFAMILIAR, 0, 0, List.of()); this.literal = literal; }