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..3cf5ddd 100644
--- a/README.md
+++ b/README.md
@@ -3,17 +3,19 @@ Jitenbot is a program for scraping Japanese dictionary websites and
compiling the scraped data into compact dictionary file formats.
### Supported Dictionaries
-* Online
- * [国語辞典オンライン](https://kokugo.jitenon.jp/) (Jitenon Kokugo)
- * [四字熟語辞典オンライン](https://yoji.jitenon.jp/) (Jitenon Yoji)
- * [故事・ことわざ・慣用句オンライン](https://kotowaza.jitenon.jp/) (Jitenon Kotowaza)
-* Offline
- * [新明解国語辞典 第八版](https://www.monokakido.jp/ja/dictionaries/smk8/index.html) (Shinmeikai 8e)
- * [大辞林 第四版](https://www.monokakido.jp/ja/dictionaries/daijirin2/index.html) (Daijirin 4e)
+* Web Dictionaries
+ * [国語辞典オンライン](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
* [Yomichan](https://github.com/foosoft/yomichan)
+* MDict (.MDX & .MDD)
# Examples
@@ -47,26 +49,50 @@ compiling the scraped data into compact dictionary file formats.

+
+ Sanseidō 8e (print | yomichan)
+
+ 
+
+
+
+ Various (GoldenDict)
+
+ 
+
+
# Usage
```
-usage: jitenbot [-h] [-p PAGE_DIR] [-i IMAGE_DIR]
- {jitenon-kokugo,jitenon-yoji,jitenon-kotowaza,smk8,daijirin2}
+usage: jitenbot [-h] [-p PAGE_DIR] [-m MEDIA_DIR] [-i MDICT_ICON]
+ [--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:
-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-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
+
```
-### 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.
@@ -74,9 +100,113 @@ 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
-Page data and image data must be procured by the user
-and passed to jitenbot via the appropriate command line flags.
+### 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 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/`
+
+
+ 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.
+
+```
+.
+├── 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 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
+└── 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.
diff --git a/TODO.md b/TODO.md
index 30c860d..4aaadee 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,11 +1,14 @@
### Todo
+- [x] Add factory classes to reduce the amount of class import statements
+- [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
+- [ ] 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 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
@@ -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/base/crawler.py b/bot/crawlers/base/crawler.py
new file mode 100644
index 0000000..bbbcb9b
--- /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"\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:
+ 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..49e4626
--- /dev/null
+++ b/bot/crawlers/base/jitenon.py
@@ -0,0 +1,30 @@
+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
+
+
+class JitenonCrawler(BaseCrawler):
+ def __init__(self, target):
+ super().__init__(target)
+ self._gojuon_url = None
+
+ 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")
+ 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"\n{timestamp()} Found {pages_len} entry pages")
diff --git a/bot/crawlers/base/monokakido.py b/bot/crawlers/base/monokakido.py
new file mode 100644
index 0000000..ca98545
--- /dev/null
+++ b/bot/crawlers/base/monokakido.py
@@ -0,0 +1,20 @@
+import os
+from bot.time import timestamp
+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"{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:
+ continue
+ path = os.path.join(page_dir, pagefile)
+ self._page_map[page_id] = path
+ pages_len = len(self._page_map)
+ print(f"{timestamp()} Found {pages_len} page files for processing")
diff --git a/bot/crawlers/crawlers.py b/bot/crawlers/crawlers.py
deleted file mode 100644
index c7bf8ea..0000000
--- a/bot/crawlers/crawlers.py
+++ /dev/null
@@ -1,151 +0,0 @@
-import os
-import re
-from abc import ABC, abstractmethod
-from bs4 import BeautifulSoup
-
-import bot.scraper as Scraper
-from bot.entries.factory import new_entry
-from bot.yomichan.exporters.factory import new_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, image_dir):
- exporter = new_exporter(self._target)
- exporter.export(self._entries, image_dir)
-
- 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):
- def __init__(self, target):
- super().__init__(target)
-
-
-class Daijirin2Crawler(_MonokakidoCrawler):
- def __init__(self, target):
- super().__init__(target)
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 081567b..0000000
--- a/bot/crawlers/factory.py
+++ /dev/null
@@ -1,18 +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
-
-
-def new_crawler(target):
- crawler_map = {
- Targets.JITENON_KOKUGO: JitenonKokugoCrawler,
- Targets.JITENON_YOJI: JitenonYojiCrawler,
- Targets.JITENON_KOTOWAZA: JitenonKotowazaCrawler,
- Targets.SMK8: Smk8Crawler,
- Targets.DAIJIRIN2: Daijirin2Crawler,
- }
- 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..e748ea1
--- /dev/null
+++ b/bot/crawlers/jitenon_kokugo.py
@@ -0,0 +1,40 @@
+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
+
+
+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):
+ print(f"{timestamp()} Scraping {self._gojuon_url}")
+ 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"\n{timestamp()} Found {pages_len} entry 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/scraper.py b/bot/crawlers/scrapers/scraper.py
similarity index 82%
rename from bot/scraper.py
rename to bot/crawlers/scrapers/scraper.py
index 577f602..eeb9534 100644
--- a/bot/scraper.py
+++ b/bot/crawlers/scrapers/scraper.py
@@ -1,24 +1,28 @@
import time
-import requests
import re
import os
import hashlib
+import random
+import math
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.time import timestamp
from bot.data import load_config
-class Scraper():
+class BaseScraper(ABC):
def __init__(self):
+ self.cache_count = 0
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):
@@ -31,9 +35,14 @@ class Scraper():
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
+ def _get_netloc_re(self):
+ raise NotImplementedError
+
def __set_session(self):
retry_strategy = Retry(
total=3,
@@ -87,21 +96,14 @@ class Scraper():
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
-
-
-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/data.py b/bot/data.py
index 5d68769..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 = "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,31 +55,21 @@ 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
@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("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("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,22 +84,33 @@ 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
@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(
+ "yomichan", "name_conversion", f"{target.value}.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_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")
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("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("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/base/entry.py b/bot/entries/base/entry.py
new file mode 100644
index 0000000..60d4f16
--- /dev/null
+++ b/bot/entries/base/entry.py
@@ -0,0 +1,68 @@
+from abc import ABC, abstractmethod
+
+
+class Entry(ABC):
+ 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):
+ raise NotImplementedError
+
+ @abstractmethod
+ def set_page(self, page):
+ raise NotImplementedError
+
+ @abstractmethod
+ def get_page_soup(self):
+ raise NotImplementedError
+
+ 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):
+ raise NotImplementedError
+
+ @abstractmethod
+ def _add_variant_expressions(self, headwords):
+ raise NotImplementedError
+
+ @abstractmethod
+ def get_part_of_speech_tags(self):
+ raise NotImplementedError
+
+ 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()))
+ expression = expressions[0]
+ return expression
+
+ def get_first_reading(self):
+ headwords = self.get_headwords()
+ reading = next(iter(headwords.keys()))
+ return reading
diff --git a/bot/entries/expressions.py b/bot/entries/base/expressions.py
similarity index 63%
rename from bot/entries/expressions.py
rename to bot/entries/base/expressions.py
index 687a325..8049a99 100644
--- a/bot/entries/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:
@@ -85,40 +88,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/base/jitenon_entry.py b/bot/entries/base/jitenon_entry.py
new file mode 100644
index 0000000..43d6005
--- /dev/null
+++ b/bot/entries/base/jitenon_entry.py
@@ -0,0 +1,142 @@
+import re
+from abc import abstractmethod
+from datetime import datetime, date
+from bs4 import BeautifulSoup
+
+from bot.entries.base.entry import Entry
+import bot.entries.base.expressions as Expressions
+
+
+class JitenonEntry(Entry):
+ 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 = ""
+
+ 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")
+ self.__set_modified_date(page)
+ self.__set_attribution(soup)
+ table = soup.find(class_="kanjirighttb")
+ if table is None:
+ raise ValueError("Error: table data not found in page.")
+ rows = table.find("tbody").find_all("tr")
+ colname = ""
+ for row in rows:
+ colname = row.th.text if row.th is not None else colname
+ colval = self.__clean_text(row.td.text)
+ self.__set_column(colname, colval)
+ self._page = table.decode()
+
+ def get_page_soup(self):
+ soup = BeautifulSoup(self._page, "html5lib")
+ return soup
+
+ def get_part_of_speech_tags(self):
+ # Jitenon doesn't have any
+ return []
+
+ def _get_headwords(self):
+ headwords = {}
+ 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)
+ return headwords
+
+ @abstractmethod
+ def _get_column_map(self):
+ raise NotImplementedError
+
+ def __set_modified_date(self, page):
+ m = re.search(r"\"dateModified\": \"(\d{4}-\d{2}-\d{2})", page)
+ if m is None:
+ return
+ 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")
+ if attribution is not None:
+ self.attribution = soup.find(class_="copyright").text
+ else:
+ self.attribution = ""
+
+ def __set_column(self, colname, colval):
+ 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)
+ elif isinstance(attr_value, list):
+ if len(attr_value) == 0:
+ setattr(self, attr_name, [colval])
+ else:
+ attr_value.append(colval)
+
+ def _get_readings(self):
+ yomikata = self.yomikata
+ m = re.search(r"^[ぁ-ヿ、]+$", yomikata)
+ if m:
+ return [yomikata]
+ m = re.search(r"^([ぁ-ヿ、]+)※", yomikata)
+ if m:
+ return [m.group(1)]
+ m = re.search(r"^[ぁ-ヿ、]+([ぁ-ヿ、])[ぁ-ヿ、]+$", yomikata)
+ if m:
+ return Expressions.expand_abbreviation(yomikata)
+ m = re.search(r"^([ぁ-ヿ、]+)(([ぁ-ヿ/\s、]+))$", yomikata)
+ if m:
+ yomikatas = [m.group(1)]
+ alts = m.group(2).split("/")
+ for alt in alts:
+ yomikatas.append(alt.strip())
+ return yomikatas
+ print(f"Invalid 読み方 format: {self.yomikata}\n{self}\n")
+ return [""]
+
+ 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 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):
+ text = text.replace("\n", "")
+ text = text.replace(",", "、")
+ text = text.replace(" ", "")
+ text = text.strip()
+ return text
+
+ def __str__(self):
+ column_map = self._get_column_map()
+ colvals = [str(self.entry_id)]
+ 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):
+ colvals.append(";".join(attr_val))
+ return ",".join(colvals)
diff --git a/bot/entries/base/sanseido_entry.py b/bot/entries/base/sanseido_entry.py
new file mode 100644
index 0000000..bb52431
--- /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):
+ raise NotImplementedError
+
+ 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 1463442..0000000
--- a/bot/entries/daijirin2.py
+++ /dev/null
@@ -1,240 +0,0 @@
-import re
-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_daijirin2_kana_abbreviations
-from bot.entries.entry import Entry
-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}")
- self.children = []
- self.phrases = []
- self._kana_abbreviations = load_daijirin2_kana_abbreviations()
-
- 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_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):
- 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)
- self._headwords = {reading: expressions}
-
- def _set_variant_headwords(self):
- for expressions in self._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(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):
- 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, page_id):
- entry_id = (page_id, 0)
- super().__init__(entry_id)
-
- def set_page(self, page):
- page = preprocess_page(page)
- super().set_page(page)
-
- def _set_headwords(self):
- soup = self.get_page_soup()
- self._delete_unused_nodes(soup)
- if soup.find("漢字見出") is not None:
- self._set_kanji_headwords(soup)
- elif soup.find("略語G") is not None:
- self._set_acronym_headwords(soup)
- else:
- self._set_regular_headwords(soup)
-
- def _set_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)
- self._headwords = {}
- for reading in readings:
- self._headwords[reading] = expressions
-
- def _set_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)
- self._headwords = {"": expressions}
-
-
-class Daijirin2ChildEntry(_BaseDaijirin2Entry):
- def __init__(self, entry_id):
- super().__init__(entry_id)
-
- def _set_headwords(self):
- soup = self.get_page_soup()
- self._delete_unused_nodes(soup)
- self._set_regular_headwords(soup)
-
-
-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):
- 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]
- self._headwords = 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):
- text = self.__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/entry.py b/bot/entries/entry.py
deleted file mode 100644
index 57316f6..0000000
--- a/bot/entries/entry.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from abc import ABC, abstractmethod
-
-
-class Entry(ABC):
- def __init__(self, entry_id):
- self.entry_id = entry_id
- self._page = None
- self._headwords = None
- self._part_of_speech_tags = None
-
- @abstractmethod
- def set_page(self, page):
- pass
-
- @abstractmethod
- def get_page_soup(self):
- pass
-
- @abstractmethod
- def get_headwords(self):
- pass
-
- @abstractmethod
- def get_part_of_speech_tags(self):
- pass
-
- def get_first_expression(self):
- headwords = self.get_headwords()
- expressions = next(iter(headwords.values()))
- expression = expressions[0]
- return expression
-
- def get_first_reading(self):
- headwords = self.get_headwords()
- reading = next(iter(headwords.keys()))
- return reading
diff --git a/bot/entries/factory.py b/bot/entries/factory.py
deleted file mode 100644
index 23ca066..0000000
--- a/bot/entries/factory.py
+++ /dev/null
@@ -1,18 +0,0 @@
-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
-
-
-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,
- }
- return entry_map[target](page_id)
diff --git a/bot/entries/jitenon.py b/bot/entries/jitenon.py
deleted file mode 100644
index fd9fcd2..0000000
--- a/bot/entries/jitenon.py
+++ /dev/null
@@ -1,227 +0,0 @@
-import re
-from datetime import datetime, date
-from bs4 import BeautifulSoup
-
-from bot.entries.entry import Entry
-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}")
- self.modified_date = date(1970, 1, 1)
- self.attribution = ""
- for column in self._COLUMNS.values():
- setattr(self, column[0], column[1])
-
- def set_page(self, page):
- soup = BeautifulSoup(page, features="html5lib")
- self.__set_modified_date(page)
- self.__set_attribution(soup)
- table = soup.find(class_="kanjirighttb")
- if table is None:
- raise ValueError("Error: table data not found in page.")
- rows = table.find("tbody").find_all("tr")
- colname = ""
- for row in rows:
- colname = row.th.text if row.th is not None else colname
- colval = self.__clean_text(row.td.text)
- self.__set_column(colname, colval)
- self._page = table.decode()
-
- def get_page_soup(self):
- 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):
- headwords = {}
- for yomikata in self._yomikatas():
- headwords[yomikata] = [self.expression]
- ikei_headwords = self._ikei_headwords()
- for reading, expressions in ikei_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
-
- 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
-
- def __set_attribution(self, soup):
- attribution = soup.find(class_="copyright")
- if attribution is not None:
- self.attribution = soup.find(class_="copyright").text
- else:
- self.attribution = ""
-
- def __set_column(self, colname, colval):
- attr_name = self._COLUMNS[colname][0]
- attr_value = getattr(self, attr_name)
- if isinstance(attr_value, str):
- setattr(self, attr_name, colval)
- elif isinstance(attr_value, list):
- if len(attr_value) == 0:
- setattr(self, attr_name, [colval])
- else:
- attr_value.append(colval)
-
- def _yomikatas(self):
- yomikata = self.yomikata
- m = re.search(r"^[ぁ-ヿ、]+$", yomikata)
- if m:
- return [yomikata]
- m = re.search(r"^([ぁ-ヿ、]+)※", yomikata)
- if m:
- return [m.group(1)]
- m = re.search(r"^[ぁ-ヿ、]+([ぁ-ヿ、])[ぁ-ヿ、]+$", yomikata)
- if m:
- return Expressions.expand_abbreviation(yomikata)
- m = re.search(r"^([ぁ-ヿ、]+)(([ぁ-ヿ/\s、]+))$", yomikata)
- if m:
- yomikatas = [m.group(1)]
- alts = m.group(2).split("/")
- for alt in alts:
- yomikatas.append(alt.strip())
- return yomikatas
- print(f"Invalid 読み方 format: {self.yomikata}\n{self}\n")
- return [""]
-
- def _ikei_headwords(self):
- ikei_headwords = {}
- for val in self.ikei:
- 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
-
- @staticmethod
- def __clean_text(text):
- text = text.replace("\n", "")
- text = text.replace(",", "、")
- text = text.replace(" ", "")
- text = text.strip()
- return text
-
- def __str__(self):
- colvals = [str(self.entry_id)]
- for attr in self._COLUMNS.values():
- attr_val = getattr(self, attr[0])
- if isinstance(attr_val, str):
- colvals.append(attr_val)
- elif isinstance(attr_val, list):
- colvals.append(";".join(attr_val))
- return ",".join(colvals)
-
-
-class JitenonYojiEntry(_JitenonEntry):
- _COLUMNS = {
- "四字熟語": ["expression", ""],
- "読み方": ["yomikata", ""],
- "意味": ["imi", ""],
- "出典": ["shutten", ""],
- "漢検級": ["kankenkyuu", ""],
- "場面用途": ["bamenyouto", ""],
- "異形": ["ikei", []],
- "類義語": ["ruigigo", []],
- }
-
- def __init__(self, entry_id):
- super().__init__(entry_id)
-
- def _set_variant_headwords(self):
- for expressions in self._headwords.values():
- Expressions.add_variant_kanji(expressions)
-
-
-class JitenonKotowazaEntry(_JitenonEntry):
- _COLUMNS = {
- "言葉": ["expression", ""],
- "読み方": ["yomikata", ""],
- "意味": ["imi", ""],
- "出典": ["shutten", ""],
- "例文": ["reibun", ""],
- "異形": ["ikei", []],
- "類句": ["ruiku", []],
- }
-
- def __init__(self, entry_id):
- super().__init__(entry_id)
-
- def _set_headwords(self):
- if self.expression == "金棒引き・鉄棒引き":
- self._headwords = {
- "かなぼうひき": ["金棒引き", "鉄棒引き"]
- }
- else:
- super()._set_headwords()
-
- def _set_variant_headwords(self):
- for expressions in self._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, entry_id):
- super().__init__(entry_id)
-
- def _set_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("・"):
- headwords[reading].append(expression)
- self._headwords = headwords
-
- def _set_variant_headwords(self):
- for expressions in self._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_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/base_entry.py b/bot/entries/sankoku8/base_entry.py
new file mode 100644
index 0000000..8d7a394
--- /dev/null
+++ b/bot/entries/sankoku8/base_entry.py
@@ -0,0 +1,104 @@
+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):
+ self._fill_alts(hyouki)
+ 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)
+
+ @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/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>〈<言換>",
+ "/": "言換>/<言換>",
+ "〉": "言換>〉言換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
new file mode 100644
index 0000000..1eee32d
--- /dev/null
+++ b/bot/entries/sankoku8/preprocess.py
@@ -0,0 +1,51 @@
+import re
+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
+
+
+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 __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:
+ 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
deleted file mode 100644
index 11ef7e6..0000000
--- a/bot/entries/smk8.py
+++ /dev/null
@@ -1,223 +0,0 @@
-import re
-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.entries.entry import Entry
-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}")
- self.children = []
- self.phrases = []
- self.kanjis = []
-
- 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):
- 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
- 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 _set_variant_headwords(self):
- for expressions in self._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(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 _clean_expression(expression):
- for x in ["〈", "〉", "{", "}", "…", " "]:
- expression = expression.replace(x, "")
- return expression
-
- @staticmethod
- def _fill_alts(soup):
- for e in soup.find_all(["親見出仮名", "親見出表記"]):
- e.string = e.attrs["alt"]
- for gaiji in soup.find_all("外字"):
- gaiji.string = gaiji.img.attrs["alt"]
-
-
-class Smk8Entry(_BaseSmk8Entry):
- def __init__(self, page_id):
- entry_id = (page_id, 0)
- super().__init__(entry_id)
-
- def set_page(self, page):
- page = preprocess_page(page)
- super().set_page(page)
-
- def _set_headwords(self):
- soup = self.get_page_soup()
- Soup.delete_soup_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)
- self._headwords = {reading: expressions}
-
-
-class Smk8ChildEntry(_BaseSmk8Entry):
- def __init__(self, entry_id):
- super().__init__(entry_id)
-
- def _set_headwords(self):
- soup = self.get_page_soup()
- Soup.delete_soup_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)
- self._headwords = {reading: expressions}
-
-
-class Smk8PhraseEntry(_BaseSmk8Entry):
- def __init__(self, entry_id):
- super().__init__(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):
- 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]
- self._headwords = headwords
-
- def _find_expressions(self, soup):
- Soup.delete_soup_nodes(soup, "ルビG")
- 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 __init__(self, entry_id):
- super().__init__(entry_id)
-
- def _set_headwords(self):
- soup = self.get_page_soup()
- self._fill_alts(soup)
- reading = self.__get_parent_reading()
- expressions = self._find_expressions(soup)
- self._headwords = {reading: expressions}
-
- 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 82%
rename from bot/entries/smk8_preprocess.py
rename to bot/entries/smk8/preprocess.py
index 2e480a8..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": "篡",
}
@@ -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/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/base/exporter.py b/bot/mdict/exporters/base/exporter.py
new file mode 100644
index 0000000..37ed376
--- /dev/null
+++ b/bot/mdict/exporters/base/exporter.py
@@ -0,0 +1,174 @@
+import os
+import shutil
+import subprocess
+from abc import ABC, abstractmethod
+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
+
+
+class BaseExporter(ABC):
+ def __init__(self, target):
+ self._target = target
+ self._terminator = new_mdict_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)
+ 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):
+ 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")
+ print(f"{timestamp()} Initializing build directory `{build_directory}`")
+ 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(f"{timestamp()} 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):
+ 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, 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"{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 = [
+ "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)
+ for idx, entry in enumerate(entries):
+ 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:
+ terms.append(term)
+ print()
+ return terms
+
+ 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 = 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():
+ shutil.copy(icon_file, out_file)
+ 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
+ out_dir = os.path.join(
+ user_documents_dir(), "jitenbot", "mdict", self._target.value)
+ print(f"{timestamp()} Initializing output directory `{out_dir}`")
+ 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 _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)
+
+ @abstractmethod
+ def _get_revision(self, entries):
+ raise NotImplementedError
+
+ @abstractmethod
+ def _get_attribution(self, entries):
+ 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/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/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/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/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/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/base/terminator.py b/bot/mdict/terms/base/terminator.py
new file mode 100644
index 0000000..945a65b
--- /dev/null
+++ b/bot/mdict/terms/base/terminator.py
@@ -0,0 +1,83 @@
+import re
+from abc import abstractmethod, ABC
+
+
+class BaseTerminator(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.__get_full_glossary(entry)
+ terms = [[gid, glossary]]
+ keys = self.__get_keys(entry)
+ link = f"@@@LINK={gid}"
+ for key in keys:
+ if key.strip() != "":
+ terms.append([key, link])
+ 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 __get_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""
+ 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):
+ raise NotImplementedError
+
+ @abstractmethod
+ def _link_glossary_parameters(self, entry):
+ raise NotImplementedError
+
+ @abstractmethod
+ def _subentry_lists(self, entry):
+ raise NotImplementedError
diff --git a/bot/mdict/terms/daijirin2.py b/bot/mdict/terms/daijirin2.py
new file mode 100644
index 0000000..640b520
--- /dev/null
+++ b/bot/mdict/terms/daijirin2.py
@@ -0,0 +1,23 @@
+from bot.mdict.terms.base.terminator import BaseTerminator
+from bot.mdict.glossary.daijirin2 import make_glossary
+
+
+class Terminator(BaseTerminator):
+ 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/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
new file mode 100644
index 0000000..71a3b8f
--- /dev/null
+++ b/bot/mdict/terms/sankoku8.py
@@ -0,0 +1,23 @@
+from bot.mdict.terms.base.terminator import BaseTerminator
+from bot.mdict.glossary.sankoku8 import make_glossary
+
+
+class Terminator(BaseTerminator):
+ 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/smk8.py b/bot/mdict/terms/smk8.py
new file mode 100644
index 0000000..ef2b7a2
--- /dev/null
+++ b/bot/mdict/terms/smk8.py
@@ -0,0 +1,24 @@
+from bot.mdict.terms.base.terminator import BaseTerminator
+from bot.mdict.glossary.smk8 import make_glossary
+
+
+class Terminator(BaseTerminator):
+ 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/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""
- else:
- markup = f""
- 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/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/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/export.py b/bot/yomichan/exporters/base/exporter.py
similarity index 57%
rename from bot/yomichan/exporters/export.py
rename to bot/yomichan/exporters/base/exporter.py
index 4658030..5e4e870 100644
--- a/bot/yomichan/exporters/export.py
+++ b/bot/yomichan/exporters/base/exporter.py
@@ -1,22 +1,27 @@
import json
import os
import shutil
+import copy
from pathlib import Path
-from datetime import datetime
+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.yomichan.terms.factory import new_terminator
+from bot.data import load_yomichan_term_schema
+from bot.factory import new_yomichan_terminator
-class Exporter:
+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
- 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"]
@@ -24,25 +29,45 @@ class Exporter:
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
+ def _get_revision(self, entries):
+ raise NotImplementedError
+
+ @abstractmethod
+ def _get_attribution(self, entries):
+ raise NotImplementedError
+
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, "yomichan_build")
+ print(f"{timestamp()} Initializing build directory `{build_directory}`")
if Path(build_directory).is_dir():
shutil.rmtree(build_directory)
os.makedirs(build_directory)
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)
if image_dir is not None:
- print("Copying image 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)
@@ -51,7 +76,7 @@ class Exporter:
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:
@@ -59,8 +84,29 @@ class Exporter:
print()
return terms
+ def __validate_terms(self, terms):
+ 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()
+ schema = load_yomichan_term_schema()
+ validator = fastjsonschema.compile(schema)
+ failure_count = 0
+ for idx, term in enumerate(terms_copy):
+ update = f"\tValidating 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"\n{timestamp()} Finished validating with {failure_count} error{'' if failure_count == 1 else 's'}")
+ if failure_count > 0:
+ print(f"{timestamp()} 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)
@@ -68,14 +114,18 @@ class Exporter:
self.__rm_build_dir()
def __write_term_banks(self, 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"\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)
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()
@@ -93,7 +143,8 @@ class Exporter:
def __write_archive(self, filename):
archive_format = "zip"
- out_dir = os.path.join(user_documents_dir(), "jitenbot")
+ 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)
out_file = f"{filename}.{archive_format}"
@@ -103,67 +154,8 @@ class Exporter:
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()
shutil.rmtree(build_dir)
-
-
-class JitenonExporter(Exporter):
- def __init__(self, target):
- super().__init__(target)
-
- 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):
- def __init__(self, target):
- super().__init__(target)
-
-
-class JitenonYojiExporter(JitenonExporter):
- def __init__(self, target):
- super().__init__(target)
-
-
-class JitenonKotowazaExporter(JitenonExporter):
- def __init__(self, target):
- super().__init__(target)
-
-
-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}"
-
- def _get_attribution(self, entries):
- return "© Sanseido Co., LTD. 2020"
-
-
-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}"
-
- def _get_attribution(self, entries):
- return "© Sanseido Co., LTD. 2019"
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 5ab9a6a..0000000
--- a/bot/yomichan/exporters/factory.py
+++ /dev/null
@@ -1,18 +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
-
-
-def new_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/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 f2b6f2c..178de00 100644
--- a/bot/yomichan/glossary/daijirin2.py
+++ b/bot/yomichan/glossary/daijirin2.py
@@ -1,14 +1,15 @@
import re
import os
-from bs4 import BeautifulSoup
from functools import cache
from pathlib import Path
-import bot.icons as Icons
+from bs4 import BeautifulSoup
+
+import bot.yomichan.glossary.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 +27,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)
@@ -111,8 +112,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 +151,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 +175,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 +199,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 +223,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/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/icons.py b/bot/yomichan/glossary/icons.py
similarity index 69%
rename from bot/icons.py
rename to bot/yomichan/glossary/icons.py
index 2e83821..4cf46c2 100644
--- a/bot/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"""
+"""
+ return svg.strip()
diff --git a/bot/yomichan/glossary/jitenon.py b/bot/yomichan/glossary/jitenon.py
index 6e3a192..61111b4 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
@@ -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()
@@ -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/sankoku8.py b/bot/yomichan/glossary/sankoku8.py
new file mode 100644
index 0000000..ceca5d7
--- /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: super; margin-left: -0.25em;"
+
+
+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/glossary/smk8.py b/bot/yomichan/glossary/smk8.py
index 870c3fc..f17ee42 100644
--- a/bot/yomichan/glossary/smk8.py
+++ b/bot/yomichan/glossary/smk8.py
@@ -2,11 +2,11 @@ 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_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)
@@ -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,
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 76%
rename from bot/yomichan/terms/terminator.py
rename to bot/yomichan/terms/base/terminator.py
index d41a50a..f57c4cc 100644
--- a/bot/yomichan/terms/terminator.py
+++ b/bot/yomichan/terms/base/terminator.py
@@ -1,7 +1,8 @@
+from abc import abstractmethod, ABC
from bot.data import load_yomichan_inflection_categories
-class Terminator:
+class BaseTerminator(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):
+ raise NotImplementedError
+
+ @abstractmethod
+ def _inflection_rules(self, entry, expression):
+ raise NotImplementedError
+
+ @abstractmethod
+ def _glossary(self, entry):
+ raise NotImplementedError
+
+ @abstractmethod
+ def _sequence(self, entry):
+ raise NotImplementedError
+
+ @abstractmethod
+ def _term_tags(self, entry):
+ raise NotImplementedError
+
+ @abstractmethod
+ def _link_glossary_parameters(self, entry):
+ raise NotImplementedError
+
+ @abstractmethod
+ def _subentry_lists(self, entry):
+ raise NotImplementedError
diff --git a/bot/yomichan/terms/daijirin2.py b/bot/yomichan/terms/daijirin2.py
index 10aaa76..7cf06fb 100644
--- a/bot/yomichan/terms/daijirin2.py
+++ b/bot/yomichan/terms/daijirin2.py
@@ -1,14 +1,10 @@
-from bot.entries.daijirin2 import Daijirin2PhraseEntry as PhraseEntry
-
-from bot.yomichan.terms.terminator import Terminator
+from bot.entries.daijirin2.phrase_entry import PhraseEntry
+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):
- def __init__(self, target):
- super().__init__(target)
-
+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 d3fc199..0000000
--- a/bot/yomichan/terms/factory.py
+++ /dev/null
@@ -1,18 +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
-
-
-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/yomichan/terms/jitenon.py b/bot/yomichan/terms/jitenon.py
deleted file mode 100644
index f74abaa..0000000
--- a/bot/yomichan/terms/jitenon.py
+++ /dev/null
@@ -1,67 +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)
-
- 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.kankenkyuu.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
new file mode 100644
index 0000000..d6e6afd
--- /dev/null
+++ b/bot/yomichan/terms/sankoku8.py
@@ -0,0 +1,43 @@
+from bot.entries.sankoku8.phrase_entry import PhraseEntry
+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 Terminator(BaseTerminator):
+ 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/bot/yomichan/terms/smk8.py b/bot/yomichan/terms/smk8.py
index d1e3ca7..9e85c17 100644
--- a/bot/yomichan/terms/smk8.py
+++ b/bot/yomichan/terms/smk8.py
@@ -1,12 +1,11 @@
-from bot.entries.smk8 import Smk8KanjiEntry as KanjiEntry
-from bot.entries.smk8 import Smk8PhraseEntry as PhraseEntry
-
-from bot.yomichan.terms.terminator import Terminator
+from bot.entries.smk8.kanji_entry import KanjiEntry
+from bot.entries.smk8.phrase_entry import PhraseEntry
+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/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 99%
rename from data/daijirin2/phrase_readings.csv
rename to data/entries/daijirin2/phrase_readings.csv
index 97022fa..dab7e7a 100644
--- a/data/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,おもうそら
@@ -5224,7 +5224,7 @@
111520,16385,てんちょうにたっする
111583,16385,てんどうぜかひか
111583,16386,てんどうひとをころさず
-111645,16385,てんばくうをいく
+111645,16385,てんばくうをゆく
111695,16385,てんびんにかける
111790,16385,てんめいをしる
111801,16385,てんもうかいかいそにしてもらさず
@@ -5713,7 +5713,7 @@
119456,16385,なまきにくぎ
119456,16386,なまきをさく
119472,16385,なまけもののあしからとりがたつ
-119472,16386,なまけもののせっくはたらき
+119472,16386,なまけもののせっくばたらき
119503,16385,なますにたたく
119503,16386,なますをふく
119507,16385,なまずをひょうたんでおさえる
@@ -7215,7 +7215,7 @@
154782,16388,みずがはいる
154782,16389,みずがひく
154782,16390,みずかる
-154782,16391,みずきょければうおすまず
+154782,16391,みずきよければうおすまず
154782,16392,みずすむ
154782,16393,みずでわる
154782,16394,みずとあぶら
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/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 62%
rename from data/variant_kanji.csv
rename to data/entries/variant_kanji.csv
index 458c86c..0272164 100644
--- a/data/variant_kanji.csv
+++ b/data/entries/variant_kanji.csv
@@ -1,45 +1,61 @@
-俠,侠
-俱,倶
-儘,侭
-凜,凛
-剝,剥
𠮟,叱
吞,呑
+靭,靱
+臈,﨟
啞,唖
-噓,嘘
嚙,噛
+屛,屏
+幷,并
+彎,弯
+搔,掻
+攪,撹
+枡,桝
+濾,沪
+繡,繍
+蔣,蒋
+蠟,蝋
+醬,醤
+穎,頴
+鷗,鴎
+鹼,鹸
+麴,麹
+俠,侠
+俱,倶
+剝,剥
+噓,嘘
囊,嚢
塡,填
-姸,妍
-屛,屏
屢,屡
-拋,抛
-搔,掻
摑,掴
-攪,撹
-潑,溌
瀆,涜
+潑,溌
焰,焔
-禱,祷
-竜,龍
-筓,笄
簞,箪
-籠,篭
-繡,繍
繫,繋
-腁,胼
萊,莱
-藪,薮
蟬,蝉
-蠟,蝋
軀,躯
-醬,醤
醱,醗
頰,頬
顚,顛
驒,騨
-鶯,鴬
-鷗,鴎
-鷽,鴬
-鹼,鹸
-麴,麹
+姸,妍
+攢,攅
+𣜜,杤
+檔,档
+槶,椢
+櫳,槞
+纊,絋
+纘,纉
+隯,陦
+筓,笄
+逬,迸
+腁,胼
+騈,駢
+拋,抛
+篡,簒
+檜,桧
+禰,祢
+禱,祷
+蘆,芦
+凜,凛
\ No newline at end of file
diff --git a/data/mdict/css/daijirin2.css b/data/mdict/css/daijirin2.css
new file mode 100644
index 0000000..2407677
--- /dev/null
+++ b/data/mdict/css/daijirin2.css
@@ -0,0 +1,436 @@
+
+@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");
+}
+
+body {
+ /*margin: 0em 1em;*/
+ line-height: 1.5em;
+ font-family: jpmincho, 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: 200px;
+ max-width: 600px;
+}
+
+p {
+ margin: 0.5em 0
+}
+
+span[data-name="i"] {
+ font-style: italic;
+}
+
+span[data-name="h1"] {
+ font-family: jpgothic, 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="sup"] {
+ font-size: 0.7em;
+ vertical-align: super;
+}
+
+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: jpgothic, sans-serif;
+ font-size: 0.7em;
+ font-weight: normal;
+ vertical-align: 0.35em;
+ -webkit-user-select: none;
+}
+
+.warichu span[data-name="ルビG"] {
+ font-family: jpmincho, serif;
+ font-size: 0.5em;
+ font-weight: normal;
+ vertical-align: 0em;
+ -webkit-user-select: none;
+}
+
+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="句表記"] {
+ font-family: jpgothic, sans-serif;
+ font-weight: bold;
+}
+
+span[data-name="句項目"] {
+ margin-top: 0.5em;
+ margin-left: 1em;
+ display: block;
+}
+
+span[data-name="和字"] {
+ font-family: jpgothic, 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: jpgothic, 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: jpgothic, sans-serif;
+}
+
+span[data-name="歴史仮名"] {
+ font-size: 0.7em;
+ vertical-align: 0.35em;
+ -webkit-user-select: none;
+}
+
+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: jpgothic, sans-serif;
+ font-size: 0.8em;
+ border: solid 1px black;
+}
+
+span[data-name="漢字音logo"] {
+ margin: 0.025em;
+ padding: 0.1em;
+ font-family: jpgothic, 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: jpgothic, sans-serif;
+ font-weight: bold;
+}
+
+span[data-name="異字同訓漢字"] {
+ font-family: jpmincho, serif;
+ font-weight: normal;
+}
+
+span[data-name="異字同訓表記"] {
+ font-family: jpgothic, sans-serif;
+ font-weight: bold;
+}
+
+span[data-name="異字同訓解説"] {
+ display: block;
+}
+
+span[data-name="異字同訓語義G"] {
+ display: block;
+}
+
+span[data-name="細義"] {
+ display: block;
+}
+
+rt {
+ -webkit-user-select: none;
+}
+
+span[data-name="見出仮名"] {
+ font-family: jpgothic, sans-serif;
+ font-weight: bold;
+}
+
+span[data-name="見出相当部"] {
+ font-family: jpgothic, 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: jpgothic, 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-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;
+ 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..da46bf8
--- /dev/null
+++ b/data/mdict/css/jitenon-kokugo.css
@@ -0,0 +1,63 @@
+
+@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");
+}
+
+body {
+ font-family: jpmincho, serif;
+ line-height: 1.5em;
+}
+
+table, th, td {
+ border: 1px solid;
+ border-collapse: collapse;
+ padding: 0.5em;
+}
+
+th {
+ font-family: jpgothic, 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: jpgothic, sans-serif;
+ font-weight: bold;
+}
+
+.意味,
+.kanjirighttb {
+ margin-left: 1.0em;
+ margin-bottom: 0.5em;
+}
+
+.num_icon {
+ font-family: jpgothic, 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..2929f89
--- /dev/null
+++ b/data/mdict/css/jitenon-kotowaza.css
@@ -0,0 +1,47 @@
+
+@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");
+}
+
+body {
+ font-family: jpmincho, serif;
+ line-height: 1.5em;
+}
+
+table, th, td {
+ border: 1px solid;
+ border-collapse: collapse;
+ padding: 0.5em;
+}
+
+th {
+ font-family: jpgothic, sans-serif;
+ color: black;
+ background-color: lightgray;
+ font-weight: normal;
+ white-space: nowrap;
+}
+
+a {
+ text-decoration: none;
+}
+
+.見出し {
+}
+
+.読み方 {
+ font-family: jpgothic, sans-serif;
+ 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
new file mode 100644
index 0000000..2929f89
--- /dev/null
+++ b/data/mdict/css/jitenon-yoji.css
@@ -0,0 +1,47 @@
+
+@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");
+}
+
+body {
+ font-family: jpmincho, serif;
+ line-height: 1.5em;
+}
+
+table, th, td {
+ border: 1px solid;
+ border-collapse: collapse;
+ padding: 0.5em;
+}
+
+th {
+ font-family: jpgothic, sans-serif;
+ color: black;
+ background-color: lightgray;
+ font-weight: normal;
+ white-space: nowrap;
+}
+
+a {
+ text-decoration: none;
+}
+
+.見出し {
+}
+
+.読み方 {
+ font-family: jpgothic, sans-serif;
+ font-weight: bold;
+}
+
+.意味,
+.kanjirighttb {
+ margin-left: 1.0em;
+ margin-bottom: 0.5em;
+}
diff --git a/data/mdict/css/sankoku8.css b/data/mdict/css/sankoku8.css
new file mode 100644
index 0000000..47b7ec0
--- /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/css/smk8.css b/data/mdict/css/smk8.css
new file mode 100644
index 0000000..55ef814
--- /dev/null
+++ b/data/mdict/css/smk8.css
@@ -0,0 +1,450 @@
+
+@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");
+}
+
+body {
+ /*margin: 0em 1em;*/
+ line-height: 1.5em;
+ font-family: jpmincho, 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: jpgothic, sans-serif;
+ font-weight: bold;
+}
+
+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="解説部"] {
+ 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;
+ -webkit-user-select: none;
+}
+
+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="アクセント"],
+span[data-name="アクセント組M"] {
+ font-size: 0.7em;
+ vertical-align: super;
+}
+
+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: jpmincho, serif;
+ font-size: 0.7em;
+ font-weight: normal;
+ vertical-align: 0.35em;
+ -webkit-user-select: none;
+}
+
+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: jpgothic, sans-serif;
+ font-weight: bold;
+}
+
+span[data-name="rect"] {
+ padding: 0.1em;
+ font-family: jpgothic, 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: jpgothic, 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="言換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: jpgothic, 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-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;
+ 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/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/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 0000000..1ef1eb3
Binary files /dev/null and b/data/mdict/icon/jitenon-kokugo.png differ
diff --git a/data/mdict/icon/jitenon-kotowaza.png b/data/mdict/icon/jitenon-kotowaza.png
new file mode 100644
index 0000000..15ccb92
Binary files /dev/null and b/data/mdict/icon/jitenon-kotowaza.png differ
diff --git a/data/mdict/icon/jitenon-yoji.png b/data/mdict/icon/jitenon-yoji.png
new file mode 100644
index 0000000..0603db0
Binary files /dev/null and b/data/mdict/icon/jitenon-yoji.png differ
diff --git a/data/mdict/name_conversion/daijirin2.json b/data/mdict/name_conversion/daijirin2.json
new file mode 100644
index 0000000..d783d28
--- /dev/null
+++ b/data/mdict/name_conversion/daijirin2.json
@@ -0,0 +1,12 @@
+{
+ "a": {},
+ "br": {},
+ "img": {},
+ "div": {},
+ "span": {},
+ "ruby": {},
+ "rt": {},
+ "p": {},
+ "漢字音G": {"name": "ul"},
+ "漢字音": {"name": "li"}
+}
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/name_conversion/smk8.json b/data/mdict/name_conversion/smk8.json
new file mode 100644
index 0000000..b9a4387
--- /dev/null
+++ b/data/mdict/name_conversion/smk8.json
@@ -0,0 +1,25 @@
+{
+ "a": {},
+ "br": {},
+ "img": {},
+ "div": {},
+ "span": {},
+ "表外字": {
+ "name": "ruby"
+ },
+ "表外字マーク": {
+ "name": "rt"
+ },
+ "表外音訓": {
+ "name": "ruby"
+ },
+ "表外音訓マーク": {
+ "name": "rt"
+ },
+ "表音式": {
+ "name": "ruby"
+ },
+ "表音表記": {
+ "name": "rt"
+ }
+}
diff --git a/data/mdict/title/daijirin2.mdx.title.html b/data/mdict/title/daijirin2.mdx.title.html
new file mode 100644
index 0000000..43fdfd7
--- /dev/null
+++ b/data/mdict/title/daijirin2.mdx.title.html
@@ -0,0 +1 @@
+大辞林 第四版
diff --git a/data/mdict/title/jitenon-kokugo.mdx.title.html b/data/mdict/title/jitenon-kokugo.mdx.title.html
new file mode 100644
index 0000000..3fee892
--- /dev/null
+++ b/data/mdict/title/jitenon-kokugo.mdx.title.html
@@ -0,0 +1 @@
+国語辞典オンライン
diff --git a/data/mdict/title/jitenon-kotowaza.mdx.title.html b/data/mdict/title/jitenon-kotowaza.mdx.title.html
new file mode 100644
index 0000000..438ffaf
--- /dev/null
+++ b/data/mdict/title/jitenon-kotowaza.mdx.title.html
@@ -0,0 +1 @@
+故事・ことわざ・慣用句オンライン
diff --git a/data/mdict/title/jitenon-yoji.mdx.title.html b/data/mdict/title/jitenon-yoji.mdx.title.html
new file mode 100644
index 0000000..456dc99
--- /dev/null
+++ b/data/mdict/title/jitenon-yoji.mdx.title.html
@@ -0,0 +1 @@
+四字熟語辞典オンライン
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/mdict/title/smk8.mdx.title.html b/data/mdict/title/smk8.mdx.title.html
new file mode 100644
index 0000000..9f41cd1
--- /dev/null
+++ b/data/mdict/title/smk8.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_metadata.json b/data/yomichan/index.json
similarity index 87%
rename from data/yomichan_metadata.json
rename to data/yomichan/index.json
index 3fa8cb4..45e2dc6 100644
--- a/data/yomichan_metadata.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
similarity index 78%
rename from data/yomichan_inflection_categories.json
rename to 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/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/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/data/smk8/yomichan_name_conversion.json b/data/yomichan/name_conversion/smk8.json
similarity index 84%
rename from data/smk8/yomichan_name_conversion.json
rename to data/yomichan/name_conversion/smk8.json
index 82c491f..91a6593 100644
--- a/data/smk8/yomichan_name_conversion.json
+++ b/data/yomichan/name_conversion/smk8.json
@@ -121,25 +121,31 @@
"style": "font-weight: bold;"
},
"表外字": {
- "name": "ruby",
+ "name": "ruby"
+ },
+ "表外字マーク": {
+ "name": "rt",
"procedures": [
{
- "procedure_name": "add_ruby_text",
+ "procedure_name": "insert_span",
"parameters": {
- "mark": "︿",
- "style": "font-size: 2em;"
+ "attr_name": "style",
+ "attr_val": "font-size: 2em;"
}
}
]
},
"表外音訓": {
- "name": "ruby",
+ "name": "ruby"
+ },
+ "表外音訓マーク": {
+ "name": "rt",
"procedures": [
{
- "procedure_name": "add_ruby_text",
+ "procedure_name": "insert_span",
"parameters": {
- "mark": "︽",
- "style": "font-size: 2em;"
+ "attr_name": "style",
+ "attr_val": "font-size: 2em;"
}
}
]
@@ -148,23 +154,7 @@
"name": "ruby"
},
"表音表記": {
- "name": "rt",
- "procedures": [
- {
- "procedure_name": "replace",
- "parameters": {
- "old": "(",
- "new": ""
- }
- },
- {
- "procedure_name": "replace",
- "parameters": {
- "old": ")",
- "new": ""
- }
- }
- ]
+ "name": "rt"
},
"派生見出": {
"name": "span",
diff --git a/jitenbot.py b/jitenbot.py
index 0a25b96..f0a2719 100644
--- a/jitenbot.py
+++ b/jitenbot.py
@@ -17,9 +17,20 @@ along with this program. If not, see .
"""
import os
+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):
+ 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):
@@ -35,34 +46,78 @@ 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-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(
+ "--validate-yomichan-terms",
+ help="validate JSON structure of exported Yomichan dictionary terms",
+ 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, args.validate_yomichan_terms)
+ 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..b19e14b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,17 +1,25 @@
+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
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 100755
index 0000000..9dcdfda
--- /dev/null
+++ b/run_all.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+export PYTHONPYCACHEPREFIX=/tmp/pycache
+
+python -m unittest discover -s tests
+
+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
+
+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_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..9091dda 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):
@@ -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)
@@ -69,28 +87,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
new file mode 100644
index 0000000..c3894e9
--- /dev/null
+++ b/tests/test_sankoku_phrases.py
@@ -0,0 +1,46 @@
+import unittest
+from bot.entries.sankoku8.parse import parse_hyouki_pattern
+
+
+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 test2(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 test3(self):
+ pattern = '{かじ・舵}を切る'
+ exps = parse_hyouki_pattern(pattern)
+ self.assertEqual(len(exps), 2)
+ self.assertIn("かじを切る", exps)
+ self.assertIn("舵を切る", exps)
+
+ def test4(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 test5(self):
+ pattern = '群盲象を〈{な・撫}でる/評する〉'
+ exps = parse_hyouki_pattern(pattern)
+ self.assertEqual(len(exps), 3)
+ self.assertIn("群盲象をなでる", exps)
+ self.assertIn("群盲象を撫でる", exps)
+ self.assertIn("群盲象を評する", exps)
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)