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> {
641 let doc_ptr = unsafe {
642 crate::abi::exports_xml2::xmlReadMemory(
643 xml_doc.as_ptr() as *const c_char,
644 xml_doc.len() as c_int,
645 c"schema.sch".as_ptr() as *const c_char,
646 ptr::null(),
647 0,
648 )
649 };
650
651 if doc_ptr.is_null() {
652 return Err("Failed to parse Schematron schema XML document".to_string());
653 }
654
655 let result = unsafe { schematron_parse_doc(doc_ptr) };
656 unsafe {
657 crate::abi::exports_xml2::xmlFreeDoc(doc_ptr);
658 }
659 result
660}
661
662unsafe fn schematron_parse_doc(doc: *mut _xmlDoc) -> Result<SchematronSchema, String> {
668 unsafe {
669 let root = (*doc).children;
670 if root.is_null() {
671 return Err("Schematron document has no root element".to_string());
672 }
673
674 let mut root_elem = root;
676 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
677 root_elem = (*root_elem).next;
678 }
679
680 if root_elem.is_null() {
681 return Err("Schematron document has no root element".to_string());
682 }
683
684 let local_name = get_local_name(root_elem);
685 if local_name != "schema" {
686 return Err(format!(
687 "Expected '<schema>' root element, found '<{}>'",
688 local_name
689 ));
690 }
691
692 Ok(schematron_parse_schema_node(root_elem))
693 }
694}
695
696unsafe fn schematron_parse_schema_node(node: *mut _xmlNode) -> SchematronSchema {
702 unsafe {
703 let mut schema = SchematronSchema::new();
704
705 if let Some(qb) = get_attr(node, "queryBinding") {
707 schema.query_binding = qb;
708 }
709 schema.title = get_attr(node, "title");
710 if let Some(df) = get_attr(node, "defaultPhase") {
711 schema.default_phase = Some(df);
712 }
713
714 let mut current_pattern_id: Option<String> = None;
716 let mut pattern_names: HashMap<String, Vec<String>> = HashMap::new();
717
718 let mut child = (*node).children;
719 while !child.is_null() {
720 if (*child).type_ == XML_ELEMENT_NODE as c_int {
721 let local = get_local_name(child);
722 match local.as_str() {
723 "title" => {
724 if schema.title.is_none() {
725 schema.title = Some(get_node_text(child).trim().to_string());
726 }
727 }
728 "ns" => {
729 let prefix = get_attr(child, "prefix").unwrap_or_default();
730 let uri = get_attr(child, "uri").unwrap_or_default();
731 if !prefix.is_empty() && !uri.is_empty() {
732 schema.ns.insert(prefix, uri);
733 }
734 }
735 "phase" => {
736 let phase = schematron_parse_phase(child);
737 schema.phases.insert(phase.id.clone(), phase);
738 }
739 "pattern" => {
740 let pat_id = schematron_parse_pattern_node(
741 child,
742 &mut schema,
743 &mut current_pattern_id,
744 &mut pattern_names,
745 );
746 current_pattern_id = pat_id;
747 }
748 "rule" => {
749 let rule = schematron_parse_rule(child, &mut schema);
751 let rule_id = rule
752 .id
753 .clone()
754 .unwrap_or_else(|| format!("_rule_{}", schema.rules.len()));
755 let rid = rule_id.clone();
757 schema.rules.insert(rid, rule);
758
759 if let Some(ref pid) = current_pattern_id {
761 schema
762 .pattern_groups
763 .entry(pid.clone())
764 .or_default()
765 .push(rule_id);
766 } else {
767 let anon_id = format!("_anon_{}", schema.pattern_order.len());
769 schema
770 .pattern_groups
771 .entry(anon_id.clone())
772 .or_default()
773 .push(rule_id);
774 if !schema.pattern_order.contains(&anon_id) {
775 schema.pattern_order.push(anon_id);
776 }
777 }
778 }
779 "diagnostics" => {
780 schematron_parse_diagnostics(child, &mut schema);
781 }
782 "include" => {
783 schematron_parse_include(child, &mut schema);
784 }
785 "p" | "caption" => {
786 }
788 _ => {
789 schema
790 .errors
791 .push(format!("Unexpected element '<{}>' in schema", local));
792 }
793 }
794 }
795 child = (*child).next;
796 }
797
798 schema
799 }
800}
801
802unsafe fn schematron_parse_pattern_node(
808 node: *mut _xmlNode,
809 schema: &mut SchematronSchema,
810 _current_pattern_id: &mut Option<String>,
811 _pattern_names: &mut HashMap<String, Vec<String>>,
812) -> Option<String> {
813 unsafe {
814 let pat_id = get_attr(node, "id");
815 let pat_name = get_attr(node, "name");
816 let pat_is_a = get_attr(node, "is-a");
817 let pat_see = get_attr(node, "see");
818 let pat_icon = get_attr(node, "icon");
819 let pat_role = get_attr(node, "role");
820
821 let pid = pat_id
822 .clone()
823 .unwrap_or_else(|| format!("_pattern_{}", schema.pattern_order.len()));
824
825 let mut rule_ids: Vec<String> = Vec::new();
826
827 let mut child = (*node).children;
829 while !child.is_null() {
830 if (*child).type_ == XML_ELEMENT_NODE as c_int {
831 let local = get_local_name(child);
832 match local.as_str() {
833 "rule" => {
834 let rule = schematron_parse_rule(child, schema);
835 let rule_id = rule
836 .id
837 .clone()
838 .unwrap_or_else(|| format!("_rule_{}", schema.rules.len()));
839 let rid = rule_id.clone();
840 schema.rules.insert(rid, rule);
841 rule_ids.push(rule_id);
842 }
843 "p" | "caption" => {
844 }
846 _ => {
847 schema
848 .errors
849 .push(format!("Unexpected element '<{}>' in pattern", local));
850 }
851 }
852 }
853 child = (*child).next;
854 }
855
856 schema.pattern_groups.insert(pid.clone(), rule_ids);
857 schema.pattern_order.push(pid.clone());
858
859 if pat_is_a.is_some() {
861 }
864
865 let _ = pat_name;
867 let _ = pat_see;
868 let _ = pat_icon;
869 let _ = pat_role;
870
871 Some(pid)
872 }
873}
874
875unsafe fn schematron_parse_rule(
881 node: *mut _xmlNode,
882 schema: &mut SchematronSchema,
883) -> SchematronRule {
884 unsafe {
885 let context = get_attr(node, "context").unwrap_or_default();
886 let mut rule = SchematronRule::new(context);
887 rule.id = get_attr(node, "id");
888
889 let abs = get_attr(node, "abstract").unwrap_or_default();
890 rule.abstract_ = abs == "true" || abs == "1";
891
892 let mut child = (*node).children;
894 while !child.is_null() {
895 if (*child).type_ == XML_ELEMENT_NODE as c_int {
896 let local = get_local_name(child);
897 match local.as_str() {
898 "assert" => {
899 let pattern = schematron_parse_assert(child, SchematronPatternType::Assert);
900 rule.patterns.push(pattern);
901 }
902 "report" => {
903 let pattern = schematron_parse_assert(child, SchematronPatternType::Report);
904 rule.patterns.push(pattern);
905 }
906 "extends" => {
907 if let Some(ext_rule) = get_attr(child, "rule") {
908 rule.extends.push(ext_rule);
909 }
910 }
911 "let" => {
912 let name = get_attr(child, "name").unwrap_or_default();
915 let value = get_attr(child, "value").unwrap_or_default();
916 if !name.is_empty() {
917 let _ = value;
920 }
921 }
922 "param" => {
923 let _name = get_attr(child, "name");
925 let _value = get_attr(child, "value");
926 }
927 "p" | "caption" => {
928 }
930 _ => {
931 schema
932 .errors
933 .push(format!("Unexpected element '<{}>' in rule", local));
934 }
935 }
936 }
937 child = (*child).next;
938 }
939
940 rule
941 }
942}
943
944unsafe fn schematron_parse_assert(
950 node: *mut _xmlNode,
951 pattern_type: SchematronPatternType,
952) -> SchematronPattern {
953 unsafe {
954 let test = get_attr(node, "test").unwrap_or_default();
955 let text = get_inline_text(node);
956
957 let mut pattern = SchematronPattern::new(pattern_type, test, text);
958 pattern.flag = get_attr(node, "flag");
959 pattern.id = get_attr(node, "id");
960 pattern.icon = get_attr(node, "icon");
961 pattern.see = get_attr(node, "see");
962 pattern.role = get_attr(node, "role");
963 pattern.diagnostics = get_attr(node, "diagnostics");
964
965 pattern
969 }
970}
971
972unsafe fn schematron_parse_phase(node: *mut _xmlNode) -> SchematronPhase {
978 unsafe {
979 let id = get_attr(node, "id").unwrap_or_default();
980 let mut phase = SchematronPhase {
981 id,
982 active_patterns: Vec::new(),
983 };
984
985 let mut child = (*node).children;
986 while !child.is_null() {
987 if (*child).type_ == XML_ELEMENT_NODE as c_int {
988 let local = get_local_name(child);
989 if local == "active" {
990 if let Some(pattern) = get_attr(child, "pattern") {
991 phase.active_patterns.push(pattern);
992 }
993 }
994 }
995 child = (*child).next;
996 }
997
998 phase
999 }
1000}
1001
1002unsafe fn schematron_parse_diagnostics(node: *mut _xmlNode, schema: &mut SchematronSchema) {
1008 unsafe {
1009 let mut child = (*node).children;
1010 while !child.is_null() {
1011 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1012 let local = get_local_name(child);
1013 if local == "diagnostic" {
1014 let diag = schematron_parse_diagnostic(child);
1015 schema.diagnostics.insert(diag.id.clone(), diag);
1016 }
1017 }
1018 child = (*child).next;
1019 }
1020 }
1021}
1022
1023unsafe fn schematron_parse_diagnostic(node: *mut _xmlNode) -> SchematronDiagnostic {
1029 unsafe {
1030 let id = get_attr(node, "id").unwrap_or_default();
1031 let text = get_inline_text(node);
1032 let icon = get_attr(node, "icon");
1033 let see = get_attr(node, "see");
1034
1035 SchematronDiagnostic {
1036 id,
1037 text,
1038 icon,
1039 see,
1040 }
1041 }
1042}
1043
1044unsafe fn schematron_parse_include(node: *mut _xmlNode, _schema: &mut SchematronSchema) {
1050 unsafe {
1051 let href = get_attr(node, "href");
1052 if let Some(url) = href {
1053 let url_c = std::ffi::CString::new(url.clone()).ok();
1054 if let Some(c) = url_c {
1055 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
1056 if !doc.is_null() {
1057 let mut root = (*doc).children;
1059 while !root.is_null() && (*root).type_ != XML_ELEMENT_NODE as c_int {
1060 root = (*root).next;
1061 }
1062 if !root.is_null() {
1063 let local = get_local_name(root);
1064 if local == "schema" || local == "pattern" || local == "rule" {
1065 }
1069 }
1070 crate::abi::exports_xml2::xmlFreeDoc(doc);
1071 }
1072 }
1073 }
1074 }
1075}
1076
1077unsafe fn expand_diagnostic_message(
1090 text: &str,
1091 context_node: *mut _xmlNode,
1092 xpath_ctxt: &mut XPathContext,
1093) -> String {
1094 let _ = context_node;
1106 let _ = xpath_ctxt;
1107 text.to_string()
1108}
1109
1110fn evaluate_xpath_boolean(
1116 compiled: &CompiledExpr,
1117 xpath_ctxt: &mut XPathContext,
1118) -> Result<bool, String> {
1119 match crate::xml::xpath::evaluate(compiled, xpath_ctxt) {
1120 Some(value) => Ok(value.as_boolean()),
1121 None => Err("XPath evaluation failed".to_string()),
1122 }
1123}
1124
1125pub unsafe fn schematron_validate_doc(
1135 schema: &SchematronSchema,
1136 doc: *mut _xmlDoc,
1137 ctxt: &mut SchematronValidCtxt,
1138) -> bool {
1139 unsafe {
1140 if doc.is_null() {
1141 ctxt.record_error("Document is null".to_string());
1142 return false;
1143 }
1144
1145 let root = (*doc).children;
1146 if root.is_null() {
1147 ctxt.record_error("Document has no children".to_string());
1148 return false;
1149 }
1150
1151 let mut root_elem = root;
1153 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
1154 root_elem = (*root_elem).next;
1155 }
1156
1157 if root_elem.is_null() {
1158 ctxt.record_error("Document has no root element".to_string());
1159 return false;
1160 }
1161
1162 let phase_id = ctxt.active_phase.as_deref();
1164 let rules = schema.active_rules(phase_id);
1165
1166 if rules.is_empty() {
1167 return true;
1169 }
1170
1171 let mut xpath_ctxt = XPathContext::new(doc);
1173
1174 let core_funcs = crate::xml::xpath::functions::core_functions();
1176 for (name, func) in core_funcs {
1177 xpath_ctxt.register_function(&name, func);
1178 }
1179
1180 for (prefix, uri) in &schema.ns {
1182 xpath_ctxt.namespaces.insert(prefix.clone(), uri.clone());
1183 }
1184
1185 let mut valid = true;
1186
1187 for rule in &rules {
1189 let matching_nodes: Vec<*mut _xmlNode> =
1191 find_matching_nodes(rule, root_elem, doc, &mut xpath_ctxt);
1192
1193 for context_node in &matching_nodes {
1194 xpath_ctxt.set_context_node(*context_node);
1196
1197 for pattern in &rule.patterns {
1199 let compiled = match &pattern.compiled_test {
1200 Some(c) => c,
1201 None => continue,
1202 };
1203
1204 let test_result = match evaluate_xpath_boolean(compiled, &mut xpath_ctxt) {
1205 Ok(val) => val,
1206 Err(e) => {
1207 ctxt.record_error(format!(
1208 "XPath error in '{}' test '{}': {}",
1209 if pattern.pattern_type == SchematronPatternType::Assert {
1210 "assert"
1211 } else {
1212 "report"
1213 },
1214 pattern.test,
1215 e
1216 ));
1217 valid = false;
1218 continue;
1219 }
1220 };
1221
1222 let message =
1223 expand_diagnostic_message(&pattern.text, *context_node, &mut xpath_ctxt);
1224
1225 match pattern.pattern_type {
1226 SchematronPatternType::Assert => {
1227 if !test_result {
1229 let node_name = get_node_qname(*context_node);
1230 let flag_str = pattern
1231 .flag
1232 .as_ref()
1233 .map(|f| format!(" [{}]", f))
1234 .unwrap_or_default();
1235 let role_str = pattern
1236 .role
1237 .as_ref()
1238 .map(|r| format!(" ({})", r))
1239 .unwrap_or_default();
1240 let msg = if message.is_empty() {
1241 format!(
1242 "assertion failed: '{}' for node '{}'{}{}",
1243 pattern.test, node_name, flag_str, role_str
1244 )
1245 } else {
1246 format!(
1247 "assertion '{}' failed for node '{}'{}{}: {}",
1248 pattern.test, node_name, flag_str, role_str, message
1249 )
1250 };
1251 ctxt.record_error(msg);
1252 valid = false;
1253 }
1254 }
1255 SchematronPatternType::Report => {
1256 if test_result {
1258 let node_name = get_node_qname(*context_node);
1259 let flag_str = pattern
1260 .flag
1261 .as_ref()
1262 .map(|f| format!(" [{}]", f))
1263 .unwrap_or_default();
1264 let role_str = pattern
1265 .role
1266 .as_ref()
1267 .map(|r| format!(" ({})", r))
1268 .unwrap_or_default();
1269 let msg = if message.is_empty() {
1270 format!(
1271 "report triggered: '{}' for node '{}'{}{}",
1272 pattern.test, node_name, flag_str, role_str
1273 )
1274 } else {
1275 format!(
1276 "report '{}' triggered for node '{}'{}{}: {}",
1277 pattern.test, node_name, flag_str, role_str, message
1278 )
1279 };
1280 ctxt.record_error(msg);
1281 valid = false;
1282 }
1283 }
1284 }
1285 }
1286 }
1287 }
1288
1289 valid
1290 }
1291}
1292
1293unsafe fn find_matching_nodes(
1300 rule: &SchematronRule,
1301 root: *mut _xmlNode,
1302 doc: *mut _xmlDoc,
1303 xpath_ctxt: &mut XPathContext,
1304) -> Vec<*mut _xmlNode> {
1305 unsafe {
1306 if rule.context.is_empty() {
1308 let mut nodes = Vec::new();
1309 collect_all_elements(root, &mut nodes);
1310 return nodes;
1311 }
1312
1313 if let Some(compiled) = &rule.compiled_context {
1315 let is_simple_name = !rule.context.contains('/')
1318 && !rule.context.contains("::")
1319 && !rule.context.contains('[')
1320 && !rule.context.contains('(');
1321
1322 if !is_simple_name {
1323 xpath_ctxt.set_context_node(root);
1324 xpath_ctxt.document = doc;
1325
1326 if let Some(XPathValue::NodeSet(ns)) =
1327 crate::xml::xpath::evaluate(compiled, xpath_ctxt)
1328 {
1329 if !ns.is_empty() {
1330 return ns.iter().collect();
1331 }
1332 }
1333 }
1334
1335 simple_context_match(&rule.context, root)
1337 } else {
1338 simple_context_match(&rule.context, root)
1340 }
1341 }
1342}
1343
1344fn simple_context_match(context: &str, root: *mut _xmlNode) -> Vec<*mut _xmlNode> {
1347 unsafe {
1348 let context = context.trim();
1349
1350 if context == "*" || context == "//*" {
1357 let mut nodes = Vec::new();
1358 collect_all_elements(root, &mut nodes);
1359 return nodes;
1360 }
1361
1362 if let Some(name) = context.strip_prefix("//") {
1363 if name.is_empty() || name == "*" {
1364 let mut nodes = Vec::new();
1365 collect_all_elements(root, &mut nodes);
1366 return nodes;
1367 }
1368 let mut nodes = Vec::new();
1370 collect_elements_by_name(root, name, &mut nodes);
1371 return nodes;
1372 }
1373
1374 if !context.contains('/') && !context.contains("::") {
1375 let mut nodes = Vec::new();
1377 let root_qname = get_node_qname(root);
1379 let root_local = get_local_name(root);
1380 if root_qname == context || root_local == context || context == "*" {
1381 nodes.push(root);
1382 }
1383 let mut child = (*root).children;
1385 while !child.is_null() {
1386 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1387 let qname = get_node_qname(child);
1388 let local = get_local_name(child);
1389 if qname == context || local == context || context == "*" {
1390 nodes.push(child);
1391 }
1392 }
1393 child = (*child).next;
1394 }
1395 return nodes;
1396 }
1397
1398 vec![root]
1400 }
1401}
1402
1403unsafe fn collect_all_elements(node: *mut _xmlNode, nodes: &mut Vec<*mut _xmlNode>) {
1409 unsafe {
1410 if node.is_null() {
1411 return;
1412 }
1413 if (*node).type_ == XML_ELEMENT_NODE as c_int {
1414 nodes.push(node);
1415 }
1416 let mut child = (*node).children;
1417 while !child.is_null() {
1418 collect_all_elements(child, nodes);
1419 child = (*child).next;
1420 }
1421 }
1422}
1423
1424unsafe fn collect_elements_by_name(
1430 node: *mut _xmlNode,
1431 name: &str,
1432 nodes: &mut Vec<*mut _xmlNode>,
1433) {
1434 unsafe {
1435 if node.is_null() {
1436 return;
1437 }
1438 if (*node).type_ == XML_ELEMENT_NODE as c_int {
1439 let qname = get_node_qname(node);
1440 let local = get_local_name(node);
1441 if qname == name || local == name {
1442 nodes.push(node);
1443 }
1444 }
1445 let mut child = (*node).children;
1446 while !child.is_null() {
1447 collect_elements_by_name(child, name, nodes);
1448 child = (*child).next;
1449 }
1450 }
1451}
1452
1453pub fn schematron_parse_schema(xml_doc: &str) -> Result<SchematronSchema, String> {
1461 schematron_parse(xml_doc)
1462}
1463
1464pub unsafe fn schematron_parse_schema_doc(doc: *mut _xmlDoc) -> Result<SchematronSchema, String> {
1470 schematron_parse_doc(doc)
1471}
1472
1473pub unsafe fn schematron_validate_doc_schema(
1481 schema: &SchematronSchema,
1482 doc: *mut _xmlDoc,
1483 ctxt: &mut SchematronValidCtxt,
1484) -> bool {
1485 schematron_validate_doc(schema, doc, ctxt)
1486}
1487
1488#[no_mangle]
1507pub unsafe extern "C" fn xmlSchematronNewParserCtxt(url: *const c_char) -> *mut c_void {
1508 if url.is_null() {
1509 return Box::into_raw(Box::new(SchematronSchema::new())) as *mut c_void;
1515 }
1516
1517 let url_str = unsafe {
1518 let mut len = 0;
1519 while *url.add(len) != 0 {
1520 len += 1;
1521 }
1522 let slice = std::slice::from_raw_parts(url as *const u8, len);
1523 String::from_utf8_lossy(slice).to_string()
1524 };
1525
1526 if !url_str.is_empty() {
1528 let url_c = std::ffi::CString::new(url_str.clone()).ok();
1529 if let Some(c) = url_c {
1530 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
1531 if !doc.is_null() {
1532 let result = schematron_parse_doc(doc);
1533 crate::abi::exports_xml2::xmlFreeDoc(doc);
1534 if let Ok(schema) = result {
1535 let schema_box = Box::new(schema);
1536 return Box::into_raw(schema_box) as *mut c_void;
1537 }
1538 }
1539 }
1540 }
1541
1542 Box::into_raw(Box::new(SchematronSchema::new())) as *mut c_void
1544}
1545
1546#[no_mangle]
1558pub unsafe extern "C" fn xmlSchematronNewMemParserCtxt(
1559 buffer: *const c_char,
1560 size: c_int,
1561) -> *mut c_void {
1562 if buffer.is_null() || size <= 0 {
1563 return ptr::null_mut();
1564 }
1565
1566 let buf_slice = unsafe { std::slice::from_raw_parts(buffer as *const u8, size as usize) };
1568 let xml_str = String::from_utf8_lossy(buf_slice).to_string();
1569
1570 match schematron_parse(&xml_str) {
1571 Ok(schema) => {
1572 let schema_box = Box::new(schema);
1573 Box::into_raw(schema_box) as *mut c_void
1574 }
1575 Err(_) => ptr::null_mut(),
1576 }
1577}
1578
1579#[no_mangle]
1591pub const unsafe extern "C" fn xmlSchematronParse(ctxt: *mut c_void) -> *mut c_void {
1592 if ctxt.is_null() {
1593 return ptr::null_mut();
1594 }
1595
1596 ctxt
1599}
1600
1601#[no_mangle]
1613pub unsafe extern "C" fn xmlSchematronFree(schema: *mut c_void) {
1614 if schema.is_null() {
1615 return;
1616 }
1617 unsafe {
1619 let _ = Box::from_raw(schema as *mut SchematronSchema);
1620 }
1621}
1622
1623#[no_mangle]
1635pub unsafe extern "C" fn xmlSchematronFreeParserCtxt(ctxt: *mut c_void) {
1636 if ctxt.is_null() {
1637 return;
1638 }
1639 unsafe {
1641 let _ = Box::from_raw(ctxt as *mut SchematronSchema);
1642 }
1643}
1644
1645#[no_mangle]
1657pub unsafe extern "C" fn xmlSchematronNewValidCtxt(schema: *mut c_void) -> *mut c_void {
1658 let mut ctxt = SchematronValidCtxt::new();
1659
1660 if !schema.is_null() {
1661 unsafe {
1663 let schema_ref = &*(schema as *const SchematronSchema);
1664 ctxt.schema = Some(schema_ref.clone());
1665 }
1666 }
1667
1668 let boxed = Box::new(ctxt);
1669 Box::into_raw(boxed) as *mut c_void
1670}
1671
1672#[no_mangle]
1684pub unsafe extern "C" fn xmlSchematronFreeValidCtxt(ctxt: *mut c_void) {
1685 if ctxt.is_null() {
1686 return;
1687 }
1688 unsafe {
1690 let _ = Box::from_raw(ctxt as *mut SchematronValidCtxt);
1691 }
1692}
1693
1694#[no_mangle]
1709pub unsafe extern "C" fn xmlSchematronValidateDoc(ctxt: *mut c_void, doc: *mut _xmlDoc) -> c_int {
1710 if ctxt.is_null() || doc.is_null() {
1711 return -1;
1712 }
1713
1714 unsafe {
1715 let valid_ctxt = &mut *(ctxt as *mut SchematronValidCtxt);
1716 let schema = match &valid_ctxt.schema {
1717 Some(s) => s,
1718 None => return -1,
1719 };
1720
1721 let mut temp_ctxt = SchematronValidCtxt::new();
1722 temp_ctxt.active_phase = valid_ctxt.active_phase.clone();
1723
1724 let valid = schematron_validate_doc(schema, doc, &mut temp_ctxt);
1725
1726 if valid {
1727 0
1728 } else {
1729 valid_ctxt.errors = temp_ctxt.errors;
1730 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
1731 temp_ctxt.nb_errors
1732 }
1733 }
1734}
1735
1736pub type SchematronValidityErrorFunc = unsafe extern "C" fn(ctx: *mut c_void, msg: *const c_char);
1749
1750pub type SchematronValidityWarningFunc = unsafe extern "C" fn(ctx: *mut c_void, msg: *const c_char);
1752
1753#[derive(Clone, Copy)]
1754struct SchematronSendPtr(*mut c_void);
1755unsafe impl Send for SchematronSendPtr {}
1756unsafe impl Sync for SchematronSendPtr {}
1757impl Default for SchematronSendPtr {
1758 fn default() -> Self {
1759 SchematronSendPtr(core::ptr::null_mut())
1760 }
1761}
1762
1763#[derive(Clone, Copy, Default)]
1764struct SchematronParserState {
1765 err: Option<SchematronValidityErrorFunc>,
1766 warn: Option<SchematronValidityWarningFunc>,
1767 ctx: SchematronSendPtr,
1768}
1769
1770#[derive(Clone, Copy, Default)]
1771struct SchematronValidState {
1772 err: Option<SchematronValidityErrorFunc>,
1773 warn: Option<SchematronValidityWarningFunc>,
1774 ctx: SchematronSendPtr,
1775 options: c_int,
1776}
1777
1778static SCHEMATRON_PARSER_STATE: once_cell::sync::Lazy<
1779 parking_lot::Mutex<std::collections::HashMap<usize, SchematronParserState>>,
1780> = once_cell::sync::Lazy::new(Default::default);
1781
1782static SCHEMATRON_VALID_STATE: once_cell::sync::Lazy<
1783 parking_lot::Mutex<std::collections::HashMap<usize, SchematronValidState>>,
1784> = once_cell::sync::Lazy::new(Default::default);
1785
1786#[no_mangle]
1808pub unsafe extern "C" fn xmlSchematronSetParserErrors(
1809 ctxt: *mut c_void,
1810 err: Option<SchematronValidityErrorFunc>,
1811 warn: Option<SchematronValidityWarningFunc>,
1812 ctx: *mut c_void,
1813) {
1814 if ctxt.is_null() {
1815 return;
1816 }
1817 let mut map = SCHEMATRON_PARSER_STATE.lock();
1818 let st = map.entry(ctxt as usize).or_default();
1819 st.err = err;
1820 st.warn = warn;
1821 st.ctx = SchematronSendPtr(ctx);
1822}
1823
1824#[no_mangle]
1845pub unsafe extern "C" fn xmlSchematronGetParserErrors(
1846 ctxt: *mut c_void,
1847 err: *mut Option<SchematronValidityErrorFunc>,
1848 warn: *mut Option<SchematronValidityWarningFunc>,
1849 ctx: *mut *mut c_void,
1850) -> c_int {
1851 if ctxt.is_null() {
1852 return -1;
1853 }
1854 let map = SCHEMATRON_PARSER_STATE.lock();
1855 let st = map.get(&(ctxt as usize)).copied().unwrap_or_default();
1856 if !err.is_null() {
1857 *err = st.err;
1858 }
1859 if !warn.is_null() {
1860 *warn = st.warn;
1861 }
1862 if !ctx.is_null() {
1863 *ctx = st.ctx.0;
1864 }
1865 0
1866}
1867
1868#[no_mangle]
1889pub unsafe extern "C" fn xmlSchematronSetValidErrors(
1890 ctxt: *mut c_void,
1891 err: Option<SchematronValidityErrorFunc>,
1892 warn: Option<SchematronValidityWarningFunc>,
1893 ctx: *mut c_void,
1894) {
1895 if ctxt.is_null() {
1896 return;
1897 }
1898 let mut map = SCHEMATRON_VALID_STATE.lock();
1899 let st = map.entry(ctxt as usize).or_default();
1900 st.err = err;
1901 st.warn = warn;
1902 st.ctx = SchematronSendPtr(ctx);
1903}
1904
1905#[no_mangle]
1926pub unsafe extern "C" fn xmlSchematronGetValidErrors(
1927 ctxt: *mut c_void,
1928 err: *mut Option<SchematronValidityErrorFunc>,
1929 warn: *mut Option<SchematronValidityWarningFunc>,
1930 ctx: *mut *mut c_void,
1931) -> c_int {
1932 if ctxt.is_null() {
1933 return -1;
1934 }
1935 let map = SCHEMATRON_VALID_STATE.lock();
1936 let st = map.get(&(ctxt as usize)).copied().unwrap_or_default();
1937 if !err.is_null() {
1938 *err = st.err;
1939 }
1940 if !warn.is_null() {
1941 *warn = st.warn;
1942 }
1943 if !ctx.is_null() {
1944 *ctx = st.ctx.0;
1945 }
1946 0
1947}
1948
1949#[no_mangle]
1967pub unsafe extern "C" fn xmlSchematronSetValidOptions(ctxt: *mut c_void, options: c_int) -> c_int {
1968 if ctxt.is_null() {
1969 return -1;
1970 }
1971 let mut map = SCHEMATRON_VALID_STATE.lock();
1972 let st = map.entry(ctxt as usize).or_default();
1973 let old = st.options;
1974 st.options = options;
1975 old
1976}
1977
1978#[no_mangle]
1995pub unsafe extern "C" fn xmlSchematronValidCtxtGetOptions(ctxt: *mut c_void) -> c_int {
1996 if ctxt.is_null() {
1997 return -1;
1998 }
1999 SCHEMATRON_VALID_STATE
2000 .lock()
2001 .get(&(ctxt as usize))
2002 .map_or(0, |st| st.options)
2003}
2004
2005#[no_mangle]
2023pub const unsafe extern "C" fn xmlSchematronIsValid(ctxt: *mut c_void) -> c_int {
2024 if ctxt.is_null() {
2025 return 0;
2026 }
2027 unsafe {
2028 let vc = &*(ctxt as *const SchematronValidCtxt);
2029 if vc.nb_errors > 0 {
2030 0
2031 } else {
2032 1
2033 }
2034 }
2035}
2036
2037#[no_mangle]
2055pub unsafe extern "C" fn xmlSchematronValidateOneElement(
2056 ctxt: *mut c_void,
2057 elem: *mut _xmlNode,
2058) -> c_int {
2059 if ctxt.is_null() || elem.is_null() {
2060 return -1;
2061 }
2062 unsafe {
2063 let valid_ctxt = &mut *(ctxt as *mut SchematronValidCtxt);
2064 let schema = match &valid_ctxt.schema {
2065 Some(s) => s,
2066 None => return -1,
2067 };
2068 let doc = (*elem).doc;
2069 if doc.is_null() {
2070 return -1;
2071 }
2072 let mut temp_ctxt = SchematronValidCtxt::new();
2075 temp_ctxt.active_phase = valid_ctxt.active_phase.clone();
2076 let valid = schematron_validate_doc(schema, doc, &mut temp_ctxt);
2077 if !valid {
2078 valid_ctxt.errors = temp_ctxt.errors;
2079 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2080 }
2081 if valid {
2082 0
2083 } else {
2084 -1
2085 }
2086 }
2087}
2088
2089#[cfg(test)]
2094mod tests {
2095 use super::*;
2096
2097 #[test]
2100 fn test_parse_simple_schema() {
2101 let schema_xml = r#"<?xml version="1.0"?>
2102<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2103 <pattern id="P1">
2104 <rule context="root">
2105 <assert test="count(*) > 0">Root must have children</assert>
2106 </rule>
2107 </pattern>
2108</schema>"#;
2109
2110 let result = schematron_parse(schema_xml);
2111 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2112 let schema = result.unwrap();
2113 assert_eq!(schema.pattern_order.len(), 1);
2114 assert_eq!(schema.rules.len(), 1);
2115 }
2116
2117 #[test]
2118 fn test_parse_with_ns() {
2119 let schema_xml = r#"<?xml version="1.0"?>
2120<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2121 <ns prefix="doc" uri="http://example.com/doc"/>
2122 <pattern id="P1">
2123 <rule context="doc:entry">
2124 <assert test="doc:title">Entry must have a title</assert>
2125 </rule>
2126 </pattern>
2127</schema>"#;
2128
2129 let result = schematron_parse(schema_xml);
2130 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2131 let schema = result.unwrap();
2132 assert!(schema.ns.contains_key("doc"));
2133 assert_eq!(schema.ns.get("doc").unwrap(), "http://example.com/doc");
2134 }
2135
2136 #[test]
2137 fn test_parse_with_phases() {
2138 let schema_xml = r#"<?xml version="1.0"?>
2139<schema xmlns="http://purl.oclc.org/dsdl/schematron" defaultPhase="phaseA">
2140 <phase id="phaseA">
2141 <active pattern="P1"/>
2142 </phase>
2143 <phase id="phaseB">
2144 <active pattern="P2"/>
2145 </phase>
2146 <pattern id="P1">
2147 <rule context="root">
2148 <assert test="true()">Always passes</assert>
2149 </rule>
2150 </pattern>
2151 <pattern id="P2">
2152 <rule context="root">
2153 <assert test="false()">Always fails</assert>
2154 </rule>
2155 </pattern>
2156</schema>"#;
2157
2158 let result = schematron_parse(schema_xml);
2159 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2160 let schema = result.unwrap();
2161 assert_eq!(schema.phases.len(), 2);
2162 assert!(schema.phases.contains_key("phaseA"));
2163 assert!(schema.phases.contains_key("phaseB"));
2164 assert_eq!(schema.default_phase.as_deref(), Some("phaseA"));
2165 }
2166
2167 #[test]
2168 fn test_parse_report_pattern() {
2169 let schema_xml = r#"<?xml version="1.0"?>
2170<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2171 <pattern id="P1">
2172 <rule context="root">
2173 <report test="@deprecated">Element is deprecated</report>
2174 </rule>
2175 </pattern>
2176</schema>"#;
2177
2178 let result = schematron_parse(schema_xml);
2179 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2180 let schema = result.unwrap();
2181 let rule = schema.rules.values().next().unwrap();
2182 assert_eq!(rule.patterns.len(), 1);
2183 assert_eq!(rule.patterns[0].pattern_type, SchematronPatternType::Report);
2184 assert_eq!(rule.patterns[0].test, "@deprecated");
2185 }
2186
2187 #[test]
2188 fn test_parse_abstract_rule() {
2189 let schema_xml = r#"<?xml version="1.0"?>
2190<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2191 <pattern id="P1">
2192 <rule id="abstractRule" abstract="true" context="*">
2193 <assert test="true()">Abstract assertion</assert>
2194 </rule>
2195 <rule id="concreteRule" context="root">
2196 <extends rule="abstractRule"/>
2197 <assert test="count(*) > 0">Concrete assertion</assert>
2198 </rule>
2199 </pattern>
2200</schema>"#;
2201
2202 let result = schematron_parse(schema_xml);
2203 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2204 let schema = result.unwrap();
2205 assert!(schema.rules.contains_key("abstractRule"));
2206 assert!(schema.rules.contains_key("concreteRule"));
2207 let abstract_rule = &schema.rules["abstractRule"];
2208 assert!(abstract_rule.abstract_);
2209 let concrete_rule = &schema.rules["concreteRule"];
2210 assert!(!concrete_rule.abstract_);
2211 assert_eq!(concrete_rule.extends.len(), 1);
2212 assert_eq!(concrete_rule.extends[0], "abstractRule");
2213 }
2214
2215 #[test]
2216 fn test_parse_with_diagnostics() {
2217 let schema_xml = r#"<?xml version="1.0"?>
2218<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2219 <diagnostics>
2220 <diagnostic id="diag1">This is a diagnostic message</diagnostic>
2221 </diagnostics>
2222 <pattern id="P1">
2223 <rule context="root">
2224 <assert test="true()" diagnostics="diag1">Assertion with diagnostic</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.diagnostics.contains_key("diag1"));
2233 assert_eq!(
2234 schema.diagnostics["diag1"].text,
2235 "This is a diagnostic message"
2236 );
2237 }
2238
2239 #[test]
2240 fn test_parse_with_attributes() {
2241 let schema_xml = r#"<?xml version="1.0"?>
2242<schema xmlns="http://purl.oclc.org/dsdl/schematron" title="Test Schema">
2243 <pattern id="P1">
2244 <rule context="root">
2245 <assert test="true()" flag="warn" role="error" id="a1" icon="info" see="http://example.com">
2246 Test message
2247 </assert>
2248 </rule>
2249 </pattern>
2250</schema>"#;
2251
2252 let result = schematron_parse(schema_xml);
2253 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2254 let schema = result.unwrap();
2255 assert_eq!(schema.title.as_deref(), Some("Test Schema"));
2256 let rule = schema.rules.values().next().unwrap();
2257 let pat = &rule.patterns[0];
2258 assert_eq!(pat.flag.as_deref(), Some("warn"));
2259 assert_eq!(pat.role.as_deref(), Some("error"));
2260 assert_eq!(pat.id.as_deref(), Some("a1"));
2261 assert_eq!(pat.icon.as_deref(), Some("info"));
2262 assert_eq!(pat.see.as_deref(), Some("http://example.com"));
2263 }
2264
2265 #[test]
2266 fn test_parse_empty_schema() {
2267 let schema_xml = r#"<?xml version="1.0"?>
2268<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2269</schema>"#;
2270
2271 let result = schematron_parse(schema_xml);
2272 assert!(result.is_ok(), "Failed to parse empty schema");
2273 let schema = result.unwrap();
2274 assert!(schema.rules.is_empty());
2275 assert!(schema.phases.is_empty());
2276 }
2277
2278 #[test]
2279 fn test_parse_no_assertions() {
2280 let schema_xml = r#"<?xml version="1.0"?>
2281<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2282 <pattern id="P1">
2283 <rule context="root">
2284 </rule>
2285 </pattern>
2286</schema>"#;
2287
2288 let result = schematron_parse(schema_xml);
2289 assert!(result.is_ok(), "Failed to parse schema with no assertions");
2290 let schema = result.unwrap();
2291 let rule = schema.rules.values().next().unwrap();
2292 assert!(rule.patterns.is_empty());
2293 }
2294
2295 #[test]
2296 fn test_parse_invalid_root_element() {
2297 let schema_xml = r#"<?xml version="1.0"?>
2298<not-schema xmlns="http://purl.oclc.org/dsdl/schematron">
2299</not-schema>"#;
2300
2301 let result = schematron_parse(schema_xml);
2302 assert!(result.is_err(), "Should fail with wrong root element");
2303 assert!(
2304 result.err().unwrap().contains("Expected '<schema>'"),
2305 "Error should mention expected schema element"
2306 );
2307 }
2308
2309 #[test]
2310 fn test_parse_empty_document_fails() {
2311 let result = schematron_parse("");
2312 assert!(result.is_err());
2313 }
2314
2315 #[test]
2316 fn test_parse_invalid_xml_fails() {
2317 let result = schematron_parse("not valid xml <<<");
2318 assert!(result.is_err());
2319 }
2320
2321 #[test]
2322 fn test_parse_schema_with_let_and_param() {
2323 let schema_xml = r#"<?xml version="1.0"?>
2324<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2325 <pattern id="P1">
2326 <rule context="root">
2327 <let name="x" value="42"/>
2328 <param name="debug" value="true"/>
2329 <assert test="true()">Test with let and param</assert>
2330 </rule>
2331 </pattern>
2332</schema>"#;
2333
2334 let result = schematron_parse(schema_xml);
2335 assert!(
2336 result.is_ok(),
2337 "Failed to parse schema with let/param: {:?}",
2338 result.err()
2339 );
2340 let schema = result.unwrap();
2341 assert_eq!(schema.rules.len(), 1);
2342 }
2343
2344 #[test]
2345 fn test_parse_schema_with_documentation() {
2346 let schema_xml = r#"<?xml version="1.0"?>
2347<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2348 <p>This is documentation</p>
2349 <caption>Table caption</caption>
2350 <pattern id="P1">
2351 <p>Pattern documentation</p>
2352 <rule context="root">
2353 <p>Rule documentation</p>
2354 <assert test="true()">Real assertion</assert>
2355 </rule>
2356 </pattern>
2357</schema>"#;
2358
2359 let result = schematron_parse(schema_xml);
2360 assert!(result.is_ok(), "Failed to parse schema with documentation");
2361 let schema = result.unwrap();
2362 assert_eq!(schema.rules.len(), 1);
2363 }
2364
2365 #[test]
2368 fn test_validate_assert_pass() {
2369 let schema_xml = r#"<?xml version="1.0"?>
2370<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2371 <pattern id="P1">
2372 <rule context="root">
2373 <assert test="true()">Always passes</assert>
2374 </rule>
2375 </pattern>
2376</schema>"#;
2377
2378 let doc_xml = r#"<?xml version="1.0"?>
2379<root>Hello</root>"#;
2380
2381 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2382
2383 let doc = unsafe {
2384 crate::abi::exports_xml2::xmlReadMemory(
2385 doc_xml.as_ptr() as *const c_char,
2386 doc_xml.len() as c_int,
2387 c"test.xml".as_ptr() as *const c_char,
2388 ptr::null(),
2389 0,
2390 )
2391 };
2392 assert!(!doc.is_null(), "Failed to parse document");
2393
2394 let mut ctxt = SchematronValidCtxt::new();
2395 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2396 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2397
2398 assert!(valid, "Validation failed: {:?}", ctxt.errors);
2399 }
2400
2401 #[test]
2402 fn test_validate_assert_fail() {
2403 let schema_xml = r#"<?xml version="1.0"?>
2404<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2405 <pattern id="P1">
2406 <rule context="root">
2407 <assert test="false()">Always fails</assert>
2408 </rule>
2409 </pattern>
2410</schema>"#;
2411
2412 let doc_xml = r#"<?xml version="1.0"?>
2413<root>Hello</root>"#;
2414
2415 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2416
2417 let doc = unsafe {
2418 crate::abi::exports_xml2::xmlReadMemory(
2419 doc_xml.as_ptr() as *const c_char,
2420 doc_xml.len() as c_int,
2421 c"test.xml".as_ptr() as *const c_char,
2422 ptr::null(),
2423 0,
2424 )
2425 };
2426 assert!(!doc.is_null());
2427
2428 let mut ctxt = SchematronValidCtxt::new();
2429 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2430 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2431
2432 assert!(
2433 !valid,
2434 "Validation should have failed, errors: {:?}",
2435 ctxt.errors
2436 );
2437 assert!(ctxt.nb_errors > 0);
2438 }
2439
2440 #[test]
2441 fn test_validate_report_pass() {
2442 let schema_xml = r#"<?xml version="1.0"?>
2443<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2444 <pattern id="P1">
2445 <rule context="root">
2446 <report test="false()">Report should not trigger</report>
2447 </rule>
2448 </pattern>
2449</schema>"#;
2450
2451 let doc_xml = r#"<?xml version="1.0"?>
2452<root>Hello</root>"#;
2453
2454 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2455
2456 let doc = unsafe {
2457 crate::abi::exports_xml2::xmlReadMemory(
2458 doc_xml.as_ptr() as *const c_char,
2459 doc_xml.len() as c_int,
2460 c"test.xml".as_ptr() as *const c_char,
2461 ptr::null(),
2462 0,
2463 )
2464 };
2465 assert!(!doc.is_null());
2466
2467 let mut ctxt = SchematronValidCtxt::new();
2468 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2469 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2470
2471 assert!(valid, "Report should not trigger: {:?}", ctxt.errors);
2472 }
2473
2474 #[test]
2475 fn test_validate_report_fail() {
2476 let schema_xml = r#"<?xml version="1.0"?>
2477<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2478 <pattern id="P1">
2479 <rule context="root">
2480 <report test="true()">Report should trigger</report>
2481 </rule>
2482 </pattern>
2483</schema>"#;
2484
2485 let doc_xml = r#"<?xml version="1.0"?>
2486<root>Hello</root>"#;
2487
2488 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2489
2490 let doc = unsafe {
2491 crate::abi::exports_xml2::xmlReadMemory(
2492 doc_xml.as_ptr() as *const c_char,
2493 doc_xml.len() as c_int,
2494 c"test.xml".as_ptr() as *const c_char,
2495 ptr::null(),
2496 0,
2497 )
2498 };
2499 assert!(!doc.is_null());
2500
2501 let mut ctxt = SchematronValidCtxt::new();
2502 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2503 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2504
2505 assert!(!valid, "Report should have triggered");
2506 assert!(ctxt.nb_errors > 0);
2507 }
2508
2509 #[test]
2510 fn test_validate_context_matching() {
2511 let schema_xml = r#"<?xml version="1.0"?>
2512<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2513 <pattern id="P1">
2514 <rule context="child">
2515 <assert test="true()">Child matches</assert>
2516 </rule>
2517 </pattern>
2518</schema>"#;
2519
2520 let doc_xml = r#"<?xml version="1.0"?>
2521<root>
2522 <child>A</child>
2523 <child>B</child>
2524</root>"#;
2525
2526 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2527
2528 let doc = unsafe {
2529 crate::abi::exports_xml2::xmlReadMemory(
2530 doc_xml.as_ptr() as *const c_char,
2531 doc_xml.len() as c_int,
2532 c"test.xml".as_ptr() as *const c_char,
2533 ptr::null(),
2534 0,
2535 )
2536 };
2537 assert!(!doc.is_null());
2538
2539 let mut ctxt = SchematronValidCtxt::new();
2540 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2541 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2542
2543 assert!(valid, "Context matching failed: {:?}", ctxt.errors);
2544 }
2545
2546 #[test]
2547 fn test_validate_multiple_rules() {
2548 let schema_xml = r#"<?xml version="1.0"?>
2549<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2550 <pattern id="P1">
2551 <rule context="root">
2552 <assert test="true()">Root passes</assert>
2553 </rule>
2554 </pattern>
2555 <pattern id="P2">
2556 <rule context="child">
2557 <assert test="true()">Child passes</assert>
2558 </rule>
2559 </pattern>
2560</schema>"#;
2561
2562 let doc_xml = r#"<?xml version="1.0"?>
2563<root>
2564 <child>Content</child>
2565</root>"#;
2566
2567 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2568
2569 let doc = unsafe {
2570 crate::abi::exports_xml2::xmlReadMemory(
2571 doc_xml.as_ptr() as *const c_char,
2572 doc_xml.len() as c_int,
2573 c"test.xml".as_ptr() as *const c_char,
2574 ptr::null(),
2575 0,
2576 )
2577 };
2578 assert!(!doc.is_null());
2579
2580 let mut ctxt = SchematronValidCtxt::new();
2581 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2582 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2583
2584 assert!(valid, "Multiple rules failed: {:?}", ctxt.errors);
2585 }
2586
2587 #[test]
2588 fn test_validate_with_phase_filtering() {
2589 let schema_xml = r#"<?xml version="1.0"?>
2590<schema xmlns="http://purl.oclc.org/dsdl/schematron" defaultPhase="phaseA">
2591 <phase id="phaseA">
2592 <active pattern="P1"/>
2593 </phase>
2594 <phase id="phaseB">
2595 <active pattern="P2"/>
2596 </phase>
2597 <pattern id="P1">
2598 <rule context="root">
2599 <assert test="true()">Always passes</assert>
2600 </rule>
2601 </pattern>
2602 <pattern id="P2">
2603 <rule context="root">
2604 <assert test="false()">Always fails</assert>
2605 </rule>
2606 </pattern>
2607</schema>"#;
2608
2609 let doc_xml = r#"<?xml version="1.0"?>
2610<root>Hello</root>"#;
2611
2612 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2613
2614 let doc = unsafe {
2615 crate::abi::exports_xml2::xmlReadMemory(
2616 doc_xml.as_ptr() as *const c_char,
2617 doc_xml.len() as c_int,
2618 c"test.xml".as_ptr() as *const c_char,
2619 ptr::null(),
2620 0,
2621 )
2622 };
2623 assert!(!doc.is_null());
2624
2625 let mut ctxt = SchematronValidCtxt::new();
2627 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2628 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2629
2630 assert!(
2631 valid,
2632 "Phase filtering should make validation pass: {:?}",
2633 ctxt.errors
2634 );
2635 }
2636
2637 #[test]
2638 fn test_validate_no_rules() {
2639 let schema_xml = r#"<?xml version="1.0"?>
2640<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2641</schema>"#;
2642
2643 let doc_xml = r#"<?xml version="1.0"?>
2644<root>Hello</root>"#;
2645
2646 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2647
2648 let doc = unsafe {
2649 crate::abi::exports_xml2::xmlReadMemory(
2650 doc_xml.as_ptr() as *const c_char,
2651 doc_xml.len() as c_int,
2652 c"test.xml".as_ptr() as *const c_char,
2653 ptr::null(),
2654 0,
2655 )
2656 };
2657 assert!(!doc.is_null());
2658
2659 let mut ctxt = SchematronValidCtxt::new();
2660 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2661 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2662
2663 assert!(valid, "Empty schema should pass validation");
2664 }
2665
2666 #[test]
2667 fn test_validate_extends_resolution() {
2668 let schema_xml = r#"<?xml version="1.0"?>
2669<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2670 <pattern id="P1">
2671 <rule id="base" abstract="true" context="*">
2672 <assert test="true()">Base assertion</assert>
2673 </rule>
2674 <rule id="derived" context="root">
2675 <extends rule="base"/>
2676 <assert test="true()">Derived assertion</assert>
2677 </rule>
2678 </pattern>
2679</schema>"#;
2680
2681 let doc_xml = r#"<?xml version="1.0"?>
2682<root>Hello</root>"#;
2683
2684 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2685
2686 let resolved = schema.resolve_rule("derived");
2688 assert!(resolved.is_some());
2689 let resolved = resolved.unwrap();
2690 assert_eq!(
2692 resolved.patterns.len(),
2693 2,
2694 "Should have inherited the base pattern"
2695 );
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();
2709 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2710 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2711
2712 assert!(valid, "Extends resolution failed: {:?}", ctxt.errors);
2713 }
2714
2715 #[test]
2716 fn test_validate_assert_with_flag() {
2717 let schema_xml = r#"<?xml version="1.0"?>
2718<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2719 <pattern id="P1">
2720 <rule context="root">
2721 <assert test="false()" flag="warn">Warning message</assert>
2722 </rule>
2723 </pattern>
2724</schema>"#;
2725
2726 let doc_xml = r#"<?xml version="1.0"?>
2727<root>Hello</root>"#;
2728
2729 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2730
2731 let doc = unsafe {
2732 crate::abi::exports_xml2::xmlReadMemory(
2733 doc_xml.as_ptr() as *const c_char,
2734 doc_xml.len() as c_int,
2735 c"test.xml".as_ptr() as *const c_char,
2736 ptr::null(),
2737 0,
2738 )
2739 };
2740 assert!(!doc.is_null());
2741
2742 let mut ctxt = SchematronValidCtxt::new();
2743 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2744 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2745
2746 assert!(!valid);
2747 assert!(ctxt.nb_errors > 0);
2748 assert!(
2750 ctxt.errors[0].contains("[warn]"),
2751 "Error should include flag"
2752 );
2753 }
2754
2755 #[test]
2758 fn test_c_abi_new_free_parser_ctxt() {
2759 let ctxt = unsafe { xmlSchematronNewParserCtxt(ptr::null()) };
2760 assert!(!ctxt.is_null());
2761 unsafe { xmlSchematronFreeParserCtxt(ctxt) };
2762 }
2764
2765 #[test]
2766 fn test_c_abi_new_free_valid_ctxt() {
2767 let schema = unsafe { xmlSchematronNewParserCtxt(ptr::null()) };
2768 assert!(!schema.is_null());
2769
2770 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema) };
2771 assert!(!valid_ctxt.is_null());
2772
2773 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2774 unsafe { xmlSchematronFreeParserCtxt(schema) };
2775 }
2777
2778 #[test]
2779 fn test_c_abi_parse_free() {
2780 let schema_xml = r#"<?xml version="1.0"?>
2781<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2782 <pattern id="P1">
2783 <rule context="root">
2784 <assert test="true()">Test</assert>
2785 </rule>
2786 </pattern>
2787</schema>"#;
2788
2789 let ctxt = unsafe {
2790 xmlSchematronNewMemParserCtxt(
2791 schema_xml.as_ptr() as *const c_char,
2792 schema_xml.len() as c_int,
2793 )
2794 };
2795 assert!(!ctxt.is_null());
2796
2797 let schema = unsafe { xmlSchematronParse(ctxt) };
2798 assert!(!schema.is_null());
2799
2800 unsafe { xmlSchematronFree(schema) };
2801 }
2803
2804 #[test]
2805 fn test_c_abi_validate_doc() {
2806 let schema_xml = r#"<?xml version="1.0"?>
2807<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2808 <pattern id="P1">
2809 <rule context="root">
2810 <assert test="true()">Always passes</assert>
2811 </rule>
2812 </pattern>
2813</schema>"#;
2814
2815 let doc_xml = r#"<?xml version="1.0"?>
2816<root>Hello</root>"#;
2817
2818 let ctxt = unsafe {
2819 xmlSchematronNewMemParserCtxt(
2820 schema_xml.as_ptr() as *const c_char,
2821 schema_xml.len() as c_int,
2822 )
2823 };
2824 assert!(!ctxt.is_null());
2825
2826 let schema = unsafe { xmlSchematronParse(ctxt) };
2827 assert!(!schema.is_null());
2828
2829 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema) };
2830 assert!(!valid_ctxt.is_null());
2831
2832 let doc = unsafe {
2833 crate::abi::exports_xml2::xmlReadMemory(
2834 doc_xml.as_ptr() as *const c_char,
2835 doc_xml.len() as c_int,
2836 c"test.xml".as_ptr() as *const c_char,
2837 ptr::null(),
2838 0,
2839 )
2840 };
2841 assert!(!doc.is_null());
2842
2843 let result = unsafe { xmlSchematronValidateDoc(valid_ctxt, doc) };
2844 assert_eq!(result, 0, "Validation should pass (return 0)");
2845
2846 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2847 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2848 unsafe { xmlSchematronFree(schema) };
2849 }
2850
2851 #[test]
2852 fn test_c_abi_validate_fail() {
2853 let schema_xml = r#"<?xml version="1.0"?>
2854<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2855 <pattern id="P1">
2856 <rule context="root">
2857 <assert test="false()">Always fails</assert>
2858 </rule>
2859 </pattern>
2860</schema>"#;
2861
2862 let doc_xml = r#"<?xml version="1.0"?>
2863<root>Hello</root>"#;
2864
2865 let ctxt = unsafe {
2866 xmlSchematronNewMemParserCtxt(
2867 schema_xml.as_ptr() as *const c_char,
2868 schema_xml.len() as c_int,
2869 )
2870 };
2871 assert!(!ctxt.is_null());
2872
2873 let schema = unsafe { xmlSchematronParse(ctxt) };
2874 assert!(!schema.is_null());
2875
2876 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema) };
2877 assert!(!valid_ctxt.is_null());
2878
2879 let doc = unsafe {
2880 crate::abi::exports_xml2::xmlReadMemory(
2881 doc_xml.as_ptr() as *const c_char,
2882 doc_xml.len() as c_int,
2883 c"test.xml".as_ptr() as *const c_char,
2884 ptr::null(),
2885 0,
2886 )
2887 };
2888 assert!(!doc.is_null());
2889
2890 let result = unsafe { xmlSchematronValidateDoc(valid_ctxt, doc) };
2891 assert!(result > 0, "Validation should fail (return > 0)");
2892
2893 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2894 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2895 unsafe { xmlSchematronFree(schema) };
2896 }
2897
2898 #[test]
2899 fn test_c_abi_null_handling() {
2900 unsafe { xmlSchematronFree(ptr::null_mut()) };
2902 unsafe { xmlSchematronFreeParserCtxt(ptr::null_mut()) };
2903 unsafe { xmlSchematronFreeValidCtxt(ptr::null_mut()) };
2904
2905 let result = unsafe { xmlSchematronParse(ptr::null_mut()) };
2907 assert!(result.is_null());
2908
2909 let result = unsafe { xmlSchematronValidateDoc(ptr::null_mut(), ptr::null_mut()) };
2911 assert_eq!(result, -1);
2912 }
2913
2914 #[test]
2917 fn test_validate_null_doc() {
2918 let schema = SchematronSchema::new();
2919 let mut ctxt = SchematronValidCtxt::new();
2920 let valid = unsafe { schematron_validate_doc(&schema, ptr::null_mut(), &mut ctxt) };
2921 assert!(!valid);
2922 assert!(ctxt.nb_errors > 0);
2923 }
2924
2925 #[test]
2926 fn test_active_rules_default_phase() {
2927 let mut schema = SchematronSchema::new();
2928
2929 let rule = SchematronRule::new("root".to_string());
2930 schema.rules.insert("r1".to_string(), rule);
2931
2932 schema
2933 .pattern_groups
2934 .insert("p1".to_string(), vec!["r1".to_string()]);
2935 schema.pattern_order.push("p1".to_string());
2936
2937 let rules = schema.active_rules(None);
2938 assert_eq!(rules.len(), 1);
2939 }
2940
2941 #[test]
2942 fn test_active_rules_unknown_phase() {
2943 let mut schema = SchematronSchema::new();
2944
2945 let rule = SchematronRule::new("root".to_string());
2946 schema.rules.insert("r1".to_string(), rule);
2947
2948 schema
2949 .pattern_groups
2950 .insert("p1".to_string(), vec!["r1".to_string()]);
2951 schema.pattern_order.push("p1".to_string());
2952
2953 let rules = schema.active_rules(Some("nonexistent"));
2954 assert_eq!(rules.len(), 1, "Unknown phase should use all patterns");
2955 }
2956
2957 #[test]
2958 fn test_parse_schema_with_span_and_emph() {
2959 let schema_xml = r#"<?xml version="1.0"?>
2960<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2961 <pattern id="P1">
2962 <rule context="root">
2963 <assert test="true()">Message with <span class="x">inline</span> and <emph>emphasis</emph></assert>
2964 </rule>
2965 </pattern>
2966</schema>"#;
2967
2968 let result = schematron_parse(schema_xml);
2969 assert!(
2970 result.is_ok(),
2971 "Failed to parse schema with span/emph: {:?}",
2972 result.err()
2973 );
2974 let schema = result.unwrap();
2975 let rule = schema.rules.values().next().unwrap();
2976 let pat = &rule.patterns[0];
2977 assert!(
2979 pat.text.contains("inline"),
2980 "Text should include span content"
2981 );
2982 assert!(
2983 pat.text.contains("emphasis"),
2984 "Text should include emph content"
2985 );
2986 }
2987
2988 #[test]
2989 fn test_schematron_pattern_new_assert() {
2990 let pat = SchematronPattern::new(
2991 SchematronPatternType::Assert,
2992 "true()".to_string(),
2993 "Test message".to_string(),
2994 );
2995 assert_eq!(pat.pattern_type, SchematronPatternType::Assert);
2996 assert_eq!(pat.test, "true()");
2997 assert_eq!(pat.text, "Test message");
2998 assert!(pat.compiled_test.is_some());
2999 }
3000
3001 #[test]
3002 fn test_schematron_pattern_new_report() {
3003 let pat = SchematronPattern::new(
3004 SchematronPatternType::Report,
3005 "false()".to_string(),
3006 "Report message".to_string(),
3007 );
3008 assert_eq!(pat.pattern_type, SchematronPatternType::Report);
3009 assert!(pat.compiled_test.is_some());
3010 }
3011
3012 #[test]
3013 fn test_schematron_rule_new() {
3014 let rule = SchematronRule::new("root".to_string());
3015 assert_eq!(rule.context, "root");
3016 assert!(rule.patterns.is_empty());
3017 assert!(!rule.abstract_);
3018 }
3019
3020 #[test]
3021 fn test_schematron_schema_new() {
3022 let schema = SchematronSchema::new();
3023 assert_eq!(schema.query_binding, "xslt");
3024 assert!(schema.rules.is_empty());
3025 assert!(schema.phases.is_empty());
3026 assert!(schema.ns.is_empty());
3027 }
3028
3029 #[test]
3030 fn test_schematron_valid_ctxt_new() {
3031 let ctxt = SchematronValidCtxt::new();
3032 assert!(ctxt.errors.is_empty());
3033 assert_eq!(ctxt.nb_errors, 0);
3034 assert!(ctxt.active_phase.is_none());
3035 }
3036
3037 #[test]
3038 fn test_validate_assert_with_child_count() {
3039 let schema_xml = r#"<?xml version="1.0"?>
3040<schema xmlns="http://purl.oclc.org/dsdl/schematron">
3041 <pattern id="P1">
3042 <rule context="root">
3043 <assert test="count(*) > 0">Root must have at least one child element</assert>
3044 </rule>
3045 </pattern>
3046</schema>"#;
3047
3048 let doc_xml = r#"<?xml version="1.0"?>
3049<root>
3050 <child>Content</child>
3051</root>"#;
3052
3053 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
3054
3055 let doc = unsafe {
3056 crate::abi::exports_xml2::xmlReadMemory(
3057 doc_xml.as_ptr() as *const c_char,
3058 doc_xml.len() as c_int,
3059 c"test.xml".as_ptr() as *const c_char,
3060 ptr::null(),
3061 0,
3062 )
3063 };
3064 assert!(!doc.is_null());
3065
3066 let mut ctxt = SchematronValidCtxt::new();
3067 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
3068 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3069
3070 assert!(valid, "Child count check failed: {:?}", ctxt.errors);
3071 }
3072}