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