1use crate::error::{SchemaError, SchemaResult};
31use crate::namespace::{is_ncname, NameTable};
32use crate::parser::attrs::AttributeMap;
33use crate::parser::location::SourceRef;
34use crate::schema::XsdVersion;
35use crate::types::facets::{normalize_whitespace, WhitespaceMode};
36
37fn collapsed(value: &str) -> String {
42 normalize_whitespace(value, WhitespaceMode::Collapse)
43}
44
45#[derive(Debug, Clone)]
47pub struct ValidationContext {
48 pub xsd_version: XsdVersion,
50 pub is_top_level: bool,
52 pub inside_complex_type: bool,
56 pub source: Option<SourceRef>,
58}
59
60impl Default for ValidationContext {
61 fn default() -> Self {
62 Self {
63 xsd_version: XsdVersion::V1_0,
64 is_top_level: false,
65 inside_complex_type: false,
66 source: None,
67 }
68 }
69}
70
71impl ValidationContext {
72 pub fn new(xsd_version: XsdVersion, is_top_level: bool) -> Self {
74 Self {
75 xsd_version,
76 is_top_level,
77 inside_complex_type: false,
78 source: None,
79 }
80 }
81
82 pub fn with_source(mut self, source: Option<SourceRef>) -> Self {
84 self.source = source;
85 self
86 }
87}
88
89pub fn validate_element_structure(
103 attrs: &AttributeMap,
104 name_table: &NameTable,
105 ctx: &ValidationContext,
106) -> SchemaResult<()> {
107 let has_name = attrs.get_value_by_name(name_table, "name").is_some();
108 let has_ref = attrs.get_value_by_name(name_table, "ref").is_some();
109
110 if let Some(name_val) = attrs.get_value_by_name(name_table, "name") {
112 if !is_ncname(&collapsed(name_val)) {
113 return Err(SchemaError::structural(
114 "src-element",
115 format!("Element 'name' value '{}' is not a valid NCName", name_val),
116 None,
117 ));
118 }
119 }
120
121 if ctx.is_top_level {
122 if !has_name {
124 return Err(SchemaError::structural(
125 "src-element",
126 "Top-level element declaration must have 'name' attribute",
127 None,
128 ));
129 }
130
131 if has_ref {
132 return Err(SchemaError::structural(
133 "src-element",
134 "Top-level element declaration cannot have 'ref' attribute",
135 None,
136 ));
137 }
138
139 for prohibited in &["minOccurs", "maxOccurs", "form", "targetNamespace"] {
143 if attrs.get_value_by_name(name_table, prohibited).is_some() {
144 return Err(SchemaError::structural(
145 "src-element",
146 format!(
147 "Top-level element declaration cannot have '{}' attribute",
148 prohibited
149 ),
150 None,
151 ));
152 }
153 }
154 } else {
155 if has_name && has_ref {
157 return Err(SchemaError::structural(
158 "src-element",
159 "Local element cannot have both 'name' and 'ref' attributes",
160 None,
161 ));
162 }
163
164 if !has_name && !has_ref {
165 return Err(SchemaError::structural(
166 "src-element",
167 "Local element must have either 'name' or 'ref' attribute",
168 None,
169 ));
170 }
171
172 if has_ref {
176 let ref_prohibited = [
177 "type",
178 "default",
179 "fixed",
180 "nillable",
181 "block",
182 "final",
183 "form",
184 "targetNamespace",
185 ];
186 for prohibited in &ref_prohibited {
187 if attrs.get_value_by_name(name_table, prohibited).is_some() {
188 return Err(SchemaError::structural(
189 "src-element",
190 format!("Element reference cannot have '{}' attribute", prohibited),
191 None,
192 ));
193 }
194 }
195 }
196
197 for prohibited in &["final", "abstract", "substitutionGroup"] {
200 if attrs.get_value_by_name(name_table, prohibited).is_some() {
201 return Err(SchemaError::structural(
202 "src-element",
203 format!(
204 "Local element declaration cannot have '{}' attribute",
205 prohibited
206 ),
207 None,
208 ));
209 }
210 }
211
212 let has_target_ns = attrs
216 .get_value_by_name(name_table, "targetNamespace")
217 .is_some();
218 if has_target_ns {
219 let has_form = attrs.get_value_by_name(name_table, "form").is_some();
220 if has_form {
221 return Err(SchemaError::structural(
222 "src-element",
223 "Local element with 'targetNamespace' cannot also have 'form' \
224 (src-element §3.3.3 clause 3.2.2)",
225 None,
226 ));
227 }
228 if !ctx.inside_complex_type {
229 return Err(SchemaError::structural(
230 "src-element",
231 "Local element with 'targetNamespace' must have a <complexType> \
232 lexical ancestor (src-element §3.3.3 clause 4)",
233 None,
234 ));
235 }
236 }
237 }
238
239 let has_default = attrs.get_value_by_name(name_table, "default").is_some();
241 let has_fixed = attrs.get_value_by_name(name_table, "fixed").is_some();
242 if has_default && has_fixed {
243 return Err(SchemaError::structural(
244 "cos-valid-default",
245 "Element cannot have both 'default' and 'fixed' attributes",
246 None,
247 ));
248 }
249
250 if let Some(final_val) = attrs.get_value_by_name(name_table, "final") {
253 validate_derivation_set_tokens(
254 final_val,
255 &["extension", "restriction"],
256 "final",
257 "element",
258 )?;
259 }
260 if let Some(block_val) = attrs.get_value_by_name(name_table, "block") {
262 validate_derivation_set_tokens(
263 block_val,
264 &["extension", "restriction", "substitution"],
265 "block",
266 "element",
267 )?;
268 }
269
270 Ok(())
271}
272
273fn validate_derivation_set_tokens(
276 value: &str,
277 allowed: &[&str],
278 attr: &str,
279 elem: &str,
280) -> SchemaResult<()> {
281 let trimmed = value.trim();
282 if trimmed == "#all" {
283 return Ok(());
284 }
285 for token in trimmed.split_whitespace() {
286 if !allowed.contains(&token) {
287 return Err(SchemaError::structural(
288 "sch-props-correct",
289 format!(
290 "'{}' on '{}' does not allow derivation method '{}'",
291 attr, elem, token
292 ),
293 None,
294 ));
295 }
296 }
297 Ok(())
298}
299
300pub fn validate_attribute_structure(
315 attrs: &AttributeMap,
316 name_table: &NameTable,
317 ctx: &ValidationContext,
318) -> SchemaResult<()> {
319 let has_name = attrs.get_value_by_name(name_table, "name").is_some();
320 let has_ref = attrs.get_value_by_name(name_table, "ref").is_some();
321
322 if let Some(name_val) = attrs.get_value_by_name(name_table, "name") {
324 let collapsed_name = collapsed(name_val);
325 if !is_ncname(&collapsed_name) {
326 return Err(SchemaError::structural(
327 "src-attribute",
328 format!(
329 "Attribute 'name' value '{}' is not a valid NCName",
330 name_val
331 ),
332 None,
333 ));
334 }
335 if collapsed_name == "xmlns" {
338 return Err(SchemaError::structural(
339 "no-xmlns",
340 "Attribute declaration name must not be 'xmlns'",
341 None,
342 ));
343 }
344 }
345
346 if ctx.is_top_level {
347 if !has_name {
349 return Err(SchemaError::structural(
350 "src-attribute",
351 "Top-level attribute declaration must have 'name' attribute",
352 None,
353 ));
354 }
355
356 if has_ref {
357 return Err(SchemaError::structural(
358 "src-attribute",
359 "Top-level attribute declaration cannot have 'ref' attribute",
360 None,
361 ));
362 }
363
364 for prohibited in &["use", "form", "targetNamespace"] {
368 if attrs.get_value_by_name(name_table, prohibited).is_some() {
369 return Err(SchemaError::structural(
370 "src-attribute",
371 format!(
372 "Top-level attribute declaration cannot have '{}' attribute",
373 prohibited
374 ),
375 None,
376 ));
377 }
378 }
379 } else {
380 if has_name && has_ref {
382 return Err(SchemaError::structural(
383 "src-attribute",
384 "Local attribute cannot have both 'name' and 'ref' attributes",
385 None,
386 ));
387 }
388
389 if !has_name && !has_ref {
390 return Err(SchemaError::structural(
391 "src-attribute",
392 "Local attribute must have either 'name' or 'ref' attribute",
393 None,
394 ));
395 }
396
397 if has_ref {
403 for prohibited in &["type", "form", "targetNamespace"] {
404 if attrs.get_value_by_name(name_table, prohibited).is_some() {
405 return Err(SchemaError::structural(
406 "src-attribute",
407 format!("Attribute reference cannot have '{}' attribute", prohibited),
408 None,
409 ));
410 }
411 }
412 }
413
414 let has_target_ns = attrs
418 .get_value_by_name(name_table, "targetNamespace")
419 .is_some();
420 if has_target_ns {
421 let has_form = attrs.get_value_by_name(name_table, "form").is_some();
422 if has_form {
423 return Err(SchemaError::structural(
424 "src-attribute",
425 "Local attribute with 'targetNamespace' cannot also have 'form' \
426 (src-attribute §3.2.3 clause 6.2)",
427 None,
428 ));
429 }
430 if !ctx.inside_complex_type {
431 return Err(SchemaError::structural(
432 "src-attribute",
433 "Local attribute with 'targetNamespace' must have a <complexType> \
434 lexical ancestor (src-attribute §3.2.3 clause 6)",
435 None,
436 ));
437 }
438 }
439 }
440
441 let has_default = attrs.get_value_by_name(name_table, "default").is_some();
443 let has_fixed = attrs.get_value_by_name(name_table, "fixed").is_some();
444 if has_default && has_fixed {
445 return Err(SchemaError::structural(
446 "cos-valid-default",
447 "Attribute cannot have both 'default' and 'fixed' attributes",
448 None,
449 ));
450 }
451
452 if has_default {
454 if let Some(use_val) = attrs.get_value_by_name(name_table, "use") {
455 if use_val != "optional" {
456 return Err(SchemaError::structural(
457 "src-attribute",
458 format!(
459 "Attribute with 'default' must have use='optional' (got '{}')",
460 use_val
461 ),
462 None,
463 ));
464 }
465 }
466 }
467
468 if let Some(use_val) = attrs.get_value_by_name(name_table, "use") {
470 if use_val == "prohibited" {
471 if has_fixed && ctx.xsd_version == XsdVersion::V1_1 {
474 return Err(SchemaError::structural(
475 "src-attribute",
476 "Prohibited attribute cannot have 'fixed' attribute",
477 None,
478 ));
479 }
480 }
481 }
482
483 Ok(())
484}
485
486pub fn validate_simple_type_structure(
496 attrs: &AttributeMap,
497 name_table: &NameTable,
498 ctx: &ValidationContext,
499) -> SchemaResult<()> {
500 let has_name = attrs.get_value_by_name(name_table, "name").is_some();
501
502 if ctx.is_top_level && !has_name {
503 return Err(SchemaError::structural(
504 "src-simple-type",
505 "Top-level simpleType must have 'name' attribute",
506 None,
507 ));
508 }
509
510 if !ctx.is_top_level && has_name {
511 return Err(SchemaError::structural(
512 "src-simple-type",
513 "Inline simpleType cannot have 'name' attribute",
514 None,
515 ));
516 }
517
518 if let Some(name_val) = attrs.get_value_by_name(name_table, "name") {
519 if !is_ncname(&collapsed(name_val)) {
520 return Err(SchemaError::structural(
521 "src-simple-type",
522 format!(
523 "simpleType 'name' value '{}' is not a valid NCName",
524 name_val
525 ),
526 None,
527 ));
528 }
529 }
530
531 if let Some(final_val) = attrs.get_value_by_name(name_table, "final") {
533 validate_derivation_set_tokens(
534 final_val,
535 &["restriction", "list", "union", "extension"],
536 "final",
537 "simpleType",
538 )?;
539 }
540
541 Ok(())
542}
543
544pub fn validate_complex_type_structure(
549 attrs: &AttributeMap,
550 name_table: &NameTable,
551 ctx: &ValidationContext,
552) -> SchemaResult<()> {
553 let has_name = attrs.get_value_by_name(name_table, "name").is_some();
554
555 if ctx.is_top_level && !has_name {
556 return Err(SchemaError::structural(
557 "src-ct",
558 "Top-level complexType must have 'name' attribute",
559 None,
560 ));
561 }
562
563 if !ctx.is_top_level && has_name {
564 return Err(SchemaError::structural(
565 "src-ct",
566 "Inline complexType cannot have 'name' attribute",
567 None,
568 ));
569 }
570
571 if let Some(name_val) = attrs.get_value_by_name(name_table, "name") {
572 if !is_ncname(&collapsed(name_val)) {
573 return Err(SchemaError::structural(
574 "src-ct",
575 format!(
576 "complexType 'name' value '{}' is not a valid NCName",
577 name_val
578 ),
579 None,
580 ));
581 }
582 }
583
584 if let Some(final_val) = attrs.get_value_by_name(name_table, "final") {
586 validate_derivation_set_tokens(
587 final_val,
588 &["extension", "restriction"],
589 "final",
590 "complexType",
591 )?;
592 }
593 if let Some(block_val) = attrs.get_value_by_name(name_table, "block") {
594 validate_derivation_set_tokens(
595 block_val,
596 &["extension", "restriction"],
597 "block",
598 "complexType",
599 )?;
600 }
601
602 Ok(())
603}
604
605pub fn validate_restriction_structure(
613 attrs: &AttributeMap,
614 name_table: &NameTable,
615 has_inline_type: bool,
616) -> SchemaResult<()> {
617 let has_base = attrs.get_value_by_name(name_table, "base").is_some();
618
619 if has_base && has_inline_type {
620 return Err(SchemaError::structural(
621 "src-restriction-base-or-simpleType",
622 "Restriction cannot have both 'base' attribute and inline type",
623 None,
624 ));
625 }
626
627 Ok(())
631}
632
633pub fn validate_extension_structure(
637 attrs: &AttributeMap,
638 name_table: &NameTable,
639) -> SchemaResult<()> {
640 let has_base = attrs.get_value_by_name(name_table, "base").is_some();
641
642 if !has_base {
643 return Err(SchemaError::structural(
644 "src-ct",
645 "Extension must have 'base' attribute",
646 None,
647 ));
648 }
649
650 Ok(())
651}
652
653pub fn validate_list_structure(
661 attrs: &AttributeMap,
662 name_table: &NameTable,
663 has_inline_type: bool,
664) -> SchemaResult<()> {
665 let has_item_type = attrs.get_value_by_name(name_table, "itemType").is_some();
666
667 if has_item_type && has_inline_type {
668 return Err(SchemaError::structural(
669 "src-list-itemType-or-simpleType",
670 "List cannot have both 'itemType' attribute and inline simpleType",
671 None,
672 ));
673 }
674
675 if !has_item_type && !has_inline_type {
676 return Err(SchemaError::structural(
677 "src-list-itemType-or-simpleType",
678 "List must have either 'itemType' attribute or inline simpleType",
679 None,
680 ));
681 }
682
683 Ok(())
684}
685
686pub fn validate_union_structure(
690 attrs: &AttributeMap,
691 name_table: &NameTable,
692 has_inline_types: bool,
693) -> SchemaResult<()> {
694 let has_member_types = attrs.get_value_by_name(name_table, "memberTypes").is_some();
695
696 if !has_member_types && !has_inline_types {
697 return Err(SchemaError::structural(
698 "src-union-memberTypes-or-simpleTypes",
699 "Union must have 'memberTypes' attribute or inline simpleType children",
700 None,
701 ));
702 }
703
704 Ok(())
705}
706
707pub fn validate_key_unique_structure(
716 attrs: &AttributeMap,
717 name_table: &NameTable,
718) -> SchemaResult<()> {
719 let has_name = attrs.get_value_by_name(name_table, "name").is_some();
720 let has_ref = attrs.get_value_by_name(name_table, "ref").is_some();
721
722 if !has_name && !has_ref {
724 return Err(SchemaError::structural(
725 "src-identity-constraint",
726 "Identity constraint (key/unique) must have 'name' or 'ref' attribute",
727 None,
728 ));
729 }
730
731 if let Some(name_val) = attrs.get_value_by_name(name_table, "name") {
732 if !is_ncname(&collapsed(name_val)) {
733 return Err(SchemaError::structural(
734 "src-identity-constraint",
735 format!(
736 "identity constraint 'name' value '{}' is not a valid NCName",
737 name_val
738 ),
739 None,
740 ));
741 }
742 }
743
744 Ok(())
745}
746
747pub fn validate_keyref_structure(attrs: &AttributeMap, name_table: &NameTable) -> SchemaResult<()> {
753 let has_name = attrs.get_value_by_name(name_table, "name").is_some();
754 let has_refer = attrs.get_value_by_name(name_table, "refer").is_some();
755 let has_ref = attrs.get_value_by_name(name_table, "ref").is_some();
756
757 if !has_name && !has_ref {
759 return Err(SchemaError::structural(
760 "src-identity-constraint",
761 "Keyref must have 'name' or 'ref' attribute",
762 None,
763 ));
764 }
765
766 if has_name && !has_refer {
768 return Err(SchemaError::structural(
769 "src-identity-constraint",
770 "Keyref must have 'refer' attribute",
771 None,
772 ));
773 }
774
775 if let Some(name_val) = attrs.get_value_by_name(name_table, "name") {
776 if !is_ncname(&collapsed(name_val)) {
777 return Err(SchemaError::structural(
778 "src-identity-constraint",
779 format!("keyref 'name' value '{}' is not a valid NCName", name_val),
780 None,
781 ));
782 }
783 }
784
785 Ok(())
786}
787
788pub fn validate_group_structure(
797 attrs: &AttributeMap,
798 name_table: &NameTable,
799 ctx: &ValidationContext,
800) -> SchemaResult<()> {
801 let has_name = attrs.get_value_by_name(name_table, "name").is_some();
802 let has_ref = attrs.get_value_by_name(name_table, "ref").is_some();
803
804 if ctx.is_top_level {
805 if !has_name {
806 return Err(SchemaError::structural(
807 "mgd-props-correct",
808 "Top-level group must have 'name' attribute",
809 None,
810 ));
811 }
812 if has_ref {
813 return Err(SchemaError::structural(
814 "mgd-props-correct",
815 "Top-level group cannot have 'ref' attribute",
816 None,
817 ));
818 }
819 for prohibited in &["minOccurs", "maxOccurs"] {
821 if attrs.get_value_by_name(name_table, prohibited).is_some() {
822 return Err(SchemaError::structural(
823 "mgd-props-correct",
824 format!("Top-level group cannot have '{}' attribute", prohibited),
825 None,
826 ));
827 }
828 }
829 } else {
830 if has_name {
833 return Err(SchemaError::structural(
834 "mgd-props-correct",
835 "Non-top-level group must use 'ref', not 'name'",
836 None,
837 ));
838 }
839 if !has_ref {
840 return Err(SchemaError::structural(
841 "mgd-props-correct",
842 "Non-top-level group must have 'ref' attribute",
843 None,
844 ));
845 }
846 }
847
848 if let Some(name_val) = attrs.get_value_by_name(name_table, "name") {
849 if !is_ncname(&collapsed(name_val)) {
850 return Err(SchemaError::structural(
851 "mgd-props-correct",
852 format!("group 'name' value '{}' is not a valid NCName", name_val),
853 None,
854 ));
855 }
856 }
857
858 Ok(())
859}
860
861pub fn validate_attribute_group_structure(
866 attrs: &AttributeMap,
867 name_table: &NameTable,
868 ctx: &ValidationContext,
869) -> SchemaResult<()> {
870 let has_name = attrs.get_value_by_name(name_table, "name").is_some();
871 let has_ref = attrs.get_value_by_name(name_table, "ref").is_some();
872
873 if ctx.is_top_level {
874 if !has_name {
875 return Err(SchemaError::structural(
876 "src-attribute_group",
877 "Top-level attributeGroup must have 'name' attribute",
878 None,
879 ));
880 }
881 if has_ref {
882 return Err(SchemaError::structural(
883 "src-attribute_group",
884 "Top-level attributeGroup cannot have 'ref' attribute",
885 None,
886 ));
887 }
888 } else {
889 if has_name {
892 return Err(SchemaError::structural(
893 "src-attribute_group",
894 "Non-top-level attributeGroup must use 'ref', not 'name'",
895 None,
896 ));
897 }
898 if !has_ref {
899 return Err(SchemaError::structural(
900 "src-attribute_group",
901 "Non-top-level attributeGroup must have 'ref' attribute",
902 None,
903 ));
904 }
905 }
906
907 if let Some(name_val) = attrs.get_value_by_name(name_table, "name") {
908 if !is_ncname(&collapsed(name_val)) {
909 return Err(SchemaError::structural(
910 "src-attribute_group",
911 format!(
912 "attributeGroup 'name' value '{}' is not a valid NCName",
913 name_val
914 ),
915 None,
916 ));
917 }
918 }
919
920 Ok(())
921}
922
923pub const XSD_1_1_ELEMENTS: &[&str] = &[
929 "assert",
930 "assertion",
931 "alternative",
932 "openContent",
933 "defaultOpenContent",
934 "override",
935 "explicitTimezone",
936];
937
938pub const XSD_1_1_ATTRIBUTES: &[&str] = &[
940 "targetNamespace", "notNamespace", "notQName", "inheritable", "defaultAttributes", "defaultAttributesApply", "xpathDefaultNamespace", ];
948
949pub fn validate_xsd_version_element(
951 element_name: &str,
952 ctx: &ValidationContext,
953) -> SchemaResult<()> {
954 if ctx.xsd_version == XsdVersion::V1_0 && XSD_1_1_ELEMENTS.contains(&element_name) {
955 return Err(SchemaError::feature(
956 format!(
957 "Element '{}' requires XSD 1.1 but schema is in XSD 1.0 mode",
958 element_name
959 ),
960 None,
961 ));
962 }
963 Ok(())
964}
965
966pub fn validate_xsd_version_attribute(
968 attr_name: &str,
969 element_name: &str,
970 ctx: &ValidationContext,
971) -> SchemaResult<()> {
972 if ctx.xsd_version == XsdVersion::V1_0 {
973 let is_xsd_1_1_attr = match (element_name, attr_name) {
975 ("element", "targetNamespace") => true,
976 ("attribute", "targetNamespace") => true,
977 ("attribute", "inheritable") => true,
978 ("complexType", "defaultAttributesApply") => true,
979 ("complexType", "xpathDefaultNamespace") => true,
980 ("any", "notNamespace") | ("any", "notQName") => true,
981 ("anyAttribute", "notNamespace") | ("anyAttribute", "notQName") => true,
982 ("schema", "defaultAttributes") => true,
983 ("schema", "xpathDefaultNamespace") => true,
984 ("selector", "xpathDefaultNamespace") => true,
985 ("field", "xpathDefaultNamespace") => true,
986 ("unique", "ref") | ("key", "ref") | ("keyref", "ref") => true,
987 ("schema", "targetNamespace") => false,
989 _ => XSD_1_1_ATTRIBUTES.contains(&attr_name),
990 };
991
992 if is_xsd_1_1_attr {
993 return Err(SchemaError::feature(
994 format!(
995 "Attribute '{}' on '{}' requires XSD 1.1 but schema is in XSD 1.0 mode",
996 attr_name, element_name
997 ),
998 None,
999 ));
1000 }
1001 }
1002 Ok(())
1003}
1004
1005pub fn validate_notation_structure(
1014 attrs: &AttributeMap,
1015 name_table: &NameTable,
1016 ctx: &ValidationContext,
1017) -> SchemaResult<()> {
1018 let has_name = attrs.get_value_by_name(name_table, "name").is_some();
1019 let has_public = attrs.get_value_by_name(name_table, "public").is_some();
1020 let has_system = attrs.get_value_by_name(name_table, "system").is_some();
1021
1022 if !has_name {
1023 return Err(SchemaError::structural(
1024 "n-props-correct",
1025 "Notation must have 'name' attribute",
1026 None,
1027 ));
1028 }
1029
1030 if let Some(name_val) = attrs.get_value_by_name(name_table, "name") {
1031 if !is_ncname(&collapsed(name_val)) {
1032 return Err(SchemaError::structural(
1033 "n-props-correct",
1034 format!("notation 'name' value '{}' is not a valid NCName", name_val),
1035 None,
1036 ));
1037 }
1038 }
1039
1040 match ctx.xsd_version {
1041 XsdVersion::V1_0 => {
1042 if !has_public {
1043 return Err(SchemaError::structural(
1044 "n-props-correct",
1045 "Notation must have 'public' attribute in XSD 1.0",
1046 None,
1047 ));
1048 }
1049 }
1050 XsdVersion::V1_1 => {
1051 if !has_public && !has_system {
1052 return Err(SchemaError::structural(
1053 "n-props-correct",
1054 "Notation must have 'public' or 'system' attribute in XSD 1.1",
1055 None,
1056 ));
1057 }
1058 }
1059 }
1060
1061 Ok(())
1062}
1063
1064pub fn validate_include_structure(
1072 attrs: &AttributeMap,
1073 name_table: &NameTable,
1074) -> SchemaResult<()> {
1075 let has_location = attrs
1076 .get_value_by_name(name_table, "schemaLocation")
1077 .is_some();
1078
1079 if !has_location {
1080 return Err(SchemaError::structural(
1081 "src-include",
1082 "Include must have 'schemaLocation' attribute",
1083 None,
1084 ));
1085 }
1086
1087 Ok(())
1088}
1089
1090pub fn validate_import_structure(attrs: &AttributeMap, name_table: &NameTable) -> SchemaResult<()> {
1097 if let Some(ns) = attrs.get_value_by_name(name_table, "namespace") {
1098 if ns.is_empty() {
1099 return Err(SchemaError::structural(
1100 "src-import",
1101 "xs:import 'namespace' must not be the empty string",
1102 None,
1103 ));
1104 }
1105 }
1106 Ok(())
1107}
1108
1109pub fn validate_schema_structure(attrs: &AttributeMap, name_table: &NameTable) -> SchemaResult<()> {
1122 if let Some(tns) = attrs.get_value_by_name(name_table, "targetNamespace") {
1123 if tns.is_empty() {
1124 return Err(SchemaError::structural(
1125 "sch-props-correct",
1126 "xs:schema 'targetNamespace' must not be the empty string",
1127 None,
1128 ));
1129 }
1130 }
1131 Ok(())
1132}
1133
1134pub fn validate_redefine_structure(
1138 attrs: &AttributeMap,
1139 name_table: &NameTable,
1140) -> SchemaResult<()> {
1141 let has_location = attrs
1142 .get_value_by_name(name_table, "schemaLocation")
1143 .is_some();
1144
1145 if !has_location {
1146 return Err(SchemaError::structural(
1147 "src-redefine",
1148 "Redefine must have 'schemaLocation' attribute",
1149 None,
1150 ));
1151 }
1152
1153 Ok(())
1154}
1155
1156#[cfg(test)]
1157mod tests {
1158 use super::*;
1159 use crate::parser::attrs::ParsedAttribute;
1160
1161 fn make_attr_map(name_table: &mut NameTable, attrs: &[(&str, &str)]) -> AttributeMap {
1162 let parsed: Vec<ParsedAttribute> = attrs
1163 .iter()
1164 .map(|(name, value)| ParsedAttribute {
1165 namespace: None,
1166 local_name: name_table.add(name),
1167 prefix: None,
1168 value: value.to_string(),
1169 source: None,
1170 })
1171 .collect();
1172 AttributeMap::new(parsed)
1173 }
1174
1175 #[test]
1176 fn test_element_top_level_valid() {
1177 let mut name_table = NameTable::new();
1178 let attrs = make_attr_map(&mut name_table, &[("name", "myElement")]);
1179 let ctx = ValidationContext::new(XsdVersion::V1_0, true);
1180
1181 let result = validate_element_structure(&attrs, &name_table, &ctx);
1182 assert!(result.is_ok());
1183 }
1184
1185 #[test]
1186 fn test_element_top_level_missing_name() {
1187 let mut name_table = NameTable::new();
1188 let attrs = make_attr_map(&mut name_table, &[("type", "xs:string")]);
1189 let ctx = ValidationContext::new(XsdVersion::V1_0, true);
1190
1191 let result = validate_element_structure(&attrs, &name_table, &ctx);
1192 assert!(result.is_err());
1193 }
1194
1195 #[test]
1196 fn test_element_top_level_has_ref() {
1197 let mut name_table = NameTable::new();
1198 let attrs = make_attr_map(&mut name_table, &[("name", "myElement"), ("ref", "other")]);
1199 let ctx = ValidationContext::new(XsdVersion::V1_0, true);
1200
1201 let result = validate_element_structure(&attrs, &name_table, &ctx);
1202 assert!(result.is_err());
1203 }
1204
1205 #[test]
1206 fn test_element_local_name_and_ref() {
1207 let mut name_table = NameTable::new();
1208 let attrs = make_attr_map(&mut name_table, &[("name", "myElement"), ("ref", "other")]);
1209 let ctx = ValidationContext::new(XsdVersion::V1_0, false);
1210
1211 let result = validate_element_structure(&attrs, &name_table, &ctx);
1212 assert!(result.is_err());
1213 }
1214
1215 #[test]
1216 fn test_element_default_and_fixed() {
1217 let mut name_table = NameTable::new();
1218 let attrs = make_attr_map(
1219 &mut name_table,
1220 &[("name", "myElement"), ("default", "a"), ("fixed", "b")],
1221 );
1222 let ctx = ValidationContext::new(XsdVersion::V1_0, true);
1223
1224 let result = validate_element_structure(&attrs, &name_table, &ctx);
1225 assert!(result.is_err());
1226 }
1227
1228 #[test]
1229 fn test_attribute_prohibited_with_default() {
1230 let mut name_table = NameTable::new();
1231 let attrs = make_attr_map(
1232 &mut name_table,
1233 &[("ref", "myAttr"), ("use", "prohibited"), ("default", "x")],
1234 );
1235 let ctx = ValidationContext::new(XsdVersion::V1_0, false);
1236
1237 let result = validate_attribute_structure(&attrs, &name_table, &ctx);
1238 assert!(result.is_err());
1239 }
1240
1241 #[test]
1242 fn test_xsd_1_1_element_in_1_0_mode() {
1243 let ctx = ValidationContext::new(XsdVersion::V1_0, false);
1244 let result = validate_xsd_version_element("assert", &ctx);
1245 assert!(result.is_err());
1246 }
1247
1248 #[test]
1249 fn test_xsd_1_1_element_in_1_1_mode() {
1250 let ctx = ValidationContext::new(XsdVersion::V1_1, false);
1251 let result = validate_xsd_version_element("assert", &ctx);
1252 assert!(result.is_ok());
1253 }
1254
1255 #[test]
1256 fn test_keyref_requires_refer() {
1257 let mut name_table = NameTable::new();
1258 let attrs = make_attr_map(&mut name_table, &[("name", "myKeyRef")]);
1259
1260 let result = validate_keyref_structure(&attrs, &name_table);
1261 assert!(result.is_err());
1262 }
1263
1264 #[test]
1265 fn test_keyref_with_refer() {
1266 let mut name_table = NameTable::new();
1267 let attrs = make_attr_map(&mut name_table, &[("name", "myKeyRef"), ("refer", "myKey")]);
1268
1269 let result = validate_keyref_structure(&attrs, &name_table);
1270 assert!(result.is_ok());
1271 }
1272
1273 #[test]
1274 fn test_list_itemtype_and_inline() {
1275 let mut name_table = NameTable::new();
1276 let attrs = make_attr_map(&mut name_table, &[("itemType", "xs:string")]);
1277
1278 let result = validate_list_structure(&attrs, &name_table, true);
1280 assert!(result.is_err());
1281 }
1282
1283 #[test]
1284 fn test_list_neither_itemtype_nor_inline() {
1285 let mut name_table = NameTable::new();
1286 let attrs = make_attr_map(&mut name_table, &[]);
1287
1288 let result = validate_list_structure(&attrs, &name_table, false);
1289 assert!(result.is_err());
1290 }
1291
1292 #[test]
1293 fn test_extension_requires_base() {
1294 let mut name_table = NameTable::new();
1295 let attrs = make_attr_map(&mut name_table, &[]);
1296
1297 let result = validate_extension_structure(&attrs, &name_table);
1298 assert!(result.is_err());
1299 }
1300
1301 #[test]
1302 fn test_notation_requires_public_in_1_0() {
1303 let mut name_table = NameTable::new();
1304 let attrs = make_attr_map(
1305 &mut name_table,
1306 &[("name", "myNotation"), ("system", "foo")],
1307 );
1308 let ctx = ValidationContext::new(XsdVersion::V1_0, true);
1309
1310 let result = validate_notation_structure(&attrs, &name_table, &ctx);
1311 assert!(result.is_err());
1312 }
1313
1314 #[test]
1315 fn test_notation_system_ok_in_1_1() {
1316 let mut name_table = NameTable::new();
1317 let attrs = make_attr_map(
1318 &mut name_table,
1319 &[("name", "myNotation"), ("system", "foo")],
1320 );
1321 let ctx = ValidationContext::new(XsdVersion::V1_1, true);
1322
1323 let result = validate_notation_structure(&attrs, &name_table, &ctx);
1324 assert!(result.is_ok());
1325 }
1326
1327 #[test]
1330 fn test_xpath_default_ns_on_selector_rejected_in_1_0() {
1331 let ctx = ValidationContext::new(XsdVersion::V1_0, false);
1332 let result = validate_xsd_version_attribute("xpathDefaultNamespace", "selector", &ctx);
1333 assert!(result.is_err());
1334 }
1335
1336 #[test]
1337 fn test_xpath_default_ns_on_field_rejected_in_1_0() {
1338 let ctx = ValidationContext::new(XsdVersion::V1_0, false);
1339 let result = validate_xsd_version_attribute("xpathDefaultNamespace", "field", &ctx);
1340 assert!(result.is_err());
1341 }
1342
1343 #[test]
1344 fn test_xpath_default_ns_on_schema_rejected_in_1_0() {
1345 let ctx = ValidationContext::new(XsdVersion::V1_0, true);
1346 let result = validate_xsd_version_attribute("xpathDefaultNamespace", "schema", &ctx);
1347 assert!(result.is_err());
1348 }
1349
1350 #[test]
1351 fn test_target_namespace_on_schema_allowed_in_1_0() {
1352 let ctx = ValidationContext::new(XsdVersion::V1_0, true);
1353 let result = validate_xsd_version_attribute("targetNamespace", "schema", &ctx);
1354 assert!(result.is_ok());
1355 }
1356
1357 #[test]
1358 fn test_xsd_1_0_rejects_default_attributes_on_schema() {
1359 let ctx = ValidationContext::new(XsdVersion::V1_0, true);
1360 let result = validate_xsd_version_attribute("defaultAttributes", "schema", &ctx);
1361 assert!(result.is_err());
1362
1363 let ctx11 = ValidationContext::new(XsdVersion::V1_1, true);
1365 let result11 = validate_xsd_version_attribute("defaultAttributes", "schema", &ctx11);
1366 assert!(result11.is_ok());
1367 }
1368
1369 #[test]
1370 fn test_xpath_default_ns_on_selector_allowed_in_1_1() {
1371 let ctx = ValidationContext::new(XsdVersion::V1_1, false);
1372 let result = validate_xsd_version_attribute("xpathDefaultNamespace", "selector", &ctx);
1373 assert!(result.is_ok());
1374 }
1375
1376 #[test]
1377 fn test_xpath_default_ns_on_field_allowed_in_1_1() {
1378 let ctx = ValidationContext::new(XsdVersion::V1_1, false);
1379 let result = validate_xsd_version_attribute("xpathDefaultNamespace", "field", &ctx);
1380 assert!(result.is_ok());
1381 }
1382}