use snafu::{ResultExt, Snafu};
use std::borrow::ToOwned;
use std::string;
use sxd_document::dom::Document;
use sxd_document::{PrefixedName, QName};
use crate::parser::Parser;
use crate::tokenizer::{TokenDeabbreviator, Tokenizer};
pub use crate::context::Context;
#[macro_use]
pub mod macros;
pub mod axis;
pub mod context;
pub mod expression;
pub mod function;
pub mod node_test;
pub mod nodeset;
pub mod parser;
mod token;
pub mod tokenizer;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct OwnedPrefixedName {
prefix: Option<String>,
local_part: String,
}
impl<'a> From<&'a str> for OwnedPrefixedName {
fn from(local_part: &'a str) -> Self {
OwnedPrefixedName {
prefix: None,
local_part: local_part.into(),
}
}
}
impl<'a> From<(&'a str, &'a str)> for OwnedPrefixedName {
fn from((prefix, local_part): (&'a str, &'a str)) -> Self {
OwnedPrefixedName {
prefix: Some(prefix.into()),
local_part: local_part.into(),
}
}
}
impl<'a> From<PrefixedName<'a>> for OwnedPrefixedName {
fn from(name: PrefixedName<'a>) -> Self {
OwnedPrefixedName {
prefix: name.prefix().map(Into::into),
local_part: name.local_part().into(),
}
}
}
impl<'a> From<&'a OwnedPrefixedName> for OwnedPrefixedName {
fn from(name: &'a OwnedPrefixedName) -> Self {
OwnedPrefixedName {
prefix: name.prefix.to_owned(),
local_part: name.local_part.to_owned(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct OwnedQName {
namespace_uri: Option<String>,
local_part: String,
}
impl<'a> From<&'a str> for OwnedQName {
fn from(local_part: &'a str) -> Self {
OwnedQName {
namespace_uri: None,
local_part: local_part.into(),
}
}
}
impl<'a> From<(&'a str, &'a str)> for OwnedQName {
fn from((namespace_uri, local_part): (&'a str, &'a str)) -> Self {
OwnedQName {
namespace_uri: Some(namespace_uri.into()),
local_part: local_part.into(),
}
}
}
impl<'a> From<QName<'a>> for OwnedQName {
fn from(name: QName<'a>) -> Self {
OwnedQName {
namespace_uri: name.namespace_uri().map(Into::into),
local_part: name.local_part().into(),
}
}
}
pub type LiteralValue = Value<'static>;
#[derive(Debug, Clone, PartialEq)]
pub enum Value<'d> {
Boolean(bool),
Number(f64),
String(string::String),
Nodeset(nodeset::Nodeset<'d>),
}
fn str_to_num(s: &str) -> f64 {
s.trim().parse().unwrap_or(::std::f64::NAN)
}
impl<'d> Value<'d> {
pub fn boolean(&self) -> bool {
use crate::Value::*;
match *self {
Boolean(val) => val,
Number(n) => n != 0.0 && !n.is_nan(),
String(ref s) => !s.is_empty(),
Nodeset(ref nodeset) => nodeset.size() > 0,
}
}
pub fn into_boolean(self) -> bool {
self.boolean()
}
pub fn number(&self) -> f64 {
use crate::Value::*;
match *self {
Boolean(val) => {
if val {
1.0
} else {
0.0
}
}
Number(val) => val,
String(ref s) => str_to_num(s),
Nodeset(..) => str_to_num(&self.string()),
}
}
pub fn into_number(self) -> f64 {
self.number()
}
pub fn string(&self) -> string::String {
use crate::Value::*;
match *self {
Boolean(v) => v.to_string(),
Number(n) => {
if n.is_infinite() {
if n.signum() < 0.0 {
"-Infinity".to_owned()
} else {
"Infinity".to_owned()
}
} else {
n.to_string()
}
}
String(ref val) => val.clone(),
Nodeset(ref ns) => match ns.document_order_first() {
Some(n) => n.string_value(),
None => "".to_owned(),
},
}
}
pub fn into_string(self) -> string::String {
use crate::Value::*;
match self {
String(val) => val,
other => other.string(),
}
}
}
macro_rules! from_impl {
($raw:ty, $variant:expr) => {
impl<'d> From<$raw> for Value<'d> {
fn from(other: $raw) -> Value<'d> {
$variant(other)
}
}
};
}
from_impl!(bool, Value::Boolean);
from_impl!(f64, Value::Number);
from_impl!(String, Value::String);
impl<'a, 'd> From<&'a str> for Value<'d> {
fn from(other: &'a str) -> Value<'d> {
Value::String(other.into())
}
}
from_impl!(nodeset::Nodeset<'d>, Value::Nodeset);
macro_rules! partial_eq_impl {
($raw:ty, $variant:pat => $b:expr) => {
impl<'d> PartialEq<$raw> for Value<'d> {
fn eq(&self, other: &$raw) -> bool {
match *self {
$variant => $b == other,
_ => false,
}
}
}
impl<'d> PartialEq<Value<'d>> for $raw {
fn eq(&self, other: &Value<'d>) -> bool {
match *other {
$variant => $b == self,
_ => false,
}
}
}
};
}
partial_eq_impl!(bool, Value::Boolean(ref v) => v);
partial_eq_impl!(f64, Value::Number(ref v) => v);
partial_eq_impl!(String, Value::String(ref v) => v);
partial_eq_impl!(&'d str, Value::String(ref v) => v);
partial_eq_impl!(nodeset::Nodeset<'d>, Value::Nodeset(ref v) => v);
#[derive(Debug)]
pub struct XPath(Box<dyn expression::Expression + 'static>);
impl XPath {
pub fn evaluate<'d, N>(
&self,
context: &Context<'d>,
node: N,
) -> Result<Value<'d>, ExecutionError>
where
N: Into<nodeset::Node<'d>>,
{
let context = context::Evaluation::new(context, node.into());
self.0.evaluate(&context).map_err(ExecutionError)
}
}
pub struct Factory {
parser: Parser,
}
impl Factory {
pub fn new() -> Factory {
Factory {
parser: Parser::new(),
}
}
pub fn build(&self, xpath: &str) -> Result<XPath, ParserError> {
let tokenizer = Tokenizer::new(xpath);
let deabbreviator = TokenDeabbreviator::new(tokenizer);
self.parser
.parse(deabbreviator)
.map(XPath)
.map_err(Into::into)
}
}
impl Default for Factory {
fn default() -> Self {
Factory::new()
}
}
#[derive(Debug, Snafu, Clone, PartialEq)]
pub struct ParserError(parser::Error);
#[derive(Debug, Snafu, Clone, PartialEq)]
pub struct ExecutionError(expression::Error);
#[derive(Debug, Snafu, Clone, PartialEq)]
pub enum Error {
#[snafu(display("Unable to parse XPath: {}", source))]
Parsing { source: ParserError },
#[snafu(display("Unable to execute XPath: {}", source))]
Executing { source: ExecutionError },
}
pub fn evaluate_xpath<'d>(document: &'d Document<'d>, xpath: &str) -> Result<Value<'d>, Error> {
let factory = Factory::new();
let expression = factory.build(xpath).context(Parsing)?;
let context = Context::new();
expression
.evaluate(&context, document.root())
.context(Executing)
}
#[cfg(test)]
mod test {
use std::borrow::ToOwned;
use sxd_document::{self, dom, Package};
use super::*;
#[test]
fn number_of_string_is_ieee_754_number() {
let v = Value::String("1.5".to_owned());
assert_eq!(1.5, v.number());
}
#[test]
fn number_of_string_with_negative_is_negative_number() {
let v = Value::String("-1.5".to_owned());
assert_eq!(-1.5, v.number());
}
#[test]
fn number_of_string_with_surrounding_whitespace_is_number_without_whitespace() {
let v = Value::String("\r\n1.5 \t".to_owned());
assert_eq!(1.5, v.number());
}
#[test]
fn number_of_garbage_string_is_nan() {
let v = Value::String("I am not an IEEE 754 number".to_owned());
assert!(v.number().is_nan());
}
#[test]
fn number_of_boolean_true_is_1() {
let v = Value::Boolean(true);
assert_eq!(1.0, v.number());
}
#[test]
fn number_of_boolean_false_is_0() {
let v = Value::Boolean(false);
assert_eq!(0.0, v.number());
}
#[test]
fn number_of_nodeset_is_number_value_of_first_node_in_document_order() {
let package = Package::new();
let doc = package.as_document();
let c1 = doc.create_comment("42.42");
let c2 = doc.create_comment("1234");
doc.root().append_child(c1);
doc.root().append_child(c2);
let v = Value::Nodeset(nodeset![c2, c1]);
assert_eq!(42.42, v.number());
}
#[test]
fn string_of_true_is_true() {
let v = Value::Boolean(true);
assert_eq!("true", v.string());
}
#[test]
fn string_of_false_is_false() {
let v = Value::Boolean(false);
assert_eq!("false", v.string());
}
#[test]
fn string_of_nan_is_nan() {
let v = Value::Number(::std::f64::NAN);
assert_eq!("NaN", v.string());
}
#[test]
fn string_of_positive_zero_is_zero() {
let v = Value::Number(0.0);
assert_eq!("0", v.string());
}
#[test]
fn string_of_negative_zero_is_zero() {
let v = Value::Number(-0.0);
assert_eq!("0", v.string());
}
#[test]
fn string_of_positive_infinity_is_infinity() {
let v = Value::Number(::std::f64::INFINITY);
assert_eq!("Infinity", v.string());
}
#[test]
fn string_of_negative_infinity_is_minus_infinity() {
let v = Value::Number(::std::f64::NEG_INFINITY);
assert_eq!("-Infinity", v.string());
}
#[test]
fn string_of_integer_has_no_decimal() {
let v = Value::Number(-42.0);
assert_eq!("-42", v.string());
}
#[test]
fn string_of_decimal_has_fractional_part() {
let v = Value::Number(1.2);
assert_eq!("1.2", v.string());
}
#[test]
fn string_of_nodeset_is_string_value_of_first_node_in_document_order() {
let package = Package::new();
let doc = package.as_document();
let c1 = doc.create_comment("comment 1");
let c2 = doc.create_comment("comment 2");
doc.root().append_child(c1);
doc.root().append_child(c2);
let v = Value::Nodeset(nodeset![c2, c1]);
assert_eq!("comment 1", v.string());
}
fn with_document<F>(xml: &str, f: F)
where
F: FnOnce(dom::Document<'_>),
{
let package = sxd_document::parser::parse(xml).expect("Unable to parse test XML");
f(package.as_document());
}
#[test]
fn xpath_evaluation_success() {
with_document("<root><child>content</child></root>", |doc| {
let result = evaluate_xpath(&doc, "/root/child");
assert_eq!(Ok("content".to_owned()), result.map(|v| v.string()));
});
}
#[test]
fn xpath_evaluation_parsing_error() {
with_document("<root><child>content</child></root>", |doc| {
let result = evaluate_xpath(&doc, "/root/child/");
let expected_error = crate::parser::TrailingSlash
.fail()
.map_err(ParserError::from)
.context(Parsing);
assert_eq!(expected_error, result);
});
}
#[test]
fn xpath_evaluation_execution_error() {
with_document("<root><child>content</child></root>", |doc| {
let result = evaluate_xpath(&doc, "$foo");
let expected_error = crate::expression::UnknownVariable { name: "foo" }
.fail()
.map_err(ExecutionError::from)
.context(Executing);
assert_eq!(expected_error, result);
});
}
}