1use std::io::{self, Write};
8
9use crate::{for_version, Schema, SchemaVersion};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct StructuralCatalogSummary {
14 pub entity_rows: usize,
16 pub type_count: usize,
18}
19
20pub fn write_structural_catalog(
32 version: SchemaVersion,
33 mut output: impl Write,
34) -> io::Result<StructuralCatalogSummary> {
35 let schema = bundled(version)?;
36 let entity_count = schema.entity_count();
37 let type_count = schema.type_count();
38
39 assert_eq!(entity_count, version.expected_entity_count());
40 assert_eq!(type_count, version.expected_type_count());
41
42 writeln!(output, "# openbim.ifc structural-catalog v1")?;
43 writeln!(
44 output,
45 "schema\t{}\t{}\t{}",
46 version.release_id(),
47 entity_count,
48 type_count
49 )?;
50
51 let mut names = schema.entity_names().collect::<Vec<_>>();
52 names.sort_unstable();
53 for name in &names {
54 assert_catalog_token(name);
55 let supertypes = schema.supertypes(name);
56 let attributes = schema.attribute_names(name);
57 for value in supertypes.iter().chain(attributes.iter()) {
58 assert_catalog_token(value);
59 }
60 writeln!(
61 output,
62 "entity\t{}\t{}\t{}",
63 name,
64 joined_or_dash(&supertypes),
65 joined_or_dash(&attributes)
66 )?;
67 }
68
69 Ok(StructuralCatalogSummary {
70 entity_rows: names.len(),
71 type_count,
72 })
73}
74
75pub fn write_direct_structural_catalog(
86 version: SchemaVersion,
87 mut output: impl Write,
88) -> io::Result<StructuralCatalogSummary> {
89 let schema = bundled(version)?;
90 let entity_count = schema.entity_count();
91 let type_count = schema.type_count();
92 assert_eq!(entity_count, version.expected_entity_count());
93 assert_eq!(type_count, version.expected_type_count());
94
95 writeln!(output, "# openbim.ifc direct-structural-catalog v1")?;
96 writeln!(
97 output,
98 "schema\t{}\t{}\t{}",
99 version.release_id(),
100 entity_count,
101 type_count
102 )?;
103
104 let mut names = schema.entity_names().collect::<Vec<_>>();
105 names.sort_unstable();
106 for name in &names {
107 assert_catalog_token(name);
108 let supertypes = schema.supertypes(name);
109 let attributes = schema.attribute_names(name);
110 let parent = supertypes.first().copied();
111 let inherited_count = if let Some(parent) = parent {
112 let parent_supertypes = schema.supertypes(parent);
113 assert_eq!(supertypes[1..], parent_supertypes);
114 let parent_attributes = schema.attribute_names(parent);
115 assert!(
116 attributes.starts_with(&parent_attributes),
117 "{name} attributes must begin with all inherited {parent} attributes"
118 );
119 parent_attributes.len()
120 } else {
121 0
122 };
123 let declared = &attributes[inherited_count..];
124 for value in parent.iter().copied().chain(declared.iter().copied()) {
125 assert_catalog_token(value);
126 }
127 writeln!(
128 output,
129 "entity\t{}\t{}\t{}",
130 name,
131 parent.unwrap_or("-"),
132 joined_or_dash(declared)
133 )?;
134 }
135
136 Ok(StructuralCatalogSummary {
137 entity_rows: names.len(),
138 type_count,
139 })
140}
141
142fn bundled(version: SchemaVersion) -> io::Result<&'static Schema> {
145 for_version(version).map_err(|refused| io::Error::new(io::ErrorKind::Unsupported, refused))
146}
147
148fn joined_or_dash(values: &[&str]) -> String {
149 if values.is_empty() {
150 "-".to_owned()
151 } else {
152 values.join(",")
153 }
154}
155
156fn assert_catalog_token(value: &str) {
157 assert!(
158 !value.contains(['\t', '\n', '\r', ',']),
159 "IFC declaration token contains a structural-catalog delimiter: {value:?}"
160 );
161}