From 4c837cd72d6fcca6140431fbfb058b3431438af2 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Sat, 8 Jul 2023 16:49:03 -0500 Subject: [PATCH 01/43] Add export support for the MDict dictionary format --- .gitignore | 1 + README.md | 64 ++- TODO.md | 4 +- bot/crawlers/crawlers.py | 19 +- bot/data.py | 8 +- bot/entries/daijirin2.py | 81 ++-- bot/entries/entry.py | 36 +- bot/entries/factory.py | 2 +- bot/entries/jitenon.py | 188 ++++---- bot/entries/smk8.py | 84 ++-- bot/entries/smk8_preprocess.py | 18 + bot/mdict/exporters/export.py | 204 ++++++++ bot/mdict/exporters/factory.py | 18 + bot/mdict/glossary/daijirin2.py | 77 +++ bot/mdict/glossary/jitenon.py | 141 ++++++ bot/mdict/glossary/smk8.py | 67 +++ bot/mdict/terms/daijirin2.py | 23 + bot/mdict/terms/factory.py | 18 + bot/mdict/terms/jitenon.py | 42 ++ bot/mdict/terms/smk8.py | 24 + bot/mdict/terms/terminator.py | 73 +++ .../glossary => }/name_conversion.py | 15 +- bot/yomichan/exporters/export.py | 43 +- bot/yomichan/exporters/factory.py | 2 +- bot/yomichan/glossary/daijirin2.py | 6 +- bot/yomichan/glossary/jitenon.py | 6 +- bot/yomichan/glossary/smk8.py | 6 +- bot/yomichan/terms/jitenon.py | 3 +- bot/yomichan/terms/terminator.py | 31 +- data/daijirin2/mdict_name_conversion.json | 12 + data/mdict/css/daijirin2.css | 414 ++++++++++++++++ data/mdict/css/jitenon-kokugo.css | 56 +++ data/mdict/css/jitenon-kotowaza.css | 40 ++ data/mdict/css/jitenon-yoji.css | 40 ++ data/mdict/css/smk8.css | 449 ++++++++++++++++++ .../daijirin2.mdx.description.html | 7 + .../jitenon-kokugo.mdx.description.html | 7 + .../jitenon-kotowaza.mdx.description.html | 7 + .../jitenon-yoji.mdx.description.html | 7 + .../description/smk8.mdx.description.html | 7 + data/mdict/icon/jitenon-kokugo.png | Bin 0 -> 2374 bytes data/mdict/icon/jitenon-kotowaza.png | Bin 0 -> 5473 bytes data/mdict/icon/jitenon-yoji.png | Bin 0 -> 2628 bytes data/mdict/title/daijirin2.mdx.title.html | 1 + .../mdict/title/jitenon-kokugo.mdx.title.html | 1 + .../title/jitenon-kotowaza.mdx.title.html | 1 + data/mdict/title/jitenon-yoji.mdx.title.html | 1 + data/mdict/title/smk8.mdx.title.html | 1 + data/smk8/mdict_name_conversion.json | 25 + data/smk8/yomichan_name_conversion.json | 40 +- jitenbot.py | 60 ++- requirements.txt | 3 + run_all.sh | 13 + 53 files changed, 2227 insertions(+), 269 deletions(-) create mode 100644 bot/mdict/exporters/export.py create mode 100644 bot/mdict/exporters/factory.py create mode 100644 bot/mdict/glossary/daijirin2.py create mode 100644 bot/mdict/glossary/jitenon.py create mode 100644 bot/mdict/glossary/smk8.py create mode 100644 bot/mdict/terms/daijirin2.py create mode 100644 bot/mdict/terms/factory.py create mode 100644 bot/mdict/terms/jitenon.py create mode 100644 bot/mdict/terms/smk8.py create mode 100644 bot/mdict/terms/terminator.py rename bot/{yomichan/glossary => }/name_conversion.py (88%) create mode 100644 data/daijirin2/mdict_name_conversion.json create mode 100644 data/mdict/css/daijirin2.css create mode 100644 data/mdict/css/jitenon-kokugo.css create mode 100644 data/mdict/css/jitenon-kotowaza.css create mode 100644 data/mdict/css/jitenon-yoji.css create mode 100644 data/mdict/css/smk8.css create mode 100644 data/mdict/description/daijirin2.mdx.description.html create mode 100644 data/mdict/description/jitenon-kokugo.mdx.description.html create mode 100644 data/mdict/description/jitenon-kotowaza.mdx.description.html create mode 100644 data/mdict/description/jitenon-yoji.mdx.description.html create mode 100644 data/mdict/description/smk8.mdx.description.html create mode 100644 data/mdict/icon/jitenon-kokugo.png create mode 100644 data/mdict/icon/jitenon-kotowaza.png create mode 100644 data/mdict/icon/jitenon-yoji.png create mode 100644 data/mdict/title/daijirin2.mdx.title.html create mode 100644 data/mdict/title/jitenon-kokugo.mdx.title.html create mode 100644 data/mdict/title/jitenon-kotowaza.mdx.title.html create mode 100644 data/mdict/title/jitenon-yoji.mdx.title.html create mode 100644 data/mdict/title/smk8.mdx.title.html create mode 100644 data/smk8/mdict_name_conversion.json create mode 100644 run_all.sh diff --git a/.gitignore b/.gitignore index b009cb5..4c7985d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ webcache/ output/ notes/ +monokakido/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/README.md b/README.md index 88d0f2b..5a872ea 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,8 @@ compiling the scraped data into compact dictionary file formats. # Usage ``` -usage: jitenbot [-h] [-p PAGE_DIR] [-i IMAGE_DIR] +usage: jitenbot [-h] [-p PAGE_DIR] [-m MEDIA_DIR] [-i MDICT_ICON] + [--no-yomichan-export] [--no-mdict-export] {jitenon-kokugo,jitenon-yoji,jitenon-kotowaza,smk8,daijirin2} Convert Japanese dictionary files to new formats. @@ -62,9 +63,15 @@ options: -h, --help show this help message and exit -p PAGE_DIR, --page-dir PAGE_DIR path to directory containing XML page files - -i IMAGE_DIR, --image-dir IMAGE_DIR - path to directory containing image folders (gaiji, - graphics, etc.) + -m MEDIA_DIR, --media-dir MEDIA_DIR + path to directory containing media folders (gaiji, + graphics, audio, etc.) + -i MDICT_ICON, --mdict-icon MDICT_ICON + path to icon file to be used with MDict + --no-yomichan-export skip export of dictionary data to Yomichan format + --no-mdict-export skip export of dictionary data to MDict format + +See README.md for details regarding media directory structures ``` ### Online Targets Jitenbot will scrape the target website and save the pages to the [user cache directory](https://pypi.org/project/platformdirs/). @@ -75,8 +82,55 @@ HTTP request headers (user agent string, etc.) may be customized by editing the [user config directory](https://pypi.org/project/platformdirs/). ### Offline Targets -Page data and image data must be procured by the user +Page data and media data must be [procured by the user](https://github.com/golddranks/monokakido/) and passed to jitenbot via the appropriate command line flags. +
+ smk8 media directory + +Since Yomichan does not support audio files from imported +dictionaries, the `audio/` directory may be omitted to save filesize +space in the output ZIP file if desired. + +``` +media +├── Audio.png +├── audio +│   ├── 00001.aac +│   ├── 00002.aac +│   ├── 00003.aac +│   │  ... +│   └── 82682.aac +└── gaiji + ├── 1d110.svg + ├── 1d15d.svg + ├── 1d15e.svg +    │  ... + └── xbunnoa.svg +``` +
+ +
+ daijirin2 media directory + +The `graphics/` directory may be omitted to save space if desired. + +``` +media +├── gaiji +│   ├── 1D10B.svg +│   ├── 1D110.svg +│   ├── 1D12A.svg +│   │  ... +│   └── vectorOB.svg +└── graphics + ├── 3djr_0002.png + ├── 3djr_0004.png + ├── 3djr_0005.png +    │  ... + └── 4djr_yahazu.png +``` +
+ # Attribution `Adobe-Japan1_sequences.txt` is provided by [The Adobe-Japan1-7 Character Collection](https://github.com/adobe-type-tools/Adobe-Japan1). diff --git a/TODO.md b/TODO.md index 30c860d..2f2a5d5 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,11 @@ ### Todo +- [x] Add factory classes to reduce the amount of class import statements +- [x] Support exporting to MDict (.MDX) dictionary format - [ ] Add test suite - [ ] Add documentation (docstrings, etc.) - [ ] Validate JSON schema of Yomichan terms during export -- [ ] Add factory classes to reduce the amount of class import statements - [ ] Add build scripts for producing program binaries -- [ ] Support exporting to MDict (.MDX) dictionary format - [ ] Validate scraped webpages after downloading - [ ] Log non-fatal failures to a log file instead of raising exceptions - [ ] Support more dictionary websites diff --git a/bot/crawlers/crawlers.py b/bot/crawlers/crawlers.py index c7bf8ea..97b3794 100644 --- a/bot/crawlers/crawlers.py +++ b/bot/crawlers/crawlers.py @@ -5,7 +5,8 @@ from bs4 import BeautifulSoup import bot.scraper as Scraper from bot.entries.factory import new_entry -from bot.yomichan.exporters.factory import new_exporter +from bot.yomichan.exporters.factory import new_yomi_exporter +from bot.mdict.exporters.factory import new_mdict_exporter class Crawler(ABC): @@ -38,9 +39,13 @@ class Crawler(ABC): self._entries.append(entry) print() - def make_yomichan_dictionary(self, image_dir): - exporter = new_exporter(self._target) - exporter.export(self._entries, image_dir) + def make_yomichan_dictionary(self, media_dir): + exporter = new_yomi_exporter(self._target) + exporter.export(self._entries, media_dir) + + def make_mdict_dictionary(self, media_dir, icon_file): + exporter = new_mdict_exporter(self._target) + exporter.export(self._entries, media_dir, icon_file) def _parse_page_id(self, page_link): m = re.search(self._page_id_pattern, page_link) @@ -142,10 +147,8 @@ class _MonokakidoCrawler(Crawler): class Smk8Crawler(_MonokakidoCrawler): - def __init__(self, target): - super().__init__(target) + pass class Daijirin2Crawler(_MonokakidoCrawler): - def __init__(self, target): - super().__init__(target) + pass diff --git a/bot/data.py b/bot/data.py index 5d68769..3b1effd 100644 --- a/bot/data.py +++ b/bot/data.py @@ -99,15 +99,15 @@ def load_daijirin2_kana_abbreviations(): @cache -def load_smk8_yomichan_name_conversion(): - file_name = os.path.join("smk8", "yomichan_name_conversion.json") +def load_yomichan_name_conversion(target): + file_name = os.path.join(target.value, "yomichan_name_conversion.json") data = __load_json(file_name) return data @cache -def load_daijirin2_yomichan_name_conversion(): - file_name = os.path.join("daijirin2", "yomichan_name_conversion.json") +def load_mdict_name_conversion(target): + file_name = os.path.join(target.value, "mdict_name_conversion.json") data = __load_json(file_name) return data diff --git a/bot/entries/daijirin2.py b/bot/entries/daijirin2.py index 1463442..196bd0c 100644 --- a/bot/entries/daijirin2.py +++ b/bot/entries/daijirin2.py @@ -1,4 +1,3 @@ -import re from bs4 import BeautifulSoup import bot.entries.expressions as Expressions @@ -10,19 +9,17 @@ from bot.entries.daijirin2_preprocess import preprocess_page class _BaseDaijirin2Entry(Entry): - ID_TO_ENTRY = {} - SUBENTRY_ID_TO_ENTRY_ID = {} - - def __init__(self, entry_id): - super().__init__(entry_id) - if entry_id not in self.ID_TO_ENTRY: - self.ID_TO_ENTRY[entry_id] = self - else: - raise Exception(f"Duplicate entry ID: {entry_id}") + def __init__(self, target, entry_id): + super().__init__(target, entry_id) self.children = [] self.phrases = [] self._kana_abbreviations = load_daijirin2_kana_abbreviations() + def get_global_identifier(self): + parent_part = format(self.entry_id[0], '06') + child_part = hex(self.entry_id[1]).lstrip('0x').zfill(4).upper() + return f"@{self.target.value}-{parent_part}-{child_part}" + def set_page(self, page): page = self.__decompose_subentries(page) self._page = page @@ -57,14 +54,7 @@ class _BaseDaijirin2Entry(Entry): else: self._part_of_speech_tags.append(pos) - def get_headwords(self): - if self._headwords is not None: - return self._headwords - self._set_headwords() - self._set_variant_headwords() - return self._headwords - - def _set_regular_headwords(self, soup): + def _get_regular_headwords(self, soup): self._fill_alts(soup) reading = soup.find("見出仮名").text expressions = [] @@ -78,10 +68,11 @@ class _BaseDaijirin2Entry(Entry): expressions = Expressions.expand_abbreviation_list(expressions) if len(expressions) == 0: expressions.append(reading) - self._headwords = {reading: expressions} + headwords = {reading: expressions} + return headwords - def _set_variant_headwords(self): - for expressions in self._headwords.values(): + def _add_variant_expressions(self, headwords): + for expressions in headwords.values(): Expressions.add_variant_kanji(expressions) Expressions.add_fullwidth(expressions) Expressions.remove_iteration_mark(expressions) @@ -101,7 +92,7 @@ class _BaseDaijirin2Entry(Entry): tag_soup.name = "項目" subentry_id = self.id_string_to_entry_id(tag_soup.attrs["id"]) self.SUBENTRY_ID_TO_ENTRY_ID[subentry_id] = self.entry_id - subentry = subentry_class(subentry_id) + subentry = subentry_class(self.target, subentry_id) page = tag_soup.decode() subentry.set_page(page) subentry_list.append(subentry) @@ -122,6 +113,8 @@ class _BaseDaijirin2Entry(Entry): @staticmethod def _delete_unused_nodes(soup): + """Remove extra markup elements that appear in the entry + headword line which are not part of the entry headword""" unused_nodes = [ "漢字音logo", "活用分節", "連語句活用分節", "語構成", "表外字マーク", "表外字マーク", "ルビG" @@ -144,25 +137,26 @@ class _BaseDaijirin2Entry(Entry): class Daijirin2Entry(_BaseDaijirin2Entry): - def __init__(self, page_id): + def __init__(self, target, page_id): entry_id = (page_id, 0) - super().__init__(entry_id) + super().__init__(target, entry_id) def set_page(self, page): page = preprocess_page(page) super().set_page(page) - def _set_headwords(self): + def _get_headwords(self): soup = self.get_page_soup() self._delete_unused_nodes(soup) if soup.find("漢字見出") is not None: - self._set_kanji_headwords(soup) + headwords = self._get_kanji_headwords(soup) elif soup.find("略語G") is not None: - self._set_acronym_headwords(soup) + headwords = self._get_acronym_headwords(soup) else: - self._set_regular_headwords(soup) + headwords = self._get_regular_headwords(soup) + return headwords - def _set_kanji_headwords(self, soup): + def _get_kanji_headwords(self, soup): readings = [] for el in soup.find_all("漢字音"): hira = Expressions.kata_to_hira(el.text) @@ -172,11 +166,12 @@ class Daijirin2Entry(_BaseDaijirin2Entry): expressions = [] for el in soup.find_all("漢字見出"): expressions.append(el.text) - self._headwords = {} + headwords = {} for reading in readings: - self._headwords[reading] = expressions + headwords[reading] = expressions + return headwords - def _set_acronym_headwords(self, soup): + def _get_acronym_headwords(self, soup): expressions = [] for el in soup.find_all("略語"): expression_parts = [] @@ -184,29 +179,24 @@ class Daijirin2Entry(_BaseDaijirin2Entry): expression_parts.append(part.text) expression = "".join(expression_parts) expressions.append(expression) - self._headwords = {"": expressions} + headwords = {"": expressions} + return headwords class Daijirin2ChildEntry(_BaseDaijirin2Entry): - def __init__(self, entry_id): - super().__init__(entry_id) - - def _set_headwords(self): + def _get_headwords(self): soup = self.get_page_soup() self._delete_unused_nodes(soup) - self._set_regular_headwords(soup) + headwords = self._get_regular_headwords(soup) + return headwords class Daijirin2PhraseEntry(_BaseDaijirin2Entry): - def __init__(self, entry_id): - super().__init__(entry_id) - self.__phrase_readings = load_daijirin2_phrase_readings() - def get_part_of_speech_tags(self): # phrases do not contain these tags return [] - def _set_headwords(self): + def _get_headwords(self): soup = self.get_page_soup() headwords = {} expressions = self._find_expressions(soup) @@ -217,7 +207,7 @@ class Daijirin2PhraseEntry(_BaseDaijirin2Entry): headwords[reading].append(expression) else: headwords[reading] = [expression] - self._headwords = headwords + return headwords def _find_expressions(self, soup): self._delete_unused_nodes(soup) @@ -231,7 +221,8 @@ class Daijirin2PhraseEntry(_BaseDaijirin2Entry): return expressions def _find_readings(self): - text = self.__phrase_readings[self.entry_id] + phrase_readings = load_daijirin2_phrase_readings() + text = phrase_readings[self.entry_id] alternatives = Expressions.expand_daijirin_alternatives(text) readings = [] for alt in alternatives: diff --git a/bot/entries/entry.py b/bot/entries/entry.py index 57316f6..3811a77 100644 --- a/bot/entries/entry.py +++ b/bot/entries/entry.py @@ -2,12 +2,24 @@ from abc import ABC, abstractmethod class Entry(ABC): - def __init__(self, entry_id): + ID_TO_ENTRY = {} + SUBENTRY_ID_TO_ENTRY_ID = {} + + def __init__(self, target, entry_id): + if entry_id not in self.ID_TO_ENTRY: + self.ID_TO_ENTRY[entry_id] = self + else: + raise Exception(f"Duplicate entry ID: {entry_id}") + self.target = target self.entry_id = entry_id self._page = None self._headwords = None self._part_of_speech_tags = None + @abstractmethod + def get_global_identifier(self): + pass + @abstractmethod def set_page(self, page): pass @@ -16,14 +28,34 @@ class Entry(ABC): def get_page_soup(self): pass - @abstractmethod def get_headwords(self): + if self._headwords is not None: + return self._headwords + headwords = self._get_headwords() + self._add_variant_expressions(headwords) + self._headwords = headwords + return headwords + + @abstractmethod + def _get_headwords(self): + pass + + @abstractmethod + def _add_variant_expressions(self, headwords): pass @abstractmethod def get_part_of_speech_tags(self): pass + def get_parent(self): + if self.entry_id in self.SUBENTRY_ID_TO_ENTRY_ID: + parent_id = self.SUBENTRY_ID_TO_ENTRY_ID[self.entry_id] + parent = self.ID_TO_ENTRY[parent_id] + else: + parent = None + return parent + def get_first_expression(self): headwords = self.get_headwords() expressions = next(iter(headwords.values())) diff --git a/bot/entries/factory.py b/bot/entries/factory.py index 23ca066..a3dec69 100644 --- a/bot/entries/factory.py +++ b/bot/entries/factory.py @@ -15,4 +15,4 @@ def new_entry(target, page_id): Targets.SMK8: Smk8Entry, Targets.DAIJIRIN2: Daijirin2Entry, } - return entry_map[target](page_id) + return entry_map[target](target, page_id) diff --git a/bot/entries/jitenon.py b/bot/entries/jitenon.py index fd9fcd2..65c4d2e 100644 --- a/bot/entries/jitenon.py +++ b/bot/entries/jitenon.py @@ -1,4 +1,5 @@ import re +from abc import abstractmethod from datetime import datetime, date from bs4 import BeautifulSoup @@ -7,18 +8,17 @@ import bot.entries.expressions as Expressions class _JitenonEntry(Entry): - ID_TO_ENTRY = {} - - def __init__(self, entry_id): - super().__init__(entry_id) - if entry_id not in self.ID_TO_ENTRY: - self.ID_TO_ENTRY[entry_id] = self - else: - raise Exception(f"Duplicate entry ID: {entry_id}") + def __init__(self, target, entry_id): + super().__init__(target, entry_id) + self.expression = "" + self.yomikata = "" + self.definition = "" + self.other_forms = [] self.modified_date = date(1970, 1, 1) self.attribution = "" - for column in self._COLUMNS.values(): - setattr(self, column[0], column[1]) + + def get_global_identifier(self): + return f"@{self.target.value}-{format(self.entry_id, '06')}" def set_page(self, page): soup = BeautifulSoup(page, features="html5lib") @@ -39,36 +39,33 @@ class _JitenonEntry(Entry): soup = BeautifulSoup(self._page, "html5lib") return soup - def get_headwords(self): - if self._headwords is not None: - return self._headwords - self._set_headwords() - self._set_variant_headwords() - return self._headwords - def get_part_of_speech_tags(self): # Jitenon doesn't have any return [] - def _set_headwords(self): + def _get_headwords(self): headwords = {} - for yomikata in self._yomikatas(): - headwords[yomikata] = [self.expression] - ikei_headwords = self._ikei_headwords() - for reading, expressions in ikei_headwords.items(): + for reading in self._get_readings(): + headwords[reading] = [self.expression] + other_form_headwords = self._other_form_headwords() + for reading, expressions in other_form_headwords.items(): if reading not in headwords: headwords[reading] = [] for expression in expressions: if expression not in headwords[reading]: headwords[reading].append(expression) - self._headwords = headwords + return headwords + + @abstractmethod + def _get_column_map(self): + pass def __set_modified_date(self, page): m = re.search(r"\"dateModified\": \"(\d{4}-\d{2}-\d{2})", page) if m is None: return - date = datetime.strptime(m.group(1), '%Y-%m-%d').date() - self.modified_date = date + modified_date = datetime.strptime(m.group(1), '%Y-%m-%d').date() + self.modified_date = modified_date def __set_attribution(self, soup): attribution = soup.find(class_="copyright") @@ -78,7 +75,8 @@ class _JitenonEntry(Entry): self.attribution = "" def __set_column(self, colname, colval): - attr_name = self._COLUMNS[colname][0] + column_map = self._get_column_map() + attr_name = column_map[colname] attr_value = getattr(self, attr_name) if isinstance(attr_value, str): setattr(self, attr_name, colval) @@ -88,7 +86,7 @@ class _JitenonEntry(Entry): else: attr_value.append(colval) - def _yomikatas(self): + def _get_readings(self): yomikata = self.yomikata m = re.search(r"^[ぁ-ヿ、]+$", yomikata) if m: @@ -109,20 +107,20 @@ class _JitenonEntry(Entry): print(f"Invalid 読み方 format: {self.yomikata}\n{self}\n") return [""] - def _ikei_headwords(self): - ikei_headwords = {} - for val in self.ikei: + def _other_form_headwords(self): + other_form_headwords = {} + for val in self.other_forms: m = re.search(r"^([^(]+)(([ぁ-ヿ、]+))$", val) if not m: print(f"Invalid 異形 format: {val}\n{self}\n") continue expression = m.group(1) reading = m.group(2) - if reading not in ikei_headwords: - ikei_headwords[reading] = [] - if expression not in ikei_headwords[reading]: - ikei_headwords[reading].append(expression) - return ikei_headwords + if reading not in other_form_headwords: + other_form_headwords[reading] = [] + if expression not in other_form_headwords[reading]: + other_form_headwords[reading].append(expression) + return other_form_headwords @staticmethod def __clean_text(text): @@ -133,9 +131,10 @@ class _JitenonEntry(Entry): return text def __str__(self): + column_map = self._get_column_map() colvals = [str(self.entry_id)] - for attr in self._COLUMNS.values(): - attr_val = getattr(self, attr[0]) + for attr_name in column_map.values(): + attr_val = getattr(self, attr_name) if isinstance(attr_val, str): colvals.append(attr_val) elif isinstance(attr_val, list): @@ -144,83 +143,100 @@ class _JitenonEntry(Entry): class JitenonYojiEntry(_JitenonEntry): - _COLUMNS = { - "四字熟語": ["expression", ""], - "読み方": ["yomikata", ""], - "意味": ["imi", ""], - "出典": ["shutten", ""], - "漢検級": ["kankenkyuu", ""], - "場面用途": ["bamenyouto", ""], - "異形": ["ikei", []], - "類義語": ["ruigigo", []], - } + def __init__(self, target, entry_id): + super().__init__(target, entry_id) + self.origin = "" + self.kanken_level = "" + self.category = "" + self.related_expressions = [] - def __init__(self, entry_id): - super().__init__(entry_id) + def _get_column_map(self): + return { + "四字熟語": "expression", + "読み方": "yomikata", + "意味": "definition", + "異形": "other_forms", + "出典": "origin", + "漢検級": "kanken_level", + "場面用途": "category", + "類義語": "related_expressions", + } - def _set_variant_headwords(self): - for expressions in self._headwords.values(): + def _add_variant_expressions(self, headwords): + for expressions in headwords.values(): Expressions.add_variant_kanji(expressions) class JitenonKotowazaEntry(_JitenonEntry): - _COLUMNS = { - "言葉": ["expression", ""], - "読み方": ["yomikata", ""], - "意味": ["imi", ""], - "出典": ["shutten", ""], - "例文": ["reibun", ""], - "異形": ["ikei", []], - "類句": ["ruiku", []], - } + def __init__(self, target, entry_id): + super().__init__(target, entry_id) + self.origin = "" + self.example = "" + self.related_expressions = [] - def __init__(self, entry_id): - super().__init__(entry_id) + def _get_column_map(self): + return { + "言葉": "expression", + "読み方": "yomikata", + "意味": "definition", + "異形": "other_forms", + "出典": "origin", + "例文": "example", + "類句": "related_expressions", + } - def _set_headwords(self): + def _get_headwords(self): if self.expression == "金棒引き・鉄棒引き": - self._headwords = { + headwords = { "かなぼうひき": ["金棒引き", "鉄棒引き"] } else: - super()._set_headwords() + headwords = super()._get_headwords() + return headwords - def _set_variant_headwords(self): - for expressions in self._headwords.values(): + def _add_variant_expressions(self, headwords): + for expressions in headwords.values(): Expressions.add_variant_kanji(expressions) Expressions.add_fullwidth(expressions) class JitenonKokugoEntry(_JitenonEntry): - _COLUMNS = { - "言葉": ["expression", ""], - "読み方": ["yomikata", ""], - "意味": ["imi", ""], - "例文": ["reibun", ""], - "別表記": ["betsuhyouki", ""], - "対義語": ["taigigo", ""], - "活用": ["katsuyou", ""], - "用例": ["yourei", ""], - "類語": ["ruigo", ""], - } + def __init__(self, target, entry_id): + super().__init__(target, entry_id) + self.example = "" + self.alt_expression = "" + self.antonym = "" + self.attachments = "" + self.compounds = "" + self.related_words = "" - def __init__(self, entry_id): - super().__init__(entry_id) + def _get_column_map(self): + return { + "言葉": "expression", + "読み方": "yomikata", + "意味": "definition", + "例文": "example", + "別表記": "alt_expression", + "対義語": "antonym", + "活用": "attachments", + "用例": "compounds", + "類語": "related_words", + } - def _set_headwords(self): + def _get_headwords(self): headwords = {} for reading in self.yomikata.split("・"): if reading not in headwords: headwords[reading] = [] for expression in self.expression.split("・"): headwords[reading].append(expression) - if self.betsuhyouki.strip() != "": - for expression in self.betsuhyouki.split("・"): + if self.alt_expression.strip() != "": + for expression in self.alt_expression.split("・"): headwords[reading].append(expression) - self._headwords = headwords + return headwords - def _set_variant_headwords(self): - for expressions in self._headwords.values(): + def _add_variant_expressions(self, headwords): + for expressions in headwords.values(): Expressions.add_variant_kanji(expressions) Expressions.add_fullwidth(expressions) Expressions.remove_iteration_mark(expressions) diff --git a/bot/entries/smk8.py b/bot/entries/smk8.py index 11ef7e6..2308893 100644 --- a/bot/entries/smk8.py +++ b/bot/entries/smk8.py @@ -1,4 +1,3 @@ -import re from bs4 import BeautifulSoup import bot.entries.expressions as Expressions @@ -9,19 +8,17 @@ from bot.entries.smk8_preprocess import preprocess_page class _BaseSmk8Entry(Entry): - ID_TO_ENTRY = {} - SUBENTRY_ID_TO_ENTRY_ID = {} - - def __init__(self, entry_id): - super().__init__(entry_id) - if entry_id not in self.ID_TO_ENTRY: - self.ID_TO_ENTRY[entry_id] = self - else: - raise Exception(f"Duplicate entry ID: {entry_id}") + def __init__(self, target, entry_id): + super().__init__(target, entry_id) self.children = [] self.phrases = [] self.kanjis = [] + def get_global_identifier(self): + parent_part = format(self.entry_id[0], '06') + child_part = hex(self.entry_id[1]).lstrip('0x').zfill(4).upper() + return f"@{self.target.value}-{parent_part}-{child_part}" + def set_page(self, page): page = self.__decompose_subentries(page) self._page = page @@ -30,13 +27,6 @@ class _BaseSmk8Entry(Entry): soup = BeautifulSoup(self._page, "xml") return soup - def get_headwords(self): - if self._headwords is not None: - return self._headwords - self._set_headwords() - self._set_variant_headwords() - return self._headwords - def get_part_of_speech_tags(self): if self._part_of_speech_tags is not None: return self._part_of_speech_tags @@ -50,8 +40,8 @@ class _BaseSmk8Entry(Entry): self._part_of_speech_tags.append(tag.text) return self._part_of_speech_tags - def _set_variant_headwords(self): - for expressions in self._headwords.values(): + def _add_variant_expressions(self, headwords): + for expressions in headwords.values(): Expressions.add_variant_kanji(expressions) Expressions.add_fullwidth(expressions) Expressions.remove_iteration_mark(expressions) @@ -87,7 +77,7 @@ class _BaseSmk8Entry(Entry): tag_soup.name = "項目" subentry_id = self.id_string_to_entry_id(tag_soup.attrs["id"]) self.SUBENTRY_ID_TO_ENTRY_ID[subentry_id] = self.entry_id - subentry = subentry_class(subentry_id) + subentry = subentry_class(self.target, subentry_id) page = tag_soup.decode() subentry.set_page(page) subentry_list.append(subentry) @@ -106,6 +96,16 @@ class _BaseSmk8Entry(Entry): else: raise Exception(f"Invalid entry ID: {id_string}") + @staticmethod + def _delete_unused_nodes(soup): + """Remove extra markup elements that appear in the entry + headword line which are not part of the entry headword""" + unused_nodes = [ + "表音表記", "表外音訓マーク", "表外字マーク", "ルビG" + ] + for name in unused_nodes: + Soup.delete_soup_nodes(soup, name) + @staticmethod def _clean_expression(expression): for x in ["〈", "〉", "{", "}", "…", " "]: @@ -114,24 +114,24 @@ class _BaseSmk8Entry(Entry): @staticmethod def _fill_alts(soup): - for e in soup.find_all(["親見出仮名", "親見出表記"]): - e.string = e.attrs["alt"] + for el in soup.find_all(["親見出仮名", "親見出表記"]): + el.string = el.attrs["alt"] for gaiji in soup.find_all("外字"): gaiji.string = gaiji.img.attrs["alt"] class Smk8Entry(_BaseSmk8Entry): - def __init__(self, page_id): + def __init__(self, target, page_id): entry_id = (page_id, 0) - super().__init__(entry_id) + super().__init__(target, entry_id) def set_page(self, page): page = preprocess_page(page) super().set_page(page) - def _set_headwords(self): + def _get_headwords(self): soup = self.get_page_soup() - Soup.delete_soup_nodes(soup, "表音表記") + self._delete_unused_nodes(soup) self._fill_alts(soup) reading = self._find_reading(soup) expressions = [] @@ -140,16 +140,14 @@ class Smk8Entry(_BaseSmk8Entry): for expression in self._find_expressions(soup): if expression not in expressions: expressions.append(expression) - self._headwords = {reading: expressions} + headwords = {reading: expressions} + return headwords class Smk8ChildEntry(_BaseSmk8Entry): - def __init__(self, entry_id): - super().__init__(entry_id) - - def _set_headwords(self): + def _get_headwords(self): soup = self.get_page_soup() - Soup.delete_soup_nodes(soup, "表音表記") + self._delete_unused_nodes(soup) self._fill_alts(soup) reading = self._find_reading(soup) expressions = [] @@ -158,19 +156,20 @@ class Smk8ChildEntry(_BaseSmk8Entry): for expression in self._find_expressions(soup): if expression not in expressions: expressions.append(expression) - self._headwords = {reading: expressions} + headwords = {reading: expressions} + return headwords class Smk8PhraseEntry(_BaseSmk8Entry): - def __init__(self, entry_id): - super().__init__(entry_id) + def __init__(self, target, entry_id): + super().__init__(target, entry_id) self.__phrase_readings = load_smk8_phrase_readings() def get_part_of_speech_tags(self): # phrases do not contain these tags return [] - def _set_headwords(self): + def _get_headwords(self): soup = self.get_page_soup() headwords = {} expressions = self._find_expressions(soup) @@ -181,10 +180,10 @@ class Smk8PhraseEntry(_BaseSmk8Entry): headwords[reading].append(expression) else: headwords[reading] = [expression] - self._headwords = headwords + return headwords def _find_expressions(self, soup): - Soup.delete_soup_nodes(soup, "ルビG") + self._delete_unused_nodes(soup) self._fill_alts(soup) text = soup.find("標準表記").text text = self._clean_expression(text) @@ -206,15 +205,14 @@ class Smk8PhraseEntry(_BaseSmk8Entry): class Smk8KanjiEntry(_BaseSmk8Entry): - def __init__(self, entry_id): - super().__init__(entry_id) - - def _set_headwords(self): + def _get_headwords(self): soup = self.get_page_soup() + self._delete_unused_nodes(soup) self._fill_alts(soup) reading = self.__get_parent_reading() expressions = self._find_expressions(soup) - self._headwords = {reading: expressions} + headwords = {reading: expressions} + return headwords def __get_parent_reading(self): parent_id = self.SUBENTRY_ID_TO_ENTRY_ID[self.entry_id] diff --git a/bot/entries/smk8_preprocess.py b/bot/entries/smk8_preprocess.py index 2e480a8..5c9b924 100644 --- a/bot/entries/smk8_preprocess.py +++ b/bot/entries/smk8_preprocess.py @@ -15,6 +15,7 @@ def preprocess_page(page): page = __strip_page(page) page = __replace_glyph_codes(page) page = __format_hyougai_marks(page) + page = __remove_pronunciation_parentheses(page) return page @@ -64,6 +65,7 @@ def __format_hyougai_marks(page): for x in ["\n", "\t", " "]: text = text.replace(x, "") text = re.sub(r"〈([^〈]+)〉", r"\1", text) + page = re.sub(r"〈([^〈]+)〉", r"␂\1␃", page) for mark in re.findall(r"《.", text): if mark[1] == "〓": @@ -79,13 +81,29 @@ def __format_hyougai_marks(page): page = re.sub(f"〈([^{mark[1]}]*)({mark[1]})", r"\1<表外字>\2", page, count=1) + page = page.replace("␂", "〈") page = page.replace("␃", "〉") soup = BeautifulSoup(page, features="xml") + for el in soup.find_all("表外音訓"): if el.text == "": el.append(el.next_sibling) + mark_xml = "<表外音訓マーク>︽" + mark_soup = BeautifulSoup(mark_xml, "xml") + el.append(mark_soup.表外音訓マーク) + for el in soup.find_all("表外字"): if el.text == "": el.append(el.next_sibling) + mark_xml = "<表外字マーク>︿" + mark_soup = BeautifulSoup(mark_xml, "xml") + el.append(mark_soup.表外字マーク) + return soup.decode() + + +def __remove_pronunciation_parentheses(page): + page = page.replace("<表音表記>(", "<表音表記>") + page = page.replace(")", "") + return page diff --git a/bot/mdict/exporters/export.py b/bot/mdict/exporters/export.py new file mode 100644 index 0000000..2d76f1d --- /dev/null +++ b/bot/mdict/exporters/export.py @@ -0,0 +1,204 @@ +# pylint: disable=too-few-public-methods + +import subprocess +import os +import shutil +from abc import ABC, abstractmethod +from pathlib import Path +from datetime import datetime +from platformdirs import user_documents_dir, user_cache_dir + +from bot.targets import Targets +from bot.mdict.terms.factory import new_terminator + + +class Exporter(ABC): + def __init__(self, target): + self._target = target + self._terminator = new_terminator(target) + self._build_dir = None + self._build_media_dir = None + self._description_file = None + self._out_dir = None + + def export(self, entries, media_dir, icon_file): + self._init_build_media_dir(media_dir) + self._init_description_file(entries) + terms = self._get_terms(entries) + print(f"Exporting {len(terms)} Mdict keys...") + self._write_mdx_file(terms) + self._write_mdd_file() + self._write_icon_file(icon_file) + self._rm_build_dir() + + def _get_build_dir(self): + if self._build_dir is not None: + return self._build_dir + cache_dir = user_cache_dir("jitenbot") + build_directory = os.path.join(cache_dir, "mdict_build") + if Path(build_directory).is_dir(): + shutil.rmtree(build_directory) + os.makedirs(build_directory) + self._build_dir = build_directory + return self._build_dir + + def _init_build_media_dir(self, media_dir): + build_dir = self._get_build_dir() + build_media_dir = os.path.join(build_dir, self._target.value) + if media_dir is not None: + print("Copying media files to build directory...") + shutil.copytree(media_dir, build_media_dir) + else: + os.makedirs(build_media_dir) + css_file = self._get_css_file() + shutil.copy(css_file, build_media_dir) + self._terminator.set_media_dir(build_media_dir) + self._build_media_dir = build_media_dir + + def _init_description_file(self, entries): + filename = f"{self._target.value}.mdx.description.html" + original_file = os.path.join( + "data", "mdict", "description", filename) + with open(original_file, "r", encoding="utf8") as f: + description = f.read() + description = description.replace( + "{{revision}}", self._get_revision(entries)) + description = description.replace( + "{{attribution}}", self._get_attribution(entries)) + build_dir = self._get_build_dir() + description_file = os.path.join(build_dir, filename) + with open(description_file, "w", encoding="utf8") as f: + f.write(description) + self._description_file = description_file + + def _get_terms(self, entries): + terms = [] + entries_len = len(entries) + for idx, entry in enumerate(entries): + update = f"Creating Mdict terms for entry {idx+1}/{entries_len}" + print(update, end='\r', flush=True) + new_terms = self._terminator.make_terms(entry) + for term in new_terms: + terms.append(term) + print() + return terms + + def _write_mdx_file(self, terms): + out_dir = self._get_out_dir() + out_file = os.path.join(out_dir, f"{self._target.value}.mdx") + params = [ + "mdict", + "-a", self._get_term_file(terms), + "--title", self._get_title_file(), + "--description", self._description_file, + out_file + ] + subprocess.run(params, check=True) + + def _write_mdd_file(self): + out_dir = self._get_out_dir() + out_file = os.path.join(out_dir, f"{self._target.value}.mdd") + params = [ + "mdict", + "-a", self._build_media_dir, + "--title", self._get_title_file(), + "--description", self._description_file, + out_file + ] + subprocess.run(params, check=True) + + def _write_icon_file(self, icon_file): + premade_icon_file = f"data/mdict/icon/{self._target.value}.png" + out_dir = self._get_out_dir() + out_file = os.path.join(out_dir, f"{self._target.value}.png") + if icon_file is not None and Path(icon_file).is_file(): + shutil.copy(icon_file, out_file) + elif Path(premade_icon_file).is_file(): + shutil.copy(premade_icon_file, out_file) + + def _get_out_dir(self): + if self._out_dir is not None: + return self._out_dir + out_dir = os.path.join( + user_documents_dir(), "jitenbot", "mdict", self._target.value) + if Path(out_dir).is_dir(): + shutil.rmtree(out_dir) + os.makedirs(out_dir) + self._out_dir = out_dir + return out_dir + + def _get_term_file(self, terms): + build_dir = self._get_build_dir() + term_file = os.path.join(build_dir, f"{self._target.value}.mdx.txt") + with open(term_file, "w", encoding="utf8") as f: + for term in terms: + f.write("\n".join(term)) + f.write("\n\n") + return term_file + + def _get_title_file(self): + return os.path.join( + "data", "mdict", "title", + f"{self._target.value}.mdx.title.html") + + def _get_css_file(self): + return os.path.join( + "data", "mdict", "css", + f"{self._target.value}.css") + + def _rm_build_dir(self): + build_dir = self._get_build_dir() + shutil.rmtree(build_dir) + + @abstractmethod + def _get_revision(self, entries): + pass + + @abstractmethod + def _get_attribution(self, entries): + pass + + +class _JitenonExporter(Exporter): + def _get_revision(self, entries): + modified_date = None + for entry in entries: + if modified_date is None or entry.modified_date > modified_date: + modified_date = entry.modified_date + revision = modified_date.strftime("%Y年%m月%d日閲覧") + return revision + + def _get_attribution(self, entries): + modified_date = None + for entry in entries: + if modified_date is None or entry.modified_date > modified_date: + attribution = entry.attribution + return attribution + + +class JitenonKokugoExporter(_JitenonExporter): + pass + + +class JitenonYojiExporter(_JitenonExporter): + pass + + +class JitenonKotowazaExporter(_JitenonExporter): + pass + + +class _MonokakidoExporter(Exporter): + def _get_revision(self, entries): + timestamp = datetime.now().strftime("%Y年%m月%d日作成") + return timestamp + + +class Smk8Exporter(_MonokakidoExporter): + def _get_attribution(self, entries): + return "© Sanseido Co., LTD. 2020" + + +class Daijirin2Exporter(_MonokakidoExporter): + def _get_attribution(self, entries): + return "© Sanseido Co., LTD. 2019" diff --git a/bot/mdict/exporters/factory.py b/bot/mdict/exporters/factory.py new file mode 100644 index 0000000..2c2015c --- /dev/null +++ b/bot/mdict/exporters/factory.py @@ -0,0 +1,18 @@ +from bot.targets import Targets + +from bot.mdict.exporters.export import JitenonKokugoExporter +from bot.mdict.exporters.export import JitenonYojiExporter +from bot.mdict.exporters.export import JitenonKotowazaExporter +from bot.mdict.exporters.export import Smk8Exporter +from bot.mdict.exporters.export import Daijirin2Exporter + + +def new_mdict_exporter(target): + exporter_map = { + Targets.JITENON_KOKUGO: JitenonKokugoExporter, + Targets.JITENON_YOJI: JitenonYojiExporter, + Targets.JITENON_KOTOWAZA: JitenonKotowazaExporter, + Targets.SMK8: Smk8Exporter, + Targets.DAIJIRIN2: Daijirin2Exporter, + } + return exporter_map[target](target) diff --git a/bot/mdict/glossary/daijirin2.py b/bot/mdict/glossary/daijirin2.py new file mode 100644 index 0000000..1a8b0d5 --- /dev/null +++ b/bot/mdict/glossary/daijirin2.py @@ -0,0 +1,77 @@ +import re +import os +from functools import cache +from pathlib import Path + +from bot.soup import delete_soup_nodes +from bot.data import load_mdict_name_conversion +from bot.name_conversion import convert_names + + +def make_glossary(entry, media_dir): + soup = entry.get_page_soup() + __add_rubies(soup) + __hyperlink_parent_expression(soup, entry) + __delete_unused_nodes(soup, media_dir) + __convert_links(soup, entry) + + name_conversion = load_mdict_name_conversion(entry.target) + convert_names(soup, name_conversion) + + glossary = soup.span.decode() + return glossary + + +def __add_rubies(soup): + for name in ["表外音訓", "表外字"]: + for ruby in soup.find_all(name): + ruby.name = "ruby" + rt = ruby.find("表外字マーク") + rt.name = "rt" + ruby.append(rt) # needs to positioned after the text + + +def __hyperlink_parent_expression(soup, entry): + if soup.find("親表記") is None: + return + parent_entry = entry.get_parent() + gid = parent_entry.get_global_identifier() + for el in soup.find_all("親表記"): + el.name = "a" + el.attrs["href"] = f"entry://{gid}" + + +def __delete_unused_nodes(soup, media_dir): + if not __graphics_directory_exists(media_dir): + delete_soup_nodes(soup, "カットG") + for el in soup.find_all("logo"): + next_sibling = el.next_sibling + if next_sibling is None: + continue + elif next_sibling.name in ["漢字見出G", "漢字音G"]: + el.decompose() + for el in soup.find_all("漢字音G"): + for child in el.find_all(string="・"): + child.replace_with("") + + +@cache +def __graphics_directory_exists(media_dir): + path = os.path.join(media_dir, "graphics") + return Path(path).is_dir() + + +def __convert_links(soup, entry): + for el in soup.find_all("a"): + href = el.attrs["href"] + if re.match(r"^[0-9]+(?:-[0-9A-F]{4})?$", href): + ref_entry_id = entry.id_string_to_entry_id(href) + ref_entry = entry.ID_TO_ENTRY[ref_entry_id] + gid = ref_entry.get_global_identifier() + el.attrs["href"] = f"entry://{gid}" + elif re.match(r"^entry:", href): + pass + elif re.match(r"^https?:[\w\W]*", href): + pass + else: + raise Exception(f"Invalid href format: {href}") diff --git a/bot/mdict/glossary/jitenon.py b/bot/mdict/glossary/jitenon.py new file mode 100644 index 0000000..737ea59 --- /dev/null +++ b/bot/mdict/glossary/jitenon.py @@ -0,0 +1,141 @@ +# pylint: disable=too-few-public-methods + +import re + + +class JitenonGlossary(): + def __init__(self): + self._id_pattern = None + self._expression_header = None + + def _replace_punctuation(self, soup): + punctuation = { + "/": "/", + ",": "、", + } + for el in soup.find_all(string=True): + text = el.text + for old, new in punctuation.items(): + text = text.replace(old, new) + el.replace_with(text) + + def _add_internal_links(self, soup, entry): + for el in soup.find_all("a"): + href = el.attrs["href"] + m = re.search(self._id_pattern, href) + if m is not None: + ref_entry_id = int(m.group(1)) + ref_entry = entry.ID_TO_ENTRY[ref_entry_id] + gid = ref_entry.get_global_identifier() + el.attrs["href"] = f"entry://{gid}" + elif re.match(r"^(?:https?:|\?)[\w\W]*", href): + pass + else: + raise Exception(f"Invalid href format: {href}") + + def _decompose_table_rows(self, soup, entry): + for tr in soup.find_all("tr"): + if tr.find("th") is None: + continue + elif tr.th.text == self._expression_header: + tr.decompose() + elif tr.th.text == "読み方": + if self._do_display_yomikata_in_headword(entry): + tr.decompose() + elif tr.th.text == "意味": + definition = tr.td + definition.name = "div" + definition.attrs["class"] = "意味" + soup.body.insert(0, definition) + tr.decompose() + if soup.find("tr") is None: + soup.table.decompose() + + def _insert_headword_line(self, soup, entry): + headword_line = soup.new_tag("div") + headword_line.attrs["class"] = "見出し" + if self._do_display_yomikata_in_headword(entry): + reading = soup.new_tag("span") + reading.attrs["class"] = "読み方" + reading.string = entry.yomikata + headword_line.append(reading) + expression = soup.new_tag("span") + expression.attrs["class"] = self._expression_header + expression.string = f"【{entry.expression}】" + headword_line.append(expression) + soup.body.insert(0, headword_line) + + def _do_display_yomikata_in_headword(self, entry): + if not re.match(r"^[ぁ-ヿ、]+$", entry.yomikata): + return False + elif len(entry.yomikata) > 10: + return False + else: + return True + + +class JitenonKokugoGlossary(JitenonGlossary): + def __init__(self): + super().__init__() + self._expression_header = "言葉" + self._id_pattern = r"kokugo.jitenon.jp/word/p([0-9]+)$" + + def make_glossary(self, entry, media_dir): + soup = entry.get_page_soup() + self._remove_antonym_list_item(soup) + self._replace_number_icons(soup, media_dir) + self._replace_punctuation(soup) + self._add_internal_links(soup, entry) + self._decompose_table_rows(soup, entry) + self._insert_headword_line(soup, entry) + glossary = soup.body.prettify() + return glossary + + def _remove_antonym_list_item(self, soup): + for el in soup.find_all("li"): + if el.text == "対義語辞典": + el.decompose() + + def _replace_number_icons(self, soup, media_dir): + for el in soup.find_all("img"): + alt = el.attrs["alt"] + text = re.search(r"[0-9]+", alt).group(0) + el.name = "span" + el.string = text + del el.attrs["src"] + del el.attrs["alt"] + + def _do_display_yomikata_in_headword(self, entry): + return len(entry.yomikata) <= 10 + + +class JitenonYojiGlossary(JitenonGlossary): + def __init__(self): + super().__init__() + self._expression_header = "四字熟語" + self._id_pattern = r"yoji.jitenon.jp/yoji.?/([0-9]+)\.html$" + + def make_glossary(self, entry, media_dir): + soup = entry.get_page_soup() + self._replace_punctuation(soup) + self._add_internal_links(soup, entry) + self._decompose_table_rows(soup, entry) + self._insert_headword_line(soup, entry) + glossary = soup.body.prettify() + return glossary + + +class JitenonKotowazaGlossary(JitenonGlossary): + def __init__(self): + super().__init__() + self._expression_header = "言葉" + self._id_pattern = r"kotowaza.jitenon.jp/kotowaza/([0-9]+)\.php$" + + def make_glossary(self, entry, media_dir): + soup = entry.get_page_soup() + self._replace_punctuation(soup) + self._add_internal_links(soup, entry) + self._decompose_table_rows(soup, entry) + self._insert_headword_line(soup, entry) + glossary = soup.body.prettify() + return glossary diff --git a/bot/mdict/glossary/smk8.py b/bot/mdict/glossary/smk8.py new file mode 100644 index 0000000..613fc1b --- /dev/null +++ b/bot/mdict/glossary/smk8.py @@ -0,0 +1,67 @@ +import re + +from bot.soup import delete_soup_nodes +from bot.data import load_mdict_name_conversion +from bot.name_conversion import convert_names + + +def make_glossary(entry, media_dir): + soup = entry.get_page_soup() + __fill_alts(soup, entry) + __delete_unused_nodes(soup) + __convert_links(soup, entry) + __convert_priority_markers(soup) + + name_conversion = load_mdict_name_conversion(entry.target) + convert_names(soup, name_conversion) + + glossary = soup.span.decode() + return glossary + + +def __fill_alts(soup, entry): + names = ["親見出仮名", "親見出表記"] + if soup.find(names) is None: + return + parent_entry = entry.get_parent() + gid = parent_entry.get_global_identifier() + for el in soup.find_all(names): + el.name = "a" + alt = el.attrs["alt"] + el.string = alt + el.attrs["href"] = f"entry://{gid}" + del el.attrs["alt"] + + +def __delete_unused_nodes(soup): + for name in ["連濁"]: + delete_soup_nodes(soup, name) + + +def __convert_links(soup, entry): + for el in soup.find_all("a"): + href = el.attrs["href"] + if href.startswith("$"): + el.unwrap() + elif re.match(r"^[0-9]+(?:-[0-9A-F]{4})?$", href): + ref_entry_id = entry.id_string_to_entry_id(href) + ref_entry = entry.ID_TO_ENTRY[ref_entry_id] + gid = ref_entry.get_global_identifier() + el.attrs["href"] = f"entry://{gid}" + elif re.match(r"^[0-9]+[ab]?\.aac$", href): + el.attrs["href"] = f"sound://audio/{href}" + elif re.match(r"^entry:", href): + pass + elif re.match(r"^https?:[\w\W]*", href): + pass + else: + raise Exception(f"Invalid href format: {href}") + + +def __convert_priority_markers(soup): + for el in soup.find_all("img", attrs={"alt": "*"}): + el.name = "span" + el.string = "*" + for el in soup.find_all("img", attrs={"alt": "⁑"}): + el.name = "span" + el.string = "**" diff --git a/bot/mdict/terms/daijirin2.py b/bot/mdict/terms/daijirin2.py new file mode 100644 index 0000000..3b5ce68 --- /dev/null +++ b/bot/mdict/terms/daijirin2.py @@ -0,0 +1,23 @@ +from bot.mdict.terms.terminator import Terminator +from bot.mdict.glossary.daijirin2 import make_glossary + + +class Daijirin2Terminator(Terminator): + def _glossary(self, entry): + if entry.entry_id in self._glossary_cache: + return self._glossary_cache[entry.entry_id] + glossary = make_glossary(entry, self._media_dir) + self._glossary_cache[entry.entry_id] = glossary + return glossary + + def _link_glossary_parameters(self, entry): + return [ + [entry.children, "子項目"], + [entry.phrases, "句項目"], + ] + + def _subentry_lists(self, entry): + return [ + entry.children, + entry.phrases, + ] diff --git a/bot/mdict/terms/factory.py b/bot/mdict/terms/factory.py new file mode 100644 index 0000000..78a05cd --- /dev/null +++ b/bot/mdict/terms/factory.py @@ -0,0 +1,18 @@ +from bot.targets import Targets + +from bot.mdict.terms.jitenon import JitenonKokugoTerminator +from bot.mdict.terms.jitenon import JitenonYojiTerminator +from bot.mdict.terms.jitenon import JitenonKotowazaTerminator +from bot.mdict.terms.smk8 import Smk8Terminator +from bot.mdict.terms.daijirin2 import Daijirin2Terminator + + +def new_terminator(target): + terminator_map = { + Targets.JITENON_KOKUGO: JitenonKokugoTerminator, + Targets.JITENON_YOJI: JitenonYojiTerminator, + Targets.JITENON_KOTOWAZA: JitenonKotowazaTerminator, + Targets.SMK8: Smk8Terminator, + Targets.DAIJIRIN2: Daijirin2Terminator, + } + return terminator_map[target](target) diff --git a/bot/mdict/terms/jitenon.py b/bot/mdict/terms/jitenon.py new file mode 100644 index 0000000..3f9cfc1 --- /dev/null +++ b/bot/mdict/terms/jitenon.py @@ -0,0 +1,42 @@ +from bot.mdict.terms.terminator import Terminator + +from bot.mdict.glossary.jitenon import JitenonKokugoGlossary +from bot.mdict.glossary.jitenon import JitenonYojiGlossary +from bot.mdict.glossary.jitenon import JitenonKotowazaGlossary + + +class JitenonTerminator(Terminator): + def __init__(self, target): + super().__init__(target) + self._glossary_maker = None + + def _glossary(self, entry): + if entry.entry_id in self._glossary_cache: + return self._glossary_cache[entry.entry_id] + glossary = self._glossary_maker.make_glossary(entry, self._media_dir) + self._glossary_cache[entry.entry_id] = glossary + return glossary + + def _link_glossary_parameters(self, entry): + return [] + + def _subentry_lists(self, entry): + return [] + + +class JitenonKokugoTerminator(JitenonTerminator): + def __init__(self, target): + super().__init__(target) + self._glossary_maker = JitenonKokugoGlossary() + + +class JitenonYojiTerminator(JitenonTerminator): + def __init__(self, target): + super().__init__(target) + self._glossary_maker = JitenonYojiGlossary() + + +class JitenonKotowazaTerminator(JitenonTerminator): + def __init__(self, target): + super().__init__(target) + self._glossary_maker = JitenonKotowazaGlossary() diff --git a/bot/mdict/terms/smk8.py b/bot/mdict/terms/smk8.py new file mode 100644 index 0000000..22275d5 --- /dev/null +++ b/bot/mdict/terms/smk8.py @@ -0,0 +1,24 @@ +from bot.mdict.terms.terminator import Terminator +from bot.mdict.glossary.smk8 import make_glossary + + +class Smk8Terminator(Terminator): + def _glossary(self, entry): + if entry.entry_id in self._glossary_cache: + return self._glossary_cache[entry.entry_id] + glossary = make_glossary(entry, self._media_dir) + self._glossary_cache[entry.entry_id] = glossary + return glossary + + def _link_glossary_parameters(self, entry): + return [ + [entry.children, "子項目"], + [entry.phrases, "句項目"], + ] + + def _subentry_lists(self, entry): + return [ + entry.children, + entry.phrases, + entry.kanjis, + ] diff --git a/bot/mdict/terms/terminator.py b/bot/mdict/terms/terminator.py new file mode 100644 index 0000000..e69d9fb --- /dev/null +++ b/bot/mdict/terms/terminator.py @@ -0,0 +1,73 @@ +from abc import abstractmethod, ABC + + +class Terminator(ABC): + def __init__(self, target): + self._target = target + self._glossary_cache = {} + self._media_dir = None + + def set_media_dir(self, media_dir): + self._media_dir = media_dir + + def make_terms(self, entry): + gid = entry.get_global_identifier() + glossary = self.__full_glossary(entry) + terms = [[gid, glossary]] + keys = set() + headwords = entry.get_headwords() + for reading, expressions in headwords.items(): + if len(expressions) == 0: + keys.add(reading) + for expression in expressions: + if expression.strip() == "": + keys.add(reading) + continue + keys.add(expression) + if reading.strip() == "": + continue + if reading != expression: + keys.add(f"{reading}【{expression}】") + else: + keys.add(reading) + link = f"@@@LINK={gid}" + for key in keys: + if key.strip() != "": + terms.append([key, link]) + for subentries in self._subentry_lists(entry): + for subentry in subentries: + for term in self.make_terms(subentry): + terms.append(term) + return terms + + def __full_glossary(self, entry): + glossary = [] + style_link = f"" + glossary.append(style_link) + glossary.append(self._glossary(entry)) + + for x in self._link_glossary_parameters(entry): + (subentries, list_title) = x + if len(subentries) == 0: + continue + items = [] + for subentry in subentries: + exp = subentry.get_first_expression() + gid = subentry.get_global_identifier() + item = f"
  • {exp}
  • " + items.append(item) + link_glossary = f"
    {list_title}
    " + glossary.append(link_glossary) + return "\n".join(glossary) + + @abstractmethod + def _glossary(self, entry): + pass + + @abstractmethod + def _link_glossary_parameters(self, entry): + pass + + @abstractmethod + def _subentry_lists(self, entry): + pass diff --git a/bot/yomichan/glossary/name_conversion.py b/bot/name_conversion.py similarity index 88% rename from bot/yomichan/glossary/name_conversion.py rename to bot/name_conversion.py index 776d65e..2c9b808 100644 --- a/bot/yomichan/glossary/name_conversion.py +++ b/bot/name_conversion.py @@ -30,7 +30,7 @@ def __apply_name_conversion_procedures(soup, procedures): "has_previous_sibling": __has_previous_sibling, "replace": __replace, "wrap": __wrap, - "add_ruby_text": __add_ruby_text, + "insert_span": __insert_span, } for procedure in procedures: function = functions[procedure["procedure_name"]] @@ -92,10 +92,9 @@ def __wrap(soup, l_wrap, r_wrap): soup.string = f"{l_wrap}{soup.text}{r_wrap}" -def __add_ruby_text(soup, mark, style): - if style.strip() != "": - markup = f"{mark}" - else: - markup = f"{mark}" - rt_soup = BeautifulSoup(markup, "xml") - soup.append(rt_soup.rt) +def __insert_span(soup, attr_name, attr_val): + span_markup = f"" + span_soup = BeautifulSoup(span_markup, "xml") + for content in reversed(soup.contents): + span_soup.span.insert(0, content.extract()) + soup.append(span_soup.span) diff --git a/bot/yomichan/exporters/export.py b/bot/yomichan/exporters/export.py index 4658030..03e1b95 100644 --- a/bot/yomichan/exporters/export.py +++ b/bot/yomichan/exporters/export.py @@ -1,15 +1,18 @@ +# pylint: disable=too-few-public-methods + import json import os import shutil from pathlib import Path from datetime import datetime +from abc import ABC, abstractmethod from platformdirs import user_documents_dir, user_cache_dir from bot.data import load_yomichan_metadata from bot.yomichan.terms.factory import new_terminator -class Exporter: +class Exporter(ABC): def __init__(self, target): self._target = target self._terminator = new_terminator(target) @@ -26,6 +29,14 @@ class Exporter: terms = self.__get_terms(entries) self.__make_dictionary(terms, index, tags) + @abstractmethod + def _get_revision(self, entries): + pass + + @abstractmethod + def _get_attribution(self, entries): + pass + def _get_build_dir(self): if self._build_dir is not None: return self._build_dir @@ -41,7 +52,7 @@ class Exporter: build_dir = self._get_build_dir() build_img_dir = os.path.join(build_dir, self._target.value) if image_dir is not None: - print("Copying image files to build directory...") + print("Copying media files to build directory...") shutil.copytree(image_dir, build_img_dir) else: os.makedirs(build_img_dir) @@ -93,7 +104,7 @@ class Exporter: def __write_archive(self, filename): archive_format = "zip" - out_dir = os.path.join(user_documents_dir(), "jitenbot") + out_dir = os.path.join(user_documents_dir(), "jitenbot", "yomichan") if not Path(out_dir).is_dir(): os.makedirs(out_dir) out_file = f"{filename}.{archive_format}" @@ -110,10 +121,7 @@ class Exporter: shutil.rmtree(build_dir) -class JitenonExporter(Exporter): - def __init__(self, target): - super().__init__(target) - +class _JitenonExporter(Exporter): def _get_revision(self, entries): modified_date = None for entry in entries: @@ -130,25 +138,19 @@ class JitenonExporter(Exporter): return attribution -class JitenonKokugoExporter(JitenonExporter): - def __init__(self, target): - super().__init__(target) +class JitenonKokugoExporter(_JitenonExporter): + pass -class JitenonYojiExporter(JitenonExporter): - def __init__(self, target): - super().__init__(target) +class JitenonYojiExporter(_JitenonExporter): + pass -class JitenonKotowazaExporter(JitenonExporter): - def __init__(self, target): - super().__init__(target) +class JitenonKotowazaExporter(_JitenonExporter): + pass class Smk8Exporter(Exporter): - def __init__(self, target): - super().__init__(target) - def _get_revision(self, entries): timestamp = datetime.now().strftime("%Y-%m-%d") return f"{self._target.value};{timestamp}" @@ -158,9 +160,6 @@ class Smk8Exporter(Exporter): class Daijirin2Exporter(Exporter): - def __init__(self, target): - super().__init__(target) - def _get_revision(self, entries): timestamp = datetime.now().strftime("%Y-%m-%d") return f"{self._target.value};{timestamp}" diff --git a/bot/yomichan/exporters/factory.py b/bot/yomichan/exporters/factory.py index 5ab9a6a..06568e3 100644 --- a/bot/yomichan/exporters/factory.py +++ b/bot/yomichan/exporters/factory.py @@ -7,7 +7,7 @@ from bot.yomichan.exporters.export import Smk8Exporter from bot.yomichan.exporters.export import Daijirin2Exporter -def new_exporter(target): +def new_yomi_exporter(target): exporter_map = { Targets.JITENON_KOKUGO: JitenonKokugoExporter, Targets.JITENON_YOJI: JitenonYojiExporter, diff --git a/bot/yomichan/glossary/daijirin2.py b/bot/yomichan/glossary/daijirin2.py index f2b6f2c..c42841c 100644 --- a/bot/yomichan/glossary/daijirin2.py +++ b/bot/yomichan/glossary/daijirin2.py @@ -6,9 +6,9 @@ from pathlib import Path import bot.icons as Icons from bot.soup import delete_soup_nodes -from bot.data import load_daijirin2_yomichan_name_conversion +from bot.data import load_yomichan_name_conversion from bot.yomichan.glossary.gloss import make_gloss -from bot.yomichan.glossary.name_conversion import convert_names +from bot.name_conversion import convert_names def make_glossary(entry, image_dir): @@ -26,7 +26,7 @@ def make_glossary(entry, image_dir): __convert_daigoginum(soup, image_dir) __convert_jundaigoginum(soup, image_dir) - name_conversion = load_daijirin2_yomichan_name_conversion() + name_conversion = load_yomichan_name_conversion(entry.target) convert_names(soup, name_conversion) gloss = make_gloss(soup.span) diff --git a/bot/yomichan/glossary/jitenon.py b/bot/yomichan/glossary/jitenon.py index 6e3a192..ca76f19 100644 --- a/bot/yomichan/glossary/jitenon.py +++ b/bot/yomichan/glossary/jitenon.py @@ -58,9 +58,9 @@ class JitenonGlossary(): if self._do_display_yomikata_in_headword(entry): tr.decompose() elif tr.th.text == "意味": - imi = tr.td - imi.name = "div" - soup.body.insert(0, imi) + definition = tr.td + definition.name = "div" + soup.body.insert(0, definition) tr.decompose() if soup.find("tr") is None: soup.table.decompose() diff --git a/bot/yomichan/glossary/smk8.py b/bot/yomichan/glossary/smk8.py index 870c3fc..8754a02 100644 --- a/bot/yomichan/glossary/smk8.py +++ b/bot/yomichan/glossary/smk8.py @@ -4,9 +4,9 @@ from bs4 import BeautifulSoup import bot.icons as Icons from bot.soup import delete_soup_nodes -from bot.data import load_smk8_yomichan_name_conversion +from bot.data import load_yomichan_name_conversion from bot.yomichan.glossary.gloss import make_gloss -from bot.yomichan.glossary.name_conversion import convert_names +from bot.name_conversion import convert_names def make_glossary(entry, image_dir): @@ -20,7 +20,7 @@ def make_glossary(entry, image_dir): __convert_gaiji(soup, image_dir) __convert_rectangles(soup, image_dir) - name_conversion = load_smk8_yomichan_name_conversion() + name_conversion = load_yomichan_name_conversion(entry.target) convert_names(soup, name_conversion) gloss = make_gloss(soup.span) diff --git a/bot/yomichan/terms/jitenon.py b/bot/yomichan/terms/jitenon.py index f74abaa..66bbed7 100644 --- a/bot/yomichan/terms/jitenon.py +++ b/bot/yomichan/terms/jitenon.py @@ -9,6 +9,7 @@ from bot.yomichan.glossary.jitenon import JitenonKotowazaGlossary class JitenonTerminator(Terminator): def __init__(self, target): super().__init__(target) + self._glossary_maker = None def _definition_tags(self, entry): return None @@ -51,7 +52,7 @@ class JitenonYojiTerminator(JitenonTerminator): return "" def _term_tags(self, entry): - tags = entry.kankenkyuu.split("/") + tags = entry.kanken_level.split("/") return " ".join(tags) diff --git a/bot/yomichan/terms/terminator.py b/bot/yomichan/terms/terminator.py index d41a50a..dd0c02d 100644 --- a/bot/yomichan/terms/terminator.py +++ b/bot/yomichan/terms/terminator.py @@ -1,7 +1,8 @@ +from abc import abstractmethod, ABC from bot.data import load_yomichan_inflection_categories -class Terminator: +class Terminator(ABC): def __init__(self, target): self._target = target self._glossary_cache = {} @@ -62,3 +63,31 @@ class Terminator: } glossary.append(gloss) return glossary + + @abstractmethod + def _definition_tags(self, entry): + pass + + @abstractmethod + def _inflection_rules(self, entry, expression): + pass + + @abstractmethod + def _glossary(self, entry): + pass + + @abstractmethod + def _sequence(self, entry): + pass + + @abstractmethod + def _term_tags(self, entry): + pass + + @abstractmethod + def _link_glossary_parameters(self, entry): + pass + + @abstractmethod + def _subentry_lists(self, entry): + pass diff --git a/data/daijirin2/mdict_name_conversion.json b/data/daijirin2/mdict_name_conversion.json new file mode 100644 index 0000000..d783d28 --- /dev/null +++ b/data/daijirin2/mdict_name_conversion.json @@ -0,0 +1,12 @@ +{ + "a": {}, + "br": {}, + "img": {}, + "div": {}, + "span": {}, + "ruby": {}, + "rt": {}, + "p": {}, + "漢字音G": {"name": "ul"}, + "漢字音": {"name": "li"} +} diff --git a/data/mdict/css/daijirin2.css b/data/mdict/css/daijirin2.css new file mode 100644 index 0000000..703cb35 --- /dev/null +++ b/data/mdict/css/daijirin2.css @@ -0,0 +1,414 @@ + +body { + margin: 1em 44px 1em 1em; + line-height: 1.5em; + font-family: serif; + font-size: 1.2em; + color: black; +} + +body.ABC { + margin: 0.5em 0.5em 2em 0.5em; +} + +a { + text-decoration: none; +} + +img.gaiji { + height: 1em; +} + +img.cut { + max-height: 100px; + max-width: 600px; +} + +p { + margin: 0.5em 0 +} + +span[data-name="i"] { + font-style: italic; +} + +span[data-name="h1"] { + font-family: sans-serif; + font-size: 1em; + font-weight: bold; +} + +span[data-name="image"] { + display: block; +} + +span[data-name="ref"] a { + text-decoration: none; +} + +span[data-name="sl"] { + text-decoration: accent; +} + +span[data-name="sm"] { + font-size: 0.7em; +} + +span[data-name="small"] { + font-size: 0.7em; + vertical-align: 0.35em; +} + +span[data-name="sub"] { + font-size: 0.7em; + vertical-align: -0.35em; +} + +span[data-name="ty2"] span[data-name="sub"] { + vertical-align: 0em; +} + +span[data-name="ty2"] span[data-name="sup"] { + vertical-align: 0.5em; +} + +span[data-name="文語形"] { + display: block; +} + +span[data-name="用例"] { + display: block; +} + +span[data-name="補説G"] { + display: block; +} + +span[data-name="語義Gnum"] + span[data-name="補説G"] { + display: inline; +} + +span[data-name="アクセントG"] + span[data-name="補説G"] { + display: inline; +} + +span[data-name="補説G"] + span[data-name="語釈"] { + display: block; +} + +span[data-name="アクセントG"] { + font-size: 0.7em; + vertical-align: super; + margin-left: 0.25em; + margin-right: 0.25em; +} + +span[data-name="カット"] { + display: block; +} + +span[data-name="カットG"] { + display: block; + margin-top: 0.5em; + margin-bottom: 0.5em; + margin-left: 1em; +} + +span[data-name="キャプション"] { + display: block; +} + +span[data-name="ルビG"] { + font-family: sans-serif; + font-size: 0.7em; + font-weight: normal; + vertical-align: 0.35em; +} + +.warichu span[data-name="ルビG"] { + font-family: serif; + font-size: 0.5em; + font-weight: normal; + vertical-align: 0em; +} + +span[data-name="中語義"] { + display: block; +} + +span[data-name="付記"] { + font-size: 0.7em; + vertical-align: 0.35em; +} + +span[data-name="副義"] { + display: block; + margin-left: 1em; +} + +span[data-name="単位名"] { + font-size: 0.5em; +} + +span[data-name="原籍"] { + font-size: 0.7em; + vertical-align: 0.35em; +} + +span[data-name="句仮名"] { + font-size: 0.7em; + vertical-align: 0.35em; +} + +span[data-name="句項目"] { + margin-top: 0.5em; + margin-left: 1em; + display: block; +} + +span[data-name="和字"] { + font-family: sans-serif; +} + +span[data-name="品詞行"] { + font-size: 0.7em; + vertical-align: 0.35em; +} + +span[data-name="品詞用法"] { + font-size: 0.7em; + vertical-align: 0.35em; +} + +span[data-name="大語義"] { + display: block; +} + +span[data-name="大語義num"] { + margin: 0.025em; + padding: 0.1em; + font-family: sans-serif; + font-size: 0.8em; + color: white; + background-color: black; +} + +span[data-name="子項目"] { + display: block; + margin-top: 0.5em; + margin-left: 1em; +} + +span[data-name="慣用G"] { + display: block; + margin-top: 0.5em; +} + +span[data-name="欧字"] { + font-family: sans-serif; +} + +span[data-name="歴史仮名"] { + font-size: 0.7em; + vertical-align: 0.35em; +} + +span[data-name="派生G"] { + display: block; + margin-top: 0.5em; +} + +span[data-name="準大語義"] { + display: block; +} + +span[data-name="準大語義num"] { + margin: 0.025em; + padding: 0.1em; + font-family: sans-serif; + font-size: 0.8em; + border: solid 1px black; +} + +span[data-name="漢字音logo"] { + margin: 0.025em; + padding: 0.1em; + font-family: sans-serif; + font-size: 0.8em; + border: solid 0.5px black; + border-radius: 1em; +} + +span[data-name="漢字音G"] { + font-size: 0.7em; + font-weight: normal; + vertical-align: 0.35em; +} + +span[data-name="生没年"] { + margin-left: 0.25em; + margin-right: 0.25em; + font-size: 0.7em; + vertical-align: 0.35em; +} + +span[data-name="生没年"]:first-child { + margin-left: 0; +} + +span[data-name="用法"] { + font-size: 0.7em; + vertical-align: 0.35em; +} + +span[data-name="異字同訓"] { + display: block; + margin-top: 0.5em; +} + +span[data-name="異字同訓仮名"] { + font-family: sans-serif; + font-weight: bold; +} + +span[data-name="異字同訓漢字"] { + font-family: serif; + font-weight: normal; +} + +span[data-name="異字同訓表記"] { + font-family: sans-serif; + font-weight: bold; +} + +span[data-name="異字同訓解説"] { + display: block; +} + +span[data-name="異字同訓語義G"] { + display: block; +} + +span[data-name="細義"] { + display: block; +} + +span[data-name="表外字マーク"] { + font-size: 0.5em; + vertical-align: 0.5em; +} + +span[data-name="見出仮名"] { + font-family: sans-serif; + font-weight: bold; +} + +span[data-name="見出相当部"] { + font-family: sans-serif; + font-weight: bold; +} + +span[data-name="見出部"] { + display: block; +} + +span[data-name="解説部"] { + display: block; + margin-left: 1em; +} + +span[data-name="語義G"] { + display: block; +} + +span[data-name="語義区切"] { + font-size: 0.7em; + vertical-align: 0.35em; +} + +span[data-name="返り点"] { + font-size: 0.5em; + font-weight: normal; + vertical-align: 1em; +} + +span[data-name="返り点"].熟語記号 { + vertical-align: 0em; +} + +span[data-name="項目"] { + display: block; +} + +span[data-name="logo"] { + margin: 0.025em 0.25em; + padding: 0.1em; + font-size: 0.8em; + border: solid 1px black; + border-radius: 0.2em; +} + +.gothic { + font-family: sans-serif; + font-weight: bold; +} + +.warichu { + font-size: 1em; +} + +.refnum { + font-size: 0.7em; + vertical-align: 0.35em; +} + +#index { + display: none; +} + +span[data-name="歴史仮名"]:before, +span[data-name="ルビG"]:before, +span[data-name="品詞行"]:before, +span[data-name="原籍"]:before, +span[data-name="品詞用法"]:before, +span[data-name="付記"]:before { + content: "("; +} + +span[data-name="歴史仮名"]:after, +span[data-name="ルビG"]:after, +span[data-name="品詞行"]:after, +span[data-name="原籍"]:after, +span[data-name="品詞用法"]:after, +span[data-name="付記"]:after { + content: ")"; +} + +div[data-child-links] { + padding-top: 1em; +} + +div[data-child-links] ul { + margin: 0; + padding-left: 2em; +} + +div[data-child-links] span { + padding: 0.1em; + font-family: sans-serif; + font-size: 0.8em; + color: white; + border-width: 0.05em; + border-style: none; + border-color: black; + word-break: keep-all; + -webkit-border-radius: 0.2em; +} + +div[data-child-links="子項目"] span { + background-color: rgb(153, 42, 103); +} + +div[data-child-links="句項目"] span { + background-color: rgb(176, 127, 57); +} diff --git a/data/mdict/css/jitenon-kokugo.css b/data/mdict/css/jitenon-kokugo.css new file mode 100644 index 0000000..687ae14 --- /dev/null +++ b/data/mdict/css/jitenon-kokugo.css @@ -0,0 +1,56 @@ + +body { + font-family: serif; + margin: 1em 44px 1em 1.5em; + line-height: 1.5em; + font-size: 1.2em; + color: black; +} + +table, th, td { + border: 1px solid; + border-collapse: collapse; + padding: 0.5em; +} + +th { + font-family: sans-serif; + color: black; + background-color: lightgray; + font-weight: normal; + white-space: nowrap; +} + +a { + text-decoration: none; +} + +td ul { + margin: -0.1em 0em -0.1em -1em; +} + +.見出し { +} + +.読み方 { + font-family: sans-serif; + font-weight: bold; +} + +.意味 { + margin-left: 1.0em; + margin-bottom: 0.5em; +} + +.num_icon { + font-family: sans-serif; + padding-left: 0.25em; + margin-right: 0.5em; + font-size: 0.8em; + word-break: keep-all; + color: white; + background-color: gray; + border-style: none; + -webkit-border-radius: 0.1em; +} + diff --git a/data/mdict/css/jitenon-kotowaza.css b/data/mdict/css/jitenon-kotowaza.css new file mode 100644 index 0000000..2dfb1be --- /dev/null +++ b/data/mdict/css/jitenon-kotowaza.css @@ -0,0 +1,40 @@ + +body { + font-family: serif; + margin: 1em 44px 1em 1.5em; + line-height: 1.5em; + font-size: 1.2em; + color: black; +} + +table, th, td { + border: 1px solid; + border-collapse: collapse; + padding: 0.5em; +} + +th { + font-family: sans-serif; + color: black; + background-color: lightgray; + font-weight: normal; + white-space: nowrap; +} + +a { + text-decoration: none; +} + +.見出し { +} + +.読み方 { + font-family: sans-serif; + font-weight: bold; +} + +.意味 { + margin-left: 1.0em; + margin-bottom: 0.5em; +} + diff --git a/data/mdict/css/jitenon-yoji.css b/data/mdict/css/jitenon-yoji.css new file mode 100644 index 0000000..2dfb1be --- /dev/null +++ b/data/mdict/css/jitenon-yoji.css @@ -0,0 +1,40 @@ + +body { + font-family: serif; + margin: 1em 44px 1em 1.5em; + line-height: 1.5em; + font-size: 1.2em; + color: black; +} + +table, th, td { + border: 1px solid; + border-collapse: collapse; + padding: 0.5em; +} + +th { + font-family: sans-serif; + color: black; + background-color: lightgray; + font-weight: normal; + white-space: nowrap; +} + +a { + text-decoration: none; +} + +.見出し { +} + +.読み方 { + font-family: sans-serif; + font-weight: bold; +} + +.意味 { + margin-left: 1.0em; + margin-bottom: 0.5em; +} + diff --git a/data/mdict/css/smk8.css b/data/mdict/css/smk8.css new file mode 100644 index 0000000..e88da1c --- /dev/null +++ b/data/mdict/css/smk8.css @@ -0,0 +1,449 @@ + +body { + margin: 1em 44px 1em 1.5em; + line-height: 1.5em; + font-family: serif; + font-size: 1.2em; + color: black; +} + +span[data-name="項目"] { + display: block; +} + +span[data-name="見出部"] { + display: block; +} + +span[data-name="見出部"].pri { + margin-left: -0.4em; +} + +span[data-name="見出仮名"] { + font-family: sans-serif; + font-weight: bold; +} + +rt[data-name="表音表記"] { + font-size: 0.65em; +} + +rt[data-name="表外音訓マーク"] { + font-size: 0.65em; +} + +rt[data-name="表外字マーク"] { + font-size: 0.65em; +} + +span[data-name="解説部"] { + display: block; + margin-left: 1em; +} + +span[data-name="大語義"] { + display: block; +} + +span[data-name="語義"] { + display: block; +} + +span[data-name="副義"] { + display: block; +} + +span[data-name="用例G"] { + display: block; +} + +span[data-name="注記"] span[data-name="用例G"] { + display: inline; +} + +span[data-name="用例"] { + display: block; +} + +span[data-name="注記"] span[data-name="用例"] { + display: inline; +} + +span[data-name="見出語省略"] { + margin-left: 0.125em; + margin-right: 0.125em; +} + +span[data-name="教育漢字"] { + color: green; +} + +span[data-name="ルビ"] { + font-size: 0.7em; + vertical-align: 0.5em; +} + +span[data-name="ルビ区切"] { + font-size: 0.7em; + vertical-align: 0.65em; +} + +span[data-name="名詞形G"] { + display: block; +} + +span[data-name="可能形G"] { + display: block; +} + +span[data-name="参照G"] { + display: block; +} + +span[data-name="参照"] { + color: blue; +} + +span[data-name="子項目"], +span[data-name="句項目"] { + display: block; + margin-bottom: 0.5em; +} + +span[data-name="子項目F"], +span[data-name="句項目F"] { + display: block; + margin-bottom: 0.5em; + margin-top: 0.5em; +} + +span[data-name="子見出部"] { + display: block; +} + +span[data-name="子解説部"] { + display: block; + margin-left: 1em; +} + +span[data-name="句見出部"] { + display: block; +} + +span[data-name="句解説部"] { + display: block; + margin-left: 1em; +} + +span[data-name="運用解説"] { + display: block; +} + +span[data-name="表記解説"] { + display: block; +} + +span[data-name="文法解説"] { + display: block; +} + +span[data-name="かぞえ方解説"] { + display: block; +} + +span[data-name="派生"] { + display: block; + margin-left: 1.25em; +} + +span[data-name="派生SubGF"] { + display: block; + text-indent: -1.25em; +} + +span[data-name="派生SubG"] { + display: block; +} + +span[data-name="派生SubGF"] span[data-name="用例G"] { + text-indent: 0; +} + +span[data-name="派生見出"] { + font-weight: bold; +} + +span[data-name="派生見出"].normal { + font-weight: normal +} + +span[data-name="造語成分項目"] { + display: block; + margin-top: 1em; +} + +span[data-name="造語成分見出"] { + font-size:1.4em; +} + +span[data-name="EM"] { + font-weight: bold; +} + +span[data-name="アクセント"] { + font-size: 0.7em; + vertical-align: super; +} + +span[data-name="アクセント組M"] { + vertical-align: 0.1em; +} + + +span[data-name="反意語M"], +span[data-name="同意語M"] { + vertical-align: 0.15em; +} + +span[data-name="B"] { + font-weight: bold; +} + +span[data-name="IT"] { + font-family: "Times New Roman"; + font-style: italic; +} + +span[data-name="EXCLAMATION"] { + font-family: "Times New Roman"; + font-style: italic; + font-size: 1.2em; +} + +span[data-name="歴史仮名"] { + font-family: serif; + font-size: 0.7em; + font-weight: normal; + vertical-align: 0.35em; + -webkit-user-select: nocopy; +} + +span[data-name="出現形"] { + font-weight: bold; +} + +span[data-name="品詞用法"] { + font-size: 0.7em; +} + +span[data-name="品詞用法"] span[data-name="品詞G"] { + font-size: 1.2em; +} + +span[data-name="基本構文型"] { + font-size: 0.8em; +} + +span[data-name="基本構文em"] { + font-weight: bold; +} + +span[data-name="ウ濁音参照"] { + font-family: sans-serif; + font-weight: bold; +} + +span[data-name="rect"] { + padding: 0.1em; + font-family: sans-serif; + font-size: 0.8em; + border-width: 0.05em; + border-style: solid; + border-color: black; + word-break: keep-all; + -webkit-border-radius: 0.1em; +} + +span[data-name="rect"].fill { + color: white; + border-style: none; + background-color: gray; +} + +span[data-name="rect"].red { + color: red; + border-color: red; +} + +span[data-name="rect"].redfill { + color: white; + border-style: none; + background-color: red; +} + +span[data-name="red"] { + color: red; +} + +span[data-name="大語義番号"], +span[data-name="語義番号"], +span[data-name="副義番号"] { + margin-right: 0.25em; + font-family: sans-serif; +} + +span[data-name="ref"] span[data-name="大語義番号"], +span[data-name="ref"] span[data-name="語義番号"], +span[data-name="ref"] span[data-name="副義番号"] { + font-size: 0.8em; + margin-right: 0; +} + +span[data-name="表外字マーク"] { + vertical-align: 0.5em; +} + +span[data-name="表外音訓マーク"] { + font-size: 0.5em; + vertical-align: 0.5em; +} + +span[data-name="言換M"] { + font-size: 0.5em; +} + +span[data-name="字音語参照項目"] { + display: block; +} + +span[data-name="本文項目M"] { + font-size: 0.7em; +} + +span[data-name="運用解説M"], +span[data-name="表記解説M"], +span[data-name="文法解説M"], +span[data-name="かぞえ方解説M"], +span[data-name="派生M"] { + margin-right: 0.25em; + font-family: sans-serif; +} + +span[data-name="派生ロゴ"] { + margin-left: 0.1em; + margin-right: 0.1em; +} + +span[data-name="文字"] { + margin: 0 0.2em; +} + +span[data-name="二分"] { + font-size: 0.5em; +} + +span[data-name="四分"] { + font-size: 0.25em; +} + +span[data-name="ref"] { + margin-left: 0.1em; + margin-right: 0.1em; +} + +span[data-name="ref-small"] { + font-size: 0.7em; +} + +span[data-name="sup"] { + font-size: 0.6em; +} + +span[data-name="外字"] img { + height: 1em; +} + +img.audio { + height: 1em; + margin: 0 0.25em; +} + +img.外字 { + height: 1em; +} + +img.外字欧 { + height: 1em; +} + +span[data-name="レ点M"] { + font-size: 0.6em; + vertical-align: -0.7em; +} + +a { + text-decoration: none; +} + +span[data-name="audio"] a { + padding-bottom: 0; + border-bottom: none; +} + +span[data-name="アクセント"] a, +span[data-name="古語M"] a, +span[data-name="雅語M"] a, +span[data-name="派生M"] a, +span[data-name="原籍M"] a, +span[data-name="品詞M"] a { + color: black; + border-bottom-style: none; +} + + +span[data-name="歴史仮名"]:before, +span[data-name="ルビ"]:before { + content: "("; +} + +span[data-name="歴史仮名"]:after, +span[data-name="ルビ"]:after { + content: ")"; +} + +div[data-child-links] { + padding-top: 1em; +} + +div[data-child-links] ul { + margin: 0; + padding-left: 2em; +} + +div[data-child-links] span { + padding: 0.1em; + font-family: sans-serif; + font-size: 0.8em; + color: white; + border-width: 0.05em; + border-style: none; + border-color: black; + word-break: keep-all; + -webkit-border-radius: 0.2em; +} + +div[data-child-links="子項目"] span { + background-color: rgb(153, 42, 103); +} + +div[data-child-links="句項目"] span { + background-color: rgb(176, 127, 57); +} + +span.pri > span.外字 { + font-size: 0.65em; + vertical-align: super; +} + + + diff --git a/data/mdict/description/daijirin2.mdx.description.html b/data/mdict/description/daijirin2.mdx.description.html new file mode 100644 index 0000000..c1eb401 --- /dev/null +++ b/data/mdict/description/daijirin2.mdx.description.html @@ -0,0 +1,7 @@ +大辞林 第四版 +

    +https://www.monokakido.jp/ja/dictionaries/daijirin2/index.html +

    +{{revision}} +

    +{{attribution}} diff --git a/data/mdict/description/jitenon-kokugo.mdx.description.html b/data/mdict/description/jitenon-kokugo.mdx.description.html new file mode 100644 index 0000000..a1c7489 --- /dev/null +++ b/data/mdict/description/jitenon-kokugo.mdx.description.html @@ -0,0 +1,7 @@ +国語辞典オンライン +

    +https://kokugo.jitenon.jp/ +

    +{{revision}} +

    +{{attribution}} diff --git a/data/mdict/description/jitenon-kotowaza.mdx.description.html b/data/mdict/description/jitenon-kotowaza.mdx.description.html new file mode 100644 index 0000000..b6d3c99 --- /dev/null +++ b/data/mdict/description/jitenon-kotowaza.mdx.description.html @@ -0,0 +1,7 @@ +故事・ことわざ・慣用句オンライン +

    +https://kotowaza.jitenon.jp/ +

    +{{revision}} +

    +{{attribution}} diff --git a/data/mdict/description/jitenon-yoji.mdx.description.html b/data/mdict/description/jitenon-yoji.mdx.description.html new file mode 100644 index 0000000..d7e3729 --- /dev/null +++ b/data/mdict/description/jitenon-yoji.mdx.description.html @@ -0,0 +1,7 @@ +四字熟語辞典オンライン +

    +https://yoji.jitenon.jp/ +

    +{{revision}} +

    +{{attribution}} diff --git a/data/mdict/description/smk8.mdx.description.html b/data/mdict/description/smk8.mdx.description.html new file mode 100644 index 0000000..7486250 --- /dev/null +++ b/data/mdict/description/smk8.mdx.description.html @@ -0,0 +1,7 @@ +新明解国語辞典 第八版 +

    +https://www.monokakido.jp/ja/dictionaries/smk8/index.html +

    +{{revision}} +

    +{{attribution}} diff --git a/data/mdict/icon/jitenon-kokugo.png b/data/mdict/icon/jitenon-kokugo.png new file mode 100644 index 0000000000000000000000000000000000000000..1ef1eb397db315fbb6ccab87da122fc515a2c2dd GIT binary patch literal 2374 zcmeHIc{CIX7aztFW64${CdOc_<$1zbo@EBZ6C#Riy=-GCp&4VTtQizTb`_c7X)I-D zjA`^Wro|hn$i9scBB379csjj*zCXVIzdyR?-0hru&OP^cH-d|kow(=`Q2+oSZjVN} z?rY9>J^z2v8W`A5Oq%jH{CyfR7(2ASfh!KtvQI zCN3c>k*IVCkMJ%f;$ zm3=EGH}7^qVNr2OY5Bd1%KKF$a&^t04=D8wjSn9+HMg|3wRd!OQM+k9bVl!!r_4Uq z^A|7s|6&ik8hric?a=VMkpk87XSH#R?i`Fm^o>(1^s zp2*+^4ge6$v`1N;jvZYY-|@?LR}@}r4lV;Rm8GM>Gs&J|qGYsmx=oOgMwSLuL&e&t zJ5a^*^k9Jw%t5=UtTcp#RntA`>L{);oc{0*y&8HLhmb5XpDTZYn_}n0_m~EM9B$h% zVw(3DekJ5f1DSFYY121xakY*5>+NGj@8^xU9kmu8OJIq6{Vg=tFH_WS zinQVZJs}! zsF1hE1fNWoWiC@SP5sou?Dk`wL2@%pd3dcCFVdE8WXcI|TRITHvwS<(WvYc9X?7y; zsZJ1Of#}ho96r?*uYGH&K7&t{>>P?5{g0nmvF(XA)nvOkqo-2S=t~#cxUbx!0>+}{ zPj!HEkKhx$UxV{y@wv*<#0-?Gik$;iO!-Wl_LTu5=&MGe1)t$yTs6JsUPDs0!Ky;} zf-WF?Tt5N%NLr`KxS}05>#qW7c)Z%vYK>BdhV9i4gF+ikDC;x9kFA_W8i&H{4uLr@mYO3MKuO9<;L9dgIuCG!CYKf1%UiJM&c@kp z^EA`JfqtKDzP&vTc9j*%W8H{VD3jTO)qAJWc8`i+<_Bq)JW0oA4JJ1lKJ%oTrG zDme#*{_dXn#aBXjF_Q9m>sx_KF4ypLq63{Rd&{6xJ;7+G*0Y!XEC+s-JyNoag1s>Q zP-nV)FuS|3kH=83ku+NwZ`%+>UA(hK=a{5Kl4v2f!#yyRx)2>tgTCt<4c~iHbx+$iUr@E&{Fn!i@G0^&YZ~W|nbwpj&8%x>DEi<8AZtBA zX&Eh8L8T~nq&G-}s%>5%DTxuKp5KVjOQz0hidanMiHT$U9V_JM z9$dcA$43Qq-gMs(YB{GnbjH32;Fk*NC_K???ZpdpHx~;JnwIl;d(oe@U8m)zr9&EI zXiUM=9YjJrbeyPo6;wez;LSFvl9%aQ-%;5()yF3dx4BD0WKTa0}N@ zsfSwOjB{(-2ojSDn0_$i!(bm?*5>bwF^a}bSGWPLBu32?A=yGJ{pv2nRPW1EOr_%< zF_ZjvAHY^1FbKRHX<98gAl%scSM|-^)a)_+V zXF(A5rS)Rv`7)tftRev@251*hslf@og>JUXel+qjI%Y!6@?HDhW#F(Iy6I-Tt8587 z|CGf4oI%z*D{i%jGdC4R{t@KlrRpp8@AgU6X2v{ykv%RC34jp6c;gdVESfr=*i%2Z zTTrX4AVX+j0i*lIBxDwS$*?AszsF5@*t0BVj5P;!_nJ9858InN$=psw6E+Qs&Z>mL z1@QNowi&6cyF*=L{Zj2R*LNEO6Ag)4>2cS5>yBxcM!b3R*9Yfa%!9^SucZMQF`<+n ecQJ+F#S@nP_$2}I1kL;Y3GHp1Pz~1p$^QVgrz&^= literal 0 HcmV?d00001 diff --git a/data/mdict/icon/jitenon-kotowaza.png b/data/mdict/icon/jitenon-kotowaza.png new file mode 100644 index 0000000000000000000000000000000000000000..15ccb9264b6dd65af3dcb51e847fcb7d8a1a7cd7 GIT binary patch literal 5473 zcmeI0*Ebvhx5Y;tz0EMlU<47p_ufbEy)$}ZLWqdogTcs1bfPC(q7y`Cln_J@61_(6 zB{$!Fxqrm{&RYANb@toY>pcD94fHg~Nf=20006lrT-E5`T>FoR@c#9V_?I030PeMc zj)~g8^}qGs1pXf;;2*4k`FHl6pOKz100$QjpMVfZL`*_TMovLVMNI<&)6zlc85kcx znV4B%tPk1PIUaF7=Hlkz<>MC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i1Yieoh=<4Yk z8X23InweWzT3OrJ+SxleIyoa;T;1F~JiVSg^+rDP@qO<1BH(4!38%)~Ucj+0KS=l+cdHL@P3X6(MO3OZcEU&1ns{Zu3rnauWp|PpC zdm0oHiXc@bV@C1eDqR^zo$ zR_*QPq(d=!i8j8c?~8nRZh`wkt%$g=AD@=CD4tOp<q!GBtcry{z)(t z`YcGMPNvS~&+Ci(z}BFH+dnVdmID_I(hN~f;3E3I$Lc}}kK^fC7Xzl6Bt+o-N}^1T z+*9dtCEKVVw{vxYT=6#Z?CZuygx#};OuCamZf)R4zAKL=y*~+7qJi!bpQLHe2HZp1 z-9?*r2XP0fOoyf}pN5>_v>1v+n(w>B@xHNp#zMr`7!*Q2iG`Pt%&|US zz_?ZVr3ghcy%Sa`Jsa=&_QlqX%55JJ9eg(f$F|Qq7ot$BmUx<;k0EQ)z%4QUZ9i0r z4T{fnuLj|VCiay-rAZ`>i4%|<>~X?xf~OrX6Ik`<6Vz#=CWgk&97In>sP8-EBO z#U!m#Da1jvooD$cT3c5&A*E2M8R4HtIj>bj`SC6h zmm1|mq#L(OC}6dna^zvmK>y%l`lldnK?TvkkDR?4iA)HGdTxpY8=vVww^l7~9`{%F z4>GIxDdMMZqsOdpmf8BW=q91h5pQtO1Dgc?D-ACO~JKtH9YE3Cf zC1tng@2p1frnaD4gRV&Pdc%uS*|O8+>i4f`h)F^#QiSYo<}X(;1T$YLXs(400>T3d zGDE!21pSv#!ca%utfo;024N#lkwD5HsdhYj)ZJ%e0Xi+wrJ7R&w6)o5i4o3+kkY~v z@wd@S6l*UFrhtQ70zOs1a@{t5tF8r`hAR<*)%I3~xuoFneTYJ^G{1d8!nG^qEu0q{e znbS zZ*kPO{a8O*(vV`eC0JJDwcwXF?#at)iKT|2E+|FLPn9CE=ZynG6j~CvD6@11g*#E` zjKi!*bA>lD`~{-}OY^#XG5~=}9!>hdxv0!izYJNUDeePFP;|yu{Lyo50P!|QLlU=KY;rPOu1mi(pN6! zMbRjKrFAnF0~vl3NZ#Pq2z=$xb>QnNp_WTzW>oI@RTtRuh%jlzKTUM1B~`M-wwQJ5 zL?z?$wJysg2;|~Pg!Ua~*n_A{O=x zO5IwV_Eml*{IiXvpT_wKmW(AY08LrjT0Q*YfD=Lg)O+U|pK+Lgp?CC9lNA(s_qH8+ zjv+9@c|v9KIa;yc&fNg*5KV@r>rg>8CDsBR_cD3O$@S4nLs(>JgLhPN>^ zij6uOwY^QmRf!GCTtN{KNZ&l1SV}hAZE=f@toAF`f7H@?5cam>(meTjbYK

    *6*; zm51;kc7Y2I2rIgxx7zZP7Tz`nRr=>l5eL24r_BErcY&0fDBoX zahNPOh=oPM-ahv=;3gqt(E1%s1yA9*jh?e!Fd}5TL=Vl~hOOof5}GE(l5Y>*NVp<} z>gl2~(57Zbv_(-gURNBl%-IFcj8;< zf7jrU^VAKFe%k5mN^6y=|LgNgUe%Kd(E5=%U!y1#&T{zKzmOq8NzoNc8S#M6L(NlXJ#ceKmB_8F$JoYc)tcj1fd0fYeBNra1 zi>+!uM0Srt!?pzo-;^pmS^0)$Dzx29?bLFOIlbgc4khP%-o%u%K)Vh((*wS$%k5Px zXK8R4O>Sz>a2W^$ekITcHKUR@sX&V9R;f|NlO2!m z%jY_aS_K*8FsZc5&)I;QkP!i4J31W7>K^C4zs$A=X2y|d(GZJV-BY{(1f@r+2vBM6 z=6e_EMNaba#8mV{l8-w&U0RxD&Vp74ir*V%VrZU{fd^BAx?L<1b1OngBlK3UK%*t5NM(&WG1hRX>!HW3eP*FW^-$pp# zCv;{pp_B!PG6|yY|DOJ-39*6eb29@6TGC!+XE^-@E~kA_ej=(Wjs4PzTv_ax`b(RV z8!^sr9g3y#4c#23f!Vk9OE`efknBBs@8;=QNAZXMM7S&shT=3*onskltbN z71@%kJhP7Tf_fdQ*yEtG&D4@-14Q`FNCIj6ptrHle_KdA8Ut2CywdR1-(2s2Xx8e3 z0irAK6pf#poS);jit^UCSr5pa5_%A3sf*WjlpfnyT-@J+oE@KzfKmPyp=r5!Fs~?z zx}A0I5uG^M*JaAkag8h2@)kQ-ga`Gv40}g0g4($KWsD@7Z+H@zCTJVam+0|>@NEW4 zJ%Ss$(&-J$62Oc^TsAuHmg1RT`}BH#bOfCV*Y5SPtM!fI#>Y0(DY!~C9`vr9_qHQ; zUvyMEBSKg|lrR8P8kdn8BTIJqQM+(MzSUCV=VHU%xKpjJ6c4$#KL-gh{L$nd!K6NC zBqmI%mc@5?SUuP-YL;*fRso>!6!-oy+C3x%T4LfX|I@!LBe*%hT?|u_a?7eV?2}m< zZ!s?%Xy`vBp9#@o!zRLvh0&Pz0X%BC=C&xtRgTVD@0>{b#FpuJA_Uoug$HKM6IZU( zj($p4h#fYkT_#&2-*`@MZPM|U!8PlP6u zdkFCRLrl49(XN%SBXw%j644{Yb5#^V(*tHrOd*KJPUKiORADjb?|?lEZ=^;_!rZn( znFVfrP)cy9RczB#GZqXXXZI55o>peqWd;A0-+vt6L;*6&0 zb>xB3qtYzuBn;U1=C_HfHWhm zXKg?IKGo78b>Tdx&thIvYn#h1JSCCJVMy#+wCeWZJNSB-5*xqk&yTYv^hc2{g%j_7Oxq{+d+x8`{s0!zKg$fKnCOH+x$_uN7v5LuYm;!Ti_co z-)Ntr{8rihZET~&1GgKq3k&()UPTY-W74=bNHKzsgR_9gyC&(=*-Q)0BXEg6c4S{p zjBU2?Y}*^rV7nSfWSVVl^d_DPd#c;6{S5)EN?)^8#i(wFB>{yvOeXB;GtimwVIw=m z`%iMJ)}#HDCMdcpV+it)}9XyuK5J(z7yuBhLzL>BA}}SZ!07$LJ^rZun+wy7D_e zYQePRl+zP?i;8obiM?Ej>3N(edHsTIR>}XCPl78naahPVVN)KpVLEjefz3=|MP oCOPyMFZn+_@xP=Z_~&sL(Vh_h_6@A`KafmQO;5F2*&*_O0A>u{Q2+n{ literal 0 HcmV?d00001 diff --git a/data/mdict/icon/jitenon-yoji.png b/data/mdict/icon/jitenon-yoji.png new file mode 100644 index 0000000000000000000000000000000000000000..0603db0b0e1751713d7d997e8405fb56a4b1fd0c GIT binary patch literal 2628 zcmai0c~sKd7Dj(YW*}ubfJ54|yecZ0IHVbcx4@yn3bULiMA0ZSHOwHnaT_#pAm{1@ znuVICq~*Y+LPFEDG!e%f(i}5q%O2hP-db%q1CDcqAearSobQ+wOH4UQXL^?}V}Y$?|;b zHSZ7KQ~AZlfA0zQ#C0O7VN%-tI(|0!-~ z>>h8lZFa|fAM@&i-qr1ZxtfI=h=2hy?DK&@+U*ono>wQ$o~96Nx_?dR8V!&~dMW6$ z2NBeKUL~+Tcq6KsOdmf%GZRDzLS@%1tvAG0Gvy&Z$aj@xt!^8WK{r9E5jGNW4no-B z!#@+uy%JCjieIbcukG$!+sj0;!@N94v3pe&qv$T=Ltz3)pOY@#p$eHC=jB=WA+>CwwzWlVUUNBfFRKr6(WK>6tblalZ0v7{CTRfJE|bu9en_TW7SgEsJ&DzQmY^ z3wx!i-@qMYZL@TeV9xCub!|%B0%tepx{D**Rg?}~j7tC9)P!`;TN|#=6?v^EC}(VolRxtp)=M=Z(^y*1&ei#td<{@ zCd3J{_4iBj(dy8g!BQsAw>@=3^2v|;q_B9+MI_}xPdbF3JXmqQ9(l%~dS9j2^iu|Y zqAK#(JPT}TU9Y?)${`&&mV#~DSKT1PUJxU);{=U7!lYKKp1WOuzAjdR>6E)o08gG? zS(}f#R0}Z=9FG5mhhJYAi<0Q>0RC(n*bO|Km9&N zuk0n*eQDjGqYGI-SZ+`bJ&oQNqrY(KFADKKM3~&&(1O(y*@LxkL)%$Yvl9BT1jVIi zLui(z+`z3E2?l|Kiyz#ulEbAP+h0W2g~UNQVK&NI!E(J!*nLu8X&ZX+&Nge-!|o2q zhi=93@}Ig|dm<~U|I~M3e~`2MrWW`dnZVO{z5-e7bxsfI-AP+}5uVtEfX_DUP(?`-qI9WBR@7myvr-+k6kxGV`dSWM1?J*vsp}@q?#Fz zuni+UU$pWjI!Uo6Uoqz8yyI>~Gg3FG}4p zry{Fioy~nd^Vm_quD6UZR~6}F!;&Cb2k2aO#Y1PC5~|n)B)YY#)4oSxEiyuL)9C78 z=5CIqz1R1|;G)l8cgHtlRBWR!Mshu)-l);`^o_xwVo(qfr=j}VC zOk~x%b2T6DZZuuD@Y~f_cQ=2SD?%iQFzhZghYD-=ywyewavrl=U-?>oDLq}RkI&ek zW+%cO7-xe-L69hzMkJgkj}PDgX$@Y$H7**nrBE4YglZ$;=-gYvuwzoxJ~hvNlLj8p z1WwUPM4cnm7U2sa`IW^?+M8SLB$Kuh??#CoV`tmD)QjcKdX@=^N8>demAZoQJK?=0 zZmNOMpuv0)v7o%z7xWxS+HStpOKANTBbvxhYkvZ*G+HZrr}3rsS<0-@X)Uo%746(` zG2bXKhpj_CI&RaFMbaOb16r5Hzc>ovFCA<(mK+~}{s9S!-_B}!o%2Qjiz@dnU0@~B zW3Llb%_1| z#kj_|uqX9=U$`J&=XBuu>3uI(D0nqz08W=$%~uWNZXQ_DG2Pn3j0%*p%GS>8x=60SqXYxtWXP$H!(W>R+frwfxb4F2TOL?)t;kQ z-?F(_Il}OC#eNR8s86*iwHCHnm@%jj zU8NqS9{t9>-!1wlRV31w9Z**CuV$X7f{WP@x zP#LpPWYu>I@pQ^x2#tZ>A2--ZQJ=0y@FR@;+a;T<0Vy_QV1QATUvsf~$DQf8Y&l`0 zcu9~dc&wKYV&iW<2obg^rZ9Q%st3#|-8GYI5?nL3ZH0>y%4pNEw1~+_xOnx+#!B@Q zpCZ|GaxI!sFqU^PwpbL%uovRNWn0D#9F7LU`W*{X=;K6LH`l~+s=)+~!-L%|St$@s zkp4C}?ls0)4(Mre02u!rfZgRY5RM^W{GX_0H`f^RE#T^U?~`Hj_n+YU$APBTSi*l( zikHolz?Eqisy_UU`cJk$bqc-c4*iqIHkE6MJ9+$~Ujnmfp5U^WF&Z<%LV`1;z)a(?|jtUz^L*Jpt)bB}YS~&sS)sv>LuHcSLzwAGI!{hlkRrVt!joF`d?w zCA-gh^zHCVxK#66(~R{)F(GerQTP1)%@>3P^mKuHU;64Q^V$Y6!OxDe=ds9|v#Y|8 zPSsm9>c26%T%AkRddsOhs=%J>|FmE^bMA*c. """ import os +import sys import argparse +import subprocess from bot.targets import Targets from bot.crawlers.factory import new_crawler +def filename(f): + if not os.path.isfile(f): + raise argparse.ArgumentTypeError(f"`{f}` is not a valid filename") + elif not os.access(f, os.R_OK): + raise argparse.ArgumentTypeError(f"Cannot access file `{f}`") + else: + return f + + def directory(d): if not os.path.isdir(d): raise argparse.ArgumentTypeError(f"`{d}` is not a valid directory") @@ -35,34 +46,71 @@ def parse_args(target_names): parser = argparse.ArgumentParser( prog="jitenbot", description="Convert Japanese dictionary files to new formats.", + epilog="See README.md for details regarding media directory structures", ) parser.add_argument( "target", choices=target_names, - help="name of dictionary to convert" + help="name of dictionary to convert", ) parser.add_argument( "-p", "--page-dir", help="path to directory containing XML page files", - type=directory + type=directory, ) parser.add_argument( - "-i", "--image-dir", - help="path to directory containing image folders (gaiji, graphics, etc.)", - type=directory + "-m", "--media-dir", + help="path to directory containing media folders (gaiji, graphics, audio, etc.)", + type=directory, + ) + parser.add_argument( + "-i", "--mdict-icon", + help="path to icon file to be used with MDict", + type=filename, + ) + parser.add_argument( + "--no-yomichan-export", + help="skip export of dictionary data to Yomichan format", + action='store_true', + ) + parser.add_argument( + "--no-mdict-export", + help="skip export of dictionary data to MDict format", + action='store_true', ) args = parser.parse_args() return args +def test_mdict(): + try: + subprocess.run( + ["mdict", "--version"], + check=True, + stdout=subprocess.DEVNULL, + ) + except FileNotFoundError: + print("Could not find `mdict` pack tool.") + print("Ensure that mdict-utils is installed and") + print("included in the environment PATH.\n") + print("Mdict export functionality may also be") + print("disabled with the --no-mdict-export flag.") + sys.exit() + + def main(): target_names = [x.value for x in Targets] args = parse_args(target_names) + if not args.no_mdict_export: + test_mdict() selected_target = Targets(args.target) crawler = new_crawler(selected_target) crawler.collect_pages(args.page_dir) crawler.read_pages() - crawler.make_yomichan_dictionary(args.image_dir) + if not args.no_yomichan_export: + crawler.make_yomichan_dictionary(args.media_dir) + if not args.no_mdict_export: + crawler.make_mdict_dictionary(args.media_dir, args.mdict_icon) if __name__ == "__main__": diff --git a/requirements.txt b/requirements.txt index 1c111af..8802356 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ css-parser==1.0.8 html5lib==1.1 idna==3.4 lxml==4.9.2 +mdict-utils==1.3.12 Pillow==9.5.0 platformdirs==3.5.0 requests==2.29.0 @@ -13,5 +14,7 @@ six==1.16.0 soupsieve==2.4.1 SudachiDict-full==20230110 SudachiPy==0.6.7 +tqdm==4.65.0 urllib3==1.26.15 webencodings==0.5.1 +xxhash==3.2.0 diff --git a/run_all.sh b/run_all.sh new file mode 100644 index 0000000..2bdd31e --- /dev/null +++ b/run_all.sh @@ -0,0 +1,13 @@ +python jitenbot.py jitenon-kokugo +python jitenbot.py jitenon-yoji +python jitenbot.py jitenon-kotowaza + +python jitenbot.py smk8 \ + --media-dir monokakido/SMK8/media \ + --page-dir monokakido/SMK8/pages \ + --mdict-icon monokakido/SMK8/SMK8-76@3x.png + +python jitenbot.py daijirin2 \ + --media-dir monokakido/DAIJIRIN2/media \ + --page-dir monokakido/DAIJIRIN2/pages \ + --mdict-icon monokakido/DAIJIRIN2/DAIJIRIN2-76@3x.png From 5c231fc6a0d03a2204d49a58cb08dcdecba9a8dd Mon Sep 17 00:00:00 2001 From: Stephen Kraus <8003332+stephenmk@users.noreply.github.com> Date: Sat, 8 Jul 2023 17:17:20 -0500 Subject: [PATCH 02/43] Update README.md --- README.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5a872ea..5e2f5dc 100644 --- a/README.md +++ b/README.md @@ -3,17 +3,18 @@ Jitenbot is a program for scraping Japanese dictionary websites and compiling the scraped data into compact dictionary file formats. ### Supported Dictionaries -* Online +* Web Dictionaries * [国語辞典オンライン](https://kokugo.jitenon.jp/) (Jitenon Kokugo) * [四字熟語辞典オンライン](https://yoji.jitenon.jp/) (Jitenon Yoji) * [故事・ことわざ・慣用句オンライン](https://kotowaza.jitenon.jp/) (Jitenon Kotowaza) -* Offline +* Monokakido (["辞書 by 物書堂"](https://www.monokakido.jp/ja/dictionaries/app/)) * [新明解国語辞典 第八版](https://www.monokakido.jp/ja/dictionaries/smk8/index.html) (Shinmeikai 8e) * [大辞林 第四版](https://www.monokakido.jp/ja/dictionaries/daijirin2/index.html) (Daijirin 4e) ### Supported Output Formats * [Yomichan](https://github.com/foosoft/yomichan) +* MDict (.MDX & .MDD) # Examples @@ -47,6 +48,12 @@ compiling the scraped data into compact dictionary file formats. ![daijirin2](https://user-images.githubusercontent.com/8003332/235578700-9dbf4fb0-0154-48b5-817c-8fe75e442afc.png) +

    + Various (GoldenDict) + + ![various](https://github.com/stephenmk/jitenbot/assets/8003332/b2519c2c-d4af-42a0-92aa-ef97ffef61ac) +
    + # Usage ``` usage: jitenbot [-h] [-p PAGE_DIR] [-m MEDIA_DIR] [-i MDICT_ICON] @@ -73,7 +80,7 @@ options: See README.md for details regarding media directory structures ``` -### Online Targets +### Web Targets Jitenbot will scrape the target website and save the pages to the [user cache directory](https://pypi.org/project/platformdirs/). As a courtesy to the website owners, jitenbot is configured to pause for 10 seconds between each page request. Consequently, a complete crawl of a target website may take several days. @@ -81,7 +88,7 @@ a complete crawl of a target website may take several days. HTTP request headers (user agent string, etc.) may be customized by editing the `config.json` file created in the [user config directory](https://pypi.org/project/platformdirs/). -### Offline Targets +### Monokakido Targets Page data and media data must be [procured by the user](https://github.com/golddranks/monokakido/) and passed to jitenbot via the appropriate command line flags. From 8855afb4640a478d98af8a85460d4a431026098d Mon Sep 17 00:00:00 2001 From: stephenmk Date: Sat, 8 Jul 2023 20:49:42 -0500 Subject: [PATCH 03/43] Reorganize data file directory --- bot/data.py | 20 +++++++++---------- bot/yomichan/glossary/daijirin2.py | 2 +- bot/{ => yomichan/glossary}/icons.py | 0 bot/yomichan/glossary/jitenon.py | 2 +- bot/yomichan/glossary/smk8.py | 2 +- .../adobe/Adobe-Japan1_sequences.txt | 0 data/{ => entries}/adobe/override_glyphs.json | 0 .../daijirin2/kana_abbreviations.csv | 0 .../daijirin2/phrase_readings.csv | 0 data/{ => entries}/smk8/phrase_readings.csv | 0 data/{ => entries}/variant_kanji.csv | 0 .../name_conversion/daijirin2.json} | 0 .../name_conversion/smk8.json} | 0 .../index.json} | 0 .../inflection_categories.json} | 0 .../name_conversion/daijirin2.json} | 0 .../name_conversion/smk8.json} | 0 17 files changed, 13 insertions(+), 13 deletions(-) rename bot/{ => yomichan/glossary}/icons.py (100%) rename data/{ => entries}/adobe/Adobe-Japan1_sequences.txt (100%) rename data/{ => entries}/adobe/override_glyphs.json (100%) rename data/{ => entries}/daijirin2/kana_abbreviations.csv (100%) rename data/{ => entries}/daijirin2/phrase_readings.csv (100%) rename data/{ => entries}/smk8/phrase_readings.csv (100%) rename data/{ => entries}/variant_kanji.csv (100%) rename data/{daijirin2/mdict_name_conversion.json => mdict/name_conversion/daijirin2.json} (100%) rename data/{smk8/mdict_name_conversion.json => mdict/name_conversion/smk8.json} (100%) rename data/{yomichan_metadata.json => yomichan/index.json} (100%) rename data/{yomichan_inflection_categories.json => yomichan/inflection_categories.json} (100%) rename data/{daijirin2/yomichan_name_conversion.json => yomichan/name_conversion/daijirin2.json} (100%) rename data/{smk8/yomichan_name_conversion.json => yomichan/name_conversion/smk8.json} (100%) diff --git a/bot/data.py b/bot/data.py index 3b1effd..e29c0a5 100644 --- a/bot/data.py +++ b/bot/data.py @@ -37,14 +37,14 @@ def load_config(): @cache def load_yomichan_inflection_categories(): - file_name = "yomichan_inflection_categories.json" + file_name = os.path.join("yomichan", "inflection_categories.json") data = __load_json(file_name) return data @cache def load_yomichan_metadata(): - file_name = "yomichan_metadata.json" + file_name = os.path.join("yomichan", "index.json") data = __load_json(file_name) return data @@ -53,7 +53,7 @@ def load_yomichan_metadata(): def load_variant_kanji(): def loader(data, row): data[row[0]] = row[1] - file_name = "variant_kanji.csv" + file_name = os.path.join("entries", "variant_kanji.csv") data = {} __load_csv(file_name, loader, data) return data @@ -65,7 +65,7 @@ def load_smk8_phrase_readings(): entry_id = (int(row[0]), int(row[1])) reading = row[2] data[entry_id] = reading - file_name = os.path.join("smk8", "phrase_readings.csv") + file_name = os.path.join("entries", "smk8", "phrase_readings.csv") data = {} __load_csv(file_name, loader, data) return data @@ -77,7 +77,7 @@ def load_daijirin2_phrase_readings(): entry_id = (int(row[0]), int(row[1])) reading = row[2] data[entry_id] = reading - file_name = os.path.join("daijirin2", "phrase_readings.csv") + file_name = os.path.join("entries", "daijirin2", "phrase_readings.csv") data = {} __load_csv(file_name, loader, data) return data @@ -92,7 +92,7 @@ def load_daijirin2_kana_abbreviations(): if abbr.strip() != "": abbreviations.append(abbr) data[entry_id] = abbreviations - file_name = os.path.join("daijirin2", "kana_abbreviations.csv") + file_name = os.path.join("entries", "daijirin2", "kana_abbreviations.csv") data = {} __load_csv(file_name, loader, data) return data @@ -100,14 +100,14 @@ def load_daijirin2_kana_abbreviations(): @cache def load_yomichan_name_conversion(target): - file_name = os.path.join(target.value, "yomichan_name_conversion.json") + file_name = os.path.join("yomichan", "name_conversion", f"{target.value}.json") data = __load_json(file_name) return data @cache def load_mdict_name_conversion(target): - file_name = os.path.join(target.value, "mdict_name_conversion.json") + file_name = os.path.join("mdict", "name_conversion", f"{target.value}.json") data = __load_json(file_name) return data @@ -131,7 +131,7 @@ def __load_adobe_glyphs(): data[code].append(character) else: data[code] = [character] - file_name = os.path.join("adobe", "Adobe-Japan1_sequences.txt") + file_name = os.path.join("entries", "adobe", "Adobe-Japan1_sequences.txt") data = {} __load_csv(file_name, loader, data, delim=';') return data @@ -139,7 +139,7 @@ def __load_adobe_glyphs(): @cache def __load_override_adobe_glyphs(): - file_name = os.path.join("adobe", "override_glyphs.json") + file_name = os.path.join("entries", "adobe", "override_glyphs.json") json_data = __load_json(file_name) data = {} for key, val in json_data.items(): diff --git a/bot/yomichan/glossary/daijirin2.py b/bot/yomichan/glossary/daijirin2.py index c42841c..71e06ad 100644 --- a/bot/yomichan/glossary/daijirin2.py +++ b/bot/yomichan/glossary/daijirin2.py @@ -4,7 +4,7 @@ from bs4 import BeautifulSoup from functools import cache from pathlib import Path -import bot.icons as Icons +import bot.yomichan.glossary.icons as Icons from bot.soup import delete_soup_nodes from bot.data import load_yomichan_name_conversion from bot.yomichan.glossary.gloss import make_gloss diff --git a/bot/icons.py b/bot/yomichan/glossary/icons.py similarity index 100% rename from bot/icons.py rename to bot/yomichan/glossary/icons.py diff --git a/bot/yomichan/glossary/jitenon.py b/bot/yomichan/glossary/jitenon.py index ca76f19..a342214 100644 --- a/bot/yomichan/glossary/jitenon.py +++ b/bot/yomichan/glossary/jitenon.py @@ -2,7 +2,7 @@ import re import os from bs4 import BeautifulSoup -import bot.icons as Icons +import bot.yomichan.glossary.icons as Icons from bot.yomichan.glossary.gloss import make_gloss diff --git a/bot/yomichan/glossary/smk8.py b/bot/yomichan/glossary/smk8.py index 8754a02..b49fc47 100644 --- a/bot/yomichan/glossary/smk8.py +++ b/bot/yomichan/glossary/smk8.py @@ -2,7 +2,7 @@ import re import os from bs4 import BeautifulSoup -import bot.icons as Icons +import bot.yomichan.glossary.icons as Icons from bot.soup import delete_soup_nodes from bot.data import load_yomichan_name_conversion from bot.yomichan.glossary.gloss import make_gloss diff --git a/data/adobe/Adobe-Japan1_sequences.txt b/data/entries/adobe/Adobe-Japan1_sequences.txt similarity index 100% rename from data/adobe/Adobe-Japan1_sequences.txt rename to data/entries/adobe/Adobe-Japan1_sequences.txt diff --git a/data/adobe/override_glyphs.json b/data/entries/adobe/override_glyphs.json similarity index 100% rename from data/adobe/override_glyphs.json rename to data/entries/adobe/override_glyphs.json diff --git a/data/daijirin2/kana_abbreviations.csv b/data/entries/daijirin2/kana_abbreviations.csv similarity index 100% rename from data/daijirin2/kana_abbreviations.csv rename to data/entries/daijirin2/kana_abbreviations.csv diff --git a/data/daijirin2/phrase_readings.csv b/data/entries/daijirin2/phrase_readings.csv similarity index 100% rename from data/daijirin2/phrase_readings.csv rename to data/entries/daijirin2/phrase_readings.csv diff --git a/data/smk8/phrase_readings.csv b/data/entries/smk8/phrase_readings.csv similarity index 100% rename from data/smk8/phrase_readings.csv rename to data/entries/smk8/phrase_readings.csv diff --git a/data/variant_kanji.csv b/data/entries/variant_kanji.csv similarity index 100% rename from data/variant_kanji.csv rename to data/entries/variant_kanji.csv diff --git a/data/daijirin2/mdict_name_conversion.json b/data/mdict/name_conversion/daijirin2.json similarity index 100% rename from data/daijirin2/mdict_name_conversion.json rename to data/mdict/name_conversion/daijirin2.json diff --git a/data/smk8/mdict_name_conversion.json b/data/mdict/name_conversion/smk8.json similarity index 100% rename from data/smk8/mdict_name_conversion.json rename to data/mdict/name_conversion/smk8.json diff --git a/data/yomichan_metadata.json b/data/yomichan/index.json similarity index 100% rename from data/yomichan_metadata.json rename to data/yomichan/index.json diff --git a/data/yomichan_inflection_categories.json b/data/yomichan/inflection_categories.json similarity index 100% rename from data/yomichan_inflection_categories.json rename to data/yomichan/inflection_categories.json diff --git a/data/daijirin2/yomichan_name_conversion.json b/data/yomichan/name_conversion/daijirin2.json similarity index 100% rename from data/daijirin2/yomichan_name_conversion.json rename to data/yomichan/name_conversion/daijirin2.json diff --git a/data/smk8/yomichan_name_conversion.json b/data/yomichan/name_conversion/smk8.json similarity index 100% rename from data/smk8/yomichan_name_conversion.json rename to data/yomichan/name_conversion/smk8.json From 9a1f70133dbec3683a3aff7e74623fd2cf355e53 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Sat, 8 Jul 2023 20:53:09 -0500 Subject: [PATCH 04/43] Use space indents instead of tabs in CSS files --- data/mdict/css/daijirin2.css | 288 ++++++++++++++-------------- data/mdict/css/jitenon-kokugo.css | 24 +-- data/mdict/css/jitenon-kotowaza.css | 10 +- data/mdict/css/jitenon-yoji.css | 10 +- data/mdict/css/smk8.css | 276 +++++++++++++------------- 5 files changed, 304 insertions(+), 304 deletions(-) diff --git a/data/mdict/css/daijirin2.css b/data/mdict/css/daijirin2.css index 703cb35..30da03e 100644 --- a/data/mdict/css/daijirin2.css +++ b/data/mdict/css/daijirin2.css @@ -1,22 +1,22 @@ body { - margin: 1em 44px 1em 1em; - line-height: 1.5em; - font-family: serif; - font-size: 1.2em; - color: black; + margin: 1em 44px 1em 1em; + line-height: 1.5em; + font-family: serif; + font-size: 1.2em; + color: black; } body.ABC { - margin: 0.5em 0.5em 2em 0.5em; + margin: 0.5em 0.5em 2em 0.5em; } a { - text-decoration: none; + text-decoration: none; } img.gaiji { - height: 1em; + height: 1em; } img.cut { @@ -25,51 +25,51 @@ img.cut { } p { - margin: 0.5em 0 + margin: 0.5em 0 } span[data-name="i"] { - font-style: italic; + font-style: italic; } span[data-name="h1"] { - font-family: sans-serif; - font-size: 1em; - font-weight: bold; + font-family: sans-serif; + font-size: 1em; + font-weight: bold; } span[data-name="image"] { - display: block; + display: block; } span[data-name="ref"] a { - text-decoration: none; + text-decoration: none; } span[data-name="sl"] { - text-decoration: accent; + text-decoration: accent; } span[data-name="sm"] { - font-size: 0.7em; + font-size: 0.7em; } span[data-name="small"] { - font-size: 0.7em; - vertical-align: 0.35em; + font-size: 0.7em; + vertical-align: 0.35em; } span[data-name="sub"] { - font-size: 0.7em; - vertical-align: -0.35em; + font-size: 0.7em; + vertical-align: -0.35em; } span[data-name="ty2"] span[data-name="sub"] { - vertical-align: 0em; + vertical-align: 0em; } span[data-name="ty2"] span[data-name="sup"] { - vertical-align: 0.5em; + vertical-align: 0.5em; } span[data-name="文語形"] { @@ -99,271 +99,271 @@ span[data-name="補説G"] + span[data-name="語釈"] { span[data-name="アクセントG"] { font-size: 0.7em; vertical-align: super; - margin-left: 0.25em; - margin-right: 0.25em; + margin-left: 0.25em; + margin-right: 0.25em; } span[data-name="カット"] { - display: block; + display: block; } span[data-name="カットG"] { - display: block; - margin-top: 0.5em; - margin-bottom: 0.5em; - margin-left: 1em; + display: block; + margin-top: 0.5em; + margin-bottom: 0.5em; + margin-left: 1em; } span[data-name="キャプション"] { - display: block; + display: block; } span[data-name="ルビG"] { - font-family: sans-serif; - font-size: 0.7em; - font-weight: normal; - vertical-align: 0.35em; + font-family: sans-serif; + font-size: 0.7em; + font-weight: normal; + vertical-align: 0.35em; } .warichu span[data-name="ルビG"] { - font-family: serif; - font-size: 0.5em; - font-weight: normal; - vertical-align: 0em; + font-family: serif; + font-size: 0.5em; + font-weight: normal; + vertical-align: 0em; } span[data-name="中語義"] { - display: block; + display: block; } span[data-name="付記"] { - font-size: 0.7em; - vertical-align: 0.35em; + font-size: 0.7em; + vertical-align: 0.35em; } span[data-name="副義"] { - display: block; + display: block; margin-left: 1em; } span[data-name="単位名"] { - font-size: 0.5em; + font-size: 0.5em; } span[data-name="原籍"] { - font-size: 0.7em; - vertical-align: 0.35em; + font-size: 0.7em; + vertical-align: 0.35em; } span[data-name="句仮名"] { - font-size: 0.7em; - vertical-align: 0.35em; + font-size: 0.7em; + vertical-align: 0.35em; } span[data-name="句項目"] { - margin-top: 0.5em; - margin-left: 1em; - display: block; + margin-top: 0.5em; + margin-left: 1em; + display: block; } span[data-name="和字"] { - font-family: sans-serif; + font-family: sans-serif; } span[data-name="品詞行"] { - font-size: 0.7em; - vertical-align: 0.35em; + font-size: 0.7em; + vertical-align: 0.35em; } span[data-name="品詞用法"] { - font-size: 0.7em; - vertical-align: 0.35em; + font-size: 0.7em; + vertical-align: 0.35em; } span[data-name="大語義"] { - display: block; + display: block; } span[data-name="大語義num"] { - margin: 0.025em; - padding: 0.1em; - font-family: sans-serif; - font-size: 0.8em; - color: white; - background-color: black; + margin: 0.025em; + padding: 0.1em; + font-family: sans-serif; + font-size: 0.8em; + color: white; + background-color: black; } span[data-name="子項目"] { - display: block; - margin-top: 0.5em; - margin-left: 1em; + display: block; + margin-top: 0.5em; + margin-left: 1em; } span[data-name="慣用G"] { - display: block; - margin-top: 0.5em; + display: block; + margin-top: 0.5em; } span[data-name="欧字"] { - font-family: sans-serif; + font-family: sans-serif; } span[data-name="歴史仮名"] { - font-size: 0.7em; - vertical-align: 0.35em; + font-size: 0.7em; + vertical-align: 0.35em; } span[data-name="派生G"] { - display: block; - margin-top: 0.5em; + display: block; + margin-top: 0.5em; } span[data-name="準大語義"] { - display: block; + display: block; } span[data-name="準大語義num"] { - margin: 0.025em; - padding: 0.1em; - font-family: sans-serif; - font-size: 0.8em; - border: solid 1px black; + margin: 0.025em; + padding: 0.1em; + font-family: sans-serif; + font-size: 0.8em; + border: solid 1px black; } span[data-name="漢字音logo"] { - margin: 0.025em; - padding: 0.1em; - font-family: sans-serif; - font-size: 0.8em; - border: solid 0.5px black; - border-radius: 1em; + margin: 0.025em; + padding: 0.1em; + font-family: sans-serif; + font-size: 0.8em; + border: solid 0.5px black; + border-radius: 1em; } span[data-name="漢字音G"] { - font-size: 0.7em; - font-weight: normal; - vertical-align: 0.35em; + font-size: 0.7em; + font-weight: normal; + vertical-align: 0.35em; } span[data-name="生没年"] { - margin-left: 0.25em; - margin-right: 0.25em; - font-size: 0.7em; - vertical-align: 0.35em; + margin-left: 0.25em; + margin-right: 0.25em; + font-size: 0.7em; + vertical-align: 0.35em; } span[data-name="生没年"]:first-child { - margin-left: 0; + margin-left: 0; } span[data-name="用法"] { - font-size: 0.7em; - vertical-align: 0.35em; + font-size: 0.7em; + vertical-align: 0.35em; } span[data-name="異字同訓"] { - display: block; - margin-top: 0.5em; + display: block; + margin-top: 0.5em; } span[data-name="異字同訓仮名"] { - font-family: sans-serif; - font-weight: bold; + font-family: sans-serif; + font-weight: bold; } span[data-name="異字同訓漢字"] { - font-family: serif; - font-weight: normal; + font-family: serif; + font-weight: normal; } span[data-name="異字同訓表記"] { - font-family: sans-serif; - font-weight: bold; + font-family: sans-serif; + font-weight: bold; } span[data-name="異字同訓解説"] { - display: block; + display: block; } span[data-name="異字同訓語義G"] { - display: block; + display: block; } span[data-name="細義"] { - display: block; + display: block; } span[data-name="表外字マーク"] { - font-size: 0.5em; - vertical-align: 0.5em; + font-size: 0.5em; + vertical-align: 0.5em; } span[data-name="見出仮名"] { - font-family: sans-serif; - font-weight: bold; + font-family: sans-serif; + font-weight: bold; } span[data-name="見出相当部"] { - font-family: sans-serif; - font-weight: bold; + font-family: sans-serif; + font-weight: bold; } span[data-name="見出部"] { - display: block; + display: block; } span[data-name="解説部"] { - display: block; - margin-left: 1em; + display: block; + margin-left: 1em; } span[data-name="語義G"] { - display: block; + display: block; } span[data-name="語義区切"] { - font-size: 0.7em; - vertical-align: 0.35em; + font-size: 0.7em; + vertical-align: 0.35em; } span[data-name="返り点"] { - font-size: 0.5em; - font-weight: normal; - vertical-align: 1em; + font-size: 0.5em; + font-weight: normal; + vertical-align: 1em; } span[data-name="返り点"].熟語記号 { - vertical-align: 0em; + vertical-align: 0em; } span[data-name="項目"] { - display: block; + display: block; } span[data-name="logo"] { - margin: 0.025em 0.25em; - padding: 0.1em; - font-size: 0.8em; - border: solid 1px black; - border-radius: 0.2em; + margin: 0.025em 0.25em; + padding: 0.1em; + font-size: 0.8em; + border: solid 1px black; + border-radius: 0.2em; } .gothic { - font-family: sans-serif; - font-weight: bold; + font-family: sans-serif; + font-weight: bold; } .warichu { - font-size: 1em; + font-size: 1em; } .refnum { - font-size: 0.7em; - vertical-align: 0.35em; + font-size: 0.7em; + vertical-align: 0.35em; } #index { - display: none; + display: none; } span[data-name="歴史仮名"]:before, @@ -395,20 +395,20 @@ div[data-child-links] ul { div[data-child-links] span { padding: 0.1em; - font-family: sans-serif; - font-size: 0.8em; - color: white; - border-width: 0.05em; - border-style: none; - border-color: black; - word-break: keep-all; - -webkit-border-radius: 0.2em; + font-family: sans-serif; + font-size: 0.8em; + color: white; + border-width: 0.05em; + border-style: none; + border-color: black; + word-break: keep-all; + -webkit-border-radius: 0.2em; } div[data-child-links="子項目"] span { - background-color: rgb(153, 42, 103); + background-color: rgb(153, 42, 103); } div[data-child-links="句項目"] span { - background-color: rgb(176, 127, 57); + background-color: rgb(176, 127, 57); } diff --git a/data/mdict/css/jitenon-kokugo.css b/data/mdict/css/jitenon-kokugo.css index 687ae14..70197ff 100644 --- a/data/mdict/css/jitenon-kokugo.css +++ b/data/mdict/css/jitenon-kokugo.css @@ -1,10 +1,10 @@ body { font-family: serif; - margin: 1em 44px 1em 1.5em; - line-height: 1.5em; - font-size: 1.2em; - color: black; + margin: 1em 44px 1em 1.5em; + line-height: 1.5em; + font-size: 1.2em; + color: black; } table, th, td { @@ -22,7 +22,7 @@ th { } a { - text-decoration: none; + text-decoration: none; } td ul { @@ -44,13 +44,13 @@ td ul { .num_icon { font-family: sans-serif; - padding-left: 0.25em; - margin-right: 0.5em; - font-size: 0.8em; - word-break: keep-all; + padding-left: 0.25em; + margin-right: 0.5em; + font-size: 0.8em; + word-break: keep-all; color: white; - background-color: gray; - border-style: none; - -webkit-border-radius: 0.1em; + background-color: gray; + border-style: none; + -webkit-border-radius: 0.1em; } diff --git a/data/mdict/css/jitenon-kotowaza.css b/data/mdict/css/jitenon-kotowaza.css index 2dfb1be..05bd394 100644 --- a/data/mdict/css/jitenon-kotowaza.css +++ b/data/mdict/css/jitenon-kotowaza.css @@ -1,10 +1,10 @@ body { font-family: serif; - margin: 1em 44px 1em 1.5em; - line-height: 1.5em; - font-size: 1.2em; - color: black; + margin: 1em 44px 1em 1.5em; + line-height: 1.5em; + font-size: 1.2em; + color: black; } table, th, td { @@ -22,7 +22,7 @@ th { } a { - text-decoration: none; + text-decoration: none; } .見出し { diff --git a/data/mdict/css/jitenon-yoji.css b/data/mdict/css/jitenon-yoji.css index 2dfb1be..05bd394 100644 --- a/data/mdict/css/jitenon-yoji.css +++ b/data/mdict/css/jitenon-yoji.css @@ -1,10 +1,10 @@ body { font-family: serif; - margin: 1em 44px 1em 1.5em; - line-height: 1.5em; - font-size: 1.2em; - color: black; + margin: 1em 44px 1em 1.5em; + line-height: 1.5em; + font-size: 1.2em; + color: black; } table, th, td { @@ -22,7 +22,7 @@ th { } a { - text-decoration: none; + text-decoration: none; } .見出し { diff --git a/data/mdict/css/smk8.css b/data/mdict/css/smk8.css index e88da1c..24710ca 100644 --- a/data/mdict/css/smk8.css +++ b/data/mdict/css/smk8.css @@ -1,31 +1,31 @@ body { - margin: 1em 44px 1em 1.5em; - line-height: 1.5em; - font-family: serif; - font-size: 1.2em; - color: black; + margin: 1em 44px 1em 1.5em; + line-height: 1.5em; + font-family: serif; + font-size: 1.2em; + color: black; } span[data-name="項目"] { - display: block; + display: block; } span[data-name="見出部"] { - display: block; + display: block; } span[data-name="見出部"].pri { - margin-left: -0.4em; + margin-left: -0.4em; } span[data-name="見出仮名"] { - font-family: sans-serif; - font-weight: bold; + font-family: sans-serif; + font-weight: bold; } rt[data-name="表音表記"] { - font-size: 0.65em; + font-size: 0.65em; } rt[data-name="表外音訓マーク"] { @@ -37,157 +37,157 @@ rt[data-name="表外字マーク"] { } span[data-name="解説部"] { - display: block; - margin-left: 1em; + display: block; + margin-left: 1em; } span[data-name="大語義"] { - display: block; + display: block; } span[data-name="語義"] { - display: block; + display: block; } span[data-name="副義"] { - display: block; + display: block; } span[data-name="用例G"] { - display: block; + display: block; } span[data-name="注記"] span[data-name="用例G"] { - display: inline; + display: inline; } span[data-name="用例"] { - display: block; + display: block; } span[data-name="注記"] span[data-name="用例"] { - display: inline; + display: inline; } span[data-name="見出語省略"] { - margin-left: 0.125em; - margin-right: 0.125em; + margin-left: 0.125em; + margin-right: 0.125em; } span[data-name="教育漢字"] { - color: green; + color: green; } span[data-name="ルビ"] { - font-size: 0.7em; - vertical-align: 0.5em; + font-size: 0.7em; + vertical-align: 0.5em; } span[data-name="ルビ区切"] { - font-size: 0.7em; - vertical-align: 0.65em; + font-size: 0.7em; + vertical-align: 0.65em; } span[data-name="名詞形G"] { - display: block; + display: block; } span[data-name="可能形G"] { - display: block; + display: block; } span[data-name="参照G"] { - display: block; + display: block; } span[data-name="参照"] { - color: blue; + color: blue; } span[data-name="子項目"], span[data-name="句項目"] { - display: block; - margin-bottom: 0.5em; + display: block; + margin-bottom: 0.5em; } span[data-name="子項目F"], span[data-name="句項目F"] { - display: block; - margin-bottom: 0.5em; - margin-top: 0.5em; + display: block; + margin-bottom: 0.5em; + margin-top: 0.5em; } span[data-name="子見出部"] { - display: block; + display: block; } span[data-name="子解説部"] { - display: block; - margin-left: 1em; + display: block; + margin-left: 1em; } span[data-name="句見出部"] { - display: block; + display: block; } span[data-name="句解説部"] { - display: block; - margin-left: 1em; + display: block; + margin-left: 1em; } span[data-name="運用解説"] { - display: block; + display: block; } span[data-name="表記解説"] { - display: block; + display: block; } span[data-name="文法解説"] { - display: block; + display: block; } span[data-name="かぞえ方解説"] { - display: block; + display: block; } span[data-name="派生"] { - display: block; - margin-left: 1.25em; + display: block; + margin-left: 1.25em; } span[data-name="派生SubGF"] { - display: block; - text-indent: -1.25em; + display: block; + text-indent: -1.25em; } span[data-name="派生SubG"] { - display: block; + display: block; } span[data-name="派生SubGF"] span[data-name="用例G"] { - text-indent: 0; + text-indent: 0; } span[data-name="派生見出"] { - font-weight: bold; + font-weight: bold; } span[data-name="派生見出"].normal { - font-weight: normal + font-weight: normal } span[data-name="造語成分項目"] { - display: block; - margin-top: 1em; + display: block; + margin-top: 1em; } span[data-name="造語成分見出"] { - font-size:1.4em; + font-size:1.4em; } span[data-name="EM"] { - font-weight: bold; + font-weight: bold; } span[data-name="アクセント"] { @@ -196,128 +196,128 @@ span[data-name="アクセント"] { } span[data-name="アクセント組M"] { - vertical-align: 0.1em; + vertical-align: 0.1em; } span[data-name="反意語M"], span[data-name="同意語M"] { - vertical-align: 0.15em; + vertical-align: 0.15em; } span[data-name="B"] { - font-weight: bold; + font-weight: bold; } span[data-name="IT"] { - font-family: "Times New Roman"; - font-style: italic; + font-family: "Times New Roman"; + font-style: italic; } span[data-name="EXCLAMATION"] { - font-family: "Times New Roman"; - font-style: italic; - font-size: 1.2em; + font-family: "Times New Roman"; + font-style: italic; + font-size: 1.2em; } span[data-name="歴史仮名"] { - font-family: serif; - font-size: 0.7em; - font-weight: normal; - vertical-align: 0.35em; - -webkit-user-select: nocopy; + font-family: serif; + font-size: 0.7em; + font-weight: normal; + vertical-align: 0.35em; + -webkit-user-select: nocopy; } span[data-name="出現形"] { - font-weight: bold; + font-weight: bold; } span[data-name="品詞用法"] { - font-size: 0.7em; + font-size: 0.7em; } span[data-name="品詞用法"] span[data-name="品詞G"] { - font-size: 1.2em; + font-size: 1.2em; } span[data-name="基本構文型"] { - font-size: 0.8em; + font-size: 0.8em; } span[data-name="基本構文em"] { - font-weight: bold; + font-weight: bold; } span[data-name="ウ濁音参照"] { - font-family: sans-serif; - font-weight: bold; + font-family: sans-serif; + font-weight: bold; } span[data-name="rect"] { - padding: 0.1em; - font-family: sans-serif; - font-size: 0.8em; - border-width: 0.05em; - border-style: solid; - border-color: black; - word-break: keep-all; - -webkit-border-radius: 0.1em; + padding: 0.1em; + font-family: sans-serif; + font-size: 0.8em; + border-width: 0.05em; + border-style: solid; + border-color: black; + word-break: keep-all; + -webkit-border-radius: 0.1em; } span[data-name="rect"].fill { - color: white; - border-style: none; - background-color: gray; + color: white; + border-style: none; + background-color: gray; } span[data-name="rect"].red { - color: red; - border-color: red; + color: red; + border-color: red; } span[data-name="rect"].redfill { - color: white; - border-style: none; - background-color: red; + color: white; + border-style: none; + background-color: red; } span[data-name="red"] { - color: red; + color: red; } span[data-name="大語義番号"], span[data-name="語義番号"], span[data-name="副義番号"] { - margin-right: 0.25em; - font-family: sans-serif; + margin-right: 0.25em; + font-family: sans-serif; } span[data-name="ref"] span[data-name="大語義番号"], span[data-name="ref"] span[data-name="語義番号"], span[data-name="ref"] span[data-name="副義番号"] { - font-size: 0.8em; - margin-right: 0; + font-size: 0.8em; + margin-right: 0; } span[data-name="表外字マーク"] { - vertical-align: 0.5em; + vertical-align: 0.5em; } span[data-name="表外音訓マーク"] { - font-size: 0.5em; - vertical-align: 0.5em; + font-size: 0.5em; + vertical-align: 0.5em; } span[data-name="言換M"] { - font-size: 0.5em; + font-size: 0.5em; } span[data-name="字音語参照項目"] { - display: block; + display: block; } span[data-name="本文項目M"] { - font-size: 0.7em; + font-size: 0.7em; } span[data-name="運用解説M"], @@ -325,69 +325,69 @@ span[data-name="表記解説M"], span[data-name="文法解説M"], span[data-name="かぞえ方解説M"], span[data-name="派生M"] { - margin-right: 0.25em; - font-family: sans-serif; + margin-right: 0.25em; + font-family: sans-serif; } span[data-name="派生ロゴ"] { - margin-left: 0.1em; - margin-right: 0.1em; + margin-left: 0.1em; + margin-right: 0.1em; } span[data-name="文字"] { - margin: 0 0.2em; + margin: 0 0.2em; } span[data-name="二分"] { - font-size: 0.5em; + font-size: 0.5em; } span[data-name="四分"] { - font-size: 0.25em; + font-size: 0.25em; } span[data-name="ref"] { - margin-left: 0.1em; - margin-right: 0.1em; + margin-left: 0.1em; + margin-right: 0.1em; } span[data-name="ref-small"] { - font-size: 0.7em; + font-size: 0.7em; } span[data-name="sup"] { - font-size: 0.6em; + font-size: 0.6em; } span[data-name="外字"] img { - height: 1em; + height: 1em; } img.audio { - height: 1em; - margin: 0 0.25em; + height: 1em; + margin: 0 0.25em; } img.外字 { - height: 1em; + height: 1em; } img.外字欧 { - height: 1em; + height: 1em; } span[data-name="レ点M"] { - font-size: 0.6em; - vertical-align: -0.7em; + font-size: 0.6em; + vertical-align: -0.7em; } a { - text-decoration: none; + text-decoration: none; } span[data-name="audio"] a { - padding-bottom: 0; - border-bottom: none; + padding-bottom: 0; + border-bottom: none; } span[data-name="アクセント"] a, @@ -396,8 +396,8 @@ span[data-name="雅語M"] a, span[data-name="派生M"] a, span[data-name="原籍M"] a, span[data-name="品詞M"] a { - color: black; - border-bottom-style: none; + color: black; + border-bottom-style: none; } @@ -422,22 +422,22 @@ div[data-child-links] ul { div[data-child-links] span { padding: 0.1em; - font-family: sans-serif; - font-size: 0.8em; - color: white; - border-width: 0.05em; - border-style: none; - border-color: black; - word-break: keep-all; - -webkit-border-radius: 0.2em; + font-family: sans-serif; + font-size: 0.8em; + color: white; + border-width: 0.05em; + border-style: none; + border-color: black; + word-break: keep-all; + -webkit-border-radius: 0.2em; } div[data-child-links="子項目"] span { - background-color: rgb(153, 42, 103); + background-color: rgb(153, 42, 103); } div[data-child-links="句項目"] span { - background-color: rgb(176, 127, 57); + background-color: rgb(176, 127, 57); } span.pri > span.外字 { From 4f3279c55a88e159abbd3daf641b785681c81994 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Sat, 8 Jul 2023 21:26:50 -0500 Subject: [PATCH 05/43] Add superscript style --- data/mdict/css/daijirin2.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/mdict/css/daijirin2.css b/data/mdict/css/daijirin2.css index 30da03e..fa512a0 100644 --- a/data/mdict/css/daijirin2.css +++ b/data/mdict/css/daijirin2.css @@ -64,6 +64,11 @@ span[data-name="sub"] { vertical-align: -0.35em; } +span[data-name="sup"] { + font-size: 0.7em; + vertical-align: super; +} + span[data-name="ty2"] span[data-name="sub"] { vertical-align: 0em; } From 19fcb0d5b2d5ad2073a4f6663b2dd5777696cb6e Mon Sep 17 00:00:00 2001 From: stephenmk Date: Sun, 9 Jul 2023 13:34:33 -0500 Subject: [PATCH 06/43] Specify font names in MDict styles --- data/mdict/css/daijirin2.css | 42 ++++++++++++++++++----------- data/mdict/css/jitenon-kokugo.css | 18 ++++++++++--- data/mdict/css/jitenon-kotowaza.css | 16 ++++++++--- data/mdict/css/jitenon-yoji.css | 16 ++++++++--- data/mdict/css/smk8.css | 27 +++++++++++++------ 5 files changed, 85 insertions(+), 34 deletions(-) diff --git a/data/mdict/css/daijirin2.css b/data/mdict/css/daijirin2.css index fa512a0..59bd75a 100644 --- a/data/mdict/css/daijirin2.css +++ b/data/mdict/css/daijirin2.css @@ -1,8 +1,18 @@ +@font-face { + font-family: jpgothic; + src: local("メイリオ"), local("ヒラギノ角ゴ Pro W3"), local("Hiragino Kaku Gothic Pro"), local("Meiryo"), local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"), local("MS Pゴシック"), local("MS Pgothic"), local("sans-serif"); +} + +@font-face { + font-family: jpmincho; + src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("Source Han Serif JP"), local("MS PMincho"), local("serif"); +} + body { margin: 1em 44px 1em 1em; line-height: 1.5em; - font-family: serif; + font-family: jpmincho; font-size: 1.2em; color: black; } @@ -33,7 +43,7 @@ span[data-name="i"] { } span[data-name="h1"] { - font-family: sans-serif; + font-family: jpgothic; font-size: 1em; font-weight: bold; } @@ -124,14 +134,14 @@ span[data-name="キャプション"] { } span[data-name="ルビG"] { - font-family: sans-serif; + font-family: jpgothic; font-size: 0.7em; font-weight: normal; vertical-align: 0.35em; } .warichu span[data-name="ルビG"] { - font-family: serif; + font-family: jpmincho; font-size: 0.5em; font-weight: normal; vertical-align: 0em; @@ -172,7 +182,7 @@ span[data-name="句項目"] { } span[data-name="和字"] { - font-family: sans-serif; + font-family: jpgothic; } span[data-name="品詞行"] { @@ -192,7 +202,7 @@ span[data-name="大語義"] { span[data-name="大語義num"] { margin: 0.025em; padding: 0.1em; - font-family: sans-serif; + font-family: jpgothic; font-size: 0.8em; color: white; background-color: black; @@ -210,7 +220,7 @@ span[data-name="慣用G"] { } span[data-name="欧字"] { - font-family: sans-serif; + font-family: jpgothic; } span[data-name="歴史仮名"] { @@ -230,7 +240,7 @@ span[data-name="準大語義"] { span[data-name="準大語義num"] { margin: 0.025em; padding: 0.1em; - font-family: sans-serif; + font-family: jpgothic; font-size: 0.8em; border: solid 1px black; } @@ -238,7 +248,7 @@ span[data-name="準大語義num"] { span[data-name="漢字音logo"] { margin: 0.025em; padding: 0.1em; - font-family: sans-serif; + font-family: jpgothic; font-size: 0.8em; border: solid 0.5px black; border-radius: 1em; @@ -272,17 +282,17 @@ span[data-name="異字同訓"] { } span[data-name="異字同訓仮名"] { - font-family: sans-serif; + font-family: jpgothic; font-weight: bold; } span[data-name="異字同訓漢字"] { - font-family: serif; + font-family: jpmincho; font-weight: normal; } span[data-name="異字同訓表記"] { - font-family: sans-serif; + font-family: jpgothic; font-weight: bold; } @@ -304,12 +314,12 @@ span[data-name="表外字マーク"] { } span[data-name="見出仮名"] { - font-family: sans-serif; + font-family: jpgothic; font-weight: bold; } span[data-name="見出相当部"] { - font-family: sans-serif; + font-family: jpgothic; font-weight: bold; } @@ -354,7 +364,7 @@ span[data-name="logo"] { } .gothic { - font-family: sans-serif; + font-family: jpgothic; font-weight: bold; } @@ -400,7 +410,7 @@ div[data-child-links] ul { div[data-child-links] span { padding: 0.1em; - font-family: sans-serif; + font-family: jpgothic; font-size: 0.8em; color: white; border-width: 0.05em; diff --git a/data/mdict/css/jitenon-kokugo.css b/data/mdict/css/jitenon-kokugo.css index 70197ff..2200c5c 100644 --- a/data/mdict/css/jitenon-kokugo.css +++ b/data/mdict/css/jitenon-kokugo.css @@ -1,6 +1,16 @@ +@font-face { + font-family: jpgothic; + src: local("メイリオ"), local("ヒラギノ角ゴ Pro W3"), local("Hiragino Kaku Gothic Pro"), local("Meiryo"), local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"), local("MS Pゴシック"), local("MS Pgothic"), local("sans-serif"); +} + +@font-face { + font-family: jpmincho; + src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("Source Han Serif JP"), local("MS PMincho"), local("serif"); +} + body { - font-family: serif; + font-family: jpmincho; margin: 1em 44px 1em 1.5em; line-height: 1.5em; font-size: 1.2em; @@ -14,7 +24,7 @@ table, th, td { } th { - font-family: sans-serif; + font-family: jpgothic; color: black; background-color: lightgray; font-weight: normal; @@ -33,7 +43,7 @@ td ul { } .読み方 { - font-family: sans-serif; + font-family: jpgothic; font-weight: bold; } @@ -43,7 +53,7 @@ td ul { } .num_icon { - font-family: sans-serif; + font-family: jpgothic; padding-left: 0.25em; margin-right: 0.5em; font-size: 0.8em; diff --git a/data/mdict/css/jitenon-kotowaza.css b/data/mdict/css/jitenon-kotowaza.css index 05bd394..bd9c29b 100644 --- a/data/mdict/css/jitenon-kotowaza.css +++ b/data/mdict/css/jitenon-kotowaza.css @@ -1,6 +1,16 @@ +@font-face { + font-family: jpgothic; + src: local("メイリオ"), local("ヒラギノ角ゴ Pro W3"), local("Hiragino Kaku Gothic Pro"), local("Meiryo"), local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"), local("MS Pゴシック"), local("MS Pgothic"), local("sans-serif"); +} + +@font-face { + font-family: jpmincho; + src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("Source Han Serif JP"), local("MS PMincho"), local("serif"); +} + body { - font-family: serif; + font-family: jpmincho; margin: 1em 44px 1em 1.5em; line-height: 1.5em; font-size: 1.2em; @@ -14,7 +24,7 @@ table, th, td { } th { - font-family: sans-serif; + font-family: jpgothic; color: black; background-color: lightgray; font-weight: normal; @@ -29,7 +39,7 @@ a { } .読み方 { - font-family: sans-serif; + font-family: jpgothic; font-weight: bold; } diff --git a/data/mdict/css/jitenon-yoji.css b/data/mdict/css/jitenon-yoji.css index 05bd394..bd9c29b 100644 --- a/data/mdict/css/jitenon-yoji.css +++ b/data/mdict/css/jitenon-yoji.css @@ -1,6 +1,16 @@ +@font-face { + font-family: jpgothic; + src: local("メイリオ"), local("ヒラギノ角ゴ Pro W3"), local("Hiragino Kaku Gothic Pro"), local("Meiryo"), local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"), local("MS Pゴシック"), local("MS Pgothic"), local("sans-serif"); +} + +@font-face { + font-family: jpmincho; + src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("Source Han Serif JP"), local("MS PMincho"), local("serif"); +} + body { - font-family: serif; + font-family: jpmincho; margin: 1em 44px 1em 1.5em; line-height: 1.5em; font-size: 1.2em; @@ -14,7 +24,7 @@ table, th, td { } th { - font-family: sans-serif; + font-family: jpgothic; color: black; background-color: lightgray; font-weight: normal; @@ -29,7 +39,7 @@ a { } .読み方 { - font-family: sans-serif; + font-family: jpgothic; font-weight: bold; } diff --git a/data/mdict/css/smk8.css b/data/mdict/css/smk8.css index 24710ca..521b053 100644 --- a/data/mdict/css/smk8.css +++ b/data/mdict/css/smk8.css @@ -1,8 +1,18 @@ +@font-face { + font-family: jpgothic; + src: local("メイリオ"), local("ヒラギノ角ゴ Pro W3"), local("Hiragino Kaku Gothic Pro"), local("Meiryo"), local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"), local("MS Pゴシック"), local("MS Pgothic"), local("sans-serif"); +} + +@font-face { + font-family: jpmincho; + src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("Source Han Serif JP"), local("MS PMincho"), local("serif"); +} + body { margin: 1em 44px 1em 1.5em; line-height: 1.5em; - font-family: serif; + font-family: jpmincho; font-size: 1.2em; color: black; } @@ -20,7 +30,7 @@ span[data-name="見出部"].pri { } span[data-name="見出仮名"] { - font-family: sans-serif; + font-family: jpgothic; font-weight: bold; } @@ -79,6 +89,7 @@ span[data-name="教育漢字"] { } span[data-name="ルビ"] { + font-family: jpgothic; font-size: 0.7em; vertical-align: 0.5em; } @@ -221,7 +232,7 @@ span[data-name="EXCLAMATION"] { } span[data-name="歴史仮名"] { - font-family: serif; + font-family: jpmincho; font-size: 0.7em; font-weight: normal; vertical-align: 0.35em; @@ -249,13 +260,13 @@ span[data-name="基本構文em"] { } span[data-name="ウ濁音参照"] { - font-family: sans-serif; + font-family: jpgothic; font-weight: bold; } span[data-name="rect"] { padding: 0.1em; - font-family: sans-serif; + font-family: jpgothic; font-size: 0.8em; border-width: 0.05em; border-style: solid; @@ -289,7 +300,7 @@ span[data-name="大語義番号"], span[data-name="語義番号"], span[data-name="副義番号"] { margin-right: 0.25em; - font-family: sans-serif; + font-family: jpgothic; } span[data-name="ref"] span[data-name="大語義番号"], @@ -326,7 +337,7 @@ span[data-name="文法解説M"], span[data-name="かぞえ方解説M"], span[data-name="派生M"] { margin-right: 0.25em; - font-family: sans-serif; + font-family: jpgothic; } span[data-name="派生ロゴ"] { @@ -422,7 +433,7 @@ div[data-child-links] ul { div[data-child-links] span { padding: 0.1em; - font-family: sans-serif; + font-family: jpgothic; font-size: 0.8em; color: white; border-width: 0.05em; From 17974cae79bdf9bde001f9671d045f26f6843021 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Sun, 9 Jul 2023 14:01:43 -0500 Subject: [PATCH 07/43] Adjust ruby sizes in smk8 mdict --- data/mdict/css/smk8.css | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/data/mdict/css/smk8.css b/data/mdict/css/smk8.css index 521b053..7b016ea 100644 --- a/data/mdict/css/smk8.css +++ b/data/mdict/css/smk8.css @@ -35,15 +35,15 @@ span[data-name="見出仮名"] { } rt[data-name="表音表記"] { - font-size: 0.65em; + font-size: 0.5em; } rt[data-name="表外音訓マーク"] { - font-size: 0.65em; + font-size: 1em; } rt[data-name="表外字マーク"] { - font-size: 0.65em; + font-size: 1em; } span[data-name="解説部"] { @@ -310,15 +310,6 @@ span[data-name="ref"] span[data-name="副義番号"] { margin-right: 0; } -span[data-name="表外字マーク"] { - vertical-align: 0.5em; -} - -span[data-name="表外音訓マーク"] { - font-size: 0.5em; - vertical-align: 0.5em; -} - span[data-name="言換M"] { font-size: 0.5em; } From ff8a0578218a8954967d771cdc0a07eb50afc16d Mon Sep 17 00:00:00 2001 From: Stephen Kraus <8003332+stephenmk@users.noreply.github.com> Date: Sun, 9 Jul 2023 14:11:15 -0500 Subject: [PATCH 08/43] Update GoldenDict example in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e2f5dc..ad56078 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ compiling the scraped data into compact dictionary file formats.
    Various (GoldenDict) - ![various](https://github.com/stephenmk/jitenbot/assets/8003332/b2519c2c-d4af-42a0-92aa-ef97ffef61ac) + ![goldendict](https://github.com/stephenmk/jitenbot/assets/8003332/76104cbf-845d-4e18-8b78-3ee3ebbf4da6)
    # Usage From a0264652d80395051d5b9590042941c9c869ef8d Mon Sep 17 00:00:00 2001 From: stephenmk Date: Sun, 9 Jul 2023 15:48:09 -0500 Subject: [PATCH 09/43] Make ruby elements unselectable in mdict --- data/mdict/css/daijirin2.css | 12 +++++++----- data/mdict/css/smk8.css | 6 +++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/data/mdict/css/daijirin2.css b/data/mdict/css/daijirin2.css index 59bd75a..18c4a32 100644 --- a/data/mdict/css/daijirin2.css +++ b/data/mdict/css/daijirin2.css @@ -138,6 +138,7 @@ span[data-name="ルビG"] { font-size: 0.7em; font-weight: normal; vertical-align: 0.35em; + -webkit-user-select: none; } .warichu span[data-name="ルビG"] { @@ -145,6 +146,7 @@ span[data-name="ルビG"] { font-size: 0.5em; font-weight: normal; vertical-align: 0em; + -webkit-user-select: none; } span[data-name="中語義"] { @@ -152,8 +154,8 @@ span[data-name="中語義"] { } span[data-name="付記"] { - font-size: 0.7em; - vertical-align: 0.35em; + /* font-size: 0.7em; */ + /* vertical-align: 0.35em; */ } span[data-name="副義"] { @@ -226,6 +228,7 @@ span[data-name="欧字"] { span[data-name="歴史仮名"] { font-size: 0.7em; vertical-align: 0.35em; + -webkit-user-select: none; } span[data-name="派生G"] { @@ -308,9 +311,8 @@ span[data-name="細義"] { display: block; } -span[data-name="表外字マーク"] { - font-size: 0.5em; - vertical-align: 0.5em; +rt { + -webkit-user-select: none; } span[data-name="見出仮名"] { diff --git a/data/mdict/css/smk8.css b/data/mdict/css/smk8.css index 7b016ea..8b49c3f 100644 --- a/data/mdict/css/smk8.css +++ b/data/mdict/css/smk8.css @@ -36,14 +36,17 @@ span[data-name="見出仮名"] { rt[data-name="表音表記"] { font-size: 0.5em; + -webkit-user-select: none; } rt[data-name="表外音訓マーク"] { font-size: 1em; + -webkit-user-select: none; } rt[data-name="表外字マーク"] { font-size: 1em; + -webkit-user-select: none; } span[data-name="解説部"] { @@ -92,6 +95,7 @@ span[data-name="ルビ"] { font-family: jpgothic; font-size: 0.7em; vertical-align: 0.5em; + -webkit-user-select: none; } span[data-name="ルビ区切"] { @@ -236,7 +240,7 @@ span[data-name="歴史仮名"] { font-size: 0.7em; font-weight: normal; vertical-align: 0.35em; - -webkit-user-select: nocopy; + -webkit-user-select: none; } span[data-name="出現形"] { From d0bc56bc80921e624e5b28a6fd4eef441704935e Mon Sep 17 00:00:00 2001 From: Stephen Kraus <8003332+stephenmk@users.noreply.github.com> Date: Sun, 9 Jul 2023 22:13:59 -0500 Subject: [PATCH 10/43] Adjust left margin --- data/mdict/css/smk8.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/mdict/css/smk8.css b/data/mdict/css/smk8.css index 8b49c3f..c51bc93 100644 --- a/data/mdict/css/smk8.css +++ b/data/mdict/css/smk8.css @@ -10,7 +10,7 @@ } body { - margin: 1em 44px 1em 1.5em; + margin: 1em 44px 1em 1em; line-height: 1.5em; font-family: jpmincho; font-size: 1.2em; From c6d3e2d5d862e719466aa1d2935127f3d67a467f Mon Sep 17 00:00:00 2001 From: Stephen Kraus <8003332+stephenmk@users.noreply.github.com> Date: Sun, 9 Jul 2023 22:41:34 -0500 Subject: [PATCH 11/43] Increase max image height --- data/mdict/css/daijirin2.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/mdict/css/daijirin2.css b/data/mdict/css/daijirin2.css index 18c4a32..f67d517 100644 --- a/data/mdict/css/daijirin2.css +++ b/data/mdict/css/daijirin2.css @@ -30,7 +30,7 @@ img.gaiji { } img.cut { - max-height: 100px; + max-height: 200px; max-width: 600px; } From dfc9a60d9e9cfddba9e4ee288dd1b86fd953abff Mon Sep 17 00:00:00 2001 From: stephenmk Date: Mon, 10 Jul 2023 12:35:46 -0500 Subject: [PATCH 12/43] Remove vertical margin from mdict entry styles --- data/mdict/css/daijirin2.css | 2 +- data/mdict/css/jitenon-kokugo.css | 2 +- data/mdict/css/jitenon-kotowaza.css | 2 +- data/mdict/css/jitenon-yoji.css | 2 +- data/mdict/css/smk8.css | 10 +++------- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/data/mdict/css/daijirin2.css b/data/mdict/css/daijirin2.css index f67d517..5783c4c 100644 --- a/data/mdict/css/daijirin2.css +++ b/data/mdict/css/daijirin2.css @@ -10,7 +10,7 @@ } body { - margin: 1em 44px 1em 1em; + margin: 0em 1em; line-height: 1.5em; font-family: jpmincho; font-size: 1.2em; diff --git a/data/mdict/css/jitenon-kokugo.css b/data/mdict/css/jitenon-kokugo.css index 2200c5c..a10e384 100644 --- a/data/mdict/css/jitenon-kokugo.css +++ b/data/mdict/css/jitenon-kokugo.css @@ -11,7 +11,7 @@ body { font-family: jpmincho; - margin: 1em 44px 1em 1.5em; + margin: 0em 1em; line-height: 1.5em; font-size: 1.2em; color: black; diff --git a/data/mdict/css/jitenon-kotowaza.css b/data/mdict/css/jitenon-kotowaza.css index bd9c29b..8590b44 100644 --- a/data/mdict/css/jitenon-kotowaza.css +++ b/data/mdict/css/jitenon-kotowaza.css @@ -11,7 +11,7 @@ body { font-family: jpmincho; - margin: 1em 44px 1em 1.5em; + margin: 0em 1em; line-height: 1.5em; font-size: 1.2em; color: black; diff --git a/data/mdict/css/jitenon-yoji.css b/data/mdict/css/jitenon-yoji.css index bd9c29b..8590b44 100644 --- a/data/mdict/css/jitenon-yoji.css +++ b/data/mdict/css/jitenon-yoji.css @@ -11,7 +11,7 @@ body { font-family: jpmincho; - margin: 1em 44px 1em 1.5em; + margin: 0em 1em; line-height: 1.5em; font-size: 1.2em; color: black; diff --git a/data/mdict/css/smk8.css b/data/mdict/css/smk8.css index c51bc93..c108aac 100644 --- a/data/mdict/css/smk8.css +++ b/data/mdict/css/smk8.css @@ -10,7 +10,7 @@ } body { - margin: 1em 44px 1em 1em; + margin: 0em 1em; line-height: 1.5em; font-family: jpmincho; font-size: 1.2em; @@ -205,16 +205,12 @@ span[data-name="EM"] { font-weight: bold; } -span[data-name="アクセント"] { +span[data-name="アクセント"], +span[data-name="アクセント組M"] { font-size: 0.7em; vertical-align: super; } -span[data-name="アクセント組M"] { - vertical-align: 0.1em; -} - - span[data-name="反意語M"], span[data-name="同意語M"] { vertical-align: 0.15em; From 227c02b9e4fc5083f3e0ac320838e2eac8b6958e Mon Sep 17 00:00:00 2001 From: stephenmk Date: Mon, 10 Jul 2023 19:17:55 -0500 Subject: [PATCH 13/43] Fix mdict font styles These are the styles used in the original Monokakido stylesheets --- data/mdict/css/daijirin2.css | 5 +++++ data/mdict/css/smk8.css | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/data/mdict/css/daijirin2.css b/data/mdict/css/daijirin2.css index 5783c4c..2743d0c 100644 --- a/data/mdict/css/daijirin2.css +++ b/data/mdict/css/daijirin2.css @@ -177,6 +177,11 @@ span[data-name="句仮名"] { vertical-align: 0.35em; } +span[data-name="句表記"] { + font-family: jpgothic; + font-weight: bold; +} + span[data-name="句項目"] { margin-top: 0.5em; margin-left: 1em; diff --git a/data/mdict/css/smk8.css b/data/mdict/css/smk8.css index c108aac..ae4d20b 100644 --- a/data/mdict/css/smk8.css +++ b/data/mdict/css/smk8.css @@ -92,7 +92,6 @@ span[data-name="教育漢字"] { } span[data-name="ルビ"] { - font-family: jpgothic; font-size: 0.7em; vertical-align: 0.5em; -webkit-user-select: none; From ce2e1066469e300d275e77fec5af2f040e818dac Mon Sep 17 00:00:00 2001 From: stephenmk Date: Mon, 10 Jul 2023 21:18:40 -0500 Subject: [PATCH 14/43] Add more detailed runtime messages --- bot/crawlers/crawlers.py | 2 +- bot/{ => crawlers}/scraper.py | 0 bot/mdict/exporters/export.py | 2 ++ bot/yomichan/exporters/export.py | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) rename bot/{ => crawlers}/scraper.py (100%) diff --git a/bot/crawlers/crawlers.py b/bot/crawlers/crawlers.py index 97b3794..3af886c 100644 --- a/bot/crawlers/crawlers.py +++ b/bot/crawlers/crawlers.py @@ -3,7 +3,7 @@ import re from abc import ABC, abstractmethod from bs4 import BeautifulSoup -import bot.scraper as Scraper +import bot.crawlers.scraper as Scraper from bot.entries.factory import new_entry from bot.yomichan.exporters.factory import new_yomi_exporter from bot.mdict.exporters.factory import new_mdict_exporter diff --git a/bot/scraper.py b/bot/crawlers/scraper.py similarity index 100% rename from bot/scraper.py rename to bot/crawlers/scraper.py diff --git a/bot/mdict/exporters/export.py b/bot/mdict/exporters/export.py index 2d76f1d..7aa0a3a 100644 --- a/bot/mdict/exporters/export.py +++ b/bot/mdict/exporters/export.py @@ -36,6 +36,7 @@ class Exporter(ABC): return self._build_dir cache_dir = user_cache_dir("jitenbot") build_directory = os.path.join(cache_dir, "mdict_build") + print(f"Initializing build directory `{build_directory}`") if Path(build_directory).is_dir(): shutil.rmtree(build_directory) os.makedirs(build_directory) @@ -121,6 +122,7 @@ class Exporter(ABC): return self._out_dir out_dir = os.path.join( user_documents_dir(), "jitenbot", "mdict", self._target.value) + print(f"Initializing output directory `{out_dir}`") if Path(out_dir).is_dir(): shutil.rmtree(out_dir) os.makedirs(out_dir) diff --git a/bot/yomichan/exporters/export.py b/bot/yomichan/exporters/export.py index 03e1b95..8665bfe 100644 --- a/bot/yomichan/exporters/export.py +++ b/bot/yomichan/exporters/export.py @@ -42,6 +42,7 @@ class Exporter(ABC): return self._build_dir cache_dir = user_cache_dir("jitenbot") build_directory = os.path.join(cache_dir, "yomichan_build") + print(f"Initializing build directory `{build_directory}`") if Path(build_directory).is_dir(): shutil.rmtree(build_directory) os.makedirs(build_directory) From 3cfca06f46b5a5dc2bd5acff2629f821c8a3ac93 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Tue, 11 Jul 2023 16:54:41 -0500 Subject: [PATCH 15/43] =?UTF-8?q?Add=20variant=20kanji=20=E9=9D=AD=20and?= =?UTF-8?q?=20=E9=9D=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/entries/variant_kanji.csv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/entries/variant_kanji.csv b/data/entries/variant_kanji.csv index 458c86c..b50ce8d 100644 --- a/data/entries/variant_kanji.csv +++ b/data/entries/variant_kanji.csv @@ -43,3 +43,5 @@ 鷽,鴬 鹼,鹸 麴,麹 +靭,靱 +靱,靭 From 4cd81cda355db11837b99fcdbb7d391348a6b9f7 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Wed, 12 Jul 2023 17:51:19 -0500 Subject: [PATCH 16/43] Adjust jitenon mdict CSS --- data/mdict/css/jitenon-kokugo.css | 7 ++----- data/mdict/css/jitenon-kotowaza.css | 7 ++----- data/mdict/css/jitenon-yoji.css | 7 ++----- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/data/mdict/css/jitenon-kokugo.css b/data/mdict/css/jitenon-kokugo.css index a10e384..94f9ec9 100644 --- a/data/mdict/css/jitenon-kokugo.css +++ b/data/mdict/css/jitenon-kokugo.css @@ -11,10 +11,7 @@ body { font-family: jpmincho; - margin: 0em 1em; line-height: 1.5em; - font-size: 1.2em; - color: black; } table, th, td { @@ -47,7 +44,8 @@ td ul { font-weight: bold; } -.意味 { +.意味, +.kanjirighttb { margin-left: 1.0em; margin-bottom: 0.5em; } @@ -63,4 +61,3 @@ td ul { border-style: none; -webkit-border-radius: 0.1em; } - diff --git a/data/mdict/css/jitenon-kotowaza.css b/data/mdict/css/jitenon-kotowaza.css index 8590b44..56d7c65 100644 --- a/data/mdict/css/jitenon-kotowaza.css +++ b/data/mdict/css/jitenon-kotowaza.css @@ -11,10 +11,7 @@ body { font-family: jpmincho; - margin: 0em 1em; line-height: 1.5em; - font-size: 1.2em; - color: black; } table, th, td { @@ -43,8 +40,8 @@ a { font-weight: bold; } -.意味 { +.意味, +.kanjirighttb { margin-left: 1.0em; margin-bottom: 0.5em; } - diff --git a/data/mdict/css/jitenon-yoji.css b/data/mdict/css/jitenon-yoji.css index 8590b44..56d7c65 100644 --- a/data/mdict/css/jitenon-yoji.css +++ b/data/mdict/css/jitenon-yoji.css @@ -11,10 +11,7 @@ body { font-family: jpmincho; - margin: 0em 1em; line-height: 1.5em; - font-size: 1.2em; - color: black; } table, th, td { @@ -43,8 +40,8 @@ a { font-weight: bold; } -.意味 { +.意味, +.kanjirighttb { margin-left: 1.0em; margin-bottom: 0.5em; } - From c9ab0aea463ae1337d20d552ba5d0a37c3b7425f Mon Sep 17 00:00:00 2001 From: stephenmk Date: Wed, 12 Jul 2023 17:53:28 -0500 Subject: [PATCH 17/43] Include copy of CSS file in mdict output directory There is also a read-only copy archived in the MDD resource file. This external file is more convenient for the user to edit. --- bot/mdict/exporters/export.py | 58 ++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/bot/mdict/exporters/export.py b/bot/mdict/exporters/export.py index 7aa0a3a..c100f67 100644 --- a/bot/mdict/exporters/export.py +++ b/bot/mdict/exporters/export.py @@ -8,7 +8,6 @@ from pathlib import Path from datetime import datetime from platformdirs import user_documents_dir, user_cache_dir -from bot.targets import Targets from bot.mdict.terms.factory import new_terminator @@ -24,11 +23,10 @@ class Exporter(ABC): def export(self, entries, media_dir, icon_file): self._init_build_media_dir(media_dir) self._init_description_file(entries) - terms = self._get_terms(entries) - print(f"Exporting {len(terms)} Mdict keys...") - self._write_mdx_file(terms) + self._write_mdx_file(entries) self._write_mdd_file() self._write_icon_file(icon_file) + self._write_css_file() self._rm_build_dir() def _get_build_dir(self): @@ -57,21 +55,34 @@ class Exporter(ABC): self._build_media_dir = build_media_dir def _init_description_file(self, entries): - filename = f"{self._target.value}.mdx.description.html" - original_file = os.path.join( - "data", "mdict", "description", filename) - with open(original_file, "r", encoding="utf8") as f: + description_template_file = self._get_description_template_file() + with open(description_template_file, "r", encoding="utf8") as f: description = f.read() description = description.replace( "{{revision}}", self._get_revision(entries)) description = description.replace( "{{attribution}}", self._get_attribution(entries)) build_dir = self._get_build_dir() - description_file = os.path.join(build_dir, filename) + description_file = os.path.join( + build_dir, f"{self._target.value}.mdx.description.html") with open(description_file, "w", encoding="utf8") as f: f.write(description) self._description_file = description_file + def _write_mdx_file(self, entries): + terms = self._get_terms(entries) + print(f"Exporting {len(terms)} Mdict keys...") + out_dir = self._get_out_dir() + out_file = os.path.join(out_dir, f"{self._target.value}.mdx") + params = [ + "mdict", + "-a", self._get_term_file(terms), + "--title", self._get_title_file(), + "--description", self._description_file, + out_file + ] + subprocess.run(params, check=True) + def _get_terms(self, entries): terms = [] entries_len = len(entries) @@ -84,18 +95,6 @@ class Exporter(ABC): print() return terms - def _write_mdx_file(self, terms): - out_dir = self._get_out_dir() - out_file = os.path.join(out_dir, f"{self._target.value}.mdx") - params = [ - "mdict", - "-a", self._get_term_file(terms), - "--title", self._get_title_file(), - "--description", self._description_file, - out_file - ] - subprocess.run(params, check=True) - def _write_mdd_file(self): out_dir = self._get_out_dir() out_file = os.path.join(out_dir, f"{self._target.value}.mdd") @@ -109,7 +108,7 @@ class Exporter(ABC): subprocess.run(params, check=True) def _write_icon_file(self, icon_file): - premade_icon_file = f"data/mdict/icon/{self._target.value}.png" + premade_icon_file = self._get_premade_icon_file() out_dir = self._get_out_dir() out_file = os.path.join(out_dir, f"{self._target.value}.png") if icon_file is not None and Path(icon_file).is_file(): @@ -117,6 +116,11 @@ class Exporter(ABC): elif Path(premade_icon_file).is_file(): shutil.copy(premade_icon_file, out_file) + def _write_css_file(self): + css_file = self._get_css_file() + out_dir = self._get_out_dir() + shutil.copy(css_file, out_dir) + def _get_out_dir(self): if self._out_dir is not None: return self._out_dir @@ -148,6 +152,16 @@ class Exporter(ABC): "data", "mdict", "css", f"{self._target.value}.css") + def _get_premade_icon_file(self): + return os.path.join( + "data", "mdict", "icon", + f"{self._target.value}.png") + + def _get_description_template_file(self): + return os.path.join( + "data", "mdict", "description", + f"{self._target.value}.mdx.description.html") + def _rm_build_dir(self): build_dir = self._get_build_dir() shutil.rmtree(build_dir) From d51de0b3dcc22edd1b5ee7430479c1d3f9aee8a3 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Wed, 12 Jul 2023 19:02:07 -0500 Subject: [PATCH 18/43] Redesign search key logic for mdict https://github.com/stephenmk/jitenbot/issues/1#issuecomment-1631583836 --- bot/mdict/terms/terminator.py | 50 +++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/bot/mdict/terms/terminator.py b/bot/mdict/terms/terminator.py index e69d9fb..ee62411 100644 --- a/bot/mdict/terms/terminator.py +++ b/bot/mdict/terms/terminator.py @@ -1,3 +1,4 @@ +import re from abc import abstractmethod, ABC @@ -12,35 +13,20 @@ class Terminator(ABC): def make_terms(self, entry): gid = entry.get_global_identifier() - glossary = self.__full_glossary(entry) + glossary = self.__get_full_glossary(entry) terms = [[gid, glossary]] - keys = set() - headwords = entry.get_headwords() - for reading, expressions in headwords.items(): - if len(expressions) == 0: - keys.add(reading) - for expression in expressions: - if expression.strip() == "": - keys.add(reading) - continue - keys.add(expression) - if reading.strip() == "": - continue - if reading != expression: - keys.add(f"{reading}【{expression}】") - else: - keys.add(reading) + keys = self.__get_keys(entry) link = f"@@@LINK={gid}" for key in keys: if key.strip() != "": terms.append([key, link]) - for subentries in self._subentry_lists(entry): - for subentry in subentries: + for subentry_list in self._subentry_lists(entry): + for subentry in subentry_list: for term in self.make_terms(subentry): terms.append(term) return terms - def __full_glossary(self, entry): + def __get_full_glossary(self, entry): glossary = [] style_link = f"" glossary.append(style_link) @@ -60,6 +46,30 @@ class Terminator(ABC): glossary.append(link_glossary) return "\n".join(glossary) + def __get_keys(self, entry): + keys = set() + headwords = entry.get_headwords() + for reading, expressions in headwords.items(): + stripped_reading = reading.strip() + keys.add(stripped_reading) + if re.match(r"^[ぁ-ヿ、]+$", stripped_reading): + kana_only_key = f"{stripped_reading}【∅】" + else: + kana_only_key = "" + if len(expressions) == 0: + keys.add(kana_only_key) + for expression in expressions: + stripped_expression = expression.strip() + keys.add(stripped_expression) + if stripped_expression == "": + keys.add(kana_only_key) + elif stripped_expression == stripped_reading: + keys.add(kana_only_key) + else: + combo_key = f"{stripped_reading}【{stripped_expression}】" + keys.add(combo_key) + return keys + @abstractmethod def _glossary(self, entry): pass From 7c40dafd52d091c05d062eb5d89bcd73a8c1e1ad Mon Sep 17 00:00:00 2001 From: stephenmk Date: Wed, 12 Jul 2023 20:27:47 -0500 Subject: [PATCH 19/43] Adjust padding style for child link lists in mdict --- data/mdict/css/daijirin2.css | 2 +- data/mdict/css/smk8.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/mdict/css/daijirin2.css b/data/mdict/css/daijirin2.css index 2743d0c..896b1ed 100644 --- a/data/mdict/css/daijirin2.css +++ b/data/mdict/css/daijirin2.css @@ -407,7 +407,7 @@ span[data-name="付記"]:after { } div[data-child-links] { - padding-top: 1em; + padding-left: 1em; } div[data-child-links] ul { diff --git a/data/mdict/css/smk8.css b/data/mdict/css/smk8.css index ae4d20b..188ab40 100644 --- a/data/mdict/css/smk8.css +++ b/data/mdict/css/smk8.css @@ -413,7 +413,7 @@ span[data-name="ルビ"]:after { } div[data-child-links] { - padding-top: 1em; + padding-left: 1em; } div[data-child-links] ul { From fd8d30472696abe156e00df11551e9f06d7b2954 Mon Sep 17 00:00:00 2001 From: Stephen Kraus <8003332+stephenmk@users.noreply.github.com> Date: Thu, 13 Jul 2023 17:08:12 -0500 Subject: [PATCH 20/43] =?UTF-8?q?Fix=20daijirin=20phrase=20reading=20for?= =?UTF-8?q?=20=E6=B0=B4=E6=B8=85=E3=81=91=E3=82=8C=E3=81=B0=E9=AD=9A?= =?UTF-8?q?=E6=A3=B2=E3=81=BE=E3=81=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/entries/daijirin2/phrase_readings.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/entries/daijirin2/phrase_readings.csv b/data/entries/daijirin2/phrase_readings.csv index 97022fa..5cb7d72 100644 --- a/data/entries/daijirin2/phrase_readings.csv +++ b/data/entries/daijirin2/phrase_readings.csv @@ -7215,7 +7215,7 @@ 154782,16388,みずがはいる 154782,16389,みずがひく 154782,16390,みずかる -154782,16391,みずきょければうおすまず +154782,16391,みずきよければうおすまず 154782,16392,みずすむ 154782,16393,みずでわる 154782,16394,みずとあぶら From 4e06482657379d868361e698e648be102facc9bf Mon Sep 17 00:00:00 2001 From: Stephen Kraus <8003332+stephenmk@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:30:44 -0500 Subject: [PATCH 21/43] =?UTF-8?q?Fix=20daijirin=20phrase=20reading=20for?= =?UTF-8?q?=20=E5=A4=A9=E9=A6=AC=E7=A9=BA=E3=82=92=E8=A1=8C=E3=81=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/entries/daijirin2/phrase_readings.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/entries/daijirin2/phrase_readings.csv b/data/entries/daijirin2/phrase_readings.csv index 5cb7d72..fa00846 100644 --- a/data/entries/daijirin2/phrase_readings.csv +++ b/data/entries/daijirin2/phrase_readings.csv @@ -5224,7 +5224,7 @@ 111520,16385,てんちょうにたっする 111583,16385,てんどうぜかひか 111583,16386,てんどうひとをころさず -111645,16385,てんばくうをいく +111645,16385,てんばくうをゆく 111695,16385,てんびんにかける 111790,16385,てんめいをしる 111801,16385,てんもうかいかいそにしてもらさず From 4d6c3c3cf52e14a81b8447ff11a6844e9d79d60f Mon Sep 17 00:00:00 2001 From: Stephen Kraus <8003332+stephenmk@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:44:06 -0500 Subject: [PATCH 22/43] =?UTF-8?q?Fix=20daijirin=20phrase=20reading=20for?= =?UTF-8?q?=20=E6=80=A0=E3=81=91=E8=80=85=E3=81=AE=E7=AF=80=E5=8F=A5?= =?UTF-8?q?=E5=83=8D=E3=81=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/entries/daijirin2/phrase_readings.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/entries/daijirin2/phrase_readings.csv b/data/entries/daijirin2/phrase_readings.csv index fa00846..48b0eab 100644 --- a/data/entries/daijirin2/phrase_readings.csv +++ b/data/entries/daijirin2/phrase_readings.csv @@ -5713,7 +5713,7 @@ 119456,16385,なまきにくぎ 119456,16386,なまきをさく 119472,16385,なまけもののあしからとりがたつ -119472,16386,なまけもののせっくはたらき +119472,16386,なまけもののせっくばたらき 119503,16385,なますにたたく 119503,16386,なますをふく 119507,16385,なまずをひょうたんでおさえる From dbf0cf0eb8c93f298425b057b926c3c7ec0c3cd4 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Sun, 16 Jul 2023 18:53:28 -0500 Subject: [PATCH 23/43] Fix Yomichan image height calculation error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For images that have greater height than width values, the image proportions for Yomichan were being calculated incorrectly. This can be observed for example in the SMK8 entry for くの字点 and the DAIJIRIN2 entry for 定積分. --- bot/yomichan/glossary/daijirin2.py | 20 ++++++++++---------- bot/yomichan/glossary/jitenon.py | 4 ++-- bot/yomichan/glossary/smk8.py | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/bot/yomichan/glossary/daijirin2.py b/bot/yomichan/glossary/daijirin2.py index 71e06ad..0adaa96 100644 --- a/bot/yomichan/glossary/daijirin2.py +++ b/bot/yomichan/glossary/daijirin2.py @@ -111,8 +111,8 @@ def __convert_gaiji(soup, image_dir): ratio = Icons.calculate_ratio(path) img = BeautifulSoup("", "xml").img img.attrs = { - "height": 1.0 if ratio > 1.0 else ratio, - "width": ratio if ratio > 1.0 else 1.0, + "height": 1.0, + "width": ratio, "sizeUnits": "em", "collapsible": False, "collapsed": False, @@ -150,8 +150,8 @@ def __convert_logos(soup, image_dir): ratio = Icons.calculate_ratio(path) img = BeautifulSoup("", "xml").img img.attrs = { - "height": 1.0 if ratio > 1.0 else ratio, - "width": ratio if ratio > 1.0 else 1.0, + "height": 1.0, + "width": ratio, "sizeUnits": "em", "collapsible": False, "collapsed": False, @@ -174,8 +174,8 @@ def __convert_kanjion_logos(soup, image_dir): ratio = Icons.calculate_ratio(path) img = BeautifulSoup("", "xml").img img.attrs = { - "height": 1.0 if ratio > 1.0 else ratio, - "width": ratio if ratio > 1.0 else 1.0, + "height": 1.0, + "width": ratio, "sizeUnits": "em", "collapsible": False, "collapsed": False, @@ -198,8 +198,8 @@ def __convert_daigoginum(soup, image_dir): ratio = Icons.calculate_ratio(path) img = BeautifulSoup("", "xml").img img.attrs = { - "height": 1.0 if ratio > 1.0 else ratio, - "width": ratio if ratio > 1.0 else 1.0, + "height": 1.0, + "width": ratio, "sizeUnits": "em", "collapsible": False, "collapsed": False, @@ -222,8 +222,8 @@ def __convert_jundaigoginum(soup, image_dir): ratio = Icons.calculate_ratio(path) img = BeautifulSoup("", "xml").img img.attrs = { - "height": 1.0 if ratio > 1.0 else ratio, - "width": ratio if ratio > 1.0 else 1.0, + "height": 1.0, + "width": ratio, "sizeUnits": "em", "collapsible": False, "collapsed": False, diff --git a/bot/yomichan/glossary/jitenon.py b/bot/yomichan/glossary/jitenon.py index a342214..61111b4 100644 --- a/bot/yomichan/glossary/jitenon.py +++ b/bot/yomichan/glossary/jitenon.py @@ -118,8 +118,8 @@ class JitenonKokugoGlossary(JitenonGlossary): ratio = Icons.calculate_ratio(path) img = BeautifulSoup("", "xml").img img.attrs = { - "height": 1.0 if ratio > 1.0 else ratio, - "width": ratio if ratio > 1.0 else 1.0, + "height": 1.0, + "width": ratio, "sizeUnits": "em", "collapsible": False, "collapsed": False, diff --git a/bot/yomichan/glossary/smk8.py b/bot/yomichan/glossary/smk8.py index b49fc47..f17ee42 100644 --- a/bot/yomichan/glossary/smk8.py +++ b/bot/yomichan/glossary/smk8.py @@ -92,8 +92,8 @@ def __convert_gaiji(soup, image_dir): ratio = Icons.calculate_ratio(path) img = BeautifulSoup("", "xml").img img.attrs = { - "height": 1.0 if ratio > 1.0 else ratio, - "width": ratio if ratio > 1.0 else 1.0, + "height": 1.0, + "width": ratio, "sizeUnits": "em", "collapsible": False, "collapsed": False, @@ -124,8 +124,8 @@ def __convert_rectangles(soup, image_dir): ratio = Icons.calculate_ratio(path) img = BeautifulSoup("", "xml").img img.attrs = { - "height": 1.0 if ratio > 1.0 else ratio, - "width": ratio if ratio > 1.0 else 1.0, + "height": 1.0, + "width": ratio, "sizeUnits": "em", "collapsible": False, "collapsed": False, From b0a9ab5caec793a217ff168fec183c8dffacdb07 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Sun, 16 Jul 2023 23:10:18 -0500 Subject: [PATCH 24/43] mdict: reconfigure font styles Taking advantage of the new fallback font selection functionality in GoldenDict-ng. If the user doesn't have one of the pre-defined fonts installed, the system serif and sans-serif fonts will be used. --- data/mdict/css/daijirin2.css | 38 ++++++++++++++--------------- data/mdict/css/jitenon-kokugo.css | 12 ++++----- data/mdict/css/jitenon-kotowaza.css | 10 ++++---- data/mdict/css/jitenon-yoji.css | 10 ++++---- data/mdict/css/smk8.css | 20 +++++++-------- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/data/mdict/css/daijirin2.css b/data/mdict/css/daijirin2.css index 896b1ed..f3f54a6 100644 --- a/data/mdict/css/daijirin2.css +++ b/data/mdict/css/daijirin2.css @@ -1,18 +1,18 @@ @font-face { font-family: jpgothic; - src: local("メイリオ"), local("ヒラギノ角ゴ Pro W3"), local("Hiragino Kaku Gothic Pro"), local("Meiryo"), local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"), local("MS Pゴシック"), local("MS Pgothic"), local("sans-serif"); + src: local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"); } @font-face { font-family: jpmincho; - src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("Source Han Serif JP"), local("MS PMincho"), local("serif"); + src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("IPAmjMincho"), local("Source Han Serif JP"), local("HanaMinA"), local("HanaMinB"); } body { margin: 0em 1em; line-height: 1.5em; - font-family: jpmincho; + font-family: jpmincho, serif; font-size: 1.2em; color: black; } @@ -43,7 +43,7 @@ span[data-name="i"] { } span[data-name="h1"] { - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-size: 1em; font-weight: bold; } @@ -134,7 +134,7 @@ span[data-name="キャプション"] { } span[data-name="ルビG"] { - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-size: 0.7em; font-weight: normal; vertical-align: 0.35em; @@ -142,7 +142,7 @@ span[data-name="ルビG"] { } .warichu span[data-name="ルビG"] { - font-family: jpmincho; + font-family: jpmincho, serif; font-size: 0.5em; font-weight: normal; vertical-align: 0em; @@ -178,7 +178,7 @@ span[data-name="句仮名"] { } span[data-name="句表記"] { - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-weight: bold; } @@ -189,7 +189,7 @@ span[data-name="句項目"] { } span[data-name="和字"] { - font-family: jpgothic; + font-family: jpgothic, sans-serif; } span[data-name="品詞行"] { @@ -209,7 +209,7 @@ span[data-name="大語義"] { span[data-name="大語義num"] { margin: 0.025em; padding: 0.1em; - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-size: 0.8em; color: white; background-color: black; @@ -227,7 +227,7 @@ span[data-name="慣用G"] { } span[data-name="欧字"] { - font-family: jpgothic; + font-family: jpgothic, sans-serif; } span[data-name="歴史仮名"] { @@ -248,7 +248,7 @@ span[data-name="準大語義"] { span[data-name="準大語義num"] { margin: 0.025em; padding: 0.1em; - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-size: 0.8em; border: solid 1px black; } @@ -256,7 +256,7 @@ span[data-name="準大語義num"] { span[data-name="漢字音logo"] { margin: 0.025em; padding: 0.1em; - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-size: 0.8em; border: solid 0.5px black; border-radius: 1em; @@ -290,17 +290,17 @@ span[data-name="異字同訓"] { } span[data-name="異字同訓仮名"] { - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-weight: bold; } span[data-name="異字同訓漢字"] { - font-family: jpmincho; + font-family: jpmincho, serif; font-weight: normal; } span[data-name="異字同訓表記"] { - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-weight: bold; } @@ -321,12 +321,12 @@ rt { } span[data-name="見出仮名"] { - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-weight: bold; } span[data-name="見出相当部"] { - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-weight: bold; } @@ -371,7 +371,7 @@ span[data-name="logo"] { } .gothic { - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-weight: bold; } @@ -417,7 +417,7 @@ div[data-child-links] ul { div[data-child-links] span { padding: 0.1em; - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-size: 0.8em; color: white; border-width: 0.05em; diff --git a/data/mdict/css/jitenon-kokugo.css b/data/mdict/css/jitenon-kokugo.css index 94f9ec9..da46bf8 100644 --- a/data/mdict/css/jitenon-kokugo.css +++ b/data/mdict/css/jitenon-kokugo.css @@ -1,16 +1,16 @@ @font-face { font-family: jpgothic; - src: local("メイリオ"), local("ヒラギノ角ゴ Pro W3"), local("Hiragino Kaku Gothic Pro"), local("Meiryo"), local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"), local("MS Pゴシック"), local("MS Pgothic"), local("sans-serif"); + src: local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"); } @font-face { font-family: jpmincho; - src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("Source Han Serif JP"), local("MS PMincho"), local("serif"); + src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("IPAmjMincho"), local("Source Han Serif JP"), local("HanaMinA"), local("HanaMinB"); } body { - font-family: jpmincho; + font-family: jpmincho, serif; line-height: 1.5em; } @@ -21,7 +21,7 @@ table, th, td { } th { - font-family: jpgothic; + font-family: jpgothic, sans-serif; color: black; background-color: lightgray; font-weight: normal; @@ -40,7 +40,7 @@ td ul { } .読み方 { - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-weight: bold; } @@ -51,7 +51,7 @@ td ul { } .num_icon { - font-family: jpgothic; + font-family: jpgothic, sans-serif; padding-left: 0.25em; margin-right: 0.5em; font-size: 0.8em; diff --git a/data/mdict/css/jitenon-kotowaza.css b/data/mdict/css/jitenon-kotowaza.css index 56d7c65..2929f89 100644 --- a/data/mdict/css/jitenon-kotowaza.css +++ b/data/mdict/css/jitenon-kotowaza.css @@ -1,16 +1,16 @@ @font-face { font-family: jpgothic; - src: local("メイリオ"), local("ヒラギノ角ゴ Pro W3"), local("Hiragino Kaku Gothic Pro"), local("Meiryo"), local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"), local("MS Pゴシック"), local("MS Pgothic"), local("sans-serif"); + src: local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"); } @font-face { font-family: jpmincho; - src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("Source Han Serif JP"), local("MS PMincho"), local("serif"); + src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("IPAmjMincho"), local("Source Han Serif JP"), local("HanaMinA"), local("HanaMinB"); } body { - font-family: jpmincho; + font-family: jpmincho, serif; line-height: 1.5em; } @@ -21,7 +21,7 @@ table, th, td { } th { - font-family: jpgothic; + font-family: jpgothic, sans-serif; color: black; background-color: lightgray; font-weight: normal; @@ -36,7 +36,7 @@ a { } .読み方 { - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-weight: bold; } diff --git a/data/mdict/css/jitenon-yoji.css b/data/mdict/css/jitenon-yoji.css index 56d7c65..2929f89 100644 --- a/data/mdict/css/jitenon-yoji.css +++ b/data/mdict/css/jitenon-yoji.css @@ -1,16 +1,16 @@ @font-face { font-family: jpgothic; - src: local("メイリオ"), local("ヒラギノ角ゴ Pro W3"), local("Hiragino Kaku Gothic Pro"), local("Meiryo"), local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"), local("MS Pゴシック"), local("MS Pgothic"), local("sans-serif"); + src: local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"); } @font-face { font-family: jpmincho; - src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("Source Han Serif JP"), local("MS PMincho"), local("serif"); + src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("IPAmjMincho"), local("Source Han Serif JP"), local("HanaMinA"), local("HanaMinB"); } body { - font-family: jpmincho; + font-family: jpmincho, serif; line-height: 1.5em; } @@ -21,7 +21,7 @@ table, th, td { } th { - font-family: jpgothic; + font-family: jpgothic, sans-serif; color: black; background-color: lightgray; font-weight: normal; @@ -36,7 +36,7 @@ a { } .読み方 { - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-weight: bold; } diff --git a/data/mdict/css/smk8.css b/data/mdict/css/smk8.css index 188ab40..29a23e8 100644 --- a/data/mdict/css/smk8.css +++ b/data/mdict/css/smk8.css @@ -1,18 +1,18 @@ @font-face { font-family: jpgothic; - src: local("メイリオ"), local("ヒラギノ角ゴ Pro W3"), local("Hiragino Kaku Gothic Pro"), local("Meiryo"), local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"), local("MS Pゴシック"), local("MS Pgothic"), local("sans-serif"); + src: local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"); } @font-face { font-family: jpmincho; - src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("Source Han Serif JP"), local("MS PMincho"), local("serif"); + src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("IPAmjMincho"), local("Source Han Serif JP"), local("HanaMinA"), local("HanaMinB"); } body { margin: 0em 1em; line-height: 1.5em; - font-family: jpmincho; + font-family: jpmincho, serif; font-size: 1.2em; color: black; } @@ -30,7 +30,7 @@ span[data-name="見出部"].pri { } span[data-name="見出仮名"] { - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-weight: bold; } @@ -231,7 +231,7 @@ span[data-name="EXCLAMATION"] { } span[data-name="歴史仮名"] { - font-family: jpmincho; + font-family: jpmincho, serif; font-size: 0.7em; font-weight: normal; vertical-align: 0.35em; @@ -259,13 +259,13 @@ span[data-name="基本構文em"] { } span[data-name="ウ濁音参照"] { - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-weight: bold; } span[data-name="rect"] { padding: 0.1em; - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-size: 0.8em; border-width: 0.05em; border-style: solid; @@ -299,7 +299,7 @@ span[data-name="大語義番号"], span[data-name="語義番号"], span[data-name="副義番号"] { margin-right: 0.25em; - font-family: jpgothic; + font-family: jpgothic, sans-serif; } span[data-name="ref"] span[data-name="大語義番号"], @@ -327,7 +327,7 @@ span[data-name="文法解説M"], span[data-name="かぞえ方解説M"], span[data-name="派生M"] { margin-right: 0.25em; - font-family: jpgothic; + font-family: jpgothic, sans-serif; } span[data-name="派生ロゴ"] { @@ -423,7 +423,7 @@ div[data-child-links] ul { div[data-child-links] span { padding: 0.1em; - font-family: jpgothic; + font-family: jpgothic, sans-serif; font-size: 0.8em; color: white; border-width: 0.05em; From e85d0a1625cb2ad633bab4d35df51d3df78d7d1b Mon Sep 17 00:00:00 2001 From: stephenmk Date: Tue, 18 Jul 2023 00:43:38 -0500 Subject: [PATCH 25/43] Add support for sankoku8 --- TODO.md | 7 +- bot/crawlers/crawlers.py | 8 +- bot/crawlers/factory.py | 2 + bot/data.py | 49 +- bot/entries/daijirin2.py | 4 +- bot/entries/factory.py | 2 + bot/entries/sankoku8.py | 260 ++ bot/entries/sankoku8_preprocess.py | 28 + bot/entries/smk8.py | 4 +- bot/mdict/exporters/export.py | 5 + bot/mdict/exporters/factory.py | 2 + bot/mdict/glossary/sankoku8.py | 137 + bot/mdict/terms/factory.py | 2 + bot/mdict/terms/sankoku8.py | 23 + bot/targets.py | 1 + bot/yomichan/exporters/export.py | 62 +- bot/yomichan/exporters/factory.py | 2 + bot/yomichan/glossary/icons.py | 48 + bot/yomichan/glossary/sankoku8.py | 344 ++ bot/yomichan/terms/factory.py | 2 + bot/yomichan/terms/sankoku8.py | 47 + data/entries/sankoku8/phrase_readings.csv | 3573 +++++++++++++++++ data/mdict/css/sankoku8.css | 611 +++ .../description/sankoku8.mdx.description.html | 7 + data/mdict/name_conversion/sankoku8.json | 22 + data/mdict/title/sankoku8.mdx.title.html | 1 + .../dictionary-term-bank-v3-schema.json | 474 +++ data/yomichan/index.json | 11 + data/yomichan/inflection_categories.json | 8 + data/yomichan/name_conversion/sankoku8.json | 495 +++ jitenbot.py | 15 +- requirements.txt | 5 + run_all.sh | 9 + tests/test_sankoku_phrases.py | 47 + 34 files changed, 6273 insertions(+), 44 deletions(-) create mode 100644 bot/entries/sankoku8.py create mode 100644 bot/entries/sankoku8_preprocess.py create mode 100644 bot/mdict/glossary/sankoku8.py create mode 100644 bot/mdict/terms/sankoku8.py create mode 100644 bot/yomichan/glossary/sankoku8.py create mode 100644 bot/yomichan/terms/sankoku8.py create mode 100644 data/entries/sankoku8/phrase_readings.csv create mode 100644 data/mdict/css/sankoku8.css create mode 100644 data/mdict/description/sankoku8.mdx.description.html create mode 100644 data/mdict/name_conversion/sankoku8.json create mode 100644 data/mdict/title/sankoku8.mdx.title.html create mode 100644 data/yomichan/dictionary-term-bank-v3-schema.json create mode 100644 data/yomichan/name_conversion/sankoku8.json mode change 100644 => 100755 run_all.sh create mode 100644 tests/test_sankoku_phrases.py diff --git a/TODO.md b/TODO.md index 2f2a5d5..877a7ee 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,13 @@ ### Todo - [x] Add factory classes to reduce the amount of class import statements +- [ ] Add dynamic import functionality to factory classes to reduce boilerplate - [x] Support exporting to MDict (.MDX) dictionary format +- [x] Validate JSON schema of Yomichan terms during export +- [ ] Add support for monokakido search keys from index files +- [ ] Delete unneeded media from temp build directory before final export - [ ] Add test suite - [ ] Add documentation (docstrings, etc.) -- [ ] Validate JSON schema of Yomichan terms during export - [ ] Add build scripts for producing program binaries - [ ] Validate scraped webpages after downloading - [ ] Log non-fatal failures to a log file instead of raising exceptions @@ -13,7 +16,7 @@ - [ ] [Yoji-Jukugo.com](https://yoji-jukugo.com/) - [ ] [実用日本語表現辞典](https://www.weblio.jp/cat/dictionary/jtnhj) - [ ] Support more Monokakido dictionaries - - [ ] 三省堂国語辞典 第8版 (SANKOKU8) + - [x] 三省堂国語辞典 第8版 (SANKOKU8) - [ ] 精選版 日本国語大辞典 (NDS) - [ ] 大辞泉 第2版 (DAIJISEN2) - [ ] 明鏡国語辞典 第3版 (MK3) diff --git a/bot/crawlers/crawlers.py b/bot/crawlers/crawlers.py index 3af886c..51e0552 100644 --- a/bot/crawlers/crawlers.py +++ b/bot/crawlers/crawlers.py @@ -39,9 +39,9 @@ class Crawler(ABC): self._entries.append(entry) print() - def make_yomichan_dictionary(self, media_dir): + def make_yomichan_dictionary(self, media_dir, validate): exporter = new_yomi_exporter(self._target) - exporter.export(self._entries, media_dir) + exporter.export(self._entries, media_dir, validate) def make_mdict_dictionary(self, media_dir, icon_file): exporter = new_mdict_exporter(self._target) @@ -152,3 +152,7 @@ class Smk8Crawler(_MonokakidoCrawler): class Daijirin2Crawler(_MonokakidoCrawler): pass + + +class Sankoku8Crawler(_MonokakidoCrawler): + pass diff --git a/bot/crawlers/factory.py b/bot/crawlers/factory.py index 081567b..d7450ea 100644 --- a/bot/crawlers/factory.py +++ b/bot/crawlers/factory.py @@ -5,6 +5,7 @@ from bot.crawlers.crawlers import JitenonYojiCrawler from bot.crawlers.crawlers import JitenonKotowazaCrawler from bot.crawlers.crawlers import Smk8Crawler from bot.crawlers.crawlers import Daijirin2Crawler +from bot.crawlers.crawlers import Sankoku8Crawler def new_crawler(target): @@ -14,5 +15,6 @@ def new_crawler(target): Targets.JITENON_KOTOWAZA: JitenonKotowazaCrawler, Targets.SMK8: Smk8Crawler, Targets.DAIJIRIN2: Daijirin2Crawler, + Targets.SANKOKU8: Sankoku8Crawler, } return crawler_map[target](target) diff --git a/bot/data.py b/bot/data.py index e29c0a5..95349db 100644 --- a/bot/data.py +++ b/bot/data.py @@ -37,14 +37,16 @@ def load_config(): @cache def load_yomichan_inflection_categories(): - file_name = os.path.join("yomichan", "inflection_categories.json") + file_name = os.path.join( + "yomichan", "inflection_categories.json") data = __load_json(file_name) return data @cache def load_yomichan_metadata(): - file_name = os.path.join("yomichan", "index.json") + file_name = os.path.join( + "yomichan", "index.json") data = __load_json(file_name) return data @@ -53,31 +55,21 @@ def load_yomichan_metadata(): def load_variant_kanji(): def loader(data, row): data[row[0]] = row[1] - file_name = os.path.join("entries", "variant_kanji.csv") + file_name = os.path.join( + "entries", "variant_kanji.csv") data = {} __load_csv(file_name, loader, data) return data @cache -def load_smk8_phrase_readings(): +def load_phrase_readings(target): def loader(data, row): entry_id = (int(row[0]), int(row[1])) reading = row[2] data[entry_id] = reading - file_name = os.path.join("entries", "smk8", "phrase_readings.csv") - data = {} - __load_csv(file_name, loader, data) - return data - - -@cache -def load_daijirin2_phrase_readings(): - def loader(data, row): - entry_id = (int(row[0]), int(row[1])) - reading = row[2] - data[entry_id] = reading - file_name = os.path.join("entries", "daijirin2", "phrase_readings.csv") + file_name = os.path.join( + "entries", target.value, "phrase_readings.csv") data = {} __load_csv(file_name, loader, data) return data @@ -92,7 +84,8 @@ def load_daijirin2_kana_abbreviations(): if abbr.strip() != "": abbreviations.append(abbr) data[entry_id] = abbreviations - file_name = os.path.join("entries", "daijirin2", "kana_abbreviations.csv") + file_name = os.path.join( + "entries", "daijirin2", "kana_abbreviations.csv") data = {} __load_csv(file_name, loader, data) return data @@ -100,14 +93,24 @@ def load_daijirin2_kana_abbreviations(): @cache def load_yomichan_name_conversion(target): - file_name = os.path.join("yomichan", "name_conversion", f"{target.value}.json") + file_name = os.path.join( + "yomichan", "name_conversion", f"{target.value}.json") data = __load_json(file_name) return data +@cache +def load_yomichan_term_schema(): + file_name = os.path.join( + "yomichan", "dictionary-term-bank-v3-schema.json") + schema = __load_json(file_name) + return schema + + @cache def load_mdict_name_conversion(target): - file_name = os.path.join("mdict", "name_conversion", f"{target.value}.json") + file_name = os.path.join( + "mdict", "name_conversion", f"{target.value}.json") data = __load_json(file_name) return data @@ -131,7 +134,8 @@ def __load_adobe_glyphs(): data[code].append(character) else: data[code] = [character] - file_name = os.path.join("entries", "adobe", "Adobe-Japan1_sequences.txt") + file_name = os.path.join( + "entries", "adobe", "Adobe-Japan1_sequences.txt") data = {} __load_csv(file_name, loader, data, delim=';') return data @@ -139,7 +143,8 @@ def __load_adobe_glyphs(): @cache def __load_override_adobe_glyphs(): - file_name = os.path.join("entries", "adobe", "override_glyphs.json") + file_name = os.path.join( + "entries", "adobe", "override_glyphs.json") json_data = __load_json(file_name) data = {} for key, val in json_data.items(): diff --git a/bot/entries/daijirin2.py b/bot/entries/daijirin2.py index 196bd0c..f7a629c 100644 --- a/bot/entries/daijirin2.py +++ b/bot/entries/daijirin2.py @@ -2,7 +2,7 @@ from bs4 import BeautifulSoup import bot.entries.expressions as Expressions import bot.soup as Soup -from bot.data import load_daijirin2_phrase_readings +from bot.data import load_phrase_readings from bot.data import load_daijirin2_kana_abbreviations from bot.entries.entry import Entry from bot.entries.daijirin2_preprocess import preprocess_page @@ -221,7 +221,7 @@ class Daijirin2PhraseEntry(_BaseDaijirin2Entry): return expressions def _find_readings(self): - phrase_readings = load_daijirin2_phrase_readings() + phrase_readings = load_phrase_readings(self.target) text = phrase_readings[self.entry_id] alternatives = Expressions.expand_daijirin_alternatives(text) readings = [] diff --git a/bot/entries/factory.py b/bot/entries/factory.py index a3dec69..162c102 100644 --- a/bot/entries/factory.py +++ b/bot/entries/factory.py @@ -5,6 +5,7 @@ from bot.entries.jitenon import JitenonYojiEntry from bot.entries.jitenon import JitenonKotowazaEntry from bot.entries.smk8 import Smk8Entry from bot.entries.daijirin2 import Daijirin2Entry +from bot.entries.sankoku8 import Sankoku8Entry def new_entry(target, page_id): @@ -14,5 +15,6 @@ def new_entry(target, page_id): Targets.JITENON_KOTOWAZA: JitenonKotowazaEntry, Targets.SMK8: Smk8Entry, Targets.DAIJIRIN2: Daijirin2Entry, + Targets.SANKOKU8: Sankoku8Entry, } return entry_map[target](target, page_id) diff --git a/bot/entries/sankoku8.py b/bot/entries/sankoku8.py new file mode 100644 index 0000000..9653f68 --- /dev/null +++ b/bot/entries/sankoku8.py @@ -0,0 +1,260 @@ +from bs4 import BeautifulSoup +import bot.entries.expressions as Expressions +import bot.soup as Soup +from bot.entries.entry import Entry +from bot.data import load_phrase_readings +from bot.entries.sankoku8_preprocess import preprocess_page + + +class _BaseSankoku8Entry(Entry): + def __init__(self, target, entry_id): + super().__init__(target, entry_id) + self.children = [] + self.phrases = [] + self._hyouki_name = "表記" + self._midashi_name = None + self._midashi_kana_name = None + + def get_global_identifier(self): + parent_part = format(self.entry_id[0], '06') + child_part = hex(self.entry_id[1]).lstrip('0x').zfill(4).upper() + return f"@{self.target.value}-{parent_part}-{child_part}" + + def set_page(self, page): + page = self.__decompose_subentries(page) + self._page = page + + def get_page_soup(self): + soup = BeautifulSoup(self._page, "xml") + return soup + + def _get_headwords(self): + soup = self.get_page_soup() + self._delete_unused_nodes(soup) + readings = self._find_readings(soup) + expressions = self._find_expressions(soup) + headwords = {} + for reading in readings: + headwords[reading] = [] + if len(readings) == 1: + reading = readings[0] + if soup.find(self._midashi_name).find(self._hyouki_name) is None: + headwords[reading].append(reading) + for exp in expressions: + if exp not in headwords[reading]: + headwords[reading].append(exp) + elif len(readings) > 1 and len(expressions) == 0: + for reading in readings: + headwords[reading].append(reading) + elif len(readings) > 1 and len(expressions) == 1: + if soup.find(self._midashi_name).find(self._hyouki_name) is None: + for reading in readings: + headwords[reading].append(reading) + expression = expressions[0] + for reading in readings: + if expression not in headwords[reading]: + headwords[reading].append(expression) + elif len(readings) > 1 and len(expressions) == len(readings): + if soup.find(self._midashi_name).find(self._hyouki_name) is None: + for reading in readings: + headwords[reading].append(reading) + for idx, reading in enumerate(readings): + exp = expressions[idx] + if exp not in headwords[reading]: + headwords[reading].append(exp) + else: + raise Exception() # shouldn't happen + return headwords + + def _add_variant_expressions(self, headwords): + for expressions in headwords.values(): + Expressions.add_variant_kanji(expressions) + Expressions.add_fullwidth(expressions) + Expressions.remove_iteration_mark(expressions) + Expressions.add_iteration_mark(expressions) + + def get_part_of_speech_tags(self): + if self._part_of_speech_tags is not None: + return self._part_of_speech_tags + self._part_of_speech_tags = [] + soup = self.get_page_soup() + for midashi in soup.find_all([self._midashi_name, "見出部要素"]): + pos_group = midashi.find("品詞G") + if pos_group is None: + continue + for tag in pos_group.find_all("a"): + if tag.text not in self._part_of_speech_tags: + self._part_of_speech_tags.append(tag.text) + return self._part_of_speech_tags + + def _find_expressions(self, soup): + expressions = [] + for hyouki in soup.find_all(self._hyouki_name): + for expression in parse_hyouki_soup(hyouki, [""]): + expressions.append(expression) + return expressions + + def _find_readings(self, soup): + midasi_kana = soup.find(self._midashi_kana_name) + readings = parse_hyouki_soup(midasi_kana, [""]) + return readings + + def __decompose_subentries(self, page): + soup = BeautifulSoup(page, features="xml") + subentry_parameters = [ + [Sankoku8ChildEntry, ["子項目"], self.children], + [Sankoku8PhraseEntry, ["句項目"], self.phrases], + ] + for x in subentry_parameters: + subentry_class, tags, subentry_list = x + for tag in tags: + tag_soup = soup.find(tag) + while tag_soup is not None: + tag_soup.name = "項目" + subentry_id = self.id_string_to_entry_id(tag_soup.attrs["id"]) + self.SUBENTRY_ID_TO_ENTRY_ID[subentry_id] = self.entry_id + subentry = subentry_class(self.target, subentry_id) + page = tag_soup.decode() + subentry.set_page(page) + subentry_list.append(subentry) + tag_soup.decompose() + tag_soup = soup.find(tag) + return soup.decode() + + @staticmethod + def id_string_to_entry_id(id_string): + parts = id_string.split("-") + if len(parts) == 1: + return (int(parts[0]), 0) + elif len(parts) == 2: + # subentries have a hexadecimal part + return (int(parts[0]), int(parts[1], 16)) + else: + raise Exception(f"Invalid entry ID: {id_string}") + + @staticmethod + def _delete_unused_nodes(soup): + """Remove extra markup elements that appear in the entry + headword line which are not part of the entry headword""" + unused_nodes = [ + "語構成", "平板", "アクセント", "表外字マーク", "表外音訓マーク", + "アクセント分節", "活用分節", "ルビG", "分書" + ] + for name in unused_nodes: + Soup.delete_soup_nodes(soup, name) + + +class Sankoku8Entry(_BaseSankoku8Entry): + def __init__(self, target, page_id): + entry_id = (page_id, 0) + super().__init__(target, entry_id) + self._midashi_name = "見出部" + self._midashi_kana_name = "見出仮名" + + def set_page(self, page): + page = preprocess_page(page) + super().set_page(page) + + +class Sankoku8ChildEntry(_BaseSankoku8Entry): + def __init__(self, target, page_id): + super().__init__(target, page_id) + self._midashi_name = "子見出部" + self._midashi_kana_name = "子見出仮名" + + +class Sankoku8PhraseEntry(_BaseSankoku8Entry): + def get_part_of_speech_tags(self): + # phrases do not contain these tags + return [] + + def _get_headwords(self): + soup = self.get_page_soup() + self._delete_unused_nodes(soup) + expressions = self._find_expressions(soup) + readings = self._find_readings(soup) + headwords = {} + if len(expressions) != len(readings): + raise Exception(f"{self.entry_id[0]}-{self.entry_id[1]}") + for idx, expression in enumerate(expressions): + reading = readings[idx] + if reading in headwords: + headwords[reading].append(expression) + else: + headwords[reading] = [expression] + return headwords + + def _find_expressions(self, soup): + phrase_soup = soup.find("句表記") + expressions = parse_hyouki_soup(phrase_soup, [""]) + return expressions + + def _find_readings(self, soup): + reading_patterns = load_phrase_readings(self.target) + reading_pattern = reading_patterns[self.entry_id] + readings = parse_hyouki_pattern(reading_pattern) + return readings + + +def parse_hyouki_soup(soup, base_exps): + omitted_characters = [ + "/", "〈", "〉", "(", ")", "⦅", "⦆", ":", "…" + ] + exps = base_exps.copy() + for child in soup.children: + new_exps = [] + if child.name == "言換G": + for alt in child.find_all("言換"): + parts = parse_hyouki_soup(alt, [""]) + for exp in exps: + for part in parts: + new_exps.append(exp + part) + elif child.name == "補足表記": + alt1 = child.find("表記対象") + alt2 = child.find("表記内容G") + parts1 = parse_hyouki_soup(alt1, [""]) + parts2 = parse_hyouki_soup(alt2, [""]) + for exp in exps: + for part in parts1: + new_exps.append(exp + part) + for part in parts2: + new_exps.append(exp + part) + elif child.name == "省略": + parts = parse_hyouki_soup(child, [""]) + for exp in exps: + new_exps.append(exp) + for part in parts: + new_exps.append(exp + part) + elif child.name is not None: + new_exps = parse_hyouki_soup(child, exps) + else: + text = child.text + for char in omitted_characters: + text = text.replace(char, "") + for exp in exps: + new_exps.append(exp + text) + exps = new_exps.copy() + return exps + + +def parse_hyouki_pattern(pattern): + replacements = { + "(": "<省略>(", + ")": ")", + "{": "<補足表記><表記対象>", + "・": "<表記内容G>(<表記内容>", + "}": "", + "〈": "<言換G>〈<言換>", + "/": "/<言換>", + "〉": "", + "⦅": "<補足表記><表記対象>", + "\": "<表記内容G>⦅<表記内容>", + "⦆": "", + } + markup = f"{pattern}" + for key, val in replacements.items(): + markup = markup.replace(key, val) + soup = BeautifulSoup(markup, "xml") + hyouki_soup = soup.find("span") + exps = parse_hyouki_soup(hyouki_soup, [""]) + return exps diff --git a/bot/entries/sankoku8_preprocess.py b/bot/entries/sankoku8_preprocess.py new file mode 100644 index 0000000..73fb31a --- /dev/null +++ b/bot/entries/sankoku8_preprocess.py @@ -0,0 +1,28 @@ +import re +from bs4 import BeautifulSoup + +from bot.data import get_adobe_glyph + + +def preprocess_page(page): + soup = BeautifulSoup(page, features="xml") + __replace_glyph_codes(soup) + page = __strip_page(soup) + return page + + +def __replace_glyph_codes(soup): + for el in soup.find_all("glyph"): + m = re.search(r"^glyph:([0-9]+);?$", el.attrs["style"]) + code = int(m.group(1)) + for geta in el.find_all(string="〓"): + glyph = get_adobe_glyph(code) + geta.replace_with(glyph) + + +def __strip_page(soup): + koumoku = soup.find(["項目"]) + if koumoku is not None: + return koumoku.decode() + else: + raise Exception(f"Primary 項目 not found in page:\n{soup.prettify()}") diff --git a/bot/entries/smk8.py b/bot/entries/smk8.py index 2308893..2d43e4a 100644 --- a/bot/entries/smk8.py +++ b/bot/entries/smk8.py @@ -2,7 +2,7 @@ from bs4 import BeautifulSoup import bot.entries.expressions as Expressions import bot.soup as Soup -from bot.data import load_smk8_phrase_readings +from bot.data import load_phrase_readings from bot.entries.entry import Entry from bot.entries.smk8_preprocess import preprocess_page @@ -163,7 +163,7 @@ class Smk8ChildEntry(_BaseSmk8Entry): class Smk8PhraseEntry(_BaseSmk8Entry): def __init__(self, target, entry_id): super().__init__(target, entry_id) - self.__phrase_readings = load_smk8_phrase_readings() + self.__phrase_readings = load_phrase_readings(self.target) def get_part_of_speech_tags(self): # phrases do not contain these tags diff --git a/bot/mdict/exporters/export.py b/bot/mdict/exporters/export.py index c100f67..b8e8347 100644 --- a/bot/mdict/exporters/export.py +++ b/bot/mdict/exporters/export.py @@ -218,3 +218,8 @@ class Smk8Exporter(_MonokakidoExporter): class Daijirin2Exporter(_MonokakidoExporter): def _get_attribution(self, entries): return "© Sanseido Co., LTD. 2019" + + +class Sankoku8Exporter(_MonokakidoExporter): + def _get_attribution(self, entries): + return "© Sanseido Co., LTD. 2021" diff --git a/bot/mdict/exporters/factory.py b/bot/mdict/exporters/factory.py index 2c2015c..5417493 100644 --- a/bot/mdict/exporters/factory.py +++ b/bot/mdict/exporters/factory.py @@ -5,6 +5,7 @@ from bot.mdict.exporters.export import JitenonYojiExporter from bot.mdict.exporters.export import JitenonKotowazaExporter from bot.mdict.exporters.export import Smk8Exporter from bot.mdict.exporters.export import Daijirin2Exporter +from bot.mdict.exporters.export import Sankoku8Exporter def new_mdict_exporter(target): @@ -14,5 +15,6 @@ def new_mdict_exporter(target): Targets.JITENON_KOTOWAZA: JitenonKotowazaExporter, Targets.SMK8: Smk8Exporter, Targets.DAIJIRIN2: Daijirin2Exporter, + Targets.SANKOKU8: Sankoku8Exporter, } return exporter_map[target](target) diff --git a/bot/mdict/glossary/sankoku8.py b/bot/mdict/glossary/sankoku8.py new file mode 100644 index 0000000..8ee16f0 --- /dev/null +++ b/bot/mdict/glossary/sankoku8.py @@ -0,0 +1,137 @@ +import re +from bs4 import BeautifulSoup +from bot.data import load_mdict_name_conversion +from bot.name_conversion import convert_names + + +def make_glossary(entry, media_dir): + soup = entry.get_page_soup() + __reposition_marks(soup) + __remove_appendix_links(soup) + __convert_images(soup) + __remove_links_without_href(soup) + __convert_links(soup, entry) + __add_parent_link(soup, entry) + __add_homophone_links(soup, entry) + + name_conversion = load_mdict_name_conversion(entry.target) + convert_names(soup, name_conversion) + + glossary = soup.span.decode() + return glossary + + +def __reposition_marks(soup): + """These 表外字マーク symbols will be converted to rubies later, so they need to + be positioned after the corresponding text in order to appear correctly""" + for elm in soup.find_all("表外字"): + mark = elm.find("表外字マーク") + elm.append(mark) + for elm in soup.find_all("表外音訓"): + mark = elm.find("表外音訓マーク") + elm.append(mark) + + +def __remove_appendix_links(soup): + """This info would be useful and nice to have, but jitenbot currently + isn't designed to fetch and process these appendix files. It probably + wouldn't be possible to include them in Yomichan, but it would definitely + be possible for Mdict.""" + for elm in soup.find_all("a"): + if not elm.has_attr("href"): + continue + if elm.attrs["href"].startswith("appendix"): + elm.attrs["data-name"] = "a" + elm.attrs["data-href"] = elm.attrs["href"] + elm.name = "span" + del elm.attrs["href"] + + +def __convert_images(soup): + conversions = [ + ["svg-logo/重要語.svg", "*"], + ["svg-logo/最重要語.svg", "**"], + ["svg-logo/一般常識語.svg", "☆☆"], + ["svg-logo/追い込み.svg", ""], + ["svg-special/区切り線.svg", "|"], + ["svg-accent/平板.svg", "⎺"], + ["svg-accent/アクセント.svg", "⌝"], + ["svg-logo/アク.svg", "アク"], + ["svg-logo/丁寧.svg", "丁寧"], + ["svg-logo/可能.svg", "可能"], + ["svg-logo/尊敬.svg", "尊敬"], + ["svg-logo/接尾.svg", "接尾"], + ["svg-logo/接頭.svg", "接頭"], + ["svg-logo/表記.svg", "表記"], + ["svg-logo/謙譲.svg", "謙譲"], + ["svg-logo/区別.svg", "区別"], + ["svg-logo/由来.svg", "由来"], + ] + for conversion in conversions: + filename, text = conversion + for elm in soup.find_all("img", attrs={"src": filename}): + elm.attrs["data-name"] = elm.name + elm.attrs["data-src"] = elm.attrs["src"] + elm.name = "span" + elm.string = text + del elm.attrs["src"] + + +def __remove_links_without_href(soup): + for elm in soup.find_all("a"): + if elm.has_attr("href"): + continue + elm.attrs["data-name"] = elm.name + elm.name = "span" + + +def __convert_links(soup, entry): + for elm in soup.find_all("a"): + href = elm.attrs["href"].split(" ")[0] + if re.match(r"^#?[0-9]+(?:-[0-9A-F]{4})?$", href): + href = href.removeprefix("#") + ref_entry_id = entry.id_string_to_entry_id(href) + if ref_entry_id in entry.ID_TO_ENTRY: + ref_entry = entry.ID_TO_ENTRY[ref_entry_id] + else: + ref_entry = entry.ID_TO_ENTRY[(ref_entry_id[0], 0)] + gid = ref_entry.get_global_identifier() + elm.attrs["href"] = f"entry://{gid}" + elif re.match(r"^entry:", href): + pass + elif re.match(r"^https?:[\w\W]*", href): + pass + else: + raise Exception(f"Invalid href format: {href}") + + +def __add_parent_link(soup, entry): + elm = soup.find("親見出相当部") + if elm is not None: + parent_entry = entry.get_parent() + gid = parent_entry.get_global_identifier() + elm.attrs["href"] = f"entry://{gid}" + elm.attrs["data-name"] = elm.name + elm.name = "a" + + +def __add_homophone_links(soup, entry): + forward_link = ["←", entry.entry_id[0] + 1] + backward_link = ["→", entry.entry_id[0] - 1] + homophone_info_list = [ + ["svg-logo/homophone1.svg", [forward_link]], + ["svg-logo/homophone2.svg", [forward_link, backward_link]], + ["svg-logo/homophone3.svg", [backward_link]], + ] + for homophone_info in homophone_info_list: + filename, link_info = homophone_info + for elm in soup.find_all("img", attrs={"src": filename}): + for info in link_info: + text, link_id = info + link_entry = entry.ID_TO_ENTRY[(link_id, 0)] + gid = link_entry.get_global_identifier() + link = BeautifulSoup("", "xml").a + link.string = text + link.attrs["href"] = f"entry://{gid}" + elm.append(link) + elm.unwrap() diff --git a/bot/mdict/terms/factory.py b/bot/mdict/terms/factory.py index 78a05cd..8cee8e7 100644 --- a/bot/mdict/terms/factory.py +++ b/bot/mdict/terms/factory.py @@ -5,6 +5,7 @@ from bot.mdict.terms.jitenon import JitenonYojiTerminator from bot.mdict.terms.jitenon import JitenonKotowazaTerminator from bot.mdict.terms.smk8 import Smk8Terminator from bot.mdict.terms.daijirin2 import Daijirin2Terminator +from bot.mdict.terms.sankoku8 import Sankoku8Terminator def new_terminator(target): @@ -14,5 +15,6 @@ def new_terminator(target): Targets.JITENON_KOTOWAZA: JitenonKotowazaTerminator, Targets.SMK8: Smk8Terminator, Targets.DAIJIRIN2: Daijirin2Terminator, + Targets.SANKOKU8: Sankoku8Terminator, } return terminator_map[target](target) diff --git a/bot/mdict/terms/sankoku8.py b/bot/mdict/terms/sankoku8.py new file mode 100644 index 0000000..5c1bfb7 --- /dev/null +++ b/bot/mdict/terms/sankoku8.py @@ -0,0 +1,23 @@ +from bot.mdict.terms.terminator import Terminator +from bot.mdict.glossary.sankoku8 import make_glossary + + +class Sankoku8Terminator(Terminator): + def _glossary(self, entry): + if entry.entry_id in self._glossary_cache: + return self._glossary_cache[entry.entry_id] + glossary = make_glossary(entry, self._media_dir) + self._glossary_cache[entry.entry_id] = glossary + return glossary + + def _link_glossary_parameters(self, entry): + return [ + [entry.children, "子項目"], + [entry.phrases, "句項目"], + ] + + def _subentry_lists(self, entry): + return [ + entry.children, + entry.phrases, + ] diff --git a/bot/targets.py b/bot/targets.py index 3c4c571..157786f 100644 --- a/bot/targets.py +++ b/bot/targets.py @@ -7,3 +7,4 @@ class Targets(Enum): JITENON_KOTOWAZA = "jitenon-kotowaza" SMK8 = "smk8" DAIJIRIN2 = "daijirin2" + SANKOKU8 = "sankoku8" diff --git a/bot/yomichan/exporters/export.py b/bot/yomichan/exporters/export.py index 8665bfe..d348fed 100644 --- a/bot/yomichan/exporters/export.py +++ b/bot/yomichan/exporters/export.py @@ -3,13 +3,16 @@ import json import os import shutil +import copy from pathlib import Path from datetime import datetime from abc import ABC, abstractmethod from platformdirs import user_documents_dir, user_cache_dir +import fastjsonschema from bot.data import load_yomichan_metadata from bot.yomichan.terms.factory import new_terminator +from bot.data import load_yomichan_term_schema class Exporter(ABC): @@ -19,7 +22,7 @@ class Exporter(ABC): self._build_dir = None self._terms_per_file = 2000 - def export(self, entries, image_dir): + def export(self, entries, image_dir, validate): self.__init_build_image_dir(image_dir) meta = load_yomichan_metadata() index = meta[self._target.value]["index"] @@ -27,6 +30,8 @@ class Exporter(ABC): index["attribution"] = self._get_attribution(entries) tags = meta[self._target.value]["tags"] terms = self.__get_terms(entries) + if validate: + self.__validate_terms(terms) self.__make_dictionary(terms, index, tags) @abstractmethod @@ -49,6 +54,14 @@ class Exporter(ABC): self._build_dir = build_directory return self._build_dir + def __get_invalid_term_dir(self): + cache_dir = user_cache_dir("jitenbot") + log_dir = os.path.join(cache_dir, "invalid_yomichan_terms") + if Path(log_dir).is_dir(): + shutil.rmtree(log_dir) + os.makedirs(log_dir) + return log_dir + def __init_build_image_dir(self, image_dir): build_dir = self._get_build_dir() build_img_dir = os.path.join(build_dir, self._target.value) @@ -71,8 +84,29 @@ class Exporter(ABC): print() return terms + def __validate_terms(self, terms): + print("Making a copy of term data for validation...") + terms_copy = copy.deepcopy(terms) # because validator will alter data! + term_count = len(terms_copy) + log_dir = self.__get_invalid_term_dir() + schema = load_yomichan_term_schema() + validator = fastjsonschema.compile(schema) + failure_count = 0 + for idx, term in enumerate(terms_copy): + update = f"Validating term {idx+1}/{term_count}" + print(update, end='\r', flush=True) + try: + validator([term]) + except fastjsonschema.JsonSchemaException: + failure_count += 1 + term_file = os.path.join(log_dir, f"{idx}.json") + with open(term_file, "w", encoding='utf8') as f: + json.dump([term], f, indent=4, ensure_ascii=False) + print(f"\nFinished validating with {failure_count} error{'' if failure_count == 1 else 's'}") + if failure_count > 0: + print(f"Invalid terms saved to `{log_dir}` for debugging") + def __make_dictionary(self, terms, index, tags): - print(f"Exporting {len(terms)} Yomichan terms...") self.__write_term_banks(terms) self.__write_index(index) self.__write_tag_bank(tags) @@ -80,14 +114,18 @@ class Exporter(ABC): self.__rm_build_dir() def __write_term_banks(self, terms): + print(f"Exporting {len(terms)} JSON terms") build_dir = self._get_build_dir() max_i = int(len(terms) / self._terms_per_file) + 1 for i in range(max_i): + start = self._terms_per_file * i + end = self._terms_per_file * (i + 1) + update = f"Writing terms to term banks {start} - {end}" + print(update, end='\r', flush=True) term_file = os.path.join(build_dir, f"term_bank_{i+1}.json") with open(term_file, "w", encoding='utf8') as f: - start = self._terms_per_file * i - end = self._terms_per_file * (i + 1) json.dump(terms[start:end], f, indent=4, ensure_ascii=False) + print() def __write_index(self, index): build_dir = self._get_build_dir() @@ -104,6 +142,7 @@ class Exporter(ABC): json.dump(tags, f, indent=4, ensure_ascii=False) def __write_archive(self, filename): + print("Archiving data to ZIP file...") archive_format = "zip" out_dir = os.path.join(user_documents_dir(), "jitenbot", "yomichan") if not Path(out_dir).is_dir(): @@ -151,19 +190,22 @@ class JitenonKotowazaExporter(_JitenonExporter): pass -class Smk8Exporter(Exporter): +class _MonokakidoExporter(Exporter): def _get_revision(self, entries): timestamp = datetime.now().strftime("%Y-%m-%d") return f"{self._target.value};{timestamp}" + +class Smk8Exporter(_MonokakidoExporter): def _get_attribution(self, entries): return "© Sanseido Co., LTD. 2020" -class Daijirin2Exporter(Exporter): - def _get_revision(self, entries): - timestamp = datetime.now().strftime("%Y-%m-%d") - return f"{self._target.value};{timestamp}" - +class Daijirin2Exporter(_MonokakidoExporter): def _get_attribution(self, entries): return "© Sanseido Co., LTD. 2019" + + +class Sankoku8Exporter(_MonokakidoExporter): + def _get_attribution(self, entries): + return "© Sanseido Co., LTD. 2021" diff --git a/bot/yomichan/exporters/factory.py b/bot/yomichan/exporters/factory.py index 06568e3..afed7fd 100644 --- a/bot/yomichan/exporters/factory.py +++ b/bot/yomichan/exporters/factory.py @@ -5,6 +5,7 @@ from bot.yomichan.exporters.export import JitenonYojiExporter from bot.yomichan.exporters.export import JitenonKotowazaExporter from bot.yomichan.exporters.export import Smk8Exporter from bot.yomichan.exporters.export import Daijirin2Exporter +from bot.yomichan.exporters.export import Sankoku8Exporter def new_yomi_exporter(target): @@ -14,5 +15,6 @@ def new_yomi_exporter(target): Targets.JITENON_KOTOWAZA: JitenonKotowazaExporter, Targets.SMK8: Smk8Exporter, Targets.DAIJIRIN2: Daijirin2Exporter, + Targets.SANKOKU8: Sankoku8Exporter, } return exporter_map[target](target) diff --git a/bot/yomichan/glossary/icons.py b/bot/yomichan/glossary/icons.py index 2e83821..4cf46c2 100644 --- a/bot/yomichan/glossary/icons.py +++ b/bot/yomichan/glossary/icons.py @@ -26,6 +26,27 @@ def make_monochrome_fill_rectangle(path, text): f.write(svg) +@cache +def make_accent(path): + svg = __svg_accent() + with open(path, "w", encoding="utf-8") as f: + f.write(svg) + + +@cache +def make_heiban(path): + svg = __svg_heiban() + with open(path, "w", encoding="utf-8") as f: + f.write(svg) + + +@cache +def make_red_char(path, char): + svg = __svg_red_character(char) + with open(path, "w", encoding="utf-8") as f: + f.write(svg) + + def __calculate_svg_ratio(path): with open(path, "r", encoding="utf-8") as f: xml = f.read() @@ -82,3 +103,30 @@ def __svg_masked_rectangle(text): fill='black' mask='url(#a)'/> """ return svg.strip() + + +def __svg_heiban(): + svg = f""" + + +""" + return svg.strip() + + +def __svg_accent(): + svg = f""" + + + +""" + return svg.strip() + + +def __svg_red_character(char): + svg = f""" + + {char} +""" + return svg.strip() diff --git a/bot/yomichan/glossary/sankoku8.py b/bot/yomichan/glossary/sankoku8.py new file mode 100644 index 0000000..5501381 --- /dev/null +++ b/bot/yomichan/glossary/sankoku8.py @@ -0,0 +1,344 @@ +import re +import os +from bs4 import BeautifulSoup + +import bot.yomichan.glossary.icons as Icons +from bot.data import load_yomichan_name_conversion +from bot.yomichan.glossary.gloss import make_gloss +from bot.name_conversion import convert_names + + +def make_glossary(entry, media_dir): + soup = entry.get_page_soup() + __remove_glyph_styles(soup) + __reposition_marks(soup) + __remove_links_without_href(soup) + __remove_appendix_links(soup) + __convert_links(soup, entry) + __add_parent_link(soup, entry) + __add_homophone_links(soup, entry) + __convert_images_to_text(soup) + __text_parens_to_images(soup, media_dir) + __replace_icons(soup, media_dir) + __replace_accent_symbols(soup, media_dir) + __convert_gaiji(soup, media_dir) + __convert_graphics(soup, media_dir) + __convert_number_icons(soup, media_dir) + + name_conversion = load_yomichan_name_conversion(entry.target) + convert_names(soup, name_conversion) + + gloss = make_gloss(soup.span) + glossary = [gloss] + return glossary + + +def __remove_glyph_styles(soup): + """The css_parser library will emit annoying warning messages + later if it sees these glyph character styles""" + for elm in soup.find_all("glyph"): + if elm.has_attr("style"): + elm["data-style"] = elm.attrs["style"] + del elm.attrs["style"] + + +def __reposition_marks(soup): + """These マーク symbols will be converted to rubies later, so they need to + be positioned after the corresponding text in order to appear correctly""" + for elm in soup.find_all("表外字"): + mark = elm.find("表外字マーク") + elm.append(mark) + for elm in soup.find_all("表外音訓"): + mark = elm.find("表外音訓マーク") + elm.append(mark) + + +def __remove_links_without_href(soup): + for elm in soup.find_all("a"): + if elm.has_attr("href"): + continue + elm.attrs["data-name"] = elm.name + elm.name = "span" + + +def __remove_appendix_links(soup): + for elm in soup.find_all("a"): + if elm.attrs["href"].startswith("appendix"): + elm.unwrap() + + +def __convert_links(soup, entry): + for elm in soup.find_all("a"): + href = elm.attrs["href"].split(" ")[0] + href = href.removeprefix("#") + if not re.match(r"^[0-9]+(?:-[0-9A-F]{4})?$", href): + raise Exception(f"Invalid href format: {href}") + ref_entry_id = entry.id_string_to_entry_id(href) + if ref_entry_id in entry.ID_TO_ENTRY: + ref_entry = entry.ID_TO_ENTRY[ref_entry_id] + else: + ref_entry = entry.ID_TO_ENTRY[(ref_entry_id[0], 0)] + expression = ref_entry.get_first_expression() + elm.attrs["href"] = f"?query={expression}&wildcards=off" + + +def __add_parent_link(soup, entry): + elm = soup.find("親見出相当部") + if elm is not None: + parent_entry = entry.get_parent() + expression = parent_entry.get_first_expression() + elm.attrs["href"] = f"?query={expression}&wildcards=off" + elm.name = "a" + + +def __add_homophone_links(soup, entry): + forward_link = ["←", entry.entry_id[0] + 1] + backward_link = ["→", entry.entry_id[0] - 1] + homophone_info_list = [ + ["svg-logo/homophone1.svg", [forward_link]], + ["svg-logo/homophone2.svg", [forward_link, backward_link]], + ["svg-logo/homophone3.svg", [backward_link]], + ] + for homophone_info in homophone_info_list: + filename, link_info = homophone_info + for elm in soup.find_all("img", attrs={"src": filename}): + for info in link_info: + text, link_id = info + link_entry = entry.ID_TO_ENTRY[(link_id, 0)] + expression = link_entry.get_first_expression() + link = BeautifulSoup("", "xml").a + link.string = text + link.attrs["href"] = f"?query={expression}&wildcards=off" + elm.append(link) + elm.unwrap() + + +def __convert_images_to_text(soup): + conversions = [ + ["svg-logo/重要語.svg", "*", "vertical-align: super; font-size: 0.6em"], + ["svg-logo/最重要語.svg", "**", "vertical-align: super; font-size: 0.6em"], + ["svg-logo/一般常識語.svg", "☆☆", "vertical-align: super; font-size: 0.6em"], + ["svg-logo/追い込み.svg", "", ""], + ["svg-special/区切り線.svg", "|", ""], + ] + for conversion in conversions: + filename, text, style = conversion + for elm in soup.find_all("img", attrs={"src": filename}): + if text == "": + elm.unwrap() + continue + if style != "": + elm.attrs["style"] = style + elm.attrs["data-name"] = elm.name + elm.attrs["data-src"] = elm.attrs["src"] + elm.name = "span" + elm.string = text + del elm.attrs["src"] + + +def __text_parens_to_images(soup, media_dir): + for elm in soup.find_all("red"): + char = elm.text + if char not in ["(", ")"]: + continue + filename = f"red_{char}.svg" + path = os.path.join(media_dir, filename) + Icons.make_red_char(path, char) + ratio = Icons.calculate_ratio(path) + img = BeautifulSoup("", "xml").img + img.attrs = { + "height": 1.0, + "width": ratio, + "sizeUnits": "em", + "collapsible": False, + "collapsed": False, + "background": False, + "appearance": "auto", + "path": f"{os.path.basename(media_dir)}/{filename}", + } + elm.attrs["data-name"] = elm.name + elm.name = "span" + elm.string = "" + elm.append(img) + elm.attrs["style"] = "vertical-align: text-bottom;" + + +def __replace_icons(soup, media_dir): + cls_to_appearance = { + "default": "monochrome", + "fill": "monochrome", + "red": "auto", + "redfill": "auto", + "none": "monochrome", + } + icon_info_list = [ + ["svg-logo/アク.svg", "アク", "default"], + ["svg-logo/丁寧.svg", "丁寧", "default"], + ["svg-logo/可能.svg", "可能", "default"], + ["svg-logo/尊敬.svg", "尊敬", "default"], + ["svg-logo/接尾.svg", "接尾", "default"], + ["svg-logo/接頭.svg", "接頭", "default"], + ["svg-logo/表記.svg", "表記", "default"], + ["svg-logo/謙譲.svg", "謙譲", "default"], + ["svg-logo/区別.svg", "区別", "redfill"], + ["svg-logo/由来.svg", "由来", "redfill"], + ["svg-logo/人.svg", "", "none"], + ["svg-logo/他.svg", "", "none"], + ["svg-logo/動.svg", "", "none"], + ["svg-logo/名.svg", "", "none"], + ["svg-logo/句.svg", "", "none"], + ["svg-logo/派.svg", "", "none"], + ["svg-logo/自.svg", "", "none"], + ["svg-logo/連.svg", "", "none"], + ["svg-logo/造.svg", "", "none"], + ["svg-logo/造2.svg", "", "none"], + ["svg-logo/造3.svg", "", "none"], + ["svg-logo/百科.svg", "", "none"], + ] + for icon_info in icon_info_list: + src, text, cls = icon_info + for elm in soup.find_all("img", attrs={"src": src}): + path = media_dir + for part in src.split("/"): + path = os.path.join(path, part) + __make_rectangle(path, text, cls) + ratio = Icons.calculate_ratio(path) + img = BeautifulSoup("", "xml").img + img.attrs = { + "height": 1.0, + "width": ratio, + "sizeUnits": "em", + "collapsible": False, + "collapsed": False, + "background": False, + "appearance": cls_to_appearance[cls], + "title": elm.attrs["alt"] if elm.has_attr("alt") else "", + "path": f"{os.path.basename(media_dir)}/{src}", + } + elm.name = "span" + elm.clear() + elm.append(img) + elm.attrs["style"] = "vertical-align: text-bottom; margin-right: 0.25em;" + + +def __replace_accent_symbols(soup, media_dir): + accent_info_list = [ + ["svg-accent/平板.svg", Icons.make_heiban], + ["svg-accent/アクセント.svg", Icons.make_accent], + ] + for info in accent_info_list: + src, write_svg_function = info + for elm in soup.find_all("img", attrs={"src": src}): + path = media_dir + for part in src.split("/"): + path = os.path.join(path, part) + write_svg_function(path) + ratio = Icons.calculate_ratio(path) + img = BeautifulSoup("", "xml").img + img.attrs = { + "height": 1.0, + "width": ratio, + "sizeUnits": "em", + "collapsible": False, + "collapsed": False, + "background": False, + "appearance": "auto", + "path": f"{os.path.basename(media_dir)}/{src}", + } + elm.name = "span" + elm.clear() + elm.append(img) + elm.attrs["style"] = "vertical-align: text-bottom;" + + +def __convert_gaiji(soup, media_dir): + for elm in soup.find_all("img"): + if not elm.has_attr("src"): + continue + src = elm.attrs["src"] + if src.startswith("graphics"): + continue + path = media_dir + for part in src.split("/"): + if part.strip() == "": + continue + path = os.path.join(path, part) + ratio = Icons.calculate_ratio(path) + img = BeautifulSoup("", "xml").img + img.attrs = { + "height": 1.0, + "width": ratio, + "sizeUnits": "em", + "collapsible": False, + "collapsed": False, + "background": False, + "appearance": "monochrome", + "title": elm.attrs["alt"] if elm.has_attr("alt") else "", + "path": f"{os.path.basename(media_dir)}/{src}", + } + elm.name = "span" + elm.clear() + elm.append(img) + elm.attrs["style"] = "vertical-align: text-bottom;" + + +def __convert_graphics(soup, media_dir): + for elm in soup.find_all("img"): + if not elm.has_attr("src"): + continue + src = elm.attrs["src"] + if not src.startswith("graphics"): + continue + elm.attrs = { + "collapsible": True, + "collapsed": True, + "title": elm.attrs["alt"] if elm.has_attr("alt") else "", + "path": f"{os.path.basename(media_dir)}/{src}", + "src": src, + } + + +def __convert_number_icons(soup, media_dir): + for elm in soup.find_all("大語義番号"): + if elm.find_parent("a") is None: + filename = f"{elm.text}-fill.svg" + appearance = "monochrome" + path = os.path.join(media_dir, filename) + __make_rectangle(path, elm.text, "fill") + else: + filename = f"{elm.text}-bluefill.svg" + appearance = "auto" + path = os.path.join(media_dir, filename) + __make_rectangle(path, elm.text, "bluefill") + ratio = Icons.calculate_ratio(path) + img = BeautifulSoup("", "xml").img + img.attrs = { + "height": 1.0, + "width": ratio, + "sizeUnits": "em", + "collapsible": False, + "collapsed": False, + "background": False, + "appearance": appearance, + "title": elm.text, + "path": f"{os.path.basename(media_dir)}/{filename}", + } + elm.name = "span" + elm.clear() + elm.append(img) + elm.attrs["style"] = "vertical-align: text-bottom; margin-right: 0.25em;" + + +def __make_rectangle(path, text, cls): + if cls == "none": + pass + elif cls == "fill": + Icons.make_monochrome_fill_rectangle(path, text) + elif cls == "red": + Icons.make_rectangle(path, text, "red", "white", "red") + elif cls == "redfill": + Icons.make_rectangle(path, text, "red", "red", "white") + elif cls == "bluefill": + Icons.make_rectangle(path, text, "blue", "blue", "white") + else: + Icons.make_rectangle(path, text, "black", "transparent", "black") diff --git a/bot/yomichan/terms/factory.py b/bot/yomichan/terms/factory.py index d3fc199..8c596cb 100644 --- a/bot/yomichan/terms/factory.py +++ b/bot/yomichan/terms/factory.py @@ -5,6 +5,7 @@ from bot.yomichan.terms.jitenon import JitenonYojiTerminator from bot.yomichan.terms.jitenon import JitenonKotowazaTerminator from bot.yomichan.terms.smk8 import Smk8Terminator from bot.yomichan.terms.daijirin2 import Daijirin2Terminator +from bot.yomichan.terms.sankoku8 import Sankoku8Terminator def new_terminator(target): @@ -14,5 +15,6 @@ def new_terminator(target): Targets.JITENON_KOTOWAZA: JitenonKotowazaTerminator, Targets.SMK8: Smk8Terminator, Targets.DAIJIRIN2: Daijirin2Terminator, + Targets.SANKOKU8: Sankoku8Terminator, } return terminator_map[target](target) diff --git a/bot/yomichan/terms/sankoku8.py b/bot/yomichan/terms/sankoku8.py new file mode 100644 index 0000000..613f3bb --- /dev/null +++ b/bot/yomichan/terms/sankoku8.py @@ -0,0 +1,47 @@ +from bot.entries.sankoku8 import Sankoku8PhraseEntry as PhraseEntry + +from bot.yomichan.terms.terminator import Terminator +from bot.yomichan.glossary.sankoku8 import make_glossary +from bot.yomichan.grammar import sudachi_rules, tags_to_rules + + +class Sankoku8Terminator(Terminator): + def __init__(self, target): + super().__init__(target) + + def _definition_tags(self, entry): + return "" + + def _inflection_rules(self, entry, expression): + if isinstance(entry, PhraseEntry): + return sudachi_rules(expression) + pos_tags = entry.get_part_of_speech_tags() + if len(pos_tags) == 0: + return sudachi_rules(expression) + else: + return tags_to_rules(expression, pos_tags, self._inflection_categories) + + def _glossary(self, entry): + if entry.entry_id in self._glossary_cache: + return self._glossary_cache[entry.entry_id] + glossary = make_glossary(entry, self._image_dir) + self._glossary_cache[entry.entry_id] = glossary + return glossary + + def _sequence(self, entry): + return entry.entry_id[0] * 100000 + entry.entry_id[1] + + def _term_tags(self, entry): + return "" + + def _link_glossary_parameters(self, entry): + return [ + [entry.children, "子"], + [entry.phrases, "句"] + ] + + def _subentry_lists(self, entry): + return [ + entry.children, + entry.phrases, + ] diff --git a/data/entries/sankoku8/phrase_readings.csv b/data/entries/sankoku8/phrase_readings.csv new file mode 100644 index 0000000..b9756f4 --- /dev/null +++ b/data/entries/sankoku8/phrase_readings.csv @@ -0,0 +1,3573 @@ +4,16385,ああいえばこういう +16,16385,アーチをかける +87,16385,あいくちがわるい +139,16385,あいそがつきる +139,16386,あいそも{こそ・こそ}もつきはてる +149,16385,あいだにたつ +161,16385,あいてにする +161,16386,あいてになる +214,16385,あいまをぬう +262,16385,あおはあいよりいでてあいよりあおし +293,16385,あおすじをたてる +302,16385,あおなにしお +342,16385,あがきがとれない +351,16385,あかごのてをひねるよう +365,16385,あかしんごうみんなでわたればこわくない +426,16385,あかるみにでる +435,16385,あきたつ +435,16386,あきのひはつるべおとし +440,16385,あきかぜがたつ +458,16385,あきないはうしのよだれ +460,16385,あきなすはよめにくわすな +485,16385,あくがつよい +489,16385,あいたくちがふさがらない +496,16385,あくうんがつよい +508,16385,あくじせんりをはしる +521,16385,あくじょのふかなさけ +536,16385,あくせんみにつかず +578,16385,あくむからさめる +587,16385,あぐらをかく +602,16385,あけにそまる +607,16385,あげあしをとる +664,16385,あごがひあがる +664,16386,あごでつかう +664,16387,あごをだす +664,16388,あごを{な・な}でる +664,16389,あごをはずす +674,16385,あさのごとくみだれる +746,16385,あしがある +746,16386,あしがちにつかない +746,16387,あしがつく +746,16388,あしがでる +746,16389,あしがとおのく +746,16390,あしがながい +746,16391,あしがはやい +746,16392,あしがはやい +746,16393,あしでかく +746,16394,あしでかせぐ +746,16395,あしをあらう +746,16396,あしをいれる +746,16397,あしを{すく・すく}われる +746,16398,あしをつかう +746,16399,あしをとられる +746,16400,あしをのばす +746,16401,あしをはこぶ +746,16402,あしをひっぱる +746,16403,あしをぼうにする +746,16404,あしをむけてねられない +746,16405,あしをむける +749,16385,あじもしゃしゃりもない +749,16386,あじもそっけもない +749,16387,あじをしめる +774,16385,あしげにする +790,16385,あしたはあしたのかぜがふく +821,16385,あしもとからとりがたつ +821,16386,あしもとがわるい +821,16387,あしもとにつけこむ +821,16388,あしもとにひがつく +821,16389,あしもとにもおよばない +821,16390,あしもとのあかるいうちに +821,16391,あしもとへもよりつけない +821,16392,あしもとを{すく・すく}われる +821,16393,あしもとをみる +837,16385,あすはわがみ +841,16385,あずかってちからがある +866,16385,あせになる +866,16386,あせをかく +866,16387,あせをながす +913,16385,あたいをとらせる +921,16385,あたかも{よ・よ}し +947,16385,あたまがあがらない +947,16386,あたまがいたい +947,16387,あたまかくしてしりかくさず +947,16388,あたまがさがる +947,16389,あたまがしろくなる +947,16390,あたまがひくい +947,16391,あたまがまわる +947,16392,あたまからゆげを〈たてる/だす〉 +947,16393,あたまがわく +947,16394,あたまがわれるよう +947,16395,あたまにくる +947,16396,あたまのうえの{はえ・はえ}もおえない +947,16397,あたまのくろい{ねずみ・ねずみ} +947,16398,あたま(のなか)がまっしろになる +947,16399,あたまひとつぬける +947,16400,あたまをいためる +947,16401,あたまをおさえる +947,16402,あたまをかかえる +947,16403,あたまをかく +947,16404,あたまをさげる +947,16405,あたまをつかう +947,16406,あたまをつっこむ +947,16407,あたまをはねる +947,16408,あたまをひねる +947,16409,あたまをひやす +947,16410,あたまをまるめる +947,16411,あたまを{もた・もた}げる +953,16385,あたらしいさけをふるいかわぶくろにもる +953,16386,あたらしいページをくわえる +959,16385,あたりをはらう +960,16385,あたりをつける +964,16385,あたらずといえどもとおからず +964,16386,あたるもはっけあたらぬもはっけ +964,16387,あたるをさいわい +973,16385,あちらたてればこちらがたたず +974,16385,あっといわせる +982,16385,あついものがこみあげる +985,16385,あっかはりょうかをくちくする +1000,16385,あっけにとられる +1006,16385,あつささむさもひがんまで +1049,16385,あつものにこりて{なます・なます}をふく +1098,16385,あとがない +1098,16386,あとにする +1098,16387,あとにもさきにも +1098,16388,あとの{かり・かり}がさきになる +1098,16389,あとはのとなれやまとなれ +1098,16390,あとをおう +1098,16391,あとをたたない +1098,16392,あとをひく +1100,16385,あとをおう +1100,16386,あとをたつ +1100,16387,あとをつける +1102,16385,あとあしですなをかける +1141,16385,アドバルーンをあげる +1164,16385,あながあく +1164,16386,あながあくほど +1164,16387,あながあったらはいりたい +1234,16385,あばたもえくぼ +1256,16385,あびるほどのむ +1271,16385,あぶないはしをわたる +1278,16385,あぶらがつかれる +1278,16386,あぶらをうる +1278,16387,あぶらをしぼる +1279,16385,あぶらがのる +1332,16385,あまいしるをすう +1332,16386,あまくみる +1367,16385,あまだれいしを{うが・うが}つ +1410,16385,あみをはる +1441,16385,あめがふろうが{やり・やり}がふろうが +1441,16386,あめのふるひはてんきがわるい +1441,16387,あめふってじかたまる +1442,16385,アメと{ムチ・むち} +1442,16386,あめをしゃぶらせる +1460,16385,あめだまをしゃぶらせる +1462,16385,あめつゆをしのぐ +1476,16385,あやをつける +1498,16385,あやまちてはあらたむるにはばかることなかれ +1578,16385,あらねつをとる +1623,16385,ありのはいでるすきもない +1673,16385,あったもので(は)ない +1673,16386,あってなきがごとし +1761,16385,あわをくう +1778,16385,あわせるかおがない +1868,16385,あんしょうにのりあげる +1877,16385,あんずるよりうむが{やす・やす}し +1905,16385,アンテナをはる +1974,16385,いはじんじゅつ +1976,16385,いをはる +1979,16385,いをたてる +1980,16385,いあまってことばたらず +1980,16386,いとする +1980,16387,いにかいする +1980,16388,いに{かな・かな}う +1980,16389,いにそう +1980,16390,いにそまない +1980,16391,いにみたない +1980,16392,いのあるところ +1980,16393,いをうける +1980,16394,いをえる +1980,16395,いを{く・く}む +1980,16396,いをつうじる +1980,16397,いをつくす +1980,16398,いをつよくする +1980,16399,いをむかえる +1980,16400,いをもちいる +1994,16385,いいいみで +2018,16385,いいかげんにしろ +2111,16385,いいえてみょう +2111,16386,いうことなし +2111,16387,いうことをきかない +2111,16388,いうにおよばず +2111,16389,いうにこと(を)かいて +2111,16390,いうにやおよぶ +2111,16391,いうは{やす・やす}くおこなうは{かた・かた}し +2111,16392,いうまでもない +2111,16393,いうもさらなり +2111,16394,いうを{ま・ま}たない +2111,16395,いったいわない +2111,16396,いわないことではない +2111,16397,いわぬがはな +2117,16385,いえをたたむ +2208,16385,いかりしんとうにはっする +2221,16385,いかんのいをひょうする +2230,16385,いきがあがる +2230,16386,いきがかかる +2230,16387,いきがきれる +2230,16388,いきがつまる +2230,16389,いきがながい +2230,16390,いきのかよった +2230,16391,いきのしたから +2230,16392,いきもつかせず +2230,16393,いきをいれる +2230,16394,いきをこらす +2230,16395,いきをころす +2230,16396,いきをつく +2230,16397,いきをぬく +2230,16398,いきを{の・の}む +2230,16399,いきをひきとる +2230,16400,いきをひそめる +2230,16401,いきをふきかえす +2232,16385,いきてんをつく +2248,16385,いきうまのめをぬく +2315,16385,いきのねをとめる +2439,16385,いざかまくら +2442,16385,いさいをはなつ +2469,16385,いしがながれてこのはがしずむ +2469,16386,いしにかじりついても +2469,16387,いしにたつや +2469,16388,いしにまくらしながれにくちすすぐ +2469,16389,いしのうえにもさんねん +2469,16390,いしもておわれる +2478,16385,いじがわるい +2478,16386,いじをはる +2489,16385,いしきがたかい +2514,16385,いしばしをたたいてわたる +2558,16385,いしょくたりてれいせつをしる +2587,16385,いずまいをただす +2595,16385,いずれ{あやめ・あやめ}か{かきつばた・かきつばた} +2632,16385,いそがばまわれ +2648,16385,いたにつく +2652,16385,いたいめをみる +2652,16386,いたくもかゆくもない +2652,16387,いたくもないはらをさぐられる +2670,16385,いたごいちまいしたはじごく +2685,16385,いたちのみちをきったよう +2723,16385,いたれりつくせり +2728,16385,いちかばちか +2728,16386,いちからじゅうまで +2728,16387,いちにもににも +2728,16388,いちぬけた +2728,16389,いちひめにたろう +2728,16390,いちふじにたかさん{なすび・なすび} +2728,16391,いちもにもなく +2728,16392,いちをきいてじゅうをしる +2752,16385,いちぎにおよばず +2764,16385,いちごにつきる +2768,16385,いちごんなかるべからず +2768,16386,いちごんのもとに +2768,16387,いちごんもない +2772,16385,いちじがばんじ +2777,16385,いちじつのちょうがある +2778,16385,いちじゅのかげいちがのながれもたしょうのえん +2811,16385,いちにちのちょうがある +2841,16385,いちまいかむ +2845,16385,いちみゃくあいつうじる +2853,16385,いちもく(を)おく +2874,16385,いちようおちててんかのあきをしる +2905,16385,いっかをなす +2941,16385,いっきょうをきっする +2953,16385,いっけんきょに{ほ・ほ}ゆればばんけんじつをつたう +2965,16385,いっこくをあらそう +2984,16385,いっし(を)むくいる +2986,16385,いっしまとわぬ +2986,16386,いっしみだれず +3002,16385,いっしゅうまわって +3011,16385,いっしょうこうなり(て)ばんこつかる +3012,16385,いっしょうにふす(る) +3028,16385,いっすんさきはやみ +3028,16386,いっすんのこういんかろんずべからず +3028,16387,いっすんのむしにもごぶのたましい +3030,16385,いっせいを{ふうび・ふうび}する +3035,16385,いっせきをとうじる +3036,16385,いっせきぶつ +3036,16386,いっせきもうける +3042,16385,いっせんをかくす +3042,16386,いっせんをこえる +3067,16385,いっちゅうをゆする +3069,16385,いっちょうあがり +3104,16385,いっとうちをぬく +3116,16385,いっぱいくわす +3117,16385,いっぱい、ちに{まみ・まみ}れる +3123,16385,いっぱんを〈みて/もって〉ぜんぴょうを〈ぼくす/おす〉 +3137,16385,いっぷくいれる +3137,16386,いっぷくつける +3137,16387,いっぷくもる +3148,16385,いっぽぬけでる +3148,16386,いっぽまちがえば +3148,16387,いっぽをゆずる +3178,16385,いとのきれた{たこ・たこ} +3178,16386,いとをたれる +3178,16387,いとをひく +3270,16385,いぬとさる +3270,16386,いぬもあるけばぼうにあたる +3270,16387,いぬもくわない +3300,16385,いのちあってのものだね +3300,16386,いのちからにばんめ +3300,16387,いのちながければはじおおし +3300,16388,いのちをおとす +3300,16389,いのちをけずる +3300,16390,いのちをちぢめる +3347,16385,いぶくろをつかむ +3347,16386,いぶくろをみたす +3377,16385,いまこそわかれめ +3377,16386,いまないた{からす・からす}がもうわらう +3377,16387,いまにはじまったことで(は)ない +3377,16388,いまはこれまで +3377,16389,いまはむかし +3377,16390,いまをときめく +3408,16385,いまやおそし +3435,16385,いも(のこ)をあらうよう +3435,16386,いものにえたもごぞんじない +3456,16385,いやもへちまもない +3473,16385,いやけがさす +3545,16385,いりまめにはながさく +3558,16385,いてもたってもいられない +3592,16385,いろのしろいはしちなんかくす +3592,16386,いろをうしなう +3592,16387,いろをうる +3592,16388,いろをこのむ +3592,16389,いろをそえる +3592,16390,いろをただす +3592,16391,いろをつける +3592,16392,いろをなす +3654,16385,いわしのあたまもしんじんから +3685,16385,いんに{こ・こ}もる +3685,16386,いんにように +3707,16385,いんかんとおからず +3730,16385,いんこうをやくする +3852,16385,いんどうをわたす +3853,16385,いんとくあればようほうあり +3928,16385,{う・う}のまねをする{からす・からす} +3928,16386,{う・う}のめ{たか・たか}のめ +3980,16385,うえからものをいう +3980,16386,うえにはうえがある +3980,16387,うえをしたへのおおさわぎ +3989,16385,ウエイトをおく +4026,16385,うおがみずをえたよう +4039,16385,うおごころあればみずごころ(あり) +4102,16385,うきみをやつす +4109,16385,うぐいすなかせたこともある +4143,16385,うけてたつ +4148,16385,うごの{たけのこ・たけのこ}のよう +4152,16385,うごきがとれない +4153,16385,うごかぬしょうこ +4167,16385,うしにひかれてぜんこうじまいり +4167,16386,うしをうまにのりかえる +4169,16385,うじよりそだち +4191,16385,うしろからてっぽうをうつ +4191,16386,うしろをみせる +4191,16387,うしろがみをひかれるおもい +4191,16388,うしろゆびをさされる +4207,16385,うすがみを〈はぐ/はがす〉よう +4247,16385,うすめをあける +4266,16385,うそからでたまこと +4266,16386,うそでぬりかためる +4266,16387,うそもひゃっかいいえばほんとうになる +4266,16388,うそもほうべん +4266,16389,うそをつけ +4306,16385,うだつがあがらない +4339,16385,うちかぶとをみすかす +4438,16385,うてばひびく +4466,16385,うっちゃりをくう +4468,16385,うつつをぬかす +4498,16385,うでがあがる +4498,16386,うでがたつ +4498,16387,うでがなる +4498,16388,うでがよなきをする +4498,16389,うでにおぼえがある +4498,16390,うでによりをかける +4498,16391,うでをこまねく +4498,16392,うでをふるう +4498,16393,うでをみがく +4576,16385,うぶごえをあげる +4587,16385,うまがあう +4587,16386,うまにはのってみよ、ひとにはそうてみよ +4587,16387,うまのせをわける +4587,16388,うまのみみにねんぶつ +4590,16385,うまいしるをすう +4611,16385,うみのもくずとなる +4611,16386,うみのものともやまのものともつかない +4633,16385,うみのおやよりそだてのおや +4646,16385,うむあいつうじる +4646,16386,うむをいわさず +4668,16385,うもれぎにはながさく +4673,16385,うゆうにきす +4678,16385,うらをかえす +4678,16386,うらをかえせば +4678,16387,うらをかく +4678,16388,うらをとる +4733,16385,うらみこつずいにてっする +4733,16386,うらみを{の・の}む +4737,16385,うらめにでる +4752,16385,{うり・うり}のつるに{なすび・なすび}は{な・な}らぬ +4766,16385,うりことばにかいことば +4823,16385,うれしいひめいをあげる +4858,16385,うわさがうわさをよぶ +4858,16386,うわさがたつ +4858,16387,うわさをすればかげ(がさす) +4883,16385,うわまえをはねる +4892,16385,うんをてんにまかせる +4893,16385,うんだともつぶれたとも +4893,16386,うんでもすんでもない +4893,16387,うんともすんとも +4928,16385,うんちくをかたむける +4956,16385,えのないところにえをすげる +4957,16385,えにかいたもち +4957,16386,えにかいたよう +4957,16387,えになる +5077,16385,えいゆういろをこのむ +5092,16385,エーからゼット(まで) +5342,16385,えつにいる +5370,16385,えつぼにいる +5376,16385,えてにほをあげる +5387,16385,えどのかたきをながさきでうつ +5434,16385,えびで{たい・たい}をつる +5514,16385,えらぶところがない +5518,16385,えりをただす +5589,16385,えんなきしゅじょうはどしがたし +5589,16386,えんはいなものあじなもの +5630,16385,えんぎでもない +5630,16386,えんぎをいわう +5630,16387,えんぎをかつぐ +5679,16385,えんじゃくいずくんぞこうこくのこころざしをしらんや +5703,16385,エンジンがかかる +5796,16385,えんぴつをなめる +5818,16385,えんまくをはる +5835,16385,えんりょえしゃくもなく +5840,16385,おをひく +5938,16385,おうたこにおしえられてあさせをわたる +5939,16385,おいつおわれつ +6014,16385,おうせつに{いとま・いとま}がない +6034,16385,おうとをもよおす +6100,16385,おおありなごやのきんの{しゃち・しゃち} +6148,16385,おおきくでる +6231,16385,おおでをひろげる +6231,16386,おおでをふる +6261,16385,おおなたをふるう +6286,16385,おおぶねにのったきもち +6289,16385,おおぶろしきをひろげる +6302,16385,おおみえをきる +6315,16385,おおめにみる +6353,16385,{おか・おか}にあがった{かっぱ・かっぱ} +6386,16385,おかぶをうばう +6389,16385,おかまをほる +6463,16385,おきゅうをすえる +6467,16385,おぎりにも +6471,16385,おくがふかい +6484,16385,おくじょうおくをかす +6492,16385,オクターブがあがる +6508,16385,おくばにもののはさまったいいかた +6509,16385,おくびにもださない +6510,16385,おくびょうかぜをふかす +6523,16385,おくらになる +6534,16385,おくれをとる +6566,16385,おことばにあまえる +6591,16385,おざがさめる +6600,16385,おさきまっくら +6600,16386,おさきぼうをかつぐ +6602,16385,おざしきがかかる +6605,16385,おさとがしれる +6627,16385,おしもおされぬ +6627,16386,おしもおされもせぬ +6760,16385,おしてしるべし +6789,16385,おそかりしゆらのすけ +6791,16385,おそきにしっする +6794,16385,おぞけをふるう +6812,16385,おそれいりやのきしもじん +6824,16385,おだをあげる +6833,16385,おたかくとまる +6853,16385,おだやかでない +6878,16385,おちゃをにごす +6878,16386,おちゃを{ひ・ひ}く +6960,16385,おつやのよう +6962,16385,おつりがくる +6963,16385,おてをはいしゃく +6991,16385,おとをたててくずれる +7006,16385,おとこになる +7006,16386,おとこをあげる +7006,16387,おとこごころとあきのそら +7021,16385,おとといきやがれ +7063,16385,おなじあなの{むじな・むじな} +7063,16386,おなじかまのめしをくう +7063,16387,おなじテーブルにつく +7076,16385,おにがでるかじゃがでるか +7076,16386,おにがわらう +7076,16387,おににかなぼう +7076,16388,おにのいぬまに(いのちの)せんたく +7076,16389,おにのくびをとったよう +7076,16390,おにのめにもなみだ +7076,16391,おにはそとふくはうち +7076,16392,おにもじゅうはち、ばんちゃもでばな +7076,16393,おにをあざむく +7122,16385,おのれにかつ +7122,16386,おのれを{むな・むな}しゅうする +7123,16385,おはうちからす +7124,16385,おはにあう +7144,16385,おはちがまわる +7161,16385,おびにみじかし{たすき・たすき}にながし +7172,16385,おひげの{ちり・ちり}をはらう +7197,16385,おひゃくどをふむ +7202,16385,おひれをつける +7243,16385,オブラートに〈つつむ/くるむ〉 +7283,16385,おぼれるものは{わら・わら}をもつかむ +7333,16385,おめにかかる +7333,16386,おめにかける +7356,16385,おもいなかばにすぎる +7356,16386,おもいにしずむ +7356,16387,おもいもつかない +7356,16388,おもいもよらない +7356,16389,おもいをいたす +7356,16390,おもいをはらす +7356,16391,おもいをめぐらす +7356,16392,おもいをよせる +7356,16393,おもいたったがきちにち +7358,16385,おもうにまかせない +7358,16386,おもってもいない +7366,16385,おもきをおく +7366,16386,おもきをなす +7385,16385,おもてをおかす +7422,16385,おやのいんががこにむくいる +7422,16386,おやのかおがみたい +7422,16387,おやのこころこしらず +7422,16388,おやのすねをかじる +7422,16389,おやのひかりはななひかり +7422,16390,おやはなくともこはそだつ +7429,16385,おやかたひのまる +7446,16385,おやすくない +7456,16385,おやぶねにのったつもり +7476,16385,およびがかかる +7476,16386,およびでない +7491,16385,おりにふれ(て) +7491,16386,おりもおり +7566,16385,おれがおれが +7602,16385,おわりよければすべてよし +7606,16385,おんにきる +7606,16386,おんを{あだ・あだ}でかえす +7606,16387,おんをうる +7606,16388,おんをかえす +7707,16385,おんなさんがいにいえなし +7707,16386,おんなになる +7707,16387,おんなごころとあきのそら +7721,16385,おんぶにだっこ +7749,16385,かもなくふかもなし +7755,16385,かをさりじつにつく +7756,16385,かのなくようなこえ +7777,16385,がをおる +7777,16386,がをはる +7782,16385,がをまげる +7845,16385,かいになる +7854,16385,かいよりはじめよ +7887,16385,かいいぬにてをかまれる +7921,16385,がいかをあげる +8057,16385,かいさいをさけぶ +8171,16385,かいじんにきす +8333,16385,かいとうらんまをたつ +8363,16385,かいなをかえす +8419,16385,がいぶんがわるい +8552,16385,かえすときの{えんま・えんま}がお +8565,16385,かえるのこはかえる +8565,16386,かえるのつらにみず +8567,16385,かえらぬたび +8567,16386,かえらぬひととなる +8567,16387,かえりなんいざ +8578,16385,かおがあう +8578,16386,かおがうれる +8578,16387,かおがきく +8578,16388,かおがたつ +8578,16389,かおがつぶれる +8578,16390,かおがひろい +8578,16391,かおがみえる +8578,16392,かおからひがでる +8578,16393,かおにかいてある +8578,16394,かおにでる +8578,16395,かおにどろをぬる +8578,16396,かおをあらってでなおしてこい +8578,16397,かおをあわせる +8578,16398,かおをかがやかせる +8578,16399,かおをかす +8578,16400,かおをくしゃくしゃにする +8578,16401,かおをだす +8578,16402,かおをつくる +8581,16385,かおいろをみる +8604,16385,かおりをきく +8944,16385,かくすよりあらわる +9069,16385,がくもんにおうどうなし +9107,16385,かげでいとをひく +9107,16386,かげになりひなたになって +9108,16385,かげがうすい +9108,16386,かげのかたちにそうよう +9108,16387,かげもかたちもない +9108,16388,かげをおとす +9108,16389,かげをかくす +9108,16390,かげをひそめる +9130,16385,かけがえのない +9161,16385,かけつけさんばい +9179,16385,かげひなたになって +9264,16385,かさにきる +9267,16385,かさにかかる +9272,16385,かざあなをあける +9288,16385,かざかみにもおけない +9303,16385,かざしもにたつ +9316,16385,かさのだいがとぶ +9329,16385,かさんをかたむける +9356,16385,{かじ・かじ}をきる +9356,16386,{かじ・かじ}をとる +9498,16385,かしらをおろす +9526,16385,かずしれず +9569,16385,かすみをくう +9592,16385,かぜかおる +9592,16386,かぜがふけばおけやがもうかる +9592,16387,かぜにたつ +9592,16388,かぜひかる +9592,16389,かぜをきる +9592,16390,かぜをくらって +9592,16391,かぜをふかせる +9593,16385,かぜをひく +9614,16385,かせぐにおいつくびんぼうなし +9661,16385,かたがつく +9662,16385,かたがつく +9664,16385,かたにはまる +9664,16386,かたにはめる +9665,16385,かたがこる +9665,16386,かたでいきをする +9665,16387,かたでかぜ(を)きる +9665,16388,かたにかかる +9665,16389,かたにちからをいれる +9665,16390,かたのちからをぬく +9665,16391,かたのにをおろす +9665,16392,かたをいからせる +9665,16393,かたをいれる +9665,16394,かたをおとす +9665,16395,かたをかす +9665,16396,かたを{すく・すく}める +9665,16397,かたをたたく +9665,16398,かたをならべる +9665,16399,かたをほぐす +9665,16400,かたをもつ +9665,16401,かたをよせあう +9670,16385,ガタがくる +9677,16385,かたあしをつっこむ +9749,16385,かたずを{の・の}む +9760,16385,かたちからはいる +9777,16385,かたなおれやつきる +9777,16386,かたなにかけても +9786,16385,かたはだ(を)ぬぐ +9796,16385,かたひじいからせる +9796,16386,かたひじはる +9807,16385,かたぼう(を)かつぐ +9817,16385,かたみがせまい +9842,16385,かたるにおちる +9842,16386,かたるにたる +9862,16385,かちをおく +9864,16385,かちにいく +9864,16386,かちにのる +9864,16387,かちをひろう +9872,16385,かちうまにのる +9913,16385,かちゅうの{くり・くり}をひろう +9926,16385,かちんとくる +9931,16385,かつをいれる +9933,16385,かって{かぶと・かぶと}のおをしめよ +9933,16386,かてばかんぐんまければぞくぐん +9994,16385,かっこにいれる +9996,16385,かっこうがつく +9996,16386,かっこうをつける +10026,16385,かっしてもとうせんのみずはのまず +10055,16385,かってなねつをふく +10055,16386,かってにしろ +10131,16385,かでんにくつをいれず +10142,16385,かどがたつ +10142,16386,かどがとれる +10197,16385,かなえのけいちょうをとう +10209,16385,かなぐつわをはめる +10247,16385,かにはこうらににせてあなをほる +10264,16385,かねが{うな・うな}る +10264,16386,かねにあかせて +10264,16387,かねにいとめをつけない +10264,16388,かねにする +10264,16389,かねになる +10264,16390,かねのきれめがえんのきれめ +10264,16391,かねのなるき +10264,16392,かねのわらじでさがす +10264,16393,かねはてんかのまわり〈もち/もの〉 +10264,16394,かねをおとす +10264,16395,かねをくう +10299,16385,かねもち{けんか・けんか}せず +10376,16385,かぶがあがる +10397,16385,かふくは{あざな・あざな}えるなわの{ごと・ごと}し +10410,16385,かぶとをぬぐ +10415,16385,かぶりをふる +10433,16385,かべにみみあり +10435,16385,がべいにきす +10452,16385,かほうはねてまて +10470,16385,かまをかける +10496,16385,かまどのしたのはいまで +10496,16386,かまどをおこす +10511,16385,かみならぬみ +10511,16386,かみにめされる +10511,16387,かみはさいぶにやどる +10511,16388,かみもほとけもない +10552,16385,かみしもをぬぐ +10567,16385,かみなりにうたれたよう +10567,16386,かみなりをおとす +10571,16385,かみのけをさかだてる +10595,16385,かんでふくめる +10616,16385,かめのこうよりとしのこう +10627,16385,かもが{ねぎ・ねぎ}をしょってくる +10652,16385,かやのそと(におかれる) +10664,16385,かゆいところにてがとどく +10681,16385,からにとじこもる +10681,16386,からをやぶる +10734,16385,からすのなかぬひはあっても +10746,16385,からだがあく +10746,16386,からだがいうことをきかない +10746,16387,からだがおよぐ +10746,16388,からだをうる +10746,16389,からだをはる +10746,16390,からだをゆるす +10912,16385,かりてきたねこ +10912,16386,かりるときのじぞうがお、かえすときの{えんま・えんま}がお +10969,16385,かれきにはな +10969,16386,かれきもやまの{にぎ・にぎ}わい +11018,16385,かわいいこにはたびをさせよ +11022,16385,かわいさあまってにくさひゃくばい +11035,16385,かわいたぞうきんをしぼる +11057,16385,かわだちはかわではてる +11102,16385,かんをかさねる +11104,16385,かんにさわる +11108,16385,かんをおおってことさだまる +11109,16385,かんをつうじる +11110,16385,かん、はつをいれず +11111,16385,かんきわまる +11111,16386,かんにたえた +11111,16387,かんにたえない +11115,16385,かんをつくす +11137,16385,ガンをつける +11160,16385,かんおけにかたあしをつっこむ +11163,16385,かんかをまじえる +11211,16385,かんきをこうむる +11303,16385,がんこうしはいにてっする +11316,16385,かんこどりがなく +11374,16385,かんしゃかんげきあめあられ +11377,16385,かんしゃくをおこす +11441,16385,がんしょくなし +11441,16386,がんしょくをうしなう +11504,16385,かんぜんするところがない +11543,16385,かんたんあいてらす +11543,16386,かんたんをくだく +11562,16385,かんちゅうぼうあり +11563,16385,がんちゅうにない +11586,16385,がんていにのこる +11634,16385,かんなんなんじをたまにす +11638,16385,かんにんぶくろのおがきれる +11677,16385,かんぱつをいれず +11683,16385,かんばんがなく +11683,16386,かんばんをおろす +11705,16385,かんぷなきまでに +11768,16385,かんむりをまげる +11808,16385,かんらくきわまりてあいじょうおおし +11845,16385,きからおちたさる +11845,16386,きではなを{くく・くく}る +11845,16387,きにたけをつぐ +11845,16388,きに{よ・よ}りてうおをもとむ +11845,16389,きをみてもりをみず +11848,16385,きがあう +11848,16386,きがある +11848,16387,きがいい +11848,16388,きがおおい +11848,16389,きがおおきくなる +11848,16390,きがおけない +11848,16391,きがおもい +11848,16392,きがかつ +11848,16393,きがかるい +11848,16394,きがききすぎてまがぬける +11848,16395,きがきく +11848,16396,きがきでない +11848,16397,きがくさる +11848,16398,きがくるう +11848,16399,きがさす +11848,16400,きがしずむ +11848,16401,きがしれない +11848,16402,きがすすまない +11848,16403,きがすむ +11848,16404,きがする +11848,16405,きが{せ・せ}く +11848,16406,きがたつ +11848,16407,きがちいさい +11848,16408,きがちがう +11848,16409,きがちる +11848,16410,きがつく +11848,16411,きがつまる +11848,16412,きがつよい +11848,16413,きがとおくなる +11848,16414,きがとがめる +11848,16415,きがない +11848,16416,きがながい +11848,16417,きがぬける +11848,16418,きがのらない +11848,16419,きがはずむ +11848,16420,きがはやい +11848,16421,きがはる +11848,16422,きがはれる +11848,16423,きがひける +11848,16424,きがふさぐ +11848,16425,きがふれる +11848,16426,きがまぎれる +11848,16427,きがまわる +11848,16428,きがみじかい +11848,16429,きがむく +11848,16430,きがめいる +11848,16431,きがもめる +11848,16432,きがやすまる +11848,16433,きがゆるむ +11848,16434,きがよわい +11848,16435,きがわかい +11848,16436,きでもつ +11848,16437,きにいる +11848,16438,きにかかる +11848,16439,きにかける +11848,16440,きにくわない +11848,16441,きにさわる +11848,16442,きにする +11848,16443,きにそう +11848,16444,きにそまない +11848,16445,きにとめる +11848,16446,きになる +11848,16447,きにやむ +11848,16448,きのおけない +11848,16449,きのきいた +11848,16450,きのない +11848,16451,きはこころ +11848,16452,きもそぞろ +11848,16453,きをいれる +11848,16454,きをうしなう +11848,16455,きをおく +11848,16456,きをおとす +11848,16457,きをかねる +11848,16458,きをきかせる +11848,16459,きをくばる +11848,16460,きをしずめる +11848,16461,きをたしかにもつ +11848,16462,きをちらす +11848,16463,きをつかう +11848,16464,きをつける +11848,16465,きをとられる +11848,16466,きをとりなおす +11848,16467,きをぬく +11848,16468,きを{の・の}まれる +11848,16469,きをはく +11848,16470,きをはらす +11848,16471,きをはる +11848,16472,きをひく +11848,16473,きをまぎらす +11848,16474,きをまわす +11848,16475,きをもたせる +11848,16476,きをもむ +11848,16477,きをゆるす +11848,16478,きをゆるめる +11848,16479,きをよくする +11848,16480,きをらくにする +11848,16481,きをわるくする +11849,16385,きを{てら・てら}う +11853,16385,きをいつにする +11856,16385,きがじゅくす +11856,16386,きにじょうじる +11856,16387,きをみるにびん +11869,16385,ぎ、しんにいる +11870,16385,ぎをみてせざるはゆうなきなり +11876,16385,ギアがはいる +11876,16386,ギアをあげる +11885,16385,きいにふれる +11888,16385,きいをえたい +11943,16385,きえんをあげる +11963,16385,きかおくべし +12039,16385,ききせまる +12097,16385,ききみみをたてる +12135,16385,きいてあきれる +12135,16386,きいてごくらくみてじごく +12135,16387,ききしにまさる +12135,16388,きくはいっときのはじ +12135,16389,きくみみをもたない +12135,16390,きくもなみだ、かたるもなみだ +12164,16385,きぐらいがたかい +12194,16385,きげんをとる +12291,16385,きじもなかずばうたれまい +12402,16385,きしんやのごとし +12419,16385,きずをなめあう +12436,16385,きずぐちにしおをすりこむ +12449,16385,きせをかける +12450,16385,きせいをあげる +12474,16385,きせきにいる +12495,16385,きせんをせいする +12534,16385,きたひには +12538,16385,きたいに{ひん・ひん}する +12593,16385,きちがいにはもの +12682,16385,きつねと{たぬき・たぬき}の〈ばかしあい/だましあい〉 +12682,16386,きつねにつままれる +12685,16385,きっぷがいい +12728,16385,きどうにのる +12764,16385,きぬをさくよう +12806,16385,きばをとぐ +12806,16386,きばをぬかれる +12806,16387,きばを{む・む}く +12848,16385,きびすをかえす +12848,16386,きびすをせっして +12885,16385,きぶんがいい +12885,16386,きぶんがわるい +12908,16385,きぼねがおれる +12914,16385,きまえがいい +12927,16385,きまりがわるい +12931,16385,きみがわるい +12945,16385,きみゃくをつうじる +12979,16385,きめんひとをおどろかす +12980,16385,きもがいれる +12980,16386,きもがすわる +12980,16387,きもがふとい +12980,16388,きもにめいじる +12980,16389,きもをつぶす +12980,16390,きもをひやす +12987,16385,きもちをいれる +13063,16385,ぎゃくてにとる +13106,16385,きゃっこうをあびる +13194,16385,きゅうをおって +13264,16385,きゅうきんをなおす +13280,16385,きゅうこうをあたためる +13303,16385,きゅうしにいっしょうをえる +13318,16385,ぎゅうじをとる +13373,16385,きゅうじんのこうをいっきにかく +13382,16385,きゅうすればつうじる +13399,16385,きゅうそねこをかむ +13431,16385,きゅうちょうふところにいる +13566,16385,きょをつく +13571,16385,ぎょいをえる +13574,16385,きょうのきだおれ +13577,16385,きょうをよむ +13668,16385,ぎょうかんをよむ +13683,16385,ぎょうぎがいい +13693,16385,きょうきんをひらく +13969,16385,きょうべんをとる +14014,16385,きょうらんをきとうに{めぐ・めぐ}らす +14040,16385,ぎょえいがこい +14206,16385,きょせいおつ +14280,16385,きよみずのぶたいからとびおりる +14332,16385,きりひとはおちててんかのあきをしる +14430,16385,きりょうをさげる +14440,16385,きりんもおいてはどばにおとる +14442,16385,きってもきれない +14442,16386,きればあかいちがでる +14442,16387,きればちのでるよう +14495,16385,ぎろんをじょうげする +14645,16385,きんしつあいわす +14672,16385,きんじょう(に)はなをそえる +14698,16385,きんせんにふれる +14735,16385,きんてきをいとめる +14830,16385,ぎんりんおどる +14841,16385,くにする +14841,16386,くにやむ +14841,16387,くはらくのたね +14843,16385,ぐにもつかない +14903,16385,くうかくわれるか +14903,16386,くうやくわず +14921,16385,くうきをよむ +14975,16385,ぐうのねもでない +15040,16385,くぎをさす +15091,16385,くさはえる +15093,16385,くさいめしをくう +15093,16386,くさいものにふたをする +15102,16385,くさきもなびく +15102,16386,くさきもねむる +15117,16385,くさのねをわけて(も)さがす +15121,16385,くさびをうちこむ +15140,16385,くさっても{たい・たい} +15146,16385,くしのはがかけるよう +15146,16386,くしのはを{ひ・ひ}くよう +15148,16385,くじをきる +15177,16385,くじゅうを〈なめる/のむ〉 +15225,16385,くすりがきく +15225,16386,くすりくそうばい +15225,16387,くすりにしたくもない +15233,16385,くせになる +15249,16385,くそくらえ +15249,16386,くそのやくにもたたない +15259,16385,くだをまく +15286,16385,くちがうまい +15286,16386,くちがうるさい +15286,16387,くちがおもい +15286,16388,くちがかかる +15286,16389,くちがかたい +15286,16390,くちがかるい +15286,16391,くちがくさっても +15286,16392,くちがこえる +15286,16393,くちがさけても +15286,16394,くちがすぎる +15286,16395,くちがすべる +15286,16396,くちがへらない +15286,16397,くちがほぐれる +15286,16398,くちがまがる +15286,16399,くちがまわる +15286,16400,くちからさきにうまれる +15286,16401,くちからしんぞうがとびでそう +15286,16402,くちからひがでる +15286,16403,くちがわるい +15286,16404,くちにかける +15286,16405,くちにする +15286,16406,くちになる +15286,16407,くちにのぼる +15286,16408,くちにのりする +15286,16409,くちにはいる +15286,16410,くちのしたから +15286,16411,くちはわざわいのかど +15286,16412,くちほどに(も)ない +15286,16413,くちもはっちょう、てもはっちょう +15286,16414,くちをあけてまつ +15286,16415,くちをあわせる +15286,16416,くちをかんする +15286,16417,くちをきく +15286,16418,くちをきる +15286,16419,くちをきわめて +15286,16420,くちをすっぱくして +15286,16421,くちをそろえて +15286,16422,くちをだす +15286,16423,くちをついてでる +15286,16424,くちをつぐむ +15286,16425,くちをつめる +15286,16426,くちをとがらせる +15286,16427,くちをとざす +15286,16428,くちをにごす +15286,16429,くちをぬぐって +15286,16430,くちをぬらす +15286,16431,くちをのりする +15286,16432,くちをはさむ +15286,16433,くちをひらく +15286,16434,くちをあけば +15286,16435,くちをふうじる +15286,16436,くちをふさぐ +15286,16437,くちをむすぶ +15286,16438,くちをわる +15295,16385,くちうらをあわせる +15311,16385,くちぐちにいう +15313,16385,くちぐるまにのせられる +15345,16385,くちのはにのぼる +15348,16385,くちばしがきいろい +15348,16386,くちばしをいれる +15350,16385,くちはっちょうてはっちょう +15354,16385,くちびをきる +15356,16385,くちびるをかむ +15356,16386,くちびるをつきだす +15356,16387,くちびるを〈ぬすむ/うばう〉 +15421,16385,ぐっとくる +15451,16385,くにやぶれてさんが{あ・あ}り +15476,16385,くはいを〈なめる/きっする〉 +15480,16385,くびがつながる +15480,16386,くびがとぶ +15480,16387,くびがまわらない +15480,16388,くびにする +15480,16389,くびになる +15480,16390,くびになわをつける +15480,16391,くびのかわいちまい +15480,16392,くびをあらってまっていろ +15480,16393,くびを{かし・かし}げる +15480,16394,くびをきる +15480,16395,くびをすげかえる +15480,16396,くびをたてにふる +15480,16397,くびをつっこむ +15480,16398,くびをながくする +15480,16399,くびをひねる +15480,16400,くびをふる +15480,16401,くびをよこにふる +15499,16385,くびつりのあしをひっぱる +15501,16385,くびねっこをおさえられる +15604,16385,くもつくばかり +15604,16386,くもを{かすみ・かすみ}と +15604,16387,くもをつかむよう +15605,16385,くものこをちらすよう +15637,16385,くらがたつ +15640,16385,くらいじんしんをきわめる +15667,16385,くらしがたつ +15675,16385,グラスをかたむける +15712,16385,くらべものにならない +15727,16385,くらやみからうしをひきだす +15819,16385,くるひもくるひも +15819,16386,くるものはこばまず +15841,16385,くるしいいきのしたから +15841,16386,くるしいときのかみだのみ +15956,16385,グローブを〈あわせる/まじえる〉 +16045,16385,くわをいれる +16074,16385,ぐんをぬく +16107,16385,くんし(は)あやうきにちかよらず +16107,16386,くんし(は)ひょうへんす +16115,16385,くんしゅさんもんにいるをゆるさず +16169,16385,ぐんばいをあげる +16169,16386,ぐんばいをかえす +16185,16385,ぐんもうぞうを〈{な・な}でる/ひょうする〉 +16186,16385,ぐんもんにくだる +16205,16385,けのはえたよう +16205,16386,けをふいて{きず・きず}をもとめる +16208,16385,けにもはれにも +16225,16385,けいたりがたくていたりがたし +16239,16385,げいがこまかい +16239,16386,げいがない +16239,16387,げいはみをたすける +16262,16385,けいがいをとどめない +16263,16385,けいがいにせっする +16305,16385,けいこうとなるもぎゅうごとなるなかれ +16359,16385,げいじゅつはながくじんせいはみじかし +16392,16385,けいしてとおざける +16401,16385,けいせつのこうをつむ +16416,16385,けいぞくはちからなり +16592,16385,げきをとばす +16655,16385,げきりんにふれる +16702,16385,けじめをつける +16723,16385,けすじほども +16741,16385,けたがちがう +16742,16385,げたをあずける +16742,16386,げたをはかせる +16742,16387,げたをはくまでわからない +16763,16385,けちがつく +16763,16386,けちをつける +16775,16385,ケツのあながちいさい +16775,16386,ケツをまくる +16775,16387,ケツをもつ +16775,16388,ケツをわる +16776,16385,けつをとる +16806,16385,けっきにはやる +16931,16385,げっぷがでるほど +16944,16385,けつまつをつける +16944,16386,けつまつをつげる +16960,16385,けつるいをしぼる +17007,16385,けびょうをつかう +17024,16385,けむにまく +17031,16385,けむりになる +17031,16386,けむりをたてる +17048,16385,けりをつける +17100,16385,げんをかまえる +17100,16386,げんをさゆうにする +17100,16387,げんを{ま・ま}たない +17137,16385,けんえんもただならぬなか +17149,16385,けんかをうる +17149,16386,けんかをかう +17196,16385,けんぎがかかる +17253,16385,げんげんひをはく +17259,16385,げんこをくらわす +17260,16385,げんごにぜっする +17278,16385,げんこつをみまう +17333,16385,げんじをろうする +17574,16385,けんばのろうをとる +17705,16385,こは{かすがい・かすがい} +17705,16386,こはさんがいのくび(っ){かせ・かせ} +17705,16387,こゆえのやみ +17705,16388,こをもってしるおやのおん +17721,16385,ごをうつ +17742,16385,こいにこいする +17742,16386,こいはしあんのほか +17742,16387,こいはもうもく +17795,16385,こうなりなとげる +17795,16386,こうをそうする +17796,16385,こうをそうする +17803,16385,こうかふこうか +17806,16385,こうをきく +17813,16385,こうをあらためる +17840,16385,ごうにいりてはごうにしたがえ +17841,16385,ごうをにやす +17869,16385,こういんやのごとし +17936,16385,こうかいさきにたたず +17957,16385,こうかくあわをとばす +18026,16385,ごうきぼくとつじんにちかし +18101,16385,こうけつをしぼる +18191,16385,こうざいあいなかばする +18209,16385,こうさんなきものはこうしんなし +18237,16385,こうじまおおし +18286,16385,こうしゅところをかえる +18366,16385,こうしんにみちをひらく +18377,16385,こうじんをはいする +18403,16385,こうせいおそるべし +18525,16385,こうだんにたつ +18604,16385,こうとししてそうくにらる +18625,16385,こうどうをふむ +18699,16385,こうばこをつくる +18763,16385,こうふんをもらす +18771,16385,こうべをたれる +18771,16386,こうべをめぐらす +18794,16385,こうぼうにもふでのあやまり +18794,16386,こうぼうふでをえらばず +18878,16385,こうらに{こけ・こけ}がはえる +18878,16386,こうらをへる +18925,16385,こうるいをしぼる +18956,16385,こえがうらがえる +18956,16386,こえがかかる +18956,16387,こえなきこえ +18956,16388,こえにならないこえ +18956,16389,こえのしたから +18956,16390,こえをおとす +18956,16391,こえをかける +18956,16392,こえをきく +18956,16393,こえをだいにする +18956,16394,こえをつまらせる +18956,16395,こえを{とが・とが}らす +18956,16396,こえを{の・の}む +18956,16397,こえをはずませる +19048,16385,ゴールをわる +19129,16385,ごきげんななめならず +19129,16386,ごきげんをうかがう +19138,16385,こきみがいい +19148,16385,こきょう〈へ/に〉にしきをかざる +19163,16385,こくにすぎる +19294,16385,こくびを{かし・かし}げる +19298,16385,こくびゃくをつける +19298,16386,こくびゃくをべんじない +19365,16385,こけつにいらずんばこじをえず +19379,16385,こけんにかかわる +19385,16385,ここのこえをあげる +19387,16385,ここであったがひゃくねんめ +19387,16386,ここにきて +19387,16387,ここをせんどと +19396,16385,ここうをしのぐ +19411,16385,こごしをかがめる +19434,16385,こころがあらわれる +19434,16386,こころがうごく +19434,16387,こころがおれる +19434,16388,こころがはずむ +19434,16389,こころここにあらず +19434,16390,こころにあながあく +19434,16391,こころにかかる +19434,16392,こころにかける +19434,16393,こころにしまない +19434,16394,こころにとめる +19434,16395,こころにもない +19434,16396,こころをあわせる +19434,16397,こころをいたす +19434,16398,こころをいためる +19434,16399,こころをいれかえる +19434,16400,こころをうごかす +19434,16401,こころをうつ +19434,16402,こころをうばわれる +19434,16403,こころをおににする +19434,16404,こころをくだく +19434,16405,こころをくばる +19434,16406,こころをこめる +19434,16407,こころをゆるす +19434,16408,こころをよせる +19490,16385,こしがおもい +19490,16386,こしがかるい +19490,16387,こしがすわる +19490,16388,こしがたかい +19490,16389,こしがたたない +19490,16390,こしがつよい +19490,16391,こしがひくい +19490,16392,こしがひける +19490,16393,こしをあげる +19490,16394,こしをいれる +19490,16395,こしをおちつける +19490,16396,こしをおる +19490,16397,こしをすえる +19490,16398,こしをぬかす +19490,16399,こしをわる +19500,16385,ごしにあまる +19500,16386,ごしにはいる +19585,16385,ごじゅうにしててんめいをしる +19589,16385,こじゅうとはおにせんびき +19650,16385,こしたことはない +19769,16385,こだてにとる +19771,16385,こだねをやどす +19774,16385,ごたぶんにもれず +19809,16385,こちんとくる +19897,16385,こつにくあい{は・は}む +19924,16385,こてがきく +19924,16386,こてをかざす +19955,16385,ことあるごとに +19955,16386,ことあれかし +19955,16387,ことここにいたる +19955,16388,ことこころざしとたがう +19955,16389,こととする +19955,16390,ことなきをえる +19955,16391,ことにあたる +19955,16392,ことにふれて +19955,16393,ことによると +19955,16394,こともあろうに +19955,16395,こともなし +19955,16396,ことをおこす +19955,16397,ことをかまえる +19955,16398,ことをこのむ +19955,16399,ことをすすめる +19955,16400,ことをなす +19955,16401,ことをはこぶ +19955,16402,ことをわける +20022,16385,ことばがすぎる +20022,16386,ことばがない +20022,16387,ことばにあまえる +20022,16388,ことばにあまる +20022,16389,ことばをうしなう +20022,16390,ことばをかえす +20022,16391,ことばをつくして +20022,16392,ことばをにごす +20028,16385,こどもができる +20028,16386,こどもはかぜのこ +20038,16385,こなをかける +20080,16385,このおやにしてこのこあり +20124,16385,こばかにする +20136,16385,こばなをうごめかす +20136,16386,こばなをふくらませる +20141,16385,こばらがたつ +20154,16385,こびをうる +20158,16385,こひざを〈うつ/たたく〉 +20187,16385,こぶさにわける +20221,16385,ごへいをかつぐ +20257,16385,こまをすすめる +20260,16385,ごまを{す・す}る +20287,16385,こまたのきれあがった +20306,16385,こまものやをひらく +20313,16385,こまわりがきく +20340,16385,こみみにはさむ +20463,16385,ごらんにいれる +20502,16385,こるいをまもる +20511,16385,これあるかな +20511,16386,これさいわい +20511,16387,これでもか +20511,16388,これよりさき +20511,16389,これをもって +20511,16390,これをようするに +20576,16385,ころばぬさきの{つえ・つえ} +20576,16386,ころんでもただではおきない +20593,16385,こわきにかかえる +20612,16385,こわもてにでる +20615,16385,こんをつめる +20890,16385,ざがしらける +20890,16386,ざがみだれる +20890,16387,ざをもつ +20938,16385,さいはなげられた +21023,16385,さいくはりゅうりゅうしあげをごろうじろ +21041,16385,さいげつひとをまたず +21080,16385,さいさんがとれる +21180,16385,さいだいもらさず +21249,16385,さいはいをふる +21273,16385,さいふのくちをしめる +21273,16386,さいふ(のそこ)をはたく +21273,16387,さいふ(のひも)がかたい +21273,16388,さいふのひもをゆるめる +21368,16385,さおをさす +21407,16385,さかずきをふくむ +21413,16385,さかてにとる +21453,16385,さきにたつ +21453,16386,さきをあらそう +21453,16387,さきをこす +21456,16385,{さぎ・さぎ}を{からす・からす}といいくるめる +21525,16385,さきんずればひとをせいす +21528,16385,さくをろうする +21558,16385,さくしさくにおぼれる +21624,16385,さぐりをいれる +21630,16385,さけにのまれる +21630,16386,さけはひゃくやくのちょう +21722,16385,さじをなげる +21832,16385,さしつさされつ +22007,16385,さつびらをきる +22090,16385,さばをよむ +22175,16385,さまになる +22220,16385,さもあらばあれ +22220,16386,さもありなん +22230,16385,さやをはらう +22317,16385,さるもきからおちる +22319,16385,さるものはおわず +22319,16386,さるものはひびにうとし +22389,16385,さわらぬかみにたたりなし +22395,16385,さんをみだす +22413,16385,さんうきたらんとほっしてかぜろうにみつ +22459,16385,ざんきにたえない +22570,16385,さんしゃをさける +22573,16385,さんじゃくさがってしのかげをふまず +22578,16385,さんじゅうにしてたつ +22578,16386,さんじゅうろっけいにげるにしかず +22584,16385,さんしょはこつぶでも〈ぴりり/ひりり〉とからい +22688,16385,さんちゅうれきじつなし +22703,16385,さんどのめしより +22725,16385,さんにんよればもんじゅのちえ +22788,16385,さんべんまわってたばこにしょ +22854,16385,しいっとうをげんずる +22854,16386,しのてんきをとる +22854,16387,しをたまう +22858,16385,しをつうじる +22881,16385,じでいく +22882,16385,じになる +22887,16385,じをひくくする +22917,16385,しあんにあまる +23105,16385,しおをする +23105,16386,しおをまく +23163,16385,しかをおうものはやまをみず +23168,16385,しがにもかけない +23293,16385,じかんをかせぐ +23323,16385,じぎに〈ひとしい/るいする〉 +23327,16385,しきいがたかい +23560,16385,じごくでほとけ +23560,16386,じごくのかま(のふた)があく +23560,16387,じごくのさたもかねしだい +23686,16385,じじつはしょうせつよりもきなり +23721,16385,しじゅうにしてまどわず +23799,16385,じしんかみなりかじ{おやじ・おやじ} +23810,16385,ししてのち{や・や}む +23810,16386,しせるこうめいいけるちゅうたつをはしらしむ +23856,16385,しせいてんにあり +23856,16386,しせいめいあり +23858,16385,しせいをただす +23896,16385,しせんをあつめる +23896,16386,しせんをあびる +23946,16385,じそんのためにびでんをかわず +23949,16385,したにもおかない +23950,16385,したがこえる +23950,16386,したがまわる +23950,16387,したをだす +23950,16388,したをならす +23950,16389,したをまく +23953,16385,じたともにゆるす +23974,16385,じだいがつく +24031,16385,したてにでる +24041,16385,したのねもかわかぬうちに +24080,16385,じだんだ(を)ふむ +24120,16385,しちゅうにかつをもとめる +24245,16385,じっしにあまる +24245,16386,じっしのさすところ +24364,16385,しっぽをだす +24364,16386,しっぽをつかまえる +24364,16387,しっぽをふる +24364,16388,しっぽをまく +24482,16385,しなをつくる +24549,16385,しにばなをさかせる +24551,16385,しにみずをとる +24559,16385,しにんにくちなし +24564,16385,しんではなみがさくものか +24564,16386,しんでもしにきれない +24575,16385,しのつく +24577,16385,しのぎをけずる +24597,16385,しばにむちうつ +24606,16385,しばいをうつ +24642,16385,じばらをきる +24694,16385,しびれがきれる +24716,16385,しぶかわの{む・む}けた +24721,16385,しふくをこやす +24746,16385,しふんのかにまよう +24752,16385,(じぶんで)じぶんのくびをしめる +24828,16385,しまつがわるい +24828,16386,しまつにおえない +24895,16385,しめいをせいする +24913,16385,しめしがつかない +24944,16385,しもをおく +24989,16385,しゃにかまえる +24994,16385,じゃのみちはへび +24994,16386,じゃはすんにしてひとを{の・の}む +25037,16385,しゃかにせっぽう +25080,16385,しゃくにさわる +25185,16385,しゃじくをながす +25276,16385,シャッターをきる +25284,16385,シャッポをぬぐ +25445,16385,しゅにまじわればあかくなる +25445,16386,しゅをいれる +25484,16385,しゆうをけっする +25488,16385,じゅうよくごうをせいす +25491,16385,じゅうかつだい +25530,16385,しゅうかてきせず +25677,16385,じゅうじをきる +25677,16386,じゅうじかをせおう +25827,16385,じゅうてんをおく +25882,16385,しゅうはをおくる +25886,16385,じゅうばこのすみを(⦅ようじ\ようじ⦆で)〈つつく/ほじくる〉 +25900,16385,しゅうびをひらく +25935,16385,じゅうまんおくどへたびだつ +25946,16385,じゅうもくのみるところ +26153,16385,しゅごがおおきい +26259,16385,しゅそりょうたんをじす +26283,16385,しゅちゅうにおさめる +26283,16386,しゅちゅうにきす +26432,16385,しゅはいをかたむける +26442,16385,しゅびはじょうじょう +26447,16385,しゅひつをいれる +26482,16385,じゅみょうがちぢまる +26628,16385,しゅんじゅうにとむ +26635,16385,しゅんしょういっこくあたいせんきん +26685,16385,じゅんぷうにほをあげる +26698,16385,しゅんみんあかつきをおぼえず +26744,16385,しょいなげをくわせる +26757,16385,しょうをいんとするものはまずうまをいよ +26764,16385,しょうにあたる +26791,16385,じょうがうつる +26791,16386,じょうにもろい +26791,16387,じょうをつうじる +26811,16385,しょういをすててだいどうにつく +26919,16385,しょうぎをさす +26924,16385,じょうきをいっする +27093,16385,しょうじきの{こうべ・こうべ}にかみやどる +27176,16385,しょうじんかんきょしてふぜんをなす +27189,16385,じょうずのてからみずがも(れ)る +27247,16385,しょうそくをたつ +27274,16385,じょうだんではない +27274,16386,じょうだんをとばす +27283,16385,しょうちゅうにおさめる +27370,16385,しょうねをすえる +27373,16385,しょうねんおいやすくがくなりがたし +27384,16385,しょうのむしをころしてだいのむしをたすける +27392,16385,しょうばいはみちによってかしこし +27425,16385,しょうぶはときのうん +27425,16386,しょうぶごとは{げた・げた}をはくまで +27500,16385,しょうめんきって +27500,16386,しょうめんをきる +27556,16385,しょうりゃくにしたがう +27674,16385,しょくがおちる +27674,16386,しょくがすすむ +27674,16387,しょくがほそい +27674,16388,しょくがほそる +27702,16385,しょくしがうごく +27704,16385,しょくじがすすむ +27707,16385,しょくしゅをのばす +27726,16385,しょくぜんにのぼせる +27851,16385,じょしとしょうじんはやしないがたし +27897,16385,しょしんわするべからず +27951,16385,しょたいをたたむ +28126,16385,しらをきる +28165,16385,しらぬがほとけ +28165,16386,しらぬぞんぜぬ +28165,16387,しらぬかおのはんべえ +28169,16385,しらはのやがたつ +28178,16385,しらべがつく +28189,16385,しりがあおい +28189,16386,しりがおもい +28189,16387,しりがかるい +28189,16388,しりがこそばゆい +28189,16389,しりがながい +28189,16390,しりがわれる +28189,16391,しりにひがつく +28189,16392,しりにほをかける +28189,16393,しりのけをぬく +28189,16394,しり(のした)にしく +28189,16395,しりをあげる +28189,16396,しりをすえる +28189,16397,しりをたたく +28189,16398,しりをぬぐう +28189,16399,しりをふる +28189,16400,しりをまくる +28189,16401,しりをもちこむ +28200,16385,しりうまにのる +28252,16385,しりょくをつくす +28260,16385,しったことではない +28260,16386,しってかしらでか +28260,16387,しってのとおり +28260,16388,しらしむべからず、よらしむべし +28260,16389,しるひとぞしる +28260,16390,しるやしらずや +28306,16385,しろいはをみせる +28306,16386,しろいめでみる +28381,16385,じわじわくる +28400,16385,しんなくばたたず +28400,16386,しんをとう +28402,16385,しんにせまる +28407,16385,しんはなきよりたにんはくいより +28503,16385,じんかんいたるところせいざんあり +28515,16385,しんき{ま・ま}きなおし +28524,16385,じんぎをきる +28524,16386,じんぎをとおす +28569,16385,しんけいを{とが・とが}らせる +28573,16385,しんけつをそそぐ +28597,16385,じんごにおちない +28614,16385,じんこうにかいしゃする +28614,16386,じんこうにのぼる +28616,16385,じんこうも{た・た}かず{へ・へ}もひらず +28627,16385,しんこんをかたむける +28643,16385,しんさんをなめる +28660,16385,じんじをつくしててんめいをまつ +28717,16385,しんしょうをはたく +28777,16385,じんせいいきにかんず +28813,16385,しんそうにそだつ +28818,16385,しんぞうがつよい +28818,16386,しんぞうがとびでそう +28818,16387,しんぞうにけがはえている +28850,16385,しんたんをさむからしめる +28914,16385,しんとう(を)めっきゃくすればひもまたすずし +28944,16385,しんにゅうをかける +29018,16385,しんぺんをきれいにする +29056,16385,しんめいをとして +29056,16386,しんめいをなげうって +29068,16385,じんもんにくだる +29130,16385,ずにあたる +29130,16386,ずにのる +29131,16385,ずがたかい +29142,16385,すいもあまいも{か・か}みわけた +29248,16385,すいせいのごとく +29280,16385,スイッチがはいる +29320,16385,すいほうにきす +29396,16385,スープのさめないきょり +29452,16385,すがたをけす +29482,16385,すきをこらす +29484,16385,すきこそもののじょうずなれ +29544,16385,すぎたるは{なお・なお}およばざるがごとし +29595,16385,スクラムをくむ +29664,16385,すごみをきかせる +29680,16385,すじがいい +29680,16386,すじがわるい +29714,16385,すずをころがすようなこえ +29714,16386,すずをつける +29714,16387,すずをはったようなめ +29740,16385,すずめひゃくまでおどりわすれず +29849,16385,すっぱいものがたべたい +29902,16385,すてたものではない +29902,16386,すててかえりみない +29902,16387,すてるかみあればひろうかみあり +29972,16385,すなをかむよう +30006,16385,すねにきずもつ +30123,16385,すべったのころんだのと +30136,16385,ずぼしをさされる +30155,16385,すまじきものはみやづかえ +30164,16385,すみにおけない +30201,16385,すめばみやこ +30211,16385,すもうにかってしょうぶにまける +30211,16386,すもうにならない +30388,16385,すんてつひとをさす +30406,16385,せにはらはかえられぬ +30406,16386,せをむける +30415,16385,せいをうける +30423,16385,せいがでる +30423,16386,せいをだす +30618,16385,せいこくを〈いる/える〉 +30640,16385,せいさつよだつのけんをにぎる +30736,16385,せいしんいっとうなにごとかならざらん +30814,16385,せいだくあわせ{の・の}む +30981,16385,せいめいをとす +31037,16385,せいるい{とも・とも}にくだる +31093,16385,せきのあたたまるいとまがない +31093,16386,せきをおなじゅうせず +31093,16387,せきをけがす +31094,16385,せきをきったよう +31097,16385,せきをいれる +31177,16385,せきひんあらうがごとし +31232,16385,セコンドをきざむ +31251,16385,せすじがさむくなる +31251,16386,せすじがのびる +31382,16385,せっしょくをたもつ +31510,16385,せなかがみえる +31510,16386,せなかをおう +31510,16387,せなかをおす +31510,16388,せなかをみてそだつ +31537,16385,ぜひにおよばぬ +31537,16386,ぜひもない +31647,16385,ゼロがひとつおおい +31647,16386,ゼロから(さい)〈しゅっぱつ/スタート〉する +31659,16385,せわがやける +31659,16386,せわにくだける +31659,16387,せわになる +31659,16388,せわはない +31659,16389,せわをやく +31669,16385,せんをこす +31674,16385,せんがほそい +31675,16385,せんにもれる +31675,16386,せんをことにする +31676,16385,せんにきる +31692,16385,ぜんをおって +31952,16385,ぜんしゃのてつをふむ +31952,16386,ぜんしゃのふくてつ、こうしゃのいましめ +32024,16385,ぜんしんぜんれいをうちこむ +32114,16385,せんたんをきる +32115,16385,せんたんをひらく +32116,16385,せんだんはふたばよりかんばし +32158,16385,ぜんてつをふむ +32179,16385,せんとうをきる +32183,16385,せんどうおおくしてふねやまにのぼる +32321,16385,せんまんにんといえどもわれいかん +32345,16385,ぜんもんのとら、こうもんのおおかみ +32369,16385,せんりのつつみも{あり・あり}のあなからくずれる +32369,16386,せんりのみちもいっぽよりはじまる +32431,16385,そうとする +32446,16385,そうはとんやがおろさない +32502,16385,そうがにかかる +32508,16385,そうかいへんじてそうでんとなる +32617,16385,そうごうをくずす +32794,16385,そうだんにのる +32864,16385,そうはくをなめる +33016,16385,ゾーンにはいる +33080,16385,ぞくじにいりやすい +33121,16385,そくせきを〈いんする/しるす〉 +33203,16385,そこがあさい +33203,16386,そこがしれない +33203,16387,そこがしれる +33203,16388,そこがぬける +33203,16389,そこがわれる +33203,16390,そこをいれる +33203,16391,そこをうつ +33203,16392,そこをつく +33203,16393,そこをつける +33203,16394,そこをはらう +33203,16395,そこをわる +33204,16385,そこへいくと +33204,16386,そこへもってきて +33209,16385,そこいじがわるい +33280,16385,そじょうにのせる +33386,16385,ぞっとしない +33393,16385,そっぽをむく +33395,16385,そでにすがる +33395,16386,そでにする +33395,16387,そでふりあうもたしょうのえん +33395,16388,そでをしぼる +33395,16389,そでをとおす +33395,16390,そでをひく +33438,16385,そとぼりをうめる +33463,16385,そのときはそのとき +33480,16385,そのてはくわなのやきはまぐり +33612,16385,そりがあわない +33612,16386,そりをうたせる +33636,16385,それかあらぬか +33636,16386,それみたことか +33671,16385,それはそうだ +33671,16386,それはないでしょう +33683,16385,そろいもそろって +33690,16385,そろばんをはじく +33737,16385,そんしてとくとれ +33756,16385,そんなこんな +33782,16385,たなし +33785,16385,たとする +33824,16385,たいをしずめる +33824,16386,たいをひらく +33837,16385,だいなりしょうなり +33837,16386,だいのむしをいかしてしょうのむしをころす +33837,16387,だいはしょうをかねる +33837,16388,だいをなす +33865,16385,だいいっせいをあげる +33868,16385,だいいっぽをふみだす +33919,16385,たいかくにれっする +33954,16385,たいぎしんをめっす +33971,16385,たいぎょをいっする +34027,16385,たいこばんをおす +34048,16385,だいこういでずんば +34073,16385,たいざんめいどうして{ねずみ・ねずみ}いっぴき +34091,16385,だいじにいたる +34091,16386,だいじのまえのしょうじ +34091,16387,だいじをとる +34183,16385,だいじんかぜをふかせる +34469,16385,たいよくはむよくににたり +34537,16385,タオルをなげる +34539,16385,たおれてのち{や・や}む +34542,16385,たかがしれている +34542,16386,たかのしれた +34542,16387,たかをくくる +34543,16385,たかはうえてもほをつまず +34544,16385,たががゆるむ +34544,16386,たがをしめる +34764,16385,たけをわったよう +34920,16385,たしてにでわる +34940,16385,たぜいにぶぜい +34954,16385,たたますますべんず +34955,16385,ただの{ねずみ・ねずみ}ではない +34955,16386,ただよりたかいものはない +34957,16385,だだをこねる +34973,16385,たたけばほこりがでる +34997,16385,たたみのうえでしぬ +35004,16385,たたらをふむ +35078,16385,たちばがない +35111,16385,たつとりあとをにごさず +35111,16386,たてばしゃくやくすわればぼたん +35202,16385,たづなをしめる +35229,16385,たてにとる +35229,16386,たてをつく +35231,16385,たてからみてもよこからみても +35231,16386,たてのものをよこにもしない +35234,16385,たでくうむしもすきずき +35241,16385,たていたにみず +35338,16385,たなからぼたもち +35338,16386,たなにあげる +35344,16385,たなごころをさすよう +35371,16385,たにんのせんきをずつうにやむ +35371,16386,たにんのふこうはみつのあじ +35374,16385,たねがわれる +35374,16386,たねをつける +35374,16387,たねをやどす +35407,16385,たばになってかかる +35424,16385,たびからたび +35424,16386,たびのはじはかきすて +35424,16387,たびはみちづれよはなさけ +35514,16385,たまに{きず・きず} +35514,16386,たまみがかざればひかりなし +35514,16387,たまをいだいてつみあり +35514,16388,たまをころがすよう +35515,16385,たまがつまる +35515,16386,たまがはしる +35515,16387,たまをなげる +35537,16385,たまごにめはな +35541,16385,たましいをいれかえる +35541,16386,たましいをてんがいへとばす +35543,16385,だまされたとおもって +35581,16385,(たみは)よらしむべし、しらしむべからず +35594,16385,ためにする +35597,16385,だめでもともと +35597,16386,だめをおす +35597,16387,だめをだす +35628,16385,たもとをしぼる +35628,16386,たもとをつらねる +35628,16387,たもとをわかつ +35717,16385,たれあって +35720,16385,だれあって +35720,16386,だれいうとなく +35720,16387,だれしらぬものがない +35720,16388,だれであれ +35720,16389,だれのめにも +35720,16390,だれ{はばか・はばか}らず +35758,16385,たわらをわる +35765,16385,たんをはっする +35798,16385,たんかをきる +35826,16385,たんきはそんき +35894,16385,だんしちゅうぼうにいらず +35905,16385,だんじておこなえばきしんもこれをさく +35924,16385,だんじょしちさいにしてせきをおなじゅうせず +36015,16385,たんちょにつく +36155,16385,ちにあしをつける +36155,16386,ちにおちる +36155,16387,ちをはらう +36156,16385,ちがかよう +36156,16386,ちがさわぐ +36156,16387,ちがのぼる +36156,16388,ちでちをあらう +36156,16389,ちとあせのけっしょう +36156,16390,ちとなりにくとなる +36156,16391,ちにうえる +36156,16392,ちのあせをながす +36156,16393,ちのあめがふる +36156,16394,ちのうみになる +36156,16395,ちのでる(よう) +36156,16396,ちのなみだをながす +36156,16397,ちのにじむ(よう) +36156,16398,ちはみずよりもこし +36156,16399,ちもなみだもない +36156,16400,ちわきにくおどる +36156,16401,ちをひく +36156,16402,ちをみる +36156,16403,ちをわける +36158,16385,ちにいてらんをわすれず +36199,16385,ちえがまわる +36199,16386,ちえをつける +36214,16385,チェックをいれる +36229,16385,ちかにもぐる +36269,16385,ちからになる +36269,16386,ちからをいれる +36269,16387,ちからをおとす +36269,16388,ちからをかす +36269,16389,ちからをかりる +36269,16390,ちからをこめる +36273,16385,ちきをえる +36469,16385,ちっとやそっと +36490,16385,ちにくをわけあう +36499,16385,ちのけがおおい +36545,16385,ちまつりにあげる +36552,16385,ちみちをあげる +36564,16385,ちゃにする +36708,16385,ちゃばらもいっとき +36768,16385,ちゅうにうく +36834,16385,ちゅうげんにしかをおう +36836,16385,ちゅうげんみみにさからう +36883,16385,ちゅうしんにくんにつかえず +36979,16385,ちゅうもんをつける +36980,16385,ちゅうやをわかたず +37011,16385,ちょにつく +37012,16385,ちよにやちよに +37026,16385,ちょうよはなよ +37124,16385,ちょうしがいい +37124,16386,ちょうしにのる +37124,16387,ちょうしのなみにのる +37124,16388,ちょうしをあわせる +37124,16389,ちょうしをとる +37167,16385,ちょうずをつかう +37199,16385,ちょうだをいっする +37220,16385,ちょうちんにつりがね +37303,16385,ちょうやのゆめをさます +37452,16385,ちょっとやそっと +37497,16385,ちりもつもればやまとなる +37497,16386,ちりをきる +37541,16385,ちんとする +37648,16385,ちんもくはきん +37648,16386,ちんもくをまもる +37648,16387,ちんもくをやぶる +37700,16385,ついずいをゆるさない +37867,16385,つえともはしらともたのむ +37867,16386,つえをひく +37910,16385,つきがみちる +37910,16386,つきとすっぽん +37910,16387,つきにむらくも、はなにかぜ +37910,16388,つきみちて +37917,16385,つぎからつぎへ +38015,16385,つきよにかまをぬかれる +38015,16386,つきよに{ちょうちん・ちょうちん} +38062,16385,つけがまわってくる +38062,16386,つけをまわす +38156,16385,つちいっしょうかねいっしょう +38156,16386,つちがつく +38156,16387,つちにかえる +38156,16388,つちにしたしむ +38156,16389,つちをふむ +38261,16385,つなをはる +38271,16385,つなみてんでんこ +38273,16385,つねにない +38280,16385,つのをためてうしをころす +38280,16386,つのをはやす +38290,16385,つばをつける +38303,16385,つぶがそろっている +38307,16385,つぶしがきく +38326,16385,つぼにはまる +38366,16385,つみがない +38366,16386,つみをきせる +38366,16387,つみをきる +38398,16385,つむじをまげる +38402,16385,つめにひをともす +38402,16386,つめの{あか・あか}ほど +38402,16387,つめの{あか・あか}をせんじてのむ +38402,16388,つめをとぐ +38402,16389,つめをのばす +38405,16385,つめあとをのこす +38418,16385,つめばらをきらせる +38478,16385,つらのかわがあつい +38478,16386,つらのかわがせんまいばりだ +38532,16385,つったさかなにえさを〈やら/あげ〉ない +38589,16385,てがあがる +38589,16386,てがうしろにまわる +38589,16387,てがかかる +38589,16388,てがこむ +38589,16389,てがすく +38589,16390,てがつく +38589,16391,てがつけられない +38589,16392,てがでない +38589,16393,てがでる +38589,16394,てがとどかない +38589,16395,てがとどく +38589,16396,てがない +38589,16397,てがながい +38589,16398,てがのびる +38589,16399,てがはいる +38589,16400,てがはなせない +38589,16401,てがはなれる +38589,16402,てがはやい +38589,16403,てがまわる +38589,16404,てにあせ(を)にぎる +38589,16405,てにあまる +38589,16406,てにいった +38589,16407,てにいれる +38589,16408,てにおえない +38589,16409,てにおちる +38589,16410,てにかける +38589,16411,てにしょくをつける +38589,16412,てにする +38589,16413,てにつかない +38589,16414,てにてをとって +38589,16415,てにとる +38589,16416,てにとるよう +38589,16417,てにのる +38589,16418,てのきれるよう +38589,16419,てのほどこしようがない +38589,16420,てのまいあしのふむところをしらず +38589,16421,てもあしもでない +38589,16422,てをあげる +38589,16423,てをあげろ +38589,16424,てをあわせる +38589,16425,てをいれる +38589,16426,てをうごかす +38589,16427,てをうつ +38589,16428,てをかえしなをかえ +38589,16429,てをかける +38589,16430,てをかす +38589,16431,てをかりる +38589,16432,てをきる +38589,16433,てをくだす +38589,16434,てをくむ +38589,16435,てをくわえる +38589,16436,てをこまねく +38589,16437,てをしめる +38589,16438,てをすかす +38589,16439,てをそめる +38589,16440,てをだす +38589,16441,てをたずさえる +38589,16442,てを{つか・つか}ねる +38589,16443,てをつくす +38589,16444,てをつける +38589,16445,てをとおす +38589,16446,てをとる +38589,16447,てをにぎる +38589,16448,てをぬく +38589,16449,てをのばす +38589,16450,てをひく +38589,16451,てをひろげる +38589,16452,てをほどこす +38589,16453,てをまわす +38589,16454,てをむすぶ +38589,16455,てをやく +38589,16456,てをやすめる +38589,16457,てをゆるめる +38589,16458,てをよごす +38589,16459,てをわかつ +38589,16460,てをわずらわす +38599,16385,てあかのついた +38776,16385,ていしゅのすきなあか{えぼし・えぼし} +38776,16386,ていしゅをしりにしく +38981,16385,テープをきる +38982,16385,テーブルにのせる +39021,16385,てかせあしかせ +39026,16385,てがたなをきる +39040,16385,てきにしおをおくる +39040,16386,てきにまわす +39040,16387,てきはほんのうじにあり +39040,16388,てきもさるもの +39155,16385,てぐすね(を)ひく +39156,16385,てぐせがわるい +39179,16385,てこでもうごかない +39186,16385,てごころをくわえる +39226,16385,てしおにかける +39293,16385,てだまにとる +39300,16385,てつはあついうちにうて +39301,16385,てつをふむ +39439,16385,てなべさげても +39459,16385,てのうらをかえすよう +39462,16385,てのひら(のうえ)でおどる +39462,16386,てのひらをかえすよう +39494,16385,ではなをくじく +39496,16385,でばなをくじく +39526,16385,てぶくろをなげる +39670,16385,でる{くい・くい}はうたれる +39670,16386,でるところへでる +39670,16387,でるまくではない +39711,16385,てんからふったかちからわいたか +39711,16386,てんたかくうまこゆ +39711,16387,てんにつばする +39711,16388,てんににじつなくくにににおうなし +39711,16389,てんにもちにも +39711,16390,てんにものぼるここち +39711,16391,てんのなせる +39711,16392,てんはにぶつをあたえず +39711,16393,てんはみずからたすくるものをたすく +39711,16394,てんをあおぐ +39711,16395,てんをまする +39736,16385,てんかをとる +39794,16385,てんぐになる +39823,16385,てんさいはわすれたころにやってくる +39855,16385,てんじゅをまっとうする +39865,16385,てんじょうてんげゆいがどくそん +39866,16385,てんじょうをうつ +39888,16385,てんすうをかせぐ +39898,16385,てんせきこけむさず +39951,16385,てんてきいしを{うが・うが}つ +40001,16385,てんばくうをゆく +40022,16385,てんびんにかける +40047,16385,てんぼうがない +40065,16385,てんもうかいかいそにしてもらさず +40104,16385,どがすぎる +40104,16386,どをうしなう +40104,16387,どをくわえる +40104,16388,どをこす +40128,16385,といえばうそになる +40145,16385,トイレがちかい +40150,16385,とうをえる +40159,16385,とうがたつ +40161,16385,とうにおちずかたるにおちる +40161,16386,とうはいっときのはじ +40177,16385,どうにいる +40181,16385,どうということはない +40224,16385,とうかしたしむべきこう +40234,16385,どうかとおもう +40261,16385,とうかんに(ふ)する +40447,16385,どうじつのだんで(は)ない +40585,16385,とうだいもとくらし +40673,16385,どうにもしようがない +40673,16386,どうにもならない +40683,16385,どうのこうの +40726,16385,どうびょうあいあわれむ +40728,16385,とうふにかすがい +40822,16385,とうりものいわざれども、したおのずからみちをなす +40877,16385,とおくのしんせきよりちかくのたにん +40916,16385,とおめがきく +40965,16385,ときはかねなり +40965,16386,ときをうつさず +40965,16387,ときをわかたず +40986,16385,ときしもあれ +41016,16385,どぎもをぬく +41040,16385,とくとする +41040,16386,とくはこならず +41050,16385,どくにもくすりにもならない +41050,16386,どくをあおぐ +41050,16387,どくをくらわばさらまで +41050,16388,どくをもってどくをせいする +41110,16385,どくしょひゃっぺん〈ぎ/い〉おのずからつうず +41238,16385,とぐろをまく +41259,16385,とこにつく +41259,16386,とこに〈ふす/ふせる〉 +41259,16387,とこをとる +41261,16385,どこにめをつけているのか +41261,16386,どこのうまのほねかわからない +41261,16387,どこふくかぜ +41261,16388,どこをおせば +41261,16389,どこをき(りと)っても +41261,16390,どこを{たた・たた}いても +41261,16391,どこを{たた・たた}けばそんなねがでるのか +41298,16385,ところかまわず +41298,16386,ところかわればしなかわる +41298,16387,ところきらわず +41298,16388,ところをえる +41309,16385,とさかにくる +41320,16385,としがあける +41320,16386,としがいく +41320,16387,としにはかてない +41320,16388,としはあらそえない +41320,16389,としをおって +41320,16390,としをこす +41320,16391,としをとる +41326,16385,どじをふむ +41367,16385,としはもいかぬ +41373,16385,としまわりがわるい +41380,16385,としゅなおじせず +41384,16385,としょにひかれるひつじのよう +41442,16385,どそくでふみこむ +41477,16385,どちらにころんでも +41502,16385,どっきにあてられる +41502,16386,どっきをぬかれる +41528,16385,とっこにとる +41551,16385,どっちへころんでも +41551,16386,どっちもどっち +41597,16385,どつぼにはまる +41621,16385,ととうをくむ +41640,16385,とどまるところをしらない +41641,16385,とどめをさす +41660,16385,となりのしばふは〈あおい/うつくしい〉 +41660,16386,となりのふこうはかものあじ +41676,16385,どのくち〈が/で〉いうのか +41676,16386,どのつらさげて +41778,16385,どひょうにつまる +41778,16386,どひょうをわる +41782,16385,とぶとりをおとすいきおい +41782,16386,とぶようにうれる +41782,16387,とんでひにいるなつのむし +41784,16385,どぶにすてる +41798,16385,とほうにくれる +41839,16385,とむねをつかれる +41888,16385,ともづなをとく +41893,16385,ともにする +41893,16386,ともにてんを{いただ・いただ}かず +41906,16385,とやにつく +41924,16385,とらのいをかる{きつね・きつね} +41924,16386,とらのおをふむ +41924,16387,とらはししてかわをのこす +41924,16388,とらをのにはなつ +41938,16385,ドライブがかかる +42013,16385,とりなきさとの{こうもり・こうもり} +42109,16385,とりつくしまもない +42125,16385,とりとめ(が)ない +42146,16385,とりはだがたつ +42197,16385,とるにたりない +42197,16386,とるものもとりあえず +42198,16385,とらぬ{たぬき・たぬき}のかわざんよう +42272,16385,どろのようにねむる +42272,16386,どろをかぶる +42272,16387,どろをぬる +42272,16388,どろをはく +42308,16385,どろぼうにおいせん +42308,16386,どろぼうにもさんぶのり +42308,16387,どろぼうをとらえてみればわがこなり +42308,16388,どろぼうをみてなわをなう +42319,16385,どろんをきめる +42325,16385,とわのねむりにつく +42412,16385,とんびが{たか・たか}をうむ +42412,16386,とんびにあぶらあげをさらわれる +42422,16385,とんぼをきる +42431,16385,なに(し)おう +42431,16386,なにはじない +42431,16387,な(の)ある +42431,16388,なのとおった +42431,16389,なはたいをあらわす +42431,16390,なもない +42431,16391,なをあげる +42431,16392,なをうる +42431,16393,なをおしむ +42431,16394,なをかりる +42431,16395,なをすててじつをとる +42431,16396,なをつらねる +42431,16397,なをなす +42431,16398,なをのこす +42431,16399,なをはずかしめる +42431,16400,なを{は・は}せる +42444,16385,ないそではふれぬ +42444,16386,ないでもない +42444,16387,ないものはない +42444,16388,なかったことにする +42444,16389,なくてななくせ +42624,16385,ながいねむりにつく +42625,16385,ながいめでみる +42625,16386,ながいものにはまかれろ +42625,16387,ながい{わらじ・わらじ}をはく +42710,16385,ながのいとまをつげる +42755,16385,ながれに{さお・さお}さす +42755,16386,ながれを{く・く}む +42761,16385,なきをいれる +42761,16386,なきをみる +42764,16385,なきにひとしい +42764,16386,なきをえない +42787,16385,なきだしそうな +42790,16385,なき(っ)つらにはち +42802,16385,なきべそをかく +42808,16385,なきものにする +42817,16385,ないてばしょくをきる +42817,16386,ないてもわらっても +42817,16387,なくことじとうにはかてぬ +42817,16388,なくこもだまる +42817,16389,なくになけない +42840,16385,なくんばあるべからず +42885,16385,なさけが{あだ・あだ}となる +42885,16386,なさけはひとのためならず +42912,16385,なせばなる +42940,16385,なたをふるう +42952,16385,なだれをうつ +42960,16385,なつとなる +43029,16385,ななえのひざをやえにおって +43047,16385,なにくわぬかお +43047,16386,なににもまして +43047,16387,なにはさておき +43047,16388,なには(とも)あれ +43047,16389,なにはなくとも +43047,16390,なにふじゆうなく +43047,16391,なにをおっしゃるうさぎさん +43050,16385,なにがかなしくて +43053,16385,なにからなにまで +43063,16385,なにするものぞ +43084,16385,なにわの{あし・あし}はいせの{はまおぎ・はまおぎ} +43085,16385,なにを{お・お}いても +43085,16386,なにをかいわんや +43085,16387,なにをかくそう +43097,16385,なのりをあげる +43116,16385,なべをふる +43141,16385,なまえをうる +43141,16386,なまえをかりる +43148,16385,なまきをさく +43152,16385,なまくびをきる +43155,16385,なまけもののせっくばたらき +43180,16385,なまつばをのみこむ +43196,16385,なまびょうほうはおおけがのもと +43212,16385,なまよいほんしょう{たが・たが}わず +43221,16385,なみしずか +43221,16386,なみたかし +43221,16387,なみにのる +43237,16385,なみだにくれる +43237,16386,なみだにしずむ +43237,16387,なみだにむせぶ +43237,16388,なみだをおさえる +43237,16389,なみだをおぼえる +43237,16390,なみだをきんじえない +43237,16391,なみだをさそう +43237,16392,なみだをそそぐ +43237,16393,なみだを{の・の}む +43237,16394,なみだをふるって +43237,16395,なみだをもよおす +43283,16385,ならいせいとなる +43286,16385,ならうよりなれよ +43305,16385,ならぬかんにんするがかんにん +43318,16385,なりをひそめる +43380,16385,なわをうつ +43426,16385,なんくせをつける +43446,16385,なんじじしんをしれ +43466,16385,なんぞしらん +43466,16386,なんぞはからん +43513,16385,なんのことはない +43550,16385,にがおもい +43550,16386,にがかつ +43592,16385,にえゆをのまされる +43610,16385,にかいからめぐすり +43610,16386,にかいへあげてはしごをはずす +43620,16385,にがしたさかなはおおきい +43629,16385,にがむしになる +43629,16386,にがむしをかみつぶしたようなかお +43652,16385,にくがあがる +43652,16386,にくをきらせてほねをたつ +43704,16385,にくまれっこよに{はばか・はばか}る +43707,16385,にくんでもあまりある +43718,16385,にげをうつ +43739,16385,にげるがかち +43766,16385,にさんにとどまらない +43770,16385,にしもひがしもわからない +43773,16385,にじのはしをわたる +43780,16385,にしきをかざる +43831,16385,にせをちぎる +43836,16385,にせものをつかまされる +43844,16385,にそくのわらじをはく +43869,16385,にちにちこれこうじつ +43949,16385,にとをおう +43950,16385,にどあることはさんどある +43976,16385,にのあしをふむ +43978,16385,にのくがつげない +44244,16385,にてもやいてもくえない +44244,16386,にるなりやくなり +44258,16385,にわとりがさきかたまごがさきか +44258,16386,にわとりをさくにぎゅうとうをもちいる +44261,16385,にんをみてほうをとけ +44275,16385,にんげんいたるところせいざんあり +44275,16386,にんげんのかわをかぶった +44275,16387,にんげんばんじさいおうがうま +44340,16385,ぬかに{くぎ・くぎ} +44350,16385,ぬかみそがくさる +44369,16385,ぬきさしならない +44381,16385,ぬきつぬかれつ +44389,16385,ぬけがいい +44404,16385,ぬけめ(が)ない +44409,16385,ぬすっとたけだけしい +44409,16386,ぬすっとにおいせん +44409,16387,ぬすっとにもさんぶのり +44409,16388,ぬすっとのひるね +44409,16389,ぬすっとをとらえてみればわがこなり +44458,16385,ぬるまゆにつかる +44471,16385,ぬれて〈で/に〉{あわ・あわ} +44477,16385,ぬれぬさきこそつゆをも{いと・いと}え +44480,16385,ねをあげる +44481,16385,ねがはる +44481,16386,ねをけす +44482,16385,ねがはえる +44482,16386,ねにもつ +44482,16387,ねもはもない +44482,16388,ねをおろす +44482,16389,ねをすえる +44518,16385,ねおきがわるい +44524,16385,ねがってもない +44553,16385,ねくびを{か・か}く +44561,16385,ねこにかつおせち +44561,16386,ねこにこばん +44561,16387,ねこにまたたび +44561,16388,ねこのくびにすずをつける +44561,16389,ねこのこいっぴきいない +44561,16390,ねこのてもかりたい +44561,16391,ねこも{しゃくし・しゃくし}も +44561,16392,ねこを{かぶ・かぶ}る +44601,16385,ねざめがわるい +44603,16385,ねじをまく +44632,16385,ねずみにひかれる +44643,16385,ねたばをあわせる +44656,16385,ねつがさめる +44656,16386,ねつにうかされる +44656,16387,ねつをあげる +44703,16385,ねってつをのむおもい +44777,16385,ねみみにみず +44816,16385,ねたこをおこす +44816,16386,ねていて +44816,16387,ねてもさめても +44826,16385,ねんをいれる +44826,16386,ねんをおす +44844,16385,ねんきがはいる +44917,16385,ねんりんをきざむ +44929,16385,のうある{たか・たか}はつめをかくす +44929,16386,のうがない +44929,16387,のうではない +44983,16385,のうじおわれりとする +45137,16385,のしをつけて〈しんていする/さしあげる〉 +45213,16385,のどがなる +45213,16386,のどからてがでる +45213,16387,のどにささったとげ +45213,16388,のどまででかかる +45218,16385,のどくびをおさえる +45226,16385,のどもとすぎればあつさをわすれる +45226,16386,のどもとまででかかる +45363,16385,のりとはさみ +45374,16385,のりかかったふね +45412,16385,のれんにうでおし +45412,16386,のれんをおろす +45412,16387,のれんをわける +45420,16385,のろしをあげる +45450,16385,はがたたない +45454,16385,はがうくよう +45454,16386,はがたたない +45454,16387,はに{きぬ・きぬ}きせぬ +45454,16388,はにははを +45454,16389,はのぬけたよう +45454,16390,はのねがあわない +45454,16391,はをくいしばる +45454,16392,はをみせる +45530,16385,ハードルがたかい +45575,16385,はいになる +45693,16385,はいぐんのしょう、へいをかたらず +45922,16385,はいふをつく +46009,16385,はえばたて、たてばあゆめのおやごころ +46050,16385,ばかと{はさみ・はさみ}はつかいよう +46050,16386,ばかにする +46050,16387,ばかにつけるくすりはない +46050,16388,ばかにならない +46050,16389,ばかをいえ +46050,16390,ばかをみる +46117,16385,はかりにかける +46151,16385,はきだめにつる(がおりたよう) +46160,16385,ばきゃくをあらわす +46179,16385,はいてすてるほど +46184,16385,ばくにつく +46226,16385,はくじつのもとに +46228,16385,はくしゃをかける +46275,16385,ばくだんをかかえる +46283,16385,はくちゅうのかんにある +46311,16385,はくはつさんぜんじょう +46320,16385,はくひょうをふむおもい +46394,16385,ばけのかわがはがれる +46458,16385,はさみをいれる +46471,16385,はしがころんでもおかしい +46471,16386,はしがころんでもわらう +46471,16387,はしがすすむ +46471,16388,はしにもぼうにもかからない +46473,16385,はじをかく +46473,16386,はじを{すす・すす}ぐ +46482,16385,はしかのようなもの +46494,16385,はしごをはずされる +46521,16385,はじめよければおわりよし +46547,16385,ばじょうゆたかに +46566,16385,はすにかまえる +46571,16385,バスにのりおくれる +46619,16385,はずみをくらう +46638,16385,ばせいをあびせる +46660,16385,はたのみるめも +46662,16385,はたをあげる +46662,16386,はたをふる +46662,16387,はたをまく +46665,16385,はだがあわない +46665,16386,はだでかんじる +46665,16387,はだをいれる +46665,16388,はだをゆるす +46780,16385,はちのじをよせる +46781,16385,はちのすをつ(つ)いたよう +46814,16385,ばつがわるい +46858,16385,はっきゅうをおう +46864,16385,はっきりいって +47015,16385,ぱっとしない +47032,16385,はっぱをかける +47114,16385,はとがまめでっぽうをくったよう +47144,16385,バトンをわたす +47145,16385,はながさく +47145,16386,はなにあらし +47145,16387,はなはさくらぎひとはぶし +47145,16388,はなもはじらう +47145,16389,はなもみもある +47145,16390,はなより{だんご・だんご} +47145,16391,はなをさかせる +47145,16392,はなをそえる +47145,16393,はなをもたせる +47147,16385,はなもひっかけない +47148,16385,はながあぐらをかく +47148,16386,はながきく +47148,16387,はながたかい +47148,16388,はながまがる +47148,16389,はなであしらう +47148,16390,はなでわらう +47148,16391,はなにかかる +47148,16392,はなにかける +47148,16393,はなにつく +47148,16394,はなをあかす +47148,16395,はなをおる +47148,16396,はなをたかくする +47148,16397,はなをつきあわす +47148,16398,はなをつく +47148,16399,はなをつままれてもわからない +47148,16400,はなをつまむ +47148,16401,はなをならす +47156,16385,はないきがあらい +47156,16386,はないきをうかがう +47185,16385,はなげをのばす +47185,16386,はなげをよまれる +47195,16385,はなしがおちる +47195,16386,はなしがかみあわない +47195,16387,はなしがつく +47195,16388,はなしがとおい +47195,16389,はなしがはずむ +47195,16390,はなしがはやい +47195,16391,はなしがみえない +47195,16392,はなしかわって +47195,16393,はなしにならない +47195,16394,はなしにはながさく +47195,16395,はなしにみがはいる +47195,16396,はなしのほをつぐ +47195,16397,はなしをあわせる +47195,16398,はなしをつける +47195,16399,はなしをとおす +47221,16385,はなぢもでない +47229,16385,はなっぱしらがつよい +47229,16386,はなっぱしらをへしおる +47241,16385,はなのさきであしらう +47242,16385,はなのしたがながい +47242,16386,はなのしたがひあがる +47242,16387,はなのしたをのばす +47283,16385,はなもちならない +47314,16385,はねがはえたよう +47314,16386,はねをのばす +47353,16385,はははつよし +47354,16385,はばをきかせる +47356,16385,ババをつかむ +47356,16386,ババをひく +47469,16385,はめをはずす +47485,16385,はもんをとうじる +47485,16386,はもんをひろげる +47485,16387,はもんをよぶ +47500,16385,はやおきはさんもんのとく +47507,16385,はやがねをうつ +47556,16385,はやめしもげいのうち +47569,16385,はらがいえる +47569,16386,はらがおおきくなる +47569,16387,はらがくだる +47569,16388,はらがくろい +47569,16389,はらがすわる +47569,16390,はらがたつ +47569,16391,はらができる +47569,16392,はらがはる +47569,16393,はらがふくれる +47569,16394,はらがふとい +47569,16395,はらがへる +47569,16396,はらがよじれる +47569,16397,はらにいちもつ +47569,16398,はらにおさめる +47569,16399,はらにおちる +47569,16400,はらにすえかねる +47569,16401,はらのかわがよじれる +47569,16402,はらはちぶんめにいしゃいらず +47569,16403,はらもみのうち +47569,16404,はらをあわせる +47569,16405,はらをいためる +47569,16406,はらをいやす +47569,16407,はらをかかえる +47569,16408,はらをかためる +47569,16409,はらをきめる +47569,16410,はらをきる +47569,16411,はらをくくる +47569,16412,はらをくだす +47569,16413,はらをこしらえる +47569,16414,はらをこやす +47569,16415,はらをこわす +47569,16416,はらをさぐる +47569,16417,はらをすえる +47569,16418,はらをたてる +47569,16419,はらをめす +47569,16420,はらをよじる +47569,16421,はらをよむ +47569,16422,はらをわる +47669,16385,はらわたがにえくりかえる +47669,16386,はらわたがよじれる +47759,16385,はるたつ +47759,16386,はるをうる +47816,16385,ばれいをかさねる +47839,16385,はれものにさわるよう +47873,16385,はんでおしたよう +47878,16385,はんをたれる +47893,16385,ばんいかんなきをきす +47893,16386,ばんやむをえず +47960,16385,はんきをひるがえす +48056,16385,ばんしにいっしょうをえる +48058,16385,ばんじきゅうす +48185,16385,ばんちゃもでばな +48371,16385,ばんりょくそうちゅうこういってん +48387,16385,ひいずるくに +48387,16386,ひいちにちと +48387,16387,ひがあさい +48387,16388,ひがあたらない +48387,16389,ひがくれてしまう +48387,16390,ひくれてみちとおし +48387,16391,ひにつきに +48387,16392,ひのあたるばしょ +48387,16393,ひをあらためて +48387,16394,ひをうつす +48387,16395,ひをおって +48387,16396,ひをおなじくしてろんずべきでない +48388,16385,ひをみない +48389,16385,ひがつく +48389,16386,ひがでる +48389,16387,ひがはいる +48389,16388,ひにあぶらをそそぐ +48389,16389,ひにかける +48389,16390,ひのきえたよう +48389,16391,ひのついたよう +48389,16392,ひのでるよう +48389,16393,ひのないところにけむりはたたぬ +48389,16394,ひのなかみずのなか +48389,16395,ひをいれる +48389,16396,ひをおとす +48389,16397,ひをかける +48389,16398,ひをしっする +48389,16399,ひをつける +48389,16400,ひをとおす +48389,16401,ひをふく +48389,16402,ひをみるより(も)あきらか +48390,16385,ひがはいる +48390,16386,ひをけす +48390,16387,ひをたやす +48393,16385,ひのうちどころがない +48393,16386,ひをならす +48405,16385,びにいりさいをうがつ +48470,16385,ピークをうつ +48494,16385,ピースがそろう +48643,16385,ひかりをあてる +48801,16385,ひくにひけない +48821,16385,びくともしない +48912,16385,ひざがぬける +48912,16386,ひざがわらう +48912,16387,ひざともだんごう +48912,16388,ひざをうつ +48912,16389,ひざをおる +48912,16390,ひざをかかえる +48912,16391,ひざをくずす +48912,16392,ひざをくっする +48912,16393,ひざをくむ +48912,16394,ひざをすすめる +48912,16395,ひざをただす +48912,16396,ひざをつきあわせる +48912,16397,ひざをつめる +48912,16398,ひざをのりだす +48912,16399,ひざをまじえる +48935,16385,ひさしをかしておもやをとられる +49088,16385,ひすればはな +49131,16385,ひそみにならう +49142,16385,ひたいがくもる +49142,16386,ひたいでみる +49142,16387,ひたいにあせする +49142,16388,ひたいをあつめる +49162,16385,ひだねをのこす +49238,16385,ひっけんをあらたにする +49245,16385,ひっこみがつかない +49255,16385,ひっしにつくしがたい +49259,16385,ひつじのかわをかぶった{おおかみ・おおかみ} +49272,16385,ひつじんをはる +49277,16385,ひつぜつにつくしがたい +49293,16385,ピッチがはやい +49293,16386,ピッチをあげる +49340,16385,ひつようにせまられる +49340,16386,ひつようははつめいのはは +49358,16385,ひとあって +49358,16386,ひとがいい +49358,16387,ひとがかわる +49358,16388,ひとがわるい +49358,16389,ひととなる +49358,16390,ひとにはそうてみよ、うまにはのってみよ +49358,16391,ひとのうわさもしちじゅうごにち +49358,16392,ひとのくちにとはたてられない +49358,16393,ひとのふこうはみつのあじ +49358,16394,ひとのふりみてわがふりなおせ +49358,16395,ひとのふんどしですもうをとる +49358,16396,ひとはいちだい、なはまつだい +49358,16397,ひとはパンのみにていくるものにあらず +49358,16398,ひともなげ +49358,16399,ひとよんで +49358,16400,ひとをえらぶ +49358,16401,ひとをくう +49358,16402,ひとをたてる +49358,16403,ひとをのろわばあなふたつ +49358,16404,ひとをばかにする +49358,16405,ひとをひとともおもわない +49358,16406,ひとをみたらどろぼうとおもえ +49358,16407,ひとをみてほうをとけ +49364,16385,ひとあせ〈かく/ながす〉 +49369,16385,ひとあわふかせる +49372,16385,ひといきいれる +49398,16385,ひとかたつく +49406,16385,ひとかわ{む・む}く +49406,16386,ひとかわ{む・む}ける +49430,16385,ひとこえせんりょう +49462,16385,ひとすじなわではいかない +49472,16385,ひとたまりもない +49478,16385,ひとつあなの{むじな・むじな} +49478,16386,ひとつうえ +49478,16387,ひとつかまのめしをくう +49478,16388,ひとつまちがえば +49484,16385,ひとっこひとり +49524,16385,ひとはたあげる +49525,16385,ひとはだぬぐ +49528,16385,ひとはなさかせる +49537,16385,ひとふでしめしまいらせそろ +49540,16385,ひとふろあびる +49542,16385,ひとほねおる +49554,16385,ひとみをこらす +49561,16385,ひとめにたつ +49561,16386,ひとめにつく +49561,16387,ひとめをしのぶ +49561,16388,ひとめをはばかる +49561,16389,ひとめをひく +49568,16385,ひとやくかう +49570,16385,ひとやまあてる +49570,16386,ひとやまこす +49575,16385,ひとりぐちはくえぬがふたりぐちはくえる +49651,16385,ひのてがあがる +49658,16385,ひのめをみる +49676,16385,ひばなをちらす +49686,16385,ひびこれこうじつ +49688,16385,ひびがはいる +49725,16385,ひぶたをきる +49769,16385,ひまがいる +49769,16386,ひまにあかせて +49769,16387,ひまをおしむ +49769,16388,ひまをくう +49769,16389,ひまをさく +49769,16390,ひまをだす +49769,16391,ひまをつぶす +49769,16392,ひまをとる +49769,16393,ひまをぬすむ +49769,16394,ひまをみる +49834,16385,ひゃくもしょうち +49841,16385,ひゃくしゃくかんとういっぽをすすめる +49856,16385,ひゃくにちのせっぽう、{へ・へ}ひとつ +49858,16385,ひゃくねんかせいをまつ +49858,16386,ひゃくねんのこいもいちじにさめる +49862,16385,ひゃくぶんはいっけんにしかず +49892,16385,ひゃっぽゆずって +49896,16385,ひやみずを〈あびせる/かける〉 +49898,16385,ひやめしをくう +49943,16385,ひょうがあく +50020,16385,ひょうしをとる +50075,16385,ひょうそくがあわない +50081,16385,ひょうたんあいいれず +50082,16385,ひょうたんからこま(がでる) +50118,16385,びょうぶとしょうにんはまっすぐではたたぬ +50139,16385,ひょうめんきって +50145,16385,ひょうりをなす +50236,16385,ピリオドをうつ +50260,16385,ひるをあざむく +50360,16385,ひんにせまる +50367,16385,ピンからキリまで +50367,16386,ピンをはねる +50386,16385,ひんくにせまる +50407,16385,ひんしゅくをかう +50414,16385,ひんすればどんする +50440,16385,ぴんとくる +50462,16385,びんぼうひまなし +50462,16386,びんぼうゆるぎもしない +50489,16385,ふにおちる +50489,16386,ふのぬけたよう +50497,16385,ぶがあつい +50497,16386,ぶがある +50497,16387,ぶがわるい +50498,16385,ぶにあわない +50592,16385,ふいをうつ +50592,16386,ふいをくらう +50645,16385,ぶいぶいいわせる +50665,16385,フィルムにおさめる +50690,16385,ふううんきゅうをつげる +50709,16385,ふうげつをともとする +50779,16385,ふうふげんかはいぬもくわない +50810,16385,ふえふけどおどらず +50810,16386,ふえをふく +50936,16385,ふかくをとる +50987,16385,ふきのきゃくとなる +51060,16385,ふぎょうてんちにはじない +51081,16385,ふくをきて(あるいて)いる +51084,16385,ふくはうち、おにはそと +51087,16385,ふけばとぶよう +51091,16385,ふぐはくいたしいのちはおしし +51173,16385,ふくすいぼんにかえらず +51243,16385,ふくむところがある +51362,16385,ぶしににごんはない +51362,16386,ぶしはあいみたがい +51362,16387,ぶしはくわねどたか{ようじ・ようじ} +51503,16385,ふせつをあわせるよう +51532,16385,ふたをあける +51532,16386,ふたをする +51536,16385,ぶたにしんじゅ +51536,16386,ぶたもおだてりゃきにのぼる +51541,16385,ぶたいにたつ +51566,16385,ふたつにひとつ +51583,16385,ふためとみられない +51669,16385,ぶつぎをかもす +51805,16385,ぶつもんにはいる +51815,16385,ふでがあれる +51815,16386,ふでがすべる +51815,16387,ふでがたつ +51815,16388,ふでをおく +51815,16389,ふでをおこす +51815,16390,ふでをおる +51815,16391,ふでをおろす +51815,16392,ふでをくわえる +51815,16393,ふでをそめる +51815,16394,ふでをたつ +51815,16395,ふでをとる +51815,16396,ふでをはしらせる +51815,16397,ふでをふるう +51815,16398,ふでをまげる +51877,16385,ふとくのいたすところ +51883,16385,ふところがあたたかい +51883,16386,ふところがいたむ +51883,16387,ふところが〈さびしい/さむい〉 +51883,16388,ふところがふかい +51883,16389,ふところにいれる +51883,16390,ふところにする +51883,16391,ふところにはいる +51883,16392,ふところをいためる +51883,16393,ふところをこやす +51951,16385,ふねを{こ・こ}ぐ +52083,16385,ふもんにふす +52089,16385,ふゆきたりなばはるとおからじ +52287,16385,ふりあげたこぶしのおろしどころ +52417,16385,ふってわいた +52422,16385,ふるいにかける +52457,16385,ふるきを{たず・たず}ねてあたらしきをしる +52601,16385,ふれなばおちん +52898,16385,ふんどしをしめる +52934,16385,ぶんぼをはらう +52978,16385,{へ・へ}でもない +52978,16386,{へ・へ}ともおもわない +52978,16387,{へ・へ}のつっぱりにもならない +53013,16385,へいを〈あげる/おこす〉 +53013,16386,へいをおさめる +53013,16387,へいをかまえる +53013,16388,へいをもよおす +53144,16385,へいたんをひらく +53148,16385,へいちにはらんをおこす +53198,16385,へいりのごとくすてる +53317,16385,へそがちゃをわかす +53317,16386,へそをまげる +53321,16385,へそのおきっていらい +53325,16385,へたなてっぽうもかずうちゃあたる +53325,16386,へたのかんがえやすむににたり +53325,16387,へたをうつ +53325,16388,へた(を)すると +53526,16385,へびににらまれた{かえる・かえる} +53587,16385,へるもんじゃなし +53632,16385,べんにきょうする +53633,16385,ペンはけんよりもつよし +53633,16386,ペンをいれる +53633,16387,ペンをおく +53633,16388,ペンをおる +53633,16389,ペンをとる +53633,16390,ペンをはしらせる +53709,16385,べんじょがちかい +53760,16385,ベンチをあたためる +53812,16385,ぺんぺんぐさがはえる +53843,16385,ほをいつにする +53847,16385,ほにでる +53893,16385,ポイントがたかい +53893,16386,ポイントをおとす +53895,16385,ほうにてらす +53909,16385,ぼうにふる +53909,16386,ぼうを{の・の}んだよう +53910,16385,ぼうをもってぼうにむくいる +53961,16385,ほうかをまじえる +54190,16385,ぼうずにくけりゃ{けさ・けさ}までにくい +54256,16385,ぼうちゅうかんあり +54259,16385,ほうちょうをにぎる +54391,16385,ほうもうをくぐる +54394,16385,ほうもんをひらく +54464,16385,ほおがおちる +54464,16386,ほおがゆるむ +54464,16387,ほおをそめる +54464,16388,ほおをつねる +54464,16389,ほおをふくらませる +54469,16385,ほおがえしがつかない +54673,16385,ぼけつをほる +54685,16385,ほこをおさめる +54687,16385,ほごにする +54716,16385,ほこりをかぶる +54735,16385,ほしがふる +54735,16386,ほしがわれる +54735,16387,ほしになる +54735,16388,ほしのかずほど +54735,16389,ほしをいただく +54735,16390,ほしをおとす +54849,16385,ほぞをかためる +54849,16386,ほぞを{か・か}む +54850,16385,ほそくながく +54882,16385,ぼだいをとむらう +55006,16385,ほっぺたがおちそう +55006,16386,ほっぺたをつねる +55037,16385,ほどがある +55037,16386,ほどがいい +55037,16387,ほどへて +55044,16385,ほとけつくってたましいいれず +55044,16386,ほとけのかおもさんど(まで) +55071,16385,ほねがおれる +55071,16386,ほねとかわになる +55071,16387,ほねのずいまで +55071,16388,ほねまでしゃぶる +55071,16389,ほねを{うず・うず}める +55071,16390,ほねをおる +55071,16391,ほねをひろう +55073,16385,ほねおりぞんのくたびれもうけ +55079,16385,ほねっぷしがふとい +55087,16385,ほねみにこたえる +55087,16386,ほねみにしみる +55087,16387,ほねみをおしまない +55087,16388,ほねみをけずる +55269,16385,ほれたはれた +55316,16385,ぼんとしょうがつがいっしょにきたよう +55356,16385,ほんきにする +55356,16386,ほんきになる +55389,16385,ほんごしをいれる +55598,16385,ほんらいむいちもつ +55613,16385,まにうける +55614,16385,まがぬける +55614,16386,まがもてない +55614,16387,まがわるい +55614,16388,まにあう +55615,16385,まがさす +55663,16385,まいきょにいとまがない +55737,16385,マウンドをふむ +55738,16385,まえにおちる +55738,16386,まえをむく +55827,16385,まがりなりにも +55891,16385,まぎれもない +55895,16385,まくがあがる +55895,16386,まくがあく +55895,16387,まくがおりる +55895,16388,まくがきっておとされる +55895,16389,まくになる +55898,16385,まかぬたねははえぬ +55924,16385,まくらをかわす +55924,16386,まくらをたかくしてねる +55924,16387,まくらをならべる +55924,16388,まくらをぬらす +55953,16385,まけるがかち +55959,16385,まごにもいしょう +55997,16385,まさるともおとらない +56067,16385,マスクをかぶる +56117,16385,またにかける +56223,16385,まつがとれる +56225,16385,まて{しば・しば}し +56225,16386,まてどくらせど +56225,16387,まてばかいろのひよりあり +56242,16385,まっくろになって +56276,16385,まっせきをけがす +56296,16385,マットにしずむ +56351,16385,まとをいる +56351,16386,まとをえる +56390,16385,まないたにのせる +56397,16385,まなじりをけっする +56426,16385,まねきをうける +56436,16385,まのあたりにする +56457,16385,まぶたがあわない +56457,16386,まぶたがおもくなる +56457,16387,まぶたにうかぶ +56521,16385,まもりにはいる +56525,16385,まゆがくもる +56525,16386,まゆにつばをつける +56525,16387,まゆにひがつく +56525,16388,まゆひとつうごかさない +56525,16389,まゆを{つ・つ}りあげる +56525,16390,まゆをひそめる +56525,16391,まゆをひらく +56525,16392,まゆをよせる +56540,16385,まよえるひつじ +56570,16385,まるいたまごもきりよ(う)でしかく +56643,16385,まれにみる +56655,16385,まわしをきる +56657,16385,まわたでくびをしめるよう +56663,16385,まんをじす +56663,16386,まんをひく +56704,16385,まんざらでもない +56716,16385,まんじりともしない +56776,16385,まんぷくのしんらいをおく +56797,16385,まんめんしゅをそそぐ +56810,16385,みがたつ +56810,16386,みがちぢむ +56810,16387,みがはいる +56810,16388,みがもたない +56810,16389,みがもてない +56810,16390,みからでた{さび・さび} +56810,16391,みにあまる +56810,16392,みにおぼえがない +56810,16393,みにしみる +56810,16394,みにつける +56810,16395,みにつける +56810,16396,みにつまされる +56810,16397,みのおきどころがない +56810,16398,みのふりかた +56810,16399,みもふたもない +56810,16400,みもほそる +56810,16401,みもよもない +56810,16402,みをあやまる +56810,16403,みをいれる +56810,16404,みをうる +56810,16405,みをおく +56810,16406,みをおこす +56810,16407,みをかくす +56810,16408,みをかためる +56810,16409,みをきられる +56810,16410,みをきる +56810,16411,みをこがす +56810,16412,みをこにして +56810,16413,みを{ささ・ささ}げる +56810,16414,みをしずめる +56810,16415,みをじする +56810,16416,みをすててこそうかぶせもあれ +56810,16417,みをたてる +56810,16418,みをつつしむ +56810,16419,みをつつむ +56810,16420,みをていする +56810,16421,みをとうじる +56810,16422,みをなげる +56810,16423,みをのりだす +56810,16424,みをひく +56810,16425,みをひそめる +56810,16426,みをまかせる +56810,16427,みをもちくずす +56810,16428,みを{もっ・もっ}て +56810,16429,みをやつす +56810,16430,みをよせる +56811,16385,みがいる +56811,16386,みをむすぶ +56843,16385,ミイラとりがミイラになる +56857,16385,みえもがいぶんもない +56857,16386,みえをきる +56857,16387,みえをはる +56891,16385,みかけによらず +56914,16385,みぎからひだり +56914,16386,みぎといえばひだり +56914,16387,みぎにでるものがいない +56914,16388,みぎもひだりもわからない +56961,16385,みこしをあげる +56961,16386,みこしをかつぐ +56961,16387,みこしをすえる +56981,16385,みさおをたてる +56989,16385,みざるきかざるいわざる +57015,16385,ミシンをふむ +57020,16385,みずがあう +57020,16386,みずがあく +57020,16387,みずがつく +57020,16388,みずがでる +57020,16389,みずがはいる +57020,16390,みずきよければうお{す・す}まず +57020,16391,みずとあぶら +57020,16392,みずにおちたいぬはうて +57020,16393,みずにながす +57020,16394,みずになる +57020,16395,みずにのる +57020,16396,みずのひくきにつくよう +57020,16397,みずはほうえんのうつわにしたがう +57020,16398,みずもしたたる +57020,16399,みずも{た・た}まらず +57020,16400,みずももらさぬ +57020,16401,みずをあける +57020,16402,みずをうったよう +57020,16403,みずをえたうおのよう +57020,16404,みずをかける +57020,16405,みずをさす +57020,16406,みずをつかむ +57020,16407,みずをつける +57020,16408,みずをむける +57054,16385,みずから(を)じする +57166,16385,みせをたたむ +57183,16385,みぜにをきる +57195,16385,みぜんにふせぐ +57196,16385,みそも{くそ・くそ}もいっしょに +57196,16386,みそをつける +57250,16385,みちがひらける +57250,16386,みちなきみち +57250,16387,みちをけっする +57250,16388,みちをつける +57250,16389,みちをひらく +57250,16390,みちをひろう +57250,16391,みちをわける +57259,16385,みちくさをくう +57284,16385,みちゆくひとのそでにすがる +57291,16385,みつればかく +57296,16385,みっかにあげず +57296,16386,みっかみぬまのさくら +57316,16385,みつごのたましいひゃくまで +57376,16385,みつゆびをついて +57475,16385,みのかわをはぐ +57476,16385,みのけがよだつ +57493,16385,みはてぬゆめ +57510,16385,みふたつになる +57532,16385,みみがいたい +57532,16386,みみがこえる +57532,16387,みみがとおい +57532,16388,みみがはやい +57532,16389,みみにする +57532,16390,みみにたこ(ができる) +57532,16391,みみにたつ +57532,16392,みみにたっする +57532,16393,みみにつく +57532,16394,みみにのこる +57532,16395,みみにはいる +57532,16396,みみにはさむ +57532,16397,みみにもかけず +57532,16398,みみをあらう +57532,16399,みみをうたがう +57532,16400,みみをうつ +57532,16401,みみをおおう +57532,16402,みみをおおってすずをぬすむ +57532,16403,みみをかす +57532,16404,みみをかたむける +57532,16405,みみをけがす +57532,16406,みみをすます +57532,16407,みみをそばだてる +57532,16408,みみをそろえる +57532,16409,みみをダンボにする +57532,16410,みみをつんざく +57532,16411,みみをふさぐ +57532,16412,みみをほる +57532,16413,みみを{ろう・ろう}する +57549,16385,みみずののたくったよう +57567,16385,みむきもしない +57584,16385,みゃくがある +57584,16386,みゃくをとる +57673,16385,みょうりがわるい +57673,16386,みょうりにつきる +57697,16385,みてのとおり +57697,16386,みてみぬふりをする +57697,16387,みぬこときよし +57697,16388,みるかげもない +57697,16389,みるにしのびない +57697,16390,みるにみかねて +57697,16391,みるめがない +57697,16392,みるめも +57769,16385,むにする +57814,16385,むかしとった{きねづか・きねづか} +57928,16385,むこうさんげんりょうどなり +57928,16386,むこうにまわす +57928,16387,むこうをはる +57962,16385,むしがいい +57962,16386,むしがしらせる +57962,16387,むしがすかない +57962,16388,むしがつく +57962,16389,むしのいどころがわるい +57962,16390,むしもころさない +57962,16391,むしをおこす +57986,16385,むしずがはしる +58078,16385,むすめひとりにむこはちにん +58124,16385,むだめしをくう +58129,16385,ムチと{ニンジン・にんじん} +58218,16385,むねがあつくなる +58218,16386,むねがいっぱいになる +58218,16387,むねがおどる +58218,16388,むねがさわぐ +58218,16389,むねがつぶれる +58218,16390,むねがつまる +58218,16391,むねがはずむ +58218,16392,むねがふくらむ +58218,16393,むねがふさがる +58218,16394,むねがやける +58218,16395,むねがわるくなる +58218,16396,むねにいちもつ +58218,16397,むねにおちる +58218,16398,むねにきく +58218,16399,むねにささる +58218,16400,むねにせまる +58218,16401,むねにたたむ +58218,16402,むねにてをあてて +58218,16403,むねにひびく +58218,16404,むねのすくよう +58218,16405,むねのつかえがおりる +58218,16406,むねをいためる +58218,16407,むねをうつ +58218,16408,むねをかす +58218,16409,むねをかりる +58218,16410,むねをこがす +58218,16411,むねをさする +58218,16412,むねをだす +58218,16413,むねをたたく +58218,16414,むねをつかれる +58218,16415,むねをなでおろす +58218,16416,むねをはる +58218,16417,むねをふくらませる +58307,16385,むりがとおればどうりがひっこむ +58348,16385,めがあく +58348,16386,めがおよぐ +58348,16387,めがかたい +58348,16388,めがきく +58348,16389,めがくもる +58348,16390,めがくらむ +58348,16391,めがこえる +58348,16392,めがさえる +58348,16393,めがさめる +58348,16394,めがすわる +58348,16395,めがたかい +58348,16396,めがちかい +58348,16397,めがでる +58348,16398,めがてんになる +58348,16399,めがとおい +58348,16400,めがとどく +58348,16401,めがとびでる +58348,16402,めがとまる +58348,16403,めがない +58348,16404,めがなくなる +58348,16405,めがはいる +58348,16406,めがはなせない +58348,16407,めがはやい +58348,16408,めがひかる +58348,16409,めがまわる +58348,16410,めからうろこがおちる +58348,16411,めからはなへぬける +58348,16412,めからひがでる +58348,16413,めじゃない +58348,16414,めとはなのさき +58348,16415,めにあまる +58348,16416,めにいっていじもない +58348,16417,めにいれてもいたくない +58348,16418,めにうかぶ +58348,16419,めにうつる +58348,16420,めにおさめる +58348,16421,めにかどをたてる +58348,16422,めにしみる +58348,16423,めにする +58348,16424,めにたつ +58348,16425,めにつく +58348,16426,めにとどめる +58348,16427,めにのこる +58348,16428,めにはいる +58348,16429,めにはめを、はにははを +58348,16430,めにふれる +58348,16431,めにみえる +58348,16432,めにもとまらぬ +58348,16433,めにものいわす +58348,16434,めにもの(を)みせる +58348,16435,めのくろいうち +58348,16436,めはくちほどにものをいう +58348,16437,めはこころのまど +58348,16438,めひきそでひき +58348,16439,めもあてられない +58348,16440,めもあや〈な/に〉 +58348,16441,めもくれない +58348,16442,めをあそばせる +58348,16443,めをあてる +58348,16444,めをあわせる +58348,16445,めをうたがう +58348,16446,めをうばわれる +58348,16447,めをおおう +58348,16448,めをおとす +58348,16449,めをかける +58348,16450,めをかすめる +58348,16451,めをきる +58348,16452,めをくばる +58348,16453,めをくれる +58348,16454,めをこらす +58348,16455,めをさらす +58348,16456,めをさら(のよう)にする +58348,16457,めをさんかくにする +58348,16458,めをしろくろさせる +58348,16459,めをすえる +58348,16460,めをそばだてる +58348,16461,めをそむける +58348,16462,めを{そ・そ}らす +58348,16463,めをたてる +58348,16464,めをつける +58348,16465,めをつぶる +58348,16466,めをてんじる +58348,16467,めをとおす +58348,16468,めをとめる +58348,16469,めをぬすむ +58348,16470,めをはなす +58348,16471,めをはなつ +58348,16472,めをひからせる +58348,16473,めをひく +58348,16474,めをひらく +58348,16475,めをほそめる +58348,16476,めをまるくする +58348,16477,めをまわす +58348,16478,めをみはる +58348,16479,めを{む・む}く +58348,16480,めをむける +58348,16481,めをやる +58348,16482,めをよろこばせる +58350,16385,めがでる +58350,16386,めをつむ +58350,16387,めをふく +58363,16385,めいたんせきにせまる +58363,16386,めいをほうじる +58366,16385,めいをきる +58370,16385,めいあんをべんじない +58370,16386,めいあんをわける +58491,16385,めいどのみやげにする +58576,16385,メートルをあげる +58593,16385,めがしらがあつくなる +58593,16386,めがしらをおさえる +58596,16385,めかどをたてる +58602,16385,めがねにかなう +58606,16385,メガホンをとる +58617,16385,めくじらをたてる +58619,16385,めくそはなくそをわらう +58626,16385,めくらせんにんめあきせんにん +58626,16386,めくらへびに{お・お}じず +58643,16385,めさきがきく +58643,16386,めさきをかえる +58654,16385,めしをくう +58694,16385,メスをいれる +58726,16385,めだまがとびでる +58748,16385,めっきがはげる +58785,16385,めどがつく +58803,16385,めのいろをかえる +58810,16385,めのたまがとびでる +58810,16386,めのたまのくろいうち +58815,16385,めのまえが〈くらく/まっくらに〉なる +58822,16385,めはながつく +58874,16385,めりはりがきく +58896,16385,めんがわれる +58896,16386,めんとむかって +58896,16387,めんをきる +58896,16388,めんをとる +58914,16385,めんしきがある +58914,16386,めんしきをもつ +58944,16385,メンチ(を)きる +58954,16385,めんどうみがいい +58954,16386,めんどうをかける +58954,16387,めんどうをみる +58961,16385,めんぴをはぐ +58966,16385,めんぼくしだいもない +58966,16386,めんぼくをほどこす +58977,16385,もにふくする +58977,16386,もをはっする +58984,16385,もうを{ひら・ひら}く +59011,16385,もうげんたしゃ +59096,16385,もうひょうたしゃ +59134,16385,モーションをかける +59320,16385,もちはもちや +59383,16385,もちやはもちや +59390,16385,もちつもたれつ +59416,16385,もったいをつける +59420,16385,もって{いかん・いかん}となす +59420,16386,もってめいすべし +59459,16385,もとの{さや・さや}におさまる +59459,16386,もともこもない +59459,16387,もとをただせば +59459,16388,もとをとる +59513,16385,ものともせず +59513,16386,ものにする +59513,16387,ものになる +59513,16388,ものはいいよう +59513,16389,ものはそうだん +59513,16390,ものはためし +59513,16391,ものをいう +59513,16392,ものをいわせる +59519,16385,ものいえばくちびるさむしあきのかぜ +59552,16385,ものごころ(が)つく +59584,16385,もののかずではない +59642,16385,もみじをちらす +59659,16385,もも{くり・くり}さんねんかきはちねん +59762,16385,もんをたたく +59780,16385,もんこをはる +59806,16385,もんぜんいちをなす +59806,16386,もんぜん{じゃくら・じゃくら}をはる +59806,16387,もんぜんのこぞうならわぬきょうをよむ +59840,16385,やでもてっぽうでももってこい +59840,16386,やもたてもたまらず +59842,16385,やにいけんなし +59842,16386,やにくだる +59865,16385,やいばにかかる +59865,16386,やいばにちぬる +59900,16385,やきがまわる +59900,16386,やきをいれる +59957,16385,やくにたつ +59957,16386,やくをつくる +60059,16385,やぐらをふる +60075,16385,やけいしにみず +60091,16385,やけのの{きぎす・きぎす}よるのつる +60097,16385,やけぼっくいにひがつく +60149,16385,やすかろうわるかろう +60154,16385,{やす・やす}きにつく +60163,16385,やすみやすみいえ +60167,16385,やすものかいのぜにうしない +60192,16385,やせてもかれても +60292,16385,やなぎにかぜ +60292,16386,やなぎにゆきおれなし +60292,16387,やなぎのしたにいつも{どじょう・どじょう}はいない +60323,16385,やぶからぼう +60323,16386,やぶをつついてへびをだす +60350,16385,やまありたにあり +60350,16386,やまがみえる +60350,16387,やまたかきがゆえにたっとからず +60350,16388,やまたかければたにふかし +60350,16389,やまねむる +60350,16390,やまわらう +60350,16391,やまをあてる +60350,16392,やまをかける +60350,16393,やまをこす +60350,16394,やまをたてる +60350,16395,やまをはる +60350,16396,やまをふむ +60355,16385,やまいこうこうにいる +60355,16386,やまいはきから +60355,16387,やまいをえる +60355,16388,やまいをやしなう +60464,16385,やみからやみへほうむる +60484,16385,やみよにてっぽう +60485,16385,やむにやまれぬ +60503,16385,ややあって +60519,16385,やりがふっても +60532,16385,やりだまにあげる +60587,16385,ゆをつかう +60611,16385,ゆうをこす +60762,16385,ゆうしゅうのびを〈かざる/なす〉 +60830,16385,ゆうだちはうまのせをわける +60904,16385,ゆうめいさかいをことにする +60924,16385,ゆうようせまらぬ +60938,16385,ゆうれいのしょうたいみたりかれおばな +60946,16385,ゆえあって +60946,16386,ゆえあるかな +60946,16387,ゆえしらず +60946,16388,ゆえなき +60946,16389,ゆえなく(して) +60946,16390,ゆえなしとしない +60978,16385,ゆきとすみ +60978,16386,ゆきをいただく +61077,16385,ゆきつもどりつ +61077,16386,ゆくとしてかならざるはない +61087,16385,ゆげがたっている +61140,16385,ゆだんもすきも(なら)ない +61181,16385,ユニフォームをぬぐ +61193,16385,ゆびをくっする +61193,16386,ゆびをくわえる +61193,16387,ゆびをさす +61193,16388,ゆびをそめる +61193,16389,ゆびをつめる +61197,16385,ゆびおりかぞえてまつ +61218,16385,ゆみおれやつきる +61218,16386,ゆみをひく +61220,16385,ゆみずのようにつかう +61226,16385,ゆみやとるみ +61228,16385,ゆめにゆめみる +61228,16386,ゆめの(また)ゆめ +61228,16387,ゆめはさかゆめ +61228,16388,ゆめをむすぶ +61236,16385,ゆめじをたどる +61243,16385,ゆめみるよう +61269,16385,ゆりかごからはかばまで +61306,16385,よがよなら +61306,16386,よにいう +61306,16387,よにでる +61306,16388,よにとう +61306,16389,よをさる +61306,16390,よをしのぶ +61306,16391,よをすくう +61306,16392,よをそむく +61306,16393,よをわたる +61308,16385,よのめもねずに +61308,16386,よもひもあけない +61308,16387,よるもひもなく +61308,16388,よをあかす +61308,16389,よをこめて +61308,16390,よをてっする +61308,16391,よをひについで +61326,16385,よいごしのかねはもたない +61348,16385,ようにきょうする +61348,16386,ようにたつ +61348,16387,ようをたす +61348,16388,ようを{な・な}す +61348,16389,ようをべんずる +61349,16385,ようをつまんで +61351,16385,ようのとうざいをとわず +61355,16385,よういわんわ +61659,16385,ようりょうがいい +61659,16386,ようりょうをえない +61709,16385,よぎなくされる +61715,16385,よくのかわがつっぱる +61715,16386,よくもとくもない +61715,16387,よくをかく +61795,16385,よこからみてもたてからみても +61795,16386,よこにおく +61795,16387,よこになる +61795,16388,よこのものをたてにもしない +61795,16389,よこをむく +61815,16385,よこぐるまをおす +61837,16385,よこづなをはる +61840,16385,よこでをうつ +61870,16385,よこやりをいれる +61889,16385,よしにする +61891,16385,よしのずいからてんじょうのぞく +61892,16385,よしとする +61937,16385,よせいをかる +61955,16385,よぜんをたもつ +61962,16385,よそおいをこらす +61975,16385,よたをとばす +61985,16385,よだれをながす +61988,16385,よだんをゆるさない +61995,16385,よつにくむ +62061,16385,よねん(が)ない +62192,16385,よんでじのごとく +62193,16385,よめとおめ{かさ・かさ}のうち +62216,16385,よりをもどす +62241,16385,よりどりみどり +62255,16385,よらばたいじゅのかげ +62290,16385,よろしくやる +62298,16385,よわいをかさねる +62299,16385,よわいいぬほどよく{ほ・ほ}える +62300,16385,よわきをだす +62301,16385,よわきをたすけつよきを{くじ・くじ}く +62306,16385,よわねをはく +62313,16385,よわりめにたたりめ +62430,16385,らいねんのことをいうとおにがわらう +62471,16385,らくあればくあり +62474,16385,らくいんをおされる +62535,16385,らくようのしかをたかからしめる +62579,16385,らちがあかない +62579,16386,らちもない +62579,16387,らちをあける +62609,16385,ラッパをふく +62797,16385,りあらず +62799,16385,りがひでも +62799,16386,りにおちる +62845,16385,りかにかんむりをたださず +62919,16385,りくつをこねる +63027,16385,りちぎもののこだくさん +63064,16385,りっすいのよちもない +63253,16385,りゅういんがさがる +63273,16385,りゅうこあいうつ +63325,16385,りゅうびをさかだてる +63356,16385,りょうとする +63359,16385,りょうをいれる +63359,16386,りょうをとる +63496,16385,りょうてにはな +63551,16385,りょうめがあく +63559,16385,りょうやくはくちににがし +63560,16385,りょうゆうならびたたず +63577,16385,りょうりょうあい{ま・ま}って +63620,16385,りょそうをとく +63685,16385,りんげんあせの{ごと・ごと}し +63760,16385,るいがおよぶ +63761,16385,るいをまする +63762,16385,るいはともをよぶ +63804,16385,るいらんのあやうきにある +63839,16385,るすになる +63862,16385,ルビコン(がわ)をわたる +63888,16385,れいをあつくする +63888,16386,れいをしっする +63888,16387,れいをとる +63890,16385,れいにない +63890,16386,れいによって +63890,16387,れいによってれいのごとし +63892,16385,れいがひとつおおい +63982,16385,れいすいを〈あびせる/かける〉 +64039,16385,れいひつをふるう +64095,16385,レールをしく +64222,16385,レッテルをはる +64391,16385,ろをきる +64405,16385,ろうおおくしてこうすくなし +64409,16385,ろうをえてしょくをのぞむ +64410,16385,ろうを{か・か}むよう +64461,16385,ろうこうにくちはてる +64510,16385,ろうしてこうなし +64625,16385,ローマはいちにちにしてならず +64652,16385,ろくを{は・は}む +64667,16385,ろくじゅうにしてみみしたがう +64762,16385,ろとうにまよう +64797,16385,ろめいをつなぐ +64802,16385,ろれつがまわらない +64803,16385,ろんよりしょうこ +64803,16386,ろんを{ま・ま}たない +64813,16385,ろんごよみのろんごしらず +64826,16385,ろんじんをはる +64850,16385,わをこうじる +64851,16385,わをかける +64903,16385,わがたにみずをひく +64903,16386,わがみちをいく +64907,16385,わがいをえたり +64960,16385,わがみかわいさ +64960,16386,わがみを{つね・つね}ってひとのいたさをしれ +64991,16385,わきがあまい +65021,16385,わきめもふらず +65027,16385,わくをはめる +65087,16385,わさびがきく +65089,16385,わざわいをてんじてふくとなす +65126,16385,わしてどうぜず +65146,16385,わたのようにつかれる +65149,16385,わだいを{ま・ま}く +65159,16385,わたしとしたことが +65164,16385,わたぼうしをかぶる +65168,16385,わたりにふね +65170,16385,わたるせけんにおにはない +65197,16385,わとうをてんじる +65220,16385,わびをいれる +65253,16385,わらにもすがり(つき)たい +65253,16386,わらのうえから +65253,16387,わらをもつかみたい +65254,16385,わらいがとまらない +65255,16385,わらうかどにはふくきたる +65256,16385,わらえないはなし +65263,16385,わらじをぬぐ +65281,16385,わりがいい +65281,16386,わりにあう +65281,16387,わりをくう +65321,16385,わるいむしがつく +65321,16386,わるいようにはしない +65344,16385,われおとらじと +65344,16386,われかんせず +65344,16387,われともなく +65344,16388,われにかえる +65344,16389,われにもあらず +65344,16390,われにもなく +65344,16391,われもわれも +65344,16392,われをわすれる +65347,16385,われがねのようなこえ +65355,16385,われなべに{と・と}じぶた +65361,16385,われるようなはくしゅ diff --git a/data/mdict/css/sankoku8.css b/data/mdict/css/sankoku8.css new file mode 100644 index 0000000..0d56d1b --- /dev/null +++ b/data/mdict/css/sankoku8.css @@ -0,0 +1,611 @@ + +@font-face { + font-family: jpgothic; + src: local("Noto Sans CJK JP"), local("IPAexGothic"), local("Source Han Sans JP"); +} + +@font-face { + font-family: jpmincho; + src: local("Noto Serif CJK JP"), local("IPAexMincho"), local("IPAmjMincho"), local("Source Han Serif JP"), local("HanaMinA"), local("HanaMinB"); +} + +@font-face { + font-family: jpkyokasho; + src: local("A-OTF Kyoukasho ICA Pro R"), local("DFKyoKaSho-W4"); +} + +body { + margin: 0em 1em; + line-height: 1.5em; + font-family: jpmincho, serif; + font-size: 1.2em; +} + +span[data-name="entry-index"] > a { + display: none; +} + +span[data-name="項目"] { + display: block; + /*max-width: 39em;*/ +} + +span[data-name="見出部"] { + display: block; +} + +span[data-name="見出仮名"] { + font-family: jpgothic, sans-serif; + font-weight: bold; +} + +span[data-name="見出仮名"].アンチック { + font-family: jpmincho, serif; +} + +span[data-name="表記G"] { + margin-left: 0.25em; +} + +span[data-name="専門G"] { + margin-right: 0.25em; +} + +span[data-name="常用漢字"] { + font-family: jpmincho, serif; +} + +span[data-name="教育漢字"] { + font-family: jpkyokasho, jpmincho, serif; + color: green; +} + +span[data-name="解説部"], +span[data-name="子解説部"], +span[data-name="句解説部"] { + display: block; + margin-left: 1em; +} + +span[data-name="大語義"] { + display: block; +} + +span[data-name="大語義"] + span[data-name="大語義"] { + margin-top: 0.5em; +} + +span[data-name="大語義番号"] { + margin-right: 0.25em; + padding: 0.1em; + font-family: jpgothic, sans-serif; + font-size: 0.8em; + font-weight: bold; + color: white; + background-color: gray; + border-radius: 0.2em; +} + +a span[data-name="大語義番号"] { + background-color: blue; + text-decoration-color: blue; +} + +span[data-name="語義番号"] { + margin-right: 0.25em; +} + +span[data-name="参照語義番号"] { + margin-left: 0.1em; +} + +span[data-name="参照語義番号"]>span[data-name="語義番号"] { + margin-right: 0.1em; +} + +span[data-name="参照語義番号"]:first-child { + margin-left: 0em; +} + +span[data-name="語義"] { + display: block; +} + +span[data-name="副義"] { + display: block; + margin-left: 1.0em; +} + +span[data-name="注記語義"] { + margin-left: 0.5em; +} + +span[data-name="語釈"] { +} + +span[data-name="用例G"] { + display: block; + /*margin-left: 1.25em;*/ +} + +span[data-name="百科"] span[data-name="用例G"] { + display: inline; +} + +span[data-name="注記"] span[data-name="用例G"] { + display: inline; +} + +span[data-name="用例"] { +} + +span[data-name="見出相当部"] { + margin-left: 0.125em; + margin-right: 0.125em; +} + +span[data-name="ルビG"] { + font-size: 0.7em; + font-weight: normal; + vertical-align: 0.5em; + -webkit-user-select: none; +} + +span[data-name="名詞形G"], +span[data-name="動詞形G"], +span[data-name="自動詞形G"], +span[data-name="他動詞形G"], +span[data-name="可能形G"], +span[data-name="人G"], +span[data-name="名詞人形G"] { + display: block; +} + +span[data-name="語義"] span[data-name="名詞形G"], +span[data-name="語義"] span[data-name="動詞形G"], +span[data-name="語義"] span[data-name="自動詞形G"], +span[data-name="語義"] span[data-name="他動詞形G"], +span[data-name="語義"] span[data-name="可能形G"], +span[data-name="語義"] span[data-name="人G"] { + display: inline; +} + +span[data-name="副義"] span[data-name="名詞形G"], +span[data-name="副義"] span[data-name="動詞形G"], +span[data-name="副義"] span[data-name="自動詞形G"], +span[data-name="副義"] span[data-name="他動詞形G"], +span[data-name="副義"] span[data-name="可能形G"], +span[data-name="副義"] span[data-name="人G"] { + display: inline; +} + +span[data-name="注記"] span[data-name="名詞形G"], +span[data-name="注記"] span[data-name="動詞形G"], +span[data-name="注記"] span[data-name="自動詞形G"], +span[data-name="注記"] span[data-name="他動詞形G"], +span[data-name="注記"] span[data-name="可能形G"], +span[data-name="注記"] span[data-name="人G"] { + display: inline; +} + +span[data-name="共通"] span[data-name="名詞形G"], +span[data-name="共通"] span[data-name="動詞形G"], +span[data-name="共通"] span[data-name="自動詞形G"], +span[data-name="共通"] span[data-name="他動詞形G"], +span[data-name="共通"] span[data-name="可能形G"], +span[data-name="共通"] span[data-name="人G"] { + display: inline; +} + +span[data-name="名詞形G"] span[data-name="用例G"], +span[data-name="動詞形G"] span[data-name="用例G"], +span[data-name="自動詞形G"] span[data-name="用例G"], +span[data-name="他動詞形G"] span[data-name="用例G"], +span[data-name="可能形G"] span[data-name="用例G"], +span[data-name="人G"] span[data-name="用例G"] { + display: inline; +} + +span[data-name="参照G"] { + display: inline; +} + +span[data-name="参照矢印"] { + margin-right: 0.25em; +} + +span[data-name="参照"] { +} + +span[data-name="子項目"] { + display: block; + margin-top: 0.5em; +} + +span[data-name="子見出部"] { + display: block; + /* text-indent: -1em; */ +} + +span[data-name="子見出仮名"] { + font-family: jpgothic, sans-serif; + font-weight: bold; +} + +span[data-name="親見出省略"] { + font-family: jpgothic, sans-serif; +} + +span[data-name="句項目"] { + display: block; + margin-top: 0.5em; +} + +span[data-name="句見出部"] { + display: block; + /* text-indent: -1em; */ +} + +span[data-name="句表記"] { + font-family: jpgothic, sans-serif; + font-weight: bold; +} + +span[data-name="対義語G"] { +} + +span[data-name="派生語G"] { + display: block; +} + +span[data-name="謙譲形G"] { + display: block; +} + +span[data-name="共通"] { + display: block; +} + +span[data-name="共通ロゴ"] { + color: red; + margin-right: 0.25em; +} + +span[data-name="rank1"] a, +span[data-name="rank2"] a, +span[data-name="rank3"] a, +span[data-name="表外字マーク"] a, +span[data-name="表外音訓マーク"] a, +span[data-name="省略形"] a, +span[data-name="熟字訓"] a, +span[data-name="原籍"] a, +span[data-name="品詞"] a, +span[data-name="専門"] a, +span[data-name="使用域"] a, +span[data-name="rect"] a { + color: black; + border-top-style: none; +} + +span[data-name="共通ロゴ"] a { + color: red; + border-top-style: none; +} + +span[data-name="rect"].red a { + color: red; +} + +a { + text-decoration: none; + padding-top: 0.04em; + /* border-top: solid 1px blue; */ +} + +a.appendix { + color: black; + text-decoration: none; + border-top-style: none; +} + +a.black { + color: black; +} + +span[data-name="カット"] { + display: block; + width: 75%; + margin-top: 1em; + margin-left: auto; + margin-right: auto; +} + +span[data-name="カット"] img { + max-height: 200px; + max-width: 600px; +} + +span[data-name="イタリック"] { + font-style: italic; +} + +span[data-name="ボールド"] { + font-weight: bold; +} + +span[data-name="色版"] { + color: red; +} + +/* 独自定義 */ + +span[data-name="rect"] { + margin-left: 0.25em; + margin-right: 0.25em; + padding: 0.1em; + font-size: 0.8em; + border-width: 0.04em; + border-style: solid; + border-color: black; + word-break: keep-all; + border-radius: 0.1em; +} + +span[data-name="rect"].fill { + color: white; + border-style: none; + background-color: gray; +} + +span[data-name="rect"].red { + color: red; + border-color: red; +} + +span[data-name="red"] { + color: red; +} + +span[data-name="glyph"] { + font-family: jpmincho, serif; +} + +span[data-name="gaiji"] { + width: 1em; +} + +span[data-name="frac"] { + width: 2em; +} + +img.logo { + display: gaiji; + margin-right: 0.25em; + height: 1em; + text-combine-horizontal: all; +} + +.logo-red { + height: 1em; + color: red; +} + +span[data-name="平板"] .logo-red { + margin-left: 0.1em; +} + +img.区切り線 { + display: gaiji; + height: 1em; + padding: 0 0.3em 0 0.05em; + color: gray; + text-combine-horizontal: all; +} + +/* *, **, and ☆☆ symbols */ +span[data-src^="svg-logo"].rank { + font-size: 0.65em; + vertical-align: super; +} + +img.gaiji { + display: gaiji; + height: 1em; + text-combine-horizontal: all; +} + +img.svg { + zoom: 250%; +} + +span[data-name="表外字マーク"] { + font-size: 0.5em; + vertical-align: 1em; + -webkit-user-select: none; +} + +span[data-name="表外音訓マーク"] { + font-size: 0.5em; + vertical-align: 1em; + -webkit-user-select: none; +} + +span[data-name="表外字ロゴ"], +span[data-name="表外音訓ロゴ"] { + margin: 0em 0.5em; + font-size: 0.5em; +} + +span[data-name="アクセント"] { +} + +span[data-name="アクセント表記"] { + font-family: jpgothic, sans-serif; + font-weight: bold; + font-size: 0.85em; +} + +span[data-name="横"] { + text-combine-horizontal: all 1; +} + +span[data-name="縦中横"] { + text-combine-horizontal: all; +} + +span[data-name="分子"], +span[data-name="分母"] { + text-combine-horizontal: all; +} + +span[data-name="英"], +span[data-name="回転"] { + writing-mode: horizontal-tb; +} + +span[data-name="i"] { + font-family: "Times New Roman"; + font-style: italic; +} + +span[data-name="横"] span[data-name="sub"] { + font-size: 0.7em; + vertical-align: 0.35em; +} + +span[data-name="kanbun"] { + font-size: 0.5em; + vertical-align: -1em; +} + +span[data-name="歴史仮名"] { + font-size: 0.7em; + vertical-align: 0.5em; + -webkit-user-select: none; +} + +span[data-name="品詞G"] { + /* margin-left: 0.25em; */ +} + +span[data-name="ロゴ"] { + margin-right: 0.25em; +} + +span[data-name="割書"] { + /*display: warichu;*/ + /*font-size: 0.5em;*/ +} + +span[data-name="尊敬形G"], +span[data-name="謙譲形G"], +span[data-name="丁寧形G"] { + display: block; +} + +span[data-name="百科"], +span[data-name="由来"], +span[data-name="区別"], +span[data-name="アクセント注記"] { + display: block; +} + +span[data-name="表記情報"] { + display: block; +} + +span[data-name="別見出"] { + font-family: jpmincho, serif; + font-weight: bold; +} + +span[data-name="読み"] { + font-size: 0.7em; +} + +span[data-name="歴史仮名"]:before, +span[data-name="ルビ"]:before { + content: "("; +} + +span[data-name="歴史仮名"]:after, +span[data-name="ルビ"]:after { + content: ")"; +} + +div[data-child-links] { + padding-left: 1em; +} + +div[data-child-links] ul { + margin: 0; + padding-left: 2em; +} + +div[data-child-links] span { + padding: 0.1em; + font-family: jpgothic, sans-serif; + font-size: 0.8em; + color: white; + border-width: 0.05em; + border-style: none; + border-color: black; + border-radius: 0.2em; + word-break: keep-all; +} + +div[data-child-links="子項目"] span { + background-color: rgb(153, 42, 103); +} + +div[data-child-links="句項目"] span { + background-color: rgb(176, 127, 57); +} + + +/* Replacements for vertical SVG icons */ +span[data-src="svg-logo/区別.svg"], +span[data-src="svg-logo/由来.svg"] { + font-family: jpgothic, sans-serif; + margin-left: 0.25em; + margin-right: 0.25em; + padding: 0em 0.1em 0.1em 0.1em; + font-size: 0.8em; + word-break: keep-all; + border-radius: 0.2em; + border-style: none; + color: white; + background-color: red; +} + +span[data-src="svg-logo/アク.svg"], +span[data-src="svg-logo/丁寧.svg"], +span[data-src="svg-logo/可能.svg"], +span[data-src="svg-logo/尊敬.svg"], +span[data-src="svg-logo/表記.svg"], +span[data-src="svg-logo/謙譲.svg"], +span[data-src="svg-logo/接尾.svg"], +span[data-src="svg-logo/接頭.svg"] { + margin-left: 0.25em; + margin-right: 0.25em; + padding: 0em 0.1em 0.1em 0.1em; + font-size: 0.8em; + word-break: keep-all; + border-width: 0.1em; + border-style: solid; + border-radius: 0.2em; +} + +span[data-src="svg-logo/アク.svg"], +span[data-src="svg-logo/丁寧.svg"], +span[data-src="svg-logo/可能.svg"], +span[data-src="svg-logo/尊敬.svg"], +span[data-src="svg-logo/表記.svg"], +span[data-src="svg-logo/謙譲.svg"] { + font-family: jpgothic, sans-serif; +} + +span[data-src="svg-logo/接尾.svg"], +span[data-src="svg-logo/接頭.svg"] { + font-family: jpmincho, serif; +} diff --git a/data/mdict/description/sankoku8.mdx.description.html b/data/mdict/description/sankoku8.mdx.description.html new file mode 100644 index 0000000..12abb77 --- /dev/null +++ b/data/mdict/description/sankoku8.mdx.description.html @@ -0,0 +1,7 @@ +三省堂国語辞典 第八版 +

    +https://www.monokakido.jp/ja/dictionaries/sankoku8/index.html +

    +{{revision}} +

    +{{attribution}} diff --git a/data/mdict/name_conversion/sankoku8.json b/data/mdict/name_conversion/sankoku8.json new file mode 100644 index 0000000..c790845 --- /dev/null +++ b/data/mdict/name_conversion/sankoku8.json @@ -0,0 +1,22 @@ +{ + "a": {}, + "br": {}, + "img": {}, + "div": {}, + "span": {}, + "small": {}, + "sup": {}, + "sub": {}, + "表外字": { + "name": "ruby" + }, + "表外字マーク": { + "name": "rt" + }, + "表外音訓": { + "name": "ruby" + }, + "表外音訓マーク": { + "name": "rt" + } +} diff --git a/data/mdict/title/sankoku8.mdx.title.html b/data/mdict/title/sankoku8.mdx.title.html new file mode 100644 index 0000000..fbb17d9 --- /dev/null +++ b/data/mdict/title/sankoku8.mdx.title.html @@ -0,0 +1 @@ +三省堂国語辞典 第八版 diff --git a/data/yomichan/dictionary-term-bank-v3-schema.json b/data/yomichan/dictionary-term-bank-v3-schema.json new file mode 100644 index 0000000..335144c --- /dev/null +++ b/data/yomichan/dictionary-term-bank-v3-schema.json @@ -0,0 +1,474 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "structuredContent": { + "oneOf": [ + { + "type": "string", + "description": "Represents a text node." + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/structuredContent", + "description": "An array of child content." + } + }, + { + "type": "object", + "oneOf": [ + { + "type": "object", + "description": "Empty tags.", + "required": [ + "tag" + ], + "additionalProperties": false, + "properties": { + "tag": { + "type": "string", + "const": "br" + }, + "data": { + "$ref": "#/definitions/structuredContentData" + } + } + }, + { + "type": "object", + "description": "Generic container tags.", + "required": [ + "tag" + ], + "additionalProperties": false, + "properties": { + "tag": { + "type": "string", + "enum": ["ruby", "rt", "rp", "table", "thead", "tbody", "tfoot", "tr"] + }, + "content": { + "$ref": "#/definitions/structuredContent" + }, + "data": { + "$ref": "#/definitions/structuredContentData" + }, + "lang": { + "type": "string", + "description": "Defines the language of an element in the format defined by RFC 5646." + } + } + }, + { + "type": "object", + "description": "Table tags.", + "required": [ + "tag" + ], + "additionalProperties": false, + "properties": { + "tag": { + "type": "string", + "enum": ["td", "th"] + }, + "content": { + "$ref": "#/definitions/structuredContent" + }, + "data": { + "$ref": "#/definitions/structuredContentData" + }, + "colSpan": { + "type": "integer", + "minimum": 1 + }, + "rowSpan": { + "type": "integer", + "minimum": 1 + }, + "style": { + "$ref": "#/definitions/structuredContentStyle" + }, + "lang": { + "type": "string", + "description": "Defines the language of an element in the format defined by RFC 5646." + } + } + }, + { + "type": "object", + "description": "Container tags supporting configurable styles.", + "required": [ + "tag" + ], + "additionalProperties": false, + "properties": { + "tag": { + "type": "string", + "enum": ["span", "div", "ol", "ul", "li"] + }, + "content": { + "$ref": "#/definitions/structuredContent" + }, + "data": { + "$ref": "#/definitions/structuredContentData" + }, + "style": { + "$ref": "#/definitions/structuredContentStyle" + }, + "lang": { + "type": "string", + "description": "Defines the language of an element in the format defined by RFC 5646." + } + } + }, + { + "type": "object", + "description": "Image tag.", + "required": [ + "tag", + "path" + ], + "additionalProperties": false, + "properties": { + "tag": { + "type": "string", + "const": "img" + }, + "data": { + "$ref": "#/definitions/structuredContentData" + }, + "path": { + "type": "string", + "description": "Path to the image file in the archive." + }, + "width": { + "type": "number", + "description": "Preferred width of the image.", + "minimum": 0 + }, + "height": { + "type": "number", + "description": "Preferred width of the image.", + "minimum": 0 + }, + "title": { + "type": "string", + "description": "Hover text for the image." + }, + "pixelated": { + "type": "boolean", + "description": "Whether or not the image should appear pixelated at sizes larger than the image's native resolution.", + "default": false + }, + "imageRendering": { + "type": "string", + "description": "Controls how the image is rendered. The value of this field supersedes the pixelated field.", + "enum": ["auto", "pixelated", "crisp-edges"], + "default": "auto" + }, + "appearance": { + "type": "string", + "description": "Controls the appearance of the image. The \"monochrome\" value will mask the opaque parts of the image using the current text color.", + "enum": ["auto", "monochrome"], + "default": "auto" + }, + "background": { + "type": "boolean", + "description": "Whether or not a background color is displayed behind the image.", + "default": true + }, + "collapsed": { + "type": "boolean", + "description": "Whether or not the image is collapsed by default.", + "default": false + }, + "collapsible": { + "type": "boolean", + "description": "Whether or not the image can be collapsed.", + "default": false + }, + "verticalAlign": { + "type": "string", + "description": "The vertical alignment of the image.", + "enum": ["baseline", "sub", "super", "text-top", "text-bottom", "middle", "top", "bottom"] + }, + "sizeUnits": { + "type": "string", + "description": "The units for the width and height.", + "enum": ["px", "em"] + } + } + }, + { + "type": "object", + "description": "Link tag.", + "required": [ + "tag", + "href" + ], + "additionalProperties": false, + "properties": { + "tag": { + "type": "string", + "const": "a" + }, + "content": { + "$ref": "#/definitions/structuredContent" + }, + "href": { + "type": "string", + "description": "The URL for the link. URLs starting with a ? are treated as internal links to other dictionary content.", + "pattern": "^(?:https?:|\\?)[\\w\\W]*" + }, + "lang": { + "type": "string", + "description": "Defines the language of an element in the format defined by RFC 5646." + } + } + } + ] + } + ] + }, + "structuredContentData": { + "type": "object", + "description": "Generic data attributes that should be added to the element.", + "additionalProperties": { + "type": "string" + } + }, + "structuredContentStyle": { + "type": "object", + "additionalProperties": false, + "properties": { + "fontStyle": { + "type": "string", + "enum": ["normal", "italic"], + "default": "normal" + }, + "fontWeight": { + "type": "string", + "enum": ["normal", "bold"], + "default": "normal" + }, + "fontSize": { + "type": "string", + "default": "medium" + }, + "textDecorationLine": { + "oneOf": [ + { + "type": "string", + "enum": ["none", "underline", "overline", "line-through"], + "default": "none" + }, + { + "type": "array", + "items": { + "type": "string", + "enum": ["underline", "overline", "line-through"], + "default": "none" + } + } + ] + }, + "verticalAlign": { + "type": "string", + "enum": ["baseline", "sub", "super", "text-top", "text-bottom", "middle", "top", "bottom"], + "default": "baseline" + }, + "textAlign": { + "type": "string", + "enum": ["start", "end", "left", "right", "center", "justify", "justify-all", "match-parent"], + "default": "start" + }, + "marginTop": { + "type": "number", + "default": 0 + }, + "marginLeft": { + "type": "number", + "default": 0 + }, + "marginRight": { + "type": "number", + "default": 0 + }, + "marginBottom": { + "type": "number", + "default": 0 + }, + "listStyleType": { + "type": "string", + "default": "disc" + } + } + } + }, + "type": "array", + "description": "Data file containing term information.", + "items": { + "type": "array", + "description": "Information about a single term.", + "minItems": 8, + "additionalItems": false, + "items": [ + { + "type": "string", + "description": "The text for the term." + }, + { + "type": "string", + "description": "Reading of the term, or an empty string if the reading is the same as the term." + }, + { + "type": ["string", "null"], + "description": "String of space-separated tags for the definition. An empty string is treated as no tags." + }, + { + "type": "string", + "description": "String of space-separated rule identifiers for the definition which is used to validate delinflection. Valid rule identifiers are: v1: ichidan verb; v5: godan verb; vs: suru verb; vk: kuru verb; adj-i: i-adjective. An empty string corresponds to words which aren't inflected, such as nouns." + }, + { + "type": "number", + "description": "Score used to determine popularity. Negative values are more rare and positive values are more frequent. This score is also used to sort search results." + }, + { + "type": "array", + "description": "Array of definitions for the term.", + "items": { + "oneOf": [ + { + "type": "string", + "description": "Single definition for the term." + }, + { + "type": "object", + "description": "Single detailed definition for the term.", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the data for this definition.", + "enum": ["text", "image", "structured-content"] + } + }, + "oneOf": [ + { + "required": [ + "type", + "text" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "const": "text" + }, + "text": { + "type": "string", + "description": "Single definition for the term." + } + } + }, + { + "required": [ + "type", + "content" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "const": "structured-content" + }, + "content": { + "$ref": "#/definitions/structuredContent", + "description": "Single definition for the term using a structured content object." + } + } + }, + { + "required": [ + "type", + "path" + ], + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "const": "image" + }, + "path": { + "type": "string", + "description": "Path to the image file in the archive." + }, + "width": { + "type": "integer", + "description": "Preferred width of the image.", + "minimum": 1 + }, + "height": { + "type": "integer", + "description": "Preferred width of the image.", + "minimum": 1 + }, + "title": { + "type": "string", + "description": "Hover text for the image." + }, + "description": { + "type": "string", + "description": "Description of the image." + }, + "pixelated": { + "type": "boolean", + "description": "Whether or not the image should appear pixelated at sizes larger than the image's native resolution.", + "default": false + }, + "imageRendering": { + "type": "string", + "description": "Controls how the image is rendered. The value of this field supersedes the pixelated field.", + "enum": ["auto", "pixelated", "crisp-edges"], + "default": "auto" + }, + "appearance": { + "type": "string", + "description": "Controls the appearance of the image. The \"monochrome\" value will mask the opaque parts of the image using the current text color.", + "enum": ["auto", "monochrome"], + "default": "auto" + }, + "background": { + "type": "boolean", + "description": "Whether or not a background color is displayed behind the image.", + "default": true + }, + "collapsed": { + "type": "boolean", + "description": "Whether or not the image is collapsed by default.", + "default": false + }, + "collapsible": { + "type": "boolean", + "description": "Whether or not the image can be collapsed.", + "default": true + } + } + } + ] + } + ] + } + }, + { + "type": "integer", + "description": "Sequence number for the term. Terms with the same sequence number can be shown together when the \"resultOutputMode\" option is set to \"merge\"." + }, + { + "type": "string", + "description": "String of space-separated tags for the term. An empty string is treated as no tags." + } + ] + } +} \ No newline at end of file diff --git a/data/yomichan/index.json b/data/yomichan/index.json index 3fa8cb4..45e2dc6 100644 --- a/data/yomichan/index.json +++ b/data/yomichan/index.json @@ -56,5 +56,16 @@ ["子", "name", 0, "子項目", 0], ["句", "expression", 0, "句項目", 0] ] + }, + "sankoku8": { + "index": { + "title": "三省堂国語辞典 第八版", + "sequenced": true, + "format": 3 + }, + "tags": [ + ["子", "name", 0, "子項目", 0], + ["句", "expression", 0, "句項目", 0] + ] } } diff --git a/data/yomichan/inflection_categories.json b/data/yomichan/inflection_categories.json index 396ddb6..a36c589 100644 --- a/data/yomichan/inflection_categories.json +++ b/data/yomichan/inflection_categories.json @@ -25,5 +25,13 @@ "keiyoushi": ["形"], "kahen": ["カ変"], "sudachi": ["助動", "接尾", "枕詞", "連体", "連語"] + }, + "sankoku8": { + "sahen": ["サ", "サ型"], + "godan": ["上二", "下二", "下二型", "四","四型", "五", "五型", "特殊型", "マス","マス型"], + "ichidan": ["上一", "下一", "下一型"], + "keiyoushi": ["形", "形型"], + "kahen": ["カ"], + "sudachi": [] } } diff --git a/data/yomichan/name_conversion/sankoku8.json b/data/yomichan/name_conversion/sankoku8.json new file mode 100644 index 0000000..f9323ac --- /dev/null +++ b/data/yomichan/name_conversion/sankoku8.json @@ -0,0 +1,495 @@ +{ + "a": {}, + "br": {}, + "img": {}, + "div": {}, + "span": {}, + "ruby": {}, + "rt": {}, + "small": { + "name": "span", + "style": "vertical-align: super; font-size: 0.65em; font-weight: normal; margin-right: 0.25em;", + "procedures": [ + { + "procedure_name": "wrap", + "parameters": { + "l_wrap": "(", + "r_wrap": ")" + } + } + ] + }, + "sup": { + "name": "span", + "style": "vertical-align: super; font-size: 0.65em;" + }, + "sub": { + "name": "span", + "style": "vertical-align: sub; font-size: 0.65em;" + }, + "表外字": { + "name": "ruby" + }, + "表外字マーク": { + "name": "rt" + }, + "表外音訓": { + "name": "ruby" + }, + "表外音訓マーク": { + "name": "rt" + }, + "語構成": { + "name": "span", + "style": "margin-right: 0.5em;" + }, + "分書": { + "name": "span", + "style": "margin-right: 0.5em;" + }, + "見出仮名": { + "name": "span", + "style": "font-weight: bold;" + }, + "解説部": { + "name": "div" + }, + "子解説部": { + "name": "div" + }, + "句解説部": { + "name": "div" + }, + "大語義": { + "name": "div" + }, + "語義": { + "name": "div" + }, + "副義": { + "name": "div", + "style": "margin-left: 1.0em;" + }, + "注記語義": { + "name": "span", + "style": "margin-left: 0.5em;" + }, + "用例G": { + "name": "div", + "procedures": [ + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "百科", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "注記", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "名詞形G", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "動詞形G", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "自動詞形G", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "他動詞形G", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "可能形G", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "人G", + "key": "name", + "value": "span" + } + } + ] + }, + "見出相当部": { + "name": "span", + "style": "margin-left: 0.125em; margin-right: 0.125em;" + }, + "ルビG": { + "name": "span", + "style": "vertical-align: super; font-size: 0.65em; font-weight: normal;" + }, + "名詞人形G": { + "name": "div" + }, + "名詞形G": { + "name": "div", + "procedures": [ + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "語義", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "副義", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "注記", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "共通", + "key": "name", + "value": "span" + } + } + ] + }, + "動詞形G": { + "name": "div", + "procedures": [ + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "語義", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "副義", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "注記", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "共通", + "key": "name", + "value": "span" + } + } + ] + }, + "自動詞形G": { + "name": "div", + "procedures": [ + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "語義", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "副義", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "注記", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "共通", + "key": "name", + "value": "span" + } + } + ] + }, + "他動詞形G": { + "name": "div", + "procedures": [ + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "語義", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "副義", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "注記", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "共通", + "key": "name", + "value": "span" + } + } + ] + }, + "可能形G": { + "name": "div", + "procedures": [ + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "語義", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "副義", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "注記", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "共通", + "key": "name", + "value": "span" + } + } + ] + }, + "人G": { + "name": "div", + "procedures": [ + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "語義", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "副義", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "注記", + "key": "name", + "value": "span" + } + }, + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "共通", + "key": "name", + "value": "span" + } + } + ] + }, + "参照矢印": { + "name": "span", + "style": "margin-right: 0.25em;" + }, + "子見出仮名": { + "name": "span", + "style": "font-weight: bold;" + }, + "句表記": { + "name": "span", + "style": "font-weight: bold;" + }, + "派生語G": { + "name": "div" + }, + "謙譲形G": { + "name": "div" + }, + "共通": { + "name": "div" + }, + "イタリック": { + "name": "span", + "style": "font-style: italic;" + }, + "ボールド": { + "name": "span", + "style": "font-weight: bold;" + }, + "アクセント表記": { + "name": "span", + "style": "font-weight: bold; font-size: 0.85em;" + }, + "i": { + "name": "span", + "style": "font-style: italic;" + }, + "sub": { + "name": "span", + "style": "font-size: 0.7em; vertical-align: sub;", + "procedures": [ + { + "procedure_name": "has_parent", + "parameters": { + "parent_name": "横", + "key": "style", + "value": "font-size: 0.7em; vertical-align: super;" + } + } + ] + }, + "kanbun": { + "name": "span", + "style": "font-size: 0.5em; vertical-align: sub;" + }, + "ロゴ": { + "name": "span", + "style": "margin-right: 0.25em" + }, + "尊敬形G": { + "name": "div" + }, + "謙譲形G": { + "name": "div" + }, + "丁寧形G": { + "name": "div" + }, + "百科": { + "name": "div" + }, + "由来": { + "name": "div" + }, + "区別": { + "name": "div" + }, + "アクセント注記": { + "name": "div" + }, + "表記情報": { + "name": "div" + }, + "別見出": { + "name": "span", + "style": "font-weight: bold;" + }, + "読み": { + "name": "span", + "style": "font-size: 0.7em;" + }, + "歴史仮名": { + "name": "span", + "style": "vertical-align: super; font-size: 0.65em; font-weight: normal;", + "procedures": [ + { + "procedure_name": "wrap", + "parameters": { + "l_wrap": "(", + "r_wrap": ")" + } + } + ] + }, + "ルビ": { + "name": "span", + "procedures": [ + { + "procedure_name": "wrap", + "parameters": { + "l_wrap": "(", + "r_wrap": ")" + } + } + ] + } +} diff --git a/jitenbot.py b/jitenbot.py index e988df9..da44905 100644 --- a/jitenbot.py +++ b/jitenbot.py @@ -68,14 +68,19 @@ def parse_args(target_names): help="path to icon file to be used with MDict", type=filename, ) + parser.add_argument( + "--no-mdict-export", + help="skip export of dictionary data to MDict format", + action='store_true', + ) parser.add_argument( "--no-yomichan-export", help="skip export of dictionary data to Yomichan format", action='store_true', ) parser.add_argument( - "--no-mdict-export", - help="skip export of dictionary data to MDict format", + "--validate-yomichan-terms", + help="validate JSON structure of exported Yomichan dictionary terms", action='store_true', ) args = parser.parse_args() @@ -108,9 +113,11 @@ def main(): crawler.collect_pages(args.page_dir) crawler.read_pages() if not args.no_yomichan_export: - crawler.make_yomichan_dictionary(args.media_dir) + crawler.make_yomichan_dictionary( + args.media_dir, args.validate_yomichan_terms) if not args.no_mdict_export: - crawler.make_mdict_dictionary(args.media_dir, args.mdict_icon) + crawler.make_mdict_dictionary( + args.media_dir, args.mdict_icon) if __name__ == "__main__": diff --git a/requirements.txt b/requirements.txt index 8802356..b19e14b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,20 @@ +attrs==23.1.0 beautifulsoup4==4.12.2 bs4==0.0.1 certifi==2022.12.7 charset-normalizer==3.1.0 css-parser==1.0.8 +fastjsonschema==2.17.1 html5lib==1.1 idna==3.4 +jsonschema-specifications==2023.6.1 lxml==4.9.2 mdict-utils==1.3.12 Pillow==9.5.0 platformdirs==3.5.0 +referencing==0.29.1 requests==2.29.0 +rpds-py==0.8.10 six==1.16.0 soupsieve==2.4.1 SudachiDict-full==20230110 diff --git a/run_all.sh b/run_all.sh old mode 100644 new mode 100755 index 2bdd31e..706a911 --- a/run_all.sh +++ b/run_all.sh @@ -1,3 +1,7 @@ +#!/bin/sh + +python -m unittest discover -s tests + python jitenbot.py jitenon-kokugo python jitenbot.py jitenon-yoji python jitenbot.py jitenon-kotowaza @@ -11,3 +15,8 @@ python jitenbot.py daijirin2 \ --media-dir monokakido/DAIJIRIN2/media \ --page-dir monokakido/DAIJIRIN2/pages \ --mdict-icon monokakido/DAIJIRIN2/DAIJIRIN2-76@3x.png + +python jitenbot.py sankoku8 \ + --media-dir monokakido/SANKOKU8/media \ + --page-dir monokakido/SANKOKU8/pages \ + --mdict-icon monokakido/SANKOKU8/SANKOKU8-76@3x.png diff --git a/tests/test_sankoku_phrases.py b/tests/test_sankoku_phrases.py new file mode 100644 index 0000000..85f42fa --- /dev/null +++ b/tests/test_sankoku_phrases.py @@ -0,0 +1,47 @@ +import unittest +from bot.entries.sankoku8 import parse_hyouki_pattern +from bs4 import BeautifulSoup + + +class TestSankokuPhrases(unittest.TestCase): + def test_sankoku_phrases1(self): + pattern = '耳にたこ(ができる)' + exps = parse_hyouki_pattern(pattern) + self.assertEqual(len(exps), 2) + self.assertIn("耳にたこ", exps) + self.assertIn("耳にたこができる", exps) + + def test_sankoku_phrases2(self): + pattern = '一斑を〈見て/もって〉全豹を〈卜す/推す〉' + exps = parse_hyouki_pattern(pattern) + self.assertEqual(len(exps), 4) + self.assertIn("一斑を見て全豹を卜す", exps) + self.assertIn("一斑を見て全豹を推す", exps) + self.assertIn("一斑をもって全豹を卜す", exps) + self.assertIn("一斑をもって全豹を推す", exps) + + def test_sankoku_phrases3(self): + pattern = '{かじ・舵}を切る' + exps = parse_hyouki_pattern(pattern) + self.assertEqual(len(exps), 2) + self.assertIn("かじを切る", exps) + self.assertIn("舵を切る", exps) + + def test_sankoku_phrases4(self): + pattern = '重箱の隅を(⦅ようじ\楊枝⦆で)〈つつく/ほじくる〉' + exps = parse_hyouki_pattern(pattern) + self.assertEqual(len(exps), 6) + self.assertIn("重箱の隅をつつく", exps) + self.assertIn("重箱の隅をようじでつつく", exps) + self.assertIn("重箱の隅を楊枝でつつく", exps) + self.assertIn("重箱の隅をほじくる", exps) + self.assertIn("重箱の隅をようじでほじくる", exps) + self.assertIn("重箱の隅を楊枝でほじくる", exps) + + def test_sankoku_phrases5(self): + pattern = '群盲象を〈{な・撫}でる/評する〉' + exps = parse_hyouki_pattern(pattern) + self.assertEqual(len(exps), 3) + self.assertIn("群盲象をなでる", exps) + self.assertIn("群盲象を撫でる", exps) + self.assertIn("群盲象を評する", exps) From 14e50fb4f4dfaa6cee890c1787a997e0830db9e4 Mon Sep 17 00:00:00 2001 From: Stephen Kraus <8003332+stephenmk@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:08:39 -0500 Subject: [PATCH 26/43] Update README.md --- README.md | 157 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 113 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index ad56078..3535ce4 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,13 @@ compiling the scraped data into compact dictionary file formats. ### Supported Dictionaries * Web Dictionaries - * [国語辞典オンライン](https://kokugo.jitenon.jp/) (Jitenon Kokugo) - * [四字熟語辞典オンライン](https://yoji.jitenon.jp/) (Jitenon Yoji) - * [故事・ことわざ・慣用句オンライン](https://kotowaza.jitenon.jp/) (Jitenon Kotowaza) -* Monokakido (["辞書 by 物書堂"](https://www.monokakido.jp/ja/dictionaries/app/)) - * [新明解国語辞典 第八版](https://www.monokakido.jp/ja/dictionaries/smk8/index.html) (Shinmeikai 8e) - * [大辞林 第四版](https://www.monokakido.jp/ja/dictionaries/daijirin2/index.html) (Daijirin 4e) + * [国語辞典オンライン](https://kokugo.jitenon.jp/) (`jitenon-kokugo`) + * [四字熟語辞典オンライン](https://yoji.jitenon.jp/) (`jitenon-yoji`) + * [故事・ことわざ・慣用句オンライン](https://kotowaza.jitenon.jp/) (`jitenon-kotowaza`) +* Monokakido + * [新明解国語辞典 第八版](https://www.monokakido.jp/ja/dictionaries/smk8/index.html) (`smk8`) + * [大辞林 第四版](https://www.monokakido.jp/ja/dictionaries/daijirin2/index.html) (`daijirin2`) + * [三省堂国語辞典 第八版](https://www.monokakido.jp/ja/dictionaries/sankoku8/index.html) (`sankoku8`) ### Supported Output Formats @@ -48,6 +49,12 @@ compiling the scraped data into compact dictionary file formats. ![daijirin2](https://user-images.githubusercontent.com/8003332/235578700-9dbf4fb0-0154-48b5-817c-8fe75e442afc.png) +
    + Sanseidō 8e (print | yomichan) + + ![sankoku8](https://github.com/stephenmk/jitenbot/assets/8003332/0358b3fc-71fb-4557-977c-1976a12229ec) +
    +
    Various (GoldenDict) @@ -57,13 +64,14 @@ compiling the scraped data into compact dictionary file formats. # Usage ``` usage: jitenbot [-h] [-p PAGE_DIR] [-m MEDIA_DIR] [-i MDICT_ICON] - [--no-yomichan-export] [--no-mdict-export] - {jitenon-kokugo,jitenon-yoji,jitenon-kotowaza,smk8,daijirin2} + [--no-mdict-export] [--no-yomichan-export] + [--validate-yomichan-terms] + {jitenon-kokugo,jitenon-yoji,jitenon-kotowaza,smk8,daijirin2,sankoku8} Convert Japanese dictionary files to new formats. positional arguments: - {jitenon-kokugo,jitenon-yoji,jitenon-kotowaza,smk8,daijirin2} + {jitenon-kokugo,jitenon-yoji,jitenon-kotowaza,smk8,daijirin2,sankoku8} name of dictionary to convert options: @@ -75,10 +83,14 @@ options: graphics, audio, etc.) -i MDICT_ICON, --mdict-icon MDICT_ICON path to icon file to be used with MDict - --no-yomichan-export skip export of dictionary data to Yomichan format --no-mdict-export skip export of dictionary data to MDict format + --no-yomichan-export skip export of dictionary data to Yomichan format + --validate-yomichan-terms + validate JSON structure of exported Yomichan + dictionary terms See README.md for details regarding media directory structures + ``` ### Web Targets Jitenbot will scrape the target website and save the pages to the [user cache directory](https://pypi.org/project/platformdirs/). @@ -89,55 +101,112 @@ HTTP request headers (user agent string, etc.) may be customized by editing the [user config directory](https://pypi.org/project/platformdirs/). ### Monokakido Targets -Page data and media data must be [procured by the user](https://github.com/golddranks/monokakido/) -and passed to jitenbot via the appropriate command line flags. +These digital dictionaries are available for purchase through the [Monokakido Dictionaries app](https://www.monokakido.jp/ja/dictionaries/app/) on MacOS/iOS. Under ideal circumstances, Jitenbot would be able to automatically fetch all the data it needs from this app's data directory[^1] on your system. In its current state of development, Jitenbot unfortunately requires you to find and assemble the necessary data yourself. The files must be organized into a particular folder structure (defined below) and then passed to Jitenbot via the corresponding command line arguments. + +Some of the files in the app's data directory[^1] are encoded and must be unencoded using [golddranks' monokakido tool](https://github.com/golddranks/monokakido/). Directories which contain these encoded files are indicated by a reference mark (※) in the notes below. + +[^1]: `/Library/Application Support/AppStoreContent/jp.monokakido.Dictionaries/Products/`
    - smk8 media directory + smk8 files -Since Yomichan does not support audio files from imported -dictionaries, the `audio/` directory may be omitted to save filesize -space in the output ZIP file if desired. +Since Yomichan does not support audio files from imported dictionaries, the `audio/` directory may be omitted to save filesize space in the output ZIP file if desired. ``` -media -├── Audio.png -├── audio -│   ├── 00001.aac -│   ├── 00002.aac -│   ├── 00003.aac -│   │  ... -│   └── 82682.aac -└── gaiji - ├── 1d110.svg - ├── 1d15d.svg - ├── 1d15e.svg -    │  ... - └── xbunnoa.svg +. +├── media +│   ├── audio (※) +│   │   ├── 00001.aac +│   │   ├── 00002.aac +│   │   ├── 00003.aac +│   │   ├── ... +│   │   └── 82682.aac +│   ├── Audio.png +│   └── gaiji +│   ├── 1d110.svg +│   ├── 1d15d.svg +│   ├── 1d15e.svg +│   ├── ... +│   └── xbunnoa.svg +└── pages (※) + ├── 0000000000.xml + ├── 0000000001.xml + ├── 0000000002.xml + ├── ... + └── 0000064581.xml ```
    - daijirin2 media directory + daijirin2 files The `graphics/` directory may be omitted to save space if desired. ``` -media -├── gaiji -│   ├── 1D10B.svg -│   ├── 1D110.svg -│   ├── 1D12A.svg -│   │  ... -│   └── vectorOB.svg -└── graphics - ├── 3djr_0002.png - ├── 3djr_0004.png - ├── 3djr_0005.png -    │  ... - └── 4djr_yahazu.png +. +├── media +│   ├── gaiji +│   │   ├── 1D10B.svg +│   │   ├── 1D110.svg +│   │   ├── 1D12A.svg +│   │   ├── ... +│   │   └── vectorOB.svg +│   └── graphics (※) +│   ├── 3djr_0002.png +│   ├── 3djr_0004.png +│   ├── 3djr_0005.png +│   ├── ... +│   └── 4djr_yahazu.png +└── pages (※) + ├── 0000000001.xml + ├── 0000000002.xml + ├── 0000000003.xml + ├── ... + └── 0000182633.xml +``` +
    + +
    + sankoku8 files + +``` +. +├── media +│   ├── graphics +│   │   ├── 000chouchou.png +│   │   ├── ... +│   │   └── 888udatsu.png +│   ├── svg-accent +│   │   ├── アクセント.svg +│   │   └── 平板.svg +│   ├── svg-frac +│   │   ├── frac-1-2.svg +│   │   ├── ... +│   │   └── frac-a-b.svg +│   ├── svg-gaiji +│   │   ├── aiaigasa.svg +│   │   ├── ... +│   │   └── 異体字_西.svg +│   ├── svg-intonation +│   │   ├── 上昇下降.svg +│   │   ├── ... +│   │   └── 長.svg +│   ├── svg-logo +│   │   ├── denshi.svg +│   │   ├── ... +│   │   └── 重要語.svg +│   └── svg-special +│   └── 区切り線.svg +└── pages (※) + ├── 0000000001.xml + ├── ... + └── 0000065457.xml ```
    # Attribution `Adobe-Japan1_sequences.txt` is provided by [The Adobe-Japan1-7 Character Collection](https://github.com/adobe-type-tools/Adobe-Japan1). + +The Yomichan term-bank schema definition `dictionary-term-bank-v3-schema.json` is provided by the [Yomichan](https://github.com/foosoft/yomichan) project. + +Many thanks to [epistularum](https://github.com/epistularum) for providing thoughtful feedback regarding the implementation of the MDict export functionality. From 4ccb97f08852a5e0feac18d42667d12253bb550c Mon Sep 17 00:00:00 2001 From: Stephen Kraus <8003332+stephenmk@users.noreply.github.com> Date: Tue, 18 Jul 2023 12:29:39 -0500 Subject: [PATCH 27/43] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3535ce4..3cf5ddd 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ HTTP request headers (user agent string, etc.) may be customized by editing the ### Monokakido Targets These digital dictionaries are available for purchase through the [Monokakido Dictionaries app](https://www.monokakido.jp/ja/dictionaries/app/) on MacOS/iOS. Under ideal circumstances, Jitenbot would be able to automatically fetch all the data it needs from this app's data directory[^1] on your system. In its current state of development, Jitenbot unfortunately requires you to find and assemble the necessary data yourself. The files must be organized into a particular folder structure (defined below) and then passed to Jitenbot via the corresponding command line arguments. -Some of the files in the app's data directory[^1] are encoded and must be unencoded using [golddranks' monokakido tool](https://github.com/golddranks/monokakido/). Directories which contain these encoded files are indicated by a reference mark (※) in the notes below. +Some of the folders in the app's data directory[^1] contain encoded files that must be unencoded using [golddranks' monokakido tool](https://github.com/golddranks/monokakido/). These folders are indicated by a reference mark (※) in the notes below. [^1]: `/Library/Application Support/AppStoreContent/jp.monokakido.Dictionaries/Products/` From cfe1e98ab39a8b6f2dc116a4c4bd2b4793dd1f57 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Tue, 18 Jul 2023 13:23:53 -0500 Subject: [PATCH 28/43] Remove unused import --- tests/test_sankoku_phrases.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_sankoku_phrases.py b/tests/test_sankoku_phrases.py index 85f42fa..7faf289 100644 --- a/tests/test_sankoku_phrases.py +++ b/tests/test_sankoku_phrases.py @@ -1,6 +1,5 @@ import unittest from bot.entries.sankoku8 import parse_hyouki_pattern -from bs4 import BeautifulSoup class TestSankokuPhrases(unittest.TestCase): From d6044e0c124dc53da0c069b04a0ad6add90ac155 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Wed, 19 Jul 2023 13:28:13 -0500 Subject: [PATCH 29/43] Yomichan: add negative left margin to pitch accent marks in sankoku --- bot/yomichan/glossary/gloss.py | 3 ++- bot/yomichan/glossary/sankoku8.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bot/yomichan/glossary/gloss.py b/bot/yomichan/glossary/gloss.py index c94e5aa..705cd7e 100644 --- a/bot/yomichan/glossary/gloss.py +++ b/bot/yomichan/glossary/gloss.py @@ -76,6 +76,7 @@ def __get_attributes(attrs): def __get_style(inline_style_string): + # pylint: disable=no-member style = {} parsed_style = parseStyle(inline_style_string) if parsed_style.fontStyle != "": @@ -100,7 +101,7 @@ def __get_style(inline_style_string): "marginLeft": parsed_style.marginLeft, } for key, val in margins.items(): - m = re.search(r"(\d+(\.\d*)?|\.\d+)em", val) + m = re.search(r"(-?\d+(\.\d*)?|-?\.\d+)em", val) if m: style[key] = float(m.group(1)) diff --git a/bot/yomichan/glossary/sankoku8.py b/bot/yomichan/glossary/sankoku8.py index 5501381..17cf6d9 100644 --- a/bot/yomichan/glossary/sankoku8.py +++ b/bot/yomichan/glossary/sankoku8.py @@ -248,7 +248,7 @@ def __replace_accent_symbols(soup, media_dir): elm.name = "span" elm.clear() elm.append(img) - elm.attrs["style"] = "vertical-align: text-bottom;" + elm.attrs["style"] = "vertical-align: text-bottom; margin-left: -0.3em;" def __convert_gaiji(soup, media_dir): From 28dbf039d5ed5f3be747933a98333476408b0f2a Mon Sep 17 00:00:00 2001 From: Stephen Kraus <8003332+stephenmk@users.noreply.github.com> Date: Wed, 19 Jul 2023 13:35:32 -0500 Subject: [PATCH 30/43] =?UTF-8?q?Fix=20daijirin=20phrase=20reading=20for?= =?UTF-8?q?=20=E6=80=9D=E3=81=86=E6=95=85=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/entries/daijirin2/phrase_readings.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/entries/daijirin2/phrase_readings.csv b/data/entries/daijirin2/phrase_readings.csv index 48b0eab..dab7e7a 100644 --- a/data/entries/daijirin2/phrase_readings.csv +++ b/data/entries/daijirin2/phrase_readings.csv @@ -1391,7 +1391,7 @@ 22544,16385,おもいこしをあげる 22634,16385,おもいたったがきちにち 22634,16386,おもいたつひがきちじつ -22728,16385,おもうゆえに +22728,16385,おもうえに 22728,16386,おもうこころ 22728,16387,おもうこといわねばはらふくる 22728,16388,おもうそら From 94c68b4e26a86972c1fb908118168fb3f278c48f Mon Sep 17 00:00:00 2001 From: stephenmk Date: Wed, 19 Jul 2023 17:31:55 -0500 Subject: [PATCH 31/43] Yomichan: adjust style of pitch accent symbols in sankoku (again) --- bot/yomichan/glossary/sankoku8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/yomichan/glossary/sankoku8.py b/bot/yomichan/glossary/sankoku8.py index 17cf6d9..79a34ae 100644 --- a/bot/yomichan/glossary/sankoku8.py +++ b/bot/yomichan/glossary/sankoku8.py @@ -248,7 +248,7 @@ def __replace_accent_symbols(soup, media_dir): elm.name = "span" elm.clear() elm.append(img) - elm.attrs["style"] = "vertical-align: text-bottom; margin-left: -0.3em;" + elm.attrs["style"] = "vertical-align: super; margin-left: -0.5em;" def __convert_gaiji(soup, media_dir): From 0cd530585f5ad1911d5b8c77b30c295f4fc40f51 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Wed, 19 Jul 2023 23:49:25 -0500 Subject: [PATCH 32/43] Adjust pitch accent style again Decided I like it this way the most --- bot/yomichan/glossary/sankoku8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/yomichan/glossary/sankoku8.py b/bot/yomichan/glossary/sankoku8.py index 79a34ae..ceca5d7 100644 --- a/bot/yomichan/glossary/sankoku8.py +++ b/bot/yomichan/glossary/sankoku8.py @@ -248,7 +248,7 @@ def __replace_accent_symbols(soup, media_dir): elm.name = "span" elm.clear() elm.append(img) - elm.attrs["style"] = "vertical-align: super; margin-left: -0.5em;" + elm.attrs["style"] = "vertical-align: super; margin-left: -0.25em;" def __convert_gaiji(soup, media_dir): From 775da669b9dc645cd5e714c031078115e2491ec5 Mon Sep 17 00:00:00 2001 From: epistularum <34507493+epistularum@users.noreply.github.com> Date: Mon, 24 Jul 2023 22:00:47 +0900 Subject: [PATCH 33/43] Overhaul of variants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete overall of variants based on the 印刷標準字体 (表外漢字表) https://www.bunka.go.jp/kokugo_nihongo/sisaku/joho/joho/kakuki/22/tosin03/index.html All characters that are either a 簡易慣用字体 or have the 旧字 as a 印刷標準字体 and have the 新字 within JIS1 have been selected. In addition, characters that are both alternate of one another and are both 人名用漢字 (亙,亘-凜,凛) are included alongside design variations within JIS1 as stated by the 表外漢字字体表 (𠮟,叱-吞,呑-靱,靭-﨟,臈) Characters removed: 姸 妍 拋 抛 筓 笄 腁 胼 鷽 鴬 --- data/entries/variant_kanji.csv | 36 +++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/data/entries/variant_kanji.csv b/data/entries/variant_kanji.csv index b50ce8d..849eec3 100644 --- a/data/entries/variant_kanji.csv +++ b/data/entries/variant_kanji.csv @@ -1,47 +1,65 @@ +亙,亘 俠,侠 俱,倶 儘,侭 凜,凛 剝,剥 -𠮟,叱 吞,呑 啞,唖 噓,嘘 嚙,噛 囊,嚢 塡,填 -姸,妍 +壺,壷 屛,屏 屢,屡 -拋,抛 +幷,并 +彎,弯 搔,掻 摑,掴 攪,撹 +曾,曽 +枡,桝 +檜,桧 +檮,梼 潑,溌 +濤,涛 +濾,沪 瀆,涜 +灌,潅 焰,焔 +瘦,痩 +禰,祢 禱,祷 -竜,龍 -筓,笄 +穎,頴 +竈,竃 簞,箪 籠,篭 繡,繍 繫,繋 -腁,胼 萊,莱 +蔣,蒋 藪,薮 +蘆,芦 蟬,蝉 +蠅,蝿 蠟,蝋 +蠣,蛎 +賤,賎 軀,躯 +邇,迩 醬,醤 醱,醗 +靱,靭 頰,頬 +頸,頚 顚,顛 驒,騨 +鰺,鯵 鶯,鴬 鷗,鴎 -鷽,鴬 鹼,鹸 麴,麹 -靭,靱 -靱,靭 +麵,麺 +﨟,臈 +𠮟,叱 From 9b3fdc86d13eb8117e876df9d836dd22413e87a1 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Wed, 26 Jul 2023 19:28:50 -0500 Subject: [PATCH 34/43] Reorganize file structure of entries modules --- bot/entries/{ => base}/entry.py | 0 bot/entries/{ => base}/expressions.py | 37 --- .../{jitenon.py => base/jitenon_entry.py} | 107 +------ bot/entries/base/sanseido_entry.py | 60 ++++ bot/entries/daijirin2.py | 231 ---------------- bot/entries/daijirin2/base_entry.py | 88 ++++++ bot/entries/daijirin2/child_entry.py | 9 + bot/entries/daijirin2/entry.py | 50 ++++ bot/entries/daijirin2/phrase_entry.py | 67 +++++ .../preprocess.py} | 0 bot/entries/factory.py | 21 +- bot/entries/jitenon_kokugo/entry.py | 45 +++ bot/entries/jitenon_kotowaza/entry.py | 35 +++ bot/entries/jitenon_yoji/entry.py | 27 ++ bot/entries/sankoku8.py | 260 ------------------ bot/entries/sankoku8/base_entry.py | 97 +++++++ bot/entries/sankoku8/child_entry.py | 8 + bot/entries/sankoku8/entry.py | 14 + bot/entries/sankoku8/parse.py | 65 +++++ bot/entries/sankoku8/phrase_entry.py | 37 +++ .../preprocess.py} | 0 bot/entries/smk8.py | 221 --------------- bot/entries/smk8/base_entry.py | 73 +++++ bot/entries/smk8/child_entry.py | 17 ++ bot/entries/smk8/entry.py | 26 ++ bot/entries/smk8/kanji_entry.py | 22 ++ bot/entries/smk8/phrase_entry.py | 64 +++++ .../preprocess.py} | 0 bot/yomichan/terms/daijirin2.py | 5 +- bot/yomichan/terms/sankoku8.py | 5 +- bot/yomichan/terms/smk8.py | 4 +- tests/test_daijirin_phrases.py | 21 ++ tests/test_expressions.py | 27 +- tests/test_sankoku_phrases.py | 14 +- tests/test_smk_phrases.py | 19 ++ 35 files changed, 863 insertions(+), 913 deletions(-) rename bot/entries/{ => base}/entry.py (100%) rename bot/entries/{ => base}/expressions.py (67%) rename bot/entries/{jitenon.py => base/jitenon_entry.py} (58%) create mode 100644 bot/entries/base/sanseido_entry.py delete mode 100644 bot/entries/daijirin2.py create mode 100644 bot/entries/daijirin2/base_entry.py create mode 100644 bot/entries/daijirin2/child_entry.py create mode 100644 bot/entries/daijirin2/entry.py create mode 100644 bot/entries/daijirin2/phrase_entry.py rename bot/entries/{daijirin2_preprocess.py => daijirin2/preprocess.py} (100%) create mode 100644 bot/entries/jitenon_kokugo/entry.py create mode 100644 bot/entries/jitenon_kotowaza/entry.py create mode 100644 bot/entries/jitenon_yoji/entry.py delete mode 100644 bot/entries/sankoku8.py create mode 100644 bot/entries/sankoku8/base_entry.py create mode 100644 bot/entries/sankoku8/child_entry.py create mode 100644 bot/entries/sankoku8/entry.py create mode 100644 bot/entries/sankoku8/parse.py create mode 100644 bot/entries/sankoku8/phrase_entry.py rename bot/entries/{sankoku8_preprocess.py => sankoku8/preprocess.py} (100%) delete mode 100644 bot/entries/smk8.py create mode 100644 bot/entries/smk8/base_entry.py create mode 100644 bot/entries/smk8/child_entry.py create mode 100644 bot/entries/smk8/entry.py create mode 100644 bot/entries/smk8/kanji_entry.py create mode 100644 bot/entries/smk8/phrase_entry.py rename bot/entries/{smk8_preprocess.py => smk8/preprocess.py} (100%) create mode 100644 tests/test_daijirin_phrases.py create mode 100644 tests/test_smk_phrases.py diff --git a/bot/entries/entry.py b/bot/entries/base/entry.py similarity index 100% rename from bot/entries/entry.py rename to bot/entries/base/entry.py diff --git a/bot/entries/expressions.py b/bot/entries/base/expressions.py similarity index 67% rename from bot/entries/expressions.py rename to bot/entries/base/expressions.py index 687a325..7d20891 100644 --- a/bot/entries/expressions.py +++ b/bot/entries/base/expressions.py @@ -85,40 +85,3 @@ def expand_abbreviation_list(expressions): if new_exp not in new_exps: new_exps.append(new_exp) return new_exps - - -def expand_smk_alternatives(text): - """Return a list of strings described by △ notation.""" - m = re.search(r"△([^(]+)(([^(]+))", text) - if m is None: - return [text] - alt_parts = [m.group(1)] - for alt_part in m.group(2).split("・"): - alt_parts.append(alt_part) - alts = [] - for alt_part in alt_parts: - alt_exp = re.sub(r"△[^(]+([^(]+)", alt_part, text) - alts.append(alt_exp) - return alts - - -def expand_daijirin_alternatives(text): - """Return a list of strings described by = notation.""" - group_pattern = r"([^=]+)(=([^(]+)(=([^(]+)))?" - groups = re.findall(group_pattern, text) - expressions = [""] - for group in groups: - new_exps = [] - for expression in expressions: - new_exps.append(expression + group[0]) - expressions = new_exps.copy() - if group[1] == "": - continue - new_exps = [] - for expression in expressions: - new_exps.append(expression + group[2]) - for expression in expressions: - for alt in group[3].split("・"): - new_exps.append(expression + alt) - expressions = new_exps.copy() - return expressions diff --git a/bot/entries/jitenon.py b/bot/entries/base/jitenon_entry.py similarity index 58% rename from bot/entries/jitenon.py rename to bot/entries/base/jitenon_entry.py index 65c4d2e..7af845b 100644 --- a/bot/entries/jitenon.py +++ b/bot/entries/base/jitenon_entry.py @@ -3,11 +3,11 @@ from abc import abstractmethod from datetime import datetime, date from bs4 import BeautifulSoup -from bot.entries.entry import Entry -import bot.entries.expressions as Expressions +from bot.entries.base.entry import Entry +import bot.entries.base.expressions as Expressions -class _JitenonEntry(Entry): +class JitenonEntry(Entry): def __init__(self, target, entry_id): super().__init__(target, entry_id) self.expression = "" @@ -140,104 +140,3 @@ class _JitenonEntry(Entry): elif isinstance(attr_val, list): colvals.append(";".join(attr_val)) return ",".join(colvals) - - -class JitenonYojiEntry(_JitenonEntry): - def __init__(self, target, entry_id): - super().__init__(target, entry_id) - self.origin = "" - self.kanken_level = "" - self.category = "" - self.related_expressions = [] - - def _get_column_map(self): - return { - "四字熟語": "expression", - "読み方": "yomikata", - "意味": "definition", - "異形": "other_forms", - "出典": "origin", - "漢検級": "kanken_level", - "場面用途": "category", - "類義語": "related_expressions", - } - - def _add_variant_expressions(self, headwords): - for expressions in headwords.values(): - Expressions.add_variant_kanji(expressions) - - -class JitenonKotowazaEntry(_JitenonEntry): - def __init__(self, target, entry_id): - super().__init__(target, entry_id) - self.origin = "" - self.example = "" - self.related_expressions = [] - - def _get_column_map(self): - return { - "言葉": "expression", - "読み方": "yomikata", - "意味": "definition", - "異形": "other_forms", - "出典": "origin", - "例文": "example", - "類句": "related_expressions", - } - - def _get_headwords(self): - if self.expression == "金棒引き・鉄棒引き": - headwords = { - "かなぼうひき": ["金棒引き", "鉄棒引き"] - } - else: - headwords = super()._get_headwords() - return headwords - - def _add_variant_expressions(self, headwords): - for expressions in headwords.values(): - Expressions.add_variant_kanji(expressions) - Expressions.add_fullwidth(expressions) - - -class JitenonKokugoEntry(_JitenonEntry): - def __init__(self, target, entry_id): - super().__init__(target, entry_id) - self.example = "" - self.alt_expression = "" - self.antonym = "" - self.attachments = "" - self.compounds = "" - self.related_words = "" - - def _get_column_map(self): - return { - "言葉": "expression", - "読み方": "yomikata", - "意味": "definition", - "例文": "example", - "別表記": "alt_expression", - "対義語": "antonym", - "活用": "attachments", - "用例": "compounds", - "類語": "related_words", - } - - def _get_headwords(self): - headwords = {} - for reading in self.yomikata.split("・"): - if reading not in headwords: - headwords[reading] = [] - for expression in self.expression.split("・"): - headwords[reading].append(expression) - if self.alt_expression.strip() != "": - for expression in self.alt_expression.split("・"): - headwords[reading].append(expression) - return headwords - - def _add_variant_expressions(self, headwords): - for expressions in headwords.values(): - Expressions.add_variant_kanji(expressions) - Expressions.add_fullwidth(expressions) - Expressions.remove_iteration_mark(expressions) - Expressions.add_iteration_mark(expressions) diff --git a/bot/entries/base/sanseido_entry.py b/bot/entries/base/sanseido_entry.py new file mode 100644 index 0000000..4e1098d --- /dev/null +++ b/bot/entries/base/sanseido_entry.py @@ -0,0 +1,60 @@ +from abc import abstractmethod +from bs4 import BeautifulSoup + +from bot.entries.base.entry import Entry +import bot.entries.base.expressions as Expressions + + +class SanseidoEntry(Entry): + def set_page(self, page): + page = self._decompose_subentries(page) + self._page = page + + def get_page_soup(self): + soup = BeautifulSoup(self._page, "xml") + return soup + + def get_global_identifier(self): + parent_part = format(self.entry_id[0], '06') + child_part = hex(self.entry_id[1]).lstrip('0x').zfill(4).upper() + return f"@{self.target.value}-{parent_part}-{child_part}" + + def _decompose_subentries(self, page): + soup = BeautifulSoup(page, features="xml") + for x in self._get_subentry_parameters(): + subentry_class, tags, subentry_list = x + for tag in tags: + tag_soup = soup.find(tag) + while tag_soup is not None: + tag_soup.name = "項目" + subentry_id = self.id_string_to_entry_id(tag_soup.attrs["id"]) + self.SUBENTRY_ID_TO_ENTRY_ID[subentry_id] = self.entry_id + subentry = subentry_class(self.target, subentry_id) + page = tag_soup.decode() + subentry.set_page(page) + subentry_list.append(subentry) + tag_soup.decompose() + tag_soup = soup.find(tag) + return soup.decode() + + @abstractmethod + def _get_subentry_parameters(self): + pass + + def _add_variant_expressions(self, headwords): + for expressions in headwords.values(): + Expressions.add_variant_kanji(expressions) + Expressions.add_fullwidth(expressions) + Expressions.remove_iteration_mark(expressions) + Expressions.add_iteration_mark(expressions) + + @staticmethod + def id_string_to_entry_id(id_string): + parts = id_string.split("-") + if len(parts) == 1: + return (int(parts[0]), 0) + elif len(parts) == 2: + # subentries have a hexadecimal part + return (int(parts[0]), int(parts[1], 16)) + else: + raise Exception(f"Invalid entry ID: {id_string}") diff --git a/bot/entries/daijirin2.py b/bot/entries/daijirin2.py deleted file mode 100644 index f7a629c..0000000 --- a/bot/entries/daijirin2.py +++ /dev/null @@ -1,231 +0,0 @@ -from bs4 import BeautifulSoup - -import bot.entries.expressions as Expressions -import bot.soup as Soup -from bot.data import load_phrase_readings -from bot.data import load_daijirin2_kana_abbreviations -from bot.entries.entry import Entry -from bot.entries.daijirin2_preprocess import preprocess_page - - -class _BaseDaijirin2Entry(Entry): - def __init__(self, target, entry_id): - super().__init__(target, entry_id) - self.children = [] - self.phrases = [] - self._kana_abbreviations = load_daijirin2_kana_abbreviations() - - def get_global_identifier(self): - parent_part = format(self.entry_id[0], '06') - child_part = hex(self.entry_id[1]).lstrip('0x').zfill(4).upper() - return f"@{self.target.value}-{parent_part}-{child_part}" - - def set_page(self, page): - page = self.__decompose_subentries(page) - self._page = page - - def get_page_soup(self): - soup = BeautifulSoup(self._page, "xml") - return soup - - def get_part_of_speech_tags(self): - if self._part_of_speech_tags is not None: - return self._part_of_speech_tags - self._part_of_speech_tags = [] - soup = self.get_page_soup() - for pos_group in soup.find_all("品詞G"): - if pos_group.parent.name == "大語義": - self._set_part_of_speech_tags(pos_group) - return self._part_of_speech_tags - - def _set_part_of_speech_tags(self, el): - pos_names = ["品詞", "品詞活用", "品詞行", "用法"] - for child in el.children: - if child.name is not None: - self._set_part_of_speech_tags(child) - continue - pos = str(child) - if el.name not in pos_names: - continue - elif pos in ["[", "]"]: - continue - elif pos in self._part_of_speech_tags: - continue - else: - self._part_of_speech_tags.append(pos) - - def _get_regular_headwords(self, soup): - self._fill_alts(soup) - reading = soup.find("見出仮名").text - expressions = [] - for el in soup.find_all("標準表記"): - expression = self._clean_expression(el.text) - if "—" in expression: - kana_abbrs = self._kana_abbreviations[self.entry_id] - for abbr in kana_abbrs: - expression = expression.replace("—", abbr, 1) - expressions.append(expression) - expressions = Expressions.expand_abbreviation_list(expressions) - if len(expressions) == 0: - expressions.append(reading) - headwords = {reading: expressions} - return headwords - - def _add_variant_expressions(self, headwords): - for expressions in headwords.values(): - Expressions.add_variant_kanji(expressions) - Expressions.add_fullwidth(expressions) - Expressions.remove_iteration_mark(expressions) - Expressions.add_iteration_mark(expressions) - - def __decompose_subentries(self, page): - soup = BeautifulSoup(page, features="xml") - subentry_parameters = [ - [Daijirin2ChildEntry, ["子項目"], self.children], - [Daijirin2PhraseEntry, ["句項目"], self.phrases], - ] - for x in subentry_parameters: - subentry_class, tags, subentry_list = x - for tag in tags: - tag_soup = soup.find(tag) - while tag_soup is not None: - tag_soup.name = "項目" - subentry_id = self.id_string_to_entry_id(tag_soup.attrs["id"]) - self.SUBENTRY_ID_TO_ENTRY_ID[subentry_id] = self.entry_id - subentry = subentry_class(self.target, subentry_id) - page = tag_soup.decode() - subentry.set_page(page) - subentry_list.append(subentry) - tag_soup.decompose() - tag_soup = soup.find(tag) - return soup.decode() - - @staticmethod - def id_string_to_entry_id(id_string): - parts = id_string.split("-") - if len(parts) == 1: - return (int(parts[0]), 0) - elif len(parts) == 2: - # subentries have a hexadecimal part - return (int(parts[0]), int(parts[1], 16)) - else: - raise Exception(f"Invalid entry ID: {id_string}") - - @staticmethod - def _delete_unused_nodes(soup): - """Remove extra markup elements that appear in the entry - headword line which are not part of the entry headword""" - unused_nodes = [ - "漢字音logo", "活用分節", "連語句活用分節", "語構成", - "表外字マーク", "表外字マーク", "ルビG" - ] - for name in unused_nodes: - Soup.delete_soup_nodes(soup, name) - - @staticmethod - def _clean_expression(expression): - for x in ["〈", "〉", "《", "》", " "]: - expression = expression.replace(x, "") - return expression - - @staticmethod - def _fill_alts(soup): - for gaiji in soup.find_all(class_="gaiji"): - if gaiji.name == "img" and gaiji.has_attr("alt"): - gaiji.name = "span" - gaiji.string = gaiji.attrs["alt"] - - -class Daijirin2Entry(_BaseDaijirin2Entry): - def __init__(self, target, page_id): - entry_id = (page_id, 0) - super().__init__(target, entry_id) - - def set_page(self, page): - page = preprocess_page(page) - super().set_page(page) - - def _get_headwords(self): - soup = self.get_page_soup() - self._delete_unused_nodes(soup) - if soup.find("漢字見出") is not None: - headwords = self._get_kanji_headwords(soup) - elif soup.find("略語G") is not None: - headwords = self._get_acronym_headwords(soup) - else: - headwords = self._get_regular_headwords(soup) - return headwords - - def _get_kanji_headwords(self, soup): - readings = [] - for el in soup.find_all("漢字音"): - hira = Expressions.kata_to_hira(el.text) - readings.append(hira) - if soup.find("漢字音") is None: - readings.append("") - expressions = [] - for el in soup.find_all("漢字見出"): - expressions.append(el.text) - headwords = {} - for reading in readings: - headwords[reading] = expressions - return headwords - - def _get_acronym_headwords(self, soup): - expressions = [] - for el in soup.find_all("略語"): - expression_parts = [] - for part in el.find_all(["欧字", "和字"]): - expression_parts.append(part.text) - expression = "".join(expression_parts) - expressions.append(expression) - headwords = {"": expressions} - return headwords - - -class Daijirin2ChildEntry(_BaseDaijirin2Entry): - def _get_headwords(self): - soup = self.get_page_soup() - self._delete_unused_nodes(soup) - headwords = self._get_regular_headwords(soup) - return headwords - - -class Daijirin2PhraseEntry(_BaseDaijirin2Entry): - def get_part_of_speech_tags(self): - # phrases do not contain these tags - return [] - - def _get_headwords(self): - soup = self.get_page_soup() - headwords = {} - expressions = self._find_expressions(soup) - readings = self._find_readings() - for idx, expression in enumerate(expressions): - reading = readings[idx] - if reading in headwords: - headwords[reading].append(expression) - else: - headwords[reading] = [expression] - return headwords - - def _find_expressions(self, soup): - self._delete_unused_nodes(soup) - text = soup.find("句表記").text - text = self._clean_expression(text) - alternatives = Expressions.expand_daijirin_alternatives(text) - expressions = [] - for alt in alternatives: - for exp in Expressions.expand_abbreviation(alt): - expressions.append(exp) - return expressions - - def _find_readings(self): - phrase_readings = load_phrase_readings(self.target) - text = phrase_readings[self.entry_id] - alternatives = Expressions.expand_daijirin_alternatives(text) - readings = [] - for alt in alternatives: - for reading in Expressions.expand_abbreviation(alt): - readings.append(reading) - return readings diff --git a/bot/entries/daijirin2/base_entry.py b/bot/entries/daijirin2/base_entry.py new file mode 100644 index 0000000..1113404 --- /dev/null +++ b/bot/entries/daijirin2/base_entry.py @@ -0,0 +1,88 @@ +import bot.soup as Soup +from bot.data import load_daijirin2_kana_abbreviations +from bot.entries.base.sanseido_entry import SanseidoEntry +import bot.entries.base.expressions as Expressions + + +class BaseEntry(SanseidoEntry): + def __init__(self, target, entry_id): + super().__init__(target, entry_id) + self.children = [] + self.phrases = [] + self._kana_abbreviations = load_daijirin2_kana_abbreviations() + + def get_part_of_speech_tags(self): + if self._part_of_speech_tags is not None: + return self._part_of_speech_tags + self._part_of_speech_tags = [] + soup = self.get_page_soup() + for pos_group in soup.find_all("品詞G"): + if pos_group.parent.name == "大語義": + self._set_part_of_speech_tags(pos_group) + return self._part_of_speech_tags + + def _set_part_of_speech_tags(self, el): + pos_names = ["品詞", "品詞活用", "品詞行", "用法"] + for child in el.children: + if child.name is not None: + self._set_part_of_speech_tags(child) + continue + pos = str(child) + if el.name not in pos_names: + continue + elif pos in ["[", "]"]: + continue + elif pos in self._part_of_speech_tags: + continue + else: + self._part_of_speech_tags.append(pos) + + def _get_regular_headwords(self, soup): + self._fill_alts(soup) + reading = soup.find("見出仮名").text + expressions = [] + for el in soup.find_all("標準表記"): + expression = self._clean_expression(el.text) + if "—" in expression: + kana_abbrs = self._kana_abbreviations[self.entry_id] + for abbr in kana_abbrs: + expression = expression.replace("—", abbr, 1) + expressions.append(expression) + expressions = Expressions.expand_abbreviation_list(expressions) + if len(expressions) == 0: + expressions.append(reading) + headwords = {reading: expressions} + return headwords + + def _get_subentry_parameters(self): + from bot.entries.daijirin2.child_entry import ChildEntry + from bot.entries.daijirin2.phrase_entry import PhraseEntry + subentry_parameters = [ + [ChildEntry, ["子項目"], self.children], + [PhraseEntry, ["句項目"], self.phrases], + ] + return subentry_parameters + + @staticmethod + def _delete_unused_nodes(soup): + """Remove extra markup elements that appear in the entry + headword line which are not part of the entry headword""" + unused_nodes = [ + "漢字音logo", "活用分節", "連語句活用分節", "語構成", + "表外字マーク", "表外字マーク", "ルビG" + ] + for name in unused_nodes: + Soup.delete_soup_nodes(soup, name) + + @staticmethod + def _clean_expression(expression): + for x in ["〈", "〉", "《", "》", " "]: + expression = expression.replace(x, "") + return expression + + @staticmethod + def _fill_alts(soup): + for gaiji in soup.find_all(class_="gaiji"): + if gaiji.name == "img" and gaiji.has_attr("alt"): + gaiji.name = "span" + gaiji.string = gaiji.attrs["alt"] diff --git a/bot/entries/daijirin2/child_entry.py b/bot/entries/daijirin2/child_entry.py new file mode 100644 index 0000000..42685a0 --- /dev/null +++ b/bot/entries/daijirin2/child_entry.py @@ -0,0 +1,9 @@ +from bot.entries.daijirin2.base_entry import BaseEntry + + +class ChildEntry(BaseEntry): + def _get_headwords(self): + soup = self.get_page_soup() + self._delete_unused_nodes(soup) + headwords = self._get_regular_headwords(soup) + return headwords diff --git a/bot/entries/daijirin2/entry.py b/bot/entries/daijirin2/entry.py new file mode 100644 index 0000000..0b6970f --- /dev/null +++ b/bot/entries/daijirin2/entry.py @@ -0,0 +1,50 @@ +import bot.entries.base.expressions as Expressions +from bot.entries.daijirin2.base_entry import BaseEntry +from bot.entries.daijirin2.preprocess import preprocess_page + + +class Entry(BaseEntry): + def __init__(self, target, page_id): + entry_id = (page_id, 0) + super().__init__(target, entry_id) + + def set_page(self, page): + page = preprocess_page(page) + super().set_page(page) + + def _get_headwords(self): + soup = self.get_page_soup() + self._delete_unused_nodes(soup) + if soup.find("漢字見出") is not None: + headwords = self._get_kanji_headwords(soup) + elif soup.find("略語G") is not None: + headwords = self._get_acronym_headwords(soup) + else: + headwords = self._get_regular_headwords(soup) + return headwords + + def _get_kanji_headwords(self, soup): + readings = [] + for el in soup.find_all("漢字音"): + hira = Expressions.kata_to_hira(el.text) + readings.append(hira) + if soup.find("漢字音") is None: + readings.append("") + expressions = [] + for el in soup.find_all("漢字見出"): + expressions.append(el.text) + headwords = {} + for reading in readings: + headwords[reading] = expressions + return headwords + + def _get_acronym_headwords(self, soup): + expressions = [] + for el in soup.find_all("略語"): + expression_parts = [] + for part in el.find_all(["欧字", "和字"]): + expression_parts.append(part.text) + expression = "".join(expression_parts) + expressions.append(expression) + headwords = {"": expressions} + return headwords diff --git a/bot/entries/daijirin2/phrase_entry.py b/bot/entries/daijirin2/phrase_entry.py new file mode 100644 index 0000000..0470d7d --- /dev/null +++ b/bot/entries/daijirin2/phrase_entry.py @@ -0,0 +1,67 @@ +import re + +import bot.entries.base.expressions as Expressions +from bot.data import load_phrase_readings +from bot.entries.daijirin2.base_entry import BaseEntry + + +class PhraseEntry(BaseEntry): + def get_part_of_speech_tags(self): + # phrases do not contain these tags + return [] + + def _get_headwords(self): + soup = self.get_page_soup() + headwords = {} + expressions = self._find_expressions(soup) + readings = self._find_readings() + for idx, expression in enumerate(expressions): + reading = readings[idx] + if reading in headwords: + headwords[reading].append(expression) + else: + headwords[reading] = [expression] + return headwords + + def _find_expressions(self, soup): + self._delete_unused_nodes(soup) + text = soup.find("句表記").text + text = self._clean_expression(text) + alternatives = parse_phrase(text) + expressions = [] + for alt in alternatives: + for exp in Expressions.expand_abbreviation(alt): + expressions.append(exp) + return expressions + + def _find_readings(self): + phrase_readings = load_phrase_readings(self.target) + text = phrase_readings[self.entry_id] + alternatives = parse_phrase(text) + readings = [] + for alt in alternatives: + for reading in Expressions.expand_abbreviation(alt): + readings.append(reading) + return readings + + +def parse_phrase(text): + """Return a list of strings described by = notation.""" + group_pattern = r"([^=]+)(=([^(]+)(=([^(]+)))?" + groups = re.findall(group_pattern, text) + expressions = [""] + for group in groups: + new_exps = [] + for expression in expressions: + new_exps.append(expression + group[0]) + expressions = new_exps.copy() + if group[1] == "": + continue + new_exps = [] + for expression in expressions: + new_exps.append(expression + group[2]) + for expression in expressions: + for alt in group[3].split("・"): + new_exps.append(expression + alt) + expressions = new_exps.copy() + return expressions diff --git a/bot/entries/daijirin2_preprocess.py b/bot/entries/daijirin2/preprocess.py similarity index 100% rename from bot/entries/daijirin2_preprocess.py rename to bot/entries/daijirin2/preprocess.py diff --git a/bot/entries/factory.py b/bot/entries/factory.py index 162c102..594762f 100644 --- a/bot/entries/factory.py +++ b/bot/entries/factory.py @@ -1,20 +1,7 @@ -from bot.targets import Targets - -from bot.entries.jitenon import JitenonKokugoEntry -from bot.entries.jitenon import JitenonYojiEntry -from bot.entries.jitenon import JitenonKotowazaEntry -from bot.entries.smk8 import Smk8Entry -from bot.entries.daijirin2 import Daijirin2Entry -from bot.entries.sankoku8 import Sankoku8Entry +import importlib def new_entry(target, page_id): - entry_map = { - Targets.JITENON_KOKUGO: JitenonKokugoEntry, - Targets.JITENON_YOJI: JitenonYojiEntry, - Targets.JITENON_KOTOWAZA: JitenonKotowazaEntry, - Targets.SMK8: Smk8Entry, - Targets.DAIJIRIN2: Daijirin2Entry, - Targets.SANKOKU8: Sankoku8Entry, - } - return entry_map[target](target, page_id) + module_path = f"bot.entries.{target.name.lower()}.entry" + module = importlib.import_module(module_path) + return module.Entry(target, page_id) diff --git a/bot/entries/jitenon_kokugo/entry.py b/bot/entries/jitenon_kokugo/entry.py new file mode 100644 index 0000000..523ac63 --- /dev/null +++ b/bot/entries/jitenon_kokugo/entry.py @@ -0,0 +1,45 @@ +from bot.entries.base.jitenon_entry import JitenonEntry +import bot.entries.base.expressions as Expressions + + +class Entry(JitenonEntry): + def __init__(self, target, entry_id): + super().__init__(target, entry_id) + self.example = "" + self.alt_expression = "" + self.antonym = "" + self.attachments = "" + self.compounds = "" + self.related_words = "" + + def _get_column_map(self): + return { + "言葉": "expression", + "読み方": "yomikata", + "意味": "definition", + "例文": "example", + "別表記": "alt_expression", + "対義語": "antonym", + "活用": "attachments", + "用例": "compounds", + "類語": "related_words", + } + + def _get_headwords(self): + headwords = {} + for reading in self.yomikata.split("・"): + if reading not in headwords: + headwords[reading] = [] + for expression in self.expression.split("・"): + headwords[reading].append(expression) + if self.alt_expression.strip() != "": + for expression in self.alt_expression.split("・"): + headwords[reading].append(expression) + return headwords + + def _add_variant_expressions(self, headwords): + for expressions in headwords.values(): + Expressions.add_variant_kanji(expressions) + Expressions.add_fullwidth(expressions) + Expressions.remove_iteration_mark(expressions) + Expressions.add_iteration_mark(expressions) diff --git a/bot/entries/jitenon_kotowaza/entry.py b/bot/entries/jitenon_kotowaza/entry.py new file mode 100644 index 0000000..71dc35f --- /dev/null +++ b/bot/entries/jitenon_kotowaza/entry.py @@ -0,0 +1,35 @@ +from bot.entries.base.jitenon_entry import JitenonEntry +import bot.entries.base.expressions as Expressions + + +class Entry(JitenonEntry): + def __init__(self, target, entry_id): + super().__init__(target, entry_id) + self.origin = "" + self.example = "" + self.related_expressions = [] + + def _get_column_map(self): + return { + "言葉": "expression", + "読み方": "yomikata", + "意味": "definition", + "異形": "other_forms", + "出典": "origin", + "例文": "example", + "類句": "related_expressions", + } + + def _get_headwords(self): + if self.expression == "金棒引き・鉄棒引き": + headwords = { + "かなぼうひき": ["金棒引き", "鉄棒引き"] + } + else: + headwords = super()._get_headwords() + return headwords + + def _add_variant_expressions(self, headwords): + for expressions in headwords.values(): + Expressions.add_variant_kanji(expressions) + Expressions.add_fullwidth(expressions) diff --git a/bot/entries/jitenon_yoji/entry.py b/bot/entries/jitenon_yoji/entry.py new file mode 100644 index 0000000..e0e8b13 --- /dev/null +++ b/bot/entries/jitenon_yoji/entry.py @@ -0,0 +1,27 @@ +import bot.entries.base.expressions as Expressions +from bot.entries.base.jitenon_entry import JitenonEntry + + +class Entry(JitenonEntry): + def __init__(self, target, entry_id): + super().__init__(target, entry_id) + self.origin = "" + self.kanken_level = "" + self.category = "" + self.related_expressions = [] + + def _get_column_map(self): + return { + "四字熟語": "expression", + "読み方": "yomikata", + "意味": "definition", + "異形": "other_forms", + "出典": "origin", + "漢検級": "kanken_level", + "場面用途": "category", + "類義語": "related_expressions", + } + + def _add_variant_expressions(self, headwords): + for expressions in headwords.values(): + Expressions.add_variant_kanji(expressions) diff --git a/bot/entries/sankoku8.py b/bot/entries/sankoku8.py deleted file mode 100644 index 9653f68..0000000 --- a/bot/entries/sankoku8.py +++ /dev/null @@ -1,260 +0,0 @@ -from bs4 import BeautifulSoup -import bot.entries.expressions as Expressions -import bot.soup as Soup -from bot.entries.entry import Entry -from bot.data import load_phrase_readings -from bot.entries.sankoku8_preprocess import preprocess_page - - -class _BaseSankoku8Entry(Entry): - def __init__(self, target, entry_id): - super().__init__(target, entry_id) - self.children = [] - self.phrases = [] - self._hyouki_name = "表記" - self._midashi_name = None - self._midashi_kana_name = None - - def get_global_identifier(self): - parent_part = format(self.entry_id[0], '06') - child_part = hex(self.entry_id[1]).lstrip('0x').zfill(4).upper() - return f"@{self.target.value}-{parent_part}-{child_part}" - - def set_page(self, page): - page = self.__decompose_subentries(page) - self._page = page - - def get_page_soup(self): - soup = BeautifulSoup(self._page, "xml") - return soup - - def _get_headwords(self): - soup = self.get_page_soup() - self._delete_unused_nodes(soup) - readings = self._find_readings(soup) - expressions = self._find_expressions(soup) - headwords = {} - for reading in readings: - headwords[reading] = [] - if len(readings) == 1: - reading = readings[0] - if soup.find(self._midashi_name).find(self._hyouki_name) is None: - headwords[reading].append(reading) - for exp in expressions: - if exp not in headwords[reading]: - headwords[reading].append(exp) - elif len(readings) > 1 and len(expressions) == 0: - for reading in readings: - headwords[reading].append(reading) - elif len(readings) > 1 and len(expressions) == 1: - if soup.find(self._midashi_name).find(self._hyouki_name) is None: - for reading in readings: - headwords[reading].append(reading) - expression = expressions[0] - for reading in readings: - if expression not in headwords[reading]: - headwords[reading].append(expression) - elif len(readings) > 1 and len(expressions) == len(readings): - if soup.find(self._midashi_name).find(self._hyouki_name) is None: - for reading in readings: - headwords[reading].append(reading) - for idx, reading in enumerate(readings): - exp = expressions[idx] - if exp not in headwords[reading]: - headwords[reading].append(exp) - else: - raise Exception() # shouldn't happen - return headwords - - def _add_variant_expressions(self, headwords): - for expressions in headwords.values(): - Expressions.add_variant_kanji(expressions) - Expressions.add_fullwidth(expressions) - Expressions.remove_iteration_mark(expressions) - Expressions.add_iteration_mark(expressions) - - def get_part_of_speech_tags(self): - if self._part_of_speech_tags is not None: - return self._part_of_speech_tags - self._part_of_speech_tags = [] - soup = self.get_page_soup() - for midashi in soup.find_all([self._midashi_name, "見出部要素"]): - pos_group = midashi.find("品詞G") - if pos_group is None: - continue - for tag in pos_group.find_all("a"): - if tag.text not in self._part_of_speech_tags: - self._part_of_speech_tags.append(tag.text) - return self._part_of_speech_tags - - def _find_expressions(self, soup): - expressions = [] - for hyouki in soup.find_all(self._hyouki_name): - for expression in parse_hyouki_soup(hyouki, [""]): - expressions.append(expression) - return expressions - - def _find_readings(self, soup): - midasi_kana = soup.find(self._midashi_kana_name) - readings = parse_hyouki_soup(midasi_kana, [""]) - return readings - - def __decompose_subentries(self, page): - soup = BeautifulSoup(page, features="xml") - subentry_parameters = [ - [Sankoku8ChildEntry, ["子項目"], self.children], - [Sankoku8PhraseEntry, ["句項目"], self.phrases], - ] - for x in subentry_parameters: - subentry_class, tags, subentry_list = x - for tag in tags: - tag_soup = soup.find(tag) - while tag_soup is not None: - tag_soup.name = "項目" - subentry_id = self.id_string_to_entry_id(tag_soup.attrs["id"]) - self.SUBENTRY_ID_TO_ENTRY_ID[subentry_id] = self.entry_id - subentry = subentry_class(self.target, subentry_id) - page = tag_soup.decode() - subentry.set_page(page) - subentry_list.append(subentry) - tag_soup.decompose() - tag_soup = soup.find(tag) - return soup.decode() - - @staticmethod - def id_string_to_entry_id(id_string): - parts = id_string.split("-") - if len(parts) == 1: - return (int(parts[0]), 0) - elif len(parts) == 2: - # subentries have a hexadecimal part - return (int(parts[0]), int(parts[1], 16)) - else: - raise Exception(f"Invalid entry ID: {id_string}") - - @staticmethod - def _delete_unused_nodes(soup): - """Remove extra markup elements that appear in the entry - headword line which are not part of the entry headword""" - unused_nodes = [ - "語構成", "平板", "アクセント", "表外字マーク", "表外音訓マーク", - "アクセント分節", "活用分節", "ルビG", "分書" - ] - for name in unused_nodes: - Soup.delete_soup_nodes(soup, name) - - -class Sankoku8Entry(_BaseSankoku8Entry): - def __init__(self, target, page_id): - entry_id = (page_id, 0) - super().__init__(target, entry_id) - self._midashi_name = "見出部" - self._midashi_kana_name = "見出仮名" - - def set_page(self, page): - page = preprocess_page(page) - super().set_page(page) - - -class Sankoku8ChildEntry(_BaseSankoku8Entry): - def __init__(self, target, page_id): - super().__init__(target, page_id) - self._midashi_name = "子見出部" - self._midashi_kana_name = "子見出仮名" - - -class Sankoku8PhraseEntry(_BaseSankoku8Entry): - def get_part_of_speech_tags(self): - # phrases do not contain these tags - return [] - - def _get_headwords(self): - soup = self.get_page_soup() - self._delete_unused_nodes(soup) - expressions = self._find_expressions(soup) - readings = self._find_readings(soup) - headwords = {} - if len(expressions) != len(readings): - raise Exception(f"{self.entry_id[0]}-{self.entry_id[1]}") - for idx, expression in enumerate(expressions): - reading = readings[idx] - if reading in headwords: - headwords[reading].append(expression) - else: - headwords[reading] = [expression] - return headwords - - def _find_expressions(self, soup): - phrase_soup = soup.find("句表記") - expressions = parse_hyouki_soup(phrase_soup, [""]) - return expressions - - def _find_readings(self, soup): - reading_patterns = load_phrase_readings(self.target) - reading_pattern = reading_patterns[self.entry_id] - readings = parse_hyouki_pattern(reading_pattern) - return readings - - -def parse_hyouki_soup(soup, base_exps): - omitted_characters = [ - "/", "〈", "〉", "(", ")", "⦅", "⦆", ":", "…" - ] - exps = base_exps.copy() - for child in soup.children: - new_exps = [] - if child.name == "言換G": - for alt in child.find_all("言換"): - parts = parse_hyouki_soup(alt, [""]) - for exp in exps: - for part in parts: - new_exps.append(exp + part) - elif child.name == "補足表記": - alt1 = child.find("表記対象") - alt2 = child.find("表記内容G") - parts1 = parse_hyouki_soup(alt1, [""]) - parts2 = parse_hyouki_soup(alt2, [""]) - for exp in exps: - for part in parts1: - new_exps.append(exp + part) - for part in parts2: - new_exps.append(exp + part) - elif child.name == "省略": - parts = parse_hyouki_soup(child, [""]) - for exp in exps: - new_exps.append(exp) - for part in parts: - new_exps.append(exp + part) - elif child.name is not None: - new_exps = parse_hyouki_soup(child, exps) - else: - text = child.text - for char in omitted_characters: - text = text.replace(char, "") - for exp in exps: - new_exps.append(exp + text) - exps = new_exps.copy() - return exps - - -def parse_hyouki_pattern(pattern): - replacements = { - "(": "<省略>(", - ")": ")", - "{": "<補足表記><表記対象>", - "・": "<表記内容G>(<表記内容>", - "}": "", - "〈": "<言換G>〈<言換>", - "/": "/<言換>", - "〉": "", - "⦅": "<補足表記><表記対象>", - "\": "<表記内容G>⦅<表記内容>", - "⦆": "", - } - markup = f"{pattern}" - for key, val in replacements.items(): - markup = markup.replace(key, val) - soup = BeautifulSoup(markup, "xml") - hyouki_soup = soup.find("span") - exps = parse_hyouki_soup(hyouki_soup, [""]) - return exps diff --git a/bot/entries/sankoku8/base_entry.py b/bot/entries/sankoku8/base_entry.py new file mode 100644 index 0000000..93c0515 --- /dev/null +++ b/bot/entries/sankoku8/base_entry.py @@ -0,0 +1,97 @@ +import bot.soup as Soup +from bot.entries.base.sanseido_entry import SanseidoEntry +from bot.entries.sankoku8.parse import parse_hyouki_soup + + +class BaseEntry(SanseidoEntry): + def __init__(self, target, entry_id): + super().__init__(target, entry_id) + self.children = [] + self.phrases = [] + self._hyouki_name = "表記" + self._midashi_name = None + self._midashi_kana_name = None + + def _get_headwords(self): + soup = self.get_page_soup() + self._delete_unused_nodes(soup) + readings = self._find_readings(soup) + expressions = self._find_expressions(soup) + headwords = {} + for reading in readings: + headwords[reading] = [] + if len(readings) == 1: + reading = readings[0] + if soup.find(self._midashi_name).find(self._hyouki_name) is None: + headwords[reading].append(reading) + for exp in expressions: + if exp not in headwords[reading]: + headwords[reading].append(exp) + elif len(readings) > 1 and len(expressions) == 0: + for reading in readings: + headwords[reading].append(reading) + elif len(readings) > 1 and len(expressions) == 1: + if soup.find(self._midashi_name).find(self._hyouki_name) is None: + for reading in readings: + headwords[reading].append(reading) + expression = expressions[0] + for reading in readings: + if expression not in headwords[reading]: + headwords[reading].append(expression) + elif len(readings) > 1 and len(expressions) == len(readings): + if soup.find(self._midashi_name).find(self._hyouki_name) is None: + for reading in readings: + headwords[reading].append(reading) + for idx, reading in enumerate(readings): + exp = expressions[idx] + if exp not in headwords[reading]: + headwords[reading].append(exp) + else: + raise Exception() # shouldn't happen + return headwords + + def get_part_of_speech_tags(self): + if self._part_of_speech_tags is not None: + return self._part_of_speech_tags + self._part_of_speech_tags = [] + soup = self.get_page_soup() + for midashi in soup.find_all([self._midashi_name, "見出部要素"]): + pos_group = midashi.find("品詞G") + if pos_group is None: + continue + for tag in pos_group.find_all("a"): + if tag.text not in self._part_of_speech_tags: + self._part_of_speech_tags.append(tag.text) + return self._part_of_speech_tags + + def _find_expressions(self, soup): + expressions = [] + for hyouki in soup.find_all(self._hyouki_name): + for expression in parse_hyouki_soup(hyouki, [""]): + expressions.append(expression) + return expressions + + def _find_readings(self, soup): + midasi_kana = soup.find(self._midashi_kana_name) + readings = parse_hyouki_soup(midasi_kana, [""]) + return readings + + def _get_subentry_parameters(self): + from bot.entries.sankoku8.child_entry import ChildEntry + from bot.entries.sankoku8.phrase_entry import PhraseEntry + subentry_parameters = [ + [ChildEntry, ["子項目"], self.children], + [PhraseEntry, ["句項目"], self.phrases], + ] + return subentry_parameters + + @staticmethod + def _delete_unused_nodes(soup): + """Remove extra markup elements that appear in the entry + headword line which are not part of the entry headword""" + unused_nodes = [ + "語構成", "平板", "アクセント", "表外字マーク", "表外音訓マーク", + "アクセント分節", "活用分節", "ルビG", "分書" + ] + for name in unused_nodes: + Soup.delete_soup_nodes(soup, name) diff --git a/bot/entries/sankoku8/child_entry.py b/bot/entries/sankoku8/child_entry.py new file mode 100644 index 0000000..9f6b1c1 --- /dev/null +++ b/bot/entries/sankoku8/child_entry.py @@ -0,0 +1,8 @@ +from bot.entries.sankoku8.base_entry import BaseEntry + + +class ChildEntry(BaseEntry): + def __init__(self, target, page_id): + super().__init__(target, page_id) + self._midashi_name = "子見出部" + self._midashi_kana_name = "子見出仮名" diff --git a/bot/entries/sankoku8/entry.py b/bot/entries/sankoku8/entry.py new file mode 100644 index 0000000..533ac66 --- /dev/null +++ b/bot/entries/sankoku8/entry.py @@ -0,0 +1,14 @@ +from bot.entries.sankoku8.base_entry import BaseEntry +from bot.entries.sankoku8.preprocess import preprocess_page + + +class Entry(BaseEntry): + def __init__(self, target, page_id): + entry_id = (page_id, 0) + super().__init__(target, entry_id) + self._midashi_name = "見出部" + self._midashi_kana_name = "見出仮名" + + def set_page(self, page): + page = preprocess_page(page) + super().set_page(page) diff --git a/bot/entries/sankoku8/parse.py b/bot/entries/sankoku8/parse.py new file mode 100644 index 0000000..a57574b --- /dev/null +++ b/bot/entries/sankoku8/parse.py @@ -0,0 +1,65 @@ +from bs4 import BeautifulSoup + + +def parse_hyouki_soup(soup, base_exps): + omitted_characters = [ + "/", "〈", "〉", "(", ")", "⦅", "⦆", ":", "…" + ] + exps = base_exps.copy() + for child in soup.children: + new_exps = [] + if child.name == "言換G": + for alt in child.find_all("言換"): + parts = parse_hyouki_soup(alt, [""]) + for exp in exps: + for part in parts: + new_exps.append(exp + part) + elif child.name == "補足表記": + alt1 = child.find("表記対象") + alt2 = child.find("表記内容G") + parts1 = parse_hyouki_soup(alt1, [""]) + parts2 = parse_hyouki_soup(alt2, [""]) + for exp in exps: + for part in parts1: + new_exps.append(exp + part) + for part in parts2: + new_exps.append(exp + part) + elif child.name == "省略": + parts = parse_hyouki_soup(child, [""]) + for exp in exps: + new_exps.append(exp) + for part in parts: + new_exps.append(exp + part) + elif child.name is not None: + new_exps = parse_hyouki_soup(child, exps) + else: + text = child.text + for char in omitted_characters: + text = text.replace(char, "") + for exp in exps: + new_exps.append(exp + text) + exps = new_exps.copy() + return exps + + +def parse_hyouki_pattern(pattern): + replacements = { + "(": "<省略>(", + ")": ")", + "{": "<補足表記><表記対象>", + "・": "<表記内容G>(<表記内容>", + "}": "", + "〈": "<言換G>〈<言換>", + "/": "/<言換>", + "〉": "", + "⦅": "<補足表記><表記対象>", + "\": "<表記内容G>⦅<表記内容>", + "⦆": "", + } + markup = f"{pattern}" + for key, val in replacements.items(): + markup = markup.replace(key, val) + soup = BeautifulSoup(markup, "xml") + hyouki_soup = soup.find("span") + exps = parse_hyouki_soup(hyouki_soup, [""]) + return exps diff --git a/bot/entries/sankoku8/phrase_entry.py b/bot/entries/sankoku8/phrase_entry.py new file mode 100644 index 0000000..e5da208 --- /dev/null +++ b/bot/entries/sankoku8/phrase_entry.py @@ -0,0 +1,37 @@ +from bot.data import load_phrase_readings +from bot.entries.sankoku8.base_entry import BaseEntry +from bot.entries.sankoku8.parse import parse_hyouki_soup +from bot.entries.sankoku8.parse import parse_hyouki_pattern + + +class PhraseEntry(BaseEntry): + def get_part_of_speech_tags(self): + # phrases do not contain these tags + return [] + + def _get_headwords(self): + soup = self.get_page_soup() + self._delete_unused_nodes(soup) + expressions = self._find_expressions(soup) + readings = self._find_readings(soup) + headwords = {} + if len(expressions) != len(readings): + raise Exception(f"{self.entry_id[0]}-{self.entry_id[1]}") + for idx, expression in enumerate(expressions): + reading = readings[idx] + if reading in headwords: + headwords[reading].append(expression) + else: + headwords[reading] = [expression] + return headwords + + def _find_expressions(self, soup): + phrase_soup = soup.find("句表記") + expressions = parse_hyouki_soup(phrase_soup, [""]) + return expressions + + def _find_readings(self, soup): + reading_patterns = load_phrase_readings(self.target) + reading_pattern = reading_patterns[self.entry_id] + readings = parse_hyouki_pattern(reading_pattern) + return readings diff --git a/bot/entries/sankoku8_preprocess.py b/bot/entries/sankoku8/preprocess.py similarity index 100% rename from bot/entries/sankoku8_preprocess.py rename to bot/entries/sankoku8/preprocess.py diff --git a/bot/entries/smk8.py b/bot/entries/smk8.py deleted file mode 100644 index 2d43e4a..0000000 --- a/bot/entries/smk8.py +++ /dev/null @@ -1,221 +0,0 @@ -from bs4 import BeautifulSoup - -import bot.entries.expressions as Expressions -import bot.soup as Soup -from bot.data import load_phrase_readings -from bot.entries.entry import Entry -from bot.entries.smk8_preprocess import preprocess_page - - -class _BaseSmk8Entry(Entry): - def __init__(self, target, entry_id): - super().__init__(target, entry_id) - self.children = [] - self.phrases = [] - self.kanjis = [] - - def get_global_identifier(self): - parent_part = format(self.entry_id[0], '06') - child_part = hex(self.entry_id[1]).lstrip('0x').zfill(4).upper() - return f"@{self.target.value}-{parent_part}-{child_part}" - - def set_page(self, page): - page = self.__decompose_subentries(page) - self._page = page - - def get_page_soup(self): - soup = BeautifulSoup(self._page, "xml") - return soup - - def get_part_of_speech_tags(self): - if self._part_of_speech_tags is not None: - return self._part_of_speech_tags - self._part_of_speech_tags = [] - soup = self.get_page_soup() - headword_info = soup.find("見出要素") - if headword_info is None: - return self._part_of_speech_tags - for tag in headword_info.find_all("品詞M"): - if tag.text not in self._part_of_speech_tags: - self._part_of_speech_tags.append(tag.text) - return self._part_of_speech_tags - - def _add_variant_expressions(self, headwords): - for expressions in headwords.values(): - Expressions.add_variant_kanji(expressions) - Expressions.add_fullwidth(expressions) - Expressions.remove_iteration_mark(expressions) - Expressions.add_iteration_mark(expressions) - - def _find_reading(self, soup): - midasi_kana = soup.find("見出仮名") - reading = midasi_kana.text - for x in [" ", "・"]: - reading = reading.replace(x, "") - return reading - - def _find_expressions(self, soup): - clean_expressions = [] - for expression in soup.find_all("標準表記"): - clean_expression = self._clean_expression(expression.text) - clean_expressions.append(clean_expression) - expressions = Expressions.expand_abbreviation_list(clean_expressions) - return expressions - - def __decompose_subentries(self, page): - soup = BeautifulSoup(page, features="xml") - subentry_parameters = [ - [Smk8ChildEntry, ["子項目F", "子項目"], self.children], - [Smk8PhraseEntry, ["句項目F", "句項目"], self.phrases], - [Smk8KanjiEntry, ["造語成分項目"], self.kanjis], - ] - for x in subentry_parameters: - subentry_class, tags, subentry_list = x - for tag in tags: - tag_soup = soup.find(tag) - while tag_soup is not None: - tag_soup.name = "項目" - subentry_id = self.id_string_to_entry_id(tag_soup.attrs["id"]) - self.SUBENTRY_ID_TO_ENTRY_ID[subentry_id] = self.entry_id - subentry = subentry_class(self.target, subentry_id) - page = tag_soup.decode() - subentry.set_page(page) - subentry_list.append(subentry) - tag_soup.decompose() - tag_soup = soup.find(tag) - return soup.decode() - - @staticmethod - def id_string_to_entry_id(id_string): - parts = id_string.split("-") - if len(parts) == 1: - return (int(parts[0]), 0) - elif len(parts) == 2: - # subentries have a hexadecimal part - return (int(parts[0]), int(parts[1], 16)) - else: - raise Exception(f"Invalid entry ID: {id_string}") - - @staticmethod - def _delete_unused_nodes(soup): - """Remove extra markup elements that appear in the entry - headword line which are not part of the entry headword""" - unused_nodes = [ - "表音表記", "表外音訓マーク", "表外字マーク", "ルビG" - ] - for name in unused_nodes: - Soup.delete_soup_nodes(soup, name) - - @staticmethod - def _clean_expression(expression): - for x in ["〈", "〉", "{", "}", "…", " "]: - expression = expression.replace(x, "") - return expression - - @staticmethod - def _fill_alts(soup): - for el in soup.find_all(["親見出仮名", "親見出表記"]): - el.string = el.attrs["alt"] - for gaiji in soup.find_all("外字"): - gaiji.string = gaiji.img.attrs["alt"] - - -class Smk8Entry(_BaseSmk8Entry): - def __init__(self, target, page_id): - entry_id = (page_id, 0) - super().__init__(target, entry_id) - - def set_page(self, page): - page = preprocess_page(page) - super().set_page(page) - - def _get_headwords(self): - soup = self.get_page_soup() - self._delete_unused_nodes(soup) - self._fill_alts(soup) - reading = self._find_reading(soup) - expressions = [] - if soup.find("見出部").find("標準表記") is None: - expressions.append(reading) - for expression in self._find_expressions(soup): - if expression not in expressions: - expressions.append(expression) - headwords = {reading: expressions} - return headwords - - -class Smk8ChildEntry(_BaseSmk8Entry): - def _get_headwords(self): - soup = self.get_page_soup() - self._delete_unused_nodes(soup) - self._fill_alts(soup) - reading = self._find_reading(soup) - expressions = [] - if soup.find("子見出部").find("標準表記") is None: - expressions.append(reading) - for expression in self._find_expressions(soup): - if expression not in expressions: - expressions.append(expression) - headwords = {reading: expressions} - return headwords - - -class Smk8PhraseEntry(_BaseSmk8Entry): - def __init__(self, target, entry_id): - super().__init__(target, entry_id) - self.__phrase_readings = load_phrase_readings(self.target) - - def get_part_of_speech_tags(self): - # phrases do not contain these tags - return [] - - def _get_headwords(self): - soup = self.get_page_soup() - headwords = {} - expressions = self._find_expressions(soup) - readings = self._find_readings() - for idx, expression in enumerate(expressions): - reading = readings[idx] - if reading in headwords: - headwords[reading].append(expression) - else: - headwords[reading] = [expression] - return headwords - - def _find_expressions(self, soup): - self._delete_unused_nodes(soup) - self._fill_alts(soup) - text = soup.find("標準表記").text - text = self._clean_expression(text) - alternatives = Expressions.expand_smk_alternatives(text) - expressions = [] - for alt in alternatives: - for exp in Expressions.expand_abbreviation(alt): - expressions.append(exp) - return expressions - - def _find_readings(self): - text = self.__phrase_readings[self.entry_id] - alternatives = Expressions.expand_smk_alternatives(text) - readings = [] - for alt in alternatives: - for reading in Expressions.expand_abbreviation(alt): - readings.append(reading) - return readings - - -class Smk8KanjiEntry(_BaseSmk8Entry): - def _get_headwords(self): - soup = self.get_page_soup() - self._delete_unused_nodes(soup) - self._fill_alts(soup) - reading = self.__get_parent_reading() - expressions = self._find_expressions(soup) - headwords = {reading: expressions} - return headwords - - def __get_parent_reading(self): - parent_id = self.SUBENTRY_ID_TO_ENTRY_ID[self.entry_id] - parent = self.ID_TO_ENTRY[parent_id] - reading = parent.get_first_reading() - return reading diff --git a/bot/entries/smk8/base_entry.py b/bot/entries/smk8/base_entry.py new file mode 100644 index 0000000..7bf32c2 --- /dev/null +++ b/bot/entries/smk8/base_entry.py @@ -0,0 +1,73 @@ +import bot.soup as Soup +import bot.entries.base.expressions as Expressions +from bot.entries.base.sanseido_entry import SanseidoEntry + + +class BaseEntry(SanseidoEntry): + def __init__(self, target, entry_id): + super().__init__(target, entry_id) + self.children = [] + self.phrases = [] + self.kanjis = [] + + def get_part_of_speech_tags(self): + if self._part_of_speech_tags is not None: + return self._part_of_speech_tags + self._part_of_speech_tags = [] + soup = self.get_page_soup() + headword_info = soup.find("見出要素") + if headword_info is None: + return self._part_of_speech_tags + for tag in headword_info.find_all("品詞M"): + if tag.text not in self._part_of_speech_tags: + self._part_of_speech_tags.append(tag.text) + return self._part_of_speech_tags + + def _find_reading(self, soup): + midasi_kana = soup.find("見出仮名") + reading = midasi_kana.text + for x in [" ", "・"]: + reading = reading.replace(x, "") + return reading + + def _find_expressions(self, soup): + clean_expressions = [] + for expression in soup.find_all("標準表記"): + clean_expression = self._clean_expression(expression.text) + clean_expressions.append(clean_expression) + expressions = Expressions.expand_abbreviation_list(clean_expressions) + return expressions + + def _get_subentry_parameters(self): + from bot.entries.smk8.child_entry import ChildEntry + from bot.entries.smk8.phrase_entry import PhraseEntry + from bot.entries.smk8.kanji_entry import KanjiEntry + subentry_parameters = [ + [ChildEntry, ["子項目F", "子項目"], self.children], + [PhraseEntry, ["句項目F", "句項目"], self.phrases], + [KanjiEntry, ["造語成分項目"], self.kanjis], + ] + return subentry_parameters + + @staticmethod + def _delete_unused_nodes(soup): + """Remove extra markup elements that appear in the entry + headword line which are not part of the entry headword""" + unused_nodes = [ + "表音表記", "表外音訓マーク", "表外字マーク", "ルビG" + ] + for name in unused_nodes: + Soup.delete_soup_nodes(soup, name) + + @staticmethod + def _clean_expression(expression): + for x in ["〈", "〉", "{", "}", "…", " "]: + expression = expression.replace(x, "") + return expression + + @staticmethod + def _fill_alts(soup): + for elm in soup.find_all(["親見出仮名", "親見出表記"]): + elm.string = elm.attrs["alt"] + for gaiji in soup.find_all("外字"): + gaiji.string = gaiji.img.attrs["alt"] diff --git a/bot/entries/smk8/child_entry.py b/bot/entries/smk8/child_entry.py new file mode 100644 index 0000000..0dbe375 --- /dev/null +++ b/bot/entries/smk8/child_entry.py @@ -0,0 +1,17 @@ +from bot.entries.smk8.base_entry import BaseEntry + + +class ChildEntry(BaseEntry): + def _get_headwords(self): + soup = self.get_page_soup() + self._delete_unused_nodes(soup) + self._fill_alts(soup) + reading = self._find_reading(soup) + expressions = [] + if soup.find("子見出部").find("標準表記") is None: + expressions.append(reading) + for expression in self._find_expressions(soup): + if expression not in expressions: + expressions.append(expression) + headwords = {reading: expressions} + return headwords diff --git a/bot/entries/smk8/entry.py b/bot/entries/smk8/entry.py new file mode 100644 index 0000000..4baed42 --- /dev/null +++ b/bot/entries/smk8/entry.py @@ -0,0 +1,26 @@ +from bot.entries.smk8.base_entry import BaseEntry +from bot.entries.smk8.preprocess import preprocess_page + + +class Entry(BaseEntry): + def __init__(self, target, page_id): + entry_id = (page_id, 0) + super().__init__(target, entry_id) + + def set_page(self, page): + page = preprocess_page(page) + super().set_page(page) + + def _get_headwords(self): + soup = self.get_page_soup() + self._delete_unused_nodes(soup) + self._fill_alts(soup) + reading = self._find_reading(soup) + expressions = [] + if soup.find("見出部").find("標準表記") is None: + expressions.append(reading) + for expression in self._find_expressions(soup): + if expression not in expressions: + expressions.append(expression) + headwords = {reading: expressions} + return headwords diff --git a/bot/entries/smk8/kanji_entry.py b/bot/entries/smk8/kanji_entry.py new file mode 100644 index 0000000..3e77faf --- /dev/null +++ b/bot/entries/smk8/kanji_entry.py @@ -0,0 +1,22 @@ +from bot.entries.smk8.base_entry import BaseEntry + + +class KanjiEntry(BaseEntry): + def get_part_of_speech_tags(self): + # kanji entries do not contain these tags + return [] + + def _get_headwords(self): + soup = self.get_page_soup() + self._delete_unused_nodes(soup) + self._fill_alts(soup) + reading = self.__get_parent_reading() + expressions = self._find_expressions(soup) + headwords = {reading: expressions} + return headwords + + def __get_parent_reading(self): + parent_id = self.SUBENTRY_ID_TO_ENTRY_ID[self.entry_id] + parent = self.ID_TO_ENTRY[parent_id] + reading = parent.get_first_reading() + return reading diff --git a/bot/entries/smk8/phrase_entry.py b/bot/entries/smk8/phrase_entry.py new file mode 100644 index 0000000..aac9b84 --- /dev/null +++ b/bot/entries/smk8/phrase_entry.py @@ -0,0 +1,64 @@ +import re + +import bot.entries.base.expressions as Expressions +from bot.data import load_phrase_readings +from bot.entries.smk8.base_entry import BaseEntry + + +class PhraseEntry(BaseEntry): + def __init__(self, target, entry_id): + super().__init__(target, entry_id) + self.__phrase_readings = load_phrase_readings(self.target) + + def get_part_of_speech_tags(self): + # phrase entries do not contain these tags + return [] + + def _get_headwords(self): + soup = self.get_page_soup() + headwords = {} + expressions = self._find_expressions(soup) + readings = self._find_readings() + for idx, expression in enumerate(expressions): + reading = readings[idx] + if reading in headwords: + headwords[reading].append(expression) + else: + headwords[reading] = [expression] + return headwords + + def _find_expressions(self, soup): + self._delete_unused_nodes(soup) + self._fill_alts(soup) + text = soup.find("標準表記").text + text = self._clean_expression(text) + alternatives = parse_phrase(text) + expressions = [] + for alt in alternatives: + for exp in Expressions.expand_abbreviation(alt): + expressions.append(exp) + return expressions + + def _find_readings(self): + text = self.__phrase_readings[self.entry_id] + alternatives = parse_phrase(text) + readings = [] + for alt in alternatives: + for reading in Expressions.expand_abbreviation(alt): + readings.append(reading) + return readings + + +def parse_phrase(text): + """Return a list of strings described by △ notation.""" + match = re.search(r"△([^(]+)(([^(]+))", text) + if match is None: + return [text] + alt_parts = [match.group(1)] + for alt_part in match.group(2).split("・"): + alt_parts.append(alt_part) + alts = [] + for alt_part in alt_parts: + alt_exp = re.sub(r"△[^(]+([^(]+)", alt_part, text) + alts.append(alt_exp) + return alts diff --git a/bot/entries/smk8_preprocess.py b/bot/entries/smk8/preprocess.py similarity index 100% rename from bot/entries/smk8_preprocess.py rename to bot/entries/smk8/preprocess.py diff --git a/bot/yomichan/terms/daijirin2.py b/bot/yomichan/terms/daijirin2.py index 10aaa76..281fac4 100644 --- a/bot/yomichan/terms/daijirin2.py +++ b/bot/yomichan/terms/daijirin2.py @@ -1,4 +1,4 @@ -from bot.entries.daijirin2 import Daijirin2PhraseEntry as PhraseEntry +from bot.entries.daijirin2.phrase_entry import PhraseEntry from bot.yomichan.terms.terminator import Terminator from bot.yomichan.glossary.daijirin2 import make_glossary @@ -6,9 +6,6 @@ from bot.yomichan.grammar import sudachi_rules, tags_to_rules class Daijirin2Terminator(Terminator): - def __init__(self, target): - super().__init__(target) - def _definition_tags(self, entry): return "" diff --git a/bot/yomichan/terms/sankoku8.py b/bot/yomichan/terms/sankoku8.py index 613f3bb..cff264f 100644 --- a/bot/yomichan/terms/sankoku8.py +++ b/bot/yomichan/terms/sankoku8.py @@ -1,4 +1,4 @@ -from bot.entries.sankoku8 import Sankoku8PhraseEntry as PhraseEntry +from bot.entries.sankoku8.phrase_entry import PhraseEntry from bot.yomichan.terms.terminator import Terminator from bot.yomichan.glossary.sankoku8 import make_glossary @@ -6,9 +6,6 @@ from bot.yomichan.grammar import sudachi_rules, tags_to_rules class Sankoku8Terminator(Terminator): - def __init__(self, target): - super().__init__(target) - def _definition_tags(self, entry): return "" diff --git a/bot/yomichan/terms/smk8.py b/bot/yomichan/terms/smk8.py index d1e3ca7..766f4a0 100644 --- a/bot/yomichan/terms/smk8.py +++ b/bot/yomichan/terms/smk8.py @@ -1,5 +1,5 @@ -from bot.entries.smk8 import Smk8KanjiEntry as KanjiEntry -from bot.entries.smk8 import Smk8PhraseEntry as PhraseEntry +from bot.entries.smk8.kanji_entry import KanjiEntry +from bot.entries.smk8.phrase_entry import PhraseEntry from bot.yomichan.terms.terminator import Terminator from bot.yomichan.glossary.smk8 import make_glossary diff --git a/tests/test_daijirin_phrases.py b/tests/test_daijirin_phrases.py new file mode 100644 index 0000000..3ab02dd --- /dev/null +++ b/tests/test_daijirin_phrases.py @@ -0,0 +1,21 @@ +import unittest +from bot.entries.daijirin2.phrase_entry import parse_phrase + + +class TestDaijirin2PhraseParse(unittest.TestCase): + def test1(self): + text = "同じ穴の=狢(=狐・狸)" + exps = parse_phrase(text) + self.assertEqual(len(exps), 3) + self.assertIn("同じ穴の狢", exps) + self.assertIn("同じ穴の狐", exps) + self.assertIn("同じ穴の狸", exps) + + def test2(self): + text = "聞くは=一時(=一旦)の恥、聞かぬは=末代(=一生)の恥" + exps = parse_phrase(text) + self.assertEqual(len(exps), 4) + self.assertIn("聞くは一時の恥、聞かぬは末代の恥", exps) + self.assertIn("聞くは一時の恥、聞かぬは一生の恥", exps) + self.assertIn("聞くは一旦の恥、聞かぬは末代の恥", exps) + self.assertIn("聞くは一旦の恥、聞かぬは一生の恥", exps) diff --git a/tests/test_expressions.py b/tests/test_expressions.py index b2ebc26..5d90ce1 100644 --- a/tests/test_expressions.py +++ b/tests/test_expressions.py @@ -1,5 +1,5 @@ import unittest -import bot.entries.expressions as Expressions +import bot.entries.base.expressions as Expressions class TestExpressions(unittest.TestCase): @@ -69,28 +69,3 @@ class TestExpressions(unittest.TestCase): self.assertIn("有合わせ", abbrs) self.assertIn("有り合せ", abbrs) self.assertIn("有合せ", abbrs) - - def test_smk_expand_alternatives(self): - text = "△金(時間・暇)に飽かして" - exps = Expressions.expand_smk_alternatives(text) - self.assertEqual(len(exps), 3) - self.assertIn("金に飽かして", exps) - self.assertIn("時間に飽かして", exps) - self.assertIn("暇に飽かして", exps) - - def test_daijirin_expand_alternatives(self): - text = "同じ穴の=狢(=狐・狸)" - exps = Expressions.expand_daijirin_alternatives(text) - self.assertEqual(len(exps), 3) - self.assertIn("同じ穴の狢", exps) - self.assertIn("同じ穴の狐", exps) - self.assertIn("同じ穴の狸", exps) - - def test_daijirin_expand_alternatives2(self): - text = "聞くは=一時(=一旦)の恥、聞かぬは=末代(=一生)の恥" - exps = Expressions.expand_daijirin_alternatives(text) - self.assertEqual(len(exps), 4) - self.assertIn("聞くは一時の恥、聞かぬは末代の恥", exps) - self.assertIn("聞くは一時の恥、聞かぬは一生の恥", exps) - self.assertIn("聞くは一旦の恥、聞かぬは末代の恥", exps) - self.assertIn("聞くは一旦の恥、聞かぬは一生の恥", exps) diff --git a/tests/test_sankoku_phrases.py b/tests/test_sankoku_phrases.py index 7faf289..c3894e9 100644 --- a/tests/test_sankoku_phrases.py +++ b/tests/test_sankoku_phrases.py @@ -1,16 +1,16 @@ import unittest -from bot.entries.sankoku8 import parse_hyouki_pattern +from bot.entries.sankoku8.parse import parse_hyouki_pattern -class TestSankokuPhrases(unittest.TestCase): - def test_sankoku_phrases1(self): +class TestSankoku8PhraseParse(unittest.TestCase): + def test1(self): pattern = '耳にたこ(ができる)' exps = parse_hyouki_pattern(pattern) self.assertEqual(len(exps), 2) self.assertIn("耳にたこ", exps) self.assertIn("耳にたこができる", exps) - def test_sankoku_phrases2(self): + def test2(self): pattern = '一斑を〈見て/もって〉全豹を〈卜す/推す〉' exps = parse_hyouki_pattern(pattern) self.assertEqual(len(exps), 4) @@ -19,14 +19,14 @@ class TestSankokuPhrases(unittest.TestCase): self.assertIn("一斑をもって全豹を卜す", exps) self.assertIn("一斑をもって全豹を推す", exps) - def test_sankoku_phrases3(self): + def test3(self): pattern = '{かじ・舵}を切る' exps = parse_hyouki_pattern(pattern) self.assertEqual(len(exps), 2) self.assertIn("かじを切る", exps) self.assertIn("舵を切る", exps) - def test_sankoku_phrases4(self): + def test4(self): pattern = '重箱の隅を(⦅ようじ\楊枝⦆で)〈つつく/ほじくる〉' exps = parse_hyouki_pattern(pattern) self.assertEqual(len(exps), 6) @@ -37,7 +37,7 @@ class TestSankokuPhrases(unittest.TestCase): self.assertIn("重箱の隅をようじでほじくる", exps) self.assertIn("重箱の隅を楊枝でほじくる", exps) - def test_sankoku_phrases5(self): + def test5(self): pattern = '群盲象を〈{な・撫}でる/評する〉' exps = parse_hyouki_pattern(pattern) self.assertEqual(len(exps), 3) diff --git a/tests/test_smk_phrases.py b/tests/test_smk_phrases.py new file mode 100644 index 0000000..e5ce231 --- /dev/null +++ b/tests/test_smk_phrases.py @@ -0,0 +1,19 @@ +import unittest +from bot.entries.smk8.phrase_entry import parse_phrase + + +class TestSmk8PhraseParse(unittest.TestCase): + def test1(self): + text = "目と鼻の△先(間)" + exps = parse_phrase(text) + self.assertEqual(len(exps), 2) + self.assertIn("目と鼻の先", exps) + self.assertIn("目と鼻の間", exps) + + def test2(self): + text = "△金(時間・暇)に飽かして" + exps = parse_phrase(text) + self.assertEqual(len(exps), 3) + self.assertIn("金に飽かして", exps) + self.assertIn("時間に飽かして", exps) + self.assertIn("暇に飽かして", exps) From 7b2ba96db9770c6ef5e827b8dc2161d52cf3e872 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Wed, 26 Jul 2023 23:48:24 -0500 Subject: [PATCH 35/43] Reorganize file structure of all other modules --- bot/crawlers/base/crawler.py | 54 ++++++ bot/crawlers/base/jitenon.py | 29 ++++ bot/crawlers/base/monokakido.py | 19 +++ bot/crawlers/crawlers.py | 158 ------------------ bot/crawlers/daijirin2.py | 5 + bot/crawlers/factory.py | 20 --- bot/crawlers/jitenon_kokugo.py | 38 +++++ bot/crawlers/jitenon_kotowaza.py | 8 + bot/crawlers/jitenon_yoji.py | 8 + bot/crawlers/sankoku8.py | 5 + bot/crawlers/scrapers/jitenon.py | 10 ++ bot/crawlers/{ => scrapers}/scraper.py | 24 ++- bot/crawlers/smk8.py | 5 + bot/entries/base/entry.py | 12 +- bot/entries/base/jitenon_entry.py | 2 +- bot/entries/base/sanseido_entry.py | 2 +- bot/entries/factory.py | 7 - bot/factory.py | 37 ++++ .../exporters/{export.py => base/exporter.py} | 66 +------- bot/mdict/exporters/base/jitenon.py | 18 ++ bot/mdict/exporters/base/monokakido.py | 8 + bot/mdict/exporters/daijirin2.py | 6 + bot/mdict/exporters/factory.py | 20 --- bot/mdict/exporters/jitenon_kokugo.py | 5 + bot/mdict/exporters/jitenon_kotowaza.py | 5 + bot/mdict/exporters/jitenon_yoji.py | 5 + bot/mdict/exporters/sankoku8.py | 6 + bot/mdict/exporters/smk8.py | 6 + bot/mdict/terms/base/jitenon.py | 20 +++ bot/mdict/terms/{ => base}/terminator.py | 8 +- bot/mdict/terms/daijirin2.py | 4 +- bot/mdict/terms/factory.py | 20 --- bot/mdict/terms/jitenon.py | 42 ----- bot/mdict/terms/jitenon_kokugo.py | 8 + bot/mdict/terms/jitenon_kotowaza.py | 8 + bot/mdict/terms/jitenon_yoji.py | 8 + bot/mdict/terms/sankoku8.py | 4 +- bot/mdict/terms/smk8.py | 4 +- .../exporters/{export.py => base/exporter.py} | 74 ++------ bot/yomichan/exporters/base/jitenon.py | 18 ++ bot/yomichan/exporters/base/monokakido.py | 8 + bot/yomichan/exporters/daijirin2.py | 6 + bot/yomichan/exporters/factory.py | 20 --- bot/yomichan/exporters/jitenon_kokugo.py | 5 + bot/yomichan/exporters/jitenon_kotowaza.py | 5 + bot/yomichan/exporters/jitenon_yoji.py | 5 + bot/yomichan/exporters/sankoku8.py | 6 + bot/yomichan/exporters/smk8.py | 6 + bot/yomichan/glossary/daijirin2.py | 3 +- bot/yomichan/terms/base/jitenon.py | 26 +++ bot/yomichan/terms/{ => base}/terminator.py | 16 +- bot/yomichan/terms/daijirin2.py | 5 +- bot/yomichan/terms/factory.py | 20 --- bot/yomichan/terms/jitenon.py | 68 -------- bot/yomichan/terms/jitenon_kokugo.py | 15 ++ bot/yomichan/terms/jitenon_kotowaza.py | 15 ++ bot/yomichan/terms/jitenon_yoji.py | 15 ++ bot/yomichan/terms/sankoku8.py | 5 +- bot/yomichan/terms/smk8.py | 5 +- jitenbot.py | 2 +- run_all.sh | 2 + 61 files changed, 517 insertions(+), 547 deletions(-) create mode 100644 bot/crawlers/base/crawler.py create mode 100644 bot/crawlers/base/jitenon.py create mode 100644 bot/crawlers/base/monokakido.py delete mode 100644 bot/crawlers/crawlers.py create mode 100644 bot/crawlers/daijirin2.py delete mode 100644 bot/crawlers/factory.py create mode 100644 bot/crawlers/jitenon_kokugo.py create mode 100644 bot/crawlers/jitenon_kotowaza.py create mode 100644 bot/crawlers/jitenon_yoji.py create mode 100644 bot/crawlers/sankoku8.py create mode 100644 bot/crawlers/scrapers/jitenon.py rename bot/crawlers/{ => scrapers}/scraper.py (93%) create mode 100644 bot/crawlers/smk8.py delete mode 100644 bot/entries/factory.py create mode 100644 bot/factory.py rename bot/mdict/exporters/{export.py => base/exporter.py} (79%) create mode 100644 bot/mdict/exporters/base/jitenon.py create mode 100644 bot/mdict/exporters/base/monokakido.py create mode 100644 bot/mdict/exporters/daijirin2.py delete mode 100644 bot/mdict/exporters/factory.py create mode 100644 bot/mdict/exporters/jitenon_kokugo.py create mode 100644 bot/mdict/exporters/jitenon_kotowaza.py create mode 100644 bot/mdict/exporters/jitenon_yoji.py create mode 100644 bot/mdict/exporters/sankoku8.py create mode 100644 bot/mdict/exporters/smk8.py create mode 100644 bot/mdict/terms/base/jitenon.py rename bot/mdict/terms/{ => base}/terminator.py (95%) delete mode 100644 bot/mdict/terms/factory.py delete mode 100644 bot/mdict/terms/jitenon.py create mode 100644 bot/mdict/terms/jitenon_kokugo.py create mode 100644 bot/mdict/terms/jitenon_kotowaza.py create mode 100644 bot/mdict/terms/jitenon_yoji.py rename bot/yomichan/exporters/{export.py => base/exporter.py} (76%) create mode 100644 bot/yomichan/exporters/base/jitenon.py create mode 100644 bot/yomichan/exporters/base/monokakido.py create mode 100644 bot/yomichan/exporters/daijirin2.py delete mode 100644 bot/yomichan/exporters/factory.py create mode 100644 bot/yomichan/exporters/jitenon_kokugo.py create mode 100644 bot/yomichan/exporters/jitenon_kotowaza.py create mode 100644 bot/yomichan/exporters/jitenon_yoji.py create mode 100644 bot/yomichan/exporters/sankoku8.py create mode 100644 bot/yomichan/exporters/smk8.py create mode 100644 bot/yomichan/terms/base/jitenon.py rename bot/yomichan/terms/{ => base}/terminator.py (91%) delete mode 100644 bot/yomichan/terms/factory.py delete mode 100644 bot/yomichan/terms/jitenon.py create mode 100644 bot/yomichan/terms/jitenon_kokugo.py create mode 100644 bot/yomichan/terms/jitenon_kotowaza.py create mode 100644 bot/yomichan/terms/jitenon_yoji.py diff --git a/bot/crawlers/base/crawler.py b/bot/crawlers/base/crawler.py new file mode 100644 index 0000000..31c3bdc --- /dev/null +++ b/bot/crawlers/base/crawler.py @@ -0,0 +1,54 @@ +import re +from abc import ABC, abstractmethod + +from bot.factory import new_entry +from bot.factory import new_yomichan_exporter +from bot.factory import new_mdict_exporter + + +class BaseCrawler(ABC): + def __init__(self, target): + self._target = target + self._page_map = {} + self._entries = [] + self._page_id_pattern = None + + @abstractmethod + def collect_pages(self, page_dir): + raise NotImplementedError + + def read_pages(self): + pages_len = len(self._page_map) + items = self._page_map.items() + for idx, (page_id, page_path) in enumerate(items): + update = f"Reading page {idx+1}/{pages_len}" + print(update, end='\r', flush=True) + entry = new_entry(self._target, page_id) + with open(page_path, "r", encoding="utf-8") as f: + page = f.read() + try: + entry.set_page(page) + except ValueError as err: + print(err) + print("Try deleting and redownloading file:") + print(f"\t{page_path}\n") + continue + self._entries.append(entry) + print() + + def make_yomichan_dictionary(self, media_dir, validate): + exporter = new_yomichan_exporter(self._target) + exporter.export(self._entries, media_dir, validate) + + def make_mdict_dictionary(self, media_dir, icon_file): + exporter = new_mdict_exporter(self._target) + exporter.export(self._entries, media_dir, icon_file) + + def _parse_page_id(self, page_link): + m = re.search(self._page_id_pattern, page_link) + if m is None: + return None + page_id = int(m.group(1)) + if page_id in self._page_map: + return None + return page_id diff --git a/bot/crawlers/base/jitenon.py b/bot/crawlers/base/jitenon.py new file mode 100644 index 0000000..ddbf3e5 --- /dev/null +++ b/bot/crawlers/base/jitenon.py @@ -0,0 +1,29 @@ +from bs4 import BeautifulSoup + +from bot.crawlers.scrapers.jitenon import Jitenon as JitenonScraper +from bot.crawlers.base.crawler import BaseCrawler + + +class JitenonCrawler(BaseCrawler): + def __init__(self, target): + super().__init__(target) + self._gojuon_url = None + + def collect_pages(self, page_dir): + print("Scraping jitenon.jp") + jitenon = JitenonScraper() + gojuon_doc, _ = jitenon.scrape(self._gojuon_url) + gojuon_soup = BeautifulSoup(gojuon_doc, features="html.parser") + for gojuon_a in gojuon_soup.select(".kana_area a", href=True): + gojuon_href = gojuon_a['href'] + kana_doc, _ = jitenon.scrape(gojuon_href) + kana_soup = BeautifulSoup(kana_doc, features="html.parser") + for kana_a in kana_soup.select(".word_box a", href=True): + page_link = kana_a['href'] + page_id = self._parse_page_id(page_link) + if page_id is None: + continue + _, page_path = jitenon.scrape(page_link) + self._page_map[page_id] = page_path + pages_len = len(self._page_map) + print(f"Finished scraping {pages_len} pages") diff --git a/bot/crawlers/base/monokakido.py b/bot/crawlers/base/monokakido.py new file mode 100644 index 0000000..057f8d4 --- /dev/null +++ b/bot/crawlers/base/monokakido.py @@ -0,0 +1,19 @@ +import os +from bot.crawlers.base.crawler import BaseCrawler + + +class MonokakidoCrawler(BaseCrawler): + def __init__(self, target): + super().__init__(target) + self._page_id_pattern = r"^([0-9]+)\.xml$" + + def collect_pages(self, page_dir): + print(f"Searching for page files in `{page_dir}`") + for pagefile in os.listdir(page_dir): + page_id = self._parse_page_id(pagefile) + if page_id is None or page_id == 0: + continue + path = os.path.join(page_dir, pagefile) + self._page_map[page_id] = path + pages_len = len(self._page_map) + print(f"Found {pages_len} page files for processing") diff --git a/bot/crawlers/crawlers.py b/bot/crawlers/crawlers.py deleted file mode 100644 index 51e0552..0000000 --- a/bot/crawlers/crawlers.py +++ /dev/null @@ -1,158 +0,0 @@ -import os -import re -from abc import ABC, abstractmethod -from bs4 import BeautifulSoup - -import bot.crawlers.scraper as Scraper -from bot.entries.factory import new_entry -from bot.yomichan.exporters.factory import new_yomi_exporter -from bot.mdict.exporters.factory import new_mdict_exporter - - -class Crawler(ABC): - def __init__(self, target): - self._target = target - self._page_map = {} - self._entries = [] - self._page_id_pattern = None - - @abstractmethod - def collect_pages(self, page_dir): - pass - - def read_pages(self): - pages_len = len(self._page_map) - items = self._page_map.items() - for idx, (page_id, page_path) in enumerate(items): - update = f"Reading page {idx+1}/{pages_len}" - print(update, end='\r', flush=True) - entry = new_entry(self._target, page_id) - with open(page_path, "r", encoding="utf-8") as f: - page = f.read() - try: - entry.set_page(page) - except ValueError as err: - print(err) - print("Try deleting and redownloading file:") - print(f"\t{page_path}\n") - continue - self._entries.append(entry) - print() - - def make_yomichan_dictionary(self, media_dir, validate): - exporter = new_yomi_exporter(self._target) - exporter.export(self._entries, media_dir, validate) - - def make_mdict_dictionary(self, media_dir, icon_file): - exporter = new_mdict_exporter(self._target) - exporter.export(self._entries, media_dir, icon_file) - - def _parse_page_id(self, page_link): - m = re.search(self._page_id_pattern, page_link) - if m is None: - return None - page_id = int(m.group(1)) - if page_id in self._page_map: - return None - return page_id - - -class JitenonKokugoCrawler(Crawler): - def __init__(self, target): - super().__init__(target) - self._gojuon_url = "https://kokugo.jitenon.jp/cat/gojuonindex.php" - self._page_id_pattern = r"word/p([0-9]+)$" - - def collect_pages(self, page_dir): - jitenon = Scraper.Jitenon() - gojuon_doc, _ = jitenon.scrape(self._gojuon_url) - gojuon_soup = BeautifulSoup(gojuon_doc, features="html.parser") - for gojuon_a in gojuon_soup.select(".kana_area a", href=True): - gojuon_href = gojuon_a['href'] - max_kana_page = 1 - current_kana_page = 1 - while current_kana_page <= max_kana_page: - kana_doc, _ = jitenon.scrape(f"{gojuon_href}&page={current_kana_page}") - current_kana_page += 1 - kana_soup = BeautifulSoup(kana_doc, features="html.parser") - page_total = kana_soup.find(class_="page_total").text - m = re.search(r"全([0-9]+)件", page_total) - if m: - max_kana_page = int(m.group(1)) - for kana_a in kana_soup.select(".word_box a", href=True): - page_link = kana_a['href'] - page_id = self._parse_page_id(page_link) - if page_id is None: - continue - _, page_path = jitenon.scrape(page_link) - self._page_map[page_id] = page_path - pages_len = len(self._page_map) - print(f"Finished scraping {pages_len} pages") - - -class _JitenonCrawler(Crawler): - def __init__(self, target): - super().__init__(target) - self._gojuon_url = None - - def collect_pages(self, page_dir): - print("Scraping jitenon.jp") - jitenon = Scraper.Jitenon() - gojuon_doc, _ = jitenon.scrape(self._gojuon_url) - gojuon_soup = BeautifulSoup(gojuon_doc, features="html.parser") - for gojuon_a in gojuon_soup.select(".kana_area a", href=True): - gojuon_href = gojuon_a['href'] - kana_doc, _ = jitenon.scrape(gojuon_href) - kana_soup = BeautifulSoup(kana_doc, features="html.parser") - for kana_a in kana_soup.select(".word_box a", href=True): - page_link = kana_a['href'] - page_id = self._parse_page_id(page_link) - if page_id is None: - continue - _, page_path = jitenon.scrape(page_link) - self._page_map[page_id] = page_path - pages_len = len(self._page_map) - print(f"Finished scraping {pages_len} pages") - - -class JitenonYojiCrawler(_JitenonCrawler): - def __init__(self, target): - super().__init__(target) - self._gojuon_url = "https://yoji.jitenon.jp/cat/gojuon.html" - self._page_id_pattern = r"([0-9]+)\.html$" - - -class JitenonKotowazaCrawler(_JitenonCrawler): - def __init__(self, target): - super().__init__(target) - self._gojuon_url = "https://kotowaza.jitenon.jp/cat/gojuon.php" - self._page_id_pattern = r"([0-9]+)\.php$" - - -class _MonokakidoCrawler(Crawler): - def __init__(self, target): - super().__init__(target) - self._page_id_pattern = r"^([0-9]+)\.xml$" - - def collect_pages(self, page_dir): - print(f"Searching for page files in `{page_dir}`") - for pagefile in os.listdir(page_dir): - page_id = self._parse_page_id(pagefile) - if page_id is None or page_id == 0: - continue - path = os.path.join(page_dir, pagefile) - self._page_map[page_id] = path - pages_len = len(self._page_map) - print(f"Found {pages_len} page files for processing") - - -class Smk8Crawler(_MonokakidoCrawler): - pass - - -class Daijirin2Crawler(_MonokakidoCrawler): - pass - - -class Sankoku8Crawler(_MonokakidoCrawler): - pass diff --git a/bot/crawlers/daijirin2.py b/bot/crawlers/daijirin2.py new file mode 100644 index 0000000..a9c711b --- /dev/null +++ b/bot/crawlers/daijirin2.py @@ -0,0 +1,5 @@ +from bot.crawlers.base.monokakido import MonokakidoCrawler + + +class Crawler(MonokakidoCrawler): + pass diff --git a/bot/crawlers/factory.py b/bot/crawlers/factory.py deleted file mode 100644 index d7450ea..0000000 --- a/bot/crawlers/factory.py +++ /dev/null @@ -1,20 +0,0 @@ -from bot.targets import Targets - -from bot.crawlers.crawlers import JitenonKokugoCrawler -from bot.crawlers.crawlers import JitenonYojiCrawler -from bot.crawlers.crawlers import JitenonKotowazaCrawler -from bot.crawlers.crawlers import Smk8Crawler -from bot.crawlers.crawlers import Daijirin2Crawler -from bot.crawlers.crawlers import Sankoku8Crawler - - -def new_crawler(target): - crawler_map = { - Targets.JITENON_KOKUGO: JitenonKokugoCrawler, - Targets.JITENON_YOJI: JitenonYojiCrawler, - Targets.JITENON_KOTOWAZA: JitenonKotowazaCrawler, - Targets.SMK8: Smk8Crawler, - Targets.DAIJIRIN2: Daijirin2Crawler, - Targets.SANKOKU8: Sankoku8Crawler, - } - return crawler_map[target](target) diff --git a/bot/crawlers/jitenon_kokugo.py b/bot/crawlers/jitenon_kokugo.py new file mode 100644 index 0000000..6d5cd66 --- /dev/null +++ b/bot/crawlers/jitenon_kokugo.py @@ -0,0 +1,38 @@ +import re +from bs4 import BeautifulSoup + +from bot.crawlers.base.crawler import BaseCrawler +from bot.crawlers.scrapers.jitenon import Jitenon as JitenonScraper + + +class Crawler(BaseCrawler): + def __init__(self, target): + super().__init__(target) + self._gojuon_url = "https://kokugo.jitenon.jp/cat/gojuonindex.php" + self._page_id_pattern = r"word/p([0-9]+)$" + + def collect_pages(self, page_dir): + jitenon = JitenonScraper() + gojuon_doc, _ = jitenon.scrape(self._gojuon_url) + gojuon_soup = BeautifulSoup(gojuon_doc, features="html.parser") + for gojuon_a in gojuon_soup.select(".kana_area a", href=True): + gojuon_href = gojuon_a['href'] + max_kana_page = 1 + current_kana_page = 1 + while current_kana_page <= max_kana_page: + kana_doc, _ = jitenon.scrape(f"{gojuon_href}&page={current_kana_page}") + current_kana_page += 1 + kana_soup = BeautifulSoup(kana_doc, features="html.parser") + page_total = kana_soup.find(class_="page_total").text + m = re.search(r"全([0-9]+)件", page_total) + if m: + max_kana_page = int(m.group(1)) + for kana_a in kana_soup.select(".word_box a", href=True): + page_link = kana_a['href'] + page_id = self._parse_page_id(page_link) + if page_id is None: + continue + _, page_path = jitenon.scrape(page_link) + self._page_map[page_id] = page_path + pages_len = len(self._page_map) + print(f"Finished scraping {pages_len} pages") diff --git a/bot/crawlers/jitenon_kotowaza.py b/bot/crawlers/jitenon_kotowaza.py new file mode 100644 index 0000000..693fa52 --- /dev/null +++ b/bot/crawlers/jitenon_kotowaza.py @@ -0,0 +1,8 @@ +from bot.crawlers.base.jitenon import JitenonCrawler + + +class Crawler(JitenonCrawler): + def __init__(self, target): + super().__init__(target) + self._gojuon_url = "https://kotowaza.jitenon.jp/cat/gojuon.php" + self._page_id_pattern = r"([0-9]+)\.php$" diff --git a/bot/crawlers/jitenon_yoji.py b/bot/crawlers/jitenon_yoji.py new file mode 100644 index 0000000..5b89875 --- /dev/null +++ b/bot/crawlers/jitenon_yoji.py @@ -0,0 +1,8 @@ +from bot.crawlers.base.jitenon import JitenonCrawler + + +class Crawler(JitenonCrawler): + def __init__(self, target): + super().__init__(target) + self._gojuon_url = "https://yoji.jitenon.jp/cat/gojuon.html" + self._page_id_pattern = r"([0-9]+)\.html$" diff --git a/bot/crawlers/sankoku8.py b/bot/crawlers/sankoku8.py new file mode 100644 index 0000000..a9c711b --- /dev/null +++ b/bot/crawlers/sankoku8.py @@ -0,0 +1,5 @@ +from bot.crawlers.base.monokakido import MonokakidoCrawler + + +class Crawler(MonokakidoCrawler): + pass diff --git a/bot/crawlers/scrapers/jitenon.py b/bot/crawlers/scrapers/jitenon.py new file mode 100644 index 0000000..e4163d9 --- /dev/null +++ b/bot/crawlers/scrapers/jitenon.py @@ -0,0 +1,10 @@ +import re +from bot.crawlers.scrapers.scraper import BaseScraper + + +class Jitenon(BaseScraper): + def _get_netloc_re(self): + domain = r"jitenon\.jp" + pattern = r"^(?:([A-Za-z0-9.\-]+)\.)?" + domain + r"$" + netloc_re = re.compile(pattern) + return netloc_re diff --git a/bot/crawlers/scraper.py b/bot/crawlers/scrapers/scraper.py similarity index 93% rename from bot/crawlers/scraper.py rename to bot/crawlers/scrapers/scraper.py index 577f602..113d090 100644 --- a/bot/crawlers/scraper.py +++ b/bot/crawlers/scrapers/scraper.py @@ -1,24 +1,24 @@ import time -import requests import re import os import hashlib from datetime import datetime -from pathlib import Path - -from platformdirs import user_cache_dir from urllib.parse import urlparse +from pathlib import Path +from abc import ABC, abstractmethod + +import requests from requests.adapters import HTTPAdapter from requests.packages.urllib3.util.retry import Retry +from platformdirs import user_cache_dir from bot.data import load_config -class Scraper(): +class BaseScraper(ABC): def __init__(self): self._config = load_config() - pattern = r"^(?:([A-Za-z0-9.\-]+)\.)?" + self.domain + r"$" - self.netloc_re = re.compile(pattern) + self.netloc_re = self._get_netloc_re() self.__set_session() def scrape(self, urlstring): @@ -34,6 +34,10 @@ class Scraper(): print("Discovering cached files...", end='\r', flush=True) return html, cache_path + @abstractmethod + def _get_netloc_re(self): + raise NotImplementedError + def __set_session(self): retry_strategy = Retry( total=3, @@ -99,9 +103,3 @@ class Scraper(): self.__set_session() response = self.session.get(urlstring, timeout=10) return response.text - - -class Jitenon(Scraper): - def __init__(self): - self.domain = r"jitenon\.jp" - super().__init__() diff --git a/bot/crawlers/smk8.py b/bot/crawlers/smk8.py new file mode 100644 index 0000000..a9c711b --- /dev/null +++ b/bot/crawlers/smk8.py @@ -0,0 +1,5 @@ +from bot.crawlers.base.monokakido import MonokakidoCrawler + + +class Crawler(MonokakidoCrawler): + pass diff --git a/bot/entries/base/entry.py b/bot/entries/base/entry.py index 3811a77..60d4f16 100644 --- a/bot/entries/base/entry.py +++ b/bot/entries/base/entry.py @@ -18,15 +18,15 @@ class Entry(ABC): @abstractmethod def get_global_identifier(self): - pass + raise NotImplementedError @abstractmethod def set_page(self, page): - pass + raise NotImplementedError @abstractmethod def get_page_soup(self): - pass + raise NotImplementedError def get_headwords(self): if self._headwords is not None: @@ -38,15 +38,15 @@ class Entry(ABC): @abstractmethod def _get_headwords(self): - pass + raise NotImplementedError @abstractmethod def _add_variant_expressions(self, headwords): - pass + raise NotImplementedError @abstractmethod def get_part_of_speech_tags(self): - pass + raise NotImplementedError def get_parent(self): if self.entry_id in self.SUBENTRY_ID_TO_ENTRY_ID: diff --git a/bot/entries/base/jitenon_entry.py b/bot/entries/base/jitenon_entry.py index 7af845b..43d6005 100644 --- a/bot/entries/base/jitenon_entry.py +++ b/bot/entries/base/jitenon_entry.py @@ -58,7 +58,7 @@ class JitenonEntry(Entry): @abstractmethod def _get_column_map(self): - pass + raise NotImplementedError def __set_modified_date(self, page): m = re.search(r"\"dateModified\": \"(\d{4}-\d{2}-\d{2})", page) diff --git a/bot/entries/base/sanseido_entry.py b/bot/entries/base/sanseido_entry.py index 4e1098d..bb52431 100644 --- a/bot/entries/base/sanseido_entry.py +++ b/bot/entries/base/sanseido_entry.py @@ -39,7 +39,7 @@ class SanseidoEntry(Entry): @abstractmethod def _get_subentry_parameters(self): - pass + raise NotImplementedError def _add_variant_expressions(self, headwords): for expressions in headwords.values(): diff --git a/bot/entries/factory.py b/bot/entries/factory.py deleted file mode 100644 index 594762f..0000000 --- a/bot/entries/factory.py +++ /dev/null @@ -1,7 +0,0 @@ -import importlib - - -def new_entry(target, page_id): - module_path = f"bot.entries.{target.name.lower()}.entry" - module = importlib.import_module(module_path) - return module.Entry(target, page_id) diff --git a/bot/factory.py b/bot/factory.py new file mode 100644 index 0000000..7b025d4 --- /dev/null +++ b/bot/factory.py @@ -0,0 +1,37 @@ +import importlib + + +def new_crawler(target): + module_path = f"bot.crawlers.{target.name.lower()}" + module = importlib.import_module(module_path) + return module.Crawler(target) + + +def new_entry(target, page_id): + module_path = f"bot.entries.{target.name.lower()}.entry" + module = importlib.import_module(module_path) + return module.Entry(target, page_id) + + +def new_yomichan_exporter(target): + module_path = f"bot.yomichan.exporters.{target.name.lower()}" + module = importlib.import_module(module_path) + return module.Exporter(target) + + +def new_yomichan_terminator(target): + module_path = f"bot.yomichan.terms.{target.name.lower()}" + module = importlib.import_module(module_path) + return module.Terminator(target) + + +def new_mdict_exporter(target): + module_path = f"bot.mdict.exporters.{target.name.lower()}" + module = importlib.import_module(module_path) + return module.Exporter(target) + + +def new_mdict_terminator(target): + module_path = f"bot.mdict.terms.{target.name.lower()}" + module = importlib.import_module(module_path) + return module.Terminator(target) diff --git a/bot/mdict/exporters/export.py b/bot/mdict/exporters/base/exporter.py similarity index 79% rename from bot/mdict/exporters/export.py rename to bot/mdict/exporters/base/exporter.py index b8e8347..26dc662 100644 --- a/bot/mdict/exporters/export.py +++ b/bot/mdict/exporters/base/exporter.py @@ -1,20 +1,18 @@ -# pylint: disable=too-few-public-methods - -import subprocess import os import shutil +import subprocess from abc import ABC, abstractmethod from pathlib import Path -from datetime import datetime + from platformdirs import user_documents_dir, user_cache_dir -from bot.mdict.terms.factory import new_terminator +from bot.factory import new_mdict_terminator -class Exporter(ABC): +class BaseExporter(ABC): def __init__(self, target): self._target = target - self._terminator = new_terminator(target) + self._terminator = new_mdict_terminator(target) self._build_dir = None self._build_media_dir = None self._description_file = None @@ -168,58 +166,8 @@ class Exporter(ABC): @abstractmethod def _get_revision(self, entries): - pass + raise NotImplementedError @abstractmethod def _get_attribution(self, entries): - pass - - -class _JitenonExporter(Exporter): - def _get_revision(self, entries): - modified_date = None - for entry in entries: - if modified_date is None or entry.modified_date > modified_date: - modified_date = entry.modified_date - revision = modified_date.strftime("%Y年%m月%d日閲覧") - return revision - - def _get_attribution(self, entries): - modified_date = None - for entry in entries: - if modified_date is None or entry.modified_date > modified_date: - attribution = entry.attribution - return attribution - - -class JitenonKokugoExporter(_JitenonExporter): - pass - - -class JitenonYojiExporter(_JitenonExporter): - pass - - -class JitenonKotowazaExporter(_JitenonExporter): - pass - - -class _MonokakidoExporter(Exporter): - def _get_revision(self, entries): - timestamp = datetime.now().strftime("%Y年%m月%d日作成") - return timestamp - - -class Smk8Exporter(_MonokakidoExporter): - def _get_attribution(self, entries): - return "© Sanseido Co., LTD. 2020" - - -class Daijirin2Exporter(_MonokakidoExporter): - def _get_attribution(self, entries): - return "© Sanseido Co., LTD. 2019" - - -class Sankoku8Exporter(_MonokakidoExporter): - def _get_attribution(self, entries): - return "© Sanseido Co., LTD. 2021" + raise NotImplementedError diff --git a/bot/mdict/exporters/base/jitenon.py b/bot/mdict/exporters/base/jitenon.py new file mode 100644 index 0000000..2e6b1df --- /dev/null +++ b/bot/mdict/exporters/base/jitenon.py @@ -0,0 +1,18 @@ +from bot.mdict.exporters.base.exporter import BaseExporter + + +class JitenonExporter(BaseExporter): + def _get_revision(self, entries): + modified_date = None + for entry in entries: + if modified_date is None or entry.modified_date > modified_date: + modified_date = entry.modified_date + revision = modified_date.strftime("%Y年%m月%d日閲覧") + return revision + + def _get_attribution(self, entries): + modified_date = None + for entry in entries: + if modified_date is None or entry.modified_date > modified_date: + attribution = entry.attribution + return attribution diff --git a/bot/mdict/exporters/base/monokakido.py b/bot/mdict/exporters/base/monokakido.py new file mode 100644 index 0000000..b9b9629 --- /dev/null +++ b/bot/mdict/exporters/base/monokakido.py @@ -0,0 +1,8 @@ +from datetime import datetime +from bot.mdict.exporters.base.exporter import BaseExporter + + +class MonokakidoExporter(BaseExporter): + def _get_revision(self, entries): + timestamp = datetime.now().strftime("%Y年%m月%d日作成") + return timestamp diff --git a/bot/mdict/exporters/daijirin2.py b/bot/mdict/exporters/daijirin2.py new file mode 100644 index 0000000..4692470 --- /dev/null +++ b/bot/mdict/exporters/daijirin2.py @@ -0,0 +1,6 @@ +from bot.mdict.exporters.base.monokakido import MonokakidoExporter + + +class Exporter(MonokakidoExporter): + def _get_attribution(self, entries): + return "© Sanseido Co., LTD. 2019" diff --git a/bot/mdict/exporters/factory.py b/bot/mdict/exporters/factory.py deleted file mode 100644 index 5417493..0000000 --- a/bot/mdict/exporters/factory.py +++ /dev/null @@ -1,20 +0,0 @@ -from bot.targets import Targets - -from bot.mdict.exporters.export import JitenonKokugoExporter -from bot.mdict.exporters.export import JitenonYojiExporter -from bot.mdict.exporters.export import JitenonKotowazaExporter -from bot.mdict.exporters.export import Smk8Exporter -from bot.mdict.exporters.export import Daijirin2Exporter -from bot.mdict.exporters.export import Sankoku8Exporter - - -def new_mdict_exporter(target): - exporter_map = { - Targets.JITENON_KOKUGO: JitenonKokugoExporter, - Targets.JITENON_YOJI: JitenonYojiExporter, - Targets.JITENON_KOTOWAZA: JitenonKotowazaExporter, - Targets.SMK8: Smk8Exporter, - Targets.DAIJIRIN2: Daijirin2Exporter, - Targets.SANKOKU8: Sankoku8Exporter, - } - return exporter_map[target](target) diff --git a/bot/mdict/exporters/jitenon_kokugo.py b/bot/mdict/exporters/jitenon_kokugo.py new file mode 100644 index 0000000..5689fa8 --- /dev/null +++ b/bot/mdict/exporters/jitenon_kokugo.py @@ -0,0 +1,5 @@ +from bot.mdict.exporters.base.jitenon import JitenonExporter + + +class Exporter(JitenonExporter): + pass diff --git a/bot/mdict/exporters/jitenon_kotowaza.py b/bot/mdict/exporters/jitenon_kotowaza.py new file mode 100644 index 0000000..5689fa8 --- /dev/null +++ b/bot/mdict/exporters/jitenon_kotowaza.py @@ -0,0 +1,5 @@ +from bot.mdict.exporters.base.jitenon import JitenonExporter + + +class Exporter(JitenonExporter): + pass diff --git a/bot/mdict/exporters/jitenon_yoji.py b/bot/mdict/exporters/jitenon_yoji.py new file mode 100644 index 0000000..5689fa8 --- /dev/null +++ b/bot/mdict/exporters/jitenon_yoji.py @@ -0,0 +1,5 @@ +from bot.mdict.exporters.base.jitenon import JitenonExporter + + +class Exporter(JitenonExporter): + pass diff --git a/bot/mdict/exporters/sankoku8.py b/bot/mdict/exporters/sankoku8.py new file mode 100644 index 0000000..6063864 --- /dev/null +++ b/bot/mdict/exporters/sankoku8.py @@ -0,0 +1,6 @@ +from bot.mdict.exporters.base.monokakido import MonokakidoExporter + + +class Exporter(MonokakidoExporter): + def _get_attribution(self, entries): + return "© Sanseido Co., LTD. 2021" diff --git a/bot/mdict/exporters/smk8.py b/bot/mdict/exporters/smk8.py new file mode 100644 index 0000000..a030b4b --- /dev/null +++ b/bot/mdict/exporters/smk8.py @@ -0,0 +1,6 @@ +from bot.mdict.exporters.base.monokakido import MonokakidoExporter + + +class Exporter(MonokakidoExporter): + def _get_attribution(self, entries): + return "© Sanseido Co., LTD. 2020" diff --git a/bot/mdict/terms/base/jitenon.py b/bot/mdict/terms/base/jitenon.py new file mode 100644 index 0000000..4f255bf --- /dev/null +++ b/bot/mdict/terms/base/jitenon.py @@ -0,0 +1,20 @@ +from bot.mdict.terms.base.terminator import BaseTerminator + + +class JitenonTerminator(BaseTerminator): + def __init__(self, target): + super().__init__(target) + self._glossary_maker = None + + def _glossary(self, entry): + if entry.entry_id in self._glossary_cache: + return self._glossary_cache[entry.entry_id] + glossary = self._glossary_maker.make_glossary(entry, self._media_dir) + self._glossary_cache[entry.entry_id] = glossary + return glossary + + def _link_glossary_parameters(self, entry): + return [] + + def _subentry_lists(self, entry): + return [] diff --git a/bot/mdict/terms/terminator.py b/bot/mdict/terms/base/terminator.py similarity index 95% rename from bot/mdict/terms/terminator.py rename to bot/mdict/terms/base/terminator.py index ee62411..945a65b 100644 --- a/bot/mdict/terms/terminator.py +++ b/bot/mdict/terms/base/terminator.py @@ -2,7 +2,7 @@ import re from abc import abstractmethod, ABC -class Terminator(ABC): +class BaseTerminator(ABC): def __init__(self, target): self._target = target self._glossary_cache = {} @@ -72,12 +72,12 @@ class Terminator(ABC): @abstractmethod def _glossary(self, entry): - pass + raise NotImplementedError @abstractmethod def _link_glossary_parameters(self, entry): - pass + raise NotImplementedError @abstractmethod def _subentry_lists(self, entry): - pass + raise NotImplementedError diff --git a/bot/mdict/terms/daijirin2.py b/bot/mdict/terms/daijirin2.py index 3b5ce68..640b520 100644 --- a/bot/mdict/terms/daijirin2.py +++ b/bot/mdict/terms/daijirin2.py @@ -1,8 +1,8 @@ -from bot.mdict.terms.terminator import Terminator +from bot.mdict.terms.base.terminator import BaseTerminator from bot.mdict.glossary.daijirin2 import make_glossary -class Daijirin2Terminator(Terminator): +class Terminator(BaseTerminator): def _glossary(self, entry): if entry.entry_id in self._glossary_cache: return self._glossary_cache[entry.entry_id] diff --git a/bot/mdict/terms/factory.py b/bot/mdict/terms/factory.py deleted file mode 100644 index 8cee8e7..0000000 --- a/bot/mdict/terms/factory.py +++ /dev/null @@ -1,20 +0,0 @@ -from bot.targets import Targets - -from bot.mdict.terms.jitenon import JitenonKokugoTerminator -from bot.mdict.terms.jitenon import JitenonYojiTerminator -from bot.mdict.terms.jitenon import JitenonKotowazaTerminator -from bot.mdict.terms.smk8 import Smk8Terminator -from bot.mdict.terms.daijirin2 import Daijirin2Terminator -from bot.mdict.terms.sankoku8 import Sankoku8Terminator - - -def new_terminator(target): - terminator_map = { - Targets.JITENON_KOKUGO: JitenonKokugoTerminator, - Targets.JITENON_YOJI: JitenonYojiTerminator, - Targets.JITENON_KOTOWAZA: JitenonKotowazaTerminator, - Targets.SMK8: Smk8Terminator, - Targets.DAIJIRIN2: Daijirin2Terminator, - Targets.SANKOKU8: Sankoku8Terminator, - } - return terminator_map[target](target) diff --git a/bot/mdict/terms/jitenon.py b/bot/mdict/terms/jitenon.py deleted file mode 100644 index 3f9cfc1..0000000 --- a/bot/mdict/terms/jitenon.py +++ /dev/null @@ -1,42 +0,0 @@ -from bot.mdict.terms.terminator import Terminator - -from bot.mdict.glossary.jitenon import JitenonKokugoGlossary -from bot.mdict.glossary.jitenon import JitenonYojiGlossary -from bot.mdict.glossary.jitenon import JitenonKotowazaGlossary - - -class JitenonTerminator(Terminator): - def __init__(self, target): - super().__init__(target) - self._glossary_maker = None - - def _glossary(self, entry): - if entry.entry_id in self._glossary_cache: - return self._glossary_cache[entry.entry_id] - glossary = self._glossary_maker.make_glossary(entry, self._media_dir) - self._glossary_cache[entry.entry_id] = glossary - return glossary - - def _link_glossary_parameters(self, entry): - return [] - - def _subentry_lists(self, entry): - return [] - - -class JitenonKokugoTerminator(JitenonTerminator): - def __init__(self, target): - super().__init__(target) - self._glossary_maker = JitenonKokugoGlossary() - - -class JitenonYojiTerminator(JitenonTerminator): - def __init__(self, target): - super().__init__(target) - self._glossary_maker = JitenonYojiGlossary() - - -class JitenonKotowazaTerminator(JitenonTerminator): - def __init__(self, target): - super().__init__(target) - self._glossary_maker = JitenonKotowazaGlossary() diff --git a/bot/mdict/terms/jitenon_kokugo.py b/bot/mdict/terms/jitenon_kokugo.py new file mode 100644 index 0000000..2a44b7b --- /dev/null +++ b/bot/mdict/terms/jitenon_kokugo.py @@ -0,0 +1,8 @@ +from bot.mdict.terms.base.jitenon import JitenonTerminator +from bot.mdict.glossary.jitenon import JitenonKokugoGlossary + + +class Terminator(JitenonTerminator): + def __init__(self, target): + super().__init__(target) + self._glossary_maker = JitenonKokugoGlossary() diff --git a/bot/mdict/terms/jitenon_kotowaza.py b/bot/mdict/terms/jitenon_kotowaza.py new file mode 100644 index 0000000..3492a49 --- /dev/null +++ b/bot/mdict/terms/jitenon_kotowaza.py @@ -0,0 +1,8 @@ +from bot.mdict.terms.base.jitenon import JitenonTerminator +from bot.mdict.glossary.jitenon import JitenonKotowazaGlossary + + +class Terminator(JitenonTerminator): + def __init__(self, target): + super().__init__(target) + self._glossary_maker = JitenonKotowazaGlossary() diff --git a/bot/mdict/terms/jitenon_yoji.py b/bot/mdict/terms/jitenon_yoji.py new file mode 100644 index 0000000..a4175a1 --- /dev/null +++ b/bot/mdict/terms/jitenon_yoji.py @@ -0,0 +1,8 @@ +from bot.mdict.terms.base.jitenon import JitenonTerminator +from bot.mdict.glossary.jitenon import JitenonYojiGlossary + + +class Terminator(JitenonTerminator): + def __init__(self, target): + super().__init__(target) + self._glossary_maker = JitenonYojiGlossary() diff --git a/bot/mdict/terms/sankoku8.py b/bot/mdict/terms/sankoku8.py index 5c1bfb7..71a3b8f 100644 --- a/bot/mdict/terms/sankoku8.py +++ b/bot/mdict/terms/sankoku8.py @@ -1,8 +1,8 @@ -from bot.mdict.terms.terminator import Terminator +from bot.mdict.terms.base.terminator import BaseTerminator from bot.mdict.glossary.sankoku8 import make_glossary -class Sankoku8Terminator(Terminator): +class Terminator(BaseTerminator): def _glossary(self, entry): if entry.entry_id in self._glossary_cache: return self._glossary_cache[entry.entry_id] diff --git a/bot/mdict/terms/smk8.py b/bot/mdict/terms/smk8.py index 22275d5..ef2b7a2 100644 --- a/bot/mdict/terms/smk8.py +++ b/bot/mdict/terms/smk8.py @@ -1,8 +1,8 @@ -from bot.mdict.terms.terminator import Terminator +from bot.mdict.terms.base.terminator import BaseTerminator from bot.mdict.glossary.smk8 import make_glossary -class Smk8Terminator(Terminator): +class Terminator(BaseTerminator): def _glossary(self, entry): if entry.entry_id in self._glossary_cache: return self._glossary_cache[entry.entry_id] diff --git a/bot/yomichan/exporters/export.py b/bot/yomichan/exporters/base/exporter.py similarity index 76% rename from bot/yomichan/exporters/export.py rename to bot/yomichan/exporters/base/exporter.py index d348fed..9389202 100644 --- a/bot/yomichan/exporters/export.py +++ b/bot/yomichan/exporters/base/exporter.py @@ -1,24 +1,22 @@ -# pylint: disable=too-few-public-methods - import json import os import shutil import copy from pathlib import Path -from datetime import datetime from abc import ABC, abstractmethod -from platformdirs import user_documents_dir, user_cache_dir import fastjsonschema +from platformdirs import user_documents_dir, user_cache_dir + from bot.data import load_yomichan_metadata -from bot.yomichan.terms.factory import new_terminator from bot.data import load_yomichan_term_schema +from bot.factory import new_yomichan_terminator -class Exporter(ABC): +class BaseExporter(ABC): def __init__(self, target): self._target = target - self._terminator = new_terminator(target) + self._terminator = new_yomichan_terminator(target) self._build_dir = None self._terms_per_file = 2000 @@ -36,11 +34,11 @@ class Exporter(ABC): @abstractmethod def _get_revision(self, entries): - pass + raise NotImplementedError @abstractmethod def _get_attribution(self, entries): - pass + raise NotImplementedError def _get_build_dir(self): if self._build_dir is not None: @@ -118,10 +116,10 @@ class Exporter(ABC): build_dir = self._get_build_dir() max_i = int(len(terms) / self._terms_per_file) + 1 for i in range(max_i): + update = f"Writing terms to term bank {i+1}/{max_i}" + print(update, end='\r', flush=True) start = self._terms_per_file * i end = self._terms_per_file * (i + 1) - update = f"Writing terms to term banks {start} - {end}" - print(update, end='\r', flush=True) term_file = os.path.join(build_dir, f"term_bank_{i+1}.json") with open(term_file, "w", encoding='utf8') as f: json.dump(terms[start:end], f, indent=4, ensure_ascii=False) @@ -142,8 +140,8 @@ class Exporter(ABC): json.dump(tags, f, indent=4, ensure_ascii=False) def __write_archive(self, filename): - print("Archiving data to ZIP file...") archive_format = "zip" + print(f"Archiving data to {archive_format.upper()} file...") out_dir = os.path.join(user_documents_dir(), "jitenbot", "yomichan") if not Path(out_dir).is_dir(): os.makedirs(out_dir) @@ -154,58 +152,8 @@ class Exporter(ABC): base_filename = os.path.join(out_dir, filename) build_dir = self._get_build_dir() shutil.make_archive(base_filename, archive_format, build_dir) - print(f"Dictionary file saved to {out_filepath}") + print(f"Dictionary file saved to `{out_filepath}`") def __rm_build_dir(self): build_dir = self._get_build_dir() shutil.rmtree(build_dir) - - -class _JitenonExporter(Exporter): - def _get_revision(self, entries): - modified_date = None - for entry in entries: - if modified_date is None or entry.modified_date > modified_date: - modified_date = entry.modified_date - revision = f"{self._target.value};{modified_date}" - return revision - - def _get_attribution(self, entries): - modified_date = None - for entry in entries: - if modified_date is None or entry.modified_date > modified_date: - attribution = entry.attribution - return attribution - - -class JitenonKokugoExporter(_JitenonExporter): - pass - - -class JitenonYojiExporter(_JitenonExporter): - pass - - -class JitenonKotowazaExporter(_JitenonExporter): - pass - - -class _MonokakidoExporter(Exporter): - def _get_revision(self, entries): - timestamp = datetime.now().strftime("%Y-%m-%d") - return f"{self._target.value};{timestamp}" - - -class Smk8Exporter(_MonokakidoExporter): - def _get_attribution(self, entries): - return "© Sanseido Co., LTD. 2020" - - -class Daijirin2Exporter(_MonokakidoExporter): - def _get_attribution(self, entries): - return "© Sanseido Co., LTD. 2019" - - -class Sankoku8Exporter(_MonokakidoExporter): - def _get_attribution(self, entries): - return "© Sanseido Co., LTD. 2021" diff --git a/bot/yomichan/exporters/base/jitenon.py b/bot/yomichan/exporters/base/jitenon.py new file mode 100644 index 0000000..80f0175 --- /dev/null +++ b/bot/yomichan/exporters/base/jitenon.py @@ -0,0 +1,18 @@ +from bot.yomichan.exporters.base.exporter import BaseExporter + + +class JitenonExporter(BaseExporter): + def _get_revision(self, entries): + modified_date = None + for entry in entries: + if modified_date is None or entry.modified_date > modified_date: + modified_date = entry.modified_date + revision = f"{self._target.value};{modified_date}" + return revision + + def _get_attribution(self, entries): + modified_date = None + for entry in entries: + if modified_date is None or entry.modified_date > modified_date: + attribution = entry.attribution + return attribution diff --git a/bot/yomichan/exporters/base/monokakido.py b/bot/yomichan/exporters/base/monokakido.py new file mode 100644 index 0000000..5c5f3fa --- /dev/null +++ b/bot/yomichan/exporters/base/monokakido.py @@ -0,0 +1,8 @@ +from datetime import datetime +from bot.yomichan.exporters.base.exporter import BaseExporter + + +class MonokakidoExporter(BaseExporter): + def _get_revision(self, entries): + timestamp = datetime.now().strftime("%Y-%m-%d") + return f"{self._target.value};{timestamp}" diff --git a/bot/yomichan/exporters/daijirin2.py b/bot/yomichan/exporters/daijirin2.py new file mode 100644 index 0000000..7115342 --- /dev/null +++ b/bot/yomichan/exporters/daijirin2.py @@ -0,0 +1,6 @@ +from bot.yomichan.exporters.base.monokakido import MonokakidoExporter + + +class Exporter(MonokakidoExporter): + def _get_attribution(self, entries): + return "© Sanseido Co., LTD. 2019" diff --git a/bot/yomichan/exporters/factory.py b/bot/yomichan/exporters/factory.py deleted file mode 100644 index afed7fd..0000000 --- a/bot/yomichan/exporters/factory.py +++ /dev/null @@ -1,20 +0,0 @@ -from bot.targets import Targets - -from bot.yomichan.exporters.export import JitenonKokugoExporter -from bot.yomichan.exporters.export import JitenonYojiExporter -from bot.yomichan.exporters.export import JitenonKotowazaExporter -from bot.yomichan.exporters.export import Smk8Exporter -from bot.yomichan.exporters.export import Daijirin2Exporter -from bot.yomichan.exporters.export import Sankoku8Exporter - - -def new_yomi_exporter(target): - exporter_map = { - Targets.JITENON_KOKUGO: JitenonKokugoExporter, - Targets.JITENON_YOJI: JitenonYojiExporter, - Targets.JITENON_KOTOWAZA: JitenonKotowazaExporter, - Targets.SMK8: Smk8Exporter, - Targets.DAIJIRIN2: Daijirin2Exporter, - Targets.SANKOKU8: Sankoku8Exporter, - } - return exporter_map[target](target) diff --git a/bot/yomichan/exporters/jitenon_kokugo.py b/bot/yomichan/exporters/jitenon_kokugo.py new file mode 100644 index 0000000..0a3ef7a --- /dev/null +++ b/bot/yomichan/exporters/jitenon_kokugo.py @@ -0,0 +1,5 @@ +from bot.yomichan.exporters.base.jitenon import JitenonExporter + + +class Exporter(JitenonExporter): + pass diff --git a/bot/yomichan/exporters/jitenon_kotowaza.py b/bot/yomichan/exporters/jitenon_kotowaza.py new file mode 100644 index 0000000..0a3ef7a --- /dev/null +++ b/bot/yomichan/exporters/jitenon_kotowaza.py @@ -0,0 +1,5 @@ +from bot.yomichan.exporters.base.jitenon import JitenonExporter + + +class Exporter(JitenonExporter): + pass diff --git a/bot/yomichan/exporters/jitenon_yoji.py b/bot/yomichan/exporters/jitenon_yoji.py new file mode 100644 index 0000000..0a3ef7a --- /dev/null +++ b/bot/yomichan/exporters/jitenon_yoji.py @@ -0,0 +1,5 @@ +from bot.yomichan.exporters.base.jitenon import JitenonExporter + + +class Exporter(JitenonExporter): + pass diff --git a/bot/yomichan/exporters/sankoku8.py b/bot/yomichan/exporters/sankoku8.py new file mode 100644 index 0000000..b33c389 --- /dev/null +++ b/bot/yomichan/exporters/sankoku8.py @@ -0,0 +1,6 @@ +from bot.yomichan.exporters.base.monokakido import MonokakidoExporter + + +class Exporter(MonokakidoExporter): + def _get_attribution(self, entries): + return "© Sanseido Co., LTD. 2021" diff --git a/bot/yomichan/exporters/smk8.py b/bot/yomichan/exporters/smk8.py new file mode 100644 index 0000000..7f71aa3 --- /dev/null +++ b/bot/yomichan/exporters/smk8.py @@ -0,0 +1,6 @@ +from bot.yomichan.exporters.base.monokakido import MonokakidoExporter + + +class Exporter(MonokakidoExporter): + def _get_attribution(self, entries): + return "© Sanseido Co., LTD. 2020" diff --git a/bot/yomichan/glossary/daijirin2.py b/bot/yomichan/glossary/daijirin2.py index 0adaa96..178de00 100644 --- a/bot/yomichan/glossary/daijirin2.py +++ b/bot/yomichan/glossary/daijirin2.py @@ -1,9 +1,10 @@ import re import os -from bs4 import BeautifulSoup from functools import cache from pathlib import Path +from bs4 import BeautifulSoup + import bot.yomichan.glossary.icons as Icons from bot.soup import delete_soup_nodes from bot.data import load_yomichan_name_conversion diff --git a/bot/yomichan/terms/base/jitenon.py b/bot/yomichan/terms/base/jitenon.py new file mode 100644 index 0000000..d0d5388 --- /dev/null +++ b/bot/yomichan/terms/base/jitenon.py @@ -0,0 +1,26 @@ +from bot.yomichan.terms.base.terminator import BaseTerminator + + +class JitenonTerminator(BaseTerminator): + def __init__(self, target): + super().__init__(target) + self._glossary_maker = None + + def _definition_tags(self, entry): + return None + + def _glossary(self, entry): + if entry.entry_id in self._glossary_cache: + return self._glossary_cache[entry.entry_id] + glossary = self._glossary_maker.make_glossary(entry, self._image_dir) + self._glossary_cache[entry.entry_id] = glossary + return glossary + + def _sequence(self, entry): + return entry.entry_id + + def _link_glossary_parameters(self, entry): + return [] + + def _subentry_lists(self, entry): + return [] diff --git a/bot/yomichan/terms/terminator.py b/bot/yomichan/terms/base/terminator.py similarity index 91% rename from bot/yomichan/terms/terminator.py rename to bot/yomichan/terms/base/terminator.py index dd0c02d..f57c4cc 100644 --- a/bot/yomichan/terms/terminator.py +++ b/bot/yomichan/terms/base/terminator.py @@ -2,7 +2,7 @@ from abc import abstractmethod, ABC from bot.data import load_yomichan_inflection_categories -class Terminator(ABC): +class BaseTerminator(ABC): def __init__(self, target): self._target = target self._glossary_cache = {} @@ -66,28 +66,28 @@ class Terminator(ABC): @abstractmethod def _definition_tags(self, entry): - pass + raise NotImplementedError @abstractmethod def _inflection_rules(self, entry, expression): - pass + raise NotImplementedError @abstractmethod def _glossary(self, entry): - pass + raise NotImplementedError @abstractmethod def _sequence(self, entry): - pass + raise NotImplementedError @abstractmethod def _term_tags(self, entry): - pass + raise NotImplementedError @abstractmethod def _link_glossary_parameters(self, entry): - pass + raise NotImplementedError @abstractmethod def _subentry_lists(self, entry): - pass + raise NotImplementedError diff --git a/bot/yomichan/terms/daijirin2.py b/bot/yomichan/terms/daijirin2.py index 281fac4..7cf06fb 100644 --- a/bot/yomichan/terms/daijirin2.py +++ b/bot/yomichan/terms/daijirin2.py @@ -1,11 +1,10 @@ from bot.entries.daijirin2.phrase_entry import PhraseEntry - -from bot.yomichan.terms.terminator import Terminator +from bot.yomichan.terms.base.terminator import BaseTerminator from bot.yomichan.glossary.daijirin2 import make_glossary from bot.yomichan.grammar import sudachi_rules, tags_to_rules -class Daijirin2Terminator(Terminator): +class Terminator(BaseTerminator): def _definition_tags(self, entry): return "" diff --git a/bot/yomichan/terms/factory.py b/bot/yomichan/terms/factory.py deleted file mode 100644 index 8c596cb..0000000 --- a/bot/yomichan/terms/factory.py +++ /dev/null @@ -1,20 +0,0 @@ -from bot.targets import Targets - -from bot.yomichan.terms.jitenon import JitenonKokugoTerminator -from bot.yomichan.terms.jitenon import JitenonYojiTerminator -from bot.yomichan.terms.jitenon import JitenonKotowazaTerminator -from bot.yomichan.terms.smk8 import Smk8Terminator -from bot.yomichan.terms.daijirin2 import Daijirin2Terminator -from bot.yomichan.terms.sankoku8 import Sankoku8Terminator - - -def new_terminator(target): - terminator_map = { - Targets.JITENON_KOKUGO: JitenonKokugoTerminator, - Targets.JITENON_YOJI: JitenonYojiTerminator, - Targets.JITENON_KOTOWAZA: JitenonKotowazaTerminator, - Targets.SMK8: Smk8Terminator, - Targets.DAIJIRIN2: Daijirin2Terminator, - Targets.SANKOKU8: Sankoku8Terminator, - } - return terminator_map[target](target) diff --git a/bot/yomichan/terms/jitenon.py b/bot/yomichan/terms/jitenon.py deleted file mode 100644 index 66bbed7..0000000 --- a/bot/yomichan/terms/jitenon.py +++ /dev/null @@ -1,68 +0,0 @@ -from bot.yomichan.grammar import sudachi_rules -from bot.yomichan.terms.terminator import Terminator - -from bot.yomichan.glossary.jitenon import JitenonKokugoGlossary -from bot.yomichan.glossary.jitenon import JitenonYojiGlossary -from bot.yomichan.glossary.jitenon import JitenonKotowazaGlossary - - -class JitenonTerminator(Terminator): - def __init__(self, target): - super().__init__(target) - self._glossary_maker = None - - def _definition_tags(self, entry): - return None - - def _glossary(self, entry): - if entry.entry_id in self._glossary_cache: - return self._glossary_cache[entry.entry_id] - glossary = self._glossary_maker.make_glossary(entry, self._image_dir) - self._glossary_cache[entry.entry_id] = glossary - return glossary - - def _sequence(self, entry): - return entry.entry_id - - def _link_glossary_parameters(self, entry): - return [] - - def _subentry_lists(self, entry): - return [] - - -class JitenonKokugoTerminator(JitenonTerminator): - def __init__(self, target): - super().__init__(target) - self._glossary_maker = JitenonKokugoGlossary() - - def _inflection_rules(self, entry, expression): - return sudachi_rules(expression) - - def _term_tags(self, entry): - return "" - - -class JitenonYojiTerminator(JitenonTerminator): - def __init__(self, target): - super().__init__(target) - self._glossary_maker = JitenonYojiGlossary() - - def _inflection_rules(self, entry, expression): - return "" - - def _term_tags(self, entry): - tags = entry.kanken_level.split("/") - return " ".join(tags) - - -class JitenonKotowazaTerminator(JitenonTerminator): - def __init__(self, target): - super().__init__(target) - self._glossary_maker = JitenonKotowazaGlossary() - - def _inflection_rules(self, entry, expression): - return sudachi_rules(expression) - - def _term_tags(self, entry): - return "" diff --git a/bot/yomichan/terms/jitenon_kokugo.py b/bot/yomichan/terms/jitenon_kokugo.py new file mode 100644 index 0000000..3e33b77 --- /dev/null +++ b/bot/yomichan/terms/jitenon_kokugo.py @@ -0,0 +1,15 @@ +from bot.yomichan.grammar import sudachi_rules +from bot.yomichan.glossary.jitenon import JitenonKokugoGlossary +from bot.yomichan.terms.base.jitenon import JitenonTerminator + + +class Terminator(JitenonTerminator): + def __init__(self, target): + super().__init__(target) + self._glossary_maker = JitenonKokugoGlossary() + + def _inflection_rules(self, entry, expression): + return sudachi_rules(expression) + + def _term_tags(self, entry): + return "" diff --git a/bot/yomichan/terms/jitenon_kotowaza.py b/bot/yomichan/terms/jitenon_kotowaza.py new file mode 100644 index 0000000..a0651b9 --- /dev/null +++ b/bot/yomichan/terms/jitenon_kotowaza.py @@ -0,0 +1,15 @@ +from bot.yomichan.grammar import sudachi_rules +from bot.yomichan.glossary.jitenon import JitenonKotowazaGlossary +from bot.yomichan.terms.base.jitenon import JitenonTerminator + + +class Terminator(JitenonTerminator): + def __init__(self, target): + super().__init__(target) + self._glossary_maker = JitenonKotowazaGlossary() + + def _inflection_rules(self, entry, expression): + return sudachi_rules(expression) + + def _term_tags(self, entry): + return "" diff --git a/bot/yomichan/terms/jitenon_yoji.py b/bot/yomichan/terms/jitenon_yoji.py new file mode 100644 index 0000000..5087539 --- /dev/null +++ b/bot/yomichan/terms/jitenon_yoji.py @@ -0,0 +1,15 @@ +from bot.yomichan.glossary.jitenon import JitenonYojiGlossary +from bot.yomichan.terms.base.jitenon import JitenonTerminator + + +class Terminator(JitenonTerminator): + def __init__(self, target): + super().__init__(target) + self._glossary_maker = JitenonYojiGlossary() + + def _inflection_rules(self, entry, expression): + return "" + + def _term_tags(self, entry): + tags = entry.kanken_level.split("/") + return " ".join(tags) diff --git a/bot/yomichan/terms/sankoku8.py b/bot/yomichan/terms/sankoku8.py index cff264f..d6e6afd 100644 --- a/bot/yomichan/terms/sankoku8.py +++ b/bot/yomichan/terms/sankoku8.py @@ -1,11 +1,10 @@ from bot.entries.sankoku8.phrase_entry import PhraseEntry - -from bot.yomichan.terms.terminator import Terminator +from bot.yomichan.terms.base.terminator import BaseTerminator from bot.yomichan.glossary.sankoku8 import make_glossary from bot.yomichan.grammar import sudachi_rules, tags_to_rules -class Sankoku8Terminator(Terminator): +class Terminator(BaseTerminator): def _definition_tags(self, entry): return "" diff --git a/bot/yomichan/terms/smk8.py b/bot/yomichan/terms/smk8.py index 766f4a0..9e85c17 100644 --- a/bot/yomichan/terms/smk8.py +++ b/bot/yomichan/terms/smk8.py @@ -1,12 +1,11 @@ from bot.entries.smk8.kanji_entry import KanjiEntry from bot.entries.smk8.phrase_entry import PhraseEntry - -from bot.yomichan.terms.terminator import Terminator +from bot.yomichan.terms.base.terminator import BaseTerminator from bot.yomichan.glossary.smk8 import make_glossary from bot.yomichan.grammar import sudachi_rules, tags_to_rules -class Smk8Terminator(Terminator): +class Terminator(BaseTerminator): def __init__(self, target): super().__init__(target) diff --git a/jitenbot.py b/jitenbot.py index da44905..f0a2719 100644 --- a/jitenbot.py +++ b/jitenbot.py @@ -21,7 +21,7 @@ import sys import argparse import subprocess from bot.targets import Targets -from bot.crawlers.factory import new_crawler +from bot.factory import new_crawler def filename(f): diff --git a/run_all.sh b/run_all.sh index 706a911..9dcdfda 100755 --- a/run_all.sh +++ b/run_all.sh @@ -1,5 +1,7 @@ #!/bin/sh +export PYTHONPYCACHEPREFIX=/tmp/pycache + python -m unittest discover -s tests python jitenbot.py jitenon-kokugo From 4eb7e12f375241cb5f5c5dbf2ee6b144b159aebd Mon Sep 17 00:00:00 2001 From: stephenmk Date: Fri, 28 Jul 2023 16:59:43 -0500 Subject: [PATCH 36/43] Update TODO --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 877a7ee..4aaadee 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,7 @@ ### Todo - [x] Add factory classes to reduce the amount of class import statements -- [ ] Add dynamic import functionality to factory classes to reduce boilerplate +- [x] Add dynamic import functionality to factory classes to reduce boilerplate - [x] Support exporting to MDict (.MDX) dictionary format - [x] Validate JSON schema of Yomichan terms during export - [ ] Add support for monokakido search keys from index files From a5bb8d6f40f93e412490d9550b73dd89e2504dff Mon Sep 17 00:00:00 2001 From: stephenmk Date: Fri, 28 Jul 2023 17:00:01 -0500 Subject: [PATCH 37/43] sankoku8 bugfix: account for images in headwords MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Affects ビャンビャンめん, さんだつ (簒奪), and some words with hypens such as J-POP and CD-ROM. --- bot/entries/sankoku8/base_entry.py | 7 +++++++ bot/entries/sankoku8/preprocess.py | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/bot/entries/sankoku8/base_entry.py b/bot/entries/sankoku8/base_entry.py index 93c0515..8d7a394 100644 --- a/bot/entries/sankoku8/base_entry.py +++ b/bot/entries/sankoku8/base_entry.py @@ -67,6 +67,7 @@ class BaseEntry(SanseidoEntry): def _find_expressions(self, soup): expressions = [] for hyouki in soup.find_all(self._hyouki_name): + self._fill_alts(hyouki) for expression in parse_hyouki_soup(hyouki, [""]): expressions.append(expression) return expressions @@ -95,3 +96,9 @@ class BaseEntry(SanseidoEntry): ] for name in unused_nodes: Soup.delete_soup_nodes(soup, name) + + @staticmethod + def _fill_alts(soup): + for img in soup.find_all("img"): + if img.has_attr("alt"): + img.string = img.attrs["alt"] diff --git a/bot/entries/sankoku8/preprocess.py b/bot/entries/sankoku8/preprocess.py index 73fb31a..aa47d00 100644 --- a/bot/entries/sankoku8/preprocess.py +++ b/bot/entries/sankoku8/preprocess.py @@ -4,9 +4,17 @@ from bs4 import BeautifulSoup from bot.data import get_adobe_glyph +__GAIJI = { + "svg-gaiji/byan.svg": "𰻞", + "svg-gaiji/G16EF.svg": "簒", +} + + def preprocess_page(page): soup = BeautifulSoup(page, features="xml") __replace_glyph_codes(soup) + __add_image_alt_text(soup) + __replace_tatehyphen(soup) page = __strip_page(soup) return page @@ -20,6 +28,21 @@ def __replace_glyph_codes(soup): geta.replace_with(glyph) +def __add_image_alt_text(soup): + for img in soup.find_all("img"): + if not img.has_attr("src"): + continue + src = img.attrs["src"] + if src in __GAIJI: + img.attrs["alt"] = __GAIJI[src] + + +def __replace_tatehyphen(soup): + for img in soup.find_all("img", {"src": "svg-gaiji/tatehyphen.svg"}): + img.string = "−" + img.unwrap() + + def __strip_page(soup): koumoku = soup.find(["項目"]) if koumoku is not None: From d37c3aca5bbc308289a5f89e83534a42ce3471a4 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Fri, 28 Jul 2023 18:06:24 -0500 Subject: [PATCH 38/43] Update "alt" text for some SVG gaiji --- bot/entries/sankoku8/preprocess.py | 2 +- bot/entries/smk8/preprocess.py | 4 ++-- data/entries/variant_kanji.csv | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/entries/sankoku8/preprocess.py b/bot/entries/sankoku8/preprocess.py index aa47d00..1eee32d 100644 --- a/bot/entries/sankoku8/preprocess.py +++ b/bot/entries/sankoku8/preprocess.py @@ -6,7 +6,7 @@ from bot.data import get_adobe_glyph __GAIJI = { "svg-gaiji/byan.svg": "𰻞", - "svg-gaiji/G16EF.svg": "簒", + "svg-gaiji/G16EF.svg": "篡", } diff --git a/bot/entries/smk8/preprocess.py b/bot/entries/smk8/preprocess.py index 5c9b924..ebda252 100644 --- a/bot/entries/smk8/preprocess.py +++ b/bot/entries/smk8/preprocess.py @@ -6,8 +6,8 @@ from bot.data import get_adobe_glyph __GAIJI = { "gaiji/5350.svg": "卐", - "gaiji/62cb.svg": "抛", - "gaiji/7be1.svg": "簒", + "gaiji/62cb.svg": "拋", + "gaiji/7be1.svg": "篡", } diff --git a/data/entries/variant_kanji.csv b/data/entries/variant_kanji.csv index b50ce8d..01c0455 100644 --- a/data/entries/variant_kanji.csv +++ b/data/entries/variant_kanji.csv @@ -21,7 +21,7 @@ 瀆,涜 焰,焔 禱,祷 -竜,龍 +龍,竜 筓,笄 簞,箪 籠,篭 @@ -43,5 +43,5 @@ 鷽,鴬 鹼,鹸 麴,麹 -靭,靱 靱,靭 +篡,簒 From 8f30f9419d0403952bfc4ae7c81fc999693dcd43 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Fri, 28 Jul 2023 18:35:22 -0500 Subject: [PATCH 39/43] Update logic for adding variant kanji forms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prior to this commit, the program would only add variant kanji forms in one direction. For example, an additional search key for 掴む would be added if a 摑む headword was found in a dictionary, but a search key for 摑む would not be added to 掴む. Search keys are now added in both directions (old-to-new and new-to-old). --- bot/entries/base/expressions.py | 9 ++++++--- tests/test_expressions.py | 22 ++++++++++++++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/bot/entries/base/expressions.py b/bot/entries/base/expressions.py index 7d20891..8049a99 100644 --- a/bot/entries/base/expressions.py +++ b/bot/entries/base/expressions.py @@ -31,11 +31,14 @@ def add_fullwidth(expressions): def add_variant_kanji(expressions): variant_kanji = load_variant_kanji() - for old_kanji, new_kanji in variant_kanji.items(): + for kyuuji, shinji in variant_kanji.items(): new_exps = [] for expression in expressions: - if old_kanji in expression: - new_exp = expression.replace(old_kanji, new_kanji) + if kyuuji in expression: + new_exp = expression.replace(kyuuji, shinji) + new_exps.append(new_exp) + if shinji in expression: + new_exp = expression.replace(shinji, kyuuji) new_exps.append(new_exp) for new_exp in new_exps: if new_exp not in expressions: diff --git a/tests/test_expressions.py b/tests/test_expressions.py index 5d90ce1..9091dda 100644 --- a/tests/test_expressions.py +++ b/tests/test_expressions.py @@ -34,8 +34,8 @@ class TestExpressions(unittest.TestCase): self.assertIn("凶々しい", exps) self.assertIn("凶凶しい", exps) - def test_add_variant_kanji(self): - exps = ["剝く", "掴む", "摑む"] + def test_add_variant_kanji1(self): + exps = ["剥く", "摑む"] Expressions.add_variant_kanji(exps) self.assertEqual(len(exps), 4) self.assertIn("剥く", exps) @@ -44,6 +44,15 @@ class TestExpressions(unittest.TestCase): self.assertIn("摑む", exps) def test_add_variant_kanji2(self): + exps = ["剝く", "掴む", "摑む"] + Expressions.add_variant_kanji(exps) + self.assertEqual(len(exps), 4) + self.assertIn("剥く", exps) + self.assertIn("剝く", exps) + self.assertIn("掴む", exps) + self.assertIn("摑む", exps) + + def test_add_variant_kanji3(self): exps = ["剝摑"] Expressions.add_variant_kanji(exps) self.assertEqual(len(exps), 4) @@ -52,6 +61,15 @@ class TestExpressions(unittest.TestCase): self.assertIn("剥掴", exps) self.assertIn("剥摑", exps) + def test_add_variant_kanji4(self): + exps = ["剥掴"] + Expressions.add_variant_kanji(exps) + self.assertEqual(len(exps), 4) + self.assertIn("剝摑", exps) + self.assertIn("剝掴", exps) + self.assertIn("剥掴", exps) + self.assertIn("剥摑", exps) + def test_expand_abbreviation(self): text = "有(り)合(わ)せ" abbrs = Expressions.expand_abbreviation(text) From b03978d1f73293ad29caff9654e5fbe7579b0d60 Mon Sep 17 00:00:00 2001 From: stephenmk Date: Fri, 28 Jul 2023 23:17:42 -0500 Subject: [PATCH 40/43] Add timestamps to command line messages This is a clumsy way of doing it (since it would be better to have a wrapper function append the timestamp), but that will be taken care of when the logging logic is all overhauled anyway. --- bot/crawlers/base/crawler.py | 2 +- bot/crawlers/base/jitenon.py | 5 +++-- bot/crawlers/base/monokakido.py | 5 +++-- bot/crawlers/jitenon_kokugo.py | 4 +++- bot/crawlers/scrapers/scraper.py | 18 +++++++++++------- bot/mdict/exporters/base/exporter.py | 11 ++++++----- bot/time.py | 5 +++++ bot/yomichan/exporters/base/exporter.py | 24 +++++++++++++----------- 8 files changed, 45 insertions(+), 29 deletions(-) create mode 100644 bot/time.py diff --git a/bot/crawlers/base/crawler.py b/bot/crawlers/base/crawler.py index 31c3bdc..bbbcb9b 100644 --- a/bot/crawlers/base/crawler.py +++ b/bot/crawlers/base/crawler.py @@ -21,7 +21,7 @@ class BaseCrawler(ABC): pages_len = len(self._page_map) items = self._page_map.items() for idx, (page_id, page_path) in enumerate(items): - update = f"Reading page {idx+1}/{pages_len}" + update = f"\tReading page {idx+1}/{pages_len}" print(update, end='\r', flush=True) entry = new_entry(self._target, page_id) with open(page_path, "r", encoding="utf-8") as f: diff --git a/bot/crawlers/base/jitenon.py b/bot/crawlers/base/jitenon.py index ddbf3e5..49e4626 100644 --- a/bot/crawlers/base/jitenon.py +++ b/bot/crawlers/base/jitenon.py @@ -1,5 +1,6 @@ from bs4 import BeautifulSoup +from bot.time import timestamp from bot.crawlers.scrapers.jitenon import Jitenon as JitenonScraper from bot.crawlers.base.crawler import BaseCrawler @@ -10,7 +11,7 @@ class JitenonCrawler(BaseCrawler): self._gojuon_url = None def collect_pages(self, page_dir): - print("Scraping jitenon.jp") + print(f"{timestamp()} Scraping {self._gojuon_url}") jitenon = JitenonScraper() gojuon_doc, _ = jitenon.scrape(self._gojuon_url) gojuon_soup = BeautifulSoup(gojuon_doc, features="html.parser") @@ -26,4 +27,4 @@ class JitenonCrawler(BaseCrawler): _, page_path = jitenon.scrape(page_link) self._page_map[page_id] = page_path pages_len = len(self._page_map) - print(f"Finished scraping {pages_len} pages") + print(f"\n{timestamp()} Found {pages_len} entry pages") diff --git a/bot/crawlers/base/monokakido.py b/bot/crawlers/base/monokakido.py index 057f8d4..ca98545 100644 --- a/bot/crawlers/base/monokakido.py +++ b/bot/crawlers/base/monokakido.py @@ -1,4 +1,5 @@ import os +from bot.time import timestamp from bot.crawlers.base.crawler import BaseCrawler @@ -8,7 +9,7 @@ class MonokakidoCrawler(BaseCrawler): self._page_id_pattern = r"^([0-9]+)\.xml$" def collect_pages(self, page_dir): - print(f"Searching for page files in `{page_dir}`") + print(f"{timestamp()} Searching for page files in `{page_dir}`") for pagefile in os.listdir(page_dir): page_id = self._parse_page_id(pagefile) if page_id is None or page_id == 0: @@ -16,4 +17,4 @@ class MonokakidoCrawler(BaseCrawler): path = os.path.join(page_dir, pagefile) self._page_map[page_id] = path pages_len = len(self._page_map) - print(f"Found {pages_len} page files for processing") + print(f"{timestamp()} Found {pages_len} page files for processing") diff --git a/bot/crawlers/jitenon_kokugo.py b/bot/crawlers/jitenon_kokugo.py index 6d5cd66..e748ea1 100644 --- a/bot/crawlers/jitenon_kokugo.py +++ b/bot/crawlers/jitenon_kokugo.py @@ -1,6 +1,7 @@ import re from bs4 import BeautifulSoup +from bot.time import timestamp from bot.crawlers.base.crawler import BaseCrawler from bot.crawlers.scrapers.jitenon import Jitenon as JitenonScraper @@ -12,6 +13,7 @@ class Crawler(BaseCrawler): self._page_id_pattern = r"word/p([0-9]+)$" def collect_pages(self, page_dir): + print(f"{timestamp()} Scraping {self._gojuon_url}") jitenon = JitenonScraper() gojuon_doc, _ = jitenon.scrape(self._gojuon_url) gojuon_soup = BeautifulSoup(gojuon_doc, features="html.parser") @@ -35,4 +37,4 @@ class Crawler(BaseCrawler): _, page_path = jitenon.scrape(page_link) self._page_map[page_id] = page_path pages_len = len(self._page_map) - print(f"Finished scraping {pages_len} pages") + print(f"\n{timestamp()} Found {pages_len} entry pages") diff --git a/bot/crawlers/scrapers/scraper.py b/bot/crawlers/scrapers/scraper.py index 113d090..eeb9534 100644 --- a/bot/crawlers/scrapers/scraper.py +++ b/bot/crawlers/scrapers/scraper.py @@ -2,6 +2,8 @@ import time import re import os import hashlib +import random +import math from datetime import datetime from urllib.parse import urlparse from pathlib import Path @@ -12,11 +14,13 @@ from requests.adapters import HTTPAdapter from requests.packages.urllib3.util.retry import Retry from platformdirs import user_cache_dir +from bot.time import timestamp from bot.data import load_config class BaseScraper(ABC): def __init__(self): + self.cache_count = 0 self._config = load_config() self.netloc_re = self._get_netloc_re() self.__set_session() @@ -31,7 +35,8 @@ class BaseScraper(ABC): with open(cache_path, "w", encoding="utf-8") as f: f.write(html) else: - print("Discovering cached files...", end='\r', flush=True) + self.cache_count += 1 + print(f"\tDiscovering cached file {self.cache_count}", end='\r', flush=True) return html, cache_path @abstractmethod @@ -91,15 +96,14 @@ class BaseScraper(ABC): def __get(self, urlstring): delay = 10 time.sleep(delay) - now = datetime.now().strftime("%H:%M:%S") - print(f"{now} scraping {urlstring} ...", end='') + print(f"{timestamp()} Scraping {urlstring} ...", end='') try: response = self.session.get(urlstring, timeout=10) - print("OK") + print(f"{timestamp()} OK") return response.text - except Exception: - print("failed") - print("resetting session and trying again") + except Exception as ex: + print(f"\tFailed: {str(ex)}") + print(f"{timestamp()} Resetting session and trying again") self.__set_session() response = self.session.get(urlstring, timeout=10) return response.text diff --git a/bot/mdict/exporters/base/exporter.py b/bot/mdict/exporters/base/exporter.py index 26dc662..37ed376 100644 --- a/bot/mdict/exporters/base/exporter.py +++ b/bot/mdict/exporters/base/exporter.py @@ -6,6 +6,7 @@ from pathlib import Path from platformdirs import user_documents_dir, user_cache_dir +from bot.time import timestamp from bot.factory import new_mdict_terminator @@ -32,7 +33,7 @@ class BaseExporter(ABC): return self._build_dir cache_dir = user_cache_dir("jitenbot") build_directory = os.path.join(cache_dir, "mdict_build") - print(f"Initializing build directory `{build_directory}`") + print(f"{timestamp()} Initializing build directory `{build_directory}`") if Path(build_directory).is_dir(): shutil.rmtree(build_directory) os.makedirs(build_directory) @@ -43,7 +44,7 @@ class BaseExporter(ABC): build_dir = self._get_build_dir() build_media_dir = os.path.join(build_dir, self._target.value) if media_dir is not None: - print("Copying media files to build directory...") + print(f"{timestamp()} Copying media files to build directory...") shutil.copytree(media_dir, build_media_dir) else: os.makedirs(build_media_dir) @@ -69,7 +70,7 @@ class BaseExporter(ABC): def _write_mdx_file(self, entries): terms = self._get_terms(entries) - print(f"Exporting {len(terms)} Mdict keys...") + print(f"{timestamp()} Exporting {len(terms)} Mdict keys...") out_dir = self._get_out_dir() out_file = os.path.join(out_dir, f"{self._target.value}.mdx") params = [ @@ -85,7 +86,7 @@ class BaseExporter(ABC): terms = [] entries_len = len(entries) for idx, entry in enumerate(entries): - update = f"Creating Mdict terms for entry {idx+1}/{entries_len}" + update = f"\tCreating MDict terms for entry {idx+1}/{entries_len}" print(update, end='\r', flush=True) new_terms = self._terminator.make_terms(entry) for term in new_terms: @@ -124,7 +125,7 @@ class BaseExporter(ABC): return self._out_dir out_dir = os.path.join( user_documents_dir(), "jitenbot", "mdict", self._target.value) - print(f"Initializing output directory `{out_dir}`") + print(f"{timestamp()} Initializing output directory `{out_dir}`") if Path(out_dir).is_dir(): shutil.rmtree(out_dir) os.makedirs(out_dir) diff --git a/bot/time.py b/bot/time.py new file mode 100644 index 0000000..f8dae94 --- /dev/null +++ b/bot/time.py @@ -0,0 +1,5 @@ +import time + + +def timestamp(): + return time.strftime('%X') diff --git a/bot/yomichan/exporters/base/exporter.py b/bot/yomichan/exporters/base/exporter.py index 9389202..5e4e870 100644 --- a/bot/yomichan/exporters/base/exporter.py +++ b/bot/yomichan/exporters/base/exporter.py @@ -8,6 +8,7 @@ from abc import ABC, abstractmethod import fastjsonschema from platformdirs import user_documents_dir, user_cache_dir +from bot.time import timestamp from bot.data import load_yomichan_metadata from bot.data import load_yomichan_term_schema from bot.factory import new_yomichan_terminator @@ -45,7 +46,7 @@ class BaseExporter(ABC): return self._build_dir cache_dir = user_cache_dir("jitenbot") build_directory = os.path.join(cache_dir, "yomichan_build") - print(f"Initializing build directory `{build_directory}`") + print(f"{timestamp()} Initializing build directory `{build_directory}`") if Path(build_directory).is_dir(): shutil.rmtree(build_directory) os.makedirs(build_directory) @@ -64,8 +65,9 @@ class BaseExporter(ABC): build_dir = self._get_build_dir() build_img_dir = os.path.join(build_dir, self._target.value) if image_dir is not None: - print("Copying media files to build directory...") + print(f"{timestamp()} Copying media files to build directory...") shutil.copytree(image_dir, build_img_dir) + print(f"{timestamp()} Finished copying files") else: os.makedirs(build_img_dir) self._terminator.set_image_dir(build_img_dir) @@ -74,7 +76,7 @@ class BaseExporter(ABC): terms = [] entries_len = len(entries) for idx, entry in enumerate(entries): - update = f"Creating Yomichan terms for entry {idx+1}/{entries_len}" + update = f"\tCreating Yomichan terms for entry {idx+1}/{entries_len}" print(update, end='\r', flush=True) new_terms = self._terminator.make_terms(entry) for term in new_terms: @@ -83,7 +85,7 @@ class BaseExporter(ABC): return terms def __validate_terms(self, terms): - print("Making a copy of term data for validation...") + print(f"{timestamp()} Making a copy of term data for validation...") terms_copy = copy.deepcopy(terms) # because validator will alter data! term_count = len(terms_copy) log_dir = self.__get_invalid_term_dir() @@ -91,7 +93,7 @@ class BaseExporter(ABC): validator = fastjsonschema.compile(schema) failure_count = 0 for idx, term in enumerate(terms_copy): - update = f"Validating term {idx+1}/{term_count}" + update = f"\tValidating term {idx+1}/{term_count}" print(update, end='\r', flush=True) try: validator([term]) @@ -100,9 +102,9 @@ class BaseExporter(ABC): term_file = os.path.join(log_dir, f"{idx}.json") with open(term_file, "w", encoding='utf8') as f: json.dump([term], f, indent=4, ensure_ascii=False) - print(f"\nFinished validating with {failure_count} error{'' if failure_count == 1 else 's'}") + print(f"\n{timestamp()} Finished validating with {failure_count} error{'' if failure_count == 1 else 's'}") if failure_count > 0: - print(f"Invalid terms saved to `{log_dir}` for debugging") + print(f"{timestamp()} Invalid terms saved to `{log_dir}` for debugging") def __make_dictionary(self, terms, index, tags): self.__write_term_banks(terms) @@ -112,11 +114,11 @@ class BaseExporter(ABC): self.__rm_build_dir() def __write_term_banks(self, terms): - print(f"Exporting {len(terms)} JSON terms") + print(f"{timestamp()} Exporting {len(terms)} JSON terms") build_dir = self._get_build_dir() max_i = int(len(terms) / self._terms_per_file) + 1 for i in range(max_i): - update = f"Writing terms to term bank {i+1}/{max_i}" + update = f"\tWriting terms to term bank {i+1}/{max_i}" print(update, end='\r', flush=True) start = self._terms_per_file * i end = self._terms_per_file * (i + 1) @@ -141,7 +143,7 @@ class BaseExporter(ABC): def __write_archive(self, filename): archive_format = "zip" - print(f"Archiving data to {archive_format.upper()} file...") + print(f"{timestamp()} Archiving data to {archive_format.upper()} file...") out_dir = os.path.join(user_documents_dir(), "jitenbot", "yomichan") if not Path(out_dir).is_dir(): os.makedirs(out_dir) @@ -152,7 +154,7 @@ class BaseExporter(ABC): base_filename = os.path.join(out_dir, filename) build_dir = self._get_build_dir() shutil.make_archive(base_filename, archive_format, build_dir) - print(f"Dictionary file saved to `{out_filepath}`") + print(f"{timestamp()} Dictionary file saved to `{out_filepath}`") def __rm_build_dir(self): build_dir = self._get_build_dir() From 22f35ffe7f26250c1d5abdf9f3cd9b95d6948b4f Mon Sep 17 00:00:00 2001 From: epistularum <34507493+epistularum@users.noreply.github.com> Date: Tue, 1 Aug 2023 18:58:22 +0900 Subject: [PATCH 41/43] Update daijirin2.css --- data/mdict/css/daijirin2.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/mdict/css/daijirin2.css b/data/mdict/css/daijirin2.css index f3f54a6..2407677 100644 --- a/data/mdict/css/daijirin2.css +++ b/data/mdict/css/daijirin2.css @@ -10,10 +10,10 @@ } body { - margin: 0em 1em; + /*margin: 0em 1em;*/ line-height: 1.5em; font-family: jpmincho, serif; - font-size: 1.2em; + /*font-size: 1.2em;*/ color: black; } From bd5b7a91d9b47519151e0e1d0df0d51945102571 Mon Sep 17 00:00:00 2001 From: epistularum <34507493+epistularum@users.noreply.github.com> Date: Tue, 1 Aug 2023 18:59:45 +0900 Subject: [PATCH 42/43] Update sankoku8.css --- data/mdict/css/sankoku8.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/mdict/css/sankoku8.css b/data/mdict/css/sankoku8.css index 0d56d1b..47b7ec0 100644 --- a/data/mdict/css/sankoku8.css +++ b/data/mdict/css/sankoku8.css @@ -15,10 +15,10 @@ } body { - margin: 0em 1em; + /*margin: 0em 1em;*/ line-height: 1.5em; font-family: jpmincho, serif; - font-size: 1.2em; + /*font-size: 1.2em;*/ } span[data-name="entry-index"] > a { From 630b529287bea5890e53e932f1e78ad98600cbee Mon Sep 17 00:00:00 2001 From: epistularum <34507493+epistularum@users.noreply.github.com> Date: Tue, 1 Aug 2023 19:00:15 +0900 Subject: [PATCH 43/43] Update smk8.css --- data/mdict/css/smk8.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/mdict/css/smk8.css b/data/mdict/css/smk8.css index 29a23e8..55ef814 100644 --- a/data/mdict/css/smk8.css +++ b/data/mdict/css/smk8.css @@ -10,10 +10,10 @@ } body { - margin: 0em 1em; + /*margin: 0em 1em;*/ line-height: 1.5em; font-family: jpmincho, serif; - font-size: 1.2em; + /*font-size: 1.2em;*/ color: black; } @@ -26,7 +26,7 @@ span[data-name="見出部"] { } span[data-name="見出部"].pri { - margin-left: -0.4em; + /*margin-left: -0.4em;*/ } span[data-name="見出仮名"] {