Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
41d990a447
|
@@ -1,9 +1,7 @@
|
|||||||
import './search/english_word_search.dart';
|
import './search/english_word_search.dart';
|
||||||
import './search/japanese_word_search.dart';
|
import './search/japanese_word_search.dart';
|
||||||
import 'search/japanese_word_search_fuzzy.dart';
|
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
await EnglishWordSearchBenchmark.main();
|
await EnglishWordSearchBenchmark.main();
|
||||||
await JapaneseWordSearchBenchmark.main();
|
await JapaneseWordSearchBenchmark.main();
|
||||||
await JapaneseWordSearchBenchmarkFuzzy.main();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class JapaneseWordSearchBenchmark extends AsyncBenchmarkBase {
|
|||||||
@override
|
@override
|
||||||
Future<void> run() async {
|
Future<void> run() async {
|
||||||
for (final term in searchTerms) {
|
for (final term in searchTerms) {
|
||||||
final result = await connection!.jadbSearchWord(term, fuzzyKana: false);
|
final result = await connection!.jadbSearchWord(term);
|
||||||
assert(
|
assert(
|
||||||
result?.isNotEmpty ?? false,
|
result?.isNotEmpty ?? false,
|
||||||
'Expected search results for term "$term"',
|
'Expected search results for term "$term"',
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
import 'package:benchmark_harness/benchmark_harness.dart';
|
|
||||||
import 'package:jadb/search.dart';
|
|
||||||
import 'package:sqflite_common/sqlite_api.dart';
|
|
||||||
|
|
||||||
import '../../test/search/setup_database_connection.dart';
|
|
||||||
|
|
||||||
class JapaneseWordSearchBenchmarkFuzzy extends AsyncBenchmarkBase {
|
|
||||||
Database? connection;
|
|
||||||
|
|
||||||
static final List<String> searchTerms = [
|
|
||||||
'仮名',
|
|
||||||
'漢字',
|
|
||||||
'かわいい',
|
|
||||||
'すし',
|
|
||||||
'ラメン',
|
|
||||||
];
|
|
||||||
|
|
||||||
JapaneseWordSearchBenchmarkFuzzy() : super('JapaneseWordSearchBenchmarkFuzzy');
|
|
||||||
|
|
||||||
static Future<void> main() async {
|
|
||||||
print('Running JapaneseWordSearchBenchmark...');
|
|
||||||
await JapaneseWordSearchBenchmarkFuzzy().report();
|
|
||||||
print('Finished JapaneseWordSearchBenchmark');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> setup() async {
|
|
||||||
connection = await setupDatabaseConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> run() async {
|
|
||||||
for (final term in searchTerms) {
|
|
||||||
final result = await connection!.jadbSearchWord(term, fuzzyKana: true);
|
|
||||||
assert(
|
|
||||||
result?.isNotEmpty ?? false,
|
|
||||||
'Expected search results for term "$term"',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> teardown() async {
|
|
||||||
await connection?.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// Future<void> exercise() => run();
|
|
||||||
}
|
|
||||||
Generated
+8
-108
@@ -1,20 +1,5 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"crane": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1780532242,
|
|
||||||
"narHash": "sha256-D+BsdpxmtUwtqGoY0IXPhHgTlmqgcZKCEo1oMyn7ep0=",
|
|
||||||
"owner": "ipetkov",
|
|
||||||
"repo": "crane",
|
|
||||||
"rev": "59a82a1222dd3b2080b5cc52a1a2e8d5f1b77f37",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "ipetkov",
|
|
||||||
"repo": "crane",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"datasources": {
|
"datasources": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -22,11 +7,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1781006603,
|
"lastModified": 1776081209,
|
||||||
"narHash": "sha256-3Ug2qTpQT2Tcj0ai02kmRV3Ipy0YG39mGJpcMo2Jcs4=",
|
"narHash": "sha256-zR1115tcOPnYLk6NznSf7YslyaJLc/MGayEHShitx18=",
|
||||||
"ref": "refs/heads/main",
|
"ref": "refs/heads/main",
|
||||||
"rev": "7718750756fc198822be2c3b034a70d3d3f31f16",
|
"rev": "7fe3552bb16e1d315c0b27b243e5eb53cd9e86fc",
|
||||||
"revCount": 33,
|
"revCount": 13,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.pvv.ntnu.no/Mugiten/datasources.git"
|
"url": "https://git.pvv.ntnu.no/Mugiten/datasources.git"
|
||||||
},
|
},
|
||||||
@@ -35,51 +20,13 @@
|
|||||||
"url": "https://git.pvv.ntnu.no/Mugiten/datasources.git"
|
"url": "https://git.pvv.ntnu.no/Mugiten/datasources.git"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"kanjivg-src": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1778620714,
|
|
||||||
"narHash": "sha256-LwNcY5A6XPGI+DASZfmP7OeYe8IFesShhSrE7Go2ux8=",
|
|
||||||
"ref": "refs/heads/master",
|
|
||||||
"rev": "1957802840a6f059d1e27dcb5755722955cc7dbb",
|
|
||||||
"revCount": 2217,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.pvv.ntnu.no/mugiten/kanjivg.git"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.pvv.ntnu.no/mugiten/kanjivg.git"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nix-sqlite": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"tamerye",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1780687708,
|
|
||||||
"narHash": "sha256-oKS+JYmFLYFQAR0t/D5wfjhREdIIczKjAVeYRnUS9F4=",
|
|
||||||
"ref": "main",
|
|
||||||
"rev": "e7ce86cba09144d2992a264b5b0b5c711478e89d",
|
|
||||||
"revCount": 31,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.pvv.ntnu.no/mugiten/nix-custom-sqlite.git"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"ref": "main",
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.pvv.ntnu.no/mugiten/nix-custom-sqlite.git"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780749050,
|
"lastModified": 1777954456,
|
||||||
"narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=",
|
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "a799d3e3886da994fa307f817a6bc705ae538eeb",
|
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -91,54 +38,7 @@
|
|||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"datasources": "datasources",
|
"datasources": "datasources",
|
||||||
"kanjivg-src": "kanjivg-src",
|
"nixpkgs": "nixpkgs"
|
||||||
"nixpkgs": "nixpkgs",
|
|
||||||
"tamerye": "tamerye"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rust-overlay": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"tamerye",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1780975129,
|
|
||||||
"narHash": "sha256-428T1pLXnbVeUgZx2wWEkbvl3ZORjras34ANZ3ACp5A=",
|
|
||||||
"owner": "oxalica",
|
|
||||||
"repo": "rust-overlay",
|
|
||||||
"rev": "fe64e6b409dc513274d2941f8da13bbd0fdcf44e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "oxalica",
|
|
||||||
"repo": "rust-overlay",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tamerye": {
|
|
||||||
"inputs": {
|
|
||||||
"crane": "crane",
|
|
||||||
"nix-sqlite": "nix-sqlite",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"rust-overlay": "rust-overlay"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1781059814,
|
|
||||||
"narHash": "sha256-rUZHtddRp/roiyi+FAgMsKESF7bqW5wUNvD9R3OE600=",
|
|
||||||
"ref": "main",
|
|
||||||
"rev": "903f990fddf77992ac0abb688f8c245e1aaeac38",
|
|
||||||
"revCount": 38,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.pvv.ntnu.no/Mugiten/tamerye.git"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"ref": "main",
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.pvv.ntnu.no/Mugiten/tamerye.git"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,28 +4,16 @@
|
|||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||||
|
|
||||||
tamerye = {
|
|
||||||
url = "git+https://git.pvv.ntnu.no/Mugiten/tamerye.git?ref=main";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
|
|
||||||
datasources = {
|
datasources = {
|
||||||
url = "git+https://git.pvv.ntnu.no/Mugiten/datasources.git";
|
url = "git+https://git.pvv.ntnu.no/Mugiten/datasources.git";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
kanjivg-src = {
|
|
||||||
url = "git+https://git.pvv.ntnu.no/mugiten/kanjivg.git";
|
|
||||||
flake = false;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = {
|
outputs = {
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
tamerye,
|
|
||||||
datasources,
|
datasources,
|
||||||
kanjivg-src,
|
|
||||||
}: let
|
}: let
|
||||||
inherit (nixpkgs) lib;
|
inherit (nixpkgs) lib;
|
||||||
systems = [
|
systems = [
|
||||||
@@ -36,14 +24,7 @@
|
|||||||
"armv7l-linux"
|
"armv7l-linux"
|
||||||
];
|
];
|
||||||
|
|
||||||
forAllSystems = f: lib.genAttrs systems (system: let
|
forAllSystems = f: lib.genAttrs systems (system: f system nixpkgs.legacyPackages.${system});
|
||||||
pkgs = import nixpkgs {
|
|
||||||
inherit system;
|
|
||||||
overlays = [
|
|
||||||
tamerye.overlays.default
|
|
||||||
];
|
|
||||||
};
|
|
||||||
in f system pkgs);
|
|
||||||
in {
|
in {
|
||||||
apps = forAllSystems (system: pkgs: {
|
apps = forAllSystems (system: pkgs: {
|
||||||
default = {
|
default = {
|
||||||
@@ -54,7 +35,7 @@
|
|||||||
|
|
||||||
runtimeEnv = {
|
runtimeEnv = {
|
||||||
JADB_PATH = "${self.packages.${system}.database}/jadb.sqlite";
|
JADB_PATH = "${self.packages.${system}.database}/jadb.sqlite";
|
||||||
LIBSQLITE_PATH = "${pkgs.tamerye-sqlite}/lib/libsqlite3.so";
|
LIBSQLITE_PATH = "${pkgs.sqlite.out}/lib/libsqlite3.so";
|
||||||
};
|
};
|
||||||
|
|
||||||
text = ''
|
text = ''
|
||||||
@@ -82,18 +63,18 @@
|
|||||||
gnumake
|
gnumake
|
||||||
lcov
|
lcov
|
||||||
sqldiff
|
sqldiff
|
||||||
tamerye-sqlite-cli
|
sqlite-interactive
|
||||||
];
|
];
|
||||||
env = {
|
env = {
|
||||||
LIBSQLITE_PATH = "${pkgs.tamerye-sqlite}/lib/libsqlite3.so";
|
LIBSQLITE_PATH = "${pkgs.sqlite.out}/lib/libsqlite3.so";
|
||||||
JADB_PATH = "result/jadb.sqlite";
|
JADB_PATH = "result/jadb.sqlite";
|
||||||
LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.tamerye-sqlite ];
|
LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.sqlite ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
sqlite-debugging = pkgs.mkShell {
|
sqlite-debugging = pkgs.mkShell {
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
tamerye-sqlite-cli
|
sqlite-interactive
|
||||||
sqlite-analyzer
|
sqlite-analyzer
|
||||||
sqlite-web
|
sqlite-web
|
||||||
# sqlint
|
# sqlint
|
||||||
@@ -135,25 +116,22 @@
|
|||||||
ln -s ${src} $out
|
ln -s ${src} $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
fts5-icu-tokenizer = pkgs.callPackage ./nix/fts5-icu-tokenizer/package.nix { };
|
||||||
|
|
||||||
inherit (datasources.packages.${system}) jmdict radkfile kanjidic2;
|
inherit (datasources.packages.${system}) jmdict radkfile kanjidic2;
|
||||||
|
|
||||||
database-tool = pkgs.callPackage ./nix/database_tool.nix {
|
database-tool = pkgs.callPackage ./nix/database_tool.nix {
|
||||||
sqlite = pkgs.tamerye-sqlite;
|
|
||||||
inherit src;
|
inherit src;
|
||||||
};
|
};
|
||||||
|
|
||||||
database = pkgs.callPackage ./nix/database.nix {
|
database = pkgs.callPackage ./nix/database.nix {
|
||||||
sqlite = pkgs.tamerye-sqlite-cli;
|
|
||||||
inherit (datasources.packages.${system}) jmdict radkfile kanjidic2 tanos-jlpt;
|
inherit (datasources.packages.${system}) jmdict radkfile kanjidic2 tanos-jlpt;
|
||||||
kanjivg = kanjivg-src;
|
|
||||||
inherit (self.packages.${system}) database-tool;
|
inherit (self.packages.${system}) database-tool;
|
||||||
inherit src;
|
inherit src;
|
||||||
};
|
};
|
||||||
|
|
||||||
database-wal = pkgs.callPackage ./nix/database.nix {
|
database-wal = pkgs.callPackage ./nix/database.nix {
|
||||||
sqlite = pkgs.tamerye-sqlite-cli;
|
|
||||||
inherit (datasources.packages.${system}) jmdict radkfile kanjidic2 tanos-jlpt;
|
inherit (datasources.packages.${system}) jmdict radkfile kanjidic2 tanos-jlpt;
|
||||||
kanjivg = kanjivg-src;
|
|
||||||
inherit (self.packages.${system}) database-tool;
|
inherit (self.packages.${system}) database-tool;
|
||||||
inherit src;
|
inherit src;
|
||||||
wal = true;
|
wal = true;
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ class KanjiElement extends Element {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, Object?> get sqlValue => {...super.sqlValue};
|
Map<String, Object?> get sqlValue => {
|
||||||
|
...super.sqlValue,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class ReadingElement extends Element {
|
class ReadingElement extends Element {
|
||||||
@@ -127,19 +129,6 @@ class XRefParts {
|
|||||||
'readingRef': readingRef,
|
'readingRef': readingRef,
|
||||||
'senseOrderNum': senseOrderNum,
|
'senseOrderNum': senseOrderNum,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
if (identical(this, other)) return true;
|
|
||||||
|
|
||||||
return other is XRefParts &&
|
|
||||||
other.kanjiRef == kanjiRef &&
|
|
||||||
other.readingRef == readingRef &&
|
|
||||||
other.senseOrderNum == senseOrderNum;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(kanjiRef, readingRef, senseOrderNum);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class XRef {
|
class XRef {
|
||||||
@@ -179,7 +168,9 @@ class Sense extends SQLWritable {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, Object?> get sqlValue => {'senseId': senseId};
|
Map<String, Object?> get sqlValue => {
|
||||||
|
'senseId': senseId,
|
||||||
|
};
|
||||||
|
|
||||||
bool get isEmpty =>
|
bool get isEmpty =>
|
||||||
antonyms.isEmpty &&
|
antonyms.isEmpty &&
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ class ResolvedXref {
|
|||||||
const ResolvedXref(this.entry, this.ambiguous);
|
const ResolvedXref(this.entry, this.ambiguous);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A constant map of xref parts to jmdict id for unresolvable xrefs.
|
|
||||||
final xrefExceptions = {
|
|
||||||
// NOTE: see https://www.edrdg.org/jmwsgi/entr.py?svc=jmdict&g=2870981.1~2369718 for details
|
|
||||||
XRefParts(kanjiRef: 'プレストレスト', readingRef: 'コンクリート'): 2472380,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Resolves an xref (pair of kanji, optionally reading, and optionally sense number) to an a specific
|
/// Resolves an xref (pair of kanji, optionally reading, and optionally sense number) to an a specific
|
||||||
/// JMdict entry, if possible.
|
/// JMdict entry, if possible.
|
||||||
///
|
///
|
||||||
@@ -34,27 +28,6 @@ ResolvedXref resolveXref(
|
|||||||
XRefParts xref,
|
XRefParts xref,
|
||||||
) {
|
) {
|
||||||
late List<Entry> candidateEntries;
|
late List<Entry> candidateEntries;
|
||||||
|
|
||||||
if (xrefExceptions.containsKey(xref)) {
|
|
||||||
final exceptionEntryId = xrefExceptions[xref]!;
|
|
||||||
// NOTE: this is slow, but we have few exceptions. Let's wait for JMdict XML-NG to be released so we can delete this :)
|
|
||||||
final exceptionEntry =
|
|
||||||
entriesByKanji.values
|
|
||||||
.expand((set) => set)
|
|
||||||
.firstWhereOrNull((entry) => entry.entryId == exceptionEntryId) ??
|
|
||||||
entriesByReading.values
|
|
||||||
.expand((set) => set)
|
|
||||||
.firstWhereOrNull((entry) => entry.entryId == exceptionEntryId);
|
|
||||||
|
|
||||||
if (exceptionEntry != null) {
|
|
||||||
return ResolvedXref(exceptionEntry, false);
|
|
||||||
} else {
|
|
||||||
throw Exception(
|
|
||||||
'Xref $xref matches an exception entry ID $exceptionEntryId, but that entry was not found among the candidates.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ((xref.kanjiRef, xref.readingRef)) {
|
switch ((xref.kanjiRef, xref.readingRef)) {
|
||||||
case (null, null):
|
case (null, null):
|
||||||
throw Exception('Xref $xref has no kanji or reading reference');
|
throw Exception('Xref $xref has no kanji or reading reference');
|
||||||
|
|||||||
@@ -1,175 +0,0 @@
|
|||||||
import 'package:jadb/_data_ingestion/sql_writable.dart';
|
|
||||||
|
|
||||||
/// Enum set in the kvg:position attribute, used by `<g>` elements in the KanjiVG SVG files.
|
|
||||||
enum KanjiPathGroupPosition {
|
|
||||||
upperA,
|
|
||||||
upperB,
|
|
||||||
lower1,
|
|
||||||
lower2,
|
|
||||||
bottom,
|
|
||||||
kamae,
|
|
||||||
kamaec,
|
|
||||||
left,
|
|
||||||
middle,
|
|
||||||
nyo,
|
|
||||||
nyoc,
|
|
||||||
right,
|
|
||||||
tare,
|
|
||||||
tarec,
|
|
||||||
top;
|
|
||||||
|
|
||||||
static KanjiPathGroupPosition? fromString(String? str) {
|
|
||||||
if (str == null) return null;
|
|
||||||
switch (str) {
|
|
||||||
case '⿵A':
|
|
||||||
return KanjiPathGroupPosition.upperA;
|
|
||||||
case '⿵B':
|
|
||||||
return KanjiPathGroupPosition.upperB;
|
|
||||||
case '⿶1':
|
|
||||||
return KanjiPathGroupPosition.lower1;
|
|
||||||
case '⿶2':
|
|
||||||
return KanjiPathGroupPosition.lower2;
|
|
||||||
case 'bottom':
|
|
||||||
return KanjiPathGroupPosition.bottom;
|
|
||||||
case 'kamae':
|
|
||||||
return KanjiPathGroupPosition.kamae;
|
|
||||||
case 'kamaec':
|
|
||||||
return KanjiPathGroupPosition.kamaec;
|
|
||||||
case 'left':
|
|
||||||
return KanjiPathGroupPosition.left;
|
|
||||||
case 'middle':
|
|
||||||
return KanjiPathGroupPosition.middle;
|
|
||||||
case 'nyo':
|
|
||||||
return KanjiPathGroupPosition.nyo;
|
|
||||||
case 'nyoc':
|
|
||||||
return KanjiPathGroupPosition.nyoc;
|
|
||||||
case 'right':
|
|
||||||
return KanjiPathGroupPosition.right;
|
|
||||||
case 'tare':
|
|
||||||
return KanjiPathGroupPosition.tare;
|
|
||||||
case 'tarec':
|
|
||||||
return KanjiPathGroupPosition.tarec;
|
|
||||||
case 'top':
|
|
||||||
return KanjiPathGroupPosition.top;
|
|
||||||
default:
|
|
||||||
throw ArgumentError('Unknown position: $str');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum KanjiVGRadical {
|
|
||||||
general,
|
|
||||||
jis,
|
|
||||||
nelson,
|
|
||||||
tradit;
|
|
||||||
|
|
||||||
static KanjiVGRadical? fromString(String? str) {
|
|
||||||
if (str == null) return null;
|
|
||||||
switch (str) {
|
|
||||||
case 'general':
|
|
||||||
return KanjiVGRadical.general;
|
|
||||||
case 'jis':
|
|
||||||
return KanjiVGRadical.jis;
|
|
||||||
case 'nelson':
|
|
||||||
return KanjiVGRadical.nelson;
|
|
||||||
case 'tradit':
|
|
||||||
return KanjiVGRadical.tradit;
|
|
||||||
default:
|
|
||||||
throw ArgumentError('Unknown radical: $str');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Contents of a \<g> element in the KanjiVG SVG files.
|
|
||||||
class KanjiPathGroupTreeNode extends SQLWritable {
|
|
||||||
final int id;
|
|
||||||
final List<KanjiPathGroupTreeNode> children;
|
|
||||||
final String? element;
|
|
||||||
final String? original;
|
|
||||||
final KanjiPathGroupPosition? position;
|
|
||||||
final KanjiVGRadical? radical;
|
|
||||||
final int? part;
|
|
||||||
|
|
||||||
// Currently unused data.
|
|
||||||
final bool radicalForm;
|
|
||||||
final bool tradForm;
|
|
||||||
final bool partial;
|
|
||||||
final String? variant;
|
|
||||||
|
|
||||||
KanjiPathGroupTreeNode({
|
|
||||||
required this.id,
|
|
||||||
this.children = const [],
|
|
||||||
this.element,
|
|
||||||
this.original,
|
|
||||||
this.position,
|
|
||||||
this.radical,
|
|
||||||
this.part,
|
|
||||||
|
|
||||||
this.variant,
|
|
||||||
this.radicalForm = false,
|
|
||||||
this.tradForm = false,
|
|
||||||
this.partial = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, Object?> get sqlValue => {
|
|
||||||
'groupId': id,
|
|
||||||
'element': element,
|
|
||||||
'original': original,
|
|
||||||
'position': position?.name,
|
|
||||||
'radical': radical?.name,
|
|
||||||
'part': part,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Contents of a `<text>` element in the StrokeNumber's group in the KanjiVG SVG files
|
|
||||||
class KanjiStrokeNumber extends SQLWritable {
|
|
||||||
final int num;
|
|
||||||
final double x;
|
|
||||||
final double y;
|
|
||||||
|
|
||||||
KanjiStrokeNumber(this.num, this.x, this.y);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, Object?> get sqlValue => {'strokeNum': num, 'x': x, 'y': y};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Contents of a `<path>` element in the KanjiVG SVG files
|
|
||||||
class KanjiVGPath extends SQLWritable {
|
|
||||||
final int id;
|
|
||||||
final int groupId;
|
|
||||||
final String? type;
|
|
||||||
final String svgPath;
|
|
||||||
|
|
||||||
KanjiVGPath({
|
|
||||||
required this.id,
|
|
||||||
required this.groupId,
|
|
||||||
required this.type,
|
|
||||||
required this.svgPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, Object?> get sqlValue => {
|
|
||||||
'pathId': id,
|
|
||||||
'groupId': groupId,
|
|
||||||
'type': type,
|
|
||||||
'svgPath': svgPath,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class KanjiVGItem extends SQLWritable {
|
|
||||||
final String character;
|
|
||||||
final List<KanjiVGPath> paths;
|
|
||||||
final List<KanjiStrokeNumber> strokeNumbers;
|
|
||||||
final List<KanjiPathGroupTreeNode> pathGroups;
|
|
||||||
|
|
||||||
KanjiVGItem({
|
|
||||||
required this.character,
|
|
||||||
required this.paths,
|
|
||||||
required this.strokeNumbers,
|
|
||||||
required this.pathGroups,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, Object?> get sqlValue => {'character': character};
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:jadb/_data_ingestion/kanjivg/objects.dart';
|
|
||||||
import 'package:xml/xml.dart';
|
|
||||||
|
|
||||||
List<KanjiVGItem> parseKanjiVGData(Directory rootDir) {
|
|
||||||
final List<KanjiVGItem> items = [];
|
|
||||||
|
|
||||||
for (final file in rootDir.listSync()) {
|
|
||||||
if (file is File && file.path.endsWith('.svg')) {
|
|
||||||
final String rawSVG = file.readAsStringSync();
|
|
||||||
final XmlDocument doc = XmlDocument.parse(rawSVG);
|
|
||||||
|
|
||||||
final strokePathsGroup = doc
|
|
||||||
.findAllElements('g')
|
|
||||||
.firstWhereOrNull(
|
|
||||||
(e) => e.getAttribute('id')?.startsWith('kvg:StrokePaths') ?? false,
|
|
||||||
);
|
|
||||||
|
|
||||||
final strokeNumbersGroup = doc
|
|
||||||
.findAllElements('g')
|
|
||||||
.firstWhereOrNull(
|
|
||||||
(e) =>
|
|
||||||
e.getAttribute('id')?.startsWith('kvg:StrokeNumbers') ?? false,
|
|
||||||
);
|
|
||||||
|
|
||||||
final pathGroups = strokePathsGroup != null
|
|
||||||
? _parsePathGroups(strokePathsGroup)
|
|
||||||
: <KanjiPathGroupTreeNode>[];
|
|
||||||
|
|
||||||
final strokeNumbers = strokeNumbersGroup != null
|
|
||||||
? _parseStrokeNumbers(strokeNumbersGroup)
|
|
||||||
: <KanjiStrokeNumber>[];
|
|
||||||
|
|
||||||
final paths = strokePathsGroup != null
|
|
||||||
? _parsePaths(strokePathsGroup)
|
|
||||||
: <KanjiVGPath>[];
|
|
||||||
|
|
||||||
items.add(
|
|
||||||
KanjiVGItem(
|
|
||||||
character: file.uri.pathSegments.last.split('.').first,
|
|
||||||
paths: paths,
|
|
||||||
strokeNumbers: strokeNumbers,
|
|
||||||
pathGroups: pathGroups,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<KanjiStrokeNumber> _parseStrokeNumbers(XmlElement group) => group
|
|
||||||
.childElements
|
|
||||||
.map((e) {
|
|
||||||
final num = int.parse(e.innerText);
|
|
||||||
final xy = e
|
|
||||||
.getAttribute('transform')!
|
|
||||||
.split('matrix(1 0 0 1 ')[1]
|
|
||||||
.split(')')[0]
|
|
||||||
.split(' ')
|
|
||||||
.map(double.parse)
|
|
||||||
.toList();
|
|
||||||
return KanjiStrokeNumber(num, xy[0], xy[1]);
|
|
||||||
})
|
|
||||||
.toList(growable: false);
|
|
||||||
|
|
||||||
List<KanjiPathGroupTreeNode> _parsePathGroups(XmlElement group) => group
|
|
||||||
.findElements('g')
|
|
||||||
.map((e) {
|
|
||||||
return KanjiPathGroupTreeNode(
|
|
||||||
// NOTE: the outermost group does not have a number
|
|
||||||
id:
|
|
||||||
int.tryParse(e.getAttribute('id')!.split('-').last.substring(1)) ??
|
|
||||||
0,
|
|
||||||
element: e.getAttribute('kvg:element'),
|
|
||||||
original: e.getAttribute('kvg:original'),
|
|
||||||
variant: e.getAttribute('kvg:variant'),
|
|
||||||
position: KanjiPathGroupPosition.fromString(
|
|
||||||
e.getAttribute('kvg:position'),
|
|
||||||
),
|
|
||||||
radical: KanjiVGRadical.fromString(e.getAttribute('kvg:radical')),
|
|
||||||
part: int.tryParse(e.getAttribute('kvg:part') ?? ''),
|
|
||||||
radicalForm: e.getAttribute('kvg:radicalForm') == 'true',
|
|
||||||
tradForm: e.getAttribute('kvg:tradForm') == 'true',
|
|
||||||
partial: e.getAttribute('kvg:partial') == 'true',
|
|
||||||
children: _parsePathGroups(e),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.toList(growable: false);
|
|
||||||
|
|
||||||
List<KanjiVGPath> _parsePaths(XmlElement group) => group
|
|
||||||
.findAllElements('g')
|
|
||||||
.map(
|
|
||||||
(g) => g
|
|
||||||
.findElements('path')
|
|
||||||
.map(
|
|
||||||
(e) => KanjiVGPath(
|
|
||||||
id: int.parse(e.getAttribute('id')!.split('-').last.substring(1)),
|
|
||||||
groupId:
|
|
||||||
int.tryParse(
|
|
||||||
g.getAttribute('id')!.split('-').last.substring(1),
|
|
||||||
) ??
|
|
||||||
0,
|
|
||||||
type: e.getAttribute('kvg:type'),
|
|
||||||
svgPath: e.getAttribute('d')!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.expand((x) => x)
|
|
||||||
.toList(growable: false);
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import 'package:jadb/_data_ingestion/kanjivg/objects.dart';
|
|
||||||
import 'package:jadb/table_names/kanjivg.dart';
|
|
||||||
import 'package:sqflite_common/sqflite.dart';
|
|
||||||
|
|
||||||
Future<void> seedKanjiVGData(Iterable<KanjiVGItem> items, Database db) {
|
|
||||||
return db.transaction((txn) async {
|
|
||||||
await txn.execute('PRAGMA defer_foreign_keys = ON');
|
|
||||||
|
|
||||||
final b = txn.batch();
|
|
||||||
|
|
||||||
for (final item in items) {
|
|
||||||
b.insert(KanjiVGTableNames.entry, item.sqlValue);
|
|
||||||
|
|
||||||
for (final path in item.paths) {
|
|
||||||
b.insert(
|
|
||||||
KanjiVGTableNames.path,
|
|
||||||
path.sqlValue..addAll({'character': item.character}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final strokeNumber in item.strokeNumbers) {
|
|
||||||
b.insert(
|
|
||||||
KanjiVGTableNames.strokeNumber,
|
|
||||||
strokeNumber.sqlValue..addAll({'character': item.character}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final pathGroup in item.pathGroups) {
|
|
||||||
_insertPathGroup(b, null, pathGroup, item.character);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await b.commit(noResult: true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recursively insert path groups and their children
|
|
||||||
void _insertPathGroup(
|
|
||||||
Batch b,
|
|
||||||
int? parentGroupId,
|
|
||||||
KanjiPathGroupTreeNode node,
|
|
||||||
String character,
|
|
||||||
) {
|
|
||||||
b.insert(
|
|
||||||
KanjiVGTableNames.pathGroup,
|
|
||||||
node.sqlValue
|
|
||||||
..addAll({'character': character, 'parentGroupId': parentGroupId}),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (final child in node.children) {
|
|
||||||
_insertPathGroup(b, node.id, child, character);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,6 @@ import 'package:jadb/_data_ingestion/jmdict/seed_data.dart';
|
|||||||
import 'package:jadb/_data_ingestion/jmdict/xml_parser.dart';
|
import 'package:jadb/_data_ingestion/jmdict/xml_parser.dart';
|
||||||
import 'package:jadb/_data_ingestion/kanjidic/seed_data.dart';
|
import 'package:jadb/_data_ingestion/kanjidic/seed_data.dart';
|
||||||
import 'package:jadb/_data_ingestion/kanjidic/xml_parser.dart';
|
import 'package:jadb/_data_ingestion/kanjidic/xml_parser.dart';
|
||||||
import 'package:jadb/_data_ingestion/kanjivg/parser.dart';
|
|
||||||
import 'package:jadb/_data_ingestion/kanjivg/seed_data.dart';
|
|
||||||
import 'package:jadb/_data_ingestion/radkfile/parser.dart';
|
import 'package:jadb/_data_ingestion/radkfile/parser.dart';
|
||||||
import 'package:jadb/_data_ingestion/radkfile/seed_data.dart';
|
import 'package:jadb/_data_ingestion/radkfile/seed_data.dart';
|
||||||
import 'package:jadb/_data_ingestion/tanos-jlpt/csv_parser.dart';
|
import 'package:jadb/_data_ingestion/tanos-jlpt/csv_parser.dart';
|
||||||
@@ -19,20 +17,14 @@ Future<void> seedData(Database db) async {
|
|||||||
await parseAndSeedDataFromRADKFILE(db);
|
await parseAndSeedDataFromRADKFILE(db);
|
||||||
await parseAndSeedDataFromKANJIDIC(db);
|
await parseAndSeedDataFromKANJIDIC(db);
|
||||||
await parseAndSeedDataFromTanosJLPT(db);
|
await parseAndSeedDataFromTanosJLPT(db);
|
||||||
await parseAndSeedDataFromKanjiVG(db);
|
|
||||||
|
|
||||||
print('Performing VACUUM');
|
print('Performing VACUUM');
|
||||||
await db.execute('VACUUM');
|
await db.execute('VACUUM');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> parseAndSeedDataFromJMdict(Database db) async {
|
Future<void> parseAndSeedDataFromJMdict(Database db) async {
|
||||||
final jmdictPath = Platform.environment['JMDICT_PATH'] ?? 'data/JMdict.xml';
|
|
||||||
if (!File(jmdictPath).existsSync()) {
|
|
||||||
throw Exception('JMdict file not found at $jmdictPath');
|
|
||||||
}
|
|
||||||
|
|
||||||
print('[JMdict] Reading file content...');
|
print('[JMdict] Reading file content...');
|
||||||
final String rawXML = File(jmdictPath).readAsStringSync();
|
final String rawXML = File('data/JMdict.xml').readAsStringSync();
|
||||||
|
|
||||||
print('[JMdict] Parsing XML tags...');
|
print('[JMdict] Parsing XML tags...');
|
||||||
final XmlElement root = XmlDocument.parse(rawXML).getElement('JMdict')!;
|
final XmlElement root = XmlDocument.parse(rawXML).getElement('JMdict')!;
|
||||||
@@ -45,14 +37,8 @@ Future<void> parseAndSeedDataFromJMdict(Database db) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> parseAndSeedDataFromKANJIDIC(Database db) async {
|
Future<void> parseAndSeedDataFromKANJIDIC(Database db) async {
|
||||||
final kanjidicPath =
|
|
||||||
Platform.environment['KANJIDIC_PATH'] ?? 'data/kanjidic2.xml';
|
|
||||||
if (!File(kanjidicPath).existsSync()) {
|
|
||||||
throw Exception('KANJIDIC file not found at $kanjidicPath');
|
|
||||||
}
|
|
||||||
|
|
||||||
print('[KANJIDIC2] Reading file...');
|
print('[KANJIDIC2] Reading file...');
|
||||||
final String rawXML = File(kanjidicPath).readAsStringSync();
|
final String rawXML = File('data/kanjidic2.xml').readAsStringSync();
|
||||||
|
|
||||||
print('[KANJIDIC2] Parsing XML...');
|
print('[KANJIDIC2] Parsing XML...');
|
||||||
final XmlElement root = XmlDocument.parse(rawXML).getElement('kanjidic2')!;
|
final XmlElement root = XmlDocument.parse(rawXML).getElement('kanjidic2')!;
|
||||||
@@ -65,13 +51,8 @@ Future<void> parseAndSeedDataFromKANJIDIC(Database db) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> parseAndSeedDataFromRADKFILE(Database db) async {
|
Future<void> parseAndSeedDataFromRADKFILE(Database db) async {
|
||||||
final radkfilePath = Platform.environment['RADKFILE_PATH'] ?? 'data/RADKFILE';
|
|
||||||
if (!File(radkfilePath).existsSync()) {
|
|
||||||
throw Exception('RADKFILE not found at $radkfilePath');
|
|
||||||
}
|
|
||||||
|
|
||||||
print('[RADKFILE] Reading file...');
|
print('[RADKFILE] Reading file...');
|
||||||
final File raw = File(radkfilePath);
|
final File raw = File('data/RADKFILE');
|
||||||
|
|
||||||
print('[RADKFILE] Parsing content...');
|
print('[RADKFILE] Parsing content...');
|
||||||
final blocks = parseRADKFILEBlocks(raw);
|
final blocks = parseRADKFILEBlocks(raw);
|
||||||
@@ -81,19 +62,13 @@ Future<void> parseAndSeedDataFromRADKFILE(Database db) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> parseAndSeedDataFromTanosJLPT(Database db) async {
|
Future<void> parseAndSeedDataFromTanosJLPT(Database db) async {
|
||||||
final tanosJlptPath =
|
|
||||||
Platform.environment['TANOS_JLPT_PATH'] ?? 'data/tanos-jlpt';
|
|
||||||
if (!Directory(tanosJlptPath).existsSync()) {
|
|
||||||
throw Exception('TANOS-JLPT directory not found at $tanosJlptPath');
|
|
||||||
}
|
|
||||||
|
|
||||||
print('[TANOS-JLPT] Reading files...');
|
print('[TANOS-JLPT] Reading files...');
|
||||||
final Map<String, File> files = {
|
final Map<String, File> files = {
|
||||||
'N1': File('$tanosJlptPath/n1.csv'),
|
'N1': File('data/tanos-jlpt/n1.csv'),
|
||||||
'N2': File('$tanosJlptPath/n2.csv'),
|
'N2': File('data/tanos-jlpt/n2.csv'),
|
||||||
'N3': File('$tanosJlptPath/n3.csv'),
|
'N3': File('data/tanos-jlpt/n3.csv'),
|
||||||
'N4': File('$tanosJlptPath/n4.csv'),
|
'N4': File('data/tanos-jlpt/n4.csv'),
|
||||||
'N5': File('$tanosJlptPath/n5.csv'),
|
'N5': File('data/tanos-jlpt/n5.csv'),
|
||||||
};
|
};
|
||||||
|
|
||||||
print('[TANOS-JLPT] Parsing content...');
|
print('[TANOS-JLPT] Parsing content...');
|
||||||
@@ -105,16 +80,3 @@ Future<void> parseAndSeedDataFromTanosJLPT(Database db) async {
|
|||||||
print('[TANOS-JLPT] Writing to database...');
|
print('[TANOS-JLPT] Writing to database...');
|
||||||
await seedTanosJLPTData(resolvedEntries, db);
|
await seedTanosJLPTData(resolvedEntries, db);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> parseAndSeedDataFromKanjiVG(Database db) async {
|
|
||||||
final kanjivgPath = Platform.environment['KANJIVG_PATH'] ?? 'data/kanjivg';
|
|
||||||
if (!Directory(kanjivgPath).existsSync()) {
|
|
||||||
throw Exception('KANJIVG directory not found at $kanjivgPath');
|
|
||||||
}
|
|
||||||
|
|
||||||
print('[KANJIVG] Parsing content...');
|
|
||||||
final items = parseKanjiVGData(Directory(kanjivgPath));
|
|
||||||
|
|
||||||
print('[KANJIVG] Writing to database...');
|
|
||||||
await seedKanjiVGData(items, db);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ class CreateDb extends Command {
|
|||||||
})
|
})
|
||||||
.catchError((error) {
|
.catchError((error) {
|
||||||
print('Error creating database: $error');
|
print('Error creating database: $error');
|
||||||
print(error.stackTrace);
|
|
||||||
failed = true;
|
failed = true;
|
||||||
})
|
})
|
||||||
.whenComplete(() {
|
.whenComplete(() {
|
||||||
|
|||||||
@@ -42,18 +42,12 @@ class CreateTanosJlptMappings extends Command {
|
|||||||
|
|
||||||
final useOverrides = argResults!.flag('overrides');
|
final useOverrides = argResults!.flag('overrides');
|
||||||
|
|
||||||
final tanosJlptPath =
|
|
||||||
Platform.environment['TANOS_JLPT_PATH'] ?? 'data/tanos-jlpt';
|
|
||||||
if (!Directory(tanosJlptPath).existsSync()) {
|
|
||||||
throw Exception('TANOS-JLPT directory not found at $tanosJlptPath');
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<String, File> files = {
|
final Map<String, File> files = {
|
||||||
'N1': File('$tanosJlptPath/n1.csv'),
|
'N1': File('data/tanos-jlpt/n1.csv'),
|
||||||
'N2': File('$tanosJlptPath/n2.csv'),
|
'N2': File('data/tanos-jlpt/n2.csv'),
|
||||||
'N3': File('$tanosJlptPath/n3.csv'),
|
'N3': File('data/tanos-jlpt/n3.csv'),
|
||||||
'N4': File('$tanosJlptPath/n4.csv'),
|
'N4': File('data/tanos-jlpt/n4.csv'),
|
||||||
'N5': File('$tanosJlptPath/n5.csv'),
|
'N5': File('data/tanos-jlpt/n5.csv'),
|
||||||
};
|
};
|
||||||
|
|
||||||
final rankedWords = await parseJLPTRankedWords(files);
|
final rankedWords = await parseJLPTRankedWords(files);
|
||||||
|
|||||||
+204
-297
@@ -1,310 +1,217 @@
|
|||||||
import 'package:collection/collection.dart';
|
const Map<int, List<String>> radicals = {
|
||||||
|
1: ['一', '|', '丶', 'ノ', '乙', '亅'],
|
||||||
class RadkfileRadical {
|
|
||||||
/// The formal variant of the radical
|
|
||||||
///
|
|
||||||
/// This is the one you want to use for searching.
|
|
||||||
final String formalVariant;
|
|
||||||
|
|
||||||
/// The informal variant of the radical (if it differs from the formal one).
|
|
||||||
///
|
|
||||||
/// This is the one you should display to the user.
|
|
||||||
final String? informalVariant;
|
|
||||||
|
|
||||||
/// The number of strokes in this radical.
|
|
||||||
final int strokeCount;
|
|
||||||
|
|
||||||
const RadkfileRadical({
|
|
||||||
required this.formalVariant,
|
|
||||||
this.informalVariant,
|
|
||||||
required this.strokeCount,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const Map<int, List<RadkfileRadical>> radicals = {
|
|
||||||
1: [
|
|
||||||
RadkfileRadical(formalVariant: '一', strokeCount: 1),
|
|
||||||
RadkfileRadical(formalVariant: '|', strokeCount: 1),
|
|
||||||
RadkfileRadical(formalVariant: '丶', strokeCount: 1),
|
|
||||||
RadkfileRadical(formalVariant: 'ノ', strokeCount: 1),
|
|
||||||
RadkfileRadical(formalVariant: '乙', strokeCount: 1),
|
|
||||||
RadkfileRadical(formalVariant: '亅', strokeCount: 1),
|
|
||||||
],
|
|
||||||
2: [
|
2: [
|
||||||
RadkfileRadical(formalVariant: '二', strokeCount: 2),
|
'二',
|
||||||
RadkfileRadical(formalVariant: '亠', strokeCount: 2),
|
'亠',
|
||||||
RadkfileRadical(formalVariant: '人', strokeCount: 2),
|
'人',
|
||||||
RadkfileRadical(formalVariant: '化', informalVariant: '⺅', strokeCount: 2),
|
'⺅',
|
||||||
RadkfileRadical(formalVariant: '个', informalVariant: '𠆢', strokeCount: 2),
|
'𠆢',
|
||||||
RadkfileRadical(formalVariant: '儿', strokeCount: 2),
|
'儿',
|
||||||
RadkfileRadical(formalVariant: '入', strokeCount: 2),
|
'入',
|
||||||
RadkfileRadical(formalVariant: 'ハ', strokeCount: 2),
|
'ハ',
|
||||||
RadkfileRadical(formalVariant: '并', strokeCount: 2),
|
'丷',
|
||||||
RadkfileRadical(formalVariant: '冂', strokeCount: 2),
|
'冂',
|
||||||
RadkfileRadical(formalVariant: '冖', strokeCount: 2),
|
'冖',
|
||||||
RadkfileRadical(formalVariant: '冫', strokeCount: 2),
|
'冫',
|
||||||
RadkfileRadical(formalVariant: '几', strokeCount: 2),
|
'几',
|
||||||
RadkfileRadical(formalVariant: '凵', strokeCount: 2),
|
'凵',
|
||||||
RadkfileRadical(formalVariant: '刀', strokeCount: 2),
|
'刀',
|
||||||
RadkfileRadical(formalVariant: '刈', strokeCount: 2),
|
'⺉',
|
||||||
RadkfileRadical(formalVariant: '力', strokeCount: 2),
|
'力',
|
||||||
RadkfileRadical(formalVariant: '勹', strokeCount: 2),
|
'勹',
|
||||||
RadkfileRadical(formalVariant: '匕', strokeCount: 2),
|
'匕',
|
||||||
RadkfileRadical(formalVariant: '匚', strokeCount: 2),
|
'匚',
|
||||||
RadkfileRadical(formalVariant: '十', strokeCount: 2),
|
'十',
|
||||||
RadkfileRadical(formalVariant: '卜', strokeCount: 2),
|
'卜',
|
||||||
RadkfileRadical(formalVariant: '卩', strokeCount: 2),
|
'卩',
|
||||||
RadkfileRadical(formalVariant: '厂', strokeCount: 2),
|
'厂',
|
||||||
RadkfileRadical(formalVariant: '厶', strokeCount: 2),
|
'厶',
|
||||||
RadkfileRadical(formalVariant: '又', strokeCount: 2),
|
'又',
|
||||||
RadkfileRadical(formalVariant: 'マ', strokeCount: 2),
|
'マ',
|
||||||
RadkfileRadical(formalVariant: '九', strokeCount: 2),
|
'九',
|
||||||
RadkfileRadical(formalVariant: 'ユ', strokeCount: 2),
|
'ユ',
|
||||||
RadkfileRadical(formalVariant: '乃', strokeCount: 2),
|
'乃',
|
||||||
RadkfileRadical(formalVariant: '乞', strokeCount: 2),
|
'𠂉',
|
||||||
],
|
],
|
||||||
3: [
|
3: [
|
||||||
RadkfileRadical(formalVariant: '込', informalVariant: '⻌', strokeCount: 3),
|
'⻌',
|
||||||
RadkfileRadical(formalVariant: '口', strokeCount: 3),
|
'口',
|
||||||
RadkfileRadical(formalVariant: '囗', strokeCount: 3),
|
'囗',
|
||||||
RadkfileRadical(formalVariant: '土', strokeCount: 3),
|
'土',
|
||||||
RadkfileRadical(formalVariant: '士', strokeCount: 3),
|
'士',
|
||||||
RadkfileRadical(formalVariant: '夂', strokeCount: 3),
|
'夂',
|
||||||
RadkfileRadical(formalVariant: '夕', strokeCount: 3),
|
'夕',
|
||||||
RadkfileRadical(formalVariant: '大', strokeCount: 3),
|
'大',
|
||||||
RadkfileRadical(formalVariant: '女', strokeCount: 3),
|
'女',
|
||||||
RadkfileRadical(formalVariant: '子', strokeCount: 3),
|
'子',
|
||||||
RadkfileRadical(formalVariant: '宀', strokeCount: 3),
|
'宀',
|
||||||
RadkfileRadical(formalVariant: '寸', strokeCount: 3),
|
'寸',
|
||||||
RadkfileRadical(formalVariant: '小', strokeCount: 3),
|
'小',
|
||||||
RadkfileRadical(formalVariant: '尚', informalVariant: '⺌', strokeCount: 3),
|
'⺌',
|
||||||
RadkfileRadical(formalVariant: '尢', strokeCount: 3),
|
'尢',
|
||||||
RadkfileRadical(formalVariant: '尸', strokeCount: 3),
|
'尸',
|
||||||
RadkfileRadical(formalVariant: '屮', strokeCount: 3),
|
'屮',
|
||||||
RadkfileRadical(formalVariant: '山', strokeCount: 3),
|
'山',
|
||||||
RadkfileRadical(formalVariant: '川', strokeCount: 3),
|
'川',
|
||||||
RadkfileRadical(formalVariant: '巛', strokeCount: 3),
|
'巛',
|
||||||
RadkfileRadical(formalVariant: '工', strokeCount: 3),
|
'工',
|
||||||
RadkfileRadical(formalVariant: '已', strokeCount: 3),
|
'已',
|
||||||
RadkfileRadical(formalVariant: '巾', strokeCount: 3),
|
'巾',
|
||||||
RadkfileRadical(formalVariant: '干', strokeCount: 3),
|
'干',
|
||||||
RadkfileRadical(formalVariant: '幺', strokeCount: 3),
|
'幺',
|
||||||
RadkfileRadical(formalVariant: '广', strokeCount: 3),
|
'广',
|
||||||
RadkfileRadical(formalVariant: '廴', strokeCount: 3),
|
'廴',
|
||||||
RadkfileRadical(formalVariant: '廾', strokeCount: 3),
|
'廾',
|
||||||
RadkfileRadical(formalVariant: '弋', strokeCount: 3),
|
'弋',
|
||||||
RadkfileRadical(formalVariant: '弓', strokeCount: 3),
|
'弓',
|
||||||
RadkfileRadical(formalVariant: 'ヨ', strokeCount: 3),
|
'ヨ',
|
||||||
RadkfileRadical(formalVariant: '彑', strokeCount: 3),
|
'彑',
|
||||||
RadkfileRadical(formalVariant: '彡', strokeCount: 3),
|
'彡',
|
||||||
RadkfileRadical(formalVariant: '彳', strokeCount: 3),
|
'彳',
|
||||||
RadkfileRadical(formalVariant: '忙', informalVariant: '⺖', strokeCount: 3),
|
'⺖',
|
||||||
RadkfileRadical(formalVariant: '扎', informalVariant: '⺘', strokeCount: 3),
|
'⺘',
|
||||||
RadkfileRadical(formalVariant: '汁', informalVariant: '⺡', strokeCount: 3),
|
'⺡',
|
||||||
RadkfileRadical(formalVariant: '犯', informalVariant: '⺨', strokeCount: 3),
|
'⺨',
|
||||||
RadkfileRadical(formalVariant: '艾', informalVariant: '⺾', strokeCount: 3),
|
'⺾',
|
||||||
RadkfileRadical(formalVariant: '邦', informalVariant: '⻏', strokeCount: 3),
|
'⻏',
|
||||||
RadkfileRadical(formalVariant: '阡', informalVariant: '⻖', strokeCount: 3),
|
'⻖',
|
||||||
RadkfileRadical(formalVariant: '也', strokeCount: 3),
|
'也',
|
||||||
RadkfileRadical(formalVariant: '亡', strokeCount: 3),
|
'亡',
|
||||||
RadkfileRadical(formalVariant: '及', strokeCount: 3),
|
'及',
|
||||||
RadkfileRadical(formalVariant: '久', strokeCount: 3),
|
'久',
|
||||||
],
|
],
|
||||||
4: [
|
4: [
|
||||||
RadkfileRadical(formalVariant: '老', informalVariant: '⺹', strokeCount: 4),
|
'⺹',
|
||||||
RadkfileRadical(formalVariant: '心', strokeCount: 4),
|
'心',
|
||||||
RadkfileRadical(formalVariant: '戈', strokeCount: 4),
|
'戈',
|
||||||
RadkfileRadical(formalVariant: '戸', strokeCount: 4),
|
'戸',
|
||||||
RadkfileRadical(formalVariant: '手', strokeCount: 4),
|
'手',
|
||||||
RadkfileRadical(formalVariant: '支', strokeCount: 4),
|
'支',
|
||||||
RadkfileRadical(formalVariant: '攵', strokeCount: 4),
|
'攵',
|
||||||
RadkfileRadical(formalVariant: '文', strokeCount: 4),
|
'文',
|
||||||
RadkfileRadical(formalVariant: '斗', strokeCount: 4),
|
'斗',
|
||||||
RadkfileRadical(formalVariant: '斤', strokeCount: 4),
|
'斤',
|
||||||
RadkfileRadical(formalVariant: '方', strokeCount: 4),
|
'方',
|
||||||
RadkfileRadical(formalVariant: '无', strokeCount: 4),
|
'无',
|
||||||
RadkfileRadical(formalVariant: '日', strokeCount: 4),
|
'日',
|
||||||
RadkfileRadical(formalVariant: '曰', strokeCount: 4),
|
'曰',
|
||||||
RadkfileRadical(formalVariant: '月', strokeCount: 4),
|
'月',
|
||||||
RadkfileRadical(formalVariant: '木', strokeCount: 4),
|
'木',
|
||||||
RadkfileRadical(formalVariant: '欠', strokeCount: 4),
|
'欠',
|
||||||
RadkfileRadical(formalVariant: '止', strokeCount: 4),
|
'止',
|
||||||
RadkfileRadical(formalVariant: '歹', strokeCount: 4),
|
'歹',
|
||||||
RadkfileRadical(formalVariant: '殳', strokeCount: 4),
|
'殳',
|
||||||
RadkfileRadical(formalVariant: '比', strokeCount: 4),
|
'比',
|
||||||
RadkfileRadical(formalVariant: '毛', strokeCount: 4),
|
'毛',
|
||||||
RadkfileRadical(formalVariant: '氏', strokeCount: 4),
|
'氏',
|
||||||
RadkfileRadical(formalVariant: '气', strokeCount: 4),
|
'气',
|
||||||
RadkfileRadical(formalVariant: '水', strokeCount: 4),
|
'水',
|
||||||
RadkfileRadical(formalVariant: '火', strokeCount: 4),
|
'火',
|
||||||
RadkfileRadical(formalVariant: '杰', strokeCount: 4),
|
'⺣',
|
||||||
RadkfileRadical(formalVariant: '爪', strokeCount: 4),
|
'爪',
|
||||||
RadkfileRadical(formalVariant: '父', strokeCount: 4),
|
'父',
|
||||||
RadkfileRadical(formalVariant: '爻', strokeCount: 4),
|
'爻',
|
||||||
RadkfileRadical(formalVariant: '爿', strokeCount: 4),
|
'爿',
|
||||||
RadkfileRadical(formalVariant: '片', strokeCount: 4),
|
'片',
|
||||||
RadkfileRadical(formalVariant: '牛', strokeCount: 4),
|
'牛',
|
||||||
RadkfileRadical(formalVariant: '犬', strokeCount: 4),
|
'犬',
|
||||||
RadkfileRadical(formalVariant: '礼', strokeCount: 4),
|
'⺭',
|
||||||
RadkfileRadical(formalVariant: '王', strokeCount: 4),
|
'王',
|
||||||
RadkfileRadical(formalVariant: '元', strokeCount: 4),
|
'元',
|
||||||
RadkfileRadical(formalVariant: '井', strokeCount: 4),
|
'井',
|
||||||
RadkfileRadical(formalVariant: '勿', strokeCount: 4),
|
'勿',
|
||||||
RadkfileRadical(formalVariant: '尤', strokeCount: 4),
|
'尤',
|
||||||
RadkfileRadical(formalVariant: '五', strokeCount: 4),
|
'五',
|
||||||
RadkfileRadical(formalVariant: '屯', strokeCount: 4),
|
'屯',
|
||||||
RadkfileRadical(formalVariant: '巴', strokeCount: 4),
|
'巴',
|
||||||
RadkfileRadical(formalVariant: '毋', strokeCount: 4),
|
'毋',
|
||||||
],
|
],
|
||||||
5: [
|
5: [
|
||||||
RadkfileRadical(formalVariant: '玄', strokeCount: 5),
|
'玄',
|
||||||
RadkfileRadical(formalVariant: '瓦', strokeCount: 5),
|
'瓦',
|
||||||
RadkfileRadical(formalVariant: '甘', strokeCount: 5),
|
'甘',
|
||||||
RadkfileRadical(formalVariant: '生', strokeCount: 5),
|
'生',
|
||||||
RadkfileRadical(formalVariant: '用', strokeCount: 5),
|
'用',
|
||||||
RadkfileRadical(formalVariant: '田', strokeCount: 5),
|
'田',
|
||||||
RadkfileRadical(formalVariant: '疋', strokeCount: 5),
|
'疋',
|
||||||
RadkfileRadical(formalVariant: '疔', strokeCount: 5),
|
'疒',
|
||||||
RadkfileRadical(formalVariant: '癶', strokeCount: 5),
|
'癶',
|
||||||
RadkfileRadical(formalVariant: '白', strokeCount: 5),
|
'白',
|
||||||
RadkfileRadical(formalVariant: '皮', strokeCount: 5),
|
'皮',
|
||||||
RadkfileRadical(formalVariant: '皿', strokeCount: 5),
|
'皿',
|
||||||
RadkfileRadical(formalVariant: '目', strokeCount: 5),
|
'目',
|
||||||
RadkfileRadical(formalVariant: '矛', strokeCount: 5),
|
'矛',
|
||||||
RadkfileRadical(formalVariant: '矢', strokeCount: 5),
|
'矢',
|
||||||
RadkfileRadical(formalVariant: '石', strokeCount: 5),
|
'石',
|
||||||
RadkfileRadical(formalVariant: '示', strokeCount: 5),
|
'示',
|
||||||
RadkfileRadical(formalVariant: '禹', strokeCount: 5),
|
'禸',
|
||||||
RadkfileRadical(formalVariant: '禾', strokeCount: 5),
|
'禾',
|
||||||
RadkfileRadical(formalVariant: '穴', strokeCount: 5),
|
'穴',
|
||||||
RadkfileRadical(formalVariant: '立', strokeCount: 5),
|
'立',
|
||||||
RadkfileRadical(formalVariant: '初', strokeCount: 5),
|
'⻂',
|
||||||
RadkfileRadical(formalVariant: '世', strokeCount: 5),
|
'世',
|
||||||
RadkfileRadical(formalVariant: '巨', strokeCount: 5),
|
'巨',
|
||||||
RadkfileRadical(formalVariant: '冊', strokeCount: 5),
|
'冊',
|
||||||
RadkfileRadical(formalVariant: '母', strokeCount: 5),
|
'母',
|
||||||
RadkfileRadical(formalVariant: '買', strokeCount: 5),
|
'⺲',
|
||||||
RadkfileRadical(formalVariant: '牙', strokeCount: 5),
|
'牙',
|
||||||
],
|
],
|
||||||
6: [
|
6: [
|
||||||
RadkfileRadical(formalVariant: '瓜', strokeCount: 6),
|
'瓜',
|
||||||
RadkfileRadical(formalVariant: '竹', strokeCount: 6),
|
'竹',
|
||||||
RadkfileRadical(formalVariant: '米', strokeCount: 6),
|
'米',
|
||||||
RadkfileRadical(formalVariant: '糸', strokeCount: 6),
|
'糸',
|
||||||
RadkfileRadical(formalVariant: '缶', strokeCount: 6),
|
'缶',
|
||||||
RadkfileRadical(formalVariant: '羊', strokeCount: 6),
|
'羊',
|
||||||
RadkfileRadical(formalVariant: '羽', strokeCount: 6),
|
'羽',
|
||||||
RadkfileRadical(formalVariant: '而', strokeCount: 6),
|
'而',
|
||||||
RadkfileRadical(formalVariant: '耒', strokeCount: 6),
|
'耒',
|
||||||
RadkfileRadical(formalVariant: '耳', strokeCount: 6),
|
'耳',
|
||||||
RadkfileRadical(formalVariant: '聿', strokeCount: 6),
|
'聿',
|
||||||
RadkfileRadical(formalVariant: '肉', strokeCount: 6),
|
'肉',
|
||||||
RadkfileRadical(formalVariant: '自', strokeCount: 6),
|
'自',
|
||||||
RadkfileRadical(formalVariant: '至', strokeCount: 6),
|
'至',
|
||||||
RadkfileRadical(formalVariant: '臼', strokeCount: 6),
|
'臼',
|
||||||
RadkfileRadical(formalVariant: '舌', strokeCount: 6),
|
'舌',
|
||||||
RadkfileRadical(formalVariant: '舟', strokeCount: 6),
|
'舟',
|
||||||
RadkfileRadical(formalVariant: '艮', strokeCount: 6),
|
'艮',
|
||||||
RadkfileRadical(formalVariant: '色', strokeCount: 6),
|
'色',
|
||||||
RadkfileRadical(formalVariant: '虍', strokeCount: 6),
|
'虍',
|
||||||
RadkfileRadical(formalVariant: '虫', strokeCount: 6),
|
'虫',
|
||||||
RadkfileRadical(formalVariant: '血', strokeCount: 6),
|
'血',
|
||||||
RadkfileRadical(formalVariant: '行', strokeCount: 6),
|
'行',
|
||||||
RadkfileRadical(formalVariant: '衣', strokeCount: 6),
|
'衣',
|
||||||
RadkfileRadical(formalVariant: '西', strokeCount: 6),
|
'西',
|
||||||
],
|
],
|
||||||
7: [
|
7: [
|
||||||
RadkfileRadical(formalVariant: '臣', strokeCount: 7),
|
'臣',
|
||||||
RadkfileRadical(formalVariant: '見', strokeCount: 7),
|
'見',
|
||||||
RadkfileRadical(formalVariant: '角', strokeCount: 7),
|
'角',
|
||||||
RadkfileRadical(formalVariant: '言', strokeCount: 7),
|
'言',
|
||||||
RadkfileRadical(formalVariant: '谷', strokeCount: 7),
|
'谷',
|
||||||
RadkfileRadical(formalVariant: '豆', strokeCount: 7),
|
'豆',
|
||||||
RadkfileRadical(formalVariant: '豕', strokeCount: 7),
|
'豕',
|
||||||
RadkfileRadical(formalVariant: '豸', strokeCount: 7),
|
'豸',
|
||||||
RadkfileRadical(formalVariant: '貝', strokeCount: 7),
|
'貝',
|
||||||
RadkfileRadical(formalVariant: '赤', strokeCount: 7),
|
'赤',
|
||||||
RadkfileRadical(formalVariant: '走', strokeCount: 7),
|
'走',
|
||||||
RadkfileRadical(formalVariant: '足', strokeCount: 7),
|
'足',
|
||||||
RadkfileRadical(formalVariant: '身', strokeCount: 7),
|
'身',
|
||||||
RadkfileRadical(formalVariant: '車', strokeCount: 7),
|
'車',
|
||||||
RadkfileRadical(formalVariant: '辛', strokeCount: 7),
|
'辛',
|
||||||
RadkfileRadical(formalVariant: '辰', strokeCount: 7),
|
'辰',
|
||||||
RadkfileRadical(formalVariant: '酉', strokeCount: 7),
|
'酉',
|
||||||
RadkfileRadical(formalVariant: '釆', strokeCount: 7),
|
'釆',
|
||||||
RadkfileRadical(formalVariant: '里', strokeCount: 7),
|
'里',
|
||||||
RadkfileRadical(formalVariant: '舛', strokeCount: 7),
|
'舛',
|
||||||
RadkfileRadical(formalVariant: '麦', strokeCount: 7),
|
'麦',
|
||||||
],
|
],
|
||||||
8: [
|
8: ['金', '長', '門', '隶', '隹', '雨', '青', '非', '奄', '岡', '免', '斉'],
|
||||||
RadkfileRadical(formalVariant: '金', strokeCount: 8),
|
9: ['面', '革', '韭', '音', '頁', '風', '飛', '食', '首', '香', '品'],
|
||||||
RadkfileRadical(formalVariant: '長', strokeCount: 8),
|
10: ['馬', '骨', '高', '髟', '鬥', '鬯', '鬲', '鬼', '竜', '韋'],
|
||||||
RadkfileRadical(formalVariant: '門', strokeCount: 8),
|
11: ['魚', '鳥', '鹵', '鹿', '麻', '亀', '啇', '黄', '黒'],
|
||||||
RadkfileRadical(formalVariant: '隶', strokeCount: 8),
|
12: ['黍', '黹', '無', '歯'],
|
||||||
RadkfileRadical(formalVariant: '隹', strokeCount: 8),
|
13: ['黽', '鼎', '鼓', '鼠'],
|
||||||
RadkfileRadical(formalVariant: '雨', strokeCount: 8),
|
14: ['鼻', '齊'],
|
||||||
RadkfileRadical(formalVariant: '青', strokeCount: 8),
|
17: ['龠'],
|
||||||
RadkfileRadical(formalVariant: '非', strokeCount: 8),
|
|
||||||
RadkfileRadical(formalVariant: '奄', strokeCount: 8),
|
|
||||||
RadkfileRadical(formalVariant: '岡', strokeCount: 8),
|
|
||||||
RadkfileRadical(formalVariant: '免', strokeCount: 8),
|
|
||||||
RadkfileRadical(formalVariant: '斉', strokeCount: 8),
|
|
||||||
],
|
|
||||||
9: [
|
|
||||||
RadkfileRadical(formalVariant: '面', strokeCount: 9),
|
|
||||||
RadkfileRadical(formalVariant: '革', strokeCount: 9),
|
|
||||||
RadkfileRadical(formalVariant: '韭', strokeCount: 9),
|
|
||||||
RadkfileRadical(formalVariant: '音', strokeCount: 9),
|
|
||||||
RadkfileRadical(formalVariant: '頁', strokeCount: 9),
|
|
||||||
RadkfileRadical(formalVariant: '風', strokeCount: 9),
|
|
||||||
RadkfileRadical(formalVariant: '飛', strokeCount: 9),
|
|
||||||
RadkfileRadical(formalVariant: '食', strokeCount: 9),
|
|
||||||
RadkfileRadical(formalVariant: '首', strokeCount: 9),
|
|
||||||
RadkfileRadical(formalVariant: '香', strokeCount: 9),
|
|
||||||
RadkfileRadical(formalVariant: '品', strokeCount: 9),
|
|
||||||
],
|
|
||||||
10: [
|
|
||||||
RadkfileRadical(formalVariant: '馬', strokeCount: 10),
|
|
||||||
RadkfileRadical(formalVariant: '骨', strokeCount: 10),
|
|
||||||
RadkfileRadical(formalVariant: '高', strokeCount: 10),
|
|
||||||
RadkfileRadical(formalVariant: '髟', strokeCount: 10),
|
|
||||||
RadkfileRadical(formalVariant: '鬥', strokeCount: 10),
|
|
||||||
RadkfileRadical(formalVariant: '鬯', strokeCount: 10),
|
|
||||||
RadkfileRadical(formalVariant: '鬲', strokeCount: 10),
|
|
||||||
RadkfileRadical(formalVariant: '鬼', strokeCount: 10),
|
|
||||||
RadkfileRadical(formalVariant: '竜', strokeCount: 10),
|
|
||||||
RadkfileRadical(formalVariant: '韋', strokeCount: 10),
|
|
||||||
],
|
|
||||||
11: [
|
|
||||||
RadkfileRadical(formalVariant: '魚', strokeCount: 11),
|
|
||||||
RadkfileRadical(formalVariant: '鳥', strokeCount: 11),
|
|
||||||
RadkfileRadical(formalVariant: '鹵', strokeCount: 11),
|
|
||||||
RadkfileRadical(formalVariant: '鹿', strokeCount: 11),
|
|
||||||
RadkfileRadical(formalVariant: '麻', strokeCount: 11),
|
|
||||||
RadkfileRadical(formalVariant: '亀', strokeCount: 11),
|
|
||||||
RadkfileRadical(formalVariant: '滴', strokeCount: 11),
|
|
||||||
RadkfileRadical(formalVariant: '黄', strokeCount: 11),
|
|
||||||
RadkfileRadical(formalVariant: '黒', strokeCount: 11),
|
|
||||||
],
|
|
||||||
12: [
|
|
||||||
RadkfileRadical(formalVariant: '黍', strokeCount: 12),
|
|
||||||
RadkfileRadical(formalVariant: '黹', strokeCount: 12),
|
|
||||||
RadkfileRadical(formalVariant: '無', strokeCount: 12),
|
|
||||||
RadkfileRadical(formalVariant: '歯', strokeCount: 12),
|
|
||||||
],
|
|
||||||
13: [
|
|
||||||
RadkfileRadical(formalVariant: '黽', strokeCount: 13),
|
|
||||||
RadkfileRadical(formalVariant: '鼎', strokeCount: 13),
|
|
||||||
RadkfileRadical(formalVariant: '鼓', strokeCount: 13),
|
|
||||||
RadkfileRadical(formalVariant: '鼠', strokeCount: 13),
|
|
||||||
],
|
|
||||||
14: [
|
|
||||||
RadkfileRadical(formalVariant: '鼻', strokeCount: 14),
|
|
||||||
RadkfileRadical(formalVariant: '齊', strokeCount: 14),
|
|
||||||
],
|
|
||||||
17: [RadkfileRadical(formalVariant: '龠', strokeCount: 17)],
|
|
||||||
};
|
|
||||||
|
|
||||||
final Map<String, RadkfileRadical> radicalsByFormalVariant = {
|
|
||||||
for (final r in radicals.values.flattened) r.formalVariant: r,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
|
||||||
|
|
||||||
import 'kanjivg_path.dart';
|
|
||||||
import 'kanjivg_path_group.dart';
|
|
||||||
|
|
||||||
/// A full KanjiVG entry for a single character.
|
|
||||||
class KanjiVGEntry extends Equatable {
|
|
||||||
/// The kanji or character this entry belongs to.
|
|
||||||
final String character;
|
|
||||||
|
|
||||||
/// All stroke paths in drawing order.
|
|
||||||
///
|
|
||||||
/// Each path includes the rendered position of its stroke label.
|
|
||||||
final List<KanjiVGPath> paths;
|
|
||||||
|
|
||||||
/// The hierarchical group structure of the entry.
|
|
||||||
///
|
|
||||||
/// These are not really used in mugiten at the moment, so querying them is optional.
|
|
||||||
final List<KanjiVGPathGroup>? pathGroups;
|
|
||||||
|
|
||||||
KanjiVGEntry({
|
|
||||||
required this.character,
|
|
||||||
this.paths = const [],
|
|
||||||
this.pathGroups = const [],
|
|
||||||
}) : assert(
|
|
||||||
paths.isEmpty ||
|
|
||||||
(paths.first.pathId == 1 &&
|
|
||||||
paths.last.pathId == paths.length &&
|
|
||||||
paths.every((p) => p.pathId > 0)),
|
|
||||||
'Paths must be listed in a strictly growing order without holes, starting from pathId 1.',
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [character, paths, pathGroups];
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'character': character,
|
|
||||||
'paths': paths.map((e) => e.toJson()).toList(),
|
|
||||||
'pathGroups': pathGroups?.map((e) => e.toJson()).toList(),
|
|
||||||
};
|
|
||||||
|
|
||||||
factory KanjiVGEntry.fromJson(Map<String, dynamic> json) => KanjiVGEntry(
|
|
||||||
character: json['character'] as String,
|
|
||||||
paths: ((json['paths'] as List<dynamic>?) ?? const [])
|
|
||||||
.map((e) => KanjiVGPath.fromJson(Map<String, dynamic>.from(e as Map)))
|
|
||||||
.toList(),
|
|
||||||
pathGroups: ((json['pathGroups'] as List<dynamic>?))
|
|
||||||
?.map(
|
|
||||||
(e) => KanjiVGPathGroup.fromJson(Map<String, dynamic>.from(e as Map)),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
|
||||||
|
|
||||||
/// A stroke path from a KanjiVG entry.
|
|
||||||
class KanjiVGPath extends Equatable {
|
|
||||||
/// The path id within the KanjiVG entry.
|
|
||||||
final int pathId;
|
|
||||||
|
|
||||||
/// The optional KanjiVG stroke type.
|
|
||||||
final String? type;
|
|
||||||
|
|
||||||
/// The raw SVG `d` path string.
|
|
||||||
final String svgPath;
|
|
||||||
|
|
||||||
/// The x-coordinate of the rendered stroke-label position.
|
|
||||||
final double labelX;
|
|
||||||
|
|
||||||
/// The y-coordinate of the rendered stroke-label position.
|
|
||||||
final double labelY;
|
|
||||||
|
|
||||||
KanjiVGPath({
|
|
||||||
required this.pathId,
|
|
||||||
required this.type,
|
|
||||||
required this.svgPath,
|
|
||||||
required this.labelX,
|
|
||||||
required this.labelY,
|
|
||||||
}) : assert(pathId > 0, 'pathId must be a positive integer. Found $pathId.'),
|
|
||||||
assert(svgPath.isNotEmpty, 'svgPath cannot be empty.'),
|
|
||||||
assert(
|
|
||||||
labelX.isFinite,
|
|
||||||
'labelX must be a finite number. Found $labelX.',
|
|
||||||
),
|
|
||||||
assert(
|
|
||||||
labelY.isFinite,
|
|
||||||
'labelY must be a finite number. Found $labelY.',
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [pathId, type, svgPath, labelX, labelY];
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'pathId': pathId,
|
|
||||||
'type': type,
|
|
||||||
'svgPath': svgPath,
|
|
||||||
'labelX': labelX,
|
|
||||||
'labelY': labelY,
|
|
||||||
};
|
|
||||||
|
|
||||||
factory KanjiVGPath.fromJson(Map<String, dynamic> json) => KanjiVGPath(
|
|
||||||
pathId: json['pathId'] as int,
|
|
||||||
type: json['type'] as String?,
|
|
||||||
svgPath: json['svgPath'] as String,
|
|
||||||
labelX: (json['labelX'] as num).toDouble(),
|
|
||||||
labelY: (json['labelY'] as num).toDouble(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:jadb/models/kanjivg/kanjivg_path.dart';
|
|
||||||
|
|
||||||
import 'kanjivg_path_group_position.dart';
|
|
||||||
import 'kanjivg_radical.dart';
|
|
||||||
|
|
||||||
/// A hierarchical path-group from a KanjiVG entry.
|
|
||||||
class KanjiVGPathGroup extends Equatable {
|
|
||||||
/// The path-group id within the entry.
|
|
||||||
final int groupId;
|
|
||||||
|
|
||||||
/// The paths directly contained in this group, in drawing order.
|
|
||||||
final List<KanjiVGPath> paths;
|
|
||||||
|
|
||||||
/// Nested child groups.
|
|
||||||
final List<KanjiVGPathGroup> children;
|
|
||||||
|
|
||||||
/// The value of the `kvg:element` attribute, if present.
|
|
||||||
final String? element;
|
|
||||||
|
|
||||||
/// The original element before simplification, if present.
|
|
||||||
final String? original;
|
|
||||||
|
|
||||||
/// Relative position of the group inside the character layout.
|
|
||||||
final KanjiVGPathGroupPosition? position;
|
|
||||||
|
|
||||||
/// Radical classification for the group.
|
|
||||||
final KanjiVGRadical? radical;
|
|
||||||
|
|
||||||
/// Part number for repeated elements, if present.
|
|
||||||
final int? part;
|
|
||||||
|
|
||||||
KanjiVGPathGroup({
|
|
||||||
required this.groupId,
|
|
||||||
this.paths = const [],
|
|
||||||
this.children = const [],
|
|
||||||
this.element,
|
|
||||||
this.original,
|
|
||||||
this.position,
|
|
||||||
this.radical,
|
|
||||||
this.part,
|
|
||||||
}) : assert(
|
|
||||||
groupId >= 0,
|
|
||||||
'groupId must be a non-negative integer. Found $groupId.',
|
|
||||||
),
|
|
||||||
assert(
|
|
||||||
paths.isEmpty ||
|
|
||||||
paths.fold<int>(
|
|
||||||
0,
|
|
||||||
(previousMax, path) => path.pathId > previousMax
|
|
||||||
? path.pathId
|
|
||||||
: throw ArgumentError(
|
|
||||||
'Paths must be listed in a strictly growing order without holes. Found pathId ${path.pathId} after $previousMax.',
|
|
||||||
),
|
|
||||||
) ==
|
|
||||||
paths.lastOrNull?.pathId,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [
|
|
||||||
groupId,
|
|
||||||
paths,
|
|
||||||
children,
|
|
||||||
element,
|
|
||||||
original,
|
|
||||||
position,
|
|
||||||
radical,
|
|
||||||
part,
|
|
||||||
];
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'groupId': groupId,
|
|
||||||
'paths': paths.map((e) => e.toJson()).toList(),
|
|
||||||
'children': children.map((e) => e.toJson()).toList(),
|
|
||||||
'element': element,
|
|
||||||
'original': original,
|
|
||||||
'position': position?.toJson(),
|
|
||||||
'radical': radical?.toJson(),
|
|
||||||
'part': part,
|
|
||||||
};
|
|
||||||
|
|
||||||
factory KanjiVGPathGroup.fromJson(
|
|
||||||
Map<String, dynamic> json,
|
|
||||||
) => KanjiVGPathGroup(
|
|
||||||
groupId: json['groupId'] as int,
|
|
||||||
paths: ((json['paths'] as List<dynamic>?) ?? const [])
|
|
||||||
.map((e) => KanjiVGPath.fromJson(Map<String, dynamic>.from(e as Map)))
|
|
||||||
.toList(),
|
|
||||||
children: ((json['children'] as List<dynamic>?) ?? const [])
|
|
||||||
.map(
|
|
||||||
(e) => KanjiVGPathGroup.fromJson(Map<String, dynamic>.from(e as Map)),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
element: json['element'] as String?,
|
|
||||||
original: json['original'] as String?,
|
|
||||||
position: KanjiVGPathGroupPosition.fromJson(json['position']),
|
|
||||||
radical: KanjiVGRadical.fromJson(json['radical']),
|
|
||||||
part: json['part'] as int?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/// Relative position tags used by KanjiVG path-groups.
|
|
||||||
///
|
|
||||||
/// In the original SVG files these come from the `kvg:position` attribute.
|
|
||||||
/// The database stores the normalized enum name, while [svgValue] contains the
|
|
||||||
/// raw KanjiVG attribute value.
|
|
||||||
enum KanjiVGPathGroupPosition {
|
|
||||||
upperA(svgValue: '⿵A'),
|
|
||||||
upperB(svgValue: '⿵B'),
|
|
||||||
lower1(svgValue: '⿶1'),
|
|
||||||
lower2(svgValue: '⿶2'),
|
|
||||||
bottom(svgValue: 'bottom'),
|
|
||||||
kamae(svgValue: 'kamae'),
|
|
||||||
kamaec(svgValue: 'kamaec'),
|
|
||||||
left(svgValue: 'left'),
|
|
||||||
middle(svgValue: 'middle'),
|
|
||||||
nyo(svgValue: 'nyo'),
|
|
||||||
nyoc(svgValue: 'nyoc'),
|
|
||||||
right(svgValue: 'right'),
|
|
||||||
tare(svgValue: 'tare'),
|
|
||||||
tarec(svgValue: 'tarec'),
|
|
||||||
top(svgValue: 'top');
|
|
||||||
|
|
||||||
final String svgValue;
|
|
||||||
|
|
||||||
const KanjiVGPathGroupPosition({required this.svgValue});
|
|
||||||
|
|
||||||
/// Parses either the normalized enum name stored in the database/JSON, or
|
|
||||||
/// the raw KanjiVG SVG attribute value.
|
|
||||||
static KanjiVGPathGroupPosition fromString(String value) => values.firstWhere(
|
|
||||||
(e) => e.name == value || e.svgValue == value,
|
|
||||||
orElse: () => throw Exception('Unknown position: $value'),
|
|
||||||
);
|
|
||||||
|
|
||||||
static KanjiVGPathGroupPosition? fromNullableString(String? value) =>
|
|
||||||
value == null ? null : fromString(value);
|
|
||||||
|
|
||||||
Object? toJson() => name;
|
|
||||||
|
|
||||||
static KanjiVGPathGroupPosition? fromJson(Object? json) =>
|
|
||||||
fromNullableString(json as String?);
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/// Radical classification tags used by KanjiVG path-groups.
|
|
||||||
enum KanjiVGRadical {
|
|
||||||
general,
|
|
||||||
jis,
|
|
||||||
nelson,
|
|
||||||
tradit;
|
|
||||||
|
|
||||||
static KanjiVGRadical fromString(String value) => values.firstWhere(
|
|
||||||
(e) => e.name == value,
|
|
||||||
orElse: () => throw Exception('Unknown radical: $value'),
|
|
||||||
);
|
|
||||||
|
|
||||||
static KanjiVGRadical? fromNullableString(String? value) =>
|
|
||||||
value == null ? null : fromString(value);
|
|
||||||
|
|
||||||
Object? toJson() => name;
|
|
||||||
|
|
||||||
static KanjiVGRadical? fromJson(Object? json) =>
|
|
||||||
fromNullableString(json as String?);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:jadb/table_names/jmdict.dart';
|
import 'package:jadb/table_names/jmdict.dart';
|
||||||
import 'package:jadb/table_names/kanjidic.dart';
|
import 'package:jadb/table_names/kanjidic.dart';
|
||||||
import 'package:jadb/table_names/kanjivg.dart';
|
|
||||||
import 'package:jadb/table_names/radkfile.dart';
|
import 'package:jadb/table_names/radkfile.dart';
|
||||||
import 'package:jadb/table_names/tanos_jlpt.dart';
|
import 'package:jadb/table_names/tanos_jlpt.dart';
|
||||||
import 'package:sqflite_common/sqlite_api.dart';
|
import 'package:sqflite_common/sqlite_api.dart';
|
||||||
@@ -22,7 +21,6 @@ Future<void> verifyTablesWithDbConnection(DatabaseExecutor db) async {
|
|||||||
...KANJIDICTableNames.allTables,
|
...KANJIDICTableNames.allTables,
|
||||||
...RADKFILETableNames.allTables,
|
...RADKFILETableNames.allTables,
|
||||||
...TanosJLPTTableNames.allTables,
|
...TanosJLPTTableNames.allTables,
|
||||||
...KanjiVGTableNames.allTables,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
final missingTables = expectedTables.difference(tables);
|
final missingTables = expectedTables.difference(tables);
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ class WordSearchMatchSpan {
|
|||||||
Map<String, Object?> toJson() => {
|
Map<String, Object?> toJson() => {
|
||||||
'spanType': spanType.toString().split('.').last,
|
'spanType': spanType.toString().split('.').last,
|
||||||
'index': index,
|
'index': index,
|
||||||
'subIndex': subIndex,
|
|
||||||
'start': start,
|
'start': start,
|
||||||
'end': end,
|
'end': end,
|
||||||
};
|
};
|
||||||
@@ -44,13 +43,12 @@ class WordSearchMatchSpan {
|
|||||||
(e) => e.toString().split('.').last == json['spanType'],
|
(e) => e.toString().split('.').last == json['spanType'],
|
||||||
),
|
),
|
||||||
index: json['index'] as int,
|
index: json['index'] as int,
|
||||||
subIndex: json['subIndex'] as int? ?? 0,
|
|
||||||
start: json['start'] as int,
|
start: json['start'] as int,
|
||||||
end: json['end'] as int,
|
end: json['end'] as int,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(spanType, index, subIndex, start, end);
|
int get hashCode => Object.hash(spanType, index, start, end);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
@@ -58,7 +56,6 @@ class WordSearchMatchSpan {
|
|||||||
return other is WordSearchMatchSpan &&
|
return other is WordSearchMatchSpan &&
|
||||||
other.spanType == spanType &&
|
other.spanType == spanType &&
|
||||||
other.index == index &&
|
other.index == index &&
|
||||||
other.subIndex == subIndex &&
|
|
||||||
other.start == start &&
|
other.start == start &&
|
||||||
other.end == end;
|
other.end == end;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class WordSearchSense {
|
|||||||
'dialects': dialects.map((e) => e.toJson()).toList(),
|
'dialects': dialects.map((e) => e.toJson()).toList(),
|
||||||
'misc': misc.map((e) => e.toJson()).toList(),
|
'misc': misc.map((e) => e.toJson()).toList(),
|
||||||
'info': info,
|
'info': info,
|
||||||
'languageSource': languageSource.map((e) => e.toJson()).toList(),
|
'languageSource': languageSource,
|
||||||
};
|
};
|
||||||
|
|
||||||
factory WordSearchSense.fromJson(Map<String, dynamic> json) =>
|
factory WordSearchSense.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ class WordSearchXrefEntry {
|
|||||||
final bool ambiguous;
|
final bool ambiguous;
|
||||||
|
|
||||||
/// The result of the cross-reference, may or may not be included in the query.
|
/// The result of the cross-reference, may or may not be included in the query.
|
||||||
///
|
|
||||||
/// Be careful not to introduce circular references when using this field.
|
|
||||||
final WordSearchResult? xrefResult;
|
final WordSearchResult? xrefResult;
|
||||||
|
|
||||||
const WordSearchXrefEntry({
|
const WordSearchXrefEntry({
|
||||||
@@ -42,10 +40,6 @@ class WordSearchXrefEntry {
|
|||||||
ambiguous: json['ambiguous'] as bool,
|
ambiguous: json['ambiguous'] as bool,
|
||||||
baseWord: json['baseWord'] as String,
|
baseWord: json['baseWord'] as String,
|
||||||
furigana: json['furigana'] as String?,
|
furigana: json['furigana'] as String?,
|
||||||
xrefResult: json['xrefResult'] != null
|
xrefResult: null,
|
||||||
? WordSearchResult.fromJson(
|
|
||||||
Map<String, dynamic>.from(json['xrefResult'] as Map),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-41
@@ -1,13 +1,9 @@
|
|||||||
import 'package:jadb/const_data/radicals.dart';
|
|
||||||
import 'package:jadb/models/kanji_search/kanji_search_result.dart';
|
import 'package:jadb/models/kanji_search/kanji_search_result.dart';
|
||||||
import 'package:jadb/models/kanjivg/kanjivg_entry.dart';
|
|
||||||
import 'package:jadb/models/verify_tables.dart';
|
import 'package:jadb/models/verify_tables.dart';
|
||||||
import 'package:jadb/models/word_search/word_search_result.dart';
|
import 'package:jadb/models/word_search/word_search_result.dart';
|
||||||
import 'package:jadb/search/filter_kanji.dart';
|
import 'package:jadb/search/filter_kanji.dart';
|
||||||
import 'package:jadb/search/kanji_search.dart';
|
import 'package:jadb/search/kanji_search.dart';
|
||||||
import 'package:jadb/search/kanji_vg_search.dart';
|
|
||||||
import 'package:jadb/search/radical_search.dart';
|
import 'package:jadb/search/radical_search.dart';
|
||||||
import 'package:jadb/search/versions.dart';
|
|
||||||
import 'package:jadb/search/word_search/word_search.dart';
|
import 'package:jadb/search/word_search/word_search.dart';
|
||||||
import 'package:sqflite_common/sqlite_api.dart';
|
import 'package:sqflite_common/sqlite_api.dart';
|
||||||
|
|
||||||
@@ -22,19 +18,8 @@ extension JaDBConnection on DatabaseExecutor {
|
|||||||
searchKanjiWithDbConnection(this, kanji);
|
searchKanjiWithDbConnection(this, kanji);
|
||||||
|
|
||||||
/// Search for a kanji in the database.
|
/// Search for a kanji in the database.
|
||||||
Future<Map<String, KanjiSearchResult>> jadbGetManyKanji(
|
Future<Map<String, KanjiSearchResult>> jadbGetManyKanji(Iterable<String> kanji) =>
|
||||||
Iterable<String> kanji,
|
searchManyKanjiWithDbConnection(this, kanji);
|
||||||
) => searchManyKanjiWithDbConnection(this, kanji);
|
|
||||||
|
|
||||||
/// Search for a KanjiVG graph in the database.
|
|
||||||
Future<KanjiVGEntry?> jadbSearchKanjiVGGraph(
|
|
||||||
String kanji, {
|
|
||||||
bool includePathGroups = false,
|
|
||||||
}) => searchKanjiVGGraphWithDbConnection(
|
|
||||||
this,
|
|
||||||
kanji,
|
|
||||||
includePathGroups: includePathGroups,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Filter a list of characters, and return the ones that are listed in the kanji dictionary.
|
/// Filter a list of characters, and return the ones that are listed in the kanji dictionary.
|
||||||
Future<List<String>> filterKanji(
|
Future<List<String>> filterKanji(
|
||||||
@@ -46,23 +31,17 @@ extension JaDBConnection on DatabaseExecutor {
|
|||||||
Future<List<WordSearchResult>?> jadbSearchWord(
|
Future<List<WordSearchResult>?> jadbSearchWord(
|
||||||
String word, {
|
String word, {
|
||||||
SearchMode searchMode = SearchMode.auto,
|
SearchMode searchMode = SearchMode.auto,
|
||||||
fuzzyKana = true,
|
|
||||||
int page = 0,
|
int page = 0,
|
||||||
int? pageSize,
|
int? pageSize,
|
||||||
}) => searchWordWithDbConnection(
|
}) => searchWordWithDbConnection(
|
||||||
this,
|
this,
|
||||||
word,
|
word,
|
||||||
searchMode: searchMode,
|
searchMode: searchMode,
|
||||||
fuzzyKana: fuzzyKana,
|
|
||||||
page: page,
|
page: page,
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Filter a list of word IDs, and return the ones that are listed in the word dictionary.
|
///
|
||||||
Future<Set<int>> jadbFilterWordIds(Iterable<int> ids) =>
|
|
||||||
filterWordIdsWithDbConnection(this, ids);
|
|
||||||
|
|
||||||
/// Get a word by its ID. Returns null if no result is found for the given ID.
|
|
||||||
Future<WordSearchResult?> jadbGetWordById(int id) =>
|
Future<WordSearchResult?> jadbGetWordById(int id) =>
|
||||||
getWordByIdWithDbConnection(this, id);
|
getWordByIdWithDbConnection(this, id);
|
||||||
|
|
||||||
@@ -76,29 +55,17 @@ extension JaDBConnection on DatabaseExecutor {
|
|||||||
Future<int?> jadbSearchWordCount(
|
Future<int?> jadbSearchWordCount(
|
||||||
String word, {
|
String word, {
|
||||||
SearchMode searchMode = SearchMode.auto,
|
SearchMode searchMode = SearchMode.auto,
|
||||||
bool fuzzyKana = true,
|
}) => searchWordCountWithDbConnection(this, word, searchMode: searchMode);
|
||||||
}) => searchWordCountWithDbConnection(
|
|
||||||
this,
|
|
||||||
word,
|
|
||||||
searchMode: searchMode,
|
|
||||||
fuzzyKana: fuzzyKana,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Given a list of radicals, search which kanji contains all
|
/// Given a list of radicals, search which kanji contains all
|
||||||
/// of the radicals, find their other radicals, and return those.
|
/// of the radicals, find their other radicals, and return those.
|
||||||
/// This is used to figure out which remaining combinations of radicals
|
/// This is used to figure out which remaining combinations of radicals
|
||||||
/// the user can search for without getting zero results.
|
/// the user can search for without getting zero results.
|
||||||
Future<List<RadkfileRadical>> jadbSearchRemainingRadicals(
|
Future<List<String>> jadbSearchRemainingRadicals(List<String> radicals) =>
|
||||||
List<RadkfileRadical> radicals,
|
searchRemainingRadicalsWithDbConnection(this, radicals);
|
||||||
) => searchRemainingRadicalsWithDbConnection(this, radicals);
|
|
||||||
|
|
||||||
/// Given a list of radicals, search which kanji contains all
|
/// Given a list of radicals, search which kanji contains all
|
||||||
/// of the radicals, and return those.
|
/// of the radicals, and return those.
|
||||||
Future<List<String>> jadbSearchKanjiByRadicals(
|
Future<List<String>> jadbSearchKanjiByRadicals(List<String> radicals) =>
|
||||||
List<RadkfileRadical> radicals,
|
searchKanjiByRadicalsWithDbConnection(this, radicals);
|
||||||
) => searchKanjiByRadicalsWithDbConnection(this, radicals);
|
|
||||||
|
|
||||||
/// Retrieve the version information for all datasources in the database.
|
|
||||||
Future<DatasourceVersions> jadbGetDatasourceVersions() =>
|
|
||||||
getDatasourceVersions(this);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,175 +0,0 @@
|
|||||||
import 'package:jadb/models/kanjivg/kanjivg_entry.dart';
|
|
||||||
import 'package:jadb/models/kanjivg/kanjivg_path.dart';
|
|
||||||
import 'package:jadb/models/kanjivg/kanjivg_path_group.dart';
|
|
||||||
import 'package:jadb/models/kanjivg/kanjivg_path_group_position.dart';
|
|
||||||
import 'package:jadb/models/kanjivg/kanjivg_radical.dart';
|
|
||||||
import 'package:jadb/table_names/kanjivg.dart';
|
|
||||||
import 'package:sqflite_common/sqlite_api.dart';
|
|
||||||
|
|
||||||
Future<List<Map<String, Object?>>> _entryQuery(
|
|
||||||
DatabaseExecutor connection,
|
|
||||||
String entryKey,
|
|
||||||
) => connection.rawQuery(
|
|
||||||
'''
|
|
||||||
SELECT *
|
|
||||||
FROM "${KanjiVGTableNames.entry}"
|
|
||||||
WHERE "character" = ?
|
|
||||||
OR "character" LIKE ?
|
|
||||||
ORDER BY "character" != ?, "character"
|
|
||||||
LIMIT 1
|
|
||||||
''',
|
|
||||||
[entryKey, '$entryKey-%', entryKey],
|
|
||||||
);
|
|
||||||
|
|
||||||
Future<List<Map<String, Object?>>> _pathsQuery(
|
|
||||||
DatabaseExecutor connection,
|
|
||||||
String entryKey,
|
|
||||||
) => connection.rawQuery(
|
|
||||||
'''
|
|
||||||
SELECT
|
|
||||||
"${KanjiVGTableNames.path}"."pathId",
|
|
||||||
"${KanjiVGTableNames.path}"."groupId",
|
|
||||||
"${KanjiVGTableNames.path}"."type",
|
|
||||||
"${KanjiVGTableNames.path}"."svgPath",
|
|
||||||
"${KanjiVGTableNames.strokeNumber}"."x",
|
|
||||||
"${KanjiVGTableNames.strokeNumber}"."y"
|
|
||||||
FROM "${KanjiVGTableNames.path}"
|
|
||||||
JOIN "${KanjiVGTableNames.strokeNumber}"
|
|
||||||
ON "${KanjiVGTableNames.path}"."character" = "${KanjiVGTableNames.strokeNumber}"."character"
|
|
||||||
AND "${KanjiVGTableNames.path}"."pathId" = "${KanjiVGTableNames.strokeNumber}"."strokeNum"
|
|
||||||
WHERE "${KanjiVGTableNames.path}"."character" = ?
|
|
||||||
ORDER BY "${KanjiVGTableNames.path}"."pathId"
|
|
||||||
''',
|
|
||||||
[entryKey],
|
|
||||||
);
|
|
||||||
|
|
||||||
Future<List<Map<String, Object?>>> _pathGroupsQuery(
|
|
||||||
DatabaseExecutor connection,
|
|
||||||
String entryKey,
|
|
||||||
) => connection.query(
|
|
||||||
KanjiVGTableNames.pathGroup,
|
|
||||||
where: 'character = ?',
|
|
||||||
whereArgs: [entryKey],
|
|
||||||
orderBy: 'groupId',
|
|
||||||
);
|
|
||||||
|
|
||||||
String _normalizeKanjiVGEntryKey(String kanji) {
|
|
||||||
final encodedMatch = RegExp(r'^([0-9a-fA-F]{5,6})(-.+)?$').firstMatch(kanji);
|
|
||||||
if (encodedMatch != null) {
|
|
||||||
return '${encodedMatch.group(1)!.toLowerCase()}${encodedMatch.group(2) ?? ''}';
|
|
||||||
}
|
|
||||||
|
|
||||||
final runes = kanji.runes.toList(growable: false);
|
|
||||||
if (runes.length == 1) {
|
|
||||||
return runes.single.toRadixString(16).padLeft(5, '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
return kanji;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _characterFromEntryKey(String entryKey, String fallback) {
|
|
||||||
final encodedMatch = RegExp(r'^([0-9a-fA-F]{5,6})').firstMatch(entryKey);
|
|
||||||
if (encodedMatch == null) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
return String.fromCharCode(int.parse(encodedMatch.group(1)!, radix: 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
KanjiVGPath _pathFromRow(Map<String, Object?> row) => KanjiVGPath(
|
|
||||||
pathId: row['pathId'] as int,
|
|
||||||
type: row['type'] as String?,
|
|
||||||
svgPath: row['svgPath'] as String,
|
|
||||||
labelX: (row['x'] as num).toDouble(),
|
|
||||||
labelY: (row['y'] as num).toDouble(),
|
|
||||||
);
|
|
||||||
|
|
||||||
List<KanjiVGPathGroup> _buildPathGroups(
|
|
||||||
List<Map<String, Object?>> pathRows,
|
|
||||||
List<Map<String, Object?>> pathGroupRows,
|
|
||||||
) {
|
|
||||||
final rowsByGroupId = <int, Map<String, Object?>>{
|
|
||||||
for (final row in pathGroupRows) (row['groupId'] as int?)!: row,
|
|
||||||
};
|
|
||||||
|
|
||||||
final childGroupIdsByParentGroupId = <int?, List<int>>{};
|
|
||||||
for (final row in pathGroupRows) {
|
|
||||||
final groupId = (row['groupId'] as int?)!;
|
|
||||||
final parentGroupId = row['parentGroupId'] as int?;
|
|
||||||
childGroupIdsByParentGroupId
|
|
||||||
.putIfAbsent(parentGroupId, () => [])
|
|
||||||
.add(groupId);
|
|
||||||
}
|
|
||||||
|
|
||||||
final pathsByGroupId = <int, List<KanjiVGPath>>{};
|
|
||||||
for (final row in pathRows) {
|
|
||||||
final groupId = (row['groupId'] as int?)!;
|
|
||||||
pathsByGroupId.putIfAbsent(groupId, () => []).add(_pathFromRow(row));
|
|
||||||
}
|
|
||||||
|
|
||||||
KanjiVGPathGroup buildGroup(int groupId) {
|
|
||||||
final row = rowsByGroupId[groupId]!;
|
|
||||||
|
|
||||||
return KanjiVGPathGroup(
|
|
||||||
groupId: groupId,
|
|
||||||
paths: pathsByGroupId[groupId] ?? const [],
|
|
||||||
children: (childGroupIdsByParentGroupId[groupId] ?? const [])
|
|
||||||
.map(buildGroup)
|
|
||||||
.toList(growable: false),
|
|
||||||
element: row['element'] as String?,
|
|
||||||
original: row['original'] as String?,
|
|
||||||
position: KanjiVGPathGroupPosition.fromNullableString(
|
|
||||||
row['position'] as String?,
|
|
||||||
),
|
|
||||||
radical: KanjiVGRadical.fromNullableString(row['radical'] as String?),
|
|
||||||
part: row['part'] as int?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (childGroupIdsByParentGroupId[null] ?? const [])
|
|
||||||
.map(buildGroup)
|
|
||||||
.toList(growable: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Searches for a KanjiVG graph and returns its stroke data, or null if the
|
|
||||||
/// kanji is not found in the database.
|
|
||||||
Future<KanjiVGEntry?> searchKanjiVGGraphWithDbConnection(
|
|
||||||
DatabaseExecutor connection,
|
|
||||||
String kanji, {
|
|
||||||
bool includePathGroups = false,
|
|
||||||
}) async {
|
|
||||||
final entryKey = _normalizeKanjiVGEntryKey(kanji);
|
|
||||||
final entryRows = await _entryQuery(connection, entryKey);
|
|
||||||
|
|
||||||
if (entryRows.isEmpty) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final matchedEntryKey = entryRows.first['character'] as String;
|
|
||||||
|
|
||||||
late final List<Map<String, Object?>> pathRows;
|
|
||||||
List<Map<String, Object?>> pathGroupRows = const [];
|
|
||||||
|
|
||||||
if (includePathGroups) {
|
|
||||||
await Future.wait([
|
|
||||||
_pathsQuery(
|
|
||||||
connection,
|
|
||||||
matchedEntryKey,
|
|
||||||
).then((value) => pathRows = value),
|
|
||||||
_pathGroupsQuery(
|
|
||||||
connection,
|
|
||||||
matchedEntryKey,
|
|
||||||
).then((value) => pathGroupRows = value),
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
pathRows = await _pathsQuery(connection, matchedEntryKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
return KanjiVGEntry(
|
|
||||||
character: _characterFromEntryKey(matchedEntryKey, kanji),
|
|
||||||
paths: pathRows.map(_pathFromRow).toList(growable: false),
|
|
||||||
pathGroups: includePathGroups
|
|
||||||
? _buildPathGroups(pathRows, pathGroupRows)
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:jadb/const_data/radicals.dart';
|
|
||||||
import 'package:jadb/table_names/radkfile.dart';
|
import 'package:jadb/table_names/radkfile.dart';
|
||||||
import 'package:sqflite_common/sqlite_api.dart';
|
import 'package:sqflite_common/sqlite_api.dart';
|
||||||
|
|
||||||
@@ -8,9 +7,9 @@ import 'package:sqflite_common/sqlite_api.dart';
|
|||||||
///
|
///
|
||||||
/// This can be used to limit the choices of additional radicals provided to a user,
|
/// This can be used to limit the choices of additional radicals provided to a user,
|
||||||
/// so that any choice they make will still yield at least one kanji.
|
/// so that any choice they make will still yield at least one kanji.
|
||||||
Future<List<RadkfileRadical>> searchRemainingRadicalsWithDbConnection(
|
Future<List<String>> searchRemainingRadicalsWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
List<RadkfileRadical> radicals,
|
List<String> radicals,
|
||||||
) async {
|
) async {
|
||||||
final distinctRadicals = radicals.toSet();
|
final distinctRadicals = radicals.toSet();
|
||||||
|
|
||||||
@@ -26,14 +25,11 @@ Future<List<RadkfileRadical>> searchRemainingRadicalsWithDbConnection(
|
|||||||
HAVING COUNT(DISTINCT "radical") = ?
|
HAVING COUNT(DISTINCT "radical") = ?
|
||||||
)
|
)
|
||||||
''',
|
''',
|
||||||
[...distinctRadicals.map((r) => r.formalVariant), distinctRadicals.length],
|
[...distinctRadicals, distinctRadicals.length],
|
||||||
);
|
);
|
||||||
|
|
||||||
final remainingRadicals = queryResult
|
final remainingRadicals = queryResult
|
||||||
.map((row) => row['radical'] as String)
|
.map((row) => row['radical'] as String)
|
||||||
// TODO: maybe we should do some runtime checking here, just to throw a sensible error
|
|
||||||
// if something goes horribly wrong?
|
|
||||||
.map((r) => radicalsByFormalVariant[r]!)
|
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
return remainingRadicals;
|
return remainingRadicals;
|
||||||
@@ -42,7 +38,7 @@ Future<List<RadkfileRadical>> searchRemainingRadicalsWithDbConnection(
|
|||||||
/// Returns a list of kanji that contain all of the input radicals.
|
/// Returns a list of kanji that contain all of the input radicals.
|
||||||
Future<List<String>> searchKanjiByRadicalsWithDbConnection(
|
Future<List<String>> searchKanjiByRadicalsWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
List<RadkfileRadical> radicals,
|
List<String> radicals,
|
||||||
) async {
|
) async {
|
||||||
final distinctRadicals = radicals.toSet();
|
final distinctRadicals = radicals.toSet();
|
||||||
|
|
||||||
@@ -54,7 +50,7 @@ Future<List<String>> searchKanjiByRadicalsWithDbConnection(
|
|||||||
GROUP BY "kanji"
|
GROUP BY "kanji"
|
||||||
HAVING COUNT(DISTINCT "radical") = ?
|
HAVING COUNT(DISTINCT "radical") = ?
|
||||||
''',
|
''',
|
||||||
[...distinctRadicals.map((r) => r.formalVariant), distinctRadicals.length],
|
[...distinctRadicals, distinctRadicals.length],
|
||||||
);
|
);
|
||||||
|
|
||||||
final kanji = queryResult.map((row) => row['kanji'] as String).toList();
|
final kanji = queryResult.map((row) => row['kanji'] as String).toList();
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
import 'package:jadb/table_names/jmdict.dart';
|
|
||||||
import 'package:jadb/table_names/kanjidic.dart';
|
|
||||||
import 'package:jadb/table_names/radkfile.dart';
|
|
||||||
import 'package:jadb/table_names/tanos_jlpt.dart';
|
|
||||||
import 'package:sqflite_common/sqlite_api.dart';
|
|
||||||
|
|
||||||
class DatasourceVersions {
|
|
||||||
final String jmdictVersion;
|
|
||||||
final DateTime jmdictDate;
|
|
||||||
final String jmdictHash;
|
|
||||||
|
|
||||||
final String kanjidic2Version;
|
|
||||||
final DateTime kanjidic2Date;
|
|
||||||
final String kanjidic2Hash;
|
|
||||||
|
|
||||||
final String radkfileVersion;
|
|
||||||
final DateTime radkfileDate;
|
|
||||||
final String radkfileHash;
|
|
||||||
|
|
||||||
final String tanosJlptVersion;
|
|
||||||
final DateTime tanosJlptDate;
|
|
||||||
final String tanosJlptHash;
|
|
||||||
|
|
||||||
const DatasourceVersions({
|
|
||||||
required this.jmdictVersion,
|
|
||||||
required this.jmdictDate,
|
|
||||||
required this.jmdictHash,
|
|
||||||
required this.kanjidic2Version,
|
|
||||||
required this.kanjidic2Date,
|
|
||||||
required this.kanjidic2Hash,
|
|
||||||
required this.radkfileVersion,
|
|
||||||
required this.radkfileDate,
|
|
||||||
required this.radkfileHash,
|
|
||||||
required this.tanosJlptVersion,
|
|
||||||
required this.tanosJlptDate,
|
|
||||||
required this.tanosJlptHash,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTime _parseDateTime(String dateString) {
|
|
||||||
try {
|
|
||||||
return DateTime.parse(dateString);
|
|
||||||
} catch (e) {
|
|
||||||
if (RegExp(r'^\d{4}-\d{2}$').hasMatch(dateString)) {
|
|
||||||
return DateTime.parse('$dateString-01');
|
|
||||||
} else if (RegExp(r'^\d{4}$').hasMatch(dateString)) {
|
|
||||||
return DateTime.parse('$dateString-01-01');
|
|
||||||
} else {
|
|
||||||
throw FormatException('Invalid date format: $dateString');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<DatasourceVersions> getDatasourceVersions(
|
|
||||||
final DatabaseExecutor connection,
|
|
||||||
) async {
|
|
||||||
final jmdictVersion = await connection
|
|
||||||
.query(JMdictTableNames.version)
|
|
||||||
.then((rows) => rows.first);
|
|
||||||
final kanjidic2Version = await connection
|
|
||||||
.query(KANJIDICTableNames.version)
|
|
||||||
.then((rows) => rows.first);
|
|
||||||
final radkfileVersion = await connection
|
|
||||||
.query(RADKFILETableNames.version)
|
|
||||||
.then((rows) => rows.first);
|
|
||||||
final tanosJlptVersion = await connection
|
|
||||||
.query(TanosJLPTTableNames.version)
|
|
||||||
.then((rows) => rows.first);
|
|
||||||
|
|
||||||
return DatasourceVersions(
|
|
||||||
jmdictVersion: jmdictVersion['version'] as String,
|
|
||||||
jmdictDate: _parseDateTime(jmdictVersion['date'].toString()),
|
|
||||||
jmdictHash: jmdictVersion['hash'] as String,
|
|
||||||
kanjidic2Version: kanjidic2Version['version'] as String,
|
|
||||||
kanjidic2Date: _parseDateTime(kanjidic2Version['date'].toString()),
|
|
||||||
kanjidic2Hash: kanjidic2Version['hash'] as String,
|
|
||||||
radkfileVersion: radkfileVersion['version'] as String,
|
|
||||||
radkfileDate: _parseDateTime(radkfileVersion['date'].toString()),
|
|
||||||
radkfileHash: radkfileVersion['hash'] as String,
|
|
||||||
tanosJlptVersion: tanosJlptVersion['version'] as String,
|
|
||||||
tanosJlptDate: _parseDateTime(tanosJlptVersion['date'].toString()),
|
|
||||||
tanosJlptHash: tanosJlptVersion['hash'] as String,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -46,7 +46,6 @@ String _filterFTSSensitiveCharacters(String word) {
|
|||||||
int? pageSize,
|
int? pageSize,
|
||||||
int? offset,
|
int? offset,
|
||||||
bool countOnly = false,
|
bool countOnly = false,
|
||||||
bool fuzzyKana = true,
|
|
||||||
}) {
|
}) {
|
||||||
assert(
|
assert(
|
||||||
tableName == JMdictTableNames.kanjiElement ||
|
tableName == JMdictTableNames.kanjiElement ||
|
||||||
@@ -68,27 +67,25 @@ String _filterFTSSensitiveCharacters(String word) {
|
|||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
"$tableName"."entryId",
|
"$tableName"."entryId",
|
||||||
100
|
100
|
||||||
+ (("$tableName"."reading" = ?1) * 100000)
|
+ (("${tableName}FTS"."reading" = ?) * 10000)
|
||||||
${fuzzyKana ? '+ (("${tableName}FTS"."reading" = normalize_jp(?1)) * 10000)' : ''}
|
|
||||||
+ (("$tableName"."reading" LIKE ?1 || '%') * 20)
|
|
||||||
+ (("$tableName"."orderNum" = 0) * 20)
|
+ (("$tableName"."orderNum" = 0) * 20)
|
||||||
+ COALESCE("JMdict_EntryScore"."score", 0)
|
+ COALESCE("JMdict_EntryScore"."score", 0)
|
||||||
AS "score"
|
AS "score"
|
||||||
${fuzzyKana ? 'FROM "${tableName}FTS" JOIN "$tableName" USING ("elementId")' : 'FROM "$tableName"'}
|
FROM "${tableName}FTS"
|
||||||
|
JOIN "$tableName" USING ("elementId")
|
||||||
LEFT JOIN "JMdict_EntryScore" USING ("elementId")
|
LEFT JOIN "JMdict_EntryScore" USING ("elementId")
|
||||||
${fuzzyKana ? 'WHERE "${tableName}FTS"."reading" MATCH normalize_jp(?1) || \'*\'' : 'WHERE "$tableName"."reading" LIKE \'?1\' || \'%\''}
|
WHERE "${tableName}FTS"."reading" MATCH ? || '*'
|
||||||
),
|
),
|
||||||
non_fts_results AS (
|
non_fts_results AS (
|
||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
"$tableName"."entryId",
|
"$tableName"."entryId",
|
||||||
50
|
50
|
||||||
+ (("$tableName"."reading" LIKE '%' || ?1 || '%') * 20)
|
|
||||||
+ (("$tableName"."orderNum" = 0) * 20)
|
+ (("$tableName"."orderNum" = 0) * 20)
|
||||||
+ COALESCE("JMdict_EntryScore"."score", 0)
|
+ COALESCE("JMdict_EntryScore"."score", 0)
|
||||||
AS "score"
|
AS "score"
|
||||||
FROM "$tableName"
|
FROM "$tableName"
|
||||||
LEFT JOIN "JMdict_EntryScore" USING ("elementId")
|
LEFT JOIN "JMdict_EntryScore" USING ("elementId")
|
||||||
WHERE "$tableName"."reading" LIKE '%' || ${fuzzyKana ? 'normalize_jp(?1)' : '?1'} || '%'
|
WHERE "reading" LIKE '%' || ? || '%'
|
||||||
AND "$tableName"."entryId" NOT IN (SELECT "entryId" FROM "fts_results")
|
AND "$tableName"."entryId" NOT IN (SELECT "entryId" FROM "fts_results")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -104,21 +101,25 @@ String _filterFTSSensitiveCharacters(String word) {
|
|||||||
${offset != null ? 'OFFSET ?' : ''}
|
${offset != null ? 'OFFSET ?' : ''}
|
||||||
'''
|
'''
|
||||||
.trim(),
|
.trim(),
|
||||||
[_filterFTSSensitiveCharacters(word), ?pageSize, ?offset],
|
[
|
||||||
|
_filterFTSSensitiveCharacters(word),
|
||||||
|
_filterFTSSensitiveCharacters(word),
|
||||||
|
_filterFTSSensitiveCharacters(word),
|
||||||
|
?pageSize,
|
||||||
|
?offset,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ScoredEntryId>> _queryKanji(
|
Future<List<ScoredEntryId>> _queryKanji(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
String word,
|
String word,
|
||||||
bool fuzzyKana,
|
|
||||||
int? pageSize,
|
int? pageSize,
|
||||||
int? offset,
|
int? offset,
|
||||||
) {
|
) {
|
||||||
final (query, args) = _kanjiReadingTemplate(
|
final (query, args) = _kanjiReadingTemplate(
|
||||||
JMdictTableNames.kanjiElement,
|
JMdictTableNames.kanjiElement,
|
||||||
word,
|
word,
|
||||||
fuzzyKana: fuzzyKana,
|
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
);
|
);
|
||||||
@@ -134,15 +135,10 @@ Future<List<ScoredEntryId>> _queryKanji(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> _queryKanjiCount(
|
Future<int> _queryKanjiCount(DatabaseExecutor connection, String word) {
|
||||||
DatabaseExecutor connection,
|
|
||||||
String word,
|
|
||||||
bool fuzzyKana,
|
|
||||||
) {
|
|
||||||
final (query, args) = _kanjiReadingTemplate(
|
final (query, args) = _kanjiReadingTemplate(
|
||||||
JMdictTableNames.kanjiElement,
|
JMdictTableNames.kanjiElement,
|
||||||
word,
|
word,
|
||||||
fuzzyKana: fuzzyKana,
|
|
||||||
countOnly: true,
|
countOnly: true,
|
||||||
);
|
);
|
||||||
return connection
|
return connection
|
||||||
@@ -153,14 +149,12 @@ Future<int> _queryKanjiCount(
|
|||||||
Future<List<ScoredEntryId>> _queryKana(
|
Future<List<ScoredEntryId>> _queryKana(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
String word,
|
String word,
|
||||||
bool fuzzyKana,
|
|
||||||
int? pageSize,
|
int? pageSize,
|
||||||
int? offset,
|
int? offset,
|
||||||
) {
|
) {
|
||||||
final (query, args) = _kanjiReadingTemplate(
|
final (query, args) = _kanjiReadingTemplate(
|
||||||
JMdictTableNames.readingElement,
|
JMdictTableNames.readingElement,
|
||||||
word,
|
word,
|
||||||
fuzzyKana: fuzzyKana,
|
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
);
|
);
|
||||||
@@ -176,15 +170,10 @@ Future<List<ScoredEntryId>> _queryKana(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> _queryKanaCount(
|
Future<int> _queryKanaCount(DatabaseExecutor connection, String word) {
|
||||||
DatabaseExecutor connection,
|
|
||||||
String word,
|
|
||||||
bool fuzzyKana,
|
|
||||||
) {
|
|
||||||
final (query, args) = _kanjiReadingTemplate(
|
final (query, args) = _kanjiReadingTemplate(
|
||||||
JMdictTableNames.readingElement,
|
JMdictTableNames.readingElement,
|
||||||
word,
|
word,
|
||||||
fuzzyKana: fuzzyKana,
|
|
||||||
countOnly: true,
|
countOnly: true,
|
||||||
);
|
);
|
||||||
return connection
|
return connection
|
||||||
@@ -254,39 +243,23 @@ Future<List<ScoredEntryId>> fetchEntryIds(
|
|||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
String word,
|
String word,
|
||||||
SearchMode searchMode,
|
SearchMode searchMode,
|
||||||
bool fuzzyKana,
|
|
||||||
int? pageSize,
|
int? pageSize,
|
||||||
int? offset,
|
int? offset,
|
||||||
) async {
|
) async {
|
||||||
assert(
|
|
||||||
word.trim().isNotEmpty,
|
|
||||||
'Word should not be empty when fetching entry IDs',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (searchMode == SearchMode.auto) {
|
if (searchMode == SearchMode.auto) {
|
||||||
searchMode = _determineSearchMode(word);
|
searchMode = _determineSearchMode(word);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(word.isNotEmpty, 'Word should not be empty when fetching entry IDs');
|
||||||
|
|
||||||
late final List<ScoredEntryId> entryIds;
|
late final List<ScoredEntryId> entryIds;
|
||||||
switch (searchMode) {
|
switch (searchMode) {
|
||||||
case SearchMode.kanji:
|
case SearchMode.kanji:
|
||||||
entryIds = await _queryKanji(
|
entryIds = await _queryKanji(connection, word, pageSize, offset);
|
||||||
connection,
|
|
||||||
word,
|
|
||||||
fuzzyKana,
|
|
||||||
pageSize,
|
|
||||||
offset,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SearchMode.kana:
|
case SearchMode.kana:
|
||||||
entryIds = await _queryKana(
|
entryIds = await _queryKana(connection, word, pageSize, offset);
|
||||||
connection,
|
|
||||||
word,
|
|
||||||
fuzzyKana,
|
|
||||||
pageSize,
|
|
||||||
offset,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SearchMode.english:
|
case SearchMode.english:
|
||||||
@@ -306,7 +279,6 @@ Future<int?> fetchEntryIdCount(
|
|||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
String word,
|
String word,
|
||||||
SearchMode searchMode,
|
SearchMode searchMode,
|
||||||
bool fuzzyKana,
|
|
||||||
) async {
|
) async {
|
||||||
if (searchMode == SearchMode.auto) {
|
if (searchMode == SearchMode.auto) {
|
||||||
searchMode = _determineSearchMode(word);
|
searchMode = _determineSearchMode(word);
|
||||||
@@ -318,11 +290,11 @@ Future<int?> fetchEntryIdCount(
|
|||||||
|
|
||||||
switch (searchMode) {
|
switch (searchMode) {
|
||||||
case SearchMode.kanji:
|
case SearchMode.kanji:
|
||||||
entryIdCount = await _queryKanjiCount(connection, word, fuzzyKana);
|
entryIdCount = await _queryKanjiCount(connection, word);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SearchMode.kana:
|
case SearchMode.kana:
|
||||||
entryIdCount = await _queryKanaCount(connection, word, fuzzyKana);
|
entryIdCount = await _queryKanaCount(connection, word);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SearchMode.english:
|
case SearchMode.english:
|
||||||
|
|||||||
@@ -38,11 +38,10 @@ Future<List<WordSearchResult>?> searchWordWithDbConnection(
|
|||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
String word, {
|
String word, {
|
||||||
SearchMode searchMode = SearchMode.auto,
|
SearchMode searchMode = SearchMode.auto,
|
||||||
bool fuzzyKana = true,
|
|
||||||
int page = 0,
|
int page = 0,
|
||||||
int? pageSize,
|
int? pageSize,
|
||||||
}) async {
|
}) async {
|
||||||
if (word.trim().isEmpty) {
|
if (word.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +50,6 @@ Future<List<WordSearchResult>?> searchWordWithDbConnection(
|
|||||||
connection,
|
connection,
|
||||||
word,
|
word,
|
||||||
searchMode,
|
searchMode,
|
||||||
fuzzyKana,
|
|
||||||
pageSize,
|
pageSize,
|
||||||
offset,
|
offset,
|
||||||
);
|
);
|
||||||
@@ -84,7 +82,6 @@ Future<int?> searchWordCountWithDbConnection(
|
|||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
String word, {
|
String word, {
|
||||||
SearchMode searchMode = SearchMode.auto,
|
SearchMode searchMode = SearchMode.auto,
|
||||||
bool fuzzyKana = true,
|
|
||||||
}) async {
|
}) async {
|
||||||
if (word.isEmpty) {
|
if (word.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
@@ -94,30 +91,11 @@ Future<int?> searchWordCountWithDbConnection(
|
|||||||
connection,
|
connection,
|
||||||
word,
|
word,
|
||||||
searchMode,
|
searchMode,
|
||||||
fuzzyKana,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return entryIdCount;
|
return entryIdCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Set<int>> filterWordIdsWithDbConnection(
|
|
||||||
DatabaseExecutor connection,
|
|
||||||
Iterable<int> ids,
|
|
||||||
) async {
|
|
||||||
if (ids.isEmpty) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
final Set<int> filteredIds = await connection
|
|
||||||
.rawQuery(
|
|
||||||
'SELECT "entryId" FROM "${JMdictTableNames.entry}" WHERE "entryId" IN (${ids.map((_) => '?').join(',')})',
|
|
||||||
ids.toList(),
|
|
||||||
)
|
|
||||||
.then((rows) => rows.map((row) => row['entryId'] as int).toSet());
|
|
||||||
|
|
||||||
return filteredIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetches a single word by its entry ID, returning null if not found.
|
/// Fetches a single word by its entry ID, returning null if not found.
|
||||||
Future<WordSearchResult?> getWordByIdWithDbConnection(
|
Future<WordSearchResult?> getWordByIdWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
abstract class KanjiVGTableNames {
|
|
||||||
static const String version = 'KanjiVG_Version';
|
|
||||||
static const String entry = 'KanjiVG_Entry';
|
|
||||||
static const String path = 'KanjiVG_Path';
|
|
||||||
static const String strokeNumber = 'KanjiVG_StrokeNumber';
|
|
||||||
static const String pathGroup = 'KanjiVG_PathGroup';
|
|
||||||
|
|
||||||
static Set<String> get allTables => {
|
|
||||||
version,
|
|
||||||
entry,
|
|
||||||
path,
|
|
||||||
strokeNumber,
|
|
||||||
pathGroup,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
const int jadbSchemaVersion = 3;
|
|
||||||
@@ -66,7 +66,6 @@ CREATE TABLE "JMdict_KanjiElement" (
|
|||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE INDEX "JMdict_KanjiElement_byReading" ON "JMdict_KanjiElement"("reading");
|
CREATE INDEX "JMdict_KanjiElement_byReading" ON "JMdict_KanjiElement"("reading");
|
||||||
CREATE INDEX "JMdict_KanjiElement_byNormalizedReading" ON "JMdict_KanjiElement"(normalize_jp("reading"));
|
|
||||||
|
|
||||||
CREATE TABLE "JMdict_KanjiElementInfo" (
|
CREATE TABLE "JMdict_KanjiElementInfo" (
|
||||||
"elementId" INTEGER NOT NULL REFERENCES "JMdict_KanjiElement"("elementId"),
|
"elementId" INTEGER NOT NULL REFERENCES "JMdict_KanjiElement"("elementId"),
|
||||||
@@ -92,7 +91,6 @@ CREATE TABLE "JMdict_ReadingElement" (
|
|||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE INDEX "JMdict_ReadingElement_byReading" ON "JMdict_ReadingElement"("reading");
|
CREATE INDEX "JMdict_ReadingElement_byReading" ON "JMdict_ReadingElement"("reading");
|
||||||
CREATE INDEX "JMdict_ReadingElement_byNormalizedReading" ON "JMdict_ReadingElement"(normalize_jp("reading"));
|
|
||||||
|
|
||||||
CREATE TABLE "JMdict_ReadingElementRestriction" (
|
CREATE TABLE "JMdict_ReadingElementRestriction" (
|
||||||
"elementId" INTEGER NOT NULL REFERENCES "JMdict_ReadingElement"("elementId"),
|
"elementId" INTEGER NOT NULL REFERENCES "JMdict_ReadingElement"("elementId"),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ CREATE TRIGGER "JMdict_KanjiElement_InsertFTS"
|
|||||||
AFTER INSERT ON "JMdict_KanjiElement"
|
AFTER INSERT ON "JMdict_KanjiElement"
|
||||||
BEGIN
|
BEGIN
|
||||||
INSERT INTO "JMdict_KanjiElementFTS"("elementId", "reading")
|
INSERT INTO "JMdict_KanjiElementFTS"("elementId", "reading")
|
||||||
VALUES (NEW."elementId", normalize_jp(NEW."reading"));
|
VALUES (NEW."elementId", NEW."reading");
|
||||||
END;
|
END;
|
||||||
|
|
||||||
CREATE TRIGGER "JMdict_KanjiElement_UpdateFTS"
|
CREATE TRIGGER "JMdict_KanjiElement_UpdateFTS"
|
||||||
@@ -14,7 +14,7 @@ BEGIN
|
|||||||
UPDATE "JMdict_KanjiElementFTS"
|
UPDATE "JMdict_KanjiElementFTS"
|
||||||
SET
|
SET
|
||||||
"elementId" = NEW."elementId",
|
"elementId" = NEW."elementId",
|
||||||
"reading" = normalize_jp(NEW."reading")
|
"reading" = NEW."reading"
|
||||||
WHERE "elementId" = OLD."elementId";
|
WHERE "elementId" = OLD."elementId";
|
||||||
END;
|
END;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ CREATE TRIGGER "JMdict_ReadingElement_InsertFTS"
|
|||||||
AFTER INSERT ON "JMdict_ReadingElement"
|
AFTER INSERT ON "JMdict_ReadingElement"
|
||||||
BEGIN
|
BEGIN
|
||||||
INSERT INTO "JMdict_ReadingElementFTS"("elementId", "reading")
|
INSERT INTO "JMdict_ReadingElementFTS"("elementId", "reading")
|
||||||
VALUES (NEW."elementId", normalize_jp(NEW."reading"));
|
VALUES (NEW."elementId", NEW."reading");
|
||||||
END;
|
END;
|
||||||
|
|
||||||
CREATE TRIGGER "JMdict_ReadingElement_UpdateFTS"
|
CREATE TRIGGER "JMdict_ReadingElement_UpdateFTS"
|
||||||
@@ -43,7 +43,7 @@ BEGIN
|
|||||||
UPDATE "JMdict_ReadingElementFTS"
|
UPDATE "JMdict_ReadingElementFTS"
|
||||||
SET
|
SET
|
||||||
"elementId" = NEW."elementId",
|
"elementId" = NEW."elementId",
|
||||||
"reading" = normalize_jp(NEW."reading")
|
"reading" = NEW."reading"
|
||||||
WHERE "elementId" = OLD."elementId";
|
WHERE "elementId" = OLD."elementId";
|
||||||
END;
|
END;
|
||||||
|
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
CREATE TABLE "KanjiVG_Version" (
|
|
||||||
"version" VARCHAR(10) PRIMARY KEY NOT NULL,
|
|
||||||
"date" DATE NOT NULL,
|
|
||||||
"hash" VARCHAR(64) NOT NULL
|
|
||||||
) WITHOUT ROWID;
|
|
||||||
|
|
||||||
CREATE TRIGGER "KanjiVG_Version_SingleRow"
|
|
||||||
BEFORE INSERT ON "KanjiVG_Version"
|
|
||||||
WHEN (SELECT COUNT(*) FROM "KanjiVG_Version") >= 1
|
|
||||||
BEGIN
|
|
||||||
SELECT RAISE(FAIL, 'Only one row allowed in KanjiVG_Version');
|
|
||||||
END;
|
|
||||||
|
|
||||||
CREATE TABLE "KanjiVG_Entry" (
|
|
||||||
"character" CHAR(1) PRIMARY KEY NOT NULL
|
|
||||||
) WITHOUT ROWID;
|
|
||||||
|
|
||||||
CREATE TABLE "KanjiVG_StrokeNumber" (
|
|
||||||
"character" CHAR(1) NOT NULL REFERENCES "KanjiVG_Entry"("character"),
|
|
||||||
"strokeNum" INTEGER NOT NULL,
|
|
||||||
"x" REAL NOT NULL,
|
|
||||||
"y" REAL NOT NULL,
|
|
||||||
PRIMARY KEY ("character", "strokeNum"),
|
|
||||||
FOREIGN KEY ("character", "strokeNum") REFERENCES "KanjiVG_Path"("character", "pathId")
|
|
||||||
) WITHOUT ROWID;
|
|
||||||
|
|
||||||
CREATE TABLE "KanjiVG_Path" (
|
|
||||||
"character" CHAR(1) NOT NULL REFERENCES "KanjiVG_Entry"("character"),
|
|
||||||
"pathId" INTEGER NOT NULL,
|
|
||||||
"groupId" INTEGER NOT NULL,
|
|
||||||
"type" VARCHAR(10),
|
|
||||||
"svgPath" TEXT NOT NULL,
|
|
||||||
PRIMARY KEY ("character", "pathId"),
|
|
||||||
FOREIGN KEY ("character", "groupId") REFERENCES "KanjiVG_PathGroup"("character", "groupId")
|
|
||||||
) WITHOUT ROWID;
|
|
||||||
|
|
||||||
CREATE TABLE "KanjiVG_PathGroup" (
|
|
||||||
"character" CHAR(1) NOT NULL REFERENCES "KanjiVG_Entry"("character"),
|
|
||||||
"groupId" INTEGER NOT NULL,
|
|
||||||
"parentGroupId" INTEGER,
|
|
||||||
"element" TEXT,
|
|
||||||
"original" TEXT,
|
|
||||||
"position" VARCHAR(6),
|
|
||||||
"radical" TEXT,
|
|
||||||
"part" INTEGER,
|
|
||||||
PRIMARY KEY ("character", "groupId"),
|
|
||||||
CHECK (
|
|
||||||
"position" IN (
|
|
||||||
'upperA',
|
|
||||||
'upperB',
|
|
||||||
'lower1',
|
|
||||||
'lower2',
|
|
||||||
'bottom',
|
|
||||||
'kamae',
|
|
||||||
'kamaec',
|
|
||||||
'left',
|
|
||||||
'middle',
|
|
||||||
'nyo',
|
|
||||||
'nyoc',
|
|
||||||
'right',
|
|
||||||
'tare',
|
|
||||||
'tarec',
|
|
||||||
'top'
|
|
||||||
)
|
|
||||||
OR
|
|
||||||
"position" IS NULL
|
|
||||||
),
|
|
||||||
FOREIGN KEY ("character", "parentGroupId") REFERENCES "KanjiVG_PathGroup"("character", "groupId")
|
|
||||||
) WITHOUT ROWID;
|
|
||||||
+6
-7
@@ -7,7 +7,6 @@
|
|||||||
radkfile,
|
radkfile,
|
||||||
kanjidic2,
|
kanjidic2,
|
||||||
tanos-jlpt,
|
tanos-jlpt,
|
||||||
kanjivg,
|
|
||||||
sqlite,
|
sqlite,
|
||||||
wal ? false,
|
wal ? false,
|
||||||
}:
|
}:
|
||||||
@@ -21,32 +20,32 @@ stdenvNoCC.mkDerivation {
|
|||||||
];
|
];
|
||||||
|
|
||||||
env = {
|
env = {
|
||||||
JMDICT_PATH = "${jmdict}/JMdict.xml";
|
|
||||||
JMDICT_VERSION = jmdict.version;
|
JMDICT_VERSION = jmdict.version;
|
||||||
JMDICT_DATE = jmdict.date;
|
JMDICT_DATE = jmdict.date;
|
||||||
JMDICT_HASH = jmdict.hash;
|
JMDICT_HASH = jmdict.hash;
|
||||||
|
|
||||||
KANJIDIC_PATH = "${kanjidic2}/kanjidic2.xml";
|
|
||||||
KANJIDIC_VERSION = kanjidic2.version;
|
KANJIDIC_VERSION = kanjidic2.version;
|
||||||
KANJIDIC_DATE = kanjidic2.date;
|
KANJIDIC_DATE = kanjidic2.date;
|
||||||
KANJIDIC_HASH = kanjidic2.hash;
|
KANJIDIC_HASH = kanjidic2.hash;
|
||||||
|
|
||||||
RADKFILE_PATH = "${radkfile}/RADKFILE";
|
|
||||||
RADKFILE_VERSION = radkfile.version;
|
RADKFILE_VERSION = radkfile.version;
|
||||||
RADKFILE_DATE = radkfile.date;
|
RADKFILE_DATE = radkfile.date;
|
||||||
RADKFILE_HASH = radkfile.hash;
|
RADKFILE_HASH = radkfile.hash;
|
||||||
|
|
||||||
TANOS_JLPT_PATH = toString tanos-jlpt;
|
|
||||||
TANOS_JLPT_VERSION = tanos-jlpt.version;
|
TANOS_JLPT_VERSION = tanos-jlpt.version;
|
||||||
TANOS_JLPT_DATE = tanos-jlpt.date;
|
TANOS_JLPT_DATE = tanos-jlpt.date;
|
||||||
TANOS_JLPT_HASH = tanos-jlpt.hash;
|
TANOS_JLPT_HASH = tanos-jlpt.hash;
|
||||||
|
|
||||||
KANJIVG_PATH = "${kanjivg}/kanji";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
runHook preBuild
|
runHook preBuild
|
||||||
|
|
||||||
|
mkdir -p data
|
||||||
|
ln -s '${jmdict}'/* data/
|
||||||
|
ln -s '${kanjidic2}'/* data/
|
||||||
|
ln -s '${radkfile}'/* data/
|
||||||
|
ln -s '${tanos-jlpt}' data/tanos-jlpt
|
||||||
|
|
||||||
for migration in migrations/*.sql; do
|
for migration in migrations/*.sql; do
|
||||||
sqlite3 jadb.sqlite < "$migration"
|
sqlite3 jadb.sqlite < "$migration"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
{
|
{
|
||||||
src,
|
src,
|
||||||
buildDartApplication,
|
buildDartApplication,
|
||||||
sqlite,
|
|
||||||
|
|
||||||
callPackage,
|
|
||||||
path,
|
|
||||||
}:
|
}:
|
||||||
buildDartApplication {
|
buildDartApplication {
|
||||||
pname = "jadb-database-tool";
|
pname = "jadb-database-tool";
|
||||||
@@ -36,9 +32,5 @@ buildDartApplication {
|
|||||||
|
|
||||||
autoPubspecLock = ../pubspec.lock;
|
autoPubspecLock = ../pubspec.lock;
|
||||||
|
|
||||||
customSourceBuilders.sqlite3 = callPackage "${path}/pkgs/development/compilers/dart/package-source-builders/sqlite3/default.nix" {
|
|
||||||
inherit sqlite;
|
|
||||||
};
|
|
||||||
|
|
||||||
meta.mainProgram = "jadb";
|
meta.mainProgram = "jadb";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
diff --git i/CMakeLists.txt w/CMakeLists.txt
|
||||||
|
index 9d99543..11ce4a4 100644
|
||||||
|
--- i/CMakeLists.txt
|
||||||
|
+++ w/CMakeLists.txt
|
||||||
|
@@ -131,6 +131,15 @@ if(NOT SQLite3_FOUND)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
+if(SQLite3_FOUND AND NOT TARGET SQLite3::SQLite3)
|
||||||
|
+ add_library(SQLite3::SQLite3 UNKNOWN IMPORTED)
|
||||||
|
+
|
||||||
|
+ set_target_properties(SQLite3::SQLite3 PROPERTIES
|
||||||
|
+ IMPORTED_LOCATION "${SQLite3_LIBRARIES}"
|
||||||
|
+ INTERFACE_INCLUDE_DIRECTORIES "${SQLite3_INCLUDE_DIRS}"
|
||||||
|
+ )
|
||||||
|
+endif()
|
||||||
|
+
|
||||||
|
# --- Configure the Library ---
|
||||||
|
|
||||||
|
# Select source files based on API version
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
fetchFromSourcehut,
|
||||||
|
cmake,
|
||||||
|
pkg-config,
|
||||||
|
icu,
|
||||||
|
sqlite,
|
||||||
|
}:
|
||||||
|
|
||||||
|
stdenv.mkDerivation (finalAttrs: {
|
||||||
|
pname = "fts5-icu-tokenizer";
|
||||||
|
version = "5.5";
|
||||||
|
src = fetchFromSourcehut {
|
||||||
|
vc = "hg";
|
||||||
|
owner = "~cwt";
|
||||||
|
repo = "fts5-icu-tokenizer";
|
||||||
|
rev = "v${finalAttrs.version}";
|
||||||
|
hash = "sha256-7Klsu9d1sY+W0buo6kwYdCyDA/u2dBTgu6WuttomTBo=";
|
||||||
|
};
|
||||||
|
|
||||||
|
patches = [
|
||||||
|
./0001-provide-sqlite-externally.patch
|
||||||
|
];
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
cmake
|
||||||
|
pkg-config
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
icu
|
||||||
|
sqlite
|
||||||
|
];
|
||||||
|
|
||||||
|
cmakeFlags = [
|
||||||
|
(lib.cmakeFeature "LOCALE" "ja")
|
||||||
|
(lib.cmakeFeature "API_VERSION" "v2")
|
||||||
|
];
|
||||||
|
})
|
||||||
+35
-43
@@ -5,18 +5,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "563c6992eaeda8625f45b87f6a6a0c547df16565d1c93d8271c7c11057710ca7"
|
sha256: "8d718c5c58904f9937290fd5dbf2d6a0e02456867706bfb6cd7b81d394e738d5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "101.0.0"
|
version: "98.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: aa6a9365901532864cae51208f2a6bb18dd01972ebead19c431efc848f60080b
|
sha256: "6141ad5d092d1e1d13929c0504658bbeccc1703505830d7c26e859908f5efc88"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "13.1.0"
|
version: "12.0.0"
|
||||||
args:
|
args:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -61,10 +61,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: code_assets
|
name: code_assets
|
||||||
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
|
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.0.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -85,10 +85,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: coverage
|
name: coverage
|
||||||
sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d"
|
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.1"
|
version: "1.15.0"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -149,10 +149,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: hooks
|
name: hooks
|
||||||
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
|
sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "1.0.2"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -197,18 +197,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
|
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.20"
|
version: "0.12.19"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d
|
sha256: df0c643f44ad098eb37988027a8e2b2b5a031fd3977f06bbfd3a76637e8df739
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.3"
|
version: "1.18.2"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -221,10 +221,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: native_toolchain_c
|
name: native_toolchain_c
|
||||||
sha256: f59351d28f49520cd3a74eb1f41c5f19ae15e53c65a3231d14af672e46510a96
|
sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.19.1"
|
version: "0.17.6"
|
||||||
node_preamble:
|
node_preamble:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -273,14 +273,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
record_use:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: record_use
|
|
||||||
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.6.0"
|
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -341,26 +333,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465"
|
sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.8"
|
version: "2.5.6"
|
||||||
sqflite_common_ffi:
|
sqflite_common_ffi:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqflite_common_ffi
|
name: sqflite_common_ffi
|
||||||
sha256: cd0c7f7de39a08f2d54ef144d9058c46eca8461879aaa648025643455c1e5a20
|
sha256: c59fcdc143839a77581f7a7c4de018e53682408903a0a0800b95ef2dc4033eff
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0+3"
|
version: "2.4.0+2"
|
||||||
sqlite3:
|
sqlite3:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqlite3
|
name: sqlite3
|
||||||
sha256: "9488c7d2cdb1091c91cacf7e207cff81b28bff8e366f042bad3afe7d34afe189"
|
sha256: caa693ad15a587a2b4fde093b728131a1827903872171089dedb16f7665d3a91
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.2"
|
version: "3.2.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -389,10 +381,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: synchronized
|
name: synchronized
|
||||||
sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5"
|
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.0+1"
|
version: "3.4.0"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -405,26 +397,26 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
sha256: ca578dc12bb8b2f40b67b7d3bd2fac4f31c01a6ff7130a14e2597b919934507f
|
sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.31.1"
|
version: "1.31.0"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
|
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.12"
|
version: "0.7.11"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
sha256: d2e98ec12998368dc59ddd47ab709f2cd55acd6b66dc7db764455a44082f4bc5
|
sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.18"
|
version: "0.6.17"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -437,10 +429,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.2.0"
|
version: "15.0.2"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -485,10 +477,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: xml
|
name: xml
|
||||||
sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
|
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "6.6.1"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -498,4 +490,4 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.11.0 <4.0.0"
|
dart: ">=3.10.1 <4.0.0"
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ dependencies:
|
|||||||
sqflite_common: ^2.5.0
|
sqflite_common: ^2.5.0
|
||||||
sqflite_common_ffi: ^2.3.0
|
sqflite_common_ffi: ^2.3.0
|
||||||
sqlite3: ^3.1.6
|
sqlite3: ^3.1.6
|
||||||
xml: '>=6.0.0 < 8.0.0'
|
xml: ^6.5.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
benchmark_harness: ^2.4.0
|
benchmark_harness: ^2.4.0
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:jadb/models/kanji_search/kanji_search_radical.dart';
|
|
||||||
import 'package:jadb/models/kanji_search/kanji_search_result.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
Object? _roundTripJson(Object? value) => jsonDecode(jsonEncode(value));
|
|
||||||
|
|
||||||
Map<String, dynamic> _roundTripMap(Object? json) =>
|
|
||||||
Map<String, dynamic>.from(_roundTripJson(json) as Map);
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('KanjiSearchRadical JSON serialization roundtrip', () {
|
|
||||||
const radical = KanjiSearchRadical(
|
|
||||||
symbol: '人',
|
|
||||||
names: ['ひと', 'にんべん'],
|
|
||||||
forms: ['亻'],
|
|
||||||
meanings: ['person', 'human'],
|
|
||||||
);
|
|
||||||
|
|
||||||
final restored = KanjiSearchRadical.fromJson(
|
|
||||||
_roundTripMap(radical.toJson()),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(restored, equals(radical));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('KanjiSearchResult JSON serialization roundtrip', () {
|
|
||||||
const result = KanjiSearchResult(
|
|
||||||
kanji: '休',
|
|
||||||
taughtIn: 1,
|
|
||||||
jlptLevel: 'N5',
|
|
||||||
newspaperFrequencyRank: 1234,
|
|
||||||
strokeCount: 6,
|
|
||||||
meanings: ['rest', 'day off'],
|
|
||||||
kunyomi: ['やす.む', 'やす.まる'],
|
|
||||||
onyomi: ['キュウ'],
|
|
||||||
radical: KanjiSearchRadical(
|
|
||||||
symbol: '人',
|
|
||||||
names: ['ひと', 'にんべん'],
|
|
||||||
forms: ['亻'],
|
|
||||||
meanings: ['person', 'human'],
|
|
||||||
),
|
|
||||||
parts: ['亻', '木'],
|
|
||||||
codepoints: {'ucs': '4F11', 'jis208': '1-22-57'},
|
|
||||||
nanori: ['やす'],
|
|
||||||
alternativeLanguageReadings: {
|
|
||||||
'korean': ['휴'],
|
|
||||||
'pinyin': ['xiū'],
|
|
||||||
},
|
|
||||||
strokeMiscounts: [5, 7],
|
|
||||||
queryCodes: {
|
|
||||||
'skip': ['1-2-4'],
|
|
||||||
'fourCorner': ['2429.0'],
|
|
||||||
},
|
|
||||||
dictionaryReferences: {'nelson_c': '122', 'heisig': '457'},
|
|
||||||
);
|
|
||||||
|
|
||||||
final restored = KanjiSearchResult.fromJson(_roundTripMap(result.toJson()));
|
|
||||||
|
|
||||||
expect(restored, equals(result));
|
|
||||||
});
|
|
||||||
|
|
||||||
test(
|
|
||||||
'KanjiSearchResult JSON serialization roundtrip - nullable and empty fields',
|
|
||||||
() {
|
|
||||||
const result = KanjiSearchResult(
|
|
||||||
kanji: '々',
|
|
||||||
taughtIn: null,
|
|
||||||
jlptLevel: null,
|
|
||||||
newspaperFrequencyRank: null,
|
|
||||||
strokeCount: 3,
|
|
||||||
meanings: ['iteration mark'],
|
|
||||||
kunyomi: [],
|
|
||||||
onyomi: [],
|
|
||||||
radical: null,
|
|
||||||
parts: [],
|
|
||||||
codepoints: {'ucs': '3005'},
|
|
||||||
nanori: [],
|
|
||||||
alternativeLanguageReadings: {},
|
|
||||||
strokeMiscounts: [],
|
|
||||||
queryCodes: {},
|
|
||||||
dictionaryReferences: {},
|
|
||||||
);
|
|
||||||
|
|
||||||
final restored = KanjiSearchResult.fromJson(
|
|
||||||
_roundTripMap(result.toJson()),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(restored, equals(result));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:jadb/models/kanjivg/kanjivg_entry.dart';
|
|
||||||
import 'package:jadb/models/kanjivg/kanjivg_path.dart';
|
|
||||||
import 'package:jadb/models/kanjivg/kanjivg_path_group.dart';
|
|
||||||
import 'package:jadb/models/kanjivg/kanjivg_path_group_position.dart';
|
|
||||||
import 'package:jadb/models/kanjivg/kanjivg_radical.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
Object? _roundTripJson(Object? value) => jsonDecode(jsonEncode(value));
|
|
||||||
|
|
||||||
Map<String, dynamic> _roundTripMap(Object? json) =>
|
|
||||||
Map<String, dynamic>.from(_roundTripJson(json) as Map);
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('KanjiVG model serialization', () {
|
|
||||||
test('KanjiVGPathGroupPosition roundtrips all values', () {
|
|
||||||
for (final value in KanjiVGPathGroupPosition.values) {
|
|
||||||
expect(
|
|
||||||
KanjiVGPathGroupPosition.fromJson(_roundTripJson(value.toJson())),
|
|
||||||
equals(value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('KanjiVGPathGroupPosition parses SVG aliases', () {
|
|
||||||
expect(
|
|
||||||
KanjiVGPathGroupPosition.fromString('⿵A'),
|
|
||||||
equals(KanjiVGPathGroupPosition.upperA),
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
KanjiVGPathGroupPosition.fromString('⿵B'),
|
|
||||||
equals(KanjiVGPathGroupPosition.upperB),
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
KanjiVGPathGroupPosition.fromString('⿶1'),
|
|
||||||
equals(KanjiVGPathGroupPosition.lower1),
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
KanjiVGPathGroupPosition.fromString('⿶2'),
|
|
||||||
equals(KanjiVGPathGroupPosition.lower2),
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
KanjiVGPathGroupPosition.fromString('left'),
|
|
||||||
equals(KanjiVGPathGroupPosition.left),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('KanjiVGRadical roundtrips all values', () {
|
|
||||||
for (final value in KanjiVGRadical.values) {
|
|
||||||
expect(
|
|
||||||
KanjiVGRadical.fromJson(_roundTripJson(value.toJson())),
|
|
||||||
equals(value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('KanjiVGPath roundtrips via JSON', () {
|
|
||||||
final path = KanjiVGPath(
|
|
||||||
pathId: 1,
|
|
||||||
type: '㇐',
|
|
||||||
svgPath: 'M12.5,18c2.1,0.4,6.1,0.6,8.1,0.4',
|
|
||||||
labelX: 12.5,
|
|
||||||
labelY: 18.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
final restored = KanjiVGPath.fromJson(_roundTripMap(path.toJson()));
|
|
||||||
|
|
||||||
expect(restored, equals(path));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('KanjiVGPath roundtrips required label coordinates', () {
|
|
||||||
final path = KanjiVGPath(
|
|
||||||
pathId: 2,
|
|
||||||
type: '㇒',
|
|
||||||
svgPath: 'M18,12c0.5,1,1,2,1.5,3',
|
|
||||||
labelX: 9.5,
|
|
||||||
labelY: 14.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
final restored = KanjiVGPath.fromJson(_roundTripMap(path.toJson()));
|
|
||||||
|
|
||||||
expect(restored, equals(path));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('KanjiVGPathGroup roundtrips nested tree data', () {
|
|
||||||
final group = KanjiVGPathGroup(
|
|
||||||
groupId: 0,
|
|
||||||
element: '休',
|
|
||||||
position: KanjiVGPathGroupPosition.left,
|
|
||||||
paths: [
|
|
||||||
KanjiVGPath(
|
|
||||||
pathId: 1,
|
|
||||||
type: '㇐',
|
|
||||||
svgPath: 'M10,10c1,0,2,0,3,0',
|
|
||||||
labelX: 7.0,
|
|
||||||
labelY: 9.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
children: [
|
|
||||||
KanjiVGPathGroup(
|
|
||||||
groupId: 1,
|
|
||||||
element: '人',
|
|
||||||
radical: KanjiVGRadical.general,
|
|
||||||
part: 1,
|
|
||||||
paths: [
|
|
||||||
KanjiVGPath(
|
|
||||||
pathId: 2,
|
|
||||||
type: '㇒',
|
|
||||||
svgPath: 'M12,8c0.5,1,1,2,1.5,3',
|
|
||||||
labelX: 11.0,
|
|
||||||
labelY: 6.5,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
KanjiVGPathGroup(
|
|
||||||
groupId: 2,
|
|
||||||
element: '木',
|
|
||||||
original: '木',
|
|
||||||
position: KanjiVGPathGroupPosition.right,
|
|
||||||
paths: [
|
|
||||||
KanjiVGPath(
|
|
||||||
pathId: 3,
|
|
||||||
type: '㇑',
|
|
||||||
svgPath: 'M18,9c0,2,0,4,0,6',
|
|
||||||
labelX: 19.0,
|
|
||||||
labelY: 7.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
final restored = KanjiVGPathGroup.fromJson(_roundTripMap(group.toJson()));
|
|
||||||
|
|
||||||
expect(restored, equals(group));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('KanjiVGEntry roundtrips populated data', () {
|
|
||||||
final entry = KanjiVGEntry(
|
|
||||||
character: '休',
|
|
||||||
paths: [
|
|
||||||
KanjiVGPath(
|
|
||||||
pathId: 1,
|
|
||||||
type: '㇒',
|
|
||||||
svgPath: 'M18,12c0.5,1,1,2,1.5,3',
|
|
||||||
labelX: 12.0,
|
|
||||||
labelY: 10.0,
|
|
||||||
),
|
|
||||||
KanjiVGPath(
|
|
||||||
pathId: 2,
|
|
||||||
type: '㇐',
|
|
||||||
svgPath: 'M30,24c2,0,6,0,8,0',
|
|
||||||
labelX: 28.0,
|
|
||||||
labelY: 21.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
pathGroups: [
|
|
||||||
KanjiVGPathGroup(
|
|
||||||
groupId: 0,
|
|
||||||
element: '休',
|
|
||||||
children: [
|
|
||||||
KanjiVGPathGroup(
|
|
||||||
groupId: 1,
|
|
||||||
element: '人',
|
|
||||||
radical: KanjiVGRadical.general,
|
|
||||||
paths: [
|
|
||||||
KanjiVGPath(
|
|
||||||
pathId: 1,
|
|
||||||
type: '㇒',
|
|
||||||
svgPath: 'M18,12c0.5,1,1,2,1.5,3',
|
|
||||||
labelX: 12.0,
|
|
||||||
labelY: 10.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
KanjiVGPathGroup(
|
|
||||||
groupId: 2,
|
|
||||||
element: '木',
|
|
||||||
position: KanjiVGPathGroupPosition.right,
|
|
||||||
paths: [
|
|
||||||
KanjiVGPath(
|
|
||||||
pathId: 2,
|
|
||||||
type: '㇐',
|
|
||||||
svgPath: 'M30,24c2,0,6,0,8,0',
|
|
||||||
labelX: 28.0,
|
|
||||||
labelY: 21.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
final restored = KanjiVGEntry.fromJson(_roundTripMap(entry.toJson()));
|
|
||||||
|
|
||||||
expect(restored, equals(entry));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:jadb/models/common/jlpt_level.dart';
|
|
||||||
import 'package:jadb/models/jmdict/jmdict_dialect.dart';
|
|
||||||
import 'package:jadb/models/jmdict/jmdict_field.dart';
|
|
||||||
import 'package:jadb/models/jmdict/jmdict_kanji_info.dart';
|
|
||||||
import 'package:jadb/models/jmdict/jmdict_misc.dart';
|
|
||||||
import 'package:jadb/models/jmdict/jmdict_pos.dart';
|
|
||||||
import 'package:jadb/models/jmdict/jmdict_reading_info.dart';
|
|
||||||
import 'package:jadb/models/word_search/word_search_match_span.dart';
|
|
||||||
import 'package:jadb/models/word_search/word_search_result.dart';
|
|
||||||
import 'package:jadb/models/word_search/word_search_ruby.dart';
|
|
||||||
import 'package:jadb/models/word_search/word_search_sense.dart';
|
|
||||||
import 'package:jadb/models/word_search/word_search_sense_language_source.dart';
|
|
||||||
import 'package:jadb/models/word_search/word_search_sources.dart';
|
|
||||||
import 'package:jadb/models/word_search/word_search_xref_entry.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
Object? _roundTripJson(Object? value) => jsonDecode(jsonEncode(value));
|
|
||||||
|
|
||||||
Map<String, dynamic> _roundTripMap(Object? json) =>
|
|
||||||
Map<String, dynamic>.from(_roundTripJson(json) as Map);
|
|
||||||
|
|
||||||
Map<String, Object?> _roundTripObjectMap(Object? json) =>
|
|
||||||
Map<String, Object?>.from(_roundTripJson(json) as Map);
|
|
||||||
|
|
||||||
void _expectScalarRoundTrip<T>({
|
|
||||||
required Iterable<T> values,
|
|
||||||
required Object? Function(T value) toJson,
|
|
||||||
required T Function(Object? json) fromJson,
|
|
||||||
}) {
|
|
||||||
for (final value in values) {
|
|
||||||
expect(
|
|
||||||
fromJson(_roundTripJson(toJson(value))),
|
|
||||||
equals(value),
|
|
||||||
reason: 'Roundtrip failed for $value',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _expectMapRoundTrip<T>({
|
|
||||||
required Iterable<T> values,
|
|
||||||
required Map<String, Object?> Function(T value) toJson,
|
|
||||||
required T Function(Map<String, Object?> json) fromJson,
|
|
||||||
}) {
|
|
||||||
for (final value in values) {
|
|
||||||
expect(
|
|
||||||
fromJson(_roundTripObjectMap(toJson(value))),
|
|
||||||
equals(value),
|
|
||||||
reason: 'Roundtrip failed for $value',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WordSearchResult _buildNestedXrefResult() => WordSearchResult(
|
|
||||||
score: 7,
|
|
||||||
entryId: 300,
|
|
||||||
isCommon: false,
|
|
||||||
japanese: [WordSearchRuby(base: '補助', furigana: 'ほじょ')],
|
|
||||||
kanjiInfo: const {},
|
|
||||||
readingInfo: const {},
|
|
||||||
senses: const [],
|
|
||||||
jlptLevel: JlptLevel.none,
|
|
||||||
sources: const WordSearchSources(jmdict: true, jmnedict: false),
|
|
||||||
);
|
|
||||||
|
|
||||||
WordSearchSense _buildSense() => WordSearchSense(
|
|
||||||
englishDefinitions: ['kana', 'syllabary'],
|
|
||||||
partsOfSpeech: [JMdictPOS.n],
|
|
||||||
seeAlso: [
|
|
||||||
WordSearchXrefEntry(
|
|
||||||
entryId: 300,
|
|
||||||
ambiguous: false,
|
|
||||||
baseWord: '仮名遣い',
|
|
||||||
furigana: 'かなづかい',
|
|
||||||
xrefResult: _buildNestedXrefResult(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
antonyms: const [
|
|
||||||
WordSearchXrefEntry(
|
|
||||||
entryId: 301,
|
|
||||||
ambiguous: true,
|
|
||||||
baseWord: '漢字',
|
|
||||||
furigana: 'かんじ',
|
|
||||||
xrefResult: null,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
restrictedToReading: ['かな'],
|
|
||||||
restrictedToKanji: ['仮名'],
|
|
||||||
fields: [JMdictField.linguistics],
|
|
||||||
dialects: [JMdictDialect.kansai],
|
|
||||||
misc: [JMdictMisc.onlyKana, JMdictMisc.rare],
|
|
||||||
info: ['Typically written using kana alone.'],
|
|
||||||
languageSource: const [
|
|
||||||
WordSearchSenseLanguageSource(
|
|
||||||
language: 'por',
|
|
||||||
phrase: 'canoa',
|
|
||||||
fullyDescribesSense: false,
|
|
||||||
constructedFromSmallerWords: true,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
WordSearchResult _buildWordSearchResult({
|
|
||||||
List<WordSearchMatchSpan>? matchSpans,
|
|
||||||
}) => WordSearchResult(
|
|
||||||
score: 42,
|
|
||||||
entryId: 123,
|
|
||||||
isCommon: true,
|
|
||||||
japanese: [
|
|
||||||
WordSearchRuby(base: '仮名', furigana: 'かな'),
|
|
||||||
WordSearchRuby(base: 'かな'),
|
|
||||||
],
|
|
||||||
kanjiInfo: {'仮名': JMdictKanjiInfo.rK, '仮': JMdictKanjiInfo.ateji},
|
|
||||||
readingInfo: {'かな': JMdictReadingInfo.gikun, 'カナ': JMdictReadingInfo.rk},
|
|
||||||
senses: [_buildSense()],
|
|
||||||
jlptLevel: JlptLevel.n5,
|
|
||||||
sources: const WordSearchSources(jmdict: true, jmnedict: true),
|
|
||||||
matchSpans: matchSpans,
|
|
||||||
);
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('JlptLevel JSON serialization roundtrip', () {
|
|
||||||
_expectScalarRoundTrip<JlptLevel>(
|
|
||||||
values: JlptLevel.values,
|
|
||||||
toJson: (value) => value.toJson(),
|
|
||||||
fromJson: JlptLevel.fromJson,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('JMdictDialect JSON serialization roundtrip', () {
|
|
||||||
_expectMapRoundTrip<JMdictDialect>(
|
|
||||||
values: JMdictDialect.values,
|
|
||||||
toJson: (value) => value.toJson(),
|
|
||||||
fromJson: JMdictDialect.fromJson,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('JMdictField JSON serialization roundtrip', () {
|
|
||||||
_expectMapRoundTrip<JMdictField>(
|
|
||||||
values: JMdictField.values,
|
|
||||||
toJson: (value) => value.toJson(),
|
|
||||||
fromJson: JMdictField.fromJson,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('JMdictKanjiInfo JSON serialization roundtrip', () {
|
|
||||||
_expectMapRoundTrip<JMdictKanjiInfo>(
|
|
||||||
values: JMdictKanjiInfo.values,
|
|
||||||
toJson: (value) => value.toJson(),
|
|
||||||
fromJson: JMdictKanjiInfo.fromJson,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('JMdictMisc JSON serialization roundtrip', () {
|
|
||||||
_expectMapRoundTrip<JMdictMisc>(
|
|
||||||
values: JMdictMisc.values,
|
|
||||||
toJson: (value) => value.toJson(),
|
|
||||||
fromJson: JMdictMisc.fromJson,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('JMdictPOS JSON serialization roundtrip', () {
|
|
||||||
_expectMapRoundTrip<JMdictPOS>(
|
|
||||||
values: JMdictPOS.values,
|
|
||||||
toJson: (value) => value.toJson(),
|
|
||||||
fromJson: JMdictPOS.fromJson,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('JMdictReadingInfo JSON serialization roundtrip', () {
|
|
||||||
_expectMapRoundTrip<JMdictReadingInfo>(
|
|
||||||
values: JMdictReadingInfo.values,
|
|
||||||
toJson: (value) => value.toJson(),
|
|
||||||
fromJson: JMdictReadingInfo.fromJson,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('WordSearchMatchSpan JSON serialization roundtrip', () {
|
|
||||||
final span = WordSearchMatchSpan(
|
|
||||||
spanType: WordSearchMatchSpanType.sense,
|
|
||||||
index: 2,
|
|
||||||
subIndex: 3,
|
|
||||||
start: 4,
|
|
||||||
end: 8,
|
|
||||||
);
|
|
||||||
|
|
||||||
final restored = WordSearchMatchSpan.fromJson(_roundTripMap(span.toJson()));
|
|
||||||
|
|
||||||
expect(restored, equals(span));
|
|
||||||
expect(restored.subIndex, equals(span.subIndex));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('WordSearchRuby JSON serialization roundtrip', () {
|
|
||||||
final ruby = WordSearchRuby(base: '仮名', furigana: 'かな');
|
|
||||||
|
|
||||||
final restored = WordSearchRuby.fromJson(_roundTripMap(ruby.toJson()));
|
|
||||||
|
|
||||||
expect(restored.toJson(), equals(ruby.toJson()));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('WordSearchSenseLanguageSource JSON serialization roundtrip', () {
|
|
||||||
const source = WordSearchSenseLanguageSource(
|
|
||||||
language: 'por',
|
|
||||||
phrase: 'canoa',
|
|
||||||
fullyDescribesSense: false,
|
|
||||||
constructedFromSmallerWords: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
final restored = WordSearchSenseLanguageSource.fromJson(
|
|
||||||
_roundTripMap(source.toJson()),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(restored.toJson(), equals(source.toJson()));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('WordSearchSources JSON serialization roundtrip', () {
|
|
||||||
const sources = WordSearchSources(jmdict: false, jmnedict: true);
|
|
||||||
|
|
||||||
final restored = WordSearchSources.fromJson(
|
|
||||||
_roundTripMap(sources.toJson()),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(restored.toJson(), equals(sources.toJson()));
|
|
||||||
expect(restored.sqlValue, equals(sources.sqlValue));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('WordSearchXrefEntry JSON serialization roundtrip', () {
|
|
||||||
final entry = WordSearchXrefEntry(
|
|
||||||
entryId: 300,
|
|
||||||
ambiguous: false,
|
|
||||||
baseWord: '仮名遣い',
|
|
||||||
furigana: 'かなづかい',
|
|
||||||
xrefResult: _buildNestedXrefResult(),
|
|
||||||
);
|
|
||||||
|
|
||||||
final restored = WordSearchXrefEntry.fromJson(
|
|
||||||
_roundTripMap(entry.toJson()),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(restored.toJson(), equals(entry.toJson()));
|
|
||||||
expect(restored.xrefResult?.toJson(), equals(entry.xrefResult?.toJson()));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('WordSearchSense JSON serialization roundtrip', () {
|
|
||||||
final sense = _buildSense();
|
|
||||||
|
|
||||||
final restored = WordSearchSense.fromJson(_roundTripMap(sense.toJson()));
|
|
||||||
|
|
||||||
expect(restored.toJson(), equals(sense.toJson()));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('WordSearchResult JSON serialization roundtrip', () {
|
|
||||||
final result = _buildWordSearchResult();
|
|
||||||
|
|
||||||
final restored = WordSearchResult.fromJson(_roundTripMap(result.toJson()));
|
|
||||||
|
|
||||||
expect(restored.toJson(), equals(result.toJson()));
|
|
||||||
expect(restored.matchSpans, isNull);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('WordSearchResult leaves matchSpans out of JSON', () {
|
|
||||||
final result = _buildWordSearchResult(
|
|
||||||
matchSpans: [
|
|
||||||
WordSearchMatchSpan(
|
|
||||||
spanType: WordSearchMatchSpanType.sense,
|
|
||||||
index: 0,
|
|
||||||
subIndex: 1,
|
|
||||||
start: 0,
|
|
||||||
end: 4,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
final json = _roundTripMap(result.toJson());
|
|
||||||
final restored = WordSearchResult.fromJson(json);
|
|
||||||
|
|
||||||
expect(json.containsKey('matchSpans'), isFalse);
|
|
||||||
expect(restored.matchSpans, isNull);
|
|
||||||
expect(restored.toJson(), equals(result.toJson()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import 'package:jadb/search.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
import 'setup_database_connection.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('Retrieve datasource versions', () async {
|
|
||||||
final connection = await setupDatabaseConnection();
|
|
||||||
final result = await connection.jadbGetDatasourceVersions();
|
|
||||||
expect(result, isNotNull);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import 'package:jadb/models/kanjivg/kanjivg_path_group.dart';
|
|
||||||
import 'package:jadb/search.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
import 'setup_database_connection.dart';
|
|
||||||
|
|
||||||
Iterable<KanjiVGPathGroup> _flattenGroups(
|
|
||||||
Iterable<KanjiVGPathGroup> groups,
|
|
||||||
) sync* {
|
|
||||||
for (final group in groups) {
|
|
||||||
yield group;
|
|
||||||
yield* _flattenGroups(group.children);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('KanjiVG search', () {
|
|
||||||
test('returns null when the entry does not exist', () async {
|
|
||||||
final connection = await setupDatabaseConnection();
|
|
||||||
addTearDown(() async => connection.close());
|
|
||||||
|
|
||||||
final result = await connection.jadbSearchKanjiVGGraph('notfound');
|
|
||||||
|
|
||||||
expect(result, isNull);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns entry paths without path groups by default', () async {
|
|
||||||
final connection = await setupDatabaseConnection();
|
|
||||||
addTearDown(() async => connection.close());
|
|
||||||
|
|
||||||
final result = await connection.jadbSearchKanjiVGGraph('休');
|
|
||||||
|
|
||||||
expect(result, isNotNull);
|
|
||||||
expect(result!.character, equals('休'));
|
|
||||||
expect(result.paths, isNotEmpty);
|
|
||||||
expect(result.pathGroups, isNull);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns the path-group graph when requested', () async {
|
|
||||||
final connection = await setupDatabaseConnection();
|
|
||||||
addTearDown(() async => connection.close());
|
|
||||||
|
|
||||||
final result = await connection.jadbSearchKanjiVGGraph(
|
|
||||||
'休',
|
|
||||||
includePathGroups: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result, isNotNull);
|
|
||||||
expect(result!.pathGroups, isNotNull);
|
|
||||||
expect(result.pathGroups, isNotEmpty);
|
|
||||||
|
|
||||||
final allGroups = _flattenGroups(result.pathGroups!).toList();
|
|
||||||
final groupedPathIds =
|
|
||||||
allGroups
|
|
||||||
.expand((group) => group.paths)
|
|
||||||
.map((path) => path.pathId)
|
|
||||||
.toList()
|
|
||||||
..sort();
|
|
||||||
final entryPathIds = result.paths.map((path) => path.pathId).toList()
|
|
||||||
..sort();
|
|
||||||
|
|
||||||
expect(allGroups.any((group) => group.groupId == 0), isTrue);
|
|
||||||
expect(allGroups.any((group) => group.paths.isNotEmpty), isTrue);
|
|
||||||
expect(groupedPathIds, equals(entryPathIds));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -7,29 +7,32 @@ import 'package:test/test.dart';
|
|||||||
import 'setup_database_connection.dart';
|
import 'setup_database_connection.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('All constant radicals should exist in the database', () async {
|
test(
|
||||||
final connection = await setupDatabaseConnection();
|
'All constant radicals should exist in the database',
|
||||||
final allRadicalsInDb = await connection.query(
|
() async {
|
||||||
RADKFILETableNames.radkfile,
|
final connection = await setupDatabaseConnection();
|
||||||
columns: ['radical'],
|
final allRadicalsInDb = await connection.query(
|
||||||
distinct: true,
|
RADKFILETableNames.radkfile,
|
||||||
);
|
columns: ['radical'],
|
||||||
|
distinct: true,
|
||||||
|
);
|
||||||
|
|
||||||
final radicalsInDb = allRadicalsInDb
|
final radicalsInDb = allRadicalsInDb
|
||||||
.map((e) => e['radical'] as String)
|
.map((e) => e['radical'] as String)
|
||||||
.toSet();
|
.toSet();
|
||||||
|
|
||||||
final missingRadicals = radicals.values.flattenedToSet
|
final missingRadicals = radicals.values.flattenedToSet.difference(
|
||||||
.map((e) => e.formalVariant)
|
radicalsInDb,
|
||||||
.toSet()
|
);
|
||||||
.difference(radicalsInDb);
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
missingRadicals,
|
missingRadicals,
|
||||||
isEmpty,
|
isEmpty,
|
||||||
reason: 'Missing radicals in database: $missingRadicals',
|
reason: 'Missing radicals in database: $missingRadicals',
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
skip: 'Test is valid, code is broken, fix me',
|
||||||
|
);
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'All radicals in database should be in the constant radical list',
|
'All radicals in database should be in the constant radical list',
|
||||||
@@ -46,7 +49,7 @@ void main() {
|
|||||||
.toSet();
|
.toSet();
|
||||||
|
|
||||||
final extraRadicals = radicalsInDb.difference(
|
final extraRadicals = radicalsInDb.difference(
|
||||||
radicals.values.flattenedToSet.map((e) => e.formalVariant).toSet(),
|
radicals.values.flattenedToSet,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@@ -56,30 +59,21 @@ void main() {
|
|||||||
'Extra radicals in database missing in the constant list: $extraRadicals',
|
'Extra radicals in database missing in the constant list: $extraRadicals',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
skip: 'Test is valid, code is broken, fix me',
|
||||||
);
|
);
|
||||||
|
|
||||||
test('All constant radicals are located in the correct stroke count group', () {
|
group(
|
||||||
for (final mapEntry in radicals.entries) {
|
'All radicals should return results',
|
||||||
final strokeCount = mapEntry.key;
|
() {
|
||||||
final radicalsInGroup = mapEntry.value;
|
for (final radical in radicals.values.flattened) {
|
||||||
for (final radical in radicalsInGroup) {
|
test(' - $radical', () async {
|
||||||
expect(
|
final connection = await setupDatabaseConnection();
|
||||||
strokeCount,
|
final result = await connection.jadbSearchKanjiByRadicals([radical]);
|
||||||
radical.strokeCount,
|
expect(result, isNotEmpty);
|
||||||
reason:
|
});
|
||||||
'Radical ${radical.formalVariant} should have stroke count $strokeCount but has ${radical.strokeCount}',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
skip:
|
||||||
|
'These will be automatically fixed once the other radical tests are passing',
|
||||||
group('All radicals should return results', () {
|
);
|
||||||
for (final radical in radicals.values.flattened) {
|
|
||||||
test(' - $radical', () async {
|
|
||||||
final connection = await setupDatabaseConnection();
|
|
||||||
final result = await connection.jadbSearchKanjiByRadicals([radical]);
|
|
||||||
expect(result, isNotEmpty);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,22 +4,6 @@ import 'package:test/test.dart';
|
|||||||
import 'setup_database_connection.dart';
|
import 'setup_database_connection.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('Search empty string - auto', () async {
|
|
||||||
final connection = await setupDatabaseConnection();
|
|
||||||
final result = await connection.jadbSearchWord('');
|
|
||||||
expect(result, isNull);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Search whitespace - auto', () async {
|
|
||||||
final connection = await setupDatabaseConnection();
|
|
||||||
final result1 = await connection.jadbSearchWord(' ');
|
|
||||||
expect(result1, isNull);
|
|
||||||
final result2 = await connection.jadbSearchWord('\t');
|
|
||||||
expect(result2, isNull);
|
|
||||||
final result3 = await connection.jadbSearchWord('\n');
|
|
||||||
expect(result3, isNull);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Search a word - english - auto', () async {
|
test('Search a word - english - auto', () async {
|
||||||
final connection = await setupDatabaseConnection();
|
final connection = await setupDatabaseConnection();
|
||||||
final result = await connection.jadbSearchWord('kana');
|
final result = await connection.jadbSearchWord('kana');
|
||||||
|
|||||||
Reference in New Issue
Block a user