Merge pull request #1 from h7x4ABk3g/development

Finish rewrite for version 1
This commit is contained in:
Oystein Kristoffer Tveit 2020-06-24 17:04:29 +02:00 committed by GitHub
commit 0aa41f075f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 4981 additions and 4927 deletions

134
README.md
View File

@ -1,4 +1,4 @@
# Warning: not functional yet
# unofficial_jisho_api
A rewrite of the [unofficial-jisho-api](https://www.npmjs.com/package/unofficial-jisho-api)
@ -9,19 +9,18 @@ Below are some basic examples.
### Word/phrase search (provided by official Jisho API)
This returns the same results as the official [Jisho.org](https://jisho.org/) API. See the discussion of that [here](http://jisho.org/forum/54fefc1f6e73340b1f160000-is-there-any-kind-of-search-api).
This returns the same results as the official [Jisho.org](https://jisho.org/) API. See the discussion of that [here](https://jisho.org/forum/54fefc1f6e73340b1f160000-is-there-any-kind-of-search-api).
```dart
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
final jisho = JishoApi();
main() async {
void main() async {
jisho.searchForPhrase('日').then((result) {
...
...
...
});
...
...
...
});
}
```
@ -32,13 +31,13 @@ import 'dart:convert' show jsonEncode;
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
final jisho = JishoApi();
main() async {
jisho.searchForKanji('語').then((result) {
print('Found: ' + result.found);
void main() async {
await jisho.searchForKanji('語').then((result) {
print('Found: ' + result.found.toString());
print('Taught in: ' + result.taughtIn);
print('JLPT level: ' + result.jlptLevel);
print('Newspaper frequency rank: ' + result.newspaperFrequencyRank);
print('Stroke count: ' + result.strokeCount);
print('Newspaper frequency rank: ' + result.newspaperFrequencyRank.toString());
print('Stroke count: ' + result.strokeCount.toString());
print('Meaning: ' + result.meaning);
print('Kunyomi: ' + jsonEncode(result.kunyomi));
print('Kunyomi example: ' + jsonEncode(result.kunyomiExamples[0]));
@ -50,7 +49,7 @@ main() async {
print('Stroke order SVG: ' + result.strokeOrderSvgUri);
print('Stroke order GIF: ' + result.strokeOrderGifUri);
print('Jisho Uri: ' + result.uri);
}
});
}
```
@ -68,11 +67,11 @@ Kunyomi example: {"example":"語る","reading":"かたる","meaning":"to talk ab
Onyomi: ["ゴ"]
Onyomi example: {"example":"語","reading":"ゴ","meaning":"language, word"}
Radical: {"symbol":"言","forms":["訁"],"meaning":"speech"}
Parts: ["口","五","言"]
Stroke order diagram: http://classic.jisho.org/static/images/stroke_diagrams/35486_frames.png
Stroke order SVG: http://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/08a9e.svg
Stroke order GIF: https://raw.githubusercontent.com/mistval/kotoba/master/resources/images/kanjianimations/08a9e_anim.gif
Jisho Uri: http://jisho.org/search/%E8%AA%9E%23kanji
Parts: ["五","口","言"]
Stroke order diagram: https://classic.jisho.org/static/images/stroke_diagrams/35486_frames.png
Stroke order SVG: https://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/08a9e.svg
Stroke order GIF: https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/8a9e.gif
Jisho Uri: https://jisho.org/search/%E8%AA%9E%23kanji
```
### Example search
@ -82,18 +81,18 @@ import 'dart:convert' show jsonEncode;
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
final jisho = JishoApi();
main() async {
jisho.searchForExamples('日').then((result) {
void main() async {
await jisho.searchForExamples('日').then((result) {
print('Jisho Uri: ' + result.uri);
print();
print('');
for (int i = 0; i < 3; ++i) {
for (int i = 0; i < 3; i++) {
var example = result.results[i];
print(example.kanji);
print(example.kana);
print(example.english);
print(jsonEncode(example.pieces));
print();
print('');
}
});
}
@ -102,24 +101,23 @@ main() async {
This outputs the following:
```
Jisho Uri: http://jisho.org/search/%E6%97%A5%23sentences
Jisho Uri: https://jisho.org/search/%E6%97%A5%23sentences
日本人ならそんなことはけっしてしないでしょう。
にほんじんならそんなことはけっしてしないでしょう。
A Japanese person would never do such a thing.
[{"lifted":"にほんじん","unlifted":"日本人"},{"lifted":"","unlifted":"なら"},{"lifted":"","unlifted":"そんな"},{"lifted":"","unlifted":"こと"},{"lifted":"","unlifted":"は"},{"lifted":"","unlifted":"けっして"},{"lifted":"","unlifted":"しない"},{"lifted":"","
unlifted":"でしょう"}]
[{"lifted":"にほんじん","unlifted":"日本人"},{"lifted":null,"unlifted":"なら"},{"lifted":null,"unlifted":"そんな"},{"lifted":null,"unlifted":"こと"},{"lifted":null,"unlifted":"は"},{"lifted":null,"unlifted":"けっして"},{"lifted":null,"unlifted":"しない"},{"lifted":null,"unlifted":"でしょう"}]
今日はとても暑い。
きょうはとてもあつい。
It is very hot today.
[{"lifted":"きょう","unlifted":"今日"},{"lifted":"","unlifted":"は"},{"lifted":"","unlifted":"とても"},{"lifted":"あつ","unlifted":"暑い"}]
[{"lifted":"きょう","unlifted":"今日"},{"lifted":null,"unlifted":"は"},{"lifted":null,"unlifted":"とても"},{"lifted":"あつ","unlifted":"暑い"}]
日本には美しい都市が多い。例えば京都、奈良だ。
にほんにはうつくしいとしがおおい。たとえばきょうと、奈良だ。
Japan is full of beautiful cities. Kyoto and Nara, for instance.
[{"lifted":"にほん","unlifted":"日本"},{"lifted":"","unlifted":"には"},{"lifted":"うつく","unlifted":"美しい"},{"lifted":"とし","unlifted":"都市"},{"lifted":"","unlifted":"が"},{"lifted":"おお","unlifted":"多い"},{"lifted":"たと","unlifted":"例えば"},{"lift
ed":"きょうと","unlifted":"京都"},{"lifted":"","unlifted":"だ"}]
[{"lifted":"にほん","unlifted":"日本"},{"lifted":null,"unlifted":"には"},{"lifted":"うつく","unlifted":"美しい"},{"lifted":"とし","unlifted":"都市"},{"lifted":null,"unlifted":"が"},{"lifted":"おお","unlifted":"多い"},{"lifted":"たと","unlifted":"例えば"},{"lifted":"きょうと","unlifted":"京都"},{"lifted":null,"unlifted":"だ"}]
```
### Word/phrase scraping
@ -127,13 +125,14 @@ ed":"きょうと","unlifted":"京都"},{"lifted":"","unlifted":"だ"}]
This scrapes the word/phrase page on Jisho.org. This can get you some data that the official API doesn't have, such as JLPT level and part-of-speech. The official API (`searchForPhrase`) should be preferred if it has the data you need.
```dart
import 'dart:convert' show jsonEncode;
import 'dart:convert';
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
final jisho = JishoApi();
final encoder = JsonEncoder.withIndent(' ');
main() async {
jisho.scrapeForPhrase('谷').then((data) {
print(jsonEncode(data);
void main() async {
await jisho.scrapeForPhrase('谷').then((data) {
print(encoder.convert(data));
});
}
```
@ -156,7 +155,7 @@ This outputs the following:
"sentences": [],
"definition": "valley",
"supplemental": [],
"definitionAbstract": "",
"definitionAbstract": null,
"tags": [
"noun"
]
@ -186,69 +185,6 @@ This outputs the following:
}
```
## Parsing HTML strings
You can provide the HTML responses from Jisho yourself. This can be useful if you need to use a CORS proxy or something. You can do whatever you need to do to get the HTML and then provide it to this module's parsing functions. For example:
### Parse kanji page HTML
```dart
import 'package:http/http.dart' as http;
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
final jisho = JishoApi();
main() async {
const SEARCH_KANJI = '車';
final SEARCH_URI = jisho.getUriForKanjiSearch(SEARCH_KANJI);
final response = await http.get(SEARCH_URI);
final json = jisho.parseKanjiPageHtml(response.body, SEARCH_KANJI);
print('JLPT level: ${json.jlptLevel}');
print('Stroke count: ${json.strokeCount}');
print('Meaning: ${json.meaning}');
}
```
### Parse example page HTML
```dart
import 'package:http/http.dart' as http;
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
final jisho = JishoApi();
main() async {
const SEARCH_EXAMPLE = '保護者';
final SEARCH_URI = jisho.getUriForExampleSearch(SEARCH_EXAMPLE);
final response = await http.get(SEARCH_URI);
final json = jisho.parseExamplePageHtml(response.body, SEARCH_EXAMPLE);
print('English: ${json.results[0].english}');
print('Kanji ${json.results[0].kanji}');
print('Kana: ${json.results[0].kana}');
}
```
### Parse phrase page HTML
```dart
import 'dart:convert' show jsonEncode;
import 'package:http/http.dart' as http;
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
final jisho = JishoApi();
main() async {
const SEARCH_EXAMPLE = '保護者';
final SEARCH_URI = jisho.getUriForPhraseScrape(SEARCH_EXAMPLE);
final response = await http.get(SEARCH_URI);
const json = jisho.parsePhraseScrapeHtml(response.body, SEARCH_EXAMPLE);
print(jsonEncode(json, null, 2));
}
```
## About
Permission to scrape granted by Jisho's admin Kimtaro: http://jisho.org/forum/54fefc1f6e73340b1f160000-is-there-any-kind-of-search-api
Permission to scrape granted by Jisho's admin Kimtaro: https://jisho.org/forum/54fefc1f6e73340b1f160000-is-there-any-kind-of-search-api

View File

@ -0,0 +1,19 @@
import 'dart:convert' show jsonEncode;
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
final jisho = JishoApi();
void main() async {
await jisho.searchForExamples('').then((result) {
print('Jisho Uri: ' + result.uri);
print('');
for (int i = 0; i < 3; i++) {
var example = result.results[i];
print(example.kanji);
print(example.kana);
print(example.english);
print(jsonEncode(example.pieces));
print('');
}
});
}

View File

@ -0,0 +1,24 @@
import 'dart:convert' show jsonEncode;
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
final jisho = JishoApi();
void main() async {
await jisho.searchForKanji('').then((result) {
print('Found: ' + result.found.toString());
print('Taught in: ' + result.taughtIn);
print('JLPT level: ' + result.jlptLevel);
print('Newspaper frequency rank: ' + result.newspaperFrequencyRank.toString());
print('Stroke count: ' + result.strokeCount.toString());
print('Meaning: ' + result.meaning);
print('Kunyomi: ' + jsonEncode(result.kunyomi));
print('Kunyomi example: ' + jsonEncode(result.kunyomiExamples[0]));
print('Onyomi: ' + jsonEncode(result.onyomi));
print('Onyomi example: ' + jsonEncode(result.onyomiExamples[0]));
print('Radical: ' + jsonEncode(result.radical));
print('Parts: ' + jsonEncode(result.parts));
print('Stroke order diagram: ' + result.strokeOrderDiagramUri);
print('Stroke order SVG: ' + result.strokeOrderSvgUri);
print('Stroke order GIF: ' + result.strokeOrderGifUri);
print('Jisho Uri: ' + result.uri);
});
}

View File

@ -0,0 +1,10 @@
import 'dart:convert';
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
final jisho = JishoApi();
final encoder = JsonEncoder.withIndent(' ');
void main() async {
await jisho.scrapeForPhrase('').then((data) {
print(encoder.convert(data));
});
}

View File

@ -0,0 +1,10 @@
import 'dart:convert';
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
final jisho = JishoApi();
final encoder = JsonEncoder.withIndent(' ');
void main() async {
await jisho.searchForPhrase('反対').then((result) {
print(encoder.convert(result));
});
}

View File

@ -1,4 +0,0 @@
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
void main() {
}

3
lib/src/baseURI.dart Normal file
View File

@ -0,0 +1,3 @@
const String JISHO_API = 'https://jisho.org/api/v1/search/words';
const String SCRAPE_BASE_URI = 'https://jisho.org/search/';
const String STROKE_ORDER_DIAGRAM_BASE_URI = 'https://classic.jisho.org/static/images/stroke_diagrams/';

123
lib/src/exampleSearch.dart Normal file
View File

@ -0,0 +1,123 @@
import './baseURI.dart';
import './objects.dart';
import 'package:html/parser.dart';
import 'package:html/dom.dart';
final RegExp kanjiRegex = RegExp(r'[\u4e00-\u9faf\u3400-\u4dbf]');
String uriForExampleSearch(String phrase) {
return '${SCRAPE_BASE_URI}${Uri.encodeComponent(phrase)}%23sentences';
}
List<Element> getChildrenAndSymbols(Element ul) {
final ulText = ul.text;
final ulCharArray = ulText.split('');
final ulChildren = ul.children;
var offsetPointer = 0;
List<Element> result = [];
for (var element in ulChildren) {
if (element.text != ulText.substring(offsetPointer, offsetPointer + element.text.length)){
var symbols = '';
while (element.text.substring(0,1) != ulCharArray[offsetPointer]) {
symbols += ulCharArray[offsetPointer];
offsetPointer++;
}
final symbolElement = Element.html('<span>${symbols}</span>');
result.add(symbolElement);
}
offsetPointer += element.text.length;
result.add(element);
}
if (offsetPointer + 1 != ulText.length){
final symbols = ulText.substring(offsetPointer, ulText.length-1);
final symbolElement = Element.html('<span>${symbols}</span>');
result.add(symbolElement);
}
return result;
}
ExampleResultData getKanjiAndKana(Element div) {
final ul = div.querySelector('ul');
final contents = getChildrenAndSymbols(ul);
var kanji = '';
var kana = '';
for (var i = 0; i < contents.length; i += 1) {
final content = contents[i];
if (content.localName == 'li') {
final li = content;
final furigana = li.querySelector('.furigana')?.text;
final unlifted = li.querySelector('.unlinked')?.text;
if (furigana != null) {
kanji += unlifted;
kana += furigana;
final kanaEnding = [];
for (var j = unlifted.length - 1; j > 0; j -= 1) {
final char = unlifted[j];
if (!kanjiRegex.hasMatch(char)) {
kanaEnding.add(char);
} else {
break;
}
}
kana += kanaEnding.reversed.join('');
} else {
kanji += unlifted;
kana += unlifted;
}
} else {
final text = content.text.trim();
if (text != null) {
kanji += text;
kana += text;
}
}
}
return ExampleResultData(
kanji: kanji,
kana: kana,
);
}
List<ExampleSentencePiece> getPieces(Element sentenceElement) {
final pieceElements = sentenceElement.querySelectorAll('li.clearfix');
final List<ExampleSentencePiece> pieces = [];
for (var pieceIndex = 0; pieceIndex < pieceElements.length; pieceIndex += 1) {
final pieceElement = pieceElements[pieceIndex];
pieces.add(ExampleSentencePiece(
lifted: pieceElement.querySelector('.furigana')?.text,
unlifted: pieceElement.querySelector('.unlinked')?.text,
));
}
return pieces;
}
ExampleResultData parseExampleDiv(Element div) {
final result = getKanjiAndKana(div);
result.english = div.querySelector('.english').text;
result.pieces = getPieces(div) ?? [];
return result;
}
ExampleResults parseExamplePageData(String pageHtml, String phrase) {
final document = parse(pageHtml);
final divs = document.querySelectorAll('.sentence_content');
final results = divs.map((div) => parseExampleDiv(div)).toList();
return ExampleResults(
query: phrase,
found: results.isNotEmpty,
results: results ?? [],
uri: uriForExampleSearch(phrase),
phrase: phrase,
);
}

214
lib/src/kanjiSearch.dart Normal file
View File

@ -0,0 +1,214 @@
import './baseURI.dart';
import './objects.dart';
import 'package:html_unescape/html_unescape.dart' as html_entities;
final htmlUnescape = html_entities.HtmlUnescape();
const String ONYOMI_LOCATOR_SYMBOL = 'On';
const String KUNYOMI_LOCATOR_SYMBOL = 'Kun';
String removeNewlines(String str) {
return str.replaceAll(RegExp(r'(?:\r|\n)') , '').trim();
}
String uriForKanjiSearch(String kanji) {
return '${SCRAPE_BASE_URI}${Uri.encodeComponent(kanji)}%23kanji';
}
String getUriForStrokeOrderDiagram(String kanji) {
return '${STROKE_ORDER_DIAGRAM_BASE_URI}${kanji.codeUnitAt(0)}_frames.png';
}
bool containsKanjiGlyph(String pageHtml, String kanji) {
final kanjiGlyphToken = '<h1 class="character" data-area-name="print" lang="ja">${kanji}</h1>';
return pageHtml.contains(kanjiGlyphToken);
}
String getStringBetweenIndicies(String data, int startIndex, int endIndex) {
final result = data.substring(startIndex, endIndex);
return removeNewlines(result).trim();
}
String getStringBetweenStrings(String data, String startString, String endString) {
final regex = RegExp('${RegExp.escape(startString)}(.*?)${RegExp.escape(endString)}', dotAll: true);
final match = regex.allMatches(data).toList();
return match.isNotEmpty ? match[0].group(1).toString() : null;
}
int getIntBetweenStrings(String pageHtml, String startString, String endString) {
final stringBetweenStrings = getStringBetweenStrings(pageHtml, startString, endString);
return int.parse(stringBetweenStrings);
}
List<String> getAllGlobalGroupMatches(String str, RegExp regex) {
var regexResults = regex.allMatches(str).toList();
List<String> results = [];
for (var match in regexResults) {
results.add(match.group(1));
}
return results;
}
List<String> parseAnchorsToArray(String str) {
final regex = RegExp(r'<a href=".*?">(.*?)<\/a>');
return getAllGlobalGroupMatches(str, regex);
}
List<String> getYomi(String pageHtml, String yomiLocatorSymbol) {
final yomiSection = getStringBetweenStrings(pageHtml, '<dt>${yomiLocatorSymbol}:</dt>', '</dl>');
return parseAnchorsToArray(yomiSection ?? '');
}
List<String> getKunyomi(String pageHtml) {
return getYomi(pageHtml, KUNYOMI_LOCATOR_SYMBOL);
}
List<String> getOnyomi(String pageHtml) {
return getYomi(pageHtml, ONYOMI_LOCATOR_SYMBOL);
}
List<YomiExample> getYomiExamples(String pageHtml, String yomiLocatorSymbol) {
final locatorString = '<h2>${yomiLocatorSymbol} reading compounds</h2>';
final exampleSection = getStringBetweenStrings(pageHtml, locatorString, '</ul>');
if (exampleSection==null) {
return null;
}
final regex = RegExp(r'<li>(.*?)<\/li>', dotAll: true);
final regexResults = getAllGlobalGroupMatches(exampleSection, regex).map((s) => s.trim());
final examples = regexResults.map((regexResult) {
final examplesLines = regexResult.split('\n').map((s) => s.trim()).toList();
return YomiExample(
example: examplesLines[0],
reading: examplesLines[1].replaceAll('', '').replaceAll('', ''),
meaning: htmlUnescape.convert(examplesLines[2]),
);
});
return examples.toList();
}
List<YomiExample> getOnyomiExamples(String pageHtml) {
return getYomiExamples(pageHtml, ONYOMI_LOCATOR_SYMBOL);
}
List<YomiExample> getKunyomiExamples(String pageHtml) {
return getYomiExamples(pageHtml, KUNYOMI_LOCATOR_SYMBOL);
}
Radical getRadical(String pageHtml) {
const radicalMeaningStartString = '<span class="radical_meaning">';
const radicalMeaningEndString = '</span>';
var radicalMeaning = getStringBetweenStrings(
pageHtml,
radicalMeaningStartString,
radicalMeaningEndString,
).trim();
if (radicalMeaning!=null) {
final radicalMeaningStartIndex = pageHtml.indexOf(radicalMeaningStartString);
final radicalMeaningEndIndex = pageHtml.indexOf(
radicalMeaningEndString,
radicalMeaningStartIndex,
);
final radicalSymbolStartIndex = radicalMeaningEndIndex + radicalMeaningEndString.length;
const radicalSymbolEndString = '</span>';
final radicalSymbolEndIndex = pageHtml.indexOf(radicalSymbolEndString, radicalSymbolStartIndex);
final radicalSymbolsString = getStringBetweenIndicies(
pageHtml,
radicalSymbolStartIndex,
radicalSymbolEndIndex,
);
if (radicalSymbolsString.length > 1) {
final radicalForms = radicalSymbolsString
.substring(1)
.replaceAll('(', '')
.replaceAll(')', '')
.trim()
.split(', ');
return Radical(
symbol: radicalSymbolsString[0],
forms: radicalForms ?? [],
meaning: radicalMeaning
);
}
return Radical (
symbol: radicalSymbolsString,
meaning: radicalMeaning
);
}
return null;
}
List<String> getParts(String pageHtml) {
const partsSectionStartString = '<dt>Parts:</dt>';
const partsSectionEndString = '</dl>';
final partsSection = getStringBetweenStrings(
pageHtml,
partsSectionStartString,
partsSectionEndString,
);
var result = parseAnchorsToArray(partsSection);
result.sort();
return (result);
}
String getSvgUri(String pageHtml) {
var svgRegex = RegExp('\/\/.*?.cloudfront.net\/.*?.svg');
final regexResult = svgRegex.firstMatch(pageHtml).group(0).toString();
return regexResult.isNotEmpty ? 'https:${regexResult}' : null;
}
String getGifUri(String kanji) {
final unicodeString = kanji.codeUnitAt(0).toRadixString(16);
final fileName = '${unicodeString}.gif';
final animationUri = 'https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/${fileName}';
return animationUri;
}
int getNewspaperFrequencyRank(String pageHtml) {
final frequencySection = getStringBetweenStrings(pageHtml, '<div class="frequency">', '</div>');
return (frequencySection != null) ? int.parse(getStringBetweenStrings(frequencySection, '<strong>', '</strong>')) : null;
}
KanjiResult parseKanjiPageData(String pageHtml, String kanji) {
final result = KanjiResult();
result.query = kanji;
result.found = containsKanjiGlyph(pageHtml, kanji);
if (result.found==false) {
return result;
}
result.taughtIn = getStringBetweenStrings(pageHtml, 'taught in <strong>', '</strong>');
result.jlptLevel = getStringBetweenStrings(pageHtml, 'JLPT level <strong>', '</strong>');
result.newspaperFrequencyRank = getNewspaperFrequencyRank(pageHtml);
result.strokeCount = getIntBetweenStrings(pageHtml, '<strong>', '</strong> strokes');
result.meaning = htmlUnescape.convert(removeNewlines(getStringBetweenStrings(pageHtml, '<div class="kanji-details__main-meanings">', '</div>')).trim());
result.kunyomi = getKunyomi(pageHtml) ?? [];
result.onyomi = getOnyomi(pageHtml) ?? [];
result.onyomiExamples = getOnyomiExamples(pageHtml) ?? [];
result.kunyomiExamples = getKunyomiExamples(pageHtml) ?? [];
result.radical = getRadical(pageHtml);
result.parts = getParts(pageHtml) ?? [];
result.strokeOrderDiagramUri = getUriForStrokeOrderDiagram(kanji);
result.strokeOrderSvgUri = getSvgUri(pageHtml);
result.strokeOrderGifUri = getGifUri(kanji);
result.uri = uriForKanjiSearch(kanji);
return result;
}

View File

@ -1,80 +1,17 @@
class PhraseScrapeSentence {
String english;
String japanese;
List<ExampleSentencePiece> pieces;
PhraseScrapeSentence ({String english, String japanese, List<ExampleSentencePiece> pieces}){
this.english = english;
this.japanese = japanese;
this.pieces = pieces;
}
}
class PhraseScrapeMeaning {
List<String> seeAlsoTerms;
List<PhraseScrapeSentence> sentences;
String definition;
List<String> supplemental;
String definitionAbstract;
List<String> tags;
PhraseScrapeMeaning({
List<String> seeAlsoTerms,
List<PhraseScrapeSentence> sentences,
String definition,
List<String> supplemental,
String definitionAbstract,
List<String> tags,
}){
this.seeAlsoTerms = seeAlsoTerms;
this.sentences = sentences;
this.definition = definition;
this.supplemental = supplemental;
this.definitionAbstract = definitionAbstract;
this.tags = tags;
}
}
class PhrasePageScrapeResult {
bool found;
String query;
String uri;
List<String> otherForms;
List<PhraseScrapeMeaning> meanings;
List<String> tags;
List<String> notes;
PhrasePageScrapeResult({
bool found,
String query,
String uri,
List<String> otherForms,
List<PhraseScrapeMeaning> meanings,
List<String> tags,
List<String> notes,
}){
this.found = found;
this.query = query;
this.uri = uri;
this.otherForms = otherForms;
this.meanings = meanings;
this.tags = tags;
this.notes = notes;
}
}
/* -------------------------------------------------------------------------- */
/* searchForKanji related classes */
/* -------------------------------------------------------------------------- */
class YomiExample {
String example;
String reading;
String meaning;
YomiExample({String example, String reading, String meaning})
{
this.example = example;
this.reading = reading;
this.meaning = meaning;
}
YomiExample({
this.example,
this.reading,
this.meaning
});
Map<String, String> toJson() =>
{
@ -90,17 +27,17 @@ class Radical {
List<String> forms;
String meaning;
Radical({String symbol, List<String> forms, String meaning}){
this.symbol = symbol;
this.forms = forms;
this.meaning = meaning;
}
Radical({
this.symbol,
this.forms,
this.meaning
});
Map<String, dynamic> toJson() =>
{
if (symbol != null) 'symbol': symbol,
if (forms != null) 'forms': forms,
if (meaning != null) 'meaning': meaning
'symbol': symbol,
'forms': forms,
'meaning': meaning
};
}
@ -125,26 +62,40 @@ class KanjiResult {
String strokeOrderGifUri;
String uri;
KanjiResult({
this.query,
this.found,
this.taughtIn,
this.jlptLevel,
this.newspaperFrequencyRank,
this.strokeCount,
this.meaning,
this.kunyomi,
this.onyomi,
this.kunyomiExamples,
this.onyomiExamples,
this.radical,
this.parts,
this.strokeOrderDiagramUri,
this.strokeOrderSvgUri,
this.strokeOrderGifUri,
this.uri
});
Map<String, dynamic> toJson() {
if (found == false) return {
'query': query,
'found': found
};
return {
'query': query,
'found': found,
'taughtIn': taughtIn,
'jlptLevel': jlptLevel,
'newspaperFrequencyRank': newspaperFrequencyRank.toString(),
'newspaperFrequencyRank': newspaperFrequencyRank,
'strokeCount': strokeCount,
'meaning': meaning,
'kunyomi': kunyomi,
'onyomi': onyomi,
'onyomiExamples': onyomiExamples.map((onyomiExample) => onyomiExample.toJson()).toList(),
'kunyomiExamples': kunyomiExamples.map((kunyomiExample) => kunyomiExample.toJson()).toList(),
'radical': radical.toJson(),
'onyomiExamples': onyomiExamples,
'kunyomiExamples': kunyomiExamples,
'radical': (radical != null) ? radical.toJson() : null,
'parts': parts,
'strokeOrderDiagramUri': strokeOrderDiagramUri,
'strokeOrderSvgUri': strokeOrderSvgUri,
@ -154,13 +105,24 @@ class KanjiResult {
}
}
class ExampleSentencePiece {
String unlifted;
String lifted;
/* -------------------------------------------------------------------------- */
/* searchForExamples related classes */
/* -------------------------------------------------------------------------- */
ExampleSentencePiece({String unlifted, String lifted}){
this.unlifted = unlifted;
this.lifted = lifted;
class ExampleSentencePiece {
String lifted;
String unlifted;
ExampleSentencePiece({
this.lifted,
this.unlifted
});
Map<String, dynamic> toJson() {
return {
'lifted': lifted,
'unlifted': unlifted
};
}
}
@ -170,11 +132,20 @@ class ExampleResultData {
String english;
List<ExampleSentencePiece> pieces;
ExampleResultData({String kanji, String kana, String english, List<ExampleSentencePiece> pieces}){
this.kanji = kanji;
this.kana = kana;
this.english = english;
this.pieces = pieces;
ExampleResultData({
this.english,
this.kanji,
this.kana,
this.pieces
});
Map<String, dynamic> toJson() {
return {
'english': english,
'kanji': kanji,
'kana': kana,
'pieces': pieces
};
}
}
@ -185,15 +156,310 @@ class ExampleResults {
List<ExampleResultData> results;
String phrase;
ExampleResults({String query, bool found, String uri, List<ExampleResultData> results, String phrase}){
this.query = query;
this.found = found;
this.uri = uri;
this.results = results;
this.phrase = phrase;
ExampleResults({
this.query,
this.found,
this.results,
this.uri,
this.phrase
});
Map<String, dynamic> toJson() {
return {
'query': query,
'found': found,
'results': results,
'uri': uri,
'phrase': phrase
};
}
}
/* -------------------------------------------------------------------------- */
/* scrapeForPhrase related classes */
/* -------------------------------------------------------------------------- */
class PhraseScrapeSentence {
String english;
String japanese;
List<ExampleSentencePiece> pieces;
PhraseScrapeSentence ({
this.english,
this.japanese,
this.pieces
});
Map<String, dynamic> toJson() => {
'english': english,
'japanese': japanese,
'pieces': pieces
};
}
class PhraseScrapeMeaning {
List<String> seeAlsoTerms;
List<PhraseScrapeSentence> sentences;
String definition;
List<String> supplemental;
String definitionAbstract;
List<String> tags;
PhraseScrapeMeaning({
this.seeAlsoTerms,
this.sentences,
this.definition,
this.supplemental,
this.definitionAbstract,
this.tags
});
Map<String, dynamic> toJson() => {
'seeAlsoTerms': seeAlsoTerms,
'sentences': sentences,
'definition': definition,
'supplemental': supplemental,
'definitionAbstract': definitionAbstract,
'tags': tags
};
}
class KanjiKanaPair {
String kanji;
String kana;
KanjiKanaPair({
this.kanji,
this.kana
});
Map<String, String> toJson() => {
'kanji': kanji,
'kana': kana
};
}
class PhrasePageScrapeResult {
bool found;
String query;
String uri;
List<String> tags;
List<PhraseScrapeMeaning> meanings;
List<KanjiKanaPair> otherForms;
List<String> notes;
PhrasePageScrapeResult({
this.found,
this.query,
this.uri,
this.tags,
this.meanings,
this.otherForms,
this.notes
});
Map<String, dynamic> toJson() => {
'found': found,
'query': query,
'uri': uri,
'tags': tags,
'meanings': meanings,
'otherForms': otherForms,
'notes': notes
};
}
/* -------------------------------------------------------------------------- */
/* searchForPhrase related classes */
/* -------------------------------------------------------------------------- */
class JishoJapaneseWord {
String word;
String reading;
JishoJapaneseWord({this.word, this.reading});
factory JishoJapaneseWord.fromJson(Map<String, dynamic> json){
return JishoJapaneseWord(
word: json['word'] as String,
reading: json['reading'] as String
);
}
Map<String, dynamic> toJson() => {
'word': word,
'reading': reading
};
}
class JishoSenseLink {
String text;
String url;
JishoSenseLink({this.text, this.url});
factory JishoSenseLink.fromJson(Map<String, dynamic> json){
return JishoSenseLink(
text: json['text'] as String,
url: json['url'] as String
);
}
Map<String, dynamic> toJson() => {
'text': text,
'url': url
};
}
class JishoWordSense {
List<String> english_definitions;
List<String> parts_of_speech;
List<JishoSenseLink> links;
List<String> tags;
List<String> see_also;
List<String> antonyms;
List<dynamic> source;
List<String> info;
List<dynamic> restrictions;
JishoWordSense({
this.english_definitions,
this.parts_of_speech,
this.links,
this.tags,
this.see_also,
this.antonyms,
this.source,
this.info,
this.restrictions
});
factory JishoWordSense.fromJson(Map<String, dynamic> json){
return JishoWordSense(
english_definitions: (json['english_definitions'] as List).map((result) => result as String).toList(),
parts_of_speech: (json['parts_of_speech'] as List).map((result) => result as String).toList(),
links: (json['links'] as List).map((result) => JishoSenseLink.fromJson(result)).toList(),
tags: (json['tags'] as List).map((result) => result as String).toList(),
see_also: (json['see_also'] as List).map((result) => result as String).toList(),
antonyms: (json['antonyms'] as List).map((result) => result as String).toList(),
source: json['source'] as List<dynamic>,
info: (json['info'] as List).map((result) => result as String).toList(),
restrictions: json['restrictions'] as List<dynamic>
);
}
Map<String, dynamic> toJson() => {
'english_definitions': english_definitions,
'parts_of_speech': parts_of_speech,
'links': links,
'tags': tags,
'see_also': see_also,
'antonyms': antonyms,
'source': source,
'info': info,
'restrictions': restrictions
};
}
class JishoAttribution {
bool jmdict;
bool jmnedict;
String dbpedia;
JishoAttribution({
this.jmdict,
this.jmnedict,
this.dbpedia
});
factory JishoAttribution.fromJson(Map<String, dynamic> json){
return JishoAttribution(
jmdict: (json['jmdict'].toString() == 'true'),
jmnedict: (json['jmnedict'].toString() == 'true'),
dbpedia: (json['dbpedia'].toString() != 'false') ? json['dbpedia'].toString() : null
);
}
Map<String, dynamic> toJson() => {
'jmdict': jmdict,
'jmnedict': jmnedict,
'dbpedia': dbpedia
};
}
class JishoResult {
String slug;
bool is_common;
List<String> tags;
List<String> jlpt;
List<JishoJapaneseWord> japanese;
List<JishoWordSense> senses;
JishoAttribution attribution;
JishoResult({
this.slug,
this.is_common,
this.tags,
this.jlpt,
this.japanese,
this.senses,
this.attribution
});
factory JishoResult.fromJson(Map<String, dynamic> json){
return JishoResult(
slug: json['slug'] as String,
is_common: json['is_common'] as bool,
tags: (json['tags'] as List).map((result) => result as String).toList(),
jlpt: (json['jlpt'] as List).map((result) => result as String).toList(),
japanese: (json['japanese'] as List).map((result) => JishoJapaneseWord.fromJson(result)).toList(),
senses: (json['senses'] as List).map((result) => JishoWordSense.fromJson(result)).toList(),
attribution: JishoAttribution.fromJson(json['attribution'])
);
}
Map<String, dynamic> toJson() => {
'slug': slug,
'is_common': is_common,
'tags': tags,
'jlpt': jlpt,
'japanese': japanese,
'senses': senses,
'attribution': attribution
};
}
class JishoResultMeta {
int status;
JishoResultMeta({this.status});
factory JishoResultMeta.fromJson(Map<String, dynamic> json){
return JishoResultMeta(
status: json['status'] as int
);
}
Map<String, dynamic> toJson() => {
'status': status
};
}
class JishoAPIResult {
JishoResultMeta meta;
List<JishoResult> data;
JishoAPIResult({this.meta, this.data});
factory JishoAPIResult.fromJson(Map<String, dynamic> json){
return JishoAPIResult(
meta: JishoResultMeta.fromJson(json['meta']),
data: (json['data'] as List).map((result) => JishoResult.fromJson(result)).toList()
);
}
Map<String, dynamic> toJson() => {
'meta': meta.toJson(),
'data': data
};
}

154
lib/src/phraseScrape.dart Normal file
View File

@ -0,0 +1,154 @@
import './objects.dart';
import './exampleSearch.dart';
import 'package:html/parser.dart';
import 'package:html/dom.dart';
List<String> getTags(Document document) {
final List<String> tags = [];
final tagElements = document.querySelectorAll('.concept_light-tag');
for (var i = 0; i < tagElements.length; i += 1) {
final tagText = tagElements[i].text;
tags.add(tagText);
}
return tags;
}
List<String> getMostRecentWordTypes(Element child) {
return child.text.split(',').map((s) => s.trim().toLowerCase()).toList();
}
List<KanjiKanaPair> getOtherForms(Element child) {
return child.text.split('')
.map((s) => s.replaceAll('', '').replaceAll('', '').split(' '))
.map((a) => (KanjiKanaPair( kanji: a[0], kana: (a.length == 2) ? a[1] : null ))).toList();
}
List<String> getNotes(Element child) => child.text.split('\n');
String getMeaning(Element child) => child.querySelector('.meaning-meaning').text;
String getMeaningAbstract(Element child) {
final meaningAbstract = child.querySelector('.meaning-abstract');
if (meaningAbstract == null) return null;
for (var element in meaningAbstract.querySelectorAll('a')) {
element.remove();
}
return child.querySelector('.meaning-abstract')?.text;
}
List<String> getSupplemental(Element child) {
final supplemental = child.querySelector('.supplemental_info');
if (supplemental == null) return [];
return supplemental.text.split(',').map((s) => s.trim()).toList();
}
List<String> getSeeAlsoTerms(List<String> supplemental) {
if (supplemental == null) return [];
final List<String> seeAlsoTerms = [];
for (var i = supplemental.length - 1; i >= 0; i -= 1) {
final supplementalEntry = supplemental[i];
if (supplementalEntry.startsWith('See also')) {
seeAlsoTerms.add(supplementalEntry.replaceAll('See also ', ''));
supplemental.removeAt(i);
}
}
return seeAlsoTerms;
}
List<PhraseScrapeSentence> getSentences(Element child) {
final sentenceElements = child.querySelector('.sentences')?.querySelectorAll('.sentence');
if (sentenceElements == null) return [];
final List<PhraseScrapeSentence> sentences = [];
for (var sentenceIndex = 0; sentenceIndex < (sentenceElements?.length ?? 0); sentenceIndex += 1) {
final sentenceElement = sentenceElements[sentenceIndex];
final english = sentenceElement.querySelector('.english').text;
final pieces = getPieces(sentenceElement);
sentenceElement.querySelector('.english').remove();
for (var element in sentenceElement.children[0].children) {
element.querySelector('.furigana')?.remove();
}
final japanese = sentenceElement.text;
sentences.add(
PhraseScrapeSentence(
english: english,
japanese: japanese,
pieces: pieces ?? []
)
);
}
return sentences;
}
PhrasePageScrapeResult getMeaningsOtherFormsAndNotes(Document document) {
final returnValues = PhrasePageScrapeResult( otherForms: [], notes: [] );
final meaningsWrapper = document.querySelector('.meanings-wrapper');
if (meaningsWrapper == null) return PhrasePageScrapeResult(found: false);
returnValues.found = true;
final meaningsChildren = meaningsWrapper.children;
final List<PhraseScrapeMeaning> meanings = [];
var mostRecentWordTypes = [];
for (var meaningIndex = 0; meaningIndex < meaningsChildren.length; meaningIndex += 1) {
final child = meaningsChildren[meaningIndex];
if (child.className.contains('meaning-tags')) {
mostRecentWordTypes = getMostRecentWordTypes(child);
} else if (mostRecentWordTypes[0] == 'other forms') {
returnValues.otherForms = getOtherForms(child);
} else if (mostRecentWordTypes[0] == 'notes') {
returnValues.notes = getNotes(child);
} else {
final meaning = getMeaning(child);
final meaningAbstract = getMeaningAbstract(child);
final supplemental = getSupplemental(child);
final seeAlsoTerms = getSeeAlsoTerms(supplemental);
final sentences = getSentences(child);
meanings.add(PhraseScrapeMeaning(
seeAlsoTerms: seeAlsoTerms ?? [],
sentences: sentences ?? [],
definition: meaning,
supplemental: supplemental ?? [],
definitionAbstract: meaningAbstract,
tags: mostRecentWordTypes ?? [],
));
}
}
returnValues.meanings = meanings;
return returnValues;
}
String uriForPhraseScrape(String searchTerm) {
return 'https://jisho.org/word/${Uri.encodeComponent(searchTerm)}';
}
PhrasePageScrapeResult parsePhrasePageData(String pageHtml, String query) {
final document = parse(pageHtml);
final result = getMeaningsOtherFormsAndNotes(document);
result.query = query;
if (!result.found) return result;
result.uri = uriForPhraseScrape(query);
result.tags = getTags(document);
return result;
}

View File

@ -0,0 +1,5 @@
import './baseURI.dart';
String uriForPhraseSearch(String phrase) {
return '${JISHO_API}?keyword=${Uri.encodeComponent(phrase)}';
}

View File

@ -1,437 +1,12 @@
import 'package:unofficial_jisho_api/src/objects.dart';
import './objects.dart';
import 'package:http/http.dart' as http;
import 'package:xml/xml.dart' as xml;
import 'package:html_unescape/html_unescape.dart' as html_entities;
import 'dart:convert';
final htmlUnescape = html_entities.HtmlUnescape();
import './phraseSearch.dart';
import './kanjiSearch.dart';
import './exampleSearch.dart';
import './phraseScrape.dart';
// TODO: Put public facing types in this file.
const String JISHO_API = 'http://jisho.org/api/v1/search/words';
const String SCRAPE_BASE_URI = 'http://jisho.org/search/';
const String STROKE_ORDER_DIAGRAM_BASE_URI = 'http://classic.jisho.org/static/images/stroke_diagrams/';
/* KANJI SEARCH FUNCTIONS START */
const String ONYOMI_LOCATOR_SYMBOL = 'On';
const KUNYOMI_LOCATOR_SYMBOL = 'Kun';
String removeNewlines(String str) {
return str.replaceAll(RegExp(r'(?:\r|\n)') , '').trim();
}
String uriForKanjiSearch(String kanji) {
return '${SCRAPE_BASE_URI}${Uri.encodeComponent(kanji)}%23kanji';
}
String getUriForStrokeOrderDiagram(String kanji) {
return '${STROKE_ORDER_DIAGRAM_BASE_URI}${kanji.codeUnitAt(0)}_frames.png';
}
String uriForPhraseSearch(String phrase) {
return '${JISHO_API}?keyword=${Uri.encodeComponent(phrase)}';
}
bool containsKanjiGlyph(String pageHtml, String kanji) {
final kanjiGlyphToken = '<h1 class="character" data-area-name="print" lang="ja">${kanji}</h1>';
return pageHtml.contains(kanjiGlyphToken);
}
String getStringBetweenIndicies(String data, int startIndex, int endIndex) {
final result = data.substring(startIndex, endIndex);
return removeNewlines(result).trim();
}
String getStringBetweenStrings(String data, String startString, String endString) {
final regex = RegExp('${RegExp.escape(startString)}(.*?)${RegExp.escape(endString)}', dotAll: true);
final match = regex.allMatches(data).toList(); //TODO: Something wrong here
return match.isNotEmpty ? match[0].group(1).toString() : null;
}
int getIntBetweenStrings(String pageHtml, String startString, String endString) {
final stringBetweenStrings = getStringBetweenStrings(pageHtml, startString, endString);
return int.parse(stringBetweenStrings);
}
List<String> getAllGlobalGroupMatches(String str, RegExp regex) {
var regexResults = regex.allMatches(str).toList();
List<String> results = [];
for (var match in regexResults) {
results.add(match.group(1));
}
return results;
}
List<String> parseAnchorsToArray(String str) {
final regex = RegExp(r'<a href=".*?">(.*?)<\/a>');
return getAllGlobalGroupMatches(str, regex);
}
List<String> getYomi(String pageHtml, String yomiLocatorSymbol) {
final yomiSection = getStringBetweenStrings(pageHtml, '<dt>${yomiLocatorSymbol}:</dt>', '</dl>');
return parseAnchorsToArray(yomiSection ?? '');
}
List<String> getKunyomi(String pageHtml) {
return getYomi(pageHtml, KUNYOMI_LOCATOR_SYMBOL);
}
List<String> getOnyomi(String pageHtml) {
return getYomi(pageHtml, ONYOMI_LOCATOR_SYMBOL);
}
List<YomiExample> getYomiExamples(String pageHtml, String yomiLocatorSymbol) {
final locatorString = '<h2>${yomiLocatorSymbol} reading compounds</h2>';
final exampleSection = getStringBetweenStrings(pageHtml, locatorString, '</ul>');
if (exampleSection==null) {
return null;
}
final regex = RegExp(r'<li>(.*?)<\/li>', dotAll: true);
final regexResults = getAllGlobalGroupMatches(exampleSection, regex).map((s) => s.trim());
final examples = regexResults.map((regexResult) {
final examplesLines = regexResult.split('\n').map((s) => s.trim()).toList();
return YomiExample(
example: examplesLines[0],
reading: examplesLines[1].replaceAll('', '').replaceAll('', ''),
meaning: htmlUnescape.convert(examplesLines[2]),
);
});
return examples.toList();
}
List<YomiExample> getOnyomiExamples(String pageHtml) {
return getYomiExamples(pageHtml, ONYOMI_LOCATOR_SYMBOL);
}
List<YomiExample> getKunyomiExamples(String pageHtml) {
return getYomiExamples(pageHtml, KUNYOMI_LOCATOR_SYMBOL);
}
Radical getRadical(String pageHtml) {
const radicalMeaningStartString = '<span class="radical_meaning">';
const radicalMeaningEndString = '</span>';
var radicalMeaning = getStringBetweenStrings(
pageHtml,
radicalMeaningStartString,
radicalMeaningEndString,
).trim();
if (radicalMeaning!=null) {
final radicalMeaningStartIndex = pageHtml.indexOf(radicalMeaningStartString);
final radicalMeaningEndIndex = pageHtml.indexOf(
radicalMeaningEndString,
radicalMeaningStartIndex,
);
final radicalSymbolStartIndex = radicalMeaningEndIndex + radicalMeaningEndString.length;
const radicalSymbolEndString = '</span>';
final radicalSymbolEndIndex = pageHtml.indexOf(radicalSymbolEndString, radicalSymbolStartIndex);
final radicalSymbolsString = getStringBetweenIndicies(
pageHtml,
radicalSymbolStartIndex,
radicalSymbolEndIndex,
);
if (radicalSymbolsString.length > 1) {
final radicalForms = radicalSymbolsString
.substring(1)
.replaceAll('(', '')
.replaceAll(')', '')
.trim()
.split(', ');
return Radical(
symbol: radicalSymbolsString[0],
forms: radicalForms,
meaning: radicalMeaning
);
}
return Radical (
symbol: radicalSymbolsString,
meaning: radicalMeaning
);
}
return null;
}
List<String> getParts(String pageHtml) {
const partsSectionStartString = '<dt>Parts:</dt>';
const partsSectionEndString = '</dl>';
final partsSection = getStringBetweenStrings(
pageHtml,
partsSectionStartString,
partsSectionEndString,
);
var result = parseAnchorsToArray(partsSection);
result.sort();
return (result);
}
String getSvgUri(String pageHtml) {
var svgRegex = RegExp('\/\/.*?.cloudfront.net\/.*?.svg');
final regexResult = svgRegex.firstMatch(pageHtml).group(0).toString();
return regexResult.isNotEmpty ? 'http:${regexResult}' : null;
}
String getGifUri(String kanji) {
final unicodeString = kanji.codeUnitAt(0).toRadixString(16);
final fileName = '${unicodeString}.gif';
final animationUri = 'https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/${fileName}';
return animationUri;
}
int getNewspaperFrequencyRank(String pageHtml) {
final frequencySection = getStringBetweenStrings(pageHtml, '<div class="frequency">', '</div>');
return frequencySection.isNotEmpty ? int.parse(getStringBetweenStrings(frequencySection, '<strong>', '</strong>')) : null;
}
KanjiResult parseKanjiPageData(String pageHtml, String kanji) {
final result = KanjiResult();
result.query = kanji;
result.found = containsKanjiGlyph(pageHtml, kanji);
if (result.found==false) {
return result;
}
result.taughtIn = getStringBetweenStrings(pageHtml, 'taught in <strong>', '</strong>');
result.jlptLevel = getStringBetweenStrings(pageHtml, 'JLPT level <strong>', '</strong>');
result.newspaperFrequencyRank = getNewspaperFrequencyRank(pageHtml);
result.strokeCount = getIntBetweenStrings(pageHtml, '<strong>', '</strong> strokes');
result.meaning = htmlUnescape.convert(removeNewlines(getStringBetweenStrings(pageHtml, '<div class="kanji-details__main-meanings">', '</div>')).trim());
result.kunyomi = getKunyomi(pageHtml);
result.onyomi = getOnyomi(pageHtml);
result.onyomiExamples = getOnyomiExamples(pageHtml);
result.kunyomiExamples = getKunyomiExamples(pageHtml);
result.radical = getRadical(pageHtml);
result.parts = getParts(pageHtml);
result.strokeOrderDiagramUri = getUriForStrokeOrderDiagram(kanji);
result.strokeOrderSvgUri = getSvgUri(pageHtml);
result.strokeOrderGifUri = getGifUri(kanji);
result.uri = uriForKanjiSearch(kanji);
return result;
}
/* KANJI SEARCH FUNCTIONS END */
/* EXAMPLE SEARCH FUNCTIONS START */
RegExp kanjiRegex = RegExp(r'[\u4e00-\u9faf\u3400-\u4dbf]');
String uriForExampleSearch(String phrase) {
return '${SCRAPE_BASE_URI}${Uri.encodeComponent(phrase)}%23sentences';
}
ExampleResultData getKanjiAndKana(div) {
final ul = div.find('ul').eq(0);
final contents = ul.contents();
var kanji = '';
var kana = '';
for (var i = 0; i < contents.length; i += 1) {
final content = contents.eq(i);
if (content[0].name == 'li') {
final li = content;
final furigana = li.find('.furigana').text();
final unlifted = li.find('.unlinked').text();
if (furigana) {
kanji += unlifted;
kana += furigana;
final kanaEnding = [];
for (var j = unlifted.length - 1; j > 0; j -= 1) {
if (!unlifted[j].match(kanjiRegex)) {
kanaEnding.add(unlifted[j]);
} else {
break;
}
}
kana += kanaEnding.reversed.join('');
} else {
kanji += unlifted;
kana += unlifted;
}
} else {
final text = content.text().trim();
if (text) {
kanji += text;
kana += text;
}
}
}
return ExampleResultData(
kanji: kanji,
kana: kana,
);
}
List<ExampleSentencePiece> getPieces(sentenceElement) {
final pieceElements = sentenceElement.find('li.clearfix');
final pieces = [];
for (var pieceIndex = 0; pieceIndex < pieceElements.length; pieceIndex += 1) {
final pieceElement = pieceElements.eq(pieceIndex);
pieces.add(ExampleSentencePiece(
lifted: pieceElement.children('.furigana').text(),
unlifted: pieceElement.children('.unlinked').text(),
));
}
return pieces;
}
ExampleResultData parseExampleDiv(div) {
final result = getKanjiAndKana(div);
result.english = div.find('.english').text();
result.pieces = getPieces(div);
return result;
}
ExampleResults parseExamplePageData(String pageHtml, String phrase) {
final document = xml.parse(pageHtml);
final divs = document.descendants.where((node) => node.attributes[0].value == 'sentence_content').toList();
final results = divs.map((div) => parseExampleDiv(div));
return ExampleResults(
query: phrase,
found: results.isNotEmpty,
results: results,
uri: uriForExampleSearch(phrase),
phrase: phrase,
);
}
/* EXAMPLE SEARCH FUNCTIONS END */
/* PHRASE SCRAPE FUNCTIONS START */
List<String> getTags(document) {
final tags = [];
final tagElements = document.descendants.where((node) => node.attributes[0].value == 'concept_light-tag').toList();
for (var i = 0; i < tagElements.length; i += 1) {
final tagText = tagElements.eq(i).text();
tags.add(tagText);
}
return tags;
}
PhrasePageScrapeResult getMeaningsOtherFormsAndNotes(document) {
final returnValues = PhrasePageScrapeResult( otherForms: [], notes: [] );
//TODO: Fix
// const meaningsWrapper = $('#page_container > div > div > article > div > div.concept_light-meanings.medium-9.columns > div');
final meaningsWrapper = document.descendants.where((node) => node.attributes[0].value == 'page_container').toList();
final meaningsChildren = meaningsWrapper.children();
final meanings = [];
var mostRecentWordTypes = [];
for (var meaningIndex = 0; meaningIndex < meaningsChildren.length; meaningIndex += 1) {
final child = meaningsChildren.eq(meaningIndex);
if (child.hasClass('meaning-tags')) {
mostRecentWordTypes = child.text().split(',').map((s) => s.trim().toLowerCase());
} else if (mostRecentWordTypes[0] == 'other forms') {
returnValues.otherForms = child.text().split('')
.map((s) => s.replaceAll('', '').replaceAll('', '').split(' '))
.map((a) => (ExampleResultData( kanji: a[0], kana: a[1] )));
} else if (mostRecentWordTypes[0] == 'notes') {
returnValues.notes = child.text().split('\n');
} else {
final meaning = child.find('.meaning-meaning').text();
final meaningAbstract = child.find('.meaning-abstract')
.find('a')
.remove()
.end()
.text();
final supplemental = child.find('.supplemental_info').text().split(',')
.map((s) => s.trim())
.filter((s) => s);
final seeAlsoTerms = [];
for (var i = supplemental.length - 1; i >= 0; i -= 1) {
final supplementalEntry = supplemental[i];
if (supplementalEntry.startsWith('See also')) {
seeAlsoTerms.add(supplementalEntry.replaceAll('See also ', ''));
supplemental.splice(i, 1);
}
}
final sentences = [];
final sentenceElements = child.find('.sentences').children('.sentence');
for (var sentenceIndex = 0; sentenceIndex < sentenceElements.length; sentenceIndex += 1) {
final sentenceElement = sentenceElements.eq(sentenceIndex);
final english = sentenceElement.find('.english').text();
final pieces = getPieces(sentenceElement);
final japanese = sentenceElement
.find('.english').remove().end()
.find('.furigana')
.remove()
.end()
.text();
sentences.add(PhraseScrapeSentence(english: english, japanese: japanese, pieces: pieces));
}
meanings.add(PhraseScrapeMeaning(
seeAlsoTerms: seeAlsoTerms,
sentences: sentences,
definition: meaning,
supplemental: supplemental,
definitionAbstract: meaningAbstract,
tags: mostRecentWordTypes,
));
}
}
returnValues.meanings = meanings;
return returnValues;
}
String uriForPhraseScrape(searchTerm) {
return 'https://jisho.org/word/${Uri.encodeComponent(searchTerm)}';
}
PhrasePageScrapeResult parsePhrasePageData(pageHtml, query) {
final document = xml.parse(pageHtml);
final result = getMeaningsOtherFormsAndNotes(document);
result.found = true;
result.query = query;
result.uri = uriForPhraseScrape(query);
result.tags = getTags(document);
// result.meanings = meanings;
// result.otherForms = forms;
// result.notes = notes;
return result;
}
class JishoApi {
@ -443,9 +18,27 @@ class JishoApi {
/// @returns {Object} The response data from the official Jisho.org API. Its format is somewhat
/// complex and is not documented, so put on your trial-and-error hat.
/// @async
searchForPhrase(String phrase) {
Future<JishoAPIResult> searchForPhrase(String phrase) async {
final uri = uriForPhraseSearch(phrase);
return http.get(uri).then((response) => jsonDecode(response.body).data);
return await http.get(uri).then((response) => JishoAPIResult.fromJson(jsonDecode(response.body)));
}
/// Scrape Jisho.org for information about a kanji character.
/// @param {string} kanji The kanji to search for.
/// @returns {KanjiResult} Information about the searched kanji.
/// @async
Future<KanjiResult> searchForKanji(String kanji) async {
final uri = uriForKanjiSearch(kanji);
return http.get(uri).then((response) => parseKanjiPageData(response.body, kanji));
}
/// Scrape Jisho.org for examples.
/// @param {string} phrase The word or phrase to search for.
/// @returns {ExampleResults}
/// @async
Future<ExampleResults> searchForExamples(String phrase) async {
final uri = uriForExampleSearch(phrase);
return http.get(uri).then((response) => parseExamplePageData(response.body, phrase));
}
/// Scrape the word page for a word/phrase.
@ -464,32 +57,14 @@ class JishoApi {
final response = await http.get(uri);
return parsePhrasePageData(response.body, phrase);
} catch (err) {
if (err.response.status == 404) {
return PhrasePageScrapeResult(
query: phrase,
found: false,
);
}
// if (response.statusCode == 404) {
// return PhrasePageScrapeResult(
// query: phrase,
// found: false,
// );
// }
throw err;
}
}
/// Scrape Jisho.org for information about a kanji character.
/// @param {string} kanji The kanji to search for.
/// @returns {KanjiResult} Information about the searched kanji.
/// @async
Future<KanjiResult> searchForKanji(String kanji) {
final uri = uriForKanjiSearch(kanji);
return http.get(uri).then((response) => parseKanjiPageData(response.body, kanji));
}
/// Scrape Jisho.org for examples.
/// @param {string} phrase The word or phrase to search for.
/// @returns {ExampleResults}
/// @async
Future<ExampleResults> searchForExamples(String phrase) {
final uri = uriForExampleSearch(phrase);
return http.get(uri).then((response) => parseExamplePageData(response.body, phrase));
}
}

View File

@ -10,6 +10,7 @@ dependencies:
# path: ^1.6.0
xml: ^3.7.0
html_unescape: ^1.0.1+3
html: ^0.14.0+3
dev_dependencies:
pedantic: ^1.8.0

View File

@ -0,0 +1,31 @@
import 'dart:io';
import 'package:path/path.dart' as path;
import 'dart:convert';
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
final jisho = JishoApi();
final encoder = JsonEncoder.withIndent(' ');
final currentdir = Directory.current.path;
void writeCases(Function apiFunction, String folderName, List<String> queries) async {
final dir = path.join(currentdir, 'test', folderName);
for (var testCount = 0; testCount < queries.length; testCount++) {
final result = await apiFunction(queries[testCount]);
final content = encoder.convert(result);
final filePath = path.join(dir, '${testCount}.json');
await File(filePath).writeAsString(content);
}
}
const kanjiQueries = ['', '', '', '極上', '', 'ネガティブ', 'wegmwrlgkrgmg', ''];
const exampleQueries = ['', '日本人', '彼*叩く', '', 'ネガティブ', 'grlgmregmneriireg'];
const phraseQueries = ['', '日本人', '', 'ネガティブ', 'grlgmregmneriireg'];
void main() async {
await writeCases(jisho.searchForKanji, 'kanji_test_cases', kanjiQueries);
await writeCases(jisho.searchForExamples, 'example_test_cases', exampleQueries);
await writeCases(jisho.scrapeForPhrase, 'phrase_scrape_test_cases', phraseQueries);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,7 @@
{
"query": "彼*叩く",
"expectedResult": {
"query": "彼*叩く",
"found": false,
"results": [],
"uri": "http://jisho.org/search/%E5%BD%BC%EF%BC%8A%E5%8F%A9%E3%81%8F%23sentences",
"phrase": "彼*叩く"
}
"found": false,
"results": [],
"uri": "https://jisho.org/search/%E5%BD%BC%EF%BC%8A%E5%8F%A9%E3%81%8F%23sentences",
"phrase": "彼*叩く"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,7 @@
{
"query": "ネガティブ",
"expectedResult": {
"query": "ネガティブ",
"found": false,
"results": [],
"uri": "http://jisho.org/search/%E3%83%8D%E3%82%AC%E3%83%86%E3%82%A3%E3%83%96%23sentences",
"phrase": "ネガティブ"
}
"found": false,
"results": [],
"uri": "https://jisho.org/search/%E3%83%8D%E3%82%AC%E3%83%86%E3%82%A3%E3%83%96%23sentences",
"phrase": "ネガティブ"
}

View File

@ -1,10 +1,7 @@
{
"query": "grlgmregmneriireg",
"expectedResult": {
"query": "grlgmregmneriireg",
"found": false,
"results": [],
"uri": "http://jisho.org/search/grlgmregmneriireg%23sentences",
"phrase": "grlgmregmneriireg"
}
"found": false,
"results": [],
"uri": "https://jisho.org/search/grlgmregmneriireg%23sentences",
"phrase": "grlgmregmneriireg"
}

View File

@ -1,68 +1,66 @@
{
"query": "車",
"expectedResult": {
"query": "車",
"found": true,
"taughtIn": "grade 1",
"jlptLevel": "N5",
"newspaperFrequencyRank": "333",
"strokeCount": 7,
"meaning": "car",
"kunyomi": [
"くるま"
],
"onyomi": [
"シャ"
],
"onyomiExamples": [
{
"example": "車",
"reading": "シャ",
"meaning": "car, vehicle"
},
{
"example": "車検",
"reading": "シャケン",
"meaning": "vehicle inspection"
},
{
"example": "見切り発車",
"reading": "ミキリハッシャ",
"meaning": "starting a train (or bus, etc.) before all the passengers are on board, making a snap decision, starting an action without considering objections to it any longer"
},
{
"example": "軽自動車",
"reading": "ケイジドウシャ",
"meaning": "light motor vehicle (up to 660cc and 64bhp), k-car, kei car"
}
],
"kunyomiExamples": [
{
"example": "車",
"reading": "くるま",
"meaning": "car, automobile, vehicle, wheel"
},
{
"example": "車椅子",
"reading": "くるまいす",
"meaning": "wheelchair, folding push-chair"
},
{
"example": "火の車",
"reading": "ひのくるま",
"meaning": "fiery chariot (which carries the souls of sinners into hell), desperate financial situation, dire straits"
}
],
"radical": {
"symbol": "車",
"meaning": "cart, car"
"found": true,
"taughtIn": "grade 1",
"jlptLevel": "N5",
"newspaperFrequencyRank": 333,
"strokeCount": 7,
"meaning": "car",
"kunyomi": [
"くるま"
],
"onyomi": [
"シャ"
],
"onyomiExamples": [
{
"example": "車",
"reading": "シャ",
"meaning": "car, vehicle"
},
"parts": [
"車"
],
"strokeOrderDiagramUri": "http://classic.jisho.org/static/images/stroke_diagrams/36554_frames.png",
"strokeOrderSvgUri": "http://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/08eca.svg",
"strokeOrderGifUri": "https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/8eca.gif",
"uri": "http://jisho.org/search/%E8%BB%8A%23kanji"
}
{
"example": "車検",
"reading": "シャケン",
"meaning": "vehicle inspection"
},
{
"example": "見切り発車",
"reading": "ミキリハッシャ",
"meaning": "starting a train (or bus, etc.) before all the passengers are on board, making a snap decision, starting an action without considering objections to it any longer"
},
{
"example": "軽自動車",
"reading": "ケイジドウシャ",
"meaning": "light motor vehicle (up to 660cc and 64bhp), k-car, kei car"
}
],
"kunyomiExamples": [
{
"example": "車",
"reading": "くるま",
"meaning": "car, automobile, vehicle, wheel"
},
{
"example": "車椅子",
"reading": "くるまいす",
"meaning": "wheelchair, folding push-chair"
},
{
"example": "火の車",
"reading": "ひのくるま",
"meaning": "fiery chariot (which carries the souls of sinners into hell), desperate financial situation, dire straits"
}
],
"radical": {
"symbol": "車",
"forms": null,
"meaning": "cart, car"
},
"parts": [
"車"
],
"strokeOrderDiagramUri": "https://classic.jisho.org/static/images/stroke_diagrams/36554_frames.png",
"strokeOrderSvgUri": "https://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/08eca.svg",
"strokeOrderGifUri": "https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/8eca.gif",
"uri": "https://jisho.org/search/%E8%BB%8A%23kanji"
}

View File

@ -1,127 +1,125 @@
{
"query": "家",
"expectedResult": {
"query": "家",
"found": true,
"taughtIn": "grade 2",
"jlptLevel": "N4",
"newspaperFrequencyRank": "133",
"strokeCount": 10,
"meaning": "house, home, family, professional, expert, performer",
"kunyomi": [
"いえ",
"や",
"うち"
],
"onyomi": [
"カ",
"ケ"
],
"onyomiExamples": [
{
"example": "家",
"reading": "カ",
"meaning": "-ist, -er"
},
{
"example": "家屋",
"reading": "カオク",
"meaning": "house, building"
},
{
"example": "研究家",
"reading": "ケンキュウカ",
"meaning": "researcher, student (of)"
},
{
"example": "活動家",
"reading": "カツドウカ",
"meaning": "activist"
},
{
"example": "家",
"reading": "ケ",
"meaning": "house (e.g. of Tokugawa), family"
},
{
"example": "家来",
"reading": "ケライ",
"meaning": "retainer, retinue, servant"
},
{
"example": "本家",
"reading": "ホンケ",
"meaning": "head house (family), birthplace, originator"
},
{
"example": "公家",
"reading": "クゲ",
"meaning": "court noble, nobility, Imperial Court"
}
],
"kunyomiExamples": [
{
"example": "家",
"reading": "いえ",
"meaning": "house, residence, dwelling, family, household, lineage, family name"
},
{
"example": "家主",
"reading": "やぬし",
"meaning": "landlord, landlady, house owner, home owner, head of the household"
},
{
"example": "本家",
"reading": "ほんけ",
"meaning": "head house (family), birthplace, originator"
},
{
"example": "小家",
"reading": "こいえ",
"meaning": "small and simple home"
},
{
"example": "屋",
"reading": "や",
"meaning": "(something) shop, somebody who sells (something) or works as (something), somebody with a (certain) personality trait, house, roof"
},
{
"example": "家内",
"reading": "かない",
"meaning": "(my) wife, inside the home, one's family"
},
{
"example": "長屋",
"reading": "ながや",
"meaning": "tenement house, row house"
},
{
"example": "本家",
"reading": "ほんけ",
"meaning": "head house (family), birthplace, originator"
},
{
"example": "家",
"reading": "うち",
"meaning": "house, home (one's own), (one's) family, (one's) household"
},
{
"example": "家中",
"reading": "うちじゅう",
"meaning": "whole family, all (members of) the family, all over the house, retainer of a daimyo, feudal domain, clan"
}
],
"radical": {
"symbol": "宀",
"meaning": "roof"
"found": true,
"taughtIn": "grade 2",
"jlptLevel": "N4",
"newspaperFrequencyRank": 133,
"strokeCount": 10,
"meaning": "house, home, family, professional, expert, performer",
"kunyomi": [
"いえ",
"や",
"うち"
],
"onyomi": [
"カ",
"ケ"
],
"onyomiExamples": [
{
"example": "家",
"reading": "カ",
"meaning": "-ist, -er"
},
"parts": [
"宀",
"豕"
],
"strokeOrderDiagramUri": "http://classic.jisho.org/static/images/stroke_diagrams/23478_frames.png",
"strokeOrderSvgUri": "http://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/05bb6.svg",
"strokeOrderGifUri": "https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/5bb6.gif",
"uri": "http://jisho.org/search/%E5%AE%B6%23kanji"
}
{
"example": "家屋",
"reading": "カオク",
"meaning": "house, building"
},
{
"example": "研究家",
"reading": "ケンキュウカ",
"meaning": "researcher, student (of)"
},
{
"example": "活動家",
"reading": "カツドウカ",
"meaning": "activist"
},
{
"example": "家",
"reading": "ケ",
"meaning": "house (e.g. of Tokugawa), family"
},
{
"example": "家来",
"reading": "ケライ",
"meaning": "retainer, retinue, servant"
},
{
"example": "本家",
"reading": "ホンケ",
"meaning": "head house (family), birthplace, originator"
},
{
"example": "公家",
"reading": "クゲ",
"meaning": "court noble, nobility, Imperial Court"
}
],
"kunyomiExamples": [
{
"example": "家",
"reading": "いえ",
"meaning": "house, residence, dwelling, family, household, lineage, family name"
},
{
"example": "家主",
"reading": "やぬし",
"meaning": "landlord, landlady, house owner, home owner, head of the household"
},
{
"example": "本家",
"reading": "ほんけ",
"meaning": "head house (family), birthplace, originator"
},
{
"example": "小家",
"reading": "こいえ",
"meaning": "small and simple home"
},
{
"example": "屋",
"reading": "や",
"meaning": "(something) shop, somebody who sells (something) or works as (something), somebody with a (certain) personality trait, house, roof"
},
{
"example": "家内",
"reading": "かない",
"meaning": "(my) wife, inside the home, one's family"
},
{
"example": "長屋",
"reading": "ながや",
"meaning": "tenement house, row house"
},
{
"example": "本家",
"reading": "ほんけ",
"meaning": "head house (family), birthplace, originator"
},
{
"example": "家",
"reading": "うち",
"meaning": "house, home (one's own), (one's) family, (one's) household"
},
{
"example": "家中",
"reading": "うちじゅう",
"meaning": "whole family, all (members of) the family, all over the house, retainer of a daimyo, feudal domain, clan"
}
],
"radical": {
"symbol": "宀",
"forms": null,
"meaning": "roof"
},
"parts": [
"宀",
"豕"
],
"strokeOrderDiagramUri": "https://classic.jisho.org/static/images/stroke_diagrams/23478_frames.png",
"strokeOrderSvgUri": "https://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/05bb6.svg",
"strokeOrderGifUri": "https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/5bb6.gif",
"uri": "https://jisho.org/search/%E5%AE%B6%23kanji"
}

View File

@ -1,99 +1,97 @@
{
"query": "楽",
"expectedResult": {
"query": "楽",
"found": true,
"taughtIn": "grade 2",
"jlptLevel": "N4",
"newspaperFrequencyRank": "373",
"strokeCount": 13,
"meaning": "music, comfort, ease",
"kunyomi": [
"たの.しい",
"たの.しむ",
"この.む"
],
"onyomi": [
"ガク",
"ラク",
"ゴウ"
],
"onyomiExamples": [
{
"example": "楽団",
"reading": "ガクダン",
"meaning": "orchestra, band"
},
{
"example": "楽章",
"reading": "ガクショウ",
"meaning": "(musical) movement"
},
{
"example": "邦楽",
"reading": "ホウガク",
"meaning": "Japanese music (esp. traditional Japanese music)"
},
{
"example": "室内楽",
"reading": "シツナイガク",
"meaning": "chamber music"
},
{
"example": "楽",
"reading": "ラク",
"meaning": "comfort, ease, relief, (at) peace, relaxation, easy, simple, without trouble, without hardships, (economically) comfortable, raku pottery"
},
{
"example": "楽園",
"reading": "ラクエン",
"meaning": "pleasure garden, paradise"
},
{
"example": "千秋楽",
"reading": "センシュウラク",
"meaning": "concluding festivities, concluding program, concluding programme, final day of a tournament"
},
{
"example": "行楽",
"reading": "コウラク",
"meaning": "outing, excursion, pleasure trip, going on a picnic"
},
{
"example": "猿楽",
"reading": "サルガク",
"meaning": "sarugaku (form of theatre popular in Japan during the 11th to 14th centuries), noh, fooling around"
}
],
"kunyomiExamples": [
{
"example": "楽しい",
"reading": "たのしい",
"meaning": "enjoyable, fun, pleasant, happy, delightful"
},
{
"example": "楽しい思い出",
"reading": "たのしいおもいで",
"meaning": "happy memory, sweet memory"
},
{
"example": "楽しむ",
"reading": "たのしむ",
"meaning": "to enjoy (oneself)"
}
],
"radical": {
"symbol": "木",
"meaning": "tree"
"found": true,
"taughtIn": "grade 2",
"jlptLevel": "N4",
"newspaperFrequencyRank": 373,
"strokeCount": 13,
"meaning": "music, comfort, ease",
"kunyomi": [
"たの.しい",
"たの.しむ",
"この.む"
],
"onyomi": [
"ガク",
"ラク",
"ゴウ"
],
"onyomiExamples": [
{
"example": "楽団",
"reading": "ガクダン",
"meaning": "orchestra, band"
},
"parts": [
"冫",
"木",
"白"
],
"strokeOrderDiagramUri": "http://classic.jisho.org/static/images/stroke_diagrams/27005_frames.png",
"strokeOrderSvgUri": "http://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/0697d.svg",
"strokeOrderGifUri": "https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/697d.gif",
"uri": "http://jisho.org/search/%E6%A5%BD%23kanji"
}
{
"example": "楽章",
"reading": "ガクショウ",
"meaning": "(musical) movement"
},
{
"example": "邦楽",
"reading": "ホウガク",
"meaning": "Japanese music (esp. traditional Japanese music)"
},
{
"example": "室内楽",
"reading": "シツナイガク",
"meaning": "chamber music"
},
{
"example": "楽",
"reading": "ラク",
"meaning": "comfort, ease, relief, (at) peace, relaxation, easy, simple, without trouble, without hardships, (economically) comfortable, raku pottery"
},
{
"example": "楽園",
"reading": "ラクエン",
"meaning": "pleasure garden, paradise"
},
{
"example": "千秋楽",
"reading": "センシュウラク",
"meaning": "concluding festivities, concluding program, concluding programme, final day of a tournament"
},
{
"example": "行楽",
"reading": "コウラク",
"meaning": "outing, excursion, pleasure trip, going on a picnic"
},
{
"example": "猿楽",
"reading": "サルガク",
"meaning": "sarugaku (form of theatre popular in Japan during the 11th to 14th centuries), noh, fooling around"
}
],
"kunyomiExamples": [
{
"example": "楽しい",
"reading": "たのしい",
"meaning": "enjoyable, fun, pleasant, happy, delightful"
},
{
"example": "楽しい思い出",
"reading": "たのしいおもいで",
"meaning": "happy memory, sweet memory"
},
{
"example": "楽しむ",
"reading": "たのしむ",
"meaning": "to enjoy (oneself)"
}
],
"radical": {
"symbol": "木",
"forms": null,
"meaning": "tree"
},
"parts": [
"冫",
"木",
"白"
],
"strokeOrderDiagramUri": "https://classic.jisho.org/static/images/stroke_diagrams/27005_frames.png",
"strokeOrderSvgUri": "https://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/0697d.svg",
"strokeOrderGifUri": "https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/697d.gif",
"uri": "https://jisho.org/search/%E6%A5%BD%23kanji"
}

View File

@ -1,7 +1,19 @@
{
"query": "極上",
"expectedResult": {
"query": "極上",
"found": false
}
"found": false,
"taughtIn": null,
"jlptLevel": null,
"newspaperFrequencyRank": null,
"strokeCount": null,
"meaning": null,
"kunyomi": null,
"onyomi": null,
"onyomiExamples": null,
"kunyomiExamples": null,
"radical": null,
"parts": null,
"strokeOrderDiagramUri": null,
"strokeOrderSvgUri": null,
"strokeOrderGifUri": null,
"uri": null
}

View File

@ -1,52 +1,53 @@
{
"query": "贄",
"expectedResult": {
"query": "贄",
"found": true,
"strokeCount": 18,
"meaning": "offering, sacrifice",
"kunyomi": [
"にえ"
],
"onyomi": [
"シ"
],
"onyomiExamples": [],
"kunyomiExamples": [
{
"example": "贄",
"reading": "にえ",
"meaning": "offering (to the gods, emperor, etc.), gift, sacrifice"
},
{
"example": "早贄",
"reading": "はやにえ",
"meaning": "butcher-bird prey impaled on twigs, thorns, etc. for later consumption, first offering of the season"
},
{
"example": "百舌の早贄",
"reading": "もずのはやにえ",
"meaning": "butcher-bird prey impaled on twigs, thorns, etc. for later consumption"
}
],
"radical": {
"symbol": "貝",
"meaning": "shell"
"found": true,
"taughtIn": null,
"jlptLevel": null,
"newspaperFrequencyRank": null,
"strokeCount": 18,
"meaning": "offering, sacrifice",
"kunyomi": [
"にえ"
],
"onyomi": [
"シ"
],
"onyomiExamples": [],
"kunyomiExamples": [
{
"example": "贄",
"reading": "にえ",
"meaning": "offering (to the gods, emperor, etc.), gift, sacrifice"
},
"parts": [
"ハ",
"",
"九",
"亠",
"十",
"目",
"立",
"貝",
"辛"
],
"strokeOrderDiagramUri": "http://classic.jisho.org/static/images/stroke_diagrams/36100_frames.png",
"strokeOrderSvgUri": "http://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/08d04.svg",
"strokeOrderGifUri": "https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/8d04.gif",
"uri": "http://jisho.org/search/%E8%B4%84%23kanji"
}
{
"example": "早贄",
"reading": "はやにえ",
"meaning": "butcher-bird prey impaled on twigs, thorns, etc. for later consumption, first offering of the season"
},
{
"example": "百舌の早贄",
"reading": "もずのはやにえ",
"meaning": "butcher-bird prey impaled on twigs, thorns, etc. for later consumption"
}
],
"radical": {
"symbol": "貝",
"forms": null,
"meaning": "shell"
},
"parts": [
"ハ",
"",
"九",
"亠",
"十",
"目",
"立",
"貝",
"辛"
],
"strokeOrderDiagramUri": "https://classic.jisho.org/static/images/stroke_diagrams/36100_frames.png",
"strokeOrderSvgUri": "https://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/08d04.svg",
"strokeOrderGifUri": "https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/8d04.gif",
"uri": "https://jisho.org/search/%E8%B4%84%23kanji"
}

View File

@ -1,7 +1,19 @@
{
"query": "ネガティブ",
"expectedResult": {
"query": "ネガティブ",
"found": false
}
"found": false,
"taughtIn": null,
"jlptLevel": null,
"newspaperFrequencyRank": null,
"strokeCount": null,
"meaning": null,
"kunyomi": null,
"onyomi": null,
"onyomiExamples": null,
"kunyomiExamples": null,
"radical": null,
"parts": null,
"strokeOrderDiagramUri": null,
"strokeOrderSvgUri": null,
"strokeOrderGifUri": null,
"uri": null
}

View File

@ -1,7 +1,19 @@
{
"query": "wegmwrlgkrgmg",
"expectedResult": {
"query": "wegmwrlgkrgmg",
"found": false
}
"found": false,
"taughtIn": null,
"jlptLevel": null,
"newspaperFrequencyRank": null,
"strokeCount": null,
"meaning": null,
"kunyomi": null,
"onyomi": null,
"onyomiExamples": null,
"kunyomiExamples": null,
"radical": null,
"parts": null,
"strokeOrderDiagramUri": null,
"strokeOrderSvgUri": null,
"strokeOrderGifUri": null,
"uri": null
}

View File

@ -1,78 +1,75 @@
{
"query": "水",
"expectedResult": {
"query": "水",
"found": true,
"taughtIn": "grade 1",
"jlptLevel": "N5",
"newspaperFrequencyRank": "223",
"strokeCount": 4,
"meaning": "water",
"kunyomi": [
"みず",
"みず-"
],
"onyomi": [
"スイ"
],
"onyomiExamples": [
{
"example": "水",
"reading": "スイ",
"meaning": "Wednesday, shaved ice (served with flavored syrup), water (fifth of the five elements)"
},
{
"example": "水位",
"reading": "スイイ",
"meaning": "water level"
},
{
"example": "用水",
"reading": "ヨウスイ",
"meaning": "irrigation water, water for fire, city water, cistern water"
},
{
"example": "浄水",
"reading": "ジョウスイ",
"meaning": "clean water"
}
],
"kunyomiExamples": [
{
"example": "水",
"reading": "みず",
"meaning": "water (esp. cool, fresh water, e.g. drinking water), fluid (esp. in an animal tissue), liquid, flood, floodwaters, water offered to wrestlers just prior to a bout, break granted to wrestlers engaged in a prolonged bout"
},
{
"example": "水揚げ",
"reading": "みずあげ",
"meaning": "landing, unloading (e.g. a ship), catch (of fish), takings, sales (of a shop), defloration (e.g. of a geisha), preservation (of cut flowers, in ikebana)"
},
{
"example": "飲み水",
"reading": "のみみず",
"meaning": "drinking water, potable water"
},
{
"example": "呼び水",
"reading": "よびみず",
"meaning": "pump-priming, rousing, stimulation"
}
],
"radical": {
"symbol": "水",
"forms": [
"氵",
"氺"
],
"meaning": "water"
"found": true,
"taughtIn": "grade 1",
"jlptLevel": "N5",
"newspaperFrequencyRank": 223,
"strokeCount": 4,
"meaning": "water",
"kunyomi": [
"みず",
"みず-"
],
"onyomi": [
"スイ"
],
"onyomiExamples": [
{
"example": "水",
"reading": "スイ",
"meaning": "Wednesday, shaved ice (served with flavored syrup), water (fifth of the five elements)"
},
"parts": [
"水"
{
"example": "水位",
"reading": "スイイ",
"meaning": "water level"
},
{
"example": "用水",
"reading": "ヨウスイ",
"meaning": "irrigation water, water for fire, city water, cistern water"
},
{
"example": "浄水",
"reading": "ジョウスイ",
"meaning": "clean water"
}
],
"kunyomiExamples": [
{
"example": "水",
"reading": "みず",
"meaning": "water (esp. cool, fresh water, e.g. drinking water), fluid (esp. in an animal tissue), liquid, flood, floodwaters, water offered to wrestlers just prior to a bout, break granted to wrestlers engaged in a prolonged bout"
},
{
"example": "水揚げ",
"reading": "みずあげ",
"meaning": "landing, unloading (e.g. a ship), catch (of fish), takings, sales (of a shop), defloration (e.g. of a geisha), preservation (of cut flowers, in ikebana)"
},
{
"example": "飲み水",
"reading": "のみみず",
"meaning": "drinking water, potable water"
},
{
"example": "呼び水",
"reading": "よびみず",
"meaning": "pump-priming, rousing, stimulation"
}
],
"radical": {
"symbol": "水",
"forms": [
"氵",
"氺"
],
"strokeOrderDiagramUri": "http://classic.jisho.org/static/images/stroke_diagrams/27700_frames.png",
"strokeOrderSvgUri": "http://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/06c34.svg",
"strokeOrderGifUri": "https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/6c34.gif",
"uri": "http://jisho.org/search/%E6%B0%B4%23kanji"
}
"meaning": "water"
},
"parts": [
"水"
],
"strokeOrderDiagramUri": "https://classic.jisho.org/static/images/stroke_diagrams/27700_frames.png",
"strokeOrderSvgUri": "https://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/06c34.svg",
"strokeOrderGifUri": "https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/6c34.gif",
"uri": "https://jisho.org/search/%E6%B0%B4%23kanji"
}

View File

@ -1,293 +0,0 @@
import 'package:unofficial_jisho_api/src/objects.dart';
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
import 'package:test/test.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
void test_local_functions() async {
/* KANJI SEARCH FUNCTION TESTS START */
test('removeNewLines', () {
final result = removeNewlines('Line \nwith\r\n Newlines and spaces\n');
expect(result, 'Line with Newlines and spaces');
});
test('uriForKanjiSearch', () {
final result = uriForKanjiSearch('');
expect(result, 'http://jisho.org/search/%E6%99%82%23kanji');
});
test('getUriForStrokeOrderDiagram', () {
final result = getUriForStrokeOrderDiagram('');
expect(result, 'http://classic.jisho.org/static/images/stroke_diagrams/26178_frames.png');
});
test('uriForPhraseSearch', () {
final result = uriForPhraseSearch('時間');
expect(result, 'http://jisho.org/api/v1/search/words?keyword=%E6%99%82%E9%96%93');
});
final kanjiPage = (await http.get('http://jisho.org/search/%E6%99%82%23kanji')).body;
test('containsKanjiGlyph', () {
final result = containsKanjiGlyph(kanjiPage, '');
expect(result, true);
});
test('getStringBetweenIndicies', () {
final result = getStringBetweenIndicies('String\n\rwith\nNewlines', 3, 9);
expect(result, 'ingw');
});
test('getStringBetweenStrings', () {
const data = 'STArT I want this string END';
final result = getStringBetweenStrings(data, 'STArT', 'END');
expect(result, ' I want this string ');
});
test('getIntBetweenStrings', () {
final result = getIntBetweenStrings(kanjiPage, '<strong>', '</strong> strokes');
expect(result, 10);
});
test('getAllGlobalGroupMatches', () {
});
test('parseAnchorsToArray', () {
const htmlCode =
'''
<div class="test">
<p>
<a href="https://test.test">Hello</a>
</p>
<a href="//xyz">Hi</a>
<span>
<p>
<a href="">How are you doing</a>
</p>
</span>
</div>
''';
final result = parseAnchorsToArray(htmlCode);
expect(result, [
'Hello', 'Hi', 'How are you doing']);
});
test('getYomi', () {
final result = getYomi(kanjiPage, 'On');
expect(result, ['']);
});
test('getKunyomi', () {
final result = getKunyomi(kanjiPage);
expect(result, ['とき', '-どき']);
});
test('getOnyomi', () {
final result = getOnyomi(kanjiPage);
expect(result, ['']);
});
test('getYomiExamples', () {
final result = getYomiExamples(kanjiPage, 'On');
expect(
json.encode(result),
json.encode([
YomiExample(
example: '',
reading: '',
meaning: '''hour, o'clock, (specified) time, when ..., during ...'''
),
YomiExample(
example: '時価',
reading: 'ジカ',
meaning: 'current value, price, market value'
),
YomiExample(
example: '零時',
reading: 'レイジ',
meaning: '''twelve o'clock, midnight, noon'''
),
YomiExample(
example: '平時',
reading: 'ヘイジ',
meaning: 'peacetime, time of peace, ordinary times, normal times'
),
])
);
});
test('getOnyomiExamples', () {
final result = getOnyomiExamples(kanjiPage);
expect(
json.encode(result),
json.encode([
YomiExample(
example: '',
reading: '',
meaning: '''hour, o'clock, (specified) time, when ..., during ...'''
),
YomiExample(
example: '時価',
reading: 'ジカ',
meaning: 'current value, price, market value'
),
YomiExample(
example: '零時',
reading: 'レイジ',
meaning: '''twelve o'clock, midnight, noon'''
),
YomiExample(
example: '平時',
reading: 'ヘイジ',
meaning: 'peacetime, time of peace, ordinary times, normal times'
),
])
);
});
test('getKunyomiExamples', () {
final result = getKunyomiExamples(kanjiPage);
expect(
json.encode(result),
json.encode([
YomiExample(
example: '',
reading: 'とき',
meaning: 'time, hour, moment, occasion, case, chance, opportunity, season, the times, the age, the day, tense'
),
YomiExample(
example: '時折',
reading: 'ときおり',
meaning: 'sometimes'
),
YomiExample(
example: '切り替え時',
reading: 'きりかえとき',
meaning: 'time to switch over, response time'
),
YomiExample(
example: '逢魔が時',
reading: 'おうまがとき',
meaning: '''twilight, time for disasters (similar to 'the witching hour' but not midnight)'''
),
])
);
});
test('getRadical', () {
final result = getRadical(kanjiPage);
expect(
json.encode(result),
json.encode(Radical(
symbol: '',
meaning: 'sun, day'
))
);
});
test('getParts', () {
final result = getParts(kanjiPage);
expect(result, ['', '', '']);
});
test('getSvgUri', () {
final result = getSvgUri(kanjiPage);
expect(result, 'http://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/06642.svg');
});
test('getGifUri', () {
final result = getGifUri(kanjiPage);
expect(result, 'https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/3c.gif');
});
test('getNewspaperFrequencyRank', () {
final result = getNewspaperFrequencyRank(kanjiPage);
expect(result, 16);
});
test('parseKanjiPageData', () {
final result = parseKanjiPageData(kanjiPage, '');
final expectedResult = KanjiResult();
expectedResult.query = '';
expectedResult.found = true;
expectedResult.taughtIn = 'grade 2';
expectedResult.jlptLevel = 'N5';
expectedResult.newspaperFrequencyRank = 16;
expectedResult.strokeCount = 10;
expectedResult.meaning = 'time, hour';
expectedResult.kunyomi = ['とき', '-どき'];
expectedResult.onyomi = [''];
expectedResult.onyomiExamples =
[
YomiExample(
example: '',
reading: '',
meaning: '''hour, o'clock, (specified) time, when ..., during ...'''
),
YomiExample(
example: '時価',
reading: 'ジカ',
meaning: 'current value, price, market value'
),
YomiExample(
example: '零時',
reading: 'レイジ',
meaning: '''twelve o'clock, midnight, noon'''
),
YomiExample(
example: '平時',
reading: 'ヘイジ',
meaning: 'peacetime, time of peace, ordinary times, normal times'
),
];
expectedResult.kunyomiExamples =
[
YomiExample(
example: '',
reading: 'とき',
meaning: 'time, hour, moment, occasion, case, chance, opportunity, season, the times, the age, the day, tense'
),
YomiExample(
example: '時折',
reading: 'ときおり',
meaning: 'sometimes'
),
YomiExample(
example: '切り替え時',
reading: 'きりかえとき',
meaning: 'time to switch over, response time'
),
YomiExample(
example: '逢魔が時',
reading: 'おうまがとき',
meaning: '''twilight, time for disasters (similar to 'the witching hour' but not midnight)'''
),
];
expectedResult.radical =
Radical(
symbol: '',
meaning: 'sun, day'
);
expectedResult.parts = ['', '', ''];
expectedResult.strokeOrderDiagramUri = 'http://classic.jisho.org/static/images/stroke_diagrams/26178_frames.png';
expectedResult.strokeOrderSvgUri = 'http://d1w6u4xc3l95km.cloudfront.net/kanji-2015-03/06642.svg';
expectedResult.strokeOrderGifUri = 'https://raw.githubusercontent.com/mistval/kanji_images/master/gifs/6642.gif';
expectedResult.uri = 'http://jisho.org/search/%E6%99%82%23kanji';
expect(
json.encode(result),
json.encode(expectedResult)
);
});
/* KANJI SEARCH FUNCTION TESTS END */
}

View File

@ -1,100 +1,97 @@
{
"found": true,
"query": "車",
"expectedResult": {
"found": true,
"query": "車",
"uri": "https://jisho.org/word/%E8%BB%8A",
"tags": [
"Common word",
"JLPT N5",
"Wanikani level 4"
],
"meanings": [
{
"seeAlsoTerms": [],
"sentences": [
{
"english": "You cannot be too careful when you drive a car.",
"japanese": "車を運転する時はいくら注意してもしすぎることはない。",
"pieces": [
{
"lifted": "くるま",
"unlifted": "車"
},
{
"lifted": "",
"unlifted": "を"
},
{
"lifted": "うんてん",
"unlifted": "運転"
},
{
"lifted": "",
"unlifted": "する"
},
{
"lifted": "とき",
"unlifted": "時"
},
{
"lifted": "",
"unlifted": "は"
},
{
"lifted": "",
"unlifted": "いくら"
},
{
"lifted": "ちゅうい",
"unlifted": "注意"
},
{
"lifted": "",
"unlifted": "して"
},
{
"lifted": "",
"unlifted": "も"
},
{
"lifted": "",
"unlifted": "しすぎる"
},
{
"lifted": "",
"unlifted": "こと"
},
{
"lifted": "",
"unlifted": "は"
},
{
"lifted": "",
"unlifted": "ない"
}
]
}
],
"definition": "car; automobile; vehicle",
"supplemental": [],
"definitionAbstract": "",
"tags": [
"noun"
]
},
{
"seeAlsoTerms": [],
"sentences": [],
"definition": "wheel",
"supplemental": [],
"definitionAbstract": "",
"tags": [
"noun"
]
}
],
"otherForms": [],
"notes": []
}
"uri": "https://jisho.org/word/%E8%BB%8A",
"tags": [
"Common word",
"JLPT N5",
"Wanikani level 4"
],
"meanings": [
{
"seeAlsoTerms": [],
"sentences": [
{
"english": "You cannot be too careful when you drive a car.",
"japanese": "車を運転する時はいくら注意してもしすぎることはない。",
"pieces": [
{
"lifted": "くるま",
"unlifted": "車"
},
{
"lifted": null,
"unlifted": "を"
},
{
"lifted": "うんてん",
"unlifted": "運転"
},
{
"lifted": null,
"unlifted": "する"
},
{
"lifted": "とき",
"unlifted": "時"
},
{
"lifted": null,
"unlifted": "は"
},
{
"lifted": null,
"unlifted": "いくら"
},
{
"lifted": "ちゅうい",
"unlifted": "注意"
},
{
"lifted": null,
"unlifted": "して"
},
{
"lifted": null,
"unlifted": "も"
},
{
"lifted": null,
"unlifted": "しすぎる"
},
{
"lifted": null,
"unlifted": "こと"
},
{
"lifted": null,
"unlifted": "は"
},
{
"lifted": null,
"unlifted": "ない"
}
]
}
],
"definition": "car; automobile; vehicle",
"supplemental": [],
"definitionAbstract": null,
"tags": [
"noun"
]
},
{
"seeAlsoTerms": [],
"sentences": [],
"definition": "wheel",
"supplemental": [],
"definitionAbstract": null,
"tags": [
"noun"
]
}
],
"otherForms": [],
"notes": []
}

View File

@ -1,40 +1,37 @@
{
"found": true,
"query": "日本人",
"expectedResult": {
"found": true,
"query": "日本人",
"uri": "https://jisho.org/word/%E6%97%A5%E6%9C%AC%E4%BA%BA",
"tags": [
"Common word"
],
"meanings": [
{
"seeAlsoTerms": [],
"sentences": [],
"definition": "Japanese person; Japanese people",
"supplemental": [],
"definitionAbstract": "",
"tags": [
"noun"
]
},
{
"seeAlsoTerms": [],
"sentences": [],
"definition": "Japanese people",
"supplemental": [],
"definitionAbstract": "The Japanese people are a nationality originating in the Japanese archipelago and are the predominant ethnic group of Japan. Worldwide, approximately 130 million people are of Japanese descent; of these, approximately 127 million are residents of Japan. People of Japanese ancestry who live in other countries are referred to as nikkeijin . The term ethnic Japanese may also be used in some contexts to refer to a locus of ethnic groups including the Yamato, Ainu and Ryukyuan people.",
"tags": [
"wikipedia definition"
]
}
],
"otherForms": [
{
"kanji": "日本人",
"kana": "にっぽんじん"
}
],
"notes": []
}
"uri": "https://jisho.org/word/%E6%97%A5%E6%9C%AC%E4%BA%BA",
"tags": [
"Common word"
],
"meanings": [
{
"seeAlsoTerms": [],
"sentences": [],
"definition": "Japanese person; Japanese people",
"supplemental": [],
"definitionAbstract": null,
"tags": [
"noun"
]
},
{
"seeAlsoTerms": [],
"sentences": [],
"definition": "Japanese people",
"supplemental": [],
"definitionAbstract": "The Japanese people are a nationality originating in the Japanese archipelago and are the predominant ethnic group of Japan. Worldwide, approximately 130 million people are of Japanese descent; of these, approximately 127 million are residents of Japan. People of Japanese ancestry who live in other countries are referred to as nikkeijin . The term ethnic Japanese may also be used in some contexts to refer to a locus of ethnic groups including the Yamato, Ainu and Ryukyuan people.",
"tags": [
"wikipedia definition"
]
}
],
"otherForms": [
{
"kanji": "日本人",
"kana": "にっぽんじん"
}
],
"notes": []
}

View File

@ -1,135 +1,132 @@
{
"found": true,
"query": "皆",
"expectedResult": {
"found": true,
"query": "皆",
"uri": "https://jisho.org/word/%E7%9A%86",
"tags": [
"Common word",
"JLPT N5"
],
"meanings": [
{
"seeAlsoTerms": [],
"sentences": [
{
"english": "The people which were here have all gone.",
"japanese": "ここにいた人々はみんな行ってしまった。",
"pieces": [
{
"lifted": "",
"unlifted": "ここ"
},
{
"lifted": "",
"unlifted": "に"
},
{
"lifted": "",
"unlifted": "いた"
},
{
"lifted": "ひとびと",
"unlifted": "人々"
},
{
"lifted": "",
"unlifted": "は"
},
{
"lifted": "",
"unlifted": "みんな"
},
{
"lifted": "い",
"unlifted": "行って"
},
{
"lifted": "",
"unlifted": "しまった"
}
]
}
],
"definition": "all; everyone; everybody",
"supplemental": [
"Usually written using kana alone"
],
"definitionAbstract": "",
"tags": [
"adverb",
"noun"
]
},
{
"seeAlsoTerms": [],
"sentences": [
{
"english": "All the dinner had been eaten before he came.",
"japanese": "ごちそうはみんな彼が来ないうちに食べられてしまった。",
"pieces": [
{
"lifted": "",
"unlifted": "ごちそう"
},
{
"lifted": "",
"unlifted": "は"
},
{
"lifted": "",
"unlifted": "みんな"
},
{
"lifted": "かれ",
"unlifted": "彼"
},
{
"lifted": "",
"unlifted": "が"
},
{
"lifted": "らい",
"unlifted": "来"
},
{
"lifted": "",
"unlifted": "ないうちに"
},
{
"lifted": "た",
"unlifted": "食べられて"
},
{
"lifted": "",
"unlifted": "しまった"
}
]
}
],
"definition": "everything",
"supplemental": [
"Usually written using kana alone"
],
"definitionAbstract": "",
"tags": [
"adverb",
"noun"
]
}
],
"otherForms": [
{
"kanji": "皆んな",
"kana": "みんな"
},
{
"kanji": "皆",
"kana": "みな"
}
],
"notes": [
"皆んな: Irregular okurigana usage."
]
}
"uri": "https://jisho.org/word/%E7%9A%86",
"tags": [
"Common word",
"JLPT N5"
],
"meanings": [
{
"seeAlsoTerms": [],
"sentences": [
{
"english": "The people which were here have all gone.",
"japanese": "ここにいた人々はみんな行ってしまった。",
"pieces": [
{
"lifted": null,
"unlifted": "ここ"
},
{
"lifted": null,
"unlifted": "に"
},
{
"lifted": null,
"unlifted": "いた"
},
{
"lifted": "ひとびと",
"unlifted": "人々"
},
{
"lifted": null,
"unlifted": "は"
},
{
"lifted": null,
"unlifted": "みんな"
},
{
"lifted": "い",
"unlifted": "行って"
},
{
"lifted": null,
"unlifted": "しまった"
}
]
}
],
"definition": "all; everyone; everybody",
"supplemental": [
"Usually written using kana alone"
],
"definitionAbstract": null,
"tags": [
"adverb",
"noun"
]
},
{
"seeAlsoTerms": [],
"sentences": [
{
"english": "All the dinner had been eaten before he came.",
"japanese": "ごちそうはみんな彼が来ないうちに食べられてしまった。",
"pieces": [
{
"lifted": null,
"unlifted": "ごちそう"
},
{
"lifted": null,
"unlifted": "は"
},
{
"lifted": null,
"unlifted": "みんな"
},
{
"lifted": "かれ",
"unlifted": "彼"
},
{
"lifted": null,
"unlifted": "が"
},
{
"lifted": "らい",
"unlifted": "来"
},
{
"lifted": null,
"unlifted": "ないうちに"
},
{
"lifted": "た",
"unlifted": "食べられて"
},
{
"lifted": null,
"unlifted": "しまった"
}
]
}
],
"definition": "everything",
"supplemental": [
"Usually written using kana alone"
],
"definitionAbstract": null,
"tags": [
"adverb",
"noun"
]
}
],
"otherForms": [
{
"kanji": "皆んな",
"kana": "みんな"
},
{
"kanji": "皆",
"kana": "みな"
}
],
"notes": [
"皆んな: Irregular okurigana usage."
]
}

View File

@ -1,69 +1,69 @@
{
"found": true,
"query": "ネガティブ",
"expectedResult": {
"found": true,
"query": "ネガティブ",
"uri": "https://jisho.org/word/%E3%83%8D%E3%82%AC%E3%83%86%E3%82%A3%E3%83%96",
"tags": [
"Common word"
],
"meanings": [
{
"seeAlsoTerms": [],
"sentences": [],
"definition": "negative (e.g. thinking)",
"supplemental": [
"Antonym: ポジティブ"
],
"definitionAbstract": "",
"tags": [
"na-adjective"
]
},
{
"seeAlsoTerms": [],
"sentences": [],
"definition": "negative (photography)",
"supplemental": [],
"definitionAbstract": "",
"tags": [
"noun"
]
},
{
"seeAlsoTerms": [
"陰極"
],
"sentences": [],
"definition": "negative (electrical polarity)",
"supplemental": [],
"definitionAbstract": "",
"tags": [
"noun"
]
},
{
"seeAlsoTerms": [],
"sentences": [],
"definition": "Negative (Finnish band)",
"supplemental": [],
"definitionAbstract": "Negative is a Finnish glam rock band founded at the end of 1997. Members of Negative cite musical influence such as Guns N' Roses, Queen, and Hanoi Rocks. The band itself labels the music as ”emotional rockn roll”.",
"tags": [
"wikipedia definition"
]
}
],
"otherForms": [
{
"kanji": "ネガティヴ"
},
{
"kanji": "ネガチブ"
},
{
"kanji": "ネガチィブ"
}
],
"notes": []
}
"uri": "https://jisho.org/word/%E3%83%8D%E3%82%AC%E3%83%86%E3%82%A3%E3%83%96",
"tags": [
"Common word"
],
"meanings": [
{
"seeAlsoTerms": [],
"sentences": [],
"definition": "negative (e.g. thinking)",
"supplemental": [
"Antonym: ポジティブ"
],
"definitionAbstract": null,
"tags": [
"na-adjective"
]
},
{
"seeAlsoTerms": [],
"sentences": [],
"definition": "negative (photography)",
"supplemental": [],
"definitionAbstract": null,
"tags": [
"noun"
]
},
{
"seeAlsoTerms": [
"陰極"
],
"sentences": [],
"definition": "negative (electrical polarity)",
"supplemental": [],
"definitionAbstract": null,
"tags": [
"noun"
]
},
{
"seeAlsoTerms": [],
"sentences": [],
"definition": "Negative (Finnish band)",
"supplemental": [],
"definitionAbstract": "Negative is a Finnish glam rock band founded at the end of 1997. Members of Negative cite musical influence such as Guns N' Roses, Queen, and Hanoi Rocks. The band itself labels the music as ”emotional rockn roll”.",
"tags": [
"wikipedia definition"
]
}
],
"otherForms": [
{
"kanji": "ネガティヴ",
"kana": null
},
{
"kanji": "ネガチブ",
"kana": null
},
{
"kanji": "ネガチィブ",
"kana": null
}
],
"notes": []
}

View File

@ -1,7 +1,9 @@
{
"found": false,
"query": "grlgmregmneriireg",
"expectedResult": {
"query": "grlgmregmneriireg",
"found": false
}
"uri": null,
"tags": null,
"meanings": null,
"otherForms": null,
"notes": null
}

View File

@ -3,7 +3,6 @@ import 'package:path/path.dart' as path;
import 'dart:convert';
import 'package:unofficial_jisho_api/unofficial_jisho_api.dart';
import 'local_function_test_cases.dart' show test_local_functions;
import 'package:test/test.dart';
final jisho = JishoApi();
@ -14,41 +13,19 @@ List<String> getFilePaths(String dirname) {
return filenames.map((filename) => path.join(currentdir, 'test', dirname, filename.path)).toList();
}
void runTestCases(List<String> testCaseFiles, String apiFunction) async {
void runTestCases(List<String> testCaseFiles, Function apiFunction) async {
for (var testCount = 0; testCount < testCaseFiles.length; testCount++) {
final file = await File(testCaseFiles[testCount]).readAsString();
final testCase = jsonDecode(file);
await test('Test ${testCount}', () async {
switch(apiFunction) {
case 'searchForKanji': {
final result = await jisho.searchForKanji(testCase['query']);
expect(result.toJson(), testCase['expectedResult']);
break;
}
case 'searchForExamples': {
final result = await jisho.searchForExamples(testCase['query']);
expect(result, testCase['expectedResult']);
break;
}
case 'scrapeForPhrase': {
final result = await jisho.scrapeForPhrase(testCase['query']);
expect(result, testCase['expectedResult']);
break;
}
throw 'No API function provided';
}
final result = await apiFunction(testCase['query']);
expect(jsonEncode(result), jsonEncode(testCase));
});
}
}
void main() async {
await test_local_functions();
await runTestCases(getFilePaths('kanji_test_cases'), 'searchForKanji');
await runTestCases(getFilePaths('example_test_cases'), 'searchForExamples');
await runTestCases(getFilePaths('phrase_scrape_test_cases'), 'scrapeForPhrase');
await runTestCases(getFilePaths('kanji_test_cases'), jisho.searchForKanji);
await runTestCases(getFilePaths('example_test_cases'), jisho.searchForExamples);
await runTestCases(getFilePaths('phrase_scrape_test_cases'), jisho.scrapeForPhrase);
}