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

1614
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -9,20 +9,21 @@ rust-version = "1.85.0"
home = "0.5.11" home = "0.5.11"
xattr = "1.6.1" xattr = "1.6.1"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
toml = "0.9.8" toml = "0.9.10"
regex = "1.12.1" regex = "1.12.2"
thiserror = "2.0.17" thiserror = "2.0.17"
clap = { version = "4.5.48", features = ["derive"] } clap = { version = "4.5.53", features = ["derive"] }
main_error = "0.1.2" main_error = "0.1.2"
tracing = "0.1.41" tracing = "0.1.44"
tracing-subscriber = "0.3.20" tracing-subscriber = "0.3.22"
notify-debouncer-full = "0.6.0" notify-debouncer-full = "0.6.0"
ctrlc = "3.5.0" ctrlc = "3.5.1"
sha2 = "0.11.0-rc.2" sha2 = "0.11.0-rc.3"
hex = "0.4.3" hex = "0.4.3"
xrust = "1.3.0" xee-xpath = "0.1.5"
xee-interpreter = "0.2.0"
notify-rust = "4.11.7" notify-rust = "4.11.7"
open = "5.3.2" open = "5.3.3"
[dev-dependencies] [dev-dependencies]
maplit = "1.0.2" maplit = "1.0.2"

View file

@ -4,11 +4,9 @@ use std::borrow::Cow;
use std::error::Error; use std::error::Error;
use std::path::PathBuf; use std::path::PathBuf;
use thiserror::Error; use thiserror::Error;
use xrust::parser::xml::parse as xmlparse; use xee_interpreter::error::SpannedError;
use xrust::parser::xpath::parse; use xee_xpath::error::DocumentsError;
use xrust::transform::context::{ContextBuilder, StaticContextBuilder}; use xee_xpath::{Documents, Queries, Query};
use xrust::trees::smite::RNode;
use xrust::{Error as XPathParseError, Item, Node, SequenceTrait};
pub struct XPathExtractor<'a> { pub struct XPathExtractor<'a> {
file: &'a FileInfo, file: &'a FileInfo,
@ -22,14 +20,16 @@ impl<'a> XPathExtractor<'a> {
impl Extractor for XPathExtractor<'_> { impl Extractor for XPathExtractor<'_> {
fn extract<'this>(&'this self, field: &str) -> Option<Result<Cow<'this, str>, Box<dyn Error>>> { 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('") .strip_prefix("xpath('")
.and_then(|query| query.strip_suffix("')"))?; .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) => { Err(error) => {
return Some(Err(XPathError::Parse { return Some(Err(XPathError::Parse {
query: query.into(), query: query_string.into(),
error, error,
} }
.into())); .into()));
@ -40,57 +40,46 @@ impl Extractor for XPathExtractor<'_> {
Ok(content) => content, Ok(content) => content,
Err(error) => return Some(Err(XPathError::ReadFile(error).into())), 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, Ok(xml) => xml,
Err(error) => { Err(error) => {
return Some(Err(XPathError::ParseXml { return Some(Err(XPathError::ParseXml {
error,
path: self.file.path.clone().into(), path: self.file.path.clone().into(),
error,
} }
.into())); .into()));
} }
}; };
let mut static_context = StaticContextBuilder::new() let result = match query.execute(&mut documents, xml) {
.message(|_| Ok(())) Ok(result) => result,
.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,
Err(error) => { Err(error) => {
return Some(Err(XPathError::MatchError { return Some(Err(XPathError::MatchError {
query: query_string.into(),
error, error,
query: query.into(),
} }
.into())); .into()));
} }
}; };
Some(Ok(sequence.to_xml().into())) Some(Ok(result.into()))
} }
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
enum XPathError { enum XPathError {
#[error("Failed to parse xpath '{query}': {error:#}")] #[error("Failed to parse xpath '{query}': {error:#}")]
Parse { Parse { error: SpannedError, query: String },
error: XPathParseError,
query: String,
},
#[error(transparent)] #[error(transparent)]
ReadFile(FileError), ReadFile(FileError),
#[error("Failed to parse xml '{}': {error:#}", path.display())] #[error("Failed to parse xml '{}': {error:#}", path.display())]
ParseXml { ParseXml {
error: XPathParseError, error: DocumentsError,
path: PathBuf, path: PathBuf,
}, },
#[error("Failed to match xpath '{query}': {error:#}")] #[error("Failed to match xpath '{query}': {error:#}")]
MatchError { MatchError { error: SpannedError, query: String },
error: XPathParseError,
query: String,
},
} }
#[test] #[test]
@ -129,4 +118,20 @@ fn test_xpath() {
.unwrap() .unwrap()
.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()
);
} }