use crate::document::{CifValue, Document};
use crate::lexer::Quoting;
use molframe_core::io::TextOutput;
use std::fmt::{self, Display, Formatter, Write as _};
use std::io::{self, Write as IoWrite};
#[must_use]
pub fn write_preserving(document: &Document) -> String {
let mut out = String::new();
render_preserving(document, &mut out);
out
}
pub fn write_preserving_to<W: IoWrite>(document: &Document, output: &mut W) -> io::Result<()> {
let mut output = TextOutput::new(output);
render_preserving(document, &mut output);
output.finish()
}
fn render_preserving(document: &Document, out: &mut impl fmt::Write) {
for block in document.blocks() {
let _ = writeln!(out, "data_{}", block.name());
for category in block.categories() {
write_category(out, category);
}
}
}
fn write_category(out: &mut impl fmt::Write, category: &crate::document::Category) {
let rows = category.row_count();
if rows == 1 {
for item in category.items() {
let Some(value) = category.value(item, 0) else {
continue;
};
let quoting = category.column(item).and_then(|column| column.quoting(0));
let _ = writeln!(
out,
"_{}.{:<30} {}",
category.name(),
item,
RenderedValue { value, quoting }
);
}
let _ = out.write_str("#\n");
return;
}
let _ = out.write_str("loop_\n");
for item in category.items() {
let _ = writeln!(out, "_{}.{item}", category.name());
}
for row in 0..rows {
let mut first = true;
for item in category.items() {
let Some(value) = category.value(item, row) else {
continue;
};
let quoting = category.column(item).and_then(|column| column.quoting(row));
if !first {
let _ = out.write_char(' ');
}
first = false;
let _ = write!(out, "{}", RenderedValue { value, quoting });
}
let _ = out.write_char('\n');
}
let _ = out.write_str("#\n");
}
struct RenderedValue<'a> {
value: &'a CifValue,
quoting: Option<Quoting>,
}
impl Display for RenderedValue<'_> {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
let text = match self.value {
CifValue::Inapplicable => return formatter.write_char('.'),
CifValue::Unknown => return formatter.write_char('?'),
CifValue::Integer(number) => return Display::fmt(number, formatter),
CifValue::Float(number) => return Display::fmt(number, formatter),
CifValue::Text(text) => text,
};
match self.quoting {
Some(Quoting::Text) => write!(formatter, "\n;{text}\n;"),
Some(Quoting::Double) => write!(formatter, "\"{text}\""),
Some(Quoting::Single) => write!(formatter, "'{text}'"),
_ => Display::fmt(&super::value::quoted(text), formatter),
}
}
}
#[cfg(test)]
#[path = "preserving_tests.rs"]
mod tests;