1use std::io::{self, Write};
8
9use crate::{for_version, 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(
27 version: SchemaVersion,
28 mut output: impl Write,
29) -> io::Result<StructuralCatalogSummary> {
30 let schema = for_version(version).expect("every SchemaVersion has a bundled schema");
31 let entity_count = schema.entity_count();
32 let type_count = schema.type_count();
33
34 assert_eq!(entity_count, version.expected_entity_count());
35 assert_eq!(type_count, version.expected_type_count());
36
37 writeln!(output, "# openbim.ifc structural-catalog v1")?;
38 writeln!(
39 output,
40 "schema\t{}\t{}\t{}",
41 version.release_id(),
42 entity_count,
43 type_count
44 )?;
45
46 let mut names = schema.entity_names().collect::<Vec<_>>();
47 names.sort_unstable();
48 for name in &names {
49 assert_catalog_token(name);
50 let supertypes = schema.supertypes(name);
51 let attributes = schema.attribute_names(name);
52 for value in supertypes.iter().chain(attributes.iter()) {
53 assert_catalog_token(value);
54 }
55 writeln!(
56 output,
57 "entity\t{}\t{}\t{}",
58 name,
59 joined_or_dash(&supertypes),
60 joined_or_dash(&attributes)
61 )?;
62 }
63
64 Ok(StructuralCatalogSummary {
65 entity_rows: names.len(),
66 type_count,
67 })
68}
69
70pub fn write_direct_structural_catalog(
77 version: SchemaVersion,
78 mut output: impl Write,
79) -> io::Result<StructuralCatalogSummary> {
80 let schema = for_version(version).expect("every SchemaVersion has a bundled schema");
81 let entity_count = schema.entity_count();
82 let type_count = schema.type_count();
83 assert_eq!(entity_count, version.expected_entity_count());
84 assert_eq!(type_count, version.expected_type_count());
85
86 writeln!(output, "# openbim.ifc direct-structural-catalog v1")?;
87 writeln!(
88 output,
89 "schema\t{}\t{}\t{}",
90 version.release_id(),
91 entity_count,
92 type_count
93 )?;
94
95 let mut names = schema.entity_names().collect::<Vec<_>>();
96 names.sort_unstable();
97 for name in &names {
98 assert_catalog_token(name);
99 let supertypes = schema.supertypes(name);
100 let attributes = schema.attribute_names(name);
101 let parent = supertypes.first().copied();
102 let inherited_count = if let Some(parent) = parent {
103 let parent_supertypes = schema.supertypes(parent);
104 assert_eq!(supertypes[1..], parent_supertypes);
105 let parent_attributes = schema.attribute_names(parent);
106 assert!(
107 attributes.starts_with(&parent_attributes),
108 "{name} attributes must begin with all inherited {parent} attributes"
109 );
110 parent_attributes.len()
111 } else {
112 0
113 };
114 let declared = &attributes[inherited_count..];
115 for value in parent.iter().copied().chain(declared.iter().copied()) {
116 assert_catalog_token(value);
117 }
118 writeln!(
119 output,
120 "entity\t{}\t{}\t{}",
121 name,
122 parent.unwrap_or("-"),
123 joined_or_dash(declared)
124 )?;
125 }
126
127 Ok(StructuralCatalogSummary {
128 entity_rows: names.len(),
129 type_count,
130 })
131}
132
133fn joined_or_dash(values: &[&str]) -> String {
134 if values.is_empty() {
135 "-".to_owned()
136 } else {
137 values.join(",")
138 }
139}
140
141fn assert_catalog_token(value: &str) {
142 assert!(
143 !value.contains(['\t', '\n', '\r', ',']),
144 "IFC declaration token contains a structural-catalog delimiter: {value:?}"
145 );
146}