1#![allow(
78 missing_docs,
79 non_snake_case,
80 non_camel_case_types,
81 non_upper_case_globals
82)]
83
84use core::ffi::c_void;
85use core::ptr;
86use std::os::raw::{c_char, c_int};
87
88use crate::abi::allocator;
89use crate::abi::structs::*;
90use crate::abi::types::xmlElementType::*;
91
92#[derive(Debug, Clone, PartialEq)]
103pub enum RelaxNgNameClass {
104 Name(String),
106 AnyName,
108 NsName(String),
110 Choice(Vec<RelaxNgNameClass>),
112 Except(Box<RelaxNgNameClass>, Box<RelaxNgNameClass>),
114}
115
116impl RelaxNgNameClass {
117 pub fn matches(&self, name: &str, ns_uri: Option<&str>) -> bool {
119 match self {
120 RelaxNgNameClass::Name(n) => name == n.as_str(),
121 RelaxNgNameClass::AnyName => true,
122 RelaxNgNameClass::NsName(ns) => {
123 if let Some(uri) = ns_uri {
124 uri == ns.as_str()
125 } else {
126 false
127 }
128 }
129 RelaxNgNameClass::Choice(choices) => choices.iter().any(|c| c.matches(name, ns_uri)),
130 RelaxNgNameClass::Except(positive, negative) => {
131 positive.matches(name, ns_uri) && !negative.matches(name, ns_uri)
132 }
133 }
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum RelaxNgPatternType {
148 Element,
149 Attribute,
150 Text,
151 Choice,
152 Sequence,
153 Interleave,
154 ZeroOrMore,
155 OneOrMore,
156 Optional,
157 List,
158 Group,
159 Data,
160 Value,
161 Ref,
162 Define,
163 Grammar,
164 NotAllowed,
165 Empty,
166 ExternalRef,
167 Include,
168 Start,
169}
170
171#[derive(Debug, Clone)]
175pub struct RelaxNgPattern {
176 pub pattern_type: RelaxNgPatternType,
178 pub name_class: Option<RelaxNgNameClass>,
180 pub children: Vec<RelaxNgPattern>,
182 pub name: Option<String>,
184 pub ns: Option<String>,
186 pub datatype: Option<String>,
188 pub value: Option<String>,
190 pub datatype_library: Option<String>,
192}
193
194impl RelaxNgPattern {
195 pub const fn new(pattern_type: RelaxNgPatternType) -> Self {
197 Self {
198 pattern_type,
199 name_class: None,
200 children: Vec::new(),
201 name: None,
202 ns: None,
203 datatype: None,
204 value: None,
205 datatype_library: None,
206 }
207 }
208
209 pub fn element(name: &str) -> Self {
211 let mut p = Self::new(RelaxNgPatternType::Element);
212 p.name_class = Some(RelaxNgNameClass::Name(name.to_string()));
213 p
214 }
215
216 pub fn attribute(name: &str) -> Self {
218 let mut p = Self::new(RelaxNgPatternType::Attribute);
219 p.name_class = Some(RelaxNgNameClass::Name(name.to_string()));
220 p
221 }
222
223 pub const fn text() -> Self {
225 Self::new(RelaxNgPatternType::Text)
226 }
227
228 pub const fn empty() -> Self {
230 Self::new(RelaxNgPatternType::Empty)
231 }
232
233 pub const fn not_allowed() -> Self {
235 Self::new(RelaxNgPatternType::NotAllowed)
236 }
237}
238
239#[derive(Debug, Clone)]
245pub struct RelaxNgDefine {
246 pub name: String,
248 pub pattern: RelaxNgPattern,
250}
251
252#[derive(Debug, Clone)]
260pub struct RelaxNgGrammar {
261 pub defines: Vec<RelaxNgDefine>,
263 pub start: Option<RelaxNgPattern>,
265 pub includes: Vec<RelaxNgGrammar>,
267}
268
269impl RelaxNgGrammar {
270 pub const fn new() -> Self {
271 Self {
272 defines: Vec::new(),
273 start: None,
274 includes: Vec::new(),
275 }
276 }
277
278 pub fn lookup(&self, name: &str) -> Option<&RelaxNgPattern> {
280 for def in &self.defines {
281 if def.name == name {
282 return Some(&def.pattern);
283 }
284 }
285 for inc in &self.includes {
287 if let Some(p) = inc.lookup(name) {
288 return Some(p);
289 }
290 }
291 None
292 }
293}
294
295impl Default for RelaxNgGrammar {
296 fn default() -> Self {
297 Self::new()
298 }
299}
300
301#[derive(Debug, Clone)]
309pub struct RelaxNgSchema {
310 pub grammar: RelaxNgGrammar,
312 pub errors: Vec<String>,
314}
315
316impl RelaxNgSchema {
317 pub const fn new() -> Self {
318 Self {
319 grammar: RelaxNgGrammar::new(),
320 errors: Vec::new(),
321 }
322 }
323}
324
325impl Default for RelaxNgSchema {
326 fn default() -> Self {
327 Self::new()
328 }
329}
330
331#[derive(Debug)]
341pub struct RelaxNgValidCtxt {
342 pub schema: Option<RelaxNgSchema>,
344 pub errors: Vec<String>,
346 pub nb_errors: i32,
348 pub path: Vec<String>,
350 pub depth_max: i32,
352 pub depth: i32,
354}
355
356impl RelaxNgValidCtxt {
357 pub const fn new() -> Self {
358 Self {
359 schema: None,
360 errors: Vec::new(),
361 nb_errors: 0,
362 path: Vec::new(),
363 depth_max: 256,
364 depth: 0,
365 }
366 }
367
368 pub fn record_error(&mut self, msg: String) {
370 self.errors.push(msg);
371 self.nb_errors += 1;
372 }
373
374 pub fn current_path(&self) -> String {
376 if self.path.is_empty() {
377 "/".to_string()
378 } else {
379 format!("/{}", self.path.join("/"))
380 }
381 }
382}
383
384impl Default for RelaxNgValidCtxt {
385 fn default() -> Self {
386 Self::new()
387 }
388}
389
390unsafe fn get_local_name(node: *mut _xmlNode) -> String {
400 if node.is_null() {
401 return String::new();
402 }
403 unsafe {
404 let name = (*node).name;
405 if name.is_null() {
406 return String::new();
407 }
408 let mut len = 0;
409 while *name.add(len) != 0 {
410 len += 1;
411 }
412 let slice = std::slice::from_raw_parts(name, len);
413 if let Ok(s) = std::str::from_utf8(slice) {
414 if let Some(pos) = s.find(':') {
415 s[pos + 1..].to_string()
416 } else {
417 s.to_string()
418 }
419 } else {
420 String::new()
421 }
422 }
423}
424
425unsafe fn get_node_text(node: *mut _xmlNode) -> String {
431 if node.is_null() {
432 return String::new();
433 }
434 let mut result = String::new();
435 unsafe {
436 let mut child = (*node).children;
437 while !child.is_null() {
438 if ((*child).type_ == XML_TEXT_NODE as c_int
439 || (*child).type_ == XML_CDATA_SECTION_NODE as c_int)
440 && !(*child).content.is_null()
441 {
442 let content = (*child).content;
443 let mut len = 0;
444 while *content.add(len) != 0 {
445 len += 1;
446 }
447 let slice = std::slice::from_raw_parts(content, len);
448 result.push_str(&String::from_utf8_lossy(slice));
449 }
450 child = (*child).next;
451 }
452 }
453 result
454}
455
456unsafe fn get_node_qname(node: *mut _xmlNode) -> String {
462 if node.is_null() {
463 return String::new();
464 }
465 unsafe {
466 let ns = (*node).ns;
467 let prefix = if !ns.is_null() && !(*ns).prefix.is_null() {
468 let mut len = 0;
469 while *(*ns).prefix.add(len) != 0 {
470 len += 1;
471 }
472 let slice = std::slice::from_raw_parts((*ns).prefix, len);
473 if let Ok(s) = std::str::from_utf8(slice) {
474 format!("{}:", s)
475 } else {
476 String::new()
477 }
478 } else {
479 String::new()
480 };
481
482 let name = (*node).name;
483 if name.is_null() {
484 return String::new();
485 }
486 let mut len = 0;
487 while *name.add(len) != 0 {
488 len += 1;
489 }
490 let slice = std::slice::from_raw_parts(name, len);
491 if let Ok(s) = std::str::from_utf8(slice) {
492 format!("{}{}", prefix, s)
493 } else {
494 String::new()
495 }
496 }
497}
498
499unsafe fn get_node_ns_uri(node: *mut _xmlNode) -> Option<String> {
505 if node.is_null() {
506 return None;
507 }
508 unsafe {
509 let ns = (*node).ns;
510 if ns.is_null() || (*ns).href.is_null() {
511 return None;
512 }
513 let href = (*ns).href;
514 let mut len = 0;
515 while *href.add(len) != 0 {
516 len += 1;
517 }
518 let slice = std::slice::from_raw_parts(href, len);
519 if let Ok(s) = std::str::from_utf8(slice) {
520 Some(s.to_string())
521 } else {
522 None
523 }
524 }
525}
526
527unsafe fn get_attr(node: *mut _xmlNode, name: &str) -> Option<String> {
533 if node.is_null() {
534 return None;
535 }
536 unsafe {
537 let mut prop = (*node).properties;
538 while !prop.is_null() {
539 let prop_name = (*prop).name;
540 if !prop_name.is_null() {
541 let mut len = 0;
542 while *prop_name.add(len) != 0 {
543 len += 1;
544 }
545 let slice = std::slice::from_raw_parts(prop_name, len);
546 if let Ok(s) = std::str::from_utf8(slice) {
547 if s == name {
548 return Some(get_node_text(prop as *mut _xmlNode));
549 }
550 }
551 }
552 prop = (*prop).next;
553 }
554 }
555 None
556}
557
558#[allow(dead_code)]
564unsafe fn node_is(node: *mut _xmlNode, local_name: &str) -> bool {
565 if node.is_null() {
566 return false;
567 }
568 unsafe {
569 let name = (*node).name;
570 if name.is_null() {
571 return false;
572 }
573 let mut len = 0;
574 while *name.add(len) != 0 {
575 len += 1;
576 }
577 let slice = std::slice::from_raw_parts(name, len);
578 if let Ok(s) = std::str::from_utf8(slice) {
579 let local = if let Some(pos) = s.find(':') {
580 &s[pos + 1..]
581 } else {
582 s
583 };
584 return local == local_name;
585 }
586 }
587 false
588}
589
590pub fn rng_parse(xml_doc: &str) -> Result<RelaxNgSchema, String> {
611 let doc_ptr = unsafe {
612 crate::abi::exports_xml2::xmlReadMemory(
613 xml_doc.as_ptr() as *const c_char,
614 xml_doc.len() as c_int,
615 c"schema.rng".as_ptr() as *const c_char,
616 ptr::null(),
617 0,
618 )
619 };
620
621 if doc_ptr.is_null() {
622 return Err("Failed to parse RELAX NG schema XML document".to_string());
623 }
624
625 let result = unsafe { rng_parse_doc(doc_ptr) };
626 unsafe {
627 crate::abi::exports_xml2::xmlFreeDoc(doc_ptr);
628 }
629 result
630}
631
632unsafe fn rng_parse_doc(doc: *mut _xmlDoc) -> Result<RelaxNgSchema, String> {
638 unsafe {
639 let root = (*doc).children;
640 if root.is_null() {
641 return Err("RELAX NG document has no root element".to_string());
642 }
643
644 let mut root_elem = root;
646 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
647 root_elem = (*root_elem).next;
648 }
649
650 if root_elem.is_null() {
651 return Err("RELAX NG document has no root element".to_string());
652 }
653
654 let local_name = get_local_name(root_elem);
655 let mut schema = RelaxNgSchema::new();
656
657 match local_name.as_str() {
658 "grammar" => {
659 schema.grammar = rng_parse_grammar_node(root_elem, &mut schema);
661 Ok(schema)
662 }
663 "element" | "attribute" | "text" | "choice" | "sequence" | "interleave"
664 | "zeroOrMore" | "oneOrMore" | "optional" | "list" | "group" | "data" | "value"
665 | "ref" | "notAllowed" | "empty" | "externalRef" | "define" | "start" | "include" => {
666 let pattern = rng_parse_pattern(root_elem, &mut schema);
668 schema.grammar.start = Some(pattern);
669 Ok(schema)
670 }
671 _ => Err(format!("Unknown RELAX NG root element: '{}'", local_name)),
672 }
673 }
674}
675
676unsafe fn rng_parse_grammar_node(
682 node: *mut _xmlNode,
683 schema: &mut RelaxNgSchema,
684) -> RelaxNgGrammar {
685 unsafe {
686 let mut grammar = RelaxNgGrammar::new();
687
688 let mut child = (*node).children;
689 while !child.is_null() {
690 if (*child).type_ == XML_ELEMENT_NODE as c_int {
691 let local = get_local_name(child);
692 match local.as_str() {
693 "define" => {
694 let def = rng_parse_define(child, schema);
695 grammar.defines.push(def);
696 }
697 "start" => {
698 grammar.start = Some(rng_parse_pattern(child, schema));
699 }
700 "include" => {
701 if let Some(inc) = rng_parse_include(child, schema) {
702 grammar.includes.push(inc);
703 }
704 }
705 "div" => {
706 let sub_grammar = rng_parse_grammar_node(child, schema);
708 grammar.defines.extend(sub_grammar.defines);
709 if sub_grammar.start.is_some() {
710 grammar.start = sub_grammar.start;
711 }
712 grammar.includes.extend(sub_grammar.includes);
713 }
714 _ => {
715 schema
717 .errors
718 .push(format!("Unexpected element '<{}>' in grammar", local));
719 }
720 }
721 }
722 child = (*child).next;
723 }
724
725 grammar
726 }
727}
728
729unsafe fn rng_parse_define(node: *mut _xmlNode, schema: &mut RelaxNgSchema) -> RelaxNgDefine {
735 unsafe {
736 let name = get_attr(node, "name").unwrap_or_default();
737 let pattern = rng_parse_pattern(node, schema);
738 RelaxNgDefine { name, pattern }
739 }
740}
741
742unsafe fn rng_parse_include(
748 node: *mut _xmlNode,
749 _schema: &mut RelaxNgSchema,
750) -> Option<RelaxNgGrammar> {
751 unsafe {
752 let href = get_attr(node, "href");
753 if let Some(url) = href {
754 let url_c = std::ffi::CString::new(url.clone()).ok()?;
757 let doc = crate::abi::exports_xml2::xmlParseFile(url_c.as_ptr());
758 if doc.is_null() {
759 return None;
760 }
761 let mut inc_schema = RelaxNgSchema::new();
762 let grammar = rng_parse_grammar_node(
763 {
764 let mut root = (*doc).children;
765 while !root.is_null() && (*root).type_ != XML_ELEMENT_NODE as c_int {
766 root = (*root).next;
767 }
768 root
769 },
770 &mut inc_schema,
771 );
772 crate::abi::exports_xml2::xmlFreeDoc(doc);
773 Some(grammar)
774 } else {
775 let mut inc_schema = RelaxNgSchema::new();
777 let grammar = rng_parse_grammar_node(node, &mut inc_schema);
778 Some(grammar)
779 }
780 }
781}
782
783unsafe fn rng_parse_pattern(node: *mut _xmlNode, schema: &mut RelaxNgSchema) -> RelaxNgPattern {
789 unsafe {
790 let local = get_local_name(node);
791
792 match local.as_str() {
793 "element" => rng_parse_element_pattern(node, schema),
794 "attribute" => rng_parse_attribute_pattern(node, schema),
795 "text" => RelaxNgPattern::text(),
796 "empty" => RelaxNgPattern::empty(),
797 "notAllowed" => RelaxNgPattern::not_allowed(),
798 "choice" => rng_parse_composite_pattern(node, RelaxNgPatternType::Choice, schema),
799 "sequence" => rng_parse_composite_pattern(node, RelaxNgPatternType::Sequence, schema),
800 "interleave" => {
801 rng_parse_composite_pattern(node, RelaxNgPatternType::Interleave, schema)
802 }
803 "zeroOrMore" => rng_parse_unary_pattern(node, RelaxNgPatternType::ZeroOrMore, schema),
804 "oneOrMore" => rng_parse_unary_pattern(node, RelaxNgPatternType::OneOrMore, schema),
805 "optional" => rng_parse_unary_pattern(node, RelaxNgPatternType::Optional, schema),
806 "list" => rng_parse_unary_pattern(node, RelaxNgPatternType::List, schema),
807 "group" => rng_parse_composite_pattern(node, RelaxNgPatternType::Group, schema),
808 "data" => rng_parse_data_pattern(node, schema),
809 "value" => rng_parse_value_pattern(node, schema),
810 "ref" => rng_parse_ref_pattern(node),
811 "externalRef" => rng_parse_external_ref(node, schema),
812 "define" | "start" | "grammar" | "include" | "div" => {
813 let mut child = (*node).children;
815 let mut result = RelaxNgPattern::empty();
816 while !child.is_null() {
817 if (*child).type_ == XML_ELEMENT_NODE as c_int {
818 result = rng_parse_pattern(child, schema);
819 break;
820 }
821 child = (*child).next;
822 }
823 result
824 }
825 _ => {
826 schema
828 .errors
829 .push(format!("Unknown pattern element '<{}>'", local));
830 RelaxNgPattern::empty()
831 }
832 }
833 }
834}
835
836unsafe fn rng_parse_element_pattern(
842 node: *mut _xmlNode,
843 schema: &mut RelaxNgSchema,
844) -> RelaxNgPattern {
845 unsafe {
846 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Element);
847
848 let name_attr = get_attr(node, "name");
850 pattern.name = name_attr.clone();
851
852 if let Some(ref n) = name_attr {
853 pattern.name_class = Some(RelaxNgNameClass::Name(n.clone()));
854 }
855
856 let mut child = (*node).children;
858 let mut content_found = false;
859
860 while !child.is_null() {
861 if (*child).type_ == XML_ELEMENT_NODE as c_int {
862 let child_local = get_local_name(child);
863
864 match child_local.as_str() {
865 "name" => {
866 let text = get_node_text(child);
867 if !text.is_empty() {
868 pattern.name_class =
869 Some(RelaxNgNameClass::Name(text.trim().to_string()));
870 }
871 }
872 "anyName" => {
873 pattern.name_class = Some(rng_parse_any_name(child));
874 }
875 "nsName" => {
876 pattern.name_class = Some(rng_parse_ns_name(child));
877 }
878 "choice" if pattern.name_class.is_none() => {
879 pattern.name_class = Some(rng_parse_name_class_choice(child));
881 }
882 _ => {
883 if !content_found {
885 pattern.children.push(rng_parse_pattern(child, schema));
886 content_found = true;
887 }
888 }
889 }
890 }
891 child = (*child).next;
892 }
893
894 pattern
895 }
896}
897
898unsafe fn rng_parse_attribute_pattern(
904 node: *mut _xmlNode,
905 schema: &mut RelaxNgSchema,
906) -> RelaxNgPattern {
907 unsafe {
908 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Attribute);
909
910 let name_attr = get_attr(node, "name");
912 pattern.name = name_attr.clone();
913
914 if let Some(ref n) = name_attr {
915 pattern.name_class = Some(RelaxNgNameClass::Name(n.clone()));
916 }
917
918 let mut child = (*node).children;
920 while !child.is_null() {
921 if (*child).type_ == XML_ELEMENT_NODE as c_int {
922 let child_local = get_local_name(child);
923
924 match child_local.as_str() {
925 "name" => {
926 let text = get_node_text(child);
927 if !text.is_empty() {
928 pattern.name_class =
929 Some(RelaxNgNameClass::Name(text.trim().to_string()));
930 }
931 }
932 "anyName" => {
933 pattern.name_class = Some(rng_parse_any_name(child));
934 }
935 "nsName" => {
936 pattern.name_class = Some(rng_parse_ns_name(child));
937 }
938 "choice" if pattern.name_class.is_none() => {
939 pattern.name_class = Some(rng_parse_name_class_choice(child));
940 }
941 _ => {
942 pattern.children.push(rng_parse_pattern(child, schema));
944 }
945 }
946 }
947 child = (*child).next;
948 }
949
950 pattern
951 }
952}
953
954unsafe fn rng_parse_any_name(node: *mut _xmlNode) -> RelaxNgNameClass {
960 unsafe {
961 let mut child = (*node).children;
963 while !child.is_null() {
964 if (*child).type_ == XML_ELEMENT_NODE as c_int && get_local_name(child) == "except" {
965 let except_nc = rng_parse_name_class_content(child);
966 return RelaxNgNameClass::Except(
967 Box::new(RelaxNgNameClass::AnyName),
968 Box::new(except_nc),
969 );
970 }
971 child = (*child).next;
972 }
973 RelaxNgNameClass::AnyName
974 }
975}
976
977unsafe fn rng_parse_ns_name(node: *mut _xmlNode) -> RelaxNgNameClass {
983 unsafe {
984 let ns = get_attr(node, "ns").unwrap_or_default();
985
986 let mut child = (*node).children;
988 while !child.is_null() {
989 if (*child).type_ == XML_ELEMENT_NODE as c_int && get_local_name(child) == "except" {
990 let except_nc = rng_parse_name_class_content(child);
991 return RelaxNgNameClass::Except(
992 Box::new(RelaxNgNameClass::NsName(ns)),
993 Box::new(except_nc),
994 );
995 }
996 child = (*child).next;
997 }
998
999 RelaxNgNameClass::NsName(ns)
1000 }
1001}
1002
1003unsafe fn rng_parse_name_class_choice(node: *mut _xmlNode) -> RelaxNgNameClass {
1009 unsafe {
1010 let mut choices = Vec::new();
1011 let mut child = (*node).children;
1012 while !child.is_null() {
1013 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1014 choices.push(rng_parse_name_class_item(child));
1015 }
1016 child = (*child).next;
1017 }
1018 if choices.len() == 1 {
1019 choices.remove(0)
1020 } else {
1021 RelaxNgNameClass::Choice(choices)
1022 }
1023 }
1024}
1025
1026unsafe fn rng_parse_name_class_item(node: *mut _xmlNode) -> RelaxNgNameClass {
1032 unsafe {
1033 let local = get_local_name(node);
1034 match local.as_str() {
1035 "name" => {
1036 let text = get_node_text(node);
1037 RelaxNgNameClass::Name(text.trim().to_string())
1038 }
1039 "anyName" => rng_parse_any_name(node),
1040 "nsName" => rng_parse_ns_name(node),
1041 "choice" => rng_parse_name_class_choice(node),
1042 _ => {
1043 let text = get_node_text(node);
1045 if text.trim().is_empty() {
1046 RelaxNgNameClass::AnyName
1047 } else {
1048 RelaxNgNameClass::Name(text.trim().to_string())
1049 }
1050 }
1051 }
1052 }
1053}
1054
1055unsafe fn rng_parse_name_class_content(node: *mut _xmlNode) -> RelaxNgNameClass {
1061 unsafe {
1062 let mut names = Vec::new();
1063 let mut child = (*node).children;
1064 while !child.is_null() {
1065 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1066 names.push(rng_parse_name_class_item(child));
1067 }
1068 child = (*child).next;
1069 }
1070 if names.is_empty() {
1071 RelaxNgNameClass::AnyName
1072 } else if names.len() == 1 {
1073 names.remove(0)
1074 } else {
1075 RelaxNgNameClass::Choice(names)
1076 }
1077 }
1078}
1079
1080unsafe fn rng_parse_composite_pattern(
1086 node: *mut _xmlNode,
1087 pattern_type: RelaxNgPatternType,
1088 schema: &mut RelaxNgSchema,
1089) -> RelaxNgPattern {
1090 unsafe {
1091 let mut pattern = RelaxNgPattern::new(pattern_type);
1092
1093 let mut child = (*node).children;
1094 while !child.is_null() {
1095 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1096 pattern.children.push(rng_parse_pattern(child, schema));
1097 }
1098 child = (*child).next;
1099 }
1100
1101 pattern
1102 }
1103}
1104
1105unsafe fn rng_parse_unary_pattern(
1111 node: *mut _xmlNode,
1112 pattern_type: RelaxNgPatternType,
1113 schema: &mut RelaxNgSchema,
1114) -> RelaxNgPattern {
1115 unsafe {
1116 let mut pattern = RelaxNgPattern::new(pattern_type);
1117
1118 let mut child = (*node).children;
1119 while !child.is_null() {
1120 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1121 pattern.children.push(rng_parse_pattern(child, schema));
1122 break;
1124 }
1125 child = (*child).next;
1126 }
1127
1128 pattern
1129 }
1130}
1131
1132unsafe fn rng_parse_data_pattern(
1138 node: *mut _xmlNode,
1139 _schema: &mut RelaxNgSchema,
1140) -> RelaxNgPattern {
1141 unsafe {
1142 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Data);
1143 pattern.datatype = get_attr(node, "type");
1144 pattern.datatype_library = get_attr(node, "datatypeLibrary");
1145
1146 pattern
1148 }
1149}
1150
1151unsafe fn rng_parse_value_pattern(
1157 node: *mut _xmlNode,
1158 _schema: &mut RelaxNgSchema,
1159) -> RelaxNgPattern {
1160 unsafe {
1161 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Value);
1162 pattern.datatype = get_attr(node, "type");
1163 pattern.datatype_library = get_attr(node, "datatypeLibrary");
1164 pattern.value = Some(get_node_text(node).trim().to_string());
1165
1166 pattern
1167 }
1168}
1169
1170unsafe fn rng_parse_ref_pattern(node: *mut _xmlNode) -> RelaxNgPattern {
1176 unsafe {
1177 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Ref);
1178 pattern.name = get_attr(node, "name");
1179 pattern
1180 }
1181}
1182
1183unsafe fn rng_parse_external_ref(
1189 node: *mut _xmlNode,
1190 _schema: &mut RelaxNgSchema,
1191) -> RelaxNgPattern {
1192 unsafe {
1193 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::ExternalRef);
1194 let href = get_attr(node, "href");
1195 pattern.name = href;
1196 pattern
1197 }
1198}
1199
1200pub unsafe fn rng_validate_doc(
1214 schema: &RelaxNgSchema,
1215 doc: *mut _xmlDoc,
1216 ctxt: &mut RelaxNgValidCtxt,
1217) -> bool {
1218 unsafe {
1219 if doc.is_null() {
1220 ctxt.record_error("Document is null".to_string());
1221 return false;
1222 }
1223
1224 let root = (*doc).children;
1225 if root.is_null() {
1226 ctxt.record_error("Document has no children".to_string());
1227 return false;
1228 }
1229
1230 let mut root_elem = root;
1232 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
1233 root_elem = (*root_elem).next;
1234 }
1235
1236 if root_elem.is_null() {
1237 ctxt.record_error("Document has no root element".to_string());
1238 return false;
1239 }
1240
1241 let start_pattern = match &schema.grammar.start {
1243 Some(p) => p,
1244 None => {
1245 ctxt.record_error("Schema has no start pattern".to_string());
1246 return false;
1247 }
1248 };
1249
1250 ctxt.path.clear();
1251 let valid = rng_validate_pattern(start_pattern, root_elem, schema, ctxt);
1252
1253 valid
1255 }
1256}
1257
1258fn rng_validate_pattern(
1264 pattern: &RelaxNgPattern,
1265 node: *mut _xmlNode,
1266 schema: &RelaxNgSchema,
1267 ctxt: &mut RelaxNgValidCtxt,
1268) -> bool {
1269 unsafe {
1270 if ctxt.depth >= ctxt.depth_max {
1271 ctxt.record_error("Maximum validation depth exceeded".to_string());
1272 return false;
1273 }
1274 ctxt.depth += 1;
1275
1276 let result = match pattern.pattern_type {
1277 RelaxNgPatternType::Element => {
1278 rng_validate_element_pattern(pattern, node, schema, ctxt)
1279 }
1280 RelaxNgPatternType::Attribute => {
1281 rng_validate_attribute_pattern(pattern, node, schema, ctxt)
1282 }
1283 RelaxNgPatternType::Text => rng_validate_text_pattern(node, ctxt),
1284 RelaxNgPatternType::Empty => rng_validate_empty_pattern(node, ctxt),
1285 RelaxNgPatternType::NotAllowed => {
1286 let name = get_node_qname(node);
1287 ctxt.record_error(format!(
1288 "Element '{}' is not allowed at '{}'",
1289 name,
1290 ctxt.current_path()
1291 ));
1292 false
1293 }
1294 RelaxNgPatternType::Choice => rng_validate_choice_pattern(pattern, node, schema, ctxt),
1295 RelaxNgPatternType::Sequence => {
1296 rng_validate_sequence_pattern(pattern, node, schema, ctxt)
1297 }
1298 RelaxNgPatternType::Interleave => {
1299 rng_validate_interleave_pattern(pattern, node, schema, ctxt)
1300 }
1301 RelaxNgPatternType::ZeroOrMore => {
1302 rng_validate_zero_or_more(pattern, node, schema, ctxt)
1303 }
1304 RelaxNgPatternType::OneOrMore => rng_validate_one_or_more(pattern, node, schema, ctxt),
1305 RelaxNgPatternType::Optional => {
1306 rng_validate_optional_pattern(pattern, node, schema, ctxt)
1307 }
1308 RelaxNgPatternType::List => rng_validate_list_pattern(pattern, node, schema, ctxt),
1309 RelaxNgPatternType::Group => rng_validate_group_pattern(pattern, node, schema, ctxt),
1310 RelaxNgPatternType::Data => rng_validate_data_pattern(pattern, node, ctxt),
1311 RelaxNgPatternType::Value => rng_validate_value_pattern(pattern, node, ctxt),
1312 RelaxNgPatternType::Ref => rng_validate_ref_pattern(pattern, node, schema, ctxt),
1313 RelaxNgPatternType::ExternalRef => {
1314 rng_validate_empty_pattern(node, ctxt)
1316 }
1317 RelaxNgPatternType::Define
1318 | RelaxNgPatternType::Grammar
1319 | RelaxNgPatternType::Start
1320 | RelaxNgPatternType::Include => {
1321 rng_validate_children(pattern, node, schema, ctxt)
1323 }
1324 };
1325
1326 ctxt.depth -= 1;
1327 result
1328 }
1329}
1330
1331fn rng_validate_children(
1337 pattern: &RelaxNgPattern,
1338 node: *mut _xmlNode,
1339 schema: &RelaxNgSchema,
1340 ctxt: &mut RelaxNgValidCtxt,
1341) -> bool {
1342 {
1343 if pattern.children.is_empty() {
1344 return true;
1345 }
1346 let mut valid = true;
1348 for child in &pattern.children {
1349 valid &= rng_validate_pattern(child, node, schema, ctxt);
1350 }
1351 valid
1352 }
1353}
1354
1355fn rng_validate_element_pattern(
1361 pattern: &RelaxNgPattern,
1362 node: *mut _xmlNode,
1363 schema: &RelaxNgSchema,
1364 ctxt: &mut RelaxNgValidCtxt,
1365) -> bool {
1366 unsafe {
1367 if node.is_null() || (*node).type_ != XML_ELEMENT_NODE as c_int {
1368 return false;
1369 }
1370
1371 let node_name = get_node_qname(node);
1372 let ns_uri = get_node_ns_uri(node);
1373
1374 if let Some(ref nc) = pattern.name_class {
1376 if !nc.matches(&node_name, ns_uri.as_deref()) {
1377 let pat_name = pattern.name.as_deref().unwrap_or("?");
1378 ctxt.record_error(format!(
1379 "Element '{}' does not match expected element pattern '{}' at '{}'",
1380 node_name,
1381 pat_name,
1382 ctxt.current_path()
1383 ));
1384 return false;
1385 }
1386 }
1387
1388 ctxt.path.push(node_name.clone());
1390
1391 let mut valid = true;
1393 if pattern.children.is_empty() {
1394 let mut child = (*node).children;
1396 while !child.is_null() {
1397 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1398 let child_name = get_node_qname(child);
1399 ctxt.record_error(format!(
1400 "Unexpected child element '{}' in empty element '{}' at '{}'",
1401 child_name,
1402 node_name,
1403 ctxt.current_path()
1404 ));
1405 valid = false;
1406 }
1407 child = (*child).next;
1408 }
1409 } else {
1410 for child_pat in &pattern.children {
1413 valid &= rng_validate_pattern(child_pat, node, schema, ctxt);
1414 }
1415 }
1416
1417 ctxt.path.pop();
1418 valid
1419 }
1420}
1421
1422fn rng_validate_attribute_pattern(
1428 pattern: &RelaxNgPattern,
1429 node: *mut _xmlNode,
1430 _schema: &RelaxNgSchema,
1431 ctxt: &mut RelaxNgValidCtxt,
1432) -> bool {
1433 unsafe {
1434 if node.is_null() || (*node).type_ != XML_ELEMENT_NODE as c_int {
1435 return false;
1436 }
1437
1438 let attr_name = match &pattern.name_class {
1440 Some(RelaxNgNameClass::Name(n)) => n.clone(),
1441 Some(RelaxNgNameClass::AnyName) => {
1442 return true;
1447 }
1448 Some(RelaxNgNameClass::NsName(ns)) => {
1449 let mut prop = (*node).properties;
1452 while !prop.is_null() {
1453 let prop_ns = get_node_ns_uri(prop as *mut _xmlNode);
1454 if let Some(ref uri) = prop_ns {
1455 if uri == ns {
1456 if let Some(content) = &pattern.children.first() {
1458 let val = get_node_text(prop as *mut _xmlNode);
1459 let valid = match content.pattern_type {
1460 RelaxNgPatternType::Text => true,
1461 RelaxNgPatternType::Data => rng_validate_datatype_value(
1462 content.datatype.as_deref(),
1463 &val,
1464 ),
1465 RelaxNgPatternType::Value => {
1466 content.value.as_deref() == Some(&val)
1467 }
1468 _ => true,
1469 };
1470 if !valid {
1471 ctxt.record_error(format!(
1472 "Attribute '{}' has invalid value at '{}'",
1473 prop_ns.unwrap_or_default(),
1474 ctxt.current_path()
1475 ));
1476 return false;
1477 }
1478 }
1479 return true;
1480 }
1481 }
1482 prop = (*prop).next;
1483 }
1484 ctxt.record_error(format!(
1487 "Required attribute in namespace '{}' is missing at '{}'",
1488 ns,
1489 ctxt.current_path()
1490 ));
1491 return false;
1492 }
1493 _ => {
1494 return true;
1497 }
1498 };
1499
1500 let attr_value = get_attr(node, &attr_name);
1502
1503 match attr_value {
1504 Some(val) => {
1505 if let Some(content) = pattern.children.first() {
1507 let valid = match content.pattern_type {
1508 RelaxNgPatternType::Text => true,
1509 RelaxNgPatternType::Data => {
1510 rng_validate_datatype_value(content.datatype.as_deref(), &val)
1511 }
1512 RelaxNgPatternType::Value => content.value.as_deref() == Some(&val),
1513 _ => true,
1514 };
1515 if !valid {
1516 ctxt.record_error(format!(
1517 "Attribute '{}' has invalid value '{}' at '{}'",
1518 attr_name,
1519 val,
1520 ctxt.current_path()
1521 ));
1522 return false;
1523 }
1524 }
1525 true
1526 }
1527 None => {
1528 ctxt.record_error(format!(
1531 "Required attribute '{}' is missing at '{}'",
1532 attr_name,
1533 ctxt.current_path()
1534 ));
1535 false
1536 }
1537 }
1538 }
1539}
1540
1541const fn rng_validate_text_pattern(node: *mut _xmlNode, _ctxt: &mut RelaxNgValidCtxt) -> bool {
1547 {
1548 if node.is_null() {
1549 return false;
1550 }
1551 true
1554 }
1555}
1556
1557fn rng_validate_empty_pattern(node: *mut _xmlNode, ctxt: &mut RelaxNgValidCtxt) -> bool {
1563 unsafe {
1564 if node.is_null() {
1565 return true;
1566 }
1567 let mut child = (*node).children;
1569 while !child.is_null() {
1570 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1571 let child_name = get_node_qname(child);
1572 ctxt.record_error(format!(
1573 "Unexpected child element '{}' in empty content at '{}'",
1574 child_name,
1575 ctxt.current_path()
1576 ));
1577 return false;
1578 }
1579 child = (*child).next;
1580 }
1581 true
1582 }
1583}
1584
1585fn rng_validate_choice_pattern(
1591 pattern: &RelaxNgPattern,
1592 node: *mut _xmlNode,
1593 schema: &RelaxNgSchema,
1594 ctxt: &mut RelaxNgValidCtxt,
1595) -> bool {
1596 unsafe {
1597 if pattern.children.is_empty() {
1598 return false;
1599 }
1600
1601 let mut last_error = String::new();
1603 for child in &pattern.children {
1604 let saved_errors = ctxt.errors.len();
1605 let saved_nb = ctxt.nb_errors;
1606
1607 if rng_validate_pattern(child, node, schema, ctxt) {
1608 return true;
1611 }
1612
1613 if ctxt.errors.len() > saved_errors {
1615 last_error = ctxt.errors.last().unwrap().clone();
1616 }
1617
1618 ctxt.errors.truncate(saved_errors);
1620 ctxt.nb_errors = saved_nb;
1621 }
1622
1623 let node_name = if node.is_null() {
1625 "null".to_string()
1626 } else {
1627 get_node_qname(node)
1628 };
1629 ctxt.record_error(format!(
1630 "No choice pattern matched for '{}' at '{}'. Last error: {}",
1631 node_name,
1632 ctxt.current_path(),
1633 last_error
1634 ));
1635 false
1636 }
1637}
1638
1639fn rng_validate_sequence_pattern(
1645 pattern: &RelaxNgPattern,
1646 node: *mut _xmlNode,
1647 schema: &RelaxNgSchema,
1648 ctxt: &mut RelaxNgValidCtxt,
1649) -> bool {
1650 unsafe {
1651 if node.is_null() {
1652 return pattern.children.is_empty();
1653 }
1654
1655 let mut valid = true;
1656
1657 let mut child_nodes: Vec<*mut _xmlNode> = Vec::new();
1661 let mut child = (*node).children;
1662 while !child.is_null() {
1663 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1664 child_nodes.push(child);
1665 }
1666 child = (*child).next;
1667 }
1668
1669 let mut child_idx = 0;
1672 for child_pat in &pattern.children {
1673 if child_idx >= child_nodes.len() {
1674 match child_pat.pattern_type {
1676 RelaxNgPatternType::Optional | RelaxNgPatternType::ZeroOrMore => {
1677 continue;
1679 }
1680 RelaxNgPatternType::OneOrMore => {
1681 ctxt.record_error(format!(
1682 "Expected at least one matching child for oneOrMore at '{}'",
1683 ctxt.current_path()
1684 ));
1685 valid = false;
1686 continue;
1687 }
1688 _ => {
1689 ctxt.record_error(format!(
1690 "Expected more child elements for sequence at '{}'",
1691 ctxt.current_path()
1692 ));
1693 valid = false;
1694 continue;
1695 }
1696 }
1697 }
1698
1699 let child_node = child_nodes[child_idx];
1700 valid &= rng_validate_pattern(child_pat, child_node, schema, ctxt);
1701 child_idx += 1;
1702 }
1703
1704 if child_idx < child_nodes.len() {
1706 let extra_name = get_node_qname(child_nodes[child_idx]);
1707 ctxt.record_error(format!(
1708 "Unexpected extra element '{}' in sequence at '{}'",
1709 extra_name,
1710 ctxt.current_path()
1711 ));
1712 valid = false;
1713 }
1714
1715 valid
1716 }
1717}
1718
1719fn rng_validate_interleave_pattern(
1725 pattern: &RelaxNgPattern,
1726 node: *mut _xmlNode,
1727 schema: &RelaxNgSchema,
1728 ctxt: &mut RelaxNgValidCtxt,
1729) -> bool {
1730 unsafe {
1731 if node.is_null() {
1732 return pattern.children.is_empty();
1733 }
1734
1735 let mut valid = true;
1739
1740 for child_pat in &pattern.children {
1741 let mut child = (*node).children;
1744 let mut matched = false;
1745
1746 while !child.is_null() {
1747 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1748 let saved_errors = ctxt.errors.len();
1749 let saved_nb = ctxt.nb_errors;
1750
1751 if rng_validate_pattern(child_pat, child, schema, ctxt) {
1752 matched = true;
1753 break;
1754 }
1755
1756 ctxt.errors.truncate(saved_errors);
1758 ctxt.nb_errors = saved_nb;
1759 }
1760 child = (*child).next;
1761 }
1762
1763 if !matched {
1764 match child_pat.pattern_type {
1766 RelaxNgPatternType::Optional | RelaxNgPatternType::ZeroOrMore => {
1767 }
1769 _ => {
1770 let pat_desc = format!("{:?}", child_pat.pattern_type);
1771 ctxt.record_error(format!(
1772 "Interleave pattern '{}' did not match any child at '{}'",
1773 pat_desc,
1774 ctxt.current_path()
1775 ));
1776 valid = false;
1777 }
1778 }
1779 }
1780 }
1781
1782 valid
1783 }
1784}
1785
1786fn rng_validate_zero_or_more(
1792 pattern: &RelaxNgPattern,
1793 node: *mut _xmlNode,
1794 schema: &RelaxNgSchema,
1795 ctxt: &mut RelaxNgValidCtxt,
1796) -> bool {
1797 unsafe {
1798 if node.is_null() || pattern.children.is_empty() {
1799 return true;
1800 }
1801
1802 let child_pat = &pattern.children[0];
1803 let valid = true;
1804
1805 let mut child = (*node).children;
1807 while !child.is_null() {
1808 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1809 let saved_errors = ctxt.errors.len();
1810 let saved_nb = ctxt.nb_errors;
1811
1812 if !rng_validate_pattern(child_pat, child, schema, ctxt) {
1813 ctxt.errors.truncate(saved_errors);
1816 ctxt.nb_errors = saved_nb;
1817 break;
1818 }
1819 }
1821 child = (*child).next;
1822 }
1823
1824 valid
1825 }
1826}
1827
1828fn rng_validate_one_or_more(
1834 pattern: &RelaxNgPattern,
1835 node: *mut _xmlNode,
1836 schema: &RelaxNgSchema,
1837 ctxt: &mut RelaxNgValidCtxt,
1838) -> bool {
1839 unsafe {
1840 if node.is_null() || pattern.children.is_empty() {
1841 ctxt.record_error(format!(
1842 "Expected at least one matching element for oneOrMore at '{}'",
1843 ctxt.current_path()
1844 ));
1845 return false;
1846 }
1847
1848 let child_pat = &pattern.children[0];
1849 let mut matched = false;
1850 let mut valid = true;
1851
1852 let mut child = (*node).children;
1854 while !child.is_null() {
1855 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1856 let saved_errors = ctxt.errors.len();
1857 let saved_nb = ctxt.nb_errors;
1858
1859 if rng_validate_pattern(child_pat, child, schema, ctxt) {
1860 matched = true;
1861 } else {
1862 ctxt.errors.truncate(saved_errors);
1864 ctxt.nb_errors = saved_nb;
1865 break;
1866 }
1867 }
1868 child = (*child).next;
1869 }
1870
1871 if !matched {
1872 ctxt.record_error(format!(
1873 "Expected at least one matching element for oneOrMore at '{}'",
1874 ctxt.current_path()
1875 ));
1876 valid = false;
1877 }
1878
1879 valid
1880 }
1881}
1882
1883fn rng_validate_optional_pattern(
1889 pattern: &RelaxNgPattern,
1890 node: *mut _xmlNode,
1891 schema: &RelaxNgSchema,
1892 ctxt: &mut RelaxNgValidCtxt,
1893) -> bool {
1894 {
1895 if pattern.children.is_empty() {
1896 return true;
1897 }
1898
1899 let child_pat = &pattern.children[0];
1901 let saved_errors = ctxt.errors.len();
1902 let saved_nb = ctxt.nb_errors;
1903
1904 let result = rng_validate_pattern(child_pat, node, schema, ctxt);
1905
1906 if !result {
1907 ctxt.errors.truncate(saved_errors);
1909 ctxt.nb_errors = saved_nb;
1910 }
1911
1912 true }
1914}
1915
1916fn rng_validate_list_pattern(
1922 pattern: &RelaxNgPattern,
1923 node: *mut _xmlNode,
1924 _schema: &RelaxNgSchema,
1925 _ctxt: &mut RelaxNgValidCtxt,
1926) -> bool {
1927 unsafe {
1928 if node.is_null() {
1929 return pattern.children.is_empty();
1930 }
1931
1932 let text = get_node_text(node);
1934 if text.trim().is_empty() {
1935 return true;
1936 }
1937
1938 let tokens: Vec<&str> = text.split_whitespace().collect();
1939 let mut valid = true;
1940
1941 for token in &tokens {
1942 if token.is_empty() {
1945 valid = false;
1946 break;
1947 }
1948 }
1949
1950 valid
1951 }
1952}
1953
1954fn rng_validate_group_pattern(
1960 pattern: &RelaxNgPattern,
1961 node: *mut _xmlNode,
1962 schema: &RelaxNgSchema,
1963 ctxt: &mut RelaxNgValidCtxt,
1964) -> bool {
1965 {
1966 rng_validate_sequence_pattern(pattern, node, schema, ctxt)
1968 }
1969}
1970
1971fn rng_validate_data_pattern(
1977 pattern: &RelaxNgPattern,
1978 node: *mut _xmlNode,
1979 ctxt: &mut RelaxNgValidCtxt,
1980) -> bool {
1981 unsafe {
1982 if node.is_null() {
1983 return false;
1984 }
1985
1986 let text = get_node_text(node);
1987 let datatype = pattern.datatype.as_deref();
1988
1989 if !rng_validate_datatype_value(datatype, &text) {
1990 ctxt.record_error(format!(
1991 "Value '{}' does not match datatype '{:?}' at '{}'",
1992 text,
1993 datatype,
1994 ctxt.current_path()
1995 ));
1996 return false;
1997 }
1998
1999 true
2000 }
2001}
2002
2003fn rng_validate_value_pattern(
2009 pattern: &RelaxNgPattern,
2010 node: *mut _xmlNode,
2011 ctxt: &mut RelaxNgValidCtxt,
2012) -> bool {
2013 unsafe {
2014 if node.is_null() {
2015 return false;
2016 }
2017
2018 let text = get_node_text(node).trim().to_string();
2019 let expected = pattern.value.as_deref().unwrap_or("");
2020
2021 if text != expected {
2022 ctxt.record_error(format!(
2023 "Value '{}' does not match expected value '{}' at '{}'",
2024 text,
2025 expected,
2026 ctxt.current_path()
2027 ));
2028 return false;
2029 }
2030
2031 true
2032 }
2033}
2034
2035fn rng_validate_ref_pattern(
2041 pattern: &RelaxNgPattern,
2042 node: *mut _xmlNode,
2043 schema: &RelaxNgSchema,
2044 ctxt: &mut RelaxNgValidCtxt,
2045) -> bool {
2046 {
2047 let ref_name = pattern.name.as_deref().unwrap_or("");
2048
2049 if ref_name.is_empty() {
2050 ctxt.record_error("Ref pattern has no name".to_string());
2051 return false;
2052 }
2053
2054 match schema.grammar.lookup(ref_name) {
2056 Some(def_pattern) => {
2057 rng_validate_pattern(def_pattern, node, schema, ctxt)
2059 }
2060 None => {
2061 ctxt.record_error(format!(
2062 "Undefined reference '{}' at '{}'",
2063 ref_name,
2064 ctxt.current_path()
2065 ));
2066 false
2067 }
2068 }
2069 }
2070}
2071
2072fn rng_validate_datatype_value(datatype: Option<&str>, value: &str) -> bool {
2081 let dt = match datatype {
2082 Some(d) => d,
2083 None => return true, };
2085
2086 match dt {
2087 "string" | "token" => true,
2088 "boolean" => {
2089 matches!(value, "true" | "false" | "1" | "0")
2090 }
2091 "integer" | "int" | "short" | "byte" | "long" => {
2092 if value.is_empty() {
2093 return false;
2094 }
2095 let trimmed = if value.starts_with('+') || value.starts_with('-') {
2096 &value[1..]
2097 } else {
2098 value
2099 };
2100 !trimmed.is_empty() && trimmed.chars().all(|c| c.is_ascii_digit())
2101 }
2102 "decimal" | "double" | "float" => {
2103 if value.is_empty() {
2104 return false;
2105 }
2106 if matches!(dt, "float" | "double") && matches!(value, "INF" | "-INF" | "NaN") {
2108 return true;
2109 }
2110 value.parse::<f64>().is_ok()
2111 }
2112 "NCName" | "Name" | "ID" | "IDREF" | "NMTOKEN" => {
2113 !value.is_empty() && !value.starts_with(|c: char| c.is_ascii_digit())
2114 }
2115 "anyURI" => {
2116 !value.is_empty() && !value.contains(char::is_whitespace)
2118 }
2119 "QName" => {
2120 if value.is_empty() {
2121 return false;
2122 }
2123 if let Some(pos) = value.find(':') {
2124 pos > 0 && pos < value.len() - 1
2125 } else {
2126 true
2127 }
2128 }
2129 _ => {
2130 true
2133 }
2134 }
2135}
2136
2137pub fn rng_parse_schema(xml_doc: &str) -> Result<RelaxNgSchema, String> {
2145 rng_parse(xml_doc)
2146}
2147
2148pub unsafe fn rng_parse_schema_doc(doc: *mut _xmlDoc) -> Result<RelaxNgSchema, String> {
2154 rng_parse_doc(doc)
2155}
2156
2157pub unsafe fn rng_validate_doc_schema(
2165 schema: &RelaxNgSchema,
2166 doc: *mut _xmlDoc,
2167 ctxt: &mut RelaxNgValidCtxt,
2168) -> bool {
2169 rng_validate_doc(schema, doc, ctxt)
2170}
2171
2172#[no_mangle]
2191pub unsafe extern "C" fn xmlRelaxNGNewParserCtxt(url: *const c_char) -> *mut c_void {
2192 if url.is_null() {
2193 let ctxt = allocator::xmlMallocZero(size_of::<RelaxNgSchema>() as usize);
2194 return ctxt;
2195 }
2196
2197 let url_str = unsafe {
2198 let mut len = 0;
2199 while *url.add(len) != 0 {
2200 len += 1;
2201 }
2202 let slice = std::slice::from_raw_parts(url as *const u8, len);
2203 String::from_utf8_lossy(slice).to_string()
2204 };
2205
2206 if !url_str.is_empty() {
2208 let url_c = std::ffi::CString::new(url_str.clone()).ok();
2209 if let Some(c) = url_c {
2210 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
2211 if !doc.is_null() {
2212 let result = rng_parse_doc(doc);
2213 crate::abi::exports_xml2::xmlFreeDoc(doc);
2214 if let Ok(schema) = result {
2215 let schema_box = Box::new(schema);
2216 return Box::into_raw(schema_box) as *mut c_void;
2217 }
2218 }
2219 }
2220 }
2221
2222 allocator::xmlMallocZero(size_of::<RelaxNgSchema>() as usize)
2225}
2226
2227#[no_mangle]
2239pub unsafe extern "C" fn xmlRelaxNGNewMemParserCtxt(
2240 buffer: *const c_char,
2241 size: c_int,
2242) -> *mut c_void {
2243 if buffer.is_null() || size <= 0 {
2244 return ptr::null_mut();
2245 }
2246
2247 let buf_slice = unsafe { std::slice::from_raw_parts(buffer as *const u8, size as usize) };
2249 let xml_str = String::from_utf8_lossy(buf_slice).to_string();
2250
2251 match rng_parse(&xml_str) {
2252 Ok(schema) => {
2253 let schema_box = Box::new(schema);
2254 Box::into_raw(schema_box) as *mut c_void
2255 }
2256 Err(_) => ptr::null_mut(),
2257 }
2258}
2259
2260#[no_mangle]
2272pub const unsafe extern "C" fn xmlRelaxNGParse(ctxt: *mut c_void) -> *mut c_void {
2273 if ctxt.is_null() {
2274 return ptr::null_mut();
2275 }
2276
2277 ctxt
2280}
2281
2282#[no_mangle]
2294pub unsafe extern "C" fn xmlRelaxNGFree(schema: *mut c_void) {
2295 if schema.is_null() {
2296 return;
2297 }
2298 unsafe {
2300 let _ = Box::from_raw(schema as *mut RelaxNgSchema);
2301 }
2302}
2303
2304#[no_mangle]
2316pub unsafe extern "C" fn xmlRelaxNGFreeParserCtxt(ctxt: *mut c_void) {
2317 if ctxt.is_null() {
2318 return;
2319 }
2320 unsafe {
2322 let _ = Box::from_raw(ctxt as *mut RelaxNgSchema);
2323 }
2324}
2325
2326#[no_mangle]
2338pub unsafe extern "C" fn xmlRelaxNGNewValidCtxt(schema: *mut c_void) -> *mut c_void {
2339 let mut ctxt = RelaxNgValidCtxt::new();
2340
2341 if !schema.is_null() {
2342 unsafe {
2344 let schema_ref = &*(schema as *const RelaxNgSchema);
2345 ctxt.schema = Some(schema_ref.clone());
2346 }
2347 }
2348
2349 let boxed = Box::new(ctxt);
2350 Box::into_raw(boxed) as *mut c_void
2351}
2352
2353#[no_mangle]
2365pub unsafe extern "C" fn xmlRelaxNGFreeValidCtxt(ctxt: *mut c_void) {
2366 if ctxt.is_null() {
2367 return;
2368 }
2369 unsafe {
2371 let _ = Box::from_raw(ctxt as *mut RelaxNgValidCtxt);
2372 }
2373}
2374
2375#[no_mangle]
2390pub unsafe extern "C" fn xmlRelaxNGValidateDoc(ctxt: *mut c_void, doc: *mut _xmlDoc) -> c_int {
2391 if ctxt.is_null() || doc.is_null() {
2392 return -1;
2393 }
2394
2395 unsafe {
2396 let valid_ctxt = &mut *(ctxt as *mut RelaxNgValidCtxt);
2397 let schema = match &valid_ctxt.schema {
2398 Some(s) => s,
2399 None => return -1,
2400 };
2401
2402 let mut temp_ctxt = RelaxNgValidCtxt::new();
2403
2404 let valid = rng_validate_doc(schema, doc, &mut temp_ctxt);
2405
2406 if valid {
2407 0
2408 } else {
2409 valid_ctxt.errors = temp_ctxt.errors;
2410 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2411 temp_ctxt.nb_errors
2412 }
2413 }
2414}
2415
2416#[no_mangle]
2434pub unsafe extern "C" fn xmlRelaxNGValidateFullElement(
2435 ctxt: *mut c_void,
2436 doc: *mut _xmlDoc,
2437 elem: *mut _xmlNode,
2438) -> c_int {
2439 if ctxt.is_null() || doc.is_null() || elem.is_null() {
2440 return -1;
2441 }
2442
2443 unsafe {
2444 let valid_ctxt = &mut *(ctxt as *mut RelaxNgValidCtxt);
2445 let schema = match &valid_ctxt.schema {
2446 Some(s) => s,
2447 None => return -1,
2448 };
2449
2450 let mut temp_ctxt = RelaxNgValidCtxt::new();
2451 temp_ctxt.path = valid_ctxt.path.clone();
2452
2453 let start_pattern = match &schema.grammar.start {
2454 Some(p) => p,
2455 None => return -1,
2456 };
2457
2458 let valid = rng_validate_pattern(start_pattern, elem, schema, &mut temp_ctxt);
2459
2460 if valid {
2461 0
2462 } else {
2463 valid_ctxt.errors = temp_ctxt.errors;
2464 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2465 temp_ctxt.nb_errors
2466 }
2467 }
2468}
2469
2470#[cfg(test)]
2475mod tests {
2476 use super::*;
2477
2478 #[test]
2481 fn test_name_class_name() {
2482 let nc = RelaxNgNameClass::Name("foo".to_string());
2483 assert!(nc.matches("foo", None));
2484 assert!(!nc.matches("bar", None));
2485 assert!(!nc.matches("FOO", None));
2486 }
2487
2488 #[test]
2489 fn test_name_class_any_name() {
2490 let nc = RelaxNgNameClass::AnyName;
2491 assert!(nc.matches("foo", None));
2492 assert!(nc.matches("bar", None));
2493 assert!(nc.matches("anything", Some("urn:ns")));
2494 }
2495
2496 #[test]
2497 fn test_name_class_ns_name() {
2498 let nc = RelaxNgNameClass::NsName("urn:example".to_string());
2499 assert!(nc.matches("foo", Some("urn:example")));
2500 assert!(!nc.matches("foo", Some("urn:other")));
2501 assert!(!nc.matches("foo", None));
2502 }
2503
2504 #[test]
2505 fn test_name_class_choice() {
2506 let nc = RelaxNgNameClass::Choice(vec![
2507 RelaxNgNameClass::Name("a".to_string()),
2508 RelaxNgNameClass::Name("b".to_string()),
2509 ]);
2510 assert!(nc.matches("a", None));
2511 assert!(nc.matches("b", None));
2512 assert!(!nc.matches("c", None));
2513 }
2514
2515 #[test]
2516 fn test_name_class_except() {
2517 let nc = RelaxNgNameClass::Except(
2518 Box::new(RelaxNgNameClass::AnyName),
2519 Box::new(RelaxNgNameClass::Name("bad".to_string())),
2520 );
2521 assert!(nc.matches("good", None));
2522 assert!(!nc.matches("bad", None));
2523 }
2524
2525 #[test]
2528 fn test_parse_simple_element_schema() {
2529 let schema_xml = r#"<?xml version="1.0"?>
2530<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2531 <text/>
2532</element>"#;
2533
2534 let result = rng_parse(schema_xml);
2535 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2536 let schema = result.unwrap();
2537 assert!(schema.grammar.start.is_some());
2538 if let Some(ref start) = schema.grammar.start {
2539 assert_eq!(start.pattern_type, RelaxNgPatternType::Element);
2540 assert_eq!(start.name.as_deref(), Some("root"));
2541 }
2542 }
2543
2544 #[test]
2545 fn test_parse_grammar_schema() {
2546 let schema_xml = r#"<?xml version="1.0"?>
2547<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2548 <start>
2549 <element name="root">
2550 <text/>
2551 </element>
2552 </start>
2553</grammar>"#;
2554
2555 let result = rng_parse(schema_xml);
2556 assert!(
2557 result.is_ok(),
2558 "Failed to parse grammar: {:?}",
2559 result.err()
2560 );
2561 let schema = result.unwrap();
2562 assert!(schema.grammar.start.is_some());
2563 }
2564
2565 #[test]
2566 fn test_parse_with_define_and_ref() {
2567 let schema_xml = r#"<?xml version="1.0"?>
2568<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2569 <define name="textBlock">
2570 <text/>
2571 </define>
2572 <start>
2573 <element name="doc">
2574 <ref name="textBlock"/>
2575 </element>
2576 </start>
2577</grammar>"#;
2578
2579 let result = rng_parse(schema_xml);
2580 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
2581 let schema = result.unwrap();
2582 assert_eq!(schema.grammar.defines.len(), 1);
2583 assert_eq!(schema.grammar.defines[0].name, "textBlock");
2584 assert!(schema.grammar.start.is_some());
2585 }
2586
2587 #[test]
2588 fn test_parse_choice_schema() {
2589 let schema_xml = r#"<?xml version="1.0"?>
2590<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2591 <start>
2592 <choice>
2593 <element name="a">
2594 <text/>
2595 </element>
2596 <element name="b">
2597 <text/>
2598 </element>
2599 </choice>
2600 </start>
2601</grammar>"#;
2602
2603 let result = rng_parse(schema_xml);
2604 assert!(result.is_ok(), "Failed to parse choice: {:?}", result.err());
2605 }
2606
2607 #[test]
2608 fn test_parse_attribute_schema() {
2609 let schema_xml = r#"<?xml version="1.0"?>
2610<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2611 <attribute name="attr1">
2612 <text/>
2613 </attribute>
2614 <text/>
2615</element>"#;
2616
2617 let result = rng_parse(schema_xml);
2618 assert!(
2619 result.is_ok(),
2620 "Failed to parse attribute: {:?}",
2621 result.err()
2622 );
2623 }
2624
2625 #[test]
2626 fn test_parse_empty_document_fails() {
2627 let result = rng_parse("");
2628 assert!(result.is_err());
2629 }
2630
2631 #[test]
2632 fn test_parse_invalid_xml_fails() {
2633 let result = rng_parse("not valid xml <<<");
2634 assert!(result.is_err());
2635 }
2636
2637 #[test]
2648 fn test_validate_simple_element() {
2649 let schema_xml = r#"<?xml version="1.0"?>
2650<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2651 <text/>
2652</element>"#;
2653
2654 let doc_xml = r#"<?xml version="1.0"?>
2655<root>Hello</root>"#;
2656
2657 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2658
2659 let doc = unsafe {
2660 crate::abi::exports_xml2::xmlReadMemory(
2661 doc_xml.as_ptr() as *const c_char,
2662 doc_xml.len() as c_int,
2663 c"test.xml".as_ptr() as *const c_char,
2664 ptr::null(),
2665 0,
2666 )
2667 };
2668 assert!(!doc.is_null(), "Failed to parse document");
2669
2670 let mut ctxt = RelaxNgValidCtxt::new();
2671 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2672 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2673
2674 assert!(valid, "Validation failed: {:?}", ctxt.errors);
2675 }
2676
2677 #[test]
2686 fn test_validate_element_mismatch() {
2687 let schema_xml = r#"<?xml version="1.0"?>
2688<element name="expected" xmlns="http://relaxng.org/ns/structure/1.0">
2689 <text/>
2690</element>"#;
2691
2692 let doc_xml = r#"<?xml version="1.0"?>
2693<actual>Content</actual>"#;
2694
2695 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2696
2697 let doc = unsafe {
2698 crate::abi::exports_xml2::xmlReadMemory(
2699 doc_xml.as_ptr() as *const c_char,
2700 doc_xml.len() as c_int,
2701 c"test.xml".as_ptr() as *const c_char,
2702 ptr::null(),
2703 0,
2704 )
2705 };
2706 assert!(!doc.is_null());
2707
2708 let mut ctxt = RelaxNgValidCtxt::new();
2709 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2710 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2711
2712 assert!(!valid, "Validation should have failed");
2713 assert!(ctxt.nb_errors > 0);
2714 }
2715
2716 #[test]
2725 fn test_validate_with_attribute() {
2726 let schema_xml = r#"<?xml version="1.0"?>
2727<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2728 <attribute name="id">
2729 <text/>
2730 </attribute>
2731 <text/>
2732</element>"#;
2733
2734 let doc_xml = r#"<?xml version="1.0"?>
2735<root id="x1">Content</root>"#;
2736
2737 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2738
2739 let doc = unsafe {
2740 crate::abi::exports_xml2::xmlReadMemory(
2741 doc_xml.as_ptr() as *const c_char,
2742 doc_xml.len() as c_int,
2743 c"test.xml".as_ptr() as *const c_char,
2744 ptr::null(),
2745 0,
2746 )
2747 };
2748 assert!(!doc.is_null());
2749
2750 let mut ctxt = RelaxNgValidCtxt::new();
2751 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2752 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2753
2754 assert!(valid, "Validation failed: {:?}", ctxt.errors);
2755 }
2756
2757 #[test]
2766 fn test_validate_missing_attribute() {
2767 let schema_xml = r#"<?xml version="1.0"?>
2768<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2769 <attribute name="required">
2770 <text/>
2771 </attribute>
2772 <text/>
2773</element>"#;
2774
2775 let doc_xml = r#"<?xml version="1.0"?>
2776<root>Content</root>"#;
2777
2778 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2779
2780 let doc = unsafe {
2781 crate::abi::exports_xml2::xmlReadMemory(
2782 doc_xml.as_ptr() as *const c_char,
2783 doc_xml.len() as c_int,
2784 c"test.xml".as_ptr() as *const c_char,
2785 ptr::null(),
2786 0,
2787 )
2788 };
2789 assert!(!doc.is_null());
2790
2791 let mut ctxt = RelaxNgValidCtxt::new();
2792 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2793 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2794
2795 assert!(
2796 !valid,
2797 "Validation should have failed for missing attribute"
2798 );
2799 }
2800
2801 #[test]
2810 fn test_validate_with_choice() {
2811 let schema_xml = r#"<?xml version="1.0"?>
2812<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2813 <start>
2814 <choice>
2815 <element name="a">
2816 <text/>
2817 </element>
2818 <element name="b">
2819 <text/>
2820 </element>
2821 </choice>
2822 </start>
2823</grammar>"#;
2824
2825 let doc_xml = r#"<?xml version="1.0"?>
2826<a>First choice</a>"#;
2827
2828 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2829
2830 let doc = unsafe {
2831 crate::abi::exports_xml2::xmlReadMemory(
2832 doc_xml.as_ptr() as *const c_char,
2833 doc_xml.len() as c_int,
2834 c"test.xml".as_ptr() as *const c_char,
2835 ptr::null(),
2836 0,
2837 )
2838 };
2839 assert!(!doc.is_null());
2840
2841 let mut ctxt = RelaxNgValidCtxt::new();
2842 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2843 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2844
2845 assert!(valid, "Choice validation failed: {:?}", ctxt.errors);
2846 }
2847
2848 #[test]
2857 fn test_validate_choice_no_match() {
2858 let schema_xml = r#"<?xml version="1.0"?>
2859<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2860 <start>
2861 <choice>
2862 <element name="a">
2863 <text/>
2864 </element>
2865 <element name="b">
2866 <text/>
2867 </element>
2868 </choice>
2869 </start>
2870</grammar>"#;
2871
2872 let doc_xml = r#"<?xml version="1.0"?>
2873<c>Neither choice</c>"#;
2874
2875 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2876
2877 let doc = unsafe {
2878 crate::abi::exports_xml2::xmlReadMemory(
2879 doc_xml.as_ptr() as *const c_char,
2880 doc_xml.len() as c_int,
2881 c"test.xml".as_ptr() as *const c_char,
2882 ptr::null(),
2883 0,
2884 )
2885 };
2886 assert!(!doc.is_null());
2887
2888 let mut ctxt = RelaxNgValidCtxt::new();
2889 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2890 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2891
2892 assert!(
2893 !valid,
2894 "Validation should have failed for no matching choice"
2895 );
2896 }
2897
2898 #[test]
2907 fn test_validate_grammar_with_ref() {
2908 let schema_xml = r#"<?xml version="1.0"?>
2909<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2910 <define name="para">
2911 <element name="p">
2912 <text/>
2913 </element>
2914 </define>
2915 <start>
2916 <element name="doc">
2917 <zeroOrMore>
2918 <ref name="para"/>
2919 </zeroOrMore>
2920 </element>
2921 </start>
2922</grammar>"#;
2923
2924 let doc_xml = r#"<?xml version="1.0"?>
2925<doc><p>First</p><p>Second</p></doc>"#;
2926
2927 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2928
2929 let doc = unsafe {
2930 crate::abi::exports_xml2::xmlReadMemory(
2931 doc_xml.as_ptr() as *const c_char,
2932 doc_xml.len() as c_int,
2933 c"test.xml".as_ptr() as *const c_char,
2934 ptr::null(),
2935 0,
2936 )
2937 };
2938 assert!(!doc.is_null());
2939
2940 let mut ctxt = RelaxNgValidCtxt::new();
2941 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2942 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2943
2944 assert!(valid, "Ref validation failed: {:?}", ctxt.errors);
2945 }
2946
2947 #[test]
2956 fn test_validate_zero_or_more() {
2957 let schema_xml = r#"<?xml version="1.0"?>
2958<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2959 <zeroOrMore>
2960 <element name="item">
2961 <text/>
2962 </element>
2963 </zeroOrMore>
2964</element>"#;
2965
2966 let doc_xml = r#"<?xml version="1.0"?>
2967<root><item>A</item><item>B</item></root>"#;
2968
2969 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2970
2971 let doc = unsafe {
2972 crate::abi::exports_xml2::xmlReadMemory(
2973 doc_xml.as_ptr() as *const c_char,
2974 doc_xml.len() as c_int,
2975 c"test.xml".as_ptr() as *const c_char,
2976 ptr::null(),
2977 0,
2978 )
2979 };
2980 assert!(!doc.is_null());
2981
2982 let mut ctxt = RelaxNgValidCtxt::new();
2983 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2984 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2985
2986 assert!(valid, "zeroOrMore validation failed: {:?}", ctxt.errors);
2987 }
2988
2989 #[test]
2998 fn test_validate_zero_or_more_empty() {
2999 let schema_xml = r#"<?xml version="1.0"?>
3000<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3001 <zeroOrMore>
3002 <element name="item">
3003 <text/>
3004 </element>
3005 </zeroOrMore>
3006</element>"#;
3007
3008 let doc_xml = r#"<?xml version="1.0"?>
3009<root></root>"#;
3010
3011 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3012
3013 let doc = unsafe {
3014 crate::abi::exports_xml2::xmlReadMemory(
3015 doc_xml.as_ptr() as *const c_char,
3016 doc_xml.len() as c_int,
3017 c"test.xml".as_ptr() as *const c_char,
3018 ptr::null(),
3019 0,
3020 )
3021 };
3022 assert!(!doc.is_null());
3023
3024 let mut ctxt = RelaxNgValidCtxt::new();
3025 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3026 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3027
3028 assert!(valid, "Empty zeroOrMore should be valid");
3029 }
3030
3031 #[test]
3040 fn test_validate_one_or_more() {
3041 let schema_xml = r#"<?xml version="1.0"?>
3042<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3043 <oneOrMore>
3044 <element name="item">
3045 <text/>
3046 </element>
3047 </oneOrMore>
3048</element>"#;
3049
3050 let doc_xml = r#"<?xml version="1.0"?>
3051<root><item>Single</item></root>"#;
3052
3053 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3054
3055 let doc = unsafe {
3056 crate::abi::exports_xml2::xmlReadMemory(
3057 doc_xml.as_ptr() as *const c_char,
3058 doc_xml.len() as c_int,
3059 c"test.xml".as_ptr() as *const c_char,
3060 ptr::null(),
3061 0,
3062 )
3063 };
3064 assert!(!doc.is_null());
3065
3066 let mut ctxt = RelaxNgValidCtxt::new();
3067 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3068 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3069
3070 assert!(valid, "oneOrMore validation failed: {:?}", ctxt.errors);
3071 }
3072
3073 #[test]
3082 fn test_validate_optional_present() {
3083 let schema_xml = r#"<?xml version="1.0"?>
3084<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3085 <optional>
3086 <element name="opt">
3087 <text/>
3088 </element>
3089 </optional>
3090 <text/>
3091</element>"#;
3092
3093 let doc_xml = r#"<?xml version="1.0"?>
3094<root><opt>present</opt>text</root>"#;
3095
3096 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3097
3098 let doc = unsafe {
3099 crate::abi::exports_xml2::xmlReadMemory(
3100 doc_xml.as_ptr() as *const c_char,
3101 doc_xml.len() as c_int,
3102 c"test.xml".as_ptr() as *const c_char,
3103 ptr::null(),
3104 0,
3105 )
3106 };
3107 assert!(!doc.is_null());
3108
3109 let mut ctxt = RelaxNgValidCtxt::new();
3110 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3111 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3112
3113 assert!(
3114 valid,
3115 "Optional present validation failed: {:?}",
3116 ctxt.errors
3117 );
3118 }
3119
3120 #[test]
3129 fn test_validate_optional_absent() {
3130 let schema_xml = r#"<?xml version="1.0"?>
3131<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3132 <optional>
3133 <element name="opt">
3134 <text/>
3135 </element>
3136 </optional>
3137 <text/>
3138</element>"#;
3139
3140 let doc_xml = r#"<?xml version="1.0"?>
3141<root>text only</root>"#;
3142
3143 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3144
3145 let doc = unsafe {
3146 crate::abi::exports_xml2::xmlReadMemory(
3147 doc_xml.as_ptr() as *const c_char,
3148 doc_xml.len() as c_int,
3149 c"test.xml".as_ptr() as *const c_char,
3150 ptr::null(),
3151 0,
3152 )
3153 };
3154 assert!(!doc.is_null());
3155
3156 let mut ctxt = RelaxNgValidCtxt::new();
3157 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3158 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3159
3160 assert!(
3161 valid,
3162 "Optional absent validation failed: {:?}",
3163 ctxt.errors
3164 );
3165 }
3166
3167 #[test]
3176 fn test_validate_sequence() {
3177 let schema_xml = r#"<?xml version="1.0"?>
3178<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3179 <start>
3180 <element name="root">
3181 <sequence>
3182 <element name="first">
3183 <text/>
3184 </element>
3185 <element name="second">
3186 <text/>
3187 </element>
3188 </sequence>
3189 </element>
3190 </start>
3191</grammar>"#;
3192
3193 let doc_xml = r#"<?xml version="1.0"?>
3194<root><first>First</first><second>Second</second></root>"#;
3195
3196 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3197
3198 let doc = unsafe {
3199 crate::abi::exports_xml2::xmlReadMemory(
3200 doc_xml.as_ptr() as *const c_char,
3201 doc_xml.len() as c_int,
3202 c"test.xml".as_ptr() as *const c_char,
3203 ptr::null(),
3204 0,
3205 )
3206 };
3207 assert!(!doc.is_null());
3208
3209 let mut ctxt = RelaxNgValidCtxt::new();
3210 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3211 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3212
3213 assert!(valid, "Sequence validation failed: {:?}", ctxt.errors);
3214 }
3215
3216 #[test]
3225 fn test_validate_data_pattern() {
3226 let schema_xml = r#"<?xml version="1.0"?>
3227<element name="age" xmlns="http://relaxng.org/ns/structure/1.0">
3228 <data type="integer"/>
3229</element>"#;
3230
3231 let doc_xml = r#"<?xml version="1.0"?>
3232<age>25</age>"#;
3233
3234 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3235
3236 let doc = unsafe {
3237 crate::abi::exports_xml2::xmlReadMemory(
3238 doc_xml.as_ptr() as *const c_char,
3239 doc_xml.len() as c_int,
3240 c"test.xml".as_ptr() as *const c_char,
3241 ptr::null(),
3242 0,
3243 )
3244 };
3245 assert!(!doc.is_null());
3246
3247 let mut ctxt = RelaxNgValidCtxt::new();
3248 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3249 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3250
3251 assert!(valid, "Data pattern validation failed: {:?}", ctxt.errors);
3252 }
3253
3254 #[test]
3263 fn test_validate_data_pattern_invalid() {
3264 let schema_xml = r#"<?xml version="1.0"?>
3265<element name="age" xmlns="http://relaxng.org/ns/structure/1.0">
3266 <data type="integer"/>
3267</element>"#;
3268
3269 let doc_xml = r#"<?xml version="1.0"?>
3270<age>not-a-number</age>"#;
3271
3272 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3273
3274 let doc = unsafe {
3275 crate::abi::exports_xml2::xmlReadMemory(
3276 doc_xml.as_ptr() as *const c_char,
3277 doc_xml.len() as c_int,
3278 c"test.xml".as_ptr() as *const c_char,
3279 ptr::null(),
3280 0,
3281 )
3282 };
3283 assert!(!doc.is_null());
3284
3285 let mut ctxt = RelaxNgValidCtxt::new();
3286 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3287 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3288
3289 assert!(!valid, "Validation should have failed for invalid integer");
3290 }
3291
3292 #[test]
3301 fn test_validate_value_pattern() {
3302 let schema_xml = r#"<?xml version="1.0"?>
3303<element name="status" xmlns="http://relaxng.org/ns/structure/1.0">
3304 <value>active</value>
3305</element>"#;
3306
3307 let doc_xml = r#"<?xml version="1.0"?>
3308<status>active</status>"#;
3309
3310 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3311
3312 let doc = unsafe {
3313 crate::abi::exports_xml2::xmlReadMemory(
3314 doc_xml.as_ptr() as *const c_char,
3315 doc_xml.len() as c_int,
3316 c"test.xml".as_ptr() as *const c_char,
3317 ptr::null(),
3318 0,
3319 )
3320 };
3321 assert!(!doc.is_null());
3322
3323 let mut ctxt = RelaxNgValidCtxt::new();
3324 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3325 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3326
3327 assert!(valid, "Value pattern validation failed: {:?}", ctxt.errors);
3328 }
3329
3330 #[test]
3339 fn test_validate_value_pattern_mismatch() {
3340 let schema_xml = r#"<?xml version="1.0"?>
3341<element name="status" xmlns="http://relaxng.org/ns/structure/1.0">
3342 <value>active</value>
3343</element>"#;
3344
3345 let doc_xml = r#"<?xml version="1.0"?>
3346<status>inactive</status>"#;
3347
3348 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3349
3350 let doc = unsafe {
3351 crate::abi::exports_xml2::xmlReadMemory(
3352 doc_xml.as_ptr() as *const c_char,
3353 doc_xml.len() as c_int,
3354 c"test.xml".as_ptr() as *const c_char,
3355 ptr::null(),
3356 0,
3357 )
3358 };
3359 assert!(!doc.is_null());
3360
3361 let mut ctxt = RelaxNgValidCtxt::new();
3362 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3363 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3364
3365 assert!(!valid, "Validation should have failed for value mismatch");
3366 }
3367
3368 #[test]
3377 fn test_validate_not_allowed() {
3378 let schema_xml = r#"<?xml version="1.0"?>
3379<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3380 <notAllowed/>
3381</element>"#;
3382
3383 let doc_xml = r#"<?xml version="1.0"?>
3384<root>should not be allowed</root>"#;
3385
3386 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3387
3388 let doc = unsafe {
3389 crate::abi::exports_xml2::xmlReadMemory(
3390 doc_xml.as_ptr() as *const c_char,
3391 doc_xml.len() as c_int,
3392 c"test.xml".as_ptr() as *const c_char,
3393 ptr::null(),
3394 0,
3395 )
3396 };
3397 assert!(!doc.is_null());
3398
3399 let mut ctxt = RelaxNgValidCtxt::new();
3400 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3401 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3402
3403 assert!(!valid, "notAllowed should cause validation failure");
3404 }
3405
3406 #[test]
3415 fn test_validate_interleave() {
3416 let schema_xml = r#"<?xml version="1.0"?>
3417<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3418 <start>
3419 <element name="root">
3420 <interleave>
3421 <element name="a">
3422 <text/>
3423 </element>
3424 <element name="b">
3425 <text/>
3426 </element>
3427 </interleave>
3428 </element>
3429 </start>
3430</grammar>"#;
3431
3432 let doc_xml = r#"<?xml version="1.0"?>
3433<root><a>A</a><b>B</b></root>"#;
3434
3435 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3436
3437 let doc = unsafe {
3438 crate::abi::exports_xml2::xmlReadMemory(
3439 doc_xml.as_ptr() as *const c_char,
3440 doc_xml.len() as c_int,
3441 c"test.xml".as_ptr() as *const c_char,
3442 ptr::null(),
3443 0,
3444 )
3445 };
3446 assert!(!doc.is_null());
3447
3448 let mut ctxt = RelaxNgValidCtxt::new();
3449 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3450 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3451
3452 assert!(valid, "Interleave validation failed: {:?}", ctxt.errors);
3453 }
3454
3455 #[test]
3464 fn test_validate_empty_element() {
3465 let schema_xml = r#"<?xml version="1.0"?>
3466<element name="br" xmlns="http://relaxng.org/ns/structure/1.0">
3467 <empty/>
3468</element>"#;
3469
3470 let doc_xml = r#"<?xml version="1.0"?>
3471<br/>"#;
3472
3473 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3474
3475 let doc = unsafe {
3476 crate::abi::exports_xml2::xmlReadMemory(
3477 doc_xml.as_ptr() as *const c_char,
3478 doc_xml.len() as c_int,
3479 c"test.xml".as_ptr() as *const c_char,
3480 ptr::null(),
3481 0,
3482 )
3483 };
3484 assert!(!doc.is_null());
3485
3486 let mut ctxt = RelaxNgValidCtxt::new();
3487 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3488 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3489
3490 assert!(valid, "Empty element validation failed: {:?}", ctxt.errors);
3491 }
3492
3493 #[test]
3496 fn test_validate_datatype_string() {
3497 assert!(rng_validate_datatype_value(Some("string"), "hello"));
3498 assert!(rng_validate_datatype_value(Some("string"), ""));
3499 }
3500
3501 #[test]
3502 fn test_validate_datatype_boolean() {
3503 assert!(rng_validate_datatype_value(Some("boolean"), "true"));
3504 assert!(rng_validate_datatype_value(Some("boolean"), "false"));
3505 assert!(rng_validate_datatype_value(Some("boolean"), "1"));
3506 assert!(rng_validate_datatype_value(Some("boolean"), "0"));
3507 assert!(!rng_validate_datatype_value(Some("boolean"), "yes"));
3508 assert!(!rng_validate_datatype_value(Some("boolean"), "no"));
3509 }
3510
3511 #[test]
3512 fn test_validate_datatype_integer() {
3513 assert!(rng_validate_datatype_value(Some("integer"), "42"));
3514 assert!(rng_validate_datatype_value(Some("integer"), "-42"));
3515 assert!(rng_validate_datatype_value(Some("integer"), "+42"));
3516 assert!(!rng_validate_datatype_value(Some("integer"), "12.5"));
3517 assert!(!rng_validate_datatype_value(Some("integer"), "abc"));
3518 assert!(!rng_validate_datatype_value(Some("integer"), ""));
3519 }
3520
3521 #[test]
3522 fn test_validate_datatype_decimal() {
3523 assert!(rng_validate_datatype_value(Some("decimal"), "42"));
3524 assert!(rng_validate_datatype_value(Some("decimal"), "12.5"));
3525 assert!(rng_validate_datatype_value(Some("decimal"), "-3.14"));
3526 assert!(!rng_validate_datatype_value(Some("decimal"), ""));
3527 }
3528
3529 #[test]
3530 fn test_validate_datatype_float() {
3531 assert!(rng_validate_datatype_value(Some("float"), "3.14"));
3532 assert!(rng_validate_datatype_value(Some("float"), "INF"));
3533 assert!(rng_validate_datatype_value(Some("float"), "-INF"));
3534 assert!(rng_validate_datatype_value(Some("float"), "NaN"));
3535 assert!(!rng_validate_datatype_value(Some("float"), ""));
3536 }
3537
3538 #[test]
3539 fn test_validate_datatype_ncname() {
3540 assert!(rng_validate_datatype_value(Some("NCName"), "myElement"));
3541 assert!(rng_validate_datatype_value(Some("NCName"), "_foo"));
3542 assert!(!rng_validate_datatype_value(Some("NCName"), "123abc"));
3543 assert!(!rng_validate_datatype_value(Some("NCName"), ""));
3544 }
3545
3546 #[test]
3547 fn test_validate_datatype_any_uri() {
3548 assert!(rng_validate_datatype_value(
3549 Some("anyURI"),
3550 "http://example.com"
3551 ));
3552 assert!(rng_validate_datatype_value(Some("anyURI"), "urn:isbn:1234"));
3553 assert!(!rng_validate_datatype_value(Some("anyURI"), ""));
3554 assert!(!rng_validate_datatype_value(Some("anyURI"), "has space"));
3555 }
3556
3557 #[test]
3558 fn test_validate_datatype_qname() {
3559 assert!(rng_validate_datatype_value(Some("QName"), "ns:local"));
3560 assert!(rng_validate_datatype_value(Some("QName"), "local"));
3561 assert!(!rng_validate_datatype_value(Some("QName"), ""));
3562 }
3563
3564 #[test]
3576 fn test_c_abi_new_parse_free() {
3577 let schema_xml = r#"<?xml version="1.0"?>
3578<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3579 <text/>
3580</element>"#;
3581
3582 let ctxt = unsafe {
3583 xmlRelaxNGNewMemParserCtxt(
3584 schema_xml.as_ptr() as *const c_char,
3585 schema_xml.len() as c_int,
3586 )
3587 };
3588 assert!(!ctxt.is_null(), "Parser context should not be null");
3589
3590 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3591 assert!(!schema.is_null(), "Schema should not be null");
3592
3593 unsafe { xmlRelaxNGFree(schema) };
3595 }
3596
3597 #[test]
3607 fn test_c_abi_validate_doc() {
3608 let schema_xml = r#"<?xml version="1.0"?>
3609<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3610 <text/>
3611</element>"#;
3612
3613 let doc_xml = r#"<?xml version="1.0"?>
3614<root>Hello</root>"#;
3615
3616 let ctxt = unsafe {
3617 xmlRelaxNGNewMemParserCtxt(
3618 schema_xml.as_ptr() as *const c_char,
3619 schema_xml.len() as c_int,
3620 )
3621 };
3622 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3623 assert!(!schema.is_null());
3624
3625 let valid_ctxt = unsafe { xmlRelaxNGNewValidCtxt(schema) };
3626 assert!(!valid_ctxt.is_null());
3627
3628 let doc = unsafe {
3629 crate::abi::exports_xml2::xmlReadMemory(
3630 doc_xml.as_ptr() as *const c_char,
3631 doc_xml.len() as c_int,
3632 c"test.xml".as_ptr() as *const c_char,
3633 ptr::null(),
3634 0,
3635 )
3636 };
3637 assert!(!doc.is_null());
3638
3639 let result = unsafe { xmlRelaxNGValidateDoc(valid_ctxt, doc) };
3640 assert_eq!(result, 0, "Validation should succeed");
3641
3642 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3643 unsafe { xmlRelaxNGFreeValidCtxt(valid_ctxt) };
3644 unsafe { xmlRelaxNGFree(schema) };
3645 }
3646
3647 #[test]
3661 fn test_c_abi_validate_full_element() {
3662 let schema_xml = r#"<?xml version="1.0"?>
3663<element name="item" xmlns="http://relaxng.org/ns/structure/1.0">
3664 <text/>
3665</element>"#;
3666
3667 let doc_xml = r#"<?xml version="1.0"?>
3668<root><item>Content</item></root>"#;
3669
3670 let ctxt = unsafe {
3671 xmlRelaxNGNewMemParserCtxt(
3672 schema_xml.as_ptr() as *const c_char,
3673 schema_xml.len() as c_int,
3674 )
3675 };
3676 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3677 assert!(!schema.is_null());
3678
3679 let valid_ctxt = unsafe { xmlRelaxNGNewValidCtxt(schema) };
3680 assert!(!valid_ctxt.is_null());
3681
3682 let doc = unsafe {
3683 crate::abi::exports_xml2::xmlReadMemory(
3684 doc_xml.as_ptr() as *const c_char,
3685 doc_xml.len() as c_int,
3686 c"test.xml".as_ptr() as *const c_char,
3687 ptr::null(),
3688 0,
3689 )
3690 };
3691 assert!(!doc.is_null());
3692
3693 let item = unsafe {
3695 let mut node = (*doc).children;
3697 while !node.is_null() {
3698 if (*node).type_ == XML_ELEMENT_NODE as c_int {
3699 break;
3700 }
3701 node = (*node).next;
3702 }
3703 if !node.is_null() {
3704 node = (*node).children;
3706 while !node.is_null() {
3707 if (*node).type_ == XML_ELEMENT_NODE as c_int {
3708 break;
3709 }
3710 node = (*node).next;
3711 }
3712 }
3713 node
3714 };
3715 assert!(!item.is_null(), "Should find <item> element");
3716
3717 let result = unsafe { xmlRelaxNGValidateFullElement(valid_ctxt, doc, item) };
3718 assert_eq!(result, 0, "Element validation should succeed");
3719
3720 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3721 unsafe { xmlRelaxNGFreeValidCtxt(valid_ctxt) };
3722 unsafe { xmlRelaxNGFree(schema) };
3723 }
3724
3725 #[test]
3733 fn test_c_abi_null_handling() {
3734 assert_eq!(
3736 unsafe { xmlRelaxNGValidateDoc(ptr::null_mut(), ptr::null_mut()) },
3737 -1
3738 );
3739 assert_eq!(
3740 unsafe {
3741 xmlRelaxNGValidateFullElement(ptr::null_mut(), ptr::null_mut(), ptr::null_mut())
3742 },
3743 -1
3744 );
3745 assert!(unsafe { xmlRelaxNGNewMemParserCtxt(ptr::null(), 0).is_null() });
3746
3747 unsafe { xmlRelaxNGFree(ptr::null_mut()) };
3749 unsafe { xmlRelaxNGFreeParserCtxt(ptr::null_mut()) };
3750 unsafe { xmlRelaxNGFreeValidCtxt(ptr::null_mut()) };
3751 }
3752
3753 #[test]
3756 fn test_parse_with_div() {
3757 let schema_xml = r#"<?xml version="1.0"?>
3758<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3759 <div>
3760 <define name="shared">
3761 <text/>
3762 </define>
3763 </div>
3764 <start>
3765 <element name="root">
3766 <ref name="shared"/>
3767 </element>
3768 </start>
3769</grammar>"#;
3770
3771 let result = rng_parse(schema_xml);
3772 assert!(
3773 result.is_ok(),
3774 "Failed to parse with div: {:?}",
3775 result.err()
3776 );
3777 let schema = result.unwrap();
3778 assert_eq!(schema.grammar.defines.len(), 1);
3779 assert_eq!(schema.grammar.defines[0].name, "shared");
3780 }
3781
3782 #[test]
3791 fn test_validate_list_pattern() {
3792 let schema_xml = r#"<?xml version="1.0"?>
3793<element name="tokens" xmlns="http://relaxng.org/ns/structure/1.0">
3794 <list>
3795 <data type="token"/>
3796 </list>
3797</element>"#;
3798
3799 let doc_xml = r#"<?xml version="1.0"?>
3800<tokens>abc def ghi</tokens>"#;
3801
3802 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3803
3804 let doc = unsafe {
3805 crate::abi::exports_xml2::xmlReadMemory(
3806 doc_xml.as_ptr() as *const c_char,
3807 doc_xml.len() as c_int,
3808 c"test.xml".as_ptr() as *const c_char,
3809 ptr::null(),
3810 0,
3811 )
3812 };
3813 assert!(!doc.is_null());
3814
3815 let mut ctxt = RelaxNgValidCtxt::new();
3816 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3817 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3818
3819 assert!(valid, "List pattern validation failed: {:?}", ctxt.errors);
3820 }
3821
3822 #[test]
3831 fn test_validate_group_pattern() {
3832 let schema_xml = r#"<?xml version="1.0"?>
3833<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3834 <start>
3835 <element name="root">
3836 <group>
3837 <element name="a">
3838 <text/>
3839 </element>
3840 <element name="b">
3841 <text/>
3842 </element>
3843 </group>
3844 </element>
3845 </start>
3846</grammar>"#;
3847
3848 let doc_xml = r#"<?xml version="1.0"?>
3849<root><a>First</a><b>Second</b></root>"#;
3850
3851 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3852
3853 let doc = unsafe {
3854 crate::abi::exports_xml2::xmlReadMemory(
3855 doc_xml.as_ptr() as *const c_char,
3856 doc_xml.len() as c_int,
3857 c"test.xml".as_ptr() as *const c_char,
3858 ptr::null(),
3859 0,
3860 )
3861 };
3862 assert!(!doc.is_null());
3863
3864 let mut ctxt = RelaxNgValidCtxt::new();
3865 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3866 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3867
3868 assert!(valid, "Group validation failed: {:?}", ctxt.errors);
3869 }
3870
3871 #[test]
3880 fn test_validate_undefined_ref() {
3881 let schema_xml = r#"<?xml version="1.0"?>
3882<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3883 <start>
3884 <element name="root">
3885 <ref name="undefined"/>
3886 </element>
3887 </start>
3888</grammar>"#;
3889
3890 let doc_xml = r#"<?xml version="1.0"?>
3891<root>Content</root>"#;
3892
3893 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3894
3895 let doc = unsafe {
3896 crate::abi::exports_xml2::xmlReadMemory(
3897 doc_xml.as_ptr() as *const c_char,
3898 doc_xml.len() as c_int,
3899 c"test.xml".as_ptr() as *const c_char,
3900 ptr::null(),
3901 0,
3902 )
3903 };
3904 assert!(!doc.is_null());
3905
3906 let mut ctxt = RelaxNgValidCtxt::new();
3907 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3908 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3909
3910 assert!(!valid, "Undefined ref should cause failure");
3911 }
3912
3913 #[test]
3920 fn test_validate_null_doc() {
3921 let schema = RelaxNgSchema::new();
3922 let mut ctxt = RelaxNgValidCtxt::new();
3923 let valid = unsafe { rng_validate_doc(&schema, ptr::null_mut(), &mut ctxt) };
3924 assert!(!valid);
3925 }
3926
3927 #[test]
3928 fn test_parse_external_ref_schema() {
3929 let schema_xml = r#"<?xml version="1.0"?>
3930<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3931 <externalRef href="external.rng"/>
3932</element>"#;
3933
3934 let result = rng_parse(schema_xml);
3935 assert!(
3936 result.is_ok(),
3937 "Failed to parse externalRef: {:?}",
3938 result.err()
3939 );
3940 let schema = result.unwrap();
3941 if let Some(ref start) = schema.grammar.start {
3942 assert_eq!(start.pattern_type, RelaxNgPatternType::Element);
3943 assert_eq!(start.name.as_deref(), Some("root"));
3944 }
3945 }
3946
3947 #[test]
3948 fn test_validate_boolean_datatype() {
3949 assert!(rng_validate_datatype_value(Some("boolean"), "true"));
3950 assert!(rng_validate_datatype_value(Some("boolean"), "false"));
3951 assert!(!rng_validate_datatype_value(Some("boolean"), "maybe"));
3952 }
3953
3954 #[test]
3955 fn test_validate_unknown_datatype() {
3956 assert!(rng_validate_datatype_value(Some("custom-type"), "anything"));
3958 }
3959
3960 #[test]
3961 fn test_validate_no_datatype() {
3962 assert!(rng_validate_datatype_value(None, "anything"));
3964 }
3965
3966 #[test]
3967 fn test_parse_schema_with_ns_prefix() {
3968 let schema_xml = r#"<?xml version="1.0"?>
3969<rng:element name="root" xmlns:rng="http://relaxng.org/ns/structure/1.0">
3970 <rng:text/>
3971</rng:element>"#;
3972
3973 let result = rng_parse(schema_xml);
3974 assert!(result.is_ok(), "Failed with ns prefix: {:?}", result.err());
3975 }
3976
3977 #[test]
3978 fn test_validation_context_path() {
3979 let mut ctxt = RelaxNgValidCtxt::new();
3980 assert_eq!(ctxt.current_path(), "/");
3981
3982 ctxt.path.push("root".to_string());
3983 assert_eq!(ctxt.current_path(), "/root");
3984
3985 ctxt.path.push("child".to_string());
3986 assert_eq!(ctxt.current_path(), "/root/child");
3987
3988 ctxt.path.pop();
3989 assert_eq!(ctxt.current_path(), "/root");
3990 }
3991
3992 #[test]
4001 fn test_validate_sequence_wrong_order() {
4002 let schema_xml = r#"<?xml version="1.0"?>
4003<grammar xmlns="http://relaxng.org/ns/structure/1.0">
4004 <start>
4005 <element name="root">
4006 <sequence>
4007 <element name="first">
4008 <text/>
4009 </element>
4010 <element name="second">
4011 <text/>
4012 </element>
4013 </sequence>
4014 </element>
4015 </start>
4016</grammar>"#;
4017
4018 let doc_xml = r#"<?xml version="1.0"?>
4019<root><second>Wrong</second><first>Order</first></root>"#;
4020
4021 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
4022
4023 let doc = unsafe {
4024 crate::abi::exports_xml2::xmlReadMemory(
4025 doc_xml.as_ptr() as *const c_char,
4026 doc_xml.len() as c_int,
4027 c"test.xml".as_ptr() as *const c_char,
4028 ptr::null(),
4029 0,
4030 )
4031 };
4032 assert!(!doc.is_null());
4033
4034 let mut ctxt = RelaxNgValidCtxt::new();
4035 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
4036 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
4037
4038 assert!(!valid, "Wrong sequence order should fail");
4041 }
4042}