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::structs::*;
89use crate::abi::types::xmlElementType::*;
90
91#[derive(Debug, Clone, PartialEq)]
102pub enum RelaxNgNameClass {
103 Name(String),
105 AnyName,
107 NsName(String),
109 Choice(Vec<RelaxNgNameClass>),
111 Except(Box<RelaxNgNameClass>, Box<RelaxNgNameClass>),
113}
114
115impl RelaxNgNameClass {
116 pub fn matches(&self, name: &str, ns_uri: Option<&str>) -> bool {
118 match self {
119 RelaxNgNameClass::Name(n) => name == n.as_str(),
120 RelaxNgNameClass::AnyName => true,
121 RelaxNgNameClass::NsName(ns) => {
122 if let Some(uri) = ns_uri {
123 uri == ns.as_str()
124 } else {
125 false
126 }
127 }
128 RelaxNgNameClass::Choice(choices) => choices.iter().any(|c| c.matches(name, ns_uri)),
129 RelaxNgNameClass::Except(positive, negative) => {
130 positive.matches(name, ns_uri) && !negative.matches(name, ns_uri)
131 }
132 }
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum RelaxNgPatternType {
147 Element,
148 Attribute,
149 Text,
150 Choice,
151 Sequence,
152 Interleave,
153 ZeroOrMore,
154 OneOrMore,
155 Optional,
156 List,
157 Group,
158 Data,
159 Value,
160 Ref,
161 Define,
162 Grammar,
163 NotAllowed,
164 Empty,
165 ExternalRef,
166 Include,
167 Start,
168}
169
170#[derive(Debug, Clone)]
174pub struct RelaxNgPattern {
175 pub pattern_type: RelaxNgPatternType,
177 pub name_class: Option<RelaxNgNameClass>,
179 pub children: Vec<RelaxNgPattern>,
181 pub name: Option<String>,
183 pub ns: Option<String>,
185 pub datatype: Option<String>,
187 pub value: Option<String>,
189 pub datatype_library: Option<String>,
191}
192
193impl RelaxNgPattern {
194 pub const fn new(pattern_type: RelaxNgPatternType) -> Self {
196 Self {
197 pattern_type,
198 name_class: None,
199 children: Vec::new(),
200 name: None,
201 ns: None,
202 datatype: None,
203 value: None,
204 datatype_library: None,
205 }
206 }
207
208 pub fn element(name: &str) -> Self {
210 let mut p = Self::new(RelaxNgPatternType::Element);
211 p.name_class = Some(RelaxNgNameClass::Name(name.to_string()));
212 p
213 }
214
215 pub fn attribute(name: &str) -> Self {
217 let mut p = Self::new(RelaxNgPatternType::Attribute);
218 p.name_class = Some(RelaxNgNameClass::Name(name.to_string()));
219 p
220 }
221
222 pub const fn text() -> Self {
224 Self::new(RelaxNgPatternType::Text)
225 }
226
227 pub const fn empty() -> Self {
229 Self::new(RelaxNgPatternType::Empty)
230 }
231
232 pub const fn not_allowed() -> Self {
234 Self::new(RelaxNgPatternType::NotAllowed)
235 }
236}
237
238#[derive(Debug, Clone)]
244pub struct RelaxNgDefine {
245 pub name: String,
247 pub pattern: RelaxNgPattern,
249}
250
251#[derive(Debug, Clone)]
259pub struct RelaxNgGrammar {
260 pub defines: Vec<RelaxNgDefine>,
262 pub start: Option<RelaxNgPattern>,
264 pub includes: Vec<RelaxNgGrammar>,
266}
267
268impl RelaxNgGrammar {
269 pub const fn new() -> Self {
270 Self {
271 defines: Vec::new(),
272 start: None,
273 includes: Vec::new(),
274 }
275 }
276
277 pub fn lookup(&self, name: &str) -> Option<&RelaxNgPattern> {
279 for def in &self.defines {
280 if def.name == name {
281 return Some(&def.pattern);
282 }
283 }
284 for inc in &self.includes {
286 if let Some(p) = inc.lookup(name) {
287 return Some(p);
288 }
289 }
290 None
291 }
292}
293
294impl Default for RelaxNgGrammar {
295 fn default() -> Self {
296 Self::new()
297 }
298}
299
300#[derive(Debug, Clone)]
308pub struct RelaxNgSchema {
309 pub grammar: RelaxNgGrammar,
311 pub errors: Vec<String>,
313 pub fatal: bool,
319}
320
321impl RelaxNgSchema {
322 pub const fn new() -> Self {
323 Self {
324 grammar: RelaxNgGrammar::new(),
325 errors: Vec::new(),
326 fatal: false,
327 }
328 }
329}
330
331impl Default for RelaxNgSchema {
332 fn default() -> Self {
333 Self::new()
334 }
335}
336
337#[derive(Debug)]
347pub struct RelaxNgValidCtxt {
348 pub schema: Option<RelaxNgSchema>,
350 pub errors: Vec<String>,
352 pub nb_errors: i32,
354 pub path: Vec<String>,
356 pub depth_max: i32,
358 pub depth: i32,
360}
361
362impl RelaxNgValidCtxt {
363 pub const fn new() -> Self {
364 Self {
365 schema: None,
366 errors: Vec::new(),
367 nb_errors: 0,
368 path: Vec::new(),
369 depth_max: 256,
370 depth: 0,
371 }
372 }
373
374 pub fn record_error(&mut self, msg: String) {
376 self.errors.push(msg);
377 self.nb_errors += 1;
378 }
379
380 pub fn current_path(&self) -> String {
382 if self.path.is_empty() {
383 "/".to_string()
384 } else {
385 format!("/{}", self.path.join("/"))
386 }
387 }
388}
389
390impl Default for RelaxNgValidCtxt {
391 fn default() -> Self {
392 Self::new()
393 }
394}
395
396unsafe fn get_local_name(node: *mut _xmlNode) -> String {
406 if node.is_null() {
407 return String::new();
408 }
409 unsafe {
410 let name = (*node).name;
411 if name.is_null() {
412 return String::new();
413 }
414 let mut len = 0;
415 while *name.add(len) != 0 {
416 len += 1;
417 }
418 let slice = std::slice::from_raw_parts(name, len);
419 if let Ok(s) = std::str::from_utf8(slice) {
420 if let Some(pos) = s.find(':') {
421 s[pos + 1..].to_string()
422 } else {
423 s.to_string()
424 }
425 } else {
426 String::new()
427 }
428 }
429}
430
431unsafe fn get_node_text(node: *mut _xmlNode) -> String {
437 if node.is_null() {
438 return String::new();
439 }
440 let mut result = String::new();
441 unsafe {
442 let mut child = (*node).children;
443 while !child.is_null() {
444 if ((*child).type_ == XML_TEXT_NODE as c_int
445 || (*child).type_ == XML_CDATA_SECTION_NODE as c_int)
446 && !(*child).content.is_null()
447 {
448 let content = (*child).content;
449 let mut len = 0;
450 while *content.add(len) != 0 {
451 len += 1;
452 }
453 let slice = std::slice::from_raw_parts(content, len);
454 result.push_str(&String::from_utf8_lossy(slice));
455 }
456 child = (*child).next;
457 }
458 }
459 result
460}
461
462unsafe fn get_node_qname(node: *mut _xmlNode) -> String {
468 if node.is_null() {
469 return String::new();
470 }
471 unsafe {
472 let ns = (*node).ns;
473 let prefix = if !ns.is_null() && !(*ns).prefix.is_null() {
474 let mut len = 0;
475 while *(*ns).prefix.add(len) != 0 {
476 len += 1;
477 }
478 let slice = std::slice::from_raw_parts((*ns).prefix, len);
479 if let Ok(s) = std::str::from_utf8(slice) {
480 format!("{}:", s)
481 } else {
482 String::new()
483 }
484 } else {
485 String::new()
486 };
487
488 let name = (*node).name;
489 if name.is_null() {
490 return String::new();
491 }
492 let mut len = 0;
493 while *name.add(len) != 0 {
494 len += 1;
495 }
496 let slice = std::slice::from_raw_parts(name, len);
497 if let Ok(s) = std::str::from_utf8(slice) {
498 format!("{}{}", prefix, s)
499 } else {
500 String::new()
501 }
502 }
503}
504
505unsafe fn get_node_ns_uri(node: *mut _xmlNode) -> Option<String> {
511 if node.is_null() {
512 return None;
513 }
514 unsafe {
515 let ns = (*node).ns;
516 if ns.is_null() || (*ns).href.is_null() {
517 return None;
518 }
519 let href = (*ns).href;
520 let mut len = 0;
521 while *href.add(len) != 0 {
522 len += 1;
523 }
524 let slice = std::slice::from_raw_parts(href, len);
525 if let Ok(s) = std::str::from_utf8(slice) {
526 Some(s.to_string())
527 } else {
528 None
529 }
530 }
531}
532
533unsafe fn get_attr(node: *mut _xmlNode, name: &str) -> Option<String> {
539 if node.is_null() {
540 return None;
541 }
542 unsafe {
543 let mut prop = (*node).properties;
544 while !prop.is_null() {
545 let prop_name = (*prop).name;
546 if !prop_name.is_null() {
547 let mut len = 0;
548 while *prop_name.add(len) != 0 {
549 len += 1;
550 }
551 let slice = std::slice::from_raw_parts(prop_name, len);
552 if let Ok(s) = std::str::from_utf8(slice) {
553 if s == name {
554 return Some(get_node_text(prop as *mut _xmlNode));
555 }
556 }
557 }
558 prop = (*prop).next;
559 }
560 }
561 None
562}
563
564#[allow(dead_code)]
570unsafe fn node_is(node: *mut _xmlNode, local_name: &str) -> bool {
571 if node.is_null() {
572 return false;
573 }
574 unsafe {
575 let name = (*node).name;
576 if name.is_null() {
577 return false;
578 }
579 let mut len = 0;
580 while *name.add(len) != 0 {
581 len += 1;
582 }
583 let slice = std::slice::from_raw_parts(name, len);
584 if let Ok(s) = std::str::from_utf8(slice) {
585 let local = if let Some(pos) = s.find(':') {
586 &s[pos + 1..]
587 } else {
588 s
589 };
590 return local == local_name;
591 }
592 }
593 false
594}
595
596pub fn rng_parse(xml_doc: &str) -> Result<RelaxNgSchema, String> {
617 let doc_ptr = unsafe {
618 crate::abi::exports_xml2::xmlReadMemory(
619 xml_doc.as_ptr() as *const c_char,
620 xml_doc.len() as c_int,
621 c"schema.rng".as_ptr() as *const c_char,
622 ptr::null(),
623 0,
624 )
625 };
626
627 if doc_ptr.is_null() {
628 return Err("Failed to parse RELAX NG schema XML document".to_string());
629 }
630
631 let result = unsafe { rng_parse_doc(doc_ptr) };
632 unsafe {
633 crate::abi::exports_xml2::xmlFreeDoc(doc_ptr);
634 }
635 result
636}
637
638unsafe fn doc_url(doc: *mut _xmlDoc) -> Option<String> {
644 if doc.is_null() {
645 return None;
646 }
647 unsafe {
648 let url = (*doc).URL;
649 if url.is_null() {
650 return None;
651 }
652 let mut len = 0;
653 while *url.add(len) != 0 {
654 len += 1;
655 }
656 let slice = std::slice::from_raw_parts(url, len);
657 Some(String::from_utf8_lossy(slice).to_string())
658 }
659}
660
661unsafe fn resolve_rng_href(doc: *mut _xmlDoc, href: &str) -> String {
671 let Some(base) = (unsafe { doc_url(doc) }) else {
672 return href.to_string();
675 };
676 if base.is_empty() {
677 return href.to_string();
678 }
679 let Ok(base_c) = std::ffi::CString::new(base.as_str()) else {
680 return href.to_string();
681 };
682 let Ok(href_c) = std::ffi::CString::new(href) else {
683 return href.to_string();
684 };
685 let resolved = crate::abi::exports_uri::xmlBuildURI(href_c.as_ptr(), base_c.as_ptr());
686 if resolved.is_null() {
687 href.to_string()
688 } else {
689 unsafe {
691 let mut len = 0;
692 while *resolved.add(len) != 0 {
693 len += 1;
694 }
695 let slice = std::slice::from_raw_parts(resolved, len);
696 let s = String::from_utf8_lossy(slice).to_string();
697 crate::abi::allocator::xmlFreeImpl(resolved as *mut core::ffi::c_void);
698 s
699 }
700 }
701}
702
703unsafe fn rng_parse_doc(doc: *mut _xmlDoc) -> Result<RelaxNgSchema, String> {
709 unsafe {
710 let root = (*doc).children;
711 if root.is_null() {
712 return Err("RELAX NG document has no root element".to_string());
713 }
714
715 let mut root_elem = root;
717 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
718 root_elem = (*root_elem).next;
719 }
720
721 if root_elem.is_null() {
722 return Err("RELAX NG document has no root element".to_string());
723 }
724
725 let base = unsafe { doc_url(doc) };
726 let local_name = get_local_name(root_elem);
727 let mut schema = RelaxNgSchema::new();
728
729 match local_name.as_str() {
730 "grammar" => {
731 schema.grammar = rng_parse_grammar_node(root_elem, &mut schema, base.as_deref());
733 Ok(schema)
734 }
735 "element" | "attribute" | "text" | "choice" | "sequence" | "interleave"
736 | "zeroOrMore" | "oneOrMore" | "optional" | "list" | "group" | "data" | "value"
737 | "ref" | "notAllowed" | "empty" | "externalRef" | "define" | "start" | "include" => {
738 let pattern = rng_parse_pattern(root_elem, &mut schema);
740 schema.grammar.start = Some(pattern);
741 Ok(schema)
742 }
743 _ => Err(format!("Unknown RELAX NG root element: '{}'", local_name)),
744 }
745 }
746}
747
748unsafe fn rng_parse_grammar_node(
757 node: *mut _xmlNode,
758 schema: &mut RelaxNgSchema,
759 base: Option<&str>,
760) -> RelaxNgGrammar {
761 unsafe {
762 let mut grammar = RelaxNgGrammar::new();
763
764 let mut child = (*node).children;
765 while !child.is_null() {
766 if (*child).type_ == XML_ELEMENT_NODE as c_int {
767 let local = get_local_name(child);
768 match local.as_str() {
769 "define" => {
770 let def = rng_parse_define(child, schema);
771 grammar.defines.push(def);
772 }
773 "start" => {
774 grammar.start = Some(rng_parse_pattern(child, schema));
775 }
776 "include" => {
777 if let Some(inc) = rng_parse_include(child, schema, base) {
778 if grammar.start.is_none() {
782 grammar.start = inc.start.clone();
783 }
784 grammar.includes.push(inc);
785 }
786 }
787 "div" => {
788 let sub_grammar = rng_parse_grammar_node(child, schema, base);
790 grammar.defines.extend(sub_grammar.defines);
791 if sub_grammar.start.is_some() {
792 grammar.start = sub_grammar.start;
793 }
794 grammar.includes.extend(sub_grammar.includes);
795 }
796 _ => {
797 schema
799 .errors
800 .push(format!("Unexpected element '<{}>' in grammar", local));
801 }
802 }
803 }
804 child = (*child).next;
805 }
806
807 grammar
808 }
809}
810
811unsafe fn rng_parse_define(node: *mut _xmlNode, schema: &mut RelaxNgSchema) -> RelaxNgDefine {
817 unsafe {
818 let name = get_attr(node, "name").unwrap_or_default();
819 let pattern = rng_parse_pattern(node, schema);
820 RelaxNgDefine { name, pattern }
821 }
822}
823
824unsafe fn rng_parse_include(
840 node: *mut _xmlNode,
841 schema: &mut RelaxNgSchema,
842 base: Option<&str>,
843) -> Option<RelaxNgGrammar> {
844 unsafe {
845 let href = get_attr(node, "href");
846 let mut grammar = if let Some(ref url) = href {
847 let resolved = match base {
851 Some(base) => {
852 let base_c = std::ffi::CString::new(base).ok()?;
853 let href_c = std::ffi::CString::new(url.clone()).ok()?;
854 let r = crate::abi::exports_uri::xmlBuildURI(href_c.as_ptr(), base_c.as_ptr());
855 if r.is_null() {
856 url.clone()
857 } else {
858 let mut len = 0;
859 while *r.add(len) != 0 {
860 len += 1;
861 }
862 let slice = std::slice::from_raw_parts(r, len);
863 let s = String::from_utf8_lossy(slice).to_string();
864 crate::abi::allocator::xmlFreeImpl(r as *mut core::ffi::c_void);
865 s
866 }
867 }
868 None => url.clone(),
869 };
870 let url_c = std::ffi::CString::new(resolved.clone()).ok()?;
871 let doc = crate::abi::exports_xml2::xmlParseFile(url_c.as_ptr());
872 if doc.is_null() {
873 return None;
874 }
875 let mut inc_schema = RelaxNgSchema::new();
876 let grammar = rng_parse_grammar_node(
877 {
878 let mut root = (*doc).children;
879 while !root.is_null() && (*root).type_ != XML_ELEMENT_NODE as c_int {
880 root = (*root).next;
881 }
882 root
883 },
884 &mut inc_schema,
885 doc_url(doc).as_deref(),
886 );
887 crate::abi::exports_xml2::xmlFreeDoc(doc);
888 grammar
889 } else {
890 let mut inc_schema = RelaxNgSchema::new();
892 rng_parse_grammar_node(node, &mut inc_schema, base)
893 };
894
895 if href.is_some() {
901 let mut ovr_schema = RelaxNgSchema::new();
902 let ovr = rng_parse_grammar_node(node, &mut ovr_schema, base);
903 for def in ovr.defines {
904 if let Some(existing) = grammar.defines.iter_mut().find(|d| d.name == def.name) {
905 *existing = def;
906 } else {
907 grammar.defines.push(def);
908 }
909 }
910 if ovr.start.is_some() {
911 grammar.start = ovr.start;
912 }
913 grammar.includes.extend(ovr.includes);
914 }
915 Some(grammar)
916 }
917}
918
919unsafe fn rng_parse_pattern(node: *mut _xmlNode, schema: &mut RelaxNgSchema) -> RelaxNgPattern {
925 unsafe {
926 let local = get_local_name(node);
927
928 match local.as_str() {
929 "element" => rng_parse_element_pattern(node, schema),
930 "attribute" => rng_parse_attribute_pattern(node, schema),
931 "text" => RelaxNgPattern::text(),
932 "empty" => RelaxNgPattern::empty(),
933 "notAllowed" => RelaxNgPattern::not_allowed(),
934 "choice" => rng_parse_composite_pattern(node, RelaxNgPatternType::Choice, schema),
935 "sequence" => rng_parse_composite_pattern(node, RelaxNgPatternType::Sequence, schema),
936 "interleave" => {
937 rng_parse_composite_pattern(node, RelaxNgPatternType::Interleave, schema)
938 }
939 "zeroOrMore" => rng_parse_unary_pattern(node, RelaxNgPatternType::ZeroOrMore, schema),
940 "oneOrMore" => rng_parse_unary_pattern(node, RelaxNgPatternType::OneOrMore, schema),
941 "optional" => rng_parse_unary_pattern(node, RelaxNgPatternType::Optional, schema),
942 "list" => rng_parse_unary_pattern(node, RelaxNgPatternType::List, schema),
943 "group" => rng_parse_composite_pattern(node, RelaxNgPatternType::Group, schema),
944 "data" => rng_parse_data_pattern(node, schema),
945 "value" => rng_parse_value_pattern(node, schema),
946 "ref" => rng_parse_ref_pattern(node),
947 "externalRef" => rng_parse_external_ref(node, schema),
948 "define" | "start" | "grammar" | "include" | "div" => {
949 let mut child = (*node).children;
951 let mut result = RelaxNgPattern::empty();
952 while !child.is_null() {
953 if (*child).type_ == XML_ELEMENT_NODE as c_int {
954 result = rng_parse_pattern(child, schema);
955 break;
956 }
957 child = (*child).next;
958 }
959 result
960 }
961 _ => {
962 schema
964 .errors
965 .push(format!("Unknown pattern element '<{}>'", local));
966 RelaxNgPattern::empty()
967 }
968 }
969 }
970}
971
972unsafe fn rng_parse_element_pattern(
978 node: *mut _xmlNode,
979 schema: &mut RelaxNgSchema,
980) -> RelaxNgPattern {
981 unsafe {
982 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Element);
983
984 let name_attr = get_attr(node, "name");
986 pattern.name = name_attr.clone();
987
988 if let Some(ref n) = name_attr {
989 pattern.name_class = Some(RelaxNgNameClass::Name(n.clone()));
990 }
991
992 let mut child = (*node).children;
994 let mut has_content = false;
995
996 while !child.is_null() {
997 if (*child).type_ == XML_ELEMENT_NODE as c_int {
998 let child_local = get_local_name(child);
999
1000 match child_local.as_str() {
1001 "name" => {
1002 let text = get_node_text(child);
1003 if !text.is_empty() {
1004 pattern.name_class =
1005 Some(RelaxNgNameClass::Name(text.trim().to_string()));
1006 }
1007 }
1008 "anyName" => {
1009 pattern.name_class = Some(rng_parse_any_name(child));
1010 }
1011 "nsName" => {
1012 pattern.name_class = Some(rng_parse_ns_name(child));
1013 }
1014 "choice" if pattern.name_class.is_none() => {
1015 pattern.name_class = Some(rng_parse_name_class_choice(child));
1017 }
1018 _ => {
1019 pattern.children.push(rng_parse_pattern(child, schema));
1027 has_content = true;
1028 }
1029 }
1030 }
1031 child = (*child).next;
1032 }
1033
1034 if !has_content {
1039 schema
1040 .errors
1041 .push("xmlRelaxNGParseElement: element has no content".to_string());
1042 schema.fatal = true;
1043 }
1044
1045 pattern
1046 }
1047}
1048
1049unsafe fn rng_parse_attribute_pattern(
1055 node: *mut _xmlNode,
1056 schema: &mut RelaxNgSchema,
1057) -> RelaxNgPattern {
1058 unsafe {
1059 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Attribute);
1060
1061 let name_attr = get_attr(node, "name");
1063 pattern.name = name_attr.clone();
1064
1065 if let Some(ref n) = name_attr {
1066 pattern.name_class = Some(RelaxNgNameClass::Name(n.clone()));
1067 }
1068
1069 let mut child = (*node).children;
1071 while !child.is_null() {
1072 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1073 let child_local = get_local_name(child);
1074
1075 match child_local.as_str() {
1076 "name" => {
1077 let text = get_node_text(child);
1078 if !text.is_empty() {
1079 pattern.name_class =
1080 Some(RelaxNgNameClass::Name(text.trim().to_string()));
1081 }
1082 }
1083 "anyName" => {
1084 pattern.name_class = Some(rng_parse_any_name(child));
1085 }
1086 "nsName" => {
1087 pattern.name_class = Some(rng_parse_ns_name(child));
1088 }
1089 "choice" if pattern.name_class.is_none() => {
1090 pattern.name_class = Some(rng_parse_name_class_choice(child));
1091 }
1092 _ => {
1093 pattern.children.push(rng_parse_pattern(child, schema));
1095 }
1096 }
1097 }
1098 child = (*child).next;
1099 }
1100
1101 pattern
1102 }
1103}
1104
1105unsafe fn rng_parse_any_name(node: *mut _xmlNode) -> RelaxNgNameClass {
1111 unsafe {
1112 let mut child = (*node).children;
1114 while !child.is_null() {
1115 if (*child).type_ == XML_ELEMENT_NODE as c_int && get_local_name(child) == "except" {
1116 let except_nc = rng_parse_name_class_content(child);
1117 return RelaxNgNameClass::Except(
1118 Box::new(RelaxNgNameClass::AnyName),
1119 Box::new(except_nc),
1120 );
1121 }
1122 child = (*child).next;
1123 }
1124 RelaxNgNameClass::AnyName
1125 }
1126}
1127
1128unsafe fn rng_parse_ns_name(node: *mut _xmlNode) -> RelaxNgNameClass {
1134 unsafe {
1135 let ns = get_attr(node, "ns").unwrap_or_default();
1136
1137 let mut child = (*node).children;
1139 while !child.is_null() {
1140 if (*child).type_ == XML_ELEMENT_NODE as c_int && get_local_name(child) == "except" {
1141 let except_nc = rng_parse_name_class_content(child);
1142 return RelaxNgNameClass::Except(
1143 Box::new(RelaxNgNameClass::NsName(ns)),
1144 Box::new(except_nc),
1145 );
1146 }
1147 child = (*child).next;
1148 }
1149
1150 RelaxNgNameClass::NsName(ns)
1151 }
1152}
1153
1154unsafe fn rng_parse_name_class_choice(node: *mut _xmlNode) -> RelaxNgNameClass {
1160 unsafe {
1161 let mut choices = Vec::new();
1162 let mut child = (*node).children;
1163 while !child.is_null() {
1164 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1165 choices.push(rng_parse_name_class_item(child));
1166 }
1167 child = (*child).next;
1168 }
1169 if choices.len() == 1 {
1170 choices.remove(0)
1171 } else {
1172 RelaxNgNameClass::Choice(choices)
1173 }
1174 }
1175}
1176
1177unsafe fn rng_parse_name_class_item(node: *mut _xmlNode) -> RelaxNgNameClass {
1183 unsafe {
1184 let local = get_local_name(node);
1185 match local.as_str() {
1186 "name" => {
1187 let text = get_node_text(node);
1188 RelaxNgNameClass::Name(text.trim().to_string())
1189 }
1190 "anyName" => rng_parse_any_name(node),
1191 "nsName" => rng_parse_ns_name(node),
1192 "choice" => rng_parse_name_class_choice(node),
1193 _ => {
1194 let text = get_node_text(node);
1196 if text.trim().is_empty() {
1197 RelaxNgNameClass::AnyName
1198 } else {
1199 RelaxNgNameClass::Name(text.trim().to_string())
1200 }
1201 }
1202 }
1203 }
1204}
1205
1206unsafe fn rng_parse_name_class_content(node: *mut _xmlNode) -> RelaxNgNameClass {
1212 unsafe {
1213 let mut names = Vec::new();
1214 let mut child = (*node).children;
1215 while !child.is_null() {
1216 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1217 names.push(rng_parse_name_class_item(child));
1218 }
1219 child = (*child).next;
1220 }
1221 if names.is_empty() {
1222 RelaxNgNameClass::AnyName
1223 } else if names.len() == 1 {
1224 names.remove(0)
1225 } else {
1226 RelaxNgNameClass::Choice(names)
1227 }
1228 }
1229}
1230
1231unsafe fn rng_parse_composite_pattern(
1237 node: *mut _xmlNode,
1238 pattern_type: RelaxNgPatternType,
1239 schema: &mut RelaxNgSchema,
1240) -> RelaxNgPattern {
1241 unsafe {
1242 let mut pattern = RelaxNgPattern::new(pattern_type);
1243
1244 let mut child = (*node).children;
1245 while !child.is_null() {
1246 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1247 pattern.children.push(rng_parse_pattern(child, schema));
1248 }
1249 child = (*child).next;
1250 }
1251
1252 pattern
1253 }
1254}
1255
1256unsafe fn rng_parse_unary_pattern(
1262 node: *mut _xmlNode,
1263 pattern_type: RelaxNgPatternType,
1264 schema: &mut RelaxNgSchema,
1265) -> RelaxNgPattern {
1266 unsafe {
1267 let mut pattern = RelaxNgPattern::new(pattern_type);
1268
1269 let mut child = (*node).children;
1270 while !child.is_null() {
1271 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1272 pattern.children.push(rng_parse_pattern(child, schema));
1273 break;
1275 }
1276 child = (*child).next;
1277 }
1278
1279 pattern
1280 }
1281}
1282
1283unsafe fn rng_parse_data_pattern(
1289 node: *mut _xmlNode,
1290 _schema: &mut RelaxNgSchema,
1291) -> RelaxNgPattern {
1292 unsafe {
1293 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Data);
1294 pattern.datatype = get_attr(node, "type");
1295 pattern.datatype_library = get_attr(node, "datatypeLibrary");
1296
1297 pattern
1299 }
1300}
1301
1302unsafe fn rng_parse_value_pattern(
1308 node: *mut _xmlNode,
1309 _schema: &mut RelaxNgSchema,
1310) -> RelaxNgPattern {
1311 unsafe {
1312 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Value);
1313 pattern.datatype = get_attr(node, "type");
1314 pattern.datatype_library = get_attr(node, "datatypeLibrary");
1315 pattern.value = Some(get_node_text(node).trim().to_string());
1316
1317 pattern
1318 }
1319}
1320
1321unsafe fn rng_parse_ref_pattern(node: *mut _xmlNode) -> RelaxNgPattern {
1327 unsafe {
1328 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Ref);
1329 pattern.name = get_attr(node, "name");
1330 pattern
1331 }
1332}
1333
1334unsafe fn rng_parse_external_ref(
1340 node: *mut _xmlNode,
1341 _schema: &mut RelaxNgSchema,
1342) -> RelaxNgPattern {
1343 unsafe {
1344 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::ExternalRef);
1345 let href = get_attr(node, "href");
1346 pattern.name = href;
1347 pattern
1348 }
1349}
1350
1351pub unsafe fn rng_validate_doc(
1365 schema: &RelaxNgSchema,
1366 doc: *mut _xmlDoc,
1367 ctxt: &mut RelaxNgValidCtxt,
1368) -> bool {
1369 unsafe {
1370 if doc.is_null() {
1371 ctxt.record_error("Document is null".to_string());
1372 return false;
1373 }
1374
1375 let root = (*doc).children;
1376 if root.is_null() {
1377 ctxt.record_error("Document has no children".to_string());
1378 return false;
1379 }
1380
1381 let mut root_elem = root;
1383 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
1384 root_elem = (*root_elem).next;
1385 }
1386
1387 if root_elem.is_null() {
1388 ctxt.record_error("Document has no root element".to_string());
1389 return false;
1390 }
1391
1392 let start_pattern = match &schema.grammar.start {
1394 Some(p) => p,
1395 None => {
1396 ctxt.record_error("Schema has no start pattern".to_string());
1397 return false;
1398 }
1399 };
1400
1401 ctxt.path.clear();
1402 let valid = rng_validate_pattern(start_pattern, root_elem, schema, ctxt);
1403
1404 valid
1406 }
1407}
1408
1409pub(crate) unsafe fn rng_validate_doc_compiled(
1420 schema: *mut c_void,
1421 doc: *mut _xmlDoc,
1422) -> (bool, Vec<String>) {
1423 unsafe {
1424 if schema.is_null() || doc.is_null() {
1425 return (false, Vec::new());
1426 }
1427 let schema_ref = &*(schema as *const RelaxNgSchema);
1428 let mut ctxt = RelaxNgValidCtxt::new();
1429 let valid = rng_validate_doc(schema_ref, doc, &mut ctxt);
1430 (valid, ctxt.errors)
1431 }
1432}
1433
1434fn rng_validate_pattern(
1440 pattern: &RelaxNgPattern,
1441 node: *mut _xmlNode,
1442 schema: &RelaxNgSchema,
1443 ctxt: &mut RelaxNgValidCtxt,
1444) -> bool {
1445 unsafe {
1446 if ctxt.depth >= ctxt.depth_max {
1447 ctxt.record_error("Maximum validation depth exceeded".to_string());
1448 return false;
1449 }
1450 ctxt.depth += 1;
1451
1452 let result = match pattern.pattern_type {
1453 RelaxNgPatternType::Element => {
1454 rng_validate_element_pattern(pattern, node, schema, ctxt)
1455 }
1456 RelaxNgPatternType::Attribute => {
1457 rng_validate_attribute_pattern(pattern, node, schema, ctxt)
1458 }
1459 RelaxNgPatternType::Text => rng_validate_text_pattern(node, ctxt),
1460 RelaxNgPatternType::Empty => rng_validate_empty_pattern(node, ctxt),
1461 RelaxNgPatternType::NotAllowed => {
1462 let name = get_node_qname(node);
1463 ctxt.record_error(format!(
1464 "Element '{}' is not allowed at '{}'",
1465 name,
1466 ctxt.current_path()
1467 ));
1468 false
1469 }
1470 RelaxNgPatternType::Choice => rng_validate_choice_pattern(pattern, node, schema, ctxt),
1471 RelaxNgPatternType::Sequence => {
1472 rng_validate_sequence_pattern(pattern, node, schema, ctxt)
1473 }
1474 RelaxNgPatternType::Interleave => {
1475 rng_validate_interleave_pattern(pattern, node, schema, ctxt)
1476 }
1477 RelaxNgPatternType::ZeroOrMore => {
1478 rng_validate_zero_or_more(pattern, node, schema, ctxt)
1479 }
1480 RelaxNgPatternType::OneOrMore => rng_validate_one_or_more(pattern, node, schema, ctxt),
1481 RelaxNgPatternType::Optional => {
1482 rng_validate_optional_pattern(pattern, node, schema, ctxt)
1483 }
1484 RelaxNgPatternType::List => rng_validate_list_pattern(pattern, node, schema, ctxt),
1485 RelaxNgPatternType::Group => rng_validate_group_pattern(pattern, node, schema, ctxt),
1486 RelaxNgPatternType::Data => rng_validate_data_pattern(pattern, node, ctxt),
1487 RelaxNgPatternType::Value => rng_validate_value_pattern(pattern, node, ctxt),
1488 RelaxNgPatternType::Ref => rng_validate_ref_pattern(pattern, node, schema, ctxt),
1489 RelaxNgPatternType::ExternalRef => {
1490 rng_validate_empty_pattern(node, ctxt)
1492 }
1493 RelaxNgPatternType::Define
1494 | RelaxNgPatternType::Grammar
1495 | RelaxNgPatternType::Start
1496 | RelaxNgPatternType::Include => {
1497 rng_validate_children(pattern, node, schema, ctxt)
1499 }
1500 };
1501
1502 ctxt.depth -= 1;
1503 result
1504 }
1505}
1506
1507fn rng_validate_children(
1513 pattern: &RelaxNgPattern,
1514 node: *mut _xmlNode,
1515 schema: &RelaxNgSchema,
1516 ctxt: &mut RelaxNgValidCtxt,
1517) -> bool {
1518 {
1519 if pattern.children.is_empty() {
1520 return true;
1521 }
1522 let mut valid = true;
1524 for child in &pattern.children {
1525 valid &= rng_validate_pattern(child, node, schema, ctxt);
1526 }
1527 valid
1528 }
1529}
1530
1531fn rng_validate_element_pattern(
1537 pattern: &RelaxNgPattern,
1538 node: *mut _xmlNode,
1539 schema: &RelaxNgSchema,
1540 ctxt: &mut RelaxNgValidCtxt,
1541) -> bool {
1542 unsafe {
1543 if node.is_null() || (*node).type_ != XML_ELEMENT_NODE as c_int {
1544 return false;
1545 }
1546
1547 let node_name = get_node_qname(node);
1548 let ns_uri = get_node_ns_uri(node);
1549
1550 if let Some(ref nc) = pattern.name_class {
1552 if !nc.matches(&node_name, ns_uri.as_deref()) {
1553 let pat_name = pattern.name.as_deref().unwrap_or("?");
1554 ctxt.record_error(format!(
1555 "Element '{}' does not match expected element pattern '{}' at '{}'",
1556 node_name,
1557 pat_name,
1558 ctxt.current_path()
1559 ));
1560 return false;
1561 }
1562 }
1563
1564 ctxt.path.push(node_name.clone());
1566
1567 let valid = rng_validate_content(&pattern.children, node, schema, ctxt);
1575
1576 ctxt.path.pop();
1577 valid
1578 }
1579}
1580
1581fn rng_validate_content(
1595 content: &[RelaxNgPattern],
1596 node: *mut _xmlNode,
1597 schema: &RelaxNgSchema,
1598 ctxt: &mut RelaxNgValidCtxt,
1599) -> bool {
1600 if content.is_empty() {
1601 return rng_validate_empty_pattern(node, ctxt);
1602 }
1603 if content.len() == 1 {
1604 let p = &content[0];
1605 match p.pattern_type {
1606 RelaxNgPatternType::Sequence
1607 | RelaxNgPatternType::Choice
1608 | RelaxNgPatternType::Interleave
1609 | RelaxNgPatternType::Group
1610 | RelaxNgPatternType::ZeroOrMore
1611 | RelaxNgPatternType::OneOrMore
1612 | RelaxNgPatternType::Optional
1613 | RelaxNgPatternType::List => {
1614 return rng_validate_pattern(p, node, schema, ctxt);
1615 }
1616 RelaxNgPatternType::Text => return rng_validate_text_pattern(node, ctxt),
1617 RelaxNgPatternType::Empty => return rng_validate_empty_pattern(node, ctxt),
1618 RelaxNgPatternType::Data => return rng_validate_data_pattern(p, node, ctxt),
1619 RelaxNgPatternType::Value => return rng_validate_value_pattern(p, node, ctxt),
1620 RelaxNgPatternType::Attribute => {
1621 return rng_validate_attribute_pattern(p, node, schema, ctxt)
1622 }
1623 _ => {}
1624 }
1625 }
1626
1627 unsafe {
1628 let mut child_nodes: Vec<*mut _xmlNode> = Vec::new();
1630 let mut child = (*node).children;
1631 while !child.is_null() {
1632 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1633 child_nodes.push(child);
1634 }
1635 child = (*child).next;
1636 }
1637
1638 let mut valid = true;
1639 let mut child_idx = 0;
1640 for child_pat in content {
1641 match child_pat.pattern_type {
1648 RelaxNgPatternType::Text => {
1649 valid &= rng_validate_text_pattern(node, ctxt);
1650 continue;
1651 }
1652 RelaxNgPatternType::Data => {
1653 valid &= rng_validate_data_pattern(child_pat, node, ctxt);
1654 continue;
1655 }
1656 RelaxNgPatternType::Value => {
1657 valid &= rng_validate_value_pattern(child_pat, node, ctxt);
1658 continue;
1659 }
1660 RelaxNgPatternType::Attribute => {
1661 valid &= rng_validate_attribute_pattern(child_pat, node, schema, ctxt);
1662 continue;
1663 }
1664 RelaxNgPatternType::Empty => {
1665 valid &= rng_validate_empty_pattern(node, ctxt);
1666 continue;
1667 }
1668 _ => {}
1669 }
1670 if child_idx >= child_nodes.len() {
1671 match child_pat.pattern_type {
1672 RelaxNgPatternType::Optional | RelaxNgPatternType::ZeroOrMore => {
1673 continue;
1675 }
1676 RelaxNgPatternType::OneOrMore => {
1677 ctxt.record_error(format!(
1678 "Expected at least one matching child for oneOrMore at '{}'",
1679 ctxt.current_path()
1680 ));
1681 valid = false;
1682 continue;
1683 }
1684 _ => {
1685 ctxt.record_error(format!(
1686 "Expected more child elements for content model at '{}'",
1687 ctxt.current_path()
1688 ));
1689 valid = false;
1690 continue;
1691 }
1692 }
1693 }
1694 let child_node = child_nodes[child_idx];
1695 valid &= rng_validate_pattern(child_pat, child_node, schema, ctxt);
1696 child_idx += 1;
1697 }
1698
1699 if child_idx < child_nodes.len() {
1701 let extra_name = get_node_qname(child_nodes[child_idx]);
1702 ctxt.record_error(format!("Did not expect element {} there", extra_name));
1705 valid = false;
1706 }
1707
1708 valid
1709 }
1710}
1711
1712fn rng_validate_attribute_pattern(
1718 pattern: &RelaxNgPattern,
1719 node: *mut _xmlNode,
1720 _schema: &RelaxNgSchema,
1721 ctxt: &mut RelaxNgValidCtxt,
1722) -> bool {
1723 unsafe {
1724 if node.is_null() || (*node).type_ != XML_ELEMENT_NODE as c_int {
1725 return false;
1726 }
1727
1728 let attr_name = match &pattern.name_class {
1730 Some(RelaxNgNameClass::Name(n)) => n.clone(),
1731 Some(RelaxNgNameClass::AnyName) => {
1732 return true;
1737 }
1738 Some(RelaxNgNameClass::NsName(ns)) => {
1739 let mut prop = (*node).properties;
1742 while !prop.is_null() {
1743 let prop_ns = get_node_ns_uri(prop as *mut _xmlNode);
1744 if let Some(ref uri) = prop_ns {
1745 if uri == ns {
1746 if let Some(content) = &pattern.children.first() {
1748 let val = get_node_text(prop as *mut _xmlNode);
1749 let valid = match content.pattern_type {
1750 RelaxNgPatternType::Text => true,
1751 RelaxNgPatternType::Data => rng_validate_datatype_value(
1752 content.datatype.as_deref(),
1753 &val,
1754 ),
1755 RelaxNgPatternType::Value => {
1756 content.value.as_deref() == Some(&val)
1757 }
1758 _ => true,
1759 };
1760 if !valid {
1761 ctxt.record_error(format!(
1762 "Attribute '{}' has invalid value at '{}'",
1763 prop_ns.unwrap_or_default(),
1764 ctxt.current_path()
1765 ));
1766 return false;
1767 }
1768 }
1769 return true;
1770 }
1771 }
1772 prop = (*prop).next;
1773 }
1774 ctxt.record_error(format!(
1777 "Required attribute in namespace '{}' is missing at '{}'",
1778 ns,
1779 ctxt.current_path()
1780 ));
1781 return false;
1782 }
1783 _ => {
1784 return true;
1787 }
1788 };
1789
1790 let attr_value = get_attr(node, &attr_name);
1792
1793 match attr_value {
1794 Some(val) => {
1795 if let Some(content) = pattern.children.first() {
1797 let valid = match content.pattern_type {
1798 RelaxNgPatternType::Text => true,
1799 RelaxNgPatternType::Data => {
1800 rng_validate_datatype_value(content.datatype.as_deref(), &val)
1801 }
1802 RelaxNgPatternType::Value => content.value.as_deref() == Some(&val),
1803 _ => true,
1804 };
1805 if !valid {
1806 ctxt.record_error(format!(
1807 "Attribute '{}' has invalid value '{}' at '{}'",
1808 attr_name,
1809 val,
1810 ctxt.current_path()
1811 ));
1812 return false;
1813 }
1814 }
1815 true
1816 }
1817 None => {
1818 ctxt.record_error(format!(
1821 "Required attribute '{}' is missing at '{}'",
1822 attr_name,
1823 ctxt.current_path()
1824 ));
1825 false
1826 }
1827 }
1828 }
1829}
1830
1831const fn rng_validate_text_pattern(node: *mut _xmlNode, _ctxt: &mut RelaxNgValidCtxt) -> bool {
1837 {
1838 if node.is_null() {
1839 return false;
1840 }
1841 true
1844 }
1845}
1846
1847fn rng_validate_empty_pattern(node: *mut _xmlNode, ctxt: &mut RelaxNgValidCtxt) -> bool {
1853 unsafe {
1854 if node.is_null() {
1855 return true;
1856 }
1857 let mut child = (*node).children;
1859 while !child.is_null() {
1860 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1861 let child_name = get_node_qname(child);
1862 ctxt.record_error(format!(
1863 "Unexpected child element '{}' in empty content at '{}'",
1864 child_name,
1865 ctxt.current_path()
1866 ));
1867 return false;
1868 }
1869 child = (*child).next;
1870 }
1871 true
1872 }
1873}
1874
1875fn rng_validate_choice_pattern(
1881 pattern: &RelaxNgPattern,
1882 node: *mut _xmlNode,
1883 schema: &RelaxNgSchema,
1884 ctxt: &mut RelaxNgValidCtxt,
1885) -> bool {
1886 unsafe {
1887 if pattern.children.is_empty() {
1888 return false;
1889 }
1890
1891 let mut last_error = String::new();
1893 for child in &pattern.children {
1894 let saved_errors = ctxt.errors.len();
1895 let saved_nb = ctxt.nb_errors;
1896
1897 if rng_validate_pattern(child, node, schema, ctxt) {
1898 return true;
1901 }
1902
1903 if ctxt.errors.len() > saved_errors {
1905 last_error = ctxt.errors.last().unwrap().clone();
1906 }
1907
1908 ctxt.errors.truncate(saved_errors);
1910 ctxt.nb_errors = saved_nb;
1911 }
1912
1913 let node_name = if node.is_null() {
1915 "null".to_string()
1916 } else {
1917 get_node_qname(node)
1918 };
1919 ctxt.record_error(format!(
1920 "No choice pattern matched for '{}' at '{}'. Last error: {}",
1921 node_name,
1922 ctxt.current_path(),
1923 last_error
1924 ));
1925 false
1926 }
1927}
1928
1929fn rng_validate_sequence_pattern(
1935 pattern: &RelaxNgPattern,
1936 node: *mut _xmlNode,
1937 schema: &RelaxNgSchema,
1938 ctxt: &mut RelaxNgValidCtxt,
1939) -> bool {
1940 unsafe {
1941 if node.is_null() {
1942 return pattern.children.is_empty();
1943 }
1944
1945 let mut valid = true;
1946
1947 let mut child_nodes: Vec<*mut _xmlNode> = Vec::new();
1951 let mut child = (*node).children;
1952 while !child.is_null() {
1953 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1954 child_nodes.push(child);
1955 }
1956 child = (*child).next;
1957 }
1958
1959 let mut child_idx = 0;
1962 for child_pat in &pattern.children {
1963 if child_idx >= child_nodes.len() {
1964 match child_pat.pattern_type {
1966 RelaxNgPatternType::Optional | RelaxNgPatternType::ZeroOrMore => {
1967 continue;
1969 }
1970 RelaxNgPatternType::OneOrMore => {
1971 ctxt.record_error(format!(
1972 "Expected at least one matching child for oneOrMore at '{}'",
1973 ctxt.current_path()
1974 ));
1975 valid = false;
1976 continue;
1977 }
1978 _ => {
1979 ctxt.record_error(format!(
1980 "Expected more child elements for sequence at '{}'",
1981 ctxt.current_path()
1982 ));
1983 valid = false;
1984 continue;
1985 }
1986 }
1987 }
1988
1989 let child_node = child_nodes[child_idx];
1990 valid &= rng_validate_pattern(child_pat, child_node, schema, ctxt);
1991 child_idx += 1;
1992 }
1993
1994 if child_idx < child_nodes.len() {
1996 let extra_name = get_node_qname(child_nodes[child_idx]);
1997 ctxt.record_error(format!(
1998 "Unexpected extra element '{}' in sequence at '{}'",
1999 extra_name,
2000 ctxt.current_path()
2001 ));
2002 valid = false;
2003 }
2004
2005 valid
2006 }
2007}
2008
2009fn rng_validate_interleave_pattern(
2015 pattern: &RelaxNgPattern,
2016 node: *mut _xmlNode,
2017 schema: &RelaxNgSchema,
2018 ctxt: &mut RelaxNgValidCtxt,
2019) -> bool {
2020 unsafe {
2021 if node.is_null() {
2022 return pattern.children.is_empty();
2023 }
2024
2025 let mut valid = true;
2029
2030 for child_pat in &pattern.children {
2031 let mut child = (*node).children;
2034 let mut matched = false;
2035
2036 while !child.is_null() {
2037 if (*child).type_ == XML_ELEMENT_NODE as c_int {
2038 let saved_errors = ctxt.errors.len();
2039 let saved_nb = ctxt.nb_errors;
2040
2041 if rng_validate_pattern(child_pat, child, schema, ctxt) {
2042 matched = true;
2043 break;
2044 }
2045
2046 ctxt.errors.truncate(saved_errors);
2048 ctxt.nb_errors = saved_nb;
2049 }
2050 child = (*child).next;
2051 }
2052
2053 if !matched {
2054 match child_pat.pattern_type {
2056 RelaxNgPatternType::Optional | RelaxNgPatternType::ZeroOrMore => {
2057 }
2059 _ => {
2060 let pat_desc = format!("{:?}", child_pat.pattern_type);
2061 ctxt.record_error(format!(
2062 "Interleave pattern '{}' did not match any child at '{}'",
2063 pat_desc,
2064 ctxt.current_path()
2065 ));
2066 valid = false;
2067 }
2068 }
2069 }
2070 }
2071
2072 valid
2073 }
2074}
2075
2076fn rng_validate_zero_or_more(
2082 pattern: &RelaxNgPattern,
2083 node: *mut _xmlNode,
2084 schema: &RelaxNgSchema,
2085 ctxt: &mut RelaxNgValidCtxt,
2086) -> bool {
2087 unsafe {
2088 if node.is_null() || pattern.children.is_empty() {
2089 return true;
2090 }
2091
2092 let child_pat = &pattern.children[0];
2093 let valid = true;
2094
2095 let mut child = (*node).children;
2097 while !child.is_null() {
2098 if (*child).type_ == XML_ELEMENT_NODE as c_int {
2099 let saved_errors = ctxt.errors.len();
2100 let saved_nb = ctxt.nb_errors;
2101
2102 if !rng_validate_pattern(child_pat, child, schema, ctxt) {
2103 ctxt.errors.truncate(saved_errors);
2106 ctxt.nb_errors = saved_nb;
2107 break;
2108 }
2109 }
2111 child = (*child).next;
2112 }
2113
2114 valid
2115 }
2116}
2117
2118fn rng_validate_one_or_more(
2124 pattern: &RelaxNgPattern,
2125 node: *mut _xmlNode,
2126 schema: &RelaxNgSchema,
2127 ctxt: &mut RelaxNgValidCtxt,
2128) -> bool {
2129 unsafe {
2130 if node.is_null() || pattern.children.is_empty() {
2131 ctxt.record_error(format!(
2132 "Expected at least one matching element for oneOrMore at '{}'",
2133 ctxt.current_path()
2134 ));
2135 return false;
2136 }
2137
2138 let child_pat = &pattern.children[0];
2139 let mut matched = false;
2140 let mut valid = true;
2141
2142 let mut child = (*node).children;
2144 while !child.is_null() {
2145 if (*child).type_ == XML_ELEMENT_NODE as c_int {
2146 let saved_errors = ctxt.errors.len();
2147 let saved_nb = ctxt.nb_errors;
2148
2149 if rng_validate_pattern(child_pat, child, schema, ctxt) {
2150 matched = true;
2151 } else {
2152 ctxt.errors.truncate(saved_errors);
2154 ctxt.nb_errors = saved_nb;
2155 break;
2156 }
2157 }
2158 child = (*child).next;
2159 }
2160
2161 if !matched {
2162 ctxt.record_error(format!(
2163 "Expected at least one matching element for oneOrMore at '{}'",
2164 ctxt.current_path()
2165 ));
2166 valid = false;
2167 }
2168
2169 valid
2170 }
2171}
2172
2173fn rng_validate_optional_pattern(
2179 pattern: &RelaxNgPattern,
2180 node: *mut _xmlNode,
2181 schema: &RelaxNgSchema,
2182 ctxt: &mut RelaxNgValidCtxt,
2183) -> bool {
2184 {
2185 if pattern.children.is_empty() {
2186 return true;
2187 }
2188
2189 let child_pat = &pattern.children[0];
2191 let saved_errors = ctxt.errors.len();
2192 let saved_nb = ctxt.nb_errors;
2193
2194 let result = rng_validate_pattern(child_pat, node, schema, ctxt);
2195
2196 if !result {
2197 ctxt.errors.truncate(saved_errors);
2199 ctxt.nb_errors = saved_nb;
2200 }
2201
2202 true }
2204}
2205
2206fn rng_validate_list_pattern(
2212 pattern: &RelaxNgPattern,
2213 node: *mut _xmlNode,
2214 _schema: &RelaxNgSchema,
2215 _ctxt: &mut RelaxNgValidCtxt,
2216) -> bool {
2217 unsafe {
2218 if node.is_null() {
2219 return pattern.children.is_empty();
2220 }
2221
2222 let text = get_node_text(node);
2224 if text.trim().is_empty() {
2225 return true;
2226 }
2227
2228 let tokens: Vec<&str> = text.split_whitespace().collect();
2229 let mut valid = true;
2230
2231 for token in &tokens {
2232 if token.is_empty() {
2235 valid = false;
2236 break;
2237 }
2238 }
2239
2240 valid
2241 }
2242}
2243
2244fn rng_validate_group_pattern(
2250 pattern: &RelaxNgPattern,
2251 node: *mut _xmlNode,
2252 schema: &RelaxNgSchema,
2253 ctxt: &mut RelaxNgValidCtxt,
2254) -> bool {
2255 {
2256 rng_validate_sequence_pattern(pattern, node, schema, ctxt)
2258 }
2259}
2260
2261fn rng_validate_data_pattern(
2267 pattern: &RelaxNgPattern,
2268 node: *mut _xmlNode,
2269 ctxt: &mut RelaxNgValidCtxt,
2270) -> bool {
2271 unsafe {
2272 if node.is_null() {
2273 return false;
2274 }
2275
2276 let text = get_node_text(node);
2277 let datatype = pattern.datatype.as_deref();
2278
2279 if !rng_validate_datatype_value(datatype, &text) {
2280 ctxt.record_error(format!(
2281 "Value '{}' does not match datatype '{:?}' at '{}'",
2282 text,
2283 datatype,
2284 ctxt.current_path()
2285 ));
2286 return false;
2287 }
2288
2289 true
2290 }
2291}
2292
2293fn rng_validate_value_pattern(
2299 pattern: &RelaxNgPattern,
2300 node: *mut _xmlNode,
2301 ctxt: &mut RelaxNgValidCtxt,
2302) -> bool {
2303 unsafe {
2304 if node.is_null() {
2305 return false;
2306 }
2307
2308 let text = get_node_text(node).trim().to_string();
2309 let expected = pattern.value.as_deref().unwrap_or("");
2310
2311 if text != expected {
2312 ctxt.record_error(format!(
2313 "Value '{}' does not match expected value '{}' at '{}'",
2314 text,
2315 expected,
2316 ctxt.current_path()
2317 ));
2318 return false;
2319 }
2320
2321 true
2322 }
2323}
2324
2325fn rng_validate_ref_pattern(
2331 pattern: &RelaxNgPattern,
2332 node: *mut _xmlNode,
2333 schema: &RelaxNgSchema,
2334 ctxt: &mut RelaxNgValidCtxt,
2335) -> bool {
2336 {
2337 let ref_name = pattern.name.as_deref().unwrap_or("");
2338
2339 if ref_name.is_empty() {
2340 ctxt.record_error("Ref pattern has no name".to_string());
2341 return false;
2342 }
2343
2344 match schema.grammar.lookup(ref_name) {
2346 Some(def_pattern) => {
2347 rng_validate_pattern(def_pattern, node, schema, ctxt)
2349 }
2350 None => {
2351 ctxt.record_error(format!(
2352 "Undefined reference '{}' at '{}'",
2353 ref_name,
2354 ctxt.current_path()
2355 ));
2356 false
2357 }
2358 }
2359 }
2360}
2361
2362fn rng_validate_datatype_value(datatype: Option<&str>, value: &str) -> bool {
2371 let dt = match datatype {
2372 Some(d) => d,
2373 None => return true, };
2375
2376 match dt {
2377 "string" | "token" => true,
2378 "boolean" => {
2379 matches!(value, "true" | "false" | "1" | "0")
2380 }
2381 "integer" | "int" | "short" | "byte" | "long" => {
2382 if value.is_empty() {
2383 return false;
2384 }
2385 let trimmed = if value.starts_with('+') || value.starts_with('-') {
2386 &value[1..]
2387 } else {
2388 value
2389 };
2390 !trimmed.is_empty() && trimmed.chars().all(|c| c.is_ascii_digit())
2391 }
2392 "decimal" | "double" | "float" => {
2393 if value.is_empty() {
2394 return false;
2395 }
2396 if matches!(dt, "float" | "double") && matches!(value, "INF" | "-INF" | "NaN") {
2398 return true;
2399 }
2400 value.parse::<f64>().is_ok()
2401 }
2402 "NCName" | "Name" | "ID" | "IDREF" | "NMTOKEN" => {
2403 !value.is_empty() && !value.starts_with(|c: char| c.is_ascii_digit())
2404 }
2405 "anyURI" => {
2406 !value.is_empty() && !value.contains(char::is_whitespace)
2408 }
2409 "QName" => {
2410 if value.is_empty() {
2411 return false;
2412 }
2413 if let Some(pos) = value.find(':') {
2414 pos > 0 && pos < value.len() - 1
2415 } else {
2416 true
2417 }
2418 }
2419 _ => {
2420 true
2423 }
2424 }
2425}
2426
2427pub fn rng_parse_schema(xml_doc: &str) -> Result<RelaxNgSchema, String> {
2435 rng_parse(xml_doc)
2436}
2437
2438pub unsafe fn rng_parse_schema_doc(doc: *mut _xmlDoc) -> Result<RelaxNgSchema, String> {
2444 rng_parse_doc(doc)
2445}
2446
2447pub unsafe fn rng_validate_doc_schema(
2455 schema: &RelaxNgSchema,
2456 doc: *mut _xmlDoc,
2457 ctxt: &mut RelaxNgValidCtxt,
2458) -> bool {
2459 rng_validate_doc(schema, doc, ctxt)
2460}
2461
2462pub(crate) struct RelaxNgParserCtxt {
2480 pub(crate) schema: Option<RelaxNgSchema>,
2482 pub(crate) url: Option<String>,
2487}
2488
2489#[no_mangle]
2501pub unsafe extern "C" fn xmlRelaxNGNewParserCtxt(url: *const c_char) -> *mut c_void {
2502 if url.is_null() {
2503 return Box::into_raw(Box::new(RelaxNgParserCtxt {
2504 schema: None,
2505 url: None,
2506 })) as *mut c_void;
2507 }
2508
2509 let url_str = unsafe {
2510 let mut len = 0;
2511 while *url.add(len) != 0 {
2512 len += 1;
2513 }
2514 let slice = std::slice::from_raw_parts(url as *const u8, len);
2515 String::from_utf8_lossy(slice).to_string()
2516 };
2517
2518 let schema = if url_str.is_empty() {
2523 None
2524 } else {
2525 let url_c = std::ffi::CString::new(url_str.clone()).ok();
2526 if let Some(c) = url_c {
2527 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
2528 if !doc.is_null() {
2529 let result = rng_parse_doc(doc);
2530 crate::abi::exports_xml2::xmlFreeDoc(doc);
2531 result.ok()
2532 } else {
2533 None
2534 }
2535 } else {
2536 None
2537 }
2538 };
2539 Box::into_raw(Box::new(RelaxNgParserCtxt {
2540 schema,
2541 url: Some(url_str),
2542 })) as *mut c_void
2543}
2544
2545#[no_mangle]
2557pub unsafe extern "C" fn xmlRelaxNGNewMemParserCtxt(
2558 buffer: *const c_char,
2559 size: c_int,
2560) -> *mut c_void {
2561 if buffer.is_null() || size <= 0 {
2562 return ptr::null_mut();
2563 }
2564
2565 let buf_slice = unsafe { std::slice::from_raw_parts(buffer as *const u8, size as usize) };
2570 let xml_str = String::from_utf8_lossy(buf_slice).to_string();
2571
2572 let schema = rng_parse(&xml_str).ok();
2573 Box::into_raw(Box::new(RelaxNgParserCtxt { schema, url: None })) as *mut c_void
2574}
2575
2576#[no_mangle]
2588pub unsafe extern "C" fn xmlRelaxNGParse(ctxt: *mut c_void) -> *mut c_void {
2589 if ctxt.is_null() {
2590 return ptr::null_mut();
2591 }
2592
2593 let pctxt = unsafe { &*ctxt.cast::<RelaxNgParserCtxt>() };
2600 match &pctxt.schema {
2601 Some(schema) => {
2602 if schema.fatal {
2608 crate::abi::exports_relaxng::dispatch_relaxng_parser_errors(
2609 ctxt as usize,
2610 &schema.errors,
2611 );
2612 return ptr::null_mut();
2613 }
2614 Box::into_raw(Box::new(schema.clone())) as *mut c_void
2615 }
2616 None => {
2617 if let Some(ref url) = pctxt.url {
2622 if !url.is_empty() {
2623 let msg = format!("xmlRelaxNGParse: could not load {}\n", url);
2624 crate::abi::exports_relaxng::dispatch_relaxng_parser_errors(
2625 ctxt as usize,
2626 std::slice::from_ref(&msg),
2627 );
2628 }
2629 }
2630 ptr::null_mut()
2631 }
2632 }
2633}
2634
2635#[no_mangle]
2647pub unsafe extern "C" fn xmlRelaxNGFree(schema: *mut c_void) {
2648 if schema.is_null() {
2649 return;
2650 }
2651 unsafe {
2653 let _ = Box::from_raw(schema as *mut RelaxNgSchema);
2654 }
2655}
2656
2657#[no_mangle]
2669pub unsafe extern "C" fn xmlRelaxNGFreeParserCtxt(ctxt: *mut c_void) {
2670 if ctxt.is_null() {
2671 return;
2672 }
2673 unsafe {
2676 let _ = Box::from_raw(ctxt as *mut RelaxNgParserCtxt);
2677 }
2678}
2679
2680#[no_mangle]
2692pub unsafe extern "C" fn xmlRelaxNGNewValidCtxt(schema: *mut c_void) -> *mut c_void {
2693 let mut ctxt = RelaxNgValidCtxt::new();
2694
2695 if !schema.is_null() {
2696 unsafe {
2698 let schema_ref = &*(schema as *const RelaxNgSchema);
2699 ctxt.schema = Some(schema_ref.clone());
2700 }
2701 }
2702
2703 let boxed = Box::new(ctxt);
2704 Box::into_raw(boxed) as *mut c_void
2705}
2706
2707#[no_mangle]
2719pub unsafe extern "C" fn xmlRelaxNGFreeValidCtxt(ctxt: *mut c_void) {
2720 if ctxt.is_null() {
2721 return;
2722 }
2723 unsafe {
2725 let _ = Box::from_raw(ctxt as *mut RelaxNgValidCtxt);
2726 }
2727}
2728
2729#[no_mangle]
2744pub unsafe extern "C" fn xmlRelaxNGValidateDoc(ctxt: *mut c_void, doc: *mut _xmlDoc) -> c_int {
2745 if ctxt.is_null() || doc.is_null() {
2746 return -1;
2747 }
2748
2749 unsafe {
2750 let valid_ctxt = &mut *(ctxt as *mut RelaxNgValidCtxt);
2751 let schema = match &valid_ctxt.schema {
2752 Some(s) => s,
2753 None => return -1,
2754 };
2755
2756 let mut temp_ctxt = RelaxNgValidCtxt::new();
2757
2758 let valid = rng_validate_doc(schema, doc, &mut temp_ctxt);
2759
2760 if valid {
2761 0
2762 } else {
2763 valid_ctxt.errors = temp_ctxt.errors;
2764 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2765 crate::abi::exports_relaxng::dispatch_relaxng_valid_errors(
2771 ctxt as usize,
2772 &valid_ctxt.errors,
2773 ptr::null_mut(),
2774 );
2775 temp_ctxt.nb_errors
2776 }
2777 }
2778}
2779
2780#[no_mangle]
2798pub unsafe extern "C" fn xmlRelaxNGValidateFullElement(
2799 ctxt: *mut c_void,
2800 doc: *mut _xmlDoc,
2801 elem: *mut _xmlNode,
2802) -> c_int {
2803 if ctxt.is_null() || doc.is_null() || elem.is_null() {
2804 return -1;
2805 }
2806
2807 unsafe {
2808 let valid_ctxt = &mut *(ctxt as *mut RelaxNgValidCtxt);
2809 let schema = match &valid_ctxt.schema {
2810 Some(s) => s,
2811 None => return -1,
2812 };
2813
2814 let mut temp_ctxt = RelaxNgValidCtxt::new();
2815 temp_ctxt.path = valid_ctxt.path.clone();
2816
2817 let start_pattern = match &schema.grammar.start {
2818 Some(p) => p,
2819 None => return -1,
2820 };
2821
2822 let valid = rng_validate_pattern(start_pattern, elem, schema, &mut temp_ctxt);
2823
2824 if valid {
2825 0
2826 } else {
2827 valid_ctxt.errors = temp_ctxt.errors;
2828 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2829 temp_ctxt.nb_errors
2830 }
2831 }
2832}
2833
2834#[cfg(test)]
2839mod tests {
2840 use super::*;
2841
2842 #[test]
2845 fn test_name_class_name() {
2846 let nc = RelaxNgNameClass::Name("foo".to_string());
2847 assert!(nc.matches("foo", None));
2848 assert!(!nc.matches("bar", None));
2849 assert!(!nc.matches("FOO", None));
2850 }
2851
2852 #[test]
2853 fn test_name_class_any_name() {
2854 let nc = RelaxNgNameClass::AnyName;
2855 assert!(nc.matches("foo", None));
2856 assert!(nc.matches("bar", None));
2857 assert!(nc.matches("anything", Some("urn:ns")));
2858 }
2859
2860 #[test]
2861 fn test_name_class_ns_name() {
2862 let nc = RelaxNgNameClass::NsName("urn:example".to_string());
2863 assert!(nc.matches("foo", Some("urn:example")));
2864 assert!(!nc.matches("foo", Some("urn:other")));
2865 assert!(!nc.matches("foo", None));
2866 }
2867
2868 #[test]
2869 fn test_name_class_choice() {
2870 let nc = RelaxNgNameClass::Choice(vec![
2871 RelaxNgNameClass::Name("a".to_string()),
2872 RelaxNgNameClass::Name("b".to_string()),
2873 ]);
2874 assert!(nc.matches("a", None));
2875 assert!(nc.matches("b", None));
2876 assert!(!nc.matches("c", None));
2877 }
2878
2879 #[test]
2880 fn test_name_class_except() {
2881 let nc = RelaxNgNameClass::Except(
2882 Box::new(RelaxNgNameClass::AnyName),
2883 Box::new(RelaxNgNameClass::Name("bad".to_string())),
2884 );
2885 assert!(nc.matches("good", None));
2886 assert!(!nc.matches("bad", None));
2887 }
2888
2889 #[test]
2892 fn test_parse_simple_element_schema() {
2893 let schema_xml = r#"<?xml version="1.0"?>
2894<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2895 <text/>
2896</element>"#;
2897
2898 let result = rng_parse(schema_xml);
2899 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2900 let schema = result.unwrap();
2901 assert!(schema.grammar.start.is_some());
2902 if let Some(ref start) = schema.grammar.start {
2903 assert_eq!(start.pattern_type, RelaxNgPatternType::Element);
2904 assert_eq!(start.name.as_deref(), Some("root"));
2905 }
2906 }
2907
2908 #[test]
2909 fn test_parse_grammar_schema() {
2910 let schema_xml = r#"<?xml version="1.0"?>
2911<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2912 <start>
2913 <element name="root">
2914 <text/>
2915 </element>
2916 </start>
2917</grammar>"#;
2918
2919 let result = rng_parse(schema_xml);
2920 assert!(
2921 result.is_ok(),
2922 "Failed to parse grammar: {:?}",
2923 result.err()
2924 );
2925 let schema = result.unwrap();
2926 assert!(schema.grammar.start.is_some());
2927 }
2928
2929 #[test]
2930 fn test_parse_with_define_and_ref() {
2931 let schema_xml = r#"<?xml version="1.0"?>
2932<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2933 <define name="textBlock">
2934 <text/>
2935 </define>
2936 <start>
2937 <element name="doc">
2938 <ref name="textBlock"/>
2939 </element>
2940 </start>
2941</grammar>"#;
2942
2943 let result = rng_parse(schema_xml);
2944 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
2945 let schema = result.unwrap();
2946 assert_eq!(schema.grammar.defines.len(), 1);
2947 assert_eq!(schema.grammar.defines[0].name, "textBlock");
2948 assert!(schema.grammar.start.is_some());
2949 }
2950
2951 #[test]
2952 fn test_parse_choice_schema() {
2953 let schema_xml = r#"<?xml version="1.0"?>
2954<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2955 <start>
2956 <choice>
2957 <element name="a">
2958 <text/>
2959 </element>
2960 <element name="b">
2961 <text/>
2962 </element>
2963 </choice>
2964 </start>
2965</grammar>"#;
2966
2967 let result = rng_parse(schema_xml);
2968 assert!(result.is_ok(), "Failed to parse choice: {:?}", result.err());
2969 }
2970
2971 #[test]
2972 fn test_parse_attribute_schema() {
2973 let schema_xml = r#"<?xml version="1.0"?>
2974<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2975 <attribute name="attr1">
2976 <text/>
2977 </attribute>
2978 <text/>
2979</element>"#;
2980
2981 let result = rng_parse(schema_xml);
2982 assert!(
2983 result.is_ok(),
2984 "Failed to parse attribute: {:?}",
2985 result.err()
2986 );
2987 }
2988
2989 #[test]
2990 fn test_parse_empty_document_fails() {
2991 let result = rng_parse("");
2992 assert!(result.is_err());
2993 }
2994
2995 #[test]
2996 fn test_parse_invalid_xml_fails() {
2997 let result = rng_parse("not valid xml <<<");
2998 assert!(result.is_err());
2999 }
3000
3001 #[test]
3012 fn test_validate_simple_element() {
3013 let schema_xml = r#"<?xml version="1.0"?>
3014<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3015 <text/>
3016</element>"#;
3017
3018 let doc_xml = r#"<?xml version="1.0"?>
3019<root>Hello</root>"#;
3020
3021 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3022
3023 let doc = unsafe {
3024 crate::abi::exports_xml2::xmlReadMemory(
3025 doc_xml.as_ptr() as *const c_char,
3026 doc_xml.len() as c_int,
3027 c"test.xml".as_ptr() as *const c_char,
3028 ptr::null(),
3029 0,
3030 )
3031 };
3032 assert!(!doc.is_null(), "Failed to parse document");
3033
3034 let mut ctxt = RelaxNgValidCtxt::new();
3035 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3036 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3037
3038 assert!(valid, "Validation failed: {:?}", ctxt.errors);
3039 }
3040
3041 #[test]
3050 fn test_validate_element_mismatch() {
3051 let schema_xml = r#"<?xml version="1.0"?>
3052<element name="expected" xmlns="http://relaxng.org/ns/structure/1.0">
3053 <text/>
3054</element>"#;
3055
3056 let doc_xml = r#"<?xml version="1.0"?>
3057<actual>Content</actual>"#;
3058
3059 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3060
3061 let doc = unsafe {
3062 crate::abi::exports_xml2::xmlReadMemory(
3063 doc_xml.as_ptr() as *const c_char,
3064 doc_xml.len() as c_int,
3065 c"test.xml".as_ptr() as *const c_char,
3066 ptr::null(),
3067 0,
3068 )
3069 };
3070 assert!(!doc.is_null());
3071
3072 let mut ctxt = RelaxNgValidCtxt::new();
3073 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3074 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3075
3076 assert!(!valid, "Validation should have failed");
3077 assert!(ctxt.nb_errors > 0);
3078 }
3079
3080 #[test]
3089 fn test_validate_with_attribute() {
3090 let schema_xml = r#"<?xml version="1.0"?>
3091<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3092 <attribute name="id">
3093 <text/>
3094 </attribute>
3095 <text/>
3096</element>"#;
3097
3098 let doc_xml = r#"<?xml version="1.0"?>
3099<root id="x1">Content</root>"#;
3100
3101 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3102
3103 let doc = unsafe {
3104 crate::abi::exports_xml2::xmlReadMemory(
3105 doc_xml.as_ptr() as *const c_char,
3106 doc_xml.len() as c_int,
3107 c"test.xml".as_ptr() as *const c_char,
3108 ptr::null(),
3109 0,
3110 )
3111 };
3112 assert!(!doc.is_null());
3113
3114 let mut ctxt = RelaxNgValidCtxt::new();
3115 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3116 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3117
3118 assert!(valid, "Validation failed: {:?}", ctxt.errors);
3119 }
3120
3121 #[test]
3130 fn test_validate_missing_attribute() {
3131 let schema_xml = r#"<?xml version="1.0"?>
3132<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3133 <attribute name="required">
3134 <text/>
3135 </attribute>
3136 <text/>
3137</element>"#;
3138
3139 let doc_xml = r#"<?xml version="1.0"?>
3140<root>Content</root>"#;
3141
3142 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3143
3144 let doc = unsafe {
3145 crate::abi::exports_xml2::xmlReadMemory(
3146 doc_xml.as_ptr() as *const c_char,
3147 doc_xml.len() as c_int,
3148 c"test.xml".as_ptr() as *const c_char,
3149 ptr::null(),
3150 0,
3151 )
3152 };
3153 assert!(!doc.is_null());
3154
3155 let mut ctxt = RelaxNgValidCtxt::new();
3156 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3157 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3158
3159 assert!(
3160 !valid,
3161 "Validation should have failed for missing attribute"
3162 );
3163 }
3164
3165 #[test]
3174 fn test_validate_with_choice() {
3175 let schema_xml = r#"<?xml version="1.0"?>
3176<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3177 <start>
3178 <choice>
3179 <element name="a">
3180 <text/>
3181 </element>
3182 <element name="b">
3183 <text/>
3184 </element>
3185 </choice>
3186 </start>
3187</grammar>"#;
3188
3189 let doc_xml = r#"<?xml version="1.0"?>
3190<a>First choice</a>"#;
3191
3192 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3193
3194 let doc = unsafe {
3195 crate::abi::exports_xml2::xmlReadMemory(
3196 doc_xml.as_ptr() as *const c_char,
3197 doc_xml.len() as c_int,
3198 c"test.xml".as_ptr() as *const c_char,
3199 ptr::null(),
3200 0,
3201 )
3202 };
3203 assert!(!doc.is_null());
3204
3205 let mut ctxt = RelaxNgValidCtxt::new();
3206 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3207 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3208
3209 assert!(valid, "Choice validation failed: {:?}", ctxt.errors);
3210 }
3211
3212 #[test]
3221 fn test_validate_choice_no_match() {
3222 let schema_xml = r#"<?xml version="1.0"?>
3223<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3224 <start>
3225 <choice>
3226 <element name="a">
3227 <text/>
3228 </element>
3229 <element name="b">
3230 <text/>
3231 </element>
3232 </choice>
3233 </start>
3234</grammar>"#;
3235
3236 let doc_xml = r#"<?xml version="1.0"?>
3237<c>Neither choice</c>"#;
3238
3239 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3240
3241 let doc = unsafe {
3242 crate::abi::exports_xml2::xmlReadMemory(
3243 doc_xml.as_ptr() as *const c_char,
3244 doc_xml.len() as c_int,
3245 c"test.xml".as_ptr() as *const c_char,
3246 ptr::null(),
3247 0,
3248 )
3249 };
3250 assert!(!doc.is_null());
3251
3252 let mut ctxt = RelaxNgValidCtxt::new();
3253 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3254 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3255
3256 assert!(
3257 !valid,
3258 "Validation should have failed for no matching choice"
3259 );
3260 }
3261
3262 #[test]
3271 fn test_validate_grammar_with_ref() {
3272 let schema_xml = r#"<?xml version="1.0"?>
3273<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3274 <define name="para">
3275 <element name="p">
3276 <text/>
3277 </element>
3278 </define>
3279 <start>
3280 <element name="doc">
3281 <zeroOrMore>
3282 <ref name="para"/>
3283 </zeroOrMore>
3284 </element>
3285 </start>
3286</grammar>"#;
3287
3288 let doc_xml = r#"<?xml version="1.0"?>
3289<doc><p>First</p><p>Second</p></doc>"#;
3290
3291 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3292
3293 let doc = unsafe {
3294 crate::abi::exports_xml2::xmlReadMemory(
3295 doc_xml.as_ptr() as *const c_char,
3296 doc_xml.len() as c_int,
3297 c"test.xml".as_ptr() as *const c_char,
3298 ptr::null(),
3299 0,
3300 )
3301 };
3302 assert!(!doc.is_null());
3303
3304 let mut ctxt = RelaxNgValidCtxt::new();
3305 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3306 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3307
3308 assert!(valid, "Ref validation failed: {:?}", ctxt.errors);
3309 }
3310
3311 #[test]
3320 fn test_validate_zero_or_more() {
3321 let schema_xml = r#"<?xml version="1.0"?>
3322<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3323 <zeroOrMore>
3324 <element name="item">
3325 <text/>
3326 </element>
3327 </zeroOrMore>
3328</element>"#;
3329
3330 let doc_xml = r#"<?xml version="1.0"?>
3331<root><item>A</item><item>B</item></root>"#;
3332
3333 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3334
3335 let doc = unsafe {
3336 crate::abi::exports_xml2::xmlReadMemory(
3337 doc_xml.as_ptr() as *const c_char,
3338 doc_xml.len() as c_int,
3339 c"test.xml".as_ptr() as *const c_char,
3340 ptr::null(),
3341 0,
3342 )
3343 };
3344 assert!(!doc.is_null());
3345
3346 let mut ctxt = RelaxNgValidCtxt::new();
3347 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3348 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3349
3350 assert!(valid, "zeroOrMore validation failed: {:?}", ctxt.errors);
3351 }
3352
3353 #[test]
3362 fn test_validate_zero_or_more_empty() {
3363 let schema_xml = r#"<?xml version="1.0"?>
3364<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3365 <zeroOrMore>
3366 <element name="item">
3367 <text/>
3368 </element>
3369 </zeroOrMore>
3370</element>"#;
3371
3372 let doc_xml = r#"<?xml version="1.0"?>
3373<root></root>"#;
3374
3375 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3376
3377 let doc = unsafe {
3378 crate::abi::exports_xml2::xmlReadMemory(
3379 doc_xml.as_ptr() as *const c_char,
3380 doc_xml.len() as c_int,
3381 c"test.xml".as_ptr() as *const c_char,
3382 ptr::null(),
3383 0,
3384 )
3385 };
3386 assert!(!doc.is_null());
3387
3388 let mut ctxt = RelaxNgValidCtxt::new();
3389 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3390 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3391
3392 assert!(valid, "Empty zeroOrMore should be valid");
3393 }
3394
3395 #[test]
3404 fn test_validate_one_or_more() {
3405 let schema_xml = r#"<?xml version="1.0"?>
3406<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3407 <oneOrMore>
3408 <element name="item">
3409 <text/>
3410 </element>
3411 </oneOrMore>
3412</element>"#;
3413
3414 let doc_xml = r#"<?xml version="1.0"?>
3415<root><item>Single</item></root>"#;
3416
3417 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3418
3419 let doc = unsafe {
3420 crate::abi::exports_xml2::xmlReadMemory(
3421 doc_xml.as_ptr() as *const c_char,
3422 doc_xml.len() as c_int,
3423 c"test.xml".as_ptr() as *const c_char,
3424 ptr::null(),
3425 0,
3426 )
3427 };
3428 assert!(!doc.is_null());
3429
3430 let mut ctxt = RelaxNgValidCtxt::new();
3431 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3432 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3433
3434 assert!(valid, "oneOrMore validation failed: {:?}", ctxt.errors);
3435 }
3436
3437 #[test]
3446 fn test_validate_optional_present() {
3447 let schema_xml = r#"<?xml version="1.0"?>
3448<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3449 <optional>
3450 <element name="opt">
3451 <text/>
3452 </element>
3453 </optional>
3454 <text/>
3455</element>"#;
3456
3457 let doc_xml = r#"<?xml version="1.0"?>
3458<root><opt>present</opt>text</root>"#;
3459
3460 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3461
3462 let doc = unsafe {
3463 crate::abi::exports_xml2::xmlReadMemory(
3464 doc_xml.as_ptr() as *const c_char,
3465 doc_xml.len() as c_int,
3466 c"test.xml".as_ptr() as *const c_char,
3467 ptr::null(),
3468 0,
3469 )
3470 };
3471 assert!(!doc.is_null());
3472
3473 let mut ctxt = RelaxNgValidCtxt::new();
3474 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3475 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3476
3477 assert!(
3478 valid,
3479 "Optional present validation failed: {:?}",
3480 ctxt.errors
3481 );
3482 }
3483
3484 #[test]
3493 fn test_validate_optional_absent() {
3494 let schema_xml = r#"<?xml version="1.0"?>
3495<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3496 <optional>
3497 <element name="opt">
3498 <text/>
3499 </element>
3500 </optional>
3501 <text/>
3502</element>"#;
3503
3504 let doc_xml = r#"<?xml version="1.0"?>
3505<root>text only</root>"#;
3506
3507 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3508
3509 let doc = unsafe {
3510 crate::abi::exports_xml2::xmlReadMemory(
3511 doc_xml.as_ptr() as *const c_char,
3512 doc_xml.len() as c_int,
3513 c"test.xml".as_ptr() as *const c_char,
3514 ptr::null(),
3515 0,
3516 )
3517 };
3518 assert!(!doc.is_null());
3519
3520 let mut ctxt = RelaxNgValidCtxt::new();
3521 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3522 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3523
3524 assert!(
3525 valid,
3526 "Optional absent validation failed: {:?}",
3527 ctxt.errors
3528 );
3529 }
3530
3531 #[test]
3540 fn test_validate_sequence() {
3541 let schema_xml = r#"<?xml version="1.0"?>
3542<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3543 <start>
3544 <element name="root">
3545 <sequence>
3546 <element name="first">
3547 <text/>
3548 </element>
3549 <element name="second">
3550 <text/>
3551 </element>
3552 </sequence>
3553 </element>
3554 </start>
3555</grammar>"#;
3556
3557 let doc_xml = r#"<?xml version="1.0"?>
3558<root><first>First</first><second>Second</second></root>"#;
3559
3560 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3561
3562 let doc = unsafe {
3563 crate::abi::exports_xml2::xmlReadMemory(
3564 doc_xml.as_ptr() as *const c_char,
3565 doc_xml.len() as c_int,
3566 c"test.xml".as_ptr() as *const c_char,
3567 ptr::null(),
3568 0,
3569 )
3570 };
3571 assert!(!doc.is_null());
3572
3573 let mut ctxt = RelaxNgValidCtxt::new();
3574 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3575 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3576
3577 assert!(valid, "Sequence validation failed: {:?}", ctxt.errors);
3578 }
3579
3580 #[test]
3589 fn test_validate_data_pattern() {
3590 let schema_xml = r#"<?xml version="1.0"?>
3591<element name="age" xmlns="http://relaxng.org/ns/structure/1.0">
3592 <data type="integer"/>
3593</element>"#;
3594
3595 let doc_xml = r#"<?xml version="1.0"?>
3596<age>25</age>"#;
3597
3598 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3599
3600 let doc = unsafe {
3601 crate::abi::exports_xml2::xmlReadMemory(
3602 doc_xml.as_ptr() as *const c_char,
3603 doc_xml.len() as c_int,
3604 c"test.xml".as_ptr() as *const c_char,
3605 ptr::null(),
3606 0,
3607 )
3608 };
3609 assert!(!doc.is_null());
3610
3611 let mut ctxt = RelaxNgValidCtxt::new();
3612 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3613 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3614
3615 assert!(valid, "Data pattern validation failed: {:?}", ctxt.errors);
3616 }
3617
3618 #[test]
3627 fn test_validate_data_pattern_invalid() {
3628 let schema_xml = r#"<?xml version="1.0"?>
3629<element name="age" xmlns="http://relaxng.org/ns/structure/1.0">
3630 <data type="integer"/>
3631</element>"#;
3632
3633 let doc_xml = r#"<?xml version="1.0"?>
3634<age>not-a-number</age>"#;
3635
3636 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3637
3638 let doc = unsafe {
3639 crate::abi::exports_xml2::xmlReadMemory(
3640 doc_xml.as_ptr() as *const c_char,
3641 doc_xml.len() as c_int,
3642 c"test.xml".as_ptr() as *const c_char,
3643 ptr::null(),
3644 0,
3645 )
3646 };
3647 assert!(!doc.is_null());
3648
3649 let mut ctxt = RelaxNgValidCtxt::new();
3650 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3651 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3652
3653 assert!(!valid, "Validation should have failed for invalid integer");
3654 }
3655
3656 #[test]
3665 fn test_validate_value_pattern() {
3666 let schema_xml = r#"<?xml version="1.0"?>
3667<element name="status" xmlns="http://relaxng.org/ns/structure/1.0">
3668 <value>active</value>
3669</element>"#;
3670
3671 let doc_xml = r#"<?xml version="1.0"?>
3672<status>active</status>"#;
3673
3674 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3675
3676 let doc = unsafe {
3677 crate::abi::exports_xml2::xmlReadMemory(
3678 doc_xml.as_ptr() as *const c_char,
3679 doc_xml.len() as c_int,
3680 c"test.xml".as_ptr() as *const c_char,
3681 ptr::null(),
3682 0,
3683 )
3684 };
3685 assert!(!doc.is_null());
3686
3687 let mut ctxt = RelaxNgValidCtxt::new();
3688 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3689 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3690
3691 assert!(valid, "Value pattern validation failed: {:?}", ctxt.errors);
3692 }
3693
3694 #[test]
3703 fn test_validate_value_pattern_mismatch() {
3704 let schema_xml = r#"<?xml version="1.0"?>
3705<element name="status" xmlns="http://relaxng.org/ns/structure/1.0">
3706 <value>active</value>
3707</element>"#;
3708
3709 let doc_xml = r#"<?xml version="1.0"?>
3710<status>inactive</status>"#;
3711
3712 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3713
3714 let doc = unsafe {
3715 crate::abi::exports_xml2::xmlReadMemory(
3716 doc_xml.as_ptr() as *const c_char,
3717 doc_xml.len() as c_int,
3718 c"test.xml".as_ptr() as *const c_char,
3719 ptr::null(),
3720 0,
3721 )
3722 };
3723 assert!(!doc.is_null());
3724
3725 let mut ctxt = RelaxNgValidCtxt::new();
3726 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3727 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3728
3729 assert!(!valid, "Validation should have failed for value mismatch");
3730 }
3731
3732 #[test]
3741 fn test_validate_not_allowed() {
3742 let schema_xml = r#"<?xml version="1.0"?>
3743<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3744 <notAllowed/>
3745</element>"#;
3746
3747 let doc_xml = r#"<?xml version="1.0"?>
3748<root>should not be allowed</root>"#;
3749
3750 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3751
3752 let doc = unsafe {
3753 crate::abi::exports_xml2::xmlReadMemory(
3754 doc_xml.as_ptr() as *const c_char,
3755 doc_xml.len() as c_int,
3756 c"test.xml".as_ptr() as *const c_char,
3757 ptr::null(),
3758 0,
3759 )
3760 };
3761 assert!(!doc.is_null());
3762
3763 let mut ctxt = RelaxNgValidCtxt::new();
3764 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3765 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3766
3767 assert!(!valid, "notAllowed should cause validation failure");
3768 }
3769
3770 #[test]
3779 fn test_validate_interleave() {
3780 let schema_xml = r#"<?xml version="1.0"?>
3781<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3782 <start>
3783 <element name="root">
3784 <interleave>
3785 <element name="a">
3786 <text/>
3787 </element>
3788 <element name="b">
3789 <text/>
3790 </element>
3791 </interleave>
3792 </element>
3793 </start>
3794</grammar>"#;
3795
3796 let doc_xml = r#"<?xml version="1.0"?>
3797<root><a>A</a><b>B</b></root>"#;
3798
3799 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3800
3801 let doc = unsafe {
3802 crate::abi::exports_xml2::xmlReadMemory(
3803 doc_xml.as_ptr() as *const c_char,
3804 doc_xml.len() as c_int,
3805 c"test.xml".as_ptr() as *const c_char,
3806 ptr::null(),
3807 0,
3808 )
3809 };
3810 assert!(!doc.is_null());
3811
3812 let mut ctxt = RelaxNgValidCtxt::new();
3813 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3814 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3815
3816 assert!(valid, "Interleave validation failed: {:?}", ctxt.errors);
3817 }
3818
3819 #[test]
3828 fn test_validate_empty_element() {
3829 let schema_xml = r#"<?xml version="1.0"?>
3830<element name="br" xmlns="http://relaxng.org/ns/structure/1.0">
3831 <empty/>
3832</element>"#;
3833
3834 let doc_xml = r#"<?xml version="1.0"?>
3835<br/>"#;
3836
3837 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3838
3839 let doc = unsafe {
3840 crate::abi::exports_xml2::xmlReadMemory(
3841 doc_xml.as_ptr() as *const c_char,
3842 doc_xml.len() as c_int,
3843 c"test.xml".as_ptr() as *const c_char,
3844 ptr::null(),
3845 0,
3846 )
3847 };
3848 assert!(!doc.is_null());
3849
3850 let mut ctxt = RelaxNgValidCtxt::new();
3851 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3852 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3853
3854 assert!(valid, "Empty element validation failed: {:?}", ctxt.errors);
3855 }
3856
3857 #[test]
3860 fn test_validate_datatype_string() {
3861 assert!(rng_validate_datatype_value(Some("string"), "hello"));
3862 assert!(rng_validate_datatype_value(Some("string"), ""));
3863 }
3864
3865 #[test]
3866 fn test_validate_datatype_boolean() {
3867 assert!(rng_validate_datatype_value(Some("boolean"), "true"));
3868 assert!(rng_validate_datatype_value(Some("boolean"), "false"));
3869 assert!(rng_validate_datatype_value(Some("boolean"), "1"));
3870 assert!(rng_validate_datatype_value(Some("boolean"), "0"));
3871 assert!(!rng_validate_datatype_value(Some("boolean"), "yes"));
3872 assert!(!rng_validate_datatype_value(Some("boolean"), "no"));
3873 }
3874
3875 #[test]
3876 fn test_validate_datatype_integer() {
3877 assert!(rng_validate_datatype_value(Some("integer"), "42"));
3878 assert!(rng_validate_datatype_value(Some("integer"), "-42"));
3879 assert!(rng_validate_datatype_value(Some("integer"), "+42"));
3880 assert!(!rng_validate_datatype_value(Some("integer"), "12.5"));
3881 assert!(!rng_validate_datatype_value(Some("integer"), "abc"));
3882 assert!(!rng_validate_datatype_value(Some("integer"), ""));
3883 }
3884
3885 #[test]
3886 fn test_validate_datatype_decimal() {
3887 assert!(rng_validate_datatype_value(Some("decimal"), "42"));
3888 assert!(rng_validate_datatype_value(Some("decimal"), "12.5"));
3889 assert!(rng_validate_datatype_value(Some("decimal"), "-3.14"));
3890 assert!(!rng_validate_datatype_value(Some("decimal"), ""));
3891 }
3892
3893 #[test]
3894 fn test_validate_datatype_float() {
3895 assert!(rng_validate_datatype_value(Some("float"), "3.14"));
3896 assert!(rng_validate_datatype_value(Some("float"), "INF"));
3897 assert!(rng_validate_datatype_value(Some("float"), "-INF"));
3898 assert!(rng_validate_datatype_value(Some("float"), "NaN"));
3899 assert!(!rng_validate_datatype_value(Some("float"), ""));
3900 }
3901
3902 #[test]
3903 fn test_validate_datatype_ncname() {
3904 assert!(rng_validate_datatype_value(Some("NCName"), "myElement"));
3905 assert!(rng_validate_datatype_value(Some("NCName"), "_foo"));
3906 assert!(!rng_validate_datatype_value(Some("NCName"), "123abc"));
3907 assert!(!rng_validate_datatype_value(Some("NCName"), ""));
3908 }
3909
3910 #[test]
3911 fn test_validate_datatype_any_uri() {
3912 assert!(rng_validate_datatype_value(
3913 Some("anyURI"),
3914 "http://example.com"
3915 ));
3916 assert!(rng_validate_datatype_value(Some("anyURI"), "urn:isbn:1234"));
3917 assert!(!rng_validate_datatype_value(Some("anyURI"), ""));
3918 assert!(!rng_validate_datatype_value(Some("anyURI"), "has space"));
3919 }
3920
3921 #[test]
3922 fn test_validate_datatype_qname() {
3923 assert!(rng_validate_datatype_value(Some("QName"), "ns:local"));
3924 assert!(rng_validate_datatype_value(Some("QName"), "local"));
3925 assert!(!rng_validate_datatype_value(Some("QName"), ""));
3926 }
3927
3928 #[test]
3940 fn test_c_abi_new_parse_free() {
3941 let schema_xml = r#"<?xml version="1.0"?>
3942<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3943 <text/>
3944</element>"#;
3945
3946 let ctxt = unsafe {
3947 xmlRelaxNGNewMemParserCtxt(
3948 schema_xml.as_ptr() as *const c_char,
3949 schema_xml.len() as c_int,
3950 )
3951 };
3952 assert!(!ctxt.is_null(), "Parser context should not be null");
3953
3954 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3955 assert!(!schema.is_null(), "Schema should not be null");
3956
3957 unsafe { xmlRelaxNGFree(schema) };
3959 }
3960
3961 #[test]
3971 fn test_c_abi_validate_doc() {
3972 let schema_xml = r#"<?xml version="1.0"?>
3973<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3974 <text/>
3975</element>"#;
3976
3977 let doc_xml = r#"<?xml version="1.0"?>
3978<root>Hello</root>"#;
3979
3980 let ctxt = unsafe {
3981 xmlRelaxNGNewMemParserCtxt(
3982 schema_xml.as_ptr() as *const c_char,
3983 schema_xml.len() as c_int,
3984 )
3985 };
3986 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3987 assert!(!schema.is_null());
3988
3989 let valid_ctxt = unsafe { xmlRelaxNGNewValidCtxt(schema) };
3990 assert!(!valid_ctxt.is_null());
3991
3992 let doc = unsafe {
3993 crate::abi::exports_xml2::xmlReadMemory(
3994 doc_xml.as_ptr() as *const c_char,
3995 doc_xml.len() as c_int,
3996 c"test.xml".as_ptr() as *const c_char,
3997 ptr::null(),
3998 0,
3999 )
4000 };
4001 assert!(!doc.is_null());
4002
4003 let result = unsafe { xmlRelaxNGValidateDoc(valid_ctxt, doc) };
4004 assert_eq!(result, 0, "Validation should succeed");
4005
4006 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
4007 unsafe { xmlRelaxNGFreeValidCtxt(valid_ctxt) };
4008 unsafe { xmlRelaxNGFree(schema) };
4009 }
4010
4011 #[test]
4025 fn test_c_abi_validate_full_element() {
4026 let schema_xml = r#"<?xml version="1.0"?>
4027<element name="item" xmlns="http://relaxng.org/ns/structure/1.0">
4028 <text/>
4029</element>"#;
4030
4031 let doc_xml = r#"<?xml version="1.0"?>
4032<root><item>Content</item></root>"#;
4033
4034 let ctxt = unsafe {
4035 xmlRelaxNGNewMemParserCtxt(
4036 schema_xml.as_ptr() as *const c_char,
4037 schema_xml.len() as c_int,
4038 )
4039 };
4040 let schema = unsafe { xmlRelaxNGParse(ctxt) };
4041 assert!(!schema.is_null());
4042
4043 let valid_ctxt = unsafe { xmlRelaxNGNewValidCtxt(schema) };
4044 assert!(!valid_ctxt.is_null());
4045
4046 let doc = unsafe {
4047 crate::abi::exports_xml2::xmlReadMemory(
4048 doc_xml.as_ptr() as *const c_char,
4049 doc_xml.len() as c_int,
4050 c"test.xml".as_ptr() as *const c_char,
4051 ptr::null(),
4052 0,
4053 )
4054 };
4055 assert!(!doc.is_null());
4056
4057 let item = unsafe {
4059 let mut node = (*doc).children;
4061 while !node.is_null() {
4062 if (*node).type_ == XML_ELEMENT_NODE as c_int {
4063 break;
4064 }
4065 node = (*node).next;
4066 }
4067 if !node.is_null() {
4068 node = (*node).children;
4070 while !node.is_null() {
4071 if (*node).type_ == XML_ELEMENT_NODE as c_int {
4072 break;
4073 }
4074 node = (*node).next;
4075 }
4076 }
4077 node
4078 };
4079 assert!(!item.is_null(), "Should find <item> element");
4080
4081 let result = unsafe { xmlRelaxNGValidateFullElement(valid_ctxt, doc, item) };
4082 assert_eq!(result, 0, "Element validation should succeed");
4083
4084 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
4085 unsafe { xmlRelaxNGFreeValidCtxt(valid_ctxt) };
4086 unsafe { xmlRelaxNGFree(schema) };
4087 }
4088
4089 #[test]
4097 fn test_c_abi_null_handling() {
4098 assert_eq!(
4100 unsafe { xmlRelaxNGValidateDoc(ptr::null_mut(), ptr::null_mut()) },
4101 -1
4102 );
4103 assert_eq!(
4104 unsafe {
4105 xmlRelaxNGValidateFullElement(ptr::null_mut(), ptr::null_mut(), ptr::null_mut())
4106 },
4107 -1
4108 );
4109 assert!(unsafe { xmlRelaxNGNewMemParserCtxt(ptr::null(), 0).is_null() });
4110
4111 unsafe { xmlRelaxNGFree(ptr::null_mut()) };
4113 unsafe { xmlRelaxNGFreeParserCtxt(ptr::null_mut()) };
4114 unsafe { xmlRelaxNGFreeValidCtxt(ptr::null_mut()) };
4115 }
4116
4117 #[test]
4120 fn test_parse_with_div() {
4121 let schema_xml = r#"<?xml version="1.0"?>
4122<grammar xmlns="http://relaxng.org/ns/structure/1.0">
4123 <div>
4124 <define name="shared">
4125 <text/>
4126 </define>
4127 </div>
4128 <start>
4129 <element name="root">
4130 <ref name="shared"/>
4131 </element>
4132 </start>
4133</grammar>"#;
4134
4135 let result = rng_parse(schema_xml);
4136 assert!(
4137 result.is_ok(),
4138 "Failed to parse with div: {:?}",
4139 result.err()
4140 );
4141 let schema = result.unwrap();
4142 assert_eq!(schema.grammar.defines.len(), 1);
4143 assert_eq!(schema.grammar.defines[0].name, "shared");
4144 }
4145
4146 #[test]
4155 fn test_validate_list_pattern() {
4156 let schema_xml = r#"<?xml version="1.0"?>
4157<element name="tokens" xmlns="http://relaxng.org/ns/structure/1.0">
4158 <list>
4159 <data type="token"/>
4160 </list>
4161</element>"#;
4162
4163 let doc_xml = r#"<?xml version="1.0"?>
4164<tokens>abc def ghi</tokens>"#;
4165
4166 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
4167
4168 let doc = unsafe {
4169 crate::abi::exports_xml2::xmlReadMemory(
4170 doc_xml.as_ptr() as *const c_char,
4171 doc_xml.len() as c_int,
4172 c"test.xml".as_ptr() as *const c_char,
4173 ptr::null(),
4174 0,
4175 )
4176 };
4177 assert!(!doc.is_null());
4178
4179 let mut ctxt = RelaxNgValidCtxt::new();
4180 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
4181 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
4182
4183 assert!(valid, "List pattern validation failed: {:?}", ctxt.errors);
4184 }
4185
4186 #[test]
4195 fn test_validate_group_pattern() {
4196 let schema_xml = r#"<?xml version="1.0"?>
4197<grammar xmlns="http://relaxng.org/ns/structure/1.0">
4198 <start>
4199 <element name="root">
4200 <group>
4201 <element name="a">
4202 <text/>
4203 </element>
4204 <element name="b">
4205 <text/>
4206 </element>
4207 </group>
4208 </element>
4209 </start>
4210</grammar>"#;
4211
4212 let doc_xml = r#"<?xml version="1.0"?>
4213<root><a>First</a><b>Second</b></root>"#;
4214
4215 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
4216
4217 let doc = unsafe {
4218 crate::abi::exports_xml2::xmlReadMemory(
4219 doc_xml.as_ptr() as *const c_char,
4220 doc_xml.len() as c_int,
4221 c"test.xml".as_ptr() as *const c_char,
4222 ptr::null(),
4223 0,
4224 )
4225 };
4226 assert!(!doc.is_null());
4227
4228 let mut ctxt = RelaxNgValidCtxt::new();
4229 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
4230 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
4231
4232 assert!(valid, "Group validation failed: {:?}", ctxt.errors);
4233 }
4234
4235 #[test]
4244 fn test_validate_undefined_ref() {
4245 let schema_xml = r#"<?xml version="1.0"?>
4246<grammar xmlns="http://relaxng.org/ns/structure/1.0">
4247 <start>
4248 <element name="root">
4249 <ref name="undefined"/>
4250 </element>
4251 </start>
4252</grammar>"#;
4253
4254 let doc_xml = r#"<?xml version="1.0"?>
4255<root>Content</root>"#;
4256
4257 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
4258
4259 let doc = unsafe {
4260 crate::abi::exports_xml2::xmlReadMemory(
4261 doc_xml.as_ptr() as *const c_char,
4262 doc_xml.len() as c_int,
4263 c"test.xml".as_ptr() as *const c_char,
4264 ptr::null(),
4265 0,
4266 )
4267 };
4268 assert!(!doc.is_null());
4269
4270 let mut ctxt = RelaxNgValidCtxt::new();
4271 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
4272 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
4273
4274 assert!(!valid, "Undefined ref should cause failure");
4275 }
4276
4277 #[test]
4284 fn test_validate_null_doc() {
4285 let schema = RelaxNgSchema::new();
4286 let mut ctxt = RelaxNgValidCtxt::new();
4287 let valid = unsafe { rng_validate_doc(&schema, ptr::null_mut(), &mut ctxt) };
4288 assert!(!valid);
4289 }
4290
4291 #[test]
4292 fn test_parse_external_ref_schema() {
4293 let schema_xml = r#"<?xml version="1.0"?>
4294<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
4295 <externalRef href="external.rng"/>
4296</element>"#;
4297
4298 let result = rng_parse(schema_xml);
4299 assert!(
4300 result.is_ok(),
4301 "Failed to parse externalRef: {:?}",
4302 result.err()
4303 );
4304 let schema = result.unwrap();
4305 if let Some(ref start) = schema.grammar.start {
4306 assert_eq!(start.pattern_type, RelaxNgPatternType::Element);
4307 assert_eq!(start.name.as_deref(), Some("root"));
4308 }
4309 }
4310
4311 #[test]
4312 fn test_validate_boolean_datatype() {
4313 assert!(rng_validate_datatype_value(Some("boolean"), "true"));
4314 assert!(rng_validate_datatype_value(Some("boolean"), "false"));
4315 assert!(!rng_validate_datatype_value(Some("boolean"), "maybe"));
4316 }
4317
4318 #[test]
4319 fn test_validate_unknown_datatype() {
4320 assert!(rng_validate_datatype_value(Some("custom-type"), "anything"));
4322 }
4323
4324 #[test]
4325 fn test_validate_no_datatype() {
4326 assert!(rng_validate_datatype_value(None, "anything"));
4328 }
4329
4330 #[test]
4331 fn test_parse_schema_with_ns_prefix() {
4332 let schema_xml = r#"<?xml version="1.0"?>
4333<rng:element name="root" xmlns:rng="http://relaxng.org/ns/structure/1.0">
4334 <rng:text/>
4335</rng:element>"#;
4336
4337 let result = rng_parse(schema_xml);
4338 assert!(result.is_ok(), "Failed with ns prefix: {:?}", result.err());
4339 }
4340
4341 #[test]
4342 fn test_validation_context_path() {
4343 let mut ctxt = RelaxNgValidCtxt::new();
4344 assert_eq!(ctxt.current_path(), "/");
4345
4346 ctxt.path.push("root".to_string());
4347 assert_eq!(ctxt.current_path(), "/root");
4348
4349 ctxt.path.push("child".to_string());
4350 assert_eq!(ctxt.current_path(), "/root/child");
4351
4352 ctxt.path.pop();
4353 assert_eq!(ctxt.current_path(), "/root");
4354 }
4355
4356 #[test]
4365 fn test_validate_sequence_wrong_order() {
4366 let schema_xml = r#"<?xml version="1.0"?>
4367<grammar xmlns="http://relaxng.org/ns/structure/1.0">
4368 <start>
4369 <element name="root">
4370 <sequence>
4371 <element name="first">
4372 <text/>
4373 </element>
4374 <element name="second">
4375 <text/>
4376 </element>
4377 </sequence>
4378 </element>
4379 </start>
4380</grammar>"#;
4381
4382 let doc_xml = r#"<?xml version="1.0"?>
4383<root><second>Wrong</second><first>Order</first></root>"#;
4384
4385 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
4386
4387 let doc = unsafe {
4388 crate::abi::exports_xml2::xmlReadMemory(
4389 doc_xml.as_ptr() as *const c_char,
4390 doc_xml.len() as c_int,
4391 c"test.xml".as_ptr() as *const c_char,
4392 ptr::null(),
4393 0,
4394 )
4395 };
4396 assert!(!doc.is_null());
4397
4398 let mut ctxt = RelaxNgValidCtxt::new();
4399 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
4400 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
4401
4402 assert!(!valid, "Wrong sequence order should fail");
4405 }
4406}