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