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::allocator;
41use crate::abi::structs::*;
42use crate::abi::types::xmlElementType::*;
43use crate::abi::types::*;
44use crate::xml::xpath::ast::CompiledExpr;
45use crate::xml::xpath::context::XPathContext;
46use crate::xml::xpath::types::XPathValue;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum SchematronPatternType {
55 Assert,
57 Report,
59}
60
61#[derive(Debug, Clone)]
67pub struct SchematronPattern {
68 pub pattern_type: SchematronPatternType,
70 pub test: String,
72 pub compiled_test: Option<CompiledExpr>,
74 pub text: String,
76 pub flag: Option<String>,
78 pub role: Option<String>,
80 pub id: Option<String>,
82 pub icon: Option<String>,
84 pub see: Option<String>,
86 pub diagnostics: Option<String>,
88}
89
90impl SchematronPattern {
91 pub fn new(pattern_type: SchematronPatternType, test: String, text: String) -> Self {
93 Self {
94 pattern_type,
95 compiled_test: crate::xml::xpath::compile(&test),
96 test,
97 text,
98 flag: None,
99 role: None,
100 id: None,
101 icon: None,
102 see: None,
103 diagnostics: None,
104 }
105 }
106}
107
108#[derive(Debug, Clone)]
118pub struct SchematronRule {
119 pub context: String,
121 pub compiled_context: Option<CompiledExpr>,
123 pub patterns: Vec<SchematronPattern>,
125 pub id: Option<String>,
127 pub abstract_: bool,
129 pub extends: Vec<String>,
131}
132
133impl SchematronRule {
134 pub fn new(context: String) -> Self {
136 Self {
137 compiled_context: crate::xml::xpath::compile(&context),
138 context,
139 patterns: Vec::new(),
140 id: None,
141 abstract_: false,
142 extends: Vec::new(),
143 }
144 }
145}
146
147#[derive(Debug, Clone)]
157pub struct SchematronPhase {
158 pub id: String,
160 pub active_patterns: Vec<String>,
162}
163
164#[derive(Debug, Clone)]
170pub struct SchematronDiagnostic {
171 pub id: String,
173 pub text: String,
175 pub icon: Option<String>,
177 pub see: Option<String>,
179}
180
181#[derive(Debug, Clone)]
191pub struct SchematronSchema {
192 pub title: Option<String>,
194 pub phases: HashMap<String, SchematronPhase>,
196 pub rules: HashMap<String, SchematronRule>,
198 pub pattern_groups: HashMap<String, Vec<String>>,
200 pub pattern_order: Vec<String>,
202 pub ns: HashMap<String, String>,
204 pub query_binding: String,
206 pub default_phase: Option<String>,
208 pub diagnostics: HashMap<String, SchematronDiagnostic>,
210 pub errors: Vec<String>,
212}
213
214impl SchematronSchema {
215 pub fn new() -> Self {
217 Self {
218 title: None,
219 phases: HashMap::new(),
220 rules: HashMap::new(),
221 pattern_groups: HashMap::new(),
222 pattern_order: Vec::new(),
223 ns: HashMap::new(),
224 query_binding: "xslt".to_string(),
225 default_phase: None,
226 diagnostics: HashMap::new(),
227 errors: Vec::new(),
228 }
229 }
230
231 pub fn resolve_rule(&self, rule_id: &str) -> Option<SchematronRule> {
233 let rule = self.rules.get(rule_id)?.clone();
234 Some(self.resolve_extends(rule))
235 }
236
237 fn resolve_extends(&self, mut rule: SchematronRule) -> SchematronRule {
239 let extended_ids: Vec<String> = rule.extends.clone();
240 for ext_id in &extended_ids {
241 if let Some(ext_rule) = self.rules.get(ext_id) {
242 let resolved_ext = self.resolve_extends(ext_rule.clone());
244 rule.patterns.extend(resolved_ext.patterns);
245 }
246 }
247 rule
248 }
249
250 pub fn active_rules(&self, phase_id: Option<&str>) -> Vec<SchematronRule> {
254 let active_patterns: Vec<String> = match phase_id {
256 Some(pid) => {
257 if let Some(phase) = self.phases.get(pid) {
258 phase.active_patterns.clone()
259 } else {
260 self.pattern_order.clone()
262 }
263 }
264 None => {
265 if let Some(default) = &self.default_phase {
267 if let Some(phase) = self.phases.get(default) {
268 phase.active_patterns.clone()
269 } else {
270 self.pattern_order.clone()
271 }
272 } else {
273 self.pattern_order.clone()
274 }
275 }
276 };
277
278 let mut result = Vec::new();
279 for pat_id in &active_patterns {
280 if let Some(rule_ids) = self.pattern_groups.get(pat_id) {
281 for rule_id in rule_ids {
282 if let Some(rule) = self.rules.get(rule_id) {
283 if !rule.abstract_ {
284 result.push(self.resolve_extends(rule.clone()));
285 }
286 }
287 }
288 }
289 }
290
291 result
292 }
293}
294
295impl Default for SchematronSchema {
296 fn default() -> Self {
297 Self::new()
298 }
299}
300
301#[derive(Debug)]
310pub struct SchematronValidCtxt {
311 pub schema: Option<SchematronSchema>,
313 pub errors: Vec<String>,
315 pub nb_errors: i32,
317 pub active_phase: Option<String>,
319}
320
321impl SchematronValidCtxt {
322 pub fn new() -> Self {
324 Self {
325 schema: None,
326 errors: Vec::new(),
327 nb_errors: 0,
328 active_phase: None,
329 }
330 }
331
332 pub fn record_error(&mut self, msg: String) {
334 self.errors.push(msg);
335 self.nb_errors += 1;
336 }
337}
338
339impl Default for SchematronValidCtxt {
340 fn default() -> Self {
341 Self::new()
342 }
343}
344
345unsafe fn get_local_name(node: *mut _xmlNode) -> String {
355 if node.is_null() {
356 return String::new();
357 }
358 unsafe {
359 let name = (*node).name;
360 if name.is_null() {
361 return String::new();
362 }
363 let mut len = 0;
364 while *name.add(len) != 0 {
365 len += 1;
366 }
367 let slice = std::slice::from_raw_parts(name, len);
368 if let Ok(s) = std::str::from_utf8(slice) {
369 if let Some(pos) = s.find(':') {
370 s[pos + 1..].to_string()
371 } else {
372 s.to_string()
373 }
374 } else {
375 String::new()
376 }
377 }
378}
379
380unsafe fn get_node_qname(node: *mut _xmlNode) -> String {
386 if node.is_null() {
387 return String::new();
388 }
389 unsafe {
390 let ns = (*node).ns;
391 let prefix = if !ns.is_null() && !(*ns).prefix.is_null() {
392 let mut len = 0;
393 while *(*ns).prefix.add(len) != 0 {
394 len += 1;
395 }
396 let slice = std::slice::from_raw_parts((*ns).prefix, len);
397 if let Ok(s) = std::str::from_utf8(slice) {
398 format!("{}:", s)
399 } else {
400 String::new()
401 }
402 } else {
403 String::new()
404 };
405
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 format!("{}{}", prefix, s)
417 } else {
418 String::new()
419 }
420 }
421}
422
423unsafe fn get_node_text(node: *mut _xmlNode) -> String {
429 if node.is_null() {
430 return String::new();
431 }
432 let mut result = String::new();
433 unsafe {
434 let mut child = (*node).children;
435 while !child.is_null() {
436 if (*child).type_ == XML_TEXT_NODE as c_int
437 || (*child).type_ == XML_CDATA_SECTION_NODE as c_int
438 {
439 if !(*child).content.is_null() {
440 let content = (*child).content;
441 let mut len = 0;
442 while *content.add(len) != 0 {
443 len += 1;
444 }
445 let slice = std::slice::from_raw_parts(content, len);
446 result.push_str(&String::from_utf8_lossy(slice));
447 }
448 }
449 child = (*child).next;
450 }
451 }
452 result
453}
454
455unsafe fn get_attr(node: *mut _xmlNode, name: &str) -> Option<String> {
461 if node.is_null() {
462 return None;
463 }
464 unsafe {
465 let mut prop = (*node).properties;
466 while !prop.is_null() {
467 let prop_name = (*prop).name;
468 if !prop_name.is_null() {
469 let mut len = 0;
470 while *prop_name.add(len) != 0 {
471 len += 1;
472 }
473 let slice = std::slice::from_raw_parts(prop_name, len);
474 if let Ok(s) = std::str::from_utf8(slice) {
475 if s == name {
476 return Some(get_node_text(prop as *mut _xmlNode));
477 }
478 }
479 }
480 prop = (*prop).next;
481 }
482 }
483 None
484}
485
486unsafe fn node_is(node: *mut _xmlNode, local_name: &str) -> bool {
492 if node.is_null() {
493 return false;
494 }
495 unsafe {
496 let name = (*node).name;
497 if name.is_null() {
498 return false;
499 }
500 let mut len = 0;
501 while *name.add(len) != 0 {
502 len += 1;
503 }
504 let slice = std::slice::from_raw_parts(name, len);
505 if let Ok(s) = std::str::from_utf8(slice) {
506 let local = if let Some(pos) = s.find(':') {
507 &s[pos + 1..]
508 } else {
509 s
510 };
511 return local == local_name;
512 }
513 }
514 false
515}
516
517unsafe fn child_elements(node: *mut _xmlNode) -> Vec<*mut _xmlNode> {
523 let mut children = Vec::new();
524 if node.is_null() {
525 return children;
526 }
527 unsafe {
528 let mut child = (*node).children;
529 while !child.is_null() {
530 if (*child).type_ == XML_ELEMENT_NODE as c_int {
531 children.push(child);
532 }
533 child = (*child).next;
534 }
535 }
536 children
537}
538
539unsafe fn get_inline_text(node: *mut _xmlNode) -> String {
546 if node.is_null() {
547 return String::new();
548 }
549 let mut result = String::new();
550 unsafe {
551 let mut child = (*node).children;
552 while !child.is_null() {
553 if (*child).type_ == XML_TEXT_NODE as c_int
554 || (*child).type_ == XML_CDATA_SECTION_NODE as c_int
555 {
556 if !(*child).content.is_null() {
557 let content = (*child).content;
558 let mut len = 0;
559 while *content.add(len) != 0 {
560 len += 1;
561 }
562 let slice = std::slice::from_raw_parts(content, len);
563 result.push_str(&String::from_utf8_lossy(slice));
564 }
565 } else if (*child).type_ == XML_ELEMENT_NODE as c_int {
566 let local = get_local_name(child);
567 match local.as_str() {
568 "span" | "emph" | "dir" => {
569 result.push_str(&get_inline_text(child));
570 }
571 _ => {}
572 }
573 }
574 child = (*child).next;
575 }
576 }
577 result
578}
579
580pub fn schematron_parse(xml_doc: &str) -> Result<SchematronSchema, String> {
593 let doc_ptr = unsafe {
594 crate::abi::exports_xml2::xmlReadMemory(
595 xml_doc.as_ptr() as *const c_char,
596 xml_doc.len() as c_int,
597 b"schema.sch\0".as_ptr() as *const c_char,
598 ptr::null(),
599 0,
600 )
601 };
602
603 if doc_ptr.is_null() {
604 return Err("Failed to parse Schematron schema XML document".to_string());
605 }
606
607 let result = unsafe { schematron_parse_doc(doc_ptr) };
608 unsafe {
609 crate::abi::exports_xml2::xmlFreeDoc(doc_ptr);
610 }
611 result
612}
613
614unsafe fn schematron_parse_doc(doc: *mut _xmlDoc) -> Result<SchematronSchema, String> {
620 unsafe {
621 let root = (*doc).children;
622 if root.is_null() {
623 return Err("Schematron document has no root element".to_string());
624 }
625
626 let mut root_elem = root;
628 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
629 root_elem = (*root_elem).next;
630 }
631
632 if root_elem.is_null() {
633 return Err("Schematron document has no root element".to_string());
634 }
635
636 let local_name = get_local_name(root_elem);
637 if local_name != "schema" {
638 return Err(format!(
639 "Expected '<schema>' root element, found '<{}>'",
640 local_name
641 ));
642 }
643
644 Ok(schematron_parse_schema_node(root_elem))
645 }
646}
647
648unsafe fn schematron_parse_schema_node(node: *mut _xmlNode) -> SchematronSchema {
654 unsafe {
655 let mut schema = SchematronSchema::new();
656
657 if let Some(qb) = get_attr(node, "queryBinding") {
659 schema.query_binding = qb;
660 }
661 schema.title = get_attr(node, "title");
662 if let Some(df) = get_attr(node, "defaultPhase") {
663 schema.default_phase = Some(df);
664 }
665
666 let mut current_pattern_id: Option<String> = None;
668 let mut pattern_names: HashMap<String, Vec<String>> = HashMap::new();
669
670 let mut child = (*node).children;
671 while !child.is_null() {
672 if (*child).type_ == XML_ELEMENT_NODE as c_int {
673 let local = get_local_name(child);
674 match local.as_str() {
675 "title" => {
676 if schema.title.is_none() {
677 schema.title = Some(get_node_text(child).trim().to_string());
678 }
679 }
680 "ns" => {
681 let prefix = get_attr(child, "prefix").unwrap_or_default();
682 let uri = get_attr(child, "uri").unwrap_or_default();
683 if !prefix.is_empty() && !uri.is_empty() {
684 schema.ns.insert(prefix, uri);
685 }
686 }
687 "phase" => {
688 let phase = schematron_parse_phase(child);
689 schema.phases.insert(phase.id.clone(), phase);
690 }
691 "pattern" => {
692 let pat_id = schematron_parse_pattern_node(
693 child,
694 &mut schema,
695 &mut current_pattern_id,
696 &mut pattern_names,
697 );
698 current_pattern_id = pat_id;
699 }
700 "rule" => {
701 let rule = schematron_parse_rule(child, &mut schema);
703 let rule_id = rule
704 .id
705 .clone()
706 .unwrap_or_else(|| format!("_rule_{}", schema.rules.len()));
707 let rid = rule_id.clone();
709 schema.rules.insert(rid, rule);
710
711 if let Some(ref pid) = current_pattern_id {
713 schema
714 .pattern_groups
715 .entry(pid.clone())
716 .or_insert_with(Vec::new)
717 .push(rule_id);
718 } else {
719 let anon_id = format!("_anon_{}", schema.pattern_order.len());
721 schema
722 .pattern_groups
723 .entry(anon_id.clone())
724 .or_insert_with(Vec::new)
725 .push(rule_id);
726 if !schema.pattern_order.contains(&anon_id) {
727 schema.pattern_order.push(anon_id);
728 }
729 }
730 }
731 "diagnostics" => {
732 schematron_parse_diagnostics(child, &mut schema);
733 }
734 "include" => {
735 schematron_parse_include(child, &mut schema);
736 }
737 "p" | "caption" => {
738 }
740 _ => {
741 schema
742 .errors
743 .push(format!("Unexpected element '<{}>' in schema", local));
744 }
745 }
746 }
747 child = (*child).next;
748 }
749
750 schema
751 }
752}
753
754unsafe fn schematron_parse_pattern_node(
760 node: *mut _xmlNode,
761 schema: &mut SchematronSchema,
762 _current_pattern_id: &mut Option<String>,
763 _pattern_names: &mut HashMap<String, Vec<String>>,
764) -> Option<String> {
765 unsafe {
766 let pat_id = get_attr(node, "id");
767 let pat_name = get_attr(node, "name");
768 let pat_is_a = get_attr(node, "is-a");
769 let pat_see = get_attr(node, "see");
770 let pat_icon = get_attr(node, "icon");
771 let pat_role = get_attr(node, "role");
772
773 let pid = pat_id
774 .clone()
775 .unwrap_or_else(|| format!("_pattern_{}", schema.pattern_order.len()));
776
777 let mut rule_ids: Vec<String> = Vec::new();
778
779 let mut child = (*node).children;
781 while !child.is_null() {
782 if (*child).type_ == XML_ELEMENT_NODE as c_int {
783 let local = get_local_name(child);
784 match local.as_str() {
785 "rule" => {
786 let rule = schematron_parse_rule(child, schema);
787 let rule_id = rule
788 .id
789 .clone()
790 .unwrap_or_else(|| format!("_rule_{}", schema.rules.len()));
791 let rid = rule_id.clone();
792 schema.rules.insert(rid, rule);
793 rule_ids.push(rule_id);
794 }
795 "p" | "caption" => {
796 }
798 _ => {
799 schema
800 .errors
801 .push(format!("Unexpected element '<{}>' in pattern", local));
802 }
803 }
804 }
805 child = (*child).next;
806 }
807
808 schema.pattern_groups.insert(pid.clone(), rule_ids);
809 schema.pattern_order.push(pid.clone());
810
811 if pat_is_a.is_some() {
813 }
816
817 let _ = pat_name;
819 let _ = pat_see;
820 let _ = pat_icon;
821 let _ = pat_role;
822
823 Some(pid)
824 }
825}
826
827unsafe fn schematron_parse_rule(
833 node: *mut _xmlNode,
834 schema: &mut SchematronSchema,
835) -> SchematronRule {
836 unsafe {
837 let context = get_attr(node, "context").unwrap_or_default();
838 let mut rule = SchematronRule::new(context);
839 rule.id = get_attr(node, "id");
840
841 let abs = get_attr(node, "abstract").unwrap_or_default();
842 rule.abstract_ = abs == "true" || abs == "1";
843
844 let mut child = (*node).children;
846 while !child.is_null() {
847 if (*child).type_ == XML_ELEMENT_NODE as c_int {
848 let local = get_local_name(child);
849 match local.as_str() {
850 "assert" => {
851 let pattern = schematron_parse_assert(child, SchematronPatternType::Assert);
852 rule.patterns.push(pattern);
853 }
854 "report" => {
855 let pattern = schematron_parse_assert(child, SchematronPatternType::Report);
856 rule.patterns.push(pattern);
857 }
858 "extends" => {
859 if let Some(ext_rule) = get_attr(child, "rule") {
860 rule.extends.push(ext_rule);
861 }
862 }
863 "let" => {
864 let name = get_attr(child, "name").unwrap_or_default();
867 let value = get_attr(child, "value").unwrap_or_default();
868 if !name.is_empty() {
869 let _ = value;
872 }
873 }
874 "param" => {
875 let _name = get_attr(child, "name");
877 let _value = get_attr(child, "value");
878 }
879 "p" | "caption" => {
880 }
882 _ => {
883 schema
884 .errors
885 .push(format!("Unexpected element '<{}>' in rule", local));
886 }
887 }
888 }
889 child = (*child).next;
890 }
891
892 rule
893 }
894}
895
896unsafe fn schematron_parse_assert(
902 node: *mut _xmlNode,
903 pattern_type: SchematronPatternType,
904) -> SchematronPattern {
905 unsafe {
906 let test = get_attr(node, "test").unwrap_or_default();
907 let text = get_inline_text(node);
908
909 let mut pattern = SchematronPattern::new(pattern_type, test, text);
910 pattern.flag = get_attr(node, "flag");
911 pattern.id = get_attr(node, "id");
912 pattern.icon = get_attr(node, "icon");
913 pattern.see = get_attr(node, "see");
914 pattern.role = get_attr(node, "role");
915 pattern.diagnostics = get_attr(node, "diagnostics");
916
917 pattern
921 }
922}
923
924unsafe fn schematron_parse_phase(node: *mut _xmlNode) -> SchematronPhase {
930 unsafe {
931 let id = get_attr(node, "id").unwrap_or_default();
932 let mut phase = SchematronPhase {
933 id,
934 active_patterns: Vec::new(),
935 };
936
937 let mut child = (*node).children;
938 while !child.is_null() {
939 if (*child).type_ == XML_ELEMENT_NODE as c_int {
940 let local = get_local_name(child);
941 if local == "active" {
942 if let Some(pattern) = get_attr(child, "pattern") {
943 phase.active_patterns.push(pattern);
944 }
945 }
946 }
947 child = (*child).next;
948 }
949
950 phase
951 }
952}
953
954unsafe fn schematron_parse_diagnostics(node: *mut _xmlNode, schema: &mut SchematronSchema) {
960 unsafe {
961 let mut child = (*node).children;
962 while !child.is_null() {
963 if (*child).type_ == XML_ELEMENT_NODE as c_int {
964 let local = get_local_name(child);
965 if local == "diagnostic" {
966 let diag = schematron_parse_diagnostic(child);
967 schema.diagnostics.insert(diag.id.clone(), diag);
968 }
969 }
970 child = (*child).next;
971 }
972 }
973}
974
975unsafe fn schematron_parse_diagnostic(node: *mut _xmlNode) -> SchematronDiagnostic {
981 unsafe {
982 let id = get_attr(node, "id").unwrap_or_default();
983 let text = get_inline_text(node);
984 let icon = get_attr(node, "icon");
985 let see = get_attr(node, "see");
986
987 SchematronDiagnostic {
988 id,
989 text,
990 icon,
991 see,
992 }
993 }
994}
995
996unsafe fn schematron_parse_include(node: *mut _xmlNode, _schema: &mut SchematronSchema) {
1002 unsafe {
1003 let href = get_attr(node, "href");
1004 if let Some(url) = href {
1005 let url_c = std::ffi::CString::new(url.clone()).ok();
1006 if let Some(c) = url_c {
1007 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
1008 if !doc.is_null() {
1009 let mut root = (*doc).children;
1011 while !root.is_null() && (*root).type_ != XML_ELEMENT_NODE as c_int {
1012 root = (*root).next;
1013 }
1014 if !root.is_null() {
1015 let local = get_local_name(root);
1016 if local == "schema" || local == "pattern" || local == "rule" {
1017 }
1021 }
1022 crate::abi::exports_xml2::xmlFreeDoc(doc);
1023 }
1024 }
1025 }
1026 }
1027}
1028
1029unsafe fn expand_diagnostic_message(
1042 text: &str,
1043 context_node: *mut _xmlNode,
1044 xpath_ctxt: &mut XPathContext,
1045) -> String {
1046 let _ = context_node;
1058 let _ = xpath_ctxt;
1059 text.to_string()
1060}
1061
1062fn evaluate_xpath_boolean(
1068 compiled: &CompiledExpr,
1069 xpath_ctxt: &mut XPathContext,
1070) -> Result<bool, String> {
1071 match crate::xml::xpath::evaluate(compiled, xpath_ctxt) {
1072 Some(value) => Ok(value.as_boolean()),
1073 None => Err("XPath evaluation failed".to_string()),
1074 }
1075}
1076
1077pub unsafe fn schematron_validate_doc(
1087 schema: &SchematronSchema,
1088 doc: *mut _xmlDoc,
1089 ctxt: &mut SchematronValidCtxt,
1090) -> bool {
1091 unsafe {
1092 if doc.is_null() {
1093 ctxt.record_error("Document is null".to_string());
1094 return false;
1095 }
1096
1097 let root = (*doc).children;
1098 if root.is_null() {
1099 ctxt.record_error("Document has no children".to_string());
1100 return false;
1101 }
1102
1103 let mut root_elem = root;
1105 while !root_elem.is_null() && (*root_elem).type_ != XML_ELEMENT_NODE as c_int {
1106 root_elem = (*root_elem).next;
1107 }
1108
1109 if root_elem.is_null() {
1110 ctxt.record_error("Document has no root element".to_string());
1111 return false;
1112 }
1113
1114 let phase_id = ctxt.active_phase.as_deref();
1116 let rules = schema.active_rules(phase_id);
1117
1118 if rules.is_empty() {
1119 return true;
1121 }
1122
1123 let mut xpath_ctxt = XPathContext::new(doc);
1125
1126 let core_funcs = crate::xml::xpath::functions::core_functions();
1128 for (name, func) in core_funcs {
1129 xpath_ctxt.register_function(&name, func);
1130 }
1131
1132 for (prefix, uri) in &schema.ns {
1134 xpath_ctxt.namespaces.insert(prefix.clone(), uri.clone());
1135 }
1136
1137 let mut valid = true;
1138
1139 for rule in &rules {
1141 let matching_nodes: Vec<*mut _xmlNode> =
1143 find_matching_nodes(rule, root_elem, doc, &mut xpath_ctxt);
1144
1145 for context_node in &matching_nodes {
1146 xpath_ctxt.set_context_node(*context_node);
1148
1149 for pattern in &rule.patterns {
1151 let compiled = match &pattern.compiled_test {
1152 Some(c) => c,
1153 None => continue,
1154 };
1155
1156 let test_result = match evaluate_xpath_boolean(compiled, &mut xpath_ctxt) {
1157 Ok(val) => val,
1158 Err(e) => {
1159 ctxt.record_error(format!(
1160 "XPath error in '{}' test '{}': {}",
1161 if pattern.pattern_type == SchematronPatternType::Assert {
1162 "assert"
1163 } else {
1164 "report"
1165 },
1166 pattern.test,
1167 e
1168 ));
1169 valid = false;
1170 continue;
1171 }
1172 };
1173
1174 let message =
1175 expand_diagnostic_message(&pattern.text, *context_node, &mut xpath_ctxt);
1176
1177 match pattern.pattern_type {
1178 SchematronPatternType::Assert => {
1179 if !test_result {
1181 let node_name = get_node_qname(*context_node);
1182 let flag_str = pattern
1183 .flag
1184 .as_ref()
1185 .map(|f| format!(" [{}]", f))
1186 .unwrap_or_default();
1187 let role_str = pattern
1188 .role
1189 .as_ref()
1190 .map(|r| format!(" ({})", r))
1191 .unwrap_or_default();
1192 let msg = if message.is_empty() {
1193 format!(
1194 "assertion failed: '{}' for node '{}'{}{}",
1195 pattern.test, node_name, flag_str, role_str
1196 )
1197 } else {
1198 format!(
1199 "assertion '{}' failed for node '{}'{}{}: {}",
1200 pattern.test, node_name, flag_str, role_str, message
1201 )
1202 };
1203 ctxt.record_error(msg);
1204 valid = false;
1205 }
1206 }
1207 SchematronPatternType::Report => {
1208 if test_result {
1210 let node_name = get_node_qname(*context_node);
1211 let flag_str = pattern
1212 .flag
1213 .as_ref()
1214 .map(|f| format!(" [{}]", f))
1215 .unwrap_or_default();
1216 let role_str = pattern
1217 .role
1218 .as_ref()
1219 .map(|r| format!(" ({})", r))
1220 .unwrap_or_default();
1221 let msg = if message.is_empty() {
1222 format!(
1223 "report triggered: '{}' for node '{}'{}{}",
1224 pattern.test, node_name, flag_str, role_str
1225 )
1226 } else {
1227 format!(
1228 "report '{}' triggered for node '{}'{}{}: {}",
1229 pattern.test, node_name, flag_str, role_str, message
1230 )
1231 };
1232 ctxt.record_error(msg);
1233 valid = false;
1234 }
1235 }
1236 }
1237 }
1238 }
1239 }
1240
1241 valid
1242 }
1243}
1244
1245unsafe fn find_matching_nodes(
1252 rule: &SchematronRule,
1253 root: *mut _xmlNode,
1254 doc: *mut _xmlDoc,
1255 xpath_ctxt: &mut XPathContext,
1256) -> Vec<*mut _xmlNode> {
1257 unsafe {
1258 if rule.context.is_empty() {
1260 let mut nodes = Vec::new();
1261 collect_all_elements(root, &mut nodes);
1262 return nodes;
1263 }
1264
1265 if let Some(compiled) = &rule.compiled_context {
1267 let is_simple_name = !rule.context.contains('/')
1270 && !rule.context.contains("::")
1271 && !rule.context.contains('[')
1272 && !rule.context.contains('(');
1273
1274 if !is_simple_name {
1275 xpath_ctxt.set_context_node(root);
1276 xpath_ctxt.document = doc;
1277
1278 match crate::xml::xpath::evaluate(compiled, xpath_ctxt) {
1279 Some(XPathValue::NodeSet(ns)) => {
1280 if !ns.is_empty() {
1281 return ns.iter().collect();
1282 }
1283 }
1284 _ => {}
1285 }
1286 }
1287
1288 simple_context_match(&rule.context, root)
1290 } else {
1291 simple_context_match(&rule.context, root)
1293 }
1294 }
1295}
1296
1297fn simple_context_match(context: &str, root: *mut _xmlNode) -> Vec<*mut _xmlNode> {
1300 unsafe {
1301 let context = context.trim();
1302
1303 if context == "*" || context == "//*" {
1310 let mut nodes = Vec::new();
1311 collect_all_elements(root, &mut nodes);
1312 return nodes;
1313 }
1314
1315 if context.starts_with("//") {
1316 let name = &context[2..];
1317 if name.is_empty() || name == "*" {
1318 let mut nodes = Vec::new();
1319 collect_all_elements(root, &mut nodes);
1320 return nodes;
1321 }
1322 let mut nodes = Vec::new();
1324 collect_elements_by_name(root, name, &mut nodes);
1325 return nodes;
1326 }
1327
1328 if !context.contains('/') && !context.contains("::") {
1329 let mut nodes = Vec::new();
1331 let root_qname = get_node_qname(root);
1333 let root_local = get_local_name(root);
1334 if root_qname == context || root_local == context || context == "*" {
1335 nodes.push(root);
1336 }
1337 let mut child = (*root).children;
1339 while !child.is_null() {
1340 if (*child).type_ == XML_ELEMENT_NODE as c_int {
1341 let qname = get_node_qname(child);
1342 let local = get_local_name(child);
1343 if qname == context || local == context || context == "*" {
1344 nodes.push(child);
1345 }
1346 }
1347 child = (*child).next;
1348 }
1349 return nodes;
1350 }
1351
1352 vec![root]
1354 }
1355}
1356
1357unsafe fn collect_all_elements(node: *mut _xmlNode, nodes: &mut Vec<*mut _xmlNode>) {
1363 unsafe {
1364 if node.is_null() {
1365 return;
1366 }
1367 if (*node).type_ == XML_ELEMENT_NODE as c_int {
1368 nodes.push(node);
1369 }
1370 let mut child = (*node).children;
1371 while !child.is_null() {
1372 collect_all_elements(child, nodes);
1373 child = (*child).next;
1374 }
1375 }
1376}
1377
1378unsafe fn collect_elements_by_name(
1384 node: *mut _xmlNode,
1385 name: &str,
1386 nodes: &mut Vec<*mut _xmlNode>,
1387) {
1388 unsafe {
1389 if node.is_null() {
1390 return;
1391 }
1392 if (*node).type_ == XML_ELEMENT_NODE as c_int {
1393 let qname = get_node_qname(node);
1394 let local = get_local_name(node);
1395 if qname == name || local == name {
1396 nodes.push(node);
1397 }
1398 }
1399 let mut child = (*node).children;
1400 while !child.is_null() {
1401 collect_elements_by_name(child, name, nodes);
1402 child = (*child).next;
1403 }
1404 }
1405}
1406
1407pub fn schematron_parse_schema(xml_doc: &str) -> Result<SchematronSchema, String> {
1415 schematron_parse(xml_doc)
1416}
1417
1418pub unsafe fn schematron_parse_schema_doc(doc: *mut _xmlDoc) -> Result<SchematronSchema, String> {
1424 schematron_parse_doc(doc)
1425}
1426
1427pub unsafe fn schematron_validate_doc_schema(
1435 schema: &SchematronSchema,
1436 doc: *mut _xmlDoc,
1437 ctxt: &mut SchematronValidCtxt,
1438) -> bool {
1439 schematron_validate_doc(schema, doc, ctxt)
1440}
1441
1442#[no_mangle]
1461pub unsafe extern "C" fn xmlSchematronNewParserCtxt(url: *const c_char) -> *mut c_void {
1462 if url.is_null() {
1463 let ctxt = allocator::xmlMallocZero(std::mem::size_of::<SchematronSchema>() as usize);
1464 return ctxt;
1465 }
1466
1467 let url_str = unsafe {
1468 let mut len = 0;
1469 while *url.add(len) != 0 {
1470 len += 1;
1471 }
1472 let slice = std::slice::from_raw_parts(url as *const u8, len);
1473 String::from_utf8_lossy(slice).to_string()
1474 };
1475
1476 if !url_str.is_empty() {
1478 let url_c = std::ffi::CString::new(url_str.clone()).ok();
1479 if let Some(c) = url_c {
1480 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
1481 if !doc.is_null() {
1482 let result = schematron_parse_doc(doc);
1483 crate::abi::exports_xml2::xmlFreeDoc(doc);
1484 if let Ok(schema) = result {
1485 let schema_box = Box::new(schema);
1486 return Box::into_raw(schema_box) as *mut c_void;
1487 }
1488 }
1489 }
1490 }
1491
1492 let ctxt = allocator::xmlMallocZero(std::mem::size_of::<SchematronSchema>() as usize);
1494 ctxt
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 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
1687#[cfg(test)]
1692mod tests {
1693 use super::*;
1694
1695 #[test]
1698 fn test_parse_simple_schema() {
1699 let schema_xml = r#"<?xml version="1.0"?>
1700<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1701 <pattern id="P1">
1702 <rule context="root">
1703 <assert test="count(*) > 0">Root must have children</assert>
1704 </rule>
1705 </pattern>
1706</schema>"#;
1707
1708 let result = schematron_parse(schema_xml);
1709 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1710 let schema = result.unwrap();
1711 assert_eq!(schema.pattern_order.len(), 1);
1712 assert_eq!(schema.rules.len(), 1);
1713 }
1714
1715 #[test]
1716 fn test_parse_with_ns() {
1717 let schema_xml = r#"<?xml version="1.0"?>
1718<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1719 <ns prefix="doc" uri="http://example.com/doc"/>
1720 <pattern id="P1">
1721 <rule context="doc:entry">
1722 <assert test="doc:title">Entry must have a title</assert>
1723 </rule>
1724 </pattern>
1725</schema>"#;
1726
1727 let result = schematron_parse(schema_xml);
1728 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1729 let schema = result.unwrap();
1730 assert!(schema.ns.contains_key("doc"));
1731 assert_eq!(schema.ns.get("doc").unwrap(), "http://example.com/doc");
1732 }
1733
1734 #[test]
1735 fn test_parse_with_phases() {
1736 let schema_xml = r#"<?xml version="1.0"?>
1737<schema xmlns="http://purl.oclc.org/dsdl/schematron" defaultPhase="phaseA">
1738 <phase id="phaseA">
1739 <active pattern="P1"/>
1740 </phase>
1741 <phase id="phaseB">
1742 <active pattern="P2"/>
1743 </phase>
1744 <pattern id="P1">
1745 <rule context="root">
1746 <assert test="true()">Always passes</assert>
1747 </rule>
1748 </pattern>
1749 <pattern id="P2">
1750 <rule context="root">
1751 <assert test="false()">Always fails</assert>
1752 </rule>
1753 </pattern>
1754</schema>"#;
1755
1756 let result = schematron_parse(schema_xml);
1757 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1758 let schema = result.unwrap();
1759 assert_eq!(schema.phases.len(), 2);
1760 assert!(schema.phases.contains_key("phaseA"));
1761 assert!(schema.phases.contains_key("phaseB"));
1762 assert_eq!(schema.default_phase.as_deref(), Some("phaseA"));
1763 }
1764
1765 #[test]
1766 fn test_parse_report_pattern() {
1767 let schema_xml = r#"<?xml version="1.0"?>
1768<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1769 <pattern id="P1">
1770 <rule context="root">
1771 <report test="@deprecated">Element is deprecated</report>
1772 </rule>
1773 </pattern>
1774</schema>"#;
1775
1776 let result = schematron_parse(schema_xml);
1777 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1778 let schema = result.unwrap();
1779 let rule = schema.rules.values().next().unwrap();
1780 assert_eq!(rule.patterns.len(), 1);
1781 assert_eq!(rule.patterns[0].pattern_type, SchematronPatternType::Report);
1782 assert_eq!(rule.patterns[0].test, "@deprecated");
1783 }
1784
1785 #[test]
1786 fn test_parse_abstract_rule() {
1787 let schema_xml = r#"<?xml version="1.0"?>
1788<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1789 <pattern id="P1">
1790 <rule id="abstractRule" abstract="true" context="*">
1791 <assert test="true()">Abstract assertion</assert>
1792 </rule>
1793 <rule id="concreteRule" context="root">
1794 <extends rule="abstractRule"/>
1795 <assert test="count(*) > 0">Concrete assertion</assert>
1796 </rule>
1797 </pattern>
1798</schema>"#;
1799
1800 let result = schematron_parse(schema_xml);
1801 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1802 let schema = result.unwrap();
1803 assert!(schema.rules.contains_key("abstractRule"));
1804 assert!(schema.rules.contains_key("concreteRule"));
1805 let abstract_rule = &schema.rules["abstractRule"];
1806 assert!(abstract_rule.abstract_);
1807 let concrete_rule = &schema.rules["concreteRule"];
1808 assert!(!concrete_rule.abstract_);
1809 assert_eq!(concrete_rule.extends.len(), 1);
1810 assert_eq!(concrete_rule.extends[0], "abstractRule");
1811 }
1812
1813 #[test]
1814 fn test_parse_with_diagnostics() {
1815 let schema_xml = r#"<?xml version="1.0"?>
1816<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1817 <diagnostics>
1818 <diagnostic id="diag1">This is a diagnostic message</diagnostic>
1819 </diagnostics>
1820 <pattern id="P1">
1821 <rule context="root">
1822 <assert test="true()" diagnostics="diag1">Assertion with diagnostic</assert>
1823 </rule>
1824 </pattern>
1825</schema>"#;
1826
1827 let result = schematron_parse(schema_xml);
1828 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1829 let schema = result.unwrap();
1830 assert!(schema.diagnostics.contains_key("diag1"));
1831 assert_eq!(
1832 schema.diagnostics["diag1"].text,
1833 "This is a diagnostic message"
1834 );
1835 }
1836
1837 #[test]
1838 fn test_parse_with_attributes() {
1839 let schema_xml = r#"<?xml version="1.0"?>
1840<schema xmlns="http://purl.oclc.org/dsdl/schematron" title="Test Schema">
1841 <pattern id="P1">
1842 <rule context="root">
1843 <assert test="true()" flag="warn" role="error" id="a1" icon="info" see="http://example.com">
1844 Test message
1845 </assert>
1846 </rule>
1847 </pattern>
1848</schema>"#;
1849
1850 let result = schematron_parse(schema_xml);
1851 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1852 let schema = result.unwrap();
1853 assert_eq!(schema.title.as_deref(), Some("Test Schema"));
1854 let rule = schema.rules.values().next().unwrap();
1855 let pat = &rule.patterns[0];
1856 assert_eq!(pat.flag.as_deref(), Some("warn"));
1857 assert_eq!(pat.role.as_deref(), Some("error"));
1858 assert_eq!(pat.id.as_deref(), Some("a1"));
1859 assert_eq!(pat.icon.as_deref(), Some("info"));
1860 assert_eq!(pat.see.as_deref(), Some("http://example.com"));
1861 }
1862
1863 #[test]
1864 fn test_parse_empty_schema() {
1865 let schema_xml = r#"<?xml version="1.0"?>
1866<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1867</schema>"#;
1868
1869 let result = schematron_parse(schema_xml);
1870 assert!(result.is_ok(), "Failed to parse empty schema");
1871 let schema = result.unwrap();
1872 assert!(schema.rules.is_empty());
1873 assert!(schema.phases.is_empty());
1874 }
1875
1876 #[test]
1877 fn test_parse_no_assertions() {
1878 let schema_xml = r#"<?xml version="1.0"?>
1879<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1880 <pattern id="P1">
1881 <rule context="root">
1882 </rule>
1883 </pattern>
1884</schema>"#;
1885
1886 let result = schematron_parse(schema_xml);
1887 assert!(result.is_ok(), "Failed to parse schema with no assertions");
1888 let schema = result.unwrap();
1889 let rule = schema.rules.values().next().unwrap();
1890 assert!(rule.patterns.is_empty());
1891 }
1892
1893 #[test]
1894 fn test_parse_invalid_root_element() {
1895 let schema_xml = r#"<?xml version="1.0"?>
1896<not-schema xmlns="http://purl.oclc.org/dsdl/schematron">
1897</not-schema>"#;
1898
1899 let result = schematron_parse(schema_xml);
1900 assert!(result.is_err(), "Should fail with wrong root element");
1901 assert!(
1902 result.err().unwrap().contains("Expected '<schema>'"),
1903 "Error should mention expected schema element"
1904 );
1905 }
1906
1907 #[test]
1908 fn test_parse_empty_document_fails() {
1909 let result = schematron_parse("");
1910 assert!(result.is_err());
1911 }
1912
1913 #[test]
1914 fn test_parse_invalid_xml_fails() {
1915 let result = schematron_parse("not valid xml <<<");
1916 assert!(result.is_err());
1917 }
1918
1919 #[test]
1920 fn test_parse_schema_with_let_and_param() {
1921 let schema_xml = r#"<?xml version="1.0"?>
1922<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1923 <pattern id="P1">
1924 <rule context="root">
1925 <let name="x" value="42"/>
1926 <param name="debug" value="true"/>
1927 <assert test="true()">Test with let and param</assert>
1928 </rule>
1929 </pattern>
1930</schema>"#;
1931
1932 let result = schematron_parse(schema_xml);
1933 assert!(
1934 result.is_ok(),
1935 "Failed to parse schema with let/param: {:?}",
1936 result.err()
1937 );
1938 let schema = result.unwrap();
1939 assert_eq!(schema.rules.len(), 1);
1940 }
1941
1942 #[test]
1943 fn test_parse_schema_with_documentation() {
1944 let schema_xml = r#"<?xml version="1.0"?>
1945<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1946 <p>This is documentation</p>
1947 <caption>Table caption</caption>
1948 <pattern id="P1">
1949 <p>Pattern documentation</p>
1950 <rule context="root">
1951 <p>Rule documentation</p>
1952 <assert test="true()">Real assertion</assert>
1953 </rule>
1954 </pattern>
1955</schema>"#;
1956
1957 let result = schematron_parse(schema_xml);
1958 assert!(result.is_ok(), "Failed to parse schema with documentation");
1959 let schema = result.unwrap();
1960 assert_eq!(schema.rules.len(), 1);
1961 }
1962
1963 #[test]
1966 fn test_validate_assert_pass() {
1967 let schema_xml = r#"<?xml version="1.0"?>
1968<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1969 <pattern id="P1">
1970 <rule context="root">
1971 <assert test="true()">Always passes</assert>
1972 </rule>
1973 </pattern>
1974</schema>"#;
1975
1976 let doc_xml = r#"<?xml version="1.0"?>
1977<root>Hello</root>"#;
1978
1979 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
1980
1981 let doc = unsafe {
1982 crate::abi::exports_xml2::xmlReadMemory(
1983 doc_xml.as_ptr() as *const c_char,
1984 doc_xml.len() as c_int,
1985 b"test.xml\0".as_ptr() as *const c_char,
1986 ptr::null(),
1987 0,
1988 )
1989 };
1990 assert!(!doc.is_null(), "Failed to parse document");
1991
1992 let mut ctxt = SchematronValidCtxt::new();
1993 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
1994 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
1995
1996 assert!(valid, "Validation failed: {:?}", ctxt.errors);
1997 }
1998
1999 #[test]
2000 fn test_validate_assert_fail() {
2001 let schema_xml = r#"<?xml version="1.0"?>
2002<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2003 <pattern id="P1">
2004 <rule context="root">
2005 <assert test="false()">Always fails</assert>
2006 </rule>
2007 </pattern>
2008</schema>"#;
2009
2010 let doc_xml = r#"<?xml version="1.0"?>
2011<root>Hello</root>"#;
2012
2013 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2014
2015 let doc = unsafe {
2016 crate::abi::exports_xml2::xmlReadMemory(
2017 doc_xml.as_ptr() as *const c_char,
2018 doc_xml.len() as c_int,
2019 b"test.xml\0".as_ptr() as *const c_char,
2020 ptr::null(),
2021 0,
2022 )
2023 };
2024 assert!(!doc.is_null());
2025
2026 let mut ctxt = SchematronValidCtxt::new();
2027 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2028 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2029
2030 assert!(
2031 !valid,
2032 "Validation should have failed, errors: {:?}",
2033 ctxt.errors
2034 );
2035 assert!(ctxt.nb_errors > 0);
2036 }
2037
2038 #[test]
2039 fn test_validate_report_pass() {
2040 let schema_xml = r#"<?xml version="1.0"?>
2041<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2042 <pattern id="P1">
2043 <rule context="root">
2044 <report test="false()">Report should not trigger</report>
2045 </rule>
2046 </pattern>
2047</schema>"#;
2048
2049 let doc_xml = r#"<?xml version="1.0"?>
2050<root>Hello</root>"#;
2051
2052 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2053
2054 let doc = unsafe {
2055 crate::abi::exports_xml2::xmlReadMemory(
2056 doc_xml.as_ptr() as *const c_char,
2057 doc_xml.len() as c_int,
2058 b"test.xml\0".as_ptr() as *const c_char,
2059 ptr::null(),
2060 0,
2061 )
2062 };
2063 assert!(!doc.is_null());
2064
2065 let mut ctxt = SchematronValidCtxt::new();
2066 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2067 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2068
2069 assert!(valid, "Report should not trigger: {:?}", ctxt.errors);
2070 }
2071
2072 #[test]
2073 fn test_validate_report_fail() {
2074 let schema_xml = r#"<?xml version="1.0"?>
2075<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2076 <pattern id="P1">
2077 <rule context="root">
2078 <report test="true()">Report should trigger</report>
2079 </rule>
2080 </pattern>
2081</schema>"#;
2082
2083 let doc_xml = r#"<?xml version="1.0"?>
2084<root>Hello</root>"#;
2085
2086 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2087
2088 let doc = unsafe {
2089 crate::abi::exports_xml2::xmlReadMemory(
2090 doc_xml.as_ptr() as *const c_char,
2091 doc_xml.len() as c_int,
2092 b"test.xml\0".as_ptr() as *const c_char,
2093 ptr::null(),
2094 0,
2095 )
2096 };
2097 assert!(!doc.is_null());
2098
2099 let mut ctxt = SchematronValidCtxt::new();
2100 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2101 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2102
2103 assert!(!valid, "Report should have triggered");
2104 assert!(ctxt.nb_errors > 0);
2105 }
2106
2107 #[test]
2108 fn test_validate_context_matching() {
2109 let schema_xml = r#"<?xml version="1.0"?>
2110<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2111 <pattern id="P1">
2112 <rule context="child">
2113 <assert test="true()">Child matches</assert>
2114 </rule>
2115 </pattern>
2116</schema>"#;
2117
2118 let doc_xml = r#"<?xml version="1.0"?>
2119<root>
2120 <child>A</child>
2121 <child>B</child>
2122</root>"#;
2123
2124 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2125
2126 let doc = unsafe {
2127 crate::abi::exports_xml2::xmlReadMemory(
2128 doc_xml.as_ptr() as *const c_char,
2129 doc_xml.len() as c_int,
2130 b"test.xml\0".as_ptr() as *const c_char,
2131 ptr::null(),
2132 0,
2133 )
2134 };
2135 assert!(!doc.is_null());
2136
2137 let mut ctxt = SchematronValidCtxt::new();
2138 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2139 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2140
2141 assert!(valid, "Context matching failed: {:?}", ctxt.errors);
2142 }
2143
2144 #[test]
2145 fn test_validate_multiple_rules() {
2146 let schema_xml = r#"<?xml version="1.0"?>
2147<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2148 <pattern id="P1">
2149 <rule context="root">
2150 <assert test="true()">Root passes</assert>
2151 </rule>
2152 </pattern>
2153 <pattern id="P2">
2154 <rule context="child">
2155 <assert test="true()">Child passes</assert>
2156 </rule>
2157 </pattern>
2158</schema>"#;
2159
2160 let doc_xml = r#"<?xml version="1.0"?>
2161<root>
2162 <child>Content</child>
2163</root>"#;
2164
2165 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2166
2167 let doc = unsafe {
2168 crate::abi::exports_xml2::xmlReadMemory(
2169 doc_xml.as_ptr() as *const c_char,
2170 doc_xml.len() as c_int,
2171 b"test.xml\0".as_ptr() as *const c_char,
2172 ptr::null(),
2173 0,
2174 )
2175 };
2176 assert!(!doc.is_null());
2177
2178 let mut ctxt = SchematronValidCtxt::new();
2179 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2180 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2181
2182 assert!(valid, "Multiple rules failed: {:?}", ctxt.errors);
2183 }
2184
2185 #[test]
2186 fn test_validate_with_phase_filtering() {
2187 let schema_xml = r#"<?xml version="1.0"?>
2188<schema xmlns="http://purl.oclc.org/dsdl/schematron" defaultPhase="phaseA">
2189 <phase id="phaseA">
2190 <active pattern="P1"/>
2191 </phase>
2192 <phase id="phaseB">
2193 <active pattern="P2"/>
2194 </phase>
2195 <pattern id="P1">
2196 <rule context="root">
2197 <assert test="true()">Always passes</assert>
2198 </rule>
2199 </pattern>
2200 <pattern id="P2">
2201 <rule context="root">
2202 <assert test="false()">Always fails</assert>
2203 </rule>
2204 </pattern>
2205</schema>"#;
2206
2207 let doc_xml = r#"<?xml version="1.0"?>
2208<root>Hello</root>"#;
2209
2210 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2211
2212 let doc = unsafe {
2213 crate::abi::exports_xml2::xmlReadMemory(
2214 doc_xml.as_ptr() as *const c_char,
2215 doc_xml.len() as c_int,
2216 b"test.xml\0".as_ptr() as *const c_char,
2217 ptr::null(),
2218 0,
2219 )
2220 };
2221 assert!(!doc.is_null());
2222
2223 let mut ctxt = SchematronValidCtxt::new();
2225 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2226 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2227
2228 assert!(
2229 valid,
2230 "Phase filtering should make validation pass: {:?}",
2231 ctxt.errors
2232 );
2233 }
2234
2235 #[test]
2236 fn test_validate_no_rules() {
2237 let schema_xml = r#"<?xml version="1.0"?>
2238<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2239</schema>"#;
2240
2241 let doc_xml = r#"<?xml version="1.0"?>
2242<root>Hello</root>"#;
2243
2244 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2245
2246 let doc = unsafe {
2247 crate::abi::exports_xml2::xmlReadMemory(
2248 doc_xml.as_ptr() as *const c_char,
2249 doc_xml.len() as c_int,
2250 b"test.xml\0".as_ptr() as *const c_char,
2251 ptr::null(),
2252 0,
2253 )
2254 };
2255 assert!(!doc.is_null());
2256
2257 let mut ctxt = SchematronValidCtxt::new();
2258 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2259 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2260
2261 assert!(valid, "Empty schema should pass validation");
2262 }
2263
2264 #[test]
2265 fn test_validate_extends_resolution() {
2266 let schema_xml = r#"<?xml version="1.0"?>
2267<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2268 <pattern id="P1">
2269 <rule id="base" abstract="true" context="*">
2270 <assert test="true()">Base assertion</assert>
2271 </rule>
2272 <rule id="derived" context="root">
2273 <extends rule="base"/>
2274 <assert test="true()">Derived assertion</assert>
2275 </rule>
2276 </pattern>
2277</schema>"#;
2278
2279 let doc_xml = r#"<?xml version="1.0"?>
2280<root>Hello</root>"#;
2281
2282 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2283
2284 let resolved = schema.resolve_rule("derived");
2286 assert!(resolved.is_some());
2287 let resolved = resolved.unwrap();
2288 assert_eq!(
2290 resolved.patterns.len(),
2291 2,
2292 "Should have inherited the base pattern"
2293 );
2294
2295 let doc = unsafe {
2296 crate::abi::exports_xml2::xmlReadMemory(
2297 doc_xml.as_ptr() as *const c_char,
2298 doc_xml.len() as c_int,
2299 b"test.xml\0".as_ptr() as *const c_char,
2300 ptr::null(),
2301 0,
2302 )
2303 };
2304 assert!(!doc.is_null());
2305
2306 let mut ctxt = SchematronValidCtxt::new();
2307 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2308 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2309
2310 assert!(valid, "Extends resolution failed: {:?}", ctxt.errors);
2311 }
2312
2313 #[test]
2314 fn test_validate_assert_with_flag() {
2315 let schema_xml = r#"<?xml version="1.0"?>
2316<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2317 <pattern id="P1">
2318 <rule context="root">
2319 <assert test="false()" flag="warn">Warning message</assert>
2320 </rule>
2321 </pattern>
2322</schema>"#;
2323
2324 let doc_xml = r#"<?xml version="1.0"?>
2325<root>Hello</root>"#;
2326
2327 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2328
2329 let doc = unsafe {
2330 crate::abi::exports_xml2::xmlReadMemory(
2331 doc_xml.as_ptr() as *const c_char,
2332 doc_xml.len() as c_int,
2333 b"test.xml\0".as_ptr() as *const c_char,
2334 ptr::null(),
2335 0,
2336 )
2337 };
2338 assert!(!doc.is_null());
2339
2340 let mut ctxt = SchematronValidCtxt::new();
2341 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2342 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2343
2344 assert!(!valid);
2345 assert!(ctxt.nb_errors > 0);
2346 assert!(
2348 ctxt.errors[0].contains("[warn]"),
2349 "Error should include flag"
2350 );
2351 }
2352
2353 #[test]
2356 fn test_c_abi_new_free_parser_ctxt() {
2357 let ctxt = unsafe { xmlSchematronNewParserCtxt(ptr::null()) };
2358 assert!(!ctxt.is_null());
2359 unsafe { xmlSchematronFreeParserCtxt(ctxt) };
2360 }
2362
2363 #[test]
2364 fn test_c_abi_new_free_valid_ctxt() {
2365 let schema = unsafe { xmlSchematronNewParserCtxt(ptr::null()) };
2366 assert!(!schema.is_null());
2367
2368 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema) };
2369 assert!(!valid_ctxt.is_null());
2370
2371 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2372 unsafe { xmlSchematronFreeParserCtxt(schema) };
2373 }
2375
2376 #[test]
2377 fn test_c_abi_parse_free() {
2378 let schema_xml = r#"<?xml version="1.0"?>
2379<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2380 <pattern id="P1">
2381 <rule context="root">
2382 <assert test="true()">Test</assert>
2383 </rule>
2384 </pattern>
2385</schema>"#;
2386
2387 let ctxt = unsafe {
2388 xmlSchematronNewMemParserCtxt(
2389 schema_xml.as_ptr() as *const c_char,
2390 schema_xml.len() as c_int,
2391 )
2392 };
2393 assert!(!ctxt.is_null());
2394
2395 let schema = unsafe { xmlSchematronParse(ctxt) };
2396 assert!(!schema.is_null());
2397
2398 unsafe { xmlSchematronFree(schema) };
2399 }
2401
2402 #[test]
2403 fn test_c_abi_validate_doc() {
2404 let schema_xml = r#"<?xml version="1.0"?>
2405<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2406 <pattern id="P1">
2407 <rule context="root">
2408 <assert test="true()">Always passes</assert>
2409 </rule>
2410 </pattern>
2411</schema>"#;
2412
2413 let doc_xml = r#"<?xml version="1.0"?>
2414<root>Hello</root>"#;
2415
2416 let ctxt = unsafe {
2417 xmlSchematronNewMemParserCtxt(
2418 schema_xml.as_ptr() as *const c_char,
2419 schema_xml.len() as c_int,
2420 )
2421 };
2422 assert!(!ctxt.is_null());
2423
2424 let schema = unsafe { xmlSchematronParse(ctxt) };
2425 assert!(!schema.is_null());
2426
2427 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema) };
2428 assert!(!valid_ctxt.is_null());
2429
2430 let doc = unsafe {
2431 crate::abi::exports_xml2::xmlReadMemory(
2432 doc_xml.as_ptr() as *const c_char,
2433 doc_xml.len() as c_int,
2434 b"test.xml\0".as_ptr() as *const c_char,
2435 ptr::null(),
2436 0,
2437 )
2438 };
2439 assert!(!doc.is_null());
2440
2441 let result = unsafe { xmlSchematronValidateDoc(valid_ctxt, doc) };
2442 assert_eq!(result, 0, "Validation should pass (return 0)");
2443
2444 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2445 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2446 unsafe { xmlSchematronFree(schema) };
2447 }
2448
2449 #[test]
2450 fn test_c_abi_validate_fail() {
2451 let schema_xml = r#"<?xml version="1.0"?>
2452<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2453 <pattern id="P1">
2454 <rule context="root">
2455 <assert test="false()">Always fails</assert>
2456 </rule>
2457 </pattern>
2458</schema>"#;
2459
2460 let doc_xml = r#"<?xml version="1.0"?>
2461<root>Hello</root>"#;
2462
2463 let ctxt = unsafe {
2464 xmlSchematronNewMemParserCtxt(
2465 schema_xml.as_ptr() as *const c_char,
2466 schema_xml.len() as c_int,
2467 )
2468 };
2469 assert!(!ctxt.is_null());
2470
2471 let schema = unsafe { xmlSchematronParse(ctxt) };
2472 assert!(!schema.is_null());
2473
2474 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema) };
2475 assert!(!valid_ctxt.is_null());
2476
2477 let doc = unsafe {
2478 crate::abi::exports_xml2::xmlReadMemory(
2479 doc_xml.as_ptr() as *const c_char,
2480 doc_xml.len() as c_int,
2481 b"test.xml\0".as_ptr() as *const c_char,
2482 ptr::null(),
2483 0,
2484 )
2485 };
2486 assert!(!doc.is_null());
2487
2488 let result = unsafe { xmlSchematronValidateDoc(valid_ctxt, doc) };
2489 assert!(result > 0, "Validation should fail (return > 0)");
2490
2491 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2492 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2493 unsafe { xmlSchematronFree(schema) };
2494 }
2495
2496 #[test]
2497 fn test_c_abi_null_handling() {
2498 unsafe { xmlSchematronFree(ptr::null_mut()) };
2500 unsafe { xmlSchematronFreeParserCtxt(ptr::null_mut()) };
2501 unsafe { xmlSchematronFreeValidCtxt(ptr::null_mut()) };
2502
2503 let result = unsafe { xmlSchematronParse(ptr::null_mut()) };
2505 assert!(result.is_null());
2506
2507 let result = unsafe { xmlSchematronValidateDoc(ptr::null_mut(), ptr::null_mut()) };
2509 assert_eq!(result, -1);
2510 }
2511
2512 #[test]
2515 fn test_validate_null_doc() {
2516 let schema = SchematronSchema::new();
2517 let mut ctxt = SchematronValidCtxt::new();
2518 let valid = unsafe { schematron_validate_doc(&schema, ptr::null_mut(), &mut ctxt) };
2519 assert!(!valid);
2520 assert!(ctxt.nb_errors > 0);
2521 }
2522
2523 #[test]
2524 fn test_active_rules_default_phase() {
2525 let mut schema = SchematronSchema::new();
2526
2527 let rule = SchematronRule::new("root".to_string());
2528 schema.rules.insert("r1".to_string(), rule);
2529
2530 schema
2531 .pattern_groups
2532 .insert("p1".to_string(), vec!["r1".to_string()]);
2533 schema.pattern_order.push("p1".to_string());
2534
2535 let rules = schema.active_rules(None);
2536 assert_eq!(rules.len(), 1);
2537 }
2538
2539 #[test]
2540 fn test_active_rules_unknown_phase() {
2541 let mut schema = SchematronSchema::new();
2542
2543 let rule = SchematronRule::new("root".to_string());
2544 schema.rules.insert("r1".to_string(), rule);
2545
2546 schema
2547 .pattern_groups
2548 .insert("p1".to_string(), vec!["r1".to_string()]);
2549 schema.pattern_order.push("p1".to_string());
2550
2551 let rules = schema.active_rules(Some("nonexistent"));
2552 assert_eq!(rules.len(), 1, "Unknown phase should use all patterns");
2553 }
2554
2555 #[test]
2556 fn test_parse_schema_with_span_and_emph() {
2557 let schema_xml = r#"<?xml version="1.0"?>
2558<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2559 <pattern id="P1">
2560 <rule context="root">
2561 <assert test="true()">Message with <span class="x">inline</span> and <emph>emphasis</emph></assert>
2562 </rule>
2563 </pattern>
2564</schema>"#;
2565
2566 let result = schematron_parse(schema_xml);
2567 assert!(
2568 result.is_ok(),
2569 "Failed to parse schema with span/emph: {:?}",
2570 result.err()
2571 );
2572 let schema = result.unwrap();
2573 let rule = schema.rules.values().next().unwrap();
2574 let pat = &rule.patterns[0];
2575 assert!(
2577 pat.text.contains("inline"),
2578 "Text should include span content"
2579 );
2580 assert!(
2581 pat.text.contains("emphasis"),
2582 "Text should include emph content"
2583 );
2584 }
2585
2586 #[test]
2587 fn test_schematron_pattern_new_assert() {
2588 let pat = SchematronPattern::new(
2589 SchematronPatternType::Assert,
2590 "true()".to_string(),
2591 "Test message".to_string(),
2592 );
2593 assert_eq!(pat.pattern_type, SchematronPatternType::Assert);
2594 assert_eq!(pat.test, "true()");
2595 assert_eq!(pat.text, "Test message");
2596 assert!(pat.compiled_test.is_some());
2597 }
2598
2599 #[test]
2600 fn test_schematron_pattern_new_report() {
2601 let pat = SchematronPattern::new(
2602 SchematronPatternType::Report,
2603 "false()".to_string(),
2604 "Report message".to_string(),
2605 );
2606 assert_eq!(pat.pattern_type, SchematronPatternType::Report);
2607 assert!(pat.compiled_test.is_some());
2608 }
2609
2610 #[test]
2611 fn test_schematron_rule_new() {
2612 let rule = SchematronRule::new("root".to_string());
2613 assert_eq!(rule.context, "root");
2614 assert!(rule.patterns.is_empty());
2615 assert!(!rule.abstract_);
2616 }
2617
2618 #[test]
2619 fn test_schematron_schema_new() {
2620 let schema = SchematronSchema::new();
2621 assert_eq!(schema.query_binding, "xslt");
2622 assert!(schema.rules.is_empty());
2623 assert!(schema.phases.is_empty());
2624 assert!(schema.ns.is_empty());
2625 }
2626
2627 #[test]
2628 fn test_schematron_valid_ctxt_new() {
2629 let ctxt = SchematronValidCtxt::new();
2630 assert!(ctxt.errors.is_empty());
2631 assert_eq!(ctxt.nb_errors, 0);
2632 assert!(ctxt.active_phase.is_none());
2633 }
2634
2635 #[test]
2636 fn test_validate_assert_with_child_count() {
2637 let schema_xml = r#"<?xml version="1.0"?>
2638<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2639 <pattern id="P1">
2640 <rule context="root">
2641 <assert test="count(*) > 0">Root must have at least one child element</assert>
2642 </rule>
2643 </pattern>
2644</schema>"#;
2645
2646 let doc_xml = r#"<?xml version="1.0"?>
2647<root>
2648 <child>Content</child>
2649</root>"#;
2650
2651 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2652
2653 let doc = unsafe {
2654 crate::abi::exports_xml2::xmlReadMemory(
2655 doc_xml.as_ptr() as *const c_char,
2656 doc_xml.len() as c_int,
2657 b"test.xml\0".as_ptr() as *const c_char,
2658 ptr::null(),
2659 0,
2660 )
2661 };
2662 assert!(!doc.is_null());
2663
2664 let mut ctxt = SchematronValidCtxt::new();
2665 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2666 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2667
2668 assert!(valid, "Child count check failed: {:?}", ctxt.errors);
2669 }
2670}