use crate::types::Handle;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DictionaryCloningFlags {
#[default]
NotApplicable = 0,
KeepExisting = 1,
UseClone = 2,
XrefName = 3,
Name = 4,
UnmangleName = 5,
}
impl DictionaryCloningFlags {
pub fn from_value(value: i16) -> Self {
match value {
1 => DictionaryCloningFlags::KeepExisting,
2 => DictionaryCloningFlags::UseClone,
3 => DictionaryCloningFlags::XrefName,
4 => DictionaryCloningFlags::Name,
5 => DictionaryCloningFlags::UnmangleName,
_ => DictionaryCloningFlags::NotApplicable,
}
}
pub fn to_value(&self) -> i16 {
*self as i16
}
pub fn to_code(&self) -> i16 {
self.to_value()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum XRecordValueType {
String,
Point3D,
Double,
Byte,
Int16,
Int32,
Int64,
Handle,
ObjectId,
Bool,
Chunk,
Unknown,
}
impl XRecordValueType {
pub fn from_code(code: i32) -> Self {
match code {
5 | 105 => XRecordValueType::Handle,
320..=329 | 480..=481 => XRecordValueType::Handle,
330..=369 => XRecordValueType::ObjectId,
0..=4 | 6..=9 | 100..=102 | 300..=309 => XRecordValueType::String,
10..=39 => XRecordValueType::Point3D,
40..=59 | 110..=149 | 210..=239 | 460..=469 => XRecordValueType::Double,
280..=289 => XRecordValueType::Byte,
60..=79 | 170..=179 | 270..=279 => XRecordValueType::Int16,
90..=99 | 420..=459 => XRecordValueType::Int32,
160..=169 => XRecordValueType::Int64,
290..=299 => XRecordValueType::Bool,
310..=319 => XRecordValueType::Chunk,
_ => XRecordValueType::Unknown,
}
}
pub fn is_handle(&self) -> bool {
matches!(self, XRecordValueType::Handle | XRecordValueType::ObjectId)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum XRecordValue {
String(String),
Double(f64),
Int16(i16),
Int32(i32),
Int64(i64),
Byte(u8),
Bool(bool),
Handle(Handle),
Point3D(f64, f64, f64),
Chunk(Vec<u8>),
}
impl XRecordValue {
pub fn as_string(&self) -> Option<&str> {
match self {
XRecordValue::String(s) => Some(s),
_ => None,
}
}
pub fn as_double(&self) -> Option<f64> {
match self {
XRecordValue::Double(v) => Some(*v),
_ => None,
}
}
pub fn as_i32(&self) -> Option<i32> {
match self {
XRecordValue::Int32(v) => Some(*v),
XRecordValue::Int16(v) => Some(*v as i32),
XRecordValue::Byte(v) => Some(*v as i32),
_ => None,
}
}
pub fn as_handle(&self) -> Option<Handle> {
match self {
XRecordValue::Handle(h) => Some(*h),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
XRecordValue::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_point3d(&self) -> Option<(f64, f64, f64)> {
match self {
XRecordValue::Point3D(x, y, z) => Some((*x, *y, *z)),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct XRecordEntry {
pub code: i32,
pub value: XRecordValue,
}
impl XRecordEntry {
pub fn new(code: i32, value: XRecordValue) -> Self {
Self { code, value }
}
pub fn string(code: i32, value: impl Into<String>) -> Self {
Self::new(code, XRecordValue::String(value.into()))
}
pub fn double(code: i32, value: f64) -> Self {
Self::new(code, XRecordValue::Double(value))
}
pub fn int16(code: i32, value: i16) -> Self {
Self::new(code, XRecordValue::Int16(value))
}
pub fn int32(code: i32, value: i32) -> Self {
Self::new(code, XRecordValue::Int32(value))
}
pub fn handle(code: i32, value: Handle) -> Self {
Self::new(code, XRecordValue::Handle(value))
}
pub fn bool(code: i32, value: bool) -> Self {
Self::new(code, XRecordValue::Bool(value))
}
pub fn point3d(x_code: i32, x: f64, y: f64, z: f64) -> Self {
Self::new(x_code, XRecordValue::Point3D(x, y, z))
}
pub fn value_type(&self) -> XRecordValueType {
XRecordValueType::from_code(self.code)
}
pub fn has_linked_object(&self) -> bool {
matches!(self.value, XRecordValue::Handle(_))
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct XRecord {
pub handle: Handle,
pub owner: Handle,
pub name: String,
pub cloning_flags: DictionaryCloningFlags,
pub entries: Vec<XRecordEntry>,
pub raw_data: Vec<u8>,
}
impl XRecord {
pub const OBJECT_TYPE: &'static str = "XRECORD";
pub fn new() -> Self {
Self {
handle: Handle::NULL,
owner: Handle::NULL,
name: String::new(),
cloning_flags: DictionaryCloningFlags::NotApplicable,
entries: Vec::new(),
raw_data: Vec::new(),
}
}
pub fn named(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Self::new()
}
}
pub fn add_entry(&mut self, entry: XRecordEntry) {
self.entries.push(entry);
}
pub fn add_string(&mut self, code: i32, value: impl Into<String>) {
self.entries.push(XRecordEntry::string(code, value));
}
pub fn add_double(&mut self, code: i32, value: f64) {
self.entries.push(XRecordEntry::double(code, value));
}
pub fn add_int16(&mut self, code: i32, value: i16) {
self.entries.push(XRecordEntry::int16(code, value));
}
pub fn add_int32(&mut self, code: i32, value: i32) {
self.entries.push(XRecordEntry::int32(code, value));
}
pub fn add_handle(&mut self, code: i32, value: Handle) {
self.entries.push(XRecordEntry::handle(code, value));
}
pub fn add_bool(&mut self, code: i32, value: bool) {
self.entries.push(XRecordEntry::bool(code, value));
}
pub fn add_point3d(&mut self, x_code: i32, x: f64, y: f64, z: f64) {
self.entries.push(XRecordEntry::point3d(x_code, x, y, z));
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn get(&self, index: usize) -> Option<&XRecordEntry> {
self.entries.get(index)
}
pub fn get_by_code(&self, code: i32) -> Vec<&XRecordEntry> {
self.entries.iter().filter(|e| e.code == code).collect()
}
pub fn get_first_by_code(&self, code: i32) -> Option<&XRecordEntry> {
self.entries.iter().find(|e| e.code == code)
}
pub fn get_strings(&self, code: i32) -> Vec<&str> {
self.entries
.iter()
.filter(|e| e.code == code)
.filter_map(|e| e.value.as_string())
.collect()
}
pub fn get_string(&self, code: i32) -> Option<&str> {
self.get_first_by_code(code)?.value.as_string()
}
pub fn get_double(&self, code: i32) -> Option<f64> {
self.get_first_by_code(code)?.value.as_double()
}
pub fn get_i32(&self, code: i32) -> Option<i32> {
self.get_first_by_code(code)?.value.as_i32()
}
pub fn remove_by_code(&mut self, code: i32) {
self.entries.retain(|e| e.code != code);
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn get_references(&self) -> Vec<Handle> {
self.entries
.iter()
.filter_map(|e| e.value.as_handle())
.collect()
}
pub fn iter(&self) -> impl Iterator<Item = &XRecordEntry> {
self.entries.iter()
}
}
impl Default for XRecord {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xrecord_creation() {
let xrecord = XRecord::new();
assert!(xrecord.is_empty());
assert_eq!(xrecord.cloning_flags, DictionaryCloningFlags::NotApplicable);
}
#[test]
fn test_xrecord_named() {
let xrecord = XRecord::named("MyRecord");
assert_eq!(xrecord.name, "MyRecord");
}
#[test]
fn test_xrecord_add_entries() {
let mut xrecord = XRecord::new();
xrecord.add_string(1, "Test");
xrecord.add_double(40, 3.14);
xrecord.add_int32(90, 42);
xrecord.add_bool(290, true);
assert_eq!(xrecord.len(), 4);
}
#[test]
fn test_xrecord_get_values() {
let mut xrecord = XRecord::new();
xrecord.add_string(1, "Hello");
xrecord.add_double(40, 2.5);
xrecord.add_int32(90, 100);
assert_eq!(xrecord.get_string(1), Some("Hello"));
assert_eq!(xrecord.get_double(40), Some(2.5));
assert_eq!(xrecord.get_i32(90), Some(100));
assert_eq!(xrecord.get_string(999), None);
}
#[test]
fn test_xrecord_get_by_code() {
let mut xrecord = XRecord::new();
xrecord.add_string(1, "First");
xrecord.add_string(1, "Second");
xrecord.add_string(2, "Other");
let code_1 = xrecord.get_by_code(1);
assert_eq!(code_1.len(), 2);
let strings = xrecord.get_strings(1);
assert_eq!(strings, vec!["First", "Second"]);
}
#[test]
fn test_xrecord_remove_by_code() {
let mut xrecord = XRecord::new();
xrecord.add_string(1, "Keep");
xrecord.add_double(40, 1.0);
xrecord.add_double(40, 2.0);
xrecord.remove_by_code(40);
assert_eq!(xrecord.len(), 1);
assert_eq!(xrecord.get_string(1), Some("Keep"));
}
#[test]
fn test_xrecord_entry_types() {
let entry = XRecordEntry::string(1, "test");
assert_eq!(entry.value_type(), XRecordValueType::String);
let entry = XRecordEntry::double(40, 1.0);
assert_eq!(entry.value_type(), XRecordValueType::Double);
let entry = XRecordEntry::int32(90, 42);
assert_eq!(entry.value_type(), XRecordValueType::Int32);
let entry = XRecordEntry::handle(330, Handle::new(100));
assert_eq!(entry.value_type(), XRecordValueType::ObjectId);
assert!(entry.has_linked_object());
}
#[test]
fn test_xrecord_point3d() {
let mut xrecord = XRecord::new();
xrecord.add_point3d(10, 1.0, 2.0, 3.0);
let entry = xrecord.get(0).unwrap();
assert_eq!(entry.value.as_point3d(), Some((1.0, 2.0, 3.0)));
}
#[test]
fn test_xrecord_get_references() {
let mut xrecord = XRecord::new();
xrecord.add_handle(330, Handle::new(100));
xrecord.add_string(1, "text");
xrecord.add_handle(340, Handle::new(200));
let refs = xrecord.get_references();
assert_eq!(refs.len(), 2);
assert!(refs.contains(&Handle::new(100)));
assert!(refs.contains(&Handle::new(200)));
}
#[test]
fn test_cloning_flags() {
assert_eq!(DictionaryCloningFlags::from_value(0), DictionaryCloningFlags::NotApplicable);
assert_eq!(DictionaryCloningFlags::from_value(1), DictionaryCloningFlags::KeepExisting);
assert_eq!(DictionaryCloningFlags::from_value(2), DictionaryCloningFlags::UseClone);
assert_eq!(DictionaryCloningFlags::KeepExisting.to_value(), 1);
}
#[test]
fn test_value_type_from_code() {
assert_eq!(XRecordValueType::from_code(1), XRecordValueType::String);
assert_eq!(XRecordValueType::from_code(10), XRecordValueType::Point3D);
assert_eq!(XRecordValueType::from_code(40), XRecordValueType::Double);
assert_eq!(XRecordValueType::from_code(90), XRecordValueType::Int32);
assert_eq!(XRecordValueType::from_code(290), XRecordValueType::Bool);
assert_eq!(XRecordValueType::from_code(330), XRecordValueType::ObjectId);
}
}