use crate::ids::NameId;
use crate::namespace::table::NameTable;
use crate::parser::frames::{IdentityKind, QNameRef};
use crate::parser::location::SourceLocation;
use crate::schema::model::XsdVersion;
use crate::types::value::XmlValue;
use crate::types::PrimitiveTypeCode;
use super::active_axis::ActiveAxis;
use super::asttree::{Asttree, IdentityXPathError};
use super::errors::{error, error_with_path, ValidationError};
use crate::arenas::IdentityConstraintData;
use crate::ids::IdentityConstraintKey;
fn extract_single_atomic(value: &XmlValue) -> Option<&crate::types::value::XmlAtomicValue> {
match &value.value {
crate::types::value::XmlValueKind::Atomic(a) => Some(a),
crate::types::value::XmlValueKind::List { items, .. } if items.len() == 1 => {
Some(&items[0])
}
crate::types::value::XmlValueKind::Union(inner) => extract_single_atomic(inner),
_ => None,
}
}
fn xml_value_ic_eq(a: &XmlValue, b: &XmlValue) -> bool {
if a.type_code == b.type_code && a.value == b.value {
return true;
}
if a.type_code == b.type_code && xml_value_datetime_eq(a, b) {
return true;
}
match (extract_single_atomic(a), extract_single_atomic(b)) {
(Some(va), Some(vb)) => va == vb || xml_atomic_datetime_eq(va, vb),
_ => false,
}
}
fn xml_atomic_datetime_eq(
a: &crate::types::value::XmlAtomicValue,
b: &crate::types::value::XmlAtomicValue,
) -> bool {
use crate::types::value::XmlAtomicValue as V;
match (a, b) {
(V::DateTime(x), V::DateTime(y)) => {
x.timezone.is_some() == y.timezone.is_some()
&& x.partial_cmp(y) == Some(std::cmp::Ordering::Equal)
}
(V::Date(x), V::Date(y)) => {
x.timezone.is_some() == y.timezone.is_some()
&& x.partial_cmp(y) == Some(std::cmp::Ordering::Equal)
}
(V::Time(x), V::Time(y)) => {
x.timezone.is_some() == y.timezone.is_some()
&& x.partial_cmp(y) == Some(std::cmp::Ordering::Equal)
}
(V::Duration(x), V::Duration(y)) => x.partial_cmp(y) == Some(std::cmp::Ordering::Equal),
(V::YearMonthDuration(x), V::YearMonthDuration(y)) => {
x.partial_cmp(y) == Some(std::cmp::Ordering::Equal)
}
(V::DayTimeDuration(x), V::DayTimeDuration(y)) => {
x.partial_cmp(y) == Some(std::cmp::Ordering::Equal)
}
_ => false,
}
}
fn xml_value_datetime_eq(a: &XmlValue, b: &XmlValue) -> bool {
use crate::types::value::XmlValueKind;
match (&a.value, &b.value) {
(XmlValueKind::Atomic(va), XmlValueKind::Atomic(vb)) => xml_atomic_datetime_eq(va, vb),
_ => false,
}
}
pub(crate) struct CompiledIdentityConstraint {
pub key: IdentityConstraintKey,
pub name: NameId,
pub kind: IdentityKind,
pub selector: Asttree,
pub fields: Vec<Asttree>,
pub refer: Option<QNameRef>,
pub refer_key: Option<IdentityConstraintKey>,
pub field_count: usize,
pub target_namespace: Option<NameId>,
}
impl CompiledIdentityConstraint {
pub fn compile(
data: &IdentityConstraintData,
key: IdentityConstraintKey,
name_table: &NameTable,
schema_xpath_default_ns: Option<NameId>,
target_namespace: Option<NameId>,
xsd_version: XsdVersion,
) -> Result<Self, IdentityXPathError> {
let selector = Asttree::compile_selector(
&data.selector.xpath,
&data.selector.ns_snapshot,
name_table,
data.selector.xpath_default_namespace.as_deref(),
schema_xpath_default_ns,
target_namespace,
xsd_version,
)?;
let selector_level_ns = match &data.selector.xpath_default_namespace {
Some(val) => match val.as_str() {
"##targetNamespace" => target_namespace,
"##local" => None,
uri => Some(name_table.add(uri)),
},
None => schema_xpath_default_ns,
};
let fields: Vec<Asttree> = data
.fields
.iter()
.map(|f| {
Asttree::compile_field(
&f.xpath,
&f.ns_snapshot,
name_table,
f.xpath_default_namespace.as_deref(),
selector_level_ns,
target_namespace,
xsd_version,
)
})
.collect::<Result<Vec<_>, _>>()?;
let field_count = fields.len();
Ok(CompiledIdentityConstraint {
key,
name: data.name,
kind: data.kind,
selector,
fields,
refer: data.refer.clone(),
refer_key: None,
field_count,
target_namespace,
})
}
}
#[derive(Debug, Clone)]
pub struct KeyFieldValue {
pub string_value: String,
pub typed_value: Option<XmlValue>,
}
impl PartialEq for KeyFieldValue {
fn eq(&self, other: &Self) -> bool {
match (&self.typed_value, &other.typed_value) {
(Some(a), Some(b)) => {
let prim_a = a.primitive_type();
let prim_b = b.primitive_type();
match (prim_a, prim_b) {
(Some(pa), Some(pb)) if pa == pb => {
if pa == PrimitiveTypeCode::Decimal {
match (a.as_decimal(), b.as_decimal()) {
(Some(da), Some(db)) => da == db,
_ => xml_value_ic_eq(a, b),
}
} else {
xml_value_ic_eq(a, b)
}
}
(Some(_), Some(_)) => false, _ => self.string_value == other.string_value,
}
}
_ => self.string_value == other.string_value,
}
}
}
impl Eq for KeyFieldValue {}
#[derive(Debug, Clone)]
pub struct KeySequence {
pub fields: Vec<Option<KeyFieldValue>>,
}
impl KeySequence {
pub fn is_complete(&self) -> bool {
self.fields.iter().all(|f| f.is_some())
}
}
impl PartialEq for KeySequence {
fn eq(&self, other: &Self) -> bool {
if self.fields.len() != other.fields.len() {
return false;
}
for (a, b) in self.fields.iter().zip(other.fields.iter()) {
match (a, b) {
(Some(va), Some(vb)) => {
if va != vb {
return false;
}
}
(None, None) => {
}
_ => return false,
}
}
true
}
}
impl Eq for KeySequence {}
pub struct KeyTable {
pub ic_key: IdentityConstraintKey,
pub constraint_name: NameId,
pub kind: IdentityKind,
pub sequences: Vec<KeySequence>,
}
impl KeyTable {
pub fn new(ic_key: IdentityConstraintKey, constraint_name: NameId, kind: IdentityKind) -> Self {
KeyTable {
ic_key,
constraint_name,
kind,
sequences: Vec::new(),
}
}
pub fn add_sequence(
&mut self,
seq: KeySequence,
name_table: &NameTable,
element_path: &str,
location: Option<SourceLocation>,
) -> Vec<ValidationError> {
let mut errors = Vec::new();
let name = name_table.resolve(self.constraint_name);
match self.kind {
IdentityKind::Key => {
if !seq.is_complete() {
errors.push(error_with_path(
"cvc-identity-constraint.4.2.1",
format!("Key constraint '{}': not all fields have values", name),
location.clone(),
element_path,
));
}
if self.find_duplicate(&seq).is_some() {
errors.push(error_with_path(
"cvc-identity-constraint.4.2.2",
format!("Key constraint '{}': duplicate key value detected", name),
location,
element_path,
));
}
}
IdentityKind::Unique => {
if seq.is_complete() && self.find_duplicate(&seq).is_some() {
errors.push(error_with_path(
"cvc-identity-constraint.4.2.2",
format!("Unique constraint '{}': duplicate key value detected", name),
location,
element_path,
));
}
}
IdentityKind::Keyref => {
}
}
self.sequences.push(seq);
errors
}
pub fn check_keyref_against(
&self,
target: &KeyTable,
name_table: &NameTable,
) -> Vec<ValidationError> {
let mut errors = Vec::new();
let name = name_table.resolve(self.constraint_name);
for seq in &self.sequences {
if !seq.is_complete() {
continue;
}
let found = target.sequences.iter().any(|ts| ts == seq);
if !found {
errors.push(error(
"cvc-identity-constraint.4.3",
format!(
"Keyref constraint '{}': no matching key value found in referenced constraint '{}'",
name,
name_table.resolve(target.constraint_name)
),
None,
));
}
}
errors
}
fn find_duplicate(&self, seq: &KeySequence) -> Option<usize> {
self.sequences.iter().position(|existing| existing == seq)
}
}
struct FieldCollectionFrame {
fields: Vec<ActiveAxis>,
current_key_sequence: Vec<Option<KeyFieldValue>>,
field_match_count: Vec<u8>,
}
pub(crate) struct ConstraintStruct {
pub ic_key: IdentityConstraintKey,
pub selector: ActiveAxis,
field_asttrees: Vec<Asttree>,
field_count: usize,
collection_stack: Vec<FieldCollectionFrame>,
pub key_table: KeyTable,
}
impl ConstraintStruct {
pub fn new(compiled: &CompiledIdentityConstraint) -> Self {
let selector = ActiveAxis::new(compiled.selector.clone());
ConstraintStruct {
ic_key: compiled.key,
selector,
field_asttrees: compiled.fields.clone(),
field_count: compiled.field_count,
collection_stack: Vec::new(),
key_table: KeyTable::new(compiled.key, compiled.name, compiled.kind),
}
}
#[cfg(test)]
pub fn collecting_fields(&self) -> bool {
!self.collection_stack.is_empty()
}
pub fn activate(&mut self) -> bool {
let is_scope_match = self.selector.activate();
if is_scope_match {
self.push_field_collection();
}
is_scope_match
}
pub fn start_element(&mut self, local_name: NameId, ns: NameId) {
self.selector.move_to_start_element(local_name, ns);
for frame in &mut self.collection_stack {
for field in &mut frame.fields {
field.move_to_start_element(local_name, ns);
}
}
if self.selector.entered_match() {
self.push_field_collection();
}
}
pub fn start_element_skipped(&mut self, _local_name: NameId, _ns: NameId) {
self.selector.move_to_skipped_element();
for frame in &mut self.collection_stack {
for field in &mut frame.fields {
field.move_to_skipped_element();
}
}
}
pub fn matching_fields(&self, local_name: NameId, ns: NameId) -> Vec<usize> {
let frame = match self.collection_stack.last() {
Some(f) => f,
None => return Vec::new(),
};
let mut indices = Vec::new();
for (i, field) in frame.fields.iter().enumerate() {
if field.matches_attribute(local_name, ns) {
indices.push(i);
}
}
indices
}
pub fn set_field_value(
&mut self,
field_idx: usize,
string_value: String,
typed_value: Option<XmlValue>,
) -> bool {
if let Some(frame) = self.collection_stack.last_mut() {
if field_idx < frame.current_key_sequence.len() {
let already_matched = frame.field_match_count[field_idx] > 0;
frame.field_match_count[field_idx] =
frame.field_match_count[field_idx].saturating_add(1);
frame.current_key_sequence[field_idx] = Some(KeyFieldValue {
string_value,
typed_value,
});
return already_matched;
}
}
false
}
pub fn increment_field_match_count(&mut self, field_idx: usize) -> bool {
if let Some(frame) = self.collection_stack.last_mut() {
if field_idx < frame.field_match_count.len() {
let already_matched = frame.field_match_count[field_idx] > 0;
frame.field_match_count[field_idx] =
frame.field_match_count[field_idx].saturating_add(1);
return already_matched;
}
}
false
}
#[allow(clippy::too_many_arguments)]
pub fn end_element_with_text(
&mut self,
text_content: &str,
typed_value: Option<&XmlValue>,
is_nil: bool,
is_complex_content: bool,
name_table: &NameTable,
element_path: &str,
location: Option<SourceLocation>,
) -> Vec<ValidationError> {
let mut errors = Vec::new();
for frame in &mut self.collection_stack {
for (field_idx, field) in frame.fields.iter_mut().enumerate() {
field.end_element();
if field.exited_match() {
if frame.field_match_count[field_idx] > 0 {
let name = name_table.resolve(self.key_table.constraint_name);
errors.push(error_with_path(
"cvc-identity-constraint.4.2.1",
format!(
"Identity constraint '{}': field {} matches more than one node",
name,
field_idx + 1
),
location.clone(),
element_path,
));
} else if frame.current_key_sequence[field_idx].is_none() {
frame.field_match_count[field_idx] =
frame.field_match_count[field_idx].saturating_add(1);
if is_complex_content {
let constraint_name =
name_table.resolve(self.key_table.constraint_name);
errors.push(error_with_path(
"cvc-identity-constraint.4",
format!(
"Identity constraint '{}': field matched an element with \
complex content type (element-only or mixed); \
typed field values must be simple-typed",
constraint_name
),
location.clone(),
element_path,
));
} else if typed_value.is_some() {
frame.current_key_sequence[field_idx] = Some(KeyFieldValue {
string_value: text_content.to_string(),
typed_value: typed_value.cloned(),
});
} else if is_nil {
frame.current_key_sequence[field_idx] = Some(KeyFieldValue {
string_value: String::new(),
typed_value: None,
});
}
}
}
}
}
self.selector.end_element();
if self.selector.exited_match() {
if let Some(frame) = self.collection_stack.pop() {
let seq = KeySequence {
fields: frame.current_key_sequence,
};
errors.extend(
self.key_table
.add_sequence(seq, name_table, element_path, location),
);
}
}
errors
}
pub fn is_active(&self) -> bool {
self.selector.is_active()
}
fn push_field_collection(&mut self) {
let fields: Vec<ActiveAxis> = self
.field_asttrees
.iter()
.map(|ast| {
let mut axis = ActiveAxis::new(ast.clone());
axis.activate();
axis
})
.collect();
self.collection_stack.push(FieldCollectionFrame {
fields,
current_key_sequence: vec![None; self.field_count],
field_match_count: vec![0; self.field_count],
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::namespace::context::NamespaceContextSnapshot;
use crate::namespace::table::NameTable;
use crate::schema::model::XsdVersion;
use crate::types::value::{XmlAtomicValue, XmlValueKind};
use crate::types::XmlTypeCode;
fn make_string_field(s: &str) -> KeyFieldValue {
KeyFieldValue {
string_value: s.to_string(),
typed_value: None,
}
}
fn make_typed_field(s: &str, type_code: XmlTypeCode, value: XmlValueKind) -> KeyFieldValue {
KeyFieldValue {
string_value: s.to_string(),
typed_value: Some(XmlValue::new(type_code, value)),
}
}
fn make_name_table() -> NameTable {
NameTable::new()
}
#[test]
fn key_field_value_untyped_same_string_equal() {
let a = make_string_field("hello");
let b = make_string_field("hello");
assert_eq!(a, b);
}
#[test]
fn key_field_value_untyped_different_string_not_equal() {
let a = make_string_field("hello");
let b = make_string_field("world");
assert_ne!(a, b);
}
#[test]
fn key_field_value_typed_same_primitive_same_value_equal() {
let a = make_typed_field(
"42",
XmlTypeCode::Integer,
XmlValueKind::Atomic(XmlAtomicValue::Integer(42.into())),
);
let b = make_typed_field(
"42",
XmlTypeCode::Integer,
XmlValueKind::Atomic(XmlAtomicValue::Integer(42.into())),
);
assert_eq!(a, b);
}
#[test]
fn key_field_value_typed_same_primitive_different_value_not_equal() {
let a = make_typed_field(
"42",
XmlTypeCode::Integer,
XmlValueKind::Atomic(XmlAtomicValue::Integer(42.into())),
);
let b = make_typed_field(
"99",
XmlTypeCode::Integer,
XmlValueKind::Atomic(XmlAtomicValue::Integer(99.into())),
);
assert_ne!(a, b);
}
#[test]
fn key_field_value_typed_different_primitive_not_equal() {
let a = make_typed_field(
"5",
XmlTypeCode::Integer,
XmlValueKind::Atomic(XmlAtomicValue::Integer(5.into())),
);
let b = make_typed_field(
"5",
XmlTypeCode::String,
XmlValueKind::Atomic(XmlAtomicValue::String("5".to_string())),
);
assert_ne!(a, b);
}
#[test]
fn key_field_value_one_typed_one_untyped_fallback_string() {
let a = make_typed_field(
"hello",
XmlTypeCode::String,
XmlValueKind::Atomic(XmlAtomicValue::String("hello".to_string())),
);
let b = make_string_field("hello");
assert_eq!(a, b);
let c = make_typed_field(
"42",
XmlTypeCode::Integer,
XmlValueKind::Atomic(XmlAtomicValue::Integer(42.into())),
);
let d = make_string_field("99");
assert_ne!(c, d);
}
#[test]
fn key_sequence_is_complete_all_present() {
let seq = KeySequence {
fields: vec![Some(make_string_field("a")), Some(make_string_field("b"))],
};
assert!(seq.is_complete());
}
#[test]
fn key_sequence_is_complete_missing_field() {
let seq = KeySequence {
fields: vec![Some(make_string_field("a")), None],
};
assert!(!seq.is_complete());
}
#[test]
fn key_sequence_equal() {
let a = KeySequence {
fields: vec![Some(make_string_field("x")), Some(make_string_field("y"))],
};
let b = KeySequence {
fields: vec![Some(make_string_field("x")), Some(make_string_field("y"))],
};
assert_eq!(a, b);
}
#[test]
fn key_sequence_not_equal() {
let a = KeySequence {
fields: vec![Some(make_string_field("x")), Some(make_string_field("y"))],
};
let b = KeySequence {
fields: vec![Some(make_string_field("x")), Some(make_string_field("z"))],
};
assert_ne!(a, b);
}
#[test]
fn key_sequence_both_none_equal() {
let a = KeySequence {
fields: vec![Some(make_string_field("x")), None],
};
let b = KeySequence {
fields: vec![Some(make_string_field("x")), None],
};
assert_eq!(a, b);
}
#[test]
fn key_table_key_duplicate_error() {
let nt = make_name_table();
let name = nt.add("pk");
let mut table = KeyTable::new(IdentityConstraintKey::default(), name, IdentityKind::Key);
let seq1 = KeySequence {
fields: vec![Some(make_string_field("1"))],
};
let errs = table.add_sequence(seq1, &nt, "/root/item[1]", None);
assert!(errs.is_empty());
let seq2 = KeySequence {
fields: vec![Some(make_string_field("1"))],
};
let errs = table.add_sequence(seq2, &nt, "/root/item[2]", None);
assert_eq!(errs.len(), 1);
assert_eq!(errs[0].constraint, "cvc-identity-constraint.4.2.2");
}
#[test]
fn key_table_key_incomplete_error() {
let nt = make_name_table();
let name = nt.add("pk");
let mut table = KeyTable::new(IdentityConstraintKey::default(), name, IdentityKind::Key);
let seq = KeySequence {
fields: vec![Some(make_string_field("a")), None],
};
let errs = table.add_sequence(seq, &nt, "/root/item[1]", None);
assert!(errs
.iter()
.any(|e| e.constraint == "cvc-identity-constraint.4.2.1"));
}
#[test]
fn key_table_unique_duplicate_error() {
let nt = make_name_table();
let name = nt.add("uq");
let mut table = KeyTable::new(IdentityConstraintKey::default(), name, IdentityKind::Unique);
let seq1 = KeySequence {
fields: vec![Some(make_string_field("val"))],
};
let errs = table.add_sequence(seq1, &nt, "/root/item[1]", None);
assert!(errs.is_empty());
let seq2 = KeySequence {
fields: vec![Some(make_string_field("val"))],
};
let errs = table.add_sequence(seq2, &nt, "/root/item[2]", None);
assert_eq!(errs.len(), 1);
assert_eq!(errs[0].constraint, "cvc-identity-constraint.4.2.2");
}
#[test]
fn key_table_unique_incomplete_no_error() {
let nt = make_name_table();
let name = nt.add("uq");
let mut table = KeyTable::new(IdentityConstraintKey::default(), name, IdentityKind::Unique);
let seq = KeySequence { fields: vec![None] };
let errs = table.add_sequence(seq, &nt, "/root/item[1]", None);
assert!(errs.is_empty());
}
#[test]
fn key_table_keyref_no_error() {
let nt = make_name_table();
let name = nt.add("fk");
let mut table = KeyTable::new(IdentityConstraintKey::default(), name, IdentityKind::Keyref);
let seq = KeySequence {
fields: vec![Some(make_string_field("anything"))],
};
let errs = table.add_sequence(seq, &nt, "/root/item[1]", None);
assert!(errs.is_empty());
}
#[test]
fn check_keyref_against_matching() {
let nt = make_name_table();
let pk_name = nt.add("pk");
let fk_name = nt.add("fk");
let mut key_table =
KeyTable::new(IdentityConstraintKey::default(), pk_name, IdentityKind::Key);
key_table.sequences.push(KeySequence {
fields: vec![Some(make_string_field("1"))],
});
let mut keyref_table = KeyTable::new(
IdentityConstraintKey::default(),
fk_name,
IdentityKind::Keyref,
);
keyref_table.sequences.push(KeySequence {
fields: vec![Some(make_string_field("1"))],
});
let errs = keyref_table.check_keyref_against(&key_table, &nt);
assert!(errs.is_empty());
}
#[test]
fn check_keyref_against_missing() {
let nt = make_name_table();
let pk_name = nt.add("pk");
let fk_name = nt.add("fk");
let key_table = KeyTable::new(IdentityConstraintKey::default(), pk_name, IdentityKind::Key);
let mut keyref_table = KeyTable::new(
IdentityConstraintKey::default(),
fk_name,
IdentityKind::Keyref,
);
keyref_table.sequences.push(KeySequence {
fields: vec![Some(make_string_field("missing"))],
});
let errs = keyref_table.check_keyref_against(&key_table, &nt);
assert_eq!(errs.len(), 1);
assert_eq!(errs[0].constraint, "cvc-identity-constraint.4.3");
}
fn make_selector_result(xpath: &str) -> crate::parser::frames::SelectorResult {
crate::parser::frames::SelectorResult {
xpath: xpath.to_string(),
xpath_default_namespace: None,
ns_snapshot: NamespaceContextSnapshot::default(),
id: None,
annotation: None,
source: None,
}
}
fn make_field_result(xpath: &str) -> crate::parser::frames::FieldResult {
crate::parser::frames::FieldResult {
xpath: xpath.to_string(),
xpath_default_namespace: None,
ns_snapshot: NamespaceContextSnapshot::default(),
id: None,
annotation: None,
source: None,
}
}
fn make_identity_data(
kind: IdentityKind,
name: NameId,
selector_xpath: &str,
field_xpaths: &[&str],
) -> IdentityConstraintData {
IdentityConstraintData {
kind,
name,
ref_name: None,
refer: None,
selector: make_selector_result(selector_xpath),
fields: field_xpaths.iter().map(|x| make_field_result(x)).collect(),
id: None,
annotation: None,
source: None,
}
}
#[test]
fn compile_simple_constraint() {
let nt = make_name_table();
let name = nt.add("testKey");
let key = IdentityConstraintKey::default();
let data = make_identity_data(IdentityKind::Key, name, "./item", &["@id"]);
let compiled =
CompiledIdentityConstraint::compile(&data, key, &nt, None, None, XsdVersion::V1_0);
assert!(compiled.is_ok());
let compiled = compiled.unwrap();
assert_eq!(compiled.field_count, 1);
assert_eq!(compiled.kind, IdentityKind::Key);
assert_eq!(compiled.name, name);
}
#[test]
fn compile_invalid_xpath_propagates_error() {
let nt = make_name_table();
let name = nt.add("badKey");
let key = IdentityConstraintKey::default();
let data = make_identity_data(IdentityKind::Key, name, "///invalid", &["@id"]);
let result =
CompiledIdentityConstraint::compile(&data, key, &nt, None, None, XsdVersion::V1_0);
assert!(result.is_err());
}
#[test]
fn constraint_struct_lifecycle() {
let nt = make_name_table();
let name = nt.add("testKey");
let key = IdentityConstraintKey::default();
let data = make_identity_data(IdentityKind::Key, name, "./item", &["@id"]);
let compiled =
CompiledIdentityConstraint::compile(&data, key, &nt, None, None, XsdVersion::V1_0)
.unwrap();
let mut cs = ConstraintStruct::new(&compiled);
let scope_match = cs.activate();
assert!(!scope_match); assert!(cs.is_active());
let item_name = nt.add("item");
let empty_ns = NameId(0);
cs.start_element(item_name, empty_ns);
assert!(cs.collecting_fields());
let id_name = nt.add("id");
let matches = cs.matching_fields(id_name, empty_ns);
assert_eq!(matches, vec![0]);
cs.set_field_value(0, "val1".to_string(), None);
let errors = cs.end_element_with_text("", None, false, false, &nt, "/root/item[1]", None);
assert!(errors.is_empty());
assert_eq!(cs.key_table.sequences.len(), 1);
assert!(cs.key_table.sequences[0].is_complete());
assert_eq!(
cs.key_table.sequences[0].fields[0]
.as_ref()
.unwrap()
.string_value,
"val1"
);
}
#[test]
fn constraint_struct_nested_selector() {
let nt = make_name_table();
let name = nt.add("uq");
let key = IdentityConstraintKey::default();
let data = make_identity_data(IdentityKind::Unique, name, ".//item", &["@id"]);
let compiled =
CompiledIdentityConstraint::compile(&data, key, &nt, None, None, XsdVersion::V1_0)
.unwrap();
let mut cs = ConstraintStruct::new(&compiled);
cs.activate();
let item = nt.add("item");
let id = nt.add("id");
let ns = NameId(0);
cs.start_element(item, ns);
assert!(cs.collecting_fields());
let m = cs.matching_fields(id, ns);
assert_eq!(m, vec![0]);
cs.set_field_value(0, "outer".to_string(), None);
cs.start_element(item, ns);
let m = cs.matching_fields(id, ns);
assert_eq!(m, vec![0]); cs.set_field_value(0, "inner".to_string(), None);
let errors = cs.end_element_with_text("", None, false, false, &nt, "/root/item/item", None);
assert!(errors.is_empty());
assert_eq!(cs.key_table.sequences.len(), 1);
assert_eq!(
cs.key_table.sequences[0].fields[0]
.as_ref()
.unwrap()
.string_value,
"inner"
);
assert!(cs.collecting_fields());
let errors = cs.end_element_with_text("", None, false, false, &nt, "/root/item", None);
assert!(errors.is_empty());
assert_eq!(cs.key_table.sequences.len(), 2);
assert_eq!(
cs.key_table.sequences[1].fields[0]
.as_ref()
.unwrap()
.string_value,
"outer"
);
assert!(!cs.collecting_fields());
}
#[test]
fn constraint_struct_multi_field_same_attr() {
let nt = make_name_table();
let name = nt.add("uq2");
let key = IdentityConstraintKey::default();
let data = make_identity_data(IdentityKind::Unique, name, "./item", &["@id", "@id"]);
let compiled =
CompiledIdentityConstraint::compile(&data, key, &nt, None, None, XsdVersion::V1_0)
.unwrap();
let mut cs = ConstraintStruct::new(&compiled);
cs.activate();
let item = nt.add("item");
let id = nt.add("id");
let ns = NameId(0);
cs.start_element(item, ns);
let matches = cs.matching_fields(id, ns);
assert_eq!(matches, vec![0, 1]);
cs.set_field_value(0, "v".to_string(), None);
cs.set_field_value(1, "v".to_string(), None);
let errors = cs.end_element_with_text("", None, false, false, &nt, "/root/item", None);
assert!(errors.is_empty());
assert!(cs.key_table.sequences[0].is_complete());
}
}