Skip to main content

crossref_xml/journal/
article.rs

1use crate::{
2    enums::{IdType, Iso639_1, ParentRelation, PublicationType, UpdateType},
3    journal::{
4        issue::{Contributor, PublicationDate, Titles},
5        metadata::{ArchiveLocations, DoiData},
6    },
7    regex::DOI_REGEX,
8};
9use chrono::NaiveDate;
10use serde::Serialize;
11use validator::Validate;
12
13#[derive(Debug, Clone, Default, Serialize, Validate)]
14pub struct JournalArticle {
15    #[serde(rename = "@publication_type")]
16    pub publication_type: PublicationType,
17    #[serde(rename = "@language")]
18    pub language: Iso639_1,
19    #[validate(nested)]
20    pub titles: Titles,
21    #[serde(default, skip_serializing_if = "Vec::is_empty")]
22    #[validate(nested)]
23    pub contributors: Vec<Contributor>,
24    #[serde(rename = "jats:abstract")]
25    #[serde(skip_serializing_if = "Option::is_none")]
26    // TODO: needs jats:p in there
27    pub jats_abstract: Option<JatsP>,
28    #[validate(nested)]
29    // TODO: Can technically be a Vec<PublicationDate> 1..10
30    pub publication_date: PublicationDate,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    #[validate(nested)]
33    pub acceptance_date: Option<PublicationDate>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    #[validate(nested)]
36    pub pages: Option<Pages>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    #[validate(nested)]
39    pub publisher_item: Option<PublisherItem>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    #[validate(nested)]
42    pub crossmark: Option<Crossmark>,
43    // #[serde(rename = "fr:program")]
44    // pub fr_program: FrProgram,
45    // #[serde(rename = "ai:program")]
46    // pub ai_program: AiProgram,
47    // #[serde(rename = "ct:program")]
48    // pub ct_program: CtProgram,
49    // #[serde(rename = "rel:program")]
50    // pub rel_program: RelProgram,
51    #[serde(skip_serializing_if = "ArchiveLocations::is_empty")]
52    #[validate(nested)]
53    pub archive_locations: ArchiveLocations,
54    // #[serde(skip_serializing_if = "Option::is_none")]
55    // pub scn_policies: Option<ScnPolicies>,
56    // #[serde(skip_serializing_if = "Option::is_none")]
57    // pub version_info: Option<VersionInfo>,
58    #[validate(nested)]
59    pub doi_data: DoiData,
60    #[serde(skip_serializing_if = "CitationList::is_empty")]
61    #[validate(nested)]
62    pub citation_list: CitationList,
63    #[serde(skip_serializing_if = "ComponentList::is_empty")]
64    #[validate(nested)]
65    pub component_list: ComponentList,
66}
67
68#[derive(Debug, Clone, Default, Serialize, Validate)]
69pub struct JatsP {
70    #[serde(rename = "jats:p")]
71    // #[serde(skip_serializing_if = "Option::is_none")]
72    // TODO: needs jats:p in there
73    pub value: String,
74}
75
76#[derive(Debug, Clone, Default, Serialize, Validate)]
77pub struct Pages {
78    #[validate(length(min = 1, max = 32))]
79    pub first_page: String,
80    #[validate(length(min = 1, max = 32))]
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub last_page: Option<String>,
83    #[validate(length(min = 1, max = 100))]
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub other_pages: Option<String>,
86}
87
88#[derive(Debug, Clone, Default, Serialize, Validate)]
89pub struct PublisherItem {
90    #[serde(skip_serializing_if = "Vec::is_empty")]
91    #[validate(length(max = 3))]
92    pub item_number: Vec<ItemNumberT>,
93    #[serde(skip_serializing_if = "Vec::is_empty")]
94    #[validate(length(max = 10))]
95    pub identifier: Vec<IdentifierT>,
96}
97
98#[derive(Debug, Clone, Default, Serialize, Validate)]
99pub struct ItemNumberT {
100    #[validate(length(min = 1, max = 32))]
101    pub value: String,
102}
103
104#[derive(Debug, Clone, Serialize, Validate)]
105pub struct IdentifierT {
106    #[serde(rename = "@id_type")]
107    pub id_type: IdType,
108    #[validate(length(min = 1, max = 255))]
109    #[serde(rename = "$text")]
110    pub value: String,
111}
112
113#[derive(Debug, Clone, Default, Serialize, Validate)]
114pub struct Crossmark {
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub crossmark_version: Option<String>,
117    #[validate(length(min = 6, max = 2048), regex(path = *DOI_REGEX))]
118    pub crossmark_policy: String,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    // pub crossmark_domains: Vec<CrossmarkDomain>
121    pub crossmark_domain_exclusive: Option<bool>,
122    #[serde(skip_serializing_if = "Updates::is_empty")]
123    #[validate(nested)]
124    pub updates: Updates,
125    // pub custom_metadata: CustomMetadata
126}
127
128#[derive(Debug, Clone, Default, Serialize, Validate)]
129pub struct Updates {
130    #[validate(length(min = 1))]
131    pub update: Vec<Update>,
132}
133
134impl Updates {
135    pub fn is_empty(&self) -> bool {
136        self.update.is_empty()
137    }
138}
139
140#[derive(Debug, Clone, Serialize, Validate)]
141pub struct Update {
142    #[serde(rename = "@type")]
143    pub update_type: UpdateType,
144    #[serde(rename = "@date")]
145    pub update_date: NaiveDate,
146    #[validate(length(min = 6, max = 2048), regex(path = *DOI_REGEX))]
147    #[serde(rename = "$text")]
148    pub value: String,
149}
150
151#[derive(Debug, Clone, Default, Serialize, Validate)]
152pub struct CitationList {
153    #[serde(rename = "$text")]
154    pub value: Vec<Citation>,
155}
156
157impl CitationList {
158    pub fn is_empty(&self) -> bool {
159        self.value.is_empty()
160    }
161}
162
163#[derive(Debug, Clone, Default, Serialize, Validate)]
164pub struct Citation {
165    #[serde(skip_serializing_if = "Option::is_none")]
166    #[validate(length(min = 6, max = 2048), regex(path = *DOI_REGEX))]
167    pub doi: Option<String>,
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub elocation_id: Option<String>,
170}
171
172#[derive(Debug, Clone, Default, Serialize, Validate)]
173pub struct ComponentList {
174    #[serde(rename = "component")]
175    pub value: Vec<Component>,
176}
177
178impl ComponentList {
179    pub fn is_empty(&self) -> bool {
180        self.value.is_empty()
181    }
182}
183
184#[derive(Debug, Clone, Default, Serialize, Validate)]
185#[serde(rename = "component")]
186pub struct Component {
187    #[serde(rename = "@parent_relation")]
188    pub parent_relation: ParentRelation,
189    // @reg-agency
190    #[serde(rename = "@language")]
191    pub language: Iso639_1,
192    // @component_size
193    #[serde(skip_serializing_if = "Option::is_none")]
194    #[validate(nested)]
195    pub titles: Option<Titles>,
196    #[serde(default, skip_serializing_if = "Vec::is_empty")]
197    #[validate(nested)]
198    pub contributors: Vec<Contributor>,
199    #[serde(skip_serializing_if = "Option::is_none")]
200    #[validate(nested)]
201    pub publication_date: Option<PublicationDate>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    #[validate(nested)]
204    pub description: Option<Description>,
205    // format
206    // ai:program
207    #[validate(nested)]
208    pub doi_data: DoiData,
209    // #[validate(length(min = 6, max = 2048), regex(path = *DOI_REGEX))]
210    // pub doi: String,
211}
212
213#[derive(Debug, Clone, Default, Serialize, Validate)]
214pub struct Description {
215    #[serde(rename = "@language")]
216    pub language: Iso639_1,
217    #[serde(rename = "$text")]
218    pub value: String,
219}
220
221#[cfg(test)]
222mod unit {
223    use super::*;
224
225    mod description {
226        use super::*;
227
228        fn valid_description() -> Description {
229            Description {
230                language: Iso639_1::En,
231                value: "Example description".to_string(),
232            }
233        }
234
235        #[test]
236        fn valid_description_passes() {
237            let desc = valid_description();
238            assert!(desc.validate().is_ok());
239        }
240    }
241
242    mod component {
243        use super::*;
244        use crate::enums::ContentVersion;
245        use crate::journal::metadata::Resource;
246
247        fn valid_component() -> Component {
248            Component {
249                parent_relation: ParentRelation::IsPartOf,
250                language: Iso639_1::En,
251                titles: None,
252                contributors: vec![],
253                publication_date: None,
254                description: None,
255                doi_data: DoiData {
256                    doi: "10.1234/component.001".to_string(),
257                    timestamp: (),
258                    resource: Resource {
259                        value: "https://example.com/component".to_string(),
260                        mime_type: None,
261                        content_version: ContentVersion::Vor,
262                    },
263                },
264            }
265        }
266
267        #[test]
268        fn valid_component_passes() {
269            let comp = valid_component();
270            assert!(comp.validate().is_ok());
271        }
272
273        #[test]
274        fn with_description_passes() {
275            let mut comp = valid_component();
276            comp.description = Some(Description {
277                language: Iso639_1::En,
278                value: "Component description".to_string(),
279            });
280            assert!(comp.validate().is_ok());
281        }
282
283        #[test]
284        fn invalid_doi_data_fails() {
285            let mut comp = valid_component();
286            comp.doi_data.doi = "invalid".to_string();
287            assert!(comp.validate().is_err());
288        }
289    }
290
291    mod component_list {
292        use super::*;
293        use crate::enums::ContentVersion;
294        use crate::journal::metadata::Resource;
295
296        fn valid_component() -> Component {
297            Component {
298                parent_relation: ParentRelation::IsPartOf,
299                language: Iso639_1::En,
300                titles: None,
301                contributors: vec![Contributor {
302                    organization: vec![],
303                    person_name: vec![],
304                    anonymous: vec![],
305                }],
306                publication_date: None,
307                description: None,
308                doi_data: DoiData {
309                    doi: "10.1234/component".to_string(),
310                    timestamp: (),
311                    resource: Resource {
312                        value: "https://example.com/component".to_string(),
313                        mime_type: None,
314                        content_version: ContentVersion::Vor,
315                    },
316                },
317            }
318        }
319
320        #[test]
321        fn empty_component_list_passes() {
322            let list = ComponentList::default();
323            assert!(list.validate().is_ok());
324            assert!(list.is_empty());
325        }
326
327        #[test]
328        fn component_list_with_components_passes() {
329            let list = ComponentList {
330                value: vec![valid_component()],
331            };
332            assert!(list.validate().is_ok());
333            assert!(!list.is_empty());
334        }
335
336        #[test]
337        fn multiple_components_pass() {
338            let list = ComponentList {
339                value: vec![valid_component(), valid_component()],
340            };
341            assert!(list.validate().is_ok());
342        }
343    }
344
345    mod citation {
346        use super::*;
347
348        #[test]
349        fn citation_with_doi_passes() {
350            let citation = Citation {
351                doi: Some("10.1234/citation.001".to_string()),
352                elocation_id: None,
353            };
354            assert!(citation.validate().is_ok());
355        }
356
357        #[test]
358        fn citation_with_elocation_id_passes() {
359            let citation = Citation {
360                doi: None,
361                elocation_id: Some("e123456".to_string()),
362            };
363            assert!(citation.validate().is_ok());
364        }
365
366        #[test]
367        fn citation_with_both_passes() {
368            let citation = Citation {
369                doi: Some("10.1234/citation.001".to_string()),
370                elocation_id: Some("e123456".to_string()),
371            };
372            assert!(citation.validate().is_ok());
373        }
374
375        #[test]
376        fn citation_with_invalid_doi_fails() {
377            let citation = Citation {
378                doi: Some("invalid".to_string()),
379                elocation_id: None,
380            };
381            assert!(citation.validate().is_err());
382        }
383
384        #[test]
385        fn empty_citation_passes() {
386            let citation = Citation::default();
387            assert!(citation.validate().is_ok());
388        }
389    }
390
391    mod citation_list {
392        use super::*;
393
394        #[test]
395        fn empty_citation_list_passes() {
396            let list = CitationList::default();
397            assert!(list.validate().is_ok());
398            assert!(list.is_empty());
399        }
400
401        #[test]
402        fn citation_list_with_citations_passes() {
403            let list = CitationList {
404                value: vec![Citation {
405                    doi: Some("10.1234/ref.001".to_string()),
406                    elocation_id: None,
407                }],
408            };
409            assert!(list.validate().is_ok());
410            assert!(!list.is_empty());
411        }
412
413        #[test]
414        fn multiple_citations_pass() {
415            let list = CitationList {
416                value: vec![
417                    Citation {
418                        doi: Some("10.1234/ref.001".to_string()),
419                        elocation_id: None,
420                    },
421                    Citation {
422                        doi: Some("10.1234/ref.002".to_string()),
423                        elocation_id: Some("e123".to_string()),
424                    },
425                ],
426            };
427            assert!(list.validate().is_ok());
428        }
429    }
430
431    mod update {
432        use super::*;
433        use chrono::NaiveDate;
434
435        fn valid_update() -> Update {
436            Update {
437                update_type: UpdateType::Correction,
438                update_date: NaiveDate::from_ymd_opt(2024, 6, 15).unwrap(),
439                value: "10.1234/update.001".to_string(),
440            }
441        }
442
443        #[test]
444        fn valid_update_passes() {
445            let update = valid_update();
446            assert!(update.validate().is_ok());
447        }
448
449        #[test]
450        fn value_invalid_doi_fails() {
451            let mut update = valid_update();
452            update.value = "invalid".to_string();
453            assert!(update.validate().is_err());
454        }
455
456        #[test]
457        fn various_update_types_pass() {
458            let types = [
459                UpdateType::Addendum,
460                UpdateType::Correction,
461                UpdateType::Corrigendum,
462                UpdateType::Erratum,
463                UpdateType::ExpressionOfConcern,
464                UpdateType::NewEdition,
465                UpdateType::NewVersion,
466                UpdateType::PartialRetraction,
467                UpdateType::Removal,
468                UpdateType::Retraction,
469                UpdateType::Withdrawal,
470            ];
471            for update_type in types {
472                let mut update = valid_update();
473                update.update_type = update_type;
474                assert!(update.validate().is_ok());
475            }
476        }
477    }
478
479    mod updates {
480        use super::*;
481        use chrono::NaiveDate;
482
483        fn valid_update() -> Update {
484            Update {
485                update_type: UpdateType::Correction,
486                update_date: NaiveDate::from_ymd_opt(2024, 6, 15).unwrap(),
487                value: "10.1234/update.001".to_string(),
488            }
489        }
490
491        #[test]
492        fn updates_with_one_update_passes() {
493            let updates = Updates {
494                update: vec![valid_update()],
495            };
496            assert!(updates.validate().is_ok());
497            assert!(!updates.is_empty());
498        }
499
500        #[test]
501        fn updates_with_multiple_updates_passes() {
502            let updates = Updates {
503                update: vec![valid_update(), valid_update()],
504            };
505            assert!(updates.validate().is_ok());
506        }
507
508        #[test]
509        fn empty_updates_fails() {
510            let updates = Updates { update: vec![] };
511            assert!(updates.validate().is_err());
512        }
513    }
514
515    mod crossmark {
516        use super::*;
517
518        fn valid_crossmark() -> Crossmark {
519            Crossmark {
520                crossmark_version: Some("1".to_string()),
521                crossmark_policy: "10.1234/policy".to_string(),
522                crossmark_domain_exclusive: Some(true),
523                updates: Updates::default(),
524            }
525        }
526
527        #[test]
528        fn valid_crossmark_passes() {
529            let mut crossmark = valid_crossmark();
530            // Need at least one update for Updates to be valid
531            crossmark.updates = Updates {
532                update: vec![Update {
533                    update_type: UpdateType::Correction,
534                    update_date: chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap(),
535                    value: "10.1234/update".to_string(),
536                }],
537            };
538            assert!(crossmark.validate().is_ok());
539        }
540
541        #[test]
542        fn crossmark_policy_invalid_doi_fails() {
543            let mut crossmark = valid_crossmark();
544            crossmark.crossmark_policy = "invalid".to_string();
545            assert!(crossmark.validate().is_err());
546        }
547    }
548
549    mod identifier_t {
550        use super::*;
551
552        fn valid_identifier() -> IdentifierT {
553            IdentifierT {
554                id_type: IdType::Pii,
555                value: "ABC123".to_string(),
556            }
557        }
558
559        #[test]
560        fn valid_identifier_passes() {
561            let id = valid_identifier();
562            assert!(id.validate().is_ok());
563        }
564
565        #[test]
566        fn various_id_types_pass() {
567            let types = [
568                IdType::Dai,
569                IdType::StdDesignation,
570                IdType::Doi,
571                IdType::Pii,
572                IdType::Pmid,
573                IdType::Sici,
574            ];
575            for id_type in types {
576                let mut id = valid_identifier();
577                id.id_type = id_type;
578                assert!(id.validate().is_ok());
579            }
580        }
581    }
582
583    mod item_number_t {
584        use super::*;
585
586        fn valid_item_number() -> ItemNumberT {
587            ItemNumberT {
588                value: "ITEM-001".to_string(),
589            }
590        }
591
592        #[test]
593        fn valid_item_number_passes() {
594            let item = valid_item_number();
595            assert!(item.validate().is_ok());
596        }
597    }
598
599    mod publisher_item {
600        use super::*;
601
602        fn valid_publisher_item() -> PublisherItem {
603            PublisherItem {
604                item_number: vec![ItemNumberT {
605                    value: "ITEM-001".to_string(),
606                }],
607                identifier: vec![IdentifierT {
608                    id_type: IdType::Pii,
609                    value: "ABC123".to_string(),
610                }],
611            }
612        }
613
614        #[test]
615        fn valid_publisher_item_passes() {
616            let item = valid_publisher_item();
617            assert!(item.validate().is_ok());
618        }
619
620        #[test]
621        fn empty_publisher_item_passes() {
622            let item = PublisherItem::default();
623            assert!(item.validate().is_ok());
624        }
625    }
626
627    mod pages {
628        use super::*;
629
630        fn valid_pages() -> Pages {
631            Pages {
632                first_page: "1".to_string(),
633                last_page: Some("10".to_string()),
634                other_pages: None,
635            }
636        }
637
638        #[test]
639        fn valid_pages_passes() {
640            let pages = valid_pages();
641            assert!(pages.validate().is_ok());
642        }
643
644        #[test]
645        fn last_page_none_passes() {
646            let mut pages = valid_pages();
647            pages.last_page = None;
648            assert!(pages.validate().is_ok());
649        }
650
651        #[test]
652        fn other_pages_none_passes() {
653            let mut pages = valid_pages();
654            pages.other_pages = None;
655            assert!(pages.validate().is_ok());
656        }
657
658        #[test]
659        fn only_first_page_passes() {
660            let pages = Pages {
661                first_page: "1".to_string(),
662                last_page: None,
663                other_pages: None,
664            };
665            assert!(pages.validate().is_ok());
666        }
667    }
668
669    mod journal_article {
670        use super::*;
671        use crate::enums::{ContentVersion, MediaTypeDate};
672        use crate::journal::metadata::Resource;
673
674        fn valid_journal_article() -> JournalArticle {
675            JournalArticle {
676                publication_type: PublicationType::FullText,
677                language: Iso639_1::En,
678                titles: Titles {
679                    title: "Example Article".to_string(),
680                    subtitle: None,
681                    original_language_title: None,
682                    orginal_language_subtitle: None,
683                },
684                contributors: vec![],
685                jats_abstract: Some(JatsP {
686                    value: "Abstract text".to_string(),
687                }),
688                publication_date: PublicationDate {
689                    media_type: MediaTypeDate::Online,
690                    month: Some(6),
691                    day: Some(15),
692                    year: 2024,
693                },
694                acceptance_date: None,
695                pages: None,
696                publisher_item: None,
697                crossmark: None,
698                archive_locations: ArchiveLocations::default(),
699                doi_data: DoiData {
700                    doi: "10.1234/article.001".to_string(),
701                    timestamp: (),
702                    resource: Resource {
703                        value: "https://example.com/article".to_string(),
704                        mime_type: None,
705                        content_version: ContentVersion::Vor,
706                    },
707                },
708                citation_list: CitationList::default(),
709                component_list: ComponentList::default(),
710            }
711        }
712
713        #[test]
714        fn valid_journal_article_passes() {
715            let article = valid_journal_article();
716            assert!(article.validate().is_ok());
717        }
718
719        #[test]
720        fn with_pages_passes() {
721            let mut article = valid_journal_article();
722            article.pages = Some(Pages {
723                first_page: "1".to_string(),
724                last_page: Some("10".to_string()),
725                other_pages: None,
726            });
727            assert!(article.validate().is_ok());
728        }
729
730        #[test]
731        fn with_invalid_pages_fails() {
732            let mut article = valid_journal_article();
733            article.pages = Some(Pages {
734                first_page: "".to_string(),
735                last_page: None,
736                other_pages: None,
737            });
738            assert!(article.validate().is_err());
739        }
740
741        #[test]
742        fn with_publisher_item_passes() {
743            let mut article = valid_journal_article();
744            article.publisher_item = Some(PublisherItem {
745                item_number: vec![ItemNumberT {
746                    value: "ITEM-001".to_string(),
747                }],
748                identifier: vec![],
749            });
750            assert!(article.validate().is_ok());
751        }
752
753        #[test]
754        fn with_acceptance_date_passes() {
755            let mut article = valid_journal_article();
756            article.acceptance_date = Some(PublicationDate {
757                media_type: MediaTypeDate::Online,
758                month: Some(5),
759                day: Some(1),
760                year: 2024,
761            });
762            assert!(article.validate().is_ok());
763        }
764
765        #[test]
766        fn with_invalid_acceptance_date_fails() {
767            let mut article = valid_journal_article();
768            article.acceptance_date = Some(PublicationDate {
769                media_type: MediaTypeDate::Online,
770                month: None,
771                day: None,
772                year: 1000,
773            });
774            assert!(article.validate().is_err());
775        }
776
777        #[test]
778        fn with_citations_passes() {
779            let mut article = valid_journal_article();
780            article.citation_list = CitationList {
781                value: vec![Citation {
782                    doi: Some("10.1234/ref.001".to_string()),
783                    elocation_id: None,
784                }],
785            };
786            assert!(article.validate().is_ok());
787        }
788
789        #[test]
790        fn with_components_passes() {
791            let mut article = valid_journal_article();
792            article.component_list = ComponentList {
793                value: vec![Component {
794                    parent_relation: ParentRelation::IsPartOf,
795                    language: Iso639_1::En,
796                    titles: None,
797                    contributors: vec![],
798                    publication_date: None,
799                    description: None,
800                    doi_data: DoiData {
801                        doi: "10.1234/comp".to_string(),
802                        timestamp: (),
803                        resource: Resource {
804                            value: "https://example.com/comp".to_string(),
805                            mime_type: None,
806                            content_version: ContentVersion::Vor,
807                        },
808                    },
809                }],
810            };
811            assert!(article.validate().is_ok());
812        }
813
814        #[test]
815        fn minimal_article_passes() {
816            let article = JournalArticle {
817                publication_type: PublicationType::FullText,
818                language: Iso639_1::En,
819                titles: Titles {
820                    title: "T".to_string(),
821                    subtitle: None,
822                    original_language_title: None,
823                    orginal_language_subtitle: None,
824                },
825                contributors: vec![],
826                jats_abstract: None,
827                publication_date: PublicationDate {
828                    media_type: MediaTypeDate::Online,
829                    month: None,
830                    day: None,
831                    year: 2024,
832                },
833                acceptance_date: None,
834                pages: None,
835                publisher_item: None,
836                crossmark: None,
837                archive_locations: ArchiveLocations::default(),
838                doi_data: DoiData {
839                    doi: "10.1234/a".to_string(),
840                    timestamp: (),
841                    resource: Resource {
842                        value: "https://example.com".to_string(),
843                        mime_type: None,
844                        content_version: ContentVersion::Vor,
845                    },
846                },
847                citation_list: CitationList::default(),
848                component_list: ComponentList::default(),
849            };
850            assert!(article.validate().is_ok());
851        }
852    }
853}