#![allow(dead_code)]
use std::fmt;
use crate::ids::NameId;
use crate::namespace::context::NamespaceContextSnapshot;
use crate::namespace::table::NameTable;
use crate::schema::model::XsdVersion;
use super::identity_lexer::IdXPathLexError;
#[derive(Debug, Clone)]
pub enum IdentityXPathError {
Lex(IdXPathLexError),
Parse { message: String, position: usize },
UnboundPrefix { prefix: String, position: usize },
Restriction { message: String, position: usize },
}
impl fmt::Display for IdentityXPathError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IdentityXPathError::Lex(e) => write!(f, "{e}"),
IdentityXPathError::Parse { message, position } => {
write!(
f,
"identity XPath parse error at position {position}: {message}"
)
}
IdentityXPathError::UnboundPrefix { prefix, position } => {
write!(
f,
"identity XPath error at position {position}: unbound prefix `{prefix}`"
)
}
IdentityXPathError::Restriction { message, position } => {
write!(
f,
"identity XPath restriction at position {position}: {message}"
)
}
}
}
}
impl std::error::Error for IdentityXPathError {}
impl From<IdXPathLexError> for IdentityXPathError {
fn from(e: IdXPathLexError) -> Self {
IdentityXPathError::Lex(e)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum NamespaceMatch {
NoNamespace,
Exact(NameId),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum NameTest {
Wildcard,
NamespaceWildcard(NameId),
QName {
namespace: NamespaceMatch,
local_name: NameId,
},
}
impl NameTest {
pub(crate) fn matches(&self, namespace_uri: NameId, local_name: NameId) -> bool {
match self {
NameTest::Wildcard => true,
NameTest::NamespaceWildcard(ns) => namespace_uri == *ns,
NameTest::QName {
namespace,
local_name: ln,
} => {
*ln == local_name
&& match namespace {
NamespaceMatch::NoNamespace => {
namespace_uri.0 == 0
}
NamespaceMatch::Exact(ns) => namespace_uri == *ns,
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum AstStep {
SelfNode,
Child(NameTest),
Attribute(NameTest),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct AstPath {
pub descendant: bool,
pub steps: Vec<AstStep>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Asttree {
pub paths: Vec<AstPath>,
}
impl Asttree {
pub fn compile_selector(
xpath: &str,
ns_snapshot: &NamespaceContextSnapshot,
name_table: &NameTable,
own_xpath_default_ns: Option<&str>,
schema_xpath_default_ns: Option<NameId>,
target_namespace: Option<NameId>,
xsd_version: XsdVersion,
) -> Result<Asttree, IdentityXPathError> {
use super::identity_parser::IdXPathParser;
let (effective_own, effective_schema) = match xsd_version {
XsdVersion::V1_0 => (None, None),
XsdVersion::V1_1 => (own_xpath_default_ns, schema_xpath_default_ns),
};
let unprefixed_ns = resolve_effective_default_ns(
effective_own,
effective_schema,
ns_snapshot,
target_namespace,
name_table,
);
let mut parser = IdXPathParser::new(xpath, ns_snapshot, name_table, unprefixed_ns)?;
parser.parse_selector()
}
pub fn compile_field(
xpath: &str,
ns_snapshot: &NamespaceContextSnapshot,
name_table: &NameTable,
own_xpath_default_ns: Option<&str>,
schema_xpath_default_ns: Option<NameId>,
target_namespace: Option<NameId>,
xsd_version: XsdVersion,
) -> Result<Asttree, IdentityXPathError> {
use super::identity_parser::IdXPathParser;
let (effective_own, effective_schema) = match xsd_version {
XsdVersion::V1_0 => (None, None),
XsdVersion::V1_1 => (own_xpath_default_ns, schema_xpath_default_ns),
};
let unprefixed_ns = resolve_effective_default_ns(
effective_own,
effective_schema,
ns_snapshot,
target_namespace,
name_table,
);
let mut parser = IdXPathParser::new(xpath, ns_snapshot, name_table, unprefixed_ns)?;
parser.parse_field()
}
}
fn resolve_effective_default_ns(
own_raw: Option<&str>,
schema_raw_id: Option<NameId>,
ns_snapshot: &NamespaceContextSnapshot,
target_namespace: Option<NameId>,
name_table: &NameTable,
) -> NamespaceMatch {
let effective = if let Some(raw) = own_raw {
Some(raw.to_string())
} else {
schema_raw_id.map(|id| name_table.resolve(id))
};
match effective.as_deref() {
Some("##defaultNamespace") => match ns_snapshot.default_ns {
Some(ns_id) => NamespaceMatch::Exact(ns_id),
None => NamespaceMatch::NoNamespace,
},
Some("##targetNamespace") => match target_namespace {
Some(ns_id) => NamespaceMatch::Exact(ns_id),
None => NamespaceMatch::NoNamespace,
},
Some("##local") => NamespaceMatch::NoNamespace,
Some(uri) => NamespaceMatch::Exact(name_table.add(uri)),
None => NamespaceMatch::NoNamespace,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::namespace::context::NamespaceContextSnapshot;
use crate::namespace::table::NameTable;
#[test]
fn no_default_unprefixed() {
let table = NameTable::new();
let snapshot = NamespaceContextSnapshot::default();
let result = resolve_effective_default_ns(None, None, &snapshot, None, &table);
assert_eq!(result, NamespaceMatch::NoNamespace);
}
#[test]
fn own_default_namespace() {
let table = NameTable::new();
let uri_id = table.add("http://example.com/ns");
let snapshot = NamespaceContextSnapshot {
default_ns: Some(uri_id),
bindings: vec![],
};
let result =
resolve_effective_default_ns(Some("##defaultNamespace"), None, &snapshot, None, &table);
assert_eq!(result, NamespaceMatch::Exact(uri_id));
}
#[test]
fn own_target_namespace() {
let table = NameTable::new();
let tns = table.add("http://example.com/target");
let snapshot = NamespaceContextSnapshot::default();
let result = resolve_effective_default_ns(
Some("##targetNamespace"),
None,
&snapshot,
Some(tns),
&table,
);
assert_eq!(result, NamespaceMatch::Exact(tns));
}
#[test]
fn own_local() {
let table = NameTable::new();
let snapshot = NamespaceContextSnapshot::default();
let result = resolve_effective_default_ns(Some("##local"), None, &snapshot, None, &table);
assert_eq!(result, NamespaceMatch::NoNamespace);
}
#[test]
fn own_literal_uri() {
let table = NameTable::new();
let snapshot = NamespaceContextSnapshot::default();
let result =
resolve_effective_default_ns(Some("http://example.com"), None, &snapshot, None, &table);
let expected_id = table.add("http://example.com");
assert_eq!(result, NamespaceMatch::Exact(expected_id));
}
#[test]
fn cascade_own_over_schema() {
let table = NameTable::new();
let schema_ns = table.add("http://example.com");
let snapshot = NamespaceContextSnapshot::default();
let result =
resolve_effective_default_ns(Some("##local"), Some(schema_ns), &snapshot, None, &table);
assert_eq!(result, NamespaceMatch::NoNamespace);
}
#[test]
fn cascade_schema_fallback() {
let table = NameTable::new();
let schema_ns = table.add("http://example.com");
let snapshot = NamespaceContextSnapshot::default();
let result = resolve_effective_default_ns(None, Some(schema_ns), &snapshot, None, &table);
assert_eq!(result, NamespaceMatch::Exact(schema_ns));
}
#[test]
fn default_ns_absent() {
let table = NameTable::new();
let snapshot = NamespaceContextSnapshot {
default_ns: None,
bindings: vec![],
};
let result =
resolve_effective_default_ns(Some("##defaultNamespace"), None, &snapshot, None, &table);
assert_eq!(result, NamespaceMatch::NoNamespace);
}
#[test]
fn target_ns_absent() {
let table = NameTable::new();
let snapshot = NamespaceContextSnapshot::default();
let result =
resolve_effective_default_ns(Some("##targetNamespace"), None, &snapshot, None, &table);
assert_eq!(result, NamespaceMatch::NoNamespace);
}
#[test]
fn wildcard_matches_anything() {
let table = NameTable::new();
let ns = table.add("http://example.com");
let ln = table.add("foo");
assert!(NameTest::Wildcard.matches(ns, ln));
}
#[test]
fn namespace_wildcard_matches_same_ns() {
let table = NameTable::new();
let ns = table.add("http://example.com");
let ln = table.add("foo");
assert!(NameTest::NamespaceWildcard(ns).matches(ns, ln));
}
#[test]
fn namespace_wildcard_rejects_different_ns() {
let table = NameTable::new();
let ns1 = table.add("http://example.com/1");
let ns2 = table.add("http://example.com/2");
let ln = table.add("foo");
assert!(!NameTest::NamespaceWildcard(ns1).matches(ns2, ln));
}
#[test]
fn qname_exact_match() {
let table = NameTable::new();
let ns = table.add("http://example.com");
let ln = table.add("foo");
let test = NameTest::QName {
namespace: NamespaceMatch::Exact(ns),
local_name: ln,
};
assert!(test.matches(ns, ln));
}
#[test]
fn qname_no_namespace_match() {
let table = NameTable::new();
let ln = table.add("foo");
let test = NameTest::QName {
namespace: NamespaceMatch::NoNamespace,
local_name: ln,
};
use crate::namespace::table::well_known;
assert!(test.matches(well_known::EMPTY, ln));
}
#[test]
fn qname_rejects_wrong_local() {
let table = NameTable::new();
let ns = table.add("http://example.com");
let ln1 = table.add("foo");
let ln2 = table.add("bar");
let test = NameTest::QName {
namespace: NamespaceMatch::Exact(ns),
local_name: ln1,
};
assert!(!test.matches(ns, ln2));
}
#[test]
fn compile_selector_v10_ignores_own_xpath_default_ns() {
let table = NameTable::new();
let snapshot = NamespaceContextSnapshot::default();
let tree = Asttree::compile_selector(
"foo",
&snapshot,
&table,
Some("http://example.com/default"),
None,
None,
XsdVersion::V1_0,
)
.unwrap();
match &tree.paths[0].steps[0] {
AstStep::Child(NameTest::QName { namespace, .. }) => {
assert_eq!(*namespace, NamespaceMatch::NoNamespace);
}
other => panic!("expected Child(QName{{NoNamespace, ..}}), got {other:?}"),
}
}
#[test]
fn compile_selector_v11_applies_own_xpath_default_ns() {
let table = NameTable::new();
let snapshot = NamespaceContextSnapshot::default();
let ns_id = table.add("http://example.com/default");
let tree = Asttree::compile_selector(
"foo",
&snapshot,
&table,
Some("http://example.com/default"),
None,
None,
XsdVersion::V1_1,
)
.unwrap();
match &tree.paths[0].steps[0] {
AstStep::Child(NameTest::QName { namespace, .. }) => {
assert_eq!(*namespace, NamespaceMatch::Exact(ns_id));
}
other => panic!("expected Child(QName{{Exact, ..}}), got {other:?}"),
}
}
#[test]
fn compile_selector_v10_ignores_schema_xpath_default_ns() {
let table = NameTable::new();
let schema_ns = table.add("http://example.com/schema");
let snapshot = NamespaceContextSnapshot::default();
let tree = Asttree::compile_selector(
"foo",
&snapshot,
&table,
None,
Some(schema_ns),
None,
XsdVersion::V1_0,
)
.unwrap();
match &tree.paths[0].steps[0] {
AstStep::Child(NameTest::QName { namespace, .. }) => {
assert_eq!(*namespace, NamespaceMatch::NoNamespace);
}
other => panic!("expected Child(QName{{NoNamespace, ..}}), got {other:?}"),
}
}
#[test]
fn compile_field_v10_ignores_xpath_default_ns() {
let table = NameTable::new();
let snapshot = NamespaceContextSnapshot::default();
let tree = Asttree::compile_field(
"foo",
&snapshot,
&table,
Some("http://example.com/default"),
None,
None,
XsdVersion::V1_0,
)
.unwrap();
match &tree.paths[0].steps[0] {
AstStep::Child(NameTest::QName { namespace, .. }) => {
assert_eq!(*namespace, NamespaceMatch::NoNamespace);
}
other => panic!("expected Child(QName{{NoNamespace, ..}}), got {other:?}"),
}
}
}