char_coal/word/
youdict.rs

1mod 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}