1#![allow(missing_docs, non_snake_case, non_camel_case_types, non_upper_case_globals)]
26
27use core::ffi::c_void;
28use core::ptr;
29use std::os::raw::{c_char, c_int};
30
31use crate::abi::allocator;
32use crate::abi::structs::*;
33use crate::abi::types::xmlElementType::*;
34use crate::abi::types::*;
35
36#[derive(Debug, Clone, PartialEq)]
47pub enum RelaxNgNameClass {
48 Name(String),
50 AnyName,
52 NsName(String),
54 Choice(Vec<RelaxNgNameClass>),
56 Except(Box<RelaxNgNameClass>, Box<RelaxNgNameClass>),
58}
59
60impl RelaxNgNameClass {
61 pub fn matches(&self, name: &str, ns_uri: Option<&str>) -> bool {
63 match self {
64 RelaxNgNameClass::Name(n) => name == n.as_str(),
65 RelaxNgNameClass::AnyName => true,
66 RelaxNgNameClass::NsName(ns) => {
67 if let Some(uri) = ns_uri {
68 uri == ns.as_str()
69 } else {
70 false
71 }
72 }
73 RelaxNgNameClass::Choice(choices) => {
74 choices.iter().any(|c| c.matches(name, ns_uri))
75 }
76 RelaxNgNameClass::Except(positive, negative) => {
77 positive.matches(name, ns_uri) && !negative.matches(name, ns_uri)
78 }
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum RelaxNgPatternType {
94 Element,
95 Attribute,
96 Text,
97 Choice,
98 Sequence,
99 Interleave,
100 ZeroOrMore,
101 OneOrMore,
102 Optional,
103 List,
104 Group,
105 Data,
106 Value,
107 Ref,
108 Define,
109 Grammar,
110 NotAllowed,
111 Empty,
112 ExternalRef,
113 Include,
114 Start,
115}
116
117#[derive(Debug, Clone)]
121pub struct RelaxNgPattern {
122 pub pattern_type: RelaxNgPatternType,
124 pub name_class: Option<RelaxNgNameClass>,
126 pub children: Vec<RelaxNgPattern>,
128 pub name: Option<String>,
130 pub ns: Option<String>,
132 pub datatype: Option<String>,
134 pub value: Option<String>,
136 pub datatype_library: Option<String>,
138}
139
140impl RelaxNgPattern {
141 pub fn new(pattern_type: RelaxNgPatternType) -> Self {
143 Self {
144 pattern_type,
145 name_class: None,
146 children: Vec::new(),
147 name: None,
148 ns: None,
149 datatype: None,
150 value: None,
151 datatype_library: None,
152 }
153 }
154
155 pub fn element(name: &str) -> Self {
157 let mut p = Self::new(RelaxNgPatternType::Element);
158 p.name_class = Some(RelaxNgNameClass::Name(name.to_string()));
159 p
160 }
161
162 pub fn attribute(name: &str) -> Self {
164 let mut p = Self::new(RelaxNgPatternType::Attribute);
165 p.name_class = Some(RelaxNgNameClass::Name(name.to_string()));
166 p
167 }
168
169 pub fn text() -> Self {
171 Self::new(RelaxNgPatternType::Text)
172 }
173
174 pub fn empty() -> Self {
176 Self::new(RelaxNgPatternType::Empty)
177 }
178
179 pub fn not_allowed() -> Self {
181 Self::new(RelaxNgPatternType::NotAllowed)
182 }
183}
184
185#[derive(Debug, Clone)]
191pub struct RelaxNgDefine {
192 pub name: String,
194 pub pattern: RelaxNgPattern,
196}
197
198#[derive(Debug, Clone)]
206pub struct RelaxNgGrammar {
207 pub defines: Vec<RelaxNgDefine>,
209 pub start: Option<RelaxNgPattern>,
211 pub includes: Vec<RelaxNgGrammar>,
213}
214
215impl RelaxNgGrammar {
216 pub fn new() -> Self {
217 Self {
218 defines: Vec::new(),
219 start: None,
220 includes: Vec::new(),
221 }
222 }
223
224 pub fn lookup(&self, name: &str) -> Option<&RelaxNgPattern> {
226 for def in &self.defines {
227 if def.name == name {
228 return Some(&def.pattern);
229 }
230 }
231 for inc in &self.includes {
233 if let Some(p) = inc.lookup(name) {
234 return Some(p);
235 }
236 }
237 None
238 }
239}
240
241impl Default for RelaxNgGrammar {
242 fn default() -> Self {
243 Self::new()
244 }
245}
246
247#[derive(Debug, Clone)]
255pub struct RelaxNgSchema {
256 pub grammar: RelaxNgGrammar,
258 pub errors: Vec<String>,
260}
261
262impl RelaxNgSchema {
263 pub fn new() -> Self {
264 Self {
265 grammar: RelaxNgGrammar::new(),
266 errors: Vec::new(),
267 }
268 }
269}
270
271impl Default for RelaxNgSchema {
272 fn default() -> Self {
273 Self::new()
274 }
275}
276
277#[derive(Debug)]
287pub struct RelaxNgValidCtxt {
288 pub schema: Option<RelaxNgSchema>,
290 pub errors: Vec<String>,
292 pub nb_errors: i32,
294 pub path: Vec<String>,
296 pub depth_max: i32,
298 pub depth: i32,
300}
301
302impl RelaxNgValidCtxt {
303 pub fn new() -> Self {
304 Self {
305 schema: None,
306 errors: Vec::new(),
307 nb_errors: 0,
308 path: Vec::new(),
309 depth_max: 256,
310 depth: 0,
311 }
312 }
313
314 pub fn record_error(&mut self, msg: String) {
316 self.errors.push(msg);
317 self.nb_errors += 1;
318 }
319
320 pub fn current_path(&self) -> String {
322 if self.path.is_empty() {
323 "/".to_string()
324 } else {
325 format!("/{}", self.path.join("/"))
326 }
327 }
328}
329
330impl Default for RelaxNgValidCtxt {
331 fn default() -> Self {
332 Self::new()
333 }
334}
335
336unsafe fn get_local_name(node: *mut _xmlNode) -> String {
346 if node.is_null() {
347 return String::new();
348 }
349 unsafe {
350 let name = (*node).name;
351 if name.is_null() {
352 return String::new();
353 }
354 let mut len = 0;
355 while *name.add(len) != 0 {
356 len += 1;
357 }
358 let slice = std::slice::from_raw_parts(name, len);
359 if let Ok(s) = std::str::from_utf8(slice) {
360 if let Some(pos) = s.find(':') {
361 s[pos + 1..].to_string()
362 } else {
363 s.to_string()
364 }
365 } else {
366 String::new()
367 }
368 }
369}
370
371unsafe fn get_node_text(node: *mut _xmlNode) -> String {
377 if node.is_null() {
378 return String::new();
379 }
380 let mut result = String::new();
381 unsafe {
382 let mut child = (*node).children;
383 while !child.is_null() {
384 if (*child).type_ == XML_TEXT_NODE as c_int
385 || (*child).type_ == XML_CDATA_SECTION_NODE as c_int
386 {
387 if !(*child).content.is_null() {
388 let content = (*child).content;
389 let mut len = 0;
390 while *content.add(len) != 0 {
391 len += 1;
392 }
393 let slice = std::slice::from_raw_parts(content, len);
394 result.push_str(&String::from_utf8_lossy(slice));
395 }
396 }
397 child = (*child).next;
398 }
399 }
400 result
401}
402
403unsafe fn get_node_qname(node: *mut _xmlNode) -> String {
409 if node.is_null() {
410 return String::new();
411 }
412 unsafe {
413 let ns = (*node).ns;
414 let prefix = if !ns.is_null() && !(*ns).prefix.is_null() {
415 let mut len = 0;
416 while *(*ns).prefix.add(len) != 0 {
417 len += 1;
418 }
419 let slice = std::slice::from_raw_parts((*ns).prefix, len);
420 if let Ok(s) = std::str::from_utf8(slice) {
421 format!("{}:", s)
422 } else {
423 String::new()
424 }
425 } else {
426 String::new()
427 };
428
429 let name = (*node).name;
430 if name.is_null() {
431 return String::new();
432 }
433 let mut len = 0;
434 while *name.add(len) != 0 {
435 len += 1;
436 }
437 let slice = std::slice::from_raw_parts(name, len);
438 if let Ok(s) = std::str::from_utf8(slice) {
439 format!("{}{}", prefix, s)
440 } else {
441 String::new()
442 }
443 }
444}
445
446unsafe fn get_node_ns_uri(node: *mut _xmlNode) -> Option<String> {
452 if node.is_null() {
453 return None;
454 }
455 unsafe {
456 let ns = (*node).ns;
457 if ns.is_null() || (*ns).href.is_null() {
458 return None;
459 }
460 let href = (*ns).href;
461 let mut len = 0;
462 while *href.add(len) != 0 {
463 len += 1;
464 }
465 let slice = std::slice::from_raw_parts(href, len);
466 if let Ok(s) = std::str::from_utf8(slice) {
467 Some(s.to_string())
468 } else {
469 None
470 }
471 }
472}
473
474unsafe fn get_attr(node: *mut _xmlNode, name: &str) -> Option<String> {
480 if node.is_null() {
481 return None;
482 }
483 unsafe {
484 let mut prop = (*node).properties;
485 while !prop.is_null() {
486 let prop_name = (*prop).name;
487 if !prop_name.is_null() {
488 let mut len = 0;
489 while *prop_name.add(len) != 0 {
490 len += 1;
491 }
492 let slice = std::slice::from_raw_parts(prop_name, len);
493 if let Ok(s) = std::str::from_utf8(slice) {
494 if s == name {
495 return Some(get_node_text(prop as *mut _xmlNode));
496 }
497 }
498 }
499 prop = (*prop).next;
500 }
501 }
502 None
503}
504
505unsafe fn node_is(node: *mut _xmlNode, local_name: &str) -> bool {
511 if node.is_null() {
512 return false;
513 }
514 unsafe {
515 let name = (*node).name;
516 if name.is_null() {
517 return false;
518 }
519 let mut len = 0;
520 while *name.add(len) != 0 {
521 len += 1;
522 }
523 let slice = std::slice::from_raw_parts(name, len);
524 if let Ok(s) = std::str::from_utf8(slice) {
525 let local = if let Some(pos) = s.find(':') {
526 &s[pos + 1..]
527 } else {
528 s
529 };
530 return local == local_name;
531 }
532 }
533 false
534}
535
536pub fn rng_parse(xml_doc: &str) -> Result<RelaxNgSchema, String> {
549 let doc_ptr = unsafe {
550 crate::abi::exports_xml2::xmlReadMemory(
551 xml_doc.as_ptr() as *const c_char,
552 xml_doc.len() as c_int,
553 b"schema.rng\0".as_ptr() as *const c_char,
554 ptr::null(),
555 0,
556 )
557 };
558
559 if doc_ptr.is_null() {
560 return Err("Failed to parse RELAX NG schema XML document".to_string());
561 }
562
563 let result = unsafe { rng_parse_doc(doc_ptr) };
564 unsafe {
565 crate::abi::exports_xml2::xmlFreeDoc(doc_ptr);
566 }
567 result
568}
569
570unsafe fn rng_parse_doc(doc: *mut _xmlDoc) -> Result<RelaxNgSchema, String> {
576 unsafe {
577 let root = (*doc).children;
578 if root.is_null() {
579 return Err("RELAX NG document has no root element".to_string());
580 }
581
582 let mut root_elem = root;
584 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
585 root_elem = (*root_elem).next;
586 }
587
588 if root_elem.is_null() {
589 return Err("RELAX NG document has no root element".to_string());
590 }
591
592 let local_name = get_local_name(root_elem);
593 let mut schema = RelaxNgSchema::new();
594
595 match local_name.as_str() {
596 "grammar" => {
597 schema.grammar = rng_parse_grammar_node(root_elem, &mut schema);
599 Ok(schema)
600 }
601 "element" | "attribute" | "text" | "choice" | "sequence" | "interleave"
602 | "zeroOrMore" | "oneOrMore" | "optional" | "list" | "group" | "data"
603 | "value" | "ref" | "notAllowed" | "empty" | "externalRef" | "define"
604 | "start" | "include" => {
605 let pattern = rng_parse_pattern(root_elem, &mut schema);
607 schema.grammar.start = Some(pattern);
608 Ok(schema)
609 }
610 _ => {
611 Err(format!(
612 "Unknown RELAX NG root element: '{}'",
613 local_name
614 ))
615 }
616 }
617 }
618}
619
620unsafe fn rng_parse_grammar_node(
626 node: *mut _xmlNode,
627 schema: &mut RelaxNgSchema,
628) -> RelaxNgGrammar {
629 unsafe {
630 let mut grammar = RelaxNgGrammar::new();
631
632 let mut child = (*node).children;
633 while !child.is_null() {
634 if (*child).type_ == XML_ELEMENT_NODE as c_int {
635 let local = get_local_name(child);
636 match local.as_str() {
637 "define" => {
638 let def = rng_parse_define(child, schema);
639 grammar.defines.push(def);
640 }
641 "start" => {
642 grammar.start = Some(rng_parse_pattern(child, schema));
643 }
644 "include" => {
645 if let Some(inc) = rng_parse_include(child, schema) {
646 grammar.includes.push(inc);
647 }
648 }
649 "div" => {
650 let sub_grammar = rng_parse_grammar_node(child, schema);
652 grammar.defines.extend(sub_grammar.defines);
653 if sub_grammar.start.is_some() {
654 grammar.start = sub_grammar.start;
655 }
656 grammar.includes.extend(sub_grammar.includes);
657 }
658 _ => {
659 schema
661 .errors
662 .push(format!("Unexpected element '<{}>' in grammar", local));
663 }
664 }
665 }
666 child = (*child).next;
667 }
668
669 grammar
670 }
671}
672
673unsafe fn rng_parse_define(node: *mut _xmlNode, schema: &mut RelaxNgSchema) -> RelaxNgDefine {
679 unsafe {
680 let name = get_attr(node, "name").unwrap_or_default();
681 let pattern = rng_parse_pattern(node, schema);
682 RelaxNgDefine { name, pattern }
683 }
684}
685
686unsafe fn rng_parse_include(
692 node: *mut _xmlNode,
693 _schema: &mut RelaxNgSchema,
694) -> Option<RelaxNgGrammar> {
695 unsafe {
696 let href = get_attr(node, "href");
697 if let Some(url) = href {
698 let url_c = std::ffi::CString::new(url.clone()).ok()?;
701 let doc = crate::abi::exports_xml2::xmlParseFile(url_c.as_ptr());
702 if doc.is_null() {
703 return None;
704 }
705 let mut inc_schema = RelaxNgSchema::new();
706 let grammar = rng_parse_grammar_node(
707 {
708 let mut root = (*doc).children;
709 while !root.is_null() && (*root).type_ != XML_ELEMENT_NODE as c_int {
710 root = (*root).next;
711 }
712 root
713 },
714 &mut inc_schema,
715 );
716 crate::abi::exports_xml2::xmlFreeDoc(doc);
717 Some(grammar)
718 } else {
719 let mut inc_schema = RelaxNgSchema::new();
721 let grammar = rng_parse_grammar_node(node, &mut inc_schema);
722 Some(grammar)
723 }
724 }
725}
726
727unsafe fn rng_parse_pattern(node: *mut _xmlNode, schema: &mut RelaxNgSchema) -> RelaxNgPattern {
733 unsafe {
734 let local = get_local_name(node);
735
736 match local.as_str() {
737 "element" => rng_parse_element_pattern(node, schema),
738 "attribute" => rng_parse_attribute_pattern(node, schema),
739 "text" => RelaxNgPattern::text(),
740 "empty" => RelaxNgPattern::empty(),
741 "notAllowed" => RelaxNgPattern::not_allowed(),
742 "choice" => rng_parse_composite_pattern(node, RelaxNgPatternType::Choice, schema),
743 "sequence" => rng_parse_composite_pattern(node, RelaxNgPatternType::Sequence, schema),
744 "interleave" => {
745 rng_parse_composite_pattern(node, RelaxNgPatternType::Interleave, schema)
746 }
747 "zeroOrMore" => {
748 rng_parse_unary_pattern(node, RelaxNgPatternType::ZeroOrMore, schema)
749 }
750 "oneOrMore" => {
751 rng_parse_unary_pattern(node, RelaxNgPatternType::OneOrMore, schema)
752 }
753 "optional" => rng_parse_unary_pattern(node, RelaxNgPatternType::Optional, schema),
754 "list" => rng_parse_unary_pattern(node, RelaxNgPatternType::List, schema),
755 "group" => rng_parse_composite_pattern(node, RelaxNgPatternType::Group, schema),
756 "data" => rng_parse_data_pattern(node, schema),
757 "value" => rng_parse_value_pattern(node, schema),
758 "ref" => rng_parse_ref_pattern(node),
759 "externalRef" => rng_parse_external_ref(node, schema),
760 "define" | "start" | "grammar" | "include" | "div" => {
761 let mut child = (*node).children;
763 let mut result = RelaxNgPattern::empty();
764 while !child.is_null() {
765 if (*child).type_ == XML_ELEMENT_NODE as c_int {
766 result = rng_parse_pattern(child, schema);
767 break;
768 }
769 child = (*child).next;
770 }
771 result
772 }
773 _ => {
774 schema
776 .errors
777 .push(format!("Unknown pattern element '<{}>'", local));
778 RelaxNgPattern::empty()
779 }
780 }
781 }
782}
783
784unsafe fn rng_parse_element_pattern(
790 node: *mut _xmlNode,
791 schema: &mut RelaxNgSchema,
792) -> RelaxNgPattern {
793 unsafe {
794 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Element);
795
796 let name_attr = get_attr(node, "name");
798 pattern.name = name_attr.clone();
799
800 if let Some(ref n) = name_attr {
801 pattern.name_class = Some(RelaxNgNameClass::Name(n.clone()));
802 }
803
804 let mut child = (*node).children;
806 let mut content_found = false;
807
808 while !child.is_null() {
809 if (*child).type_ == XML_ELEMENT_NODE as c_int {
810 let child_local = get_local_name(child);
811
812 match child_local.as_str() {
813 "name" => {
814 let text = get_node_text(child);
815 if !text.is_empty() {
816 pattern.name_class = Some(RelaxNgNameClass::Name(text.trim().to_string()));
817 }
818 }
819 "anyName" => {
820 pattern.name_class = Some(rng_parse_any_name(child));
821 }
822 "nsName" => {
823 pattern.name_class = Some(rng_parse_ns_name(child));
824 }
825 "choice" if pattern.name_class.is_none() => {
826 pattern.name_class = Some(rng_parse_name_class_choice(child));
828 }
829 _ => {
830 if !content_found {
832 pattern.children.push(rng_parse_pattern(child, schema));
833 content_found = true;
834 }
835 }
836 }
837 }
838 child = (*child).next;
839 }
840
841 pattern
842 }
843}
844
845unsafe fn rng_parse_attribute_pattern(
851 node: *mut _xmlNode,
852 schema: &mut RelaxNgSchema,
853) -> RelaxNgPattern {
854 unsafe {
855 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Attribute);
856
857 let name_attr = get_attr(node, "name");
859 pattern.name = name_attr.clone();
860
861 if let Some(ref n) = name_attr {
862 pattern.name_class = Some(RelaxNgNameClass::Name(n.clone()));
863 }
864
865 let mut child = (*node).children;
867 while !child.is_null() {
868 if (*child).type_ == XML_ELEMENT_NODE as c_int {
869 let child_local = get_local_name(child);
870
871 match child_local.as_str() {
872 "name" => {
873 let text = get_node_text(child);
874 if !text.is_empty() {
875 pattern.name_class =
876 Some(RelaxNgNameClass::Name(text.trim().to_string()));
877 }
878 }
879 "anyName" => {
880 pattern.name_class = Some(rng_parse_any_name(child));
881 }
882 "nsName" => {
883 pattern.name_class = Some(rng_parse_ns_name(child));
884 }
885 "choice" if pattern.name_class.is_none() => {
886 pattern.name_class = Some(rng_parse_name_class_choice(child));
887 }
888 _ => {
889 pattern.children.push(rng_parse_pattern(child, schema));
891 }
892 }
893 }
894 child = (*child).next;
895 }
896
897 pattern
898 }
899}
900
901unsafe fn rng_parse_any_name(node: *mut _xmlNode) -> RelaxNgNameClass {
907 unsafe {
908 let mut child = (*node).children;
910 while !child.is_null() {
911 if (*child).type_ == XML_ELEMENT_NODE as c_int && get_local_name(child) == "except" {
912 let except_nc = rng_parse_name_class_content(child);
913 return RelaxNgNameClass::Except(
914 Box::new(RelaxNgNameClass::AnyName),
915 Box::new(except_nc),
916 );
917 }
918 child = (*child).next;
919 }
920 RelaxNgNameClass::AnyName
921 }
922}
923
924unsafe fn rng_parse_ns_name(node: *mut _xmlNode) -> RelaxNgNameClass {
930 unsafe {
931 let ns = get_attr(node, "ns").unwrap_or_default();
932
933 let mut child = (*node).children;
935 while !child.is_null() {
936 if (*child).type_ == XML_ELEMENT_NODE as c_int && get_local_name(child) == "except" {
937 let except_nc = rng_parse_name_class_content(child);
938 return RelaxNgNameClass::Except(
939 Box::new(RelaxNgNameClass::NsName(ns)),
940 Box::new(except_nc),
941 );
942 }
943 child = (*child).next;
944 }
945
946 RelaxNgNameClass::NsName(ns)
947 }
948}
949
950unsafe fn rng_parse_name_class_choice(node: *mut _xmlNode) -> RelaxNgNameClass {
956 unsafe {
957 let mut choices = Vec::new();
958 let mut child = (*node).children;
959 while !child.is_null() {
960 if (*child).type_ == XML_ELEMENT_NODE as c_int {
961 choices.push(rng_parse_name_class_item(child));
962 }
963 child = (*child).next;
964 }
965 if choices.len() == 1 {
966 choices.remove(0)
967 } else {
968 RelaxNgNameClass::Choice(choices)
969 }
970 }
971}
972
973unsafe fn rng_parse_name_class_item(node: *mut _xmlNode) -> RelaxNgNameClass {
979 unsafe {
980 let local = get_local_name(node);
981 match local.as_str() {
982 "name" => {
983 let text = get_node_text(node);
984 RelaxNgNameClass::Name(text.trim().to_string())
985 }
986 "anyName" => rng_parse_any_name(node),
987 "nsName" => rng_parse_ns_name(node),
988 "choice" => rng_parse_name_class_choice(node),
989 _ => {
990 let text = get_node_text(node);
992 if text.trim().is_empty() {
993 RelaxNgNameClass::AnyName
994 } else {
995 RelaxNgNameClass::Name(text.trim().to_string())
996 }
997 }
998 }
999 }
1000}
1001
1002unsafe fn rng_parse_name_class_content(node: *mut _xmlNode) -> RelaxNgNameClass {
1008 unsafe {
1009 let mut names = Vec::new();
1010 let mut child = (*node).children;
1011 while !child.is_null() {
1012 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1013 names.push(rng_parse_name_class_item(child));
1014 }
1015 child = (*child).next;
1016 }
1017 if names.is_empty() {
1018 RelaxNgNameClass::AnyName
1019 } else if names.len() == 1 {
1020 names.remove(0)
1021 } else {
1022 RelaxNgNameClass::Choice(names)
1023 }
1024 }
1025}
1026
1027unsafe fn rng_parse_composite_pattern(
1033 node: *mut _xmlNode,
1034 pattern_type: RelaxNgPatternType,
1035 schema: &mut RelaxNgSchema,
1036) -> RelaxNgPattern {
1037 unsafe {
1038 let mut pattern = RelaxNgPattern::new(pattern_type);
1039
1040 let mut child = (*node).children;
1041 while !child.is_null() {
1042 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1043 pattern.children.push(rng_parse_pattern(child, schema));
1044 }
1045 child = (*child).next;
1046 }
1047
1048 pattern
1049 }
1050}
1051
1052unsafe fn rng_parse_unary_pattern(
1058 node: *mut _xmlNode,
1059 pattern_type: RelaxNgPatternType,
1060 schema: &mut RelaxNgSchema,
1061) -> RelaxNgPattern {
1062 unsafe {
1063 let mut pattern = RelaxNgPattern::new(pattern_type);
1064
1065 let mut child = (*node).children;
1066 while !child.is_null() {
1067 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1068 pattern.children.push(rng_parse_pattern(child, schema));
1069 break;
1071 }
1072 child = (*child).next;
1073 }
1074
1075 pattern
1076 }
1077}
1078
1079unsafe fn rng_parse_data_pattern(
1085 node: *mut _xmlNode,
1086 _schema: &mut RelaxNgSchema,
1087) -> RelaxNgPattern {
1088 unsafe {
1089 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Data);
1090 pattern.datatype = get_attr(node, "type");
1091 pattern.datatype_library = get_attr(node, "datatypeLibrary");
1092
1093 pattern
1095 }
1096}
1097
1098unsafe fn rng_parse_value_pattern(
1104 node: *mut _xmlNode,
1105 _schema: &mut RelaxNgSchema,
1106) -> RelaxNgPattern {
1107 unsafe {
1108 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Value);
1109 pattern.datatype = get_attr(node, "type");
1110 pattern.datatype_library = get_attr(node, "datatypeLibrary");
1111 pattern.value = Some(get_node_text(node).trim().to_string());
1112
1113 pattern
1114 }
1115}
1116
1117unsafe fn rng_parse_ref_pattern(node: *mut _xmlNode) -> RelaxNgPattern {
1123 unsafe {
1124 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::Ref);
1125 pattern.name = get_attr(node, "name");
1126 pattern
1127 }
1128}
1129
1130unsafe fn rng_parse_external_ref(
1136 node: *mut _xmlNode,
1137 _schema: &mut RelaxNgSchema,
1138) -> RelaxNgPattern {
1139 unsafe {
1140 let mut pattern = RelaxNgPattern::new(RelaxNgPatternType::ExternalRef);
1141 let href = get_attr(node, "href");
1142 pattern.name = href;
1143 pattern
1144 }
1145}
1146
1147pub unsafe fn rng_validate_doc(
1161 schema: &RelaxNgSchema,
1162 doc: *mut _xmlDoc,
1163 ctxt: &mut RelaxNgValidCtxt,
1164) -> bool {
1165 unsafe {
1166 if doc.is_null() {
1167 ctxt.record_error("Document is null".to_string());
1168 return false;
1169 }
1170
1171 let root = (*doc).children;
1172 if root.is_null() {
1173 ctxt.record_error("Document has no children".to_string());
1174 return false;
1175 }
1176
1177 let mut root_elem = root;
1179 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
1180 root_elem = (*root_elem).next;
1181 }
1182
1183 if root_elem.is_null() {
1184 ctxt.record_error("Document has no root element".to_string());
1185 return false;
1186 }
1187
1188 let start_pattern = match &schema.grammar.start {
1190 Some(p) => p,
1191 None => {
1192 ctxt.record_error("Schema has no start pattern".to_string());
1193 return false;
1194 }
1195 };
1196
1197 ctxt.path.clear();
1198 let valid = rng_validate_pattern(start_pattern, root_elem, schema, ctxt);
1199
1200 valid
1202 }
1203}
1204
1205fn rng_validate_pattern(
1211 pattern: &RelaxNgPattern,
1212 node: *mut _xmlNode,
1213 schema: &RelaxNgSchema,
1214 ctxt: &mut RelaxNgValidCtxt,
1215) -> bool {
1216 unsafe {
1217 if ctxt.depth >= ctxt.depth_max {
1218 ctxt.record_error("Maximum validation depth exceeded".to_string());
1219 return false;
1220 }
1221 ctxt.depth += 1;
1222
1223 let result = match pattern.pattern_type {
1224 RelaxNgPatternType::Element => rng_validate_element_pattern(pattern, node, schema, ctxt),
1225 RelaxNgPatternType::Attribute => {
1226 rng_validate_attribute_pattern(pattern, node, schema, ctxt)
1227 }
1228 RelaxNgPatternType::Text => rng_validate_text_pattern(node, ctxt),
1229 RelaxNgPatternType::Empty => rng_validate_empty_pattern(node, ctxt),
1230 RelaxNgPatternType::NotAllowed => {
1231 let name = get_node_qname(node);
1232 ctxt.record_error(format!(
1233 "Element '{}' is not allowed at '{}'",
1234 name,
1235 ctxt.current_path()
1236 ));
1237 false
1238 }
1239 RelaxNgPatternType::Choice => {
1240 rng_validate_choice_pattern(pattern, node, schema, ctxt)
1241 }
1242 RelaxNgPatternType::Sequence => {
1243 rng_validate_sequence_pattern(pattern, node, schema, ctxt)
1244 }
1245 RelaxNgPatternType::Interleave => {
1246 rng_validate_interleave_pattern(pattern, node, schema, ctxt)
1247 }
1248 RelaxNgPatternType::ZeroOrMore => {
1249 rng_validate_zero_or_more(pattern, node, schema, ctxt)
1250 }
1251 RelaxNgPatternType::OneOrMore => {
1252 rng_validate_one_or_more(pattern, node, schema, ctxt)
1253 }
1254 RelaxNgPatternType::Optional => {
1255 rng_validate_optional_pattern(pattern, node, schema, ctxt)
1256 }
1257 RelaxNgPatternType::List => rng_validate_list_pattern(pattern, node, schema, ctxt),
1258 RelaxNgPatternType::Group => {
1259 rng_validate_group_pattern(pattern, node, schema, ctxt)
1260 }
1261 RelaxNgPatternType::Data => rng_validate_data_pattern(pattern, node, ctxt),
1262 RelaxNgPatternType::Value => rng_validate_value_pattern(pattern, node, ctxt),
1263 RelaxNgPatternType::Ref => rng_validate_ref_pattern(pattern, node, schema, ctxt),
1264 RelaxNgPatternType::ExternalRef => {
1265 rng_validate_empty_pattern(node, ctxt)
1267 }
1268 RelaxNgPatternType::Define | RelaxNgPatternType::Grammar | RelaxNgPatternType::Start
1269 | RelaxNgPatternType::Include => {
1270 rng_validate_children(pattern, node, schema, ctxt)
1272 }
1273 };
1274
1275 ctxt.depth -= 1;
1276 result
1277 }
1278}
1279
1280fn rng_validate_children(
1286 pattern: &RelaxNgPattern,
1287 node: *mut _xmlNode,
1288 schema: &RelaxNgSchema,
1289 ctxt: &mut RelaxNgValidCtxt,
1290) -> bool {
1291 unsafe {
1292 if pattern.children.is_empty() {
1293 return true;
1294 }
1295 let mut valid = true;
1297 for child in &pattern.children {
1298 valid &= rng_validate_pattern(child, node, schema, ctxt);
1299 }
1300 valid
1301 }
1302}
1303
1304fn rng_validate_element_pattern(
1310 pattern: &RelaxNgPattern,
1311 node: *mut _xmlNode,
1312 schema: &RelaxNgSchema,
1313 ctxt: &mut RelaxNgValidCtxt,
1314) -> bool {
1315 unsafe {
1316 if node.is_null() || (*node).type_ != XML_ELEMENT_NODE as c_int {
1317 return false;
1318 }
1319
1320 let node_name = get_node_qname(node);
1321 let ns_uri = get_node_ns_uri(node);
1322
1323 if let Some(ref nc) = pattern.name_class {
1325 if !nc.matches(&node_name, ns_uri.as_deref()) {
1326 let pat_name = pattern.name.as_deref().unwrap_or("?");
1327 ctxt.record_error(format!(
1328 "Element '{}' does not match expected element pattern '{}' at '{}'",
1329 node_name,
1330 pat_name,
1331 ctxt.current_path()
1332 ));
1333 return false;
1334 }
1335 }
1336
1337 ctxt.path.push(node_name.clone());
1339
1340 let mut valid = true;
1342 if pattern.children.is_empty() {
1343 let mut child = (*node).children;
1345 while !child.is_null() {
1346 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1347 let child_name = get_node_qname(child);
1348 ctxt.record_error(format!(
1349 "Unexpected child element '{}' in empty element '{}' at '{}'",
1350 child_name,
1351 node_name,
1352 ctxt.current_path()
1353 ));
1354 valid = false;
1355 }
1356 child = (*child).next;
1357 }
1358 } else {
1359 for child_pat in &pattern.children {
1362 valid &= rng_validate_pattern(child_pat, node, schema, ctxt);
1363 }
1364 }
1365
1366 ctxt.path.pop();
1367 valid
1368 }
1369}
1370
1371fn rng_validate_attribute_pattern(
1377 pattern: &RelaxNgPattern,
1378 node: *mut _xmlNode,
1379 schema: &RelaxNgSchema,
1380 ctxt: &mut RelaxNgValidCtxt,
1381) -> bool {
1382 unsafe {
1383 if node.is_null() || (*node).type_ != XML_ELEMENT_NODE as c_int {
1384 return false;
1385 }
1386
1387 let attr_name = match &pattern.name_class {
1389 Some(RelaxNgNameClass::Name(n)) => n.clone(),
1390 Some(RelaxNgNameClass::AnyName) => {
1391 return true;
1396 }
1397 Some(RelaxNgNameClass::NsName(ns)) => {
1398 let mut prop = (*node).properties;
1401 while !prop.is_null() {
1402 let prop_ns = get_node_ns_uri(prop as *mut _xmlNode);
1403 if let Some(ref uri) = prop_ns {
1404 if uri == ns {
1405 if let Some(content) = &pattern.children.first() {
1407 let val = get_node_text(prop as *mut _xmlNode);
1408 let valid = match content.pattern_type {
1409 RelaxNgPatternType::Text => true,
1410 RelaxNgPatternType::Data => {
1411 rng_validate_datatype_value(
1412 content.datatype.as_deref(),
1413 &val,
1414 )
1415 }
1416 RelaxNgPatternType::Value => {
1417 content.value.as_deref() == Some(&val)
1418 }
1419 _ => true,
1420 };
1421 if !valid {
1422 ctxt.record_error(format!(
1423 "Attribute '{}' has invalid value at '{}'",
1424 prop_ns.unwrap_or_default(),
1425 ctxt.current_path()
1426 ));
1427 return false;
1428 }
1429 }
1430 return true;
1431 }
1432 }
1433 prop = (*prop).next;
1434 }
1435 ctxt.record_error(format!(
1438 "Required attribute in namespace '{}' is missing at '{}'",
1439 ns,
1440 ctxt.current_path()
1441 ));
1442 return false;
1443 }
1444 _ => {
1445 return true;
1448 }
1449 };
1450
1451 let attr_value = get_attr(node, &attr_name);
1453
1454 match attr_value {
1455 Some(val) => {
1456 if let Some(content) = pattern.children.first() {
1458 let valid = match content.pattern_type {
1459 RelaxNgPatternType::Text => true,
1460 RelaxNgPatternType::Data => {
1461 rng_validate_datatype_value(content.datatype.as_deref(), &val)
1462 }
1463 RelaxNgPatternType::Value => {
1464 content.value.as_deref() == Some(&val)
1465 }
1466 _ => true,
1467 };
1468 if !valid {
1469 ctxt.record_error(format!(
1470 "Attribute '{}' has invalid value '{}' at '{}'",
1471 attr_name,
1472 val,
1473 ctxt.current_path()
1474 ));
1475 return false;
1476 }
1477 }
1478 true
1479 }
1480 None => {
1481 ctxt.record_error(format!(
1484 "Required attribute '{}' is missing at '{}'",
1485 attr_name,
1486 ctxt.current_path()
1487 ));
1488 false
1489 }
1490 }
1491 }
1492}
1493
1494fn rng_validate_text_pattern(node: *mut _xmlNode, _ctxt: &mut RelaxNgValidCtxt) -> bool {
1500 unsafe {
1501 if node.is_null() {
1502 return false;
1503 }
1504 true
1507 }
1508}
1509
1510fn rng_validate_empty_pattern(node: *mut _xmlNode, ctxt: &mut RelaxNgValidCtxt) -> bool {
1516 unsafe {
1517 if node.is_null() {
1518 return true;
1519 }
1520 let mut child = (*node).children;
1522 while !child.is_null() {
1523 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1524 let child_name = get_node_qname(child);
1525 ctxt.record_error(format!(
1526 "Unexpected child element '{}' in empty content at '{}'",
1527 child_name,
1528 ctxt.current_path()
1529 ));
1530 return false;
1531 }
1532 child = (*child).next;
1533 }
1534 true
1535 }
1536}
1537
1538fn rng_validate_choice_pattern(
1544 pattern: &RelaxNgPattern,
1545 node: *mut _xmlNode,
1546 schema: &RelaxNgSchema,
1547 ctxt: &mut RelaxNgValidCtxt,
1548) -> bool {
1549 unsafe {
1550 if pattern.children.is_empty() {
1551 return false;
1552 }
1553
1554 let mut last_error = String::new();
1556 for child in &pattern.children {
1557 let saved_errors = ctxt.errors.len();
1558 let saved_nb = ctxt.nb_errors;
1559
1560 if rng_validate_pattern(child, node, schema, ctxt) {
1561 return true;
1564 }
1565
1566 if ctxt.errors.len() > saved_errors {
1568 last_error = ctxt.errors.last().unwrap().clone();
1569 }
1570
1571 ctxt.errors.truncate(saved_errors);
1573 ctxt.nb_errors = saved_nb;
1574 }
1575
1576 let node_name = if node.is_null() {
1578 "null".to_string()
1579 } else {
1580 get_node_qname(node)
1581 };
1582 ctxt.record_error(format!(
1583 "No choice pattern matched for '{}' at '{}'. Last error: {}",
1584 node_name,
1585 ctxt.current_path(),
1586 last_error
1587 ));
1588 false
1589 }
1590}
1591
1592fn rng_validate_sequence_pattern(
1598 pattern: &RelaxNgPattern,
1599 node: *mut _xmlNode,
1600 schema: &RelaxNgSchema,
1601 ctxt: &mut RelaxNgValidCtxt,
1602) -> bool {
1603 unsafe {
1604 if node.is_null() {
1605 return pattern.children.is_empty();
1606 }
1607
1608 let mut valid = true;
1609
1610 let mut child_nodes: Vec<*mut _xmlNode> = Vec::new();
1614 let mut child = (*node).children;
1615 while !child.is_null() {
1616 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1617 child_nodes.push(child);
1618 }
1619 child = (*child).next;
1620 }
1621
1622 let mut child_idx = 0;
1625 for child_pat in &pattern.children {
1626 if child_idx >= child_nodes.len() {
1627 match child_pat.pattern_type {
1629 RelaxNgPatternType::Optional | RelaxNgPatternType::ZeroOrMore => {
1630 continue;
1632 }
1633 RelaxNgPatternType::OneOrMore => {
1634 ctxt.record_error(format!(
1635 "Expected at least one matching child for oneOrMore at '{}'",
1636 ctxt.current_path()
1637 ));
1638 valid = false;
1639 continue;
1640 }
1641 _ => {
1642 ctxt.record_error(format!(
1643 "Expected more child elements for sequence at '{}'",
1644 ctxt.current_path()
1645 ));
1646 valid = false;
1647 continue;
1648 }
1649 }
1650 }
1651
1652 let child_node = child_nodes[child_idx];
1653 valid &= rng_validate_pattern(child_pat, child_node, schema, ctxt);
1654 child_idx += 1;
1655 }
1656
1657 if child_idx < child_nodes.len() {
1659 let extra_name = get_node_qname(child_nodes[child_idx]);
1660 ctxt.record_error(format!(
1661 "Unexpected extra element '{}' in sequence at '{}'",
1662 extra_name,
1663 ctxt.current_path()
1664 ));
1665 valid = false;
1666 }
1667
1668 valid
1669 }
1670}
1671
1672fn rng_validate_interleave_pattern(
1678 pattern: &RelaxNgPattern,
1679 node: *mut _xmlNode,
1680 schema: &RelaxNgSchema,
1681 ctxt: &mut RelaxNgValidCtxt,
1682) -> bool {
1683 unsafe {
1684 if node.is_null() {
1685 return pattern.children.is_empty();
1686 }
1687
1688 let mut valid = true;
1692
1693 for child_pat in &pattern.children {
1694 let mut child = (*node).children;
1697 let mut matched = false;
1698
1699 while !child.is_null() {
1700 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1701 let saved_errors = ctxt.errors.len();
1702 let saved_nb = ctxt.nb_errors;
1703
1704 if rng_validate_pattern(child_pat, child, schema, ctxt) {
1705 matched = true;
1706 break;
1707 }
1708
1709 ctxt.errors.truncate(saved_errors);
1711 ctxt.nb_errors = saved_nb;
1712 }
1713 child = (*child).next;
1714 }
1715
1716 if !matched {
1717 match child_pat.pattern_type {
1719 RelaxNgPatternType::Optional | RelaxNgPatternType::ZeroOrMore => {
1720 }
1722 _ => {
1723 let pat_desc = format!("{:?}", child_pat.pattern_type);
1724 ctxt.record_error(format!(
1725 "Interleave pattern '{}' did not match any child at '{}'",
1726 pat_desc,
1727 ctxt.current_path()
1728 ));
1729 valid = false;
1730 }
1731 }
1732 }
1733 }
1734
1735 valid
1736 }
1737}
1738
1739fn rng_validate_zero_or_more(
1745 pattern: &RelaxNgPattern,
1746 node: *mut _xmlNode,
1747 schema: &RelaxNgSchema,
1748 ctxt: &mut RelaxNgValidCtxt,
1749) -> bool {
1750 unsafe {
1751 if node.is_null() || pattern.children.is_empty() {
1752 return true;
1753 }
1754
1755 let child_pat = &pattern.children[0];
1756 let mut valid = true;
1757
1758 let mut child = (*node).children;
1760 while !child.is_null() {
1761 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1762 let saved_errors = ctxt.errors.len();
1763 let saved_nb = ctxt.nb_errors;
1764
1765 if !rng_validate_pattern(child_pat, child, schema, ctxt) {
1766 ctxt.errors.truncate(saved_errors);
1769 ctxt.nb_errors = saved_nb;
1770 break;
1771 }
1772 }
1774 child = (*child).next;
1775 }
1776
1777 valid
1778 }
1779}
1780
1781fn rng_validate_one_or_more(
1787 pattern: &RelaxNgPattern,
1788 node: *mut _xmlNode,
1789 schema: &RelaxNgSchema,
1790 ctxt: &mut RelaxNgValidCtxt,
1791) -> bool {
1792 unsafe {
1793 if node.is_null() || pattern.children.is_empty() {
1794 ctxt.record_error(format!(
1795 "Expected at least one matching element for oneOrMore at '{}'",
1796 ctxt.current_path()
1797 ));
1798 return false;
1799 }
1800
1801 let child_pat = &pattern.children[0];
1802 let mut matched = false;
1803 let mut valid = true;
1804
1805 let mut child = (*node).children;
1807 while !child.is_null() {
1808 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1809 let saved_errors = ctxt.errors.len();
1810 let saved_nb = ctxt.nb_errors;
1811
1812 if rng_validate_pattern(child_pat, child, schema, ctxt) {
1813 matched = true;
1814 } else {
1815 ctxt.errors.truncate(saved_errors);
1817 ctxt.nb_errors = saved_nb;
1818 break;
1819 }
1820 }
1821 child = (*child).next;
1822 }
1823
1824 if !matched {
1825 ctxt.record_error(format!(
1826 "Expected at least one matching element for oneOrMore at '{}'",
1827 ctxt.current_path()
1828 ));
1829 valid = false;
1830 }
1831
1832 valid
1833 }
1834}
1835
1836fn rng_validate_optional_pattern(
1842 pattern: &RelaxNgPattern,
1843 node: *mut _xmlNode,
1844 schema: &RelaxNgSchema,
1845 ctxt: &mut RelaxNgValidCtxt,
1846) -> bool {
1847 unsafe {
1848 if pattern.children.is_empty() {
1849 return true;
1850 }
1851
1852 let child_pat = &pattern.children[0];
1854 let saved_errors = ctxt.errors.len();
1855 let saved_nb = ctxt.nb_errors;
1856
1857 let result = rng_validate_pattern(child_pat, node, schema, ctxt);
1858
1859 if !result {
1860 ctxt.errors.truncate(saved_errors);
1862 ctxt.nb_errors = saved_nb;
1863 }
1864
1865 true }
1867}
1868
1869fn rng_validate_list_pattern(
1875 pattern: &RelaxNgPattern,
1876 node: *mut _xmlNode,
1877 schema: &RelaxNgSchema,
1878 ctxt: &mut RelaxNgValidCtxt,
1879) -> bool {
1880 unsafe {
1881 if node.is_null() {
1882 return pattern.children.is_empty();
1883 }
1884
1885 let text = get_node_text(node);
1887 if text.trim().is_empty() {
1888 return true;
1889 }
1890
1891 let tokens: Vec<&str> = text.split_whitespace().collect();
1892 let mut valid = true;
1893
1894 for token in &tokens {
1895 if token.is_empty() {
1898 valid = false;
1899 break;
1900 }
1901 }
1902
1903 valid
1904 }
1905}
1906
1907fn rng_validate_group_pattern(
1913 pattern: &RelaxNgPattern,
1914 node: *mut _xmlNode,
1915 schema: &RelaxNgSchema,
1916 ctxt: &mut RelaxNgValidCtxt,
1917) -> bool {
1918 unsafe {
1919 rng_validate_sequence_pattern(pattern, node, schema, ctxt)
1921 }
1922}
1923
1924fn rng_validate_data_pattern(
1930 pattern: &RelaxNgPattern,
1931 node: *mut _xmlNode,
1932 ctxt: &mut RelaxNgValidCtxt,
1933) -> bool {
1934 unsafe {
1935 if node.is_null() {
1936 return false;
1937 }
1938
1939 let text = get_node_text(node);
1940 let datatype = pattern.datatype.as_deref();
1941
1942 if !rng_validate_datatype_value(datatype, &text) {
1943 ctxt.record_error(format!(
1944 "Value '{}' does not match datatype '{:?}' at '{}'",
1945 text,
1946 datatype,
1947 ctxt.current_path()
1948 ));
1949 return false;
1950 }
1951
1952 true
1953 }
1954}
1955
1956fn rng_validate_value_pattern(
1962 pattern: &RelaxNgPattern,
1963 node: *mut _xmlNode,
1964 ctxt: &mut RelaxNgValidCtxt,
1965) -> bool {
1966 unsafe {
1967 if node.is_null() {
1968 return false;
1969 }
1970
1971 let text = get_node_text(node).trim().to_string();
1972 let expected = pattern.value.as_deref().unwrap_or("");
1973
1974 if text != expected {
1975 ctxt.record_error(format!(
1976 "Value '{}' does not match expected value '{}' at '{}'",
1977 text,
1978 expected,
1979 ctxt.current_path()
1980 ));
1981 return false;
1982 }
1983
1984 true
1985 }
1986}
1987
1988fn rng_validate_ref_pattern(
1994 pattern: &RelaxNgPattern,
1995 node: *mut _xmlNode,
1996 schema: &RelaxNgSchema,
1997 ctxt: &mut RelaxNgValidCtxt,
1998) -> bool {
1999 unsafe {
2000 let ref_name = pattern.name.as_deref().unwrap_or("");
2001
2002 if ref_name.is_empty() {
2003 ctxt.record_error("Ref pattern has no name".to_string());
2004 return false;
2005 }
2006
2007 match schema.grammar.lookup(ref_name) {
2009 Some(def_pattern) => {
2010 rng_validate_pattern(def_pattern, node, schema, ctxt)
2012 }
2013 None => {
2014 ctxt.record_error(format!(
2015 "Undefined reference '{}' at '{}'",
2016 ref_name,
2017 ctxt.current_path()
2018 ));
2019 false
2020 }
2021 }
2022 }
2023}
2024
2025fn rng_validate_datatype_value(datatype: Option<&str>, value: &str) -> bool {
2034 let dt = match datatype {
2035 Some(d) => d,
2036 None => return true, };
2038
2039 match dt {
2040 "string" | "token" => true,
2041 "boolean" => {
2042 matches!(value, "true" | "false" | "1" | "0")
2043 }
2044 "integer" | "int" | "short" | "byte" | "long" => {
2045 if value.is_empty() {
2046 return false;
2047 }
2048 let trimmed = if value.starts_with('+') || value.starts_with('-') {
2049 &value[1..]
2050 } else {
2051 value
2052 };
2053 !trimmed.is_empty() && trimmed.chars().all(|c| c.is_ascii_digit())
2054 }
2055 "decimal" | "double" | "float" => {
2056 if value.is_empty() {
2057 return false;
2058 }
2059 if matches!(dt, "float" | "double")
2061 && matches!(value, "INF" | "-INF" | "NaN")
2062 {
2063 return true;
2064 }
2065 value.parse::<f64>().is_ok()
2066 }
2067 "NCName" | "Name" | "ID" | "IDREF" | "NMTOKEN" => {
2068 !value.is_empty() && !value.starts_with(|c: char| c.is_ascii_digit())
2069 }
2070 "anyURI" => {
2071 !value.is_empty() && !value.contains(char::is_whitespace)
2073 }
2074 "QName" => {
2075 if value.is_empty() {
2076 return false;
2077 }
2078 if let Some(pos) = value.find(':') {
2079 pos > 0 && pos < value.len() - 1
2080 } else {
2081 true
2082 }
2083 }
2084 _ => {
2085 true
2088 }
2089 }
2090}
2091
2092pub fn rng_parse_schema(xml_doc: &str) -> Result<RelaxNgSchema, String> {
2100 rng_parse(xml_doc)
2101}
2102
2103pub unsafe fn rng_parse_schema_doc(doc: *mut _xmlDoc) -> Result<RelaxNgSchema, String> {
2109 rng_parse_doc(doc)
2110}
2111
2112pub unsafe fn rng_validate_doc_schema(
2120 schema: &RelaxNgSchema,
2121 doc: *mut _xmlDoc,
2122 ctxt: &mut RelaxNgValidCtxt,
2123) -> bool {
2124 rng_validate_doc(schema, doc, ctxt)
2125}
2126
2127#[no_mangle]
2146pub unsafe extern "C" fn xmlRelaxNGNewParserCtxt(url: *const c_char) -> *mut c_void {
2147 if url.is_null() {
2148 let ctxt = allocator::xmlMallocZero(size_of::<RelaxNgSchema>() as usize);
2149 return ctxt;
2150 }
2151
2152 let url_str = unsafe {
2153 let mut len = 0;
2154 while *url.add(len) != 0 {
2155 len += 1;
2156 }
2157 let slice = std::slice::from_raw_parts(url as *const u8, len);
2158 String::from_utf8_lossy(slice).to_string()
2159 };
2160
2161 if !url_str.is_empty() {
2163 let url_c = std::ffi::CString::new(url_str.clone()).ok();
2164 if let Some(c) = url_c {
2165 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
2166 if !doc.is_null() {
2167 let result = rng_parse_doc(doc);
2168 crate::abi::exports_xml2::xmlFreeDoc(doc);
2169 if let Ok(schema) = result {
2170 let schema_box = Box::new(schema);
2171 return Box::into_raw(schema_box) as *mut c_void;
2172 }
2173 }
2174 }
2175 }
2176
2177 let ctxt = allocator::xmlMallocZero(size_of::<RelaxNgSchema>() as usize);
2179 ctxt
2180}
2181
2182#[no_mangle]
2194pub unsafe extern "C" fn xmlRelaxNGNewMemParserCtxt(
2195 buffer: *const c_char,
2196 size: c_int,
2197) -> *mut c_void {
2198 if buffer.is_null() || size <= 0 {
2199 return ptr::null_mut();
2200 }
2201
2202 let buf_slice = unsafe { std::slice::from_raw_parts(buffer as *const u8, size as usize) };
2204 let xml_str = String::from_utf8_lossy(buf_slice).to_string();
2205
2206 match rng_parse(&xml_str) {
2207 Ok(schema) => {
2208 let schema_box = Box::new(schema);
2209 Box::into_raw(schema_box) as *mut c_void
2210 }
2211 Err(_) => ptr::null_mut(),
2212 }
2213}
2214
2215#[no_mangle]
2227pub unsafe extern "C" fn xmlRelaxNGParse(ctxt: *mut c_void) -> *mut c_void {
2228 if ctxt.is_null() {
2229 return ptr::null_mut();
2230 }
2231
2232 ctxt
2235}
2236
2237#[no_mangle]
2249pub unsafe extern "C" fn xmlRelaxNGFree(schema: *mut c_void) {
2250 if schema.is_null() {
2251 return;
2252 }
2253 unsafe {
2255 let _ = Box::from_raw(schema as *mut RelaxNgSchema);
2256 }
2257}
2258
2259#[no_mangle]
2271pub unsafe extern "C" fn xmlRelaxNGFreeParserCtxt(ctxt: *mut c_void) {
2272 if ctxt.is_null() {
2273 return;
2274 }
2275 unsafe {
2277 let _ = Box::from_raw(ctxt as *mut RelaxNgSchema);
2278 }
2279}
2280
2281#[no_mangle]
2293pub unsafe extern "C" fn xmlRelaxNGNewValidCtxt(schema: *mut c_void) -> *mut c_void {
2294 let mut ctxt = RelaxNgValidCtxt::new();
2295
2296 if !schema.is_null() {
2297 unsafe {
2299 let schema_ref = &*(schema as *const RelaxNgSchema);
2300 ctxt.schema = Some(schema_ref.clone());
2301 }
2302 }
2303
2304 let boxed = Box::new(ctxt);
2305 Box::into_raw(boxed) as *mut c_void
2306}
2307
2308#[no_mangle]
2320pub unsafe extern "C" fn xmlRelaxNGFreeValidCtxt(ctxt: *mut c_void) {
2321 if ctxt.is_null() {
2322 return;
2323 }
2324 unsafe {
2326 let _ = Box::from_raw(ctxt as *mut RelaxNgValidCtxt);
2327 }
2328}
2329
2330#[no_mangle]
2345pub unsafe extern "C" fn xmlRelaxNGValidateDoc(
2346 ctxt: *mut c_void,
2347 doc: *mut _xmlDoc,
2348) -> c_int {
2349 if ctxt.is_null() || doc.is_null() {
2350 return -1;
2351 }
2352
2353 unsafe {
2354 let valid_ctxt = &mut *(ctxt as *mut RelaxNgValidCtxt);
2355 let schema = match &valid_ctxt.schema {
2356 Some(s) => s,
2357 None => return -1,
2358 };
2359
2360 let mut temp_ctxt = RelaxNgValidCtxt::new();
2361
2362 let valid = rng_validate_doc(schema, doc, &mut temp_ctxt);
2363
2364 if valid {
2365 0
2366 } else {
2367 valid_ctxt.errors = temp_ctxt.errors;
2368 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2369 temp_ctxt.nb_errors
2370 }
2371 }
2372}
2373
2374#[no_mangle]
2392pub unsafe extern "C" fn xmlRelaxNGValidateFullElement(
2393 ctxt: *mut c_void,
2394 doc: *mut _xmlDoc,
2395 elem: *mut _xmlNode,
2396) -> c_int {
2397 if ctxt.is_null() || doc.is_null() || elem.is_null() {
2398 return -1;
2399 }
2400
2401 unsafe {
2402 let valid_ctxt = &mut *(ctxt as *mut RelaxNgValidCtxt);
2403 let schema = match &valid_ctxt.schema {
2404 Some(s) => s,
2405 None => return -1,
2406 };
2407
2408 let mut temp_ctxt = RelaxNgValidCtxt::new();
2409 temp_ctxt.path = valid_ctxt.path.clone();
2410
2411 let start_pattern = match &schema.grammar.start {
2412 Some(p) => p,
2413 None => return -1,
2414 };
2415
2416 let valid = rng_validate_pattern(start_pattern, elem, schema, &mut temp_ctxt);
2417
2418 if valid {
2419 0
2420 } else {
2421 valid_ctxt.errors = temp_ctxt.errors;
2422 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2423 temp_ctxt.nb_errors
2424 }
2425 }
2426}
2427
2428#[cfg(test)]
2433mod tests {
2434 use super::*;
2435
2436 #[test]
2439 fn test_name_class_name() {
2440 let nc = RelaxNgNameClass::Name("foo".to_string());
2441 assert!(nc.matches("foo", None));
2442 assert!(!nc.matches("bar", None));
2443 assert!(!nc.matches("FOO", None));
2444 }
2445
2446 #[test]
2447 fn test_name_class_any_name() {
2448 let nc = RelaxNgNameClass::AnyName;
2449 assert!(nc.matches("foo", None));
2450 assert!(nc.matches("bar", None));
2451 assert!(nc.matches("anything", Some("urn:ns")));
2452 }
2453
2454 #[test]
2455 fn test_name_class_ns_name() {
2456 let nc = RelaxNgNameClass::NsName("urn:example".to_string());
2457 assert!(nc.matches("foo", Some("urn:example")));
2458 assert!(!nc.matches("foo", Some("urn:other")));
2459 assert!(!nc.matches("foo", None));
2460 }
2461
2462 #[test]
2463 fn test_name_class_choice() {
2464 let nc = RelaxNgNameClass::Choice(vec![
2465 RelaxNgNameClass::Name("a".to_string()),
2466 RelaxNgNameClass::Name("b".to_string()),
2467 ]);
2468 assert!(nc.matches("a", None));
2469 assert!(nc.matches("b", None));
2470 assert!(!nc.matches("c", None));
2471 }
2472
2473 #[test]
2474 fn test_name_class_except() {
2475 let nc = RelaxNgNameClass::Except(
2476 Box::new(RelaxNgNameClass::AnyName),
2477 Box::new(RelaxNgNameClass::Name("bad".to_string())),
2478 );
2479 assert!(nc.matches("good", None));
2480 assert!(!nc.matches("bad", None));
2481 }
2482
2483 #[test]
2486 fn test_parse_simple_element_schema() {
2487 let schema_xml = r#"<?xml version="1.0"?>
2488<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2489 <text/>
2490</element>"#;
2491
2492 let result = rng_parse(schema_xml);
2493 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2494 let schema = result.unwrap();
2495 assert!(schema.grammar.start.is_some());
2496 if let Some(ref start) = schema.grammar.start {
2497 assert_eq!(start.pattern_type, RelaxNgPatternType::Element);
2498 assert_eq!(start.name.as_deref(), Some("root"));
2499 }
2500 }
2501
2502 #[test]
2503 fn test_parse_grammar_schema() {
2504 let schema_xml = r#"<?xml version="1.0"?>
2505<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2506 <start>
2507 <element name="root">
2508 <text/>
2509 </element>
2510 </start>
2511</grammar>"#;
2512
2513 let result = rng_parse(schema_xml);
2514 assert!(result.is_ok(), "Failed to parse grammar: {:?}", result.err());
2515 let schema = result.unwrap();
2516 assert!(schema.grammar.start.is_some());
2517 }
2518
2519 #[test]
2520 fn test_parse_with_define_and_ref() {
2521 let schema_xml = r#"<?xml version="1.0"?>
2522<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2523 <define name="textBlock">
2524 <text/>
2525 </define>
2526 <start>
2527 <element name="doc">
2528 <ref name="textBlock"/>
2529 </element>
2530 </start>
2531</grammar>"#;
2532
2533 let result = rng_parse(schema_xml);
2534 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
2535 let schema = result.unwrap();
2536 assert_eq!(schema.grammar.defines.len(), 1);
2537 assert_eq!(schema.grammar.defines[0].name, "textBlock");
2538 assert!(schema.grammar.start.is_some());
2539 }
2540
2541 #[test]
2542 fn test_parse_choice_schema() {
2543 let schema_xml = r#"<?xml version="1.0"?>
2544<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2545 <start>
2546 <choice>
2547 <element name="a">
2548 <text/>
2549 </element>
2550 <element name="b">
2551 <text/>
2552 </element>
2553 </choice>
2554 </start>
2555</grammar>"#;
2556
2557 let result = rng_parse(schema_xml);
2558 assert!(result.is_ok(), "Failed to parse choice: {:?}", result.err());
2559 }
2560
2561 #[test]
2562 fn test_parse_attribute_schema() {
2563 let schema_xml = r#"<?xml version="1.0"?>
2564<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2565 <attribute name="attr1">
2566 <text/>
2567 </attribute>
2568 <text/>
2569</element>"#;
2570
2571 let result = rng_parse(schema_xml);
2572 assert!(result.is_ok(), "Failed to parse attribute: {:?}", result.err());
2573 }
2574
2575 #[test]
2576 fn test_parse_empty_document_fails() {
2577 let result = rng_parse("");
2578 assert!(result.is_err());
2579 }
2580
2581 #[test]
2582 fn test_parse_invalid_xml_fails() {
2583 let result = rng_parse("not valid xml <<<");
2584 assert!(result.is_err());
2585 }
2586
2587 #[test]
2590 fn test_validate_simple_element() {
2591 let schema_xml = r#"<?xml version="1.0"?>
2592<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2593 <text/>
2594</element>"#;
2595
2596 let doc_xml = r#"<?xml version="1.0"?>
2597<root>Hello</root>"#;
2598
2599 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2600
2601 let doc = unsafe {
2602 crate::abi::exports_xml2::xmlReadMemory(
2603 doc_xml.as_ptr() as *const c_char,
2604 doc_xml.len() as c_int,
2605 b"test.xml\0".as_ptr() as *const c_char,
2606 ptr::null(),
2607 0,
2608 )
2609 };
2610 assert!(!doc.is_null(), "Failed to parse document");
2611
2612 let mut ctxt = RelaxNgValidCtxt::new();
2613 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2614 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2615
2616 assert!(valid, "Validation failed: {:?}", ctxt.errors);
2617 }
2618
2619 #[test]
2620 fn test_validate_element_mismatch() {
2621 let schema_xml = r#"<?xml version="1.0"?>
2622<element name="expected" xmlns="http://relaxng.org/ns/structure/1.0">
2623 <text/>
2624</element>"#;
2625
2626 let doc_xml = r#"<?xml version="1.0"?>
2627<actual>Content</actual>"#;
2628
2629 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2630
2631 let doc = unsafe {
2632 crate::abi::exports_xml2::xmlReadMemory(
2633 doc_xml.as_ptr() as *const c_char,
2634 doc_xml.len() as c_int,
2635 b"test.xml\0".as_ptr() as *const c_char,
2636 ptr::null(),
2637 0,
2638 )
2639 };
2640 assert!(!doc.is_null());
2641
2642 let mut ctxt = RelaxNgValidCtxt::new();
2643 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2644 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2645
2646 assert!(!valid, "Validation should have failed");
2647 assert!(ctxt.nb_errors > 0);
2648 }
2649
2650 #[test]
2651 fn test_validate_with_attribute() {
2652 let schema_xml = r#"<?xml version="1.0"?>
2653<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2654 <attribute name="id">
2655 <text/>
2656 </attribute>
2657 <text/>
2658</element>"#;
2659
2660 let doc_xml = r#"<?xml version="1.0"?>
2661<root id="x1">Content</root>"#;
2662
2663 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2664
2665 let doc = unsafe {
2666 crate::abi::exports_xml2::xmlReadMemory(
2667 doc_xml.as_ptr() as *const c_char,
2668 doc_xml.len() as c_int,
2669 b"test.xml\0".as_ptr() as *const c_char,
2670 ptr::null(),
2671 0,
2672 )
2673 };
2674 assert!(!doc.is_null());
2675
2676 let mut ctxt = RelaxNgValidCtxt::new();
2677 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2678 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2679
2680 assert!(valid, "Validation failed: {:?}", ctxt.errors);
2681 }
2682
2683 #[test]
2684 fn test_validate_missing_attribute() {
2685 let schema_xml = r#"<?xml version="1.0"?>
2686<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2687 <attribute name="required">
2688 <text/>
2689 </attribute>
2690 <text/>
2691</element>"#;
2692
2693 let doc_xml = r#"<?xml version="1.0"?>
2694<root>Content</root>"#;
2695
2696 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2697
2698 let doc = unsafe {
2699 crate::abi::exports_xml2::xmlReadMemory(
2700 doc_xml.as_ptr() as *const c_char,
2701 doc_xml.len() as c_int,
2702 b"test.xml\0".as_ptr() as *const c_char,
2703 ptr::null(),
2704 0,
2705 )
2706 };
2707 assert!(!doc.is_null());
2708
2709 let mut ctxt = RelaxNgValidCtxt::new();
2710 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2711 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2712
2713 assert!(!valid, "Validation should have failed for missing attribute");
2714 }
2715
2716 #[test]
2717 fn test_validate_with_choice() {
2718 let schema_xml = r#"<?xml version="1.0"?>
2719<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2720 <start>
2721 <choice>
2722 <element name="a">
2723 <text/>
2724 </element>
2725 <element name="b">
2726 <text/>
2727 </element>
2728 </choice>
2729 </start>
2730</grammar>"#;
2731
2732 let doc_xml = r#"<?xml version="1.0"?>
2733<a>First choice</a>"#;
2734
2735 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2736
2737 let doc = unsafe {
2738 crate::abi::exports_xml2::xmlReadMemory(
2739 doc_xml.as_ptr() as *const c_char,
2740 doc_xml.len() as c_int,
2741 b"test.xml\0".as_ptr() as *const c_char,
2742 ptr::null(),
2743 0,
2744 )
2745 };
2746 assert!(!doc.is_null());
2747
2748 let mut ctxt = RelaxNgValidCtxt::new();
2749 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2750 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2751
2752 assert!(valid, "Choice validation failed: {:?}", ctxt.errors);
2753 }
2754
2755 #[test]
2756 fn test_validate_choice_no_match() {
2757 let schema_xml = r#"<?xml version="1.0"?>
2758<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2759 <start>
2760 <choice>
2761 <element name="a">
2762 <text/>
2763 </element>
2764 <element name="b">
2765 <text/>
2766 </element>
2767 </choice>
2768 </start>
2769</grammar>"#;
2770
2771 let doc_xml = r#"<?xml version="1.0"?>
2772<c>Neither choice</c>"#;
2773
2774 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2775
2776 let doc = unsafe {
2777 crate::abi::exports_xml2::xmlReadMemory(
2778 doc_xml.as_ptr() as *const c_char,
2779 doc_xml.len() as c_int,
2780 b"test.xml\0".as_ptr() as *const c_char,
2781 ptr::null(),
2782 0,
2783 )
2784 };
2785 assert!(!doc.is_null());
2786
2787 let mut ctxt = RelaxNgValidCtxt::new();
2788 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2789 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2790
2791 assert!(!valid, "Validation should have failed for no matching choice");
2792 }
2793
2794 #[test]
2795 fn test_validate_grammar_with_ref() {
2796 let schema_xml = r#"<?xml version="1.0"?>
2797<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2798 <define name="para">
2799 <element name="p">
2800 <text/>
2801 </element>
2802 </define>
2803 <start>
2804 <element name="doc">
2805 <zeroOrMore>
2806 <ref name="para"/>
2807 </zeroOrMore>
2808 </element>
2809 </start>
2810</grammar>"#;
2811
2812 let doc_xml = r#"<?xml version="1.0"?>
2813<doc><p>First</p><p>Second</p></doc>"#;
2814
2815 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2816
2817 let doc = unsafe {
2818 crate::abi::exports_xml2::xmlReadMemory(
2819 doc_xml.as_ptr() as *const c_char,
2820 doc_xml.len() as c_int,
2821 b"test.xml\0".as_ptr() as *const c_char,
2822 ptr::null(),
2823 0,
2824 )
2825 };
2826 assert!(!doc.is_null());
2827
2828 let mut ctxt = RelaxNgValidCtxt::new();
2829 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2830 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2831
2832 assert!(valid, "Ref validation failed: {:?}", ctxt.errors);
2833 }
2834
2835 #[test]
2836 fn test_validate_zero_or_more() {
2837 let schema_xml = r#"<?xml version="1.0"?>
2838<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2839 <zeroOrMore>
2840 <element name="item">
2841 <text/>
2842 </element>
2843 </zeroOrMore>
2844</element>"#;
2845
2846 let doc_xml = r#"<?xml version="1.0"?>
2847<root><item>A</item><item>B</item></root>"#;
2848
2849 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2850
2851 let doc = unsafe {
2852 crate::abi::exports_xml2::xmlReadMemory(
2853 doc_xml.as_ptr() as *const c_char,
2854 doc_xml.len() as c_int,
2855 b"test.xml\0".as_ptr() as *const c_char,
2856 ptr::null(),
2857 0,
2858 )
2859 };
2860 assert!(!doc.is_null());
2861
2862 let mut ctxt = RelaxNgValidCtxt::new();
2863 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2864 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2865
2866 assert!(valid, "zeroOrMore validation failed: {:?}", ctxt.errors);
2867 }
2868
2869 #[test]
2870 fn test_validate_zero_or_more_empty() {
2871 let schema_xml = r#"<?xml version="1.0"?>
2872<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2873 <zeroOrMore>
2874 <element name="item">
2875 <text/>
2876 </element>
2877 </zeroOrMore>
2878</element>"#;
2879
2880 let doc_xml = r#"<?xml version="1.0"?>
2881<root></root>"#;
2882
2883 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2884
2885 let doc = unsafe {
2886 crate::abi::exports_xml2::xmlReadMemory(
2887 doc_xml.as_ptr() as *const c_char,
2888 doc_xml.len() as c_int,
2889 b"test.xml\0".as_ptr() as *const c_char,
2890 ptr::null(),
2891 0,
2892 )
2893 };
2894 assert!(!doc.is_null());
2895
2896 let mut ctxt = RelaxNgValidCtxt::new();
2897 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2898 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2899
2900 assert!(valid, "Empty zeroOrMore should be valid");
2901 }
2902
2903 #[test]
2904 fn test_validate_one_or_more() {
2905 let schema_xml = r#"<?xml version="1.0"?>
2906<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2907 <oneOrMore>
2908 <element name="item">
2909 <text/>
2910 </element>
2911 </oneOrMore>
2912</element>"#;
2913
2914 let doc_xml = r#"<?xml version="1.0"?>
2915<root><item>Single</item></root>"#;
2916
2917 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2918
2919 let doc = unsafe {
2920 crate::abi::exports_xml2::xmlReadMemory(
2921 doc_xml.as_ptr() as *const c_char,
2922 doc_xml.len() as c_int,
2923 b"test.xml\0".as_ptr() as *const c_char,
2924 ptr::null(),
2925 0,
2926 )
2927 };
2928 assert!(!doc.is_null());
2929
2930 let mut ctxt = RelaxNgValidCtxt::new();
2931 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2932 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2933
2934 assert!(valid, "oneOrMore validation failed: {:?}", ctxt.errors);
2935 }
2936
2937 #[test]
2938 fn test_validate_optional_present() {
2939 let schema_xml = r#"<?xml version="1.0"?>
2940<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2941 <optional>
2942 <element name="opt">
2943 <text/>
2944 </element>
2945 </optional>
2946 <text/>
2947</element>"#;
2948
2949 let doc_xml = r#"<?xml version="1.0"?>
2950<root><opt>present</opt>text</root>"#;
2951
2952 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2953
2954 let doc = unsafe {
2955 crate::abi::exports_xml2::xmlReadMemory(
2956 doc_xml.as_ptr() as *const c_char,
2957 doc_xml.len() as c_int,
2958 b"test.xml\0".as_ptr() as *const c_char,
2959 ptr::null(),
2960 0,
2961 )
2962 };
2963 assert!(!doc.is_null());
2964
2965 let mut ctxt = RelaxNgValidCtxt::new();
2966 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
2967 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2968
2969 assert!(valid, "Optional present validation failed: {:?}", ctxt.errors);
2970 }
2971
2972 #[test]
2973 fn test_validate_optional_absent() {
2974 let schema_xml = r#"<?xml version="1.0"?>
2975<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
2976 <optional>
2977 <element name="opt">
2978 <text/>
2979 </element>
2980 </optional>
2981 <text/>
2982</element>"#;
2983
2984 let doc_xml = r#"<?xml version="1.0"?>
2985<root>text only</root>"#;
2986
2987 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
2988
2989 let doc = unsafe {
2990 crate::abi::exports_xml2::xmlReadMemory(
2991 doc_xml.as_ptr() as *const c_char,
2992 doc_xml.len() as c_int,
2993 b"test.xml\0".as_ptr() as *const c_char,
2994 ptr::null(),
2995 0,
2996 )
2997 };
2998 assert!(!doc.is_null());
2999
3000 let mut ctxt = RelaxNgValidCtxt::new();
3001 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3002 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3003
3004 assert!(valid, "Optional absent validation failed: {:?}", ctxt.errors);
3005 }
3006
3007 #[test]
3008 fn test_validate_sequence() {
3009 let schema_xml = r#"<?xml version="1.0"?>
3010<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3011 <start>
3012 <element name="root">
3013 <sequence>
3014 <element name="first">
3015 <text/>
3016 </element>
3017 <element name="second">
3018 <text/>
3019 </element>
3020 </sequence>
3021 </element>
3022 </start>
3023</grammar>"#;
3024
3025 let doc_xml = r#"<?xml version="1.0"?>
3026<root><first>First</first><second>Second</second></root>"#;
3027
3028 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3029
3030 let doc = unsafe {
3031 crate::abi::exports_xml2::xmlReadMemory(
3032 doc_xml.as_ptr() as *const c_char,
3033 doc_xml.len() as c_int,
3034 b"test.xml\0".as_ptr() as *const c_char,
3035 ptr::null(),
3036 0,
3037 )
3038 };
3039 assert!(!doc.is_null());
3040
3041 let mut ctxt = RelaxNgValidCtxt::new();
3042 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3043 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3044
3045 assert!(valid, "Sequence validation failed: {:?}", ctxt.errors);
3046 }
3047
3048 #[test]
3049 fn test_validate_data_pattern() {
3050 let schema_xml = r#"<?xml version="1.0"?>
3051<element name="age" xmlns="http://relaxng.org/ns/structure/1.0">
3052 <data type="integer"/>
3053</element>"#;
3054
3055 let doc_xml = r#"<?xml version="1.0"?>
3056<age>25</age>"#;
3057
3058 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3059
3060 let doc = unsafe {
3061 crate::abi::exports_xml2::xmlReadMemory(
3062 doc_xml.as_ptr() as *const c_char,
3063 doc_xml.len() as c_int,
3064 b"test.xml\0".as_ptr() as *const c_char,
3065 ptr::null(),
3066 0,
3067 )
3068 };
3069 assert!(!doc.is_null());
3070
3071 let mut ctxt = RelaxNgValidCtxt::new();
3072 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3073 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3074
3075 assert!(valid, "Data pattern validation failed: {:?}", ctxt.errors);
3076 }
3077
3078 #[test]
3079 fn test_validate_data_pattern_invalid() {
3080 let schema_xml = r#"<?xml version="1.0"?>
3081<element name="age" xmlns="http://relaxng.org/ns/structure/1.0">
3082 <data type="integer"/>
3083</element>"#;
3084
3085 let doc_xml = r#"<?xml version="1.0"?>
3086<age>not-a-number</age>"#;
3087
3088 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3089
3090 let doc = unsafe {
3091 crate::abi::exports_xml2::xmlReadMemory(
3092 doc_xml.as_ptr() as *const c_char,
3093 doc_xml.len() as c_int,
3094 b"test.xml\0".as_ptr() as *const c_char,
3095 ptr::null(),
3096 0,
3097 )
3098 };
3099 assert!(!doc.is_null());
3100
3101 let mut ctxt = RelaxNgValidCtxt::new();
3102 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3103 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3104
3105 assert!(!valid, "Validation should have failed for invalid integer");
3106 }
3107
3108 #[test]
3109 fn test_validate_value_pattern() {
3110 let schema_xml = r#"<?xml version="1.0"?>
3111<element name="status" xmlns="http://relaxng.org/ns/structure/1.0">
3112 <value>active</value>
3113</element>"#;
3114
3115 let doc_xml = r#"<?xml version="1.0"?>
3116<status>active</status>"#;
3117
3118 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3119
3120 let doc = unsafe {
3121 crate::abi::exports_xml2::xmlReadMemory(
3122 doc_xml.as_ptr() as *const c_char,
3123 doc_xml.len() as c_int,
3124 b"test.xml\0".as_ptr() as *const c_char,
3125 ptr::null(),
3126 0,
3127 )
3128 };
3129 assert!(!doc.is_null());
3130
3131 let mut ctxt = RelaxNgValidCtxt::new();
3132 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3133 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3134
3135 assert!(valid, "Value pattern validation failed: {:?}", ctxt.errors);
3136 }
3137
3138 #[test]
3139 fn test_validate_value_pattern_mismatch() {
3140 let schema_xml = r#"<?xml version="1.0"?>
3141<element name="status" xmlns="http://relaxng.org/ns/structure/1.0">
3142 <value>active</value>
3143</element>"#;
3144
3145 let doc_xml = r#"<?xml version="1.0"?>
3146<status>inactive</status>"#;
3147
3148 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3149
3150 let doc = unsafe {
3151 crate::abi::exports_xml2::xmlReadMemory(
3152 doc_xml.as_ptr() as *const c_char,
3153 doc_xml.len() as c_int,
3154 b"test.xml\0".as_ptr() as *const c_char,
3155 ptr::null(),
3156 0,
3157 )
3158 };
3159 assert!(!doc.is_null());
3160
3161 let mut ctxt = RelaxNgValidCtxt::new();
3162 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3163 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3164
3165 assert!(!valid, "Validation should have failed for value mismatch");
3166 }
3167
3168 #[test]
3169 fn test_validate_not_allowed() {
3170 let schema_xml = r#"<?xml version="1.0"?>
3171<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3172 <notAllowed/>
3173</element>"#;
3174
3175 let doc_xml = r#"<?xml version="1.0"?>
3176<root>should not be allowed</root>"#;
3177
3178 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3179
3180 let doc = unsafe {
3181 crate::abi::exports_xml2::xmlReadMemory(
3182 doc_xml.as_ptr() as *const c_char,
3183 doc_xml.len() as c_int,
3184 b"test.xml\0".as_ptr() as *const c_char,
3185 ptr::null(),
3186 0,
3187 )
3188 };
3189 assert!(!doc.is_null());
3190
3191 let mut ctxt = RelaxNgValidCtxt::new();
3192 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3193 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3194
3195 assert!(!valid, "notAllowed should cause validation failure");
3196 }
3197
3198 #[test]
3199 fn test_validate_interleave() {
3200 let schema_xml = r#"<?xml version="1.0"?>
3201<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3202 <start>
3203 <element name="root">
3204 <interleave>
3205 <element name="a">
3206 <text/>
3207 </element>
3208 <element name="b">
3209 <text/>
3210 </element>
3211 </interleave>
3212 </element>
3213 </start>
3214</grammar>"#;
3215
3216 let doc_xml = r#"<?xml version="1.0"?>
3217<root><a>A</a><b>B</b></root>"#;
3218
3219 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3220
3221 let doc = unsafe {
3222 crate::abi::exports_xml2::xmlReadMemory(
3223 doc_xml.as_ptr() as *const c_char,
3224 doc_xml.len() as c_int,
3225 b"test.xml\0".as_ptr() as *const c_char,
3226 ptr::null(),
3227 0,
3228 )
3229 };
3230 assert!(!doc.is_null());
3231
3232 let mut ctxt = RelaxNgValidCtxt::new();
3233 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3234 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3235
3236 assert!(valid, "Interleave validation failed: {:?}", ctxt.errors);
3237 }
3238
3239 #[test]
3240 fn test_validate_empty_element() {
3241 let schema_xml = r#"<?xml version="1.0"?>
3242<element name="br" xmlns="http://relaxng.org/ns/structure/1.0">
3243 <empty/>
3244</element>"#;
3245
3246 let doc_xml = r#"<?xml version="1.0"?>
3247<br/>"#;
3248
3249 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3250
3251 let doc = unsafe {
3252 crate::abi::exports_xml2::xmlReadMemory(
3253 doc_xml.as_ptr() as *const c_char,
3254 doc_xml.len() as c_int,
3255 b"test.xml\0".as_ptr() as *const c_char,
3256 ptr::null(),
3257 0,
3258 )
3259 };
3260 assert!(!doc.is_null());
3261
3262 let mut ctxt = RelaxNgValidCtxt::new();
3263 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3264 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3265
3266 assert!(valid, "Empty element validation failed: {:?}", ctxt.errors);
3267 }
3268
3269 #[test]
3272 fn test_validate_datatype_string() {
3273 assert!(rng_validate_datatype_value(Some("string"), "hello"));
3274 assert!(rng_validate_datatype_value(Some("string"), ""));
3275 }
3276
3277 #[test]
3278 fn test_validate_datatype_boolean() {
3279 assert!(rng_validate_datatype_value(Some("boolean"), "true"));
3280 assert!(rng_validate_datatype_value(Some("boolean"), "false"));
3281 assert!(rng_validate_datatype_value(Some("boolean"), "1"));
3282 assert!(rng_validate_datatype_value(Some("boolean"), "0"));
3283 assert!(!rng_validate_datatype_value(Some("boolean"), "yes"));
3284 assert!(!rng_validate_datatype_value(Some("boolean"), "no"));
3285 }
3286
3287 #[test]
3288 fn test_validate_datatype_integer() {
3289 assert!(rng_validate_datatype_value(Some("integer"), "42"));
3290 assert!(rng_validate_datatype_value(Some("integer"), "-42"));
3291 assert!(rng_validate_datatype_value(Some("integer"), "+42"));
3292 assert!(!rng_validate_datatype_value(Some("integer"), "12.5"));
3293 assert!(!rng_validate_datatype_value(Some("integer"), "abc"));
3294 assert!(!rng_validate_datatype_value(Some("integer"), ""));
3295 }
3296
3297 #[test]
3298 fn test_validate_datatype_decimal() {
3299 assert!(rng_validate_datatype_value(Some("decimal"), "42"));
3300 assert!(rng_validate_datatype_value(Some("decimal"), "12.5"));
3301 assert!(rng_validate_datatype_value(Some("decimal"), "-3.14"));
3302 assert!(!rng_validate_datatype_value(Some("decimal"), ""));
3303 }
3304
3305 #[test]
3306 fn test_validate_datatype_float() {
3307 assert!(rng_validate_datatype_value(Some("float"), "3.14"));
3308 assert!(rng_validate_datatype_value(Some("float"), "INF"));
3309 assert!(rng_validate_datatype_value(Some("float"), "-INF"));
3310 assert!(rng_validate_datatype_value(Some("float"), "NaN"));
3311 assert!(!rng_validate_datatype_value(Some("float"), ""));
3312 }
3313
3314 #[test]
3315 fn test_validate_datatype_ncname() {
3316 assert!(rng_validate_datatype_value(Some("NCName"), "myElement"));
3317 assert!(rng_validate_datatype_value(Some("NCName"), "_foo"));
3318 assert!(!rng_validate_datatype_value(Some("NCName"), "123abc"));
3319 assert!(!rng_validate_datatype_value(Some("NCName"), ""));
3320 }
3321
3322 #[test]
3323 fn test_validate_datatype_any_uri() {
3324 assert!(rng_validate_datatype_value(
3325 Some("anyURI"),
3326 "http://example.com"
3327 ));
3328 assert!(rng_validate_datatype_value(Some("anyURI"), "urn:isbn:1234"));
3329 assert!(!rng_validate_datatype_value(Some("anyURI"), ""));
3330 assert!(!rng_validate_datatype_value(Some("anyURI"), "has space"));
3331 }
3332
3333 #[test]
3334 fn test_validate_datatype_qname() {
3335 assert!(rng_validate_datatype_value(Some("QName"), "ns:local"));
3336 assert!(rng_validate_datatype_value(Some("QName"), "local"));
3337 assert!(!rng_validate_datatype_value(Some("QName"), ""));
3338 }
3339
3340 #[test]
3343 fn test_c_abi_new_parse_free() {
3344 let schema_xml = r#"<?xml version="1.0"?>
3345<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3346 <text/>
3347</element>"#;
3348
3349 let ctxt = unsafe {
3350 xmlRelaxNGNewMemParserCtxt(
3351 schema_xml.as_ptr() as *const c_char,
3352 schema_xml.len() as c_int,
3353 )
3354 };
3355 assert!(!ctxt.is_null(), "Parser context should not be null");
3356
3357 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3358 assert!(!schema.is_null(), "Schema should not be null");
3359
3360 unsafe { xmlRelaxNGFree(schema) };
3362 }
3363
3364 #[test]
3365 fn test_c_abi_validate_doc() {
3366 let schema_xml = r#"<?xml version="1.0"?>
3367<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3368 <text/>
3369</element>"#;
3370
3371 let doc_xml = r#"<?xml version="1.0"?>
3372<root>Hello</root>"#;
3373
3374 let ctxt = unsafe {
3375 xmlRelaxNGNewMemParserCtxt(
3376 schema_xml.as_ptr() as *const c_char,
3377 schema_xml.len() as c_int,
3378 )
3379 };
3380 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3381 assert!(!schema.is_null());
3382
3383 let valid_ctxt = unsafe { xmlRelaxNGNewValidCtxt(schema) };
3384 assert!(!valid_ctxt.is_null());
3385
3386 let doc = unsafe {
3387 crate::abi::exports_xml2::xmlReadMemory(
3388 doc_xml.as_ptr() as *const c_char,
3389 doc_xml.len() as c_int,
3390 b"test.xml\0".as_ptr() as *const c_char,
3391 ptr::null(),
3392 0,
3393 )
3394 };
3395 assert!(!doc.is_null());
3396
3397 let result = unsafe { xmlRelaxNGValidateDoc(valid_ctxt, doc) };
3398 assert_eq!(result, 0, "Validation should succeed");
3399
3400 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3401 unsafe { xmlRelaxNGFreeValidCtxt(valid_ctxt) };
3402 unsafe { xmlRelaxNGFree(schema) };
3403 }
3404
3405 #[test]
3406 fn test_c_abi_validate_full_element() {
3407 let schema_xml = r#"<?xml version="1.0"?>
3408<element name="item" xmlns="http://relaxng.org/ns/structure/1.0">
3409 <text/>
3410</element>"#;
3411
3412 let doc_xml = r#"<?xml version="1.0"?>
3413<root><item>Content</item></root>"#;
3414
3415 let ctxt = unsafe {
3416 xmlRelaxNGNewMemParserCtxt(
3417 schema_xml.as_ptr() as *const c_char,
3418 schema_xml.len() as c_int,
3419 )
3420 };
3421 let schema = unsafe { xmlRelaxNGParse(ctxt) };
3422 assert!(!schema.is_null());
3423
3424 let valid_ctxt = unsafe { xmlRelaxNGNewValidCtxt(schema) };
3425 assert!(!valid_ctxt.is_null());
3426
3427 let doc = unsafe {
3428 crate::abi::exports_xml2::xmlReadMemory(
3429 doc_xml.as_ptr() as *const c_char,
3430 doc_xml.len() as c_int,
3431 b"test.xml\0".as_ptr() as *const c_char,
3432 ptr::null(),
3433 0,
3434 )
3435 };
3436 assert!(!doc.is_null());
3437
3438 let item = unsafe {
3440 let mut node = (*doc).children;
3442 while !node.is_null() {
3443 if (*node).type_ == XML_ELEMENT_NODE as c_int {
3444 break;
3445 }
3446 node = (*node).next;
3447 }
3448 if !node.is_null() {
3449 node = (*node).children;
3451 while !node.is_null() {
3452 if (*node).type_ == XML_ELEMENT_NODE as c_int {
3453 break;
3454 }
3455 node = (*node).next;
3456 }
3457 }
3458 node
3459 };
3460 assert!(!item.is_null(), "Should find <item> element");
3461
3462 let result = unsafe { xmlRelaxNGValidateFullElement(valid_ctxt, doc, item) };
3463 assert_eq!(result, 0, "Element validation should succeed");
3464
3465 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3466 unsafe { xmlRelaxNGFreeValidCtxt(valid_ctxt) };
3467 unsafe { xmlRelaxNGFree(schema) };
3468 }
3469
3470 #[test]
3471 fn test_c_abi_null_handling() {
3472 assert_eq!(unsafe { xmlRelaxNGValidateDoc(ptr::null_mut(), ptr::null_mut()) }, -1);
3474 assert_eq!(unsafe { xmlRelaxNGValidateFullElement(ptr::null_mut(), ptr::null_mut(), ptr::null_mut()) }, -1);
3475 assert!(unsafe { xmlRelaxNGNewMemParserCtxt(ptr::null(), 0).is_null() });
3476
3477 unsafe { xmlRelaxNGFree(ptr::null_mut()) };
3479 unsafe { xmlRelaxNGFreeParserCtxt(ptr::null_mut()) };
3480 unsafe { xmlRelaxNGFreeValidCtxt(ptr::null_mut()) };
3481 }
3482
3483 #[test]
3486 fn test_parse_with_div() {
3487 let schema_xml = r#"<?xml version="1.0"?>
3488<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3489 <div>
3490 <define name="shared">
3491 <text/>
3492 </define>
3493 </div>
3494 <start>
3495 <element name="root">
3496 <ref name="shared"/>
3497 </element>
3498 </start>
3499</grammar>"#;
3500
3501 let result = rng_parse(schema_xml);
3502 assert!(result.is_ok(), "Failed to parse with div: {:?}", result.err());
3503 let schema = result.unwrap();
3504 assert_eq!(schema.grammar.defines.len(), 1);
3505 assert_eq!(schema.grammar.defines[0].name, "shared");
3506 }
3507
3508 #[test]
3509 fn test_validate_list_pattern() {
3510 let schema_xml = r#"<?xml version="1.0"?>
3511<element name="tokens" xmlns="http://relaxng.org/ns/structure/1.0">
3512 <list>
3513 <data type="token"/>
3514 </list>
3515</element>"#;
3516
3517 let doc_xml = r#"<?xml version="1.0"?>
3518<tokens>abc def ghi</tokens>"#;
3519
3520 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3521
3522 let doc = unsafe {
3523 crate::abi::exports_xml2::xmlReadMemory(
3524 doc_xml.as_ptr() as *const c_char,
3525 doc_xml.len() as c_int,
3526 b"test.xml\0".as_ptr() as *const c_char,
3527 ptr::null(),
3528 0,
3529 )
3530 };
3531 assert!(!doc.is_null());
3532
3533 let mut ctxt = RelaxNgValidCtxt::new();
3534 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3535 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3536
3537 assert!(valid, "List pattern validation failed: {:?}", ctxt.errors);
3538 }
3539
3540 #[test]
3541 fn test_validate_group_pattern() {
3542 let schema_xml = r#"<?xml version="1.0"?>
3543<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3544 <start>
3545 <element name="root">
3546 <group>
3547 <element name="a">
3548 <text/>
3549 </element>
3550 <element name="b">
3551 <text/>
3552 </element>
3553 </group>
3554 </element>
3555 </start>
3556</grammar>"#;
3557
3558 let doc_xml = r#"<?xml version="1.0"?>
3559<root><a>First</a><b>Second</b></root>"#;
3560
3561 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3562
3563 let doc = unsafe {
3564 crate::abi::exports_xml2::xmlReadMemory(
3565 doc_xml.as_ptr() as *const c_char,
3566 doc_xml.len() as c_int,
3567 b"test.xml\0".as_ptr() as *const c_char,
3568 ptr::null(),
3569 0,
3570 )
3571 };
3572 assert!(!doc.is_null());
3573
3574 let mut ctxt = RelaxNgValidCtxt::new();
3575 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3576 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3577
3578 assert!(valid, "Group validation failed: {:?}", ctxt.errors);
3579 }
3580
3581 #[test]
3582 fn test_validate_undefined_ref() {
3583 let schema_xml = r#"<?xml version="1.0"?>
3584<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3585 <start>
3586 <element name="root">
3587 <ref name="undefined"/>
3588 </element>
3589 </start>
3590</grammar>"#;
3591
3592 let doc_xml = r#"<?xml version="1.0"?>
3593<root>Content</root>"#;
3594
3595 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3596
3597 let doc = unsafe {
3598 crate::abi::exports_xml2::xmlReadMemory(
3599 doc_xml.as_ptr() as *const c_char,
3600 doc_xml.len() as c_int,
3601 b"test.xml\0".as_ptr() as *const c_char,
3602 ptr::null(),
3603 0,
3604 )
3605 };
3606 assert!(!doc.is_null());
3607
3608 let mut ctxt = RelaxNgValidCtxt::new();
3609 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3610 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3611
3612 assert!(!valid, "Undefined ref should cause failure");
3613 }
3614
3615 #[test]
3616 fn test_validate_null_doc() {
3617 let schema = RelaxNgSchema::new();
3618 let mut ctxt = RelaxNgValidCtxt::new();
3619 let valid = unsafe { rng_validate_doc(&schema, ptr::null_mut(), &mut ctxt) };
3620 assert!(!valid);
3621 }
3622
3623 #[test]
3624 fn test_parse_external_ref_schema() {
3625 let schema_xml = r#"<?xml version="1.0"?>
3626<element name="root" xmlns="http://relaxng.org/ns/structure/1.0">
3627 <externalRef href="external.rng"/>
3628</element>"#;
3629
3630 let result = rng_parse(schema_xml);
3631 assert!(result.is_ok(), "Failed to parse externalRef: {:?}", result.err());
3632 let schema = result.unwrap();
3633 if let Some(ref start) = schema.grammar.start {
3634 assert_eq!(start.pattern_type, RelaxNgPatternType::Element);
3635 assert_eq!(start.name.as_deref(), Some("root"));
3636 }
3637 }
3638
3639 #[test]
3640 fn test_validate_boolean_datatype() {
3641 assert!(rng_validate_datatype_value(Some("boolean"), "true"));
3642 assert!(rng_validate_datatype_value(Some("boolean"), "false"));
3643 assert!(!rng_validate_datatype_value(Some("boolean"), "maybe"));
3644 }
3645
3646 #[test]
3647 fn test_validate_unknown_datatype() {
3648 assert!(rng_validate_datatype_value(Some("custom-type"), "anything"));
3650 }
3651
3652 #[test]
3653 fn test_validate_no_datatype() {
3654 assert!(rng_validate_datatype_value(None, "anything"));
3656 }
3657
3658 #[test]
3659 fn test_parse_schema_with_ns_prefix() {
3660 let schema_xml = r#"<?xml version="1.0"?>
3661<rng:element name="root" xmlns:rng="http://relaxng.org/ns/structure/1.0">
3662 <rng:text/>
3663</rng:element>"#;
3664
3665 let result = rng_parse(schema_xml);
3666 assert!(result.is_ok(), "Failed with ns prefix: {:?}", result.err());
3667 }
3668
3669 #[test]
3670 fn test_validation_context_path() {
3671 let mut ctxt = RelaxNgValidCtxt::new();
3672 assert_eq!(ctxt.current_path(), "/");
3673
3674 ctxt.path.push("root".to_string());
3675 assert_eq!(ctxt.current_path(), "/root");
3676
3677 ctxt.path.push("child".to_string());
3678 assert_eq!(ctxt.current_path(), "/root/child");
3679
3680 ctxt.path.pop();
3681 assert_eq!(ctxt.current_path(), "/root");
3682 }
3683
3684 #[test]
3685 fn test_validate_sequence_wrong_order() {
3686 let schema_xml = r#"<?xml version="1.0"?>
3687<grammar xmlns="http://relaxng.org/ns/structure/1.0">
3688 <start>
3689 <element name="root">
3690 <sequence>
3691 <element name="first">
3692 <text/>
3693 </element>
3694 <element name="second">
3695 <text/>
3696 </element>
3697 </sequence>
3698 </element>
3699 </start>
3700</grammar>"#;
3701
3702 let doc_xml = r#"<?xml version="1.0"?>
3703<root><second>Wrong</second><first>Order</first></root>"#;
3704
3705 let schema = rng_parse(schema_xml).expect("Failed to parse schema");
3706
3707 let doc = unsafe {
3708 crate::abi::exports_xml2::xmlReadMemory(
3709 doc_xml.as_ptr() as *const c_char,
3710 doc_xml.len() as c_int,
3711 b"test.xml\0".as_ptr() as *const c_char,
3712 ptr::null(),
3713 0,
3714 )
3715 };
3716 assert!(!doc.is_null());
3717
3718 let mut ctxt = RelaxNgValidCtxt::new();
3719 let valid = unsafe { rng_validate_doc(&schema, doc, &mut ctxt) };
3720 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3721
3722 assert!(!valid, "Wrong sequence order should fail");
3725 }
3726}