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 return Box::into_raw(Box::new(SchematronSchema::new())) as *mut c_void;
1469 }
1470
1471 let url_str = unsafe {
1472 let mut len = 0;
1473 while *url.add(len) != 0 {
1474 len += 1;
1475 }
1476 let slice = std::slice::from_raw_parts(url as *const u8, len);
1477 String::from_utf8_lossy(slice).to_string()
1478 };
1479
1480 if !url_str.is_empty() {
1482 let url_c = std::ffi::CString::new(url_str.clone()).ok();
1483 if let Some(c) = url_c {
1484 let doc = crate::abi::exports_xml2::xmlParseFile(c.as_ptr());
1485 if !doc.is_null() {
1486 let result = schematron_parse_doc(doc);
1487 crate::abi::exports_xml2::xmlFreeDoc(doc);
1488 if let Ok(schema) = result {
1489 let schema_box = Box::new(schema);
1490 return Box::into_raw(schema_box) as *mut c_void;
1491 }
1492 }
1493 }
1494 }
1495
1496 Box::into_raw(Box::new(SchematronSchema::new())) as *mut c_void
1498}
1499
1500#[no_mangle]
1512pub unsafe extern "C" fn xmlSchematronNewMemParserCtxt(
1513 buffer: *const c_char,
1514 size: c_int,
1515) -> *mut c_void {
1516 if buffer.is_null() || size <= 0 {
1517 return ptr::null_mut();
1518 }
1519
1520 let buf_slice = unsafe { std::slice::from_raw_parts(buffer as *const u8, size as usize) };
1522 let xml_str = String::from_utf8_lossy(buf_slice).to_string();
1523
1524 match schematron_parse(&xml_str) {
1525 Ok(schema) => {
1526 let schema_box = Box::new(schema);
1527 Box::into_raw(schema_box) as *mut c_void
1528 }
1529 Err(_) => ptr::null_mut(),
1530 }
1531}
1532
1533#[no_mangle]
1545pub unsafe extern "C" fn xmlSchematronParse(ctxt: *mut c_void) -> *mut c_void {
1546 if ctxt.is_null() {
1547 return ptr::null_mut();
1548 }
1549
1550 ctxt
1553}
1554
1555#[no_mangle]
1567pub unsafe extern "C" fn xmlSchematronFree(schema: *mut c_void) {
1568 if schema.is_null() {
1569 return;
1570 }
1571 unsafe {
1573 let _ = Box::from_raw(schema as *mut SchematronSchema);
1574 }
1575}
1576
1577#[no_mangle]
1589pub unsafe extern "C" fn xmlSchematronFreeParserCtxt(ctxt: *mut c_void) {
1590 if ctxt.is_null() {
1591 return;
1592 }
1593 unsafe {
1595 let _ = Box::from_raw(ctxt as *mut SchematronSchema);
1596 }
1597}
1598
1599#[no_mangle]
1611pub unsafe extern "C" fn xmlSchematronNewValidCtxt(schema: *mut c_void) -> *mut c_void {
1612 let mut ctxt = SchematronValidCtxt::new();
1613
1614 if !schema.is_null() {
1615 unsafe {
1617 let schema_ref = &*(schema as *const SchematronSchema);
1618 ctxt.schema = Some(schema_ref.clone());
1619 }
1620 }
1621
1622 let boxed = Box::new(ctxt);
1623 Box::into_raw(boxed) as *mut c_void
1624}
1625
1626#[no_mangle]
1638pub unsafe extern "C" fn xmlSchematronFreeValidCtxt(ctxt: *mut c_void) {
1639 if ctxt.is_null() {
1640 return;
1641 }
1642 unsafe {
1644 let _ = Box::from_raw(ctxt as *mut SchematronValidCtxt);
1645 }
1646}
1647
1648#[no_mangle]
1663pub unsafe extern "C" fn xmlSchematronValidateDoc(ctxt: *mut c_void, doc: *mut _xmlDoc) -> c_int {
1664 if ctxt.is_null() || doc.is_null() {
1665 return -1;
1666 }
1667
1668 unsafe {
1669 let valid_ctxt = &mut *(ctxt as *mut SchematronValidCtxt);
1670 let schema = match &valid_ctxt.schema {
1671 Some(s) => s,
1672 None => return -1,
1673 };
1674
1675 let mut temp_ctxt = SchematronValidCtxt::new();
1676 temp_ctxt.active_phase = valid_ctxt.active_phase.clone();
1677
1678 let valid = schematron_validate_doc(schema, doc, &mut temp_ctxt);
1679
1680 if valid {
1681 0
1682 } else {
1683 valid_ctxt.errors = temp_ctxt.errors;
1684 valid_ctxt.nb_errors = temp_ctxt.nb_errors;
1685 temp_ctxt.nb_errors
1686 }
1687 }
1688}
1689
1690#[cfg(test)]
1695mod tests {
1696 use super::*;
1697
1698 #[test]
1701 fn test_parse_simple_schema() {
1702 let schema_xml = r#"<?xml version="1.0"?>
1703<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1704 <pattern id="P1">
1705 <rule context="root">
1706 <assert test="count(*) > 0">Root must have children</assert>
1707 </rule>
1708 </pattern>
1709</schema>"#;
1710
1711 let result = schematron_parse(schema_xml);
1712 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1713 let schema = result.unwrap();
1714 assert_eq!(schema.pattern_order.len(), 1);
1715 assert_eq!(schema.rules.len(), 1);
1716 }
1717
1718 #[test]
1719 fn test_parse_with_ns() {
1720 let schema_xml = r#"<?xml version="1.0"?>
1721<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1722 <ns prefix="doc" uri="http://example.com/doc"/>
1723 <pattern id="P1">
1724 <rule context="doc:entry">
1725 <assert test="doc:title">Entry must have a title</assert>
1726 </rule>
1727 </pattern>
1728</schema>"#;
1729
1730 let result = schematron_parse(schema_xml);
1731 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1732 let schema = result.unwrap();
1733 assert!(schema.ns.contains_key("doc"));
1734 assert_eq!(schema.ns.get("doc").unwrap(), "http://example.com/doc");
1735 }
1736
1737 #[test]
1738 fn test_parse_with_phases() {
1739 let schema_xml = r#"<?xml version="1.0"?>
1740<schema xmlns="http://purl.oclc.org/dsdl/schematron" defaultPhase="phaseA">
1741 <phase id="phaseA">
1742 <active pattern="P1"/>
1743 </phase>
1744 <phase id="phaseB">
1745 <active pattern="P2"/>
1746 </phase>
1747 <pattern id="P1">
1748 <rule context="root">
1749 <assert test="true()">Always passes</assert>
1750 </rule>
1751 </pattern>
1752 <pattern id="P2">
1753 <rule context="root">
1754 <assert test="false()">Always fails</assert>
1755 </rule>
1756 </pattern>
1757</schema>"#;
1758
1759 let result = schematron_parse(schema_xml);
1760 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1761 let schema = result.unwrap();
1762 assert_eq!(schema.phases.len(), 2);
1763 assert!(schema.phases.contains_key("phaseA"));
1764 assert!(schema.phases.contains_key("phaseB"));
1765 assert_eq!(schema.default_phase.as_deref(), Some("phaseA"));
1766 }
1767
1768 #[test]
1769 fn test_parse_report_pattern() {
1770 let schema_xml = r#"<?xml version="1.0"?>
1771<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1772 <pattern id="P1">
1773 <rule context="root">
1774 <report test="@deprecated">Element is deprecated</report>
1775 </rule>
1776 </pattern>
1777</schema>"#;
1778
1779 let result = schematron_parse(schema_xml);
1780 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1781 let schema = result.unwrap();
1782 let rule = schema.rules.values().next().unwrap();
1783 assert_eq!(rule.patterns.len(), 1);
1784 assert_eq!(rule.patterns[0].pattern_type, SchematronPatternType::Report);
1785 assert_eq!(rule.patterns[0].test, "@deprecated");
1786 }
1787
1788 #[test]
1789 fn test_parse_abstract_rule() {
1790 let schema_xml = r#"<?xml version="1.0"?>
1791<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1792 <pattern id="P1">
1793 <rule id="abstractRule" abstract="true" context="*">
1794 <assert test="true()">Abstract assertion</assert>
1795 </rule>
1796 <rule id="concreteRule" context="root">
1797 <extends rule="abstractRule"/>
1798 <assert test="count(*) > 0">Concrete assertion</assert>
1799 </rule>
1800 </pattern>
1801</schema>"#;
1802
1803 let result = schematron_parse(schema_xml);
1804 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1805 let schema = result.unwrap();
1806 assert!(schema.rules.contains_key("abstractRule"));
1807 assert!(schema.rules.contains_key("concreteRule"));
1808 let abstract_rule = &schema.rules["abstractRule"];
1809 assert!(abstract_rule.abstract_);
1810 let concrete_rule = &schema.rules["concreteRule"];
1811 assert!(!concrete_rule.abstract_);
1812 assert_eq!(concrete_rule.extends.len(), 1);
1813 assert_eq!(concrete_rule.extends[0], "abstractRule");
1814 }
1815
1816 #[test]
1817 fn test_parse_with_diagnostics() {
1818 let schema_xml = r#"<?xml version="1.0"?>
1819<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1820 <diagnostics>
1821 <diagnostic id="diag1">This is a diagnostic message</diagnostic>
1822 </diagnostics>
1823 <pattern id="P1">
1824 <rule context="root">
1825 <assert test="true()" diagnostics="diag1">Assertion with diagnostic</assert>
1826 </rule>
1827 </pattern>
1828</schema>"#;
1829
1830 let result = schematron_parse(schema_xml);
1831 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1832 let schema = result.unwrap();
1833 assert!(schema.diagnostics.contains_key("diag1"));
1834 assert_eq!(
1835 schema.diagnostics["diag1"].text,
1836 "This is a diagnostic message"
1837 );
1838 }
1839
1840 #[test]
1841 fn test_parse_with_attributes() {
1842 let schema_xml = r#"<?xml version="1.0"?>
1843<schema xmlns="http://purl.oclc.org/dsdl/schematron" title="Test Schema">
1844 <pattern id="P1">
1845 <rule context="root">
1846 <assert test="true()" flag="warn" role="error" id="a1" icon="info" see="http://example.com">
1847 Test message
1848 </assert>
1849 </rule>
1850 </pattern>
1851</schema>"#;
1852
1853 let result = schematron_parse(schema_xml);
1854 assert!(result.is_ok(), "Failed to parse schema: {:?}", result.err());
1855 let schema = result.unwrap();
1856 assert_eq!(schema.title.as_deref(), Some("Test Schema"));
1857 let rule = schema.rules.values().next().unwrap();
1858 let pat = &rule.patterns[0];
1859 assert_eq!(pat.flag.as_deref(), Some("warn"));
1860 assert_eq!(pat.role.as_deref(), Some("error"));
1861 assert_eq!(pat.id.as_deref(), Some("a1"));
1862 assert_eq!(pat.icon.as_deref(), Some("info"));
1863 assert_eq!(pat.see.as_deref(), Some("http://example.com"));
1864 }
1865
1866 #[test]
1867 fn test_parse_empty_schema() {
1868 let schema_xml = r#"<?xml version="1.0"?>
1869<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1870</schema>"#;
1871
1872 let result = schematron_parse(schema_xml);
1873 assert!(result.is_ok(), "Failed to parse empty schema");
1874 let schema = result.unwrap();
1875 assert!(schema.rules.is_empty());
1876 assert!(schema.phases.is_empty());
1877 }
1878
1879 #[test]
1880 fn test_parse_no_assertions() {
1881 let schema_xml = r#"<?xml version="1.0"?>
1882<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1883 <pattern id="P1">
1884 <rule context="root">
1885 </rule>
1886 </pattern>
1887</schema>"#;
1888
1889 let result = schematron_parse(schema_xml);
1890 assert!(result.is_ok(), "Failed to parse schema with no assertions");
1891 let schema = result.unwrap();
1892 let rule = schema.rules.values().next().unwrap();
1893 assert!(rule.patterns.is_empty());
1894 }
1895
1896 #[test]
1897 fn test_parse_invalid_root_element() {
1898 let schema_xml = r#"<?xml version="1.0"?>
1899<not-schema xmlns="http://purl.oclc.org/dsdl/schematron">
1900</not-schema>"#;
1901
1902 let result = schematron_parse(schema_xml);
1903 assert!(result.is_err(), "Should fail with wrong root element");
1904 assert!(
1905 result.err().unwrap().contains("Expected '<schema>'"),
1906 "Error should mention expected schema element"
1907 );
1908 }
1909
1910 #[test]
1911 fn test_parse_empty_document_fails() {
1912 let result = schematron_parse("");
1913 assert!(result.is_err());
1914 }
1915
1916 #[test]
1917 fn test_parse_invalid_xml_fails() {
1918 let result = schematron_parse("not valid xml <<<");
1919 assert!(result.is_err());
1920 }
1921
1922 #[test]
1923 fn test_parse_schema_with_let_and_param() {
1924 let schema_xml = r#"<?xml version="1.0"?>
1925<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1926 <pattern id="P1">
1927 <rule context="root">
1928 <let name="x" value="42"/>
1929 <param name="debug" value="true"/>
1930 <assert test="true()">Test with let and param</assert>
1931 </rule>
1932 </pattern>
1933</schema>"#;
1934
1935 let result = schematron_parse(schema_xml);
1936 assert!(
1937 result.is_ok(),
1938 "Failed to parse schema with let/param: {:?}",
1939 result.err()
1940 );
1941 let schema = result.unwrap();
1942 assert_eq!(schema.rules.len(), 1);
1943 }
1944
1945 #[test]
1946 fn test_parse_schema_with_documentation() {
1947 let schema_xml = r#"<?xml version="1.0"?>
1948<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1949 <p>This is documentation</p>
1950 <caption>Table caption</caption>
1951 <pattern id="P1">
1952 <p>Pattern documentation</p>
1953 <rule context="root">
1954 <p>Rule documentation</p>
1955 <assert test="true()">Real assertion</assert>
1956 </rule>
1957 </pattern>
1958</schema>"#;
1959
1960 let result = schematron_parse(schema_xml);
1961 assert!(result.is_ok(), "Failed to parse schema with documentation");
1962 let schema = result.unwrap();
1963 assert_eq!(schema.rules.len(), 1);
1964 }
1965
1966 #[test]
1969 fn test_validate_assert_pass() {
1970 let schema_xml = r#"<?xml version="1.0"?>
1971<schema xmlns="http://purl.oclc.org/dsdl/schematron">
1972 <pattern id="P1">
1973 <rule context="root">
1974 <assert test="true()">Always passes</assert>
1975 </rule>
1976 </pattern>
1977</schema>"#;
1978
1979 let doc_xml = r#"<?xml version="1.0"?>
1980<root>Hello</root>"#;
1981
1982 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
1983
1984 let doc = unsafe {
1985 crate::abi::exports_xml2::xmlReadMemory(
1986 doc_xml.as_ptr() as *const c_char,
1987 doc_xml.len() as c_int,
1988 b"test.xml\0".as_ptr() as *const c_char,
1989 ptr::null(),
1990 0,
1991 )
1992 };
1993 assert!(!doc.is_null(), "Failed to parse document");
1994
1995 let mut ctxt = SchematronValidCtxt::new();
1996 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
1997 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
1998
1999 assert!(valid, "Validation failed: {:?}", ctxt.errors);
2000 }
2001
2002 #[test]
2003 fn test_validate_assert_fail() {
2004 let schema_xml = r#"<?xml version="1.0"?>
2005<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2006 <pattern id="P1">
2007 <rule context="root">
2008 <assert test="false()">Always fails</assert>
2009 </rule>
2010 </pattern>
2011</schema>"#;
2012
2013 let doc_xml = r#"<?xml version="1.0"?>
2014<root>Hello</root>"#;
2015
2016 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2017
2018 let doc = unsafe {
2019 crate::abi::exports_xml2::xmlReadMemory(
2020 doc_xml.as_ptr() as *const c_char,
2021 doc_xml.len() as c_int,
2022 b"test.xml\0".as_ptr() as *const c_char,
2023 ptr::null(),
2024 0,
2025 )
2026 };
2027 assert!(!doc.is_null());
2028
2029 let mut ctxt = SchematronValidCtxt::new();
2030 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2031 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2032
2033 assert!(
2034 !valid,
2035 "Validation should have failed, errors: {:?}",
2036 ctxt.errors
2037 );
2038 assert!(ctxt.nb_errors > 0);
2039 }
2040
2041 #[test]
2042 fn test_validate_report_pass() {
2043 let schema_xml = r#"<?xml version="1.0"?>
2044<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2045 <pattern id="P1">
2046 <rule context="root">
2047 <report test="false()">Report should not trigger</report>
2048 </rule>
2049 </pattern>
2050</schema>"#;
2051
2052 let doc_xml = r#"<?xml version="1.0"?>
2053<root>Hello</root>"#;
2054
2055 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2056
2057 let doc = unsafe {
2058 crate::abi::exports_xml2::xmlReadMemory(
2059 doc_xml.as_ptr() as *const c_char,
2060 doc_xml.len() as c_int,
2061 b"test.xml\0".as_ptr() as *const c_char,
2062 ptr::null(),
2063 0,
2064 )
2065 };
2066 assert!(!doc.is_null());
2067
2068 let mut ctxt = SchematronValidCtxt::new();
2069 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2070 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2071
2072 assert!(valid, "Report should not trigger: {:?}", ctxt.errors);
2073 }
2074
2075 #[test]
2076 fn test_validate_report_fail() {
2077 let schema_xml = r#"<?xml version="1.0"?>
2078<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2079 <pattern id="P1">
2080 <rule context="root">
2081 <report test="true()">Report should trigger</report>
2082 </rule>
2083 </pattern>
2084</schema>"#;
2085
2086 let doc_xml = r#"<?xml version="1.0"?>
2087<root>Hello</root>"#;
2088
2089 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2090
2091 let doc = unsafe {
2092 crate::abi::exports_xml2::xmlReadMemory(
2093 doc_xml.as_ptr() as *const c_char,
2094 doc_xml.len() as c_int,
2095 b"test.xml\0".as_ptr() as *const c_char,
2096 ptr::null(),
2097 0,
2098 )
2099 };
2100 assert!(!doc.is_null());
2101
2102 let mut ctxt = SchematronValidCtxt::new();
2103 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2104 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2105
2106 assert!(!valid, "Report should have triggered");
2107 assert!(ctxt.nb_errors > 0);
2108 }
2109
2110 #[test]
2111 fn test_validate_context_matching() {
2112 let schema_xml = r#"<?xml version="1.0"?>
2113<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2114 <pattern id="P1">
2115 <rule context="child">
2116 <assert test="true()">Child matches</assert>
2117 </rule>
2118 </pattern>
2119</schema>"#;
2120
2121 let doc_xml = r#"<?xml version="1.0"?>
2122<root>
2123 <child>A</child>
2124 <child>B</child>
2125</root>"#;
2126
2127 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2128
2129 let doc = unsafe {
2130 crate::abi::exports_xml2::xmlReadMemory(
2131 doc_xml.as_ptr() as *const c_char,
2132 doc_xml.len() as c_int,
2133 b"test.xml\0".as_ptr() as *const c_char,
2134 ptr::null(),
2135 0,
2136 )
2137 };
2138 assert!(!doc.is_null());
2139
2140 let mut ctxt = SchematronValidCtxt::new();
2141 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2142 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2143
2144 assert!(valid, "Context matching failed: {:?}", ctxt.errors);
2145 }
2146
2147 #[test]
2148 fn test_validate_multiple_rules() {
2149 let schema_xml = r#"<?xml version="1.0"?>
2150<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2151 <pattern id="P1">
2152 <rule context="root">
2153 <assert test="true()">Root passes</assert>
2154 </rule>
2155 </pattern>
2156 <pattern id="P2">
2157 <rule context="child">
2158 <assert test="true()">Child passes</assert>
2159 </rule>
2160 </pattern>
2161</schema>"#;
2162
2163 let doc_xml = r#"<?xml version="1.0"?>
2164<root>
2165 <child>Content</child>
2166</root>"#;
2167
2168 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2169
2170 let doc = unsafe {
2171 crate::abi::exports_xml2::xmlReadMemory(
2172 doc_xml.as_ptr() as *const c_char,
2173 doc_xml.len() as c_int,
2174 b"test.xml\0".as_ptr() as *const c_char,
2175 ptr::null(),
2176 0,
2177 )
2178 };
2179 assert!(!doc.is_null());
2180
2181 let mut ctxt = SchematronValidCtxt::new();
2182 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2183 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2184
2185 assert!(valid, "Multiple rules failed: {:?}", ctxt.errors);
2186 }
2187
2188 #[test]
2189 fn test_validate_with_phase_filtering() {
2190 let schema_xml = r#"<?xml version="1.0"?>
2191<schema xmlns="http://purl.oclc.org/dsdl/schematron" defaultPhase="phaseA">
2192 <phase id="phaseA">
2193 <active pattern="P1"/>
2194 </phase>
2195 <phase id="phaseB">
2196 <active pattern="P2"/>
2197 </phase>
2198 <pattern id="P1">
2199 <rule context="root">
2200 <assert test="true()">Always passes</assert>
2201 </rule>
2202 </pattern>
2203 <pattern id="P2">
2204 <rule context="root">
2205 <assert test="false()">Always fails</assert>
2206 </rule>
2207 </pattern>
2208</schema>"#;
2209
2210 let doc_xml = r#"<?xml version="1.0"?>
2211<root>Hello</root>"#;
2212
2213 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2214
2215 let doc = unsafe {
2216 crate::abi::exports_xml2::xmlReadMemory(
2217 doc_xml.as_ptr() as *const c_char,
2218 doc_xml.len() as c_int,
2219 b"test.xml\0".as_ptr() as *const c_char,
2220 ptr::null(),
2221 0,
2222 )
2223 };
2224 assert!(!doc.is_null());
2225
2226 let mut ctxt = SchematronValidCtxt::new();
2228 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2229 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2230
2231 assert!(
2232 valid,
2233 "Phase filtering should make validation pass: {:?}",
2234 ctxt.errors
2235 );
2236 }
2237
2238 #[test]
2239 fn test_validate_no_rules() {
2240 let schema_xml = r#"<?xml version="1.0"?>
2241<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2242</schema>"#;
2243
2244 let doc_xml = r#"<?xml version="1.0"?>
2245<root>Hello</root>"#;
2246
2247 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2248
2249 let doc = unsafe {
2250 crate::abi::exports_xml2::xmlReadMemory(
2251 doc_xml.as_ptr() as *const c_char,
2252 doc_xml.len() as c_int,
2253 b"test.xml\0".as_ptr() as *const c_char,
2254 ptr::null(),
2255 0,
2256 )
2257 };
2258 assert!(!doc.is_null());
2259
2260 let mut ctxt = SchematronValidCtxt::new();
2261 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2262 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2263
2264 assert!(valid, "Empty schema should pass validation");
2265 }
2266
2267 #[test]
2268 fn test_validate_extends_resolution() {
2269 let schema_xml = r#"<?xml version="1.0"?>
2270<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2271 <pattern id="P1">
2272 <rule id="base" abstract="true" context="*">
2273 <assert test="true()">Base assertion</assert>
2274 </rule>
2275 <rule id="derived" context="root">
2276 <extends rule="base"/>
2277 <assert test="true()">Derived assertion</assert>
2278 </rule>
2279 </pattern>
2280</schema>"#;
2281
2282 let doc_xml = r#"<?xml version="1.0"?>
2283<root>Hello</root>"#;
2284
2285 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2286
2287 let resolved = schema.resolve_rule("derived");
2289 assert!(resolved.is_some());
2290 let resolved = resolved.unwrap();
2291 assert_eq!(
2293 resolved.patterns.len(),
2294 2,
2295 "Should have inherited the base pattern"
2296 );
2297
2298 let doc = unsafe {
2299 crate::abi::exports_xml2::xmlReadMemory(
2300 doc_xml.as_ptr() as *const c_char,
2301 doc_xml.len() as c_int,
2302 b"test.xml\0".as_ptr() as *const c_char,
2303 ptr::null(),
2304 0,
2305 )
2306 };
2307 assert!(!doc.is_null());
2308
2309 let mut ctxt = SchematronValidCtxt::new();
2310 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2311 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2312
2313 assert!(valid, "Extends resolution failed: {:?}", ctxt.errors);
2314 }
2315
2316 #[test]
2317 fn test_validate_assert_with_flag() {
2318 let schema_xml = r#"<?xml version="1.0"?>
2319<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2320 <pattern id="P1">
2321 <rule context="root">
2322 <assert test="false()" flag="warn">Warning message</assert>
2323 </rule>
2324 </pattern>
2325</schema>"#;
2326
2327 let doc_xml = r#"<?xml version="1.0"?>
2328<root>Hello</root>"#;
2329
2330 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2331
2332 let doc = unsafe {
2333 crate::abi::exports_xml2::xmlReadMemory(
2334 doc_xml.as_ptr() as *const c_char,
2335 doc_xml.len() as c_int,
2336 b"test.xml\0".as_ptr() as *const c_char,
2337 ptr::null(),
2338 0,
2339 )
2340 };
2341 assert!(!doc.is_null());
2342
2343 let mut ctxt = SchematronValidCtxt::new();
2344 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2345 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2346
2347 assert!(!valid);
2348 assert!(ctxt.nb_errors > 0);
2349 assert!(
2351 ctxt.errors[0].contains("[warn]"),
2352 "Error should include flag"
2353 );
2354 }
2355
2356 #[test]
2359 fn test_c_abi_new_free_parser_ctxt() {
2360 let ctxt = unsafe { xmlSchematronNewParserCtxt(ptr::null()) };
2361 assert!(!ctxt.is_null());
2362 unsafe { xmlSchematronFreeParserCtxt(ctxt) };
2363 }
2365
2366 #[test]
2367 fn test_c_abi_new_free_valid_ctxt() {
2368 let schema = unsafe { xmlSchematronNewParserCtxt(ptr::null()) };
2369 assert!(!schema.is_null());
2370
2371 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema) };
2372 assert!(!valid_ctxt.is_null());
2373
2374 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2375 unsafe { xmlSchematronFreeParserCtxt(schema) };
2376 }
2378
2379 #[test]
2380 fn test_c_abi_parse_free() {
2381 let schema_xml = r#"<?xml version="1.0"?>
2382<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2383 <pattern id="P1">
2384 <rule context="root">
2385 <assert test="true()">Test</assert>
2386 </rule>
2387 </pattern>
2388</schema>"#;
2389
2390 let ctxt = unsafe {
2391 xmlSchematronNewMemParserCtxt(
2392 schema_xml.as_ptr() as *const c_char,
2393 schema_xml.len() as c_int,
2394 )
2395 };
2396 assert!(!ctxt.is_null());
2397
2398 let schema = unsafe { xmlSchematronParse(ctxt) };
2399 assert!(!schema.is_null());
2400
2401 unsafe { xmlSchematronFree(schema) };
2402 }
2404
2405 #[test]
2406 fn test_c_abi_validate_doc() {
2407 let schema_xml = r#"<?xml version="1.0"?>
2408<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2409 <pattern id="P1">
2410 <rule context="root">
2411 <assert test="true()">Always passes</assert>
2412 </rule>
2413 </pattern>
2414</schema>"#;
2415
2416 let doc_xml = r#"<?xml version="1.0"?>
2417<root>Hello</root>"#;
2418
2419 let ctxt = unsafe {
2420 xmlSchematronNewMemParserCtxt(
2421 schema_xml.as_ptr() as *const c_char,
2422 schema_xml.len() as c_int,
2423 )
2424 };
2425 assert!(!ctxt.is_null());
2426
2427 let schema = unsafe { xmlSchematronParse(ctxt) };
2428 assert!(!schema.is_null());
2429
2430 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema) };
2431 assert!(!valid_ctxt.is_null());
2432
2433 let doc = unsafe {
2434 crate::abi::exports_xml2::xmlReadMemory(
2435 doc_xml.as_ptr() as *const c_char,
2436 doc_xml.len() as c_int,
2437 b"test.xml\0".as_ptr() as *const c_char,
2438 ptr::null(),
2439 0,
2440 )
2441 };
2442 assert!(!doc.is_null());
2443
2444 let result = unsafe { xmlSchematronValidateDoc(valid_ctxt, doc) };
2445 assert_eq!(result, 0, "Validation should pass (return 0)");
2446
2447 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2448 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2449 unsafe { xmlSchematronFree(schema) };
2450 }
2451
2452 #[test]
2453 fn test_c_abi_validate_fail() {
2454 let schema_xml = r#"<?xml version="1.0"?>
2455<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2456 <pattern id="P1">
2457 <rule context="root">
2458 <assert test="false()">Always fails</assert>
2459 </rule>
2460 </pattern>
2461</schema>"#;
2462
2463 let doc_xml = r#"<?xml version="1.0"?>
2464<root>Hello</root>"#;
2465
2466 let ctxt = unsafe {
2467 xmlSchematronNewMemParserCtxt(
2468 schema_xml.as_ptr() as *const c_char,
2469 schema_xml.len() as c_int,
2470 )
2471 };
2472 assert!(!ctxt.is_null());
2473
2474 let schema = unsafe { xmlSchematronParse(ctxt) };
2475 assert!(!schema.is_null());
2476
2477 let valid_ctxt = unsafe { xmlSchematronNewValidCtxt(schema) };
2478 assert!(!valid_ctxt.is_null());
2479
2480 let doc = unsafe {
2481 crate::abi::exports_xml2::xmlReadMemory(
2482 doc_xml.as_ptr() as *const c_char,
2483 doc_xml.len() as c_int,
2484 b"test.xml\0".as_ptr() as *const c_char,
2485 ptr::null(),
2486 0,
2487 )
2488 };
2489 assert!(!doc.is_null());
2490
2491 let result = unsafe { xmlSchematronValidateDoc(valid_ctxt, doc) };
2492 assert!(result > 0, "Validation should fail (return > 0)");
2493
2494 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2495 unsafe { xmlSchematronFreeValidCtxt(valid_ctxt) };
2496 unsafe { xmlSchematronFree(schema) };
2497 }
2498
2499 #[test]
2500 fn test_c_abi_null_handling() {
2501 unsafe { xmlSchematronFree(ptr::null_mut()) };
2503 unsafe { xmlSchematronFreeParserCtxt(ptr::null_mut()) };
2504 unsafe { xmlSchematronFreeValidCtxt(ptr::null_mut()) };
2505
2506 let result = unsafe { xmlSchematronParse(ptr::null_mut()) };
2508 assert!(result.is_null());
2509
2510 let result = unsafe { xmlSchematronValidateDoc(ptr::null_mut(), ptr::null_mut()) };
2512 assert_eq!(result, -1);
2513 }
2514
2515 #[test]
2518 fn test_validate_null_doc() {
2519 let schema = SchematronSchema::new();
2520 let mut ctxt = SchematronValidCtxt::new();
2521 let valid = unsafe { schematron_validate_doc(&schema, ptr::null_mut(), &mut ctxt) };
2522 assert!(!valid);
2523 assert!(ctxt.nb_errors > 0);
2524 }
2525
2526 #[test]
2527 fn test_active_rules_default_phase() {
2528 let mut schema = SchematronSchema::new();
2529
2530 let rule = SchematronRule::new("root".to_string());
2531 schema.rules.insert("r1".to_string(), rule);
2532
2533 schema
2534 .pattern_groups
2535 .insert("p1".to_string(), vec!["r1".to_string()]);
2536 schema.pattern_order.push("p1".to_string());
2537
2538 let rules = schema.active_rules(None);
2539 assert_eq!(rules.len(), 1);
2540 }
2541
2542 #[test]
2543 fn test_active_rules_unknown_phase() {
2544 let mut schema = SchematronSchema::new();
2545
2546 let rule = SchematronRule::new("root".to_string());
2547 schema.rules.insert("r1".to_string(), rule);
2548
2549 schema
2550 .pattern_groups
2551 .insert("p1".to_string(), vec!["r1".to_string()]);
2552 schema.pattern_order.push("p1".to_string());
2553
2554 let rules = schema.active_rules(Some("nonexistent"));
2555 assert_eq!(rules.len(), 1, "Unknown phase should use all patterns");
2556 }
2557
2558 #[test]
2559 fn test_parse_schema_with_span_and_emph() {
2560 let schema_xml = r#"<?xml version="1.0"?>
2561<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2562 <pattern id="P1">
2563 <rule context="root">
2564 <assert test="true()">Message with <span class="x">inline</span> and <emph>emphasis</emph></assert>
2565 </rule>
2566 </pattern>
2567</schema>"#;
2568
2569 let result = schematron_parse(schema_xml);
2570 assert!(
2571 result.is_ok(),
2572 "Failed to parse schema with span/emph: {:?}",
2573 result.err()
2574 );
2575 let schema = result.unwrap();
2576 let rule = schema.rules.values().next().unwrap();
2577 let pat = &rule.patterns[0];
2578 assert!(
2580 pat.text.contains("inline"),
2581 "Text should include span content"
2582 );
2583 assert!(
2584 pat.text.contains("emphasis"),
2585 "Text should include emph content"
2586 );
2587 }
2588
2589 #[test]
2590 fn test_schematron_pattern_new_assert() {
2591 let pat = SchematronPattern::new(
2592 SchematronPatternType::Assert,
2593 "true()".to_string(),
2594 "Test message".to_string(),
2595 );
2596 assert_eq!(pat.pattern_type, SchematronPatternType::Assert);
2597 assert_eq!(pat.test, "true()");
2598 assert_eq!(pat.text, "Test message");
2599 assert!(pat.compiled_test.is_some());
2600 }
2601
2602 #[test]
2603 fn test_schematron_pattern_new_report() {
2604 let pat = SchematronPattern::new(
2605 SchematronPatternType::Report,
2606 "false()".to_string(),
2607 "Report message".to_string(),
2608 );
2609 assert_eq!(pat.pattern_type, SchematronPatternType::Report);
2610 assert!(pat.compiled_test.is_some());
2611 }
2612
2613 #[test]
2614 fn test_schematron_rule_new() {
2615 let rule = SchematronRule::new("root".to_string());
2616 assert_eq!(rule.context, "root");
2617 assert!(rule.patterns.is_empty());
2618 assert!(!rule.abstract_);
2619 }
2620
2621 #[test]
2622 fn test_schematron_schema_new() {
2623 let schema = SchematronSchema::new();
2624 assert_eq!(schema.query_binding, "xslt");
2625 assert!(schema.rules.is_empty());
2626 assert!(schema.phases.is_empty());
2627 assert!(schema.ns.is_empty());
2628 }
2629
2630 #[test]
2631 fn test_schematron_valid_ctxt_new() {
2632 let ctxt = SchematronValidCtxt::new();
2633 assert!(ctxt.errors.is_empty());
2634 assert_eq!(ctxt.nb_errors, 0);
2635 assert!(ctxt.active_phase.is_none());
2636 }
2637
2638 #[test]
2639 fn test_validate_assert_with_child_count() {
2640 let schema_xml = r#"<?xml version="1.0"?>
2641<schema xmlns="http://purl.oclc.org/dsdl/schematron">
2642 <pattern id="P1">
2643 <rule context="root">
2644 <assert test="count(*) > 0">Root must have at least one child element</assert>
2645 </rule>
2646 </pattern>
2647</schema>"#;
2648
2649 let doc_xml = r#"<?xml version="1.0"?>
2650<root>
2651 <child>Content</child>
2652</root>"#;
2653
2654 let schema = schematron_parse(schema_xml).expect("Failed to parse schema");
2655
2656 let doc = unsafe {
2657 crate::abi::exports_xml2::xmlReadMemory(
2658 doc_xml.as_ptr() as *const c_char,
2659 doc_xml.len() as c_int,
2660 b"test.xml\0".as_ptr() as *const c_char,
2661 ptr::null(),
2662 0,
2663 )
2664 };
2665 assert!(!doc.is_null());
2666
2667 let mut ctxt = SchematronValidCtxt::new();
2668 let valid = unsafe { schematron_validate_doc(&schema, doc, &mut ctxt) };
2669 unsafe { crate::abi::exports_xml2::xmlFreeDoc(doc) };
2670
2671 assert!(valid, "Child count check failed: {:?}", ctxt.errors);
2672 }
2673}