use std::collections::HashSet;
use crate::schema::types::{CompiledConstraint, CompiledConstraintType};
use crate::schema::xsd::constraints::KeyValue;
#[derive(Debug, Clone)]
pub(crate) struct SelectorPath {
pub descendant: bool,
pub steps: Vec<String>,
}
#[derive(Debug, Clone)]
pub(crate) struct FieldPath {
pub steps: Vec<String>,
pub attr: Option<String>,
}
pub(crate) fn parse_selector(xpath: &str) -> Option<Vec<SelectorPath>> {
let mut paths = Vec::new();
for alt in xpath.split('|') {
let alt = alt.trim();
let (descendant, rest) = if let Some(rest) = alt.strip_prefix(".//") {
(true, rest)
} else if let Some(rest) = alt.strip_prefix("./") {
(false, rest)
} else {
(false, alt)
};
let mut steps = Vec::new();
for step in rest.split('/') {
let step = step.trim();
if step.is_empty() || step.starts_with('@') || step == "." {
return None;
}
let step = step.strip_prefix("child::").unwrap_or(step);
let local = step.rsplit(':').next().unwrap_or(step);
steps.push(local.to_string());
}
if steps.is_empty() {
return None;
}
paths.push(SelectorPath { descendant, steps });
}
Some(paths)
}
pub(crate) fn parse_field(xpath: &str) -> Option<FieldPath> {
let xpath = xpath.trim();
if xpath == "." {
return Some(FieldPath {
steps: Vec::new(),
attr: None,
});
}
if xpath.contains("//") {
return None;
}
let rest = xpath.strip_prefix("./").unwrap_or(xpath);
let mut steps = Vec::new();
let mut attr = None;
let parts: Vec<&str> = rest.split('/').collect();
for (i, part) in parts.iter().enumerate() {
let part = part.trim();
let part = part.strip_prefix("child::").unwrap_or(part);
if let Some(a) = part
.strip_prefix('@')
.or_else(|| part.strip_prefix("attribute::"))
{
if i != parts.len() - 1 {
return None; }
let local = a.rsplit(':').next().unwrap_or(a);
attr = Some(local.to_string());
} else {
if part.is_empty() || part == "." {
return None;
}
let local = part.rsplit(':').next().unwrap_or(part);
steps.push(local.to_string());
}
}
Some(FieldPath { steps, attr })
}
pub(crate) fn selector_matches(paths: &[SelectorPath], rel_path: &[&str]) -> bool {
paths.iter().any(|p| {
if p.descendant {
rel_path.len() >= p.steps.len()
&& steps_match(&p.steps, &rel_path[rel_path.len() - p.steps.len()..])
} else {
rel_path.len() == p.steps.len() && steps_match(&p.steps, rel_path)
}
})
}
pub(crate) fn field_steps_match(field: &FieldPath, rel_path: &[&str]) -> bool {
rel_path.len() == field.steps.len() && steps_match(&field.steps, rel_path)
}
fn steps_match(steps: &[String], rel_path: &[&str]) -> bool {
steps.iter().zip(rel_path).all(|(s, p)| s == "*" || s == p)
}
pub(crate) fn attr_matches(attr_name: &str, pattern: &str) -> bool {
pattern == "*" || attr_name.rsplit(':').next().unwrap_or(attr_name) == pattern
}
#[derive(Debug, Clone)]
pub(crate) enum FieldState {
Unset,
Set(String),
Multiple,
}
#[derive(Debug)]
pub(crate) struct SelectedState {
pub depth: usize,
pub fields: Vec<FieldState>,
}
#[derive(Debug)]
pub(crate) struct ScopeState {
pub constraint: CompiledConstraint,
pub selector: Vec<SelectorPath>,
pub fields: Vec<FieldPath>,
pub depth: usize,
pub selected: Vec<SelectedState>,
pub seen: HashSet<KeyValue>,
}
impl ScopeState {
pub fn new(constraint: &CompiledConstraint, depth: usize) -> Option<Self> {
let selector = parse_selector(&constraint.selector_xpath)?;
let fields = constraint
.field_xpaths
.iter()
.map(|f| parse_field(f))
.collect::<Option<Vec<_>>>()?;
Some(Self {
constraint: constraint.clone(),
selector,
fields,
depth,
selected: Vec::new(),
seen: HashSet::new(),
})
}
pub fn is_keyref(&self) -> bool {
self.constraint.constraint_type == CompiledConstraintType::KeyRef
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_selectors() {
let p = parse_selector(".//tn:key").unwrap();
assert!(p[0].descendant);
assert_eq!(p[0].steps, ["key"]);
let p = parse_selector("a/b | c").unwrap();
assert_eq!(p.len(), 2);
assert!(!p[0].descendant);
assert_eq!(p[0].steps, ["a", "b"]);
assert_eq!(p[1].steps, ["c"]);
assert!(parse_selector("a[@x]").is_some()); }
#[test]
fn parses_fields() {
let f = parse_field(".").unwrap();
assert!(f.steps.is_empty() && f.attr.is_none());
let f = parse_field("@val").unwrap();
assert_eq!(f.attr.as_deref(), Some("val"));
let f = parse_field("a/b/@val").unwrap();
assert_eq!(f.steps, ["a", "b"]);
assert_eq!(f.attr.as_deref(), Some("val"));
}
#[test]
fn matches_paths() {
let sel = parse_selector(".//uid").unwrap();
assert!(selector_matches(&sel, &["uid"]));
assert!(selector_matches(&sel, &["a", "uid"]));
assert!(!selector_matches(&sel, &["uid", "a"]));
let sel = parse_selector("a/b").unwrap();
assert!(selector_matches(&sel, &["a", "b"]));
assert!(!selector_matches(&sel, &["b"]));
}
}