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