1use ifc_model::{Entity, EntityId, Transaction, Value};
4
5use crate::error::{TabularError, TabularResult};
6
7const TABLE: &str = "IFCTABLE";
8const ROW: &str = "IFCTABLEROW";
9const COLUMN: &str = "IFCTABLECOLUMN";
10
11pub fn add_table_row(
21 tx: &mut Transaction,
22 cells: Vec<Value>,
23 is_heading: bool,
24) -> TabularResult<EntityId> {
25 if cells.is_empty() {
26 return Err(TabularError::EmptyList {
27 entity: ROW,
28 attribute: "RowCells",
29 });
30 }
31 let attributes = vec![Value::List(cells), Value::Bool(is_heading)];
32 Ok(tx.create(Entity::new(ROW, attributes)))
33}
34
35#[derive(Debug, Clone, Copy, Default)]
37#[non_exhaustive]
38pub struct ColumnDraft<'a> {
39 pub identifier: Option<&'a str>,
41 pub name: Option<&'a str>,
43 pub description: Option<&'a str>,
45 pub unit: Option<EntityId>,
47 pub reference_path: Option<EntityId>,
52}
53
54impl<'a> ColumnDraft<'a> {
55 #[must_use]
57 pub fn new() -> Self {
58 Self {
59 identifier: None,
60 name: None,
61 description: None,
62 unit: None,
63 reference_path: None,
64 }
65 }
66
67 #[must_use]
69 pub fn identifier(mut self, value: &'a str) -> Self {
70 self.identifier = Some(value);
71 self
72 }
73
74 #[must_use]
76 pub fn name(mut self, value: &'a str) -> Self {
77 self.name = Some(value);
78 self
79 }
80
81 #[must_use]
83 pub fn description(mut self, value: &'a str) -> Self {
84 self.description = Some(value);
85 self
86 }
87
88 #[must_use]
90 pub fn unit(mut self, value: EntityId) -> Self {
91 self.unit = Some(value);
92 self
93 }
94
95 #[must_use]
97 pub fn reference_path(mut self, value: EntityId) -> Self {
98 self.reference_path = Some(value);
99 self
100 }
101}
102
103fn optional_text(value: Option<&str>) -> Value {
104 value.map_or(Value::Null, |text| Value::Text(text.into()))
105}
106
107pub fn add_table_column(tx: &mut Transaction, draft: ColumnDraft<'_>) -> TabularResult<EntityId> {
117 if draft.identifier.is_some_and(|id| id.trim().is_empty()) {
118 return Err(TabularError::BlankRequired {
119 entity: COLUMN,
120 attribute: "Identifier",
121 });
122 }
123 let attributes = vec![
124 optional_text(draft.identifier),
125 optional_text(draft.name),
126 optional_text(draft.description),
127 draft.unit.map_or(Value::Null, Value::Ref),
128 draft.reference_path.map_or(Value::Null, Value::Ref),
129 ];
130 Ok(tx.create(Entity::new(COLUMN, attributes)))
131}
132
133pub type StagedRow = (EntityId, usize, bool);
140
141pub fn add_table(
155 tx: &mut Transaction,
156 name: Option<&str>,
157 rows: &[StagedRow],
158 columns: &[EntityId],
159) -> TabularResult<EntityId> {
160 if let Some((_, first_width, _)) = rows.first() {
161 for (index, (_, width, _)) in rows.iter().enumerate() {
162 if width != first_width {
163 return Err(TabularError::RaggedRow {
164 entity: TABLE,
165 row: index,
166 expected: *first_width,
167 found: *width,
168 });
169 }
170 }
171 }
172 let headings = rows.iter().filter(|(_, _, heading)| *heading).count();
173 if headings > 1 {
174 return Err(TabularError::TooManyHeadings {
175 entity: TABLE,
176 found: headings,
177 });
178 }
179 let refs = |ids: &[EntityId]| {
180 if ids.is_empty() {
181 Value::Null
182 } else {
183 Value::List(ids.iter().copied().map(Value::Ref).collect())
184 }
185 };
186 let row_ids: Vec<EntityId> = rows.iter().map(|(id, _, _)| *id).collect();
187 let attributes = vec![optional_text(name), refs(&row_ids), refs(columns)];
188 Ok(tx.create(Entity::new(TABLE, attributes)))
189}