Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
1120050912
|
|||
|
bbdb177fa4
|
|||
|
8ba7c66e67
|
|||
|
0e32fbaeb1
|
|||
|
fe3ba1a2d3
|
|||
|
d91584f75a
|
|||
|
fa7192152d
|
|||
|
c3d2b9eb10
|
|||
|
e0d1391d20
|
|||
|
e93cd0df4b
|
|||
|
ce45ae7cc8
|
|||
|
f0e919c397
|
|||
|
ab878b3469
|
|||
|
832a74f9c2
|
|||
|
a46d61c335
|
|||
|
f3dba3ffab
|
|||
|
5556a73899
|
|||
|
541680b95d
|
|||
|
505380d2cc
|
|||
|
c4abcabca2
|
|||
|
5629198539
|
|||
|
7360144136
|
|||
|
c43285c78a
|
|||
|
c22e96b7f9
|
|||
|
d3516495ab
|
|||
|
be4dd72dcb
|
|||
|
32ec34a150
|
|||
|
bd0822b740
|
|||
|
8827893101
|
|||
|
28c4403e2d
|
|||
|
d13138f8a5
|
|||
|
cbaa9ec6b3
|
|||
|
d1e2fa3748
|
|||
|
3f4fdf470d
|
|||
|
556d07913d
|
|||
|
6165045ea7
|
|||
|
316dff3b46
|
|||
|
747e680a02
|
|||
|
4f73e07056
|
|||
|
15540514f6
|
|||
|
4faf543d6e
|
|||
|
d1a6f39cca
|
|||
|
a222b2d9b8
|
|||
|
6364457d9e
|
|||
|
5d26b41524
|
|||
|
114febbe02
|
|||
|
20243dec09
|
|||
|
f6de8680ad
|
|||
|
99218a6987
|
|||
|
e8ee1ab944
|
|||
|
4f320e4ea9
|
|||
|
9c9f5543c8
|
|||
|
be493a6150
|
|||
|
8d742b92be
|
|||
|
9b9c771eff
|
|||
|
eebeaba0e0
|
|||
|
61ac226fc3
|
|||
|
ede57a7a00
|
|||
|
2ad1e038f1
|
|||
|
f40825de65
|
|||
|
5aa068eaec
|
|||
|
170c3a853e
|
@@ -16,19 +16,14 @@ jobs:
|
|||||||
uses: https://github.com/cachix/install-nix-action@v31
|
uses: https://github.com/cachix/install-nix-action@v31
|
||||||
with:
|
with:
|
||||||
extra_nix_config: |
|
extra_nix_config: |
|
||||||
experimental-features = nix-command flakes
|
|
||||||
show-trace = true
|
show-trace = true
|
||||||
max-jobs = auto
|
max-jobs = auto
|
||||||
trusted-users = root
|
trusted-users = root
|
||||||
experimental-features = nix-command flakes
|
experimental-features = nix-command flakes
|
||||||
build-users-group =
|
build-users-group =
|
||||||
|
|
||||||
- name: Update database inputs
|
# - name: Update database inputs
|
||||||
run: |
|
# run: nix flake update datasources
|
||||||
nix flake update jmdict-src
|
|
||||||
nix flake update jmdict-with-examples-src
|
|
||||||
nix flake update radkfile-src
|
|
||||||
nix flake update kanjidic2-src
|
|
||||||
|
|
||||||
- name: Build database
|
- name: Build database
|
||||||
run: nix build .#database -L
|
run: nix build .#database -L
|
||||||
@@ -44,7 +39,7 @@ jobs:
|
|||||||
compression: 0
|
compression: 0
|
||||||
|
|
||||||
- name: Print database statistics
|
- name: Print database statistics
|
||||||
run: nix develop .# --command sqlite3_analyzer result/jadb.sqlite
|
run: nix develop .#sqlite-debugging --command sqlite3_analyzer result/jadb.sqlite
|
||||||
|
|
||||||
# TODO: Defer failure of tests until after the coverage report is generated and uploaded.
|
# TODO: Defer failure of tests until after the coverage report is generated and uploaded.
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
|||||||
@@ -18,26 +18,4 @@ Note that while the license for the code is MIT, the data has various licenses.
|
|||||||
| **Tanos JLPT levels:** | https://www.tanos.co.uk/jlpt/ |
|
| **Tanos JLPT levels:** | https://www.tanos.co.uk/jlpt/ |
|
||||||
| **Kangxi Radicals:** | https://ctext.org/kangxi-zidian |
|
| **Kangxi Radicals:** | https://ctext.org/kangxi-zidian |
|
||||||
|
|
||||||
## Implementation details
|
See [docs/overview.md](./docs/overview.md) for notes and implementation details.
|
||||||
|
|
||||||
### Word search
|
|
||||||
|
|
||||||
The word search procedure is currently split into 3 parts:
|
|
||||||
|
|
||||||
1. **Entry ID query**:
|
|
||||||
|
|
||||||
Use a complex query with various scoring factors to try to get list of
|
|
||||||
database ids pointing at dictionary entries, sorted by how likely we think this
|
|
||||||
word is the word that the caller is looking for. The output here is a `List<int>`
|
|
||||||
|
|
||||||
2. **Data Query**:
|
|
||||||
|
|
||||||
Takes the entry id list from the last search, and performs all queries needed to retrieve
|
|
||||||
all the dictionary data for those IDs. The result is a struct with a bunch of flattened lists
|
|
||||||
with data for all the dictionary entries. These lists are sorted by the order that the ids
|
|
||||||
were provided.
|
|
||||||
|
|
||||||
3. **Regrouping**:
|
|
||||||
|
|
||||||
Takes the flattened data, and regroups the items into structs with a more "hierarchical" structure.
|
|
||||||
All data tagged with the same ID will end up in the same struct. Returns a list of these structs.
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import './search/word_search.dart';
|
import './search/english_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 WordSearchBenchmark.main();
|
await EnglishWordSearchBenchmark.main();
|
||||||
|
await JapaneseWordSearchBenchmark.main();
|
||||||
|
await JapaneseWordSearchBenchmarkFuzzy.main();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,23 +4,23 @@ import 'package:sqflite_common/sqlite_api.dart';
|
|||||||
|
|
||||||
import '../../test/search/setup_database_connection.dart';
|
import '../../test/search/setup_database_connection.dart';
|
||||||
|
|
||||||
class WordSearchBenchmark extends AsyncBenchmarkBase {
|
class EnglishWordSearchBenchmark extends AsyncBenchmarkBase {
|
||||||
Database? connection;
|
Database? connection;
|
||||||
|
|
||||||
static final List<String> searchTerms = [
|
static final List<String> searchTerms = [
|
||||||
'kana',
|
'kana',
|
||||||
'kanji',
|
'kanji',
|
||||||
'kawaii',
|
'cute',
|
||||||
'sushi',
|
'sushi',
|
||||||
'ramen',
|
'ramen',
|
||||||
];
|
];
|
||||||
|
|
||||||
WordSearchBenchmark() : super('WordSearchBenchmark');
|
EnglishWordSearchBenchmark() : super('EnglishWordSearchBenchmark');
|
||||||
|
|
||||||
static Future<void> main() async {
|
static Future<void> main() async {
|
||||||
print('Running WordSearchBenchmark...');
|
print('Running EnglishWordSearchBenchmark...');
|
||||||
await WordSearchBenchmark().report();
|
await EnglishWordSearchBenchmark().report();
|
||||||
print('Finished WordSearchBenchmark');
|
print('Finished EnglishWordSearchBenchmark');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -31,7 +31,11 @@ class WordSearchBenchmark extends AsyncBenchmarkBase {
|
|||||||
@override
|
@override
|
||||||
Future<void> run() async {
|
Future<void> run() async {
|
||||||
for (final term in searchTerms) {
|
for (final term in searchTerms) {
|
||||||
await connection!.jadbSearchWord(term);
|
final result = await connection!.jadbSearchWord(term);
|
||||||
|
assert(
|
||||||
|
result?.isNotEmpty ?? false,
|
||||||
|
'Expected search results for term "$term"',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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 JapaneseWordSearchBenchmark extends AsyncBenchmarkBase {
|
||||||
|
Database? connection;
|
||||||
|
|
||||||
|
static final List<String> searchTerms = [
|
||||||
|
'仮名',
|
||||||
|
'漢字',
|
||||||
|
'かわいい',
|
||||||
|
'すし',
|
||||||
|
'ラメン',
|
||||||
|
];
|
||||||
|
|
||||||
|
JapaneseWordSearchBenchmark() : super('JapaneseWordSearchBenchmark');
|
||||||
|
|
||||||
|
static Future<void> main() async {
|
||||||
|
print('Running JapaneseWordSearchBenchmark...');
|
||||||
|
await JapaneseWordSearchBenchmark().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: false);
|
||||||
|
assert(
|
||||||
|
result?.isNotEmpty ?? false,
|
||||||
|
'Expected search results for term "$term"',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> teardown() async {
|
||||||
|
await connection?.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Future<void> exercise() => run();
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,582 +0,0 @@
|
|||||||
,あ,Ah
|
|
||||||
,ああ,like that
|
|
||||||
間,あいだ,a space
|
|
||||||
合う,あう,to match
|
|
||||||
,あかちゃん,baby
|
|
||||||
上る,あがる,to rise
|
|
||||||
赤ん坊,あかんぼう,baby
|
|
||||||
空く,あく,"to open, to become empty"
|
|
||||||
,あげる,to give
|
|
||||||
浅い,あさい,"shallow, superficial"
|
|
||||||
味,あじ,flavour
|
|
||||||
明日,あす・あした,tomorrow
|
|
||||||
遊び,あそび,play
|
|
||||||
集る,あつまる,to gather
|
|
||||||
集める,あつめる,to collect something
|
|
||||||
謝る,あやまる,to apologize
|
|
||||||
安心,あんしん,relief
|
|
||||||
安全,あんぜん,safety
|
|
||||||
,あんな,such
|
|
||||||
以下,いか,less than
|
|
||||||
以外,いがい,with the exception of
|
|
||||||
医学,いがく,medical science
|
|
||||||
生きる,いきる,to live
|
|
||||||
意見,いけん,opinion
|
|
||||||
石,いし,stone
|
|
||||||
,いじめる,to tease
|
|
||||||
以上,いじょう,"more than, this is all"
|
|
||||||
急ぐ,いそぐ,to hurry
|
|
||||||
致す,いたす,(humble) to do
|
|
||||||
一度,いちど,once
|
|
||||||
一生懸命,いっしょうけんめい,with utmost effort
|
|
||||||
,いっぱい,full
|
|
||||||
糸,いと,thread
|
|
||||||
以内,いない,within
|
|
||||||
田舎,いなか,countryside
|
|
||||||
祈る,いのる,to pray
|
|
||||||
,いらっしゃる,"(respectful) to be, to come or to go"
|
|
||||||
植える,うえる,"to plant, to grow"
|
|
||||||
受付,うけつけ,receipt
|
|
||||||
受ける,うける,to take a lesson or test
|
|
||||||
動く,うごく,to move
|
|
||||||
,うち,within
|
|
||||||
打つ,うつ,to hit
|
|
||||||
美しい,うつくしい,beautiful
|
|
||||||
写す,うつす,to copy or photograph
|
|
||||||
移る,うつる,to move house or transfer
|
|
||||||
腕,うで,arm
|
|
||||||
裏,うら,reverse side
|
|
||||||
売り場,うりば,place where things are sold
|
|
||||||
,うん,(informal) yes
|
|
||||||
運転手,うんてんしゅ,driver
|
|
||||||
枝,えだ,"branch, twig"
|
|
||||||
選ぶ,えらぶ,to choose
|
|
||||||
遠慮,えんりょ・する,"to be reserved, to be restrained"
|
|
||||||
,おいでになる,(respectful) to be
|
|
||||||
お祝い,おいわい,congratulation
|
|
||||||
,おかげ,"owing to, thanks to"
|
|
||||||
,おかしい,strange or funny
|
|
||||||
億,おく,one hundred million
|
|
||||||
屋上,おくじょう,rooftop
|
|
||||||
贈り物,おくりもの,gift
|
|
||||||
送る,おくる,to send
|
|
||||||
遅れる,おくれる,to be late
|
|
||||||
起す,おこす,to wake
|
|
||||||
行う,おこなう,to do
|
|
||||||
怒る,おこる,"to get angry, to be angry"
|
|
||||||
押し入れ,おしいれ,closet
|
|
||||||
お嬢さん,おじょうさん,young lady
|
|
||||||
お宅,おたく,(polite) your house
|
|
||||||
落る,おちる,to fall or drop
|
|
||||||
,おっしゃる,(respectful) to say
|
|
||||||
夫,おっと,husband
|
|
||||||
,おつり,"change from purchase, balance"
|
|
||||||
音,おと,"sound, note"
|
|
||||||
落す,おとす,to drop
|
|
||||||
踊り,おどり,a dance
|
|
||||||
踊る,おどる,to dance
|
|
||||||
驚く,おどろく,to be surprised
|
|
||||||
お祭り,おまつり,festival
|
|
||||||
お見舞い,おみまい,"calling on someone who is ill, enquiry"
|
|
||||||
お土産,おみやげ,souvenir
|
|
||||||
思い出す,おもいだす,to remember
|
|
||||||
思う,おもう,"to think, to feel"
|
|
||||||
,おもちゃ,toy
|
|
||||||
表,おもて,the front
|
|
||||||
親,おや,parents
|
|
||||||
泳ぎ方,およぎかた,way of swimming
|
|
||||||
下りる,おりる,"to get off, to descend"
|
|
||||||
折る,おる,to break or to fold
|
|
||||||
お礼,おれい,expression of gratitude
|
|
||||||
折れる,おれる,to break or be folded
|
|
||||||
終わり,おわり,the end
|
|
||||||
海岸,かいがん,coast
|
|
||||||
会議,かいぎ,meeting
|
|
||||||
会議室,かいぎしつ,meeting room
|
|
||||||
会場,かいじょう,assembly hall or meeting place
|
|
||||||
会話,かいわ,conversation
|
|
||||||
帰り,かえり,return
|
|
||||||
変える,かえる,to change
|
|
||||||
科学,かがく,science
|
|
||||||
鏡,かがみ,mirror
|
|
||||||
掛ける,かける,to hang something
|
|
||||||
飾る,かざる,to decorate
|
|
||||||
火事,かじ,fire
|
|
||||||
,ガス,gas
|
|
||||||
堅/硬/固い,かたい,hard
|
|
||||||
形,かたち,shape
|
|
||||||
片付ける,かたづける,to tidy up
|
|
||||||
課長,かちょう,section manager
|
|
||||||
勝つ,かつ,to win
|
|
||||||
家内,かない,housewife
|
|
||||||
悲しい,かなしい,sad
|
|
||||||
必ず,かならず,"certainly,necessarily"
|
|
||||||
お・金持ち,かねもち/おかねもち,rich man
|
|
||||||
彼女,かのじょ,"she,girlfriend"
|
|
||||||
壁,かべ,wall
|
|
||||||
髪,かみ,hair
|
|
||||||
噛む,かむ,"to bite,to chew"
|
|
||||||
通う,かよう,to commute
|
|
||||||
彼,かれ,"he,boyfriend"
|
|
||||||
乾く,かわく,to get dry
|
|
||||||
代わり,かわり,"substitute,alternate"
|
|
||||||
変わる,かわる,to change
|
|
||||||
考える,かんがえる,to consider
|
|
||||||
関係,かんけい,relationship
|
|
||||||
看護師,かんごし, nurse
|
|
||||||
簡単,かんたん,simple
|
|
||||||
気,き,"spirit,mood"
|
|
||||||
機会,きかい,opportunity
|
|
||||||
危険,きけん,danger
|
|
||||||
聞こえる,きこえる,to be heard
|
|
||||||
汽車,きしゃ,steam train
|
|
||||||
技術,ぎじゅつ,"art,technology,skill"
|
|
||||||
季節,きせつ,season
|
|
||||||
規則,きそく,regulations
|
|
||||||
,きっと,surely
|
|
||||||
絹,きぬ,silk
|
|
||||||
厳しい,きびしい,strict
|
|
||||||
気分,きぶん,mood
|
|
||||||
決る,きまる,to be decided
|
|
||||||
君,きみ,(informal) You
|
|
||||||
決める,きめる,to decide
|
|
||||||
気持ち,きもち,"feeling,mood"
|
|
||||||
着物,きもの,kimono
|
|
||||||
客,きゃく,"guest,customer"
|
|
||||||
急,きゅう,"urgent, steep"
|
|
||||||
急行,きゅうこう,"speedy, express"
|
|
||||||
教育,きょういく,education
|
|
||||||
教会,きょうかい,church
|
|
||||||
競争,きょうそう,competition
|
|
||||||
興味,きょうみ,an interest
|
|
||||||
近所,きんじょ,neighbourhood
|
|
||||||
具合,ぐあい,"condition,health"
|
|
||||||
空気,くうき,"air,atmosphere"
|
|
||||||
空港,くうこう,airport
|
|
||||||
草,くさ,grass
|
|
||||||
首,くび,neck
|
|
||||||
雲,くも,cloud
|
|
||||||
比べる,くらべる,to compare
|
|
||||||
,くれる,to give
|
|
||||||
暮れる,くれる,"to get dark,to come to an end"
|
|
||||||
君,くん,suffix for familiar young male
|
|
||||||
毛,け,hair or fur
|
|
||||||
経済,けいざい,"finance,economy"
|
|
||||||
警察,けいさつ,police
|
|
||||||
景色,けしき,"scene,landscape"
|
|
||||||
消しゴム,けしゴム,eraser
|
|
||||||
下宿,げしゅく,lodging
|
|
||||||
決して,けっして,never
|
|
||||||
,けれど/けれども,however
|
|
||||||
原因,げんいん,"cause,source"
|
|
||||||
,けんか・する,to quarrel
|
|
||||||
研究,けんきゅう,research
|
|
||||||
研究室,けんきゅうしつ,"study room,laboratory"
|
|
||||||
見物,けんぶつ,sightseeing
|
|
||||||
子,こ,child
|
|
||||||
,こう,this way
|
|
||||||
郊外,こうがい,outskirts
|
|
||||||
講義,こうぎ,lecture
|
|
||||||
工業,こうぎょう,the manufacturing industry
|
|
||||||
高校,こうこう,high school
|
|
||||||
高校生,こうこうせい,high school student
|
|
||||||
工場,こうじょう/こうば,"factory,plant,mill,workshop"
|
|
||||||
校長,こうちょう,headmaster
|
|
||||||
交通,こうつう,"traffic,transportation"
|
|
||||||
講堂,こうどう,auditorium
|
|
||||||
高等学校,こうとうがっこう,high school
|
|
||||||
公務員,こうむいん,"civil servant, government worker"
|
|
||||||
国際,こくさい,international
|
|
||||||
心,こころ,"heart, mind, core"
|
|
||||||
御主人,ごしゅじん,(honorable) your husband
|
|
||||||
故障,こしょう・する,to break-down
|
|
||||||
ご存じ,ごぞんじ,(respect form ) to know
|
|
||||||
答,こたえ,response
|
|
||||||
,ごちそう,a feast
|
|
||||||
小鳥,ことり,small bird
|
|
||||||
,このあいだ,"the other day,recently"
|
|
||||||
,このごろ,"these days,nowadays"
|
|
||||||
細かい,こまかい,"small, fine"
|
|
||||||
込む,こむ,to include
|
|
||||||
米,こめ,uncooked rice
|
|
||||||
,ごらんになる,(respectful) to see
|
|
||||||
,これから,after this
|
|
||||||
怖い,こわい,frightening
|
|
||||||
壊す,こわす,to break
|
|
||||||
壊れる,こわれる,to be broken
|
|
||||||
今度,こんど,"now,next time"
|
|
||||||
今夜,こんや,tonight
|
|
||||||
最近,さいきん,"latest,nowadays"
|
|
||||||
最後,さいご,"last,end"
|
|
||||||
最初,さいしょ,"beginning,first"
|
|
||||||
坂,さか,"slope,hill"
|
|
||||||
探す,さがす,to look for
|
|
||||||
下る,さがる,"to get down,to descend"
|
|
||||||
盛ん,さかん,"popularity,prosperous"
|
|
||||||
下げる,さげる,"to hang,to lower,to move back"
|
|
||||||
差し上げる,さしあげる,(polite) to give
|
|
||||||
,さっき,some time ago
|
|
||||||
寂しい,さびしい,lonely
|
|
||||||
さ来月,さらいげつ,the month after next
|
|
||||||
さ来週,さらいしゅう,the week after next
|
|
||||||
騒ぐ,さわぐ,"to make noise,to be excited"
|
|
||||||
触る,さわる,to touch
|
|
||||||
産業,さんぎょう,industry
|
|
||||||
残念,ざんねん,disappointment
|
|
||||||
市,し,city
|
|
||||||
字,じ,character
|
|
||||||
試合,しあい,"match,game"
|
|
||||||
仕方,しかた,method
|
|
||||||
試験,しけん,examination
|
|
||||||
事故,じこ,accident
|
|
||||||
地震,じしん,earthquake
|
|
||||||
時代,じだい,era
|
|
||||||
下着,したぎ,underwear
|
|
||||||
,しっかり,"firmly,steadily"
|
|
||||||
失敗,しっぱい,"failure,mistake"
|
|
||||||
辞典,じてん,dictionary
|
|
||||||
品物,しなもの,goods
|
|
||||||
,しばらく,little while
|
|
||||||
島,しま,island
|
|
||||||
市民,しみん,citizen
|
|
||||||
事務所,じむしょ,office
|
|
||||||
社会,しゃかい,"society,public"
|
|
||||||
社長,しゃちょう,company president
|
|
||||||
自由,じゆう,freedom
|
|
||||||
習慣,しゅうかん,"custom,manners"
|
|
||||||
住所,じゅうしょ,"an address,a residence"
|
|
||||||
柔道,じゅうどう,judo
|
|
||||||
十分,じゅうぶん,enough
|
|
||||||
趣味,しゅみ,hobby
|
|
||||||
紹介,しょうかい,introduction
|
|
||||||
小学校,しょうがっこう,elementary school
|
|
||||||
小説,しょうせつ,novel
|
|
||||||
将来,しょうらい,"future,prospects"
|
|
||||||
食料品,しょくりょうひん,groceries
|
|
||||||
女性,じょせい,woman
|
|
||||||
知らせる,しらせる,to notify
|
|
||||||
調べる,しらべる,to investigate
|
|
||||||
人口,じんこう,population
|
|
||||||
神社,じんじゃ,Shinto shrine
|
|
||||||
親切,しんせつ,kindness
|
|
||||||
新聞社,しんぶんしゃ,newspaper company
|
|
||||||
水泳,すいえい,swimming
|
|
||||||
水道,すいどう,water supply
|
|
||||||
数学,すうがく,"mathematics,arithmetic"
|
|
||||||
過ぎる,すぎる,to exceed
|
|
||||||
凄い,すごい,terrific
|
|
||||||
進む,すすむ,to make progress
|
|
||||||
,すっかり,completely
|
|
||||||
,すっと,"straight,all of a sudden"
|
|
||||||
捨てる,すてる,to throw away
|
|
||||||
砂,すな,sand
|
|
||||||
滑る,すべる,"to slide,to slip"
|
|
||||||
隅,すみ,"corner,nook"
|
|
||||||
済む,すむ,to finish
|
|
||||||
,すり,pickpocket
|
|
||||||
,すると,then
|
|
||||||
生活,せいかつ・する,to live
|
|
||||||
生産,せいさん・する,to produce
|
|
||||||
政治,せいじ,"politics,government"
|
|
||||||
西洋,せいよう,western countries
|
|
||||||
世界,せかい,the world
|
|
||||||
席,せき,seat
|
|
||||||
説明,せつめい,explanation
|
|
||||||
背中,せなか,back of the body
|
|
||||||
線,せん,line
|
|
||||||
戦争,せんそう,war
|
|
||||||
先輩,せんぱい,senior
|
|
||||||
,そう,really
|
|
||||||
育てる,そだてる,"to rear,to bring up"
|
|
||||||
卒業,そつぎょう,graduation
|
|
||||||
祖父,そふ,grandfather
|
|
||||||
祖母,そぼ,grandmother
|
|
||||||
,それで,because of that
|
|
||||||
,それに,moreover
|
|
||||||
,それほど,to that extent
|
|
||||||
,そろそろ,"gradually,soon"
|
|
||||||
,そんな,that sort of
|
|
||||||
,そんなに,"so much,like that"
|
|
||||||
退院,たいいん・する,to leave hospital
|
|
||||||
大学生,だいがくせい,university student
|
|
||||||
大事,だいじ,"important,valuable,serious matter"
|
|
||||||
大体,だいたい,generally
|
|
||||||
,たいてい,usually
|
|
||||||
大分,だいぶ,greatly
|
|
||||||
台風,たいふう,typhoon
|
|
||||||
倒れる,たおれる,to break down
|
|
||||||
,だから,"so,therefore"
|
|
||||||
確か,たしか,definite
|
|
||||||
足す,たす,to add a number
|
|
||||||
訪ねる,たずねる,to visit
|
|
||||||
尋ねる,たずねる,to ask
|
|
||||||
正しい,ただしい,correct
|
|
||||||
畳,たたみ,Japanese straw mat
|
|
||||||
立てる,たてる,to stand something up
|
|
||||||
建てる,たてる,to build
|
|
||||||
例えば,たとえば,for example
|
|
||||||
棚,たな,shelves
|
|
||||||
楽しみ,たのしみ,joy
|
|
||||||
楽む,たのしむ,to enjoy oneself
|
|
||||||
,たまに,occasionally
|
|
||||||
為,ため,in order to
|
|
||||||
足りる,たりる,to be enough
|
|
||||||
男性,だんせい,male
|
|
||||||
暖房,だんぼう,heating
|
|
||||||
血,ち,blood
|
|
||||||
,チェック・する,to check
|
|
||||||
力,ちから,"strength,power"
|
|
||||||
,ちっとも,not at all (used with a negative verb)
|
|
||||||
,ちゃん,suffix for familiar person
|
|
||||||
注意,ちゅうい,caution
|
|
||||||
中学校,ちゅうがっこう,"junior high school,middle school"
|
|
||||||
注射,ちゅうしゃ,injection
|
|
||||||
駐車場,ちゅうしゃじょう,parking lot
|
|
||||||
地理,ちり,geography
|
|
||||||
捕まえる,つかまえる,to seize
|
|
||||||
付く,つく,to be attached
|
|
||||||
漬ける,つける,"to soak,to pickle"
|
|
||||||
都合,つごう,"circumstances,convenience"
|
|
||||||
伝える,つたえる,to report
|
|
||||||
続く,つづく,to be continued
|
|
||||||
続ける,つづける,to continue
|
|
||||||
包む,つつむ,to wrap
|
|
||||||
妻,つま,my wife
|
|
||||||
,つもり,intention
|
|
||||||
釣る,つる,to fish
|
|
||||||
丁寧,ていねい,polite
|
|
||||||
適当,てきとう,suitability
|
|
||||||
手伝う,てつだう,to assist
|
|
||||||
手袋,てぶくろ,glove
|
|
||||||
寺,てら,temple
|
|
||||||
点,てん,"point,dot"
|
|
||||||
店員,てんいん,shop assistant
|
|
||||||
天気予報,てんきよほう,weather forecast
|
|
||||||
電灯,でんとう,electric light
|
|
||||||
電報,でんぽう,telegram
|
|
||||||
展覧会,てんらんかい,exhibition
|
|
||||||
都,と,metropolitan
|
|
||||||
道具,どうぐ,"tool,means"
|
|
||||||
,とうとう,"finally, after all"
|
|
||||||
動物園,どうぶつえん,zoo
|
|
||||||
遠く,とおく,distant
|
|
||||||
通る,とおる,to go through
|
|
||||||
特に,とくに,"particularly,especially"
|
|
||||||
特別,とくべつ,special
|
|
||||||
,とこや,barber
|
|
||||||
途中,とちゅう,on the way
|
|
||||||
特急,とっきゅう,limited express train (faster than an express train)
|
|
||||||
届ける,とどける,"to send, to deliver, to report"
|
|
||||||
泊まる,とまる,to lodge at
|
|
||||||
止める,とめる,to stop something
|
|
||||||
取り替える,とりかえる,to exchange
|
|
||||||
泥棒,どろぼう,thief
|
|
||||||
,どんどん,more and more
|
|
||||||
直す,なおす,"to fix,to repair"
|
|
||||||
直る,なおる,"to be fixed,to be repaired"
|
|
||||||
治る,なおる,"to be cured,to heal"
|
|
||||||
泣く,なく,to weep
|
|
||||||
無くなる,なくなる,"to disappear,to get lost"
|
|
||||||
亡くなる,なくなる,to die
|
|
||||||
投げる,なげる,to throw or cast away
|
|
||||||
,なさる,(respectful) to do
|
|
||||||
鳴る,なる,to sound
|
|
||||||
,なるべく,as much as possible
|
|
||||||
,なるほど,now I understand
|
|
||||||
慣れる,なれる,to grow accustomed to
|
|
||||||
苦い,にがい,bitter
|
|
||||||
二階建て,にかいだて,two storied
|
|
||||||
逃げる,にげる,to escape
|
|
||||||
日記,にっき,journal
|
|
||||||
入院,にゅういん・する,"to hospitalise, hospitalisation"
|
|
||||||
入学,にゅうがく・する,to enter school or university
|
|
||||||
似る,にる,to be similar
|
|
||||||
人形,にんぎょう,"doll, figure"
|
|
||||||
盗む,ぬすむ,to steal
|
|
||||||
塗る,ぬる,"to paint, to colour, to plaster"
|
|
||||||
,ぬれる,to get wet
|
|
||||||
,ねだん,price
|
|
||||||
熱,ねつ,fever
|
|
||||||
寝坊,ねぼう,sleeping in late
|
|
||||||
眠い,ねむい,sleepy
|
|
||||||
眠る,ねむる,to sleep
|
|
||||||
残る,のこる,to remain
|
|
||||||
乗り換える,のりかえる,to change between buses or trains
|
|
||||||
乗り物,のりもの,vehicle
|
|
||||||
葉,は,leaf
|
|
||||||
場合,ばあい,situation
|
|
||||||
倍,ばい,double
|
|
||||||
拝見,はいけん・する,(humble) to look at
|
|
||||||
歯医者,はいしゃ,dentist
|
|
||||||
運ぶ,はこぶ,to transport
|
|
||||||
始める,はじめる,to begin
|
|
||||||
場所,ばしょ,location
|
|
||||||
,はず,it should be so
|
|
||||||
恥ずかしい,はずかしい,embarrassed
|
|
||||||
発音,はつおん,pronunciation
|
|
||||||
,はっきり,clearly
|
|
||||||
花見,はなみ,cherry-blossom viewing
|
|
||||||
林,はやし,"woods,forester"
|
|
||||||
払う,はらう,to pay
|
|
||||||
番組,ばんぐみ,television or radio program
|
|
||||||
反対,はんたい,opposition
|
|
||||||
日,ひ,"day, sun"
|
|
||||||
火,ひ,fire
|
|
||||||
冷える,ひえる,to grow cold
|
|
||||||
光,ひかり,light
|
|
||||||
光る,ひかる,"to shine,to glitter"
|
|
||||||
引き出し,ひきだし,"drawer,drawing out"
|
|
||||||
,ひきだす,to withdraw
|
|
||||||
,ひげ,beard
|
|
||||||
飛行場,ひこうじょう,airport
|
|
||||||
久しぶり,ひさしぶり,after a long time
|
|
||||||
美術館,びじゅつかん,art gallery
|
|
||||||
非常に,ひじょうに,extremely
|
|
||||||
引っ越す,ひっこす,to move house
|
|
||||||
必要,ひつよう,necessary
|
|
||||||
,ひどい,awful
|
|
||||||
開く,ひらく,to open an event
|
|
||||||
昼間,ひるま,"daytime,during the day"
|
|
||||||
昼休み,ひるやすみ,noon break
|
|
||||||
拾う,ひろう,"to pick up,to gather"
|
|
||||||
増える,ふえる,to increase
|
|
||||||
深い,ふかい,deep
|
|
||||||
複雑,ふくざつ,"complexity,complication"
|
|
||||||
復習,ふくしゅう,revision
|
|
||||||
部長,ぶちょう,head of a section
|
|
||||||
普通,ふつう,"usually, or a train that stops at every station"
|
|
||||||
,ぶどう,grapes
|
|
||||||
太る,ふとる,to become fat
|
|
||||||
布団,ふとん,"Japanese bedding, futon"
|
|
||||||
舟,ふね,ship
|
|
||||||
不便,ふべん,inconvenience
|
|
||||||
踏む,ふむ,to step on
|
|
||||||
降り出す,ふりだす,to start to rain
|
|
||||||
文化,ぶんか,culture
|
|
||||||
文学,ぶんがく,literature
|
|
||||||
文法,ぶんぽう,grammar
|
|
||||||
別,べつ,different
|
|
||||||
変,へん,strange
|
|
||||||
返事,へんじ,reply
|
|
||||||
貿易,ぼうえき,trade
|
|
||||||
法律,ほうりつ,law
|
|
||||||
僕,ぼく,I (used by males)
|
|
||||||
星,ほし,star
|
|
||||||
,ほとんど,mostly
|
|
||||||
,ほめる,to praise
|
|
||||||
翻訳,ほんやく,translation
|
|
||||||
参る,まいる,"(humble) to go,to come"
|
|
||||||
負ける,まける,to lose
|
|
||||||
,または,"or,otherwise"
|
|
||||||
間違える,まちがえる,to make a mistake
|
|
||||||
間に合う,まにあう,to be in time for
|
|
||||||
周り,まわり,surroundings
|
|
||||||
回る,まわる,to go around
|
|
||||||
漫画,まんが,comic
|
|
||||||
真中,まんなか,middle
|
|
||||||
見える,みえる,to be in sight
|
|
||||||
湖,みずうみ,lake
|
|
||||||
味噌,みそ,"miso, soybean paste"
|
|
||||||
見つかる,みつかる,to be discovered
|
|
||||||
見つける,みつける,to discover
|
|
||||||
皆,みな,everybody
|
|
||||||
港,みなと,harbour
|
|
||||||
向かう,むかう,to face
|
|
||||||
迎える,むかえる,to go out to meet
|
|
||||||
昔,むかし,"old times, old days, long ago, formerly"
|
|
||||||
虫,むし,insect
|
|
||||||
息子,むすこ,(humble) son
|
|
||||||
娘,むすめ,(humble) daughter
|
|
||||||
無理,むり,impossible
|
|
||||||
召し上がる,めしあがる,(polite) to eat
|
|
||||||
珍しい,めずらしい,rare
|
|
||||||
申し上げる,もうしあげる,"(humble) to say,to tell"
|
|
||||||
申す,もうす,"(humble) to be called,to say"
|
|
||||||
,もうすぐ,soon
|
|
||||||
,もし,if
|
|
||||||
戻る,もどる,to turn back
|
|
||||||
木綿,もめん,cotton
|
|
||||||
森,もり,forest
|
|
||||||
焼く,やく,"to bake,to grill"
|
|
||||||
約束,やくそく,promise
|
|
||||||
役に立つ,やくにたつ,to be helpful
|
|
||||||
焼ける,やける,"to burn,to be roasted"
|
|
||||||
優しい,やさしい,kind
|
|
||||||
痩せる,やせる,to become thin
|
|
||||||
,やっと,at last
|
|
||||||
止む,やむ,to stop
|
|
||||||
止める,やめる,to stop
|
|
||||||
柔らかい,やわらかい,soft
|
|
||||||
湯,ゆ,hot water
|
|
||||||
指,ゆび,finger
|
|
||||||
指輪,ゆびわ,a ring
|
|
||||||
夢,ゆめ,dream
|
|
||||||
揺れる,ゆれる,"to shake,to sway"
|
|
||||||
用,よう,use
|
|
||||||
用意,ようい,preparation
|
|
||||||
用事,ようじ,things to do
|
|
||||||
汚れる,よごれる,to get dirty
|
|
||||||
予習,よしゅう,preparation for a lesson
|
|
||||||
予定,よてい,arrangement
|
|
||||||
予約,よやく,reservation
|
|
||||||
寄る,よる,to visit
|
|
||||||
喜ぶ,よろこぶ,to be delighted
|
|
||||||
理由,りゆう,reason
|
|
||||||
利用,りよう,utilization
|
|
||||||
両方,りょうほう,both sides
|
|
||||||
旅館,りょかん,Japanese hotel
|
|
||||||
留守,るす,absence
|
|
||||||
冷房,れいぼう,air conditioning
|
|
||||||
歴史,れきし,history
|
|
||||||
連絡,れんらく,contact
|
|
||||||
沸かす,わかす,"to boil,to heat"
|
|
||||||
別れる,わかれる,to separate
|
|
||||||
沸く,わく,"to boil, to grow hot,to get excited"
|
|
||||||
訳,わけ,"meaning,reason"
|
|
||||||
忘れ物,わすれもの,lost article
|
|
||||||
笑う,わらう,"to laugh,to smile"
|
|
||||||
割合,わりあい,"rate,ratio,percentage"
|
|
||||||
割れる,われる,to break
|
|
||||||
,アクセサリー,accessory
|
|
||||||
,アジア,Asia
|
|
||||||
,アナウンサー,announcer
|
|
||||||
,アフリカ,Africa
|
|
||||||
,アメリカ,America
|
|
||||||
,アルコール,alcohol
|
|
||||||
,アルバイト,part-time job
|
|
||||||
,エスカレーター,escalator
|
|
||||||
,オートバイ,motorcycle
|
|
||||||
,カーテン,curtain
|
|
||||||
,ガス,gas
|
|
||||||
,ガソリン,petrol
|
|
||||||
,ガソリンスタンド,petrol station
|
|
||||||
,ガラス,a glass pane
|
|
||||||
,ケーキ,cake
|
|
||||||
消しゴム,けしゴム,"eraser, rubber"
|
|
||||||
,コンサート,concert
|
|
||||||
,コンピューター,computer
|
|
||||||
,サラダ,salad
|
|
||||||
,サンダル,sandal
|
|
||||||
,サンドイッチ,sandwich
|
|
||||||
,ジャム,jam
|
|
||||||
,スーツ,suit
|
|
||||||
,スーツケース,suitcase
|
|
||||||
,スクリーン,screen
|
|
||||||
,ステーキ,steak
|
|
||||||
,ステレオ,stereo
|
|
||||||
,ソフト,soft
|
|
||||||
,タイプ,"type,style"
|
|
||||||
,チェック・する,to check
|
|
||||||
,テキスト,"text,text book"
|
|
||||||
,テニス,tennis
|
|
||||||
,パート,part time
|
|
||||||
,パソコン,personal computer
|
|
||||||
,ハンドバッグ,handbag
|
|
||||||
,ピアノ,piano
|
|
||||||
,ビル,building or bill
|
|
||||||
,ファックス,fax
|
|
||||||
,プレゼント,present
|
|
||||||
,ベル,bell
|
|
||||||
,レジ,register
|
|
||||||
,レポート/リポート,report
|
|
||||||
,ワープロ,word processor
|
|
||||||
|
@@ -1,669 +0,0 @@
|
|||||||
会う,あう,to meet
|
|
||||||
青,あお,blue
|
|
||||||
青い,あおい,blue
|
|
||||||
赤,あか,red
|
|
||||||
赤い,あかい,red
|
|
||||||
明い,あかるい,bright
|
|
||||||
秋,あき,autumn
|
|
||||||
開く,あく,"to open,to become open"
|
|
||||||
開ける,あける,to open
|
|
||||||
上げる,あげる,to give
|
|
||||||
朝,あさ,morning
|
|
||||||
朝御飯,あさごはん,breakfast
|
|
||||||
,あさって,day after tomorrow
|
|
||||||
足,あし,"foot,leg"
|
|
||||||
明日,あした,tomorrow
|
|
||||||
,あそこ,over there
|
|
||||||
遊ぶ,あそぶ,"to play,to make a visit"
|
|
||||||
暖かい,あたたかい,warm
|
|
||||||
頭,あたま,head
|
|
||||||
新しい,あたらしい,new
|
|
||||||
,あちら,there
|
|
||||||
暑い,あつい,hot
|
|
||||||
熱い,あつい,hot to the touch
|
|
||||||
厚い,あつい,"kind, deep, thick"
|
|
||||||
,あっち,over there
|
|
||||||
後,あと,afterwards
|
|
||||||
,あなた,you
|
|
||||||
兄,あに,(humble) older brother
|
|
||||||
姉,あね,(humble) older sister
|
|
||||||
,あの,that over there
|
|
||||||
,あの,um...
|
|
||||||
,アパート,apartment
|
|
||||||
,あびる,"to bathe,to shower"
|
|
||||||
危ない,あぶない,dangerous
|
|
||||||
甘い,あまい,sweet
|
|
||||||
,あまり,not very
|
|
||||||
雨,あめ,rain
|
|
||||||
飴,あめ,candy
|
|
||||||
洗う,あらう,to wash
|
|
||||||
,ある,"to be,to have (used for inanimate objects)"
|
|
||||||
歩く,あるく,to walk
|
|
||||||
,あれ,that
|
|
||||||
,いい/よい,good
|
|
||||||
,いいえ,no
|
|
||||||
言う,いう,to say
|
|
||||||
家,いえ,house
|
|
||||||
,いかが,how
|
|
||||||
行く,いく,to go
|
|
||||||
,いくつ,"how many?,how old?"
|
|
||||||
,いくら,how much?
|
|
||||||
池,いけ,pond
|
|
||||||
医者,いしゃ,medical doctor
|
|
||||||
,いす,chair
|
|
||||||
忙しい,いそがしい,"busy,irritated"
|
|
||||||
痛い,いたい,painful
|
|
||||||
一,いち,one
|
|
||||||
一日,いちにち,"(1) one day, (2) first of month"
|
|
||||||
,いちばん,"best,first"
|
|
||||||
,いつ,when
|
|
||||||
五日,いつか,"five days, fifth day"
|
|
||||||
一緒,いっしょ,together
|
|
||||||
五つ,いつつ,five
|
|
||||||
,いつも,always
|
|
||||||
犬,いぬ,dog
|
|
||||||
今,いま,now
|
|
||||||
意味,いみ,meaning
|
|
||||||
妹,いもうと,(humble) younger sister
|
|
||||||
嫌,いや,unpleasant
|
|
||||||
入口,いりぐち,entrance
|
|
||||||
居る,いる,"to be, to have (used for people and animals)"
|
|
||||||
要る,いる,to need
|
|
||||||
入れる,いれる,to put in
|
|
||||||
色,いろ,colour
|
|
||||||
,いろいろ,various
|
|
||||||
上,うえ,on top of
|
|
||||||
後ろ,うしろ,behind
|
|
||||||
薄い,うすい,"thin,weak"
|
|
||||||
歌,うた,song
|
|
||||||
歌う,うたう,to sing
|
|
||||||
生まれる,うまれる,to be born
|
|
||||||
海,うみ,sea
|
|
||||||
売る,うる,to sell
|
|
||||||
煩い,うるさい,"noisy,annoying"
|
|
||||||
上着,うわぎ,jacket
|
|
||||||
絵,え,picture
|
|
||||||
映画,えいが,movie
|
|
||||||
映画館,えいがかん,cinema
|
|
||||||
英語,えいご,English language
|
|
||||||
,ええ,yes
|
|
||||||
駅,えき,station
|
|
||||||
,エレベーター,elevator
|
|
||||||
鉛筆,えんぴつ,pencil
|
|
||||||
,おいしい,delicious
|
|
||||||
多い,おおい,many
|
|
||||||
大きい,おおきい,big
|
|
||||||
大きな,おおきな,big
|
|
||||||
大勢,おおぜい,great number of people
|
|
||||||
お母さん,おかあさん,(honorable) mother
|
|
||||||
お菓子,おかし,"sweets, candy"
|
|
||||||
お金,おかね,money
|
|
||||||
起きる,おきる,to get up
|
|
||||||
置く,おく,to put
|
|
||||||
奥さん,おくさん,(honorable) wife
|
|
||||||
お酒,おさけ,"alcohol, rice wine"
|
|
||||||
お皿,おさら,"plate, dish"
|
|
||||||
伯父/叔父,おじいさん,"grandfather,male senior citizen"
|
|
||||||
教える,おしえる,"to teach,to tell"
|
|
||||||
伯父/叔父,おじさん,"uncle,middle aged gentleman"
|
|
||||||
押す,おす,"to push, to stamp something"
|
|
||||||
遅い,おそい,"late,slow"
|
|
||||||
お茶,おちゃ,green tea
|
|
||||||
お手洗い,おてあらい,bathroom
|
|
||||||
お父さん,おとうさん,(honorable) father
|
|
||||||
弟,おとうと,younger brother
|
|
||||||
男,おとこ,man
|
|
||||||
男の子,おとこのこ,boy
|
|
||||||
一昨日,おととい,day before yesterday
|
|
||||||
一昨年,おととし,year before last
|
|
||||||
大人,おとな,adult
|
|
||||||
,おなか,stomach
|
|
||||||
同じ,おなじ,same
|
|
||||||
お兄さん,おにいさん,(honorable) older brother
|
|
||||||
お姉さん,おねえさん,(honorable) older sister
|
|
||||||
,おばあさん,"grandmother,female senior-citizen"
|
|
||||||
伯母さん/叔母さん,おばさん,aunt
|
|
||||||
お風呂,おふろ,bath
|
|
||||||
お弁当,おべんとう,boxed lunch
|
|
||||||
覚える,おぼえる,to remember
|
|
||||||
,おまわりさん,friendly term for policeman
|
|
||||||
重い,おもい,heavy
|
|
||||||
,おもしろい,interesting
|
|
||||||
泳ぐ,およぐ,to swim
|
|
||||||
降りる,おりる,"to get off, to descend"
|
|
||||||
終る,おわる,to finish
|
|
||||||
音楽,おんがく,music
|
|
||||||
女,おんな,woman
|
|
||||||
女の子,おんなのこ,girl
|
|
||||||
外国,がいこく,foreign country
|
|
||||||
外国人,がいこくじん,foreigner
|
|
||||||
会社,かいしゃ,company
|
|
||||||
階段,かいだん,stairs
|
|
||||||
買い物,かいもの,shopping
|
|
||||||
買う,かう,to buy
|
|
||||||
返す,かえす,to return something
|
|
||||||
帰る,かえる,to go back
|
|
||||||
,かかる,to take time or money
|
|
||||||
,かぎ,key
|
|
||||||
書く,かく,to write
|
|
||||||
学生,がくせい,student
|
|
||||||
,かける,to call by phone
|
|
||||||
傘,かさ,umbrella
|
|
||||||
貸す,かす,to lend
|
|
||||||
風,かぜ,wind
|
|
||||||
風邪,かぜ,a cold
|
|
||||||
家族,かぞく,family
|
|
||||||
方,かた,"person, way of doing"
|
|
||||||
学校,がっこう,school
|
|
||||||
,カップ,cup
|
|
||||||
家庭,かてい,household
|
|
||||||
角,かど,a corner
|
|
||||||
,かばん,"bag,basket"
|
|
||||||
花瓶,かびん,a vase
|
|
||||||
紙,かみ,paper
|
|
||||||
,カメラ,camera
|
|
||||||
火曜日,かようび,Tuesday
|
|
||||||
辛い,からい,spicy
|
|
||||||
体,からだ,body
|
|
||||||
借りる,かりる,to borrow
|
|
||||||
軽い,かるい,light
|
|
||||||
,カレー,curry
|
|
||||||
,カレンダー,calendar
|
|
||||||
川/河,かわ,river
|
|
||||||
,かわいい,cute
|
|
||||||
漢字,かんじ,Chinese character
|
|
||||||
木,き,"tree,wood"
|
|
||||||
黄色,きいろ,yellow
|
|
||||||
黄色い,きいろい,yellow
|
|
||||||
消える,きえる,to disappear
|
|
||||||
聞く,きく,"to hear,to listen to,to ask"
|
|
||||||
北,きた,north
|
|
||||||
,ギター,guitar
|
|
||||||
汚い,きたない,dirty
|
|
||||||
喫茶店,きっさてん,coffee lounge
|
|
||||||
切手,きって,postage stamp
|
|
||||||
切符,きっぷ,ticket
|
|
||||||
昨日,きのう,yesterday
|
|
||||||
九,きゅう / く,nine
|
|
||||||
牛肉,ぎゅうにく,beef
|
|
||||||
牛乳,ぎゅうにゅう,milk
|
|
||||||
今日,きょう,today
|
|
||||||
教室,きょうしつ,classroom
|
|
||||||
兄弟,きょうだい,(humble) siblings
|
|
||||||
去年,きょねん,last year
|
|
||||||
嫌い,きらい,hate
|
|
||||||
切る,きる,to cut
|
|
||||||
着る,きる,to put on from the shoulders down
|
|
||||||
,きれい,"pretty,clean"
|
|
||||||
,キロ/キログラム,kilogram
|
|
||||||
,キロ/キロメートル,kilometre
|
|
||||||
銀行,ぎんこう,bank
|
|
||||||
金曜日,きんようび,Friday
|
|
||||||
薬,くすり,medicine
|
|
||||||
,ください,please
|
|
||||||
果物,くだもの,fruit
|
|
||||||
口,くち,"mouth,opening"
|
|
||||||
靴,くつ,shoes
|
|
||||||
靴下,くつした,socks
|
|
||||||
国,くに,country
|
|
||||||
曇り,くもり,cloudy weather
|
|
||||||
曇る,くもる,"to become cloudy,to become dim"
|
|
||||||
暗い,くらい,gloomy
|
|
||||||
,クラス,class
|
|
||||||
,グラム,gram
|
|
||||||
来る,くる,to come
|
|
||||||
車,くるま,"car,vehicle"
|
|
||||||
黒,くろ,black
|
|
||||||
黒い,くろい,black
|
|
||||||
警官,けいかん,policeman
|
|
||||||
今朝,けさ,this morning
|
|
||||||
消す,けす,"to erase,to turn off power"
|
|
||||||
結構,けっこう,"splendid,enough"
|
|
||||||
結婚,けっこん,marriage
|
|
||||||
月曜日,げつようび,Monday
|
|
||||||
玄関,げんかん,entry hall
|
|
||||||
元気,げんき,"health, vitality"
|
|
||||||
五,ご,five
|
|
||||||
公園,こうえん,park
|
|
||||||
交差点,こうさてん,intersection
|
|
||||||
紅茶,こうちゃ,black tea
|
|
||||||
交番,こうばん,police box
|
|
||||||
声,こえ,voice
|
|
||||||
,コート,"coat,tennis court"
|
|
||||||
,コーヒー,coffee
|
|
||||||
,ここ,here
|
|
||||||
午後,ごご,afternoon
|
|
||||||
九日,ここのか,"nine days, ninth day"
|
|
||||||
九つ,ここのつ,nine
|
|
||||||
午前,ごぜん,morning
|
|
||||||
答える,こたえる,to answer
|
|
||||||
,こちら,this person or way
|
|
||||||
,こっち,this person or way
|
|
||||||
,コップ,a glass
|
|
||||||
今年,ことし,this year
|
|
||||||
言葉,ことば,"word,language"
|
|
||||||
子供,こども,child
|
|
||||||
,この,this
|
|
||||||
御飯,ごはん,"cooked rice,meal"
|
|
||||||
,コピーする,to copy
|
|
||||||
困る,こまる,to be worried
|
|
||||||
,これ,this
|
|
||||||
今月,こんげつ,this month
|
|
||||||
今週,こんしゅう,this week
|
|
||||||
,こんな,such
|
|
||||||
今晩,こんばん,this evening
|
|
||||||
,さあ,well…
|
|
||||||
財布,さいふ,wallet
|
|
||||||
魚,さかな,fish
|
|
||||||
先,さき,"the future,previous"
|
|
||||||
咲く,さく,to bloom
|
|
||||||
作文,さくぶん,"composition,writing"
|
|
||||||
差す,さす,"to stretch out hands,to raise an umbrella"
|
|
||||||
雑誌,ざっし,magazine
|
|
||||||
砂糖,さとう,sugar
|
|
||||||
寒い,さむい,cold
|
|
||||||
さ来年,さらいねん,year after next
|
|
||||||
三,さん,three
|
|
||||||
散歩,さんぽする,to stroll
|
|
||||||
四,し / よん,four
|
|
||||||
塩,しお,salt
|
|
||||||
,しかし,however
|
|
||||||
時間,じかん,time
|
|
||||||
仕事,しごと,job
|
|
||||||
辞書,じしょ,dictionary
|
|
||||||
静か,しずか,quiet
|
|
||||||
下,した,below
|
|
||||||
七,しち / なな,seven
|
|
||||||
質問,しつもん,question
|
|
||||||
自転車,じてんしゃ,bicycle
|
|
||||||
自動車,じどうしゃ,automobile
|
|
||||||
死ぬ,しぬ,to die
|
|
||||||
字引,じびき,dictionary
|
|
||||||
自分,じぶん,oneself
|
|
||||||
閉まる,しまる,"to close,to be closed"
|
|
||||||
閉める,しめる,to close something
|
|
||||||
締める,しめる,to tie
|
|
||||||
,じゃ/じゃあ,well then…
|
|
||||||
写真,しゃしん,photograph
|
|
||||||
,シャツ,shirt
|
|
||||||
,シャワー,shower
|
|
||||||
十,じゅう とお,ten
|
|
||||||
授業,じゅぎょう,"lesson,class work"
|
|
||||||
宿題,しゅくだい,homework
|
|
||||||
上手,じょうず,skillful
|
|
||||||
丈夫,じょうぶ,"strong,durable"
|
|
||||||
,しょうゆ,soy sauce
|
|
||||||
食堂,しょくどう,dining hall
|
|
||||||
知る,しる,to know
|
|
||||||
白,しろ,white
|
|
||||||
白い,しろい,white
|
|
||||||
新聞,しんぶん,newspaper
|
|
||||||
水曜日,すいようび,Wednesday
|
|
||||||
吸う,すう,"to smoke,to suck"
|
|
||||||
,スカート,skirt
|
|
||||||
好き,すき,likeable
|
|
||||||
少ない,すくない,a few
|
|
||||||
,すぐに,instantly
|
|
||||||
少し,すこし,few
|
|
||||||
涼しい,すずしい,refreshing
|
|
||||||
,ストーブ,heater
|
|
||||||
,スプーン,spoon
|
|
||||||
,スポーツ,sport
|
|
||||||
,ズボン,trousers
|
|
||||||
住む,すむ,to live in
|
|
||||||
,スリッパ,slippers
|
|
||||||
,する,to do
|
|
||||||
座る,すわる,to sit
|
|
||||||
背,せ,"height,stature"
|
|
||||||
生徒,せいと,pupil
|
|
||||||
,セーター,"sweater,jumper"
|
|
||||||
,せっけん,soap
|
|
||||||
背広,せびろ,business suit
|
|
||||||
狭い,せまい,narrow
|
|
||||||
,ゼロ,zero
|
|
||||||
千,せん,thousand
|
|
||||||
先月,せんげつ,last month
|
|
||||||
先週,せんしゅう,last week
|
|
||||||
先生,せんせい,"teacher,doctor"
|
|
||||||
洗濯,せんたく,washing
|
|
||||||
全部,ぜんぶ,all
|
|
||||||
掃除,そうじする,"to clean, to sweep"
|
|
||||||
,そうして/そして,and
|
|
||||||
,そこ,that place
|
|
||||||
,そちら,over there
|
|
||||||
,そっち,over there
|
|
||||||
外,そと,outside
|
|
||||||
,その,that
|
|
||||||
,そば,"near,beside"
|
|
||||||
空,そら,sky
|
|
||||||
,それ,that
|
|
||||||
,それから,after that
|
|
||||||
,それでは,in that situation
|
|
||||||
大学,だいがく,university
|
|
||||||
大使館,たいしかん,embassy
|
|
||||||
大丈夫,だいじょうぶ,all right
|
|
||||||
大好き,だいすき,to be very likeable
|
|
||||||
大切,たいせつ,important
|
|
||||||
台所,だいどころ,kitchen
|
|
||||||
,たいへん,very
|
|
||||||
,たいへん,difficult situation
|
|
||||||
高い,たかい,"tall, expensive"
|
|
||||||
,たくさん,many
|
|
||||||
,タクシー,taxi
|
|
||||||
出す,だす,to put out
|
|
||||||
立つ,たつ,to stand
|
|
||||||
,たて,"length,height"
|
|
||||||
建物,たてもの,building
|
|
||||||
楽しい,たのしい,enjoyable
|
|
||||||
頼む,たのむ,to ask
|
|
||||||
,たばこ,"tobacco,cigarettes"
|
|
||||||
,たぶん,probably
|
|
||||||
食べ物,たべもの,food
|
|
||||||
食べる,たべる,to eat
|
|
||||||
卵,たまご,egg
|
|
||||||
誰,だれ,who
|
|
||||||
誰,だれか,somebody
|
|
||||||
誕生日,たんじょうび,birthday
|
|
||||||
,だんだん,gradually
|
|
||||||
小さい,ちいさい,little
|
|
||||||
小さな,ちいさな,little
|
|
||||||
近い,ちかい,near
|
|
||||||
違う,ちがう,to differ
|
|
||||||
近く,ちかく,near
|
|
||||||
地下鉄,ちかてつ,underground train
|
|
||||||
地図,ちず,map
|
|
||||||
茶色,ちゃいろ,brown
|
|
||||||
,ちゃわん,rice bowl
|
|
||||||
,ちょうど,exactly
|
|
||||||
,ちょっと,somewhat
|
|
||||||
一日,ついたち,first of month
|
|
||||||
使う,つかう,to use
|
|
||||||
疲れる,つかれる,to get tired
|
|
||||||
次,つぎ,next
|
|
||||||
着く,つく,to arrive at
|
|
||||||
机,つくえ,desk
|
|
||||||
作る,つくる,to make
|
|
||||||
,つける,to turn on
|
|
||||||
勤める,つとめる,to work for someone
|
|
||||||
,つまらない,boring
|
|
||||||
冷たい,つめたい,cold to the touch
|
|
||||||
強い,つよい,powerful
|
|
||||||
手,て,hand
|
|
||||||
,テープ,tape
|
|
||||||
,テーブル,table
|
|
||||||
,テープレコーダー,tape recorder
|
|
||||||
出かける,でかける,to go out
|
|
||||||
手紙,てがみ,letter
|
|
||||||
,できる,to be able to
|
|
||||||
出口,でぐち,exit
|
|
||||||
,テスト,test
|
|
||||||
,では,with that...
|
|
||||||
,デパート,department store
|
|
||||||
,でも,but
|
|
||||||
出る,でる,"to appear,to leave"
|
|
||||||
,テレビ,television
|
|
||||||
天気,てんき,weather
|
|
||||||
電気,でんき,"electricity,electric light"
|
|
||||||
電車,でんしゃ,electric train
|
|
||||||
電話,でんわ,telephone
|
|
||||||
戸,と,Japanese style door
|
|
||||||
,ドア,Western style door
|
|
||||||
,トイレ,toilet
|
|
||||||
,どう,"how,in what way"
|
|
||||||
,どうして,for what reason
|
|
||||||
,どうぞ,please
|
|
||||||
動物,どうぶつ,animal
|
|
||||||
,どうも,thanks
|
|
||||||
遠い,とおい,far
|
|
||||||
十日,とおか,"ten days,the tenth day"
|
|
||||||
時々,ときどき,sometimes
|
|
||||||
時計,とけい,"watch,clock"
|
|
||||||
,どこ,where
|
|
||||||
所,ところ,place
|
|
||||||
年,とし,year
|
|
||||||
図書館,としょかん,library
|
|
||||||
,どちら,which of two
|
|
||||||
,どっち,which
|
|
||||||
,とても,very
|
|
||||||
,どなた,who
|
|
||||||
隣,となり,next door to
|
|
||||||
,どの,which
|
|
||||||
飛ぶ,とぶ,"to fly,to hop"
|
|
||||||
止まる,とまる,to come to a halt
|
|
||||||
友達,ともだち,friend
|
|
||||||
土曜日,どようび,Saturday
|
|
||||||
鳥,とり,bird
|
|
||||||
とり肉,とりにく,chicken meat
|
|
||||||
取る,とる,to take something
|
|
||||||
撮る,とる,to take a photo or record a film
|
|
||||||
,どれ,which (of three or more)
|
|
||||||
,ナイフ,knife
|
|
||||||
中,なか,middle
|
|
||||||
長い,ながい,long
|
|
||||||
鳴く,なく,"animal noise. to chirp, roar or croak etc."
|
|
||||||
無くす,なくす,to lose something
|
|
||||||
,なぜ,why
|
|
||||||
夏,なつ,summer
|
|
||||||
夏休み,なつやすみ,summer holiday
|
|
||||||
,など,et cetera
|
|
||||||
七つ,ななつ,seven
|
|
||||||
七日,なのか,"seven days,the seventh day"
|
|
||||||
名前,なまえ,name
|
|
||||||
習う,ならう,to learn
|
|
||||||
並ぶ,ならぶ,"to line up,to stand in a line"
|
|
||||||
並べる,ならべる,"to line up,to set up"
|
|
||||||
,なる,to become
|
|
||||||
何,なん/なに,what
|
|
||||||
二,に,two
|
|
||||||
賑やか,にぎやか,"bustling,busy"
|
|
||||||
肉,にく,meat
|
|
||||||
西,にし,west
|
|
||||||
日曜日,にちようび,Sunday
|
|
||||||
荷物,にもつ,luggage
|
|
||||||
,ニュース,news
|
|
||||||
庭,にわ,garden
|
|
||||||
脱ぐ,ぬぐ,to take off clothes
|
|
||||||
温い,ぬるい,luke warm
|
|
||||||
,ネクタイ,"tie,necktie"
|
|
||||||
猫,ねこ,cat
|
|
||||||
寝る,ねる,"to go to bed,to sleep"
|
|
||||||
,ノート,"notebook,exercise book"
|
|
||||||
登る,のぼる,to climb
|
|
||||||
飲み物,のみもの,a drink
|
|
||||||
飲む,のむ,to drink
|
|
||||||
乗る,のる,"to get on,to ride"
|
|
||||||
歯,は,tooth
|
|
||||||
,パーティー,party
|
|
||||||
,はい,yes
|
|
||||||
灰皿,はいざら,ashtray
|
|
||||||
入る,はいる,"to enter,to contain"
|
|
||||||
葉書,はがき,postcard
|
|
||||||
,はく,"to wear,to put on trousers"
|
|
||||||
箱,はこ,box
|
|
||||||
橋,はし,bridge
|
|
||||||
,はし,chopsticks
|
|
||||||
始まる,はじまる,to begin
|
|
||||||
初め/始め,はじめ,beginning
|
|
||||||
初めて,はじめて,for the first time
|
|
||||||
走る,はしる,to run
|
|
||||||
,バス,bus
|
|
||||||
,バター,butter
|
|
||||||
二十歳,はたち,"20 years old,20th year"
|
|
||||||
働く,はたらく,to work
|
|
||||||
八,はち,eight
|
|
||||||
二十日,はつか,"twenty days,twentieth"
|
|
||||||
花,はな,flower
|
|
||||||
鼻,はな,nose
|
|
||||||
話,はなし,"talk,story"
|
|
||||||
話す,はなす,to speak
|
|
||||||
早い,はやい,early
|
|
||||||
速い,はやい,quick
|
|
||||||
春,はる,spring
|
|
||||||
貼る,はる,to stick
|
|
||||||
晴れ,はれ,clear weather
|
|
||||||
晴れる,はれる,to be sunny
|
|
||||||
半,はん,half
|
|
||||||
晩,ばん,evening
|
|
||||||
,パン,bread
|
|
||||||
,ハンカチ,handkerchief
|
|
||||||
番号,ばんごう,number
|
|
||||||
晩御飯,ばんごはん,evening meal
|
|
||||||
半分,はんぶん,half minute
|
|
||||||
東,ひがし,east
|
|
||||||
引く,ひく,to pull
|
|
||||||
弾く,ひく,"to play an instrument with strings, including piano"
|
|
||||||
低い,ひくい,"short,low"
|
|
||||||
飛行機,ひこうき,aeroplane
|
|
||||||
左,ひだり,left hand side
|
|
||||||
人,ひと,person
|
|
||||||
一つ,ひとつ,one
|
|
||||||
一月,ひとつき,one month
|
|
||||||
一人,ひとり,one person
|
|
||||||
暇,ひま,free time
|
|
||||||
百,ひゃく,hundred
|
|
||||||
病院,びょういん,hospital
|
|
||||||
病気,びょうき,illness
|
|
||||||
昼,ひる,"noon, daytime"
|
|
||||||
昼御飯,ひるごはん,midday meal
|
|
||||||
広い,ひろい,"spacious,wide"
|
|
||||||
,フィルム,roll of film
|
|
||||||
封筒,ふうとう,envelope
|
|
||||||
,プール,swimming pool
|
|
||||||
,フォーク,fork
|
|
||||||
吹く,ふく,to blow
|
|
||||||
服,ふく,clothes
|
|
||||||
二つ,ふたつ,two
|
|
||||||
豚肉,ぶたにく,pork
|
|
||||||
二人,ふたり,two people
|
|
||||||
二日,ふつか,"two days, second day of the month"
|
|
||||||
太い,ふとい,fat
|
|
||||||
冬,ふゆ,winter
|
|
||||||
降る,ふる,"to fall, e.g. rain or snow"
|
|
||||||
古い,ふるい,old (not used for people)
|
|
||||||
,ふろ,bath
|
|
||||||
文章,ぶんしょう,"sentence,text"
|
|
||||||
,ページ,page
|
|
||||||
下手,へた,unskillful
|
|
||||||
,ベッド,bed
|
|
||||||
,ペット,pet
|
|
||||||
部屋,へや,room
|
|
||||||
辺,へん,area
|
|
||||||
,ペン,pen
|
|
||||||
勉強,べんきょうする,to study
|
|
||||||
便利,べんり,"useful, convenient"
|
|
||||||
帽子,ぼうし,hat
|
|
||||||
,ボールペン,ball-point pen
|
|
||||||
,ほか,"other, the rest"
|
|
||||||
,ポケット,pocket
|
|
||||||
欲しい,ほしい,want
|
|
||||||
,ポスト,post
|
|
||||||
細い,ほそい,thin
|
|
||||||
,ボタン,button
|
|
||||||
,ホテル,hotel
|
|
||||||
本,ほん,book
|
|
||||||
本棚,ほんだな,bookshelves
|
|
||||||
,ほんとう,truth
|
|
||||||
毎朝,まいあさ,every morning
|
|
||||||
毎月,まいげつ/まいつき,every month
|
|
||||||
毎週,まいしゅう,every week
|
|
||||||
毎日,まいにち,every day
|
|
||||||
毎年,まいねん/まいとし,every year
|
|
||||||
毎晩,まいばん,every night
|
|
||||||
前,まえ,before
|
|
||||||
曲る,まがる,"to turn,to bend"
|
|
||||||
,まずい,unpleasant
|
|
||||||
,また,"again,and"
|
|
||||||
,まだ,"yet,still"
|
|
||||||
町,まち,"town,city"
|
|
||||||
待つ,まつ,to wait
|
|
||||||
,まっすぐ,"straight ahead,direct"
|
|
||||||
,マッチ,match
|
|
||||||
窓,まど,window
|
|
||||||
丸い/円い,まるい,"round,circular"
|
|
||||||
万,まん,ten thousand
|
|
||||||
万年筆,まんねんひつ,fountain pen
|
|
||||||
磨く,みがく,"to brush teeth, to polish"
|
|
||||||
右,みぎ,right side
|
|
||||||
短い,みじかい,short
|
|
||||||
水,みず,water
|
|
||||||
店,みせ,shop
|
|
||||||
見せる,みせる,to show
|
|
||||||
道,みち,street
|
|
||||||
三日,みっか,"three days, third day of the month"
|
|
||||||
三つ,みっつ,three
|
|
||||||
緑,みどり,green
|
|
||||||
皆さん,みなさん,everyone
|
|
||||||
南,みなみ,south
|
|
||||||
耳,みみ,ear
|
|
||||||
見る 観る,みる,"to see, to watch"
|
|
||||||
,みんな,everyone
|
|
||||||
六日,むいか,"six days, sixth day of the month"
|
|
||||||
向こう,むこう,over there
|
|
||||||
難しい,むずかしい,difficult
|
|
||||||
六つ,むっつ,six
|
|
||||||
村,むら,village
|
|
||||||
目,め,eye
|
|
||||||
,メートル,metre
|
|
||||||
眼鏡,めがね,glasses
|
|
||||||
,もう,already
|
|
||||||
もう一度,もういちど,again
|
|
||||||
木曜日,もくようび,Thursday
|
|
||||||
持つ,もつ,to hold
|
|
||||||
,もっと,more
|
|
||||||
物,もの,thing
|
|
||||||
門,もん,gate
|
|
||||||
問題,もんだい,problem
|
|
||||||
八百屋,やおや,greengrocer
|
|
||||||
野菜,やさい,vegetable
|
|
||||||
易しい,やさしい,"easy, simple"
|
|
||||||
安い,やすい,cheap
|
|
||||||
休み,やすみ,"rest,holiday"
|
|
||||||
休む,やすむ,to rest
|
|
||||||
八つ,やっつ,eight
|
|
||||||
山,やま,mountain
|
|
||||||
,やる,to do
|
|
||||||
夕方,ゆうがた,evening
|
|
||||||
夕飯,ゆうはん,dinner
|
|
||||||
郵便局,ゆうびんきょく,post office
|
|
||||||
昨夜,ゆうべ,last night
|
|
||||||
有名,ゆうめい,famous
|
|
||||||
雪,ゆき,snow
|
|
||||||
行く,ゆく,to go
|
|
||||||
,ゆっくりと,slowly
|
|
||||||
八日,ようか,"eight days, eighth day of the month"
|
|
||||||
洋服,ようふく,western-style clothes
|
|
||||||
,よく,"often, well"
|
|
||||||
横,よこ,"beside,side,width"
|
|
||||||
四日,よっか,"four days, fouth day of the month"
|
|
||||||
四つ,よっつ,four
|
|
||||||
呼ぶ,よぶ,"to call out,to invite"
|
|
||||||
読む,よむ,to read
|
|
||||||
夜,よる,"evening,night"
|
|
||||||
弱い,よわい,weak
|
|
||||||
来月,らいげつ,next month
|
|
||||||
来週,らいしゅう,next week
|
|
||||||
来年,らいねん,next year
|
|
||||||
,ラジオ,radio
|
|
||||||
,ラジカセ / ラジオカセット,radio cassette player
|
|
||||||
,りっぱ,splendid
|
|
||||||
留学生,りゅうがくせい,overseas student
|
|
||||||
両親,りょうしん,both parents
|
|
||||||
料理,りょうり,cuisine
|
|
||||||
旅行,りょこう,travel
|
|
||||||
零,れい,zero
|
|
||||||
冷蔵庫,れいぞうこ,refrigerator
|
|
||||||
,レコード,record
|
|
||||||
,レストラン,restaurant
|
|
||||||
練習,れんしゅうする,to practice
|
|
||||||
廊下,ろうか,corridor
|
|
||||||
六,ろく,six
|
|
||||||
,ワイシャツ,business shirt
|
|
||||||
若い,わかい,young
|
|
||||||
分かる,わかる,to be understood
|
|
||||||
忘れる,わすれる,to forget
|
|
||||||
私,わたくし,"(humble) I,myself"
|
|
||||||
私,わたし,"I,myself"
|
|
||||||
渡す,わたす,to hand over
|
|
||||||
渡る,わたる,to go across
|
|
||||||
悪い,わるい,bad
|
|
||||||
,より、ほう,Used for comparison.
|
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
# Database
|
||||||
|
|
||||||
|
Here are some choices that have been made when designing the schema
|
||||||
|
|
||||||
|
### `JMdict_{Reading,Kanji}Element.elementId` and `JMdict_Sense.senseId`
|
||||||
|
|
||||||
|
The `elementId`/`senseId` field acts as a unique identifier for each individual element in these tables.
|
||||||
|
It is a packed version of the `(entryId, orderNum)` pair, where the first number is given 7 digits and the second is given 2 digits (max count found so far is `40`).
|
||||||
|
Since `entryId` already is a field in the table, it would technically have been fine to store the `orderNum` as a separate field,
|
||||||
|
but it is easier to be able to refer to the entries without a composite foreign key in other tables.
|
||||||
|
|
||||||
|
(NOTE: `entryId` is now inferred from `elementId` within sqlite using a generated column, so saying it is "stored in a separate field" might be a stretch)
|
||||||
|
|
||||||
|
In addition, the reading element id's are added with `1000000000` to make them unique from the kanji element id's. This reduces the amount of space needed for indices in some locations, because you can simply filter out each part with `>` or `<`.
|
||||||
|
|
||||||
|
We used to generate the `elementId` separately from `orderNum` as a sequential id, but it lead to all values
|
||||||
|
shifting whenever the data was updated, leading to very big diffs. Making it be a unique composite of data coming
|
||||||
|
from the source data itself means that the values will be stable across updates.
|
||||||
|
|
||||||
|
Due to the way the data is structured, we can use the `elementId` as the ordering number as well.
|
||||||
|
|
||||||
|
### `JMdict_EntryScore`
|
||||||
|
|
||||||
|
The `JMdict_EntryScore` table is used to store the score of each entry, which is used for sorting search results. The score is calculated based on a number of variables.
|
||||||
|
|
||||||
|
The table is automatically generated from other tables via triggers, and should be considered as a materialized view.
|
||||||
|
|
||||||
|
<s>There is a score row for every single entry in both `JMdict_KanjiElement` and `JMdict_ReadingElement`, split by the `type` field.</s>
|
||||||
|
This is no longer true, we now only store the rows for which the score is not `0`. The `type` field is now also virtual, since the `elementId` fields for both kanji and readings are unique to each other.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Lemmatizer
|
||||||
|
|
||||||
|
The lemmatizer is still quite experimental, but will play a more important role in the project in the future.
|
||||||
|
|
||||||
|
It is a manual implementation of a [Finite State Transducer](https://en.wikipedia.org/wiki/Morphological_dictionary#Finite_State_Transducers) for morphological parsing. The FST is used to recursively remove affixes from a word until it (hopefully) deconjugates into its dictionary form. This iterative deconjugation tree will then be combined with queries into the dictionary data to determine if the deconjugation leads to a real known word.
|
||||||
|
|
||||||
|
Each separate rule is a separate static object declared in `lib/util/lemmatizer/rules`.
|
||||||
|
|
||||||
|
There is a cli subcommand for testing the tool interactively, you can run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dart run jadb lemmatize -w '食べさせられない'
|
||||||
|
```
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Overview
|
||||||
|
|
||||||
|
This is the documentation for `jadb`. Since I'm currently the only one working on it, the documentation is more or less just notes to myself, to ensure I remember how and why I implemented certain features in a certain way a few months down the road. This is not a comprehensive and formal documentation for downstream use, neither for developers nor end-users.
|
||||||
|
|
||||||
|
- [Word Search](./word-search.md)
|
||||||
|
- [Database](./database.md)
|
||||||
|
- [Lemmatizer](./lemmatizer.md)
|
||||||
|
|
||||||
|
## Project structure
|
||||||
|
|
||||||
|
- `lib/_data_ingestion` contains all the code for reading data sources, transforming them and compiling them into an SQLite database. This is for the most part isolated from the rest of the codebase, and should not be depended on by any code used for querying the database.
|
||||||
|
- `lib/cli` contains code for cli tooling (e.g. argument parsing, subcommand handling, etc.)
|
||||||
|
- `lib/const_data` contains database data that is small enough to warrant being hardcoded as dart constants.
|
||||||
|
- `lib/models` contains all the code for representing the database schema as Dart classes, and for converting between those classes and the actual database.
|
||||||
|
- `lib/search` contains all the code for searching the database.
|
||||||
|
- `lib/util/lemmatizer` contains the code for lemmatization, which will be used by the search code in the future.
|
||||||
|
- `migrations` contains raw SQL files for creating the database schema.
|
||||||
|
|
||||||
|
## SQLite naming conventions
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> All of these conventions are actually not enforced yet, it will be fixed at some point.
|
||||||
|
|
||||||
|
- Indices are prefixed with `IDX__`
|
||||||
|
- Crossref tables are prefixed with `XREF__`
|
||||||
|
- Trigger names are prefixed with `TRG__`
|
||||||
|
- Views are prefixed with `VW__`
|
||||||
|
- All data sources should have a `<datasource>_Version` table, which contains a single row with the version of the data source used to generate the database.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Word search
|
||||||
|
|
||||||
|
The word search procedure is currently split into 3 parts:
|
||||||
|
|
||||||
|
1. **Entry ID query**:
|
||||||
|
|
||||||
|
Use a complex query with various scoring factors to try to get list of
|
||||||
|
database ids pointing at dictionary entries, sorted by how likely we think this
|
||||||
|
word is the word that the caller is looking for. The output here is a `List<int>`
|
||||||
|
|
||||||
|
2. **Data Query**:
|
||||||
|
|
||||||
|
Takes the entry id list from the last search, and performs all queries needed to retrieve
|
||||||
|
all the dictionary data for those IDs. The result is a struct with a bunch of flattened lists
|
||||||
|
with data for all the dictionary entries. These lists are sorted by the order that the ids
|
||||||
|
were provided.
|
||||||
|
|
||||||
|
3. **Regrouping**:
|
||||||
|
|
||||||
|
Takes the flattened data, and regroups the items into structs with a more "hierarchical" structure.
|
||||||
|
All data tagged with the same ID will end up in the same struct. Returns a list of these structs.
|
||||||
Generated
+108
-39
@@ -1,48 +1,85 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"jmdict-src": {
|
"crane": {
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"narHash": "sha256-lh46uougUzBrRhhwa7cOb32j5Jt9/RjBUhlVjwVzsII=",
|
"lastModified": 1780532242,
|
||||||
"type": "file",
|
"narHash": "sha256-D+BsdpxmtUwtqGoY0IXPhHgTlmqgcZKCEo1oMyn7ep0=",
|
||||||
"url": "http://ftp.edrdg.org/pub/Nihongo/JMdict_e.gz"
|
"owner": "ipetkov",
|
||||||
|
"repo": "crane",
|
||||||
|
"rev": "59a82a1222dd3b2080b5cc52a1a2e8d5f1b77f37",
|
||||||
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "file",
|
"owner": "ipetkov",
|
||||||
"url": "http://ftp.edrdg.org/pub/Nihongo/JMdict_e.gz"
|
"repo": "crane",
|
||||||
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jmdict-with-examples-src": {
|
"datasources": {
|
||||||
"flake": false,
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"narHash": "sha256-5oS2xDyetbuSM6ax3LUjYA3N60x+D3Hg41HEXGFMqLQ=",
|
"lastModified": 1781006603,
|
||||||
"type": "file",
|
"narHash": "sha256-3Ug2qTpQT2Tcj0ai02kmRV3Ipy0YG39mGJpcMo2Jcs4=",
|
||||||
"url": "http://ftp.edrdg.org/pub/Nihongo/JMdict_e_examp.gz"
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "7718750756fc198822be2c3b034a70d3d3f31f16",
|
||||||
|
"revCount": 33,
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.pvv.ntnu.no/Mugiten/datasources.git"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "file",
|
"type": "git",
|
||||||
"url": "http://ftp.edrdg.org/pub/Nihongo/JMdict_e_examp.gz"
|
"url": "https://git.pvv.ntnu.no/Mugiten/datasources.git"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"kanjidic2-src": {
|
"kanjivg-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"narHash": "sha256-orSeQqSxhn9TtX3anYtbiMEm7nFkuomGnIKoVIUR2CM=",
|
"lastModified": 1778620714,
|
||||||
"type": "file",
|
"narHash": "sha256-LwNcY5A6XPGI+DASZfmP7OeYe8IFesShhSrE7Go2ux8=",
|
||||||
"url": "https://www.edrdg.org/kanjidic/kanjidic2.xml.gz"
|
"ref": "refs/heads/master",
|
||||||
|
"rev": "1957802840a6f059d1e27dcb5755722955cc7dbb",
|
||||||
|
"revCount": 2217,
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.pvv.ntnu.no/mugiten/kanjivg.git"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "file",
|
"type": "git",
|
||||||
"url": "https://www.edrdg.org/kanjidic/kanjidic2.xml.gz"
|
"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": 1771848320,
|
"lastModified": 1780749050,
|
||||||
"narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=",
|
"narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "2fc6539b481e1d2569f25f8799236694180c0993",
|
"rev": "a799d3e3886da994fa307f817a6bc705ae538eeb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -51,25 +88,57 @@
|
|||||||
"type": "indirect"
|
"type": "indirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"radkfile-src": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"narHash": "sha256-DHpMUE2Umje8PbzXUCS6pHZeXQ5+WTxbjSkGU3erDHQ=",
|
|
||||||
"type": "file",
|
|
||||||
"url": "http://ftp.edrdg.org/pub/Nihongo/radkfile.gz"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "file",
|
|
||||||
"url": "http://ftp.edrdg.org/pub/Nihongo/radkfile.gz"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"jmdict-src": "jmdict-src",
|
"datasources": "datasources",
|
||||||
"jmdict-with-examples-src": "jmdict-with-examples-src",
|
"kanjivg-src": "kanjivg-src",
|
||||||
"kanjidic2-src": "kanjidic2-src",
|
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"radkfile-src": "radkfile-src"
|
"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,24 +4,18 @@
|
|||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||||
|
|
||||||
jmdict-src = {
|
tamerye = {
|
||||||
# url = "http://ftp.edrdg.org/pub/Nihongo/JMdict.gz";
|
url = "git+https://git.pvv.ntnu.no/Mugiten/tamerye.git?ref=main";
|
||||||
url = "http://ftp.edrdg.org/pub/Nihongo/JMdict_e.gz";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
flake = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
jmdict-with-examples-src = {
|
datasources = {
|
||||||
url = "http://ftp.edrdg.org/pub/Nihongo/JMdict_e_examp.gz";
|
url = "git+https://git.pvv.ntnu.no/Mugiten/datasources.git";
|
||||||
flake = false;
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
radkfile-src = {
|
kanjivg-src = {
|
||||||
url = "http://ftp.edrdg.org/pub/Nihongo/radkfile.gz";
|
url = "git+https://git.pvv.ntnu.no/mugiten/kanjivg.git";
|
||||||
flake = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
kanjidic2-src = {
|
|
||||||
url = "https://www.edrdg.org/kanjidic/kanjidic2.xml.gz";
|
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -29,10 +23,9 @@
|
|||||||
outputs = {
|
outputs = {
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
jmdict-src,
|
tamerye,
|
||||||
jmdict-with-examples-src,
|
datasources,
|
||||||
radkfile-src,
|
kanjivg-src,
|
||||||
kanjidic2-src
|
|
||||||
}: let
|
}: let
|
||||||
inherit (nixpkgs) lib;
|
inherit (nixpkgs) lib;
|
||||||
systems = [
|
systems = [
|
||||||
@@ -43,7 +36,14 @@
|
|||||||
"armv7l-linux"
|
"armv7l-linux"
|
||||||
];
|
];
|
||||||
|
|
||||||
forAllSystems = f: lib.genAttrs systems (system: f system nixpkgs.legacyPackages.${system});
|
forAllSystems = f: lib.genAttrs systems (system: let
|
||||||
|
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 +54,7 @@
|
|||||||
|
|
||||||
runtimeEnv = {
|
runtimeEnv = {
|
||||||
JADB_PATH = "${self.packages.${system}.database}/jadb.sqlite";
|
JADB_PATH = "${self.packages.${system}.database}/jadb.sqlite";
|
||||||
LIBSQLITE_PATH = "${pkgs.sqlite.out}/lib/libsqlite3.so";
|
LIBSQLITE_PATH = "${pkgs.tamerye-sqlite}/lib/libsqlite3.so";
|
||||||
};
|
};
|
||||||
|
|
||||||
text = ''
|
text = ''
|
||||||
@@ -77,21 +77,28 @@
|
|||||||
|
|
||||||
devShells = forAllSystems (system: pkgs: {
|
devShells = forAllSystems (system: pkgs: {
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
packages = with pkgs; [
|
||||||
dart
|
dart
|
||||||
gnumake
|
gnumake
|
||||||
lcov
|
lcov
|
||||||
|
sqldiff
|
||||||
|
tamerye-sqlite-cli
|
||||||
|
];
|
||||||
|
env = {
|
||||||
|
LIBSQLITE_PATH = "${pkgs.tamerye-sqlite}/lib/libsqlite3.so";
|
||||||
|
JADB_PATH = "result/jadb.sqlite";
|
||||||
|
LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.tamerye-sqlite ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sqlite-debugging = pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
tamerye-sqlite-cli
|
||||||
sqlite-analyzer
|
sqlite-analyzer
|
||||||
sqlite-interactive
|
|
||||||
sqlite-web
|
sqlite-web
|
||||||
# sqlint
|
# sqlint
|
||||||
sqlfluff
|
sqlfluff
|
||||||
];
|
];
|
||||||
env = {
|
|
||||||
LIBSQLITE_PATH = "${pkgs.sqlite.out}/lib/libsqlite3.so";
|
|
||||||
JADB_PATH = "result/jadb.sqlite";
|
|
||||||
LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.sqlite ];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -128,29 +135,26 @@
|
|||||||
ln -s ${src} $out
|
ln -s ${src} $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
jmdict = pkgs.callPackage ./nix/jmdict.nix {
|
inherit (datasources.packages.${system}) jmdict radkfile kanjidic2;
|
||||||
inherit jmdict-src jmdict-with-examples-src edrdgMetadata;
|
|
||||||
};
|
|
||||||
|
|
||||||
radkfile = pkgs.callPackage ./nix/radkfile.nix {
|
|
||||||
inherit radkfile-src edrdgMetadata;
|
|
||||||
};
|
|
||||||
|
|
||||||
kanjidic2 = pkgs.callPackage ./nix/kanjidic2.nix {
|
|
||||||
inherit kanjidic2-src edrdgMetadata;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {
|
||||||
inherit (self.packages.${system}) database-tool jmdict radkfile kanjidic2;
|
sqlite = pkgs.tamerye-sqlite-cli;
|
||||||
|
inherit (datasources.packages.${system}) jmdict radkfile kanjidic2 tanos-jlpt;
|
||||||
|
kanjivg = kanjivg-src;
|
||||||
|
inherit (self.packages.${system}) database-tool;
|
||||||
inherit src;
|
inherit src;
|
||||||
};
|
};
|
||||||
|
|
||||||
database-wal = pkgs.callPackage ./nix/database.nix {
|
database-wal = pkgs.callPackage ./nix/database.nix {
|
||||||
inherit (self.packages.${system}) database-tool jmdict radkfile kanjidic2;
|
sqlite = pkgs.tamerye-sqlite-cli;
|
||||||
|
inherit (datasources.packages.${system}) jmdict radkfile kanjidic2 tanos-jlpt;
|
||||||
|
kanjivg = kanjivg-src;
|
||||||
|
inherit (self.packages.${system}) database-tool;
|
||||||
inherit src;
|
inherit src;
|
||||||
wal = true;
|
wal = true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import 'package:jadb/_data_ingestion/sql_writable.dart';
|
import 'package:jadb/_data_ingestion/sql_writable.dart';
|
||||||
|
|
||||||
abstract class Element extends SQLWritable {
|
abstract class Element extends SQLWritable {
|
||||||
|
final int elementId;
|
||||||
final String reading;
|
final String reading;
|
||||||
final int? news;
|
final int? news;
|
||||||
final int? ichi;
|
final int? ichi;
|
||||||
final int? spec;
|
final int? spec;
|
||||||
final int? gai;
|
final int? gai;
|
||||||
final int? nf;
|
final int? nf;
|
||||||
const Element({
|
Element({
|
||||||
|
required this.elementId,
|
||||||
required this.reading,
|
required this.reading,
|
||||||
this.news,
|
this.news,
|
||||||
this.ichi,
|
this.ichi,
|
||||||
@@ -18,6 +20,7 @@ abstract class Element extends SQLWritable {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, Object?> get sqlValue => {
|
Map<String, Object?> get sqlValue => {
|
||||||
|
'elementId': elementId,
|
||||||
'reading': reading,
|
'reading': reading,
|
||||||
'news': news,
|
'news': news,
|
||||||
'ichi': ichi,
|
'ichi': ichi,
|
||||||
@@ -28,12 +31,11 @@ abstract class Element extends SQLWritable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class KanjiElement extends Element {
|
class KanjiElement extends Element {
|
||||||
int orderNum;
|
|
||||||
List<String> info;
|
List<String> info;
|
||||||
|
|
||||||
KanjiElement({
|
KanjiElement({
|
||||||
this.info = const [],
|
this.info = const [],
|
||||||
required this.orderNum,
|
required super.elementId,
|
||||||
required super.reading,
|
required super.reading,
|
||||||
super.news,
|
super.news,
|
||||||
super.ichi,
|
super.ichi,
|
||||||
@@ -43,23 +45,19 @@ class KanjiElement extends Element {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, Object?> get sqlValue => {
|
Map<String, Object?> get sqlValue => {...super.sqlValue};
|
||||||
...super.sqlValue,
|
|
||||||
'orderNum': orderNum,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ReadingElement extends Element {
|
class ReadingElement extends Element {
|
||||||
int orderNum;
|
|
||||||
bool readingDoesNotMatchKanji;
|
bool readingDoesNotMatchKanji;
|
||||||
List<String> info;
|
List<String> info;
|
||||||
List<String> restrictions;
|
List<String> restrictions;
|
||||||
|
|
||||||
ReadingElement({
|
ReadingElement({
|
||||||
required this.orderNum,
|
|
||||||
required this.readingDoesNotMatchKanji,
|
required this.readingDoesNotMatchKanji,
|
||||||
this.info = const [],
|
this.info = const [],
|
||||||
this.restrictions = const [],
|
this.restrictions = const [],
|
||||||
|
required super.elementId,
|
||||||
required super.reading,
|
required super.reading,
|
||||||
super.news,
|
super.news,
|
||||||
super.ichi,
|
super.ichi,
|
||||||
@@ -71,7 +69,6 @@ class ReadingElement extends Element {
|
|||||||
@override
|
@override
|
||||||
Map<String, Object?> get sqlValue => {
|
Map<String, Object?> get sqlValue => {
|
||||||
...super.sqlValue,
|
...super.sqlValue,
|
||||||
'orderNum': orderNum,
|
|
||||||
'readingDoesNotMatchKanji': readingDoesNotMatchKanji,
|
'readingDoesNotMatchKanji': readingDoesNotMatchKanji,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -107,9 +104,8 @@ class Glossary extends SQLWritable {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, Object?> get sqlValue => {
|
Map<String, Object?> get sqlValue => {
|
||||||
'language': language,
|
// 'language': language,
|
||||||
'phrase': phrase,
|
'phrase': phrase,
|
||||||
'type': type,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +127,19 @@ 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 {
|
||||||
@@ -142,7 +151,6 @@ class XRef {
|
|||||||
|
|
||||||
class Sense extends SQLWritable {
|
class Sense extends SQLWritable {
|
||||||
final int senseId;
|
final int senseId;
|
||||||
final int orderNum;
|
|
||||||
final List<XRefParts> antonyms;
|
final List<XRefParts> antonyms;
|
||||||
final List<String> dialects;
|
final List<String> dialects;
|
||||||
final List<String> fields;
|
final List<String> fields;
|
||||||
@@ -157,7 +165,6 @@ class Sense extends SQLWritable {
|
|||||||
|
|
||||||
const Sense({
|
const Sense({
|
||||||
required this.senseId,
|
required this.senseId,
|
||||||
required this.orderNum,
|
|
||||||
this.antonyms = const [],
|
this.antonyms = const [],
|
||||||
this.dialects = const [],
|
this.dialects = const [],
|
||||||
this.fields = const [],
|
this.fields = const [],
|
||||||
@@ -172,10 +179,7 @@ class Sense extends SQLWritable {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, Object?> get sqlValue => {
|
Map<String, Object?> get sqlValue => {'senseId': senseId};
|
||||||
'senseId': senseId,
|
|
||||||
'orderNum': orderNum,
|
|
||||||
};
|
|
||||||
|
|
||||||
bool get isEmpty =>
|
bool get isEmpty =>
|
||||||
antonyms.isEmpty &&
|
antonyms.isEmpty &&
|
||||||
|
|||||||
@@ -1,31 +1,97 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:jadb/_data_ingestion/jmdict/objects.dart';
|
import 'package:jadb/_data_ingestion/jmdict/objects.dart';
|
||||||
import 'package:jadb/table_names/jmdict.dart';
|
import 'package:jadb/table_names/jmdict.dart';
|
||||||
import 'package:sqflite_common/sqlite_api.dart';
|
import 'package:sqflite_common/sqlite_api.dart';
|
||||||
|
|
||||||
|
/// A wrapper for the result of resolving an xref, which includes the resolved entry and a flag
|
||||||
|
/// indicating whether the xref was ambiguous (i.e. could refer to multiple entries).
|
||||||
class ResolvedXref {
|
class ResolvedXref {
|
||||||
Entry entry;
|
final Entry entry;
|
||||||
bool ambiguous;
|
final bool ambiguous;
|
||||||
|
|
||||||
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
|
||||||
|
/// JMdict entry, if possible.
|
||||||
|
///
|
||||||
|
/// If the xref is ambiguous (i.e. it could refer to multiple entries), the
|
||||||
|
/// first entry is returned, and the returned value is marked as ambiguous.
|
||||||
|
///
|
||||||
|
/// If the xref cannot be resolved to any entry at all, an exception is thrown.
|
||||||
ResolvedXref resolveXref(
|
ResolvedXref resolveXref(
|
||||||
SplayTreeMap<String, Set<Entry>> entriesByKanji,
|
SplayTreeMap<String, Set<Entry>> entriesByKanji,
|
||||||
SplayTreeMap<String, Set<Entry>> entriesByReading,
|
SplayTreeMap<String, Set<Entry>> entriesByReading,
|
||||||
XRefParts xref,
|
XRefParts xref,
|
||||||
) {
|
) {
|
||||||
List<Entry> candidateEntries = switch ((xref.kanjiRef, xref.readingRef)) {
|
late List<Entry> candidateEntries;
|
||||||
(null, null) => throw Exception(
|
|
||||||
'Xref $xref has no kanji or reading reference',
|
if (xrefExceptions.containsKey(xref)) {
|
||||||
),
|
final exceptionEntryId = xrefExceptions[xref]!;
|
||||||
(final String k, null) => entriesByKanji[k]!.toList(),
|
// NOTE: this is slow, but we have few exceptions. Let's wait for JMdict XML-NG to be released so we can delete this :)
|
||||||
(null, final String r) => entriesByReading[r]!.toList(),
|
final exceptionEntry =
|
||||||
(final String k, final String r) =>
|
entriesByKanji.values
|
||||||
entriesByKanji[k]!.intersection(entriesByReading[r]!).toList(),
|
.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)) {
|
||||||
|
case (null, null):
|
||||||
|
throw Exception('Xref $xref has no kanji or reading reference');
|
||||||
|
|
||||||
|
case (final String k, null):
|
||||||
|
if (!entriesByKanji.containsKey(k)) {
|
||||||
|
throw Exception(
|
||||||
|
'Xref $xref has kanji reference "$k" but no entries found with that kanji',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
candidateEntries = entriesByKanji[k]!.toList();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case (null, final String r):
|
||||||
|
if (!entriesByReading.containsKey(r)) {
|
||||||
|
throw Exception(
|
||||||
|
'Xref $xref has reading reference "$r" but no entries found with that reading',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
candidateEntries = entriesByReading[r]!.toList();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case (final String k, final String r):
|
||||||
|
if (!entriesByKanji.containsKey(k)) {
|
||||||
|
throw Exception(
|
||||||
|
'Xref $xref has kanji reference "$k" but no entries found with that kanji',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!entriesByReading.containsKey(r)) {
|
||||||
|
throw Exception(
|
||||||
|
'Xref $xref has reading reference "$r" but no entries found with that reading',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
candidateEntries = entriesByKanji[k]!
|
||||||
|
.intersection(entriesByReading[r]!)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
// Filter out entries that don't have the number of senses specified in the xref
|
// Filter out entries that don't have the number of senses specified in the xref
|
||||||
if (xref.senseOrderNum != null) {
|
if (xref.senseOrderNum != null) {
|
||||||
@@ -75,41 +141,47 @@ Future<void> seedJMDictData(List<Entry> entries, Database db) async {
|
|||||||
print(' [JMdict] Batch 1 - Kanji and readings');
|
print(' [JMdict] Batch 1 - Kanji and readings');
|
||||||
Batch b = db.batch();
|
Batch b = db.batch();
|
||||||
|
|
||||||
int elementId = 0;
|
if (Platform.environment['JMDICT_VERSION'] != null &&
|
||||||
|
Platform.environment['JMDICT_DATE'] != null &&
|
||||||
|
Platform.environment['JMDICT_HASH'] != null) {
|
||||||
|
b.insert(JMdictTableNames.version, {
|
||||||
|
'version': Platform.environment['JMDICT_VERSION']!,
|
||||||
|
'date': Platform.environment['JMDICT_DATE']!,
|
||||||
|
'hash': Platform.environment['JMDICT_HASH']!,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
print(
|
||||||
|
'WARNING: JMDICT version information not found in environment variables. '
|
||||||
|
'This may cause issues with future updates.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (final e in entries) {
|
for (final e in entries) {
|
||||||
b.insert(JMdictTableNames.entry, e.sqlValue);
|
b.insert(JMdictTableNames.entry, e.sqlValue);
|
||||||
|
|
||||||
for (final k in e.kanji) {
|
for (final k in e.kanji) {
|
||||||
elementId++;
|
b.insert(JMdictTableNames.kanjiElement, k.sqlValue);
|
||||||
b.insert(
|
|
||||||
JMdictTableNames.kanjiElement,
|
|
||||||
k.sqlValue..addAll({'entryId': e.entryId, 'elementId': elementId}),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (final i in k.info) {
|
for (final i in k.info) {
|
||||||
b.insert(JMdictTableNames.kanjiInfo, {
|
b.insert(JMdictTableNames.kanjiInfo, {
|
||||||
'elementId': elementId,
|
'elementId': k.elementId,
|
||||||
'info': i,
|
'info': i,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final r in e.readings) {
|
for (final r in e.readings) {
|
||||||
elementId++;
|
b.insert(JMdictTableNames.readingElement, r.sqlValue);
|
||||||
b.insert(
|
|
||||||
JMdictTableNames.readingElement,
|
|
||||||
r.sqlValue..addAll({'entryId': e.entryId, 'elementId': elementId}),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (final i in r.info) {
|
for (final i in r.info) {
|
||||||
b.insert(JMdictTableNames.readingInfo, {
|
b.insert(JMdictTableNames.readingInfo, {
|
||||||
'elementId': elementId,
|
'elementId': r.elementId,
|
||||||
'info': i,
|
'info': i,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (final res in r.restrictions) {
|
for (final res in r.restrictions) {
|
||||||
b.insert(JMdictTableNames.readingRestriction, {
|
b.insert(JMdictTableNames.readingRestriction, {
|
||||||
'elementId': elementId,
|
'elementId': r.elementId,
|
||||||
'restriction': res,
|
'restriction': res,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -123,10 +195,7 @@ Future<void> seedJMDictData(List<Entry> entries, Database db) async {
|
|||||||
|
|
||||||
for (final e in entries) {
|
for (final e in entries) {
|
||||||
for (final s in e.senses) {
|
for (final s in e.senses) {
|
||||||
b.insert(
|
b.insert(JMdictTableNames.sense, s.sqlValue);
|
||||||
JMdictTableNames.sense,
|
|
||||||
s.sqlValue..addAll({'entryId': e.entryId}),
|
|
||||||
);
|
|
||||||
for (final d in s.dialects) {
|
for (final d in s.dialects) {
|
||||||
b.insert(JMdictTableNames.senseDialect, {
|
b.insert(JMdictTableNames.senseDialect, {
|
||||||
'senseId': s.senseId,
|
'senseId': s.senseId,
|
||||||
@@ -150,16 +219,18 @@ Future<void> seedJMDictData(List<Entry> entries, Database db) async {
|
|||||||
}
|
}
|
||||||
for (final rk in s.restrictedToKanji) {
|
for (final rk in s.restrictedToKanji) {
|
||||||
b.insert(JMdictTableNames.senseRestrictedToKanji, {
|
b.insert(JMdictTableNames.senseRestrictedToKanji, {
|
||||||
'entryId': e.entryId,
|
|
||||||
'senseId': s.senseId,
|
'senseId': s.senseId,
|
||||||
'kanji': rk,
|
'kanjiElementId': e.kanji
|
||||||
|
.firstWhere((k) => k.reading == rk)
|
||||||
|
.elementId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (final rr in s.restrictedToReading) {
|
for (final rr in s.restrictedToReading) {
|
||||||
b.insert(JMdictTableNames.senseRestrictedToReading, {
|
b.insert(JMdictTableNames.senseRestrictedToReading, {
|
||||||
'entryId': e.entryId,
|
|
||||||
'senseId': s.senseId,
|
'senseId': s.senseId,
|
||||||
'reading': rr,
|
'readingElementId': e.readings
|
||||||
|
.firstWhere((r) => r.reading == rr)
|
||||||
|
.elementId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (final ls in s.languageSource) {
|
for (final ls in s.languageSource) {
|
||||||
@@ -173,6 +244,14 @@ Future<void> seedJMDictData(List<Entry> entries, Database db) async {
|
|||||||
JMdictTableNames.senseGlossary,
|
JMdictTableNames.senseGlossary,
|
||||||
g.sqlValue..addAll({'senseId': s.senseId}),
|
g.sqlValue..addAll({'senseId': s.senseId}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (g.type != null) {
|
||||||
|
b.insert(JMdictTableNames.senseGlossaryType, {
|
||||||
|
'senseId': s.senseId,
|
||||||
|
'phrase': g.phrase,
|
||||||
|
'type': g.type!,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,24 +260,17 @@ Future<void> seedJMDictData(List<Entry> entries, Database db) async {
|
|||||||
|
|
||||||
print(' [JMdict] Building xref trees');
|
print(' [JMdict] Building xref trees');
|
||||||
final SplayTreeMap<String, Set<Entry>> entriesByKanji = SplayTreeMap();
|
final SplayTreeMap<String, Set<Entry>> entriesByKanji = SplayTreeMap();
|
||||||
|
final SplayTreeMap<String, Set<Entry>> entriesByReading = SplayTreeMap();
|
||||||
|
|
||||||
for (final entry in entries) {
|
for (final entry in entries) {
|
||||||
for (final kanji in entry.kanji) {
|
for (final kanji in entry.kanji) {
|
||||||
if (entriesByKanji.containsKey(kanji.reading)) {
|
entriesByKanji.putIfAbsent(kanji.reading, () => {});
|
||||||
entriesByKanji.update(kanji.reading, (list) => list..add(entry));
|
entriesByKanji.update(kanji.reading, (set) => set..add(entry));
|
||||||
} else {
|
|
||||||
entriesByKanji.putIfAbsent(kanji.reading, () => {entry});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
final SplayTreeMap<String, Set<Entry>> entriesByReading = SplayTreeMap();
|
|
||||||
for (final entry in entries) {
|
|
||||||
for (final reading in entry.readings) {
|
for (final reading in entry.readings) {
|
||||||
if (entriesByReading.containsKey(reading.reading)) {
|
entriesByReading.putIfAbsent(reading.reading, () => {});
|
||||||
entriesByReading.update(reading.reading, (list) => list..add(entry));
|
entriesByReading.update(reading.reading, (set) => set..add(entry));
|
||||||
} else {
|
|
||||||
entriesByReading.putIfAbsent(reading.reading, () => {entry});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,6 +279,7 @@ Future<void> seedJMDictData(List<Entry> entries, Database db) async {
|
|||||||
|
|
||||||
for (final e in entries) {
|
for (final e in entries) {
|
||||||
for (final s in e.senses) {
|
for (final s in e.senses) {
|
||||||
|
final seenSeeAlsoXrefs = <int>{};
|
||||||
for (final xref in s.seeAlso) {
|
for (final xref in s.seeAlso) {
|
||||||
final resolvedEntry = resolveXref(
|
final resolvedEntry = resolveXref(
|
||||||
entriesByKanji,
|
entriesByKanji,
|
||||||
@@ -214,16 +287,26 @@ Future<void> seedJMDictData(List<Entry> entries, Database db) async {
|
|||||||
xref,
|
xref,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (seenSeeAlsoXrefs.contains(resolvedEntry.entry.entryId)) {
|
||||||
|
print(
|
||||||
|
'WARNING: Skipping duplicate seeAlso xref from sense ${s.senseId} to entry ${resolvedEntry.entry.entryId}\n'
|
||||||
|
' (kanjiRef: ${xref.kanjiRef}, readingRef: ${xref.readingRef}, senseOrderNum: ${xref.senseOrderNum})',
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seenSeeAlsoXrefs.add(resolvedEntry.entry.entryId);
|
||||||
|
|
||||||
b.insert(JMdictTableNames.senseSeeAlso, {
|
b.insert(JMdictTableNames.senseSeeAlso, {
|
||||||
'senseId': s.senseId,
|
'senseId': s.senseId,
|
||||||
'xrefEntryId': resolvedEntry.entry.entryId,
|
'xrefEntryId': resolvedEntry.entry.entryId,
|
||||||
'seeAlsoKanji': xref.kanjiRef,
|
'seeAlsoSense': xref.senseOrderNum != null
|
||||||
'seeAlsoReading': xref.readingRef,
|
? xref.senseOrderNum! - 1
|
||||||
'seeAlsoSense': xref.senseOrderNum,
|
: null,
|
||||||
'ambiguous': resolvedEntry.ambiguous,
|
'ambiguous': resolvedEntry.ambiguous,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final seenAntonymXrefs = <int>{};
|
||||||
for (final ant in s.antonyms) {
|
for (final ant in s.antonyms) {
|
||||||
final resolvedEntry = resolveXref(
|
final resolvedEntry = resolveXref(
|
||||||
entriesByKanji,
|
entriesByKanji,
|
||||||
@@ -231,12 +314,21 @@ Future<void> seedJMDictData(List<Entry> entries, Database db) async {
|
|||||||
ant,
|
ant,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (seenAntonymXrefs.contains(resolvedEntry.entry.entryId)) {
|
||||||
|
print(
|
||||||
|
'WARNING: Skipping duplicate antonym xref from sense ${s.senseId} to entry ${resolvedEntry.entry.entryId}\n'
|
||||||
|
' (kanjiRef: ${ant.kanjiRef}, readingRef: ${ant.readingRef}, senseOrderNum: ${ant.senseOrderNum})',
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seenAntonymXrefs.add(resolvedEntry.entry.entryId);
|
||||||
|
|
||||||
b.insert(JMdictTableNames.senseAntonyms, {
|
b.insert(JMdictTableNames.senseAntonyms, {
|
||||||
'senseId': s.senseId,
|
'senseId': s.senseId,
|
||||||
'xrefEntryId': resolvedEntry.entry.entryId,
|
'xrefEntryId': resolvedEntry.entry.entryId,
|
||||||
'antonymKanji': ant.kanjiRef,
|
'antonymSense': ant.senseOrderNum != null
|
||||||
'antonymReading': ant.readingRef,
|
? ant.senseOrderNum! - 1
|
||||||
'antonymSense': ant.senseOrderNum,
|
: null,
|
||||||
'ambiguous': resolvedEntry.ambiguous,
|
'ambiguous': resolvedEntry.ambiguous,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,6 @@ XRefParts parseXrefParts(String s) {
|
|||||||
List<Entry> parseJMDictData(XmlElement root) {
|
List<Entry> parseJMDictData(XmlElement root) {
|
||||||
final List<Entry> entries = [];
|
final List<Entry> entries = [];
|
||||||
|
|
||||||
int senseId = 0;
|
|
||||||
|
|
||||||
for (final entry in root.childElements) {
|
for (final entry in root.childElements) {
|
||||||
final entryId = int.parse(entry.findElements('ent_seq').first.innerText);
|
final entryId = int.parse(entry.findElements('ent_seq').first.innerText);
|
||||||
|
|
||||||
@@ -80,11 +78,18 @@ List<Entry> parseJMDictData(XmlElement root) {
|
|||||||
final List<ReadingElement> readingEls = [];
|
final List<ReadingElement> readingEls = [];
|
||||||
final List<Sense> senses = [];
|
final List<Sense> senses = [];
|
||||||
|
|
||||||
for (final (kanjiNum, kEle) in entry.findElements('k_ele').indexed) {
|
for (final (orderNum, kEle) in entry.findElements('k_ele').indexed) {
|
||||||
|
assert(
|
||||||
|
orderNum < 100,
|
||||||
|
'Entry $entryId has more than 100 kanji elements, which will break the elementId generation logic.',
|
||||||
|
);
|
||||||
|
final elementId = entryId * 100 + orderNum;
|
||||||
|
|
||||||
final kePri = getPriorityValues(kEle, 'ke');
|
final kePri = getPriorityValues(kEle, 'ke');
|
||||||
|
|
||||||
kanjiEls.add(
|
kanjiEls.add(
|
||||||
KanjiElement(
|
KanjiElement(
|
||||||
orderNum: kanjiNum + 1,
|
elementId: elementId,
|
||||||
info: kEle
|
info: kEle
|
||||||
.findElements('ke_inf')
|
.findElements('ke_inf')
|
||||||
.map((e) => e.innerText.substring(1, e.innerText.length - 1))
|
.map((e) => e.innerText.substring(1, e.innerText.length - 1))
|
||||||
@@ -100,13 +105,20 @@ List<Entry> parseJMDictData(XmlElement root) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final (orderNum, rEle) in entry.findElements('r_ele').indexed) {
|
for (final (orderNum, rEle) in entry.findElements('r_ele').indexed) {
|
||||||
|
assert(
|
||||||
|
orderNum < 100,
|
||||||
|
'Entry $entryId has more than 100 readings, which will break the elementId generation logic.',
|
||||||
|
);
|
||||||
|
final elementId = 1_000_000_000 + entryId * 100 + orderNum;
|
||||||
|
|
||||||
final rePri = getPriorityValues(rEle, 're');
|
final rePri = getPriorityValues(rEle, 're');
|
||||||
final readingDoesNotMatchKanji = rEle
|
final readingDoesNotMatchKanji = rEle
|
||||||
.findElements('re_nokanji')
|
.findElements('re_nokanji')
|
||||||
.isNotEmpty;
|
.isNotEmpty;
|
||||||
|
|
||||||
readingEls.add(
|
readingEls.add(
|
||||||
ReadingElement(
|
ReadingElement(
|
||||||
orderNum: orderNum + 1,
|
elementId: elementId,
|
||||||
readingDoesNotMatchKanji: readingDoesNotMatchKanji,
|
readingDoesNotMatchKanji: readingDoesNotMatchKanji,
|
||||||
info: rEle
|
info: rEle
|
||||||
.findElements('re_inf')
|
.findElements('re_inf')
|
||||||
@@ -127,10 +139,14 @@ List<Entry> parseJMDictData(XmlElement root) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final (orderNum, sense) in entry.findElements('sense').indexed) {
|
for (final (orderNum, sense) in entry.findElements('sense').indexed) {
|
||||||
senseId++;
|
assert(
|
||||||
|
orderNum < 100,
|
||||||
|
'Entry $entryId has more than 100 senses, which will break the senseId generation logic.',
|
||||||
|
);
|
||||||
|
final senseId = entryId * 100 + orderNum;
|
||||||
|
|
||||||
final result = Sense(
|
final result = Sense(
|
||||||
senseId: senseId,
|
senseId: senseId,
|
||||||
orderNum: orderNum + 1,
|
|
||||||
restrictedToKanji: sense
|
restrictedToKanji: sense
|
||||||
.findElements('stagk')
|
.findElements('stagk')
|
||||||
.map((e) => e.innerText)
|
.map((e) => e.innerText)
|
||||||
|
|||||||
@@ -248,9 +248,6 @@ class Character extends SQLWritable {
|
|||||||
@override
|
@override
|
||||||
Map<String, Object?> get sqlValue => {
|
Map<String, Object?> get sqlValue => {
|
||||||
'literal': literal,
|
'literal': literal,
|
||||||
'grade': grade,
|
|
||||||
'strokeCount': strokeCount,
|
'strokeCount': strokeCount,
|
||||||
'frequency': frequency,
|
|
||||||
'jlpt': jlpt,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:jadb/table_names/kanjidic.dart';
|
import 'package:jadb/table_names/kanjidic.dart';
|
||||||
import 'package:sqflite_common/sqlite_api.dart';
|
import 'package:sqflite_common/sqlite_api.dart';
|
||||||
|
|
||||||
@@ -5,6 +7,22 @@ import 'objects.dart';
|
|||||||
|
|
||||||
Future<void> seedKANJIDICData(List<Character> characters, Database db) async {
|
Future<void> seedKANJIDICData(List<Character> characters, Database db) async {
|
||||||
final b = db.batch();
|
final b = db.batch();
|
||||||
|
|
||||||
|
if (Platform.environment['KANJIDIC_VERSION'] != null &&
|
||||||
|
Platform.environment['KANJIDIC_DATE'] != null &&
|
||||||
|
Platform.environment['KANJIDIC_HASH'] != null) {
|
||||||
|
b.insert(KANJIDICTableNames.version, {
|
||||||
|
'version': Platform.environment['KANJIDIC_VERSION']!,
|
||||||
|
'date': Platform.environment['KANJIDIC_DATE']!,
|
||||||
|
'hash': Platform.environment['KANJIDIC_HASH']!,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
print(
|
||||||
|
'WARNING: KANJIDIC version information not found in environment variables. '
|
||||||
|
'This may cause issues with future updates.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (final c in characters) {
|
for (final c in characters) {
|
||||||
// if (c.dictionaryReferences.any((e) =>
|
// if (c.dictionaryReferences.any((e) =>
|
||||||
// c.dictionaryReferences
|
// c.dictionaryReferences
|
||||||
@@ -15,6 +33,24 @@ Future<void> seedKANJIDICData(List<Character> characters, Database db) async {
|
|||||||
// }
|
// }
|
||||||
b.insert(KANJIDICTableNames.character, c.sqlValue);
|
b.insert(KANJIDICTableNames.character, c.sqlValue);
|
||||||
|
|
||||||
|
if (c.grade != null) {
|
||||||
|
b.insert(KANJIDICTableNames.grade, {
|
||||||
|
'kanji': c.literal,
|
||||||
|
'grade': c.grade!,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.frequency != null) {
|
||||||
|
b.insert(KANJIDICTableNames.frequency, {
|
||||||
|
'kanji': c.literal,
|
||||||
|
'frequency': c.frequency!,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.jlpt != null) {
|
||||||
|
b.insert(KANJIDICTableNames.jlpt, {'kanji': c.literal, 'jlpt': c.jlpt!});
|
||||||
|
}
|
||||||
|
|
||||||
for (final n in c.radicalName) {
|
for (final n in c.radicalName) {
|
||||||
assert(c.radical != null, 'Radical name without radical');
|
assert(c.radical != null, 'Radical name without radical');
|
||||||
b.insert(
|
b.insert(
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
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};
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
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);
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
Iterable<String> parseRADKFILEBlocks(File radkfile) {
|
Iterable<String> parseRADKFILEBlocks(File radkfile) {
|
||||||
final String content = File('data/tmp/radkfile_utf8').readAsStringSync();
|
final String content = radkfile.readAsStringSync();
|
||||||
|
|
||||||
final Iterable<String> blocks = content
|
final Iterable<String> blocks = content
|
||||||
.replaceAll(RegExp(r'^#.*$'), '')
|
.replaceAll(RegExp(r'^#.*$'), '')
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
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';
|
||||||
|
|
||||||
Future<void> seedRADKFILEData(Iterable<String> blocks, Database db) async {
|
Future<void> seedRADKFILEData(Iterable<String> blocks, Database db) async {
|
||||||
final b = db.batch();
|
final b = db.batch();
|
||||||
|
|
||||||
|
if (Platform.environment['RADKFILE_VERSION'] != null &&
|
||||||
|
Platform.environment['RADKFILE_DATE'] != null &&
|
||||||
|
Platform.environment['RADKFILE_HASH'] != null) {
|
||||||
|
b.insert(RADKFILETableNames.version, {
|
||||||
|
'version': Platform.environment['RADKFILE_VERSION']!,
|
||||||
|
'date': Platform.environment['RADKFILE_DATE']!,
|
||||||
|
'hash': Platform.environment['RADKFILE_HASH']!,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
print(
|
||||||
|
'WARNING: RADKFILE version information not found in environment variables. '
|
||||||
|
'This may cause issues with future updates.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (final block in blocks) {
|
for (final block in blocks) {
|
||||||
final String radical = block[1];
|
final String radical = block[1];
|
||||||
final List<String> kanjiList =
|
final List<String> kanjiList =
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ 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';
|
||||||
@@ -17,14 +19,20 @@ 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('data/tmp/JMdict.xml').readAsStringSync();
|
final String rawXML = File(jmdictPath).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')!;
|
||||||
@@ -37,8 +45,14 @@ 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('data/tmp/kanjidic2.xml').readAsStringSync();
|
final String rawXML = File(kanjidicPath).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')!;
|
||||||
@@ -51,8 +65,13 @@ 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('data/tmp/RADKFILE');
|
final File raw = File(radkfilePath);
|
||||||
|
|
||||||
print('[RADKFILE] Parsing content...');
|
print('[RADKFILE] Parsing content...');
|
||||||
final blocks = parseRADKFILEBlocks(raw);
|
final blocks = parseRADKFILEBlocks(raw);
|
||||||
@@ -62,13 +81,19 @@ 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('data/tanos-jlpt/n1.csv'),
|
'N1': File('$tanosJlptPath/n1.csv'),
|
||||||
'N2': File('data/tanos-jlpt/n2.csv'),
|
'N2': File('$tanosJlptPath/n2.csv'),
|
||||||
'N3': File('data/tanos-jlpt/n3.csv'),
|
'N3': File('$tanosJlptPath/n3.csv'),
|
||||||
'N4': File('data/tanos-jlpt/n4.csv'),
|
'N4': File('$tanosJlptPath/n4.csv'),
|
||||||
'N5': File('data/tanos-jlpt/n5.csv'),
|
'N5': File('$tanosJlptPath/n5.csv'),
|
||||||
};
|
};
|
||||||
|
|
||||||
print('[TANOS-JLPT] Parsing content...');
|
print('[TANOS-JLPT] Parsing content...');
|
||||||
@@ -80,3 +105,16 @@ 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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,18 +3,18 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:csv/csv.dart';
|
import 'package:csv/csv.dart';
|
||||||
import 'package:jadb/_data_ingestion/tanos-jlpt/objects.dart';
|
import 'package:jadb/_data_ingestion/tanos-jlpt/objects.dart';
|
||||||
import 'package:xml/xml_events.dart';
|
|
||||||
|
|
||||||
Future<List<JLPTRankedWord>> parseJLPTRankedWords(
|
Future<List<JLPTRankedWord>> parseJLPTRankedWords(
|
||||||
Map<String, File> files,
|
Map<String, File> files,
|
||||||
) async {
|
) async {
|
||||||
final List<JLPTRankedWord> result = [];
|
final List<JLPTRankedWord> result = [];
|
||||||
|
|
||||||
final codec = CsvCodec(
|
final codec = Csv(
|
||||||
fieldDelimiter: ',',
|
fieldDelimiter: ',',
|
||||||
lineDelimiter: '\n',
|
lineDelimiter: '\n',
|
||||||
quoteMode: QuoteMode.strings,
|
quoteMode: QuoteMode.strings,
|
||||||
escapeCharacter: '\\',
|
escapeCharacter: '\\',
|
||||||
|
parseHeaders: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (final entry in files.entries) {
|
for (final entry in files.entries) {
|
||||||
@@ -29,7 +29,6 @@ Future<List<JLPTRankedWord>> parseJLPTRankedWords(
|
|||||||
.openRead()
|
.openRead()
|
||||||
.transform(utf8.decoder)
|
.transform(utf8.decoder)
|
||||||
.transform(codec.decoder)
|
.transform(codec.decoder)
|
||||||
.flatten()
|
|
||||||
.map((row) {
|
.map((row) {
|
||||||
if (row.length != 3) {
|
if (row.length != 3) {
|
||||||
throw Exception('Invalid line in $jlptLevel: $row');
|
throw Exception('Invalid line in $jlptLevel: $row');
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
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';
|
||||||
|
|
||||||
@@ -7,6 +9,21 @@ Future<void> seedTanosJLPTData(
|
|||||||
) async {
|
) async {
|
||||||
final Batch b = db.batch();
|
final Batch b = db.batch();
|
||||||
|
|
||||||
|
if (Platform.environment['TANOS_JLPT_VERSION'] != null &&
|
||||||
|
Platform.environment['TANOS_JLPT_DATE'] != null &&
|
||||||
|
Platform.environment['TANOS_JLPT_HASH'] != null) {
|
||||||
|
b.insert(TanosJLPTTableNames.version, {
|
||||||
|
'version': Platform.environment['TANOS_JLPT_VERSION']!,
|
||||||
|
'date': Platform.environment['TANOS_JLPT_DATE']!,
|
||||||
|
'hash': Platform.environment['TANOS_JLPT_HASH']!,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
print(
|
||||||
|
'WARNING: Tanos JLPT version information not found in environment variables. '
|
||||||
|
'This may cause issues with future updates.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (final jlptLevel in resolvedEntries.entries) {
|
for (final jlptLevel in resolvedEntries.entries) {
|
||||||
final level = jlptLevel.key;
|
final level = jlptLevel.key;
|
||||||
final entryIds = jlptLevel.value;
|
final entryIds = jlptLevel.value;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ 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,12 +42,18 @@ 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('data/tanos-jlpt/n1.csv'),
|
'N1': File('$tanosJlptPath/n1.csv'),
|
||||||
'N2': File('data/tanos-jlpt/n2.csv'),
|
'N2': File('$tanosJlptPath/n2.csv'),
|
||||||
'N3': File('data/tanos-jlpt/n3.csv'),
|
'N3': File('$tanosJlptPath/n3.csv'),
|
||||||
'N4': File('data/tanos-jlpt/n4.csv'),
|
'N4': File('$tanosJlptPath/n4.csv'),
|
||||||
'N5': File('data/tanos-jlpt/n5.csv'),
|
'N5': File('$tanosJlptPath/n5.csv'),
|
||||||
};
|
};
|
||||||
|
|
||||||
final rankedWords = await parseJLPTRankedWords(files);
|
final rankedWords = await parseJLPTRankedWords(files);
|
||||||
|
|||||||
+297
-204
@@ -1,217 +1,310 @@
|
|||||||
const Map<int, List<String>> radicals = {
|
import 'package:collection/collection.dart';
|
||||||
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: [
|
||||||
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),
|
||||||
|
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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
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?,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/// 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?);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/// 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,5 +1,6 @@
|
|||||||
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';
|
||||||
@@ -21,6 +22,7 @@ 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,6 +33,7 @@ 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,
|
||||||
};
|
};
|
||||||
@@ -43,12 +44,13 @@ 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, start, end);
|
int get hashCode => Object.hash(spanType, index, subIndex, start, end);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
@@ -56,6 +58,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:jadb/models/common/jlpt_level.dart';
|
import 'package:jadb/models/common/jlpt_level.dart';
|
||||||
import 'package:jadb/models/jmdict/jmdict_kanji_info.dart';
|
import 'package:jadb/models/jmdict/jmdict_kanji_info.dart';
|
||||||
|
import 'package:jadb/models/jmdict/jmdict_misc.dart';
|
||||||
import 'package:jadb/models/jmdict/jmdict_reading_info.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_match_span.dart';
|
||||||
import 'package:jadb/models/word_search/word_search_ruby.dart';
|
import 'package:jadb/models/word_search/word_search_ruby.dart';
|
||||||
@@ -45,6 +46,13 @@ class WordSearchResult {
|
|||||||
/// the original searchword.
|
/// the original searchword.
|
||||||
List<WordSearchMatchSpan>? matchSpans;
|
List<WordSearchMatchSpan>? matchSpans;
|
||||||
|
|
||||||
|
/// Whether the first item in [japanese] contains kanji that likely is rare.
|
||||||
|
bool get hasUnusualKanji =>
|
||||||
|
(japanese.first.furigana != null &&
|
||||||
|
kanjiInfo[japanese.first.base] == JMdictKanjiInfo.rK) ||
|
||||||
|
senses.where((sense) => sense.misc.contains(JMdictMisc.onlyKana)).length >
|
||||||
|
(senses.length / 2);
|
||||||
|
|
||||||
/// All contents of [japanese], transliterated to romaji
|
/// All contents of [japanese], transliterated to romaji
|
||||||
List<String> get romaji => japanese
|
List<String> get romaji => japanese
|
||||||
.map((word) => transliterateKanaToLatin(word.furigana ?? word.base))
|
.map((word) => transliterateKanaToLatin(word.furigana ?? word.base))
|
||||||
|
|||||||
@@ -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,
|
'languageSource': languageSource.map((e) => e.toJson()).toList(),
|
||||||
};
|
};
|
||||||
|
|
||||||
factory WordSearchSense.fromJson(Map<String, dynamic> json) =>
|
factory WordSearchSense.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ 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({
|
||||||
@@ -40,6 +42,10 @@ 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: null,
|
xrefResult: json['xrefResult'] != null
|
||||||
|
? WordSearchResult.fromJson(
|
||||||
|
Map<String, dynamic>.from(json['xrefResult'] as Map),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-9
@@ -1,9 +1,13 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
@@ -18,12 +22,23 @@ 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(Set<String> kanji) =>
|
Future<Map<String, KanjiSearchResult>> jadbGetManyKanji(
|
||||||
searchManyKanjiWithDbConnection(this, kanji);
|
Iterable<String> 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(
|
||||||
List<String> kanji, {
|
Iterable<String> kanji, {
|
||||||
bool deduplicate = false,
|
bool deduplicate = false,
|
||||||
}) => filterKanjiWithDbConnection(this, kanji, deduplicate);
|
}) => filterKanjiWithDbConnection(this, kanji, deduplicate);
|
||||||
|
|
||||||
@@ -31,17 +46,23 @@ 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);
|
||||||
|
|
||||||
@@ -55,17 +76,29 @@ extension JaDBConnection on DatabaseExecutor {
|
|||||||
Future<int?> jadbSearchWordCount(
|
Future<int?> jadbSearchWordCount(
|
||||||
String word, {
|
String word, {
|
||||||
SearchMode searchMode = SearchMode.auto,
|
SearchMode searchMode = SearchMode.auto,
|
||||||
}) => searchWordCountWithDbConnection(this, word, searchMode: searchMode);
|
bool fuzzyKana = true,
|
||||||
|
}) => 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<String>> jadbSearchRemainingRadicals(List<String> radicals) =>
|
Future<List<RadkfileRadical>> jadbSearchRemainingRadicals(
|
||||||
searchRemainingRadicalsWithDbConnection(this, radicals);
|
List<RadkfileRadical> 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(List<String> radicals) =>
|
Future<List<String>> jadbSearchKanjiByRadicals(
|
||||||
searchKanjiByRadicalsWithDbConnection(this, radicals);
|
List<RadkfileRadical> radicals,
|
||||||
|
) => searchKanjiByRadicalsWithDbConnection(this, radicals);
|
||||||
|
|
||||||
|
/// Retrieve the version information for all datasources in the database.
|
||||||
|
Future<DatasourceVersions> jadbGetDatasourceVersions() =>
|
||||||
|
getDatasourceVersions(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:sqflite_common/sqflite.dart';
|
|||||||
/// If [deduplicate] is true, the returned list will deduplicate the input kanji list before returning the filtered results.
|
/// If [deduplicate] is true, the returned list will deduplicate the input kanji list before returning the filtered results.
|
||||||
Future<List<String>> filterKanjiWithDbConnection(
|
Future<List<String>> filterKanjiWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
List<String> kanji,
|
Iterable<String> kanji,
|
||||||
bool deduplicate,
|
bool deduplicate,
|
||||||
) async {
|
) async {
|
||||||
final Set<String> filteredKanji = await connection
|
final Set<String> filteredKanji = await connection
|
||||||
@@ -14,7 +14,7 @@ Future<List<String>> filterKanjiWithDbConnection(
|
|||||||
SELECT "literal"
|
SELECT "literal"
|
||||||
FROM "${KANJIDICTableNames.character}"
|
FROM "${KANJIDICTableNames.character}"
|
||||||
WHERE "literal" IN (${kanji.map((_) => '?').join(',')})
|
WHERE "literal" IN (${kanji.map((_) => '?').join(',')})
|
||||||
''', kanji)
|
''', kanji.toList())
|
||||||
.then((value) => value.map((e) => e['literal'] as String).toSet());
|
.then((value) => value.map((e) => e['literal'] as String).toSet());
|
||||||
|
|
||||||
if (deduplicate) {
|
if (deduplicate) {
|
||||||
|
|||||||
@@ -8,10 +8,21 @@ import 'package:sqflite_common/sqflite.dart';
|
|||||||
Future<List<Map<String, Object?>>> _charactersQuery(
|
Future<List<Map<String, Object?>>> _charactersQuery(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
String kanji,
|
String kanji,
|
||||||
) => connection.query(
|
) => connection.rawQuery(
|
||||||
KANJIDICTableNames.character,
|
'''
|
||||||
where: 'literal = ?',
|
SELECT
|
||||||
whereArgs: [kanji],
|
"${KANJIDICTableNames.character}"."literal",
|
||||||
|
"${KANJIDICTableNames.character}"."strokeCount",
|
||||||
|
"${KANJIDICTableNames.grade}"."grade",
|
||||||
|
"${KANJIDICTableNames.jlpt}"."jlpt",
|
||||||
|
"${KANJIDICTableNames.frequency}"."frequency"
|
||||||
|
FROM "${KANJIDICTableNames.character}"
|
||||||
|
LEFT JOIN "${KANJIDICTableNames.grade}" ON "${KANJIDICTableNames.character}"."literal" = "${KANJIDICTableNames.grade}"."kanji"
|
||||||
|
LEFT JOIN "${KANJIDICTableNames.jlpt}" ON "${KANJIDICTableNames.character}"."literal" = "${KANJIDICTableNames.jlpt}"."kanji"
|
||||||
|
LEFT JOIN "${KANJIDICTableNames.frequency}" ON "${KANJIDICTableNames.character}"."literal" = "${KANJIDICTableNames.frequency}"."kanji"
|
||||||
|
WHERE "literal" = ?
|
||||||
|
''',
|
||||||
|
[kanji],
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<List<Map<String, Object?>>> _codepointsQuery(
|
Future<List<Map<String, Object?>>> _codepointsQuery(
|
||||||
@@ -263,7 +274,7 @@ Future<KanjiSearchResult?> searchKanjiWithDbConnection(
|
|||||||
/// Searches for multiple kanji at once, returning a map of kanji to their search results.
|
/// Searches for multiple kanji at once, returning a map of kanji to their search results.
|
||||||
Future<Map<String, KanjiSearchResult>> searchManyKanjiWithDbConnection(
|
Future<Map<String, KanjiSearchResult>> searchManyKanjiWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
Set<String> kanji,
|
Iterable<String> kanji,
|
||||||
) async {
|
) async {
|
||||||
if (kanji.isEmpty) {
|
if (kanji.isEmpty) {
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
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,3 +1,4 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
@@ -7,9 +8,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<String>> searchRemainingRadicalsWithDbConnection(
|
Future<List<RadkfileRadical>> searchRemainingRadicalsWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
List<String> radicals,
|
List<RadkfileRadical> radicals,
|
||||||
) async {
|
) async {
|
||||||
final distinctRadicals = radicals.toSet();
|
final distinctRadicals = radicals.toSet();
|
||||||
|
|
||||||
@@ -25,11 +26,14 @@ Future<List<String>> searchRemainingRadicalsWithDbConnection(
|
|||||||
HAVING COUNT(DISTINCT "radical") = ?
|
HAVING COUNT(DISTINCT "radical") = ?
|
||||||
)
|
)
|
||||||
''',
|
''',
|
||||||
[...distinctRadicals, distinctRadicals.length],
|
[...distinctRadicals.map((r) => r.formalVariant), 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;
|
||||||
@@ -38,7 +42,7 @@ Future<List<String>> 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<String> radicals,
|
List<RadkfileRadical> radicals,
|
||||||
) async {
|
) async {
|
||||||
final distinctRadicals = radicals.toSet();
|
final distinctRadicals = radicals.toSet();
|
||||||
|
|
||||||
@@ -50,7 +54,7 @@ Future<List<String>> searchKanjiByRadicalsWithDbConnection(
|
|||||||
GROUP BY "kanji"
|
GROUP BY "kanji"
|
||||||
HAVING COUNT(DISTINCT "radical") = ?
|
HAVING COUNT(DISTINCT "radical") = ?
|
||||||
''',
|
''',
|
||||||
[...distinctRadicals, distinctRadicals.length],
|
[...distinctRadicals.map((r) => r.formalVariant), distinctRadicals.length],
|
||||||
);
|
);
|
||||||
|
|
||||||
final kanji = queryResult.map((row) => row['kanji'] as String).toList();
|
final kanji = queryResult.map((row) => row['kanji'] as String).toList();
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -69,7 +69,7 @@ Future<List<Map<String, Object?>>> _readingelementsQuery(
|
|||||||
JMdictTableNames.readingElement,
|
JMdictTableNames.readingElement,
|
||||||
where: 'entryId IN (${List.filled(entryIds.length, '?').join(',')})',
|
where: 'entryId IN (${List.filled(entryIds.length, '?').join(',')})',
|
||||||
whereArgs: entryIds,
|
whereArgs: entryIds,
|
||||||
orderBy: 'orderNum',
|
orderBy: 'elementId',
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<List<Map<String, Object?>>> _kanjielementsQuery(
|
Future<List<Map<String, Object?>>> _kanjielementsQuery(
|
||||||
@@ -79,7 +79,7 @@ Future<List<Map<String, Object?>>> _kanjielementsQuery(
|
|||||||
JMdictTableNames.kanjiElement,
|
JMdictTableNames.kanjiElement,
|
||||||
where: 'entryId IN (${List.filled(entryIds.length, '?').join(',')})',
|
where: 'entryId IN (${List.filled(entryIds.length, '?').join(',')})',
|
||||||
whereArgs: entryIds,
|
whereArgs: entryIds,
|
||||||
orderBy: 'orderNum',
|
orderBy: 'elementId',
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<List<Map<String, Object?>>> _jlpttagsQuery(
|
Future<List<Map<String, Object?>>> _jlpttagsQuery(
|
||||||
@@ -216,19 +216,43 @@ Future<List<Map<String, Object?>>> _sensepossQuery(
|
|||||||
Future<List<Map<String, Object?>>> _senserestrictedtokanjisQuery(
|
Future<List<Map<String, Object?>>> _senserestrictedtokanjisQuery(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
List<int> senseIds,
|
List<int> senseIds,
|
||||||
) => connection.query(
|
) => connection.rawQuery(
|
||||||
JMdictTableNames.senseRestrictedToKanji,
|
"""
|
||||||
where: 'senseId IN (${List.filled(senseIds.length, '?').join(',')})',
|
SELECT
|
||||||
whereArgs: senseIds,
|
"${JMdictTableNames.senseRestrictedToKanji}".senseId,
|
||||||
|
"${JMdictTableNames.senseRestrictedToKanji}".kanjiElementId,
|
||||||
|
"${JMdictTableNames.kanjiElement}".reading
|
||||||
|
FROM "${JMdictTableNames.senseRestrictedToKanji}"
|
||||||
|
JOIN "${JMdictTableNames.kanjiElement}"
|
||||||
|
ON "${JMdictTableNames.senseRestrictedToKanji}"."kanjiElementId" = "${JMdictTableNames.kanjiElement}"."elementId"
|
||||||
|
WHERE
|
||||||
|
"senseId" IN (${List.filled(senseIds.length, '?').join(',')})
|
||||||
|
ORDER BY
|
||||||
|
"${JMdictTableNames.senseRestrictedToKanji}"."senseId",
|
||||||
|
"${JMdictTableNames.senseRestrictedToKanji}"."kanjiElementId"
|
||||||
|
""",
|
||||||
|
[...senseIds],
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<List<Map<String, Object?>>> _senserestrictedtoreadingsQuery(
|
Future<List<Map<String, Object?>>> _senserestrictedtoreadingsQuery(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
List<int> senseIds,
|
List<int> senseIds,
|
||||||
) => connection.query(
|
) => connection.rawQuery(
|
||||||
JMdictTableNames.senseRestrictedToReading,
|
"""
|
||||||
where: 'senseId IN (${List.filled(senseIds.length, '?').join(',')})',
|
SELECT
|
||||||
whereArgs: senseIds,
|
"${JMdictTableNames.senseRestrictedToReading}".senseId,
|
||||||
|
"${JMdictTableNames.senseRestrictedToReading}".readingElementId,
|
||||||
|
"${JMdictTableNames.readingElement}".reading
|
||||||
|
FROM "${JMdictTableNames.senseRestrictedToReading}"
|
||||||
|
JOIN "${JMdictTableNames.readingElement}"
|
||||||
|
ON "${JMdictTableNames.senseRestrictedToReading}"."readingElementId" = "${JMdictTableNames.readingElement}"."elementId"
|
||||||
|
WHERE
|
||||||
|
"senseId" IN (${List.filled(senseIds.length, '?').join(',')})
|
||||||
|
ORDER BY
|
||||||
|
"${JMdictTableNames.senseRestrictedToReading}"."senseId",
|
||||||
|
"${JMdictTableNames.senseRestrictedToReading}"."readingElementId"
|
||||||
|
""",
|
||||||
|
[...senseIds],
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<List<Map<String, Object?>>> _examplesentencesQuery(
|
Future<List<Map<String, Object?>>> _examplesentencesQuery(
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ 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 ||
|
||||||
@@ -67,26 +68,28 @@ String _filterFTSSensitiveCharacters(String word) {
|
|||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
"$tableName"."entryId",
|
"$tableName"."entryId",
|
||||||
100
|
100
|
||||||
+ (("${tableName}FTS"."reading" = ?) * 10000)
|
+ (("$tableName"."reading" = ?1) * 100000)
|
||||||
+ "JMdict_EntryScore"."score"
|
${fuzzyKana ? '+ (("${tableName}FTS"."reading" = normalize_jp(?1)) * 10000)' : ''}
|
||||||
|
+ (("$tableName"."reading" LIKE ?1 || '%') * 20)
|
||||||
|
+ (("$tableName"."orderNum" = 0) * 20)
|
||||||
|
+ COALESCE("JMdict_EntryScore"."score", 0)
|
||||||
AS "score"
|
AS "score"
|
||||||
FROM "${tableName}FTS"
|
${fuzzyKana ? 'FROM "${tableName}FTS" JOIN "$tableName" USING ("elementId")' : 'FROM "$tableName"'}
|
||||||
JOIN "$tableName" USING ("elementId")
|
LEFT JOIN "JMdict_EntryScore" USING ("elementId")
|
||||||
JOIN "JMdict_EntryScore" USING ("elementId")
|
${fuzzyKana ? 'WHERE "${tableName}FTS"."reading" MATCH normalize_jp(?1) || \'*\'' : 'WHERE "$tableName"."reading" LIKE \'?1\' || \'%\''}
|
||||||
WHERE "${tableName}FTS"."reading" MATCH ? || '*'
|
|
||||||
AND "JMdict_EntryScore"."type" = '${tableName == JMdictTableNames.kanjiElement ? 'k' : 'r'}'
|
|
||||||
),
|
),
|
||||||
non_fts_results AS (
|
non_fts_results AS (
|
||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
"$tableName"."entryId",
|
"$tableName"."entryId",
|
||||||
50
|
50
|
||||||
+ "JMdict_EntryScore"."score"
|
+ (("$tableName"."reading" LIKE '%' || ?1 || '%') * 20)
|
||||||
|
+ (("$tableName"."orderNum" = 0) * 20)
|
||||||
|
+ COALESCE("JMdict_EntryScore"."score", 0)
|
||||||
AS "score"
|
AS "score"
|
||||||
FROM "$tableName"
|
FROM "$tableName"
|
||||||
JOIN "JMdict_EntryScore" USING ("elementId")
|
LEFT JOIN "JMdict_EntryScore" USING ("elementId")
|
||||||
WHERE "reading" LIKE '%' || ? || '%'
|
WHERE "$tableName"."reading" LIKE '%' || ${fuzzyKana ? 'normalize_jp(?1)' : '?1'} || '%'
|
||||||
AND "$tableName"."entryId" NOT IN (SELECT "entryId" FROM "fts_results")
|
AND "$tableName"."entryId" NOT IN (SELECT "entryId" FROM "fts_results")
|
||||||
AND "JMdict_EntryScore"."type" = '${tableName == JMdictTableNames.kanjiElement ? 'k' : 'r'}'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
SELECT ${countOnly ? 'COUNT(DISTINCT "entryId") AS count' : '"entryId", MAX("score") AS "score"'}
|
SELECT ${countOnly ? 'COUNT(DISTINCT "entryId") AS count' : '"entryId", MAX("score") AS "score"'}
|
||||||
@@ -101,25 +104,21 @@ 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,
|
||||||
);
|
);
|
||||||
@@ -135,10 +134,15 @@ Future<List<ScoredEntryId>> _queryKanji(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> _queryKanjiCount(DatabaseExecutor connection, String word) {
|
Future<int> _queryKanjiCount(
|
||||||
|
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
|
||||||
@@ -149,12 +153,14 @@ Future<int> _queryKanjiCount(DatabaseExecutor connection, String word) {
|
|||||||
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,
|
||||||
);
|
);
|
||||||
@@ -170,10 +176,15 @@ Future<List<ScoredEntryId>> _queryKana(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> _queryKanaCount(DatabaseExecutor connection, String word) {
|
Future<int> _queryKanaCount(
|
||||||
|
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
|
||||||
@@ -198,16 +209,16 @@ Future<List<ScoredEntryId>> _queryEnglish(
|
|||||||
'''
|
'''
|
||||||
SELECT
|
SELECT
|
||||||
"${JMdictTableNames.sense}"."entryId",
|
"${JMdictTableNames.sense}"."entryId",
|
||||||
MAX("JMdict_EntryScore"."score")
|
COALESCE(MAX("JMdict_EntryScore"."score"), 0)
|
||||||
+ (("${JMdictTableNames.senseGlossary}"."phrase" = ?1 AND "${JMdictTableNames.sense}"."orderNum" = 1) * 50)
|
+ (("${JMdictTableNames.senseGlossary}"."phrase" = ?1 AND "${JMdictTableNames.sense}"."orderNum" = 0) * 50)
|
||||||
+ (("${JMdictTableNames.senseGlossary}"."phrase" = ?1 AND "${JMdictTableNames.sense}"."orderNum" = 2) * 30)
|
+ (("${JMdictTableNames.senseGlossary}"."phrase" = ?1 AND "${JMdictTableNames.sense}"."orderNum" = 1) * 30)
|
||||||
+ (("${JMdictTableNames.senseGlossary}"."phrase" = ?1) * 20)
|
+ (("${JMdictTableNames.senseGlossary}"."phrase" = ?1 AND "${JMdictTableNames.sense}"."orderNum" > 1) * 20)
|
||||||
as "score"
|
as "score"
|
||||||
FROM "${JMdictTableNames.senseGlossary}"
|
FROM "${JMdictTableNames.senseGlossary}"
|
||||||
JOIN "${JMdictTableNames.sense}" USING ("senseId")
|
JOIN "${JMdictTableNames.sense}" USING ("senseId")
|
||||||
JOIN "JMdict_EntryScore" USING ("entryId")
|
LEFT JOIN "JMdict_EntryScore" USING ("entryId")
|
||||||
WHERE "${JMdictTableNames.senseGlossary}"."phrase" LIKE ?2
|
WHERE "${JMdictTableNames.senseGlossary}"."phrase" LIKE ?2
|
||||||
GROUP BY "JMdict_EntryScore"."entryId"
|
GROUP BY "${JMdictTableNames.sense}"."entryId"
|
||||||
ORDER BY
|
ORDER BY
|
||||||
"score" DESC,
|
"score" DESC,
|
||||||
"${JMdictTableNames.sense}"."entryId" ASC
|
"${JMdictTableNames.sense}"."entryId" ASC
|
||||||
@@ -215,7 +226,7 @@ Future<List<ScoredEntryId>> _queryEnglish(
|
|||||||
${offset != null ? 'OFFSET ?4' : ''}
|
${offset != null ? 'OFFSET ?4' : ''}
|
||||||
'''
|
'''
|
||||||
.trim(),
|
.trim(),
|
||||||
[word, '%${word.replaceAll('%', '')}%', if (pageSize != null) pageSize, if (offset != null) offset],
|
[word, '%${word.replaceAll('%', '')}%', ?pageSize, ?offset],
|
||||||
);
|
);
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -243,23 +254,39 @@ 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(connection, word, pageSize, offset);
|
entryIds = await _queryKanji(
|
||||||
|
connection,
|
||||||
|
word,
|
||||||
|
fuzzyKana,
|
||||||
|
pageSize,
|
||||||
|
offset,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SearchMode.kana:
|
case SearchMode.kana:
|
||||||
entryIds = await _queryKana(connection, word, pageSize, offset);
|
entryIds = await _queryKana(
|
||||||
|
connection,
|
||||||
|
word,
|
||||||
|
fuzzyKana,
|
||||||
|
pageSize,
|
||||||
|
offset,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SearchMode.english:
|
case SearchMode.english:
|
||||||
@@ -279,6 +306,7 @@ 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);
|
||||||
@@ -290,11 +318,11 @@ Future<int?> fetchEntryIdCount(
|
|||||||
|
|
||||||
switch (searchMode) {
|
switch (searchMode) {
|
||||||
case SearchMode.kanji:
|
case SearchMode.kanji:
|
||||||
entryIdCount = await _queryKanjiCount(connection, word);
|
entryIdCount = await _queryKanjiCount(connection, word, fuzzyKana);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SearchMode.kana:
|
case SearchMode.kana:
|
||||||
entryIdCount = await _queryKanaCount(connection, word);
|
entryIdCount = await _queryKanaCount(connection, word, fuzzyKana);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SearchMode.english:
|
case SearchMode.english:
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ List<WordSearchSense> _regroupSenses({
|
|||||||
.map((e) => e['reading'] as String)
|
.map((e) => e['reading'] as String)
|
||||||
.toList(),
|
.toList(),
|
||||||
restrictedToKanji: restrictedToKanjis
|
restrictedToKanji: restrictedToKanjis
|
||||||
.map((e) => e['kanji'] as String)
|
.map((e) => e['reading'] as String)
|
||||||
.toList(),
|
.toList(),
|
||||||
fields: fields
|
fields: fields
|
||||||
.map((e) => JMdictField.fromId(e['field'] as String))
|
.map((e) => JMdictField.fromId(e['field'] as String))
|
||||||
|
|||||||
@@ -38,10 +38,11 @@ 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.isEmpty) {
|
if (word.trim().isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ Future<List<WordSearchResult>?> searchWordWithDbConnection(
|
|||||||
connection,
|
connection,
|
||||||
word,
|
word,
|
||||||
searchMode,
|
searchMode,
|
||||||
|
fuzzyKana,
|
||||||
pageSize,
|
pageSize,
|
||||||
offset,
|
offset,
|
||||||
);
|
);
|
||||||
@@ -82,6 +84,7 @@ 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;
|
||||||
@@ -91,11 +94,30 @@ 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,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ abstract class JMdictTableNames {
|
|||||||
static const String senseDialect = 'JMdict_SenseDialect';
|
static const String senseDialect = 'JMdict_SenseDialect';
|
||||||
static const String senseField = 'JMdict_SenseField';
|
static const String senseField = 'JMdict_SenseField';
|
||||||
static const String senseGlossary = 'JMdict_SenseGlossary';
|
static const String senseGlossary = 'JMdict_SenseGlossary';
|
||||||
|
static const String senseGlossaryType = 'JMdict_SenseGlossaryType';
|
||||||
static const String senseInfo = 'JMdict_SenseInfo';
|
static const String senseInfo = 'JMdict_SenseInfo';
|
||||||
static const String senseMisc = 'JMdict_SenseMisc';
|
static const String senseMisc = 'JMdict_SenseMisc';
|
||||||
static const String sensePOS = 'JMdict_SensePOS';
|
static const String sensePOS = 'JMdict_SensePOS';
|
||||||
@@ -33,6 +34,7 @@ abstract class JMdictTableNames {
|
|||||||
senseDialect,
|
senseDialect,
|
||||||
senseField,
|
senseField,
|
||||||
senseGlossary,
|
senseGlossary,
|
||||||
|
senseGlossaryType,
|
||||||
senseInfo,
|
senseInfo,
|
||||||
senseMisc,
|
senseMisc,
|
||||||
sensePOS,
|
sensePOS,
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
abstract class KANJIDICTableNames {
|
abstract class KANJIDICTableNames {
|
||||||
static const String version = 'KANJIDIC_Version';
|
static const String version = 'KANJIDIC_Version';
|
||||||
static const String character = 'KANJIDIC_Character';
|
static const String character = 'KANJIDIC_Character';
|
||||||
|
static const String grade = 'KANJIDIC_Grade';
|
||||||
|
static const String frequency = 'KANJIDIC_Frequency';
|
||||||
|
static const String jlpt = 'KANJIDIC_JLPT';
|
||||||
static const String radicalName = 'KANJIDIC_RadicalName';
|
static const String radicalName = 'KANJIDIC_RadicalName';
|
||||||
static const String codepoint = 'KANJIDIC_Codepoint';
|
static const String codepoint = 'KANJIDIC_Codepoint';
|
||||||
static const String radical = 'KANJIDIC_Radical';
|
static const String radical = 'KANJIDIC_Radical';
|
||||||
@@ -20,6 +23,9 @@ abstract class KANJIDICTableNames {
|
|||||||
static Set<String> get allTables => {
|
static Set<String> get allTables => {
|
||||||
version,
|
version,
|
||||||
character,
|
character,
|
||||||
|
grade,
|
||||||
|
frequency,
|
||||||
|
jlpt,
|
||||||
radicalName,
|
radicalName,
|
||||||
codepoint,
|
codepoint,
|
||||||
radical,
|
radical,
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
const int jadbSchemaVersion = 3;
|
||||||
+50
-37
@@ -53,20 +53,20 @@ CREATE TABLE "JMdict_Entry" (
|
|||||||
|
|
||||||
CREATE TABLE "JMdict_KanjiElement" (
|
CREATE TABLE "JMdict_KanjiElement" (
|
||||||
"elementId" INTEGER PRIMARY KEY,
|
"elementId" INTEGER PRIMARY KEY,
|
||||||
"entryId" INTEGER NOT NULL REFERENCES "JMdict_Entry"("entryId"),
|
"entryId" INTEGER NOT NULL GENERATED ALWAYS AS ("elementId" / 100) STORED,
|
||||||
"orderNum" INTEGER NOT NULL,
|
"orderNum" INTEGER NOT NULL GENERATED ALWAYS AS ("elementId" % 100) VIRTUAL,
|
||||||
"reading" TEXT NOT NULL,
|
"reading" TEXT NOT NULL,
|
||||||
"news" INTEGER CHECK ("news" BETWEEN 1 AND 2),
|
"news" INTEGER CHECK ("news" BETWEEN 1 AND 2),
|
||||||
"ichi" INTEGER CHECK ("ichi" BETWEEN 1 AND 2),
|
"ichi" INTEGER CHECK ("ichi" BETWEEN 1 AND 2),
|
||||||
"spec" INTEGER CHECK ("spec" BETWEEN 1 AND 2),
|
"spec" INTEGER CHECK ("spec" BETWEEN 1 AND 2),
|
||||||
"gai" INTEGER CHECK ("gai" BETWEEN 1 AND 2),
|
"gai" INTEGER CHECK ("gai" BETWEEN 1 AND 2),
|
||||||
"nf" INTEGER CHECK ("nf" BETWEEN 1 AND 48),
|
"nf" INTEGER CHECK ("nf" BETWEEN 1 AND 48),
|
||||||
UNIQUE("entryId", "reading"),
|
FOREIGN KEY ("entryId") REFERENCES "JMdict_Entry"("entryId"),
|
||||||
UNIQUE("entryId", "orderNum")
|
UNIQUE("entryId", "reading")
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE INDEX "JMdict_KanjiElement_byEntryId_byOrderNum" ON "JMdict_KanjiElement"("entryId", "orderNum");
|
|
||||||
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"),
|
||||||
@@ -78,8 +78,8 @@ CREATE TABLE "JMdict_KanjiElementInfo" (
|
|||||||
|
|
||||||
CREATE TABLE "JMdict_ReadingElement" (
|
CREATE TABLE "JMdict_ReadingElement" (
|
||||||
"elementId" INTEGER PRIMARY KEY,
|
"elementId" INTEGER PRIMARY KEY,
|
||||||
"entryId" INTEGER NOT NULL REFERENCES "JMdict_Entry"("entryId"),
|
"entryId" INTEGER NOT NULL GENERATED ALWAYS AS (("elementId" / 100) % 10000000) STORED,
|
||||||
"orderNum" INTEGER NOT NULL,
|
"orderNum" INTEGER NOT NULL GENERATED ALWAYS AS ("elementId" % 100) VIRTUAL,
|
||||||
"reading" TEXT NOT NULL,
|
"reading" TEXT NOT NULL,
|
||||||
"readingDoesNotMatchKanji" BOOLEAN NOT NULL DEFAULT FALSE,
|
"readingDoesNotMatchKanji" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
"news" INTEGER CHECK ("news" BETWEEN 1 AND 2),
|
"news" INTEGER CHECK ("news" BETWEEN 1 AND 2),
|
||||||
@@ -87,12 +87,12 @@ CREATE TABLE "JMdict_ReadingElement" (
|
|||||||
"spec" INTEGER CHECK ("spec" BETWEEN 1 AND 2),
|
"spec" INTEGER CHECK ("spec" BETWEEN 1 AND 2),
|
||||||
"gai" INTEGER CHECK ("gai" BETWEEN 1 AND 2),
|
"gai" INTEGER CHECK ("gai" BETWEEN 1 AND 2),
|
||||||
"nf" INTEGER CHECK ("nf" BETWEEN 1 AND 48),
|
"nf" INTEGER CHECK ("nf" BETWEEN 1 AND 48),
|
||||||
UNIQUE("entryId", "reading"),
|
FOREIGN KEY ("entryId") REFERENCES "JMdict_Entry"("entryId"),
|
||||||
UNIQUE("entryId", "orderNum")
|
UNIQUE("entryId", "reading")
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE INDEX "JMdict_ReadingElement_byEntryId_byOrderNum" ON "JMdict_ReadingElement"("entryId", "orderNum");
|
|
||||||
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"),
|
||||||
@@ -110,27 +110,22 @@ CREATE TABLE "JMdict_ReadingElementInfo" (
|
|||||||
|
|
||||||
CREATE TABLE "JMdict_Sense" (
|
CREATE TABLE "JMdict_Sense" (
|
||||||
"senseId" INTEGER PRIMARY KEY,
|
"senseId" INTEGER PRIMARY KEY,
|
||||||
"entryId" INTEGER NOT NULL REFERENCES "JMdict_Entry"("entryId"),
|
"entryId" INTEGER NOT NULL GENERATED ALWAYS AS ("senseId" / 100) STORED,
|
||||||
"orderNum" INTEGER NOT NULL,
|
"orderNum" INTEGER NOT NULL GENERATED ALWAYS AS ("senseId" % 100) VIRTUAL,
|
||||||
|
FOREIGN KEY ("entryId") REFERENCES "JMdict_Entry"("entryId"),
|
||||||
UNIQUE("entryId", "orderNum")
|
UNIQUE("entryId", "orderNum")
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX "JMdict_Sense_byEntryId_byOrderNum" ON "JMdict_Sense"("entryId", "orderNum");
|
|
||||||
|
|
||||||
CREATE TABLE "JMdict_SenseRestrictedToKanji" (
|
CREATE TABLE "JMdict_SenseRestrictedToKanji" (
|
||||||
"entryId" INTEGER NOT NULL,
|
|
||||||
"senseId" INTEGER NOT NULL REFERENCES "JMdict_Sense"("senseId"),
|
"senseId" INTEGER NOT NULL REFERENCES "JMdict_Sense"("senseId"),
|
||||||
"kanji" TEXT NOT NULL,
|
"kanjiElementId" INTEGER NOT NULL REFERENCES "JMdict_KanjiElement"("elementId"),
|
||||||
FOREIGN KEY ("entryId", "kanji") REFERENCES "JMdict_KanjiElement"("entryId", "reading"),
|
PRIMARY KEY ("senseId", "kanjiElementId")
|
||||||
PRIMARY KEY ("entryId", "senseId", "kanji")
|
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE TABLE "JMdict_SenseRestrictedToReading" (
|
CREATE TABLE "JMdict_SenseRestrictedToReading" (
|
||||||
"entryId" INTEGER NOT NULL,
|
|
||||||
"senseId" INTEGER NOT NULL REFERENCES "JMdict_Sense"("senseId"),
|
"senseId" INTEGER NOT NULL REFERENCES "JMdict_Sense"("senseId"),
|
||||||
"reading" TEXT NOT NULL,
|
"readingElementId" INTEGER NOT NULL REFERENCES "JMdict_ReadingElement"("elementId"),
|
||||||
FOREIGN KEY ("entryId", "reading") REFERENCES "JMdict_ReadingElement"("entryId", "reading"),
|
PRIMARY KEY ("senseId", "readingElementId")
|
||||||
PRIMARY KEY ("entryId", "senseId", "reading")
|
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
-- In order to add xrefs, you will need to have added the entry to xref to.
|
-- In order to add xrefs, you will need to have added the entry to xref to.
|
||||||
@@ -146,31 +141,41 @@ CREATE TABLE "JMdict_SenseRestrictedToReading" (
|
|||||||
CREATE TABLE "JMdict_SenseSeeAlso" (
|
CREATE TABLE "JMdict_SenseSeeAlso" (
|
||||||
"senseId" INTEGER NOT NULL REFERENCES "JMdict_Sense"("senseId"),
|
"senseId" INTEGER NOT NULL REFERENCES "JMdict_Sense"("senseId"),
|
||||||
"xrefEntryId" INTEGER NOT NULL,
|
"xrefEntryId" INTEGER NOT NULL,
|
||||||
"seeAlsoReading" TEXT,
|
|
||||||
"seeAlsoKanji" TEXT,
|
|
||||||
"seeAlsoSense" INTEGER,
|
"seeAlsoSense" INTEGER,
|
||||||
-- For some entries, the cross reference is ambiguous. This means that while the ingestion
|
-- For some entries, the cross reference is ambiguous. This means that while the ingestion
|
||||||
-- has determined some xrefEntryId, it is not guaranteed to be the correct one.
|
-- has determined some xrefEntryId, it is not guaranteed to be the correct one.
|
||||||
"ambiguous" BOOLEAN NOT NULL DEFAULT FALSE,
|
"ambiguous" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
FOREIGN KEY ("xrefEntryId", "seeAlsoKanji") REFERENCES "JMdict_KanjiElement"("entryId", "reading"),
|
|
||||||
FOREIGN KEY ("xrefEntryId", "seeAlsoReading") REFERENCES "JMdict_ReadingElement"("entryId", "reading"),
|
"seeAlsoSenseKey" INTEGER GENERATED ALWAYS AS (
|
||||||
FOREIGN KEY ("xrefEntryId", "seeAlsoSense") REFERENCES "JMdict_Sense"("entryId", "orderNum"),
|
CASE
|
||||||
UNIQUE("senseId", "xrefEntryId", "seeAlsoReading", "seeAlsoKanji", "seeAlsoSense")
|
WHEN "seeAlsoSense" IS NOT NULL THEN ("xrefEntryId" * 100) + "seeAlsoSense"
|
||||||
|
ELSE NULL
|
||||||
|
END
|
||||||
|
) VIRTUAL,
|
||||||
|
|
||||||
|
FOREIGN KEY ("seeAlsoSenseKey") REFERENCES "JMdict_Sense"("senseId"),
|
||||||
|
|
||||||
|
PRIMARY KEY ("senseId", "xrefEntryId", "seeAlsoSense")
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE "JMdict_SenseAntonym" (
|
CREATE TABLE "JMdict_SenseAntonym" (
|
||||||
"senseId" INTEGER NOT NULL REFERENCES "JMdict_Sense"("senseId"),
|
"senseId" INTEGER NOT NULL REFERENCES "JMdict_Sense"("senseId"),
|
||||||
"xrefEntryId" INTEGER NOT NULL,
|
"xrefEntryId" INTEGER NOT NULL,
|
||||||
"antonymReading" TEXT,
|
|
||||||
"antonymKanji" TEXT,
|
|
||||||
"antonymSense" INTEGER,
|
"antonymSense" INTEGER,
|
||||||
-- For some entries, the cross reference is ambiguous. This means that while the ingestion
|
-- For some entries, the cross reference is ambiguous. This means that while the ingestion
|
||||||
-- has determined some xrefEntryId, it is not guaranteed to be the correct one.
|
-- has determined some xrefEntryId, it is not guaranteed to be the correct one.
|
||||||
"ambiguous" BOOLEAN NOT NULL DEFAULT FALSE,
|
"ambiguous" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
FOREIGN KEY ("xrefEntryId", "antonymKanji") REFERENCES "JMdict_KanjiElement"("entryId", "reading"),
|
|
||||||
FOREIGN KEY ("xrefEntryId", "antonymReading") REFERENCES "JMdict_ReadingElement"("entryId", "reading"),
|
"antonymSenseKey" INTEGER GENERATED ALWAYS AS (
|
||||||
FOREIGN KEY ("xrefEntryId", "antonymSense") REFERENCES "JMdict_Sense"("entryId", "orderNum"),
|
CASE
|
||||||
UNIQUE("senseId", "xrefEntryId", "antonymReading", "antonymKanji", "antonymSense")
|
WHEN "antonymSense" IS NOT NULL THEN ("xrefEntryId" * 100) + "antonymSense"
|
||||||
|
ELSE NULL
|
||||||
|
END
|
||||||
|
) VIRTUAL,
|
||||||
|
|
||||||
|
FOREIGN KEY ("antonymSenseKey") REFERENCES "JMdict_Sense"("senseId"),
|
||||||
|
|
||||||
|
PRIMARY KEY ("senseId", "xrefEntryId", "antonymSense")
|
||||||
);
|
);
|
||||||
|
|
||||||
-- These cross references are going to be mostly accessed from a sense
|
-- These cross references are going to be mostly accessed from a sense
|
||||||
@@ -221,13 +226,21 @@ CREATE TABLE "JMdict_SenseDialect" (
|
|||||||
CREATE TABLE "JMdict_SenseGlossary" (
|
CREATE TABLE "JMdict_SenseGlossary" (
|
||||||
"senseId" INTEGER NOT NULL REFERENCES "JMdict_Sense"("senseId"),
|
"senseId" INTEGER NOT NULL REFERENCES "JMdict_Sense"("senseId"),
|
||||||
"phrase" TEXT NOT NULL,
|
"phrase" TEXT NOT NULL,
|
||||||
"language" CHAR(3) NOT NULL DEFAULT "eng",
|
-- "language" CHAR(3) NOT NULL DEFAULT "eng",
|
||||||
"type" TEXT,
|
-- PRIMARY KEY ("senseId", "language", "phrase")
|
||||||
PRIMARY KEY ("senseId", "language", "phrase")
|
PRIMARY KEY ("senseId", "phrase")
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE INDEX "JMdict_SenseGlossary_byPhrase" ON JMdict_SenseGlossary("phrase");
|
CREATE INDEX "JMdict_SenseGlossary_byPhrase" ON JMdict_SenseGlossary("phrase");
|
||||||
|
|
||||||
|
CREATE TABLE "JMdict_SenseGlossaryType" (
|
||||||
|
"senseId" INTEGER NOT NULL REFERENCES "JMdict_Sense"("senseId"),
|
||||||
|
"phrase" TEXT NOT NULL,
|
||||||
|
"type" TEXT NOT NULL,
|
||||||
|
PRIMARY KEY ("senseId", "phrase", "type"),
|
||||||
|
FOREIGN KEY ("senseId", "phrase") REFERENCES "JMdict_SenseGlossary"("senseId", "phrase")
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE TABLE "JMdict_SenseInfo" (
|
CREATE TABLE "JMdict_SenseInfo" (
|
||||||
"senseId" INTEGER NOT NULL REFERENCES "JMdict_Sense"("senseId"),
|
"senseId" INTEGER NOT NULL REFERENCES "JMdict_Sense"("senseId"),
|
||||||
"info" TEXT NOT NULL,
|
"info" TEXT NOT NULL,
|
||||||
|
|||||||
@@ -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", NEW."reading");
|
VALUES (NEW."elementId", normalize_jp(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" = NEW."reading"
|
"reading" = normalize_jp(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", NEW."reading");
|
VALUES (NEW."elementId", normalize_jp(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" = NEW."reading"
|
"reading" = normalize_jp(NEW."reading")
|
||||||
WHERE "elementId" = OLD."elementId";
|
WHERE "elementId" = OLD."elementId";
|
||||||
END;
|
END;
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
CREATE TABLE "JMdict_EntryScore" (
|
CREATE TABLE "JMdict_EntryScore" (
|
||||||
"type" CHAR(1) NOT NULL CHECK ("type" IN ('r', 'k')),
|
"elementId" INTEGER PRIMARY KEY,
|
||||||
"entryId" INTEGER NOT NULL REFERENCES "JMdict_Entry"("entryId"),
|
"score" INTEGER NOT NULL,
|
||||||
"elementId" INTEGER NOT NULL,
|
|
||||||
"score" INTEGER NOT NULL DEFAULT 0,
|
|
||||||
"common" BOOLEAN NOT NULL DEFAULT FALSE,
|
"common" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
PRIMARY KEY ("type", "elementId")
|
|
||||||
|
"entryId" INTEGER NOT NULL GENERATED ALWAYS AS (("elementId" / 100) % 10000000) STORED,
|
||||||
|
"type" CHAR(1) NOT NULL GENERATED ALWAYS AS (CASE
|
||||||
|
WHEN "elementId" / 1000000000 = 0 THEN 'k'
|
||||||
|
ELSE 'r'
|
||||||
|
END) VIRTUAL,
|
||||||
|
|
||||||
|
FOREIGN KEY ("entryId") REFERENCES "JMdict_Entry"("entryId")
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE INDEX "JMdict_EntryScore_byElementId_byScore" ON "JMdict_EntryScore"("elementId", "score");
|
CREATE INDEX "JMdict_EntryScore_byElementId_byScore" ON "JMdict_EntryScore"("elementId", "score");
|
||||||
CREATE INDEX "JMdict_EntryScore_byScore" ON "JMdict_EntryScore"("score");
|
CREATE INDEX "JMdict_EntryScore_byScore" ON "JMdict_EntryScore"("score");
|
||||||
CREATE INDEX "JMdict_EntryScore_byCommon" ON "JMdict_EntryScore"("common");
|
|
||||||
|
|
||||||
CREATE INDEX "JMdict_EntryScore_byType_byElementId_byScore" ON "JMdict_EntryScore"("type", "elementId", "score");
|
CREATE INDEX "JMdict_EntryScore_byCommon" ON "JMdict_EntryScore"("common") WHERE "common" = TRUE;
|
||||||
CREATE INDEX "JMdict_EntryScore_byType_byScore" ON "JMdict_EntryScore"("type", "score");
|
|
||||||
CREATE INDEX "JMdict_EntryScore_byType_byCommon" ON "JMdict_EntryScore"("type", "common");
|
|
||||||
|
|
||||||
-- NOTE: these views are deduplicated in order not to perform an unnecessary
|
-- NOTE: these views are deduplicated in order not to perform an unnecessary
|
||||||
-- UNION on every trigger
|
-- UNION on every trigger
|
||||||
|
|
||||||
CREATE VIEW "JMdict_EntryScoreView_Reading" AS
|
CREATE VIEW "JMdict_EntryScoreView_Reading" AS
|
||||||
SELECT
|
SELECT
|
||||||
'r' AS "type",
|
|
||||||
"JMdict_ReadingElement"."entryId",
|
"JMdict_ReadingElement"."entryId",
|
||||||
"JMdict_ReadingElement"."elementId",
|
"JMdict_ReadingElement"."elementId",
|
||||||
(
|
(
|
||||||
@@ -44,7 +45,7 @@ SELECT
|
|||||||
+ (("spec" IS 2) * 5)
|
+ (("spec" IS 2) * 5)
|
||||||
+ (("gai" IS 1) * 10)
|
+ (("gai" IS 1) * 10)
|
||||||
+ (("gai" IS 2) * 5)
|
+ (("gai" IS 2) * 5)
|
||||||
+ (("orderNum" IS 1) * 20)
|
-- + (("orderNum" IS 0) * 20)
|
||||||
- (substr(COALESCE("JMdict_JLPTTag"."jlptLevel", 'N0'), 2) * -5)
|
- (substr(COALESCE("JMdict_JLPTTag"."jlptLevel", 'N0'), 2) * -5)
|
||||||
AS "score"
|
AS "score"
|
||||||
FROM "JMdict_ReadingElement"
|
FROM "JMdict_ReadingElement"
|
||||||
@@ -52,7 +53,6 @@ LEFT JOIN "JMdict_JLPTTag" USING ("entryId");
|
|||||||
|
|
||||||
CREATE VIEW "JMdict_EntryScoreView_Kanji" AS
|
CREATE VIEW "JMdict_EntryScoreView_Kanji" AS
|
||||||
SELECT
|
SELECT
|
||||||
'k' AS "type",
|
|
||||||
"JMdict_KanjiElement"."entryId",
|
"JMdict_KanjiElement"."entryId",
|
||||||
"JMdict_KanjiElement"."elementId",
|
"JMdict_KanjiElement"."elementId",
|
||||||
(
|
(
|
||||||
@@ -76,7 +76,7 @@ SELECT
|
|||||||
+ (("spec" IS 2) * 5)
|
+ (("spec" IS 2) * 5)
|
||||||
+ (("gai" IS 1) * 10)
|
+ (("gai" IS 1) * 10)
|
||||||
+ (("gai" IS 2) * 5)
|
+ (("gai" IS 2) * 5)
|
||||||
+ (("orderNum" IS 1) * 20)
|
-- + (("orderNum" IS 0) * 20)
|
||||||
- (substr(COALESCE("JMdict_JLPTTag"."jlptLevel", 'N0'), 2) * -5)
|
- (substr(COALESCE("JMdict_JLPTTag"."jlptLevel", 'N0'), 2) * -5)
|
||||||
AS "score"
|
AS "score"
|
||||||
FROM "JMdict_KanjiElement"
|
FROM "JMdict_KanjiElement"
|
||||||
@@ -96,19 +96,18 @@ CREATE TRIGGER "JMdict_EntryScore_Insert_JMdict_ReadingElement"
|
|||||||
AFTER INSERT ON "JMdict_ReadingElement"
|
AFTER INSERT ON "JMdict_ReadingElement"
|
||||||
BEGIN
|
BEGIN
|
||||||
INSERT INTO "JMdict_EntryScore" (
|
INSERT INTO "JMdict_EntryScore" (
|
||||||
"type",
|
|
||||||
"entryId",
|
|
||||||
"elementId",
|
"elementId",
|
||||||
"score",
|
"score",
|
||||||
"common"
|
"common"
|
||||||
)
|
)
|
||||||
SELECT "type", "entryId", "elementId", "score", "common"
|
SELECT "elementId", "score", "common"
|
||||||
FROM "JMdict_EntryScoreView_Reading"
|
FROM "JMdict_EntryScoreView_Reading"
|
||||||
WHERE "elementId" = NEW."elementId";
|
WHERE "elementId" = NEW."elementId"
|
||||||
|
AND "score" > 0;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
CREATE TRIGGER "JMdict_EntryScore_Update_JMdict_ReadingElement"
|
CREATE TRIGGER "JMdict_EntryScore_Update_JMdict_ReadingElement"
|
||||||
AFTER UPDATE OF "news", "ichi", "spec", "gai", "nf", "orderNum"
|
AFTER UPDATE OF "news", "ichi", "spec", "gai", "nf", "elementId"
|
||||||
ON "JMdict_ReadingElement"
|
ON "JMdict_ReadingElement"
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE "JMdict_EntryScore"
|
UPDATE "JMdict_EntryScore"
|
||||||
@@ -117,14 +116,17 @@ BEGIN
|
|||||||
"common" = "JMdict_EntryScoreView_Reading"."common"
|
"common" = "JMdict_EntryScoreView_Reading"."common"
|
||||||
FROM "JMdict_EntryScoreView_Reading"
|
FROM "JMdict_EntryScoreView_Reading"
|
||||||
WHERE "elementId" = NEW."elementId";
|
WHERE "elementId" = NEW."elementId";
|
||||||
|
|
||||||
|
DELETE FROM "JMdict_EntryScore"
|
||||||
|
WHERE "elementId" = NEW."elementId"
|
||||||
|
AND "score" <= 0;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
CREATE TRIGGER "JMdict_EntryScore_Delete_JMdict_ReadingElement"
|
CREATE TRIGGER "JMdict_EntryScore_Delete_JMdict_ReadingElement"
|
||||||
AFTER DELETE ON "JMdict_ReadingElement"
|
AFTER DELETE ON "JMdict_ReadingElement"
|
||||||
BEGIN
|
BEGIN
|
||||||
DELETE FROM "JMdict_EntryScore"
|
DELETE FROM "JMdict_EntryScore"
|
||||||
WHERE "type" = 'r'
|
WHERE "elementId" = OLD."elementId";
|
||||||
AND "elementId" = OLD."elementId";
|
|
||||||
END;
|
END;
|
||||||
|
|
||||||
--- JMdict_KanjiElement triggers
|
--- JMdict_KanjiElement triggers
|
||||||
@@ -133,19 +135,18 @@ CREATE TRIGGER "JMdict_EntryScore_Insert_JMdict_KanjiElement"
|
|||||||
AFTER INSERT ON "JMdict_KanjiElement"
|
AFTER INSERT ON "JMdict_KanjiElement"
|
||||||
BEGIN
|
BEGIN
|
||||||
INSERT INTO "JMdict_EntryScore" (
|
INSERT INTO "JMdict_EntryScore" (
|
||||||
"type",
|
|
||||||
"entryId",
|
|
||||||
"elementId",
|
"elementId",
|
||||||
"score",
|
"score",
|
||||||
"common"
|
"common"
|
||||||
)
|
)
|
||||||
SELECT "type", "entryId", "elementId", "score", "common"
|
SELECT "elementId", "score", "common"
|
||||||
FROM "JMdict_EntryScoreView_Kanji"
|
FROM "JMdict_EntryScoreView_Kanji"
|
||||||
WHERE "elementId" = NEW."elementId";
|
WHERE "elementId" = NEW."elementId"
|
||||||
|
AND "score" > 0;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
CREATE TRIGGER "JMdict_EntryScore_Update_JMdict_KanjiElement"
|
CREATE TRIGGER "JMdict_EntryScore_Update_JMdict_KanjiElement"
|
||||||
AFTER UPDATE OF "news", "ichi", "spec", "gai", "nf", "orderNum"
|
AFTER UPDATE OF "news", "ichi", "spec", "gai", "nf", "elementId"
|
||||||
ON "JMdict_KanjiElement"
|
ON "JMdict_KanjiElement"
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE "JMdict_EntryScore"
|
UPDATE "JMdict_EntryScore"
|
||||||
@@ -154,14 +155,17 @@ BEGIN
|
|||||||
"common" = "JMdict_EntryScoreView_Kanji"."common"
|
"common" = "JMdict_EntryScoreView_Kanji"."common"
|
||||||
FROM "JMdict_EntryScoreView_Kanji"
|
FROM "JMdict_EntryScoreView_Kanji"
|
||||||
WHERE "elementId" = NEW."elementId";
|
WHERE "elementId" = NEW."elementId";
|
||||||
|
|
||||||
|
DELETE FROM "JMdict_EntryScore"
|
||||||
|
WHERE "elementId" = NEW."elementId"
|
||||||
|
AND "score" <= 0;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
CREATE TRIGGER "JMdict_EntryScore_Delete_JMdict_KanjiElement"
|
CREATE TRIGGER "JMdict_EntryScore_Delete_JMdict_KanjiElement"
|
||||||
AFTER DELETE ON "JMdict_KanjiElement"
|
AFTER DELETE ON "JMdict_KanjiElement"
|
||||||
BEGIN
|
BEGIN
|
||||||
DELETE FROM "JMdict_EntryScore"
|
DELETE FROM "JMdict_EntryScore"
|
||||||
WHERE "type" = 'k'
|
WHERE "elementId" = OLD."elementId";
|
||||||
AND "elementId" = OLD."elementId";
|
|
||||||
END;
|
END;
|
||||||
|
|
||||||
--- JMdict_JLPTTag triggers
|
--- JMdict_JLPTTag triggers
|
||||||
@@ -204,4 +208,8 @@ BEGIN
|
|||||||
WHERE "JMdict_EntryScoreView"."entryId" = OLD."entryId"
|
WHERE "JMdict_EntryScoreView"."entryId" = OLD."entryId"
|
||||||
AND "JMdict_EntryScore"."entryId" = OLD."entryId"
|
AND "JMdict_EntryScore"."entryId" = OLD."entryId"
|
||||||
AND "JMdict_EntryScoreView"."elementId" = "JMdict_EntryScore"."elementId";
|
AND "JMdict_EntryScoreView"."elementId" = "JMdict_EntryScore"."elementId";
|
||||||
|
|
||||||
|
DELETE FROM "JMdict_EntryScore"
|
||||||
|
WHERE "elementId" = OLD."elementId"
|
||||||
|
AND "score" <= 0;
|
||||||
END;
|
END;
|
||||||
|
|||||||
@@ -18,4 +18,3 @@ CREATE TABLE "RADKFILE" (
|
|||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE INDEX "RADK" ON "RADKFILE"("radical");
|
CREATE INDEX "RADK" ON "RADKFILE"("radical");
|
||||||
CREATE INDEX "KRAD" ON "RADKFILE"("kanji");
|
|
||||||
|
|||||||
@@ -19,6 +19,21 @@ CREATE TABLE "KANJIDIC_Character" (
|
|||||||
"jlpt" INTEGER
|
"jlpt" INTEGER
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
|
CREATE TABLE "KANJIDIC_Grade" (
|
||||||
|
"kanji" CHAR(1) NOT NULL PRIMARY KEY REFERENCES "KANJIDIC_Character"("literal"),
|
||||||
|
"grade" INTEGER NOT NULL CHECK ("grade" BETWEEN 1 AND 10)
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
|
CREATE TABLE "KANJIDIC_Frequency" (
|
||||||
|
"kanji" CHAR(1) NOT NULL PRIMARY KEY REFERENCES "KANJIDIC_Character"("literal"),
|
||||||
|
"frequency" INTEGER NOT NULL
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
|
CREATE TABLE "KANJIDIC_JLPT" (
|
||||||
|
"kanji" CHAR(1) NOT NULL PRIMARY KEY REFERENCES "KANJIDIC_Character"("literal"),
|
||||||
|
"jlpt" INTEGER NOT NULL CHECK ("jlpt" BETWEEN 1 AND 5)
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE TABLE "KANJIDIC_Codepoint" (
|
CREATE TABLE "KANJIDIC_Codepoint" (
|
||||||
"kanji" CHAR(1) NOT NULL REFERENCES "KANJIDIC_Character"("literal"),
|
"kanji" CHAR(1) NOT NULL REFERENCES "KANJIDIC_Character"("literal"),
|
||||||
"type" VARCHAR(6) NOT NULL CHECK ("type" IN ('jis208', 'jis212', 'jis213', 'ucs')),
|
"type" VARCHAR(6) NOT NULL CHECK ("type" IN ('jis208', 'jis212', 'jis213', 'ucs')),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ CREATE TABLE "XREF__KANJIDIC_Radical__RADKFILE"(
|
|||||||
PRIMARY KEY ("radicalId", "radicalSymbol")
|
PRIMARY KEY ("radicalId", "radicalSymbol")
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE INDEX "XREF__KANJIDIC_Radical__RADKFILE__byRadicalId" ON "XREF__KANJIDIC_Radical__RADKFILE"("radicalId");
|
|
||||||
CREATE INDEX "XREF__KANJIDIC_Radical__RADKFILE__byRadicalSymbol" ON "XREF__KANJIDIC_Radical__RADKFILE"("radicalSymbol");
|
CREATE INDEX "XREF__KANJIDIC_Radical__RADKFILE__byRadicalSymbol" ON "XREF__KANJIDIC_Radical__RADKFILE"("radicalSymbol");
|
||||||
|
|
||||||
/* Source: https://ctext.org/kangxi-zidian */
|
/* Source: https://ctext.org/kangxi-zidian */
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
CREATE TABLE "XREF__JMdict_KanjiElement__KANJIDIC_Character"(
|
-- CREATE TABLE "XREF__JMdict_KanjiElement__KANJIDIC_Character"(
|
||||||
"entryId" INTEGER NOT NULL,
|
-- "entryId" INTEGER NOT NULL,
|
||||||
"reading" TEXT NOT NULL,
|
-- "reading" TEXT NOT NULL,
|
||||||
"kanji" CHAR(1) NOT NULL REFERENCES "KANJIDIC_Character"("literal"),
|
-- "kanji" CHAR(1) NOT NULL REFERENCES "KANJIDIC_Character"("literal"),
|
||||||
PRIMARY KEY ("entryId", "reading", "kanji"),
|
-- PRIMARY KEY ("entryId", "reading", "kanji"),
|
||||||
FOREIGN KEY ("entryId", "reading") REFERENCES "JMdict_KanjiElement"("entryId", "reading")
|
-- FOREIGN KEY ("entryId", "reading") REFERENCES "JMdict_KanjiElement"("entryId", "reading")
|
||||||
) WITHOUT ROWID;
|
-- ) WITHOUT ROWID;
|
||||||
|
|
||||||
CREATE INDEX "XREF__JMdict_KanjiElement__KANJIDIC_Character__byEntryId_byReading" ON "XREF__JMdict_KanjiElement__KANJIDIC_Character"("entryId", "reading");
|
-- CREATE INDEX "XREF__JMdict_KanjiElement__KANJIDIC_Character__byEntryId_byReading" ON "XREF__JMdict_KanjiElement__KANJIDIC_Character"("entryId", "reading");
|
||||||
CREATE INDEX "XREF__JMdict_KanjiElement__KANJIDIC_Character__byKanji" ON "XREF__JMdict_KanjiElement__KANJIDIC_Character"("kanji");
|
-- CREATE INDEX "XREF__JMdict_KanjiElement__KANJIDIC_Character__byKanji" ON "XREF__JMdict_KanjiElement__KANJIDIC_Character"("kanji");
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ SELECT
|
|||||||
THEN "JMdict_ReadingElement"."reading"
|
THEN "JMdict_ReadingElement"."reading"
|
||||||
ELSE NULL
|
ELSE NULL
|
||||||
END AS "furigana",
|
END AS "furigana",
|
||||||
COALESCE("JMdict_KanjiElement"."orderNum", 1)
|
COALESCE("JMdict_KanjiElement"."orderNum", 0)
|
||||||
+ "JMdict_ReadingElement"."orderNum"
|
+ "JMdict_ReadingElement"."orderNum"
|
||||||
= 2
|
= 0
|
||||||
AS "isFirst",
|
AS "isFirst",
|
||||||
"JMdict_KanjiElement"."orderNum" AS "kanjiOrderNum",
|
"JMdict_KanjiElement"."orderNum" AS "kanjiOrderNum",
|
||||||
"JMdict_ReadingElement"."orderNum" AS "readingOrderNum"
|
"JMdict_ReadingElement"."orderNum" AS "readingOrderNum"
|
||||||
@@ -65,9 +65,7 @@ JOIN "JMdict_KanjiElement"
|
|||||||
ON "JMdict_KanjiElementFTS"."entryId" = "JMdict_KanjiElement"."entryId"
|
ON "JMdict_KanjiElementFTS"."entryId" = "JMdict_KanjiElement"."entryId"
|
||||||
AND "JMdict_KanjiElementFTS"."reading" LIKE '%' || "JMdict_KanjiElement"."reading"
|
AND "JMdict_KanjiElementFTS"."reading" LIKE '%' || "JMdict_KanjiElement"."reading"
|
||||||
JOIN "JMdict_EntryScore"
|
JOIN "JMdict_EntryScore"
|
||||||
ON "JMdict_EntryScore"."type" = 'k'
|
ON "JMdict_EntryScore"."elementId" = "JMdict_KanjiElement"."elementId"
|
||||||
AND "JMdict_KanjiElement"."entryId" = "JMdict_EntryScore"."entryId"
|
|
||||||
AND "JMdict_KanjiElement"."reading" = "JMdict_EntryScore"."reading"
|
|
||||||
WHERE "JMdict_EntryScore"."common" = 1;
|
WHERE "JMdict_EntryScore"."common" = 1;
|
||||||
|
|
||||||
|
|
||||||
@@ -77,13 +75,7 @@ SELECT DISTINCT "radical" FROM "RADKFILE";
|
|||||||
CREATE VIEW "JMdict_CombinedEntryScore"
|
CREATE VIEW "JMdict_CombinedEntryScore"
|
||||||
AS
|
AS
|
||||||
SELECT
|
SELECT
|
||||||
CASE
|
|
||||||
WHEN "JMdict_EntryScore"."type" = 'k'
|
|
||||||
THEN (SELECT entryId FROM "JMdict_KanjiElement" WHERE "elementId" = "JMdict_EntryScore"."elementId")
|
|
||||||
WHEN "JMdict_EntryScore"."type" = 'r'
|
|
||||||
THEN (SELECT entryId FROM "JMdict_ReadingElement" WHERE "elementId" = "JMdict_EntryScore"."elementId")
|
|
||||||
END AS "entryId",
|
|
||||||
MAX("JMdict_EntryScore"."score") AS "score",
|
MAX("JMdict_EntryScore"."score") AS "score",
|
||||||
MAX("JMdict_EntryScore"."common") AS "common"
|
MAX("JMdict_EntryScore"."common") AS "common"
|
||||||
FROM "JMdict_EntryScore"
|
FROM "JMdict_EntryScore"
|
||||||
GROUP BY "entryId";
|
GROUP BY "JMdict_EntryScore"."entryId";
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
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;
|
||||||
+26
-5
@@ -6,6 +6,8 @@
|
|||||||
jmdict,
|
jmdict,
|
||||||
radkfile,
|
radkfile,
|
||||||
kanjidic2,
|
kanjidic2,
|
||||||
|
tanos-jlpt,
|
||||||
|
kanjivg,
|
||||||
sqlite,
|
sqlite,
|
||||||
wal ? false,
|
wal ? false,
|
||||||
}:
|
}:
|
||||||
@@ -18,14 +20,33 @@ stdenvNoCC.mkDerivation {
|
|||||||
sqlite
|
sqlite
|
||||||
];
|
];
|
||||||
|
|
||||||
|
env = {
|
||||||
|
JMDICT_PATH = "${jmdict}/JMdict.xml";
|
||||||
|
JMDICT_VERSION = jmdict.version;
|
||||||
|
JMDICT_DATE = jmdict.date;
|
||||||
|
JMDICT_HASH = jmdict.hash;
|
||||||
|
|
||||||
|
KANJIDIC_PATH = "${kanjidic2}/kanjidic2.xml";
|
||||||
|
KANJIDIC_VERSION = kanjidic2.version;
|
||||||
|
KANJIDIC_DATE = kanjidic2.date;
|
||||||
|
KANJIDIC_HASH = kanjidic2.hash;
|
||||||
|
|
||||||
|
RADKFILE_PATH = "${radkfile}/RADKFILE";
|
||||||
|
RADKFILE_VERSION = radkfile.version;
|
||||||
|
RADKFILE_DATE = radkfile.date;
|
||||||
|
RADKFILE_HASH = radkfile.hash;
|
||||||
|
|
||||||
|
TANOS_JLPT_PATH = toString tanos-jlpt;
|
||||||
|
TANOS_JLPT_VERSION = tanos-jlpt.version;
|
||||||
|
TANOS_JLPT_DATE = tanos-jlpt.date;
|
||||||
|
TANOS_JLPT_HASH = tanos-jlpt.hash;
|
||||||
|
|
||||||
|
KANJIVG_PATH = "${kanjivg}/kanji";
|
||||||
|
};
|
||||||
|
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
runHook preBuild
|
runHook preBuild
|
||||||
|
|
||||||
mkdir -p data/tmp
|
|
||||||
ln -s "${jmdict}"/* data/tmp
|
|
||||||
ln -s "${radkfile}"/* data/tmp
|
|
||||||
ln -s "${kanjidic2}"/* data/tmp
|
|
||||||
|
|
||||||
for migration in migrations/*.sql; do
|
for migration in migrations/*.sql; do
|
||||||
sqlite3 jadb.sqlite < "$migration"
|
sqlite3 jadb.sqlite < "$migration"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
{
|
{
|
||||||
src,
|
src,
|
||||||
buildDartApplication,
|
buildDartApplication,
|
||||||
|
sqlite,
|
||||||
|
|
||||||
|
callPackage,
|
||||||
|
path,
|
||||||
}:
|
}:
|
||||||
buildDartApplication {
|
buildDartApplication {
|
||||||
pname = "jadb-database-tool";
|
pname = "jadb-database-tool";
|
||||||
@@ -32,5 +36,9 @@ 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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
{
|
|
||||||
stdenvNoCC,
|
|
||||||
jmdict-src,
|
|
||||||
jmdict-with-examples-src,
|
|
||||||
xmlformat,
|
|
||||||
gzip,
|
|
||||||
edrdgMetadata,
|
|
||||||
}:
|
|
||||||
stdenvNoCC.mkDerivation {
|
|
||||||
name = "jmdict";
|
|
||||||
|
|
||||||
dontUnpack = true;
|
|
||||||
srcs = [
|
|
||||||
jmdict-src
|
|
||||||
jmdict-with-examples-src
|
|
||||||
];
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
gzip
|
|
||||||
xmlformat
|
|
||||||
];
|
|
||||||
|
|
||||||
buildPhase = ''
|
|
||||||
runHook preBuild
|
|
||||||
|
|
||||||
gzip -dkc "${jmdict-src}" > JMdict.xml
|
|
||||||
gzip -dkc "${jmdict-with-examples-src}" > JMdict_with_examples.xml
|
|
||||||
xmlformat -i JMdict.xml
|
|
||||||
xmlformat -i JMdict_with_examples.xml
|
|
||||||
|
|
||||||
runHook postBuild
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
runHook preInstall
|
|
||||||
|
|
||||||
install -Dt "$out" JMdict.xml JMdict_with_examples.xml
|
|
||||||
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = edrdgMetadata // {
|
|
||||||
description = "A Japanese-Multilingual Dictionary providing lexical data for japanese words";
|
|
||||||
homepage = "https://www.edrdg.org/jmdict/j_jmdict.html";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
stdenvNoCC,
|
|
||||||
kanjidic2-src,
|
|
||||||
xmlformat,
|
|
||||||
gzip,
|
|
||||||
edrdgMetadata,
|
|
||||||
}:
|
|
||||||
stdenvNoCC.mkDerivation {
|
|
||||||
name = "kanjidic2";
|
|
||||||
|
|
||||||
src = kanjidic2-src;
|
|
||||||
dontUnpack = true;
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
gzip
|
|
||||||
xmlformat
|
|
||||||
];
|
|
||||||
|
|
||||||
buildPhase = ''
|
|
||||||
runHook preBuild
|
|
||||||
|
|
||||||
gzip -dkc "${kanjidic2-src}" > kanjidic2.xml
|
|
||||||
xmlformat -i kanjidic2.xml
|
|
||||||
|
|
||||||
runHook postBuild
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
runHook preInstall
|
|
||||||
|
|
||||||
install -Dt "$out" kanjidic2.xml
|
|
||||||
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = edrdgMetadata // {
|
|
||||||
description = "A consolidated XML-format kanji database";
|
|
||||||
homepage = "https://www.edrdg.org/kanjidic/kanjd2index_legacy.html";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{
|
|
||||||
stdenv,
|
|
||||||
radkfile-src,
|
|
||||||
gzip,
|
|
||||||
iconv,
|
|
||||||
edrdgMetadata,
|
|
||||||
}:
|
|
||||||
stdenv.mkDerivation {
|
|
||||||
name = "radkfile";
|
|
||||||
|
|
||||||
src = radkfile-src;
|
|
||||||
dontUnpack = true;
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
gzip
|
|
||||||
iconv
|
|
||||||
];
|
|
||||||
|
|
||||||
buildPhase = ''
|
|
||||||
runHook preBuild
|
|
||||||
|
|
||||||
gzip -dkc "$src" > radkfile
|
|
||||||
iconv -f EUC-JP -t UTF-8 -o radkfile_utf8 radkfile
|
|
||||||
|
|
||||||
runHook postBuild
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
runHook preInstall
|
|
||||||
|
|
||||||
install -Dt "$out" radkfile_utf8
|
|
||||||
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = edrdgMetadata // {
|
|
||||||
description = "A file providing searchable decompositions of kanji characters";
|
|
||||||
homepage = "https://www.edrdg.org/krad/kradinf.html";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
+47
-39
@@ -5,18 +5,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "3b19a47f6ea7c2632760777c78174f47f6aec1e05f0cd611380d4593b8af1dbc"
|
sha256: "563c6992eaeda8625f45b87f6a6a0c547df16565d1c93d8271c7c11057710ca7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "96.0.0"
|
version: "101.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "0c516bc4ad36a1a75759e54d5047cb9d15cded4459df01aa35a0b5ec7db2c2a0"
|
sha256: aa6a9365901532864cae51208f2a6bb18dd01972ebead19c431efc848f60080b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.2.0"
|
version: "13.1.0"
|
||||||
args:
|
args:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -29,10 +29,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.13.0"
|
version: "2.13.1"
|
||||||
benchmark_harness:
|
benchmark_harness:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -61,10 +61,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: code_assets
|
name: code_assets
|
||||||
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
|
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.2.1"
|
||||||
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: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
|
sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.15.1"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -101,10 +101,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: csv
|
name: csv
|
||||||
sha256: bef2950f7a753eb82f894a2eabc3072e73cf21c17096296a5a992797e50b1d0d
|
sha256: "2e0a52fb729f2faacd19c9c0c954ff450bba37aa8ab999410309e2342e7013a2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.1.0"
|
version: "8.0.0"
|
||||||
equatable:
|
equatable:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -149,10 +149,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: hooks
|
name: hooks
|
||||||
sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6"
|
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "2.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: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.18"
|
version: "0.12.20"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "9f29b9bcc8ee287b1a31e0d01be0eae99a930dbffdaecf04b3f3d82a969f296f"
|
sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.1"
|
version: "1.18.3"
|
||||||
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: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
|
sha256: f59351d28f49520cd3a74eb1f41c5f19ae15e53c65a3231d14af672e46510a96
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.17.4"
|
version: "0.19.1"
|
||||||
node_preamble:
|
node_preamble:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -273,6 +273,14 @@ 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:
|
||||||
@@ -333,26 +341,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
|
sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.6"
|
version: "2.5.8"
|
||||||
sqflite_common_ffi:
|
sqflite_common_ffi:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqflite_common_ffi
|
name: sqflite_common_ffi
|
||||||
sha256: c59fcdc143839a77581f7a7c4de018e53682408903a0a0800b95ef2dc4033eff
|
sha256: cd0c7f7de39a08f2d54ef144d9058c46eca8461879aaa648025643455c1e5a20
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0+2"
|
version: "2.4.0+3"
|
||||||
sqlite3:
|
sqlite3:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqlite3
|
name: sqlite3
|
||||||
sha256: b7cf6b37667f6a921281797d2499ffc60fb878b161058d422064f0ddc78f6aa6
|
sha256: "9488c7d2cdb1091c91cacf7e207cff81b28bff8e366f042bad3afe7d34afe189"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.6"
|
version: "3.3.2"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -381,10 +389,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: synchronized
|
name: synchronized
|
||||||
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
|
sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.0"
|
version: "3.4.0+1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -397,26 +405,26 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
sha256: "54c516bbb7cee2754d327ad4fca637f78abfc3cbcc5ace83b3eda117e42cd71a"
|
sha256: ca578dc12bb8b2f40b67b7d3bd2fac4f31c01a6ff7130a14e2597b919934507f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.29.0"
|
version: "1.31.1"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.9"
|
version: "0.7.12"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
sha256: "394f07d21f0f2255ec9e3989f21e54d3c7dc0e6e9dbce160e5a9c1a6be0e2943"
|
sha256: d2e98ec12998368dc59ddd47ab709f2cd55acd6b66dc7db764455a44082f4bc5
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.15"
|
version: "0.6.18"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -429,10 +437,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.0.2"
|
version: "15.2.0"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -477,10 +485,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: xml
|
name: xml
|
||||||
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.6.1"
|
version: "7.0.1"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -490,4 +498,4 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.10.1 <4.0.0"
|
dart: ">=3.11.0 <4.0.0"
|
||||||
|
|||||||
+2
-2
@@ -9,13 +9,13 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
args: ^2.7.0
|
args: ^2.7.0
|
||||||
collection: ^1.19.0
|
collection: ^1.19.0
|
||||||
csv: ^7.1.0
|
csv: ^8.0.0
|
||||||
equatable: ^2.0.0
|
equatable: ^2.0.0
|
||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
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.5.0
|
xml: '>=6.0.0 < 8.0.0'
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
benchmark_harness: ^2.4.0
|
benchmark_harness: ^2.4.0
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
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));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
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));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
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()));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
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));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:jadb/const_data/radicals.dart';
|
||||||
|
import 'package:jadb/search.dart';
|
||||||
|
import 'package:jadb/table_names/radkfile.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'setup_database_connection.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('All constant radicals should exist in the database', () async {
|
||||||
|
final connection = await setupDatabaseConnection();
|
||||||
|
final allRadicalsInDb = await connection.query(
|
||||||
|
RADKFILETableNames.radkfile,
|
||||||
|
columns: ['radical'],
|
||||||
|
distinct: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
final radicalsInDb = allRadicalsInDb
|
||||||
|
.map((e) => e['radical'] as String)
|
||||||
|
.toSet();
|
||||||
|
|
||||||
|
final missingRadicals = radicals.values.flattenedToSet
|
||||||
|
.map((e) => e.formalVariant)
|
||||||
|
.toSet()
|
||||||
|
.difference(radicalsInDb);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
missingRadicals,
|
||||||
|
isEmpty,
|
||||||
|
reason: 'Missing radicals in database: $missingRadicals',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'All radicals in database should be in the constant radical list',
|
||||||
|
() async {
|
||||||
|
final connection = await setupDatabaseConnection();
|
||||||
|
final allRadicalsInDb = await connection.query(
|
||||||
|
RADKFILETableNames.radkfile,
|
||||||
|
columns: ['radical'],
|
||||||
|
distinct: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
final radicalsInDb = allRadicalsInDb
|
||||||
|
.map((e) => e['radical'] as String)
|
||||||
|
.toSet();
|
||||||
|
|
||||||
|
final extraRadicals = radicalsInDb.difference(
|
||||||
|
radicals.values.flattenedToSet.map((e) => e.formalVariant).toSet(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
extraRadicals,
|
||||||
|
isEmpty,
|
||||||
|
reason:
|
||||||
|
'Extra radicals in database missing in the constant list: $extraRadicals',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('All constant radicals are located in the correct stroke count group', () {
|
||||||
|
for (final mapEntry in radicals.entries) {
|
||||||
|
final strokeCount = mapEntry.key;
|
||||||
|
final radicalsInGroup = mapEntry.value;
|
||||||
|
for (final radical in radicalsInGroup) {
|
||||||
|
expect(
|
||||||
|
strokeCount,
|
||||||
|
radical.strokeCount,
|
||||||
|
reason:
|
||||||
|
'Radical ${radical.formalVariant} should have stroke count $strokeCount but has ${radical.strokeCount}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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,6 +4,22 @@ 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');
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ void main() {
|
|||||||
expect(result, 'かたまり');
|
expect(result, 'かたまり');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Basic test with diacritics', () {
|
test('Basic test with dakuten', () {
|
||||||
final result = transliterateLatinToHiragana('gadamari');
|
final result = transliterateLatinToHiragana('gadamari');
|
||||||
expect(result, 'がだまり');
|
expect(result, 'がだまり');
|
||||||
});
|
});
|
||||||
@@ -54,7 +54,7 @@ void main() {
|
|||||||
|
|
||||||
test('Basic test', expectSpans('katamari', ['か', 'た', 'ま', 'り']));
|
test('Basic test', expectSpans('katamari', ['か', 'た', 'ま', 'り']));
|
||||||
test(
|
test(
|
||||||
'Basic test with diacritics',
|
'Basic test with dakuten',
|
||||||
expectSpans('gadamari', ['が', 'だ', 'ま', 'り']),
|
expectSpans('gadamari', ['が', 'だ', 'ま', 'り']),
|
||||||
);
|
);
|
||||||
test('wi and we', expectSpans('wiwe', ['うぃ', 'うぇ']));
|
test('wi and we', expectSpans('wiwe', ['うぃ', 'うぇ']));
|
||||||
@@ -72,7 +72,7 @@ void main() {
|
|||||||
expect(result, 'katamari');
|
expect(result, 'katamari');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Basic test with diacritics', () {
|
test('Basic test with dakuten', () {
|
||||||
final result = transliterateHiraganaToLatin('がだまり');
|
final result = transliterateHiraganaToLatin('がだまり');
|
||||||
expect(result, 'gadamari');
|
expect(result, 'gadamari');
|
||||||
});
|
});
|
||||||
@@ -91,6 +91,21 @@ void main() {
|
|||||||
final result = transliterateHiraganaToLatin('かっぱ');
|
final result = transliterateHiraganaToLatin('かっぱ');
|
||||||
expect(result, 'kappa');
|
expect(result, 'kappa');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Iteration mark', () {
|
||||||
|
final result = transliterateHiraganaToLatin('さゝき');
|
||||||
|
expect(result, 'sasaki');
|
||||||
|
}, skip: 'Not yet implemented');
|
||||||
|
|
||||||
|
test('Iteration mark with dakuten', () {
|
||||||
|
final result = transliterateHiraganaToLatin('あひゞき');
|
||||||
|
expect(result, 'ahibiki');
|
||||||
|
}, skip: 'Not yet implemented');
|
||||||
|
|
||||||
|
test('Yori', () {
|
||||||
|
final result = transliterateHiraganaToLatin('ゟ');
|
||||||
|
expect(result, 'yori');
|
||||||
|
}, skip: 'Not yet implemented');
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Hiragana -> Romaji Spans', () {
|
group('Hiragana -> Romaji Spans', () {
|
||||||
@@ -110,7 +125,7 @@ void main() {
|
|||||||
|
|
||||||
test('Basic test', expectSpans('かたまり', ['ka', 'ta', 'ma', 'ri']));
|
test('Basic test', expectSpans('かたまり', ['ka', 'ta', 'ma', 'ri']));
|
||||||
test(
|
test(
|
||||||
'Basic test with diacritics',
|
'Basic test with dakuten',
|
||||||
expectSpans('がだまり', ['ga', 'da', 'ma', 'ri']),
|
expectSpans('がだまり', ['ga', 'da', 'ma', 'ri']),
|
||||||
);
|
);
|
||||||
test('wi and we', expectSpans('うぃうぇ', ['whi', 'whe']));
|
test('wi and we', expectSpans('うぃうぇ', ['whi', 'whe']));
|
||||||
@@ -118,5 +133,17 @@ void main() {
|
|||||||
|
|
||||||
// TODO: fix the implementation
|
// TODO: fix the implementation
|
||||||
// test('Double consonant', expectSpans('かっぱ', ['ka', 'ppa']));
|
// test('Double consonant', expectSpans('かっぱ', ['ka', 'ppa']));
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Iteration mark',
|
||||||
|
expectSpans('さゝき', ['sa', 'sa', 'ki']),
|
||||||
|
skip: 'Not yet implemented',
|
||||||
|
);
|
||||||
|
test(
|
||||||
|
'Iteration mark with dakuten',
|
||||||
|
expectSpans('あひゞき', ['a', 'hi', 'bi', 'ki']),
|
||||||
|
skip: 'Not yet implemented',
|
||||||
|
);
|
||||||
|
test('Yori', expectSpans('ゟ', ['yori']), skip: 'Not yet implemented');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user