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)]
#[non_exhaustive]
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>,
}
impl<'a> ColumnDraft<'a> {
#[must_use]
pub fn new() -> Self {
Self {
identifier: None,
name: None,
description: None,
unit: None,
reference_path: None,
}
}
#[must_use]
pub fn identifier(mut self, value: &'a str) -> Self {
self.identifier = Some(value);
self
}
#[must_use]
pub fn name(mut self, value: &'a str) -> Self {
self.name = Some(value);
self
}
#[must_use]
pub fn description(mut self, value: &'a str) -> Self {
self.description = Some(value);
self
}
#[must_use]
pub fn unit(mut self, value: EntityId) -> Self {
self.unit = Some(value);
self
}
#[must_use]
pub fn reference_path(mut self, value: EntityId) -> Self {
self.reference_path = Some(value);
self
}
}
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)))
}