use crate::ole::OleError;
use bytes::Bytes;
use zerocopy::{FromBytes, LE, U16, U32, I16, I32, F64};
#[inline]
pub fn read_u16_le(data: &[u8], offset: usize) -> Result<u16, OleError> {
if offset + 2 > data.len() {
return Err(OleError::InvalidData("Not enough data for u16".to_string()));
}
U16::<LE>::read_from_bytes(&data[offset..offset + 2])
.map(|v| v.get())
.map_err(|_| OleError::InvalidData("Failed to read u16".to_string()))
}
#[inline]
pub fn read_u16_le_at(data: &[u8], offset: usize) -> Result<u16, OleError> {
read_u16_le(data, offset)
}
#[inline]
pub fn read_i16_le(data: &[u8], offset: usize) -> Result<i16, OleError> {
if offset + 2 > data.len() {
return Err(OleError::InvalidData("Not enough data for i16".to_string()));
}
I16::<LE>::read_from_bytes(&data[offset..offset + 2])
.map(|v| v.get())
.map_err(|_| OleError::InvalidData("Failed to read i16".to_string()))
}
#[inline]
pub fn read_u32_le(data: &[u8], offset: usize) -> Result<u32, OleError> {
if offset + 4 > data.len() {
return Err(OleError::InvalidData("Not enough data for u32".to_string()));
}
U32::<LE>::read_from_bytes(&data[offset..offset + 4])
.map(|v| v.get())
.map_err(|_| OleError::InvalidData("Failed to read u32".to_string()))
}
#[inline]
pub fn read_u32_le_at(data: &[u8], offset: usize) -> Result<u32, OleError> {
read_u32_le(data, offset)
}
#[inline]
pub fn read_i32_le(data: &[u8], offset: usize) -> Result<i32, OleError> {
if offset + 4 > data.len() {
return Err(OleError::InvalidData("Not enough data for i32".to_string()));
}
I32::<LE>::read_from_bytes(&data[offset..offset + 4])
.map(|v| v.get())
.map_err(|_| OleError::InvalidData("Failed to read i32".to_string()))
}
#[inline]
pub fn read_f64_le(data: &[u8], offset: usize) -> Result<f64, OleError> {
if offset + 8 > data.len() {
return Err(OleError::InvalidData("Not enough data for f64".to_string()));
}
F64::<LE>::read_from_bytes(&data[offset..offset + 8])
.map(|v| v.get())
.map_err(|_| OleError::InvalidData("Failed to read f64".to_string()))
}
#[inline]
pub fn read_f64_le_at(data: &[u8], offset: usize) -> Result<f64, OleError> {
read_f64_le(data, offset)
}
pub fn parse_utf16le_string(data: &[u8]) -> String {
if data.is_empty() || data.len() < 2 {
return String::new();
}
let estimated_chars = data.len() / 2;
let mut result = String::with_capacity(estimated_chars);
let mut i = 0;
while i + 1 < data.len() {
let code_unit = U16::<LE>::read_from_bytes(&data[i..i + 2])
.map(|v| v.get())
.unwrap_or(0);
i += 2;
if code_unit == 0 {
break;
}
if let Some(ch) = char::from_u32(code_unit as u32) {
result.push(ch);
}
}
result.shrink_to_fit();
result
}
pub fn parse_utf16le_string_len(data: &[u8], offset: usize, char_count: usize) -> String {
let byte_count = char_count * 2;
if offset + byte_count > data.len() {
return String::new();
}
let mut result = String::with_capacity(char_count);
let mut pos = offset;
let end = offset + byte_count;
while pos + 1 < end {
let code_unit = U16::<LE>::read_from_bytes(&data[pos..pos + 2])
.map(|v| v.get())
.unwrap_or(0);
pos += 2;
if let Some(ch) = char::from_u32(code_unit as u32) {
result.push(ch);
}
}
result
}
pub fn parse_windows1252_string(data: &[u8]) -> String {
data.iter()
.take_while(|&&b| b != 0)
.map(|&b| windows_1252_to_char(b))
.collect()
}
pub fn parse_windows1252_string_len(data: &[u8], offset: usize, length: usize) -> String {
if offset + length > data.len() {
return String::new();
}
data[offset..offset + length]
.iter()
.map(|&b| windows_1252_to_char(b))
.collect()
}
#[inline]
fn windows_1252_to_char(byte: u8) -> char {
match byte {
0x80 => '€',
0x82 => '‚',
0x83 => 'Æ’',
0x84 => '„',
0x85 => '…',
0x86 => '†',
0x87 => '‡',
0x88 => 'ˆ',
0x89 => '‰',
0x8A => 'Å ',
0x8B => '‹',
0x8C => 'Å’',
0x8E => 'Ž',
0x91 => '\u{2018}',
0x92 => '\u{2019}',
0x93 => '"',
0x94 => '"',
0x95 => '•',
0x96 => '–',
0x97 => '—',
0x98 => '˜',
0x99 => 'â„¢',
0x9A => 'Å¡',
0x9B => '›',
0x9C => 'Å“',
0x9E => 'ž',
0x9F => 'Ÿ',
_ => byte as char,
}
}
pub struct PlcfParser {
positions: Vec<u32>,
properties_data: Bytes,
properties_offsets: Vec<(usize, usize)>, }
impl PlcfParser {
pub fn parse(data: &[u8], element_size: usize) -> Option<Self> {
if data.len() < 4 {
return None;
}
let n = if element_size > 0 {
(data.len() - 4) / (4 + element_size)
} else {
return None;
};
if n == 0 {
return Some(Self {
positions: Vec::new(),
properties_data: Bytes::new(),
properties_offsets: Vec::new(),
});
}
let mut positions = Vec::with_capacity(n + 1);
for i in 0..=n {
let offset = i * 4;
if let Ok(cp) = read_u32_le(data, offset) {
positions.push(cp);
} else {
return None;
}
}
let props_start = (n + 1) * 4;
let props_end = props_start + (n * element_size);
if props_end > data.len() {
return None;
}
let properties_data = Bytes::copy_from_slice(&data[props_start..props_end]);
let mut properties_offsets = Vec::with_capacity(n);
for i in 0..n {
let offset = i * element_size;
properties_offsets.push((offset, element_size));
}
Some(Self {
positions,
properties_data,
properties_offsets,
})
}
#[inline]
pub fn count(&self) -> usize {
self.properties_offsets.len()
}
#[inline]
pub fn position(&self, index: usize) -> Option<u32> {
self.positions.get(index).copied()
}
#[inline]
pub fn property(&self, index: usize) -> Option<&[u8]> {
self.properties_offsets.get(index).map(|(offset, len)| {
&self.properties_data[*offset..*offset + *len]
})
}
pub fn range(&self, index: usize) -> Option<(u32, u32)> {
if index >= self.properties_offsets.len() {
return None;
}
Some((self.positions[index], self.positions[index + 1]))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_read_u16_le() {
let data = [0x34, 0x12, 0x78, 0x56];
assert!(read_u16_le(&data, 0).is_ok_and(|v| v == 0x1234));
assert!(read_u16_le(&data, 2).is_ok_and(|v| v == 0x5678));
assert!(read_u16_le(&data, 3).is_err());
}
#[test]
fn test_read_u32_le() {
let data = [0x78, 0x56, 0x34, 0x12];
assert!(read_u32_le(&data, 0).is_ok_and(|v| v == 0x12345678));
assert!(read_u32_le(&data, 1).is_err());
}
#[test]
fn test_parse_utf16le() {
let data = vec![
0x48, 0x00, 0x65, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x00, 0x00, ];
assert_eq!(parse_utf16le_string(&data), "Hello");
}
#[test]
fn test_parse_windows1252() {
let data = b"Hello\x93World\x94\0";
let result = parse_windows1252_string(data);
assert!(result.starts_with("Hello"));
assert!(result.contains('"'));
}
#[test]
fn test_plcf_parser() {
let data = vec![
0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, ];
let plcf = PlcfParser::parse(&data, 2).unwrap();
assert_eq!(plcf.count(), 2);
assert_eq!(plcf.position(0), Some(0));
assert_eq!(plcf.position(1), Some(10));
assert_eq!(plcf.position(2), Some(20));
assert_eq!(plcf.range(0), Some((0, 10)));
assert_eq!(plcf.range(1), Some((10, 20)));
}
}