use crate::loss::StepLossCode;
use crate::options::StepSchema;
use crate::parse::schema_identifier::split_schema_identifier;
use crate::parse::Exchange;
use cadmpeg_core::dialect::{Admission, DialectId, DialectLayers, DialectMatch, Grammar};
use cadmpeg_core::CodecError;
use std::collections::BTreeMap;
include!("dialect/registry_ids.rs");
const DECLARED_FILE_SCHEMA_IDENTIFIER: &str = "file_schema_identifier";
const DECLARED_FILE_SCHEMA_IDENTIFIERS: &str = "file_schema_identifiers";
const DECLARED_LONG_FORM_ARCS: &str = "long_form_arcs";
const DECLARED_IMPLEMENTATION_LEVEL: &str = "implementation_level";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum StepDialect {
Part21(Part21Dialect),
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum AlternateEncoding {
Part28Xml,
Ap242BoModelXml,
Part26Hdf5,
}
impl AlternateEncoding {
#[cfg(test)]
const ALL: [Self; 3] = [Self::Part28Xml, Self::Ap242BoModelXml, Self::Part26Hdf5];
const fn id(self) -> DialectId {
match self {
Self::Part28Xml => STEP_PART28_XML,
Self::Ap242BoModelXml => STEP_AP242_BO_MODEL_XML,
Self::Part26Hdf5 => STEP_PART26_HDF5,
}
}
const fn refusal_message(self) -> &'static str {
match self {
Self::Part26Hdf5 => "STEP Part 26 binary/HDF5 encoding",
Self::Part28Xml => "STEP Part 28 XML encoding",
Self::Ap242BoModelXml => "AP242 BO-Model XML sidecar",
}
}
fn refused_match(self) -> DialectMatch {
DialectMatch::refused(self.id())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum Part21Dialect {
Schema(StepSchema),
Ap242,
}
impl Part21Dialect {
const fn id(self) -> DialectId {
match self {
Self::Schema(schema) => schema.id(),
Self::Ap242 => STEP_AP242,
}
}
const fn schema_identifier(self) -> &'static str {
match self {
Self::Schema(schema) => schema.file_schema(),
Self::Ap242 => "AP242_MANAGED_MODEL_BASED_3D_ENGINEERING_MIM_LF",
}
}
}
const NEAREST_STRATEGY: Part21Dialect = Part21Dialect::Schema(StepSchema::Ap242Edition3);
const AP214_COMMITTEE_DRAFT_SCHEMA_NAME: &str = "AUTOMOTIVE_DESIGN_CC2";
impl StepDialect {
#[cfg(test)]
const ALL: [Self; 8] = [
Self::Part21(Part21Dialect::Schema(StepSchema::Ap203Edition1)),
Self::Part21(Part21Dialect::Schema(StepSchema::Ap203Edition2)),
Self::Part21(Part21Dialect::Schema(StepSchema::Ap214)),
Self::Part21(Part21Dialect::Ap242),
Self::Part21(Part21Dialect::Schema(StepSchema::Ap242Edition1)),
Self::Part21(Part21Dialect::Schema(StepSchema::Ap242Edition2)),
Self::Part21(Part21Dialect::Schema(StepSchema::Ap242Edition3)),
Self::Unknown,
];
const fn id(self) -> DialectId {
match self {
Self::Part21(row) => row.id(),
Self::Unknown => STEP_UNKNOWN,
}
}
fn from_schema_identifier(identifier: &str, object_identifier: Option<&[u64]>) -> Self {
let Some((name, object_identifier_text)) = split_schema_identifier(identifier) else {
return Self::Unknown;
};
let ap242_name = Part21Dialect::Ap242.schema_identifier();
if name.eq_ignore_ascii_case(ap242_name) {
if object_identifier_text.is_none() {
return Self::Part21(Part21Dialect::Ap242);
}
return Self::ap242_edition(object_identifier).map_or(Self::Unknown, Self::Part21);
}
if name.eq_ignore_ascii_case(AP214_COMMITTEE_DRAFT_SCHEMA_NAME) {
return Self::Part21(Part21Dialect::Schema(StepSchema::Ap214));
}
[
Part21Dialect::Schema(StepSchema::Ap203Edition1),
Part21Dialect::Schema(StepSchema::Ap203Edition2),
Part21Dialect::Schema(StepSchema::Ap214),
]
.into_iter()
.find(|row| {
split_schema_identifier(row.schema_identifier())
.is_some_and(|(candidate, _)| name.eq_ignore_ascii_case(candidate))
})
.map_or(Self::Unknown, Self::Part21)
}
fn ap242_edition(object_identifier: Option<&[u64]>) -> Option<Part21Dialect> {
match object_identifier? {
[1, 0, 10303, 442, 1, 1, 4] => Some(Part21Dialect::Schema(StepSchema::Ap242Edition1)),
[1, 0, 10303, 442, 3, 1, 4] => Some(Part21Dialect::Schema(StepSchema::Ap242Edition2)),
[1, 0, 10303, 442, 4, 1, 4] => Some(Part21Dialect::Schema(StepSchema::Ap242Edition3)),
_ => None,
}
}
pub(crate) fn classify(exchange: &Exchange) -> DialectMatch {
let first = exchange.schema_identifiers().next();
let object_identifier = exchange.primary_schema_object_identifier();
let dialect = first.map_or(Self::Unknown, |identifier| {
Self::from_schema_identifier(identifier, object_identifier.as_deref())
});
let mut declared = BTreeMap::new();
if let Some(identifier) = first {
declared.insert(
cadmpeg_core::nonblank_const!(DECLARED_FILE_SCHEMA_IDENTIFIER),
identifier.to_owned(),
);
if let Some((_, Some(arcs))) = split_schema_identifier(identifier) {
declared.insert(
cadmpeg_core::nonblank_const!(DECLARED_LONG_FORM_ARCS),
arcs.to_owned(),
);
}
}
if exchange.schema_identifiers().nth(1).is_some() {
declared.insert(
cadmpeg_core::nonblank_const!(DECLARED_FILE_SCHEMA_IDENTIFIERS),
exchange.joined_schema_identifiers(),
);
}
declared.insert(
cadmpeg_core::nonblank_const!(DECLARED_IMPLEMENTATION_LEVEL),
exchange.implementation_level().to_owned(),
);
if dialect == Self::Unknown {
DialectMatch::unverified(dialect.id(), Grammar::of(&NEAREST_STRATEGY.id()))
} else {
DialectMatch::admitted(dialect.id())
}
.with_declared(declared)
}
}
pub(crate) fn dialect_loss(matched: &DialectMatch) -> Option<cadmpeg_ir::report::loss::LossNote> {
let Admission::Unverified { using } = matched.admission() else {
return None;
};
let message = match matched.declared().get(DECLARED_FILE_SCHEMA_IDENTIFIER) {
Some(identifier) => format!("FILE_SCHEMA identifier {identifier}; it satisfies no declared STEP dialect, so this decode read the exchange \
with the entity vocabulary verified for {FORMAT}:{}",
using.as_str()),
None => format!("The exchange declares no FILE_SCHEMA identifier; it satisfies no declared STEP dialect, so this decode read the exchange \
with the entity vocabulary verified for {FORMAT}:{}",
using.as_str()),
};
Some(StepLossCode::SourceDialectUnverified.note(message))
}
pub(crate) fn refuse_alternate_encoding(bytes: &[u8]) -> Result<(), CodecError> {
let encoding = if crate::codec::is_part26_hdf5(bytes) {
AlternateEncoding::Part26Hdf5
} else if crate::codec::is_part28_xml(bytes) {
AlternateEncoding::Part28Xml
} else if crate::codec::is_ap242_bo_model_xml(bytes) {
AlternateEncoding::Ap242BoModelXml
} else {
return Ok(());
};
Err(CodecError::UnsupportedDialect {
dialects: Box::new(DialectLayers::of(encoding.refused_match())),
message: encoding.refusal_message().into(),
})
}
#[cfg(test)]
mod tests;