1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
mod error;

pub use error::*;

pub use xpather::{
	self,
	Document,
	Node,
	Value
};

/// Used to scrape data for a struct.
///
/// An example of this would be here:
/// ```rust
/// pub struct RedditListItem {
///     pub url: String
/// }
///
/// impl ScraperMain for RedditListItem {
///     fn scrape(doc: &Document, container: Option<Node>) -> Result<Self> {
///        Ok(Self {
///             url: evaluate(".//a[@data-click-id=\"body\"]/@href", doc, container).convert_from(doc)?
///         })
///     }
/// }
/// ```
pub trait ScraperMain: Sized {
	fn scrape(doc: &Document, container: Option<Node>) -> Result<Self>;
}

/// A simple document evaluation fn.
/// Mainly defined for macros.
/// Allows for evaluating from the Document for from the Node in the document.
pub fn evaluate<S: Into<String>>(search: S, doc: &Document, container: Option<Node>) -> Result<Value> {
	Ok(if let Some(node) = container {
		doc.evaluate_from(search, node)?
	} else {
		doc.evaluate(search)?
	})
}

// TODO: Rename to ConvertToValue
/// Allows for Conversion from `Option<Value>` into another.
pub trait ConvertFromValue<T>: Sized {
	fn convert_from(self, doc: &Document) -> Result<T>;
}

impl ConvertFromValue<Option<String>> for Result<Value> {
	fn convert_from(self, _: &Document) -> Result<Option<String>> {
		Ok(value_to_string(self?).ok())
	}
}

impl ConvertFromValue<String> for Result<Value> {
	fn convert_from(self, _: &Document) -> Result<String> {
		value_to_string(self?)
	}
}

impl ConvertFromValue<Vec<String>> for Result<Value> {
	fn convert_from(self, _: &Document) -> Result<Vec<String>> {
		Ok(value_to_string_vec(self?))
	}
}

impl<T> ConvertFromValue<Vec<T>> for Result<Value> where T: ScraperMain {
	fn convert_from(self, doc: &Document) -> Result<Vec<T>> {
		let value = self?.into_iterset()?;
		Ok(value.map(|n| T::scrape(doc, Some(n))).collect::<Result<Vec<_>>>()?)
	}
}

/// Convert Value to an Optional String.
pub fn value_to_string(value: Value) -> Result<String> {
	match value {
		Value::Nodeset(set) => {
			set.nodes.first()
			.ok_or(Error::ConvertFromValue(None))
			.and_then(|n| value_to_string(n.value()?))
		}

		Value::String(v) => Ok(v),

		value => Err(Error::ConvertFromValue(Some(value)))
	}
}

/// Convert Value to a Vec of Strings.
pub fn value_to_string_vec(value: Value) -> Vec<String> {
	match value {
		Value::Nodeset(set) => {
			set.nodes.into_iter()
				.filter_map(|n| value_to_string(n.value().ok()?).ok())
				.collect()
		}

		Value::String(v) => vec![v],

		_ => Vec::new()
	}
}