scraper_main/
lib.rs

1mod error;
2
3pub use error::*;
4
5pub use xpather::{
6	self,
7	factory::ProduceIter,
8	value::Node,
9	Document,
10	Value
11};
12
13pub use scraper_macros::Scraper;
14
15/// Used to scrape data for a struct.
16///
17/// An example of this would look like with macros:
18/// ```rust
19/// pub struct RedditListItem {
20///     pub url: String
21/// }
22///
23/// impl ScraperMain for RedditListItem {
24///     fn scrape(doc: &Document, container: Option<Node>) -> Result<Self> {
25///        Ok(Self {
26///             url: evaluate(".//a[@data-click-id=\"body\"]/@href", doc, container).convert_from(doc)?
27///         })
28///     }
29/// }
30/// ```
31pub trait ScraperMain: Sized {
32	fn scrape(doc: &Document, container: Option<&Node>) -> Result<Self>;
33}
34
35/// A simple [Document] evaluation fn.
36///
37/// Mainly defined for macros.
38///
39/// Allows for evaluating from the start of the [Document] or from a [Node] in the Document.
40pub fn evaluate<'a, S: Into<String>>(search: S, doc: &'a Document, container: Option<&'a Node>) -> Result<ProduceIter<'a>> {
41	Ok(if let Some(node) = container {
42		doc.evaluate_from(search, node)?
43	} else {
44		doc.evaluate(search)?
45	})
46}
47
48/// Allows for Conversion from [ProduceIter] into another.
49pub trait ConvertToValue<T>: Sized {
50	fn convert_from(self, doc: &Document) -> Result<T>;
51}
52
53
54impl<'a> ConvertToValue<Option<String>> for Result<ProduceIter<'a>> {
55	fn convert_from(self, _: &Document) -> Result<Option<String>> {
56		self?.next().map(value_to_string).transpose()
57	}
58}
59
60impl<'a> ConvertToValue<String> for Result<ProduceIter<'a>> {
61	fn convert_from(self, _: &Document) -> Result<String> {
62		self?.next()
63			.map(value_to_string)
64			.transpose()?
65			.ok_or(Error::ConvertFromValue(None))
66	}
67}
68
69impl<'a> ConvertToValue<Vec<String>> for Result<ProduceIter<'a>> {
70	fn convert_from(self, _: &Document) -> Result<Vec<String>> {
71		Ok(value_to_string_vec(self?).into_iter().filter_map(|v| v.ok()).collect())
72	}
73}
74
75impl<'a> ConvertToValue<Vec<Result<String>>> for Result<ProduceIter<'a>> {
76	fn convert_from(self, _: &Document) -> Result<Vec<Result<String>>> {
77		Ok(value_to_string_vec(self?))
78	}
79}
80
81
82
83impl<'a, T> ConvertToValue<Vec<T>> for Result<ProduceIter<'a>> where T: ScraperMain {
84	fn convert_from(self, doc: &Document) -> Result<Vec<T>> {
85		self?.map(|n| T::scrape(doc, Some(n?.as_node()?))).collect::<Result<Vec<_>>>()
86	}
87}
88
89impl<'a, T> ConvertToValue<Option<T>> for Result<ProduceIter<'a>> where T: ScraperMain {
90	fn convert_from(self, doc: &Document) -> Result<Option<T>> {
91		self?.next().map(|n| T::scrape(doc, Some(n?.as_node()?))).transpose()
92	}
93}
94
95/// Converts [Value] to an [Result]<[String]>.
96pub fn value_to_string(value: xpather::Result<Value>) -> Result<String> {
97	match value? {
98		Value::Node(node) => {
99			value_to_string(node.value())
100		}
101
102		Value::String(v) => Ok(v),
103
104		value => Err(Error::ConvertFromValue(Some(value)))
105	}
106}
107
108/// Converts [Value] to [Vec]<[String]>.
109pub fn value_to_string_vec(iter: ProduceIter) -> Vec<Result<String>> {
110	iter.map(value_to_string).collect()
111}