1#![allow(
26 missing_docs,
27 non_snake_case,
28 non_camel_case_types,
29 non_upper_case_globals
30)]
31
32use core::ffi::c_void;
33use core::ptr;
34use std::os::raw::{c_char, c_int};
35
36use crate::abi::allocator;
37use crate::abi::structs::*;
38use crate::abi::types::xmlElementType::*;
39use crate::abi::types::*;
40
41#[derive(Debug, Clone, PartialEq)]
52pub enum RelaxNgNameClass {
53 Name(String),
55 AnyName,
57 NsName(String),
59 Choice(Vec<RelaxNgNameClass>),
61 Except(Box<RelaxNgNameClass>, Box<RelaxNgNameClass>),
63}
64
65impl RelaxNgNameClass {
66 pub fn matches(&self, name: &str, ns_uri: Option<&str>) -> bool {
68 match self {
69 RelaxNgNameClass::Name(n) => name == n.as_str(),
70 RelaxNgNameClass::AnyName => true,
71 RelaxNgNameClass::NsName(ns) => {
72 if let Some(uri) = ns_uri {
73 uri == ns.as_str()
74 } else {
75 false
76 }
77 }
78 RelaxNgNameClass::Choice(choices) => choices.iter().any(|c| c.matches(name, ns_uri)),
79 RelaxNgNameClass::Except(positive, negative) => {
80 positive.matches(name, ns_uri) && !negative.matches(name, ns_uri)
81 }
82 }
83 }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum RelaxNgPatternType {
97 Element,
98 Attribute,
99 Text,
100 Choice,
101 Sequence,
102 Interleave,
103 ZeroOrMore,
104 OneOrMore,
105 Optional,
106 List,
107 Group,
108 Data,
109 Value,
110 Ref,
111 Define,
112 Grammar,
113 NotAllowed,
114 Empty,
115 ExternalRef,
116 Include,
117 Start,
118}
119
120#[derive(Debug, Clone)]
124pub struct RelaxNgPattern {
125 pub pattern_type: RelaxNgPatternType,
127 pub name_class: Option<RelaxNgNameClass>,
129 pub children: Vec<RelaxNgPattern>,
131 pub name: Option<String>,
133 pub ns: Option<String>,
135 pub datatype: Option<String>,
137 pub value: Option<String>,
139 pub datatype_library: Option<String>,
141}
142
143impl RelaxNgPattern {
144 pub fn new(pattern_type: RelaxNgPatternType) -> Self {
146 Self {
147 pattern_type,
148 name_class: None,
149 children: Vec::new(),
150 name: None,
151 ns: None,
152 datatype: None,
153 value: None,
154 datatype_library: None,
155 }
156 }
157
158 pub fn element(name: &str) -> Self {
160 let mut p = Self::new(RelaxNgPatternType::Element);
161 p.name_class = Some(RelaxNgNameClass::Name(name.to_string()));
162 p
163 }
164
165 pub fn attribute(name: &str) -> Self {
167 let mut p = Self::new(RelaxNgPatternType::Attribute);
168 p.name_class = Some(RelaxNgNameClass::Name(name.to_string()));
169 p
170 }
171
172 pub fn text() -> Self {
174 Self::new(RelaxNgPatternType::Text)
175 }
176
177 pub fn empty() -> Self {
179 Self::new(RelaxNgPatternType::Empty)
180 }
181
182 pub fn not_allowed() -> Self {
184 Self::new(RelaxNgPatternType::NotAllowed)
185 }
186}
187
188#[derive(Debug, Clone)]
194pub struct RelaxNgDefine {
195 pub name: String,
197 pub pattern: RelaxNgPattern,
199}
200
201#[derive(Debug, Clone)]
209pub struct RelaxNgGrammar {
210 pub defines: Vec<RelaxNgDefine>,
212 pub start: Option<RelaxNgPattern>,
214 pub includes: Vec<RelaxNgGrammar>,
216}
217
218impl RelaxNgGrammar {
219 pub fn new() -> Self {
220 Self {
221 defines: Vec::new(),
222 start: None,
223 includes: Vec::new(),
224 }
225 }
226
227 pub fn lookup(&self, name: &str) -> Option<&RelaxNgPattern> {
229 for def in &self.defines {
230 if def.name == name {
231 return Some(&def.pattern);
232 }
233 }
234 for inc in &self.includes {
236 if let Some(p) = inc.lookup(name) {
237 return Some(p);
238 }
239 }
240 None
241 }
242}
243
244impl Default for RelaxNgGrammar {
245 fn default() -> Self {
246 Self::new()
247 }
248}
249
250#[derive(Debug, Clone)]
258pub struct RelaxNgSchema {
259 pub grammar: RelaxNgGrammar,
261 pub errors: Vec<String>,
263}
264
265impl RelaxNgSchema {
266 pub fn new() -> Self {
267 Self {
268 grammar: RelaxNgGrammar::new(),
269 errors: Vec::new(),
270 }
271 }
272}
273
274impl Default for RelaxNgSchema {
275 fn default() -> Self {
276 Self::new()
277 }
278}
279
280#[derive(Debug)]
290pub struct RelaxNgValidCtxt {
291 pub schema: Option<RelaxNgSchema>,
293 pub errors: Vec<String>,
295 pub nb_errors: i32,
297 pub path: Vec<String>,
299 pub depth_max: i32,
301 pub depth: i32,
303}
304
305impl RelaxNgValidCtxt {
306 pub fn new() -> Self {
307 Self {
308 schema: None,
309 errors: Vec::new(),
310 nb_errors: 0,
311 path: Vec::new(),
312 depth_max: 256,
313 depth: 0,
314 }
315 }
316
317 pub fn record_error(&mut self, msg: String) {
319 self.errors.push(msg);
320 self.nb_errors += 1;
321 }
322
323 pub fn current_path(&self) -> String {
325 if self.path.is_empty() {
326 "/".to_string()
327 } else {
328 format!("/{}", self.path.join("/"))
329 }
330 }
331}
332
333impl Default for RelaxNgValidCtxt {
334 fn default() -> Self {
335 Self::new()
336 }
337}
338
339unsafe fn get_local_name(node: *mut _xmlNode) -> String {
349 if node.is_null() {
350 return String::new();
351 }
352 unsafe {
353 let name = (*node).name;
354 if name.is_null() {
355 return String::new();
356 }
357 let mut len = 0;
358 while *name.add(len) != 0 {
359 len += 1;
360 }
361 let slice = std::slice::from_raw_parts(name, len);
362 if let Ok(s) = std::str::from_utf8(slice) {
363 if let Some(pos) = s.find(':') {
364 s[pos + 1..].to_string()
365 } else {
366 s.to_string()
367 }
368 } else {
369 String::new()
370 }
371 }
372}
373
374unsafe fn get_node_text(node: *mut _xmlNode) -> String {
380 if node.is_null() {
381 return String::new();
382 }
383 let mut result = String::new();
384 unsafe {
385 let mut child = (*node).children;
386 while !child.is_null() {
387 if (*child).type_ == XML_TEXT_NODE as c_int
388 || (*child).type_ == XML_CDATA_SECTION_NODE as c_int
389 {
390 if !(*child).content.is_null() {
391 let content = (*child).content;
392 let mut len = 0;
393 while *content.add(len) != 0 {
394 len += 1;
395 }
396 let slice = std::slice::from_raw_parts(content, len);
397 result.push_str(&String::from_utf8_lossy(slice));
398 }
399 }
400 child = (*child).next;
401 }
402 }
403 result
404}
405
406unsafe fn get_node_qname(node: *mut _xmlNode) -> String {
412 if node.is_null() {
413 return String::new();
414 }
415 unsafe {
416 let ns = (*node).ns;
417 let prefix = if !ns.is_null() && !(*ns).prefix.is_null() {
418 let mut len = 0;
419 while *(*ns).prefix.add(len) != 0 {
420 len += 1;
421 }
422 let slice = std::slice::from_raw_parts((*ns).prefix, len);
423 if let Ok(s) = std::str::from_utf8(slice) {
424 format!("{}:", s)
425 } else {
426 String::new()
427 }
428 } else {
429 String::new()
430 };
431
432 let name = (*node).name;
433 if name.is_null() {
434 return String::new();
435 }
436 let mut len = 0;
437 while *name.add(len) != 0 {
438 len += 1;
439 }
440 let slice = std::slice::from_raw_parts(name, len);
441 if let Ok(s) = std::str::from_utf8(slice) {
442 format!("{}{}", prefix, s)
443 } else {
444 String::new()
445 }
446 }
447}
448
449unsafe fn get_node_ns_uri(node: *mut _xmlNode) -> Option<String> {
455 if node.is_null() {
456 return None;
457 }
458 unsafe {
459 let ns = (*node).ns;
460 if ns.is_null() || (*ns).href.is_null() {
461 return None;
462 }
463 let href = (*ns).href;
464 let mut len = 0;
465 while *href.add(len) != 0 {
466 len += 1;
467 }
468 let slice = std::slice::from_raw_parts(href, len);
469 if let Ok(s) = std::str::from_utf8(slice) {
470 Some(s.to_string())
471 } else {
472 None
473 }
474 }
475}
476
477unsafe fn get_attr(node: *mut _xmlNode, name: &str) -> Option<String> {
483 if node.is_null() {
484 return None;
485 }
486 unsafe {
487 let mut prop = (*node).properties;
488 while !prop.is_null() {
489 let prop_name = (*prop).name;
490 if !prop_name.is_null() {
491 let mut len = 0;
492 while *prop_name.add(len) != 0 {
493 len += 1;
494 }
495 let slice = std::slice::from_raw_parts(prop_name, len);
496 if let Ok(s) = std::str::from_utf8(slice) {
497 if s == name {
498 return Some(get_node_text(prop as *mut _xmlNode));
499 }
500 }
501 }
502 prop = (*prop).next;
503 }
504 }
505 None
506}
507
508unsafe fn node_is(node: *mut _xmlNode, local_name: &str) -> bool {
514 if node.is_null() {
515 return false;
516 }
517 unsafe {
518 let name = (*node).name;
519 if name.is_null() {
520 return false;
521 }
522 let mut len = 0;
523 while *name.add(len) != 0 {
524 len += 1;
525 }
526 let slice = std::slice::from_raw_parts(name, len);
527 if let Ok(s) = std::str::from_utf8(slice) {
528 let local = if let Some(pos) = s.find(':') {
529 &s[pos + 1..]
530 } else {
531 s
532 };
533 return local == local_name;
534 }
535 }
536 false
537}
538
539pub fn rng_parse(xml_doc: &str) -> Result<RelaxNgSchema, String> {
552 let doc_ptr = unsafe {
553 crate::abi::exports_xml2::xmlReadMemory(
554 xml_doc.as_ptr() as *const c_char,
555 xml_doc.len() as c_int,
556 b"schema.rng\0".as_ptr() as *const c_char,
557 ptr::null(),
558 0,
559 )
560 };
561
562 if doc_ptr.is_null() {
563 return Err("Failed to parse RELAX NG schema XML document".to_string());
564 }
565
566 let result = unsafe { rng_parse_doc(doc_ptr) };
567 unsafe {
568 crate::abi::exports_xml2::xmlFreeDoc(doc_ptr);
569 }
570 result
571}
572
573unsafe fn rng_parse_doc(doc: *mut _xmlDoc) -> Result<RelaxNgSchema, String> {
579 unsafe {
580 let root = (*doc).children;
581 if root.is_null() {
582 return Err("RELAX NG document has no root element".to_string());
583 }
584
585 let mut root_elem = root;
587 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
588 root_elem = (*root_elem).next;
589 }
590
591 if root_elem.is_null() {
592 return Err("RELAX NG document has no root element".to_string());
593 }
594
595 let local_name = get_local_name(root_elem);
596 let mut schema = RelaxNgSchema::new();
597
598 match local_name.as_str() {
599 "grammar" => {
600 schema.grammar = rng_parse_grammar_node(root_elem, &mut schema);
602 Ok(schema)
603 }
604 "element" | "attribute" | "text" | "choice" | "sequence" | "interleave"
605 | "zeroOrMore" | "oneOrMore" | "optional" | "list" | "group" | "data" | "value"
606 | "ref" | "notAllowed" | "empty" | "externalRef" | "define" | "start" | "include" => {
607 let pattern = rng_parse_pattern(root_elem, &mut schema);
609 schema.grammar.start = Some(pattern);
610 Ok(schema)
611 }
612 _ => Err(format!("Unknown RELAX NG root element: '{}'", local_name)),
613 }
614 }
615}
616
617unsafe fn rng_parse_grammar_node(
623 node: *mut _xmlNode,
624 schema: &mut RelaxNgSchema,
625) -> RelaxNgGrammar {
626 unsafe {
627 let mut grammar = RelaxNgGrammar::new();
628
629 let mut child = (*node).children;
630 while !child.is_null() {
631 if (*child).type_ == XML_ELEMENT_NODE as c_int {
632 let local = get_local_name(child);
633 match local.as_str() {
634 "define" => {
635 let def = rng_parse_define(child, schema);
636 grammar.defines.push(def);
637 }
638 "start" => {
639 grammar.start = Some(rng_parse_pattern(child, schema));
640 }
641 "include" => {
642 if let Some(inc) = rng_parse_include(child, schema) {
643 grammar.includes.push(inc);
644 }
645 }
646 "div" => {
647 let sub_grammar = rng_parse_grammar_node(child, schema);
649 grammar.defines.extend(sub_grammar.defines);
650 if sub_grammar.start.is_some() {
651 grammar.start = sub_grammar.start;
652 }
653 grammar.includes.extend(sub_grammar.includes);
654 }
655 _ => {
656 schema
658 .errors
659 .push(format!("Unexpected element '<{}>' in grammar", local));
660 }
661 }
662 }
663 child = (*child).next;
664 }
665
666 grammar
667 }
668}
669
670unsafe fn rng_parse_define(node: *mut _xmlNode, schema: &mut RelaxNgSchema) -> RelaxNgDefine {
676 unsafe {
677 let name = get_attr(node, "name").unwrap_or_default();
678 let pattern = rng_parse_pattern(node, schema);
679 RelaxNgDefine { name, pattern }
680 }
681}
682
683unsafe fn rng_parse_include(
689 node: *mut _xmlNode,
690 _schema: &mut RelaxNgSchema,
691) -> Option<RelaxNgGrammar> {
692 unsafe {
693 let href = get_attr(node, "href");
694 if let Some(url) = href {
695 let url_c = std::ffi::CString::new(url.clone()).ok()?;
698 let doc = crate::abi::exports_xml2::xmlParseFile(url_c.as_ptr());
699 if doc.is_null() {
700 return None;
701 }
702 let mut inc_schema = RelaxNgSchema::new();
703 let grammar = rng_parse_grammar_node(
704 {
705 let mut root = (*doc).children;
706 while !root.is_null() && (*root).type_ != XML_ELEMENT_NODE as c_int {
707 root = (*root).next;
708 }
709 root
710 },
711 &mut inc_schema,
712 );
713 crate::abi::exports_xml2::xmlFreeDoc(doc);
714 Some(grammar)
715 } else {
716 let mut inc_schema = RelaxNgSchema::new();
718 let grammar = rng_parse_grammar_node(node, &mut inc_schema);
719 Some(grammar)
720 }
721 }
722}
723
724unsafe fn rng_parse_pattern(node: *mut _xmlNode, schema: &mut RelaxNgSchema) -> RelaxNgPattern {
730 unsafe {
731 let local = get_local_name(node);
732
733 match local.as_str() {
734 "element" => rng_parse_element_pattern(node, schema),
735 "attribute" => rng_parse_attribute_pattern(node, schema),
736 "text" => RelaxNgPattern::text(),
737 "empty" => RelaxNgPattern::empty(),
738 "notAllowed" => RelaxNgPattern::not_allowed(),
739 "choice" => rng_parse_composite_pattern(node, RelaxNgPatternType::Choice, schema),
740 "sequence" => rng_parse_composite_pattern(node, RelaxNgPatternType::Sequence, schema),
741 "interleave" => {
742 rng_parse_composite_pattern(node, RelaxNgPatternType::Interleave, schema)
743 }
744 "zeroOrMore" => rng_parse_unary_pattern(node, RelaxNgPatternType::ZeroOrMore, schema),
745 "oneOrMore" => rng_parse_unary_pattern(node, RelaxNgPatternType::OneOrMore, schema),
746 "optional" => rng_parse_unary_pattern(node, RelaxNgPatternType::Optional, schema),
747 "list" => rng_parse_unary_pattern(node, RelaxNgPatternType::List, schema),
748 "group" => rng_parse_composite_pattern(node, RelaxNgPatternType::Group, schema),
749 "data" => rng_parse_data_pattern(node, schema),
750 "value" => rng_parse_value_pattern(node, schema),
751 "ref" => rng_parse_ref_pattern(node),
752 "externalRef" => rng_parse_external_ref(node, schema),
753 "define" | "start" | "grammar" | "include" | "div" => {
754 let mut child = (*node).children;
756 let mut result = RelaxNgPattern::empty();
757 while !child.is_null() {
758 if (*child).type_ == XML_ELEMENT_NODE as c_int {
759 result = rng_parse_pattern(child, schema);
760 break;
761 }
762 child = (*child).next;
763 }
764 result
765 }
766 _ => {
767 schema
769 .errors
770 .push(format!("Unknown pattern element '<{}>'", local));
771 RelaxNgPattern::empty()
772 }
773 }
774 }
775}
776
777unsafe fn rng_parse_element_pattern(
783 node: *mut _xmlNode,
784 schema: &mut RelaxNgSchema,
785) -> RelaxNgPattern {
786 unsafe {
787 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Element);
788
789 let name_attr = get_attr(node, "name");
791 pattern.name = name_attr.clone();
792
793 if let Some(ref n) = name_attr {
794 pattern.name_class = Some(RelaxNgNameClass::Name(n.clone()));
795 }
796
797 let mut child = (*node).children;
799 let mut content_found = false;
800
801 while !child.is_null() {
802 if (*child).type_ == XML_ELEMENT_NODE as c_int {
803 let child_local = get_local_name(child);
804
805 match child_local.as_str() {
806 "name" => {
807 let text = get_node_text(child);
808 if !text.is_empty() {
809 pattern.name_class =
810 Some(RelaxNgNameClass::Name(text.trim().to_string()));
811 }
812 }
813 "anyName" => {
814 pattern.name_class = Some(rng_parse_any_name(child));
815 }
816 "nsName" => {
817 pattern.name_class = Some(rng_parse_ns_name(child));
818 }
819 "choice" if pattern.name_class.is_none() => {
820 pattern.name_class = Some(rng_parse_name_class_choice(child));
822 }
823 _ => {
824 if !content_found {
826 pattern.children.push(rng_parse_pattern(child, schema));
827 content_found = true;
828 }
829 }
830 }
831 }
832 child = (*child).next;
833 }
834
835 pattern
836 }
837}
838
839unsafe fn rng_parse_attribute_pattern(
845 node: *mut _xmlNode,
846 schema: &mut RelaxNgSchema,
847) -> RelaxNgPattern {
848 unsafe {
849 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Attribute);
850
851 let name_attr = get_attr(node, "name");
853 pattern.name = name_attr.clone();
854
855 if let Some(ref n) = name_attr {
856 pattern.name_class = Some(RelaxNgNameClass::Name(n.clone()));
857 }
858
859 let mut child = (*node).children;
861 while !child.is_null() {
862 if (*child).type_ == XML_ELEMENT_NODE as c_int {
863 let child_local = get_local_name(child);
864
865 match child_local.as_str() {
866 "name" => {
867 let text = get_node_text(child);
868 if !text.is_empty() {
869 pattern.name_class =
870 Some(RelaxNgNameClass::Name(text.trim().to_string()));
871 }
872 }
873 "anyName" => {
874 pattern.name_class = Some(rng_parse_any_name(child));
875 }
876 "nsName" => {
877 pattern.name_class = Some(rng_parse_ns_name(child));
878 }
879 "choice" if pattern.name_class.is_none() => {
880 pattern.name_class = Some(rng_parse_name_class_choice(child));
881 }
882 _ => {
883 pattern.children.push(rng_parse_pattern(child, schema));
885 }
886 }
887 }
888 child = (*child).next;
889 }
890
891 pattern
892 }
893}
894
895unsafe fn rng_parse_any_name(node: *mut _xmlNode) -> RelaxNgNameClass {
901 unsafe {
902 let mut child = (*node).children;
904 while !child.is_null() {
905 if (*child).type_ == XML_ELEMENT_NODE as c_int && get_local_name(child) == "except" {
906 let except_nc = rng_parse_name_class_content(child);
907 return RelaxNgNameClass::Except(
908 Box::new(RelaxNgNameClass::AnyName),
909 Box::new(except_nc),
910 );
911 }
912 child = (*child).next;
913 }
914 RelaxNgNameClass::AnyName
915 }
916}
917
918unsafe fn rng_parse_ns_name(node: *mut _xmlNode) -> RelaxNgNameClass {
924 unsafe {
925 let ns = get_attr(node, "ns").unwrap_or_default();
926
927 let mut child = (*node).children;
929 while !child.is_null() {
930 if (*child).type_ == XML_ELEMENT_NODE as c_int && get_local_name(child) == "except" {
931 let except_nc = rng_parse_name_class_content(child);
932 return RelaxNgNameClass::Except(
933 Box::new(RelaxNgNameClass::NsName(ns)),
934 Box::new(except_nc),
935 );
936 }
937 child = (*child).next;
938 }
939
940 RelaxNgNameClass::NsName(ns)
941 }
942}
943
944unsafe fn rng_parse_name_class_choice(node: *mut _xmlNode) -> RelaxNgNameClass {
950 unsafe {
951 let mut choices = Vec::new();
952 let mut child = (*node).children;
953 while !child.is_null() {
954 if (*child).type_ == XML_ELEMENT_NODE as c_int {
955 choices.push(rng_parse_name_class_item(child));
956 }
957 child = (*child).next;
958 }
959 if choices.len() == 1 {
960 choices.remove(0)
961 } else {
962 RelaxNgNameClass::Choice(choices)
963 }
964 }
965}
966
967unsafe fn rng_parse_name_class_item(node: *mut _xmlNode) -> RelaxNgNameClass {
973 unsafe {
974 let local = get_local_name(node);
975 match local.as_str() {
976 "name" => {
977 let text = get_node_text(node);
978 RelaxNgNameClass::Name(text.trim().to_string())
979 }
980 "anyName" => rng_parse_any_name(node),
981 "nsName" => rng_parse_ns_name(node),
982 "choice" => rng_parse_name_class_choice(node),
983 _ => {
984 let text = get_node_text(node);
986 if text.trim().is_empty() {
987 RelaxNgNameClass::AnyName
988 } else {
989 RelaxNgNameClass::Name(text.trim().to_string())
990 }
991 }
992 }
993 }
994}
995
996unsafe fn rng_parse_name_class_content(node: *mut _xmlNode) -> RelaxNgNameClass {
1002 unsafe {
1003 let mut names = Vec::new();
1004 let mut child = (*node).children;
1005 while !child.is_null() {
1006 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1007 names.push(rng_parse_name_class_item(child));
1008 }
1009 child = (*child).next;
1010 }
1011 if names.is_empty() {
1012 RelaxNgNameClass::AnyName
1013 } else if names.len() == 1 {
1014 names.remove(0)
1015 } else {
1016 RelaxNgNameClass::Choice(names)
1017 }
1018 }
1019}
1020
1021unsafe fn rng_parse_composite_pattern(
1027 node: *mut _xmlNode,
1028 pattern_type: RelaxNgPatternType,
1029 schema: &mut RelaxNgSchema,
1030) -> RelaxNgPattern {
1031 unsafe {
1032 let mut pattern = RelaxNgPattern::new(pattern_type);
1033
1034 let mut child = (*node).children;
1035 while !child.is_null() {
1036 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1037 pattern.children.push(rng_parse_pattern(child, schema));
1038 }
1039 child = (*child).next;
1040 }
1041
1042 pattern
1043 }
1044}
1045
1046unsafe fn rng_parse_unary_pattern(
1052 node: *mut _xmlNode,
1053 pattern_type: RelaxNgPatternType,
1054 schema: &mut RelaxNgSchema,
1055) -> RelaxNgPattern {
1056 unsafe {
1057 let mut pattern = RelaxNgPattern::new(pattern_type);
1058
1059 let mut child = (*node).children;
1060 while !child.is_null() {
1061 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1062 pattern.children.push(rng_parse_pattern(child, schema));
1063 break;
1065 }
1066 child = (*child).next;
1067 }
1068
1069 pattern
1070 }
1071}
1072
1073unsafe fn rng_parse_data_pattern(
1079 node: *mut _xmlNode,
1080 _schema: &mut RelaxNgSchema,
1081) -> RelaxNgPattern {
1082 unsafe {
1083 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Data);
1084 pattern.datatype = get_attr(node, "type");
1085 pattern.datatype_library = get_attr(node, "datatypeLibrary");
1086
1087 pattern
1089 }
1090}
1091
1092unsafe fn rng_parse_value_pattern(
1098 node: *mut _xmlNode,
1099 _schema: &mut RelaxNgSchema,
1100) -> RelaxNgPattern {
1101 unsafe {
1102 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Value);
1103 pattern.datatype = get_attr(node, "type");
1104 pattern.datatype_library = get_attr(node, "datatypeLibrary");
1105 pattern.value = Some(get_node_text(node).trim().to_string());
1106
1107 pattern
1108 }
1109}
1110
1111unsafe fn rng_parse_ref_pattern(node: *mut _xmlNode) -> RelaxNgPattern {
1117 unsafe {
1118 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Ref);
1119 pattern.name = get_attr(node, "name");
1120 pattern
1121 }
1122}
1123
1124unsafe fn rng_parse_external_ref(
1130 node: *mut _xmlNode,
1131 _schema: &mut RelaxNgSchema,
1132) -> RelaxNgPattern {
1133 unsafe {
1134 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::ExternalRef);
1135 let href = get_attr(node, "href");
1136 pattern.name = href;
1137 pattern
1138 }
1139}
1140
1141pub unsafe fn rng_validate_doc(
1155 schema: &RelaxNgSchema,
1156 doc: *mut _xmlDoc,
1157 ctxt: &mut RelaxNgValidCtxt,
1158) -> bool {
1159 unsafe {
1160 if doc.is_null() {
1161 ctxt.record_error("Document is null".to_string());
1162 return false;
1163 }
1164
1165 let root = (*doc).children;
1166 if root.is_null() {
1167 ctxt.record_error("Document has no children".to_string());
1168 return false;
1169 }
1170
1171 let mut root_elem = root;
1173 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
1174 root_elem = (*root_elem).next;
1175 }
1176
1177 if root_elem.is_null() {
1178 ctxt.record_error("Document has no root element".to_string());
1179 return false;
1180 }
1181
1182 let start_pattern = match &schema.grammar.start {
1184 Some(p) => p,
1185 None => {
1186 ctxt.record_error("Schema has no start pattern".to_string());
1187 return false;
1188 }
1189 };
1190
1191 ctxt.path.clear();
1192 let valid = rng_validate_pattern(start_pattern, root_elem, schema, ctxt);
1193
1194 valid
1196 }
1197}
1198
1199fn rng_validate_pattern(
1205 pattern: &RelaxNgPattern,
1206 node: *mut _xmlNode,
1207 schema: &RelaxNgSchema,
1208 ctxt: &mut RelaxNgValidCtxt,
1209) -> bool {
1210 unsafe {
1211 if ctxt.depth >= ctxt.depth_max {
1212 ctxt.record_error("Maximum validation depth exceeded".to_string());
1213 return false;
1214 }
1215 ctxt.depth += 1;
1216
1217 let result = match pattern.pattern_type {
1218 RelaxNgPatternType::Element => {
1219 rng_validate_element_pattern(pattern, node, schema, ctxt)
1220 }
1221 RelaxNgPatternType::Attribute => {
1222 rng_validate_attribute_pattern(pattern, node, schema, ctxt)
1223 }
1224 RelaxNgPatternType::Text => rng_validate_text_pattern(node, ctxt),
1225 RelaxNgPatternType::Empty => rng_validate_empty_pattern(node, ctxt),
1226 RelaxNgPatternType::NotAllowed => {
1227 let name = get_node_qname(node);
1228 ctxt.record_error(format!(
1229 "Element '{}' is not allowed at '{}'",
1230 name,
1231 ctxt.current_path()
1232 ));
1233 false
1234 }
1235 RelaxNgPatternType::Choice => rng_validate_choice_pattern(pattern, node, schema, ctxt),
1236 RelaxNgPatternType::Sequence => {
1237 rng_validate_sequence_pattern(pattern, node, schema, ctxt)
1238 }
1239 RelaxNgPatternType::Interleave => {
1240 rng_validate_interleave_pattern(pattern, node, schema, ctxt)
1241 }
1242 RelaxNgPatternType::ZeroOrMore => {
1243 rng_validate_zero_or_more(pattern, node, schema, ctxt)
1244 }
1245 RelaxNgPatternType::OneOrMore => rng_validate_one_or_more(pattern, node, schema, ctxt),
1246 RelaxNgPatternType::Optional => {
1247 rng_validate_optional_pattern(pattern, node, schema, ctxt)
1248 }
1249 RelaxNgPatternType::List => rng_validate_list_pattern(pattern, node, schema, ctxt),
1250 RelaxNgPatternType::Group => rng_validate_group_pattern(pattern, node, schema, ctxt),
1251 RelaxNgPatternType::Data => rng_validate_data_pattern(pattern, node, ctxt),
1252 RelaxNgPatternType::Value => rng_validate_value_pattern(pattern, node, ctxt),
1253 RelaxNgPatternType::Ref => rng_validate_ref_pattern(pattern, node, schema, ctxt),
1254 RelaxNgPatternType::ExternalRef => {
1255 rng_validate_empty_pattern(node, ctxt)
1257 }
1258 RelaxNgPatternType::Define
1259 | RelaxNgPatternType::Grammar
1260 | RelaxNgPatternType::Start
1261 | RelaxNgPatternType::Include => {
1262 rng_validate_children(pattern, node, schema, ctxt)
1264 }
1265 };
1266
1267 ctxt.depth -= 1;
1268 result
1269 }
1270}
1271
1272fn rng_validate_children(
1278 pattern: &RelaxNgPattern,
1279 node: *mut _xmlNode,
1280 schema: &RelaxNgSchema,
1281 ctxt: &mut RelaxNgValidCtxt,
1282) -> bool {
1283 unsafe {
1284 if pattern.children.is_empty() {
1285 return true;
1286 }
1287 let mut valid = true;
1289 for child in &pattern.children {
1290 valid &= rng_validate_pattern(child, node, schema, ctxt);
1291 }
1292 valid
1293 }
1294}
1295
1296fn rng_validate_element_pattern(
1302 pattern: &RelaxNgPattern,
1303 node: *mut _xmlNode,
1304 schema: &RelaxNgSchema,
1305 ctxt: &mut RelaxNgValidCtxt,
1306) -> bool {
1307 unsafe {
1308 if node.is_null() || (*node).type_ != XML_ELEMENT_NODE as c_int {
1309 return false;
1310 }
1311
1312 let node_name = get_node_qname(node);
1313 let ns_uri = get_node_ns_uri(node);
1314
1315 if let Some(ref nc) = pattern.name_class {
1317 if !nc.matches(&node_name, ns_uri.as_deref()) {
1318 let pat_name = pattern.name.as_deref().unwrap_or("?");
1319 ctxt.record_error(format!(
1320 "Element '{}' does not match expected element pattern '{}' at '{}'",
1321 node_name,
1322 pat_name,
1323 ctxt.current_path()
1324 ));
1325 return false;
1326 }
1327 }
1328
1329 ctxt.path.push(node_name.clone());
1331
1332 let mut valid = true;
1334 if pattern.children.is_empty() {
1335 let mut child = (*node).children;
1337 while !child.is_null() {
1338 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1339 let child_name = get_node_qname(child);
1340 ctxt.record_error(format!(
1341 "Unexpected child element '{}' in empty element '{}' at '{}'",
1342 child_name,
1343 node_name,
1344 ctxt.current_path()
1345 ));
1346 valid = false;
1347 }
1348 child = (*child).next;
1349 }
1350 } else {
1351 for child_pat in &pattern.children {
1354 valid &= rng_validate_pattern(child_pat, node, schema, ctxt);
1355 }
1356 }
1357
1358 ctxt.path.pop();
1359 valid
1360 }
1361}
1362
1363fn rng_validate_attribute_pattern(
1369 pattern: &RelaxNgPattern,
1370 node: *mut _xmlNode,
1371 schema: &RelaxNgSchema,
1372 ctxt: &mut RelaxNgValidCtxt,
1373) -> bool {
1374 unsafe {
1375 if node.is_null() || (*node).type_ != XML_ELEMENT_NODE as c_int {
1376 return false;
1377 }
1378
1379 let attr_name = match &pattern.name_class {
1381 Some(RelaxNgNameClass::Name(n)) => n.clone(),
1382 Some(RelaxNgNameClass::AnyName) => {
1383 return true;
1388 }
1389 Some(RelaxNgNameClass::NsName(ns)) => {
1390 let mut prop = (*node).properties;
1393 while !prop.is_null() {
1394 let prop_ns = get_node_ns_uri(prop as *mut _xmlNode);
1395 if let Some(ref uri) = prop_ns {
1396 if uri == ns {
1397 if let Some(content) = &pattern.children.first() {
1399 let val = get_node_text(prop as *mut _xmlNode);
1400 let valid = match content.pattern_type {
1401 RelaxNgPatternType::Text => true,
1402 RelaxNgPatternType::Data => rng_validate_datatype_value(
1403 content.datatype.as_deref(),
1404 &val,
1405 ),
1406 RelaxNgPatternType::Value => {
1407 content.value.as_deref() == Some(&val)
1408 }
1409 _ => true,
1410 };
1411 if !valid {
1412 ctxt.record_error(format!(
1413 "Attribute '{}' has invalid value at '{}'",
1414 prop_ns.unwrap_or_default(),
1415 ctxt.current_path()
1416 ));
1417 return false;
1418 }
1419 }
1420 return true;
1421 }
1422 }
1423 prop = (*prop).next;
1424 }
1425 ctxt.record_error(format!(
1428 "Required attribute in namespace '{}' is missing at '{}'",
1429 ns,
1430 ctxt.current_path()
1431 ));
1432 return false;
1433 }
1434 _ => {
1435 return true;
1438 }
1439 };
1440
1441 let attr_value = get_attr(node, &attr_name);
1443
1444 match attr_value {
1445 Some(val) => {
1446 if let Some(content) = pattern.children.first() {
1448 let valid = match content.pattern_type {
1449 RelaxNgPatternType::Text => true,
1450 RelaxNgPatternType::Data => {
1451 rng_validate_datatype_value(content.datatype.as_deref(), &val)
1452 }
1453 RelaxNgPatternType::Value => content.value.as_deref() == Some(&val),
1454 _ => true,
1455 };
1456 if !valid {
1457 ctxt.record_error(format!(
1458 "Attribute '{}' has invalid value '{}' at '{}'",
1459 attr_name,
1460 val,
1461 ctxt.current_path()
1462 ));
1463 return false;
1464 }
1465 }
1466 true
1467 }
1468 None => {
1469 ctxt.record_error(format!(
1472 "Required attribute '{}' is missing at '{}'",
1473 attr_name,
1474 ctxt.current_path()
1475 ));
1476 false
1477 }
1478 }
1479 }
1480}
1481
1482fn rng_validate_text_pattern(node: *mut _xmlNode, _ctxt: &mut RelaxNgValidCtxt) -> bool {
1488 unsafe {
1489 if node.is_null() {
1490 return false;
1491 }
1492 true
1495 }
1496}
1497
1498fn rng_validate_empty_pattern(node: *mut _xmlNode, ctxt: &mut RelaxNgValidCtxt) -> bool {
1504 unsafe {
1505 if node.is_null() {
1506 return true;
1507 }
1508 let mut child = (*node).children;
1510 while !child.is_null() {
1511 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1512 let child_name = get_node_qname(child);
1513 ctxt.record_error(format!(
1514 "Unexpected child element '{}' in empty content at '{}'",
1515 child_name,
1516 ctxt.current_path()
1517 ));
1518 return false;
1519 }
1520 child = (*child).next;
1521 }
1522 true
1523 }
1524}
1525
1526fn rng_validate_choice_pattern(
1532 pattern: &RelaxNgPattern,
1533 node: *mut _xmlNode,
1534 schema: &RelaxNgSchema,
1535 ctxt: &mut RelaxNgValidCtxt,
1536) -> bool {
1537 unsafe {
1538 if pattern.children.is_empty() {
1539 return false;
1540 }
1541
1542 let mut last_error = String::new();
1544 for child in &pattern.children {
1545 let saved_errors = ctxt.errors.len();
1546 let saved_nb = ctxt.nb_errors;
1547
1548 if rng_validate_pattern(child, node, schema, ctxt) {
1549 return true;
1552 }
1553
1554 if ctxt.errors.len() > saved_errors {
1556 last_error = ctxt.errors.last().unwrap().clone();
1557 }
1558
1559 ctxt.errors.truncate(saved_errors);
1561 ctxt.nb_errors = saved_nb;
1562 }
1563
1564 let node_name = if node.is_null() {
1566 "null".to_string()
1567 } else {
1568 get_node_qname(node)
1569 };
1570 ctxt.record_error(format!(
1571 "No choice pattern matched for '{}' at '{}'. Last error: {}",
1572 node_name,
1573 ctxt.current_path(),
1574 last_error
1575 ));
1576 false
1577 }
1578}
1579
1580fn rng_validate_sequence_pattern(
1586 pattern: &RelaxNgPattern,
1587 node: *mut _xmlNode,
1588 schema: &RelaxNgSchema,
1589 ctxt: &mut RelaxNgValidCtxt,
1590) -> bool {
1591 unsafe {
1592 if node.is_null() {
1593 return pattern.children.is_empty();
1594 }
1595
1596 let mut valid = true;
1597
1598 let mut child_nodes: Vec<*mut _xmlNode> = Vec::new();
1602 let mut child = (*node).children;
1603 while !child.is_null() {
1604 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1605 child_nodes.push(child);
1606 }
1607 child = (*child).next;
1608 }
1609
1610 let mut child_idx = 0;
1613 for child_pat in &pattern.children {
1614 if child_idx >= child_nodes.len() {
1615 match child_pat.pattern_type {
1617 RelaxNgPatternType::Optional | RelaxNgPatternType::ZeroOrMore => {
1618 continue;
1620 }
1621 RelaxNgPatternType::OneOrMore => {
1622 ctxt.record_error(format!(
1623 "Expected at least one matching child for oneOrMore at '{}'",
1624 ctxt.current_path()
1625 ));
1626 valid = false;
1627 continue;
1628 }
1629 _ => {
1630 ctxt.record_error(format!(
1631 "Expected more child elements for sequence at '{}'",
1632 ctxt.current_path()
1633 ));
1634 valid = false;
1635 continue;
1636 }
1637 }
1638 }
1639
1640 let child_node = child_nodes[child_idx];
1641 valid &= rng_validate_pattern(child_pat, child_node, schema, ctxt);
1642 child_idx += 1;
1643 }
1644
1645 if child_idx < child_nodes.len() {
1647 let extra_name = get_node_qname(child_nodes[child_idx]);
1648 ctxt.record_error(format!(
1649 "Unexpected extra element '{}' in sequence at '{}'",
1650 extra_name,
1651 ctxt.current_path()
1652 ));
1653 valid = false;
1654 }
1655
1656 valid
1657 }
1658}
1659
1660fn rng_validate_interleave_pattern(
1666 pattern: &RelaxNgPattern,
1667 node: *mut _xmlNode,
1668 schema: &RelaxNgSchema,
1669 ctxt: &mut RelaxNgValidCtxt,
1670) -> bool {
1671 unsafe {
1672 if node.is_null() {
1673 return pattern.children.is_empty();
1674 }
1675
1676 let mut valid = true;
1680
1681 for child_pat in &pattern.children {
1682 let mut child = (*node).children;
1685 let mut matched = false;
1686
1687 while !child.is_null() {
1688 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1689 let saved_errors = ctxt.errors.len();
1690 let saved_nb = ctxt.nb_errors;
1691
1692 if rng_validate_pattern(child_pat, child, schema, ctxt) {
1693 matched = true;
1694 break;
1695 }
1696
1697 ctxt.errors.truncate(saved_errors);
1699 ctxt.nb_errors = saved_nb;
1700 }
1701 child = (*child).next;
1702 }
1703
1704 if !matched {
1705 match child_pat.pattern_type {
1707 RelaxNgPatternType::Optional | RelaxNgPatternType::ZeroOrMore => {
1708 }
1710 _ => {
1711 let pat_desc = format!("{:?}", child_pat.pattern_type);
1712 ctxt.record_error(format!(
1713 "Interleave pattern '{}' did not match any child at '{}'",
1714 pat_desc,
1715 ctxt.current_path()
1716 ));
1717 valid = false;
1718 }
1719 }
1720 }
1721 }
1722
1723 valid
1724 }
1725}
1726
1727fn rng_validate_zero_or_more(
1733 pattern: &RelaxNgPattern,
1734 node: *mut _xmlNode,
1735 schema: &RelaxNgSchema,
1736 ctxt: &mut RelaxNgValidCtxt,
1737) -> bool {
1738 unsafe {
1739 if node.is_null() || pattern.children.is_empty() {
1740 return true;
1741 }
1742
1743 let child_pat = &pattern.children[0];
1744 let mut valid = true;
1745
1746 let mut child = (*node).children;
1748 while !child.is_null() {
1749 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1750 let saved_errors = ctxt.errors.len();
1751 let saved_nb = ctxt.nb_errors;
1752
1753 if !rng_validate_pattern(child_pat, child, schema, ctxt) {
1754 ctxt.errors.truncate(saved_errors);
1757 ctxt.nb_errors = saved_nb;
1758 break;
1759 }
1760 }
1762 child = (*child).next;
1763 }
1764
1765 valid
1766 }
1767}
1768
1769fn rng_validate_one_or_more(
1775 pattern: &RelaxNgPattern,
1776 node: *mut _xmlNode,
1777 schema: &RelaxNgSchema,
1778 ctxt: &mut RelaxNgValidCtxt,
1779) -> bool {
1780 unsafe {
1781 if node.is_null() || pattern.children.is_empty() {
1782 ctxt.record_error(format!(
1783 "Expected at least one matching element for oneOrMore at '{}'",
1784 ctxt.current_path()
1785 ));
1786 return false;
1787 }
1788
1789 let child_pat = &pattern.children[0];
1790 let mut matched = false;
1791 let mut valid = true;
1792
1793 let mut child = (*node).children;
1795 while !child.is_null() {
1796 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1797 let saved_errors = ctxt.errors.len();
1798 let saved_nb = ctxt.nb_errors;
1799
1800 if rng_validate_pattern(child_pat, child, schema, ctxt) {
1801 matched = true;
1802 } else {
1803 ctxt.errors.truncate(saved_errors);
1805 ctxt.nb_errors = saved_nb;
1806 break;
1807 }
1808 }
1809 child = (*child).next;
1810 }
1811
1812 if !matched {
1813 ctxt.record_error(format!(
1814 "Expected at least one matching element for oneOrMore at '{}'",
1815 ctxt.current_path()
1816 ));
1817 valid = false;
1818 }
1819
1820 valid
1821 }
1822}
1823
1824fn rng_validate_optional_pattern(
1830 pattern: &RelaxNgPattern,
1831 node: *mut _xmlNode,
1832 schema: &RelaxNgSchema,
1833 ctxt: &mut RelaxNgValidCtxt,
1834) -> bool {
1835 unsafe {
1836 if pattern.children.is_empty() {
1837 return true;
1838 }
1839
1840 let child_pat = &pattern.children[0];
1842 let saved_errors = ctxt.errors.len();
1843 let saved_nb = ctxt.nb_errors;
1844
1845 let result = rng_validate_pattern(child_pat, node, schema, ctxt);
1846
1847 if !result {
1848 ctxt.errors.truncate(saved_errors);
1850 ctxt.nb_errors = saved_nb;
1851 }
1852
1853 true }
1855}
1856
1857fn rng_validate_list_pattern(
1863 pattern: &RelaxNgPattern,
1864 node: *mut _xmlNode,
1865 schema: &RelaxNgSchema,
1866 ctxt: &mut RelaxNgValidCtxt,
1867) -> bool {
1868 unsafe {
1869 if node.is_null() {
1870 return pattern.children.is_empty();
1871 }
1872
1873 let text = get_node_text(node);
1875 if text.trim().is_empty() {
1876 return true;
1877 }
1878
1879 let tokens: Vec<&str> = text.split_whitespace().collect();
1880 let mut valid = true;
1881
1882 for token in &tokens {
1883 if token.is_empty() {
1886 valid = false;
1887 break;
1888 }
1889 }
1890
1891 valid
1892 }
1893}
1894
1895fn rng_validate_group_pattern(
1901 pattern: &RelaxNgPattern,
1902 node: *mut _xmlNode,
1903 schema: &RelaxNgSchema,
1904 ctxt: &mut RelaxNgValidCtxt,
1905) -> bool {
1906 unsafe {
1907 rng_validate_sequence_pattern(pattern, node, schema, ctxt)
1909 }
1910}
1911
1912fn rng_validate_data_pattern(
1918 pattern: &RelaxNgPattern,
1919 node: *mut _xmlNode,
1920 ctxt: &mut RelaxNgValidCtxt,
1921) -> bool {
1922 unsafe {
1923 if node.is_null() {
1924 return false;
1925 }
1926
1927 let text = get_node_text(node);
1928 let datatype = pattern.datatype.as_deref();
1929
1930 if !rng_validate_datatype_value(datatype, &text) {
1931 ctxt.record_error(format!(
1932 "Value '{}' does not match datatype '{:?}' at '{}'",
1933 text,
1934 datatype,
1935 ctxt.current_path()
1936 ));
1937 return false;
1938 }
1939
1940 true
1941 }
1942}
1943
1944fn rng_validate_value_pattern(
1950 pattern: &RelaxNgPattern,
1951 node: *mut _xmlNode,
1952 ctxt: &mut RelaxNgValidCtxt,
1953) -> bool {
1954 unsafe {
1955 if node.is_null() {
1956 return false;
1957 }
1958
1959 let text = get_node_text(node).trim().to_string();
1960 let expected = pattern.value.as_deref().unwrap_or("");
1961
1962 if text != expected {
1963 ctxt.record_error(format!(
1964 "Value '{}' does not match expected value '{}' at '{}'",
1965 text,
1966 expected,
1967 ctxt.current_path()
1968 ));
1969 return false;
1970 }
1971
1972 true
1973 }
1974}
1975
1976fn rng_validate_ref_pattern(
1982 pattern: &RelaxNgPattern,
1983 node: *mut _xmlNode,
1984 schema: &RelaxNgSchema,
1985 ctxt: &mut RelaxNgValidCtxt,
1986) -> bool {
1987 unsafe {
1988 let ref_name = pattern.name.as_deref().unwrap_or("");
1989
1990 if ref_name.is_empty() {
1991 ctxt.record_error("Ref pattern has no name".to_string());
1992 return false;
1993 }
1994
1995 match schema.grammar.lookup(ref_name) {
1997 Some(def_pattern) => {
1998 rng_validate_pattern(def_pattern, node, schema, ctxt)
2000 }
2001 None => {
2002 ctxt.record_error(format!(
2003 "Undefined reference '{}' at '{}'",
2004 ref_name,
2005 ctxt.current_path()
2006 ));
2007 false
2008 }
2009 }
2010 }
2011}
2012
2013fn rng_validate_datatype_value(datatype: Option<&str>, value: &str) -> bool {
2022 let dt = match datatype {
2023 Some(d) => d,
2024 None => return true, };
2026
2027 match dt {
2028 "string" | "token" => true,
2029 "boolean" => {
2030 matches!(value, "true" | "false" | "1" | "0")
2031 }
2032 "integer" | "int" | "short" | "byte" | "long" => {
2033 if value.is_empty() {
2034 return false;
2035 }
2036 let trimmed = if value.starts_with('+') || value.starts_with('-') {
2037 &value[1..]
2038 } else {
2039 value
2040 };
2041 !trimmed.is_empty() && trimmed.chars().all(|c| c.is_ascii_digit())
2042 }
2043 "decimal" | "double" | "float" => {
2044 if value.is_empty() {
2045 return false;
2046 }
2047 if matches!(dt, "float" | "double") && matches!(value, "INF" | "-INF" | "NaN") {
2049 return true;
2050 }
2051 value.parse::<f64>().is_ok()
2052 }
2053 "NCName" | "Name" | "ID" | "IDREF" | "NMTOKEN" => {
2054 !value.is_empty() && !value.starts_with(|c: char| c.is_ascii_digit())
2055 }
2056 "anyURI" => {
2057 !value.is_empty() && !value.contains(char::is_whitespace)
2059 }
2060 "QName" => {
2061 if value.is_empty() {
2062 return false;
2063 }
2064 if let Some(pos) = value.find(':') {
2065 pos > 0 && pos < value.len() - 1
2066 } else {
2067 true
2068 }
2069 }
2070 _ => {
2071 true
2074 }
2075 }
2076}
2077
2078pub fn rng_parse_schema(xml_doc: &str) -> Result<RelaxNgSchema, String> {
2086 rng_parse(xml_doc)
2087}
2088
2089pub unsafe fn rng_parse_schema_doc(doc: *mut _xmlDoc) -> Result<RelaxNgSchema, String> {
2095 rng_parse_doc(doc)
2096}
2097
2098pub unsafe fn rng_validate_doc_schema(
2106 schema: &RelaxNgSchema,
2107 doc: *mut _xmlDoc,
2108 ctxt: &mut RelaxNgValidCtxt,
2109) -> bool {
2110 rng_validate_doc(schema, doc, ctxt)
2111}
2112
2113#[no_mangle]
2132pub unsafe extern "C" fn xmlRelaxNGNewParserCtxt(url: *const c_char) -> *mut c_void {
2133 if url.is_null() {
2134 let ctxt = allocator::xmlMallocZero(size_of::<RelaxNgSchema>() as usize);
2135 return ctxt;
2136 }
2137
2138 let url_str = unsafe {
2139 let mut len = 0;
2140 while *url.add(len) != 0 {
2141 len += 1;
2142 }
2143 let slice = std::slice::from_raw_parts(url as *const u8, len);
2144 String::from_utf8_lossy(slice).to_string()
2145 };
2146
2147 if !url_str.is_empty() {
2149 let url_c = std::ffi::CString::new(url_str.clone()).ok();
2150 if let Some(c) = url_c {
2151 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
2152 if !doc.is_null() {
2153 let result = rng_parse_doc(doc);
2154 crate::abi::exports_xml2::xmlFreeDoc(doc);
2155 if let Ok(schema) = result {
2156 let schema_box = Box::new(schema);
2157 return Box::into_raw(schema_box) as *mut c_void;
2158 }
2159 }
2160 }
2161 }
2162
2163 let ctxt = allocator::xmlMallocZero(size_of::<RelaxNgSchema>() as usize);
2165 ctxt
2166}
2167
2168#[no_mangle]
2180pub unsafe extern "C" fn xmlRelaxNGNewMemParserCtxt(
2181 buffer: *const c_char,
2182 size: c_int,
2183) -> *mut c_void {
2184 if buffer.is_null() || size <= 0 {
2185 return ptr::null_mut();
2186 }
2187
2188 let buf_slice = unsafe { std::slice::from_raw_parts(buffer as *const u8, size as usize) };
2190 let xml_str = String::from_utf8_lossy(buf_slice).to_string();
2191
2192 match rng_parse(&xml_str) {
2193 Ok(schema) => {
2194 let schema_box = Box::new(schema);
2195 Box::into_raw(schema_box) as *mut c_void
2196 }
2197 Err(_) => ptr::null_mut(),
2198 }
2199}
2200
2201#[no_mangle]
2213pub unsafe extern "C" fn xmlRelaxNGParse(ctxt: *mut c_void) -> *mut c_void {
2214 if ctxt.is_null() {
2215 return ptr::null_mut();
2216 }
2217
2218 ctxt
2221}
2222
2223#[no_mangle]
2235pub unsafe extern "C" fn xmlRelaxNGFree(schema: *mut c_void) {
2236 if schema.is_null() {
2237 return;
2238 }
2239 unsafe {
2241 let _ = Box::from_raw(schema as *mut RelaxNgSchema);
2242 }
2243}
2244
2245#[no_mangle]
2257pub unsafe extern "C" fn xmlRelaxNGFreeParserCtxt(ctxt: *mut c_void) {
2258 if ctxt.is_null() {
2259 return;
2260 }
2261 unsafe {
2263 let _ = Box::from_raw(ctxt as *mut RelaxNgSchema);
2264 }
2265}
2266
2267#[no_mangle]
2279pub unsafe extern "C" fn xmlRelaxNGNewValidCtxt(schema: *mut c_void) -> *mut c_void {
2280 let mut ctxt = RelaxNgValidCtxt::new();
2281
2282 if !schema.is_null() {
2283 unsafe {
2285 let schema_ref = &*(schema as *const RelaxNgSchema);
2286 ctxt.schema = Some(schema_ref.clone());
2287 }
2288 }
2289
2290 let boxed = Box::new(ctxt);
2291 Box::into_raw(boxed) as *mut c_void
2292}
2293
2294#[no_mangle]
2306pub unsafe extern "C" fn xmlRelaxNGFreeValidCtxt(ctxt: *mut c_void) {
2307 if ctxt.is_null() {
2308 return;
2309 }
2310 unsafe {
2312 let _ = Box::from_raw(ctxt as *mut RelaxNgValidCtxt);
2313 }
2314}
2315
2316#[no_mangle]
2331pub unsafe extern "C" fn xmlRelaxNGValidateDoc(ctxt: *mut c_void, doc: *mut _xmlDoc) -> c_int {
2332 if ctxt.is_null() || doc.is_null() {
2333 return -1;
2334 }
2335
2336 unsafe {
2337 let valid_ctxt = &mut *(ctxt as *mut RelaxNgValidCtxt);
2338 let schema = match &valid_ctxt.schema {
2339 Some(s) => s,
2340 None => return -1,
2341 };
2342
2343 let mut temp_ctxt = RelaxNgValidCtxt::new();
2344
2345 let valid = rng_validate_doc(schema, doc, &mut temp_ctxt);
2346
2347 if valid {
2348 0
2349 } else {
2350 valid_ctxt.errors = temp_ctxt.errors;
2351 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2352 temp_ctxt.nb_errors
2353 }
2354 }
2355}
2356
2357#[no_mangle]
2375pub unsafe extern "C" fn xmlRelaxNGValidateFullElement(
2376 ctxt: *mut c_void,
2377 doc: *mut _xmlDoc,
2378 elem: *mut _xmlNode,
2379) -> c_int {
2380 if ctxt.is_null() || doc.is_null() || elem.is_null() {
2381 return -1;
2382 }
2383
2384 unsafe {
2385 let valid_ctxt = &mut *(ctxt as *mut RelaxNgValidCtxt);
2386 let schema = match &valid_ctxt.schema {
2387 Some(s) => s,
2388 None => return -1,
2389 };
2390
2391 let mut temp_ctxt = RelaxNgValidCtxt::new();
2392 temp_ctxt.path = valid_ctxt.path.clone();
2393
2394 let start_pattern = match &schema.grammar.start {
2395 Some(p) => p,
2396 None => return -1,
2397 };
2398
2399 let valid = rng_validate_pattern(start_pattern, elem, schema, &mut temp_ctxt);
2400
2401 if valid {
2402 0
2403 } else {
2404 valid_ctxt.errors = temp_ctxt.errors;
2405 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2406 temp_ctxt.nb_errors
2407 }
2408 }
2409}
2410
2411#[cfg(test)]
2416mod tests {
2417 use super::*;
2418
2419 #[test]
2422 fn test_name_class_name() {
2423 let nc = RelaxNgNameClass::Name("foo".to_string());
2424 assert!(nc.matches("foo", None));
2425 assert!(!nc.matches("bar", None));
2426 assert!(!nc.matches("FOO", None));
2427 }
2428
2429 #[test]
2430 fn test_name_class_any_name() {
2431 let nc = RelaxNgNameClass::AnyName;
2432 assert!(nc.matches("foo", None));
2433 assert!(nc.matches("bar", None));
2434 assert!(nc.matches("anything", Some("urn:ns")));
2435 }
2436
2437 #[test]
2438 fn test_name_class_ns_name() {
2439 let nc = RelaxNgNameClass::NsName("urn:example".to_string());
2440 assert!(nc.matches("foo", Some("urn:example")));
2441 assert!(!nc.matches("foo", Some("urn:other")));
2442 assert!(!nc.matches("foo", None));
2443 }
2444
2445 #[test]
2446 fn test_name_class_choice() {
2447 let nc = RelaxNgNameClass::Choice(vec![
2448 RelaxNgNameClass::Name("a".to_string()),
2449 RelaxNgNameClass::Name("b".to_string()),
2450 ]);
2451 assert!(nc.matches("a", None));
2452 assert!(nc.matches("b", None));
2453 assert!(!nc.matches("c", None));
2454 }
2455
2456 #[test]
2457 fn test_name_class_except() {
2458 let nc = RelaxNgNameClass::Except(
2459 Box::new(RelaxNgNameClass::AnyName),
2460 Box::new(RelaxNgNameClass::Name("bad".to_string())),
2461 );
2462 assert!(nc.matches("good", None));
2463 assert!(!nc.matches("bad", None));
2464 }
2465
2466 #[test]
2469 fn test_parse_simple_element_schema() {
2470 let schema_xml = r#"<?xml version="1.0"?>
2471<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2472 <text/>
2473</element>"#;
2474
2475 let result = rng_parse(schema_xml);
2476 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2477 let schema = result.unwrap();
2478 assert!(schema.grammar.start.is_some());
2479 if let Some(ref start) = schema.grammar.start {
2480 assert_eq!(start.pattern_type, RelaxNgPatternType::Element);
2481 assert_eq!(start.name.as_deref(), Some("root"));
2482 }
2483 }
2484
2485 #[test]
2486 fn test_parse_grammar_schema() {
2487 let schema_xml = r#"<?xml version="1.0"?>
2488<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2489 <start>
2490 <element name="root">
2491 <text/>
2492 </element>
2493 </start>
2494</grammar>"#;
2495
2496 let result = rng_parse(schema_xml);
2497 assert!(
2498 result.is_ok(),
2499 "Failed to parse grammar: {:?}",
2500 result.err()
2501 );
2502 let schema = result.unwrap();
2503 assert!(schema.grammar.start.is_some());
2504 }
2505
2506 #[test]
2507 fn test_parse_with_define_and_ref() {
2508 let schema_xml = r#"<?xml version="1.0"?>
2509<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2510 <define name="textBlock">
2511 <text/>
2512 </define>
2513 <start>
2514 <element name="doc">
2515 <ref name="textBlock"/>
2516 </element>
2517 </start>
2518</grammar>"#;
2519
2520 let result = rng_parse(schema_xml);
2521 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
2522 let schema = result.unwrap();
2523 assert_eq!(schema.grammar.defines.len(), 1);
2524 assert_eq!(schema.grammar.defines[0].name, "textBlock");
2525 assert!(schema.grammar.start.is_some());
2526 }
2527
2528 #[test]
2529 fn test_parse_choice_schema() {
2530 let schema_xml = r#"<?xml version="1.0"?>
2531<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2532 <start>
2533 <choice>
2534 <element name="a">
2535 <text/>
2536 </element>
2537 <element name="b">
2538 <text/>
2539 </element>
2540 </choice>
2541 </start>
2542</grammar>"#;
2543
2544 let result = rng_parse(schema_xml);
2545 assert!(result.is_ok(), "Failed to parse choice: {:?}", result.err());
2546 }
2547
2548 #[test]
2549 fn test_parse_attribute_schema() {
2550 let schema_xml = r#"<?xml version="1.0"?>
2551<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2552 <attribute name="attr1">
2553 <text/>
2554 </attribute>
2555 <text/>
2556</element>"#;
2557
2558 let result = rng_parse(schema_xml);
2559 assert!(
2560 result.is_ok(),
2561 "Failed to parse attribute: {:?}",
2562 result.err()
2563 );
2564 }
2565
2566 #[test]
2567 fn test_parse_empty_document_fails() {
2568 let result = rng_parse("");
2569 assert!(result.is_err());
2570 }
2571
2572 #[test]
2573 fn test_parse_invalid_xml_fails() {
2574 let result = rng_parse("not valid xml <<<");
2575 assert!(result.is_err());
2576 }
2577
2578 #[test]
2581 fn test_validate_simple_element() {
2582 let schema_xml = r#"<?xml version="1.0"?>
2583<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2584 <text/>
2585</element>"#;
2586
2587 let doc_xml = r#"<?xml version="1.0"?>
2588<root>Hello</root>"#;
2589
2590 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2591
2592 let doc = unsafe {
2593 crate::abi::exports_xml2::xmlReadMemory(
2594 doc_xml.as_ptr() as *const c_char,
2595 doc_xml.len() as c_int,
2596 b"test.xml\0".as_ptr() as *const c_char,
2597 ptr::null(),
2598 0,
2599 )
2600 };
2601 assert!(!doc.is_null(), "Failed to parse document");
2602
2603 let mut ctxt = RelaxNgValidCtxt::new();
2604 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2605 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2606
2607 assert!(valid, "Validation failed: {:?}", ctxt.errors);
2608 }
2609
2610 #[test]
2611 fn test_validate_element_mismatch() {
2612 let schema_xml = r#"<?xml version="1.0"?>
2613<element name="expected" xmlns="http://relaxng.org/ns/structure/1.0">
2614 <text/>
2615</element>"#;
2616
2617 let doc_xml = r#"<?xml version="1.0"?>
2618<actual>Content</actual>"#;
2619
2620 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2621
2622 let doc = unsafe {
2623 crate::abi::exports_xml2::xmlReadMemory(
2624 doc_xml.as_ptr() as *const c_char,
2625 doc_xml.len() as c_int,
2626 b"test.xml\0".as_ptr() as *const c_char,
2627 ptr::null(),
2628 0,
2629 )
2630 };
2631 assert!(!doc.is_null());
2632
2633 let mut ctxt = RelaxNgValidCtxt::new();
2634 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2635 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2636
2637 assert!(!valid, "Validation should have failed");
2638 assert!(ctxt.nb_errors > 0);
2639 }
2640
2641 #[test]
2642 fn test_validate_with_attribute() {
2643 let schema_xml = r#"<?xml version="1.0"?>
2644<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2645 <attribute name="id">
2646 <text/>
2647 </attribute>
2648 <text/>
2649</element>"#;
2650
2651 let doc_xml = r#"<?xml version="1.0"?>
2652<root id="x1">Content</root>"#;
2653
2654 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2655
2656 let doc = unsafe {
2657 crate::abi::exports_xml2::xmlReadMemory(
2658 doc_xml.as_ptr() as *const c_char,
2659 doc_xml.len() as c_int,
2660 b"test.xml\0".as_ptr() as *const c_char,
2661 ptr::null(),
2662 0,
2663 )
2664 };
2665 assert!(!doc.is_null());
2666
2667 let mut ctxt = RelaxNgValidCtxt::new();
2668 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2669 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2670
2671 assert!(valid, "Validation failed: {:?}", ctxt.errors);
2672 }
2673
2674 #[test]
2675 fn test_validate_missing_attribute() {
2676 let schema_xml = r#"<?xml version="1.0"?>
2677<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2678 <attribute name="required">
2679 <text/>
2680 </attribute>
2681 <text/>
2682</element>"#;
2683
2684 let doc_xml = r#"<?xml version="1.0"?>
2685<root>Content</root>"#;
2686
2687 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2688
2689 let doc = unsafe {
2690 crate::abi::exports_xml2::xmlReadMemory(
2691 doc_xml.as_ptr() as *const c_char,
2692 doc_xml.len() as c_int,
2693 b"test.xml\0".as_ptr() as *const c_char,
2694 ptr::null(),
2695 0,
2696 )
2697 };
2698 assert!(!doc.is_null());
2699
2700 let mut ctxt = RelaxNgValidCtxt::new();
2701 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2702 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2703
2704 assert!(
2705 !valid,
2706 "Validation should have failed for missing attribute"
2707 );
2708 }
2709
2710 #[test]
2711 fn test_validate_with_choice() {
2712 let schema_xml = r#"<?xml version="1.0"?>
2713<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2714 <start>
2715 <choice>
2716 <element name="a">
2717 <text/>
2718 </element>
2719 <element name="b">
2720 <text/>
2721 </element>
2722 </choice>
2723 </start>
2724</grammar>"#;
2725
2726 let doc_xml = r#"<?xml version="1.0"?>
2727<a>First choice</a>"#;
2728
2729 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2730
2731 let doc = unsafe {
2732 crate::abi::exports_xml2::xmlReadMemory(
2733 doc_xml.as_ptr() as *const c_char,
2734 doc_xml.len() as c_int,
2735 b"test.xml\0".as_ptr() as *const c_char,
2736 ptr::null(),
2737 0,
2738 )
2739 };
2740 assert!(!doc.is_null());
2741
2742 let mut ctxt = RelaxNgValidCtxt::new();
2743 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2744 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2745
2746 assert!(valid, "Choice validation failed: {:?}", ctxt.errors);
2747 }
2748
2749 #[test]
2750 fn test_validate_choice_no_match() {
2751 let schema_xml = r#"<?xml version="1.0"?>
2752<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2753 <start>
2754 <choice>
2755 <element name="a">
2756 <text/>
2757 </element>
2758 <element name="b">
2759 <text/>
2760 </element>
2761 </choice>
2762 </start>
2763</grammar>"#;
2764
2765 let doc_xml = r#"<?xml version="1.0"?>
2766<c>Neither choice</c>"#;
2767
2768 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2769
2770 let doc = unsafe {
2771 crate::abi::exports_xml2::xmlReadMemory(
2772 doc_xml.as_ptr() as *const c_char,
2773 doc_xml.len() as c_int,
2774 b"test.xml\0".as_ptr() as *const c_char,
2775 ptr::null(),
2776 0,
2777 )
2778 };
2779 assert!(!doc.is_null());
2780
2781 let mut ctxt = RelaxNgValidCtxt::new();
2782 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2783 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2784
2785 assert!(
2786 !valid,
2787 "Validation should have failed for no matching choice"
2788 );
2789 }
2790
2791 #[test]
2792 fn test_validate_grammar_with_ref() {
2793 let schema_xml = r#"<?xml version="1.0"?>
2794<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2795 <define name="para">
2796 <element name="p">
2797 <text/>
2798 </element>
2799 </define>
2800 <start>
2801 <element name="doc">
2802 <zeroOrMore>
2803 <ref name="para"/>
2804 </zeroOrMore>
2805 </element>
2806 </start>
2807</grammar>"#;
2808
2809 let doc_xml = r#"<?xml version="1.0"?>
2810<doc><p>First</p><p>Second</p></doc>"#;
2811
2812 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2813
2814 let doc = unsafe {
2815 crate::abi::exports_xml2::xmlReadMemory(
2816 doc_xml.as_ptr() as *const c_char,
2817 doc_xml.len() as c_int,
2818 b"test.xml\0".as_ptr() as *const c_char,
2819 ptr::null(),
2820 0,
2821 )
2822 };
2823 assert!(!doc.is_null());
2824
2825 let mut ctxt = RelaxNgValidCtxt::new();
2826 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2827 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2828
2829 assert!(valid, "Ref validation failed: {:?}", ctxt.errors);
2830 }
2831
2832 #[test]
2833 fn test_validate_zero_or_more() {
2834 let schema_xml = r#"<?xml version="1.0"?>
2835<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2836 <zeroOrMore>
2837 <element name="item">
2838 <text/>
2839 </element>
2840 </zeroOrMore>
2841</element>"#;
2842
2843 let doc_xml = r#"<?xml version="1.0"?>
2844<root><item>A</item><item>B</item></root>"#;
2845
2846 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2847
2848 let doc = unsafe {
2849 crate::abi::exports_xml2::xmlReadMemory(
2850 doc_xml.as_ptr() as *const c_char,
2851 doc_xml.len() as c_int,
2852 b"test.xml\0".as_ptr() as *const c_char,
2853 ptr::null(),
2854 0,
2855 )
2856 };
2857 assert!(!doc.is_null());
2858
2859 let mut ctxt = RelaxNgValidCtxt::new();
2860 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2861 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2862
2863 assert!(valid, "zeroOrMore validation failed: {:?}", ctxt.errors);
2864 }
2865
2866 #[test]
2867 fn test_validate_zero_or_more_empty() {
2868 let schema_xml = r#"<?xml version="1.0"?>
2869<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2870 <zeroOrMore>
2871 <element name="item">
2872 <text/>
2873 </element>
2874 </zeroOrMore>
2875</element>"#;
2876
2877 let doc_xml = r#"<?xml version="1.0"?>
2878<root></root>"#;
2879
2880 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2881
2882 let doc = unsafe {
2883 crate::abi::exports_xml2::xmlReadMemory(
2884 doc_xml.as_ptr() as *const c_char,
2885 doc_xml.len() as c_int,
2886 b"test.xml\0".as_ptr() as *const c_char,
2887 ptr::null(),
2888 0,
2889 )
2890 };
2891 assert!(!doc.is_null());
2892
2893 let mut ctxt = RelaxNgValidCtxt::new();
2894 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2895 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2896
2897 assert!(valid, "Empty zeroOrMore should be valid");
2898 }
2899
2900 #[test]
2901 fn test_validate_one_or_more() {
2902 let schema_xml = r#"<?xml version="1.0"?>
2903<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2904 <oneOrMore>
2905 <element name="item">
2906 <text/>
2907 </element>
2908 </oneOrMore>
2909</element>"#;
2910
2911 let doc_xml = r#"<?xml version="1.0"?>
2912<root><item>Single</item></root>"#;
2913
2914 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2915
2916 let doc = unsafe {
2917 crate::abi::exports_xml2::xmlReadMemory(
2918 doc_xml.as_ptr() as *const c_char,
2919 doc_xml.len() as c_int,
2920 b"test.xml\0".as_ptr() as *const c_char,
2921 ptr::null(),
2922 0,
2923 )
2924 };
2925 assert!(!doc.is_null());
2926
2927 let mut ctxt = RelaxNgValidCtxt::new();
2928 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2929 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2930
2931 assert!(valid, "oneOrMore validation failed: {:?}", ctxt.errors);
2932 }
2933
2934 #[test]
2935 fn test_validate_optional_present() {
2936 let schema_xml = r#"<?xml version="1.0"?>
2937<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2938 <optional>
2939 <element name="opt">
2940 <text/>
2941 </element>
2942 </optional>
2943 <text/>
2944</element>"#;
2945
2946 let doc_xml = r#"<?xml version="1.0"?>
2947<root><opt>present</opt>text</root>"#;
2948
2949 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2950
2951 let doc = unsafe {
2952 crate::abi::exports_xml2::xmlReadMemory(
2953 doc_xml.as_ptr() as *const c_char,
2954 doc_xml.len() as c_int,
2955 b"test.xml\0".as_ptr() as *const c_char,
2956 ptr::null(),
2957 0,
2958 )
2959 };
2960 assert!(!doc.is_null());
2961
2962 let mut ctxt = RelaxNgValidCtxt::new();
2963 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2964 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2965
2966 assert!(
2967 valid,
2968 "Optional present validation failed: {:?}",
2969 ctxt.errors
2970 );
2971 }
2972
2973 #[test]
2974 fn test_validate_optional_absent() {
2975 let schema_xml = r#"<?xml version="1.0"?>
2976<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2977 <optional>
2978 <element name="opt">
2979 <text/>
2980 </element>
2981 </optional>
2982 <text/>
2983</element>"#;
2984
2985 let doc_xml = r#"<?xml version="1.0"?>
2986<root>text only</root>"#;
2987
2988 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2989
2990 let doc = unsafe {
2991 crate::abi::exports_xml2::xmlReadMemory(
2992 doc_xml.as_ptr() as *const c_char,
2993 doc_xml.len() as c_int,
2994 b"test.xml\0".as_ptr() as *const c_char,
2995 ptr::null(),
2996 0,
2997 )
2998 };
2999 assert!(!doc.is_null());
3000
3001 let mut ctxt = RelaxNgValidCtxt::new();
3002 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3003 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3004
3005 assert!(
3006 valid,
3007 "Optional absent validation failed: {:?}",
3008 ctxt.errors
3009 );
3010 }
3011
3012 #[test]
3013 fn test_validate_sequence() {
3014 let schema_xml = r#"<?xml version="1.0"?>
3015<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3016 <start>
3017 <element name="root">
3018 <sequence>
3019 <element name="first">
3020 <text/>
3021 </element>
3022 <element name="second">
3023 <text/>
3024 </element>
3025 </sequence>
3026 </element>
3027 </start>
3028</grammar>"#;
3029
3030 let doc_xml = r#"<?xml version="1.0"?>
3031<root><first>First</first><second>Second</second></root>"#;
3032
3033 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3034
3035 let doc = unsafe {
3036 crate::abi::exports_xml2::xmlReadMemory(
3037 doc_xml.as_ptr() as *const c_char,
3038 doc_xml.len() as c_int,
3039 b"test.xml\0".as_ptr() as *const c_char,
3040 ptr::null(),
3041 0,
3042 )
3043 };
3044 assert!(!doc.is_null());
3045
3046 let mut ctxt = RelaxNgValidCtxt::new();
3047 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3048 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3049
3050 assert!(valid, "Sequence validation failed: {:?}", ctxt.errors);
3051 }
3052
3053 #[test]
3054 fn test_validate_data_pattern() {
3055 let schema_xml = r#"<?xml version="1.0"?>
3056<element name="age" xmlns="http://relaxng.org/ns/structure/1.0">
3057 <data type="integer"/>
3058</element>"#;
3059
3060 let doc_xml = r#"<?xml version="1.0"?>
3061<age>25</age>"#;
3062
3063 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3064
3065 let doc = unsafe {
3066 crate::abi::exports_xml2::xmlReadMemory(
3067 doc_xml.as_ptr() as *const c_char,
3068 doc_xml.len() as c_int,
3069 b"test.xml\0".as_ptr() as *const c_char,
3070 ptr::null(),
3071 0,
3072 )
3073 };
3074 assert!(!doc.is_null());
3075
3076 let mut ctxt = RelaxNgValidCtxt::new();
3077 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3078 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3079
3080 assert!(valid, "Data pattern validation failed: {:?}", ctxt.errors);
3081 }
3082
3083 #[test]
3084 fn test_validate_data_pattern_invalid() {
3085 let schema_xml = r#"<?xml version="1.0"?>
3086<element name="age" xmlns="http://relaxng.org/ns/structure/1.0">
3087 <data type="integer"/>
3088</element>"#;
3089
3090 let doc_xml = r#"<?xml version="1.0"?>
3091<age>not-a-number</age>"#;
3092
3093 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3094
3095 let doc = unsafe {
3096 crate::abi::exports_xml2::xmlReadMemory(
3097 doc_xml.as_ptr() as *const c_char,
3098 doc_xml.len() as c_int,
3099 b"test.xml\0".as_ptr() as *const c_char,
3100 ptr::null(),
3101 0,
3102 )
3103 };
3104 assert!(!doc.is_null());
3105
3106 let mut ctxt = RelaxNgValidCtxt::new();
3107 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3108 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3109
3110 assert!(!valid, "Validation should have failed for invalid integer");
3111 }
3112
3113 #[test]
3114 fn test_validate_value_pattern() {
3115 let schema_xml = r#"<?xml version="1.0"?>
3116<element name="status" xmlns="http://relaxng.org/ns/structure/1.0">
3117 <value>active</value>
3118</element>"#;
3119
3120 let doc_xml = r#"<?xml version="1.0"?>
3121<status>active</status>"#;
3122
3123 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3124
3125 let doc = unsafe {
3126 crate::abi::exports_xml2::xmlReadMemory(
3127 doc_xml.as_ptr() as *const c_char,
3128 doc_xml.len() as c_int,
3129 b"test.xml\0".as_ptr() as *const c_char,
3130 ptr::null(),
3131 0,
3132 )
3133 };
3134 assert!(!doc.is_null());
3135
3136 let mut ctxt = RelaxNgValidCtxt::new();
3137 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3138 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3139
3140 assert!(valid, "Value pattern validation failed: {:?}", ctxt.errors);
3141 }
3142
3143 #[test]
3144 fn test_validate_value_pattern_mismatch() {
3145 let schema_xml = r#"<?xml version="1.0"?>
3146<element name="status" xmlns="http://relaxng.org/ns/structure/1.0">
3147 <value>active</value>
3148</element>"#;
3149
3150 let doc_xml = r#"<?xml version="1.0"?>
3151<status>inactive</status>"#;
3152
3153 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3154
3155 let doc = unsafe {
3156 crate::abi::exports_xml2::xmlReadMemory(
3157 doc_xml.as_ptr() as *const c_char,
3158 doc_xml.len() as c_int,
3159 b"test.xml\0".as_ptr() as *const c_char,
3160 ptr::null(),
3161 0,
3162 )
3163 };
3164 assert!(!doc.is_null());
3165
3166 let mut ctxt = RelaxNgValidCtxt::new();
3167 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3168 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3169
3170 assert!(!valid, "Validation should have failed for value mismatch");
3171 }
3172
3173 #[test]
3174 fn test_validate_not_allowed() {
3175 let schema_xml = r#"<?xml version="1.0"?>
3176<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3177 <notAllowed/>
3178</element>"#;
3179
3180 let doc_xml = r#"<?xml version="1.0"?>
3181<root>should not be allowed</root>"#;
3182
3183 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3184
3185 let doc = unsafe {
3186 crate::abi::exports_xml2::xmlReadMemory(
3187 doc_xml.as_ptr() as *const c_char,
3188 doc_xml.len() as c_int,
3189 b"test.xml\0".as_ptr() as *const c_char,
3190 ptr::null(),
3191 0,
3192 )
3193 };
3194 assert!(!doc.is_null());
3195
3196 let mut ctxt = RelaxNgValidCtxt::new();
3197 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3198 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3199
3200 assert!(!valid, "notAllowed should cause validation failure");
3201 }
3202
3203 #[test]
3204 fn test_validate_interleave() {
3205 let schema_xml = r#"<?xml version="1.0"?>
3206<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3207 <start>
3208 <element name="root">
3209 <interleave>
3210 <element name="a">
3211 <text/>
3212 </element>
3213 <element name="b">
3214 <text/>
3215 </element>
3216 </interleave>
3217 </element>
3218 </start>
3219</grammar>"#;
3220
3221 let doc_xml = r#"<?xml version="1.0"?>
3222<root><a>A</a><b>B</b></root>"#;
3223
3224 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3225
3226 let doc = unsafe {
3227 crate::abi::exports_xml2::xmlReadMemory(
3228 doc_xml.as_ptr() as *const c_char,
3229 doc_xml.len() as c_int,
3230 b"test.xml\0".as_ptr() as *const c_char,
3231 ptr::null(),
3232 0,
3233 )
3234 };
3235 assert!(!doc.is_null());
3236
3237 let mut ctxt = RelaxNgValidCtxt::new();
3238 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3239 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3240
3241 assert!(valid, "Interleave validation failed: {:?}", ctxt.errors);
3242 }
3243
3244 #[test]
3245 fn test_validate_empty_element() {
3246 let schema_xml = r#"<?xml version="1.0"?>
3247<element name="br" xmlns="http://relaxng.org/ns/structure/1.0">
3248 <empty/>
3249</element>"#;
3250
3251 let doc_xml = r#"<?xml version="1.0"?>
3252<br/>"#;
3253
3254 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3255
3256 let doc = unsafe {
3257 crate::abi::exports_xml2::xmlReadMemory(
3258 doc_xml.as_ptr() as *const c_char,
3259 doc_xml.len() as c_int,
3260 b"test.xml\0".as_ptr() as *const c_char,
3261 ptr::null(),
3262 0,
3263 )
3264 };
3265 assert!(!doc.is_null());
3266
3267 let mut ctxt = RelaxNgValidCtxt::new();
3268 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3269 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3270
3271 assert!(valid, "Empty element validation failed: {:?}", ctxt.errors);
3272 }
3273
3274 #[test]
3277 fn test_validate_datatype_string() {
3278 assert!(rng_validate_datatype_value(Some("string"), "hello"));
3279 assert!(rng_validate_datatype_value(Some("string"), ""));
3280 }
3281
3282 #[test]
3283 fn test_validate_datatype_boolean() {
3284 assert!(rng_validate_datatype_value(Some("boolean"), "true"));
3285 assert!(rng_validate_datatype_value(Some("boolean"), "false"));
3286 assert!(rng_validate_datatype_value(Some("boolean"), "1"));
3287 assert!(rng_validate_datatype_value(Some("boolean"), "0"));
3288 assert!(!rng_validate_datatype_value(Some("boolean"), "yes"));
3289 assert!(!rng_validate_datatype_value(Some("boolean"), "no"));
3290 }
3291
3292 #[test]
3293 fn test_validate_datatype_integer() {
3294 assert!(rng_validate_datatype_value(Some("integer"), "42"));
3295 assert!(rng_validate_datatype_value(Some("integer"), "-42"));
3296 assert!(rng_validate_datatype_value(Some("integer"), "+42"));
3297 assert!(!rng_validate_datatype_value(Some("integer"), "12.5"));
3298 assert!(!rng_validate_datatype_value(Some("integer"), "abc"));
3299 assert!(!rng_validate_datatype_value(Some("integer"), ""));
3300 }
3301
3302 #[test]
3303 fn test_validate_datatype_decimal() {
3304 assert!(rng_validate_datatype_value(Some("decimal"), "42"));
3305 assert!(rng_validate_datatype_value(Some("decimal"), "12.5"));
3306 assert!(rng_validate_datatype_value(Some("decimal"), "-3.14"));
3307 assert!(!rng_validate_datatype_value(Some("decimal"), ""));
3308 }
3309
3310 #[test]
3311 fn test_validate_datatype_float() {
3312 assert!(rng_validate_datatype_value(Some("float"), "3.14"));
3313 assert!(rng_validate_datatype_value(Some("float"), "INF"));
3314 assert!(rng_validate_datatype_value(Some("float"), "-INF"));
3315 assert!(rng_validate_datatype_value(Some("float"), "NaN"));
3316 assert!(!rng_validate_datatype_value(Some("float"), ""));
3317 }
3318
3319 #[test]
3320 fn test_validate_datatype_ncname() {
3321 assert!(rng_validate_datatype_value(Some("NCName"), "myElement"));
3322 assert!(rng_validate_datatype_value(Some("NCName"), "_foo"));
3323 assert!(!rng_validate_datatype_value(Some("NCName"), "123abc"));
3324 assert!(!rng_validate_datatype_value(Some("NCName"), ""));
3325 }
3326
3327 #[test]
3328 fn test_validate_datatype_any_uri() {
3329 assert!(rng_validate_datatype_value(
3330 Some("anyURI"),
3331 "http://example.com"
3332 ));
3333 assert!(rng_validate_datatype_value(Some("anyURI"), "urn:isbn:1234"));
3334 assert!(!rng_validate_datatype_value(Some("anyURI"), ""));
3335 assert!(!rng_validate_datatype_value(Some("anyURI"), "has space"));
3336 }
3337
3338 #[test]
3339 fn test_validate_datatype_qname() {
3340 assert!(rng_validate_datatype_value(Some("QName"), "ns:local"));
3341 assert!(rng_validate_datatype_value(Some("QName"), "local"));
3342 assert!(!rng_validate_datatype_value(Some("QName"), ""));
3343 }
3344
3345 #[test]
3348 fn test_c_abi_new_parse_free() {
3349 let schema_xml = r#"<?xml version="1.0"?>
3350<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3351 <text/>
3352</element>"#;
3353
3354 let ctxt = unsafe {
3355 xmlRelaxNGNewMemParserCtxt(
3356 schema_xml.as_ptr() as *const c_char,
3357 schema_xml.len() as c_int,
3358 )
3359 };
3360 assert!(!ctxt.is_null(), "Parser context should not be null");
3361
3362 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3363 assert!(!schema.is_null(), "Schema should not be null");
3364
3365 unsafe { xmlRelaxNGFree(schema) };
3367 }
3368
3369 #[test]
3370 fn test_c_abi_validate_doc() {
3371 let schema_xml = r#"<?xml version="1.0"?>
3372<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3373 <text/>
3374</element>"#;
3375
3376 let doc_xml = r#"<?xml version="1.0"?>
3377<root>Hello</root>"#;
3378
3379 let ctxt = unsafe {
3380 xmlRelaxNGNewMemParserCtxt(
3381 schema_xml.as_ptr() as *const c_char,
3382 schema_xml.len() as c_int,
3383 )
3384 };
3385 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3386 assert!(!schema.is_null());
3387
3388 let valid_ctxt = unsafe { xmlRelaxNGNewValidCtxt(schema) };
3389 assert!(!valid_ctxt.is_null());
3390
3391 let doc = unsafe {
3392 crate::abi::exports_xml2::xmlReadMemory(
3393 doc_xml.as_ptr() as *const c_char,
3394 doc_xml.len() as c_int,
3395 b"test.xml\0".as_ptr() as *const c_char,
3396 ptr::null(),
3397 0,
3398 )
3399 };
3400 assert!(!doc.is_null());
3401
3402 let result = unsafe { xmlRelaxNGValidateDoc(valid_ctxt, doc) };
3403 assert_eq!(result, 0, "Validation should succeed");
3404
3405 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3406 unsafe { xmlRelaxNGFreeValidCtxt(valid_ctxt) };
3407 unsafe { xmlRelaxNGFree(schema) };
3408 }
3409
3410 #[test]
3411 fn test_c_abi_validate_full_element() {
3412 let schema_xml = r#"<?xml version="1.0"?>
3413<element name="item" xmlns="http://relaxng.org/ns/structure/1.0">
3414 <text/>
3415</element>"#;
3416
3417 let doc_xml = r#"<?xml version="1.0"?>
3418<root><item>Content</item></root>"#;
3419
3420 let ctxt = unsafe {
3421 xmlRelaxNGNewMemParserCtxt(
3422 schema_xml.as_ptr() as *const c_char,
3423 schema_xml.len() as c_int,
3424 )
3425 };
3426 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3427 assert!(!schema.is_null());
3428
3429 let valid_ctxt = unsafe { xmlRelaxNGNewValidCtxt(schema) };
3430 assert!(!valid_ctxt.is_null());
3431
3432 let doc = unsafe {
3433 crate::abi::exports_xml2::xmlReadMemory(
3434 doc_xml.as_ptr() as *const c_char,
3435 doc_xml.len() as c_int,
3436 b"test.xml\0".as_ptr() as *const c_char,
3437 ptr::null(),
3438 0,
3439 )
3440 };
3441 assert!(!doc.is_null());
3442
3443 let item = unsafe {
3445 let mut node = (*doc).children;
3447 while !node.is_null() {
3448 if (*node).type_ == XML_ELEMENT_NODE as c_int {
3449 break;
3450 }
3451 node = (*node).next;
3452 }
3453 if !node.is_null() {
3454 node = (*node).children;
3456 while !node.is_null() {
3457 if (*node).type_ == XML_ELEMENT_NODE as c_int {
3458 break;
3459 }
3460 node = (*node).next;
3461 }
3462 }
3463 node
3464 };
3465 assert!(!item.is_null(), "Should find <item> element");
3466
3467 let result = unsafe { xmlRelaxNGValidateFullElement(valid_ctxt, doc, item) };
3468 assert_eq!(result, 0, "Element validation should succeed");
3469
3470 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3471 unsafe { xmlRelaxNGFreeValidCtxt(valid_ctxt) };
3472 unsafe { xmlRelaxNGFree(schema) };
3473 }
3474
3475 #[test]
3476 fn test_c_abi_null_handling() {
3477 assert_eq!(
3479 unsafe { xmlRelaxNGValidateDoc(ptr::null_mut(), ptr::null_mut()) },
3480 -1
3481 );
3482 assert_eq!(
3483 unsafe {
3484 xmlRelaxNGValidateFullElement(ptr::null_mut(), ptr::null_mut(), ptr::null_mut())
3485 },
3486 -1
3487 );
3488 assert!(unsafe { xmlRelaxNGNewMemParserCtxt(ptr::null(), 0).is_null() });
3489
3490 unsafe { xmlRelaxNGFree(ptr::null_mut()) };
3492 unsafe { xmlRelaxNGFreeParserCtxt(ptr::null_mut()) };
3493 unsafe { xmlRelaxNGFreeValidCtxt(ptr::null_mut()) };
3494 }
3495
3496 #[test]
3499 fn test_parse_with_div() {
3500 let schema_xml = r#"<?xml version="1.0"?>
3501<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3502 <div>
3503 <define name="shared">
3504 <text/>
3505 </define>
3506 </div>
3507 <start>
3508 <element name="root">
3509 <ref name="shared"/>
3510 </element>
3511 </start>
3512</grammar>"#;
3513
3514 let result = rng_parse(schema_xml);
3515 assert!(
3516 result.is_ok(),
3517 "Failed to parse with div: {:?}",
3518 result.err()
3519 );
3520 let schema = result.unwrap();
3521 assert_eq!(schema.grammar.defines.len(), 1);
3522 assert_eq!(schema.grammar.defines[0].name, "shared");
3523 }
3524
3525 #[test]
3526 fn test_validate_list_pattern() {
3527 let schema_xml = r#"<?xml version="1.0"?>
3528<element name="tokens" xmlns="http://relaxng.org/ns/structure/1.0">
3529 <list>
3530 <data type="token"/>
3531 </list>
3532</element>"#;
3533
3534 let doc_xml = r#"<?xml version="1.0"?>
3535<tokens>abc def ghi</tokens>"#;
3536
3537 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3538
3539 let doc = unsafe {
3540 crate::abi::exports_xml2::xmlReadMemory(
3541 doc_xml.as_ptr() as *const c_char,
3542 doc_xml.len() as c_int,
3543 b"test.xml\0".as_ptr() as *const c_char,
3544 ptr::null(),
3545 0,
3546 )
3547 };
3548 assert!(!doc.is_null());
3549
3550 let mut ctxt = RelaxNgValidCtxt::new();
3551 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3552 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3553
3554 assert!(valid, "List pattern validation failed: {:?}", ctxt.errors);
3555 }
3556
3557 #[test]
3558 fn test_validate_group_pattern() {
3559 let schema_xml = r#"<?xml version="1.0"?>
3560<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3561 <start>
3562 <element name="root">
3563 <group>
3564 <element name="a">
3565 <text/>
3566 </element>
3567 <element name="b">
3568 <text/>
3569 </element>
3570 </group>
3571 </element>
3572 </start>
3573</grammar>"#;
3574
3575 let doc_xml = r#"<?xml version="1.0"?>
3576<root><a>First</a><b>Second</b></root>"#;
3577
3578 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3579
3580 let doc = unsafe {
3581 crate::abi::exports_xml2::xmlReadMemory(
3582 doc_xml.as_ptr() as *const c_char,
3583 doc_xml.len() as c_int,
3584 b"test.xml\0".as_ptr() as *const c_char,
3585 ptr::null(),
3586 0,
3587 )
3588 };
3589 assert!(!doc.is_null());
3590
3591 let mut ctxt = RelaxNgValidCtxt::new();
3592 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3593 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3594
3595 assert!(valid, "Group validation failed: {:?}", ctxt.errors);
3596 }
3597
3598 #[test]
3599 fn test_validate_undefined_ref() {
3600 let schema_xml = r#"<?xml version="1.0"?>
3601<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3602 <start>
3603 <element name="root">
3604 <ref name="undefined"/>
3605 </element>
3606 </start>
3607</grammar>"#;
3608
3609 let doc_xml = r#"<?xml version="1.0"?>
3610<root>Content</root>"#;
3611
3612 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3613
3614 let doc = unsafe {
3615 crate::abi::exports_xml2::xmlReadMemory(
3616 doc_xml.as_ptr() as *const c_char,
3617 doc_xml.len() as c_int,
3618 b"test.xml\0".as_ptr() as *const c_char,
3619 ptr::null(),
3620 0,
3621 )
3622 };
3623 assert!(!doc.is_null());
3624
3625 let mut ctxt = RelaxNgValidCtxt::new();
3626 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3627 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3628
3629 assert!(!valid, "Undefined ref should cause failure");
3630 }
3631
3632 #[test]
3633 fn test_validate_null_doc() {
3634 let schema = RelaxNgSchema::new();
3635 let mut ctxt = RelaxNgValidCtxt::new();
3636 let valid = unsafe { rng_validate_doc(&schema, ptr::null_mut(), &mut ctxt) };
3637 assert!(!valid);
3638 }
3639
3640 #[test]
3641 fn test_parse_external_ref_schema() {
3642 let schema_xml = r#"<?xml version="1.0"?>
3643<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3644 <externalRef href="external.rng"/>
3645</element>"#;
3646
3647 let result = rng_parse(schema_xml);
3648 assert!(
3649 result.is_ok(),
3650 "Failed to parse externalRef: {:?}",
3651 result.err()
3652 );
3653 let schema = result.unwrap();
3654 if let Some(ref start) = schema.grammar.start {
3655 assert_eq!(start.pattern_type, RelaxNgPatternType::Element);
3656 assert_eq!(start.name.as_deref(), Some("root"));
3657 }
3658 }
3659
3660 #[test]
3661 fn test_validate_boolean_datatype() {
3662 assert!(rng_validate_datatype_value(Some("boolean"), "true"));
3663 assert!(rng_validate_datatype_value(Some("boolean"), "false"));
3664 assert!(!rng_validate_datatype_value(Some("boolean"), "maybe"));
3665 }
3666
3667 #[test]
3668 fn test_validate_unknown_datatype() {
3669 assert!(rng_validate_datatype_value(Some("custom-type"), "anything"));
3671 }
3672
3673 #[test]
3674 fn test_validate_no_datatype() {
3675 assert!(rng_validate_datatype_value(None, "anything"));
3677 }
3678
3679 #[test]
3680 fn test_parse_schema_with_ns_prefix() {
3681 let schema_xml = r#"<?xml version="1.0"?>
3682<rng:element name="root" xmlns:rng="http://relaxng.org/ns/structure/1.0">
3683 <rng:text/>
3684</rng:element>"#;
3685
3686 let result = rng_parse(schema_xml);
3687 assert!(result.is_ok(), "Failed with ns prefix: {:?}", result.err());
3688 }
3689
3690 #[test]
3691 fn test_validation_context_path() {
3692 let mut ctxt = RelaxNgValidCtxt::new();
3693 assert_eq!(ctxt.current_path(), "/");
3694
3695 ctxt.path.push("root".to_string());
3696 assert_eq!(ctxt.current_path(), "/root");
3697
3698 ctxt.path.push("child".to_string());
3699 assert_eq!(ctxt.current_path(), "/root/child");
3700
3701 ctxt.path.pop();
3702 assert_eq!(ctxt.current_path(), "/root");
3703 }
3704
3705 #[test]
3706 fn test_validate_sequence_wrong_order() {
3707 let schema_xml = r#"<?xml version="1.0"?>
3708<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3709 <start>
3710 <element name="root">
3711 <sequence>
3712 <element name="first">
3713 <text/>
3714 </element>
3715 <element name="second">
3716 <text/>
3717 </element>
3718 </sequence>
3719 </element>
3720 </start>
3721</grammar>"#;
3722
3723 let doc_xml = r#"<?xml version="1.0"?>
3724<root><second>Wrong</second><first>Order</first></root>"#;
3725
3726 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3727
3728 let doc = unsafe {
3729 crate::abi::exports_xml2::xmlReadMemory(
3730 doc_xml.as_ptr() as *const c_char,
3731 doc_xml.len() as c_int,
3732 b"test.xml\0".as_ptr() as *const c_char,
3733 ptr::null(),
3734 0,
3735 )
3736 };
3737 assert!(!doc.is_null());
3738
3739 let mut ctxt = RelaxNgValidCtxt::new();
3740 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3741 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3742
3743 assert!(!valid, "Wrong sequence order should fail");
3746 }
3747}