switch to xee-xpath

This commit is contained in:
Robin Appelman 2025-12-20 15:44:43 +01:00
commit b42653f43a
3 changed files with 1342 additions and 330 deletions

View file

@ -4,11 +4,9 @@ use std::borrow::Cow;
use std::error::Error;
use std::path::PathBuf;
use thiserror::Error;
use xrust::parser::xml::parse as xmlparse;
use xrust::parser::xpath::parse;
use xrust::transform::context::{ContextBuilder, StaticContextBuilder};
use xrust::trees::smite::RNode;
use xrust::{Error as XPathParseError, Item, Node, SequenceTrait};
use xee_interpreter::error::SpannedError;
use xee_xpath::error::DocumentsError;
use xee_xpath::{Documents, Queries, Query};
pub struct XPathExtractor<'a> {
file: &'a FileInfo,
@ -22,14 +20,16 @@ impl<'a> XPathExtractor<'a> {
impl Extractor for XPathExtractor<'_> {
fn extract<'this>(&'this self, field: &str) -> Option<Result<Cow<'this, str>, Box<dyn Error>>> {
let query = field
let query_string = field
.strip_prefix("xpath('")
.and_then(|query| query.strip_suffix("')"))?;
let transform = match parse::<RNode>(query, None) {
Ok(transform) => transform,
let queries = Queries::default();
let query = match queries.one(query_string, |doc, item| Ok(item.string_value(doc.xot())?)) {
Ok(query) => query,
Err(error) => {
return Some(Err(XPathError::Parse {
query: query.into(),
query: query_string.into(),
error,
}
.into()));
@ -40,57 +40,46 @@ impl Extractor for XPathExtractor<'_> {
Ok(content) => content,
Err(error) => return Some(Err(XPathError::ReadFile(error).into())),
};
let xml = match xmlparse(RNode::new_document(), content, None) {
let mut documents = Documents::new();
let xml = match documents.add_string_without_uri(content) {
Ok(xml) => xml,
Err(error) => {
return Some(Err(XPathError::ParseXml {
error,
path: self.file.path.clone().into(),
error,
}
.into()));
}
};
let mut static_context = StaticContextBuilder::new()
.message(|_| Ok(()))
.fetcher(|_| Ok(String::new()))
.parser(|_| unreachable!())
.build();
let context = ContextBuilder::new().context(vec![Item::Node(xml)]).build();
let sequence = match context.dispatch(&mut static_context, &transform) {
Ok(res) => res,
let result = match query.execute(&mut documents, xml) {
Ok(result) => result,
Err(error) => {
return Some(Err(XPathError::MatchError {
query: query_string.into(),
error,
query: query.into(),
}
.into()));
}
};
Some(Ok(sequence.to_xml().into()))
Some(Ok(result.into()))
}
}
#[derive(Debug, Error)]
enum XPathError {
#[error("Failed to parse xpath '{query}': {error:#}")]
Parse {
error: XPathParseError,
query: String,
},
Parse { error: SpannedError, query: String },
#[error(transparent)]
ReadFile(FileError),
#[error("Failed to parse xml '{}': {error:#}", path.display())]
ParseXml {
error: XPathParseError,
error: DocumentsError,
path: PathBuf,
},
#[error("Failed to match xpath '{query}': {error:#}")]
MatchError {
error: XPathParseError,
query: String,
},
MatchError { error: SpannedError, query: String },
}
#[test]
@ -129,4 +118,20 @@ fn test_xpath() {
.unwrap()
.unwrap()
);
assert_eq!(
"Any%",
matcher
.extract("xpath('//CategoryName/text()')")
.unwrap()
.unwrap()
);
assert_eq!(
"No Major Glitches",
matcher
.extract(
"xpath('//Metadata/Variables/Variable[contains(@name, \"Subcategory\")]/text()')"
)
.unwrap()
.unwrap()
);
}