use anyhow::Result;
use xee_xpath::{context, Queries, Query};
use xee_xpath_load::{convert_string, ContextLoadable};
use crate::{catalog::LoadContext, hashmap::FxIndexSet};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct DependencySpec {
pub(crate) type_: String,
pub(crate) value: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Dependency {
pub(crate) spec: DependencySpec,
pub(crate) satisfied: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Dependencies {
pub(crate) dependencies: Vec<Dependency>,
}
impl Dependencies {
pub(crate) fn new(dependencies: Vec<Dependency>) -> Self {
Self { dependencies }
}
}
#[derive(Debug)]
pub(crate) struct KnownDependencies {
specs: FxIndexSet<DependencySpec>,
}
impl KnownDependencies {
pub(crate) fn new(specs: &[DependencySpec]) -> Self {
let specs = specs.iter().cloned().collect();
Self { specs }
}
fn is_supported(&self, dependency: &Dependency) -> bool {
let contains = self.specs.contains(&dependency.spec);
if dependency.satisfied {
contains
} else {
!contains
}
}
}
impl Dependency {
pub(crate) fn load<'a>(queries: &'a Queries) -> Result<impl Query<Vec<Vec<Dependency>>> + 'a> {
let satisfied_query = queries.option("@satisfied/string()", convert_string)?;
let type_query = queries.one("@type/string()", convert_string)?;
let value_query = queries.one("@value/string()", convert_string)?;
let dependency_query = queries.many("dependency", move |session, item| {
let satisfied = satisfied_query.execute(session, item)?;
let satisfied = if let Some(satisfied) = satisfied {
if satisfied == "true" {
true
} else if satisfied == "false" {
false
} else {
panic!("Unexpected satisfied value: {:?}", satisfied);
}
} else {
true
};
let value = value_query.execute(session, item)?;
let values = value.split(' ');
let type_ = type_query.execute(session, item)?;
Ok(values
.map(|value| Dependency {
spec: DependencySpec {
type_: type_.clone(),
value: value.to_string(),
},
satisfied,
})
.collect::<Vec<Dependency>>())
})?;
Ok(dependency_query)
}
}
impl Dependencies {
#[cfg(test)]
pub(crate) fn empty() -> Self {
Self {
dependencies: Vec::new(),
}
}
pub(crate) fn is_spec_supported(&self, known_dependencies: &KnownDependencies) -> bool {
let mut spec_dependency_seen: bool = false;
for dependency in &self.dependencies {
if dependency.spec.type_ == "spec" {
spec_dependency_seen = true;
if known_dependencies.is_supported(dependency) {
return true;
}
}
}
!spec_dependency_seen
}
pub(crate) fn is_feature_supported(&self, known_dependencies: &KnownDependencies) -> bool {
for dependency in &self.dependencies {
if dependency.spec.type_ == "feature" && !known_dependencies.is_supported(dependency) {
return false;
}
}
true
}
pub(crate) fn is_xml_version_supported(&self, known_dependencies: &KnownDependencies) -> bool {
for dependency in &self.dependencies {
if dependency.spec.type_ == "xml-version"
&& !known_dependencies.is_supported(dependency)
{
return false;
}
}
true
}
pub(crate) fn is_xsd_version_supported(&self, known_dependencies: &KnownDependencies) -> bool {
for dependency in &self.dependencies {
if dependency.spec.type_ == "xsd-version"
&& !known_dependencies.is_supported(dependency)
{
return false;
}
}
true
}
pub(crate) fn is_supported(&self, known_dependencies: &KnownDependencies) -> bool {
if self.dependencies.is_empty() {
return true;
}
if !self.is_spec_supported(known_dependencies) {
return false;
}
if !self.is_xml_version_supported(known_dependencies) {
return false;
}
if !self.is_xsd_version_supported(known_dependencies) {
return false;
}
self.is_feature_supported(known_dependencies)
}
}
impl ContextLoadable<LoadContext> for Dependencies {
fn static_context_builder(context: &LoadContext) -> context::StaticContextBuilder {
let mut builder = context::StaticContextBuilder::default();
builder.default_element_namespace(context.catalog_ns);
builder
}
fn load_with_context(queries: &Queries, _context: &LoadContext) -> Result<impl Query<Self>> {
let dependency_query = Dependency::load(queries)?;
Ok(dependency_query.map(|dependencies, _, _| {
Ok(Dependencies {
dependencies: dependencies.into_iter().flatten().collect(),
})
}))
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
use crate::{language::XPathLanguage, ns::XPATH_TEST_NS};
#[test]
fn test_load_dependencies() {
let xml = format!(
r#"
<doc xmlns="{}">
<dependency type="feature" value="non_unicode_codepoint_collation"/>
<dependency type="spec" value="XP31 XQ31"/>
</doc>"#,
XPATH_TEST_NS
);
let context = LoadContext::new::<XPathLanguage>(PathBuf::new());
let dependencies = Dependencies::load_from_xml_with_context(&xml, &context).unwrap();
assert_eq!(
dependencies,
Dependencies {
dependencies: vec![
Dependency {
spec: DependencySpec {
type_: "feature".to_string(),
value: "non_unicode_codepoint_collation".to_string(),
},
satisfied: true,
},
Dependency {
spec: DependencySpec {
type_: "spec".to_string(),
value: "XP31".to_string(),
},
satisfied: true,
},
Dependency {
spec: DependencySpec {
type_: "spec".to_string(),
value: "XQ31".to_string(),
},
satisfied: true,
},
],
}
);
}
}