1#![allow(
29 missing_docs,
30 non_snake_case,
31 non_camel_case_types,
32 non_upper_case_globals
33)]
34
35use core::ffi::c_void;
36use core::ptr;
37use std::collections::HashMap;
38use std::os::raw::{c_char, c_int};
39
40use crate::abi::structs::*;
41use crate::abi::types::xmlElementType::*;
42use crate::xml::xpath::ast::CompiledExpr;
43use crate::xml::xpath::context::XPathContext;
44use crate::xml::xpath::types::XPathValue;
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum SchematronPatternType {
53 Assert,
55 Report,
57}
58
59#[derive(Debug, Clone)]
65pub struct SchematronPattern {
66 pub pattern_type: SchematronPatternType,
68 pub test: String,
70 pub compiled_test: Option<CompiledExpr>,
72 pub text: String,
74 pub flag: Option<String>,
76 pub role: Option<String>,
78 pub id: Option<String>,
80 pub icon: Option<String>,
82 pub see: Option<String>,
84 pub diagnostics: Option<String>,
86}
87
88impl SchematronPattern {
89 pub fn new(pattern_type: SchematronPatternType, test: String, text: String) -> Self {
91 Self {
92 pattern_type,
93 compiled_test: crate::xml::xpath::compile(&test),
94 test,
95 text,
96 flag: None,
97 role: None,
98 id: None,
99 icon: None,
100 see: None,
101 diagnostics: None,
102 }
103 }
104}
105
106#[derive(Debug, Clone)]
116pub struct SchematronRule {
117 pub context: String,
119 pub compiled_context: Option<CompiledExpr>,
121 pub patterns: Vec<SchematronPattern>,
123 pub id: Option<String>,
125 pub abstract_: bool,
127 pub extends: Vec<String>,
129}
130
131impl SchematronRule {
132 pub fn new(context: String) -> Self {
134 Self {
135 compiled_context: crate::xml::xpath::compile(&context),
136 context,
137 patterns: Vec::new(),
138 id: None,
139 abstract_: false,
140 extends: Vec::new(),
141 }
142 }
143}
144
145#[derive(Debug, Clone)]
155pub struct SchematronPhase {
156 pub id: String,
158 pub active_patterns: Vec<String>,
160}
161
162#[derive(Debug, Clone)]
168pub struct SchematronDiagnostic {
169 pub id: String,
171 pub text: String,
173 pub icon: Option<String>,
175 pub see: Option<String>,
177}
178
179#[derive(Debug, Clone)]
189pub struct SchematronSchema {
190 pub title: Option<String>,
192 pub phases: HashMap<String, SchematronPhase>,
194 pub rules: HashMap<String, SchematronRule>,
196 pub pattern_groups: HashMap<String, Vec<String>>,
198 pub pattern_order: Vec<String>,
200 pub ns: HashMap<String, String>,
202 pub query_binding: String,
204 pub default_phase: Option<String>,
206 pub diagnostics: HashMap<String, SchematronDiagnostic>,
208 pub errors: Vec<String>,
210}
211
212impl SchematronSchema {
213 pub fn new() -> Self {
215 Self {
216 title: None,
217 phases: HashMap::new(),
218 rules: HashMap::new(),
219 pattern_groups: HashMap::new(),
220 pattern_order: Vec::new(),
221 ns: HashMap::new(),
222 query_binding: "xslt".to_string(),
223 default_phase: None,
224 diagnostics: HashMap::new(),
225 errors: Vec::new(),
226 }
227 }
228
229 pub fn resolve_rule(&self, rule_id: &str) -> Option<SchematronRule> {
231 let rule = self.rules.get(rule_id)?.clone();
232 Some(self.resolve_extends(rule))
233 }
234
235 fn resolve_extends(&self, mut rule: SchematronRule) -> SchematronRule {
237 let extended_ids: Vec<String> = rule.extends.clone();
238 for ext_id in &extended_ids {
239 if let Some(ext_rule) = self.rules.get(ext_id) {
240 let resolved_ext = self.resolve_extends(ext_rule.clone());
242 rule.patterns.extend(resolved_ext.patterns);
243 }
244 }
245 rule
246 }
247
248 pub fn active_rules(&self, phase_id: Option<&str>) -> Vec<SchematronRule> {
252 let active_patterns: Vec<String> = match phase_id {
254 Some(pid) => {
255 if let Some(phase) = self.phases.get(pid) {
256 phase.active_patterns.clone()
257 } else {
258 self.pattern_order.clone()
260 }
261 }
262 None => {
263 if let Some(default) = &self.default_phase {
265 if let Some(phase) = self.phases.get(default) {
266 phase.active_patterns.clone()
267 } else {
268 self.pattern_order.clone()
269 }
270 } else {
271 self.pattern_order.clone()
272 }
273 }
274 };
275
276 let mut result = Vec::new();
277 for pat_id in &active_patterns {
278 if let Some(rule_ids) = self.pattern_groups.get(pat_id) {
279 for rule_id in rule_ids {
280 if let Some(rule) = self.rules.get(rule_id) {
281 if !rule.abstract_ {
282 result.push(self.resolve_extends(rule.clone()));
283 }
284 }
285 }
286 }
287 }
288
289 result
290 }
291}
292
293impl Default for SchematronSchema {
294 fn default() -> Self {
295 Self::new()
296 }
297}
298
299#[derive(Debug)]
308pub struct SchematronValidCtxt {
309 pub schema: Option<SchematronSchema>,
311 pub errors: Vec<String>,
313 pub nb_errors: i32,
315 pub active_phase: Option<String>,
317}
318
319impl SchematronValidCtxt {
320 pub const fn new() -> Self {
322 Self {
323 schema: None,
324 errors: Vec::new(),
325 nb_errors: 0,
326 active_phase: None,
327 }
328 }
329
330 pub fn record_error(&mut self, msg: String) {
332 self.errors.push(msg);
333 self.nb_errors += 1;
334 }
335}
336
337impl Default for SchematronValidCtxt {
338 fn default() -> Self {
339 Self::new()
340 }
341}
342
343unsafe fn get_local_name(node: *mut _xmlNode) -> String {
353 if node.is_null() {
354 return String::new();
355 }
356 unsafe {
357 let name = (*node).name;
358 if name.is_null() {
359 return String::new();
360 }
361 let mut len = 0;
362 while *name.add(len) != 0 {
363 len += 1;
364 }
365 let slice = std::slice::from_raw_parts(name, len);
366 if let Ok(s) = std::str::from_utf8(slice) {
367 if let Some(pos) = s.find(':') {
368 s[pos + 1..].to_string()
369 } else {
370 s.to_string()
371 }
372 } else {
373 String::new()
374 }
375 }
376}
377
378unsafe fn get_node_qname(node: *mut _xmlNode) -> String {
384 if node.is_null() {
385 return String::new();
386 }
387 unsafe {
388 let ns = (*node).ns;
389 let prefix = if !ns.is_null() && !(*ns).prefix.is_null() {
390 let mut len = 0;
391 while *(*ns).prefix.add(len) != 0 {
392 len += 1;
393 }
394 let slice = std::slice::from_raw_parts((*ns).prefix, len);
395 if let Ok(s) = std::str::from_utf8(slice) {
396 format!("{}:", s)
397 } else {
398 String::new()
399 }
400 } else {
401 String::new()
402 };
403
404 let name = (*node).name;
405 if name.is_null() {
406 return String::new();
407 }
408 let mut len = 0;
409 while *name.add(len) != 0 {
410 len += 1;
411 }
412 let slice = std::slice::from_raw_parts(name, len);
413 if let Ok(s) = std::str::from_utf8(slice) {
414 format!("{}{}", prefix, s)
415 } else {
416 String::new()
417 }
418 }
419}
420
421unsafe fn get_node_text(node: *mut _xmlNode) -> String {
427 if node.is_null() {
428 return String::new();
429 }
430 let mut result = String::new();
431 unsafe {
432 let mut child = (*node).children;
433 while !child.is_null() {
434 if ((*child).type_ == XML_TEXT_NODE as c_int
435 || (*child).type_ == XML_CDATA_SECTION_NODE as c_int)
436 && !(*child).content.is_null()
437 {
438 let content = (*child).content;
439 let mut len = 0;
440 while *content.add(len) != 0 {
441 len += 1;
442 }
443 let slice = std::slice::from_raw_parts(content, len);
444 result.push_str(&String::from_utf8_lossy(slice));
445 }
446 child = (*child).next;
447 }
448 }
449 result
450}
451
452unsafe fn get_attr(node: *mut _xmlNode, name: &str) -> Option<String> {
458 if node.is_null() {
459 return None;
460 }
461 unsafe {
462 let mut prop = (*node).properties;
463 while !prop.is_null() {
464 let prop_name = (*prop).name;
465 if !prop_name.is_null() {
466 let mut len = 0;
467 while *prop_name.add(len) != 0 {
468 len += 1;
469 }
470 let slice = std::slice::from_raw_parts(prop_name, len);
471 if let Ok(s) = std::str::from_utf8(slice) {
472 if s == name {
473 return Some(get_node_text(prop as *mut _xmlNode));
474 }
475 }
476 }
477 prop = (*prop).next;
478 }
479 }
480 None
481}
482
483#[allow(dead_code)]
489unsafe fn node_is(node: *mut _xmlNode, local_name: &str) -> bool {
490 if node.is_null() {
491 return false;
492 }
493 unsafe {
494 let name = (*node).name;
495 if name.is_null() {
496 return false;
497 }
498 let mut len = 0;
499 while *name.add(len) != 0 {
500 len += 1;
501 }
502 let slice = std::slice::from_raw_parts(name, len);
503 if let Ok(s) = std::str::from_utf8(slice) {
504 let local = if let Some(pos) = s.find(':') {
505 &s[pos + 1..]
506 } else {
507 s
508 };
509 return local == local_name;
510 }
511 }
512 false
513}
514
515#[allow(dead_code)]
521unsafe fn child_elements(node: *mut _xmlNode) -> Vec<*mut _xmlNode> {
522 let mut children = Vec::new();
523 if node.is_null() {
524 return children;
525 }
526 unsafe {
527 let mut child = (*node).children;
528 while !child.is_null() {
529 if (*child).type_ == XML_ELEMENT_NODE as c_int {
530 children.push(child);
531 }
532 child = (*child).next;
533 }
534 }
535 children
536}
537
538unsafe fn get_inline_text(node: *mut _xmlNode) -> String {
545 if node.is_null() {
546 return String::new();
547 }
548 let mut result = String::new();
549 unsafe {
550 let mut child = (*node).children;
551 while !child.is_null() {
552 if (*child).type_ == XML_TEXT_NODE as c_int
553 || (*child).type_ == XML_CDATA_SECTION_NODE as c_int
554 {
555 if !(*child).content.is_null() {
556 let content = (*child).content;
557 let mut len = 0;
558 while *content.add(len) != 0 {
559 len += 1;
560 }
561 let slice = std::slice::from_raw_parts(content, len);
562 result.push_str(&String::from_utf8_lossy(slice));
563 }
564 } else if (*child).type_ == XML_ELEMENT_NODE as c_int {
565 let local = get_local_name(child);
566 match local.as_str() {
567 "span" | "emph" | "dir" => {
568 result.push_str(&get_inline_text(child));
569 }
570 _ => {}
571 }
572 }
573 child = (*child).next;
574 }
575 }
576 result
577}
578
579pub fn schematron_parse(xml_doc: &str) -> Result<SchematronSchema, String> {
592 let doc_ptr = unsafe {
593 crate::abi::exports_xml2::xmlReadMemory(
594 xml_doc.as_ptr() as *const c_char,
595 xml_doc.len() as c_int,
596 c"schema.sch".as_ptr() as *const c_char,
597 ptr::null(),
598 0,
599 )
600 };
601
602 if doc_ptr.is_null() {
603 return Err("Failed to parse Schematron schema XML document".to_string());
604 }
605
606 let result = unsafe { schematron_parse_doc(doc_ptr) };
607 unsafe {
608 crate::abi::exports_xml2::xmlFreeDoc(doc_ptr);
609 }
610 result
611}
612
613unsafe fn schematron_parse_doc(doc: *mut _xmlDoc) -> Result<SchematronSchema, String> {
619 unsafe {
620 let root = (*doc).children;
621 if root.is_null() {
622 return Err("Schematron document has no root element".to_string());
623 }
624
625 let mut root_elem = root;
627 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
628 root_elem = (*root_elem).next;
629 }
630
631 if root_elem.is_null() {
632 return Err("Schematron document has no root element".to_string());
633 }
634
635 let local_name = get_local_name(root_elem);
636 if local_name != "schema" {
637 return Err(format!(
638 "Expected '<schema>' root element, found '<{}>'",
639 local_name
640 ));
641 }
642
643 Ok(schematron_parse_schema_node(root_elem))
644 }
645}
646
647unsafe fn schematron_parse_schema_node(node: *mut _xmlNode) -> SchematronSchema {
653 unsafe {
654 let mut schema = SchematronSchema::new();
655
656 if let Some(qb) = get_attr(node, "queryBinding") {
658 schema.query_binding = qb;
659 }
660 schema.title = get_attr(node, "title");
661 if let Some(df) = get_attr(node, "defaultPhase") {
662 schema.default_phase = Some(df);
663 }
664
665 let mut current_pattern_id: Option<String> = None;
667 let mut pattern_names: HashMap<String, Vec<String>> = HashMap::new();
668
669 let mut child = (*node).children;
670 while !child.is_null() {
671 if (*child).type_ == XML_ELEMENT_NODE as c_int {
672 let local = get_local_name(child);
673 match local.as_str() {
674 "title" => {
675 if schema.title.is_none() {
676 schema.title = Some(get_node_text(child).trim().to_string());
677 }
678 }
679 "ns" => {
680 let prefix = get_attr(child, "prefix").unwrap_or_default();
681 let uri = get_attr(child, "uri").unwrap_or_default();
682 if !prefix.is_empty() && !uri.is_empty() {
683 schema.ns.insert(prefix, uri);
684 }
685 }
686 "phase" => {
687 let phase = schematron_parse_phase(child);
688 schema.phases.insert(phase.id.clone(), phase);
689 }
690 "pattern" => {
691 let pat_id = schematron_parse_pattern_node(
692 child,
693 &mut schema,
694 &mut current_pattern_id,
695 &mut pattern_names,
696 );
697 current_pattern_id = pat_id;
698 }
699 "rule" => {
700 let rule = schematron_parse_rule(child, &mut schema);
702 let rule_id = rule
703 .id
704 .clone()
705 .unwrap_or_else(|| format!("_rule_{}", schema.rules.len()));
706 let rid = rule_id.clone();
708 schema.rules.insert(rid, rule);
709
710 if let Some(ref pid) = current_pattern_id {
712 schema
713 .pattern_groups
714 .entry(pid.clone())
715 .or_default()
716 .push(rule_id);
717 } else {
718 let anon_id = format!("_anon_{}", schema.pattern_order.len());
720 schema
721 .pattern_groups
722 .entry(anon_id.clone())
723 .or_default()
724 .push(rule_id);
725 if !schema.pattern_order.contains(&anon_id) {
726 schema.pattern_order.push(anon_id);
727 }
728 }
729 }
730 "diagnostics" => {
731 schematron_parse_diagnostics(child, &mut schema);
732 }
733 "include" => {
734 schematron_parse_include(child, &mut schema);
735 }
736 "p" | "caption" => {
737 }
739 _ => {
740 schema
741 .errors
742 .push(format!("Unexpected element '<{}>' in schema", local));
743 }
744 }
745 }
746 child = (*child).next;
747 }
748
749 schema
750 }
751}
752
753unsafe fn schematron_parse_pattern_node(
759 node: *mut _xmlNode,
760 schema: &mut SchematronSchema,
761 _current_pattern_id: &mut Option<String>,
762 _pattern_names: &mut HashMap<String, Vec<String>>,
763) -> Option<String> {
764 unsafe {
765 let pat_id = get_attr(node, "id");
766 let pat_name = get_attr(node, "name");
767 let pat_is_a = get_attr(node, "is-a");
768 let pat_see = get_attr(node, "see");
769 let pat_icon = get_attr(node, "icon");
770 let pat_role = get_attr(node, "role");
771
772 let pid = pat_id
773 .clone()
774 .unwrap_or_else(|| format!("_pattern_{}", schema.pattern_order.len()));
775
776 let mut rule_ids: Vec<String> = Vec::new();
777
778 let mut child = (*node).children;
780 while !child.is_null() {
781 if (*child).type_ == XML_ELEMENT_NODE as c_int {
782 let local = get_local_name(child);
783 match local.as_str() {
784 "rule" => {
785 let rule = schematron_parse_rule(child, schema);
786 let rule_id = rule
787 .id
788 .clone()
789 .unwrap_or_else(|| format!("_rule_{}", schema.rules.len()));
790 let rid = rule_id.clone();
791 schema.rules.insert(rid, rule);
792 rule_ids.push(rule_id);
793 }
794 "p" | "caption" => {
795 }
797 _ => {
798 schema
799 .errors
800 .push(format!("Unexpected element '<{}>' in pattern", local));
801 }
802 }
803 }
804 child = (*child).next;
805 }
806
807 schema.pattern_groups.insert(pid.clone(), rule_ids);
808 schema.pattern_order.push(pid.clone());
809
810 if pat_is_a.is_some() {
812 }
815
816 let _ = pat_name;
818 let _ = pat_see;
819 let _ = pat_icon;
820 let _ = pat_role;
821
822 Some(pid)
823 }
824}
825
826unsafe fn schematron_parse_rule(
832 node: *mut _xmlNode,
833 schema: &mut SchematronSchema,
834) -> SchematronRule {
835 unsafe {
836 let context = get_attr(node, "context").unwrap_or_default();
837 let mut rule = SchematronRule::new(context);
838 rule.id = get_attr(node, "id");
839
840 let abs = get_attr(node, "abstract").unwrap_or_default();
841 rule.abstract_ = abs == "true" || abs == "1";
842
843 let mut child = (*node).children;
845 while !child.is_null() {
846 if (*child).type_ == XML_ELEMENT_NODE as c_int {
847 let local = get_local_name(child);
848 match local.as_str() {
849 "assert" => {
850 let pattern = schematron_parse_assert(child, SchematronPatternType::Assert);
851 rule.patterns.push(pattern);
852 }
853 "report" => {
854 let pattern = schematron_parse_assert(child, SchematronPatternType::Report);
855 rule.patterns.push(pattern);
856 }
857 "extends" => {
858 if let Some(ext_rule) = get_attr(child, "rule") {
859 rule.extends.push(ext_rule);
860 }
861 }
862 "let" => {
863 let name = get_attr(child, "name").unwrap_or_default();
866 let value = get_attr(child, "value").unwrap_or_default();
867 if !name.is_empty() {
868 let _ = value;
871 }
872 }
873 "param" => {
874 let _name = get_attr(child, "name");
876 let _value = get_attr(child, "value");
877 }
878 "p" | "caption" => {
879 }
881 _ => {
882 schema
883 .errors
884 .push(format!("Unexpected element '<{}>' in rule", local));
885 }
886 }
887 }
888 child = (*child).next;
889 }
890
891 rule
892 }
893}
894
895unsafe fn schematron_parse_assert(
901 node: *mut _xmlNode,
902 pattern_type: SchematronPatternType,
903) -> SchematronPattern {
904 unsafe {
905 let test = get_attr(node, "test").unwrap_or_default();
906 let text = get_inline_text(node);
907
908 let mut pattern = SchematronPattern::new(pattern_type, test, text);
909 pattern.flag = get_attr(node, "flag");
910 pattern.id = get_attr(node, "id");
911 pattern.icon = get_attr(node, "icon");
912 pattern.see = get_attr(node, "see");
913 pattern.role = get_attr(node, "role");
914 pattern.diagnostics = get_attr(node, "diagnostics");
915
916 pattern
920 }
921}
922
923unsafe fn schematron_parse_phase(node: *mut _xmlNode) -> SchematronPhase {
929 unsafe {
930 let id = get_attr(node, "id").unwrap_or_default();
931 let mut phase = SchematronPhase {
932 id,
933 active_patterns: Vec::new(),
934 };
935
936 let mut child = (*node).children;
937 while !child.is_null() {
938 if (*child).type_ == XML_ELEMENT_NODE as c_int {
939 let local = get_local_name(child);
940 if local == "active" {
941 if let Some(pattern) = get_attr(child, "pattern") {
942 phase.active_patterns.push(pattern);
943 }
944 }
945 }
946 child = (*child).next;
947 }
948
949 phase
950 }
951}
952
953unsafe fn schematron_parse_diagnostics(node: *mut _xmlNode, schema: &mut SchematronSchema) {
959 unsafe {
960 let mut child = (*node).children;
961 while !child.is_null() {
962 if (*child).type_ == XML_ELEMENT_NODE as c_int {
963 let local = get_local_name(child);
964 if local == "diagnostic" {
965 let diag = schematron_parse_diagnostic(child);
966 schema.diagnostics.insert(diag.id.clone(), diag);
967 }
968 }
969 child = (*child).next;
970 }
971 }
972}
973
974unsafe fn schematron_parse_diagnostic(node: *mut _xmlNode) -> SchematronDiagnostic {
980 unsafe {
981 let id = get_attr(node, "id").unwrap_or_default();
982 let text = get_inline_text(node);
983 let icon = get_attr(node, "icon");
984 let see = get_attr(node, "see");
985
986 SchematronDiagnostic {
987 id,
988 text,
989 icon,
990 see,
991 }
992 }
993}
994
995unsafe fn schematron_parse_include(node: *mut _xmlNode, _schema: &mut SchematronSchema) {
1001 unsafe {
1002 let href = get_attr(node, "href");
1003 if let Some(url) = href {
1004 let url_c = std::ffi::CString::new(url.clone()).ok();
1005 if let Some(c) = url_c {
1006 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
1007 if !doc.is_null() {
1008 let mut root = (*doc).children;
1010 while !root.is_null() && (*root).type_ != XML_ELEMENT_NODE as c_int {
1011 root = (*root).next;
1012 }
1013 if !root.is_null() {
1014 let local = get_local_name(root);
1015 if local == "schema" || local == "pattern" || local == "rule" {
1016 }
1020 }
1021 crate::abi::exports_xml2::xmlFreeDoc(doc);
1022 }
1023 }
1024 }
1025 }
1026}
1027
1028unsafe fn expand_diagnostic_message(
1041 text: &str,
1042 context_node: *mut _xmlNode,
1043 xpath_ctxt: &mut XPathContext,
1044) -> String {
1045 let _ = context_node;
1057 let _ = xpath_ctxt;
1058 text.to_string()
1059}
1060
1061fn evaluate_xpath_boolean(
1067 compiled: &CompiledExpr,
1068 xpath_ctxt: &mut XPathContext,
1069) -> Result<bool, String> {
1070 match crate::xml::xpath::evaluate(compiled, xpath_ctxt) {
1071 Some(value) => Ok(value.as_boolean()),
1072 None => Err("XPath evaluation failed".to_string()),
1073 }
1074}
1075
1076pub unsafe fn schematron_validate_doc(
1086 schema: &SchematronSchema,
1087 doc: *mut _xmlDoc,
1088 ctxt: &mut SchematronValidCtxt,
1089) -> bool {
1090 unsafe {
1091 if doc.is_null() {
1092 ctxt.record_error("Document is null".to_string());
1093 return false;
1094 }
1095
1096 let root = (*doc).children;
1097 if root.is_null() {
1098 ctxt.record_error("Document has no children".to_string());
1099 return false;
1100 }
1101
1102 let mut root_elem = root;
1104 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
1105 root_elem = (*root_elem).next;
1106 }
1107
1108 if root_elem.is_null() {
1109 ctxt.record_error("Document has no root element".to_string());
1110 return false;
1111 }
1112
1113 let phase_id = ctxt.active_phase.as_deref();
1115 let rules = schema.active_rules(phase_id);
1116
1117 if rules.is_empty() {
1118 return true;
1120 }
1121
1122 let mut xpath_ctxt = XPathContext::new(doc);
1124
1125 let core_funcs = crate::xml::xpath::functions::core_functions();
1127 for (name, func) in core_funcs {
1128 xpath_ctxt.register_function(&name, func);
1129 }
1130
1131 for (prefix, uri) in &schema.ns {
1133 xpath_ctxt.namespaces.insert(prefix.clone(), uri.clone());
1134 }
1135
1136 let mut valid = true;
1137
1138 for rule in &rules {
1140 let matching_nodes: Vec<*mut _xmlNode> =
1142 find_matching_nodes(rule, root_elem, doc, &mut xpath_ctxt);
1143
1144 for context_node in &matching_nodes {
1145 xpath_ctxt.set_context_node(*context_node);
1147
1148 for pattern in &rule.patterns {
1150 let compiled = match &pattern.compiled_test {
1151 Some(c) => c,
1152 None => continue,
1153 };
1154
1155 let test_result = match evaluate_xpath_boolean(compiled, &mut xpath_ctxt) {
1156 Ok(val) => val,
1157 Err(e) => {
1158 ctxt.record_error(format!(
1159 "XPath error in '{}' test '{}': {}",
1160 if pattern.pattern_type == SchematronPatternType::Assert {
1161 "assert"
1162 } else {
1163 "report"
1164 },
1165 pattern.test,
1166 e
1167 ));
1168 valid = false;
1169 continue;
1170 }
1171 };
1172
1173 let message =
1174 expand_diagnostic_message(&pattern.text, *context_node, &mut xpath_ctxt);
1175
1176 match pattern.pattern_type {
1177 SchematronPatternType::Assert => {
1178 if !test_result {
1180 let node_name = get_node_qname(*context_node);
1181 let flag_str = pattern
1182 .flag
1183 .as_ref()
1184 .map(|f| format!(" [{}]", f))
1185 .unwrap_or_default();
1186 let role_str = pattern
1187 .role
1188 .as_ref()
1189 .map(|r| format!(" ({})", r))
1190 .unwrap_or_default();
1191 let msg = if message.is_empty() {
1192 format!(
1193 "assertion failed: '{}' for node '{}'{}{}",
1194 pattern.test, node_name, flag_str, role_str
1195 )
1196 } else {
1197 format!(
1198 "assertion '{}' failed for node '{}'{}{}: {}",
1199 pattern.test, node_name, flag_str, role_str, message
1200 )
1201 };
1202 ctxt.record_error(msg);
1203 valid = false;
1204 }
1205 }
1206 SchematronPatternType::Report => {
1207 if test_result {
1209 let node_name = get_node_qname(*context_node);
1210 let flag_str = pattern
1211 .flag
1212 .as_ref()
1213 .map(|f| format!(" [{}]", f))
1214 .unwrap_or_default();
1215 let role_str = pattern
1216 .role
1217 .as_ref()
1218 .map(|r| format!(" ({})", r))
1219 .unwrap_or_default();
1220 let msg = if message.is_empty() {
1221 format!(
1222 "report triggered: '{}' for node '{}'{}{}",
1223 pattern.test, node_name, flag_str, role_str
1224 )
1225 } else {
1226 format!(
1227 "report '{}' triggered for node '{}'{}{}: {}",
1228 pattern.test, node_name, flag_str, role_str, message
1229 )
1230 };
1231 ctxt.record_error(msg);
1232 valid = false;
1233 }
1234 }
1235 }
1236 }
1237 }
1238 }
1239
1240 valid
1241 }
1242}
1243
1244unsafe fn find_matching_nodes(
1251 rule: &SchematronRule,
1252 root: *mut _xmlNode,
1253 doc: *mut _xmlDoc,
1254 xpath_ctxt: &mut XPathContext,
1255) -> Vec<*mut _xmlNode> {
1256 unsafe {
1257 if rule.context.is_empty() {
1259 let mut nodes = Vec::new();
1260 collect_all_elements(root, &mut nodes);
1261 return nodes;
1262 }
1263
1264 if let Some(compiled) = &rule.compiled_context {
1266 let is_simple_name = !rule.context.contains('/')
1269 && !rule.context.contains("::")
1270 && !rule.context.contains('[')
1271 && !rule.context.contains('(');
1272
1273 if !is_simple_name {
1274 xpath_ctxt.set_context_node(root);
1275 xpath_ctxt.document = doc;
1276
1277 if let Some(XPathValue::NodeSet(ns)) =
1278 crate::xml::xpath::evaluate(compiled, xpath_ctxt)
1279 {
1280 if !ns.is_empty() {
1281 return ns.iter().collect();
1282 }
1283 }
1284 }
1285
1286 simple_context_match(&rule.context, root)
1288 } else {
1289 simple_context_match(&rule.context, root)
1291 }
1292 }
1293}
1294
1295fn simple_context_match(context: &str, root: *mut _xmlNode) -> Vec<*mut _xmlNode> {
1298 unsafe {
1299 let context = context.trim();
1300
1301 if context == "*" || context == "//*" {
1308 let mut nodes = Vec::new();
1309 collect_all_elements(root, &mut nodes);
1310 return nodes;
1311 }
1312
1313 if let Some(name) = context.strip_prefix("//") {
1314 if name.is_empty() || name == "*" {
1315 let mut nodes = Vec::new();
1316 collect_all_elements(root, &mut nodes);
1317 return nodes;
1318 }
1319 let mut nodes = Vec::new();
1321 collect_elements_by_name(root, name, &mut nodes);
1322 return nodes;
1323 }
1324
1325 if !context.contains('/') && !context.contains("::") {
1326 let mut nodes = Vec::new();
1328 let root_qname = get_node_qname(root);
1330 let root_local = get_local_name(root);
1331 if root_qname == context || root_local == context || context == "*" {
1332 nodes.push(root);
1333 }
1334 let mut child = (*root).children;
1336 while !child.is_null() {
1337 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1338 let qname = get_node_qname(child);
1339 let local = get_local_name(child);
1340 if qname == context || local == context || context == "*" {
1341 nodes.push(child);
1342 }
1343 }
1344 child = (*child).next;
1345 }
1346 return nodes;
1347 }
1348
1349 vec![root]
1351 }
1352}
1353
1354unsafe fn collect_all_elements(node: *mut _xmlNode, nodes: &mut Vec<*mut _xmlNode>) {
1360 unsafe {
1361 if node.is_null() {
1362 return;
1363 }
1364 if (*node).type_ == XML_ELEMENT_NODE as c_int {
1365 nodes.push(node);
1366 }
1367 let mut child = (*node).children;
1368 while !child.is_null() {
1369 collect_all_elements(child, nodes);
1370 child = (*child).next;
1371 }
1372 }
1373}
1374
1375unsafe fn collect_elements_by_name(
1381 node: *mut _xmlNode,
1382 name: &str,
1383 nodes: &mut Vec<*mut _xmlNode>,
1384) {
1385 unsafe {
1386 if node.is_null() {
1387 return;
1388 }
1389 if (*node).type_ == XML_ELEMENT_NODE as c_int {
1390 let qname = get_node_qname(node);
1391 let local = get_local_name(node);
1392 if qname == name || local == name {
1393 nodes.push(node);
1394 }
1395 }
1396 let mut child = (*node).children;
1397 while !child.is_null() {
1398 collect_elements_by_name(child, name, nodes);
1399 child = (*child).next;
1400 }
1401 }
1402}
1403
1404pub fn schematron_parse_schema(xml_doc: &str) -> Result<SchematronSchema, String> {
1412 schematron_parse(xml_doc)
1413}
1414
1415pub unsafe fn schematron_parse_schema_doc(doc: *mut _xmlDoc) -> Result<SchematronSchema, String> {
1421 schematron_parse_doc(doc)
1422}
1423
1424pub unsafe fn schematron_validate_doc_schema(
1432 schema: &SchematronSchema,
1433 doc: *mut _xmlDoc,
1434 ctxt: &mut SchematronValidCtxt,
1435) -> bool {
1436 schematron_validate_doc(schema, doc, ctxt)
1437}
1438
1439#[no_mangle]
1458pub unsafe extern "C" fn xmlSchematronNewParserCtxt(url: *const c_char) -> *mut c_void {
1459 if url.is_null() {
1460 return Box::into_raw(Box::new(SchematronSchema::new())) as *mut c_void;
1466 }
1467
1468 let url_str = unsafe {
1469 let mut len = 0;
1470 while *url.add(len) != 0 {
1471 len += 1;
1472 }
1473 let slice = std::slice::from_raw_parts(url as *const u8, len);
1474 String::from_utf8_lossy(slice).to_string()
1475 };
1476
1477 if !url_str.is_empty() {
1479 let url_c = std::ffi::CString::new(url_str.clone()).ok();
1480 if let Some(c) = url_c {
1481 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
1482 if !doc.is_null() {
1483 let result = schematron_parse_doc(doc);
1484 crate::abi::exports_xml2::xmlFreeDoc(doc);
1485 if let Ok(schema) = result {
1486 let schema_box = Box::new(schema);
1487 return Box::into_raw(schema_box) as *mut c_void;
1488 }
1489 }
1490 }
1491 }
1492
1493 Box::into_raw(Box::new(SchematronSchema::new())) as *mut c_void
1495}
1496
1497#[no_mangle]
1509pub unsafe extern "C" fn xmlSchematronNewMemParserCtxt(
1510 buffer: *const c_char,
1511 size: c_int,
1512) -> *mut c_void {
1513 if buffer.is_null() || size <= 0 {
1514 return ptr::null_mut();
1515 }
1516
1517 let buf_slice = unsafe { std::slice::from_raw_parts(buffer as *const u8, size as usize) };
1519 let xml_str = String::from_utf8_lossy(buf_slice).to_string();
1520
1521 match schematron_parse(&xml_str) {
1522 Ok(schema) => {
1523 let schema_box = Box::new(schema);
1524 Box::into_raw(schema_box) as *mut c_void
1525 }
1526 Err(_) => ptr::null_mut(),
1527 }
1528}
1529
1530#[no_mangle]
1542pub const unsafe extern "C" fn xmlSchematronParse(ctxt: *mut c_void) -> *mut c_void {
1543 if ctxt.is_null() {
1544 return ptr::null_mut();
1545 }
1546
1547 ctxt
1550}
1551
1552#[no_mangle]
1564pub unsafe extern "C" fn xmlSchematronFree(schema: *mut c_void) {
1565 if schema.is_null() {
1566 return;
1567 }
1568 unsafe {
1570 let _ = Box::from_raw(schema as *mut SchematronSchema);
1571 }
1572}
1573
1574#[no_mangle]
1586pub unsafe extern "C" fn xmlSchematronFreeParserCtxt(ctxt: *mut c_void) {
1587 if ctxt.is_null() {
1588 return;
1589 }
1590 unsafe {
1592 let _ = Box::from_raw(ctxt as *mut SchematronSchema);
1593 }
1594}
1595
1596#[no_mangle]
1608pub unsafe extern "C" fn xmlSchematronNewValidCtxt(schema: *mut c_void) -> *mut c_void {
1609 let mut ctxt = SchematronValidCtxt::new();
1610
1611 if !schema.is_null() {
1612 unsafe {
1614 let schema_ref = &*(schema as *const SchematronSchema);
1615 ctxt.schema = Some(schema_ref.clone());
1616 }
1617 }
1618
1619 let boxed = Box::new(ctxt);
1620 Box::into_raw(boxed) as *mut c_void
1621}
1622
1623#[no_mangle]
1635pub unsafe extern "C" fn xmlSchematronFreeValidCtxt(ctxt: *mut c_void) {
1636 if ctxt.is_null() {
1637 return;
1638 }
1639 unsafe {
1641 let _ = Box::from_raw(ctxt as *mut SchematronValidCtxt);
1642 }
1643}
1644
1645#[no_mangle]
1660pub unsafe extern "C" fn xmlSchematronValidateDoc(ctxt: *mut c_void, doc: *mut _xmlDoc) -> c_int {
1661 if ctxt.is_null() || doc.is_null() {
1662 return -1;
1663 }
1664
1665 unsafe {
1666 let valid_ctxt = &mut *(ctxt as *mut SchematronValidCtxt);
1667 let schema = match &valid_ctxt.schema {
1668 Some(s) => s,
1669 None => return -1,
1670 };
1671
1672 let mut temp_ctxt = SchematronValidCtxt::new();
1673 temp_ctxt.active_phase = valid_ctxt.active_phase.clone();
1674
1675 let valid = schematron_validate_doc(schema, doc, &mut temp_ctxt);
1676
1677 if valid {
1678 0
1679 } else {
1680 valid_ctxt.errors = temp_ctxt.errors;
1681 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
1682 temp_ctxt.nb_errors
1683 }
1684 }
1685}
1686
1687pub type SchematronValidityErrorFunc = unsafe extern "C" fn(ctx: *mut c_void, msg: *const c_char);
1700
1701pub type SchematronValidityWarningFunc = unsafe extern "C" fn(ctx: *mut c_void, msg: *const c_char);
1703
1704#[derive(Clone, Copy)]
1705struct SchematronSendPtr(*mut c_void);
1706unsafe impl Send for SchematronSendPtr {}
1707unsafe impl Sync for SchematronSendPtr {}
1708impl Default for SchematronSendPtr {
1709 fn default() -> Self {
1710 SchematronSendPtr(core::ptr::null_mut())
1711 }
1712}
1713
1714#[derive(Clone, Copy, Default)]
1715struct SchematronParserState {
1716 err: Option<SchematronValidityErrorFunc>,
1717 warn: Option<SchematronValidityWarningFunc>,
1718 ctx: SchematronSendPtr,
1719}
1720
1721#[derive(Clone, Copy, Default)]
1722struct SchematronValidState {
1723 err: Option<SchematronValidityErrorFunc>,
1724 warn: Option<SchematronValidityWarningFunc>,
1725 ctx: SchematronSendPtr,
1726 options: c_int,
1727}
1728
1729static SCHEMATRON_PARSER_STATE: once_cell::sync::Lazy<
1730 parking_lot::Mutex<std::collections::HashMap<usize, SchematronParserState>>,
1731> = once_cell::sync::Lazy::new(Default::default);
1732
1733static SCHEMATRON_VALID_STATE: once_cell::sync::Lazy<
1734 parking_lot::Mutex<std::collections::HashMap<usize, SchematronValidState>>,
1735> = once_cell::sync::Lazy::new(Default::default);
1736
1737#[no_mangle]
1759pub unsafe extern "C" fn xmlSchematronSetParserErrors(
1760 ctxt: *mut c_void,
1761 err: Option<SchematronValidityErrorFunc>,
1762 warn: Option<SchematronValidityWarningFunc>,
1763 ctx: *mut c_void,
1764) {
1765 if ctxt.is_null() {
1766 return;
1767 }
1768 let mut map = SCHEMATRON_PARSER_STATE.lock();
1769 let st = map.entry(ctxt as usize).or_default();
1770 st.err = err;
1771 st.warn = warn;
1772 st.ctx = SchematronSendPtr(ctx);
1773}
1774
1775#[no_mangle]
1796pub unsafe extern "C" fn xmlSchematronGetParserErrors(
1797 ctxt: *mut c_void,
1798 err: *mut Option<SchematronValidityErrorFunc>,
1799 warn: *mut Option<SchematronValidityWarningFunc>,
1800 ctx: *mut *mut c_void,
1801) -> c_int {
1802 if ctxt.is_null() {
1803 return -1;
1804 }
1805 let map = SCHEMATRON_PARSER_STATE.lock();
1806 let st = map.get(&(ctxt as usize)).copied().unwrap_or_default();
1807 if !err.is_null() {
1808 *err = st.err;
1809 }
1810 if !warn.is_null() {
1811 *warn = st.warn;
1812 }
1813 if !ctx.is_null() {
1814 *ctx = st.ctx.0;
1815 }
1816 0
1817}
1818
1819#[no_mangle]
1840pub unsafe extern "C" fn xmlSchematronSetValidErrors(
1841 ctxt: *mut c_void,
1842 err: Option<SchematronValidityErrorFunc>,
1843 warn: Option<SchematronValidityWarningFunc>,
1844 ctx: *mut c_void,
1845) {
1846 if ctxt.is_null() {
1847 return;
1848 }
1849 let mut map = SCHEMATRON_VALID_STATE.lock();
1850 let st = map.entry(ctxt as usize).or_default();
1851 st.err = err;
1852 st.warn = warn;
1853 st.ctx = SchematronSendPtr(ctx);
1854}
1855
1856#[no_mangle]
1877pub unsafe extern "C" fn xmlSchematronGetValidErrors(
1878 ctxt: *mut c_void,
1879 err: *mut Option<SchematronValidityErrorFunc>,
1880 warn: *mut Option<SchematronValidityWarningFunc>,
1881 ctx: *mut *mut c_void,
1882) -> c_int {
1883 if ctxt.is_null() {
1884 return -1;
1885 }
1886 let map = SCHEMATRON_VALID_STATE.lock();
1887 let st = map.get(&(ctxt as usize)).copied().unwrap_or_default();
1888 if !err.is_null() {
1889 *err = st.err;
1890 }
1891 if !warn.is_null() {
1892 *warn = st.warn;
1893 }
1894 if !ctx.is_null() {
1895 *ctx = st.ctx.0;
1896 }
1897 0
1898}
1899
1900#[no_mangle]
1918pub unsafe extern "C" fn xmlSchematronSetValidOptions(ctxt: *mut c_void, options: c_int) -> c_int {
1919 if ctxt.is_null() {
1920 return -1;
1921 }
1922 let mut map = SCHEMATRON_VALID_STATE.lock();
1923 let st = map.entry(ctxt as usize).or_default();
1924 let old = st.options;
1925 st.options = options;
1926 old
1927}
1928
1929#[no_mangle]
1946pub unsafe extern "C" fn xmlSchematronValidCtxtGetOptions(ctxt: *mut c_void) -> c_int {
1947 if ctxt.is_null() {
1948 return -1;
1949 }
1950 SCHEMATRON_VALID_STATE
1951 .lock()
1952 .get(&(ctxt as usize))
1953 .map_or(0, |st| st.options)
1954}
1955
1956#[no_mangle]
1974pub const unsafe extern "C" fn xmlSchematronIsValid(ctxt: *mut c_void) -> c_int {
1975 if ctxt.is_null() {
1976 return 0;
1977 }
1978 unsafe {
1979 let vc = &*(ctxt as *const SchematronValidCtxt);
1980 if vc.nb_errors > 0 {
1981 0
1982 } else {
1983 1
1984 }
1985 }
1986}
1987
1988#[no_mangle]
2006pub unsafe extern "C" fn xmlSchematronValidateOneElement(
2007 ctxt: *mut c_void,
2008 elem: *mut _xmlNode,
2009) -> c_int {
2010 if ctxt.is_null() || elem.is_null() {
2011 return -1;
2012 }
2013 unsafe {
2014 let valid_ctxt = &mut *(ctxt as *mut SchematronValidCtxt);
2015 let schema = match &valid_ctxt.schema {
2016 Some(s) => s,
2017 None => return -1,
2018 };
2019 let doc = (*elem).doc;
2020 if doc.is_null() {
2021 return -1;
2022 }
2023 let mut temp_ctxt = SchematronValidCtxt::new();
2026 temp_ctxt.active_phase = valid_ctxt.active_phase.clone();
2027 let valid = schematron_validate_doc(schema, doc, &mut temp_ctxt);
2028 if !valid {
2029 valid_ctxt.errors = temp_ctxt.errors;
2030 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
2031 }
2032 if valid {
2033 0
2034 } else {
2035 -1
2036 }
2037 }
2038}
2039
2040#[cfg(test)]
2045mod tests {
2046 use super::*;
2047
2048 #[test]
2051 fn test_parse_simple_schema() {
2052 let schema_xml = r#"<?xml version="1.0"?>
2053<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2054 <pattern id="P1">
2055 <rule context="root">
2056 <assert test="count(*) > 0">Root must have children</assert>
2057 </rule>
2058 </pattern>
2059</schema>"#;
2060
2061 let result = schematron_parse(schema_xml);
2062 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2063 let schema = result.unwrap();
2064 assert_eq!(schema.pattern_order.len(), 1);
2065 assert_eq!(schema.rules.len(), 1);
2066 }
2067
2068 #[test]
2069 fn test_parse_with_ns() {
2070 let schema_xml = r#"<?xml version="1.0"?>
2071<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2072 <ns prefix="doc" uri="http://example.com/doc"/>
2073 <pattern id="P1">
2074 <rule context="doc:entry">
2075 <assert test="doc:title">Entry must have a title</assert>
2076 </rule>
2077 </pattern>
2078</schema>"#;
2079
2080 let result = schematron_parse(schema_xml);
2081 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2082 let schema = result.unwrap();
2083 assert!(schema.ns.contains_key("doc"));
2084 assert_eq!(schema.ns.get("doc").unwrap(), "http://example.com/doc");
2085 }
2086
2087 #[test]
2088 fn test_parse_with_phases() {
2089 let schema_xml = r#"<?xml version="1.0"?>
2090<schema xmlns="http://purl.oclc.org/dsdl/schematron" defaultPhase="phaseA">
2091 <phase id="phaseA">
2092 <active pattern="P1"/>
2093 </phase>
2094 <phase id="phaseB">
2095 <active pattern="P2"/>
2096 </phase>
2097 <pattern id="P1">
2098 <rule context="root">
2099 <assert test="true()">Always passes</assert>
2100 </rule>
2101 </pattern>
2102 <pattern id="P2">
2103 <rule context="root">
2104 <assert test="false()">Always fails</assert>
2105 </rule>
2106 </pattern>
2107</schema>"#;
2108
2109 let result = schematron_parse(schema_xml);
2110 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2111 let schema = result.unwrap();
2112 assert_eq!(schema.phases.len(), 2);
2113 assert!(schema.phases.contains_key("phaseA"));
2114 assert!(schema.phases.contains_key("phaseB"));
2115 assert_eq!(schema.default_phase.as_deref(), Some("phaseA"));
2116 }
2117
2118 #[test]
2119 fn test_parse_report_pattern() {
2120 let schema_xml = r#"<?xml version="1.0"?>
2121<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2122 <pattern id="P1">
2123 <rule context="root">
2124 <report test="@deprecated">Element is deprecated</report>
2125 </rule>
2126 </pattern>
2127</schema>"#;
2128
2129 let result = schematron_parse(schema_xml);
2130 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2131 let schema = result.unwrap();
2132 let rule = schema.rules.values().next().unwrap();
2133 assert_eq!(rule.patterns.len(), 1);
2134 assert_eq!(rule.patterns[0].pattern_type, SchematronPatternType::Report);
2135 assert_eq!(rule.patterns[0].test, "@deprecated");
2136 }
2137
2138 #[test]
2139 fn test_parse_abstract_rule() {
2140 let schema_xml = r#"<?xml version="1.0"?>
2141<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2142 <pattern id="P1">
2143 <rule id="abstractRule" abstract="true" context="*">
2144 <assert test="true()">Abstract assertion</assert>
2145 </rule>
2146 <rule id="concreteRule" context="root">
2147 <extends rule="abstractRule"/>
2148 <assert test="count(*) > 0">Concrete assertion</assert>
2149 </rule>
2150 </pattern>
2151</schema>"#;
2152
2153 let result = schematron_parse(schema_xml);
2154 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2155 let schema = result.unwrap();
2156 assert!(schema.rules.contains_key("abstractRule"));
2157 assert!(schema.rules.contains_key("concreteRule"));
2158 let abstract_rule = &schema.rules["abstractRule"];
2159 assert!(abstract_rule.abstract_);
2160 let concrete_rule = &schema.rules["concreteRule"];
2161 assert!(!concrete_rule.abstract_);
2162 assert_eq!(concrete_rule.extends.len(), 1);
2163 assert_eq!(concrete_rule.extends[0], "abstractRule");
2164 }
2165
2166 #[test]
2167 fn test_parse_with_diagnostics() {
2168 let schema_xml = r#"<?xml version="1.0"?>
2169<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2170 <diagnostics>
2171 <diagnostic id="diag1">This is a diagnostic message</diagnostic>
2172 </diagnostics>
2173 <pattern id="P1">
2174 <rule context="root">
2175 <assert test="true()" diagnostics="diag1">Assertion with diagnostic</assert>
2176 </rule>
2177 </pattern>
2178</schema>"#;
2179
2180 let result = schematron_parse(schema_xml);
2181 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2182 let schema = result.unwrap();
2183 assert!(schema.diagnostics.contains_key("diag1"));
2184 assert_eq!(
2185 schema.diagnostics["diag1"].text,
2186 "This is a diagnostic message"
2187 );
2188 }
2189
2190 #[test]
2191 fn test_parse_with_attributes() {
2192 let schema_xml = r#"<?xml version="1.0"?>
2193<schema xmlns="http://purl.oclc.org/dsdl/schematron" title="Test Schema">
2194 <pattern id="P1">
2195 <rule context="root">
2196 <assert test="true()" flag="warn" role="error" id="a1" icon="info" see="http://example.com">
2197 Test message
2198 </assert>
2199 </rule>
2200 </pattern>
2201</schema>"#;
2202
2203 let result = schematron_parse(schema_xml);
2204 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
2205 let schema = result.unwrap();
2206 assert_eq!(schema.title.as_deref(), Some("Test Schema"));
2207 let rule = schema.rules.values().next().unwrap();
2208 let pat = &rule.patterns[0];
2209 assert_eq!(pat.flag.as_deref(), Some("warn"));
2210 assert_eq!(pat.role.as_deref(), Some("error"));
2211 assert_eq!(pat.id.as_deref(), Some("a1"));
2212 assert_eq!(pat.icon.as_deref(), Some("info"));
2213 assert_eq!(pat.see.as_deref(), Some("http://example.com"));
2214 }
2215
2216 #[test]
2217 fn test_parse_empty_schema() {
2218 let schema_xml = r#"<?xml version="1.0"?>
2219<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2220</schema>"#;
2221
2222 let result = schematron_parse(schema_xml);
2223 assert!(result.is_ok(), "Failed to parse empty schema");
2224 let schema = result.unwrap();
2225 assert!(schema.rules.is_empty());
2226 assert!(schema.phases.is_empty());
2227 }
2228
2229 #[test]
2230 fn test_parse_no_assertions() {
2231 let schema_xml = r#"<?xml version="1.0"?>
2232<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2233 <pattern id="P1">
2234 <rule context="root">
2235 </rule>
2236 </pattern>
2237</schema>"#;
2238
2239 let result = schematron_parse(schema_xml);
2240 assert!(result.is_ok(), "Failed to parse schema with no assertions");
2241 let schema = result.unwrap();
2242 let rule = schema.rules.values().next().unwrap();
2243 assert!(rule.patterns.is_empty());
2244 }
2245
2246 #[test]
2247 fn test_parse_invalid_root_element() {
2248 let schema_xml = r#"<?xml version="1.0"?>
2249<not-schema xmlns="http://purl.oclc.org/dsdl/schematron">
2250</not-schema>"#;
2251
2252 let result = schematron_parse(schema_xml);
2253 assert!(result.is_err(), "Should fail with wrong root element");
2254 assert!(
2255 result.err().unwrap().contains("Expected '<schema>'"),
2256 "Error should mention expected schema element"
2257 );
2258 }
2259
2260 #[test]
2261 fn test_parse_empty_document_fails() {
2262 let result = schematron_parse("");
2263 assert!(result.is_err());
2264 }
2265
2266 #[test]
2267 fn test_parse_invalid_xml_fails() {
2268 let result = schematron_parse("not valid xml <<<");
2269 assert!(result.is_err());
2270 }
2271
2272 #[test]
2273 fn test_parse_schema_with_let_and_param() {
2274 let schema_xml = r#"<?xml version="1.0"?>
2275<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2276 <pattern id="P1">
2277 <rule context="root">
2278 <let name="x" value="42"/>
2279 <param name="debug" value="true"/>
2280 <assert test="true()">Test with let and param</assert>
2281 </rule>
2282 </pattern>
2283</schema>"#;
2284
2285 let result = schematron_parse(schema_xml);
2286 assert!(
2287 result.is_ok(),
2288 "Failed to parse schema with let/param: {:?}",
2289 result.err()
2290 );
2291 let schema = result.unwrap();
2292 assert_eq!(schema.rules.len(), 1);
2293 }
2294
2295 #[test]
2296 fn test_parse_schema_with_documentation() {
2297 let schema_xml = r#"<?xml version="1.0"?>
2298<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2299 <p>This is documentation</p>
2300 <caption>Table caption</caption>
2301 <pattern id="P1">
2302 <p>Pattern documentation</p>
2303 <rule context="root">
2304 <p>Rule documentation</p>
2305 <assert test="true()">Real assertion</assert>
2306 </rule>
2307 </pattern>
2308</schema>"#;
2309
2310 let result = schematron_parse(schema_xml);
2311 assert!(result.is_ok(), "Failed to parse schema with documentation");
2312 let schema = result.unwrap();
2313 assert_eq!(schema.rules.len(), 1);
2314 }
2315
2316 #[test]
2319 fn test_validate_assert_pass() {
2320 let schema_xml = r#"<?xml version="1.0"?>
2321<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2322 <pattern id="P1">
2323 <rule context="root">
2324 <assert test="true()">Always passes</assert>
2325 </rule>
2326 </pattern>
2327</schema>"#;
2328
2329 let doc_xml = r#"<?xml version="1.0"?>
2330<root>Hello</root>"#;
2331
2332 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2333
2334 let doc = unsafe {
2335 crate::abi::exports_xml2::xmlReadMemory(
2336 doc_xml.as_ptr() as *const c_char,
2337 doc_xml.len() as c_int,
2338 c"test.xml".as_ptr() as *const c_char,
2339 ptr::null(),
2340 0,
2341 )
2342 };
2343 assert!(!doc.is_null(), "Failed to parse document");
2344
2345 let mut ctxt = SchematronValidCtxt::new();
2346 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2347 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2348
2349 assert!(valid, "Validation failed: {:?}", ctxt.errors);
2350 }
2351
2352 #[test]
2353 fn test_validate_assert_fail() {
2354 let schema_xml = r#"<?xml version="1.0"?>
2355<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2356 <pattern id="P1">
2357 <rule context="root">
2358 <assert test="false()">Always fails</assert>
2359 </rule>
2360 </pattern>
2361</schema>"#;
2362
2363 let doc_xml = r#"<?xml version="1.0"?>
2364<root>Hello</root>"#;
2365
2366 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2367
2368 let doc = unsafe {
2369 crate::abi::exports_xml2::xmlReadMemory(
2370 doc_xml.as_ptr() as *const c_char,
2371 doc_xml.len() as c_int,
2372 c"test.xml".as_ptr() as *const c_char,
2373 ptr::null(),
2374 0,
2375 )
2376 };
2377 assert!(!doc.is_null());
2378
2379 let mut ctxt = SchematronValidCtxt::new();
2380 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2381 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2382
2383 assert!(
2384 !valid,
2385 "Validation should have failed, errors: {:?}",
2386 ctxt.errors
2387 );
2388 assert!(ctxt.nb_errors > 0);
2389 }
2390
2391 #[test]
2392 fn test_validate_report_pass() {
2393 let schema_xml = r#"<?xml version="1.0"?>
2394<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2395 <pattern id="P1">
2396 <rule context="root">
2397 <report test="false()">Report should not trigger</report>
2398 </rule>
2399 </pattern>
2400</schema>"#;
2401
2402 let doc_xml = r#"<?xml version="1.0"?>
2403<root>Hello</root>"#;
2404
2405 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2406
2407 let doc = unsafe {
2408 crate::abi::exports_xml2::xmlReadMemory(
2409 doc_xml.as_ptr() as *const c_char,
2410 doc_xml.len() as c_int,
2411 c"test.xml".as_ptr() as *const c_char,
2412 ptr::null(),
2413 0,
2414 )
2415 };
2416 assert!(!doc.is_null());
2417
2418 let mut ctxt = SchematronValidCtxt::new();
2419 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2420 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2421
2422 assert!(valid, "Report should not trigger: {:?}", ctxt.errors);
2423 }
2424
2425 #[test]
2426 fn test_validate_report_fail() {
2427 let schema_xml = r#"<?xml version="1.0"?>
2428<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2429 <pattern id="P1">
2430 <rule context="root">
2431 <report test="true()">Report should trigger</report>
2432 </rule>
2433 </pattern>
2434</schema>"#;
2435
2436 let doc_xml = r#"<?xml version="1.0"?>
2437<root>Hello</root>"#;
2438
2439 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2440
2441 let doc = unsafe {
2442 crate::abi::exports_xml2::xmlReadMemory(
2443 doc_xml.as_ptr() as *const c_char,
2444 doc_xml.len() as c_int,
2445 c"test.xml".as_ptr() as *const c_char,
2446 ptr::null(),
2447 0,
2448 )
2449 };
2450 assert!(!doc.is_null());
2451
2452 let mut ctxt = SchematronValidCtxt::new();
2453 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2454 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2455
2456 assert!(!valid, "Report should have triggered");
2457 assert!(ctxt.nb_errors > 0);
2458 }
2459
2460 #[test]
2461 fn test_validate_context_matching() {
2462 let schema_xml = r#"<?xml version="1.0"?>
2463<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2464 <pattern id="P1">
2465 <rule context="child">
2466 <assert test="true()">Child matches</assert>
2467 </rule>
2468 </pattern>
2469</schema>"#;
2470
2471 let doc_xml = r#"<?xml version="1.0"?>
2472<root>
2473 <child>A</child>
2474 <child>B</child>
2475</root>"#;
2476
2477 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2478
2479 let doc = unsafe {
2480 crate::abi::exports_xml2::xmlReadMemory(
2481 doc_xml.as_ptr() as *const c_char,
2482 doc_xml.len() as c_int,
2483 c"test.xml".as_ptr() as *const c_char,
2484 ptr::null(),
2485 0,
2486 )
2487 };
2488 assert!(!doc.is_null());
2489
2490 let mut ctxt = SchematronValidCtxt::new();
2491 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2492 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2493
2494 assert!(valid, "Context matching failed: {:?}", ctxt.errors);
2495 }
2496
2497 #[test]
2498 fn test_validate_multiple_rules() {
2499 let schema_xml = r#"<?xml version="1.0"?>
2500<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2501 <pattern id="P1">
2502 <rule context="root">
2503 <assert test="true()">Root passes</assert>
2504 </rule>
2505 </pattern>
2506 <pattern id="P2">
2507 <rule context="child">
2508 <assert test="true()">Child passes</assert>
2509 </rule>
2510 </pattern>
2511</schema>"#;
2512
2513 let doc_xml = r#"<?xml version="1.0"?>
2514<root>
2515 <child>Content</child>
2516</root>"#;
2517
2518 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2519
2520 let doc = unsafe {
2521 crate::abi::exports_xml2::xmlReadMemory(
2522 doc_xml.as_ptr() as *const c_char,
2523 doc_xml.len() as c_int,
2524 c"test.xml".as_ptr() as *const c_char,
2525 ptr::null(),
2526 0,
2527 )
2528 };
2529 assert!(!doc.is_null());
2530
2531 let mut ctxt = SchematronValidCtxt::new();
2532 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2533 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2534
2535 assert!(valid, "Multiple rules failed: {:?}", ctxt.errors);
2536 }
2537
2538 #[test]
2539 fn test_validate_with_phase_filtering() {
2540 let schema_xml = r#"<?xml version="1.0"?>
2541<schema xmlns="http://purl.oclc.org/dsdl/schematron" defaultPhase="phaseA">
2542 <phase id="phaseA">
2543 <active pattern="P1"/>
2544 </phase>
2545 <phase id="phaseB">
2546 <active pattern="P2"/>
2547 </phase>
2548 <pattern id="P1">
2549 <rule context="root">
2550 <assert test="true()">Always passes</assert>
2551 </rule>
2552 </pattern>
2553 <pattern id="P2">
2554 <rule context="root">
2555 <assert test="false()">Always fails</assert>
2556 </rule>
2557 </pattern>
2558</schema>"#;
2559
2560 let doc_xml = r#"<?xml version="1.0"?>
2561<root>Hello</root>"#;
2562
2563 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2564
2565 let doc = unsafe {
2566 crate::abi::exports_xml2::xmlReadMemory(
2567 doc_xml.as_ptr() as *const c_char,
2568 doc_xml.len() as c_int,
2569 c"test.xml".as_ptr() as *const c_char,
2570 ptr::null(),
2571 0,
2572 )
2573 };
2574 assert!(!doc.is_null());
2575
2576 let mut ctxt = SchematronValidCtxt::new();
2578 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2579 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2580
2581 assert!(
2582 valid,
2583 "Phase filtering should make validation pass: {:?}",
2584 ctxt.errors
2585 );
2586 }
2587
2588 #[test]
2589 fn test_validate_no_rules() {
2590 let schema_xml = r#"<?xml version="1.0"?>
2591<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2592</schema>"#;
2593
2594 let doc_xml = r#"<?xml version="1.0"?>
2595<root>Hello</root>"#;
2596
2597 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2598
2599 let doc = unsafe {
2600 crate::abi::exports_xml2::xmlReadMemory(
2601 doc_xml.as_ptr() as *const c_char,
2602 doc_xml.len() as c_int,
2603 c"test.xml".as_ptr() as *const c_char,
2604 ptr::null(),
2605 0,
2606 )
2607 };
2608 assert!(!doc.is_null());
2609
2610 let mut ctxt = SchematronValidCtxt::new();
2611 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2612 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2613
2614 assert!(valid, "Empty schema should pass validation");
2615 }
2616
2617 #[test]
2618 fn test_validate_extends_resolution() {
2619 let schema_xml = r#"<?xml version="1.0"?>
2620<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2621 <pattern id="P1">
2622 <rule id="base" abstract="true" context="*">
2623 <assert test="true()">Base assertion</assert>
2624 </rule>
2625 <rule id="derived" context="root">
2626 <extends rule="base"/>
2627 <assert test="true()">Derived assertion</assert>
2628 </rule>
2629 </pattern>
2630</schema>"#;
2631
2632 let doc_xml = r#"<?xml version="1.0"?>
2633<root>Hello</root>"#;
2634
2635 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2636
2637 let resolved = schema.resolve_rule("derived");
2639 assert!(resolved.is_some());
2640 let resolved = resolved.unwrap();
2641 assert_eq!(
2643 resolved.patterns.len(),
2644 2,
2645 "Should have inherited the base pattern"
2646 );
2647
2648 let doc = unsafe {
2649 crate::abi::exports_xml2::xmlReadMemory(
2650 doc_xml.as_ptr() as *const c_char,
2651 doc_xml.len() as c_int,
2652 c"test.xml".as_ptr() as *const c_char,
2653 ptr::null(),
2654 0,
2655 )
2656 };
2657 assert!(!doc.is_null());
2658
2659 let mut ctxt = SchematronValidCtxt::new();
2660 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2661 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2662
2663 assert!(valid, "Extends resolution failed: {:?}", ctxt.errors);
2664 }
2665
2666 #[test]
2667 fn test_validate_assert_with_flag() {
2668 let schema_xml = r#"<?xml version="1.0"?>
2669<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2670 <pattern id="P1">
2671 <rule context="root">
2672 <assert test="false()" flag="warn">Warning message</assert>
2673 </rule>
2674 </pattern>
2675</schema>"#;
2676
2677 let doc_xml = r#"<?xml version="1.0"?>
2678<root>Hello</root>"#;
2679
2680 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2681
2682 let doc = unsafe {
2683 crate::abi::exports_xml2::xmlReadMemory(
2684 doc_xml.as_ptr() as *const c_char,
2685 doc_xml.len() as c_int,
2686 c"test.xml".as_ptr() as *const c_char,
2687 ptr::null(),
2688 0,
2689 )
2690 };
2691 assert!(!doc.is_null());
2692
2693 let mut ctxt = SchematronValidCtxt::new();
2694 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2695 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2696
2697 assert!(!valid);
2698 assert!(ctxt.nb_errors > 0);
2699 assert!(
2701 ctxt.errors[0].contains("[warn]"),
2702 "Error should include flag"
2703 );
2704 }
2705
2706 #[test]
2709 fn test_c_abi_new_free_parser_ctxt() {
2710 let ctxt = unsafe { xmlSchematronNewParserCtxt(ptr::null()) };
2711 assert!(!ctxt.is_null());
2712 unsafe { xmlSchematronFreeParserCtxt(ctxt) };
2713 }
2715
2716 #[test]
2717 fn test_c_abi_new_free_valid_ctxt() {
2718 let schema = unsafe { xmlSchematronNewParserCtxt(ptr::null()) };
2719 assert!(!schema.is_null());
2720
2721 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema) };
2722 assert!(!valid_ctxt.is_null());
2723
2724 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2725 unsafe { xmlSchematronFreeParserCtxt(schema) };
2726 }
2728
2729 #[test]
2730 fn test_c_abi_parse_free() {
2731 let schema_xml = r#"<?xml version="1.0"?>
2732<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2733 <pattern id="P1">
2734 <rule context="root">
2735 <assert test="true()">Test</assert>
2736 </rule>
2737 </pattern>
2738</schema>"#;
2739
2740 let ctxt = unsafe {
2741 xmlSchematronNewMemParserCtxt(
2742 schema_xml.as_ptr() as *const c_char,
2743 schema_xml.len() as c_int,
2744 )
2745 };
2746 assert!(!ctxt.is_null());
2747
2748 let schema = unsafe { xmlSchematronParse(ctxt) };
2749 assert!(!schema.is_null());
2750
2751 unsafe { xmlSchematronFree(schema) };
2752 }
2754
2755 #[test]
2756 fn test_c_abi_validate_doc() {
2757 let schema_xml = r#"<?xml version="1.0"?>
2758<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2759 <pattern id="P1">
2760 <rule context="root">
2761 <assert test="true()">Always passes</assert>
2762 </rule>
2763 </pattern>
2764</schema>"#;
2765
2766 let doc_xml = r#"<?xml version="1.0"?>
2767<root>Hello</root>"#;
2768
2769 let ctxt = unsafe {
2770 xmlSchematronNewMemParserCtxt(
2771 schema_xml.as_ptr() as *const c_char,
2772 schema_xml.len() as c_int,
2773 )
2774 };
2775 assert!(!ctxt.is_null());
2776
2777 let schema = unsafe { xmlSchematronParse(ctxt) };
2778 assert!(!schema.is_null());
2779
2780 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema) };
2781 assert!(!valid_ctxt.is_null());
2782
2783 let doc = unsafe {
2784 crate::abi::exports_xml2::xmlReadMemory(
2785 doc_xml.as_ptr() as *const c_char,
2786 doc_xml.len() as c_int,
2787 c"test.xml".as_ptr() as *const c_char,
2788 ptr::null(),
2789 0,
2790 )
2791 };
2792 assert!(!doc.is_null());
2793
2794 let result = unsafe { xmlSchematronValidateDoc(valid_ctxt, doc) };
2795 assert_eq!(result, 0, "Validation should pass (return 0)");
2796
2797 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2798 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2799 unsafe { xmlSchematronFree(schema) };
2800 }
2801
2802 #[test]
2803 fn test_c_abi_validate_fail() {
2804 let schema_xml = r#"<?xml version="1.0"?>
2805<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2806 <pattern id="P1">
2807 <rule context="root">
2808 <assert test="false()">Always fails</assert>
2809 </rule>
2810 </pattern>
2811</schema>"#;
2812
2813 let doc_xml = r#"<?xml version="1.0"?>
2814<root>Hello</root>"#;
2815
2816 let ctxt = unsafe {
2817 xmlSchematronNewMemParserCtxt(
2818 schema_xml.as_ptr() as *const c_char,
2819 schema_xml.len() as c_int,
2820 )
2821 };
2822 assert!(!ctxt.is_null());
2823
2824 let schema = unsafe { xmlSchematronParse(ctxt) };
2825 assert!(!schema.is_null());
2826
2827 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema) };
2828 assert!(!valid_ctxt.is_null());
2829
2830 let doc = unsafe {
2831 crate::abi::exports_xml2::xmlReadMemory(
2832 doc_xml.as_ptr() as *const c_char,
2833 doc_xml.len() as c_int,
2834 c"test.xml".as_ptr() as *const c_char,
2835 ptr::null(),
2836 0,
2837 )
2838 };
2839 assert!(!doc.is_null());
2840
2841 let result = unsafe { xmlSchematronValidateDoc(valid_ctxt, doc) };
2842 assert!(result > 0, "Validation should fail (return > 0)");
2843
2844 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2845 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2846 unsafe { xmlSchematronFree(schema) };
2847 }
2848
2849 #[test]
2850 fn test_c_abi_null_handling() {
2851 unsafe { xmlSchematronFree(ptr::null_mut()) };
2853 unsafe { xmlSchematronFreeParserCtxt(ptr::null_mut()) };
2854 unsafe { xmlSchematronFreeValidCtxt(ptr::null_mut()) };
2855
2856 let result = unsafe { xmlSchematronParse(ptr::null_mut()) };
2858 assert!(result.is_null());
2859
2860 let result = unsafe { xmlSchematronValidateDoc(ptr::null_mut(), ptr::null_mut()) };
2862 assert_eq!(result, -1);
2863 }
2864
2865 #[test]
2868 fn test_validate_null_doc() {
2869 let schema = SchematronSchema::new();
2870 let mut ctxt = SchematronValidCtxt::new();
2871 let valid = unsafe { schematron_validate_doc(&schema, ptr::null_mut(), &mut ctxt) };
2872 assert!(!valid);
2873 assert!(ctxt.nb_errors > 0);
2874 }
2875
2876 #[test]
2877 fn test_active_rules_default_phase() {
2878 let mut schema = SchematronSchema::new();
2879
2880 let rule = SchematronRule::new("root".to_string());
2881 schema.rules.insert("r1".to_string(), rule);
2882
2883 schema
2884 .pattern_groups
2885 .insert("p1".to_string(), vec!["r1".to_string()]);
2886 schema.pattern_order.push("p1".to_string());
2887
2888 let rules = schema.active_rules(None);
2889 assert_eq!(rules.len(), 1);
2890 }
2891
2892 #[test]
2893 fn test_active_rules_unknown_phase() {
2894 let mut schema = SchematronSchema::new();
2895
2896 let rule = SchematronRule::new("root".to_string());
2897 schema.rules.insert("r1".to_string(), rule);
2898
2899 schema
2900 .pattern_groups
2901 .insert("p1".to_string(), vec!["r1".to_string()]);
2902 schema.pattern_order.push("p1".to_string());
2903
2904 let rules = schema.active_rules(Some("nonexistent"));
2905 assert_eq!(rules.len(), 1, "Unknown phase should use all patterns");
2906 }
2907
2908 #[test]
2909 fn test_parse_schema_with_span_and_emph() {
2910 let schema_xml = r#"<?xml version="1.0"?>
2911<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2912 <pattern id="P1">
2913 <rule context="root">
2914 <assert test="true()">Message with <span class="x">inline</span> and <emph>emphasis</emph></assert>
2915 </rule>
2916 </pattern>
2917</schema>"#;
2918
2919 let result = schematron_parse(schema_xml);
2920 assert!(
2921 result.is_ok(),
2922 "Failed to parse schema with span/emph: {:?}",
2923 result.err()
2924 );
2925 let schema = result.unwrap();
2926 let rule = schema.rules.values().next().unwrap();
2927 let pat = &rule.patterns[0];
2928 assert!(
2930 pat.text.contains("inline"),
2931 "Text should include span content"
2932 );
2933 assert!(
2934 pat.text.contains("emphasis"),
2935 "Text should include emph content"
2936 );
2937 }
2938
2939 #[test]
2940 fn test_schematron_pattern_new_assert() {
2941 let pat = SchematronPattern::new(
2942 SchematronPatternType::Assert,
2943 "true()".to_string(),
2944 "Test message".to_string(),
2945 );
2946 assert_eq!(pat.pattern_type, SchematronPatternType::Assert);
2947 assert_eq!(pat.test, "true()");
2948 assert_eq!(pat.text, "Test message");
2949 assert!(pat.compiled_test.is_some());
2950 }
2951
2952 #[test]
2953 fn test_schematron_pattern_new_report() {
2954 let pat = SchematronPattern::new(
2955 SchematronPatternType::Report,
2956 "false()".to_string(),
2957 "Report message".to_string(),
2958 );
2959 assert_eq!(pat.pattern_type, SchematronPatternType::Report);
2960 assert!(pat.compiled_test.is_some());
2961 }
2962
2963 #[test]
2964 fn test_schematron_rule_new() {
2965 let rule = SchematronRule::new("root".to_string());
2966 assert_eq!(rule.context, "root");
2967 assert!(rule.patterns.is_empty());
2968 assert!(!rule.abstract_);
2969 }
2970
2971 #[test]
2972 fn test_schematron_schema_new() {
2973 let schema = SchematronSchema::new();
2974 assert_eq!(schema.query_binding, "xslt");
2975 assert!(schema.rules.is_empty());
2976 assert!(schema.phases.is_empty());
2977 assert!(schema.ns.is_empty());
2978 }
2979
2980 #[test]
2981 fn test_schematron_valid_ctxt_new() {
2982 let ctxt = SchematronValidCtxt::new();
2983 assert!(ctxt.errors.is_empty());
2984 assert_eq!(ctxt.nb_errors, 0);
2985 assert!(ctxt.active_phase.is_none());
2986 }
2987
2988 #[test]
2989 fn test_validate_assert_with_child_count() {
2990 let schema_xml = r#"<?xml version="1.0"?>
2991<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2992 <pattern id="P1">
2993 <rule context="root">
2994 <assert test="count(*) > 0">Root must have at least one child element</assert>
2995 </rule>
2996 </pattern>
2997</schema>"#;
2998
2999 let doc_xml = r#"<?xml version="1.0"?>
3000<root>
3001 <child>Content</child>
3002</root>"#;
3003
3004 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
3005
3006 let doc = unsafe {
3007 crate::abi::exports_xml2::xmlReadMemory(
3008 doc_xml.as_ptr() as *const c_char,
3009 doc_xml.len() as c_int,
3010 c"test.xml".as_ptr() as *const c_char,
3011 ptr::null(),
3012 0,
3013 )
3014 };
3015 assert!(!doc.is_null());
3016
3017 let mut ctxt = SchematronValidCtxt::new();
3018 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
3019 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
3020
3021 assert!(valid, "Child count check failed: {:?}", ctxt.errors);
3022 }
3023}