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::collections::HashMap;
87use std::os::raw::{c_char, c_int};
88
89use crate::abi::structs::*;
90use crate::abi::types::xmlElementType::*;
91use crate::xml::xpath::ast::CompiledExpr;
92use crate::xml::xpath::context::XPathContext;
93use crate::xml::xpath::types::XPathValue;
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum SchematronPatternType {
102 Assert,
104 Report,
106}
107
108#[derive(Debug, Clone)]
114pub struct SchematronPattern {
115 pub pattern_type: SchematronPatternType,
117 pub test: String,
119 pub compiled_test: Option<CompiledExpr>,
121 pub text: String,
123 pub flag: Option<String>,
125 pub role: Option<String>,
127 pub id: Option<String>,
129 pub icon: Option<String>,
131 pub see: Option<String>,
133 pub diagnostics: Option<String>,
135}
136
137impl SchematronPattern {
138 pub fn new(pattern_type: SchematronPatternType, test: String, text: String) -> Self {
140 Self {
141 pattern_type,
142 compiled_test: crate::xml::xpath::compile(&test),
143 test,
144 text,
145 flag: None,
146 role: None,
147 id: None,
148 icon: None,
149 see: None,
150 diagnostics: None,
151 }
152 }
153}
154
155#[derive(Debug, Clone)]
165pub struct SchematronRule {
166 pub context: String,
168 pub compiled_context: Option<CompiledExpr>,
170 pub patterns: Vec<SchematronPattern>,
172 pub id: Option<String>,
174 pub abstract_: bool,
176 pub extends: Vec<String>,
178}
179
180impl SchematronRule {
181 pub fn new(context: String) -> Self {
183 Self {
184 compiled_context: crate::xml::xpath::compile(&context),
185 context,
186 patterns: Vec::new(),
187 id: None,
188 abstract_: false,
189 extends: Vec::new(),
190 }
191 }
192}
193
194#[derive(Debug, Clone)]
204pub struct SchematronPhase {
205 pub id: String,
207 pub active_patterns: Vec<String>,
209}
210
211#[derive(Debug, Clone)]
217pub struct SchematronDiagnostic {
218 pub id: String,
220 pub text: String,
222 pub icon: Option<String>,
224 pub see: Option<String>,
226}
227
228#[derive(Debug, Clone)]
238pub struct SchematronSchema {
239 pub title: Option<String>,
241 pub phases: HashMap<String, SchematronPhase>,
243 pub rules: HashMap<String, SchematronRule>,
245 pub pattern_groups: HashMap<String, Vec<String>>,
247 pub pattern_order: Vec<String>,
249 pub ns: HashMap<String, String>,
251 pub query_binding: String,
253 pub default_phase: Option<String>,
255 pub diagnostics: HashMap<String, SchematronDiagnostic>,
257 pub errors: Vec<String>,
259}
260
261impl SchematronSchema {
262 pub fn new() -> Self {
264 Self {
265 title: None,
266 phases: HashMap::new(),
267 rules: HashMap::new(),
268 pattern_groups: HashMap::new(),
269 pattern_order: Vec::new(),
270 ns: HashMap::new(),
271 query_binding: "xslt".to_string(),
272 default_phase: None,
273 diagnostics: HashMap::new(),
274 errors: Vec::new(),
275 }
276 }
277
278 pub fn resolve_rule(&self, rule_id: &str) -> Option<SchematronRule> {
280 let rule = self.rules.get(rule_id)?.clone();
281 Some(self.resolve_extends(rule))
282 }
283
284 fn resolve_extends(&self, mut rule: SchematronRule) -> SchematronRule {
286 let extended_ids: Vec<String> = rule.extends.clone();
287 for ext_id in &extended_ids {
288 if let Some(ext_rule) = self.rules.get(ext_id) {
289 let resolved_ext = self.resolve_extends(ext_rule.clone());
291 rule.patterns.extend(resolved_ext.patterns);
292 }
293 }
294 rule
295 }
296
297 pub fn active_rules(&self, phase_id: Option<&str>) -> Vec<SchematronRule> {
301 let active_patterns: Vec<String> = match phase_id {
303 Some(pid) => {
304 if let Some(phase) = self.phases.get(pid) {
305 phase.active_patterns.clone()
306 } else {
307 self.pattern_order.clone()
309 }
310 }
311 None => {
312 if let Some(default) = &self.default_phase {
314 if let Some(phase) = self.phases.get(default) {
315 phase.active_patterns.clone()
316 } else {
317 self.pattern_order.clone()
318 }
319 } else {
320 self.pattern_order.clone()
321 }
322 }
323 };
324
325 let mut result = Vec::new();
326 for pat_id in &active_patterns {
327 if let Some(rule_ids) = self.pattern_groups.get(pat_id) {
328 for rule_id in rule_ids {
329 if let Some(rule) = self.rules.get(rule_id) {
330 if !rule.abstract_ {
331 result.push(self.resolve_extends(rule.clone()));
332 }
333 }
334 }
335 }
336 }
337
338 result
339 }
340}
341
342impl Default for SchematronSchema {
343 fn default() -> Self {
344 Self::new()
345 }
346}
347
348#[derive(Debug)]
357pub struct SchematronValidCtxt {
358 pub schema: Option<SchematronSchema>,
360 pub errors: Vec<String>,
362 pub nb_errors: i32,
364 pub active_phase: Option<String>,
366}
367
368impl SchematronValidCtxt {
369 pub const fn new() -> Self {
371 Self {
372 schema: None,
373 errors: Vec::new(),
374 nb_errors: 0,
375 active_phase: None,
376 }
377 }
378
379 pub fn record_error(&mut self, msg: String) {
381 self.errors.push(msg);
382 self.nb_errors += 1;
383 }
384}
385
386impl Default for SchematronValidCtxt {
387 fn default() -> Self {
388 Self::new()
389 }
390}
391
392unsafe fn get_local_name(node: *mut _xmlNode) -> String {
402 if node.is_null() {
403 return String::new();
404 }
405 unsafe {
406 let name = (*node).name;
407 if name.is_null() {
408 return String::new();
409 }
410 let mut len = 0;
411 while *name.add(len) != 0 {
412 len += 1;
413 }
414 let slice = std::slice::from_raw_parts(name, len);
415 if let Ok(s) = std::str::from_utf8(slice) {
416 if let Some(pos) = s.find(':') {
417 s[pos + 1..].to_string()
418 } else {
419 s.to_string()
420 }
421 } else {
422 String::new()
423 }
424 }
425}
426
427unsafe fn get_node_qname(node: *mut _xmlNode) -> String {
433 if node.is_null() {
434 return String::new();
435 }
436 unsafe {
437 let ns = (*node).ns;
438 let prefix = if !ns.is_null() && !(*ns).prefix.is_null() {
439 let mut len = 0;
440 while *(*ns).prefix.add(len) != 0 {
441 len += 1;
442 }
443 let slice = std::slice::from_raw_parts((*ns).prefix, len);
444 if let Ok(s) = std::str::from_utf8(slice) {
445 format!("{}:", s)
446 } else {
447 String::new()
448 }
449 } else {
450 String::new()
451 };
452
453 let name = (*node).name;
454 if name.is_null() {
455 return String::new();
456 }
457 let mut len = 0;
458 while *name.add(len) != 0 {
459 len += 1;
460 }
461 let slice = std::slice::from_raw_parts(name, len);
462 if let Ok(s) = std::str::from_utf8(slice) {
463 format!("{}{}", prefix, s)
464 } else {
465 String::new()
466 }
467 }
468}
469
470unsafe fn get_node_text(node: *mut _xmlNode) -> String {
476 if node.is_null() {
477 return String::new();
478 }
479 let mut result = String::new();
480 unsafe {
481 let mut child = (*node).children;
482 while !child.is_null() {
483 if ((*child).type_ == XML_TEXT_NODE as c_int
484 || (*child).type_ == XML_CDATA_SECTION_NODE as c_int)
485 && !(*child).content.is_null()
486 {
487 let content = (*child).content;
488 let mut len = 0;
489 while *content.add(len) != 0 {
490 len += 1;
491 }
492 let slice = std::slice::from_raw_parts(content, len);
493 result.push_str(&String::from_utf8_lossy(slice));
494 }
495 child = (*child).next;
496 }
497 }
498 result
499}
500
501unsafe fn get_attr(node: *mut _xmlNode, name: &str) -> Option<String> {
507 if node.is_null() {
508 return None;
509 }
510 unsafe {
511 let mut prop = (*node).properties;
512 while !prop.is_null() {
513 let prop_name = (*prop).name;
514 if !prop_name.is_null() {
515 let mut len = 0;
516 while *prop_name.add(len) != 0 {
517 len += 1;
518 }
519 let slice = std::slice::from_raw_parts(prop_name, len);
520 if let Ok(s) = std::str::from_utf8(slice) {
521 if s == name {
522 return Some(get_node_text(prop as *mut _xmlNode));
523 }
524 }
525 }
526 prop = (*prop).next;
527 }
528 }
529 None
530}
531
532#[allow(dead_code)]
538unsafe fn node_is(node: *mut _xmlNode, local_name: &str) -> bool {
539 if node.is_null() {
540 return false;
541 }
542 unsafe {
543 let name = (*node).name;
544 if name.is_null() {
545 return false;
546 }
547 let mut len = 0;
548 while *name.add(len) != 0 {
549 len += 1;
550 }
551 let slice = std::slice::from_raw_parts(name, len);
552 if let Ok(s) = std::str::from_utf8(slice) {
553 let local = if let Some(pos) = s.find(':') {
554 &s[pos + 1..]
555 } else {
556 s
557 };
558 return local == local_name;
559 }
560 }
561 false
562}
563
564#[allow(dead_code)]
570unsafe fn child_elements(node: *mut _xmlNode) -> Vec<*mut _xmlNode> {
571 let mut children = Vec::new();
572 if node.is_null() {
573 return children;
574 }
575 unsafe {
576 let mut child = (*node).children;
577 while !child.is_null() {
578 if (*child).type_ == XML_ELEMENT_NODE as c_int {
579 children.push(child);
580 }
581 child = (*child).next;
582 }
583 }
584 children
585}
586
587unsafe fn get_inline_text(node: *mut _xmlNode) -> String {
594 if node.is_null() {
595 return String::new();
596 }
597 let mut result = String::new();
598 unsafe {
599 let mut child = (*node).children;
600 while !child.is_null() {
601 if (*child).type_ == XML_TEXT_NODE as c_int
602 || (*child).type_ == XML_CDATA_SECTION_NODE as c_int
603 {
604 if !(*child).content.is_null() {
605 let content = (*child).content;
606 let mut len = 0;
607 while *content.add(len) != 0 {
608 len += 1;
609 }
610 let slice = std::slice::from_raw_parts(content, len);
611 result.push_str(&String::from_utf8_lossy(slice));
612 }
613 } else if (*child).type_ == XML_ELEMENT_NODE as c_int {
614 let local = get_local_name(child);
615 match local.as_str() {
616 "span" | "emph" | "dir" => {
617 result.push_str(&get_inline_text(child));
618 }
619 _ => {}
620 }
621 }
622 child = (*child).next;
623 }
624 }
625 result
626}
627
628pub fn schematron_parse(xml_doc: &str) -> Result<SchematronSchema, String> {
651 let doc_ptr = unsafe {
652 crate::abi::exports_xml2::xmlReadMemory(
653 xml_doc.as_ptr() as *const c_char,
654 xml_doc.len() as c_int,
655 c"schema.sch".as_ptr() as *const c_char,
656 ptr::null(),
657 0,
658 )
659 };
660
661 if doc_ptr.is_null() {
662 return Err("Failed to parse Schematron schema XML document".to_string());
663 }
664
665 let result = unsafe { schematron_parse_doc(doc_ptr) };
666 unsafe {
667 crate::abi::exports_xml2::xmlFreeDoc(doc_ptr);
668 }
669 result
670}
671
672unsafe fn schematron_parse_doc(doc: *mut _xmlDoc) -> Result<SchematronSchema, String> {
678 unsafe {
679 let root = (*doc).children;
680 if root.is_null() {
681 return Err("Schematron document has no root element".to_string());
682 }
683
684 let mut root_elem = root;
686 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
687 root_elem = (*root_elem).next;
688 }
689
690 if root_elem.is_null() {
691 return Err("Schematron document has no root element".to_string());
692 }
693
694 let local_name = get_local_name(root_elem);
695 if local_name != "schema" {
696 return Err(format!(
697 "Expected '<schema>' root element, found '<{}>'",
698 local_name
699 ));
700 }
701
702 Ok(schematron_parse_schema_node(root_elem))
703 }
704}
705
706unsafe fn schematron_parse_schema_node(node: *mut _xmlNode) -> SchematronSchema {
712 unsafe {
713 let mut schema = SchematronSchema::new();
714
715 if let Some(qb) = get_attr(node, "queryBinding") {
717 schema.query_binding = qb;
718 }
719 schema.title = get_attr(node, "title");
720 if let Some(df) = get_attr(node, "defaultPhase") {
721 schema.default_phase = Some(df);
722 }
723
724 let mut current_pattern_id: Option<String> = None;
726 let mut pattern_names: HashMap<String, Vec<String>> = HashMap::new();
727
728 let mut child = (*node).children;
729 while !child.is_null() {
730 if (*child).type_ == XML_ELEMENT_NODE as c_int {
731 let local = get_local_name(child);
732 match local.as_str() {
733 "title" => {
734 if schema.title.is_none() {
735 schema.title = Some(get_node_text(child).trim().to_string());
736 }
737 }
738 "ns" => {
739 let prefix = get_attr(child, "prefix").unwrap_or_default();
740 let uri = get_attr(child, "uri").unwrap_or_default();
741 if !prefix.is_empty() && !uri.is_empty() {
742 schema.ns.insert(prefix, uri);
743 }
744 }
745 "phase" => {
746 let phase = schematron_parse_phase(child);
747 schema.phases.insert(phase.id.clone(), phase);
748 }
749 "pattern" => {
750 let pat_id = schematron_parse_pattern_node(
751 child,
752 &mut schema,
753 &mut current_pattern_id,
754 &mut pattern_names,
755 );
756 current_pattern_id = pat_id;
757 }
758 "rule" => {
759 let rule = schematron_parse_rule(child, &mut schema);
761 let rule_id = rule
762 .id
763 .clone()
764 .unwrap_or_else(|| format!("_rule_{}", schema.rules.len()));
765 let rid = rule_id.clone();
767 schema.rules.insert(rid, rule);
768
769 if let Some(ref pid) = current_pattern_id {
771 schema
772 .pattern_groups
773 .entry(pid.clone())
774 .or_default()
775 .push(rule_id);
776 } else {
777 let anon_id = format!("_anon_{}", schema.pattern_order.len());
779 schema
780 .pattern_groups
781 .entry(anon_id.clone())
782 .or_default()
783 .push(rule_id);
784 if !schema.pattern_order.contains(&anon_id) {
785 schema.pattern_order.push(anon_id);
786 }
787 }
788 }
789 "diagnostics" => {
790 schematron_parse_diagnostics(child, &mut schema);
791 }
792 "include" => {
793 schematron_parse_include(child, &mut schema);
794 }
795 "p" | "caption" => {
796 }
798 _ => {
799 schema
800 .errors
801 .push(format!("Unexpected element '<{}>' in schema", local));
802 }
803 }
804 }
805 child = (*child).next;
806 }
807
808 schema
809 }
810}
811
812unsafe fn schematron_parse_pattern_node(
818 node: *mut _xmlNode,
819 schema: &mut SchematronSchema,
820 _current_pattern_id: &mut Option<String>,
821 _pattern_names: &mut HashMap<String, Vec<String>>,
822) -> Option<String> {
823 unsafe {
824 let pat_id = get_attr(node, "id");
825 let pat_name = get_attr(node, "name");
826 let pat_is_a = get_attr(node, "is-a");
827 let pat_see = get_attr(node, "see");
828 let pat_icon = get_attr(node, "icon");
829 let pat_role = get_attr(node, "role");
830
831 let pid = pat_id
832 .clone()
833 .unwrap_or_else(|| format!("_pattern_{}", schema.pattern_order.len()));
834
835 let mut rule_ids: Vec<String> = Vec::new();
836
837 let mut child = (*node).children;
839 while !child.is_null() {
840 if (*child).type_ == XML_ELEMENT_NODE as c_int {
841 let local = get_local_name(child);
842 match local.as_str() {
843 "rule" => {
844 let rule = schematron_parse_rule(child, schema);
845 let rule_id = rule
846 .id
847 .clone()
848 .unwrap_or_else(|| format!("_rule_{}", schema.rules.len()));
849 let rid = rule_id.clone();
850 schema.rules.insert(rid, rule);
851 rule_ids.push(rule_id);
852 }
853 "p" | "caption" => {
854 }
856 _ => {
857 schema
858 .errors
859 .push(format!("Unexpected element '<{}>' in pattern", local));
860 }
861 }
862 }
863 child = (*child).next;
864 }
865
866 schema.pattern_groups.insert(pid.clone(), rule_ids);
867 schema.pattern_order.push(pid.clone());
868
869 if pat_is_a.is_some() {
871 }
874
875 let _ = pat_name;
877 let _ = pat_see;
878 let _ = pat_icon;
879 let _ = pat_role;
880
881 Some(pid)
882 }
883}
884
885unsafe fn schematron_parse_rule(
891 node: *mut _xmlNode,
892 schema: &mut SchematronSchema,
893) -> SchematronRule {
894 unsafe {
895 let context = get_attr(node, "context").unwrap_or_default();
896 let mut rule = SchematronRule::new(context);
897 rule.id = get_attr(node, "id");
898
899 let abs = get_attr(node, "abstract").unwrap_or_default();
900 rule.abstract_ = abs == "true" || abs == "1";
901
902 let mut child = (*node).children;
904 while !child.is_null() {
905 if (*child).type_ == XML_ELEMENT_NODE as c_int {
906 let local = get_local_name(child);
907 match local.as_str() {
908 "assert" => {
909 let pattern = schematron_parse_assert(child, SchematronPatternType::Assert);
910 rule.patterns.push(pattern);
911 }
912 "report" => {
913 let pattern = schematron_parse_assert(child, SchematronPatternType::Report);
914 rule.patterns.push(pattern);
915 }
916 "extends" => {
917 if let Some(ext_rule) = get_attr(child, "rule") {
918 rule.extends.push(ext_rule);
919 }
920 }
921 "let" => {
922 let name = get_attr(child, "name").unwrap_or_default();
925 let value = get_attr(child, "value").unwrap_or_default();
926 if !name.is_empty() {
927 let _ = value;
930 }
931 }
932 "param" => {
933 let _name = get_attr(child, "name");
935 let _value = get_attr(child, "value");
936 }
937 "p" | "caption" => {
938 }
940 _ => {
941 schema
942 .errors
943 .push(format!("Unexpected element '<{}>' in rule", local));
944 }
945 }
946 }
947 child = (*child).next;
948 }
949
950 rule
951 }
952}
953
954unsafe fn schematron_parse_assert(
960 node: *mut _xmlNode,
961 pattern_type: SchematronPatternType,
962) -> SchematronPattern {
963 unsafe {
964 let test = get_attr(node, "test").unwrap_or_default();
965 let text = get_inline_text(node);
966
967 let mut pattern = SchematronPattern::new(pattern_type, test, text);
968 pattern.flag = get_attr(node, "flag");
969 pattern.id = get_attr(node, "id");
970 pattern.icon = get_attr(node, "icon");
971 pattern.see = get_attr(node, "see");
972 pattern.role = get_attr(node, "role");
973 pattern.diagnostics = get_attr(node, "diagnostics");
974
975 pattern
979 }
980}
981
982unsafe fn schematron_parse_phase(node: *mut _xmlNode) -> SchematronPhase {
988 unsafe {
989 let id = get_attr(node, "id").unwrap_or_default();
990 let mut phase = SchematronPhase {
991 id,
992 active_patterns: Vec::new(),
993 };
994
995 let mut child = (*node).children;
996 while !child.is_null() {
997 if (*child).type_ == XML_ELEMENT_NODE as c_int {
998 let local = get_local_name(child);
999 if local == "active" {
1000 if let Some(pattern) = get_attr(child, "pattern") {
1001 phase.active_patterns.push(pattern);
1002 }
1003 }
1004 }
1005 child = (*child).next;
1006 }
1007
1008 phase
1009 }
1010}
1011
1012unsafe fn schematron_parse_diagnostics(node: *mut _xmlNode, schema: &mut SchematronSchema) {
1018 unsafe {
1019 let mut child = (*node).children;
1020 while !child.is_null() {
1021 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1022 let local = get_local_name(child);
1023 if local == "diagnostic" {
1024 let diag = schematron_parse_diagnostic(child);
1025 schema.diagnostics.insert(diag.id.clone(), diag);
1026 }
1027 }
1028 child = (*child).next;
1029 }
1030 }
1031}
1032
1033unsafe fn schematron_parse_diagnostic(node: *mut _xmlNode) -> SchematronDiagnostic {
1039 unsafe {
1040 let id = get_attr(node, "id").unwrap_or_default();
1041 let text = get_inline_text(node);
1042 let icon = get_attr(node, "icon");
1043 let see = get_attr(node, "see");
1044
1045 SchematronDiagnostic {
1046 id,
1047 text,
1048 icon,
1049 see,
1050 }
1051 }
1052}
1053
1054unsafe fn schematron_parse_include(node: *mut _xmlNode, _schema: &mut SchematronSchema) {
1060 unsafe {
1061 let href = get_attr(node, "href");
1062 if let Some(url) = href {
1063 let url_c = std::ffi::CString::new(url.clone()).ok();
1064 if let Some(c) = url_c {
1065 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
1066 if !doc.is_null() {
1067 let mut root = (*doc).children;
1069 while !root.is_null() && (*root).type_ != XML_ELEMENT_NODE as c_int {
1070 root = (*root).next;
1071 }
1072 if !root.is_null() {
1073 let local = get_local_name(root);
1074 if local == "schema" || local == "pattern" || local == "rule" {
1075 }
1079 }
1080 crate::abi::exports_xml2::xmlFreeDoc(doc);
1081 }
1082 }
1083 }
1084 }
1085}
1086
1087unsafe fn expand_diagnostic_message(
1100 text: &str,
1101 context_node: *mut _xmlNode,
1102 xpath_ctxt: &mut XPathContext,
1103) -> String {
1104 let _ = context_node;
1116 let _ = xpath_ctxt;
1117 text.to_string()
1118}
1119
1120fn evaluate_xpath_boolean(
1126 compiled: &CompiledExpr,
1127 xpath_ctxt: &mut XPathContext,
1128) -> Result<bool, String> {
1129 match crate::xml::xpath::evaluate(compiled, xpath_ctxt) {
1130 Some(value) => Ok(value.as_boolean()),
1131 None => Err("XPath evaluation failed".to_string()),
1132 }
1133}
1134
1135pub unsafe fn schematron_validate_doc(
1145 schema: &SchematronSchema,
1146 doc: *mut _xmlDoc,
1147 ctxt: &mut SchematronValidCtxt,
1148) -> bool {
1149 unsafe {
1150 if doc.is_null() {
1151 ctxt.record_error("Document is null".to_string());
1152 return false;
1153 }
1154
1155 let root = (*doc).children;
1156 if root.is_null() {
1157 ctxt.record_error("Document has no children".to_string());
1158 return false;
1159 }
1160
1161 let mut root_elem = root;
1163 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
1164 root_elem = (*root_elem).next;
1165 }
1166
1167 if root_elem.is_null() {
1168 ctxt.record_error("Document has no root element".to_string());
1169 return false;
1170 }
1171
1172 let phase_id = ctxt.active_phase.as_deref();
1174 let rules = schema.active_rules(phase_id);
1175
1176 if rules.is_empty() {
1177 return true;
1179 }
1180
1181 let mut xpath_ctxt = XPathContext::new(doc);
1183
1184 let core_funcs = crate::xml::xpath::functions::core_functions();
1186 for (name, func) in core_funcs {
1187 xpath_ctxt.register_function(&name, func);
1188 }
1189
1190 for (prefix, uri) in &schema.ns {
1192 xpath_ctxt.namespaces.insert(prefix.clone(), uri.clone());
1193 }
1194
1195 let mut valid = true;
1196
1197 for rule in &rules {
1199 let matching_nodes: Vec<*mut _xmlNode> =
1201 find_matching_nodes(rule, root_elem, doc, &mut xpath_ctxt);
1202
1203 for context_node in &matching_nodes {
1204 xpath_ctxt.set_context_node(*context_node);
1206
1207 for pattern in &rule.patterns {
1209 let compiled = match &pattern.compiled_test {
1210 Some(c) => c,
1211 None => continue,
1212 };
1213
1214 let test_result = match evaluate_xpath_boolean(compiled, &mut xpath_ctxt) {
1215 Ok(val) => val,
1216 Err(e) => {
1217 ctxt.record_error(format!(
1218 "XPath error in '{}' test '{}': {}",
1219 if pattern.pattern_type == SchematronPatternType::Assert {
1220 "assert"
1221 } else {
1222 "report"
1223 },
1224 pattern.test,
1225 e
1226 ));
1227 valid = false;
1228 continue;
1229 }
1230 };
1231
1232 let message =
1233 expand_diagnostic_message(&pattern.text, *context_node, &mut xpath_ctxt);
1234
1235 match pattern.pattern_type {
1236 SchematronPatternType::Assert => {
1237 if !test_result {
1239 let node_name = get_node_qname(*context_node);
1240 let flag_str = pattern
1241 .flag
1242 .as_ref()
1243 .map(|f| format!(" [{}]", f))
1244 .unwrap_or_default();
1245 let role_str = pattern
1246 .role
1247 .as_ref()
1248 .map(|r| format!(" ({})", r))
1249 .unwrap_or_default();
1250 let msg = if message.is_empty() {
1251 format!(
1252 "assertion failed: '{}' for node '{}'{}{}",
1253 pattern.test, node_name, flag_str, role_str
1254 )
1255 } else {
1256 format!(
1257 "assertion '{}' failed for node '{}'{}{}: {}",
1258 pattern.test, node_name, flag_str, role_str, message
1259 )
1260 };
1261 ctxt.record_error(msg);
1262 valid = false;
1263 }
1264 }
1265 SchematronPatternType::Report => {
1266 if test_result {
1268 let node_name = get_node_qname(*context_node);
1269 let flag_str = pattern
1270 .flag
1271 .as_ref()
1272 .map(|f| format!(" [{}]", f))
1273 .unwrap_or_default();
1274 let role_str = pattern
1275 .role
1276 .as_ref()
1277 .map(|r| format!(" ({})", r))
1278 .unwrap_or_default();
1279 let msg = if message.is_empty() {
1280 format!(
1281 "report triggered: '{}' for node '{}'{}{}",
1282 pattern.test, node_name, flag_str, role_str
1283 )
1284 } else {
1285 format!(
1286 "report '{}' triggered for node '{}'{}{}: {}",
1287 pattern.test, node_name, flag_str, role_str, message
1288 )
1289 };
1290 ctxt.record_error(msg);
1291 valid = false;
1292 }
1293 }
1294 }
1295 }
1296 }
1297 }
1298
1299 valid
1300 }
1301}
1302
1303unsafe fn find_matching_nodes(
1310 rule: &SchematronRule,
1311 root: *mut _xmlNode,
1312 doc: *mut _xmlDoc,
1313 xpath_ctxt: &mut XPathContext,
1314) -> Vec<*mut _xmlNode> {
1315 unsafe {
1316 if rule.context.is_empty() {
1318 let mut nodes = Vec::new();
1319 collect_all_elements(root, &mut nodes);
1320 return nodes;
1321 }
1322
1323 if let Some(compiled) = &rule.compiled_context {
1325 let is_simple_name = !rule.context.contains('/')
1328 && !rule.context.contains("::")
1329 && !rule.context.contains('[')
1330 && !rule.context.contains('(');
1331
1332 if !is_simple_name {
1333 xpath_ctxt.set_context_node(root);
1334 xpath_ctxt.document = doc;
1335
1336 if let Some(XPathValue::NodeSet(ns)) =
1337 crate::xml::xpath::evaluate(compiled, xpath_ctxt)
1338 {
1339 if !ns.is_empty() {
1340 return ns.iter().collect();
1341 }
1342 }
1343 }
1344
1345 simple_context_match(&rule.context, root)
1347 } else {
1348 simple_context_match(&rule.context, root)
1350 }
1351 }
1352}
1353
1354fn simple_context_match(context: &str, root: *mut _xmlNode) -> Vec<*mut _xmlNode> {
1368 unsafe {
1369 let context = context.trim();
1370
1371 if context == "*" || context == "//*" {
1378 let mut nodes = Vec::new();
1379 collect_all_elements(root, &mut nodes);
1380 return nodes;
1381 }
1382
1383 if let Some(name) = context.strip_prefix("//") {
1384 if name.is_empty() || name == "*" {
1385 let mut nodes = Vec::new();
1386 collect_all_elements(root, &mut nodes);
1387 return nodes;
1388 }
1389 let mut nodes = Vec::new();
1391 collect_elements_by_name(root, name, &mut nodes);
1392 return nodes;
1393 }
1394
1395 if !context.contains('/') && !context.contains("::") {
1396 let mut nodes = Vec::new();
1398 let root_qname = get_node_qname(root);
1400 let root_local = get_local_name(root);
1401 if root_qname == context || root_local == context || context == "*" {
1402 nodes.push(root);
1403 }
1404 let mut child = (*root).children;
1406 while !child.is_null() {
1407 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1408 let qname = get_node_qname(child);
1409 let local = get_local_name(child);
1410 if qname == context || local == context || context == "*" {
1411 nodes.push(child);
1412 }
1413 }
1414 child = (*child).next;
1415 }
1416 return nodes;
1417 }
1418
1419 vec![root]
1421 }
1422}
1423
1424unsafe fn collect_all_elements(node: *mut _xmlNode, nodes: &mut Vec<*mut _xmlNode>) {
1430 unsafe {
1431 if node.is_null() {
1432 return;
1433 }
1434 if (*node).type_ == XML_ELEMENT_NODE as c_int {
1435 nodes.push(node);
1436 }
1437 let mut child = (*node).children;
1438 while !child.is_null() {
1439 collect_all_elements(child, nodes);
1440 child = (*child).next;
1441 }
1442 }
1443}
1444
1445unsafe fn collect_elements_by_name(
1451 node: *mut _xmlNode,
1452 name: &str,
1453 nodes: &mut Vec<*mut _xmlNode>,
1454) {
1455 unsafe {
1456 if node.is_null() {
1457 return;
1458 }
1459 if (*node).type_ == XML_ELEMENT_NODE as c_int {
1460 let qname = get_node_qname(node);
1461 let local = get_local_name(node);
1462 if qname == name || local == name {
1463 nodes.push(node);
1464 }
1465 }
1466 let mut child = (*node).children;
1467 while !child.is_null() {
1468 collect_elements_by_name(child, name, nodes);
1469 child = (*child).next;
1470 }
1471 }
1472}
1473
1474pub fn schematron_parse_schema(xml_doc: &str) -> Result<SchematronSchema, String> {
1482 schematron_parse(xml_doc)
1483}
1484
1485pub unsafe fn schematron_parse_schema_doc(doc: *mut _xmlDoc) -> Result<SchematronSchema, String> {
1491 schematron_parse_doc(doc)
1492}
1493
1494pub unsafe fn schematron_validate_doc_schema(
1502 schema: &SchematronSchema,
1503 doc: *mut _xmlDoc,
1504 ctxt: &mut SchematronValidCtxt,
1505) -> bool {
1506 schematron_validate_doc(schema, doc, ctxt)
1507}
1508
1509#[no_mangle]
1528pub unsafe extern "C" fn xmlSchematronNewParserCtxt(url: *const c_char) -> *mut c_void {
1529 if url.is_null() {
1530 return Box::into_raw(Box::new(SchematronSchema::new())) as *mut c_void;
1536 }
1537
1538 let url_str = unsafe {
1539 let mut len = 0;
1540 while *url.add(len) != 0 {
1541 len += 1;
1542 }
1543 let slice = std::slice::from_raw_parts(url as *const u8, len);
1544 String::from_utf8_lossy(slice).to_string()
1545 };
1546
1547 if !url_str.is_empty() {
1549 let url_c = std::ffi::CString::new(url_str.clone()).ok();
1550 if let Some(c) = url_c {
1551 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
1552 if !doc.is_null() {
1553 let result = schematron_parse_doc(doc);
1554 crate::abi::exports_xml2::xmlFreeDoc(doc);
1555 if let Ok(schema) = result {
1556 let schema_box = Box::new(schema);
1557 return Box::into_raw(schema_box) as *mut c_void;
1558 }
1559 }
1560 }
1561 }
1562
1563 Box::into_raw(Box::new(SchematronSchema::new())) as *mut c_void
1565}
1566
1567#[no_mangle]
1579pub unsafe extern "C" fn xmlSchematronNewMemParserCtxt(
1580 buffer: *const c_char,
1581 size: c_int,
1582) -> *mut c_void {
1583 if buffer.is_null() || size <= 0 {
1584 return ptr::null_mut();
1585 }
1586
1587 let buf_slice = unsafe { std::slice::from_raw_parts(buffer as *const u8, size as usize) };
1589 let xml_str = String::from_utf8_lossy(buf_slice).to_string();
1590
1591 match schematron_parse(&xml_str) {
1592 Ok(schema) => {
1593 let schema_box = Box::new(schema);
1594 Box::into_raw(schema_box) as *mut c_void
1595 }
1596 Err(_) => ptr::null_mut(),
1597 }
1598}
1599
1600#[no_mangle]
1612pub const unsafe extern "C" fn xmlSchematronParse(ctxt: *mut c_void) -> *mut c_void {
1613 if ctxt.is_null() {
1614 return ptr::null_mut();
1615 }
1616
1617 ctxt
1620}
1621
1622#[no_mangle]
1634pub unsafe extern "C" fn xmlSchematronFree(schema: *mut c_void) {
1635 if schema.is_null() {
1636 return;
1637 }
1638 unsafe {
1640 let _ = Box::from_raw(schema as *mut SchematronSchema);
1641 }
1642}
1643
1644#[no_mangle]
1656pub unsafe extern "C" fn xmlSchematronFreeParserCtxt(ctxt: *mut c_void) {
1657 if ctxt.is_null() {
1658 return;
1659 }
1660 unsafe {
1662 let _ = Box::from_raw(ctxt as *mut SchematronSchema);
1663 }
1664}
1665
1666#[no_mangle]
1681pub unsafe extern "C" fn xmlSchematronNewValidCtxt(
1682 schema: *mut c_void,
1683 _options: c_int,
1684) -> *mut c_void {
1685 let mut ctxt = SchematronValidCtxt::new();
1686
1687 if !schema.is_null() {
1688 unsafe {
1690 let schema_ref = &*(schema as *const SchematronSchema);
1691 ctxt.schema = Some(schema_ref.clone());
1692 }
1693 }
1694
1695 let boxed = Box::new(ctxt);
1696 Box::into_raw(boxed) as *mut c_void
1697}
1698
1699#[no_mangle]
1711pub unsafe extern "C" fn xmlSchematronFreeValidCtxt(ctxt: *mut c_void) {
1712 if ctxt.is_null() {
1713 return;
1714 }
1715 unsafe {
1717 let _ = Box::from_raw(ctxt as *mut SchematronValidCtxt);
1718 }
1719}
1720
1721#[no_mangle]
1736pub unsafe extern "C" fn xmlSchematronValidateDoc(ctxt: *mut c_void, doc: *mut _xmlDoc) -> c_int {
1737 if ctxt.is_null() || doc.is_null() {
1738 return -1;
1739 }
1740
1741 unsafe {
1742 let valid_ctxt = &mut *(ctxt as *mut SchematronValidCtxt);
1743 let schema = match &valid_ctxt.schema {
1744 Some(s) => s,
1745 None => return -1,
1746 };
1747
1748 let mut temp_ctxt = SchematronValidCtxt::new();
1749 temp_ctxt.active_phase = valid_ctxt.active_phase.clone();
1750
1751 let valid = schematron_validate_doc(schema, doc, &mut temp_ctxt);
1752
1753 if valid {
1754 0
1755 } else {
1756 valid_ctxt.errors = temp_ctxt.errors;
1757 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
1758 temp_ctxt.nb_errors
1759 }
1760 }
1761}
1762
1763pub type SchematronValidityErrorFunc = unsafe extern "C" fn(ctx: *mut c_void, msg: *const c_char);
1776
1777pub type SchematronValidityWarningFunc = unsafe extern "C" fn(ctx: *mut c_void, msg: *const c_char);
1779
1780#[derive(Clone, Copy)]
1781struct SchematronSendPtr(*mut c_void);
1782unsafe impl Send for SchematronSendPtr {}
1783unsafe impl Sync for SchematronSendPtr {}
1784impl Default for SchematronSendPtr {
1785 fn default() -> Self {
1786 SchematronSendPtr(core::ptr::null_mut())
1787 }
1788}
1789
1790#[derive(Clone, Copy, Default)]
1791struct SchematronParserState {
1792 err: Option<SchematronValidityErrorFunc>,
1793 warn: Option<SchematronValidityWarningFunc>,
1794 ctx: SchematronSendPtr,
1795}
1796
1797#[derive(Clone, Copy, Default)]
1798struct SchematronValidState {
1799 err: Option<SchematronValidityErrorFunc>,
1800 warn: Option<SchematronValidityWarningFunc>,
1801 ctx: SchematronSendPtr,
1802 options: c_int,
1803}
1804
1805static SCHEMATRON_PARSER_STATE: once_cell::sync::Lazy<
1806 parking_lot::Mutex<std::collections::HashMap<usize, SchematronParserState>>,
1807> = once_cell::sync::Lazy::new(Default::default);
1808
1809static SCHEMATRON_VALID_STATE: once_cell::sync::Lazy<
1810 parking_lot::Mutex<std::collections::HashMap<usize, SchematronValidState>>,
1811> = once_cell::sync::Lazy::new(Default::default);
1812
1813#[no_mangle]
1835pub unsafe extern "C" fn xmlSchematronSetParserErrors(
1836 ctxt: *mut c_void,
1837 err: Option<SchematronValidityErrorFunc>,
1838 warn: Option<SchematronValidityWarningFunc>,
1839 ctx: *mut c_void,
1840) {
1841 if ctxt.is_null() {
1842 return;
1843 }
1844 let mut map = SCHEMATRON_PARSER_STATE.lock();
1845 let st = map.entry(ctxt as usize).or_default();
1846 st.err = err;
1847 st.warn = warn;
1848 st.ctx = SchematronSendPtr(ctx);
1849}
1850
1851#[no_mangle]
1872pub unsafe extern "C" fn xmlSchematronGetParserErrors(
1873 ctxt: *mut c_void,
1874 err: *mut Option<SchematronValidityErrorFunc>,
1875 warn: *mut Option<SchematronValidityWarningFunc>,
1876 ctx: *mut *mut c_void,
1877) -> c_int {
1878 if ctxt.is_null() {
1879 return -1;
1880 }
1881 let map = SCHEMATRON_PARSER_STATE.lock();
1882 let st = map.get(&(ctxt as usize)).copied().unwrap_or_default();
1883 if !err.is_null() {
1884 *err = st.err;
1885 }
1886 if !warn.is_null() {
1887 *warn = st.warn;
1888 }
1889 if !ctx.is_null() {
1890 *ctx = st.ctx.0;
1891 }
1892 0
1893}
1894
1895#[no_mangle]
1916pub unsafe extern "C" fn xmlSchematronSetValidErrors(
1917 ctxt: *mut c_void,
1918 err: Option<SchematronValidityErrorFunc>,
1919 warn: Option<SchematronValidityWarningFunc>,
1920 ctx: *mut c_void,
1921) {
1922 if ctxt.is_null() {
1923 return;
1924 }
1925 let mut map = SCHEMATRON_VALID_STATE.lock();
1926 let st = map.entry(ctxt as usize).or_default();
1927 st.err = err;
1928 st.warn = warn;
1929 st.ctx = SchematronSendPtr(ctx);
1930}
1931
1932#[no_mangle]
1953pub unsafe extern "C" fn xmlSchematronGetValidErrors(
1954 ctxt: *mut c_void,
1955 err: *mut Option<SchematronValidityErrorFunc>,
1956 warn: *mut Option<SchematronValidityWarningFunc>,
1957 ctx: *mut *mut c_void,
1958) -> c_int {
1959 if ctxt.is_null() {
1960 return -1;
1961 }
1962 let map = SCHEMATRON_VALID_STATE.lock();
1963 let st = map.get(&(ctxt as usize)).copied().unwrap_or_default();
1964 if !err.is_null() {
1965 *err = st.err;
1966 }
1967 if !warn.is_null() {
1968 *warn = st.warn;
1969 }
1970 if !ctx.is_null() {
1971 *ctx = st.ctx.0;
1972 }
1973 0
1974}
1975
1976#[no_mangle]
1994pub unsafe extern "C" fn xmlSchematronSetValidOptions(ctxt: *mut c_void, options: c_int) -> c_int {
1995 if ctxt.is_null() {
1996 return -1;
1997 }
1998 let mut map = SCHEMATRON_VALID_STATE.lock();
1999 let st = map.entry(ctxt as usize).or_default();
2000 let old = st.options;
2001 st.options = options;
2002 old
2003}
2004
2005#[no_mangle]
2022pub unsafe extern "C" fn xmlSchematronValidCtxtGetOptions(ctxt: *mut c_void) -> c_int {
2023 if ctxt.is_null() {
2024 return -1;
2025 }
2026 SCHEMATRON_VALID_STATE
2027 .lock()
2028 .get(&(ctxt as usize))
2029 .map_or(0, |st| st.options)
2030}
2031
2032#[no_mangle]
2050pub const unsafe extern "C" fn xmlSchematronIsValid(ctxt: *mut c_void) -> c_int {
2051 if ctxt.is_null() {
2052 return 0;
2053 }
2054 unsafe {
2055 let vc = &*(ctxt as *const SchematronValidCtxt);
2056 if vc.nb_errors > 0 {
2057 0
2058 } else {
2059 1
2060 }
2061 }
2062}
2063
2064#[no_mangle]
2082pub unsafe extern "C" fn xmlSchematronValidateOneElement(
2083 ctxt: *mut c_void,
2084 elem: *mut _xmlNode,
2085) -> c_int {
2086 if ctxt.is_null() || elem.is_null() {
2087 return -1;
2088 }
2089 unsafe {
2090 let valid_ctxt = &mut *(ctxt as *mut SchematronValidCtxt);
2091 let schema = match &valid_ctxt.schema {
2092 Some(s) => s,
2093 None => return -1,
2094 };
2095 let doc = (*elem).doc;
2096 if doc.is_null() {
2097 return -1;
2098 }
2099 let mut temp_ctxt = SchematronValidCtxt::new();
2102 temp_ctxt.active_phase = valid_ctxt.active_phase.clone();
2103 let valid = schematron_validate_doc(schema, doc, &mut temp_ctxt);
2104 if !valid {
2105 valid_ctxt.errors = temp_ctxt.errors;
2106 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2107 }
2108 if valid {
2109 0
2110 } else {
2111 -1
2112 }
2113 }
2114}
2115
2116#[cfg(test)]
2121mod tests {
2122 use super::*;
2123
2124 #[test]
2127 fn test_parse_simple_schema() {
2128 let schema_xml = r#"<?xml version="1.0"?>
2129<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2130 <pattern id="P1">
2131 <rule context="root">
2132 <assert test="count(*) > 0">Root must have children</assert>
2133 </rule>
2134 </pattern>
2135</schema>"#;
2136
2137 let result = schematron_parse(schema_xml);
2138 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2139 let schema = result.unwrap();
2140 assert_eq!(schema.pattern_order.len(), 1);
2141 assert_eq!(schema.rules.len(), 1);
2142 }
2143
2144 #[test]
2145 fn test_parse_with_ns() {
2146 let schema_xml = r#"<?xml version="1.0"?>
2147<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2148 <ns prefix="doc" uri="http://example.com/doc"/>
2149 <pattern id="P1">
2150 <rule context="doc:entry">
2151 <assert test="doc:title">Entry must have a title</assert>
2152 </rule>
2153 </pattern>
2154</schema>"#;
2155
2156 let result = schematron_parse(schema_xml);
2157 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2158 let schema = result.unwrap();
2159 assert!(schema.ns.contains_key("doc"));
2160 assert_eq!(schema.ns.get("doc").unwrap(), "http://example.com/doc");
2161 }
2162
2163 #[test]
2164 fn test_parse_with_phases() {
2165 let schema_xml = r#"<?xml version="1.0"?>
2166<schema xmlns="http://purl.oclc.org/dsdl/schematron" defaultPhase="phaseA">
2167 <phase id="phaseA">
2168 <active pattern="P1"/>
2169 </phase>
2170 <phase id="phaseB">
2171 <active pattern="P2"/>
2172 </phase>
2173 <pattern id="P1">
2174 <rule context="root">
2175 <assert test="true()">Always passes</assert>
2176 </rule>
2177 </pattern>
2178 <pattern id="P2">
2179 <rule context="root">
2180 <assert test="false()">Always fails</assert>
2181 </rule>
2182 </pattern>
2183</schema>"#;
2184
2185 let result = schematron_parse(schema_xml);
2186 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2187 let schema = result.unwrap();
2188 assert_eq!(schema.phases.len(), 2);
2189 assert!(schema.phases.contains_key("phaseA"));
2190 assert!(schema.phases.contains_key("phaseB"));
2191 assert_eq!(schema.default_phase.as_deref(), Some("phaseA"));
2192 }
2193
2194 #[test]
2195 fn test_parse_report_pattern() {
2196 let schema_xml = r#"<?xml version="1.0"?>
2197<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2198 <pattern id="P1">
2199 <rule context="root">
2200 <report test="@deprecated">Element is deprecated</report>
2201 </rule>
2202 </pattern>
2203</schema>"#;
2204
2205 let result = schematron_parse(schema_xml);
2206 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2207 let schema = result.unwrap();
2208 let rule = schema.rules.values().next().unwrap();
2209 assert_eq!(rule.patterns.len(), 1);
2210 assert_eq!(rule.patterns[0].pattern_type, SchematronPatternType::Report);
2211 assert_eq!(rule.patterns[0].test, "@deprecated");
2212 }
2213
2214 #[test]
2215 fn test_parse_abstract_rule() {
2216 let schema_xml = r#"<?xml version="1.0"?>
2217<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2218 <pattern id="P1">
2219 <rule id="abstractRule" abstract="true" context="*">
2220 <assert test="true()">Abstract assertion</assert>
2221 </rule>
2222 <rule id="concreteRule" context="root">
2223 <extends rule="abstractRule"/>
2224 <assert test="count(*) > 0">Concrete assertion</assert>
2225 </rule>
2226 </pattern>
2227</schema>"#;
2228
2229 let result = schematron_parse(schema_xml);
2230 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2231 let schema = result.unwrap();
2232 assert!(schema.rules.contains_key("abstractRule"));
2233 assert!(schema.rules.contains_key("concreteRule"));
2234 let abstract_rule = &schema.rules["abstractRule"];
2235 assert!(abstract_rule.abstract_);
2236 let concrete_rule = &schema.rules["concreteRule"];
2237 assert!(!concrete_rule.abstract_);
2238 assert_eq!(concrete_rule.extends.len(), 1);
2239 assert_eq!(concrete_rule.extends[0], "abstractRule");
2240 }
2241
2242 #[test]
2243 fn test_parse_with_diagnostics() {
2244 let schema_xml = r#"<?xml version="1.0"?>
2245<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2246 <diagnostics>
2247 <diagnostic id="diag1">This is a diagnostic message</diagnostic>
2248 </diagnostics>
2249 <pattern id="P1">
2250 <rule context="root">
2251 <assert test="true()" diagnostics="diag1">Assertion with diagnostic</assert>
2252 </rule>
2253 </pattern>
2254</schema>"#;
2255
2256 let result = schematron_parse(schema_xml);
2257 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2258 let schema = result.unwrap();
2259 assert!(schema.diagnostics.contains_key("diag1"));
2260 assert_eq!(
2261 schema.diagnostics["diag1"].text,
2262 "This is a diagnostic message"
2263 );
2264 }
2265
2266 #[test]
2267 fn test_parse_with_attributes() {
2268 let schema_xml = r#"<?xml version="1.0"?>
2269<schema xmlns="http://purl.oclc.org/dsdl/schematron" title="Test Schema">
2270 <pattern id="P1">
2271 <rule context="root">
2272 <assert test="true()" flag="warn" role="error" id="a1" icon="info" see="http://example.com">
2273 Test message
2274 </assert>
2275 </rule>
2276 </pattern>
2277</schema>"#;
2278
2279 let result = schematron_parse(schema_xml);
2280 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2281 let schema = result.unwrap();
2282 assert_eq!(schema.title.as_deref(), Some("Test Schema"));
2283 let rule = schema.rules.values().next().unwrap();
2284 let pat = &rule.patterns[0];
2285 assert_eq!(pat.flag.as_deref(), Some("warn"));
2286 assert_eq!(pat.role.as_deref(), Some("error"));
2287 assert_eq!(pat.id.as_deref(), Some("a1"));
2288 assert_eq!(pat.icon.as_deref(), Some("info"));
2289 assert_eq!(pat.see.as_deref(), Some("http://example.com"));
2290 }
2291
2292 #[test]
2293 fn test_parse_empty_schema() {
2294 let schema_xml = r#"<?xml version="1.0"?>
2295<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2296</schema>"#;
2297
2298 let result = schematron_parse(schema_xml);
2299 assert!(result.is_ok(), "Failed to parse empty schema");
2300 let schema = result.unwrap();
2301 assert!(schema.rules.is_empty());
2302 assert!(schema.phases.is_empty());
2303 }
2304
2305 #[test]
2306 fn test_parse_no_assertions() {
2307 let schema_xml = r#"<?xml version="1.0"?>
2308<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2309 <pattern id="P1">
2310 <rule context="root">
2311 </rule>
2312 </pattern>
2313</schema>"#;
2314
2315 let result = schematron_parse(schema_xml);
2316 assert!(result.is_ok(), "Failed to parse schema with no assertions");
2317 let schema = result.unwrap();
2318 let rule = schema.rules.values().next().unwrap();
2319 assert!(rule.patterns.is_empty());
2320 }
2321
2322 #[test]
2323 fn test_parse_invalid_root_element() {
2324 let schema_xml = r#"<?xml version="1.0"?>
2325<not-schema xmlns="http://purl.oclc.org/dsdl/schematron">
2326</not-schema>"#;
2327
2328 let result = schematron_parse(schema_xml);
2329 assert!(result.is_err(), "Should fail with wrong root element");
2330 assert!(
2331 result.err().unwrap().contains("Expected '<schema>'"),
2332 "Error should mention expected schema element"
2333 );
2334 }
2335
2336 #[test]
2337 fn test_parse_empty_document_fails() {
2338 let result = schematron_parse("");
2339 assert!(result.is_err());
2340 }
2341
2342 #[test]
2343 fn test_parse_invalid_xml_fails() {
2344 let result = schematron_parse("not valid xml <<<");
2345 assert!(result.is_err());
2346 }
2347
2348 #[test]
2349 fn test_parse_schema_with_let_and_param() {
2350 let schema_xml = r#"<?xml version="1.0"?>
2351<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2352 <pattern id="P1">
2353 <rule context="root">
2354 <let name="x" value="42"/>
2355 <param name="debug" value="true"/>
2356 <assert test="true()">Test with let and param</assert>
2357 </rule>
2358 </pattern>
2359</schema>"#;
2360
2361 let result = schematron_parse(schema_xml);
2362 assert!(
2363 result.is_ok(),
2364 "Failed to parse schema with let/param: {:?}",
2365 result.err()
2366 );
2367 let schema = result.unwrap();
2368 assert_eq!(schema.rules.len(), 1);
2369 }
2370
2371 #[test]
2372 fn test_parse_schema_with_documentation() {
2373 let schema_xml = r#"<?xml version="1.0"?>
2374<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2375 <p>This is documentation</p>
2376 <caption>Table caption</caption>
2377 <pattern id="P1">
2378 <p>Pattern documentation</p>
2379 <rule context="root">
2380 <p>Rule documentation</p>
2381 <assert test="true()">Real assertion</assert>
2382 </rule>
2383 </pattern>
2384</schema>"#;
2385
2386 let result = schematron_parse(schema_xml);
2387 assert!(result.is_ok(), "Failed to parse schema with documentation");
2388 let schema = result.unwrap();
2389 assert_eq!(schema.rules.len(), 1);
2390 }
2391
2392 #[test]
2403 fn test_validate_assert_pass() {
2404 let schema_xml = r#"<?xml version="1.0"?>
2405<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2406 <pattern id="P1">
2407 <rule context="root">
2408 <assert test="true()">Always passes</assert>
2409 </rule>
2410 </pattern>
2411</schema>"#;
2412
2413 let doc_xml = r#"<?xml version="1.0"?>
2414<root>Hello</root>"#;
2415
2416 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2417
2418 let doc = unsafe {
2419 crate::abi::exports_xml2::xmlReadMemory(
2420 doc_xml.as_ptr() as *const c_char,
2421 doc_xml.len() as c_int,
2422 c"test.xml".as_ptr() as *const c_char,
2423 ptr::null(),
2424 0,
2425 )
2426 };
2427 assert!(!doc.is_null(), "Failed to parse document");
2428
2429 let mut ctxt = SchematronValidCtxt::new();
2430 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2431 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2432
2433 assert!(valid, "Validation failed: {:?}", ctxt.errors);
2434 }
2435
2436 #[test]
2445 fn test_validate_assert_fail() {
2446 let schema_xml = r#"<?xml version="1.0"?>
2447<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2448 <pattern id="P1">
2449 <rule context="root">
2450 <assert test="false()">Always fails</assert>
2451 </rule>
2452 </pattern>
2453</schema>"#;
2454
2455 let doc_xml = r#"<?xml version="1.0"?>
2456<root>Hello</root>"#;
2457
2458 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2459
2460 let doc = unsafe {
2461 crate::abi::exports_xml2::xmlReadMemory(
2462 doc_xml.as_ptr() as *const c_char,
2463 doc_xml.len() as c_int,
2464 c"test.xml".as_ptr() as *const c_char,
2465 ptr::null(),
2466 0,
2467 )
2468 };
2469 assert!(!doc.is_null());
2470
2471 let mut ctxt = SchematronValidCtxt::new();
2472 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2473 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2474
2475 assert!(
2476 !valid,
2477 "Validation should have failed, errors: {:?}",
2478 ctxt.errors
2479 );
2480 assert!(ctxt.nb_errors > 0);
2481 }
2482
2483 #[test]
2492 fn test_validate_report_pass() {
2493 let schema_xml = r#"<?xml version="1.0"?>
2494<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2495 <pattern id="P1">
2496 <rule context="root">
2497 <report test="false()">Report should not trigger</report>
2498 </rule>
2499 </pattern>
2500</schema>"#;
2501
2502 let doc_xml = r#"<?xml version="1.0"?>
2503<root>Hello</root>"#;
2504
2505 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2506
2507 let doc = unsafe {
2508 crate::abi::exports_xml2::xmlReadMemory(
2509 doc_xml.as_ptr() as *const c_char,
2510 doc_xml.len() as c_int,
2511 c"test.xml".as_ptr() as *const c_char,
2512 ptr::null(),
2513 0,
2514 )
2515 };
2516 assert!(!doc.is_null());
2517
2518 let mut ctxt = SchematronValidCtxt::new();
2519 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2520 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2521
2522 assert!(valid, "Report should not trigger: {:?}", ctxt.errors);
2523 }
2524
2525 #[test]
2534 fn test_validate_report_fail() {
2535 let schema_xml = r#"<?xml version="1.0"?>
2536<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2537 <pattern id="P1">
2538 <rule context="root">
2539 <report test="true()">Report should trigger</report>
2540 </rule>
2541 </pattern>
2542</schema>"#;
2543
2544 let doc_xml = r#"<?xml version="1.0"?>
2545<root>Hello</root>"#;
2546
2547 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2548
2549 let doc = unsafe {
2550 crate::abi::exports_xml2::xmlReadMemory(
2551 doc_xml.as_ptr() as *const c_char,
2552 doc_xml.len() as c_int,
2553 c"test.xml".as_ptr() as *const c_char,
2554 ptr::null(),
2555 0,
2556 )
2557 };
2558 assert!(!doc.is_null());
2559
2560 let mut ctxt = SchematronValidCtxt::new();
2561 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2562 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2563
2564 assert!(!valid, "Report should have triggered");
2565 assert!(ctxt.nb_errors > 0);
2566 }
2567
2568 #[test]
2577 fn test_validate_context_matching() {
2578 let schema_xml = r#"<?xml version="1.0"?>
2579<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2580 <pattern id="P1">
2581 <rule context="child">
2582 <assert test="true()">Child matches</assert>
2583 </rule>
2584 </pattern>
2585</schema>"#;
2586
2587 let doc_xml = r#"<?xml version="1.0"?>
2588<root>
2589 <child>A</child>
2590 <child>B</child>
2591</root>"#;
2592
2593 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2594
2595 let doc = unsafe {
2596 crate::abi::exports_xml2::xmlReadMemory(
2597 doc_xml.as_ptr() as *const c_char,
2598 doc_xml.len() as c_int,
2599 c"test.xml".as_ptr() as *const c_char,
2600 ptr::null(),
2601 0,
2602 )
2603 };
2604 assert!(!doc.is_null());
2605
2606 let mut ctxt = SchematronValidCtxt::new();
2607 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2608 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2609
2610 assert!(valid, "Context matching failed: {:?}", ctxt.errors);
2611 }
2612
2613 #[test]
2622 fn test_validate_multiple_rules() {
2623 let schema_xml = r#"<?xml version="1.0"?>
2624<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2625 <pattern id="P1">
2626 <rule context="root">
2627 <assert test="true()">Root passes</assert>
2628 </rule>
2629 </pattern>
2630 <pattern id="P2">
2631 <rule context="child">
2632 <assert test="true()">Child passes</assert>
2633 </rule>
2634 </pattern>
2635</schema>"#;
2636
2637 let doc_xml = r#"<?xml version="1.0"?>
2638<root>
2639 <child>Content</child>
2640</root>"#;
2641
2642 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2643
2644 let doc = unsafe {
2645 crate::abi::exports_xml2::xmlReadMemory(
2646 doc_xml.as_ptr() as *const c_char,
2647 doc_xml.len() as c_int,
2648 c"test.xml".as_ptr() as *const c_char,
2649 ptr::null(),
2650 0,
2651 )
2652 };
2653 assert!(!doc.is_null());
2654
2655 let mut ctxt = SchematronValidCtxt::new();
2656 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2657 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2658
2659 assert!(valid, "Multiple rules failed: {:?}", ctxt.errors);
2660 }
2661
2662 #[test]
2671 fn test_validate_with_phase_filtering() {
2672 let schema_xml = r#"<?xml version="1.0"?>
2673<schema xmlns="http://purl.oclc.org/dsdl/schematron" defaultPhase="phaseA">
2674 <phase id="phaseA">
2675 <active pattern="P1"/>
2676 </phase>
2677 <phase id="phaseB">
2678 <active pattern="P2"/>
2679 </phase>
2680 <pattern id="P1">
2681 <rule context="root">
2682 <assert test="true()">Always passes</assert>
2683 </rule>
2684 </pattern>
2685 <pattern id="P2">
2686 <rule context="root">
2687 <assert test="false()">Always fails</assert>
2688 </rule>
2689 </pattern>
2690</schema>"#;
2691
2692 let doc_xml = r#"<?xml version="1.0"?>
2693<root>Hello</root>"#;
2694
2695 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2696
2697 let doc = unsafe {
2698 crate::abi::exports_xml2::xmlReadMemory(
2699 doc_xml.as_ptr() as *const c_char,
2700 doc_xml.len() as c_int,
2701 c"test.xml".as_ptr() as *const c_char,
2702 ptr::null(),
2703 0,
2704 )
2705 };
2706 assert!(!doc.is_null());
2707
2708 let mut ctxt = SchematronValidCtxt::new();
2710 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2711 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2712
2713 assert!(
2714 valid,
2715 "Phase filtering should make validation pass: {:?}",
2716 ctxt.errors
2717 );
2718 }
2719
2720 #[test]
2729 fn test_validate_no_rules() {
2730 let schema_xml = r#"<?xml version="1.0"?>
2731<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2732</schema>"#;
2733
2734 let doc_xml = r#"<?xml version="1.0"?>
2735<root>Hello</root>"#;
2736
2737 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2738
2739 let doc = unsafe {
2740 crate::abi::exports_xml2::xmlReadMemory(
2741 doc_xml.as_ptr() as *const c_char,
2742 doc_xml.len() as c_int,
2743 c"test.xml".as_ptr() as *const c_char,
2744 ptr::null(),
2745 0,
2746 )
2747 };
2748 assert!(!doc.is_null());
2749
2750 let mut ctxt = SchematronValidCtxt::new();
2751 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2752 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2753
2754 assert!(valid, "Empty schema should pass validation");
2755 }
2756
2757 #[test]
2766 fn test_validate_extends_resolution() {
2767 let schema_xml = r#"<?xml version="1.0"?>
2768<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2769 <pattern id="P1">
2770 <rule id="base" abstract="true" context="*">
2771 <assert test="true()">Base assertion</assert>
2772 </rule>
2773 <rule id="derived" context="root">
2774 <extends rule="base"/>
2775 <assert test="true()">Derived assertion</assert>
2776 </rule>
2777 </pattern>
2778</schema>"#;
2779
2780 let doc_xml = r#"<?xml version="1.0"?>
2781<root>Hello</root>"#;
2782
2783 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2784
2785 let resolved = schema.resolve_rule("derived");
2787 assert!(resolved.is_some());
2788 let resolved = resolved.unwrap();
2789 assert_eq!(
2791 resolved.patterns.len(),
2792 2,
2793 "Should have inherited the base pattern"
2794 );
2795
2796 let doc = unsafe {
2797 crate::abi::exports_xml2::xmlReadMemory(
2798 doc_xml.as_ptr() as *const c_char,
2799 doc_xml.len() as c_int,
2800 c"test.xml".as_ptr() as *const c_char,
2801 ptr::null(),
2802 0,
2803 )
2804 };
2805 assert!(!doc.is_null());
2806
2807 let mut ctxt = SchematronValidCtxt::new();
2808 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2809 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2810
2811 assert!(valid, "Extends resolution failed: {:?}", ctxt.errors);
2812 }
2813
2814 #[test]
2823 fn test_validate_assert_with_flag() {
2824 let schema_xml = r#"<?xml version="1.0"?>
2825<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2826 <pattern id="P1">
2827 <rule context="root">
2828 <assert test="false()" flag="warn">Warning message</assert>
2829 </rule>
2830 </pattern>
2831</schema>"#;
2832
2833 let doc_xml = r#"<?xml version="1.0"?>
2834<root>Hello</root>"#;
2835
2836 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2837
2838 let doc = unsafe {
2839 crate::abi::exports_xml2::xmlReadMemory(
2840 doc_xml.as_ptr() as *const c_char,
2841 doc_xml.len() as c_int,
2842 c"test.xml".as_ptr() as *const c_char,
2843 ptr::null(),
2844 0,
2845 )
2846 };
2847 assert!(!doc.is_null());
2848
2849 let mut ctxt = SchematronValidCtxt::new();
2850 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2851 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2852
2853 assert!(!valid);
2854 assert!(ctxt.nb_errors > 0);
2855 assert!(
2857 ctxt.errors[0].contains("[warn]"),
2858 "Error should include flag"
2859 );
2860 }
2861
2862 #[test]
2872 fn test_c_abi_new_free_parser_ctxt() {
2873 let ctxt = unsafe { xmlSchematronNewParserCtxt(ptr::null()) };
2874 assert!(!ctxt.is_null());
2875 unsafe { xmlSchematronFreeParserCtxt(ctxt) };
2876 }
2878
2879 #[test]
2890 fn test_c_abi_new_free_valid_ctxt() {
2891 let schema = unsafe { xmlSchematronNewParserCtxt(ptr::null()) };
2892 assert!(!schema.is_null());
2893
2894 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema, 0) };
2895 assert!(!valid_ctxt.is_null());
2896
2897 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2898 unsafe { xmlSchematronFreeParserCtxt(schema) };
2899 }
2901
2902 #[test]
2912 fn test_c_abi_parse_free() {
2913 let schema_xml = r#"<?xml version="1.0"?>
2914<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2915 <pattern id="P1">
2916 <rule context="root">
2917 <assert test="true()">Test</assert>
2918 </rule>
2919 </pattern>
2920</schema>"#;
2921
2922 let ctxt = unsafe {
2923 xmlSchematronNewMemParserCtxt(
2924 schema_xml.as_ptr() as *const c_char,
2925 schema_xml.len() as c_int,
2926 )
2927 };
2928 assert!(!ctxt.is_null());
2929
2930 let schema = unsafe { xmlSchematronParse(ctxt) };
2931 assert!(!schema.is_null());
2932
2933 unsafe { xmlSchematronFree(schema) };
2934 }
2936
2937 #[test]
2949 fn test_c_abi_validate_doc() {
2950 let schema_xml = r#"<?xml version="1.0"?>
2951<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2952 <pattern id="P1">
2953 <rule context="root">
2954 <assert test="true()">Always passes</assert>
2955 </rule>
2956 </pattern>
2957</schema>"#;
2958
2959 let doc_xml = r#"<?xml version="1.0"?>
2960<root>Hello</root>"#;
2961
2962 let ctxt = unsafe {
2963 xmlSchematronNewMemParserCtxt(
2964 schema_xml.as_ptr() as *const c_char,
2965 schema_xml.len() as c_int,
2966 )
2967 };
2968 assert!(!ctxt.is_null());
2969
2970 let schema = unsafe { xmlSchematronParse(ctxt) };
2971 assert!(!schema.is_null());
2972
2973 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema, 0) };
2974 assert!(!valid_ctxt.is_null());
2975
2976 let doc = unsafe {
2977 crate::abi::exports_xml2::xmlReadMemory(
2978 doc_xml.as_ptr() as *const c_char,
2979 doc_xml.len() as c_int,
2980 c"test.xml".as_ptr() as *const c_char,
2981 ptr::null(),
2982 0,
2983 )
2984 };
2985 assert!(!doc.is_null());
2986
2987 let result = unsafe { xmlSchematronValidateDoc(valid_ctxt, doc) };
2988 assert_eq!(result, 0, "Validation should pass (return 0)");
2989
2990 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2991 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2992 unsafe { xmlSchematronFree(schema) };
2993 }
2994
2995 #[test]
3007 fn test_c_abi_validate_fail() {
3008 let schema_xml = r#"<?xml version="1.0"?>
3009<schema xmlns="http://purl.oclc.org/dsdl/schematron">
3010 <pattern id="P1">
3011 <rule context="root">
3012 <assert test="false()">Always fails</assert>
3013 </rule>
3014 </pattern>
3015</schema>"#;
3016
3017 let doc_xml = r#"<?xml version="1.0"?>
3018<root>Hello</root>"#;
3019
3020 let ctxt = unsafe {
3021 xmlSchematronNewMemParserCtxt(
3022 schema_xml.as_ptr() as *const c_char,
3023 schema_xml.len() as c_int,
3024 )
3025 };
3026 assert!(!ctxt.is_null());
3027
3028 let schema = unsafe { xmlSchematronParse(ctxt) };
3029 assert!(!schema.is_null());
3030
3031 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema, 0) };
3032 assert!(!valid_ctxt.is_null());
3033
3034 let doc = unsafe {
3035 crate::abi::exports_xml2::xmlReadMemory(
3036 doc_xml.as_ptr() as *const c_char,
3037 doc_xml.len() as c_int,
3038 c"test.xml".as_ptr() as *const c_char,
3039 ptr::null(),
3040 0,
3041 )
3042 };
3043 assert!(!doc.is_null());
3044
3045 let result = unsafe { xmlSchematronValidateDoc(valid_ctxt, doc) };
3046 assert!(result > 0, "Validation should fail (return > 0)");
3047
3048 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3049 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
3050 unsafe { xmlSchematronFree(schema) };
3051 }
3052
3053 #[test]
3061 fn test_c_abi_null_handling() {
3062 unsafe { xmlSchematronFree(ptr::null_mut()) };
3064 unsafe { xmlSchematronFreeParserCtxt(ptr::null_mut()) };
3065 unsafe { xmlSchematronFreeValidCtxt(ptr::null_mut()) };
3066
3067 let result = unsafe { xmlSchematronParse(ptr::null_mut()) };
3069 assert!(result.is_null());
3070
3071 let result = unsafe { xmlSchematronValidateDoc(ptr::null_mut(), ptr::null_mut()) };
3073 assert_eq!(result, -1);
3074 }
3075
3076 #[test]
3086 fn test_validate_null_doc() {
3087 let schema = SchematronSchema::new();
3088 let mut ctxt = SchematronValidCtxt::new();
3089 let valid = unsafe { schematron_validate_doc(&schema, ptr::null_mut(), &mut ctxt) };
3090 assert!(!valid);
3091 assert!(ctxt.nb_errors > 0);
3092 }
3093
3094 #[test]
3095 fn test_active_rules_default_phase() {
3096 let mut schema = SchematronSchema::new();
3097
3098 let rule = SchematronRule::new("root".to_string());
3099 schema.rules.insert("r1".to_string(), rule);
3100
3101 schema
3102 .pattern_groups
3103 .insert("p1".to_string(), vec!["r1".to_string()]);
3104 schema.pattern_order.push("p1".to_string());
3105
3106 let rules = schema.active_rules(None);
3107 assert_eq!(rules.len(), 1);
3108 }
3109
3110 #[test]
3111 fn test_active_rules_unknown_phase() {
3112 let mut schema = SchematronSchema::new();
3113
3114 let rule = SchematronRule::new("root".to_string());
3115 schema.rules.insert("r1".to_string(), rule);
3116
3117 schema
3118 .pattern_groups
3119 .insert("p1".to_string(), vec!["r1".to_string()]);
3120 schema.pattern_order.push("p1".to_string());
3121
3122 let rules = schema.active_rules(Some("nonexistent"));
3123 assert_eq!(rules.len(), 1, "Unknown phase should use all patterns");
3124 }
3125
3126 #[test]
3127 fn test_parse_schema_with_span_and_emph() {
3128 let schema_xml = r#"<?xml version="1.0"?>
3129<schema xmlns="http://purl.oclc.org/dsdl/schematron">
3130 <pattern id="P1">
3131 <rule context="root">
3132 <assert test="true()">Message with <span class="x">inline</span> and <emph>emphasis</emph></assert>
3133 </rule>
3134 </pattern>
3135</schema>"#;
3136
3137 let result = schematron_parse(schema_xml);
3138 assert!(
3139 result.is_ok(),
3140 "Failed to parse schema with span/emph: {:?}",
3141 result.err()
3142 );
3143 let schema = result.unwrap();
3144 let rule = schema.rules.values().next().unwrap();
3145 let pat = &rule.patterns[0];
3146 assert!(
3148 pat.text.contains("inline"),
3149 "Text should include span content"
3150 );
3151 assert!(
3152 pat.text.contains("emphasis"),
3153 "Text should include emph content"
3154 );
3155 }
3156
3157 #[test]
3158 fn test_schematron_pattern_new_assert() {
3159 let pat = SchematronPattern::new(
3160 SchematronPatternType::Assert,
3161 "true()".to_string(),
3162 "Test message".to_string(),
3163 );
3164 assert_eq!(pat.pattern_type, SchematronPatternType::Assert);
3165 assert_eq!(pat.test, "true()");
3166 assert_eq!(pat.text, "Test message");
3167 assert!(pat.compiled_test.is_some());
3168 }
3169
3170 #[test]
3171 fn test_schematron_pattern_new_report() {
3172 let pat = SchematronPattern::new(
3173 SchematronPatternType::Report,
3174 "false()".to_string(),
3175 "Report message".to_string(),
3176 );
3177 assert_eq!(pat.pattern_type, SchematronPatternType::Report);
3178 assert!(pat.compiled_test.is_some());
3179 }
3180
3181 #[test]
3182 fn test_schematron_rule_new() {
3183 let rule = SchematronRule::new("root".to_string());
3184 assert_eq!(rule.context, "root");
3185 assert!(rule.patterns.is_empty());
3186 assert!(!rule.abstract_);
3187 }
3188
3189 #[test]
3190 fn test_schematron_schema_new() {
3191 let schema = SchematronSchema::new();
3192 assert_eq!(schema.query_binding, "xslt");
3193 assert!(schema.rules.is_empty());
3194 assert!(schema.phases.is_empty());
3195 assert!(schema.ns.is_empty());
3196 }
3197
3198 #[test]
3199 fn test_schematron_valid_ctxt_new() {
3200 let ctxt = SchematronValidCtxt::new();
3201 assert!(ctxt.errors.is_empty());
3202 assert_eq!(ctxt.nb_errors, 0);
3203 assert!(ctxt.active_phase.is_none());
3204 }
3205
3206 #[test]
3215 fn test_validate_assert_with_child_count() {
3216 let schema_xml = r#"<?xml version="1.0"?>
3217<schema xmlns="http://purl.oclc.org/dsdl/schematron">
3218 <pattern id="P1">
3219 <rule context="root">
3220 <assert test="count(*) > 0">Root must have at least one child element</assert>
3221 </rule>
3222 </pattern>
3223</schema>"#;
3224
3225 let doc_xml = r#"<?xml version="1.0"?>
3226<root>
3227 <child>Content</child>
3228</root>"#;
3229
3230 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
3231
3232 let doc = unsafe {
3233 crate::abi::exports_xml2::xmlReadMemory(
3234 doc_xml.as_ptr() as *const c_char,
3235 doc_xml.len() as c_int,
3236 c"test.xml".as_ptr() as *const c_char,
3237 ptr::null(),
3238 0,
3239 )
3240 };
3241 assert!(!doc.is_null());
3242
3243 let mut ctxt = SchematronValidCtxt::new();
3244 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
3245 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3246
3247 assert!(valid, "Child count check failed: {:?}", ctxt.errors);
3248 }
3249}