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)]
37pub struct ColumnDraft<'a> {
38 pub identifier: Option<&'a str>,
40 pub name: Option<&'a str>,
42 pub description: Option<&'a str>,
44 pub unit: Option<EntityId>,
46 pub reference_path: Option<EntityId>,
51}
52
53fn optional_text(value: Option<&str>) -> Value {
54 value.map_or(Value::Null, |text| Value::Text(text.into()))
55}
56
57pub fn add_table_column(tx: &mut Transaction, draft: ColumnDraft<'_>) -> TabularResult<EntityId> {
67 if draft.identifier.is_some_and(|id| id.trim().is_empty()) {
68 return Err(TabularError::BlankRequired {
69 entity: COLUMN,
70 attribute: "Identifier",
71 });
72 }
73 let attributes = vec![
74 optional_text(draft.identifier),
75 optional_text(draft.name),
76 optional_text(draft.description),
77 draft.unit.map_or(Value::Null, Value::Ref),
78 draft.reference_path.map_or(Value::Null, Value::Ref),
79 ];
80 Ok(tx.create(Entity::new(COLUMN, attributes)))
81}
82
83pub type StagedRow = (EntityId, usize, bool);
90
91pub fn add_table(
105 tx: &mut Transaction,
106 name: Option<&str>,
107 rows: &[StagedRow],
108 columns: &[EntityId],
109) -> TabularResult<EntityId> {
110 if let Some((_, first_width, _)) = rows.first() {
111 for (index, (_, width, _)) in rows.iter().enumerate() {
112 if width != first_width {
113 return Err(TabularError::RaggedRow {
114 entity: TABLE,
115 row: index,
116 expected: *first_width,
117 found: *width,
118 });
119 }
120 }
121 }
122 let headings = rows.iter().filter(|(_, _, heading)| *heading).count();
123 if headings > 1 {
124 return Err(TabularError::TooManyHeadings {
125 entity: TABLE,
126 found: headings,
127 });
128 }
129 let refs = |ids: &[EntityId]| {
130 if ids.is_empty() {
131 Value::Null
132 } else {
133 Value::List(ids.iter().copied().map(Value::Ref).collect())
134 }
135 };
136 let row_ids: Vec<EntityId> = rows.iter().map(|(id, _, _)| *id).collect();
137 let attributes = vec![optional_text(name), refs(&row_ids), refs(columns)];
138 Ok(tx.create(Entity::new(TABLE, attributes)))
139}