From 5dd4d232e74e9ef653b602db88b2e98c57933c2c Mon Sep 17 00:00:00 2001 From: qiudejia Date: Wed, 29 Apr 2026 17:11:19 +0800 Subject: [PATCH] =?UTF-8?q?fix/=E5=8A=A0=E5=85=A5=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E5=B1=82=E5=A4=84=E7=90=86=E4=B8=8D=E6=94=AF=E6=8C=81=E7=9A=84?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E7=BB=84=E7=A0=81-qdj-0429?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dxf_loader.rs | 209 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 +- 2 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 src/dxf_loader.rs diff --git a/src/dxf_loader.rs b/src/dxf_loader.rs new file mode 100644 index 0000000..f12e7c2 --- /dev/null +++ b/src/dxf_loader.rs @@ -0,0 +1,209 @@ +use anyhow::{Context, Result}; +use dxf::Drawing; +use std::io::Write; +use std::path::Path; +use tempfile::NamedTempFile; +use tracing::warn; + +struct SanitizedDxf { + bytes: Vec, + removed_pairs: usize, +} + +pub fn load_file(path: &Path) -> Result { + match Drawing::load_file(path) { + Ok(drawing) => Ok(drawing), + Err(original_error) => { + let Some(sanitized) = sanitize_file(path)? else { + return Err(original_error.into()); + }; + + warn!( + path = %path.display(), + removed_pairs = sanitized.removed_pairs, + original_error = %original_error, + "retrying DXF load after removing unsupported XRECORD group codes" + ); + + let mut temp_file = + NamedTempFile::new().context("Could not create sanitized DXF temporary file")?; + temp_file + .write_all(&sanitized.bytes) + .context("Could not write sanitized DXF temporary file")?; + + Drawing::load_file(temp_file.path()).with_context(|| { + format!( + "Failed to load sanitized DXF copy after removing {} unsupported XRECORD group code pair(s). Original error: {original_error}", + sanitized.removed_pairs + ) + }) + } + } +} + +fn sanitize_file(path: &Path) -> Result> { + let bytes = + std::fs::read(path).with_context(|| format!("Could not read {}", path.display()))?; + Ok(sanitize_unsupported_xrecord_pairs(&bytes)) +} + +fn sanitize_unsupported_xrecord_pairs(bytes: &[u8]) -> Option { + let lines = split_lines_preserving_endings(bytes); + let mut output = Vec::with_capacity(bytes.len()); + let mut removed_pairs = 0; + let mut in_objects_section = false; + let mut waiting_for_section_name = false; + let mut in_xrecord = false; + let mut index = 0; + + while index < lines.len() { + let code_line = lines[index]; + let Some(value_line) = lines.get(index + 1).copied() else { + output.extend_from_slice(code_line); + break; + }; + + let code = parse_group_code(code_line); + let skip_pair = code + .is_some_and(|code| in_objects_section && in_xrecord && !is_supported_group_code(code)); + + if skip_pair { + removed_pairs += 1; + } else { + output.extend_from_slice(code_line); + output.extend_from_slice(value_line); + } + + if let Some(code) = code { + update_section_state( + code, + trimmed_ascii(value_line), + &mut in_objects_section, + &mut waiting_for_section_name, + &mut in_xrecord, + ); + } + + index += 2; + } + + (removed_pairs > 0).then_some(SanitizedDxf { + bytes: output, + removed_pairs, + }) +} + +fn update_section_state( + code: i32, + value: &[u8], + in_objects_section: &mut bool, + waiting_for_section_name: &mut bool, + in_xrecord: &mut bool, +) { + if *waiting_for_section_name { + if code == 2 { + *in_objects_section = value.eq_ignore_ascii_case(b"OBJECTS"); + *in_xrecord = false; + } + + *waiting_for_section_name = false; + } + + if code != 0 { + return; + } + + if value.eq_ignore_ascii_case(b"SECTION") { + *waiting_for_section_name = true; + *in_xrecord = false; + } else if value.eq_ignore_ascii_case(b"ENDSEC") { + *in_objects_section = false; + *waiting_for_section_name = false; + *in_xrecord = false; + } else if *in_objects_section { + *in_xrecord = value.eq_ignore_ascii_case(b"XRECORD"); + } else { + *in_xrecord = false; + } +} + +fn is_supported_group_code(code: i32) -> bool { + matches!( + code, + 0..=79 + | 90..=102 + | 105 + | 110..=149 + | 160..=179 + | 210..=239 + | 270..=481 + | 999 + | 1000..=1071 + ) +} + +fn parse_group_code(line: &[u8]) -> Option { + std::str::from_utf8(trimmed_ascii(line)) + .ok()? + .parse::() + .ok() +} + +fn trimmed_ascii(line: &[u8]) -> &[u8] { + let mut start = 0; + let mut end = line.len(); + + while start < end && line[start].is_ascii_whitespace() { + start += 1; + } + + while start < end && line[end - 1].is_ascii_whitespace() { + end -= 1; + } + + &line[start..end] +} + +fn split_lines_preserving_endings(bytes: &[u8]) -> Vec<&[u8]> { + let mut lines = Vec::new(); + let mut start = 0; + + for (index, byte) in bytes.iter().enumerate() { + if *byte == b'\n' { + lines.push(&bytes[start..=index]); + start = index + 1; + } + } + + if start < bytes.len() { + lines.push(&bytes[start..]); + } + + lines +} + +#[cfg(test)] +mod tests { + use super::sanitize_unsupported_xrecord_pairs; + + #[test] + fn removes_unsupported_group_codes_only_from_object_xrecords() { + let input = b" 0\r\nSECTION\r\n 2\r\nOBJECTS\r\n 0\r\nXRECORD\r\n100\r\nAcDbXrecord\r\n280\r\n 1\r\n5005\r\nMETRIC\r\n1070\r\n 0\r\n 0\r\nENDSEC\r\n 0\r\nEOF\r\n"; + + let sanitized = sanitize_unsupported_xrecord_pairs(input).expect("expected cleanup"); + let output = String::from_utf8(sanitized.bytes).expect("expected ASCII test data"); + + assert_eq!(1, sanitized.removed_pairs); + assert!(!output.contains("5005")); + assert!(output.contains("XRECORD")); + assert!(output.contains("1070\r\n 0")); + } + + #[test] + fn leaves_unsupported_group_codes_outside_object_xrecords_unchanged() { + let input = + b" 0\nSECTION\n 2\nENTITIES\n 0\nXRECORD\n5005\nMETRIC\n 0\nENDSEC\n 0\nEOF\n"; + + assert!(sanitize_unsupported_xrecord_pairs(input).is_none()); + } +} diff --git a/src/main.rs b/src/main.rs index c102bc7..da13200 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ use tracing_subscriber::prelude::*; #[cfg(feature = "venator")] use venator::Venator; +mod dxf_loader; mod qelmt; #[derive(Parser, Debug)] @@ -109,7 +110,7 @@ fn main() -> Result<()> { .unwrap_or_else(|| file_name.as_os_str()) .to_string_lossy(); // 加载dxf文件,获取dxf解析对象drawing - let drawing: Drawing = Drawing::load_file(&file_name).context(format!( + let drawing: Drawing = dxf_loader::load_file(&file_name).context(format!( "Failed to load {friendly_file_name}...\n\tMake sure the file is a valid .dxf file.", ))?; // 传入文件名、步长、dxf解析对象drawing,创建elmt的definition标签