1use parking_lot::RwLock;
114use roxmltree::{Document as XmlDocument, Node, ParsingOptions};
115use std::collections::HashMap;
116use std::fmt;
117use std::fs;
118use std::path::{Path, PathBuf};
119use std::sync::Arc;
120
121#[derive(Debug, Clone, PartialEq)]
123pub enum ValidationError {
124 SchemaParseError {
126 message: String,
128 },
129
130 DocumentParseError {
132 message: String,
134 line: Option<usize>,
136 column: Option<usize>,
138 },
139
140 ElementValidationError {
142 element: String,
144 expected: String,
146 found: String,
148 line: Option<usize>,
150 },
151
152 AttributeValidationError {
154 element: String,
156 attribute: String,
158 message: String,
160 line: Option<usize>,
162 },
163
164 TypeValidationError {
166 name: String,
168 expected_type: String,
170 value: String,
172 line: Option<usize>,
174 },
175
176 CardinalityError {
178 element: String,
180 min: usize,
182 max: Option<usize>,
184 actual: usize,
186 line: Option<usize>,
188 },
189
190 RequiredAttributeMissing {
192 element: String,
194 attribute: String,
196 line: Option<usize>,
198 },
199
200 UnknownElement {
202 element: String,
204 line: Option<usize>,
206 },
207
208 SchemaNotFound {
210 path: PathBuf,
212 },
213
214 IoError {
216 message: String,
218 },
219}
220
221impl fmt::Display for ValidationError {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 match self {
224 ValidationError::SchemaParseError { message } => {
225 write!(f, "Schema parse error: {}", message)
226 }
227 ValidationError::DocumentParseError {
228 message,
229 line,
230 column,
231 } => {
232 write!(f, "Document parse error: {}", message)?;
233 if let Some(l) = line {
234 write!(f, " at line {}", l)?;
235 if let Some(c) = column {
236 write!(f, ", column {}", c)?;
237 }
238 }
239 Ok(())
240 }
241 ValidationError::ElementValidationError {
242 element,
243 expected,
244 found,
245 line,
246 } => {
247 write!(
248 f,
249 "Element validation failed for '{}': expected {}, found '{}'",
250 element, expected, found
251 )?;
252 if let Some(l) = line {
253 write!(f, " at line {}", l)?;
254 }
255 Ok(())
256 }
257 ValidationError::AttributeValidationError {
258 element,
259 attribute,
260 message,
261 line,
262 } => {
263 write!(
264 f,
265 "Attribute validation failed for '{}.{}': {}",
266 element, attribute, message
267 )?;
268 if let Some(l) = line {
269 write!(f, " at line {}", l)?;
270 }
271 Ok(())
272 }
273 ValidationError::TypeValidationError {
274 name,
275 expected_type,
276 value,
277 line,
278 } => {
279 write!(
280 f,
281 "Type validation failed for '{}': expected {}, found '{}'",
282 name, expected_type, value
283 )?;
284 if let Some(l) = line {
285 write!(f, " at line {}", l)?;
286 }
287 Ok(())
288 }
289 ValidationError::CardinalityError {
290 element,
291 min,
292 max,
293 actual,
294 line,
295 } => {
296 write!(
297 f,
298 "Cardinality error for '{}': expected {}..{}, found {}",
299 element,
300 min,
301 max.map_or("unbounded".to_string(), |m| m.to_string()),
302 actual
303 )?;
304 if let Some(l) = line {
305 write!(f, " at line {}", l)?;
306 }
307 Ok(())
308 }
309 ValidationError::RequiredAttributeMissing {
310 element,
311 attribute,
312 line,
313 } => {
314 write!(
315 f,
316 "Required attribute '{}' missing from element '{}'",
317 attribute, element
318 )?;
319 if let Some(l) = line {
320 write!(f, " at line {}", l)?;
321 }
322 Ok(())
323 }
324 ValidationError::UnknownElement { element, line } => {
325 write!(f, "Unknown element '{}' not defined in schema", element)?;
326 if let Some(l) = line {
327 write!(f, " at line {}", l)?;
328 }
329 Ok(())
330 }
331 ValidationError::SchemaNotFound { path } => {
332 write!(f, "Schema file not found: {}", path.display())
333 }
334 ValidationError::IoError { message } => {
335 write!(f, "I/O error: {}", message)
336 }
337 }
338 }
339}
340
341impl std::error::Error for ValidationError {}
342
343#[derive(Debug, Clone)]
345struct Schema {
346 elements: HashMap<String, ElementDef>,
347 #[allow(dead_code)]
348 target_namespace: Option<String>,
349}
350
351#[derive(Debug, Clone)]
353struct ElementDef {
354 name: String,
355 type_name: Option<String>,
356 complex_type: Option<ComplexType>,
357 min_occurs: usize,
358 max_occurs: Option<usize>,
359}
360
361#[derive(Debug, Clone)]
363struct ComplexType {
364 sequence: Vec<ElementDef>,
365 attributes: Vec<AttributeDef>,
366}
367
368#[derive(Debug, Clone)]
370struct AttributeDef {
371 name: String,
372 type_name: String,
373 required: bool,
374}
375
376#[derive(Debug, Clone)]
380pub struct SchemaValidator {
381 schema: Schema,
382}
383
384impl SchemaValidator {
385 pub fn from_xsd(xsd: &str) -> Result<Self, ValidationError> {
409 let schema = Self::parse_xsd(xsd)?;
410 Ok(Self { schema })
411 }
412
413 fn parse_xsd(xsd: &str) -> Result<Schema, ValidationError> {
415 let options = ParsingOptions {
417 allow_dtd: false, ..Default::default()
419 };
420
421 let doc = XmlDocument::parse_with_options(xsd, options).map_err(|e| {
422 ValidationError::SchemaParseError {
423 message: e.to_string(),
424 }
425 })?;
426
427 let root = doc.root_element();
428
429 if root.tag_name().name() != "schema" {
431 return Err(ValidationError::SchemaParseError {
432 message: "Root element must be <xs:schema>".to_string(),
433 });
434 }
435
436 let target_namespace = root.attribute("targetNamespace").map(|s| s.to_string());
437 let mut elements = HashMap::new();
438
439 for child in root.children().filter(|n| n.is_element()) {
441 if child.tag_name().name() == "element" {
442 let elem_def = Self::parse_element(&child)?;
443 elements.insert(elem_def.name.clone(), elem_def);
444 }
445 }
446
447 Ok(Schema {
448 elements,
449 target_namespace,
450 })
451 }
452
453 fn parse_element(node: &Node<'_, '_>) -> Result<ElementDef, ValidationError> {
455 let name = node
456 .attribute("name")
457 .ok_or_else(|| ValidationError::SchemaParseError {
458 message: "Element must have 'name' attribute".to_string(),
459 })?
460 .to_string();
461
462 let type_name = node.attribute("type").map(|s| s.to_string());
463 let min_occurs = node
464 .attribute("minOccurs")
465 .and_then(|s| s.parse::<usize>().ok())
466 .unwrap_or(1);
467 let max_occurs = node.attribute("maxOccurs").and_then(|s| {
468 if s == "unbounded" {
469 None
470 } else {
471 s.parse::<usize>().ok()
472 }
473 });
474
475 let mut complex_type = None;
477 for child in node.children().filter(|n| n.is_element()) {
478 if child.tag_name().name() == "complexType" {
479 complex_type = Some(Self::parse_complex_type(&child)?);
480 break;
481 }
482 }
483
484 Ok(ElementDef {
485 name,
486 type_name,
487 complex_type,
488 min_occurs,
489 max_occurs,
490 })
491 }
492
493 fn parse_complex_type(node: &Node<'_, '_>) -> Result<ComplexType, ValidationError> {
495 let mut sequence = Vec::new();
496 let mut attributes = Vec::new();
497
498 for child in node.children().filter(|n| n.is_element()) {
499 match child.tag_name().name() {
500 "sequence" => {
501 for elem_node in child.children().filter(|n| n.is_element()) {
502 if elem_node.tag_name().name() == "element" {
503 sequence.push(Self::parse_element(&elem_node)?);
504 }
505 }
506 }
507 "attribute" => {
508 attributes.push(Self::parse_attribute(&child)?);
509 }
510 _ => {}
511 }
512 }
513
514 Ok(ComplexType {
515 sequence,
516 attributes,
517 })
518 }
519
520 fn parse_attribute(node: &Node<'_, '_>) -> Result<AttributeDef, ValidationError> {
522 let name = node
523 .attribute("name")
524 .ok_or_else(|| ValidationError::SchemaParseError {
525 message: "Attribute must have 'name' attribute".to_string(),
526 })?
527 .to_string();
528
529 let type_name = node.attribute("type").unwrap_or("xs:string").to_string();
530
531 let required = node.attribute("use") == Some("required");
532
533 Ok(AttributeDef {
534 name,
535 type_name,
536 required,
537 })
538 }
539
540 pub fn from_file(path: &Path) -> Result<Self, ValidationError> {
562 if !path.exists() {
563 return Err(ValidationError::SchemaNotFound {
564 path: path.to_path_buf(),
565 });
566 }
567
568 let content = fs::read_to_string(path).map_err(|e| ValidationError::IoError {
569 message: e.to_string(),
570 })?;
571
572 Self::from_xsd(&content)
573 }
574
575 pub fn validate(&self, xml: &str) -> Result<(), ValidationError> {
602 let options = ParsingOptions {
604 allow_dtd: false, ..Default::default()
606 };
607
608 let doc = XmlDocument::parse_with_options(xml, options).map_err(|e| {
609 ValidationError::DocumentParseError {
610 message: e.to_string(),
611 line: None,
612 column: None,
613 }
614 })?;
615
616 let root = doc.root_element();
617 let root_name = root.tag_name().name();
618
619 let schema_elem =
621 self.schema
622 .elements
623 .get(root_name)
624 .ok_or_else(|| ValidationError::UnknownElement {
625 element: root_name.to_string(),
626 line: Some(doc.text_pos_at(root.range().start).row as usize),
627 })?;
628
629 self.validate_element(&root, schema_elem)?;
630
631 Ok(())
632 }
633
634 fn validate_element(
636 &self,
637 node: &Node<'_, '_>,
638 schema_elem: &ElementDef,
639 ) -> Result<(), ValidationError> {
640 let line = node.document().text_pos_at(node.range().start).row as usize;
641
642 if let Some(ref type_name) = schema_elem.type_name {
644 self.validate_type(node, type_name, line)?;
645 }
646
647 if let Some(ref complex_type) = schema_elem.complex_type {
649 self.validate_attributes_complex(node, complex_type, line)?;
651
652 self.validate_children_complex(node, complex_type, line)?;
654 }
655
656 Ok(())
657 }
658
659 fn validate_type(
661 &self,
662 node: &Node<'_, '_>,
663 type_ref: &str,
664 line: usize,
665 ) -> Result<(), ValidationError> {
666 let text = node.text().unwrap_or("");
667
668 match type_ref {
670 "xs:string" | "string" => {
671 }
673 "xs:integer" | "integer" => {
674 if text.parse::<i64>().is_err() {
675 return Err(ValidationError::TypeValidationError {
676 name: node.tag_name().name().to_string(),
677 expected_type: "xs:integer".to_string(),
678 value: text.to_string(),
679 line: Some(line),
680 });
681 }
682 }
683 "xs:decimal" | "decimal" => {
684 if text.parse::<f64>().is_err() {
685 return Err(ValidationError::TypeValidationError {
686 name: node.tag_name().name().to_string(),
687 expected_type: "xs:decimal".to_string(),
688 value: text.to_string(),
689 line: Some(line),
690 });
691 }
692 }
693 "xs:boolean" | "boolean" => {
694 if !["true", "false", "1", "0"].contains(&text) {
695 return Err(ValidationError::TypeValidationError {
696 name: node.tag_name().name().to_string(),
697 expected_type: "xs:boolean".to_string(),
698 value: text.to_string(),
699 line: Some(line),
700 });
701 }
702 }
703 _ => {
704 }
706 }
707
708 Ok(())
709 }
710
711 fn validate_attributes_complex(
713 &self,
714 node: &Node<'_, '_>,
715 complex_type: &ComplexType,
716 line: usize,
717 ) -> Result<(), ValidationError> {
718 let element_name = node.tag_name().name();
719
720 for attr_def in &complex_type.attributes {
722 if attr_def.required && node.attribute(attr_def.name.as_str()).is_none() {
723 return Err(ValidationError::RequiredAttributeMissing {
724 element: element_name.to_string(),
725 attribute: attr_def.name.clone(),
726 line: Some(line),
727 });
728 }
729
730 if let Some(value) = node.attribute(attr_def.name.as_str()) {
732 self.validate_simple_type(value, &attr_def.type_name)
733 .map_err(|_| ValidationError::AttributeValidationError {
734 element: element_name.to_string(),
735 attribute: attr_def.name.clone(),
736 message: format!("Expected type {}, found '{}'", attr_def.type_name, value),
737 line: Some(line),
738 })?;
739 }
740 }
741
742 Ok(())
743 }
744
745 fn validate_children_complex(
747 &self,
748 node: &Node<'_, '_>,
749 complex_type: &ComplexType,
750 line: usize,
751 ) -> Result<(), ValidationError> {
752 let children: Vec<_> = node.children().filter(|n| n.is_element()).collect();
753
754 for child in &children {
756 let child_name = child.tag_name().name();
757
758 let schema_elem = complex_type
760 .sequence
761 .iter()
762 .find(|e| e.name == child_name)
763 .ok_or_else(|| ValidationError::UnknownElement {
764 element: child_name.to_string(),
765 line: Some(child.document().text_pos_at(child.range().start).row as usize),
766 })?;
767
768 self.validate_element(child, schema_elem)?;
769 }
770
771 for elem_def in &complex_type.sequence {
773 let count = children
774 .iter()
775 .filter(|n| n.tag_name().name() == elem_def.name)
776 .count();
777
778 if count < elem_def.min_occurs {
779 return Err(ValidationError::CardinalityError {
780 element: elem_def.name.clone(),
781 min: elem_def.min_occurs,
782 max: elem_def.max_occurs,
783 actual: count,
784 line: Some(line),
785 });
786 }
787
788 if let Some(max) = elem_def.max_occurs {
789 if count > max {
790 return Err(ValidationError::CardinalityError {
791 element: elem_def.name.clone(),
792 min: elem_def.min_occurs,
793 max: elem_def.max_occurs,
794 actual: count,
795 line: Some(line),
796 });
797 }
798 }
799 }
800
801 Ok(())
802 }
803
804 fn validate_simple_type(&self, value: &str, type_name: &str) -> Result<(), ()> {
806 match type_name {
807 "xs:string" | "string" => Ok(()),
808 "xs:integer" | "integer" => value.parse::<i64>().map(|_| ()).map_err(|_| ()),
809 "xs:decimal" | "decimal" => value.parse::<f64>().map(|_| ()).map_err(|_| ()),
810 "xs:boolean" | "boolean" => {
811 if ["true", "false", "1", "0"].contains(&value) {
812 Ok(())
813 } else {
814 Err(())
815 }
816 }
817 _ => Ok(()), }
819 }
820}
821
822pub struct SchemaCache {
842 cache: Arc<RwLock<HashMap<PathBuf, Arc<SchemaValidator>>>>,
843 max_size: usize,
844}
845
846impl SchemaCache {
847 pub fn new(max_size: usize) -> Self {
861 Self {
862 cache: Arc::new(RwLock::new(HashMap::new())),
863 max_size,
864 }
865 }
866
867 pub fn get_or_load(&self, path: &Path) -> Result<Arc<SchemaValidator>, ValidationError> {
891 {
893 let cache = self.cache.read();
894 if let Some(validator) = cache.get(path) {
895 return Ok(Arc::clone(validator));
896 }
897 }
898
899 let mut cache = self.cache.write();
901
902 if let Some(validator) = cache.get(path) {
904 return Ok(Arc::clone(validator));
905 }
906
907 let validator = Arc::new(SchemaValidator::from_file(path)?);
909
910 if cache.len() >= self.max_size {
912 if let Some(oldest_key) = cache.keys().next().cloned() {
913 cache.remove(&oldest_key);
914 }
915 }
916
917 cache.insert(path.to_path_buf(), Arc::clone(&validator));
918
919 Ok(validator)
920 }
921
922 pub fn clear(&self) {
933 self.cache.write().clear();
934 }
935
936 pub fn size(&self) -> usize {
947 self.cache.read().len()
948 }
949}
950
951impl Default for SchemaCache {
952 fn default() -> Self {
954 Self::new(100)
955 }
956}
957
958#[cfg(test)]
959mod tests {
960 use super::*;
961
962 const SIMPLE_SCHEMA: &str = r#"<?xml version="1.0"?>
963<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
964 <xs:element name="person">
965 <xs:complexType>
966 <xs:sequence>
967 <xs:element name="name" type="xs:string"/>
968 <xs:element name="age" type="xs:integer"/>
969 </xs:sequence>
970 </xs:complexType>
971 </xs:element>
972</xs:schema>"#;
973
974 #[test]
975 fn test_schema_validator_creation() {
976 let validator = SchemaValidator::from_xsd(SIMPLE_SCHEMA);
977 assert!(validator.is_ok());
978 }
979
980 #[test]
981 fn test_valid_document() {
982 let validator = SchemaValidator::from_xsd(SIMPLE_SCHEMA).unwrap();
983
984 let xml = r#"<?xml version="1.0"?>
985<person>
986 <name>Alice</name>
987 <age>30</age>
988</person>"#;
989
990 assert!(validator.validate(xml).is_ok());
991 }
992
993 #[test]
994 fn test_invalid_type() {
995 let validator = SchemaValidator::from_xsd(SIMPLE_SCHEMA).unwrap();
996
997 let xml = r#"<?xml version="1.0"?>
998<person>
999 <name>Alice</name>
1000 <age>thirty</age>
1001</person>"#;
1002
1003 let result = validator.validate(xml);
1004 assert!(result.is_err());
1005
1006 if let Err(ValidationError::TypeValidationError {
1007 name,
1008 expected_type,
1009 value,
1010 ..
1011 }) = result
1012 {
1013 assert_eq!(name, "age");
1014 assert_eq!(expected_type, "xs:integer");
1015 assert_eq!(value, "thirty");
1016 } else {
1017 panic!("Expected TypeValidationError");
1018 }
1019 }
1020
1021 #[test]
1022 fn test_unknown_element() {
1023 let validator = SchemaValidator::from_xsd(SIMPLE_SCHEMA).unwrap();
1024
1025 let xml = r#"<?xml version="1.0"?>
1026<person>
1027 <name>Alice</name>
1028 <age>30</age>
1029 <email>alice@example.com</email>
1030</person>"#;
1031
1032 let result = validator.validate(xml);
1033 assert!(result.is_err());
1034
1035 if let Err(ValidationError::UnknownElement { element, .. }) = result {
1036 assert_eq!(element, "email");
1037 } else {
1038 panic!("Expected UnknownElement error");
1039 }
1040 }
1041
1042 #[test]
1043 fn test_malformed_xml() {
1044 let validator = SchemaValidator::from_xsd(SIMPLE_SCHEMA).unwrap();
1045
1046 let xml = r#"<?xml version="1.0"?>
1047<person>
1048 <name>Alice
1049 <age>30</age>
1050</person>"#;
1051
1052 let result = validator.validate(xml);
1053 assert!(result.is_err());
1054 assert!(matches!(
1055 result,
1056 Err(ValidationError::DocumentParseError { .. })
1057 ));
1058 }
1059
1060 #[test]
1061 fn test_schema_cache() {
1062 use std::io::Write;
1063 use tempfile::NamedTempFile;
1064
1065 let cache = SchemaCache::new(5);
1066 assert_eq!(cache.size(), 0);
1067
1068 let mut temp_file = NamedTempFile::new().unwrap();
1070 temp_file.write_all(SIMPLE_SCHEMA.as_bytes()).unwrap();
1071 let path = temp_file.path();
1072
1073 let validator1 = cache.get_or_load(path).unwrap();
1075 assert_eq!(cache.size(), 1);
1076
1077 let validator2 = cache.get_or_load(path).unwrap();
1079 assert_eq!(cache.size(), 1);
1080
1081 assert!(Arc::ptr_eq(&validator1, &validator2));
1083
1084 cache.clear();
1086 assert_eq!(cache.size(), 0);
1087 }
1088
1089 #[test]
1090 fn test_cache_eviction() {
1091 use std::io::Write;
1092 use tempfile::NamedTempFile;
1093
1094 let cache = SchemaCache::new(2);
1095
1096 let mut files = vec![];
1098 for _ in 0..3 {
1099 let mut temp_file = NamedTempFile::new().unwrap();
1100 temp_file.write_all(SIMPLE_SCHEMA.as_bytes()).unwrap();
1101 files.push(temp_file);
1102 }
1103
1104 cache.get_or_load(files[0].path()).unwrap();
1106 cache.get_or_load(files[1].path()).unwrap();
1107 assert_eq!(cache.size(), 2);
1108
1109 cache.get_or_load(files[2].path()).unwrap();
1111 assert_eq!(cache.size(), 2);
1112 }
1113
1114 #[test]
1115 fn test_error_display() {
1116 let err = ValidationError::TypeValidationError {
1117 name: "age".to_string(),
1118 expected_type: "xs:integer".to_string(),
1119 value: "thirty".to_string(),
1120 line: Some(5),
1121 };
1122
1123 let display = err.to_string();
1124 assert!(display.contains("age"));
1125 assert!(display.contains("xs:integer"));
1126 assert!(display.contains("thirty"));
1127 assert!(display.contains("line 5"));
1128 }
1129
1130 #[test]
1131 fn test_schema_not_found() {
1132 let result = SchemaValidator::from_file(Path::new("/nonexistent/schema.xsd"));
1133 assert!(result.is_err());
1134 assert!(matches!(
1135 result,
1136 Err(ValidationError::SchemaNotFound { .. })
1137 ));
1138 }
1139
1140 #[test]
1141 fn test_invalid_schema() {
1142 let invalid_schema = r#"<?xml version="1.0"?>
1143<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1144 <xs:element name="broken" type="nonexistent:type"/>
1145</xs:schema>"#;
1146
1147 let _result = SchemaValidator::from_xsd(invalid_schema);
1148 }
1151
1152 #[test]
1153 fn test_boolean_type_validation() {
1154 let schema = r#"<?xml version="1.0"?>
1155<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1156 <xs:element name="flag" type="xs:boolean"/>
1157</xs:schema>"#;
1158
1159 let validator = SchemaValidator::from_xsd(schema).unwrap();
1160
1161 for val in &["true", "false", "1", "0"] {
1163 let xml = format!(r#"<?xml version="1.0"?><flag>{}</flag>"#, val);
1164 assert!(validator.validate(&xml).is_ok());
1165 }
1166
1167 let xml = r#"<?xml version="1.0"?><flag>yes</flag>"#;
1169 assert!(validator.validate(xml).is_err());
1170 }
1171
1172 #[test]
1173 fn test_decimal_type_validation() {
1174 let schema = r#"<?xml version="1.0"?>
1175<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1176 <xs:element name="price" type="xs:decimal"/>
1177</xs:schema>"#;
1178
1179 let validator = SchemaValidator::from_xsd(schema).unwrap();
1180
1181 let xml = r#"<?xml version="1.0"?><price>19.99</price>"#;
1183 assert!(validator.validate(xml).is_ok());
1184
1185 let xml = r#"<?xml version="1.0"?><price>not a number</price>"#;
1187 assert!(validator.validate(xml).is_err());
1188 }
1189
1190 #[test]
1191 fn test_concurrent_cache_access() {
1192 use std::io::Write;
1193 use std::sync::Arc;
1194 use std::thread;
1195 use tempfile::NamedTempFile;
1196
1197 let cache = Arc::new(SchemaCache::new(10));
1198
1199 let mut temp_file = NamedTempFile::new().unwrap();
1201 temp_file.write_all(SIMPLE_SCHEMA.as_bytes()).unwrap();
1202 let path = temp_file.path().to_path_buf();
1203
1204 let mut handles = vec![];
1206 for _ in 0..10 {
1207 let cache_clone = Arc::clone(&cache);
1208 let path_clone = path.clone();
1209 let handle = thread::spawn(move || {
1210 for _ in 0..100 {
1211 let _validator = cache_clone.get_or_load(&path_clone).unwrap();
1212 }
1213 });
1214 handles.push(handle);
1215 }
1216
1217 for handle in handles {
1219 handle.join().unwrap();
1220 }
1221
1222 assert_eq!(cache.size(), 1);
1224 }
1225}