crystal_cif_io/grammar/structures/
loop_struct.rs

1use std::fmt::Display;
2
3use winnow::Parser;
4
5use crate::{
6    data_dict::CifTerm,
7    grammar::{tags_values::Value, SyntacticUnit, Tag},
8};
9
10pub use self::{body::LoopBody, header::LoopHeader};
11
12pub use body::LoopColumn;
13
14mod body;
15mod header;
16
17#[derive(Debug, Clone)]
18pub struct LoopUnit {
19    header: LoopHeader,
20    body: LoopBody,
21}
22
23#[derive(Debug, Clone, Default)]
24pub struct LoopUnitBuilder {
25    value_columns: Option<Vec<LoopColumn>>,
26    column_length: usize,
27}
28
29#[derive(Debug, Clone, Default)]
30pub struct LoopColumns {
31    columns: Vec<LoopColumn>,
32}
33
34impl LoopUnitBuilder {
35    pub fn with_value_columns(mut self, value_columns: Vec<LoopColumn>) -> Self {
36        self.column_length = value_columns[0].as_ref().len();
37        self.value_columns = Some(value_columns);
38        self
39    }
40
41    pub fn build(self) -> LoopUnit {
42        let header = self
43            .value_columns
44            .as_ref()
45            .map(|columns| {
46                columns
47                    .iter()
48                    .map(|c| c.tag().clone())
49                    .collect::<Vec<Tag>>()
50            })
51            .unwrap_or_default();
52        let header = LoopHeader::new(header);
53        let body = LoopBody::from_columns(&self.value_columns.unwrap(), self.column_length);
54        LoopUnit::new(header, body)
55    }
56}
57
58impl LoopUnit {
59    pub fn new(header: LoopHeader, body: LoopBody) -> Self {
60        Self { header, body }
61    }
62
63    pub fn builder() -> LoopUnitBuilder {
64        LoopUnitBuilder::default()
65    }
66
67    pub fn header(&self) -> &LoopHeader {
68        &self.header
69    }
70
71    pub fn find_loop_column_by_tag<T: AsRef<str>>(&self, tag: T) -> Option<LoopColumn> {
72        self.header.get_tag_index(&tag).map(|index| {
73            let values = self
74                .body
75                .nth_column_values(index, self.header.num_of_tags());
76            LoopColumn::new(Tag::new(tag.as_ref().to_string()), values)
77        })
78    }
79}
80
81impl SyntacticUnit for LoopUnit {
82    type ParseResult = Self;
83
84    type FormatOutput = String;
85
86    fn parser(input: &mut &str) -> winnow::prelude::PResult<Self::ParseResult> {
87        (LoopHeader::parser, LoopBody::parser)
88            .map(|(header, body)| LoopUnit::new(header, body))
89            .parse_next(input)
90    }
91
92    fn formatted_output(&self) -> Self::FormatOutput {
93        let body_output = self
94            .body
95            .values()
96            .chunks(self.header.num_of_tags())
97            .map(|chunk| {
98                chunk
99                    .iter()
100                    .map(|v| v.to_string())
101                    .collect::<Vec<String>>()
102                    .join(" ")
103            })
104            .collect::<Vec<String>>()
105            .join("\n");
106        format!("{}{}", self.header, body_output)
107    }
108}
109
110impl Display for LoopUnit {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        write!(f, "{}", self.formatted_output())
113    }
114}
115
116impl LoopColumns {
117    pub fn find_loop_column_by_tag<T: AsRef<str>>(&self, tag: T) -> Option<&LoopColumn> {
118        self.columns
119            .iter()
120            .find(|col| col.tag().as_ref() == tag.as_ref())
121    }
122
123    pub fn find_loop_column_mut_by_tag<T: AsRef<str>>(
124        &mut self,
125        tag: T,
126    ) -> Option<&mut LoopColumn> {
127        self.columns_mut()
128            .iter_mut()
129            .find(|col| col.tag().as_ref() == tag.as_ref())
130    }
131
132    pub fn columns(&self) -> &[LoopColumn] {
133        &self.columns
134    }
135
136    pub fn columns_mut(&mut self) -> &mut Vec<LoopColumn> {
137        &mut self.columns
138    }
139}
140
141impl From<LoopColumns> for LoopUnit {
142    fn from(value: LoopColumns) -> Self {
143        LoopUnit::builder()
144            .with_value_columns(value.columns)
145            .build()
146    }
147}
148
149impl From<&LoopColumns> for LoopUnit {
150    fn from(value: &LoopColumns) -> Self {
151        LoopUnit::builder()
152            .with_value_columns(value.columns.clone())
153            .build()
154    }
155}
156
157impl From<LoopUnit> for LoopColumns {
158    fn from(value: LoopUnit) -> Self {
159        let column_width = value.header().tags().len();
160        LoopColumns {
161            columns: value
162                .header()
163                .tags()
164                .iter()
165                .enumerate()
166                .map(|(i, tag)| {
167                    LoopColumn::new(tag.clone(), value.body.nth_column_values(i, column_width))
168                })
169                .collect::<Vec<LoopColumn>>(),
170        }
171    }
172}
173
174#[cfg(test)]
175mod test {
176    use crate::grammar::SyntacticUnit;
177
178    use super::LoopUnit;
179
180    #[test]
181    fn loop_data_parsing() {
182        let mut input = r#"loop_
183 _atom_type_symbol
184 _atom_type_description
185 _atom_type_scat_dispersion_real
186 _atom_type_scat_dispersion_imag
187 _atom_type_scat_source
188 'C'  'C'   0.0033(9)   0.0000
189 'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
190 'H'  'H'   0.0000   0.0000
191 'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
192 'N'  'N'   0.0061   0.0000
193 'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
194 'O'  'O'   0.0106   0.0000
195 'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
196
197_symmetry_cell_setting            triclinic
198_symmetry_space_group_name_H-M    'P 1'
199_symmetry_space_group_name_Hall   'P 1'
200"#;
201        let parse_result = LoopUnit::parser(&mut input);
202        match parse_result {
203            Ok(l) => {
204                l.body.values().iter().for_each(|v| println!("{v:?}"));
205            }
206            Err(e) => {
207                dbg!(e);
208            }
209        }
210    }
211}