fix/加入兼容层处理不支持的扩展组码-qdj-0429
parent
f2d9fb75c7
commit
5dd4d232e7
@ -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<u8>,
|
||||
removed_pairs: usize,
|
||||
}
|
||||
|
||||
pub fn load_file(path: &Path) -> Result<Drawing> {
|
||||
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<Option<SanitizedDxf>> {
|
||||
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<SanitizedDxf> {
|
||||
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<i32> {
|
||||
std::str::from_utf8(trimmed_ascii(line))
|
||||
.ok()?
|
||||
.parse::<i32>()
|
||||
.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());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue