anker/src/Entity/Note.php

186 lines
5.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Entity;
//use App\Repository\NoteRepository;
//use Doctrine\ORM\Mapping as ORM;
//#[ORM\Entity(repositoryClass: NoteRepository::class)]
class Note
{
//#[ORM\Id]
//#[ORM\GeneratedValue]
//#[ORM\Column]
private int $id;
private int $mod;
private array $terms = [];
private string $profile;
private array $tags = [];
private string $model;
// Maybe these doesn't make sense to keep but leaving it here just in
// case for handiness' sake
private array $fields = [];
private ?array $mediaInfo = null;
private array $cardIds;
const HIGHLIGHT_PATTERN = '/<span\s+([^>]*)>(.*?)<\/span>/i';
const HIGHLIGHT_ATTR_KANJI = 'style="color: rgb(255, 78, 8);"';
public function getId(): int
{
return $this->id;
}
public function hasTerm(string $kanji): bool
{
foreach ($this->terms as $term) {
assert($term instanceof Term);
if ($term->kanji == $kanji) return true;
}
return false;
}
public static function fromAnki(array $noteInfo): self
{
$note = new self();
[
'noteId' => $note->id,
'mod' => $note->mod,
'profile' => $note->profile,
'tags' => $note->tags,
'modelName' => $note->model,
'cards' => $note->cardIds,
] = $noteInfo;
// the fields array key value comes with an order fields that is
// already maintained by PHP since arrays are ordered dictionaries.
// So we can safely just drop it.
//
// REVIEW: Having said that, maybe ordering the array before throwing
// the order would be advisable.
$note->fields = array_map(fn($x) => $x['value'], $noteInfo['fields']);
$note->mediaInfo = $note->parseMediaInfo($note->fields['Notes']);
// Set VocabKanji field
$note->terms = Term::fromNoteFields($note->fields);
// If not defined, find them from the highlighted parts in the sentence
if (empty($note->terms)) {
// 1. Get all spans in the text
preg_match_all(
self::HIGHLIGHT_PATTERN,
$note->fields['SentKanji'],
$matches,
PREG_SET_ORDER,
);
// 2. Check the ones that match with the kanji color
foreach ($matches as $match) {
if ($match[1] === self::HIGHLIGHT_ATTR_KANJI) {
$term = new Term();
$term->kanji = mb_trim($match[2]);
$term->definitionEn = null;
$term->definitionJp = null;
$note->terms[] = $term;
}
}
}
// Set to null whatever is null
$readings = array_map(
fn($x) => in_array($x, ['_', '_', '']) ? null : $x,
explode('', $note->fields['VocabFurigana']),
);
// Set readings from furigana field
foreach ($note->terms as $key => &$term) {
if (null === $term->getReading()) {
if (null !== ($readings[$key] ?? null)) {
$term->kanji .= '[' . $readings[$key] . ']';
}
}
}
return $note;
}
public function toAnki(): array
{
return [
'id' => $this->id,
'fields' => [
'VocabKanji' => join('', array_map(
fn(Term $x) => $x->getKanji(),
$this->terms,
)),
'VocabFurigana' => join('', array_map(
fn(Term $x) => $x->getReading() ?? '_',
$this->terms,
)),
'VocabDef' => join("<br>\n", array_map(
fn(Term $x) => $x->toAnkiVocabDef(),
$this->terms,
)),
],
];
}
public function parseMediaInfo(string $notes): ?array
{
$matches = null;
// Parse the notes fields. It can be in the form of
// series-name_S01 EP07 (11h22m33s44ms)
// or
// movie-name EP (11h22m33s44ms)
if (1 !== preg_match(
'/(?<name>[a-z\-_]+)(_S)?(?<season>\d*) EP(?<episode>\d*) \((?<time>.*)\)/n',
$notes,
$matches,
)) {
return null;
}
// Remove number-indexed matches, cast numbers to integers
$matches = [
'name' => $matches['name'],
'time' => $matches['time'],
// NOTE: intval returns 0 if not a number, which is false-like
'season' => intval($matches['season']) ?: null,
'episode' => intval($matches['episode']) ?: null,
];
// Parse time into a DateInterval and replace it in the matches array
$time = new \DateInterval('PT0S');
preg_match('/(\d+)ms/', $matches['time'], $milliseconds);
preg_match('/(\d+)s/', $matches['time'], $seconds);
preg_match('/(\d+)m/', $matches['time'], $minutes);
preg_match('/(\d+)h/', $matches['time'], $hours);
if ($milliseconds[1] ?? false) $time->f = $milliseconds[1] * 1000;
if ($seconds[1] ?? false) $time->s = $seconds[1];
if ($minutes[1] ?? false) $time->i = $minutes[1];
if ($hours[1] ?? false) $time->h = $hours[1];
$matches['time'] = $time;
return $matches;
}
public function getCreatedAt(): \DateTimeImmutable
{
$timestamp = ceil($this->id / 1000);
return \DateTimeImmutable::createFromFormat('U', $timestamp);
}
public function getUpdatedAt(): \DateTimeImmutable
{
return \DateTimeImmutable::createFromFormat('U', $this->mod);
}
}