use std::fmt;
use crate::document::XmlDocument;
use crate::error::Result;
use crate::node::XmlNode;
use super::XPathResult;
use super::evaluator::XPathEvaluator;
use super::parser::{Expr, parse_xpath};
#[derive(Debug, Clone)]
pub struct Query {
expr: Expr,
namespaces: Vec<(String, String)>,
}
impl Query {
pub fn compile(xpath: &str) -> Result<Self> {
Ok(Self {
expr: parse_xpath(xpath)?,
namespaces: Vec::new(),
})
}
pub(crate) fn from_expr(expr: Expr) -> Self {
Self {
expr,
namespaces: Vec::new(),
}
}
pub(crate) fn expr(&self) -> &Expr {
&self.expr
}
pub fn namespace(mut self, prefix: impl Into<String>, uri: impl Into<String>) -> Self {
self.namespaces.push((prefix.into(), uri.into()));
self
}
fn evaluator<'a>(&self, doc: &'a XmlDocument) -> XPathEvaluator<'a> {
let mut evaluator = XPathEvaluator::new(doc);
for (prefix, uri) in &self.namespaces {
evaluator.register_namespace(prefix, uri);
}
evaluator
}
pub fn eval(&self, doc: &XmlDocument) -> Result<XPathResult> {
self.evaluator(doc).evaluate_expr(&self.expr)
}
pub fn eval_from(&self, doc: &XmlDocument, context: &XmlNode) -> Result<XPathResult> {
self.evaluator(doc).evaluate_expr_from(&self.expr, context)
}
pub fn find_nodes(&self, doc: &XmlDocument) -> Result<Vec<XmlNode>> {
Ok(self.eval(doc)?.into_nodes())
}
}
impl fmt::Display for Query {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.expr.fmt(f)
}
}
pub trait AsQuery {
fn eval_on(&self, doc: &XmlDocument) -> Result<XPathResult>;
}
impl AsQuery for Query {
fn eval_on(&self, doc: &XmlDocument) -> Result<XPathResult> {
self.eval(doc)
}
}
impl AsQuery for str {
fn eval_on(&self, doc: &XmlDocument) -> Result<XPathResult> {
Query::compile(self)?.eval(doc)
}
}
impl AsQuery for String {
fn eval_on(&self, doc: &XmlDocument) -> Result<XPathResult> {
self.as_str().eval_on(doc)
}
}
pub trait QueryExt {
fn query<Q: AsQuery + ?Sized>(&self, query: &Q) -> Result<XPathResult>;
fn query_nodes<Q: AsQuery + ?Sized>(&self, query: &Q) -> Result<Vec<XmlNode>>;
}
impl QueryExt for XmlDocument {
fn query<Q: AsQuery + ?Sized>(&self, query: &Q) -> Result<XPathResult> {
query.eval_on(self)
}
fn query_nodes<Q: AsQuery + ?Sized>(&self, query: &Q) -> Result<Vec<XmlNode>> {
Ok(query.eval_on(self)?.into_nodes())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Parser;
#[test]
fn compile_once_eval_many() {
let query = Query::compile("//item").unwrap();
let a = Parser::from("<root><item/><item/></root>").parse().unwrap();
let b = Parser::from("<root><item/></root>").parse().unwrap();
assert_eq!(query.find_nodes(&a).unwrap().len(), 2);
assert_eq!(query.find_nodes(&b).unwrap().len(), 1);
}
#[test]
fn invalid_expression_errors_at_compile() {
assert!(Query::compile("///bad[").is_err());
}
#[test]
fn eval_returns_typed_result() {
let query = Query::compile("count(//item)").unwrap();
let doc = Parser::from("<root><item/><item/><item/></root>")
.parse()
.unwrap();
assert_eq!(query.eval(&doc).unwrap().to_number(), 3.0);
}
#[test]
fn eval_from_context_node() {
let query = Query::compile("item").unwrap();
let doc = Parser::from("<root><group><item/><item/></group></root>")
.parse()
.unwrap();
let group = Query::compile("//group")
.unwrap()
.find_nodes(&doc)
.unwrap()
.remove(0);
assert_eq!(query.eval_from(&doc, &group).unwrap().into_nodes().len(), 2);
}
#[test]
fn query_to_string_roundtrips() {
let q = Query::compile("//item[@id='2']").unwrap();
let rendered = q.to_string();
let doc = Parser::from(r#"<root><item id="1"/><item id="2"/></root>"#)
.parse()
.unwrap();
let again = Query::compile(&rendered).unwrap();
assert_eq!(
q.find_nodes(&doc).unwrap().len(),
again.find_nodes(&doc).unwrap().len()
);
assert_eq!(rendered, "//item[@id='2']");
}
#[test]
fn query_ext_accepts_str_and_compiled_query() {
let doc = Parser::from("<root><item/><item/><item/></root>")
.parse()
.unwrap();
assert_eq!(doc.query_nodes("//item").unwrap().len(), 3);
assert_eq!(doc.query("count(//item)").unwrap().to_number(), 3.0);
let q = Query::compile("//item").unwrap();
assert_eq!(doc.query_nodes(&q).unwrap().len(), 3);
assert_eq!(doc.query(&q).unwrap().into_nodes().len(), 3);
let owned = String::from("//item");
assert_eq!(doc.query_nodes(&owned).unwrap().len(), 3);
}
#[test]
fn explicit_namespace_binding() {
let query = Query::compile("//g:point")
.unwrap()
.namespace("g", "http://example.com/gml");
let doc = Parser::from(
r#"<root xmlns:gml="http://example.com/gml"><gml:point/><gml:point/></root>"#,
)
.parse()
.unwrap();
assert_eq!(query.find_nodes(&doc).unwrap().len(), 2);
}
}