283 lines
8.4 KiB
Dart
283 lines
8.4 KiB
Dart
import 'package:collection/collection.dart';
|
|
import 'package:jadb/models/kanji_search/kanji_search_radical.dart';
|
|
import 'package:jadb/models/kanji_search/kanji_search_result.dart';
|
|
import 'package:jadb/table_names/kanjidic.dart';
|
|
import 'package:jadb/table_names/radkfile.dart';
|
|
import 'package:sqflite_common/sqflite.dart';
|
|
|
|
Future<List<Map<String, Object?>>> _charactersQuery(
|
|
DatabaseExecutor connection,
|
|
String kanji,
|
|
) => connection.query(
|
|
KANJIDICTableNames.character,
|
|
where: 'literal = ?',
|
|
whereArgs: [kanji],
|
|
);
|
|
|
|
Future<List<Map<String, Object?>>> _codepointsQuery(
|
|
DatabaseExecutor connection,
|
|
String kanji,
|
|
) => connection.query(
|
|
KANJIDICTableNames.codepoint,
|
|
where: 'kanji = ?',
|
|
whereArgs: [kanji],
|
|
);
|
|
|
|
Future<List<Map<String, Object?>>> _kunyomisQuery(
|
|
DatabaseExecutor connection,
|
|
String kanji,
|
|
) => connection.query(
|
|
KANJIDICTableNames.kunyomi,
|
|
where: 'kanji = ?',
|
|
whereArgs: [kanji],
|
|
orderBy: 'orderNum',
|
|
);
|
|
|
|
Future<List<Map<String, Object?>>> _onyomisQuery(
|
|
DatabaseExecutor connection,
|
|
String kanji,
|
|
) => connection.query(
|
|
KANJIDICTableNames.onyomi,
|
|
where: 'kanji = ?',
|
|
whereArgs: [kanji],
|
|
orderBy: 'orderNum',
|
|
);
|
|
|
|
Future<List<Map<String, Object?>>> _meaningsQuery(
|
|
DatabaseExecutor connection,
|
|
String kanji,
|
|
) => connection.query(
|
|
KANJIDICTableNames.meaning,
|
|
where: 'kanji = ? AND language = ?',
|
|
whereArgs: [kanji, 'eng'],
|
|
orderBy: 'orderNum',
|
|
);
|
|
|
|
Future<List<Map<String, Object?>>> _nanorisQuery(
|
|
DatabaseExecutor connection,
|
|
String kanji,
|
|
) => connection.query(
|
|
KANJIDICTableNames.nanori,
|
|
where: 'kanji = ?',
|
|
whereArgs: [kanji],
|
|
);
|
|
|
|
Future<List<Map<String, Object?>>> _dictionaryReferencesQuery(
|
|
DatabaseExecutor connection,
|
|
String kanji,
|
|
) => connection.query(
|
|
KANJIDICTableNames.dictionaryReference,
|
|
where: 'kanji = ?',
|
|
whereArgs: [kanji],
|
|
);
|
|
|
|
Future<List<Map<String, Object?>>> _queryCodesQuery(
|
|
DatabaseExecutor connection,
|
|
String kanji,
|
|
) => connection.query(
|
|
KANJIDICTableNames.queryCode,
|
|
where: 'kanji = ?',
|
|
whereArgs: [kanji],
|
|
);
|
|
|
|
Future<List<Map<String, Object?>>> _radicalsQuery(
|
|
DatabaseExecutor connection,
|
|
String kanji,
|
|
) => connection.rawQuery(
|
|
'''
|
|
SELECT DISTINCT
|
|
"XREF__KANJIDIC_Radical__RADKFILE"."radicalSymbol" AS "symbol",
|
|
"names"
|
|
FROM "${KANJIDICTableNames.radical}"
|
|
JOIN "XREF__KANJIDIC_Radical__RADKFILE" USING ("radicalId")
|
|
LEFT JOIN (
|
|
SELECT "radicalId", group_concat("name") AS "names"
|
|
FROM "${KANJIDICTableNames.radicalName}"
|
|
GROUP BY "radicalId"
|
|
) USING ("radicalId")
|
|
WHERE "${KANJIDICTableNames.radical}"."kanji" = ?
|
|
''',
|
|
[kanji],
|
|
);
|
|
|
|
Future<List<Map<String, Object?>>> _partsQuery(
|
|
DatabaseExecutor connection,
|
|
String kanji,
|
|
) => connection.query(
|
|
RADKFILETableNames.radkfile,
|
|
where: 'kanji = ?',
|
|
whereArgs: [kanji],
|
|
);
|
|
|
|
Future<List<Map<String, Object?>>> _readingsQuery(
|
|
DatabaseExecutor connection,
|
|
String kanji,
|
|
) => connection.query(
|
|
KANJIDICTableNames.reading,
|
|
where: 'kanji = ?',
|
|
whereArgs: [kanji],
|
|
);
|
|
|
|
Future<List<Map<String, Object?>>> _strokeMiscountsQuery(
|
|
DatabaseExecutor connection,
|
|
String kanji,
|
|
) => connection.query(
|
|
KANJIDICTableNames.strokeMiscount,
|
|
where: 'kanji = ?',
|
|
whereArgs: [kanji],
|
|
);
|
|
|
|
// Future<List<Map<String, Object?>>> _variantsQuery(
|
|
// DatabaseExecutor connection,
|
|
// String kanji,
|
|
// ) => connection.query(
|
|
// KANJIDICTableNames.variant,
|
|
// where: 'kanji = ?',
|
|
// whereArgs: [kanji],
|
|
// );
|
|
|
|
/// Searches for a kanji character and returns its details, or null if the kanji is not found in the database.
|
|
Future<KanjiSearchResult?> searchKanjiWithDbConnection(
|
|
DatabaseExecutor connection,
|
|
String kanji,
|
|
) async {
|
|
late final List<Map<String, Object?>> characters;
|
|
late final List<Map<String, Object?>> codepoints;
|
|
late final List<Map<String, Object?>> kunyomis;
|
|
late final List<Map<String, Object?>> onyomis;
|
|
late final List<Map<String, Object?>> meanings;
|
|
late final List<Map<String, Object?>> nanoris;
|
|
late final List<Map<String, Object?>> dictionaryReferences;
|
|
late final List<Map<String, Object?>> queryCodes;
|
|
late final List<Map<String, Object?>> radicals;
|
|
late final List<Map<String, Object?>> parts;
|
|
late final List<Map<String, Object?>> readings;
|
|
late final List<Map<String, Object?>> strokeMiscounts;
|
|
// TODO: add variant data to result
|
|
// late final List<Map<String, Object?>> variants;
|
|
|
|
// TODO: Search for kunyomi and onyomi usage of the characters
|
|
// from JMDict. We'll need to fuzzy aquery JMDict_KanjiElement for matches,
|
|
// filter JMdict_ReadingElement for kunyomi/onyomi, and then sort the main entry
|
|
// by JLPT, news frequency, etc.
|
|
|
|
await _charactersQuery(connection, kanji).then((value) => characters = value);
|
|
|
|
if (characters.isEmpty) {
|
|
return null;
|
|
}
|
|
|
|
await Future.wait({
|
|
_codepointsQuery(connection, kanji).then((value) => codepoints = value),
|
|
_kunyomisQuery(connection, kanji).then((value) => kunyomis = value),
|
|
_onyomisQuery(connection, kanji).then((value) => onyomis = value),
|
|
_meaningsQuery(connection, kanji).then((value) => meanings = value),
|
|
_nanorisQuery(connection, kanji).then((value) => nanoris = value),
|
|
_dictionaryReferencesQuery(
|
|
connection,
|
|
kanji,
|
|
).then((value) => dictionaryReferences = value),
|
|
_queryCodesQuery(connection, kanji).then((value) => queryCodes = value),
|
|
_radicalsQuery(connection, kanji).then((value) => radicals = value),
|
|
_partsQuery(connection, kanji).then((value) => parts = value),
|
|
_readingsQuery(connection, kanji).then((value) => readings = value),
|
|
_strokeMiscountsQuery(
|
|
connection,
|
|
kanji,
|
|
).then((value) => strokeMiscounts = value),
|
|
// variants_query.then((value) => variants = value),
|
|
});
|
|
|
|
final entry = characters.first;
|
|
|
|
assert(radicals.length <= 1, 'There should be at most one radical per kanji');
|
|
final radical = radicals.isNotEmpty
|
|
? KanjiSearchRadical(
|
|
symbol: radicals.first['symbol'] as String,
|
|
names: (radicals.first['names'] as String?)?.split(',') ?? [],
|
|
// TODO: add radical form data
|
|
forms: [],
|
|
// TODO: add radical meaning data
|
|
meanings: [],
|
|
)
|
|
: null;
|
|
|
|
final alternativeLanguageReadings = readings
|
|
.groupListsBy((item) => item['type'] as String)
|
|
.map(
|
|
(key, value) => MapEntry(
|
|
key,
|
|
value.map((item) => item['reading'] as String).toList(),
|
|
),
|
|
);
|
|
|
|
// TODO: Add `SKIPMisclassification` to the entries
|
|
final queryCodes_ = queryCodes
|
|
.groupListsBy((item) => item['type'] as String)
|
|
.map(
|
|
(key, value) =>
|
|
MapEntry(key, value.map((item) => item['code'] as String).toList()),
|
|
);
|
|
|
|
// TODO: Add `volume` and `page` to the entries
|
|
final dictionaryReferences_ = {
|
|
for (final entry in dictionaryReferences)
|
|
entry['type'] as String: entry['ref'] as String,
|
|
};
|
|
|
|
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: entry['grade'] as int?,
|
|
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(),
|
|
parts: parts.map((item) => item['radical'] as String).toList(),
|
|
onyomi: onyomis.map((item) => item['yomi'] as String).toList(),
|
|
radical: radical,
|
|
codepoints: {
|
|
for (final codepoint in codepoints)
|
|
codepoint['type'] as String: codepoint['codepoint'] as String,
|
|
},
|
|
nanori: nanoris.map((item) => item['nanori'] as String).toList(),
|
|
alternativeLanguageReadings: alternativeLanguageReadings,
|
|
strokeMiscounts: strokeMiscounts
|
|
.map((item) => item['strokeCount'] as int)
|
|
.toList(),
|
|
queryCodes: queryCodes_,
|
|
dictionaryReferences: dictionaryReferences_,
|
|
);
|
|
}
|
|
|
|
// TODO: Use fewer queries with `IN` clauses to reduce the number of queries
|
|
|
|
/// Searches for multiple kanji at once, returning a map of kanji to their search results.
|
|
Future<Map<String, KanjiSearchResult>> searchManyKanjiWithDbConnection(
|
|
DatabaseExecutor connection,
|
|
Set<String> kanji,
|
|
) async {
|
|
if (kanji.isEmpty) {
|
|
return {};
|
|
}
|
|
|
|
final results = <String, KanjiSearchResult>{};
|
|
|
|
for (final k in kanji) {
|
|
final result = await searchKanjiWithDbConnection(connection, k);
|
|
if (result != null) {
|
|
results[k] = result;
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|