1#![allow(
78 missing_docs,
79 non_snake_case,
80 non_camel_case_types,
81 non_upper_case_globals
82)]
83
84use core::ffi::c_void;
85use core::ptr;
86use std::os::raw::{c_char, c_int};
87
88use crate::abi::allocator;
89use crate::abi::structs::*;
90use crate::abi::types::xmlElementType::*;
91
92#[derive(Debug, Clone, PartialEq)]
103pub enum RelaxNgNameClass {
104 Name(String),
106 AnyName,
108 NsName(String),
110 Choice(Vec<RelaxNgNameClass>),
112 Except(Box<RelaxNgNameClass>, Box<RelaxNgNameClass>),
114}
115
116impl RelaxNgNameClass {
117 pub fn matches(&self, name: &str, ns_uri: Option<&str>) -> bool {
119 match self {
120 RelaxNgNameClass::Name(n) => name == n.as_str(),
121 RelaxNgNameClass::AnyName => true,
122 RelaxNgNameClass::NsName(ns) => {
123 if let Some(uri) = ns_uri {
124 uri == ns.as_str()
125 } else {
126 false
127 }
128 }
129 RelaxNgNameClass::Choice(choices) => choices.iter().any(|c| c.matches(name, ns_uri)),
130 RelaxNgNameClass::Except(positive, negative) => {
131 positive.matches(name, ns_uri) && !negative.matches(name, ns_uri)
132 }
133 }
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum RelaxNgPatternType {
148 Element,
149 Attribute,
150 Text,
151 Choice,
152 Sequence,
153 Interleave,
154 ZeroOrMore,
155 OneOrMore,
156 Optional,
157 List,
158 Group,
159 Data,
160 Value,
161 Ref,
162 Define,
163 Grammar,
164 NotAllowed,
165 Empty,
166 ExternalRef,
167 Include,
168 Start,
169}
170
171#[derive(Debug, Clone)]
175pub struct RelaxNgPattern {
176 pub pattern_type: RelaxNgPatternType,
178 pub name_class: Option<RelaxNgNameClass>,
180 pub children: Vec<RelaxNgPattern>,
182 pub name: Option<String>,
184 pub ns: Option<String>,
186 pub datatype: Option<String>,
188 pub value: Option<String>,
190 pub datatype_library: Option<String>,
192}
193
194impl RelaxNgPattern {
195 pub const fn new(pattern_type: RelaxNgPatternType) -> Self {
197 Self {
198 pattern_type,
199 name_class: None,
200 children: Vec::new(),
201 name: None,
202 ns: None,
203 datatype: None,
204 value: None,
205 datatype_library: None,
206 }
207 }
208
209 pub fn element(name: &str) -> Self {
211 let mut p = Self::new(RelaxNgPatternType::Element);
212 p.name_class = Some(RelaxNgNameClass::Name(name.to_string()));
213 p
214 }
215
216 pub fn attribute(name: &str) -> Self {
218 let mut p = Self::new(RelaxNgPatternType::Attribute);
219 p.name_class = Some(RelaxNgNameClass::Name(name.to_string()));
220 p
221 }
222
223 pub const fn text() -> Self {
225 Self::new(RelaxNgPatternType::Text)
226 }
227
228 pub const fn empty() -> Self {
230 Self::new(RelaxNgPatternType::Empty)
231 }
232
233 pub const fn not_allowed() -> Self {
235 Self::new(RelaxNgPatternType::NotAllowed)
236 }
237}
238
239#[derive(Debug, Clone)]
245pub struct RelaxNgDefine {
246 pub name: String,
248 pub pattern: RelaxNgPattern,
250}
251
252#[derive(Debug, Clone)]
260pub struct RelaxNgGrammar {
261 pub defines: Vec<RelaxNgDefine>,
263 pub start: Option<RelaxNgPattern>,
265 pub includes: Vec<RelaxNgGrammar>,
267}
268
269impl RelaxNgGrammar {
270 pub const fn new() -> Self {
271 Self {
272 defines: Vec::new(),
273 start: None,
274 includes: Vec::new(),
275 }
276 }
277
278 pub fn lookup(&self, name: &str) -> Option<&RelaxNgPattern> {
280 for def in &self.defines {
281 if def.name == name {
282 return Some(&def.pattern);
283 }
284 }
285 for inc in &self.includes {
287 if let Some(p) = inc.lookup(name) {
288 return Some(p);
289 }
290 }
291 None
292 }
293}
294
295impl Default for RelaxNgGrammar {
296 fn default() -> Self {
297 Self::new()
298 }
299}
300
301#[derive(Debug, Clone)]
309pub struct RelaxNgSchema {
310 pub grammar: RelaxNgGrammar,
312 pub errors: Vec<String>,
314}
315
316impl RelaxNgSchema {
317 pub const fn new() -> Self {
318 Self {
319 grammar: RelaxNgGrammar::new(),
320 errors: Vec::new(),
321 }
322 }
323}
324
325impl Default for RelaxNgSchema {
326 fn default() -> Self {
327 Self::new()
328 }
329}
330
331#[derive(Debug)]
341pub struct RelaxNgValidCtxt {
342 pub schema: Option<RelaxNgSchema>,
344 pub errors: Vec<String>,
346 pub nb_errors: i32,
348 pub path: Vec<String>,
350 pub depth_max: i32,
352 pub depth: i32,
354}
355
356impl RelaxNgValidCtxt {
357 pub const fn new() -> Self {
358 Self {
359 schema: None,
360 errors: Vec::new(),
361 nb_errors: 0,
362 path: Vec::new(),
363 depth_max: 256,
364 depth: 0,
365 }
366 }
367
368 pub fn record_error(&mut self, msg: String) {
370 self.errors.push(msg);
371 self.nb_errors += 1;
372 }
373
374 pub fn current_path(&self) -> String {
376 if self.path.is_empty() {
377 "/".to_string()
378 } else {
379 format!("/{}", self.path.join("/"))
380 }
381 }
382}
383
384impl Default for RelaxNgValidCtxt {
385 fn default() -> Self {
386 Self::new()
387 }
388}
389
390unsafe fn get_local_name(node: *mut _xmlNode) -> String {
400 if node.is_null() {
401 return String::new();
402 }
403 unsafe {
404 let name = (*node).name;
405 if name.is_null() {
406 return String::new();
407 }
408 let mut len = 0;
409 while *name.add(len) != 0 {
410 len += 1;
411 }
412 let slice = std::slice::from_raw_parts(name, len);
413 if let Ok(s) = std::str::from_utf8(slice) {
414 if let Some(pos) = s.find(':') {
415 s[pos + 1..].to_string()
416 } else {
417 s.to_string()
418 }
419 } else {
420 String::new()
421 }
422 }
423}
424
425unsafe fn get_node_text(node: *mut _xmlNode) -> String {
431 if node.is_null() {
432 return String::new();
433 }
434 let mut result = String::new();
435 unsafe {
436 let mut child = (*node).children;
437 while !child.is_null() {
438 if ((*child).type_ == XML_TEXT_NODE as c_int
439 || (*child).type_ == XML_CDATA_SECTION_NODE as c_int)
440 && !(*child).content.is_null()
441 {
442 let content = (*child).content;
443 let mut len = 0;
444 while *content.add(len) != 0 {
445 len += 1;
446 }
447 let slice = std::slice::from_raw_parts(content, len);
448 result.push_str(&String::from_utf8_lossy(slice));
449 }
450 child = (*child).next;
451 }
452 }
453 result
454}
455
456unsafe fn get_node_qname(node: *mut _xmlNode) -> String {
462 if node.is_null() {
463 return String::new();
464 }
465 unsafe {
466 let ns = (*node).ns;
467 let prefix = if !ns.is_null() && !(*ns).prefix.is_null() {
468 let mut len = 0;
469 while *(*ns).prefix.add(len) != 0 {
470 len += 1;
471 }
472 let slice = std::slice::from_raw_parts((*ns).prefix, len);
473 if let Ok(s) = std::str::from_utf8(slice) {
474 format!("{}:", s)
475 } else {
476 String::new()
477 }
478 } else {
479 String::new()
480 };
481
482 let name = (*node).name;
483 if name.is_null() {
484 return String::new();
485 }
486 let mut len = 0;
487 while *name.add(len) != 0 {
488 len += 1;
489 }
490 let slice = std::slice::from_raw_parts(name, len);
491 if let Ok(s) = std::str::from_utf8(slice) {
492 format!("{}{}", prefix, s)
493 } else {
494 String::new()
495 }
496 }
497}
498
499unsafe fn get_node_ns_uri(node: *mut _xmlNode) -> Option<String> {
505 if node.is_null() {
506 return None;
507 }
508 unsafe {
509 let ns = (*node).ns;
510 if ns.is_null() || (*ns).href.is_null() {
511 return None;
512 }
513 let href = (*ns).href;
514 let mut len = 0;
515 while *href.add(len) != 0 {
516 len += 1;
517 }
518 let slice = std::slice::from_raw_parts(href, len);
519 if let Ok(s) = std::str::from_utf8(slice) {
520 Some(s.to_string())
521 } else {
522 None
523 }
524 }
525}
526
527unsafe fn get_attr(node: *mut _xmlNode, name: &str) -> Option<String> {
533 if node.is_null() {
534 return None;
535 }
536 unsafe {
537 let mut prop = (*node).properties;
538 while !prop.is_null() {
539 let prop_name = (*prop).name;
540 if !prop_name.is_null() {
541 let mut len = 0;
542 while *prop_name.add(len) != 0 {
543 len += 1;
544 }
545 let slice = std::slice::from_raw_parts(prop_name, len);
546 if let Ok(s) = std::str::from_utf8(slice) {
547 if s == name {
548 return Some(get_node_text(prop as *mut _xmlNode));
549 }
550 }
551 }
552 prop = (*prop).next;
553 }
554 }
555 None
556}
557
558#[allow(dead_code)]
564unsafe fn node_is(node: *mut _xmlNode, local_name: &str) -> bool {
565 if node.is_null() {
566 return false;
567 }
568 unsafe {
569 let name = (*node).name;
570 if name.is_null() {
571 return false;
572 }
573 let mut len = 0;
574 while *name.add(len) != 0 {
575 len += 1;
576 }
577 let slice = std::slice::from_raw_parts(name, len);
578 if let Ok(s) = std::str::from_utf8(slice) {
579 let local = if let Some(pos) = s.find(':') {
580 &s[pos + 1..]
581 } else {
582 s
583 };
584 return local == local_name;
585 }
586 }
587 false
588}
589
590pub fn rng_parse(xml_doc: &str) -> Result<RelaxNgSchema, String> {
603 let doc_ptr = unsafe {
604 crate::abi::exports_xml2::xmlReadMemory(
605 xml_doc.as_ptr() as *const c_char,
606 xml_doc.len() as c_int,
607 c"schema.rng".as_ptr() as *const c_char,
608 ptr::null(),
609 0,
610 )
611 };
612
613 if doc_ptr.is_null() {
614 return Err("Failed to parse RELAX NG schema XML document".to_string());
615 }
616
617 let result = unsafe { rng_parse_doc(doc_ptr) };
618 unsafe {
619 crate::abi::exports_xml2::xmlFreeDoc(doc_ptr);
620 }
621 result
622}
623
624unsafe fn rng_parse_doc(doc: *mut _xmlDoc) -> Result<RelaxNgSchema, String> {
630 unsafe {
631 let root = (*doc).children;
632 if root.is_null() {
633 return Err("RELAX NG document has no root element".to_string());
634 }
635
636 let mut root_elem = root;
638 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
639 root_elem = (*root_elem).next;
640 }
641
642 if root_elem.is_null() {
643 return Err("RELAX NG document has no root element".to_string());
644 }
645
646 let local_name = get_local_name(root_elem);
647 let mut schema = RelaxNgSchema::new();
648
649 match local_name.as_str() {
650 "grammar" => {
651 schema.grammar = rng_parse_grammar_node(root_elem, &mut schema);
653 Ok(schema)
654 }
655 "element" | "attribute" | "text" | "choice" | "sequence" | "interleave"
656 | "zeroOrMore" | "oneOrMore" | "optional" | "list" | "group" | "data" | "value"
657 | "ref" | "notAllowed" | "empty" | "externalRef" | "define" | "start" | "include" => {
658 let pattern = rng_parse_pattern(root_elem, &mut schema);
660 schema.grammar.start = Some(pattern);
661 Ok(schema)
662 }
663 _ => Err(format!("Unknown RELAX NG root element: '{}'", local_name)),
664 }
665 }
666}
667
668unsafe fn rng_parse_grammar_node(
674 node: *mut _xmlNode,
675 schema: &mut RelaxNgSchema,
676) -> RelaxNgGrammar {
677 unsafe {
678 let mut grammar = RelaxNgGrammar::new();
679
680 let mut child = (*node).children;
681 while !child.is_null() {
682 if (*child).type_ == XML_ELEMENT_NODE as c_int {
683 let local = get_local_name(child);
684 match local.as_str() {
685 "define" => {
686 let def = rng_parse_define(child, schema);
687 grammar.defines.push(def);
688 }
689 "start" => {
690 grammar.start = Some(rng_parse_pattern(child, schema));
691 }
692 "include" => {
693 if let Some(inc) = rng_parse_include(child, schema) {
694 grammar.includes.push(inc);
695 }
696 }
697 "div" => {
698 let sub_grammar = rng_parse_grammar_node(child, schema);
700 grammar.defines.extend(sub_grammar.defines);
701 if sub_grammar.start.is_some() {
702 grammar.start = sub_grammar.start;
703 }
704 grammar.includes.extend(sub_grammar.includes);
705 }
706 _ => {
707 schema
709 .errors
710 .push(format!("Unexpected element '<{}>' in grammar", local));
711 }
712 }
713 }
714 child = (*child).next;
715 }
716
717 grammar
718 }
719}
720
721unsafe fn rng_parse_define(node: *mut _xmlNode, schema: &mut RelaxNgSchema) -> RelaxNgDefine {
727 unsafe {
728 let name = get_attr(node, "name").unwrap_or_default();
729 let pattern = rng_parse_pattern(node, schema);
730 RelaxNgDefine { name, pattern }
731 }
732}
733
734unsafe fn rng_parse_include(
740 node: *mut _xmlNode,
741 _schema: &mut RelaxNgSchema,
742) -> Option<RelaxNgGrammar> {
743 unsafe {
744 let href = get_attr(node, "href");
745 if let Some(url) = href {
746 let url_c = std::ffi::CString::new(url.clone()).ok()?;
749 let doc = crate::abi::exports_xml2::xmlParseFile(url_c.as_ptr());
750 if doc.is_null() {
751 return None;
752 }
753 let mut inc_schema = RelaxNgSchema::new();
754 let grammar = rng_parse_grammar_node(
755 {
756 let mut root = (*doc).children;
757 while !root.is_null() && (*root).type_ != XML_ELEMENT_NODE as c_int {
758 root = (*root).next;
759 }
760 root
761 },
762 &mut inc_schema,
763 );
764 crate::abi::exports_xml2::xmlFreeDoc(doc);
765 Some(grammar)
766 } else {
767 let mut inc_schema = RelaxNgSchema::new();
769 let grammar = rng_parse_grammar_node(node, &mut inc_schema);
770 Some(grammar)
771 }
772 }
773}
774
775unsafe fn rng_parse_pattern(node: *mut _xmlNode, schema: &mut RelaxNgSchema) -> RelaxNgPattern {
781 unsafe {
782 let local = get_local_name(node);
783
784 match local.as_str() {
785 "element" => rng_parse_element_pattern(node, schema),
786 "attribute" => rng_parse_attribute_pattern(node, schema),
787 "text" => RelaxNgPattern::text(),
788 "empty" => RelaxNgPattern::empty(),
789 "notAllowed" => RelaxNgPattern::not_allowed(),
790 "choice" => rng_parse_composite_pattern(node, RelaxNgPatternType::Choice, schema),
791 "sequence" => rng_parse_composite_pattern(node, RelaxNgPatternType::Sequence, schema),
792 "interleave" => {
793 rng_parse_composite_pattern(node, RelaxNgPatternType::Interleave, schema)
794 }
795 "zeroOrMore" => rng_parse_unary_pattern(node, RelaxNgPatternType::ZeroOrMore, schema),
796 "oneOrMore" => rng_parse_unary_pattern(node, RelaxNgPatternType::OneOrMore, schema),
797 "optional" => rng_parse_unary_pattern(node, RelaxNgPatternType::Optional, schema),
798 "list" => rng_parse_unary_pattern(node, RelaxNgPatternType::List, schema),
799 "group" => rng_parse_composite_pattern(node, RelaxNgPatternType::Group, schema),
800 "data" => rng_parse_data_pattern(node, schema),
801 "value" => rng_parse_value_pattern(node, schema),
802 "ref" => rng_parse_ref_pattern(node),
803 "externalRef" => rng_parse_external_ref(node, schema),
804 "define" | "start" | "grammar" | "include" | "div" => {
805 let mut child = (*node).children;
807 let mut result = RelaxNgPattern::empty();
808 while !child.is_null() {
809 if (*child).type_ == XML_ELEMENT_NODE as c_int {
810 result = rng_parse_pattern(child, schema);
811 break;
812 }
813 child = (*child).next;
814 }
815 result
816 }
817 _ => {
818 schema
820 .errors
821 .push(format!("Unknown pattern element '<{}>'", local));
822 RelaxNgPattern::empty()
823 }
824 }
825 }
826}
827
828unsafe fn rng_parse_element_pattern(
834 node: *mut _xmlNode,
835 schema: &mut RelaxNgSchema,
836) -> RelaxNgPattern {
837 unsafe {
838 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Element);
839
840 let name_attr = get_attr(node, "name");
842 pattern.name = name_attr.clone();
843
844 if let Some(ref n) = name_attr {
845 pattern.name_class = Some(RelaxNgNameClass::Name(n.clone()));
846 }
847
848 let mut child = (*node).children;
850 let mut content_found = false;
851
852 while !child.is_null() {
853 if (*child).type_ == XML_ELEMENT_NODE as c_int {
854 let child_local = get_local_name(child);
855
856 match child_local.as_str() {
857 "name" => {
858 let text = get_node_text(child);
859 if !text.is_empty() {
860 pattern.name_class =
861 Some(RelaxNgNameClass::Name(text.trim().to_string()));
862 }
863 }
864 "anyName" => {
865 pattern.name_class = Some(rng_parse_any_name(child));
866 }
867 "nsName" => {
868 pattern.name_class = Some(rng_parse_ns_name(child));
869 }
870 "choice" if pattern.name_class.is_none() => {
871 pattern.name_class = Some(rng_parse_name_class_choice(child));
873 }
874 _ => {
875 if !content_found {
877 pattern.children.push(rng_parse_pattern(child, schema));
878 content_found = true;
879 }
880 }
881 }
882 }
883 child = (*child).next;
884 }
885
886 pattern
887 }
888}
889
890unsafe fn rng_parse_attribute_pattern(
896 node: *mut _xmlNode,
897 schema: &mut RelaxNgSchema,
898) -> RelaxNgPattern {
899 unsafe {
900 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Attribute);
901
902 let name_attr = get_attr(node, "name");
904 pattern.name = name_attr.clone();
905
906 if let Some(ref n) = name_attr {
907 pattern.name_class = Some(RelaxNgNameClass::Name(n.clone()));
908 }
909
910 let mut child = (*node).children;
912 while !child.is_null() {
913 if (*child).type_ == XML_ELEMENT_NODE as c_int {
914 let child_local = get_local_name(child);
915
916 match child_local.as_str() {
917 "name" => {
918 let text = get_node_text(child);
919 if !text.is_empty() {
920 pattern.name_class =
921 Some(RelaxNgNameClass::Name(text.trim().to_string()));
922 }
923 }
924 "anyName" => {
925 pattern.name_class = Some(rng_parse_any_name(child));
926 }
927 "nsName" => {
928 pattern.name_class = Some(rng_parse_ns_name(child));
929 }
930 "choice" if pattern.name_class.is_none() => {
931 pattern.name_class = Some(rng_parse_name_class_choice(child));
932 }
933 _ => {
934 pattern.children.push(rng_parse_pattern(child, schema));
936 }
937 }
938 }
939 child = (*child).next;
940 }
941
942 pattern
943 }
944}
945
946unsafe fn rng_parse_any_name(node: *mut _xmlNode) -> RelaxNgNameClass {
952 unsafe {
953 let mut child = (*node).children;
955 while !child.is_null() {
956 if (*child).type_ == XML_ELEMENT_NODE as c_int && get_local_name(child) == "except" {
957 let except_nc = rng_parse_name_class_content(child);
958 return RelaxNgNameClass::Except(
959 Box::new(RelaxNgNameClass::AnyName),
960 Box::new(except_nc),
961 );
962 }
963 child = (*child).next;
964 }
965 RelaxNgNameClass::AnyName
966 }
967}
968
969unsafe fn rng_parse_ns_name(node: *mut _xmlNode) -> RelaxNgNameClass {
975 unsafe {
976 let ns = get_attr(node, "ns").unwrap_or_default();
977
978 let mut child = (*node).children;
980 while !child.is_null() {
981 if (*child).type_ == XML_ELEMENT_NODE as c_int && get_local_name(child) == "except" {
982 let except_nc = rng_parse_name_class_content(child);
983 return RelaxNgNameClass::Except(
984 Box::new(RelaxNgNameClass::NsName(ns)),
985 Box::new(except_nc),
986 );
987 }
988 child = (*child).next;
989 }
990
991 RelaxNgNameClass::NsName(ns)
992 }
993}
994
995unsafe fn rng_parse_name_class_choice(node: *mut _xmlNode) -> RelaxNgNameClass {
1001 unsafe {
1002 let mut choices = Vec::new();
1003 let mut child = (*node).children;
1004 while !child.is_null() {
1005 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1006 choices.push(rng_parse_name_class_item(child));
1007 }
1008 child = (*child).next;
1009 }
1010 if choices.len() == 1 {
1011 choices.remove(0)
1012 } else {
1013 RelaxNgNameClass::Choice(choices)
1014 }
1015 }
1016}
1017
1018unsafe fn rng_parse_name_class_item(node: *mut _xmlNode) -> RelaxNgNameClass {
1024 unsafe {
1025 let local = get_local_name(node);
1026 match local.as_str() {
1027 "name" => {
1028 let text = get_node_text(node);
1029 RelaxNgNameClass::Name(text.trim().to_string())
1030 }
1031 "anyName" => rng_parse_any_name(node),
1032 "nsName" => rng_parse_ns_name(node),
1033 "choice" => rng_parse_name_class_choice(node),
1034 _ => {
1035 let text = get_node_text(node);
1037 if text.trim().is_empty() {
1038 RelaxNgNameClass::AnyName
1039 } else {
1040 RelaxNgNameClass::Name(text.trim().to_string())
1041 }
1042 }
1043 }
1044 }
1045}
1046
1047unsafe fn rng_parse_name_class_content(node: *mut _xmlNode) -> RelaxNgNameClass {
1053 unsafe {
1054 let mut names = Vec::new();
1055 let mut child = (*node).children;
1056 while !child.is_null() {
1057 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1058 names.push(rng_parse_name_class_item(child));
1059 }
1060 child = (*child).next;
1061 }
1062 if names.is_empty() {
1063 RelaxNgNameClass::AnyName
1064 } else if names.len() == 1 {
1065 names.remove(0)
1066 } else {
1067 RelaxNgNameClass::Choice(names)
1068 }
1069 }
1070}
1071
1072unsafe fn rng_parse_composite_pattern(
1078 node: *mut _xmlNode,
1079 pattern_type: RelaxNgPatternType,
1080 schema: &mut RelaxNgSchema,
1081) -> RelaxNgPattern {
1082 unsafe {
1083 let mut pattern = RelaxNgPattern::new(pattern_type);
1084
1085 let mut child = (*node).children;
1086 while !child.is_null() {
1087 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1088 pattern.children.push(rng_parse_pattern(child, schema));
1089 }
1090 child = (*child).next;
1091 }
1092
1093 pattern
1094 }
1095}
1096
1097unsafe fn rng_parse_unary_pattern(
1103 node: *mut _xmlNode,
1104 pattern_type: RelaxNgPatternType,
1105 schema: &mut RelaxNgSchema,
1106) -> RelaxNgPattern {
1107 unsafe {
1108 let mut pattern = RelaxNgPattern::new(pattern_type);
1109
1110 let mut child = (*node).children;
1111 while !child.is_null() {
1112 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1113 pattern.children.push(rng_parse_pattern(child, schema));
1114 break;
1116 }
1117 child = (*child).next;
1118 }
1119
1120 pattern
1121 }
1122}
1123
1124unsafe fn rng_parse_data_pattern(
1130 node: *mut _xmlNode,
1131 _schema: &mut RelaxNgSchema,
1132) -> RelaxNgPattern {
1133 unsafe {
1134 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Data);
1135 pattern.datatype = get_attr(node, "type");
1136 pattern.datatype_library = get_attr(node, "datatypeLibrary");
1137
1138 pattern
1140 }
1141}
1142
1143unsafe fn rng_parse_value_pattern(
1149 node: *mut _xmlNode,
1150 _schema: &mut RelaxNgSchema,
1151) -> RelaxNgPattern {
1152 unsafe {
1153 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Value);
1154 pattern.datatype = get_attr(node, "type");
1155 pattern.datatype_library = get_attr(node, "datatypeLibrary");
1156 pattern.value = Some(get_node_text(node).trim().to_string());
1157
1158 pattern
1159 }
1160}
1161
1162unsafe fn rng_parse_ref_pattern(node: *mut _xmlNode) -> RelaxNgPattern {
1168 unsafe {
1169 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Ref);
1170 pattern.name = get_attr(node, "name");
1171 pattern
1172 }
1173}
1174
1175unsafe fn rng_parse_external_ref(
1181 node: *mut _xmlNode,
1182 _schema: &mut RelaxNgSchema,
1183) -> RelaxNgPattern {
1184 unsafe {
1185 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::ExternalRef);
1186 let href = get_attr(node, "href");
1187 pattern.name = href;
1188 pattern
1189 }
1190}
1191
1192pub unsafe fn rng_validate_doc(
1206 schema: &RelaxNgSchema,
1207 doc: *mut _xmlDoc,
1208 ctxt: &mut RelaxNgValidCtxt,
1209) -> bool {
1210 unsafe {
1211 if doc.is_null() {
1212 ctxt.record_error("Document is null".to_string());
1213 return false;
1214 }
1215
1216 let root = (*doc).children;
1217 if root.is_null() {
1218 ctxt.record_error("Document has no children".to_string());
1219 return false;
1220 }
1221
1222 let mut root_elem = root;
1224 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
1225 root_elem = (*root_elem).next;
1226 }
1227
1228 if root_elem.is_null() {
1229 ctxt.record_error("Document has no root element".to_string());
1230 return false;
1231 }
1232
1233 let start_pattern = match &schema.grammar.start {
1235 Some(p) => p,
1236 None => {
1237 ctxt.record_error("Schema has no start pattern".to_string());
1238 return false;
1239 }
1240 };
1241
1242 ctxt.path.clear();
1243 let valid = rng_validate_pattern(start_pattern, root_elem, schema, ctxt);
1244
1245 valid
1247 }
1248}
1249
1250fn rng_validate_pattern(
1256 pattern: &RelaxNgPattern,
1257 node: *mut _xmlNode,
1258 schema: &RelaxNgSchema,
1259 ctxt: &mut RelaxNgValidCtxt,
1260) -> bool {
1261 unsafe {
1262 if ctxt.depth >= ctxt.depth_max {
1263 ctxt.record_error("Maximum validation depth exceeded".to_string());
1264 return false;
1265 }
1266 ctxt.depth += 1;
1267
1268 let result = match pattern.pattern_type {
1269 RelaxNgPatternType::Element => {
1270 rng_validate_element_pattern(pattern, node, schema, ctxt)
1271 }
1272 RelaxNgPatternType::Attribute => {
1273 rng_validate_attribute_pattern(pattern, node, schema, ctxt)
1274 }
1275 RelaxNgPatternType::Text => rng_validate_text_pattern(node, ctxt),
1276 RelaxNgPatternType::Empty => rng_validate_empty_pattern(node, ctxt),
1277 RelaxNgPatternType::NotAllowed => {
1278 let name = get_node_qname(node);
1279 ctxt.record_error(format!(
1280 "Element '{}' is not allowed at '{}'",
1281 name,
1282 ctxt.current_path()
1283 ));
1284 false
1285 }
1286 RelaxNgPatternType::Choice => rng_validate_choice_pattern(pattern, node, schema, ctxt),
1287 RelaxNgPatternType::Sequence => {
1288 rng_validate_sequence_pattern(pattern, node, schema, ctxt)
1289 }
1290 RelaxNgPatternType::Interleave => {
1291 rng_validate_interleave_pattern(pattern, node, schema, ctxt)
1292 }
1293 RelaxNgPatternType::ZeroOrMore => {
1294 rng_validate_zero_or_more(pattern, node, schema, ctxt)
1295 }
1296 RelaxNgPatternType::OneOrMore => rng_validate_one_or_more(pattern, node, schema, ctxt),
1297 RelaxNgPatternType::Optional => {
1298 rng_validate_optional_pattern(pattern, node, schema, ctxt)
1299 }
1300 RelaxNgPatternType::List => rng_validate_list_pattern(pattern, node, schema, ctxt),
1301 RelaxNgPatternType::Group => rng_validate_group_pattern(pattern, node, schema, ctxt),
1302 RelaxNgPatternType::Data => rng_validate_data_pattern(pattern, node, ctxt),
1303 RelaxNgPatternType::Value => rng_validate_value_pattern(pattern, node, ctxt),
1304 RelaxNgPatternType::Ref => rng_validate_ref_pattern(pattern, node, schema, ctxt),
1305 RelaxNgPatternType::ExternalRef => {
1306 rng_validate_empty_pattern(node, ctxt)
1308 }
1309 RelaxNgPatternType::Define
1310 | RelaxNgPatternType::Grammar
1311 | RelaxNgPatternType::Start
1312 | RelaxNgPatternType::Include => {
1313 rng_validate_children(pattern, node, schema, ctxt)
1315 }
1316 };
1317
1318 ctxt.depth -= 1;
1319 result
1320 }
1321}
1322
1323fn rng_validate_children(
1329 pattern: &RelaxNgPattern,
1330 node: *mut _xmlNode,
1331 schema: &RelaxNgSchema,
1332 ctxt: &mut RelaxNgValidCtxt,
1333) -> bool {
1334 {
1335 if pattern.children.is_empty() {
1336 return true;
1337 }
1338 let mut valid = true;
1340 for child in &pattern.children {
1341 valid &= rng_validate_pattern(child, node, schema, ctxt);
1342 }
1343 valid
1344 }
1345}
1346
1347fn rng_validate_element_pattern(
1353 pattern: &RelaxNgPattern,
1354 node: *mut _xmlNode,
1355 schema: &RelaxNgSchema,
1356 ctxt: &mut RelaxNgValidCtxt,
1357) -> bool {
1358 unsafe {
1359 if node.is_null() || (*node).type_ != XML_ELEMENT_NODE as c_int {
1360 return false;
1361 }
1362
1363 let node_name = get_node_qname(node);
1364 let ns_uri = get_node_ns_uri(node);
1365
1366 if let Some(ref nc) = pattern.name_class {
1368 if !nc.matches(&node_name, ns_uri.as_deref()) {
1369 let pat_name = pattern.name.as_deref().unwrap_or("?");
1370 ctxt.record_error(format!(
1371 "Element '{}' does not match expected element pattern '{}' at '{}'",
1372 node_name,
1373 pat_name,
1374 ctxt.current_path()
1375 ));
1376 return false;
1377 }
1378 }
1379
1380 ctxt.path.push(node_name.clone());
1382
1383 let mut valid = true;
1385 if pattern.children.is_empty() {
1386 let mut child = (*node).children;
1388 while !child.is_null() {
1389 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1390 let child_name = get_node_qname(child);
1391 ctxt.record_error(format!(
1392 "Unexpected child element '{}' in empty element '{}' at '{}'",
1393 child_name,
1394 node_name,
1395 ctxt.current_path()
1396 ));
1397 valid = false;
1398 }
1399 child = (*child).next;
1400 }
1401 } else {
1402 for child_pat in &pattern.children {
1405 valid &= rng_validate_pattern(child_pat, node, schema, ctxt);
1406 }
1407 }
1408
1409 ctxt.path.pop();
1410 valid
1411 }
1412}
1413
1414fn rng_validate_attribute_pattern(
1420 pattern: &RelaxNgPattern,
1421 node: *mut _xmlNode,
1422 _schema: &RelaxNgSchema,
1423 ctxt: &mut RelaxNgValidCtxt,
1424) -> bool {
1425 unsafe {
1426 if node.is_null() || (*node).type_ != XML_ELEMENT_NODE as c_int {
1427 return false;
1428 }
1429
1430 let attr_name = match &pattern.name_class {
1432 Some(RelaxNgNameClass::Name(n)) => n.clone(),
1433 Some(RelaxNgNameClass::AnyName) => {
1434 return true;
1439 }
1440 Some(RelaxNgNameClass::NsName(ns)) => {
1441 let mut prop = (*node).properties;
1444 while !prop.is_null() {
1445 let prop_ns = get_node_ns_uri(prop as *mut _xmlNode);
1446 if let Some(ref uri) = prop_ns {
1447 if uri == ns {
1448 if let Some(content) = &pattern.children.first() {
1450 let val = get_node_text(prop as *mut _xmlNode);
1451 let valid = match content.pattern_type {
1452 RelaxNgPatternType::Text => true,
1453 RelaxNgPatternType::Data => rng_validate_datatype_value(
1454 content.datatype.as_deref(),
1455 &val,
1456 ),
1457 RelaxNgPatternType::Value => {
1458 content.value.as_deref() == Some(&val)
1459 }
1460 _ => true,
1461 };
1462 if !valid {
1463 ctxt.record_error(format!(
1464 "Attribute '{}' has invalid value at '{}'",
1465 prop_ns.unwrap_or_default(),
1466 ctxt.current_path()
1467 ));
1468 return false;
1469 }
1470 }
1471 return true;
1472 }
1473 }
1474 prop = (*prop).next;
1475 }
1476 ctxt.record_error(format!(
1479 "Required attribute in namespace '{}' is missing at '{}'",
1480 ns,
1481 ctxt.current_path()
1482 ));
1483 return false;
1484 }
1485 _ => {
1486 return true;
1489 }
1490 };
1491
1492 let attr_value = get_attr(node, &attr_name);
1494
1495 match attr_value {
1496 Some(val) => {
1497 if let Some(content) = pattern.children.first() {
1499 let valid = match content.pattern_type {
1500 RelaxNgPatternType::Text => true,
1501 RelaxNgPatternType::Data => {
1502 rng_validate_datatype_value(content.datatype.as_deref(), &val)
1503 }
1504 RelaxNgPatternType::Value => content.value.as_deref() == Some(&val),
1505 _ => true,
1506 };
1507 if !valid {
1508 ctxt.record_error(format!(
1509 "Attribute '{}' has invalid value '{}' at '{}'",
1510 attr_name,
1511 val,
1512 ctxt.current_path()
1513 ));
1514 return false;
1515 }
1516 }
1517 true
1518 }
1519 None => {
1520 ctxt.record_error(format!(
1523 "Required attribute '{}' is missing at '{}'",
1524 attr_name,
1525 ctxt.current_path()
1526 ));
1527 false
1528 }
1529 }
1530 }
1531}
1532
1533const fn rng_validate_text_pattern(node: *mut _xmlNode, _ctxt: &mut RelaxNgValidCtxt) -> bool {
1539 {
1540 if node.is_null() {
1541 return false;
1542 }
1543 true
1546 }
1547}
1548
1549fn rng_validate_empty_pattern(node: *mut _xmlNode, ctxt: &mut RelaxNgValidCtxt) -> bool {
1555 unsafe {
1556 if node.is_null() {
1557 return true;
1558 }
1559 let mut child = (*node).children;
1561 while !child.is_null() {
1562 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1563 let child_name = get_node_qname(child);
1564 ctxt.record_error(format!(
1565 "Unexpected child element '{}' in empty content at '{}'",
1566 child_name,
1567 ctxt.current_path()
1568 ));
1569 return false;
1570 }
1571 child = (*child).next;
1572 }
1573 true
1574 }
1575}
1576
1577fn rng_validate_choice_pattern(
1583 pattern: &RelaxNgPattern,
1584 node: *mut _xmlNode,
1585 schema: &RelaxNgSchema,
1586 ctxt: &mut RelaxNgValidCtxt,
1587) -> bool {
1588 unsafe {
1589 if pattern.children.is_empty() {
1590 return false;
1591 }
1592
1593 let mut last_error = String::new();
1595 for child in &pattern.children {
1596 let saved_errors = ctxt.errors.len();
1597 let saved_nb = ctxt.nb_errors;
1598
1599 if rng_validate_pattern(child, node, schema, ctxt) {
1600 return true;
1603 }
1604
1605 if ctxt.errors.len() > saved_errors {
1607 last_error = ctxt.errors.last().unwrap().clone();
1608 }
1609
1610 ctxt.errors.truncate(saved_errors);
1612 ctxt.nb_errors = saved_nb;
1613 }
1614
1615 let node_name = if node.is_null() {
1617 "null".to_string()
1618 } else {
1619 get_node_qname(node)
1620 };
1621 ctxt.record_error(format!(
1622 "No choice pattern matched for '{}' at '{}'. Last error: {}",
1623 node_name,
1624 ctxt.current_path(),
1625 last_error
1626 ));
1627 false
1628 }
1629}
1630
1631fn rng_validate_sequence_pattern(
1637 pattern: &RelaxNgPattern,
1638 node: *mut _xmlNode,
1639 schema: &RelaxNgSchema,
1640 ctxt: &mut RelaxNgValidCtxt,
1641) -> bool {
1642 unsafe {
1643 if node.is_null() {
1644 return pattern.children.is_empty();
1645 }
1646
1647 let mut valid = true;
1648
1649 let mut child_nodes: Vec<*mut _xmlNode> = Vec::new();
1653 let mut child = (*node).children;
1654 while !child.is_null() {
1655 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1656 child_nodes.push(child);
1657 }
1658 child = (*child).next;
1659 }
1660
1661 let mut child_idx = 0;
1664 for child_pat in &pattern.children {
1665 if child_idx >= child_nodes.len() {
1666 match child_pat.pattern_type {
1668 RelaxNgPatternType::Optional | RelaxNgPatternType::ZeroOrMore => {
1669 continue;
1671 }
1672 RelaxNgPatternType::OneOrMore => {
1673 ctxt.record_error(format!(
1674 "Expected at least one matching child for oneOrMore at '{}'",
1675 ctxt.current_path()
1676 ));
1677 valid = false;
1678 continue;
1679 }
1680 _ => {
1681 ctxt.record_error(format!(
1682 "Expected more child elements for sequence at '{}'",
1683 ctxt.current_path()
1684 ));
1685 valid = false;
1686 continue;
1687 }
1688 }
1689 }
1690
1691 let child_node = child_nodes[child_idx];
1692 valid &= rng_validate_pattern(child_pat, child_node, schema, ctxt);
1693 child_idx += 1;
1694 }
1695
1696 if child_idx < child_nodes.len() {
1698 let extra_name = get_node_qname(child_nodes[child_idx]);
1699 ctxt.record_error(format!(
1700 "Unexpected extra element '{}' in sequence at '{}'",
1701 extra_name,
1702 ctxt.current_path()
1703 ));
1704 valid = false;
1705 }
1706
1707 valid
1708 }
1709}
1710
1711fn rng_validate_interleave_pattern(
1717 pattern: &RelaxNgPattern,
1718 node: *mut _xmlNode,
1719 schema: &RelaxNgSchema,
1720 ctxt: &mut RelaxNgValidCtxt,
1721) -> bool {
1722 unsafe {
1723 if node.is_null() {
1724 return pattern.children.is_empty();
1725 }
1726
1727 let mut valid = true;
1731
1732 for child_pat in &pattern.children {
1733 let mut child = (*node).children;
1736 let mut matched = false;
1737
1738 while !child.is_null() {
1739 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1740 let saved_errors = ctxt.errors.len();
1741 let saved_nb = ctxt.nb_errors;
1742
1743 if rng_validate_pattern(child_pat, child, schema, ctxt) {
1744 matched = true;
1745 break;
1746 }
1747
1748 ctxt.errors.truncate(saved_errors);
1750 ctxt.nb_errors = saved_nb;
1751 }
1752 child = (*child).next;
1753 }
1754
1755 if !matched {
1756 match child_pat.pattern_type {
1758 RelaxNgPatternType::Optional | RelaxNgPatternType::ZeroOrMore => {
1759 }
1761 _ => {
1762 let pat_desc = format!("{:?}", child_pat.pattern_type);
1763 ctxt.record_error(format!(
1764 "Interleave pattern '{}' did not match any child at '{}'",
1765 pat_desc,
1766 ctxt.current_path()
1767 ));
1768 valid = false;
1769 }
1770 }
1771 }
1772 }
1773
1774 valid
1775 }
1776}
1777
1778fn rng_validate_zero_or_more(
1784 pattern: &RelaxNgPattern,
1785 node: *mut _xmlNode,
1786 schema: &RelaxNgSchema,
1787 ctxt: &mut RelaxNgValidCtxt,
1788) -> bool {
1789 unsafe {
1790 if node.is_null() || pattern.children.is_empty() {
1791 return true;
1792 }
1793
1794 let child_pat = &pattern.children[0];
1795 let valid = true;
1796
1797 let mut child = (*node).children;
1799 while !child.is_null() {
1800 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1801 let saved_errors = ctxt.errors.len();
1802 let saved_nb = ctxt.nb_errors;
1803
1804 if !rng_validate_pattern(child_pat, child, schema, ctxt) {
1805 ctxt.errors.truncate(saved_errors);
1808 ctxt.nb_errors = saved_nb;
1809 break;
1810 }
1811 }
1813 child = (*child).next;
1814 }
1815
1816 valid
1817 }
1818}
1819
1820fn rng_validate_one_or_more(
1826 pattern: &RelaxNgPattern,
1827 node: *mut _xmlNode,
1828 schema: &RelaxNgSchema,
1829 ctxt: &mut RelaxNgValidCtxt,
1830) -> bool {
1831 unsafe {
1832 if node.is_null() || pattern.children.is_empty() {
1833 ctxt.record_error(format!(
1834 "Expected at least one matching element for oneOrMore at '{}'",
1835 ctxt.current_path()
1836 ));
1837 return false;
1838 }
1839
1840 let child_pat = &pattern.children[0];
1841 let mut matched = false;
1842 let mut valid = true;
1843
1844 let mut child = (*node).children;
1846 while !child.is_null() {
1847 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1848 let saved_errors = ctxt.errors.len();
1849 let saved_nb = ctxt.nb_errors;
1850
1851 if rng_validate_pattern(child_pat, child, schema, ctxt) {
1852 matched = true;
1853 } else {
1854 ctxt.errors.truncate(saved_errors);
1856 ctxt.nb_errors = saved_nb;
1857 break;
1858 }
1859 }
1860 child = (*child).next;
1861 }
1862
1863 if !matched {
1864 ctxt.record_error(format!(
1865 "Expected at least one matching element for oneOrMore at '{}'",
1866 ctxt.current_path()
1867 ));
1868 valid = false;
1869 }
1870
1871 valid
1872 }
1873}
1874
1875fn rng_validate_optional_pattern(
1881 pattern: &RelaxNgPattern,
1882 node: *mut _xmlNode,
1883 schema: &RelaxNgSchema,
1884 ctxt: &mut RelaxNgValidCtxt,
1885) -> bool {
1886 {
1887 if pattern.children.is_empty() {
1888 return true;
1889 }
1890
1891 let child_pat = &pattern.children[0];
1893 let saved_errors = ctxt.errors.len();
1894 let saved_nb = ctxt.nb_errors;
1895
1896 let result = rng_validate_pattern(child_pat, node, schema, ctxt);
1897
1898 if !result {
1899 ctxt.errors.truncate(saved_errors);
1901 ctxt.nb_errors = saved_nb;
1902 }
1903
1904 true }
1906}
1907
1908fn rng_validate_list_pattern(
1914 pattern: &RelaxNgPattern,
1915 node: *mut _xmlNode,
1916 _schema: &RelaxNgSchema,
1917 _ctxt: &mut RelaxNgValidCtxt,
1918) -> bool {
1919 unsafe {
1920 if node.is_null() {
1921 return pattern.children.is_empty();
1922 }
1923
1924 let text = get_node_text(node);
1926 if text.trim().is_empty() {
1927 return true;
1928 }
1929
1930 let tokens: Vec<&str> = text.split_whitespace().collect();
1931 let mut valid = true;
1932
1933 for token in &tokens {
1934 if token.is_empty() {
1937 valid = false;
1938 break;
1939 }
1940 }
1941
1942 valid
1943 }
1944}
1945
1946fn rng_validate_group_pattern(
1952 pattern: &RelaxNgPattern,
1953 node: *mut _xmlNode,
1954 schema: &RelaxNgSchema,
1955 ctxt: &mut RelaxNgValidCtxt,
1956) -> bool {
1957 {
1958 rng_validate_sequence_pattern(pattern, node, schema, ctxt)
1960 }
1961}
1962
1963fn rng_validate_data_pattern(
1969 pattern: &RelaxNgPattern,
1970 node: *mut _xmlNode,
1971 ctxt: &mut RelaxNgValidCtxt,
1972) -> bool {
1973 unsafe {
1974 if node.is_null() {
1975 return false;
1976 }
1977
1978 let text = get_node_text(node);
1979 let datatype = pattern.datatype.as_deref();
1980
1981 if !rng_validate_datatype_value(datatype, &text) {
1982 ctxt.record_error(format!(
1983 "Value '{}' does not match datatype '{:?}' at '{}'",
1984 text,
1985 datatype,
1986 ctxt.current_path()
1987 ));
1988 return false;
1989 }
1990
1991 true
1992 }
1993}
1994
1995fn rng_validate_value_pattern(
2001 pattern: &RelaxNgPattern,
2002 node: *mut _xmlNode,
2003 ctxt: &mut RelaxNgValidCtxt,
2004) -> bool {
2005 unsafe {
2006 if node.is_null() {
2007 return false;
2008 }
2009
2010 let text = get_node_text(node).trim().to_string();
2011 let expected = pattern.value.as_deref().unwrap_or("");
2012
2013 if text != expected {
2014 ctxt.record_error(format!(
2015 "Value '{}' does not match expected value '{}' at '{}'",
2016 text,
2017 expected,
2018 ctxt.current_path()
2019 ));
2020 return false;
2021 }
2022
2023 true
2024 }
2025}
2026
2027fn rng_validate_ref_pattern(
2033 pattern: &RelaxNgPattern,
2034 node: *mut _xmlNode,
2035 schema: &RelaxNgSchema,
2036 ctxt: &mut RelaxNgValidCtxt,
2037) -> bool {
2038 {
2039 let ref_name = pattern.name.as_deref().unwrap_or("");
2040
2041 if ref_name.is_empty() {
2042 ctxt.record_error("Ref pattern has no name".to_string());
2043 return false;
2044 }
2045
2046 match schema.grammar.lookup(ref_name) {
2048 Some(def_pattern) => {
2049 rng_validate_pattern(def_pattern, node, schema, ctxt)
2051 }
2052 None => {
2053 ctxt.record_error(format!(
2054 "Undefined reference '{}' at '{}'",
2055 ref_name,
2056 ctxt.current_path()
2057 ));
2058 false
2059 }
2060 }
2061 }
2062}
2063
2064fn rng_validate_datatype_value(datatype: Option<&str>, value: &str) -> bool {
2073 let dt = match datatype {
2074 Some(d) => d,
2075 None => return true, };
2077
2078 match dt {
2079 "string" | "token" => true,
2080 "boolean" => {
2081 matches!(value, "true" | "false" | "1" | "0")
2082 }
2083 "integer" | "int" | "short" | "byte" | "long" => {
2084 if value.is_empty() {
2085 return false;
2086 }
2087 let trimmed = if value.starts_with('+') || value.starts_with('-') {
2088 &value[1..]
2089 } else {
2090 value
2091 };
2092 !trimmed.is_empty() && trimmed.chars().all(|c| c.is_ascii_digit())
2093 }
2094 "decimal" | "double" | "float" => {
2095 if value.is_empty() {
2096 return false;
2097 }
2098 if matches!(dt, "float" | "double") && matches!(value, "INF" | "-INF" | "NaN") {
2100 return true;
2101 }
2102 value.parse::<f64>().is_ok()
2103 }
2104 "NCName" | "Name" | "ID" | "IDREF" | "NMTOKEN" => {
2105 !value.is_empty() && !value.starts_with(|c: char| c.is_ascii_digit())
2106 }
2107 "anyURI" => {
2108 !value.is_empty() && !value.contains(char::is_whitespace)
2110 }
2111 "QName" => {
2112 if value.is_empty() {
2113 return false;
2114 }
2115 if let Some(pos) = value.find(':') {
2116 pos > 0 && pos < value.len() - 1
2117 } else {
2118 true
2119 }
2120 }
2121 _ => {
2122 true
2125 }
2126 }
2127}
2128
2129pub fn rng_parse_schema(xml_doc: &str) -> Result<RelaxNgSchema, String> {
2137 rng_parse(xml_doc)
2138}
2139
2140pub unsafe fn rng_parse_schema_doc(doc: *mut _xmlDoc) -> Result<RelaxNgSchema, String> {
2146 rng_parse_doc(doc)
2147}
2148
2149pub unsafe fn rng_validate_doc_schema(
2157 schema: &RelaxNgSchema,
2158 doc: *mut _xmlDoc,
2159 ctxt: &mut RelaxNgValidCtxt,
2160) -> bool {
2161 rng_validate_doc(schema, doc, ctxt)
2162}
2163
2164#[no_mangle]
2183pub unsafe extern "C" fn xmlRelaxNGNewParserCtxt(url: *const c_char) -> *mut c_void {
2184 if url.is_null() {
2185 let ctxt = allocator::xmlMallocZero(size_of::<RelaxNgSchema>() as usize);
2186 return ctxt;
2187 }
2188
2189 let url_str = unsafe {
2190 let mut len = 0;
2191 while *url.add(len) != 0 {
2192 len += 1;
2193 }
2194 let slice = std::slice::from_raw_parts(url as *const u8, len);
2195 String::from_utf8_lossy(slice).to_string()
2196 };
2197
2198 if !url_str.is_empty() {
2200 let url_c = std::ffi::CString::new(url_str.clone()).ok();
2201 if let Some(c) = url_c {
2202 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
2203 if !doc.is_null() {
2204 let result = rng_parse_doc(doc);
2205 crate::abi::exports_xml2::xmlFreeDoc(doc);
2206 if let Ok(schema) = result {
2207 let schema_box = Box::new(schema);
2208 return Box::into_raw(schema_box) as *mut c_void;
2209 }
2210 }
2211 }
2212 }
2213
2214 allocator::xmlMallocZero(size_of::<RelaxNgSchema>() as usize)
2217}
2218
2219#[no_mangle]
2231pub unsafe extern "C" fn xmlRelaxNGNewMemParserCtxt(
2232 buffer: *const c_char,
2233 size: c_int,
2234) -> *mut c_void {
2235 if buffer.is_null() || size <= 0 {
2236 return ptr::null_mut();
2237 }
2238
2239 let buf_slice = unsafe { std::slice::from_raw_parts(buffer as *const u8, size as usize) };
2241 let xml_str = String::from_utf8_lossy(buf_slice).to_string();
2242
2243 match rng_parse(&xml_str) {
2244 Ok(schema) => {
2245 let schema_box = Box::new(schema);
2246 Box::into_raw(schema_box) as *mut c_void
2247 }
2248 Err(_) => ptr::null_mut(),
2249 }
2250}
2251
2252#[no_mangle]
2264pub const unsafe extern "C" fn xmlRelaxNGParse(ctxt: *mut c_void) -> *mut c_void {
2265 if ctxt.is_null() {
2266 return ptr::null_mut();
2267 }
2268
2269 ctxt
2272}
2273
2274#[no_mangle]
2286pub unsafe extern "C" fn xmlRelaxNGFree(schema: *mut c_void) {
2287 if schema.is_null() {
2288 return;
2289 }
2290 unsafe {
2292 let _ = Box::from_raw(schema as *mut RelaxNgSchema);
2293 }
2294}
2295
2296#[no_mangle]
2308pub unsafe extern "C" fn xmlRelaxNGFreeParserCtxt(ctxt: *mut c_void) {
2309 if ctxt.is_null() {
2310 return;
2311 }
2312 unsafe {
2314 let _ = Box::from_raw(ctxt as *mut RelaxNgSchema);
2315 }
2316}
2317
2318#[no_mangle]
2330pub unsafe extern "C" fn xmlRelaxNGNewValidCtxt(schema: *mut c_void) -> *mut c_void {
2331 let mut ctxt = RelaxNgValidCtxt::new();
2332
2333 if !schema.is_null() {
2334 unsafe {
2336 let schema_ref = &*(schema as *const RelaxNgSchema);
2337 ctxt.schema = Some(schema_ref.clone());
2338 }
2339 }
2340
2341 let boxed = Box::new(ctxt);
2342 Box::into_raw(boxed) as *mut c_void
2343}
2344
2345#[no_mangle]
2357pub unsafe extern "C" fn xmlRelaxNGFreeValidCtxt(ctxt: *mut c_void) {
2358 if ctxt.is_null() {
2359 return;
2360 }
2361 unsafe {
2363 let _ = Box::from_raw(ctxt as *mut RelaxNgValidCtxt);
2364 }
2365}
2366
2367#[no_mangle]
2382pub unsafe extern "C" fn xmlRelaxNGValidateDoc(ctxt: *mut c_void, doc: *mut _xmlDoc) -> c_int {
2383 if ctxt.is_null() || doc.is_null() {
2384 return -1;
2385 }
2386
2387 unsafe {
2388 let valid_ctxt = &mut *(ctxt as *mut RelaxNgValidCtxt);
2389 let schema = match &valid_ctxt.schema {
2390 Some(s) => s,
2391 None => return -1,
2392 };
2393
2394 let mut temp_ctxt = RelaxNgValidCtxt::new();
2395
2396 let valid = rng_validate_doc(schema, doc, &mut temp_ctxt);
2397
2398 if valid {
2399 0
2400 } else {
2401 valid_ctxt.errors = temp_ctxt.errors;
2402 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2403 temp_ctxt.nb_errors
2404 }
2405 }
2406}
2407
2408#[no_mangle]
2426pub unsafe extern "C" fn xmlRelaxNGValidateFullElement(
2427 ctxt: *mut c_void,
2428 doc: *mut _xmlDoc,
2429 elem: *mut _xmlNode,
2430) -> c_int {
2431 if ctxt.is_null() || doc.is_null() || elem.is_null() {
2432 return -1;
2433 }
2434
2435 unsafe {
2436 let valid_ctxt = &mut *(ctxt as *mut RelaxNgValidCtxt);
2437 let schema = match &valid_ctxt.schema {
2438 Some(s) => s,
2439 None => return -1,
2440 };
2441
2442 let mut temp_ctxt = RelaxNgValidCtxt::new();
2443 temp_ctxt.path = valid_ctxt.path.clone();
2444
2445 let start_pattern = match &schema.grammar.start {
2446 Some(p) => p,
2447 None => return -1,
2448 };
2449
2450 let valid = rng_validate_pattern(start_pattern, elem, schema, &mut temp_ctxt);
2451
2452 if valid {
2453 0
2454 } else {
2455 valid_ctxt.errors = temp_ctxt.errors;
2456 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2457 temp_ctxt.nb_errors
2458 }
2459 }
2460}
2461
2462#[cfg(test)]
2467mod tests {
2468 use super::*;
2469
2470 #[test]
2473 fn test_name_class_name() {
2474 let nc = RelaxNgNameClass::Name("foo".to_string());
2475 assert!(nc.matches("foo", None));
2476 assert!(!nc.matches("bar", None));
2477 assert!(!nc.matches("FOO", None));
2478 }
2479
2480 #[test]
2481 fn test_name_class_any_name() {
2482 let nc = RelaxNgNameClass::AnyName;
2483 assert!(nc.matches("foo", None));
2484 assert!(nc.matches("bar", None));
2485 assert!(nc.matches("anything", Some("urn:ns")));
2486 }
2487
2488 #[test]
2489 fn test_name_class_ns_name() {
2490 let nc = RelaxNgNameClass::NsName("urn:example".to_string());
2491 assert!(nc.matches("foo", Some("urn:example")));
2492 assert!(!nc.matches("foo", Some("urn:other")));
2493 assert!(!nc.matches("foo", None));
2494 }
2495
2496 #[test]
2497 fn test_name_class_choice() {
2498 let nc = RelaxNgNameClass::Choice(vec![
2499 RelaxNgNameClass::Name("a".to_string()),
2500 RelaxNgNameClass::Name("b".to_string()),
2501 ]);
2502 assert!(nc.matches("a", None));
2503 assert!(nc.matches("b", None));
2504 assert!(!nc.matches("c", None));
2505 }
2506
2507 #[test]
2508 fn test_name_class_except() {
2509 let nc = RelaxNgNameClass::Except(
2510 Box::new(RelaxNgNameClass::AnyName),
2511 Box::new(RelaxNgNameClass::Name("bad".to_string())),
2512 );
2513 assert!(nc.matches("good", None));
2514 assert!(!nc.matches("bad", None));
2515 }
2516
2517 #[test]
2520 fn test_parse_simple_element_schema() {
2521 let schema_xml = r#"<?xml version="1.0"?>
2522<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2523 <text/>
2524</element>"#;
2525
2526 let result = rng_parse(schema_xml);
2527 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2528 let schema = result.unwrap();
2529 assert!(schema.grammar.start.is_some());
2530 if let Some(ref start) = schema.grammar.start {
2531 assert_eq!(start.pattern_type, RelaxNgPatternType::Element);
2532 assert_eq!(start.name.as_deref(), Some("root"));
2533 }
2534 }
2535
2536 #[test]
2537 fn test_parse_grammar_schema() {
2538 let schema_xml = r#"<?xml version="1.0"?>
2539<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2540 <start>
2541 <element name="root">
2542 <text/>
2543 </element>
2544 </start>
2545</grammar>"#;
2546
2547 let result = rng_parse(schema_xml);
2548 assert!(
2549 result.is_ok(),
2550 "Failed to parse grammar: {:?}",
2551 result.err()
2552 );
2553 let schema = result.unwrap();
2554 assert!(schema.grammar.start.is_some());
2555 }
2556
2557 #[test]
2558 fn test_parse_with_define_and_ref() {
2559 let schema_xml = r#"<?xml version="1.0"?>
2560<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2561 <define name="textBlock">
2562 <text/>
2563 </define>
2564 <start>
2565 <element name="doc">
2566 <ref name="textBlock"/>
2567 </element>
2568 </start>
2569</grammar>"#;
2570
2571 let result = rng_parse(schema_xml);
2572 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
2573 let schema = result.unwrap();
2574 assert_eq!(schema.grammar.defines.len(), 1);
2575 assert_eq!(schema.grammar.defines[0].name, "textBlock");
2576 assert!(schema.grammar.start.is_some());
2577 }
2578
2579 #[test]
2580 fn test_parse_choice_schema() {
2581 let schema_xml = r#"<?xml version="1.0"?>
2582<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2583 <start>
2584 <choice>
2585 <element name="a">
2586 <text/>
2587 </element>
2588 <element name="b">
2589 <text/>
2590 </element>
2591 </choice>
2592 </start>
2593</grammar>"#;
2594
2595 let result = rng_parse(schema_xml);
2596 assert!(result.is_ok(), "Failed to parse choice: {:?}", result.err());
2597 }
2598
2599 #[test]
2600 fn test_parse_attribute_schema() {
2601 let schema_xml = r#"<?xml version="1.0"?>
2602<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2603 <attribute name="attr1">
2604 <text/>
2605 </attribute>
2606 <text/>
2607</element>"#;
2608
2609 let result = rng_parse(schema_xml);
2610 assert!(
2611 result.is_ok(),
2612 "Failed to parse attribute: {:?}",
2613 result.err()
2614 );
2615 }
2616
2617 #[test]
2618 fn test_parse_empty_document_fails() {
2619 let result = rng_parse("");
2620 assert!(result.is_err());
2621 }
2622
2623 #[test]
2624 fn test_parse_invalid_xml_fails() {
2625 let result = rng_parse("not valid xml <<<");
2626 assert!(result.is_err());
2627 }
2628
2629 #[test]
2632 fn test_validate_simple_element() {
2633 let schema_xml = r#"<?xml version="1.0"?>
2634<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2635 <text/>
2636</element>"#;
2637
2638 let doc_xml = r#"<?xml version="1.0"?>
2639<root>Hello</root>"#;
2640
2641 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2642
2643 let doc = unsafe {
2644 crate::abi::exports_xml2::xmlReadMemory(
2645 doc_xml.as_ptr() as *const c_char,
2646 doc_xml.len() as c_int,
2647 c"test.xml".as_ptr() as *const c_char,
2648 ptr::null(),
2649 0,
2650 )
2651 };
2652 assert!(!doc.is_null(), "Failed to parse document");
2653
2654 let mut ctxt = RelaxNgValidCtxt::new();
2655 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2656 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2657
2658 assert!(valid, "Validation failed: {:?}", ctxt.errors);
2659 }
2660
2661 #[test]
2662 fn test_validate_element_mismatch() {
2663 let schema_xml = r#"<?xml version="1.0"?>
2664<element name="expected" xmlns="http://relaxng.org/ns/structure/1.0">
2665 <text/>
2666</element>"#;
2667
2668 let doc_xml = r#"<?xml version="1.0"?>
2669<actual>Content</actual>"#;
2670
2671 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2672
2673 let doc = unsafe {
2674 crate::abi::exports_xml2::xmlReadMemory(
2675 doc_xml.as_ptr() as *const c_char,
2676 doc_xml.len() as c_int,
2677 c"test.xml".as_ptr() as *const c_char,
2678 ptr::null(),
2679 0,
2680 )
2681 };
2682 assert!(!doc.is_null());
2683
2684 let mut ctxt = RelaxNgValidCtxt::new();
2685 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2686 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2687
2688 assert!(!valid, "Validation should have failed");
2689 assert!(ctxt.nb_errors > 0);
2690 }
2691
2692 #[test]
2693 fn test_validate_with_attribute() {
2694 let schema_xml = r#"<?xml version="1.0"?>
2695<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2696 <attribute name="id">
2697 <text/>
2698 </attribute>
2699 <text/>
2700</element>"#;
2701
2702 let doc_xml = r#"<?xml version="1.0"?>
2703<root id="x1">Content</root>"#;
2704
2705 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2706
2707 let doc = unsafe {
2708 crate::abi::exports_xml2::xmlReadMemory(
2709 doc_xml.as_ptr() as *const c_char,
2710 doc_xml.len() as c_int,
2711 c"test.xml".as_ptr() as *const c_char,
2712 ptr::null(),
2713 0,
2714 )
2715 };
2716 assert!(!doc.is_null());
2717
2718 let mut ctxt = RelaxNgValidCtxt::new();
2719 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2720 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2721
2722 assert!(valid, "Validation failed: {:?}", ctxt.errors);
2723 }
2724
2725 #[test]
2726 fn test_validate_missing_attribute() {
2727 let schema_xml = r#"<?xml version="1.0"?>
2728<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2729 <attribute name="required">
2730 <text/>
2731 </attribute>
2732 <text/>
2733</element>"#;
2734
2735 let doc_xml = r#"<?xml version="1.0"?>
2736<root>Content</root>"#;
2737
2738 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2739
2740 let doc = unsafe {
2741 crate::abi::exports_xml2::xmlReadMemory(
2742 doc_xml.as_ptr() as *const c_char,
2743 doc_xml.len() as c_int,
2744 c"test.xml".as_ptr() as *const c_char,
2745 ptr::null(),
2746 0,
2747 )
2748 };
2749 assert!(!doc.is_null());
2750
2751 let mut ctxt = RelaxNgValidCtxt::new();
2752 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2753 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2754
2755 assert!(
2756 !valid,
2757 "Validation should have failed for missing attribute"
2758 );
2759 }
2760
2761 #[test]
2762 fn test_validate_with_choice() {
2763 let schema_xml = r#"<?xml version="1.0"?>
2764<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2765 <start>
2766 <choice>
2767 <element name="a">
2768 <text/>
2769 </element>
2770 <element name="b">
2771 <text/>
2772 </element>
2773 </choice>
2774 </start>
2775</grammar>"#;
2776
2777 let doc_xml = r#"<?xml version="1.0"?>
2778<a>First choice</a>"#;
2779
2780 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2781
2782 let doc = unsafe {
2783 crate::abi::exports_xml2::xmlReadMemory(
2784 doc_xml.as_ptr() as *const c_char,
2785 doc_xml.len() as c_int,
2786 c"test.xml".as_ptr() as *const c_char,
2787 ptr::null(),
2788 0,
2789 )
2790 };
2791 assert!(!doc.is_null());
2792
2793 let mut ctxt = RelaxNgValidCtxt::new();
2794 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2795 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2796
2797 assert!(valid, "Choice validation failed: {:?}", ctxt.errors);
2798 }
2799
2800 #[test]
2801 fn test_validate_choice_no_match() {
2802 let schema_xml = r#"<?xml version="1.0"?>
2803<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2804 <start>
2805 <choice>
2806 <element name="a">
2807 <text/>
2808 </element>
2809 <element name="b">
2810 <text/>
2811 </element>
2812 </choice>
2813 </start>
2814</grammar>"#;
2815
2816 let doc_xml = r#"<?xml version="1.0"?>
2817<c>Neither choice</c>"#;
2818
2819 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2820
2821 let doc = unsafe {
2822 crate::abi::exports_xml2::xmlReadMemory(
2823 doc_xml.as_ptr() as *const c_char,
2824 doc_xml.len() as c_int,
2825 c"test.xml".as_ptr() as *const c_char,
2826 ptr::null(),
2827 0,
2828 )
2829 };
2830 assert!(!doc.is_null());
2831
2832 let mut ctxt = RelaxNgValidCtxt::new();
2833 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2834 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2835
2836 assert!(
2837 !valid,
2838 "Validation should have failed for no matching choice"
2839 );
2840 }
2841
2842 #[test]
2843 fn test_validate_grammar_with_ref() {
2844 let schema_xml = r#"<?xml version="1.0"?>
2845<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2846 <define name="para">
2847 <element name="p">
2848 <text/>
2849 </element>
2850 </define>
2851 <start>
2852 <element name="doc">
2853 <zeroOrMore>
2854 <ref name="para"/>
2855 </zeroOrMore>
2856 </element>
2857 </start>
2858</grammar>"#;
2859
2860 let doc_xml = r#"<?xml version="1.0"?>
2861<doc><p>First</p><p>Second</p></doc>"#;
2862
2863 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2864
2865 let doc = unsafe {
2866 crate::abi::exports_xml2::xmlReadMemory(
2867 doc_xml.as_ptr() as *const c_char,
2868 doc_xml.len() as c_int,
2869 c"test.xml".as_ptr() as *const c_char,
2870 ptr::null(),
2871 0,
2872 )
2873 };
2874 assert!(!doc.is_null());
2875
2876 let mut ctxt = RelaxNgValidCtxt::new();
2877 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2878 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2879
2880 assert!(valid, "Ref validation failed: {:?}", ctxt.errors);
2881 }
2882
2883 #[test]
2884 fn test_validate_zero_or_more() {
2885 let schema_xml = r#"<?xml version="1.0"?>
2886<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2887 <zeroOrMore>
2888 <element name="item">
2889 <text/>
2890 </element>
2891 </zeroOrMore>
2892</element>"#;
2893
2894 let doc_xml = r#"<?xml version="1.0"?>
2895<root><item>A</item><item>B</item></root>"#;
2896
2897 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2898
2899 let doc = unsafe {
2900 crate::abi::exports_xml2::xmlReadMemory(
2901 doc_xml.as_ptr() as *const c_char,
2902 doc_xml.len() as c_int,
2903 c"test.xml".as_ptr() as *const c_char,
2904 ptr::null(),
2905 0,
2906 )
2907 };
2908 assert!(!doc.is_null());
2909
2910 let mut ctxt = RelaxNgValidCtxt::new();
2911 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2912 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2913
2914 assert!(valid, "zeroOrMore validation failed: {:?}", ctxt.errors);
2915 }
2916
2917 #[test]
2918 fn test_validate_zero_or_more_empty() {
2919 let schema_xml = r#"<?xml version="1.0"?>
2920<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2921 <zeroOrMore>
2922 <element name="item">
2923 <text/>
2924 </element>
2925 </zeroOrMore>
2926</element>"#;
2927
2928 let doc_xml = r#"<?xml version="1.0"?>
2929<root></root>"#;
2930
2931 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2932
2933 let doc = unsafe {
2934 crate::abi::exports_xml2::xmlReadMemory(
2935 doc_xml.as_ptr() as *const c_char,
2936 doc_xml.len() as c_int,
2937 c"test.xml".as_ptr() as *const c_char,
2938 ptr::null(),
2939 0,
2940 )
2941 };
2942 assert!(!doc.is_null());
2943
2944 let mut ctxt = RelaxNgValidCtxt::new();
2945 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2946 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2947
2948 assert!(valid, "Empty zeroOrMore should be valid");
2949 }
2950
2951 #[test]
2952 fn test_validate_one_or_more() {
2953 let schema_xml = r#"<?xml version="1.0"?>
2954<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2955 <oneOrMore>
2956 <element name="item">
2957 <text/>
2958 </element>
2959 </oneOrMore>
2960</element>"#;
2961
2962 let doc_xml = r#"<?xml version="1.0"?>
2963<root><item>Single</item></root>"#;
2964
2965 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2966
2967 let doc = unsafe {
2968 crate::abi::exports_xml2::xmlReadMemory(
2969 doc_xml.as_ptr() as *const c_char,
2970 doc_xml.len() as c_int,
2971 c"test.xml".as_ptr() as *const c_char,
2972 ptr::null(),
2973 0,
2974 )
2975 };
2976 assert!(!doc.is_null());
2977
2978 let mut ctxt = RelaxNgValidCtxt::new();
2979 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2980 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2981
2982 assert!(valid, "oneOrMore validation failed: {:?}", ctxt.errors);
2983 }
2984
2985 #[test]
2986 fn test_validate_optional_present() {
2987 let schema_xml = r#"<?xml version="1.0"?>
2988<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2989 <optional>
2990 <element name="opt">
2991 <text/>
2992 </element>
2993 </optional>
2994 <text/>
2995</element>"#;
2996
2997 let doc_xml = r#"<?xml version="1.0"?>
2998<root><opt>present</opt>text</root>"#;
2999
3000 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3001
3002 let doc = unsafe {
3003 crate::abi::exports_xml2::xmlReadMemory(
3004 doc_xml.as_ptr() as *const c_char,
3005 doc_xml.len() as c_int,
3006 c"test.xml".as_ptr() as *const c_char,
3007 ptr::null(),
3008 0,
3009 )
3010 };
3011 assert!(!doc.is_null());
3012
3013 let mut ctxt = RelaxNgValidCtxt::new();
3014 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3015 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3016
3017 assert!(
3018 valid,
3019 "Optional present validation failed: {:?}",
3020 ctxt.errors
3021 );
3022 }
3023
3024 #[test]
3025 fn test_validate_optional_absent() {
3026 let schema_xml = r#"<?xml version="1.0"?>
3027<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3028 <optional>
3029 <element name="opt">
3030 <text/>
3031 </element>
3032 </optional>
3033 <text/>
3034</element>"#;
3035
3036 let doc_xml = r#"<?xml version="1.0"?>
3037<root>text only</root>"#;
3038
3039 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3040
3041 let doc = unsafe {
3042 crate::abi::exports_xml2::xmlReadMemory(
3043 doc_xml.as_ptr() as *const c_char,
3044 doc_xml.len() as c_int,
3045 c"test.xml".as_ptr() as *const c_char,
3046 ptr::null(),
3047 0,
3048 )
3049 };
3050 assert!(!doc.is_null());
3051
3052 let mut ctxt = RelaxNgValidCtxt::new();
3053 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3054 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3055
3056 assert!(
3057 valid,
3058 "Optional absent validation failed: {:?}",
3059 ctxt.errors
3060 );
3061 }
3062
3063 #[test]
3064 fn test_validate_sequence() {
3065 let schema_xml = r#"<?xml version="1.0"?>
3066<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3067 <start>
3068 <element name="root">
3069 <sequence>
3070 <element name="first">
3071 <text/>
3072 </element>
3073 <element name="second">
3074 <text/>
3075 </element>
3076 </sequence>
3077 </element>
3078 </start>
3079</grammar>"#;
3080
3081 let doc_xml = r#"<?xml version="1.0"?>
3082<root><first>First</first><second>Second</second></root>"#;
3083
3084 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3085
3086 let doc = unsafe {
3087 crate::abi::exports_xml2::xmlReadMemory(
3088 doc_xml.as_ptr() as *const c_char,
3089 doc_xml.len() as c_int,
3090 c"test.xml".as_ptr() as *const c_char,
3091 ptr::null(),
3092 0,
3093 )
3094 };
3095 assert!(!doc.is_null());
3096
3097 let mut ctxt = RelaxNgValidCtxt::new();
3098 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3099 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3100
3101 assert!(valid, "Sequence validation failed: {:?}", ctxt.errors);
3102 }
3103
3104 #[test]
3105 fn test_validate_data_pattern() {
3106 let schema_xml = r#"<?xml version="1.0"?>
3107<element name="age" xmlns="http://relaxng.org/ns/structure/1.0">
3108 <data type="integer"/>
3109</element>"#;
3110
3111 let doc_xml = r#"<?xml version="1.0"?>
3112<age>25</age>"#;
3113
3114 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3115
3116 let doc = unsafe {
3117 crate::abi::exports_xml2::xmlReadMemory(
3118 doc_xml.as_ptr() as *const c_char,
3119 doc_xml.len() as c_int,
3120 c"test.xml".as_ptr() as *const c_char,
3121 ptr::null(),
3122 0,
3123 )
3124 };
3125 assert!(!doc.is_null());
3126
3127 let mut ctxt = RelaxNgValidCtxt::new();
3128 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3129 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3130
3131 assert!(valid, "Data pattern validation failed: {:?}", ctxt.errors);
3132 }
3133
3134 #[test]
3135 fn test_validate_data_pattern_invalid() {
3136 let schema_xml = r#"<?xml version="1.0"?>
3137<element name="age" xmlns="http://relaxng.org/ns/structure/1.0">
3138 <data type="integer"/>
3139</element>"#;
3140
3141 let doc_xml = r#"<?xml version="1.0"?>
3142<age>not-a-number</age>"#;
3143
3144 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3145
3146 let doc = unsafe {
3147 crate::abi::exports_xml2::xmlReadMemory(
3148 doc_xml.as_ptr() as *const c_char,
3149 doc_xml.len() as c_int,
3150 c"test.xml".as_ptr() as *const c_char,
3151 ptr::null(),
3152 0,
3153 )
3154 };
3155 assert!(!doc.is_null());
3156
3157 let mut ctxt = RelaxNgValidCtxt::new();
3158 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3159 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3160
3161 assert!(!valid, "Validation should have failed for invalid integer");
3162 }
3163
3164 #[test]
3165 fn test_validate_value_pattern() {
3166 let schema_xml = r#"<?xml version="1.0"?>
3167<element name="status" xmlns="http://relaxng.org/ns/structure/1.0">
3168 <value>active</value>
3169</element>"#;
3170
3171 let doc_xml = r#"<?xml version="1.0"?>
3172<status>active</status>"#;
3173
3174 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3175
3176 let doc = unsafe {
3177 crate::abi::exports_xml2::xmlReadMemory(
3178 doc_xml.as_ptr() as *const c_char,
3179 doc_xml.len() as c_int,
3180 c"test.xml".as_ptr() as *const c_char,
3181 ptr::null(),
3182 0,
3183 )
3184 };
3185 assert!(!doc.is_null());
3186
3187 let mut ctxt = RelaxNgValidCtxt::new();
3188 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3189 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3190
3191 assert!(valid, "Value pattern validation failed: {:?}", ctxt.errors);
3192 }
3193
3194 #[test]
3195 fn test_validate_value_pattern_mismatch() {
3196 let schema_xml = r#"<?xml version="1.0"?>
3197<element name="status" xmlns="http://relaxng.org/ns/structure/1.0">
3198 <value>active</value>
3199</element>"#;
3200
3201 let doc_xml = r#"<?xml version="1.0"?>
3202<status>inactive</status>"#;
3203
3204 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3205
3206 let doc = unsafe {
3207 crate::abi::exports_xml2::xmlReadMemory(
3208 doc_xml.as_ptr() as *const c_char,
3209 doc_xml.len() as c_int,
3210 c"test.xml".as_ptr() as *const c_char,
3211 ptr::null(),
3212 0,
3213 )
3214 };
3215 assert!(!doc.is_null());
3216
3217 let mut ctxt = RelaxNgValidCtxt::new();
3218 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3219 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3220
3221 assert!(!valid, "Validation should have failed for value mismatch");
3222 }
3223
3224 #[test]
3225 fn test_validate_not_allowed() {
3226 let schema_xml = r#"<?xml version="1.0"?>
3227<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3228 <notAllowed/>
3229</element>"#;
3230
3231 let doc_xml = r#"<?xml version="1.0"?>
3232<root>should not be allowed</root>"#;
3233
3234 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3235
3236 let doc = unsafe {
3237 crate::abi::exports_xml2::xmlReadMemory(
3238 doc_xml.as_ptr() as *const c_char,
3239 doc_xml.len() as c_int,
3240 c"test.xml".as_ptr() as *const c_char,
3241 ptr::null(),
3242 0,
3243 )
3244 };
3245 assert!(!doc.is_null());
3246
3247 let mut ctxt = RelaxNgValidCtxt::new();
3248 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3249 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3250
3251 assert!(!valid, "notAllowed should cause validation failure");
3252 }
3253
3254 #[test]
3255 fn test_validate_interleave() {
3256 let schema_xml = r#"<?xml version="1.0"?>
3257<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3258 <start>
3259 <element name="root">
3260 <interleave>
3261 <element name="a">
3262 <text/>
3263 </element>
3264 <element name="b">
3265 <text/>
3266 </element>
3267 </interleave>
3268 </element>
3269 </start>
3270</grammar>"#;
3271
3272 let doc_xml = r#"<?xml version="1.0"?>
3273<root><a>A</a><b>B</b></root>"#;
3274
3275 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3276
3277 let doc = unsafe {
3278 crate::abi::exports_xml2::xmlReadMemory(
3279 doc_xml.as_ptr() as *const c_char,
3280 doc_xml.len() as c_int,
3281 c"test.xml".as_ptr() as *const c_char,
3282 ptr::null(),
3283 0,
3284 )
3285 };
3286 assert!(!doc.is_null());
3287
3288 let mut ctxt = RelaxNgValidCtxt::new();
3289 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3290 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3291
3292 assert!(valid, "Interleave validation failed: {:?}", ctxt.errors);
3293 }
3294
3295 #[test]
3296 fn test_validate_empty_element() {
3297 let schema_xml = r#"<?xml version="1.0"?>
3298<element name="br" xmlns="http://relaxng.org/ns/structure/1.0">
3299 <empty/>
3300</element>"#;
3301
3302 let doc_xml = r#"<?xml version="1.0"?>
3303<br/>"#;
3304
3305 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3306
3307 let doc = unsafe {
3308 crate::abi::exports_xml2::xmlReadMemory(
3309 doc_xml.as_ptr() as *const c_char,
3310 doc_xml.len() as c_int,
3311 c"test.xml".as_ptr() as *const c_char,
3312 ptr::null(),
3313 0,
3314 )
3315 };
3316 assert!(!doc.is_null());
3317
3318 let mut ctxt = RelaxNgValidCtxt::new();
3319 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3320 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3321
3322 assert!(valid, "Empty element validation failed: {:?}", ctxt.errors);
3323 }
3324
3325 #[test]
3328 fn test_validate_datatype_string() {
3329 assert!(rng_validate_datatype_value(Some("string"), "hello"));
3330 assert!(rng_validate_datatype_value(Some("string"), ""));
3331 }
3332
3333 #[test]
3334 fn test_validate_datatype_boolean() {
3335 assert!(rng_validate_datatype_value(Some("boolean"), "true"));
3336 assert!(rng_validate_datatype_value(Some("boolean"), "false"));
3337 assert!(rng_validate_datatype_value(Some("boolean"), "1"));
3338 assert!(rng_validate_datatype_value(Some("boolean"), "0"));
3339 assert!(!rng_validate_datatype_value(Some("boolean"), "yes"));
3340 assert!(!rng_validate_datatype_value(Some("boolean"), "no"));
3341 }
3342
3343 #[test]
3344 fn test_validate_datatype_integer() {
3345 assert!(rng_validate_datatype_value(Some("integer"), "42"));
3346 assert!(rng_validate_datatype_value(Some("integer"), "-42"));
3347 assert!(rng_validate_datatype_value(Some("integer"), "+42"));
3348 assert!(!rng_validate_datatype_value(Some("integer"), "12.5"));
3349 assert!(!rng_validate_datatype_value(Some("integer"), "abc"));
3350 assert!(!rng_validate_datatype_value(Some("integer"), ""));
3351 }
3352
3353 #[test]
3354 fn test_validate_datatype_decimal() {
3355 assert!(rng_validate_datatype_value(Some("decimal"), "42"));
3356 assert!(rng_validate_datatype_value(Some("decimal"), "12.5"));
3357 assert!(rng_validate_datatype_value(Some("decimal"), "-3.14"));
3358 assert!(!rng_validate_datatype_value(Some("decimal"), ""));
3359 }
3360
3361 #[test]
3362 fn test_validate_datatype_float() {
3363 assert!(rng_validate_datatype_value(Some("float"), "3.14"));
3364 assert!(rng_validate_datatype_value(Some("float"), "INF"));
3365 assert!(rng_validate_datatype_value(Some("float"), "-INF"));
3366 assert!(rng_validate_datatype_value(Some("float"), "NaN"));
3367 assert!(!rng_validate_datatype_value(Some("float"), ""));
3368 }
3369
3370 #[test]
3371 fn test_validate_datatype_ncname() {
3372 assert!(rng_validate_datatype_value(Some("NCName"), "myElement"));
3373 assert!(rng_validate_datatype_value(Some("NCName"), "_foo"));
3374 assert!(!rng_validate_datatype_value(Some("NCName"), "123abc"));
3375 assert!(!rng_validate_datatype_value(Some("NCName"), ""));
3376 }
3377
3378 #[test]
3379 fn test_validate_datatype_any_uri() {
3380 assert!(rng_validate_datatype_value(
3381 Some("anyURI"),
3382 "http://example.com"
3383 ));
3384 assert!(rng_validate_datatype_value(Some("anyURI"), "urn:isbn:1234"));
3385 assert!(!rng_validate_datatype_value(Some("anyURI"), ""));
3386 assert!(!rng_validate_datatype_value(Some("anyURI"), "has space"));
3387 }
3388
3389 #[test]
3390 fn test_validate_datatype_qname() {
3391 assert!(rng_validate_datatype_value(Some("QName"), "ns:local"));
3392 assert!(rng_validate_datatype_value(Some("QName"), "local"));
3393 assert!(!rng_validate_datatype_value(Some("QName"), ""));
3394 }
3395
3396 #[test]
3399 fn test_c_abi_new_parse_free() {
3400 let schema_xml = r#"<?xml version="1.0"?>
3401<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3402 <text/>
3403</element>"#;
3404
3405 let ctxt = unsafe {
3406 xmlRelaxNGNewMemParserCtxt(
3407 schema_xml.as_ptr() as *const c_char,
3408 schema_xml.len() as c_int,
3409 )
3410 };
3411 assert!(!ctxt.is_null(), "Parser context should not be null");
3412
3413 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3414 assert!(!schema.is_null(), "Schema should not be null");
3415
3416 unsafe { xmlRelaxNGFree(schema) };
3418 }
3419
3420 #[test]
3421 fn test_c_abi_validate_doc() {
3422 let schema_xml = r#"<?xml version="1.0"?>
3423<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3424 <text/>
3425</element>"#;
3426
3427 let doc_xml = r#"<?xml version="1.0"?>
3428<root>Hello</root>"#;
3429
3430 let ctxt = unsafe {
3431 xmlRelaxNGNewMemParserCtxt(
3432 schema_xml.as_ptr() as *const c_char,
3433 schema_xml.len() as c_int,
3434 )
3435 };
3436 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3437 assert!(!schema.is_null());
3438
3439 let valid_ctxt = unsafe { xmlRelaxNGNewValidCtxt(schema) };
3440 assert!(!valid_ctxt.is_null());
3441
3442 let doc = unsafe {
3443 crate::abi::exports_xml2::xmlReadMemory(
3444 doc_xml.as_ptr() as *const c_char,
3445 doc_xml.len() as c_int,
3446 c"test.xml".as_ptr() as *const c_char,
3447 ptr::null(),
3448 0,
3449 )
3450 };
3451 assert!(!doc.is_null());
3452
3453 let result = unsafe { xmlRelaxNGValidateDoc(valid_ctxt, doc) };
3454 assert_eq!(result, 0, "Validation should succeed");
3455
3456 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3457 unsafe { xmlRelaxNGFreeValidCtxt(valid_ctxt) };
3458 unsafe { xmlRelaxNGFree(schema) };
3459 }
3460
3461 #[test]
3462 fn test_c_abi_validate_full_element() {
3463 let schema_xml = r#"<?xml version="1.0"?>
3464<element name="item" xmlns="http://relaxng.org/ns/structure/1.0">
3465 <text/>
3466</element>"#;
3467
3468 let doc_xml = r#"<?xml version="1.0"?>
3469<root><item>Content</item></root>"#;
3470
3471 let ctxt = unsafe {
3472 xmlRelaxNGNewMemParserCtxt(
3473 schema_xml.as_ptr() as *const c_char,
3474 schema_xml.len() as c_int,
3475 )
3476 };
3477 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3478 assert!(!schema.is_null());
3479
3480 let valid_ctxt = unsafe { xmlRelaxNGNewValidCtxt(schema) };
3481 assert!(!valid_ctxt.is_null());
3482
3483 let doc = unsafe {
3484 crate::abi::exports_xml2::xmlReadMemory(
3485 doc_xml.as_ptr() as *const c_char,
3486 doc_xml.len() as c_int,
3487 c"test.xml".as_ptr() as *const c_char,
3488 ptr::null(),
3489 0,
3490 )
3491 };
3492 assert!(!doc.is_null());
3493
3494 let item = unsafe {
3496 let mut node = (*doc).children;
3498 while !node.is_null() {
3499 if (*node).type_ == XML_ELEMENT_NODE as c_int {
3500 break;
3501 }
3502 node = (*node).next;
3503 }
3504 if !node.is_null() {
3505 node = (*node).children;
3507 while !node.is_null() {
3508 if (*node).type_ == XML_ELEMENT_NODE as c_int {
3509 break;
3510 }
3511 node = (*node).next;
3512 }
3513 }
3514 node
3515 };
3516 assert!(!item.is_null(), "Should find <item> element");
3517
3518 let result = unsafe { xmlRelaxNGValidateFullElement(valid_ctxt, doc, item) };
3519 assert_eq!(result, 0, "Element validation should succeed");
3520
3521 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3522 unsafe { xmlRelaxNGFreeValidCtxt(valid_ctxt) };
3523 unsafe { xmlRelaxNGFree(schema) };
3524 }
3525
3526 #[test]
3527 fn test_c_abi_null_handling() {
3528 assert_eq!(
3530 unsafe { xmlRelaxNGValidateDoc(ptr::null_mut(), ptr::null_mut()) },
3531 -1
3532 );
3533 assert_eq!(
3534 unsafe {
3535 xmlRelaxNGValidateFullElement(ptr::null_mut(), ptr::null_mut(), ptr::null_mut())
3536 },
3537 -1
3538 );
3539 assert!(unsafe { xmlRelaxNGNewMemParserCtxt(ptr::null(), 0).is_null() });
3540
3541 unsafe { xmlRelaxNGFree(ptr::null_mut()) };
3543 unsafe { xmlRelaxNGFreeParserCtxt(ptr::null_mut()) };
3544 unsafe { xmlRelaxNGFreeValidCtxt(ptr::null_mut()) };
3545 }
3546
3547 #[test]
3550 fn test_parse_with_div() {
3551 let schema_xml = r#"<?xml version="1.0"?>
3552<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3553 <div>
3554 <define name="shared">
3555 <text/>
3556 </define>
3557 </div>
3558 <start>
3559 <element name="root">
3560 <ref name="shared"/>
3561 </element>
3562 </start>
3563</grammar>"#;
3564
3565 let result = rng_parse(schema_xml);
3566 assert!(
3567 result.is_ok(),
3568 "Failed to parse with div: {:?}",
3569 result.err()
3570 );
3571 let schema = result.unwrap();
3572 assert_eq!(schema.grammar.defines.len(), 1);
3573 assert_eq!(schema.grammar.defines[0].name, "shared");
3574 }
3575
3576 #[test]
3577 fn test_validate_list_pattern() {
3578 let schema_xml = r#"<?xml version="1.0"?>
3579<element name="tokens" xmlns="http://relaxng.org/ns/structure/1.0">
3580 <list>
3581 <data type="token"/>
3582 </list>
3583</element>"#;
3584
3585 let doc_xml = r#"<?xml version="1.0"?>
3586<tokens>abc def ghi</tokens>"#;
3587
3588 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3589
3590 let doc = unsafe {
3591 crate::abi::exports_xml2::xmlReadMemory(
3592 doc_xml.as_ptr() as *const c_char,
3593 doc_xml.len() as c_int,
3594 c"test.xml".as_ptr() as *const c_char,
3595 ptr::null(),
3596 0,
3597 )
3598 };
3599 assert!(!doc.is_null());
3600
3601 let mut ctxt = RelaxNgValidCtxt::new();
3602 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3603 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3604
3605 assert!(valid, "List pattern validation failed: {:?}", ctxt.errors);
3606 }
3607
3608 #[test]
3609 fn test_validate_group_pattern() {
3610 let schema_xml = r#"<?xml version="1.0"?>
3611<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3612 <start>
3613 <element name="root">
3614 <group>
3615 <element name="a">
3616 <text/>
3617 </element>
3618 <element name="b">
3619 <text/>
3620 </element>
3621 </group>
3622 </element>
3623 </start>
3624</grammar>"#;
3625
3626 let doc_xml = r#"<?xml version="1.0"?>
3627<root><a>First</a><b>Second</b></root>"#;
3628
3629 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3630
3631 let doc = unsafe {
3632 crate::abi::exports_xml2::xmlReadMemory(
3633 doc_xml.as_ptr() as *const c_char,
3634 doc_xml.len() as c_int,
3635 c"test.xml".as_ptr() as *const c_char,
3636 ptr::null(),
3637 0,
3638 )
3639 };
3640 assert!(!doc.is_null());
3641
3642 let mut ctxt = RelaxNgValidCtxt::new();
3643 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3644 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3645
3646 assert!(valid, "Group validation failed: {:?}", ctxt.errors);
3647 }
3648
3649 #[test]
3650 fn test_validate_undefined_ref() {
3651 let schema_xml = r#"<?xml version="1.0"?>
3652<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3653 <start>
3654 <element name="root">
3655 <ref name="undefined"/>
3656 </element>
3657 </start>
3658</grammar>"#;
3659
3660 let doc_xml = r#"<?xml version="1.0"?>
3661<root>Content</root>"#;
3662
3663 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3664
3665 let doc = unsafe {
3666 crate::abi::exports_xml2::xmlReadMemory(
3667 doc_xml.as_ptr() as *const c_char,
3668 doc_xml.len() as c_int,
3669 c"test.xml".as_ptr() as *const c_char,
3670 ptr::null(),
3671 0,
3672 )
3673 };
3674 assert!(!doc.is_null());
3675
3676 let mut ctxt = RelaxNgValidCtxt::new();
3677 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3678 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3679
3680 assert!(!valid, "Undefined ref should cause failure");
3681 }
3682
3683 #[test]
3684 fn test_validate_null_doc() {
3685 let schema = RelaxNgSchema::new();
3686 let mut ctxt = RelaxNgValidCtxt::new();
3687 let valid = unsafe { rng_validate_doc(&schema, ptr::null_mut(), &mut ctxt) };
3688 assert!(!valid);
3689 }
3690
3691 #[test]
3692 fn test_parse_external_ref_schema() {
3693 let schema_xml = r#"<?xml version="1.0"?>
3694<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3695 <externalRef href="external.rng"/>
3696</element>"#;
3697
3698 let result = rng_parse(schema_xml);
3699 assert!(
3700 result.is_ok(),
3701 "Failed to parse externalRef: {:?}",
3702 result.err()
3703 );
3704 let schema = result.unwrap();
3705 if let Some(ref start) = schema.grammar.start {
3706 assert_eq!(start.pattern_type, RelaxNgPatternType::Element);
3707 assert_eq!(start.name.as_deref(), Some("root"));
3708 }
3709 }
3710
3711 #[test]
3712 fn test_validate_boolean_datatype() {
3713 assert!(rng_validate_datatype_value(Some("boolean"), "true"));
3714 assert!(rng_validate_datatype_value(Some("boolean"), "false"));
3715 assert!(!rng_validate_datatype_value(Some("boolean"), "maybe"));
3716 }
3717
3718 #[test]
3719 fn test_validate_unknown_datatype() {
3720 assert!(rng_validate_datatype_value(Some("custom-type"), "anything"));
3722 }
3723
3724 #[test]
3725 fn test_validate_no_datatype() {
3726 assert!(rng_validate_datatype_value(None, "anything"));
3728 }
3729
3730 #[test]
3731 fn test_parse_schema_with_ns_prefix() {
3732 let schema_xml = r#"<?xml version="1.0"?>
3733<rng:element name="root" xmlns:rng="http://relaxng.org/ns/structure/1.0">
3734 <rng:text/>
3735</rng:element>"#;
3736
3737 let result = rng_parse(schema_xml);
3738 assert!(result.is_ok(), "Failed with ns prefix: {:?}", result.err());
3739 }
3740
3741 #[test]
3742 fn test_validation_context_path() {
3743 let mut ctxt = RelaxNgValidCtxt::new();
3744 assert_eq!(ctxt.current_path(), "/");
3745
3746 ctxt.path.push("root".to_string());
3747 assert_eq!(ctxt.current_path(), "/root");
3748
3749 ctxt.path.push("child".to_string());
3750 assert_eq!(ctxt.current_path(), "/root/child");
3751
3752 ctxt.path.pop();
3753 assert_eq!(ctxt.current_path(), "/root");
3754 }
3755
3756 #[test]
3757 fn test_validate_sequence_wrong_order() {
3758 let schema_xml = r#"<?xml version="1.0"?>
3759<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3760 <start>
3761 <element name="root">
3762 <sequence>
3763 <element name="first">
3764 <text/>
3765 </element>
3766 <element name="second">
3767 <text/>
3768 </element>
3769 </sequence>
3770 </element>
3771 </start>
3772</grammar>"#;
3773
3774 let doc_xml = r#"<?xml version="1.0"?>
3775<root><second>Wrong</second><first>Order</first></root>"#;
3776
3777 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3778
3779 let doc = unsafe {
3780 crate::abi::exports_xml2::xmlReadMemory(
3781 doc_xml.as_ptr() as *const c_char,
3782 doc_xml.len() as c_int,
3783 c"test.xml".as_ptr() as *const c_char,
3784 ptr::null(),
3785 0,
3786 )
3787 };
3788 assert!(!doc.is_null());
3789
3790 let mut ctxt = RelaxNgValidCtxt::new();
3791 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3792 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3793
3794 assert!(!valid, "Wrong sequence order should fail");
3797 }
3798}