lib: implement basic kanji search

This commit is contained in:
2025-04-19 01:20:48 +02:00
parent 23f90c1127
commit 68eb46174f
6 changed files with 342 additions and 11 deletions

View File

@@ -0,0 +1,40 @@
import 'package:equatable/equatable.dart';
class KanjiSearchRadical extends Equatable {
/// The radical symbol.
final String symbol;
/// The radical forms used in this kanji.
final List<String> forms;
/// The meaning of the radical.
final String meaning;
// ignore: public_member_api_docs
const KanjiSearchRadical({
required this.symbol,
this.forms = const [],
required this.meaning,
});
@override
List<Object> get props => [
symbol,
forms,
meaning,
];
Map<String, dynamic> toJson() => {
'symbol': symbol,
'forms': forms,
'meaning': meaning,
};
factory KanjiSearchRadical.fromJson(Map<String, dynamic> json) {
return KanjiSearchRadical(
symbol: json['symbol'] as String,
forms: (json['forms'] as List).map((e) => e as String).toList(),
meaning: json['meaning'] as String,
);
}
}

View File

@@ -1,3 +1,117 @@
class KanjiSearchResult {
// TODO: implement me
import 'package:equatable/equatable.dart';
import 'kanji_search_radical.dart';
class KanjiSearchResult extends Equatable {
/// The kanji symbol
final String kanji;
/// The school level that the kanji is taught in, if applicable.
final String? taughtIn;
/// The lowest JLPT exam that this kanji is likely to appear in, if applicable.
///
/// 'N5' or 'N4' or 'N3' or 'N2' or 'N1'.
final String? jlptLevel;
/// A number representing this kanji's frequency rank in newspapers, if applicable.
final int? newspaperFrequencyRank;
/// How many strokes this kanji is typically drawn in.
final int strokeCount;
/// The meanings of the kanji.
final List<String> meanings;
/// This character's kunyomi.
final List<String> kunyomi;
/// This character's onyomi.
final List<String> onyomi;
// /// Examples of this character's kunyomi being used.
// final List<YomiExample> kunyomiExamples;
// /// Examples of this character's onyomi being used.
// final List<YomiExample> onyomiExamples;
/// Information about this character's radical, if applicable.
final KanjiSearchRadical? radical;
// TODO: document more accurately what kind of parts?
/// The parts used in this kanji.
final List<String> parts;
const KanjiSearchResult({
required this.kanji,
this.taughtIn,
this.jlptLevel,
this.newspaperFrequencyRank,
required this.strokeCount,
required this.meanings,
this.kunyomi = const [],
this.onyomi = const [],
// this.kunyomiExamples = const [],
// this.onyomiExamples = const [],
this.radical,
this.parts = const [],
});
@override
// ignore: public_member_api_docs
List<Object?> get props => [
taughtIn,
jlptLevel,
newspaperFrequencyRank,
strokeCount,
meanings,
kunyomi,
onyomi,
// kunyomiExamples,
// onyomiExamples,
radical,
parts,
];
Map<String, dynamic> toJson() => {
'kanji': kanji,
'taughtIn': taughtIn,
'jlptLevel': jlptLevel,
'newspaperFrequencyRank': newspaperFrequencyRank,
'strokeCount': strokeCount,
'meanings': meanings,
'kunyomi': kunyomi,
'onyomi': onyomi,
// 'onyomiExamples': onyomiExamples,
// 'kunyomiExamples': kunyomiExamples,
'radical': radical?.toJson(),
'parts': parts,
// 'strokeOrderDiagramUri': strokeOrderDiagramUri,
// 'strokeOrderSvgUri': strokeOrderSvgUri,
// 'strokeOrderGifUri': strokeOrderGifUri,
// 'uri': uri,
};
factory KanjiSearchResult.fromJson(Map<String, dynamic> json) {
return KanjiSearchResult(
kanji: json['kanji'] as String,
taughtIn: json['taughtIn'] as String?,
jlptLevel: json['jlptLevel'] as String?,
newspaperFrequencyRank: json['newspaperFrequencyRank'] as int?,
strokeCount: json['strokeCount'] as int,
meanings: (json['meanings'] as List).map((e) => e as String).toList(),
kunyomi: (json['kunyomi'] as List).map((e) => e as String).toList(),
onyomi: (json['onyomi'] as List).map((e) => e as String).toList(),
// kunyomiExamples: (json['kunyomiExamples'] as List)
// .map((e) => YomiExample.fromJson(e))
// .toList(),
// onyomiExamples: (json['onyomiExamples'] as List)
// .map((e) => YomiExample.fromJson(e))
// .toList(),
radical: json['radical'] != null
? KanjiSearchRadical.fromJson(json['radical'])
: null,
parts: (json['parts'] as List).map((e) => e as String).toList(),
);
}
}

View File

@@ -2,14 +2,23 @@ import 'package:jadb/models/jmdict/word_search_result.dart';
import 'package:jadb/models/kanjidic/kanji_search_result.dart';
import 'package:jadb/models/radkfile/radicals_search_result.dart';
Future<KanjiSearchResult> searchKanji(String kanji) async {
throw UnimplementedError();
}
import 'package:jadb/search/kanji.dart';
Future<RadicalsSearchResult> searchKanjiByRadicals(List<String> radicals) {
throw UnimplementedError();
}
import 'package:sqflite_common/sqlite_api.dart';
Future<WordSearchResult> searchWord(String word) async {
throw UnimplementedError();
class JaDBConnection {
final DatabaseExecutor _connection;
const JaDBConnection(this._connection);
Future<KanjiSearchResult?> searchKanji(String kanji) async =>
searchKanjiWithDbConnection(this._connection, kanji);
Future<RadicalsSearchResult> searchKanjiByRadicals(List<String> radicals) {
throw UnimplementedError();
}
Future<WordSearchResult> searchWord(String word) async {
throw UnimplementedError();
}
}

158
lib/search/kanji.dart Normal file
View File

@@ -0,0 +1,158 @@
import 'package:jadb/models/kanjidic/kanji_search_result.dart';
import 'package:sqflite_common/sqflite.dart';
Future<KanjiSearchResult?> searchKanjiWithDbConnection(
DatabaseExecutor connection,
String kanji,
) async {
late final List<Map<String, Object?>> characters;
final characters_query = connection.query(
"KANJIDIC_Character",
where: "KANJIDIC_Character.literal = ?",
whereArgs: [kanji],
);
late final List<Map<String, Object?>> codepoints;
final codepoints_query = connection.query(
"KANJIDIC_Codepoint",
where: "KANJIDIC_Codepoint.kanji = ?",
whereArgs: [kanji],
);
late final List<Map<String, Object?>> kunyomis;
final kunyomis_query = connection.query(
"KANJIDIC_Kunyomi",
where: "KANJIDIC_Kunyomi.kanji = ?",
whereArgs: [kanji],
);
late final List<Map<String, Object?>> onyomis;
final onyomis_query = connection.query(
"KANJIDIC_Onyomi",
where: "KANJIDIC_Onyomi.kanji = ?",
whereArgs: [kanji],
);
late final List<Map<String, Object?>> meanings;
final meanings_query = connection.query(
"KANJIDIC_Meaning",
where: "KANJIDIC_Meaning.kanji = ? AND KANJIDIC_Meaning.language = ?",
whereArgs: [kanji, 'eng'],
);
late final List<Map<String, Object?>> nanoris;
final nanoris_query = connection.query(
"KANJIDIC_Nanori",
where: "KANJIDIC_Nanori.kanji = ?",
whereArgs: [kanji],
);
late final List<Map<String, Object?>> dictionary_references;
final dictionary_references_query = connection.query(
"KANJIDIC_DictionaryReference",
where: "KANJIDIC_DictionaryReference.kanji = ?",
whereArgs: [kanji],
);
late final List<Map<String, Object?>> query_codes;
final query_codes_query = connection.query(
"KANJIDIC_QueryCode",
where: "KANJIDIC_QueryCode.kanji = ?",
whereArgs: [kanji],
);
late final List<Map<String, Object?>> radicals;
final radicals_query = connection.query(
"KANJIDIC_Radical",
where: "KANJIDIC_Radical.kanji = ?",
whereArgs: [kanji],
);
late final List<Map<String, Object?>> radical_names;
final radical_names_query = connection.query(
"KANJIDIC_RadicalName",
where: "KANJIDIC_RadicalName.kanji = ?",
whereArgs: [kanji],
);
late final List<Map<String, Object?>> readings;
final readings_query = connection.query(
"KANJIDIC_Reading",
where: "KANJIDIC_Reading.kanji = ?",
whereArgs: [kanji],
);
late final List<Map<String, Object?>> stroke_miscounts;
final stroke_miscounts_query = connection.query(
"KANJIDIC_StrokeMiscount",
where: "KANJIDIC_StrokeMiscount.kanji = ?",
whereArgs: [kanji],
);
late final List<Map<String, Object?>> variants;
final variants_query = connection.query(
"KANJIDIC_Variant",
where: "KANJIDIC_Variant.kanji = ?",
whereArgs: [kanji],
);
// TODO: Search for kunyomi and onyomi usage of the characters
// from JMDict. We'll need to fuzzy aquery JMDict_KanjiElement for mathces,
// filter JMdict_ReadingElement for kunyomi/onyomi, and then sort the main entry
// by JLPT, news frequency, etc.
await characters_query.then((value) => characters = value);
if (characters.isEmpty) {
return null;
}
await Future.wait({
codepoints_query.then((value) => codepoints = value),
kunyomis_query.then((value) => kunyomis = value),
onyomis_query.then((value) => onyomis = value),
meanings_query.then((value) => meanings = value),
nanoris_query.then((value) => nanoris = value),
dictionary_references_query.then((value) => dictionary_references = value),
query_codes_query.then((value) => query_codes = value),
radicals_query.then((value) => radicals = value),
radical_names_query.then((value) => radical_names = value),
readings_query.then((value) => readings = value),
stroke_miscounts_query.then((value) => stroke_miscounts = value),
variants_query.then((value) => variants = value),
});
final entry = characters.first;
final String? grade = {
1: 'grade 1',
2: 'grade 2',
3: 'grade 3',
4: 'grade 4',
5: 'grade 5',
6: 'grade 6',
7: 'grade 7',
8: 'grade 8',
9: 'grade 9',
10: 'grade 10',
}[entry['grade'] as int?];
final String? jlptLevel = {
5: 'N5',
4: 'N4',
3: 'N3',
2: 'N2',
1: 'N1',
}[entry['jlpt'] as int?];
return KanjiSearchResult(
kanji: entry['literal']! as String,
taughtIn: grade,
jlptLevel: jlptLevel,
newspaperFrequencyRank: entry['frequency'] as int?,
strokeCount: entry['strokeCount'] as int,
meanings: meanings.map((item) => item['meaning'] as String).toList(),
kunyomi: kunyomis.map((item) => item['yomi'] as String).toList(),
onyomi: onyomis.map((item) => item['yomi'] as String).toList(),
);
}

View File

@@ -17,6 +17,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
equatable:
dependency: "direct main"
description:
name: equatable
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
url: "https://pub.dev"
source: hosted
version: "2.0.7"
ffi:
dependency: transitive
description:
@@ -58,7 +66,7 @@ packages:
source: hosted
version: "6.1.0"
sqflite_common:
dependency: transitive
dependency: "direct main"
description:
name: sqflite_common
sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b"

View File

@@ -9,6 +9,8 @@ environment:
dependencies:
args: ^2.7.0
collection: ^1.19.1
equatable: ^2.0.7
sqflite_common: ^2.5.5
sqflite_common_ffi: ^2.3.5
xml: ^6.5.0