crystal_cif_io/grammar/structures/
loop_struct.rs1use 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}