anker/src/Controller/KanjiController.php

187 lines
5.9 KiB
PHP

<?php
namespace App\Controller;
use App\Entity\UnicodeNote;
use App\Service\AnkiService;
use App\Service\CharListService;
use App\Utils\Japanese;
use App\Utils\Number;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\VarExporter\VarExporter;
#[Route('/kanji', name: 'app_kanji_')]
class KanjiController extends AbstractController
{
function __construct(
private AnkiService $anki,
private CharListService $charList,
private string $varBasepath,
) {}
public static function tmpl(string $path): string
{
return "kanji/$path.html.twig";
}
/** @return array<string, string> */
public function getIgnoredList(): array
{
$path = "{$this->getParameter('kernel.project_dir')}/data/ignored.list";
$ret = [];
$file = fopen($path, 'r');
while (($kanji = fgets($file)) !== false) {
$kanji = mb_trim($kanji);
$ret[$kanji] = '';
if (mb_strlen($kanji) !== 1) throw new \Exception(sprintf(
'Kanji expected, got "%s"',
$kanji,
));
while (($explanation = fgets($file)) !== false) {
if ($explanation === "\n") continue 2;
$ret[$kanji] .= $explanation;
}
$ret[$kanji] = mb_rtrim($ret[$kanji]);
}
return $ret;
}
/** @return array<string, array<string, 0>> */
private function getCharInfo(bool $force = false): array
{
$cacheFile = "$this->varBasepath/charlist.php";
if (!$force and file_exists($cacheFile)) return require $cacheFile;
$ret = [];
$lists = [
'sn' => $this->anki->getKnownSnKanjiCounts(),
'sln' => $this->anki->getKnownSlnKanjiCounts(),
'unicode' => $this->anki->getUnicodeKanji(),
'ignored' => $this->getIgnoredList(),
];
// @formatter:off
$storedLists = [
'taiwan', 'bushu', 'kyoyou', 'kyuujitai', 'kanken', 'hsk', 'zhcn',
'jimaku', 'ebook',
];
// @formatter:on
foreach ($storedLists as $storedName) {
$storedList = $this->charList->getList($storedName);
// Simple list
if (key_exists('chars', $storedList)) {
$lists[$storedName] = $storedList['chars'];
} else {
foreach ($storedList['sublists'] as $subname => $sublist) {
$lists["$storedName-$subname"] = $sublist['chars'];
}
}
}
foreach ($lists as $listName => $list) {
foreach (array_keys($list) as $char) {
$ret[$char] ??= [];
$ret[$char][$listName] = 0;
}
}
file_put_contents($cacheFile, '<?php return ' . VarExporter::export($ret) . ';');
return $ret;
}
#[Route('/{start}-{end}', name: 'index')]
public function index(
string $start,
string $end,
Request $request,
): Response {
$charInfo = $this->getCharInfo(force: $request->isNoCache());
$completedRows = [];
$completedFlag = true;
$chars = [];
foreach (range(intval("{$start}0", 16), intval("{$end}f", 16)) as $codepoint) {
$charStr = mb_chr($codepoint, 'UTF-8');
if (
!key_exists('ignored', $charInfo[$charStr] ?? ['ignored' => 0])
and !key_exists('unicode', $charInfo[$charStr] ?? [])
) {
$completedFlag = false;
}
if (($codepoint + 1) % 0x10 === 0) {
if ($completedFlag === true) $completedRows[] = dechex($codepoint - 0xf);
$completedFlag = true;
}
$chars[] = [
'str' => $charStr,
'codepoint' => dechex($codepoint),
'lists' => $charInfo[$charStr] ?? [],
];
}
return $this->render(self::tmpl('grid'), [
'characters' => $chars,
'completed' => $completedRows,
]);
}
#[Route('/{codepoint}/register', 'register', methods: 'GET')]
public function register(string $codepoint): Response
{
// Properly check it's valid (make a util?)
$charStr = mb_chr(Number::hexint($codepoint), 'UTF-8');
$note = UnicodeNote::fromCharacter($charStr);
$terms = $this->anki->searchTerms($charStr);
$note->setTerms($terms);
$this->anki->addNote($note, UnicodeNote::DECK);
return new Response();
}
#[Route('view', 'view', methods: 'GET')]
public function view(#[MapQueryParameter] string $char): Response
{
$charStr = ctype_xdigit($char) ? Number::parseCodepoint($char) : $char;
$codepoint = ctype_xdigit($char) ? $char : dechex(mb_ord($char));
$charInfo = $this->getCharInfo()[$charStr] ?? [];
//$listTitles = $this->charList->getTitles();
$jiten = json_decode(file_get_contents(
"{$this->getParameter('kernel.project_dir')}/data/kanken-links.json",
), true);
$terms = $this->anki->searchTerms($charStr);
$ebookRef = require "$this->varBasepath/ebook-ref.php";
return $this->render(self::tmpl('view'), [
'ignored_text' => $this->getIgnoredList()[$charStr] ?? null,
'char' => $charStr,
'codepoint' => $codepoint,
'info' => $charInfo,
'terms' => $terms,
'ref' => $ebookRef[$charStr] ?? [],
'jiten_href' => key_exists($charStr, $jiten)
? "https://kanji.jitenon.jp/kanji{$jiten[$charStr]}"
: null
]);
}
}