diff --git a/.gitignore b/.gitignore index 6d1e94f82..01860efb1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.rlib *.dll Cargo.lock +.DS_Store # Executables *.exe diff --git a/src/bindings.rs b/src/bindings.rs index fc0d06988..827f91136 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -14926,6 +14926,13 @@ extern "C" { pub fn xmlXPathSetContextNode(node: xmlNodePtr, ctx: xmlXPathContextPtr) -> ::std::os::raw::c_int; } +extern "C" { + pub fn xmlXPathSetErrorHandler( + ctxt: xmlXPathContextPtr, + handler: xmlStructuredErrorFunc, + data: *mut ::std::os::raw::c_void, + ); +} extern "C" { pub fn xmlXPathNodeEval( node: xmlNodePtr, diff --git a/src/schemas/mod.rs b/src/schemas/mod.rs index 026e1dd49..493be7b34 100644 --- a/src/schemas/mod.rs +++ b/src/schemas/mod.rs @@ -16,3 +16,4 @@ use schema::Schema; // internally handled by SchemaValidationContext pub use parser::SchemaParserContext; pub use validation::SchemaValidationContext; +pub use common::structured_error_handler; \ No newline at end of file diff --git a/src/xpath.rs b/src/xpath.rs index cb9af4073..5cc557702 100644 --- a/src/xpath.rs +++ b/src/xpath.rs @@ -1,9 +1,13 @@ //! The `XPath` functionality -use crate::bindings::*; -use crate::c_helpers::*; -use crate::readonly::RoNode; -use crate::tree::{Document, DocumentRef, DocumentWeak, Node}; +use crate::{ + bindings::{self, *}, + c_helpers::*, + error::StructuredError, + readonly::RoNode, + schemas::structured_error_handler, + tree::{Document, DocumentRef, DocumentWeak, Node}, +}; use libc::{c_char, c_void, size_t}; use std::cell::RefCell; use std::ffi::{CStr, CString}; @@ -33,6 +37,19 @@ pub struct Context { pub(crate) context_ptr: ContextRef, ///Document contains pointer, needed for ContextPtr, so we need to borrow Document to prevent it's freeing pub(crate) document: DocumentWeak, + ///Errors registered during libxml2 xpath processing3 + pub(crate) errlog: *mut Vec, +} + +impl Drop for Context { + fn drop(&mut self) { + unsafe { + if !self.errlog.is_null() { + let errors: Box> = std::mem::transmute(self.errlog); + drop(errors) + } + } + } } ///Essentially, the result of the evaluation of some xpath expression @@ -50,21 +67,45 @@ impl Context { if ctxtptr.is_null() { Err(()) } else { - Ok(Context { - context_ptr: Rc::new(RefCell::new(_Context(ctxtptr))), - document: Rc::downgrade(&doc.0), - }) + let errors: Box> = Box::default(); + + unsafe { + let reference: *mut Vec = std::mem::transmute(errors); + bindings::xmlXPathSetErrorHandler( + ctxtptr, + Some(structured_error_handler), + reference as *mut _, + ); + Ok(Context { + context_ptr: Rc::new(RefCell::new(_Context(ctxtptr))), + document: Rc::downgrade(&doc.0), + errlog: reference as *mut _, + }) + } } } + pub(crate) fn new_ptr(docref: &DocumentRef) -> Result { let ctxtptr = unsafe { xmlXPathNewContext(docref.borrow().doc_ptr) }; if ctxtptr.is_null() { Err(()) } else { - Ok(Context { - context_ptr: Rc::new(RefCell::new(_Context(ctxtptr))), - document: Rc::downgrade(docref), - }) + let errors: Box> = Box::default(); + + unsafe { + let reference: *mut Vec = std::mem::transmute(errors); + bindings::xmlXPathSetErrorHandler( + ctxtptr, + Some(structured_error_handler), + reference as *mut _, + ); + + Ok(Context { + context_ptr: Rc::new(RefCell::new(_Context(ctxtptr))), + document: Rc::downgrade(docref), + errlog: reference as *mut _, + }) + } } } @@ -73,6 +114,13 @@ impl Context { self.context_ptr.borrow().0 } + /// Drains error log from errors that might have accumulated while evaluating an xpath + pub fn drain_errors(&mut self) -> Vec { + assert!(!self.errlog.is_null()); + let errors = unsafe { &mut *self.errlog }; + std::mem::take(errors) + } + /// Instantiate a new Context for the Document of a given Node. /// Note: the Context is root-level for that document, use `.set_context_node` to limit scope to this node pub fn from_node(node: &Node) -> Result { @@ -269,7 +317,6 @@ impl Object { } vec } - } impl fmt::Display for Object { diff --git a/tests/xpath_tests.rs b/tests/xpath_tests.rs index 8f09cfa55..710481c45 100644 --- a/tests/xpath_tests.rs +++ b/tests/xpath_tests.rs @@ -207,7 +207,11 @@ fn xpath_find_string_values() { let empty_values = xpath.findvalues(".//@xml:id", Some(empty_test)); assert_eq!(empty_values, Ok(Vec::new())); let ids_values = xpath.findvalues(".//@xml:id", Some(ids_test)); - let expected_ids = Ok(vec![String::from("start"),String::from("mid"),String::from("end")]); + let expected_ids = Ok(vec![ + String::from("start"), + String::from("mid"), + String::from("end"), + ]); assert_eq!(ids_values, expected_ids); let node_ids_values = ids_test.findvalues(".//@xml:id"); assert_eq!(node_ids_values, expected_ids); @@ -216,6 +220,28 @@ fn xpath_find_string_values() { } } +#[test] +// brew install --HEAD libxml2 +// export LIBXML2=`ls /opt/homebrew/Cellar/libxml2/*/lib/libxml2.dylib` && echo $LIBXML2 +// cargo clean +// cargo test +fn xpath_context_new() { + let parser = Parser::default_html(); + let doc_result = parser.parse_file("tests/resources/file02.xml"); + assert!(doc_result.is_ok()); + let doc = doc_result.unwrap(); + + // Xpath interface + let mut context = Context::new(&doc).unwrap(); + match context.evaluate("/html/un:body") { + Ok(_) => assert!(false), + Err(e) => { + // for msg in context.drain_errors() { + // assert_eq!(1,msg.code); + // } + } + } +} /// Tests for checking xpath well-formedness mod compile_tests { use libxml::xpath::is_well_formed_xpath;