kanji)['reading'];
}
public function getKanji(): string
{
return self::parseFurigana($this->kanji)['kanji'];
}
public static function parseFurigana(string $furigana): array
{
// 0: all, 1: (kanji/hiragana), 2: ([reading]): 3: (reading)
preg_match_all('/([^ \[]+)(\[([^\]]*)\])? ?/', $furigana, $matches, PREG_SET_ORDER);
$matchedKanji = array_map(fn($x) => $x[1], $matches);
$matchedReading = array_map(fn($x) => $x[3] ?? $x[1], $matches);
return [
'kanji' => join('', $matchedKanji),
'reading' => $matchedKanji == $matchedReading
? null
: join('', $matchedReading),
];
}
public function toAnkiVocabDef()
{
$ret = '' . $this->kanji;
$ret .= match ([null !== $this->definitionJp, null !== $this->definitionEn]) {
[false, false] => ':_',
[false, true] => ': ' . $this->definitionEn,
[true, false] => ':' . $this->definitionJp,
[true, true] => ':' . $this->definitionJp . '(' . $this->definitionEn . ')',
};
return $ret;
}
private static function fromVocabDefLine(string $vocabDefLine): ?Term
{
$term = new Term();
// ------------------------------------------------------ Get Kanji ---
$jpStart = mb_strpos($vocabDefLine, ':');
$enStart = mb_strpos($vocabDefLine, ':');
// Get the kanji, as it may not be in the same order for some reason
if (false !== $jpStart) {
$term->kanji = mb_substr($vocabDefLine, 0, $jpStart);
$def = mb_substr($vocabDefLine, $jpStart + 1, null);
$jpStart = 0;
} elseif (false !== $enStart) {
$term->kanji = mb_substr($vocabDefLine, 0, $enStart);
$def = mb_substr($vocabDefLine, $enStart + 1, null);
$enStart = 0;
} else {
// Can't extract term from definition, it doesn't conform to the
// established pattern.
return null;
}
// Convert 「this」 into [this]
$term->kanji = mb_trim(strtr($term->kanji, [
'「' => '[',
'」' => ']',
' ' => ' ',
]));
$def = mb_trim($def);
if (!is_string($term->kanji)) {
return null;
}
// -------------------------------------------------- No definition ---
// Special case where there's no definitions
if ($def === '' or $def === '_' or $def === '_') {
$term->definitionJp = null;
$term->definitionEn = null;
return $term;
}
// This means there's both en and jp
$parentStart = mb_strpos($def, '(');
// -------------------------------------------------- Only Japanese ---
if (false !== $jpStart and false === $parentStart) {
// It's all japanese, start to finish
$term->definitionJp = mb_trim(mb_substr($def, 0));
$term->definitionEn = null;
return $term;
}
// -------------------------------------- Both Japanese and English ---
if (false !== $jpStart and false !== $parentStart) {
$term->definitionJp = mb_trim(mb_substr($def, 0, $parentStart));
// -1 to remove the parenthesis end
$term->definitionEn = mb_trim(mb_substr($def, $parentStart + 1, -1));
return $term;
}
// --------------------------------------------------- Only english ---
if (false !== $enStart) {
$term->definitionJp = null;
$term->definitionEn = mb_trim(mb_substr($def, 0));
return $term;
}
// ------------------------------------------------- Unvalid syntax ---
dd("Unexpected error, couldn't parse definition line", $vocabDefLine);
}
/** Turns a def like "" turn into ":" */
private static function fromLegacyLine(string $kanji, string $def): ?Term
{
// Select appropriate semicolon for the job
$separator = preg_match('/[[:alpha:]]/u', $def)
? ': '
: ':';
// Stick the kanji at the start and see if it makes sense
return Term::fromVocabDefLine($kanji . $separator . $def);
}
public static function fromNoteFields(array $fields): ?array
{
// -------------------- Trying to extract it with the modern syntax ---
// 言葉: word
// 上げる:上に動くこと。
// 雨:水粒を降ること。(rain)
$terms = [];
foreach (preg_split('|
|', $fields['VocabDef']) as $line) {
$terms[] = self::fromVocabDefLine(strip_tags($line));
};
// If there's no nulls, everything went good
if (!in_array(null, $terms, true)) return $terms;
// ------------ Extracting failed, try to infer from other syntaxes ---
$kanjis = explode('|', $fields['VocabKanji']);
$defs = explode('|', $fields['VocabDef']);
// Number of legacy definitions is different from number of kanji
if (count($kanjis) !== count($defs)) return null;
$terms = [];
foreach (array_combine($kanjis, $defs) as $kanji => $def) {
$terms[] = self::fromLegacyLine($kanji, $def);
}
// Search for nulls, if found, it's owari da
return in_array(null, $terms, true) ? null : $terms;
}
}