1use super::provenance::Provenance;
8use std::fmt::Write;
9
10impl Provenance {
11 #[must_use]
16 pub fn to_json(&self) -> String {
17 let fields = fields(self);
18 let mut output = String::from("{");
19 for (position, (key, value)) in fields.iter().enumerate() {
20 if position != 0 {
21 output.push(',');
22 }
23 json_string(&mut output, key);
24 output.push(':');
25 json_string(&mut output, value);
26 }
27 output.push('}');
28 output
29 }
30
31 #[must_use]
36 pub fn to_mmcif(&self) -> String {
37 let fields = fields(self);
38 let mut output = String::from(
39 "data_molframe_provenance\n#\nloop_\n_molframe_provenance.key\n_molframe_provenance.value\n",
40 );
41 for (key, value) in fields {
42 cif_value(&mut output, &key);
43 output.push(' ');
44 cif_value(&mut output, &value);
45 output.push('\n');
46 }
47 output.push_str("#\n");
48 output
49 }
50}
51
52fn fields(record: &Provenance) -> Vec<(String, String)> {
53 let policy = &record.policy;
54 let mut result = vec![
55 ("molframe_version", record.molframe_version.to_owned()),
56 ("input_source", record.input_source.to_string()),
57 (
58 "input_fingerprint",
59 optional_display(record.input_fingerprint),
60 ),
61 ("policy_fingerprint", record.policy_fingerprint.to_string()),
62 ("profile", optional_profile(record)),
63 (
64 "schema_version",
65 optional_version(record.schema_version.as_ref()),
66 ),
67 (
68 "component_version",
69 optional_version(record.component_version.as_ref()),
70 ),
71 ("timestamp", optional_text(record.timestamp.as_deref())),
72 ("policy.assembly", format!("{:?}", policy.assembly)),
73 ("policy.model", format!("{:?}", policy.model)),
74 ("policy.altloc", format!("{:?}", policy.altloc)),
75 ("policy.identifiers", format!("{:?}", policy.identifiers)),
76 (
77 "policy.missing_atoms",
78 format!("{:?}", policy.missing_atoms),
79 ),
80 ("policy.hydrogens", format!("{:?}", policy.hydrogens)),
81 (
82 "policy.atom_equivalence",
83 format!("{:?}", policy.atom_equivalence),
84 ),
85 ("policy.symmetry", format!("{:?}", policy.symmetry)),
86 ("policy.alignment", format!("{:?}", policy.alignment)),
87 ("policy.precision", format!("{:?}", policy.precision)),
88 ("policy.periodic", format!("{:?}", policy.periodic)),
89 ("policy.vdw_radii", format!("{:?}", policy.vdw_radii)),
90 ("policy.contact_def", format!("{:?}", policy.contact_def)),
91 (
92 "policy.float_tolerance.relative",
93 policy.float_tolerance.relative.to_string(),
94 ),
95 (
96 "policy.float_tolerance.absolute",
97 policy.float_tolerance.absolute.to_string(),
98 ),
99 ]
100 .into_iter()
101 .map(|(key, value)| (key.to_owned(), value))
102 .collect::<Vec<_>>();
103 if let Some(algorithm) = &record.algorithm {
104 result.push(("algorithm.name".to_owned(), algorithm.name().to_owned()));
105 result.push((
106 "algorithm.version".to_owned(),
107 algorithm.version().to_owned(),
108 ));
109 }
110 result.extend(
111 record
112 .parameters
113 .iter()
114 .map(|(name, value)| (format!("parameter.{name}"), value.serialised())),
115 );
116 result
117}
118
119fn optional_display(value: Option<impl std::fmt::Display>) -> String {
120 match value {
121 Some(value) => value.to_string(),
122 None => ".".to_owned(),
123 }
124}
125
126fn optional_profile(record: &Provenance) -> String {
127 match record.profile {
128 Some(profile) => profile.as_str().to_owned(),
129 None => ".".to_owned(),
130 }
131}
132
133fn optional_version(value: Option<&super::provenance::DictionaryVersion>) -> String {
134 match value {
135 Some(value) => value.as_str().to_owned(),
136 None => ".".to_owned(),
137 }
138}
139
140fn optional_text(value: Option<&str>) -> String {
141 match value {
142 Some(value) => value.to_owned(),
143 None => ".".to_owned(),
144 }
145}
146
147fn json_string(output: &mut String, value: &str) {
148 output.push('"');
149 for character in value.chars() {
150 match character {
151 '"' => output.push_str("\\\""),
152 '\\' => output.push_str("\\\\"),
153 '\n' => output.push_str("\\n"),
154 '\r' => output.push_str("\\r"),
155 '\t' => output.push_str("\\t"),
156 control if control.is_control() => {
157 let _ = write!(output, "\\u{:04x}", u32::from(control));
158 }
159 ordinary => output.push(ordinary),
160 }
161 }
162 output.push('"');
163}
164
165fn cif_value(output: &mut String, value: &str) {
166 if bare_cif(value) {
167 output.push_str(value);
168 } else if !value.contains('\'') && !value.contains('\n') {
169 output.push('\'');
170 output.push_str(value);
171 output.push('\'');
172 } else if !value.contains('"') && !value.contains('\n') {
173 output.push('"');
174 output.push_str(value);
175 output.push('"');
176 } else {
177 output.push_str("\n;");
178 output.push_str(value);
179 output.push_str("\n;");
180 }
181}
182
183fn bare_cif(value: &str) -> bool {
184 !value.is_empty()
185 && value != "."
186 && value != "?"
187 && !value.chars().any(char::is_whitespace)
188 && !value.starts_with(['#', '$', ';', '_', '\'', '"'])
189 && !value.eq_ignore_ascii_case("loop_")
190 && !value.to_ascii_lowercase().starts_with("data_")
191 && !value.to_ascii_lowercase().starts_with("save_")
192}
193
194#[cfg(test)]
195#[path = "serialise_tests.rs"]
196mod tests;