char_coal/word/
youdict.rs1mod sentence;
2
3use self::sentence::Sentence;
4use super::{FromYoudict, Query, Select, WordEntry, WordQuery};
5use scraper::{ElementRef, Selector};
6
7impl Query for FromYoudict {
8 fn query(&mut self, word_query: &WordQuery) -> anyhow::Result<WordEntry> {
9 async fn get_html(url: impl AsRef<str> + reqwest::IntoUrl) -> anyhow::Result<String> {
10 let body = reqwest::get(url).await?.text().await?;
11 Ok(body)
12 }
13 let youdao_dict_url = url::Url::parse(&format!(
14 "http://dict.youdao.com/search?q={}",
15 word_query.word()
16 ))?;
17
18 let xml = futures::executor::block_on(async { get_html(youdao_dict_url).await })?;
19 let doc = scraper::Html::parse_document(&xml);
20
21 FromYoudict::select(doc.root_element(), word_query)
22 }
23}
24
25impl Select for FromYoudict {
26 type Target = WordEntry;
27
28 fn select(elem: ElementRef, word_query: &WordQuery) -> anyhow::Result<Self::Target> {
29 let doc = elem;
30 let pronunciation = {
31 let sel = Selector::parse("span.pronounce").unwrap();
32 doc.select(&sel)
33 .filter_map(|child| {
34 let mut iter = child.text().filter_map(trim_str);
35 match (iter.next(), iter.next()) {
36 (Some(region), Some(pron)) => Some((region, pron)),
37 _ => None,
38 }
39 })
40 .collect()
41 };
42
43 let brief = {
44 let sel = Selector::parse("#phrsListTab .trans-container ul li").unwrap();
45 doc.select(&sel)
46 .map(|child| {
47 child
48 .text()
49 .filter_map(trim_str)
50 .collect::<Vec<String>>()
51 .join("")
52 })
53 .collect()
54 };
55
56 let variants = {
57 let sel = Selector::parse("#phrsListTab .trans-container p").unwrap();
58 doc.select(&sel)
59 .flat_map(|child| {
60 child.text().map(|t| {
61 t.split('\n')
62 .filter_map(trim_str)
63 .collect::<Vec<String>>()
64 .join(" ")
65 })
66 })
67 .filter(|s| !s.is_empty())
68 .collect()
69 };
70
71 let sentence = Sentence::select(elem, word_query)?;
72
73 Ok(WordEntry {
74 pronunciation,
75 brief,
76 variants,
77 authority: Vec::new(),
78 sentence,
79 })
80 }
81}
82
83fn trim_str(t: &str) -> Option<String> {
84 let t = t.trim();
85 if t.is_empty() {
86 None
87 } else {
88 Some(t.to_owned())
89 }
90}