Add export support for the MDict dictionary format
This commit is contained in:
parent
e4a2e75d82
commit
4c837cd72d
53 changed files with 2233 additions and 275 deletions
77
bot/mdict/glossary/daijirin2.py
Normal file
77
bot/mdict/glossary/daijirin2.py
Normal file
|
|
@ -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}")
|
||||
141
bot/mdict/glossary/jitenon.py
Normal file
141
bot/mdict/glossary/jitenon.py
Normal file
|
|
@ -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
|
||||
67
bot/mdict/glossary/smk8.py
Normal file
67
bot/mdict/glossary/smk8.py
Normal file
|
|
@ -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 = "**"
|
||||
Loading…
Reference in a new issue