use std::io::{self, Write};
use crate::{for_version, Schema, SchemaVersion};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StructuralCatalogSummary {
pub entity_rows: usize,
pub type_count: usize,
}
pub fn write_structural_catalog(
version: SchemaVersion,
mut output: impl Write,
) -> io::Result<StructuralCatalogSummary> {
let schema = bundled(version)?;
let entity_count = schema.entity_count();
let type_count = schema.type_count();
assert_eq!(entity_count, version.expected_entity_count());
assert_eq!(type_count, version.expected_type_count());
writeln!(output, "# openbim.ifc structural-catalog v1")?;
writeln!(
output,
"schema\t{}\t{}\t{}",
version.release_id(),
entity_count,
type_count
)?;
let mut names = schema.entity_names().collect::<Vec<_>>();
names.sort_unstable();
for name in &names {
assert_catalog_token(name);
let supertypes = schema.supertypes(name);
let attributes = schema.attribute_names(name);
for value in supertypes.iter().chain(attributes.iter()) {
assert_catalog_token(value);
}
writeln!(
output,
"entity\t{}\t{}\t{}",
name,
joined_or_dash(&supertypes),
joined_or_dash(&attributes)
)?;
}
Ok(StructuralCatalogSummary {
entity_rows: names.len(),
type_count,
})
}
pub fn write_direct_structural_catalog(
version: SchemaVersion,
mut output: impl Write,
) -> io::Result<StructuralCatalogSummary> {
let schema = bundled(version)?;
let entity_count = schema.entity_count();
let type_count = schema.type_count();
assert_eq!(entity_count, version.expected_entity_count());
assert_eq!(type_count, version.expected_type_count());
writeln!(output, "# openbim.ifc direct-structural-catalog v1")?;
writeln!(
output,
"schema\t{}\t{}\t{}",
version.release_id(),
entity_count,
type_count
)?;
let mut names = schema.entity_names().collect::<Vec<_>>();
names.sort_unstable();
for name in &names {
assert_catalog_token(name);
let supertypes = schema.supertypes(name);
let attributes = schema.attribute_names(name);
let parent = supertypes.first().copied();
let inherited_count = if let Some(parent) = parent {
let parent_supertypes = schema.supertypes(parent);
assert_eq!(supertypes[1..], parent_supertypes);
let parent_attributes = schema.attribute_names(parent);
assert!(
attributes.starts_with(&parent_attributes),
"{name} attributes must begin with all inherited {parent} attributes"
);
parent_attributes.len()
} else {
0
};
let declared = &attributes[inherited_count..];
for value in parent.iter().copied().chain(declared.iter().copied()) {
assert_catalog_token(value);
}
writeln!(
output,
"entity\t{}\t{}\t{}",
name,
parent.unwrap_or("-"),
joined_or_dash(declared)
)?;
}
Ok(StructuralCatalogSummary {
entity_rows: names.len(),
type_count,
})
}
fn bundled(version: SchemaVersion) -> io::Result<&'static Schema> {
for_version(version).map_err(|refused| io::Error::new(io::ErrorKind::Unsupported, refused))
}
fn joined_or_dash(values: &[&str]) -> String {
if values.is_empty() {
"-".to_owned()
} else {
values.join(",")
}
}
fn assert_catalog_token(value: &str) {
assert!(
!value.contains(['\t', '\n', '\r', ',']),
"IFC declaration token contains a structural-catalog delimiter: {value:?}"
);
}