1#![cfg(feature = "xsd11")]
13
14use std::collections::HashMap;
15
16use crate::compiler::SubstitutionGroupMap;
17use crate::ids::{ComplexTypeKey, ElementKey, NameId, TypeKey};
18use crate::parser::frames::{ComplexContentResult, ParticleResult, ParticleTerm};
19use crate::schema::model::{DerivationSet, SchemaSet};
20
21#[derive(Debug, Clone, Copy)]
23pub enum DefaultBinding {
24 Element {
28 key: ElementKey,
29 resolved_type: Option<TypeKey>,
33 },
34 #[allow(dead_code)]
36 Strict,
37 #[allow(dead_code)]
39 Lax,
40 #[allow(dead_code)]
42 Skip,
43}
44
45#[derive(Debug, Clone)]
47pub enum EdcOutcome {
48 NoLocalBinding,
50 Subsumes,
52 Mismatch { reason: String },
54}
55
56pub fn check_dynamic_edc(
58 schema_set: &SchemaSet,
59 subst_groups: Option<&SubstitutionGroupMap>,
60 parent_ct: ComplexTypeKey,
61 qname: (Option<NameId>, NameId),
62 governing_type: Option<TypeKey>,
63 governing_decl: Option<ElementKey>,
64) -> EdcOutcome {
65 let bindings = collect_local_bindings(schema_set, subst_groups, parent_ct);
66 let Some(local) = bindings.get(&qname).copied() else {
67 return EdcOutcome::NoLocalBinding;
68 };
69
70 if let DefaultBinding::Element { resolved_type, .. } = local {
71 let Some(local_type) = resolved_type else {
72 return EdcOutcome::Subsumes;
73 };
74 let gov_type = governing_type
75 .or_else(|| governing_decl.and_then(|k| schema_set.arenas.elements[k].resolved_type));
76 let Some(gov_type) = gov_type else {
77 return EdcOutcome::Subsumes;
78 };
79 if !schema_set.is_type_derived_from(gov_type, local_type, DerivationSet::empty()) {
80 return EdcOutcome::Mismatch {
81 reason: "governing type is not validly substitutable for the local element's type"
82 .to_string(),
83 };
84 }
85 }
86 EdcOutcome::Subsumes
87}
88
89pub fn collect_local_bindings(
93 schema_set: &SchemaSet,
94 subst_groups: Option<&SubstitutionGroupMap>,
95 ct_key: ComplexTypeKey,
96) -> HashMap<(Option<NameId>, NameId), DefaultBinding> {
97 let mut out = HashMap::new();
98 let mut visited = std::collections::HashSet::new();
99 let mut current = Some(ct_key);
100 while let Some(k) = current {
101 if !visited.insert(k) {
102 break;
103 }
104 let ct = &schema_set.arenas.complex_types[k];
105 let target_ns = ct.target_namespace;
106 if let ComplexContentResult::Complex(content) = &ct.content {
107 if let Some(particle) = content.particle.as_ref() {
108 let mut flat_idx = 0usize;
109 walk_particle(
110 schema_set,
111 subst_groups,
112 particle,
113 target_ns,
114 &ct.resolved_content_particle_elements,
115 &ct.resolved_content_particle_types,
116 &mut flat_idx,
117 &mut out,
118 0,
119 );
120 }
121 }
122 current = match ct.resolved_base_type {
124 Some(TypeKey::Complex(parent)) => Some(parent),
125 _ => None,
126 };
127 }
128 out
129}
130
131#[allow(clippy::too_many_arguments)]
135fn walk_particle(
136 schema_set: &SchemaSet,
137 subst_groups: Option<&SubstitutionGroupMap>,
138 particle: &ParticleResult,
139 target_ns: Option<NameId>,
140 local_keys: &[Option<ElementKey>],
141 local_types: &[Option<TypeKey>],
142 flat_idx: &mut usize,
143 out: &mut HashMap<(Option<NameId>, NameId), DefaultBinding>,
144 depth: usize,
145) {
146 if depth > 64 {
147 return;
148 }
149 match &particle.term {
150 ParticleTerm::Element(elem) => {
151 let (qname_ns, qname_local, key, resolved_type) = if let Some(ref_qn) = &elem.ref_name {
152 let key = schema_set.lookup_element(ref_qn.namespace, ref_qn.local_name);
153 let ty = key.and_then(|k| schema_set.arenas.elements[k].resolved_type);
154 let idx = *flat_idx;
155 *flat_idx += 1;
156 let _ = idx; (ref_qn.namespace, ref_qn.local_name, key, ty)
158 } else if let Some(name) = elem.name {
159 let ns = elem.target_namespace.or(target_ns);
160 let idx = *flat_idx;
161 *flat_idx += 1;
162 let key = local_keys.get(idx).copied().flatten();
163 let ty = local_types
164 .get(idx)
165 .copied()
166 .flatten()
167 .or_else(|| key.and_then(|k| schema_set.arenas.elements[k].resolved_type));
168 (ns, name, key, ty)
169 } else {
170 return;
171 };
172
173 let Some(binding_key) = key else { return };
174
175 out.entry((qname_ns, qname_local))
177 .or_insert(DefaultBinding::Element {
178 key: binding_key,
179 resolved_type,
180 });
181
182 if let Some(map) = subst_groups {
185 if let Some(members) = map.get(&binding_key) {
186 for &(member_name, member_ns) in members.iter() {
187 if (member_ns, member_name) == (qname_ns, qname_local) {
188 continue;
189 }
190 out.entry((member_ns, member_name))
191 .or_insert(DefaultBinding::Element {
192 key: binding_key,
193 resolved_type,
194 });
195 }
196 }
197 }
198 }
199 ParticleTerm::Group(mg) => {
200 if let Some(ref_qn) = &mg.ref_name {
201 if let Some(group_key) =
202 schema_set.lookup_model_group(ref_qn.namespace, ref_qn.local_name)
203 {
204 let group = &schema_set.arenas.model_groups[group_key];
205 let mut group_flat_idx = 0usize;
206 for child in &group.particles {
207 walk_particle(
208 schema_set,
209 subst_groups,
210 child,
211 group.target_namespace.or(target_ns),
212 &group.resolved_particle_elements,
213 &group.resolved_particle_types,
214 &mut group_flat_idx,
215 out,
216 depth + 1,
217 );
218 }
219 }
220 } else {
223 for child in &mg.particles {
224 walk_particle(
225 schema_set,
226 subst_groups,
227 child,
228 target_ns,
229 local_keys,
230 local_types,
231 flat_idx,
232 out,
233 depth + 1,
234 );
235 }
236 }
237 }
238 ParticleTerm::Any(_) => {
239 }
241 }
242}