use crate::objects::{ProxyObjectReference, ProxyReferenceKind};
use crate::types::{Handle, Vector3};
#[cfg(feature = "serde")]
fn xrecord_entries_complete() -> bool {
true
}
#[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 {
code if code < 0 => XRecordValueType::Handle,
5 | 105 | 320..=329 | 480..=481 => {
XRecordValueType::Handle
}
330..=369 => {
XRecordValueType::ObjectId
}
390..=399 | 1005 => XRecordValueType::Handle,
0..=4 | 6..=9 | 100..=102 | 300..=309 | 410..=419
| 430..=439 | 470..=479 | 999 | 1000..=1003 => {
XRecordValueType::String
}
10..=37 | 110..=139 | 210..=269 | 1010..=1039
| 1043..=1069 => XRecordValueType::Point3D,
38..=59 | 140..=149 | 460..=469 | 1040..=1042 => {
XRecordValueType::Double
}
280..=289 => XRecordValueType::Byte,
60..=79 | 170..=179 | 270..=279 | 370..=389
| 400..=409 | 1070 => XRecordValueType::Int16,
80..=99 | 420..=429 | 440..=459 | 1071 => {
XRecordValueType::Int32
}
150..=169 => XRecordValueType::Int64,
290..=299 => XRecordValueType::Bool,
310..=319 | 1004 => 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, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum KnownXRecordKind {
LayerViewportAlphaOverride,
LayerViewportColorOverride,
LayerViewportLinetypeOverride,
LayerViewportLineweightOverride,
LayerReconciled,
LayerStateAnnotationScale,
AnnotationScale,
MeshTextureCoordinates,
XrecordRoundTrip,
MTextRoundTrip,
HeaderRoundTrip,
RecomposeData,
DynamicBlockHistory,
LayoutThumbnail,
ViewTransition,
PhotometricLight,
LastSavedVersion,
PreviousProductInfo,
DrawingProperties,
FingerprintGuid,
DimensionStyleData,
UcsData,
PlotData,
Hyperlink,
AdvancedMaterial,
MaterialAsset,
Metadata,
Unknown,
}
impl KnownXRecordKind {
pub fn from_name(name: &str) -> Self {
match name.to_ascii_uppercase().as_str() {
"ADSK_XREC_LAYER_ALPHA_OVR" => Self::LayerViewportAlphaOverride,
"ADSK_XREC_LAYER_COLOR_OVR" => Self::LayerViewportColorOverride,
"ADSK_XREC_LAYER_LINETYPE_OVR" => Self::LayerViewportLinetypeOverride,
"ADSK_XREC_LAYER_LINEWT_OVR" => Self::LayerViewportLineweightOverride,
"ADSK_XREC_LAYER_RECONCILED" => Self::LayerReconciled,
"ACADLAYERSTATEANNOSCALE" => Self::LayerStateAnnotationScale,
"ASDK_XREC_ANNOTATION_SCALE_INFO" | "ASDK_XREC_ANNO_SCALE_INFO"
| "ADSK_XREC_VTR_ANNOSCALE_DATA" => Self::AnnotationScale,
"ADSK_XREC_SUBDVERTEXTEXCOORDS" => Self::MeshTextureCoordinates,
"ACAD_XREC_ROUNDTRIP" => Self::XrecordRoundTrip,
"ACAD_MTEXT_RT" | "ACAD_MTEXT_2008_RT" => Self::MTextRoundTrip,
"ACDBHEADERROUNDTRIPXREC" | "AUXHDRRNDTRIPDATA" => Self::HeaderRoundTrip,
"ACDB_RECOMPOSE_DATA" => Self::RecomposeData,
"ACAD_ENHANCEDBLOCKHISTORY" => Self::DynamicBlockHistory,
"ASEBLOCKHIERARCHYINDEXRECORD" => Self::DynamicBlockHistory,
"ADSK_XREC_LAYOUTTHUMBNAIL" => Self::LayoutThumbnail,
"ADSK_XREC_VTRANIMATIONINFO" | "ADSK_XREC_VTRTHUMBNAIL"
| "ADSK_XREC_VTRVIEWINFO" => Self::ViewTransition,
"ADSK_XREC_PHOTOMETRICLIGHTINFO" | "LIGHTINGQUALITY" => {
Self::PhotometricLight
}
"ACAD_LAST_SAVED_VERSION_INFO" => Self::LastSavedVersion,
"ACAD_CIP_PREVIOUS_PRODUCT_INFO" => Self::PreviousProductInfo,
"DWGPROPS" => Self::DrawingProperties,
"FINGERPRINTGUID" => Self::FingerprintGuid,
"ACADDIM" | "DIMSTYLEDATA" | "DIMLTEX1" | "DIMLTEX2" | "DIMLTYPE"
| "TSTACKALIGN" | "TSTACKSIZE" => {
Self::DimensionStyleData
}
"PUCSBASE" | "PUCSORGBACK" | "PUCSORGBOTTOM" | "PUCSORGFRONT"
| "PUCSORGLEFT" | "PUCSORGRIGHT" | "PUCSORGTOP" | "PUCSORTHOREF"
| "PUCSORTHOVIEW" | "UCSBASE" | "UCSORGBACK" | "UCSORGBOTTOM"
| "UCSORGFRONT" | "UCSORGLEFT" | "UCSORGRIGHT" | "UCSORGTOP"
| "UCSORTHOREF" | "UCSORTHOVIEW" => Self::UcsData,
"ACAD_LAYOUTSELFREF" | "LAYOUTDICT" | "PLOTSETDICT"
| "PLOTSTYLNAMDICT" | "PSVPSCALE" | "STYLESHEET" => Self::PlotData,
"HYPERLINKBASE" => Self::Hyperlink,
"ADVMATERIAL" => Self::AdvancedMaterial,
"FBXASSET" | "BUMPTILE" | "DIFFUSETILE" | "OPACITYTILE"
| "REFLECTIONTILE" | "REFRACTIONTILE" | "SPECULARTILE" | "BUMP"
| "DIFFUSE" | "MATERIALDICT" | "VIZ XML MATERIAL DEFINITION" => {
Self::MaterialAsset
}
"ACAD_MLATT" | "ACAD_VIEWS_VIEW_CUSTOM" | "AECDEPRECATIONHISTORY"
| "CEPSNID" | "CEPSNTYPE" | "COLORDICT" | "INSUNITS" | "LWETCUNION"
| "MCS_DOCUMENT_ID" | "MCS_PARAMS_DATA" | "MC_VERSION_DATA"
| "VERSIONGUID" => Self::Metadata,
_ => Self::Unknown,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct XRecordSection {
pub name: String,
pub entries: Vec<XRecordEntry>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct XRecord {
pub handle: Handle,
pub owner: Handle,
pub reactors: Vec<Handle>,
pub xdictionary_handle: Option<Handle>,
pub name: String,
pub cloning_flags: DictionaryCloningFlags,
pub entries: Vec<XRecordEntry>,
#[cfg_attr(feature = "serde", serde(default))]
pub object_references: Vec<ProxyObjectReference>,
#[cfg_attr(feature = "serde", serde(default))]
pub preserve_object_reference_stream: bool,
#[cfg_attr(feature = "serde", serde(default = "xrecord_entries_complete"))]
pub entries_complete: bool,
pub raw_data: Vec<u8>,
#[cfg_attr(feature = "serde", serde(skip))]
pub raw_dwg_data: Option<Vec<u8>>,
pub raw_dwg_handle_bits: i64,
#[cfg_attr(feature = "serde", serde(skip))]
pub raw_dwg_version: Option<crate::types::DxfVersion>,
}
impl XRecord {
pub const OBJECT_TYPE: &'static str = "XRECORD";
pub fn new() -> Self {
Self {
handle: Handle::NULL,
owner: Handle::NULL,
reactors: Vec::new(),
xdictionary_handle: None,
name: String::new(),
cloning_flags: DictionaryCloningFlags::NotApplicable,
entries: Vec::new(),
object_references: Vec::new(),
preserve_object_reference_stream: false,
entries_complete: true,
raw_data: Vec::new(),
raw_dwg_data: None,
raw_dwg_handle_bits: 0,
raw_dwg_version: None,
}
}
pub fn named(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Self::new()
}
}
pub fn add_entry(&mut self, entry: XRecordEntry) {
let is_object_id = (330..=369).contains(&entry.code);
self.entries.push(entry);
if is_object_id {
self.synchronize_object_references();
}
self.entries_complete = true;
}
pub fn add_string(&mut self, code: i32, value: impl Into<String>) {
self.entries.push(XRecordEntry::string(code, value));
self.entries_complete = true;
}
pub fn add_double(&mut self, code: i32, value: f64) {
self.entries.push(XRecordEntry::double(code, value));
self.entries_complete = true;
}
pub fn add_int16(&mut self, code: i32, value: i16) {
self.entries.push(XRecordEntry::int16(code, value));
self.entries_complete = true;
}
pub fn add_int32(&mut self, code: i32, value: i32) {
self.entries.push(XRecordEntry::int32(code, value));
self.entries_complete = true;
}
pub fn add_handle(&mut self, code: i32, value: Handle) {
self.entries.push(XRecordEntry::handle(code, value));
if (330..=369).contains(&code) {
self.synchronize_object_references();
}
self.entries_complete = true;
}
pub fn add_bool(&mut self, code: i32, value: bool) {
self.entries.push(XRecordEntry::bool(code, value));
self.entries_complete = true;
}
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));
self.entries_complete = true;
}
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);
if (330..=369).contains(&code) {
self.synchronize_object_references();
}
self.entries_complete = true;
}
pub fn clear(&mut self) {
self.entries.clear();
self.object_references.clear();
self.preserve_object_reference_stream = false;
self.entries_complete = true;
}
pub fn get_references(&self) -> Vec<Handle> {
let mut references: Vec<Handle> = self.entries
.iter()
.filter_map(|e| e.value.as_handle())
.collect();
references.extend(self.object_references.iter().map(|value| value.handle));
references.sort_by_key(|handle| handle.value());
references.dedup();
references
}
pub fn iter(&self) -> impl Iterator<Item = &XRecordEntry> {
self.entries.iter()
}
pub fn known_kind(&self) -> KnownXRecordKind {
KnownXRecordKind::from_name(&self.name)
}
pub fn sections(&self) -> Vec<XRecordSection> {
let mut sections = Vec::new();
let mut stack: Vec<(String, Vec<XRecordEntry>)> = Vec::new();
for entry in &self.entries {
match (entry.code, &entry.value) {
(102, XRecordValue::String(value)) if value.starts_with('{') => {
stack.push((
value.trim_start_matches('{').to_string(),
Vec::new(),
));
}
(102, XRecordValue::String(value))
if stack.last().is_some_and(|(name, _)| {
value == "}"
|| value
.strip_suffix('}')
.is_some_and(|closing| closing.eq_ignore_ascii_case(name))
}) =>
{
if let Some((name, entries)) = stack.pop() {
let section = XRecordSection { name, entries };
if let Some((_, parent)) = stack.last_mut() {
parent.push(XRecordEntry::string(
102,
format!("{{{}", section.name),
));
parent.extend(section.entries.clone());
parent.push(XRecordEntry::string(102, "}"));
}
sections.push(section);
}
}
_ => {
if let Some((_, entries)) = stack.last_mut() {
entries.push(entry.clone());
}
}
}
}
sections
}
pub fn section(&self, name: &str) -> Option<XRecordSection> {
self.sections()
.into_iter()
.find(|section| section.name.eq_ignore_ascii_case(name))
}
pub fn set_section(&mut self, name: &str, entries: Vec<XRecordEntry>) {
let start_text = format!("{{{name}");
let mut start = None;
let mut depth = 0usize;
let mut end = None;
for (index, entry) in self.entries.iter().enumerate() {
match (entry.code, &entry.value) {
(102, XRecordValue::String(value))
if value.eq_ignore_ascii_case(&start_text) =>
{
if start.is_none() {
start = Some(index);
depth = 1;
} else if depth > 0 {
depth += 1;
}
}
(102, XRecordValue::String(value))
if start.is_some() && value.starts_with('{') =>
{
depth += 1;
}
(102, XRecordValue::String(value))
if start.is_some()
&& (value == "}"
|| (!value.starts_with('{') && value.ends_with('}'))) =>
{
depth = depth.saturating_sub(1);
if depth == 0 {
end = Some(index);
break;
}
}
_ => {}
}
}
let replacement = std::iter::once(XRecordEntry::string(102, start_text))
.chain(entries)
.chain(std::iter::once(XRecordEntry::string(102, "}")));
if let (Some(start), Some(end)) = (start, end) {
self.entries.splice(start..=end, replacement);
} else {
self.entries.extend(replacement);
}
self.synchronize_object_references();
self.entries_complete = true;
}
pub fn mesh_texture_coordinates(&self) -> Vec<Vector3> {
let mut result = Vec::new();
let mut values = self.entries.iter();
while let Some(entry) = values.next() {
if entry.code != 43 {
continue;
}
let Some(u) = entry.value.as_double() else {
continue;
};
let Some(v_entry) = values.next() else {
break;
};
let Some(w_entry) = values.next() else {
break;
};
if v_entry.code == 44 && w_entry.code == 45 {
if let (Some(v), Some(w)) =
(v_entry.value.as_double(), w_entry.value.as_double())
{
result.push(Vector3::new(u, v, w));
}
}
}
result
}
pub fn set_mesh_texture_coordinates(&mut self, coordinates: &[Vector3]) {
self.entries
.retain(|entry| !matches!(entry.code, 43..=45));
for coordinate in coordinates {
self.entries.push(XRecordEntry::double(43, coordinate.x));
self.entries.push(XRecordEntry::double(44, coordinate.y));
self.entries.push(XRecordEntry::double(45, coordinate.z));
}
self.entries_complete = true;
}
pub fn annotation_scale_handle(&self) -> Option<Handle> {
self.entries
.iter()
.find(|entry| entry.code == 340)
.and_then(|entry| entry.value.as_handle())
}
pub fn set_annotation_scale_handle(&mut self, handle: Handle) {
if let Some(entry) = self.entries.iter_mut().find(|entry| entry.code == 340) {
entry.value = XRecordValue::Handle(handle);
} else {
self.entries.push(XRecordEntry::handle(340, handle));
}
self.synchronize_object_references();
self.entries_complete = true;
}
pub fn layer_viewport_overrides(
&self,
value_code: i32,
) -> Vec<(Handle, XRecordValue)> {
let mut result = Vec::new();
let mut viewport = None;
for entry in &self.entries {
if entry.code == 335 {
viewport = entry.value.as_handle();
} else if entry.code == value_code {
if let Some(handle) = viewport.take() {
result.push((handle, entry.value.clone()));
}
}
}
result
}
pub fn set_layer_viewport_override(
&mut self,
section_name: &str,
value_code: i32,
viewport: Handle,
value: XRecordValue,
) {
let mut updated = false;
let mut index = 0usize;
while index < self.entries.len() {
if self.entries[index].code == 335
&& self.entries[index].value.as_handle() == Some(viewport)
{
if let Some(value_entry) = self.entries[index + 1..]
.iter_mut()
.take_while(|entry| {
entry.code != 335
&& !matches!(
&entry.value,
XRecordValue::String(value)
if entry.code == 102 && value.ends_with('}')
)
})
.find(|entry| entry.code == value_code)
{
value_entry.value = value.clone();
updated = true;
break;
}
}
index += 1;
}
if !updated {
self.entries
.push(XRecordEntry::string(102, format!("{{{section_name}")));
self.entries.push(XRecordEntry::handle(335, viewport));
self.entries.push(XRecordEntry::new(value_code, value));
self.entries.push(XRecordEntry::string(102, "}"));
}
self.entries_complete = true;
self.synchronize_object_references();
}
pub fn synchronize_object_references(&mut self) {
let previous = self.object_references.clone();
self.object_references = self
.entries
.iter()
.filter(|entry| (330..=369).contains(&entry.code))
.filter_map(|entry| entry.value.as_handle().map(|handle| (entry.code, handle)))
.enumerate()
.map(|(index, (code, handle))| ProxyObjectReference {
handle,
kind: previous.get(index).map(|reference| reference.kind).unwrap_or(
match code {
330..=339 => ProxyReferenceKind::SoftPointer,
340..=349 => ProxyReferenceKind::HardPointer,
350..=359 => ProxyReferenceKind::SoftOwnership,
360..=369 => ProxyReferenceKind::HardOwnership,
_ => ProxyReferenceKind::Undefined,
},
),
})
.collect();
self.preserve_object_reference_stream = false;
}
}
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);
}
}