use std::sync::Arc;
use ifc_model::{Entity, EntityId, Model, Value};
use ifc_schema::{for_version, SchemaVersion};
use crate::error::PropertyAnomaly;
use crate::nesting::Nesting;
use crate::quantity::set::{Quantity, QuantityKind};
use crate::unit::unit_type;
const DESCRIPTION: usize = 1;
const UNIT: usize = 2;
const VALUE: usize = 3;
const FORMULA: usize = 4;
#[derive(Debug, Clone, Copy)]
pub(super) struct ValueSlots {
value: usize,
formula: usize,
}
pub(super) fn value_slots(model: &Model, kind: QuantityKind) -> Option<ValueSlots> {
if kind != QuantityKind::Number {
return Some(ValueSlots {
value: VALUE,
formula: FORMULA,
});
}
let [token] = model.header().schema.as_slice() else {
return None;
};
let schema = for_version(SchemaVersion::from_header_token(token)?)?;
let names = schema.attribute_names("IFCQUANTITYNUMBER");
let slot = |name: &str| names.iter().position(|n| n.eq_ignore_ascii_case(name));
Some(ValueSlots {
value: slot("NumberValue")?,
formula: slot("Formula")?,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum UnresolvedValue {
Missing,
NotNumeric {
found: String,
},
}
pub(super) fn read_simple(
model: &Model,
id: EntityId,
entity: &Entity,
kind: QuantityKind,
slots: ValueSlots,
name: Option<Arc<str>>,
nesting: &mut Nesting<'_>,
) -> Quantity {
let description = entity.attributes.get(DESCRIPTION).and_then(text);
let formula = entity.attributes.get(slots.formula).and_then(text);
let unit = entity.attributes.get(UNIT).and_then(one_ref);
let value = match entity.attributes.get(slots.value) {
None | Some(Value::Null) => Err(UnresolvedValue::Missing),
Some(stated) => stated
.unwrap_typed()
.as_f64()
.ok_or_else(|| UnresolvedValue::NotNumeric {
found: format!("{stated:?}"),
}),
};
match &value {
Err(UnresolvedValue::Missing) => {
nesting.report(PropertyAnomaly::QuantityValueMissing { quantity: id });
}
Err(UnresolvedValue::NotNumeric { found }) => {
nesting.report(PropertyAnomaly::QuantityValueNotNumeric {
quantity: id,
found: found.clone(),
});
}
Ok(value) if *value < 0.0 && kind.requires_non_negative() => {
nesting.report(PropertyAnomaly::NegativeQuantity {
quantity: id,
value: *value,
})
}
Ok(_) => {}
}
if let (Some(unit_id), Some(expected)) = (unit, kind.required_unit()) {
if let Some(found) = unit_type(model, unit_id) {
if &*found != expected {
nesting.report(PropertyAnomaly::QuantityUnitMismatch {
quantity: id,
unit: unit_id,
expected,
found: found.to_string(),
});
}
}
}
match value {
Ok(value) => Quantity::Simple {
id,
name,
description,
kind,
value,
unit,
formula,
},
Err(reason) => Quantity::Unresolved {
id,
name,
description,
kind,
unit,
formula,
reason,
},
}
}
pub(super) fn text(value: &Value) -> Option<Arc<str>> {
match value.unwrap_typed() {
Value::Text(t) => Some(t.clone()),
_ => None,
}
}
fn one_ref(value: &Value) -> Option<EntityId> {
match value.unwrap_typed() {
Value::Ref(id) => Some(*id),
_ => None,
}
}