mod projection;
pub use projection::LossReport;
use crate::generated::model::StepModel;
use crate::generated::profile::{ApdInfo, SchemaProfile, SchemaTarget};
use crate::generated::write::Writer;
const UNIVERSAL_FILE_SCHEMA: &[&str] = &["STEPIO_UNIVERSAL"];
static UNIVERSAL_APD: ApdInfo = ApdInfo {
status: "not a standard",
name: "stepio_universal",
year: 0,
description: "step-io universal union (non-standard, all-AP superset)",
};
#[doc(hidden)]
#[must_use]
pub fn write_universal(model: &mut StepModel) -> String {
write_target(model, SchemaTarget::Universal).0
}
#[doc(hidden)]
#[must_use]
pub fn write_target(model: &mut StepModel, target: SchemaTarget) -> (String, LossReport) {
write_target_with_header(model, target, &FileHeader::default())
}
#[doc(hidden)]
#[must_use]
pub fn write_target_with_header(
model: &mut StepModel,
target: SchemaTarget,
header: &FileHeader,
) -> (String, LossReport) {
let profile = SchemaProfile::for_target(target);
stamp_header(model, profile.map_or(&UNIVERSAL_APD, |p| &p.apd));
let file_schema = profile.map_or(UNIVERSAL_FILE_SCHEMA, |p| p.file_schema);
let (body, loss) = match profile {
Some(profile) => {
let w = Writer::new(&*model);
let plan = projection::plan(&w, profile);
(w.emit_all_with_plan(&plan.dropped, plan.rename), plan.loss)
}
None => (Writer::new(&*model).emit_all(), LossReport::default()),
};
(wrap_envelope(&body, file_schema, header), loss)
}
fn stamp_header(model: &mut StepModel, apd: &ApdInfo) {
for x in &mut model.application_protocol_definition_arena.items {
x.status = apd.status.to_string();
x.application_interpreted_model_schema_name = apd.name.to_string();
x.application_protocol_year = apd.year;
}
for ac in &mut model.application_context_arena.items {
ac.application = apd.description.to_string();
}
}
#[derive(Debug, Clone, Default)]
pub struct FileHeader {
pub description: String,
pub file_name: String,
pub time_stamp: String,
pub authors: Vec<String>,
pub organizations: Vec<String>,
pub preprocessor_version: String,
pub originating_system: String,
pub authorisation: String,
}
fn header_str(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('\'');
for ch in s.chars() {
if ch == '\'' {
out.push('\'');
}
out.push(ch);
}
out.push('\'');
out
}
fn header_list(items: &[String]) -> String {
if items.is_empty() {
return "('')".to_owned();
}
let inner = items
.iter()
.map(|s| header_str(s))
.collect::<Vec<_>>()
.join(",");
format!("({inner})")
}
fn wrap_envelope(data_body: &str, file_schema: &[&str], header: &FileHeader) -> String {
let schema = file_schema
.iter()
.map(|s| format!("'{s}'"))
.collect::<Vec<_>>()
.join(",");
format!(
"ISO-10303-21;\nHEADER;\nFILE_DESCRIPTION(({desc}),'2;1');\n\
FILE_NAME({name},{stamp},{authors},{orgs},{pre},{orig},{auth});\n\
FILE_SCHEMA(({schema}));\nENDSEC;\nDATA;\n{data_body}ENDSEC;\nEND-ISO-10303-21;\n",
desc = header_str(&header.description),
name = header_str(&header.file_name),
stamp = header_str(&header.time_stamp),
authors = header_list(&header.authors),
orgs = header_list(&header.organizations),
pre = header_str(&header.preprocessor_version),
orig = header_str(&header.originating_system),
auth = header_str(&header.authorisation),
)
}