use std::path::Path;
use lo_core::{ColumnType, DatabaseDocument, DbValue, Result};
use crate::common::{content_root_attrs, package_document, ExtraFile, MIME_ODB};
fn type_name(column_type: &ColumnType) -> &'static str {
match column_type {
ColumnType::Integer => "integer",
ColumnType::Float => "float",
ColumnType::Text => "string",
ColumnType::Bool => "boolean",
}
}
fn value_text(value: &DbValue) -> String {
match value {
DbValue::Null => String::new(),
DbValue::Integer(v) => v.to_string(),
DbValue::Float(v) => v.to_string(),
DbValue::Text(v) => lo_core::escape_text(v),
DbValue::Bool(v) => v.to_string(),
}
}
pub fn serialize_database_document(database: &DatabaseDocument) -> String {
let mut xml = lo_core::XmlBuilder::new();
xml.declaration();
xml.open("office:document-content", &content_root_attrs());
xml.empty("office:scripts", &[]);
xml.empty("office:automatic-styles", &[]);
xml.open("office:body", &[]);
xml.open("office:database", &[]);
for table in &database.tables {
xml.raw(&format!(
"<table:table table:name=\"{}\">",
lo_core::escape_attr(&table.name)
));
xml.raw("<table:table-header-rows><table:table-row>");
for column in &table.columns {
xml.raw(&format!(
"<table:table-cell office:value-type=\"string\"><text:p>{} ({})</text:p></table:table-cell>",
lo_core::escape_text(&column.name),
type_name(&column.column_type)
));
}
xml.raw("</table:table-row></table:table-header-rows>");
for row in &table.rows {
xml.raw("<table:table-row>");
for value in row {
xml.raw(&format!(
"<table:table-cell office:value-type=\"string\"><text:p>{}</text:p></table:table-cell>",
value_text(value)
));
}
xml.raw("</table:table-row>");
}
xml.raw("</table:table>");
}
xml.close();
xml.close();
xml.close();
xml.finish()
}
fn csv_for_table(table: &lo_core::TableData) -> Vec<u8> {
let mut out = String::new();
out.push_str(
&table
.columns
.iter()
.map(|column| column.name.replace('"', "\"\""))
.collect::<Vec<_>>()
.join(","),
);
out.push('\n');
for row in &table.rows {
let cols: Vec<String> = row
.iter()
.map(|value| match value {
DbValue::Null => String::new(),
DbValue::Integer(v) => v.to_string(),
DbValue::Float(v) => v.to_string(),
DbValue::Text(v) => {
if v.contains(',') || v.contains('"') || v.contains('\n') {
format!("\"{}\"", v.replace('"', "\"\""))
} else {
v.clone()
}
}
DbValue::Bool(v) => v.to_string(),
})
.collect();
out.push_str(&cols.join(","));
out.push('\n');
}
out.into_bytes()
}
pub fn save_database_document(path: impl AsRef<Path>, database: &DatabaseDocument) -> Result<()> {
let content = serialize_database_document(database);
let mut extras = Vec::new();
for table in &database.tables {
extras.push(ExtraFile::new(
format!("database/{}.csv", table.name),
"text/csv",
csv_for_table(table),
));
}
package_document(path, MIME_ODB, content, &database.meta, extras)
}