#[macro_use]
extern crate peresil;
extern crate sxd_document;
#[macro_use]
extern crate quick_error;
use std::borrow::ToOwned;
use std::string;
use sxd_document::{PrefixedName, QName};
use sxd_document::dom::Document;
use parser::Parser;
use tokenizer::{Tokenizer, TokenDeabbreviator};
pub use context::Context;
#[macro_use]
pub mod macros;
pub mod nodeset;
pub mod context;
mod axis;
mod expression;
pub mod function;
mod node_test;
mod parser;
mod token;
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(),
}
}
}
#[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(),
}
}
}
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 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 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 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 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<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<Option<XPath>, ParserError> {
let tokenizer = Tokenizer::new(xpath);
let deabbreviator = TokenDeabbreviator::new(tokenizer);
self.parser.parse(deabbreviator).map(|x| x.map(XPath)).map_err(ParserError)
}
}
impl Default for Factory {
fn default() -> Self {
Factory::new()
}
}
macro_rules! opaque_error {
(
$(#[$attr:meta])+
$name:ident($inner:ty)
) => {
#[derive(Debug, Clone, PartialEq)]
$(#[$attr])+
pub struct $name($inner);
impl std::error::Error for $name {
fn description(&self) -> &str {
self.0.description()
}
fn cause(&self) -> Option<&std::error::Error> {
self.0.cause()
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl From<$inner> for $name {
fn from(other: $inner) -> $name {
$name(other)
}
}
}
}
opaque_error!(
ParserError(parser::Error)
);
opaque_error!(
ExecutionError(expression::Error)
);
quick_error! {
#[derive(Debug, Clone, PartialEq)]
pub enum Error {
Parsing(err: ParserError) {
from()
cause(err)
description("Unable to parse XPath")
display("Unable to parse XPath: {}", err)
}
NoXPath {
description("XPath was empty")
}
Executing(err: ExecutionError) {
from()
cause(err)
description("Unable to execute XPath")
display("Unable to execute XPath: {}", err)
}
}
}
pub fn evaluate_xpath<'d>(document: &'d Document<'d>, xpath: &str) -> Result<Value<'d>, Error> {
let factory = Factory::new();
let expression = factory.build(xpath)?;
let expression = expression.ok_or(Error::NoXPath)?;
let context = Context::new();
expression.evaluate(&context, document.root()).map_err(Into::into)
}
#[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| {
use Error::*;
use parser::Error::*;
let result = evaluate_xpath(&doc, "/root/child/");
assert_eq!(Err(Parsing(ParserError(TrailingSlash))), result);
});
}
#[test]
fn xpath_evaluation_execution_error() {
with_document("<root><child>content</child></root>", |doc| {
use Error::*;
use expression::Error::*;
let result = evaluate_xpath(&doc, "$foo");
assert_eq!(Err(Executing(ExecutionError(UnknownVariable("foo".into())))), result);
});
}
#[test]
fn xpath_evaluation_no_xpath_error() {
with_document("<root><child>content</child></root>", |doc| {
let result = evaluate_xpath(&doc, "");
assert_eq!(Err(Error::NoXPath), result);
});
}
}