use super::{ReaderContext, has_all_parts, require_part_attrs};
use crate::ir::attr::{check_count, read_entity_ref, read_entity_ref_list, read_enum, read_string};
use crate::ir::error::ConvertError;
use crate::ir::model::{AngleUnit, LengthUnit, SolidAngleUnit, UnitContext};
use crate::parser::entity::{Attribute, RawEntityPart};
impl ReaderContext {
pub(super) fn convert_unit_leaf(
&mut self,
entity_id: u64,
parts: &[RawEntityPart],
) -> Result<(), ConvertError> {
let is_length = has_part(parts, "LENGTH_UNIT");
let is_plane_angle = has_part(parts, "PLANE_ANGLE_UNIT");
let is_solid_angle = has_part(parts, "SOLID_ANGLE_UNIT");
if !(is_length || is_plane_angle || is_solid_angle) {
return Ok(());
}
if has_part(parts, "CONVERSION_BASED_UNIT") {
return self.convert_conversion_based_unit(entity_id, parts, is_length, is_plane_angle);
}
if !has_part(parts, "SI_UNIT") {
return Ok(());
}
let si_attrs = require_part_attrs(parts, "SI_UNIT", entity_id)?;
check_count(si_attrs, 2, entity_id, "SI_UNIT")?;
let prefix = read_optional_enum(si_attrs, 0, entity_id, "prefix")?;
let name = read_enum(si_attrs, 1, entity_id, "name")?;
if is_length {
if let Some(unit) = match_length_unit(prefix, name) {
self.length_unit_map.insert(entity_id, unit);
} else {
self.warnings.push(ConvertError::UnexpectedEntityForm {
entity_id,
detail: format!(
"unsupported SI length unit (prefix={prefix:?}, name={name:?})"
),
});
}
} else if is_plane_angle {
if let Some(unit) = match_angle_unit(prefix, name) {
self.angle_unit_map.insert(entity_id, unit);
} else {
self.warnings.push(ConvertError::UnexpectedEntityForm {
entity_id,
detail: format!("unsupported SI angle unit (prefix={prefix:?}, name={name:?})"),
});
}
} else if is_solid_angle {
if let Some(unit) = match_solid_angle_unit(prefix, name) {
self.solid_angle_unit_map.insert(entity_id, unit);
} else {
self.warnings.push(ConvertError::UnexpectedEntityForm {
entity_id,
detail: format!(
"unsupported SI solid-angle unit (prefix={prefix:?}, name={name:?})"
),
});
}
}
Ok(())
}
fn convert_conversion_based_unit(
&mut self,
entity_id: u64,
parts: &[RawEntityPart],
is_length: bool,
is_plane_angle: bool,
) -> Result<(), ConvertError> {
let cbu_attrs = require_part_attrs(parts, "CONVERSION_BASED_UNIT", entity_id)?;
check_count(cbu_attrs, 2, entity_id, "CONVERSION_BASED_UNIT")?;
let name = read_string(cbu_attrs, 0, entity_id, "name")?;
let upper = name.to_uppercase();
if is_length {
if let Some(unit) = match_length_conversion(&upper) {
self.length_unit_map.insert(entity_id, unit);
} else {
self.warnings.push(ConvertError::UnexpectedEntityForm {
entity_id,
detail: format!("unsupported CONVERSION_BASED_UNIT length name: {name:?}"),
});
}
} else if is_plane_angle {
if let Some(unit) = match_angle_conversion(&upper) {
self.angle_unit_map.insert(entity_id, unit);
} else {
self.warnings.push(ConvertError::UnexpectedEntityForm {
entity_id,
detail: format!("unsupported CONVERSION_BASED_UNIT angle name: {name:?}"),
});
}
}
Ok(())
}
pub(super) fn convert_uncertainty_measure_with_unit(
&mut self,
entity_id: u64,
attrs: &[Attribute],
) -> Result<(), ConvertError> {
check_count(attrs, 4, entity_id, "UNCERTAINTY_MEASURE_WITH_UNIT")?;
let value = match attrs.first() {
Some(Attribute::Typed { value, .. }) => match value.as_ref() {
Attribute::Real(v) => *v,
_ => return Ok(()),
},
_ => return Ok(()),
};
let unit_ref = read_entity_ref(attrs, 1, entity_id, "unit_component")?;
if self.length_unit_map.contains_key(&unit_ref) {
self.length_uncertainty_map.insert(entity_id, value);
}
Ok(())
}
pub(super) fn convert_global_unit_assigned_context(
&mut self,
entity_id: u64,
parts: &[RawEntityPart],
) -> Result<(), ConvertError> {
if self.units.is_some() {
return Ok(());
}
let guac_attrs = require_part_attrs(parts, "GLOBAL_UNIT_ASSIGNED_CONTEXT", entity_id)?;
check_count(guac_attrs, 1, entity_id, "GLOBAL_UNIT_ASSIGNED_CONTEXT")?;
let unit_refs = read_entity_ref_list(guac_attrs, 0, entity_id, "units")?;
let mut length = None;
let mut plane_angle = None;
let mut solid_angle = None;
for r in &unit_refs {
if let Some(&u) = self.length_unit_map.get(r) {
length = Some(u);
} else if let Some(&u) = self.angle_unit_map.get(r) {
plane_angle = Some(u);
} else if let Some(&u) = self.solid_angle_unit_map.get(r) {
solid_angle = Some(u);
}
}
match (length, plane_angle, solid_angle) {
(Some(length), Some(plane_angle), Some(solid_angle)) => {
let length_uncertainty = self.extract_length_uncertainty(parts);
self.units = Some(UnitContext {
length,
plane_angle,
solid_angle,
length_uncertainty,
});
}
_ => {
self.warnings.push(ConvertError::UnexpectedEntityForm {
entity_id,
detail: format!(
"incomplete unit context: length={}, plane_angle={}, solid_angle={}",
length.is_some(),
plane_angle.is_some(),
solid_angle.is_some(),
),
});
}
}
Ok(())
}
fn extract_length_uncertainty(&self, parts: &[RawEntityPart]) -> Option<f64> {
let guac = parts
.iter()
.find(|p| p.name == "GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT")?;
let refs = read_entity_ref_list(&guac.attributes, 0, 0, "uncertainty").ok()?;
refs.iter()
.find_map(|r| self.length_uncertainty_map.get(r).copied())
}
}
fn has_part(parts: &[RawEntityPart], name: &str) -> bool {
has_all_parts(parts, &[name])
}
fn read_optional_enum<'a>(
attrs: &'a [Attribute],
index: usize,
entity_id: u64,
field_name: &'static str,
) -> Result<Option<&'a str>, ConvertError> {
match attrs.get(index) {
Some(Attribute::Unset) => Ok(None),
Some(_) => read_enum(attrs, index, entity_id, field_name).map(Some),
None => Err(ConvertError::AttributeIndex {
entity_id,
field_name,
index,
len: attrs.len(),
}),
}
}
fn match_length_unit(prefix: Option<&str>, name: &str) -> Option<LengthUnit> {
match (prefix, name) {
(None, "METRE") => Some(LengthUnit::Metre),
(Some("MILLI"), "METRE") => Some(LengthUnit::Millimetre),
(Some("CENTI"), "METRE") => Some(LengthUnit::Centimetre),
_ => None,
}
}
fn match_angle_unit(prefix: Option<&str>, name: &str) -> Option<AngleUnit> {
match (prefix, name) {
(None, "RADIAN") => Some(AngleUnit::Radian),
_ => None,
}
}
fn match_solid_angle_unit(prefix: Option<&str>, name: &str) -> Option<SolidAngleUnit> {
match (prefix, name) {
(None, "STERADIAN") => Some(SolidAngleUnit::Steradian),
_ => None,
}
}
fn match_length_conversion(upper_name: &str) -> Option<LengthUnit> {
match upper_name {
"INCH" => Some(LengthUnit::Inch),
"FOOT" => Some(LengthUnit::Foot),
"MILLIMETRE" => Some(LengthUnit::Millimetre),
"CENTIMETRE" => Some(LengthUnit::Centimetre),
"METRE" => Some(LengthUnit::Metre),
_ => None,
}
}
fn match_angle_conversion(upper_name: &str) -> Option<AngleUnit> {
match upper_name {
"DEGREE" => Some(AngleUnit::Degree),
"RADIAN" => Some(AngleUnit::Radian),
_ => None,
}
}