use parking_lot::RwLock;
use crate::document::XmlDocument;
use crate::error::Result;
use crate::namespace::NamespaceResolver;
use crate::node::{XmlNode, XmlRoNode};
use super::evaluator::{XPathEvaluator, XPathResult};
pub struct XmlContext {
doc: XmlDocument,
resolver: NamespaceResolver,
}
impl XmlContext {
pub fn new(doc: &XmlDocument) -> Self {
let resolver = doc.namespace_resolver().read().clone();
Self {
doc: doc.clone(),
resolver,
}
}
pub fn register_namespace(&mut self, prefix: &str, uri: &str) -> Result<()> {
self.resolver.register(prefix, uri);
Ok(())
}
pub fn evaluate(&self, xpath: &str) -> Result<XPathResult> {
let evaluator = XPathEvaluator::with_resolver(&self.doc, self.resolver.clone());
evaluator.evaluate(xpath)
}
pub fn evaluate_from(&self, xpath: &str, node: &XmlNode) -> Result<XPathResult> {
let evaluator = XPathEvaluator::with_resolver(&self.doc, self.resolver.clone());
evaluator.evaluate_from(xpath, node)
}
pub fn find_nodes(&self, xpath: &str) -> Result<Vec<XmlNode>> {
let result = self.evaluate(xpath)?;
Ok(result.into_nodes())
}
pub fn find_nodes_from(&self, xpath: &str, node: &XmlNode) -> Result<Vec<XmlNode>> {
let result = self.evaluate_from(xpath, node)?;
Ok(result.into_nodes())
}
}
impl Clone for XmlContext {
fn clone(&self) -> Self {
Self {
doc: self.doc.clone(),
resolver: self.resolver.clone(),
}
}
}
pub struct XmlSafeContext {
inner: RwLock<XmlContext>,
}
impl XmlSafeContext {
pub fn new(doc: &XmlDocument) -> Self {
Self {
inner: RwLock::new(XmlContext::new(doc)),
}
}
pub fn register_namespace(&self, prefix: &str, uri: &str) -> Result<()> {
let mut ctx = self.inner.write();
ctx.register_namespace(prefix, uri)
}
pub fn evaluate(&self, xpath: &str) -> Result<XPathResult> {
let ctx = self.inner.read();
ctx.evaluate(xpath)
}
pub fn evaluate_from(&self, xpath: &str, node: &XmlNode) -> Result<XPathResult> {
let ctx = self.inner.read();
ctx.evaluate_from(xpath, node)
}
pub fn find_nodes(&self, xpath: &str) -> Result<Vec<XmlNode>> {
let ctx = self.inner.read();
ctx.find_nodes(xpath)
}
pub fn find_nodes_from(&self, xpath: &str, node: &XmlNode) -> Result<Vec<XmlNode>> {
let ctx = self.inner.read();
ctx.find_nodes_from(xpath, node)
}
}
pub fn create_context(doc: &XmlDocument) -> Result<XmlContext> {
Ok(XmlContext::new(doc))
}
pub fn create_safe_context(doc: &XmlDocument) -> Result<XmlSafeContext> {
Ok(XmlSafeContext::new(doc))
}
pub fn find_nodes_by_xpath(ctx: &XmlContext, xpath: &str, node: &XmlNode) -> Result<Vec<XmlNode>> {
ctx.find_nodes_from(xpath, node)
}
pub fn find_readonly_nodes_by_xpath(
ctx: &XmlContext,
xpath: &str,
node: &XmlRoNode,
) -> Result<Vec<XmlRoNode>> {
let result = ctx.find_nodes_from(xpath, &node.clone().into_node())?;
Ok(result.into_iter().map(XmlRoNode::from_node).collect())
}
pub fn find_safe_readonly_nodes_by_xpath(
ctx: &XmlSafeContext,
xpath: &str,
node: &XmlRoNode,
) -> Result<Vec<XmlRoNode>> {
let result = ctx.find_nodes_from(xpath, &node.clone().into_node())?;
Ok(result.into_iter().map(XmlRoNode::from_node).collect())
}
pub fn find_readonly_nodes_in_elements(
ctx: &XmlContext,
node: &XmlRoNode,
elements_to_match: &[&str],
) -> Result<Vec<XmlRoNode>> {
if elements_to_match.is_empty() {
return Ok(Vec::new());
}
let conditions: Vec<String> = elements_to_match
.iter()
.map(|name| format!("name()='{}'", name))
.collect();
let xpath = if conditions.len() == 1 {
format!("//*[{}]", conditions[0])
} else {
format!("//*[({})]", conditions.join(" or "))
};
find_readonly_nodes_by_xpath(ctx, &xpath, node)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parse;
#[test]
fn test_context() {
let doc = parse(r#"<root><child>text</child></root>"#).unwrap();
let ctx = create_context(&doc).unwrap();
let nodes = ctx.find_nodes("/root/child").unwrap();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_name(), "child");
}
#[test]
fn test_safe_context() {
let doc = parse(r#"<root><child>text</child></root>"#).unwrap();
let ctx = create_safe_context(&doc).unwrap();
let nodes = ctx.find_nodes("/root/child").unwrap();
assert_eq!(nodes.len(), 1);
assert_eq!(nodes[0].get_name(), "child");
}
#[test]
fn test_find_nodes_in_elements() {
let doc = parse(r#"<root><Building/><Room/><Window/></root>"#).unwrap();
let ctx = create_context(&doc).unwrap();
let root = doc.get_root_element_ro().unwrap();
let nodes = find_readonly_nodes_in_elements(&ctx, &root, &["Building", "Room"]).unwrap();
assert_eq!(nodes.len(), 2);
}
#[test]
fn test_namespace_registration() {
let doc = parse(
r#"<gml:root xmlns:gml="http://www.opengis.net/gml">
<gml:name>test</gml:name>
</gml:root>"#,
)
.unwrap();
let mut ctx = create_context(&doc).unwrap();
ctx.register_namespace("gml", "http://www.opengis.net/gml")
.unwrap();
let nodes = ctx.find_nodes("/gml:root/gml:name").unwrap();
assert_eq!(nodes.len(), 1);
}
}