use ifc_model::{Entity, EntityId, Transaction, Value};
use crate::error::{TabularError, TabularResult};
const TABLE: &str = "IFCTABLE";
const ROW: &str = "IFCTABLEROW";
const COLUMN: &str = "IFCTABLECOLUMN";
pub fn add_table_row(
tx: &mut Transaction,
cells: Vec<Value>,
is_heading: bool,
) -> TabularResult<EntityId> {
if cells.is_empty() {
return Err(TabularError::EmptyList {
entity: ROW,
attribute: "RowCells",
});
}
let attributes = vec![Value::List(cells), Value::Bool(is_heading)];
Ok(tx.create(Entity::new(ROW, attributes)))
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ColumnDraft<'a> {
pub identifier: Option<&'a str>,
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub unit: Option<EntityId>,
pub reference_path: Option<EntityId>,
}
fn optional_text(value: Option<&str>) -> Value {
value.map_or(Value::Null, |text| Value::Text(text.into()))
}
pub fn add_table_column(tx: &mut Transaction, draft: ColumnDraft<'_>) -> TabularResult<EntityId> {
if draft.identifier.is_some_and(|id| id.trim().is_empty()) {
return Err(TabularError::BlankRequired {
entity: COLUMN,
attribute: "Identifier",
});
}
let attributes = vec![
optional_text(draft.identifier),
optional_text(draft.name),
optional_text(draft.description),
draft.unit.map_or(Value::Null, Value::Ref),
draft.reference_path.map_or(Value::Null, Value::Ref),
];
Ok(tx.create(Entity::new(COLUMN, attributes)))
}
pub type StagedRow = (EntityId, usize, bool);
pub fn add_table(
tx: &mut Transaction,
name: Option<&str>,
rows: &[StagedRow],
columns: &[EntityId],
) -> TabularResult<EntityId> {
if let Some((_, first_width, _)) = rows.first() {
for (index, (_, width, _)) in rows.iter().enumerate() {
if width != first_width {
return Err(TabularError::RaggedRow {
entity: TABLE,
row: index,
expected: *first_width,
found: *width,
});
}
}
}
let headings = rows.iter().filter(|(_, _, heading)| *heading).count();
if headings > 1 {
return Err(TabularError::TooManyHeadings {
entity: TABLE,
found: headings,
});
}
let refs = |ids: &[EntityId]| {
if ids.is_empty() {
Value::Null
} else {
Value::List(ids.iter().copied().map(Value::Ref).collect())
}
};
let row_ids: Vec<EntityId> = rows.iter().map(|(id, _, _)| *id).collect();
let attributes = vec![optional_text(name), refs(&row_ids), refs(columns)];
Ok(tx.create(Entity::new(TABLE, attributes)))
}