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]
1660pub unsafe extern "C" fn xmlSchematronNewValidCtxt(
1661 schema: *mut c_void,
1662 _options: c_int,
1663) -> *mut c_void {
1664 let mut ctxt = SchematronValidCtxt::new();
1665
1666 if !schema.is_null() {
1667 unsafe {
1669 let schema_ref = &*(schema as *const SchematronSchema);
1670 ctxt.schema = Some(schema_ref.clone());
1671 }
1672 }
1673
1674 let boxed = Box::new(ctxt);
1675 Box::into_raw(boxed) as *mut c_void
1676}
1677
1678#[no_mangle]
1690pub unsafe extern "C" fn xmlSchematronFreeValidCtxt(ctxt: *mut c_void) {
1691 if ctxt.is_null() {
1692 return;
1693 }
1694 unsafe {
1696 let _ = Box::from_raw(ctxt as *mut SchematronValidCtxt);
1697 }
1698}
1699
1700#[no_mangle]
1715pub unsafe extern "C" fn xmlSchematronValidateDoc(ctxt: *mut c_void, doc: *mut _xmlDoc) -> c_int {
1716 if ctxt.is_null() || doc.is_null() {
1717 return -1;
1718 }
1719
1720 unsafe {
1721 let valid_ctxt = &mut *(ctxt as *mut SchematronValidCtxt);
1722 let schema = match &valid_ctxt.schema {
1723 Some(s) => s,
1724 None => return -1,
1725 };
1726
1727 let mut temp_ctxt = SchematronValidCtxt::new();
1728 temp_ctxt.active_phase = valid_ctxt.active_phase.clone();
1729
1730 let valid = schematron_validate_doc(schema, doc, &mut temp_ctxt);
1731
1732 if valid {
1733 0
1734 } else {
1735 valid_ctxt.errors = temp_ctxt.errors;
1736 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
1737 temp_ctxt.nb_errors
1738 }
1739 }
1740}
1741
1742pub type SchematronValidityErrorFunc = unsafe extern "C" fn(ctx: *mut c_void, msg: *const c_char);
1755
1756pub type SchematronValidityWarningFunc = unsafe extern "C" fn(ctx: *mut c_void, msg: *const c_char);
1758
1759#[derive(Clone, Copy)]
1760struct SchematronSendPtr(*mut c_void);
1761unsafe impl Send for SchematronSendPtr {}
1762unsafe impl Sync for SchematronSendPtr {}
1763impl Default for SchematronSendPtr {
1764 fn default() -> Self {
1765 SchematronSendPtr(core::ptr::null_mut())
1766 }
1767}
1768
1769#[derive(Clone, Copy, Default)]
1770struct SchematronParserState {
1771 err: Option<SchematronValidityErrorFunc>,
1772 warn: Option<SchematronValidityWarningFunc>,
1773 ctx: SchematronSendPtr,
1774}
1775
1776#[derive(Clone, Copy, Default)]
1777struct SchematronValidState {
1778 err: Option<SchematronValidityErrorFunc>,
1779 warn: Option<SchematronValidityWarningFunc>,
1780 ctx: SchematronSendPtr,
1781 options: c_int,
1782}
1783
1784static SCHEMATRON_PARSER_STATE: once_cell::sync::Lazy<
1785 parking_lot::Mutex<std::collections::HashMap<usize, SchematronParserState>>,
1786> = once_cell::sync::Lazy::new(Default::default);
1787
1788static SCHEMATRON_VALID_STATE: once_cell::sync::Lazy<
1789 parking_lot::Mutex<std::collections::HashMap<usize, SchematronValidState>>,
1790> = once_cell::sync::Lazy::new(Default::default);
1791
1792#[no_mangle]
1814pub unsafe extern "C" fn xmlSchematronSetParserErrors(
1815 ctxt: *mut c_void,
1816 err: Option<SchematronValidityErrorFunc>,
1817 warn: Option<SchematronValidityWarningFunc>,
1818 ctx: *mut c_void,
1819) {
1820 if ctxt.is_null() {
1821 return;
1822 }
1823 let mut map = SCHEMATRON_PARSER_STATE.lock();
1824 let st = map.entry(ctxt as usize).or_default();
1825 st.err = err;
1826 st.warn = warn;
1827 st.ctx = SchematronSendPtr(ctx);
1828}
1829
1830#[no_mangle]
1851pub unsafe extern "C" fn xmlSchematronGetParserErrors(
1852 ctxt: *mut c_void,
1853 err: *mut Option<SchematronValidityErrorFunc>,
1854 warn: *mut Option<SchematronValidityWarningFunc>,
1855 ctx: *mut *mut c_void,
1856) -> c_int {
1857 if ctxt.is_null() {
1858 return -1;
1859 }
1860 let map = SCHEMATRON_PARSER_STATE.lock();
1861 let st = map.get(&(ctxt as usize)).copied().unwrap_or_default();
1862 if !err.is_null() {
1863 *err = st.err;
1864 }
1865 if !warn.is_null() {
1866 *warn = st.warn;
1867 }
1868 if !ctx.is_null() {
1869 *ctx = st.ctx.0;
1870 }
1871 0
1872}
1873
1874#[no_mangle]
1895pub unsafe extern "C" fn xmlSchematronSetValidErrors(
1896 ctxt: *mut c_void,
1897 err: Option<SchematronValidityErrorFunc>,
1898 warn: Option<SchematronValidityWarningFunc>,
1899 ctx: *mut c_void,
1900) {
1901 if ctxt.is_null() {
1902 return;
1903 }
1904 let mut map = SCHEMATRON_VALID_STATE.lock();
1905 let st = map.entry(ctxt as usize).or_default();
1906 st.err = err;
1907 st.warn = warn;
1908 st.ctx = SchematronSendPtr(ctx);
1909}
1910
1911#[no_mangle]
1932pub unsafe extern "C" fn xmlSchematronGetValidErrors(
1933 ctxt: *mut c_void,
1934 err: *mut Option<SchematronValidityErrorFunc>,
1935 warn: *mut Option<SchematronValidityWarningFunc>,
1936 ctx: *mut *mut c_void,
1937) -> c_int {
1938 if ctxt.is_null() {
1939 return -1;
1940 }
1941 let map = SCHEMATRON_VALID_STATE.lock();
1942 let st = map.get(&(ctxt as usize)).copied().unwrap_or_default();
1943 if !err.is_null() {
1944 *err = st.err;
1945 }
1946 if !warn.is_null() {
1947 *warn = st.warn;
1948 }
1949 if !ctx.is_null() {
1950 *ctx = st.ctx.0;
1951 }
1952 0
1953}
1954
1955#[no_mangle]
1973pub unsafe extern "C" fn xmlSchematronSetValidOptions(ctxt: *mut c_void, options: c_int) -> c_int {
1974 if ctxt.is_null() {
1975 return -1;
1976 }
1977 let mut map = SCHEMATRON_VALID_STATE.lock();
1978 let st = map.entry(ctxt as usize).or_default();
1979 let old = st.options;
1980 st.options = options;
1981 old
1982}
1983
1984#[no_mangle]
2001pub unsafe extern "C" fn xmlSchematronValidCtxtGetOptions(ctxt: *mut c_void) -> c_int {
2002 if ctxt.is_null() {
2003 return -1;
2004 }
2005 SCHEMATRON_VALID_STATE
2006 .lock()
2007 .get(&(ctxt as usize))
2008 .map_or(0, |st| st.options)
2009}
2010
2011#[no_mangle]
2029pub const unsafe extern "C" fn xmlSchematronIsValid(ctxt: *mut c_void) -> c_int {
2030 if ctxt.is_null() {
2031 return 0;
2032 }
2033 unsafe {
2034 let vc = &*(ctxt as *const SchematronValidCtxt);
2035 if vc.nb_errors > 0 {
2036 0
2037 } else {
2038 1
2039 }
2040 }
2041}
2042
2043#[no_mangle]
2061pub unsafe extern "C" fn xmlSchematronValidateOneElement(
2062 ctxt: *mut c_void,
2063 elem: *mut _xmlNode,
2064) -> c_int {
2065 if ctxt.is_null() || elem.is_null() {
2066 return -1;
2067 }
2068 unsafe {
2069 let valid_ctxt = &mut *(ctxt as *mut SchematronValidCtxt);
2070 let schema = match &valid_ctxt.schema {
2071 Some(s) => s,
2072 None => return -1,
2073 };
2074 let doc = (*elem).doc;
2075 if doc.is_null() {
2076 return -1;
2077 }
2078 let mut temp_ctxt = SchematronValidCtxt::new();
2081 temp_ctxt.active_phase = valid_ctxt.active_phase.clone();
2082 let valid = schematron_validate_doc(schema, doc, &mut temp_ctxt);
2083 if !valid {
2084 valid_ctxt.errors = temp_ctxt.errors;
2085 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2086 }
2087 if valid {
2088 0
2089 } else {
2090 -1
2091 }
2092 }
2093}
2094
2095#[cfg(test)]
2100mod tests {
2101 use super::*;
2102
2103 #[test]
2106 fn test_parse_simple_schema() {
2107 let schema_xml = r#"<?xml version="1.0"?>
2108<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2109 <pattern id="P1">
2110 <rule context="root">
2111 <assert test="count(*) > 0">Root must have children</assert>
2112 </rule>
2113 </pattern>
2114</schema>"#;
2115
2116 let result = schematron_parse(schema_xml);
2117 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2118 let schema = result.unwrap();
2119 assert_eq!(schema.pattern_order.len(), 1);
2120 assert_eq!(schema.rules.len(), 1);
2121 }
2122
2123 #[test]
2124 fn test_parse_with_ns() {
2125 let schema_xml = r#"<?xml version="1.0"?>
2126<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2127 <ns prefix="doc" uri="http://example.com/doc"/>
2128 <pattern id="P1">
2129 <rule context="doc:entry">
2130 <assert test="doc:title">Entry must have a title</assert>
2131 </rule>
2132 </pattern>
2133</schema>"#;
2134
2135 let result = schematron_parse(schema_xml);
2136 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2137 let schema = result.unwrap();
2138 assert!(schema.ns.contains_key("doc"));
2139 assert_eq!(schema.ns.get("doc").unwrap(), "http://example.com/doc");
2140 }
2141
2142 #[test]
2143 fn test_parse_with_phases() {
2144 let schema_xml = r#"<?xml version="1.0"?>
2145<schema xmlns="http://purl.oclc.org/dsdl/schematron" defaultPhase="phaseA">
2146 <phase id="phaseA">
2147 <active pattern="P1"/>
2148 </phase>
2149 <phase id="phaseB">
2150 <active pattern="P2"/>
2151 </phase>
2152 <pattern id="P1">
2153 <rule context="root">
2154 <assert test="true()">Always passes</assert>
2155 </rule>
2156 </pattern>
2157 <pattern id="P2">
2158 <rule context="root">
2159 <assert test="false()">Always fails</assert>
2160 </rule>
2161 </pattern>
2162</schema>"#;
2163
2164 let result = schematron_parse(schema_xml);
2165 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2166 let schema = result.unwrap();
2167 assert_eq!(schema.phases.len(), 2);
2168 assert!(schema.phases.contains_key("phaseA"));
2169 assert!(schema.phases.contains_key("phaseB"));
2170 assert_eq!(schema.default_phase.as_deref(), Some("phaseA"));
2171 }
2172
2173 #[test]
2174 fn test_parse_report_pattern() {
2175 let schema_xml = r#"<?xml version="1.0"?>
2176<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2177 <pattern id="P1">
2178 <rule context="root">
2179 <report test="@deprecated">Element is deprecated</report>
2180 </rule>
2181 </pattern>
2182</schema>"#;
2183
2184 let result = schematron_parse(schema_xml);
2185 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2186 let schema = result.unwrap();
2187 let rule = schema.rules.values().next().unwrap();
2188 assert_eq!(rule.patterns.len(), 1);
2189 assert_eq!(rule.patterns[0].pattern_type, SchematronPatternType::Report);
2190 assert_eq!(rule.patterns[0].test, "@deprecated");
2191 }
2192
2193 #[test]
2194 fn test_parse_abstract_rule() {
2195 let schema_xml = r#"<?xml version="1.0"?>
2196<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2197 <pattern id="P1">
2198 <rule id="abstractRule" abstract="true" context="*">
2199 <assert test="true()">Abstract assertion</assert>
2200 </rule>
2201 <rule id="concreteRule" context="root">
2202 <extends rule="abstractRule"/>
2203 <assert test="count(*) > 0">Concrete assertion</assert>
2204 </rule>
2205 </pattern>
2206</schema>"#;
2207
2208 let result = schematron_parse(schema_xml);
2209 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2210 let schema = result.unwrap();
2211 assert!(schema.rules.contains_key("abstractRule"));
2212 assert!(schema.rules.contains_key("concreteRule"));
2213 let abstract_rule = &schema.rules["abstractRule"];
2214 assert!(abstract_rule.abstract_);
2215 let concrete_rule = &schema.rules["concreteRule"];
2216 assert!(!concrete_rule.abstract_);
2217 assert_eq!(concrete_rule.extends.len(), 1);
2218 assert_eq!(concrete_rule.extends[0], "abstractRule");
2219 }
2220
2221 #[test]
2222 fn test_parse_with_diagnostics() {
2223 let schema_xml = r#"<?xml version="1.0"?>
2224<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2225 <diagnostics>
2226 <diagnostic id="diag1">This is a diagnostic message</diagnostic>
2227 </diagnostics>
2228 <pattern id="P1">
2229 <rule context="root">
2230 <assert test="true()" diagnostics="diag1">Assertion with diagnostic</assert>
2231 </rule>
2232 </pattern>
2233</schema>"#;
2234
2235 let result = schematron_parse(schema_xml);
2236 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2237 let schema = result.unwrap();
2238 assert!(schema.diagnostics.contains_key("diag1"));
2239 assert_eq!(
2240 schema.diagnostics["diag1"].text,
2241 "This is a diagnostic message"
2242 );
2243 }
2244
2245 #[test]
2246 fn test_parse_with_attributes() {
2247 let schema_xml = r#"<?xml version="1.0"?>
2248<schema xmlns="http://purl.oclc.org/dsdl/schematron" title="Test Schema">
2249 <pattern id="P1">
2250 <rule context="root">
2251 <assert test="true()" flag="warn" role="error" id="a1" icon="info" see="http://example.com">
2252 Test message
2253 </assert>
2254 </rule>
2255 </pattern>
2256</schema>"#;
2257
2258 let result = schematron_parse(schema_xml);
2259 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2260 let schema = result.unwrap();
2261 assert_eq!(schema.title.as_deref(), Some("Test Schema"));
2262 let rule = schema.rules.values().next().unwrap();
2263 let pat = &rule.patterns[0];
2264 assert_eq!(pat.flag.as_deref(), Some("warn"));
2265 assert_eq!(pat.role.as_deref(), Some("error"));
2266 assert_eq!(pat.id.as_deref(), Some("a1"));
2267 assert_eq!(pat.icon.as_deref(), Some("info"));
2268 assert_eq!(pat.see.as_deref(), Some("http://example.com"));
2269 }
2270
2271 #[test]
2272 fn test_parse_empty_schema() {
2273 let schema_xml = r#"<?xml version="1.0"?>
2274<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2275</schema>"#;
2276
2277 let result = schematron_parse(schema_xml);
2278 assert!(result.is_ok(), "Failed to parse empty schema");
2279 let schema = result.unwrap();
2280 assert!(schema.rules.is_empty());
2281 assert!(schema.phases.is_empty());
2282 }
2283
2284 #[test]
2285 fn test_parse_no_assertions() {
2286 let schema_xml = r#"<?xml version="1.0"?>
2287<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2288 <pattern id="P1">
2289 <rule context="root">
2290 </rule>
2291 </pattern>
2292</schema>"#;
2293
2294 let result = schematron_parse(schema_xml);
2295 assert!(result.is_ok(), "Failed to parse schema with no assertions");
2296 let schema = result.unwrap();
2297 let rule = schema.rules.values().next().unwrap();
2298 assert!(rule.patterns.is_empty());
2299 }
2300
2301 #[test]
2302 fn test_parse_invalid_root_element() {
2303 let schema_xml = r#"<?xml version="1.0"?>
2304<not-schema xmlns="http://purl.oclc.org/dsdl/schematron">
2305</not-schema>"#;
2306
2307 let result = schematron_parse(schema_xml);
2308 assert!(result.is_err(), "Should fail with wrong root element");
2309 assert!(
2310 result.err().unwrap().contains("Expected '<schema>'"),
2311 "Error should mention expected schema element"
2312 );
2313 }
2314
2315 #[test]
2316 fn test_parse_empty_document_fails() {
2317 let result = schematron_parse("");
2318 assert!(result.is_err());
2319 }
2320
2321 #[test]
2322 fn test_parse_invalid_xml_fails() {
2323 let result = schematron_parse("not valid xml <<<");
2324 assert!(result.is_err());
2325 }
2326
2327 #[test]
2328 fn test_parse_schema_with_let_and_param() {
2329 let schema_xml = r#"<?xml version="1.0"?>
2330<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2331 <pattern id="P1">
2332 <rule context="root">
2333 <let name="x" value="42"/>
2334 <param name="debug" value="true"/>
2335 <assert test="true()">Test with let and param</assert>
2336 </rule>
2337 </pattern>
2338</schema>"#;
2339
2340 let result = schematron_parse(schema_xml);
2341 assert!(
2342 result.is_ok(),
2343 "Failed to parse schema with let/param: {:?}",
2344 result.err()
2345 );
2346 let schema = result.unwrap();
2347 assert_eq!(schema.rules.len(), 1);
2348 }
2349
2350 #[test]
2351 fn test_parse_schema_with_documentation() {
2352 let schema_xml = r#"<?xml version="1.0"?>
2353<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2354 <p>This is documentation</p>
2355 <caption>Table caption</caption>
2356 <pattern id="P1">
2357 <p>Pattern documentation</p>
2358 <rule context="root">
2359 <p>Rule documentation</p>
2360 <assert test="true()">Real assertion</assert>
2361 </rule>
2362 </pattern>
2363</schema>"#;
2364
2365 let result = schematron_parse(schema_xml);
2366 assert!(result.is_ok(), "Failed to parse schema with documentation");
2367 let schema = result.unwrap();
2368 assert_eq!(schema.rules.len(), 1);
2369 }
2370
2371 #[test]
2374 fn test_validate_assert_pass() {
2375 let schema_xml = r#"<?xml version="1.0"?>
2376<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2377 <pattern id="P1">
2378 <rule context="root">
2379 <assert test="true()">Always passes</assert>
2380 </rule>
2381 </pattern>
2382</schema>"#;
2383
2384 let doc_xml = r#"<?xml version="1.0"?>
2385<root>Hello</root>"#;
2386
2387 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2388
2389 let doc = unsafe {
2390 crate::abi::exports_xml2::xmlReadMemory(
2391 doc_xml.as_ptr() as *const c_char,
2392 doc_xml.len() as c_int,
2393 c"test.xml".as_ptr() as *const c_char,
2394 ptr::null(),
2395 0,
2396 )
2397 };
2398 assert!(!doc.is_null(), "Failed to parse document");
2399
2400 let mut ctxt = SchematronValidCtxt::new();
2401 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2402 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2403
2404 assert!(valid, "Validation failed: {:?}", ctxt.errors);
2405 }
2406
2407 #[test]
2408 fn test_validate_assert_fail() {
2409 let schema_xml = r#"<?xml version="1.0"?>
2410<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2411 <pattern id="P1">
2412 <rule context="root">
2413 <assert test="false()">Always fails</assert>
2414 </rule>
2415 </pattern>
2416</schema>"#;
2417
2418 let doc_xml = r#"<?xml version="1.0"?>
2419<root>Hello</root>"#;
2420
2421 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2422
2423 let doc = unsafe {
2424 crate::abi::exports_xml2::xmlReadMemory(
2425 doc_xml.as_ptr() as *const c_char,
2426 doc_xml.len() as c_int,
2427 c"test.xml".as_ptr() as *const c_char,
2428 ptr::null(),
2429 0,
2430 )
2431 };
2432 assert!(!doc.is_null());
2433
2434 let mut ctxt = SchematronValidCtxt::new();
2435 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2436 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2437
2438 assert!(
2439 !valid,
2440 "Validation should have failed, errors: {:?}",
2441 ctxt.errors
2442 );
2443 assert!(ctxt.nb_errors > 0);
2444 }
2445
2446 #[test]
2447 fn test_validate_report_pass() {
2448 let schema_xml = r#"<?xml version="1.0"?>
2449<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2450 <pattern id="P1">
2451 <rule context="root">
2452 <report test="false()">Report should not trigger</report>
2453 </rule>
2454 </pattern>
2455</schema>"#;
2456
2457 let doc_xml = r#"<?xml version="1.0"?>
2458<root>Hello</root>"#;
2459
2460 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2461
2462 let doc = unsafe {
2463 crate::abi::exports_xml2::xmlReadMemory(
2464 doc_xml.as_ptr() as *const c_char,
2465 doc_xml.len() as c_int,
2466 c"test.xml".as_ptr() as *const c_char,
2467 ptr::null(),
2468 0,
2469 )
2470 };
2471 assert!(!doc.is_null());
2472
2473 let mut ctxt = SchematronValidCtxt::new();
2474 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2475 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2476
2477 assert!(valid, "Report should not trigger: {:?}", ctxt.errors);
2478 }
2479
2480 #[test]
2481 fn test_validate_report_fail() {
2482 let schema_xml = r#"<?xml version="1.0"?>
2483<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2484 <pattern id="P1">
2485 <rule context="root">
2486 <report test="true()">Report should trigger</report>
2487 </rule>
2488 </pattern>
2489</schema>"#;
2490
2491 let doc_xml = r#"<?xml version="1.0"?>
2492<root>Hello</root>"#;
2493
2494 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2495
2496 let doc = unsafe {
2497 crate::abi::exports_xml2::xmlReadMemory(
2498 doc_xml.as_ptr() as *const c_char,
2499 doc_xml.len() as c_int,
2500 c"test.xml".as_ptr() as *const c_char,
2501 ptr::null(),
2502 0,
2503 )
2504 };
2505 assert!(!doc.is_null());
2506
2507 let mut ctxt = SchematronValidCtxt::new();
2508 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2509 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2510
2511 assert!(!valid, "Report should have triggered");
2512 assert!(ctxt.nb_errors > 0);
2513 }
2514
2515 #[test]
2516 fn test_validate_context_matching() {
2517 let schema_xml = r#"<?xml version="1.0"?>
2518<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2519 <pattern id="P1">
2520 <rule context="child">
2521 <assert test="true()">Child matches</assert>
2522 </rule>
2523 </pattern>
2524</schema>"#;
2525
2526 let doc_xml = r#"<?xml version="1.0"?>
2527<root>
2528 <child>A</child>
2529 <child>B</child>
2530</root>"#;
2531
2532 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2533
2534 let doc = unsafe {
2535 crate::abi::exports_xml2::xmlReadMemory(
2536 doc_xml.as_ptr() as *const c_char,
2537 doc_xml.len() as c_int,
2538 c"test.xml".as_ptr() as *const c_char,
2539 ptr::null(),
2540 0,
2541 )
2542 };
2543 assert!(!doc.is_null());
2544
2545 let mut ctxt = SchematronValidCtxt::new();
2546 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2547 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2548
2549 assert!(valid, "Context matching failed: {:?}", ctxt.errors);
2550 }
2551
2552 #[test]
2553 fn test_validate_multiple_rules() {
2554 let schema_xml = r#"<?xml version="1.0"?>
2555<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2556 <pattern id="P1">
2557 <rule context="root">
2558 <assert test="true()">Root passes</assert>
2559 </rule>
2560 </pattern>
2561 <pattern id="P2">
2562 <rule context="child">
2563 <assert test="true()">Child passes</assert>
2564 </rule>
2565 </pattern>
2566</schema>"#;
2567
2568 let doc_xml = r#"<?xml version="1.0"?>
2569<root>
2570 <child>Content</child>
2571</root>"#;
2572
2573 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2574
2575 let doc = unsafe {
2576 crate::abi::exports_xml2::xmlReadMemory(
2577 doc_xml.as_ptr() as *const c_char,
2578 doc_xml.len() as c_int,
2579 c"test.xml".as_ptr() as *const c_char,
2580 ptr::null(),
2581 0,
2582 )
2583 };
2584 assert!(!doc.is_null());
2585
2586 let mut ctxt = SchematronValidCtxt::new();
2587 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2588 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2589
2590 assert!(valid, "Multiple rules failed: {:?}", ctxt.errors);
2591 }
2592
2593 #[test]
2594 fn test_validate_with_phase_filtering() {
2595 let schema_xml = r#"<?xml version="1.0"?>
2596<schema xmlns="http://purl.oclc.org/dsdl/schematron" defaultPhase="phaseA">
2597 <phase id="phaseA">
2598 <active pattern="P1"/>
2599 </phase>
2600 <phase id="phaseB">
2601 <active pattern="P2"/>
2602 </phase>
2603 <pattern id="P1">
2604 <rule context="root">
2605 <assert test="true()">Always passes</assert>
2606 </rule>
2607 </pattern>
2608 <pattern id="P2">
2609 <rule context="root">
2610 <assert test="false()">Always fails</assert>
2611 </rule>
2612 </pattern>
2613</schema>"#;
2614
2615 let doc_xml = r#"<?xml version="1.0"?>
2616<root>Hello</root>"#;
2617
2618 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2619
2620 let doc = unsafe {
2621 crate::abi::exports_xml2::xmlReadMemory(
2622 doc_xml.as_ptr() as *const c_char,
2623 doc_xml.len() as c_int,
2624 c"test.xml".as_ptr() as *const c_char,
2625 ptr::null(),
2626 0,
2627 )
2628 };
2629 assert!(!doc.is_null());
2630
2631 let mut ctxt = SchematronValidCtxt::new();
2633 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2634 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2635
2636 assert!(
2637 valid,
2638 "Phase filtering should make validation pass: {:?}",
2639 ctxt.errors
2640 );
2641 }
2642
2643 #[test]
2644 fn test_validate_no_rules() {
2645 let schema_xml = r#"<?xml version="1.0"?>
2646<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2647</schema>"#;
2648
2649 let doc_xml = r#"<?xml version="1.0"?>
2650<root>Hello</root>"#;
2651
2652 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2653
2654 let doc = unsafe {
2655 crate::abi::exports_xml2::xmlReadMemory(
2656 doc_xml.as_ptr() as *const c_char,
2657 doc_xml.len() as c_int,
2658 c"test.xml".as_ptr() as *const c_char,
2659 ptr::null(),
2660 0,
2661 )
2662 };
2663 assert!(!doc.is_null());
2664
2665 let mut ctxt = SchematronValidCtxt::new();
2666 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2667 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2668
2669 assert!(valid, "Empty schema should pass validation");
2670 }
2671
2672 #[test]
2673 fn test_validate_extends_resolution() {
2674 let schema_xml = r#"<?xml version="1.0"?>
2675<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2676 <pattern id="P1">
2677 <rule id="base" abstract="true" context="*">
2678 <assert test="true()">Base assertion</assert>
2679 </rule>
2680 <rule id="derived" context="root">
2681 <extends rule="base"/>
2682 <assert test="true()">Derived assertion</assert>
2683 </rule>
2684 </pattern>
2685</schema>"#;
2686
2687 let doc_xml = r#"<?xml version="1.0"?>
2688<root>Hello</root>"#;
2689
2690 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2691
2692 let resolved = schema.resolve_rule("derived");
2694 assert!(resolved.is_some());
2695 let resolved = resolved.unwrap();
2696 assert_eq!(
2698 resolved.patterns.len(),
2699 2,
2700 "Should have inherited the base pattern"
2701 );
2702
2703 let doc = unsafe {
2704 crate::abi::exports_xml2::xmlReadMemory(
2705 doc_xml.as_ptr() as *const c_char,
2706 doc_xml.len() as c_int,
2707 c"test.xml".as_ptr() as *const c_char,
2708 ptr::null(),
2709 0,
2710 )
2711 };
2712 assert!(!doc.is_null());
2713
2714 let mut ctxt = SchematronValidCtxt::new();
2715 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2716 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2717
2718 assert!(valid, "Extends resolution failed: {:?}", ctxt.errors);
2719 }
2720
2721 #[test]
2722 fn test_validate_assert_with_flag() {
2723 let schema_xml = r#"<?xml version="1.0"?>
2724<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2725 <pattern id="P1">
2726 <rule context="root">
2727 <assert test="false()" flag="warn">Warning message</assert>
2728 </rule>
2729 </pattern>
2730</schema>"#;
2731
2732 let doc_xml = r#"<?xml version="1.0"?>
2733<root>Hello</root>"#;
2734
2735 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2736
2737 let doc = unsafe {
2738 crate::abi::exports_xml2::xmlReadMemory(
2739 doc_xml.as_ptr() as *const c_char,
2740 doc_xml.len() as c_int,
2741 c"test.xml".as_ptr() as *const c_char,
2742 ptr::null(),
2743 0,
2744 )
2745 };
2746 assert!(!doc.is_null());
2747
2748 let mut ctxt = SchematronValidCtxt::new();
2749 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2750 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2751
2752 assert!(!valid);
2753 assert!(ctxt.nb_errors > 0);
2754 assert!(
2756 ctxt.errors[0].contains("[warn]"),
2757 "Error should include flag"
2758 );
2759 }
2760
2761 #[test]
2764 fn test_c_abi_new_free_parser_ctxt() {
2765 let ctxt = unsafe { xmlSchematronNewParserCtxt(ptr::null()) };
2766 assert!(!ctxt.is_null());
2767 unsafe { xmlSchematronFreeParserCtxt(ctxt) };
2768 }
2770
2771 #[test]
2772 fn test_c_abi_new_free_valid_ctxt() {
2773 let schema = unsafe { xmlSchematronNewParserCtxt(ptr::null()) };
2774 assert!(!schema.is_null());
2775
2776 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema, 0) };
2777 assert!(!valid_ctxt.is_null());
2778
2779 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2780 unsafe { xmlSchematronFreeParserCtxt(schema) };
2781 }
2783
2784 #[test]
2785 fn test_c_abi_parse_free() {
2786 let schema_xml = r#"<?xml version="1.0"?>
2787<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2788 <pattern id="P1">
2789 <rule context="root">
2790 <assert test="true()">Test</assert>
2791 </rule>
2792 </pattern>
2793</schema>"#;
2794
2795 let ctxt = unsafe {
2796 xmlSchematronNewMemParserCtxt(
2797 schema_xml.as_ptr() as *const c_char,
2798 schema_xml.len() as c_int,
2799 )
2800 };
2801 assert!(!ctxt.is_null());
2802
2803 let schema = unsafe { xmlSchematronParse(ctxt) };
2804 assert!(!schema.is_null());
2805
2806 unsafe { xmlSchematronFree(schema) };
2807 }
2809
2810 #[test]
2811 fn test_c_abi_validate_doc() {
2812 let schema_xml = r#"<?xml version="1.0"?>
2813<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2814 <pattern id="P1">
2815 <rule context="root">
2816 <assert test="true()">Always passes</assert>
2817 </rule>
2818 </pattern>
2819</schema>"#;
2820
2821 let doc_xml = r#"<?xml version="1.0"?>
2822<root>Hello</root>"#;
2823
2824 let ctxt = unsafe {
2825 xmlSchematronNewMemParserCtxt(
2826 schema_xml.as_ptr() as *const c_char,
2827 schema_xml.len() as c_int,
2828 )
2829 };
2830 assert!(!ctxt.is_null());
2831
2832 let schema = unsafe { xmlSchematronParse(ctxt) };
2833 assert!(!schema.is_null());
2834
2835 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema, 0) };
2836 assert!(!valid_ctxt.is_null());
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 result = unsafe { xmlSchematronValidateDoc(valid_ctxt, doc) };
2850 assert_eq!(result, 0, "Validation should pass (return 0)");
2851
2852 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2853 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2854 unsafe { xmlSchematronFree(schema) };
2855 }
2856
2857 #[test]
2858 fn test_c_abi_validate_fail() {
2859 let schema_xml = r#"<?xml version="1.0"?>
2860<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2861 <pattern id="P1">
2862 <rule context="root">
2863 <assert test="false()">Always fails</assert>
2864 </rule>
2865 </pattern>
2866</schema>"#;
2867
2868 let doc_xml = r#"<?xml version="1.0"?>
2869<root>Hello</root>"#;
2870
2871 let ctxt = unsafe {
2872 xmlSchematronNewMemParserCtxt(
2873 schema_xml.as_ptr() as *const c_char,
2874 schema_xml.len() as c_int,
2875 )
2876 };
2877 assert!(!ctxt.is_null());
2878
2879 let schema = unsafe { xmlSchematronParse(ctxt) };
2880 assert!(!schema.is_null());
2881
2882 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema, 0) };
2883 assert!(!valid_ctxt.is_null());
2884
2885 let doc = unsafe {
2886 crate::abi::exports_xml2::xmlReadMemory(
2887 doc_xml.as_ptr() as *const c_char,
2888 doc_xml.len() as c_int,
2889 c"test.xml".as_ptr() as *const c_char,
2890 ptr::null(),
2891 0,
2892 )
2893 };
2894 assert!(!doc.is_null());
2895
2896 let result = unsafe { xmlSchematronValidateDoc(valid_ctxt, doc) };
2897 assert!(result > 0, "Validation should fail (return > 0)");
2898
2899 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2900 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2901 unsafe { xmlSchematronFree(schema) };
2902 }
2903
2904 #[test]
2905 fn test_c_abi_null_handling() {
2906 unsafe { xmlSchematronFree(ptr::null_mut()) };
2908 unsafe { xmlSchematronFreeParserCtxt(ptr::null_mut()) };
2909 unsafe { xmlSchematronFreeValidCtxt(ptr::null_mut()) };
2910
2911 let result = unsafe { xmlSchematronParse(ptr::null_mut()) };
2913 assert!(result.is_null());
2914
2915 let result = unsafe { xmlSchematronValidateDoc(ptr::null_mut(), ptr::null_mut()) };
2917 assert_eq!(result, -1);
2918 }
2919
2920 #[test]
2923 fn test_validate_null_doc() {
2924 let schema = SchematronSchema::new();
2925 let mut ctxt = SchematronValidCtxt::new();
2926 let valid = unsafe { schematron_validate_doc(&schema, ptr::null_mut(), &mut ctxt) };
2927 assert!(!valid);
2928 assert!(ctxt.nb_errors > 0);
2929 }
2930
2931 #[test]
2932 fn test_active_rules_default_phase() {
2933 let mut schema = SchematronSchema::new();
2934
2935 let rule = SchematronRule::new("root".to_string());
2936 schema.rules.insert("r1".to_string(), rule);
2937
2938 schema
2939 .pattern_groups
2940 .insert("p1".to_string(), vec!["r1".to_string()]);
2941 schema.pattern_order.push("p1".to_string());
2942
2943 let rules = schema.active_rules(None);
2944 assert_eq!(rules.len(), 1);
2945 }
2946
2947 #[test]
2948 fn test_active_rules_unknown_phase() {
2949 let mut schema = SchematronSchema::new();
2950
2951 let rule = SchematronRule::new("root".to_string());
2952 schema.rules.insert("r1".to_string(), rule);
2953
2954 schema
2955 .pattern_groups
2956 .insert("p1".to_string(), vec!["r1".to_string()]);
2957 schema.pattern_order.push("p1".to_string());
2958
2959 let rules = schema.active_rules(Some("nonexistent"));
2960 assert_eq!(rules.len(), 1, "Unknown phase should use all patterns");
2961 }
2962
2963 #[test]
2964 fn test_parse_schema_with_span_and_emph() {
2965 let schema_xml = r#"<?xml version="1.0"?>
2966<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2967 <pattern id="P1">
2968 <rule context="root">
2969 <assert test="true()">Message with <span class="x">inline</span> and <emph>emphasis</emph></assert>
2970 </rule>
2971 </pattern>
2972</schema>"#;
2973
2974 let result = schematron_parse(schema_xml);
2975 assert!(
2976 result.is_ok(),
2977 "Failed to parse schema with span/emph: {:?}",
2978 result.err()
2979 );
2980 let schema = result.unwrap();
2981 let rule = schema.rules.values().next().unwrap();
2982 let pat = &rule.patterns[0];
2983 assert!(
2985 pat.text.contains("inline"),
2986 "Text should include span content"
2987 );
2988 assert!(
2989 pat.text.contains("emphasis"),
2990 "Text should include emph content"
2991 );
2992 }
2993
2994 #[test]
2995 fn test_schematron_pattern_new_assert() {
2996 let pat = SchematronPattern::new(
2997 SchematronPatternType::Assert,
2998 "true()".to_string(),
2999 "Test message".to_string(),
3000 );
3001 assert_eq!(pat.pattern_type, SchematronPatternType::Assert);
3002 assert_eq!(pat.test, "true()");
3003 assert_eq!(pat.text, "Test message");
3004 assert!(pat.compiled_test.is_some());
3005 }
3006
3007 #[test]
3008 fn test_schematron_pattern_new_report() {
3009 let pat = SchematronPattern::new(
3010 SchematronPatternType::Report,
3011 "false()".to_string(),
3012 "Report message".to_string(),
3013 );
3014 assert_eq!(pat.pattern_type, SchematronPatternType::Report);
3015 assert!(pat.compiled_test.is_some());
3016 }
3017
3018 #[test]
3019 fn test_schematron_rule_new() {
3020 let rule = SchematronRule::new("root".to_string());
3021 assert_eq!(rule.context, "root");
3022 assert!(rule.patterns.is_empty());
3023 assert!(!rule.abstract_);
3024 }
3025
3026 #[test]
3027 fn test_schematron_schema_new() {
3028 let schema = SchematronSchema::new();
3029 assert_eq!(schema.query_binding, "xslt");
3030 assert!(schema.rules.is_empty());
3031 assert!(schema.phases.is_empty());
3032 assert!(schema.ns.is_empty());
3033 }
3034
3035 #[test]
3036 fn test_schematron_valid_ctxt_new() {
3037 let ctxt = SchematronValidCtxt::new();
3038 assert!(ctxt.errors.is_empty());
3039 assert_eq!(ctxt.nb_errors, 0);
3040 assert!(ctxt.active_phase.is_none());
3041 }
3042
3043 #[test]
3044 fn test_validate_assert_with_child_count() {
3045 let schema_xml = r#"<?xml version="1.0"?>
3046<schema xmlns="http://purl.oclc.org/dsdl/schematron">
3047 <pattern id="P1">
3048 <rule context="root">
3049 <assert test="count(*) > 0">Root must have at least one child element</assert>
3050 </rule>
3051 </pattern>
3052</schema>"#;
3053
3054 let doc_xml = r#"<?xml version="1.0"?>
3055<root>
3056 <child>Content</child>
3057</root>"#;
3058
3059 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
3060
3061 let doc = unsafe {
3062 crate::abi::exports_xml2::xmlReadMemory(
3063 doc_xml.as_ptr() as *const c_char,
3064 doc_xml.len() as c_int,
3065 c"test.xml".as_ptr() as *const c_char,
3066 ptr::null(),
3067 0,
3068 )
3069 };
3070 assert!(!doc.is_null());
3071
3072 let mut ctxt = SchematronValidCtxt::new();
3073 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
3074 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3075
3076 assert!(valid, "Child count check failed: {:?}", ctxt.errors);
3077 }
3078}