use quick_xml::events::{BytesStart, Event};
use quick_xml::Reader;
use crate::slave::core::Slave;
use crate::utils::esi::{
EsiBootstrapInfo, EsiCoEDetails, EsiDcConfiguration, EsiDcOpMode, EsiDeviceInfo,
EsiEepromConfiguration, EsiPdoConfiguration, EsiPdoEntry, EsiPdoInfo, EsiSyncManagerInfo,
parse_u16_hex_or_dec, parse_uint_hex_or_dec,
};
use crate::utils::ffi;
pub struct EsiLoader {
slave: Slave,
loaded: bool,
file_path: Option<String>,
device_info: Option<EsiDeviceInfo>,
}
impl EsiLoader {
pub(crate) fn new(slave: Slave) -> Self {
Self {
slave,
loaded: false,
file_path: None,
device_info: None,
}
}
pub fn load(&mut self, file_path: &str) -> bool {
if file_path.is_empty() {
return false;
}
if !std::path::Path::new(file_path).exists() {
return false;
}
let info = match load_esi_file(file_path) {
Ok(info) => info,
Err(_) => return false,
};
let xml_content = match read_file_auto_encoding(file_path) {
Ok(content) => content,
Err(_) => {
self.loaded = true;
self.file_path = Some(file_path.to_string());
self.device_info = Some(info);
return true;
}
};
let params = extract_startup_parameters(&xml_content);
for param in ¶ms {
let _ = self.slave.add_startup_parameter(
param.index,
param.subindex,
¶m.data,
param.transition,
param.timing,
param.complete_access,
);
}
self.loaded = true;
self.file_path = Some(file_path.to_string());
self.device_info = Some(info);
true
}
pub fn is_loaded(&self) -> bool {
self.loaded
}
pub fn file_path(&self) -> Option<&str> {
self.file_path.as_deref()
}
pub fn device_info(&self) -> Option<&EsiDeviceInfo> {
self.device_info.as_ref()
}
}
impl std::fmt::Display for EsiLoader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.loaded {
write!(
f,
"从站 {}: ESI 已加载 ({})",
self.slave.index(),
self.file_path.as_deref().unwrap_or("未知")
)
} else {
write!(f, "从站 {}: ESI 未加载", self.slave.index())
}
}
}
pub struct EsiStartupParam {
pub index: u16,
pub subindex: u8,
pub data: Vec<u8>,
pub transition: u8,
pub timing: u8,
pub complete_access: bool,
}
fn read_file_auto_encoding(path: &str) -> Result<String, String> {
let raw_bytes = std::fs::read(path).map_err(|e| format!("读取文件失败: {}", e))?;
if raw_bytes.starts_with(&[0xEF, 0xBB, 0xBF]) {
return String::from_utf8(raw_bytes[3..].to_vec())
.map_err(|e| format!("UTF-8 解码失败: {}", e));
}
if raw_bytes.starts_with(&[0xFF, 0xFE]) {
let (result, _, had_errors) = encoding_rs::UTF_16LE.decode(&raw_bytes[2..]);
if had_errors {
return Err("UTF-16 LE 解码失败".to_string());
}
return Ok(result.into_owned());
}
if raw_bytes.starts_with(&[0xFE, 0xFF]) {
let (result, _, had_errors) = encoding_rs::UTF_16BE.decode(&raw_bytes[2..]);
if had_errors {
return Err("UTF-16 BE 解码失败".to_string());
}
return Ok(result.into_owned());
}
if let Ok(s) = String::from_utf8(raw_bytes.clone()) {
return Ok(s);
}
let encoding = detect_xml_encoding(&raw_bytes);
let decoder = match encoding.to_lowercase().as_str() {
"gbk" | "gb2312" | "gb18030" => encoding_rs::GBK,
"iso-8859-1" | "latin1" => encoding_rs::WINDOWS_1252,
"shift_jis" | "shift-jis" => encoding_rs::SHIFT_JIS,
_ => encoding_rs::GBK,
};
let (result, _, had_errors) = decoder.decode(&raw_bytes);
if had_errors {
return Err(format!("使用 {} 编码解码失败", encoding));
}
Ok(result.into_owned())
}
fn detect_xml_encoding(data: &[u8]) -> String {
let search_len = data.len().min(200);
let header = &data[..search_len];
if let Some(pos) = header
.windows(9)
.position(|w| w.eq_ignore_ascii_case(b"encoding="))
{
let after = &header[pos + 9..];
if let Some("e) = after.first() {
if quote == b'"' || quote == b'\'' {
if let Some(end) = after[1..].iter().position(|&b| b == quote) {
if let Ok(enc) = std::str::from_utf8(&after[1..1 + end]) {
return enc.to_string();
}
}
}
}
}
"utf-8".to_string()
}
pub fn load_esi_file(path: &str) -> Result<EsiDeviceInfo, String> {
let xml_content = read_file_auto_encoding(path)?;
parse_esi_xml(&xml_content)
}
pub fn load_and_apply_startup(mi: u16, si: u16, path: &str) -> Result<i32, String> {
let xml_content = read_file_auto_encoding(path)?;
let params = extract_startup_parameters(&xml_content);
let mut count = 0i32;
for param in ¶ms {
let mut ffi_param: ffi::StartupParam = unsafe { std::mem::zeroed() };
ffi_param.index = param.index;
ffi_param.sub_index = param.subindex;
ffi_param.data_len = param.data.len() as u16;
ffi_param.transition = param.transition;
ffi_param.timing = param.timing;
ffi_param.complete_access = if param.complete_access { 1 } else { 0 };
let copy_len = param.data.len().min(256);
ffi_param.data[..copy_len].copy_from_slice(¶m.data[..copy_len]);
let ret = unsafe { ffi::AddStartupParameter(mi, si, &ffi_param) };
if ret >= 0 {
count += 1;
} else {
return Err(format!(
"添加启动参数失败: 索引=0x{:04X} 子索引={} 返回码={}",
param.index, param.subindex, ret
));
}
}
Ok(count)
}
fn parse_esi_xml(xml: &str) -> Result<EsiDeviceInfo, String> {
let mut reader = Reader::from_str(xml);
reader.config_mut().trim_text(true);
let mut info = EsiDeviceInfo::default();
let mut buf = Vec::new();
if !navigate_to_device(&mut reader, &mut buf)? {
return Err("未找到 <Descriptions><Devices><Device> 节点".to_string());
}
parse_device_element(&mut reader, &mut buf, &mut info)?;
Ok(info)
}
fn navigate_to_device(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<bool, String> {
let mut depth_path: Vec<String> = Vec::new();
loop {
buf.clear();
match reader.read_event_into(buf) {
Ok(Event::Start(ref e)) => {
let name = local_name_str(e);
depth_path.push(name.clone());
if depth_path.len() >= 3 {
let len = depth_path.len();
if depth_path[len - 3] == "Descriptions"
&& depth_path[len - 2] == "Devices"
&& depth_path[len - 1] == "Device"
{
return Ok(true);
}
}
}
Ok(Event::End(_)) => {
depth_path.pop();
}
Ok(Event::Eof) => return Ok(false),
Err(e) => return Err(format!("XML 解析错误: {}", e)),
_ => {}
}
}
}
fn parse_device_element(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
info: &mut EsiDeviceInfo,
) -> Result<(), String> {
let mut sm_list: Vec<EsiSyncManagerInfo> = Vec::new();
let mut pdo_config = EsiPdoConfiguration::default();
let mut depth = 1u32;
loop {
buf.clear();
match reader.read_event_into(buf) {
Ok(Event::Start(e)) => {
let e = e.into_owned();
let tag = local_name_str(&e);
depth += 1;
match tag.as_str() {
"Type" => {
parse_type_element(reader, buf, &e, info)?;
depth -= 1;
}
"Name" => {
if depth == 2 {
let text = read_text_content(reader, buf)?;
if info.product_name.is_empty() {
info.product_name = text;
}
depth -= 1;
}
}
"GroupType" => {
info.group_type = read_text_content(reader, buf)?;
depth -= 1;
}
"Profile" => {
parse_profile_element(reader, buf, info)?;
depth -= 1;
}
"Mailbox" => {
parse_mailbox_element(reader, buf, info)?;
depth -= 1;
}
"Dc" => {
let dc = parse_dc_element(reader, buf)?;
info.dc_configuration = Some(dc);
depth -= 1;
}
"Sm" => {
let sm = parse_sm_element(reader, buf, &e)?;
sm_list.push(sm);
depth -= 1;
}
"RxPdo" => {
let pdo = parse_pdo_element(reader, buf, &e)?;
pdo_config.rx_pdos.push(pdo);
depth -= 1;
}
"TxPdo" => {
let pdo = parse_pdo_element(reader, buf, &e)?;
pdo_config.tx_pdos.push(pdo);
depth -= 1;
}
"Eeprom" => {
let eeprom = parse_eeprom_element(reader, buf)?;
info.eeprom_configuration = Some(eeprom);
depth -= 1;
}
_ => {
}
}
}
Ok(Event::End(_)) => {
depth -= 1;
if depth == 0 {
break;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(format!("XML 解析错误: {}", e)),
_ => {}
}
}
let _ = sm_list;
let _ = pdo_config;
Ok(())
}
fn parse_type_element(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
start: &BytesStart<'_>,
info: &mut EsiDeviceInfo,
) -> Result<(), String> {
for attr in start.attributes().flatten() {
let key = std::str::from_utf8(attr.key.as_ref()).unwrap_or("");
let val = attr.unescape_value().unwrap_or_default();
match key {
"ProductCode" => {
info.product_id = parse_uint_hex_or_dec(&val).unwrap_or(0);
}
"RevisionNo" => {
info.revision_id = parse_uint_hex_or_dec(&val).unwrap_or(0);
}
_ => {}
}
}
let type_name = read_text_content(reader, buf)?;
if info.product_name.is_empty() {
info.product_name = type_name;
}
Ok(())
}
fn parse_profile_element(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
info: &mut EsiDeviceInfo,
) -> Result<(), String> {
let mut depth = 1u32;
loop {
buf.clear();
match reader.read_event_into(buf) {
Ok(Event::Start(ref e)) => {
let tag = local_name_str(e);
depth += 1;
if tag == "ProfileNo" {
info.profile_number = read_text_content(reader, buf)?;
depth -= 1;
}
}
Ok(Event::End(_)) => {
depth -= 1;
if depth == 0 {
break;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(format!("解析 Profile 错误: {}", e)),
_ => {}
}
}
Ok(())
}
fn parse_mailbox_element(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
info: &mut EsiDeviceInfo,
) -> Result<(), String> {
let mut depth = 1u32;
let mut coe_details = EsiCoEDetails::default();
let mut has_coe = false;
loop {
buf.clear();
match reader.read_event_into(buf) {
Ok(Event::Start(ref e)) => {
let tag = local_name_str(e);
depth += 1;
match tag.as_str() {
"CoE" => {
has_coe = true;
info.supports_coe = true;
for attr in e.attributes().flatten() {
let key =
std::str::from_utf8(attr.key.as_ref()).unwrap_or("");
let val = attr.unescape_value().unwrap_or_default();
let bval = val == "1" || val.eq_ignore_ascii_case("true");
match key {
"SdoInfo" => coe_details.sdo_info = bval,
"PdoAssign" => coe_details.pdo_assign = bval,
"PdoConfig" | "PdoConfiguration" => {
coe_details.pdo_config = bval
}
"CompleteAccess" => {
coe_details.complete_access = bval
}
"PdoUpload" | "SegmentedSdo" => {
coe_details.segmented_sdo = bval
}
"DiagHistory" => coe_details.diag_history = bval,
_ => {}
}
}
}
"SoE" => info.supports_soe = true,
"FoE" => info.supports_foe = true,
"EoE" => info.supports_eoe = true,
"VoE" => info.supports_voe = true,
_ => {}
}
}
Ok(Event::Empty(ref e)) => {
let tag = local_name_str(e);
match tag.as_str() {
"CoE" => {
has_coe = true;
info.supports_coe = true;
for attr in e.attributes().flatten() {
let key =
std::str::from_utf8(attr.key.as_ref()).unwrap_or("");
let val = attr.unescape_value().unwrap_or_default();
let bval = val == "1" || val.eq_ignore_ascii_case("true");
match key {
"SdoInfo" => coe_details.sdo_info = bval,
"PdoAssign" => coe_details.pdo_assign = bval,
"PdoConfig" | "PdoConfiguration" => {
coe_details.pdo_config = bval
}
"CompleteAccess" => {
coe_details.complete_access = bval
}
"PdoUpload" | "SegmentedSdo" => {
coe_details.segmented_sdo = bval
}
"DiagHistory" => coe_details.diag_history = bval,
_ => {}
}
}
}
"SoE" => info.supports_soe = true,
"FoE" => info.supports_foe = true,
"EoE" => info.supports_eoe = true,
"VoE" => info.supports_voe = true,
_ => {}
}
}
Ok(Event::End(_)) => {
depth -= 1;
if depth == 0 {
break;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(format!("解析 Mailbox 错误: {}", e)),
_ => {}
}
}
if has_coe {
info.coe_details = Some(coe_details);
}
Ok(())
}
fn parse_dc_element(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<EsiDcConfiguration, String> {
let mut dc = EsiDcConfiguration::default();
let mut depth = 1u32;
loop {
buf.clear();
match reader.read_event_into(buf) {
Ok(Event::Start(ref e)) => {
let tag = local_name_str(e);
depth += 1;
if tag == "OpMode" {
let mode = parse_dc_opmode(reader, buf)?;
dc.op_modes.push(mode);
depth -= 1;
}
}
Ok(Event::End(_)) => {
depth -= 1;
if depth == 0 {
break;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(format!("解析 Dc 错误: {}", e)),
_ => {}
}
}
Ok(dc)
}
fn parse_dc_opmode(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<EsiDcOpMode, String> {
let mut mode = EsiDcOpMode::default();
let mut depth = 1u32;
loop {
buf.clear();
match reader.read_event_into(buf) {
Ok(Event::Start(ref e)) => {
let tag = local_name_str(e);
depth += 1;
match tag.as_str() {
"Name" => {
mode.name = read_text_content(reader, buf)?;
depth -= 1;
}
"Description" => {
mode.description = read_text_content(reader, buf)?;
depth -= 1;
}
"AssignActivate" => {
let text = read_text_content(reader, buf)?;
mode.assign_activate =
parse_u16_hex_or_dec(&text).unwrap_or(0);
depth -= 1;
}
"CycleTimeSync0" => {
let text = read_text_content(reader, buf)?;
mode.cycle_time_sync0 =
parse_uint_hex_or_dec(&text).unwrap_or(0);
depth -= 1;
}
"CycleTimeSync1" => {
let text = read_text_content(reader, buf)?;
mode.cycle_time_sync1 =
parse_uint_hex_or_dec(&text).unwrap_or(0);
depth -= 1;
}
"ShiftTimeSync0" | "ShiftTime" => {
let text = read_text_content(reader, buf)?;
mode.shift_time_sync0 =
text.trim().parse::<i32>().unwrap_or(0);
depth -= 1;
}
"ShiftTimeSync1" => {
let text = read_text_content(reader, buf)?;
mode.shift_time_sync1 =
text.trim().parse::<i32>().unwrap_or(0);
depth -= 1;
}
"CycleTimeSync0Factor" => {
let text = read_text_content(reader, buf)?;
mode.cycle_time_sync0_factor =
text.trim().parse::<i32>().unwrap_or(0);
depth -= 1;
}
"CycleTimeSync1Factor" => {
let text = read_text_content(reader, buf)?;
mode.cycle_time_sync1_factor =
text.trim().parse::<i32>().unwrap_or(0);
depth -= 1;
}
_ => {}
}
}
Ok(Event::End(_)) => {
depth -= 1;
if depth == 0 {
break;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(format!("解析 OpMode 错误: {}", e)),
_ => {}
}
}
Ok(mode)
}
fn parse_sm_element(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
start: &BytesStart<'_>,
) -> Result<EsiSyncManagerInfo, String> {
let mut sm = EsiSyncManagerInfo::default();
for attr in start.attributes().flatten() {
let key = std::str::from_utf8(attr.key.as_ref()).unwrap_or("");
let val = attr.unescape_value().unwrap_or_default();
match key {
"No" | "Index" => {
sm.index = val.trim().parse::<i32>().unwrap_or(0);
}
"StartAddress" => {
sm.start_address = parse_u16_hex_or_dec(&val).unwrap_or(0);
}
"DefaultSize" => {
sm.default_size = parse_u16_hex_or_dec(&val).unwrap_or(0);
}
"ControlByte" => {
sm.control_byte = parse_uint_hex_or_dec(&val).unwrap_or(0) as u8;
}
"Enable" => {
let bval = val == "1" || val.eq_ignore_ascii_case("true");
sm.enable = bval;
}
"MinSize" => {
sm.min_size = parse_u16_hex_or_dec(&val).unwrap_or(0);
}
"MaxSize" => {
sm.max_size = parse_u16_hex_or_dec(&val).unwrap_or(0);
}
_ => {}
}
}
let text = read_text_content(reader, buf)?;
if !text.is_empty() {
sm.name = text;
}
Ok(sm)
}
fn parse_pdo_element(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
start: &BytesStart<'_>,
) -> Result<EsiPdoInfo, String> {
let mut pdo = EsiPdoInfo::default();
for attr in start.attributes().flatten() {
let key = std::str::from_utf8(attr.key.as_ref()).unwrap_or("");
let val = attr.unescape_value().unwrap_or_default();
match key {
"Fixed" => {
pdo.is_fixed = val == "1" || val.eq_ignore_ascii_case("true");
}
"Mandatory" => {
pdo.is_mandatory = val == "1" || val.eq_ignore_ascii_case("true");
}
"Sm" | "SM" => {
pdo.sync_manager = val.trim().parse::<u8>().unwrap_or(0);
}
_ => {}
}
}
let mut depth = 1u32;
loop {
buf.clear();
match reader.read_event_into(buf) {
Ok(Event::Start(ref e)) => {
let tag = local_name_str(e);
depth += 1;
match tag.as_str() {
"Index" => {
let text = read_text_content(reader, buf)?;
pdo.index = parse_u16_hex_or_dec(&text).unwrap_or(0);
depth -= 1;
}
"Name" => {
pdo.name = read_text_content(reader, buf)?;
depth -= 1;
}
"Exclude" => {
let text = read_text_content(reader, buf)?;
if let Some(v) = parse_u16_hex_or_dec(&text) {
pdo.exclude_indices.push(v);
}
depth -= 1;
}
"Entry" => {
let entry = parse_pdo_entry(reader, buf)?;
pdo.entries.push(entry);
depth -= 1;
}
_ => {}
}
}
Ok(Event::End(_)) => {
depth -= 1;
if depth == 0 {
break;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(format!("解析 PDO 错误: {}", e)),
_ => {}
}
}
Ok(pdo)
}
fn parse_pdo_entry(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<EsiPdoEntry, String> {
let mut entry = EsiPdoEntry::default();
let mut depth = 1u32;
loop {
buf.clear();
match reader.read_event_into(buf) {
Ok(Event::Start(ref e)) => {
let tag = local_name_str(e);
depth += 1;
match tag.as_str() {
"Index" => {
let text = read_text_content(reader, buf)?;
entry.index = parse_u16_hex_or_dec(&text).unwrap_or(0);
depth -= 1;
}
"SubIndex" => {
let text = read_text_content(reader, buf)?;
entry.sub_index =
parse_uint_hex_or_dec(&text).unwrap_or(0) as u8;
depth -= 1;
}
"BitLen" => {
let text = read_text_content(reader, buf)?;
entry.bit_length =
text.trim().parse::<u8>().unwrap_or(0);
depth -= 1;
}
"Name" => {
entry.name = read_text_content(reader, buf)?;
depth -= 1;
}
"DataType" => {
entry.data_type = read_text_content(reader, buf)?;
depth -= 1;
}
_ => {}
}
}
Ok(Event::End(_)) => {
depth -= 1;
if depth == 0 {
break;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(format!("解析 PDO Entry 错误: {}", e)),
_ => {}
}
}
Ok(entry)
}
fn parse_eeprom_element(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<EsiEepromConfiguration, String> {
let mut eeprom = EsiEepromConfiguration::default();
let mut depth = 1u32;
loop {
buf.clear();
match reader.read_event_into(buf) {
Ok(Event::Start(ref e)) => {
let tag = local_name_str(e);
depth += 1;
match tag.as_str() {
"ByteSize" => {
let text = read_text_content(reader, buf)?;
eeprom.byte_size = text.trim().parse::<i32>().unwrap_or(0);
depth -= 1;
}
"ConfigData" => {
eeprom.config_data = read_text_content(reader, buf)?;
depth -= 1;
}
"BootStrap" => {
let bs = parse_bootstrap_element(reader, buf)?;
eeprom.bootstrap = Some(bs);
depth -= 1;
}
_ => {}
}
}
Ok(Event::End(_)) => {
depth -= 1;
if depth == 0 {
break;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(format!("解析 Eeprom 错误: {}", e)),
_ => {}
}
}
Ok(eeprom)
}
fn parse_bootstrap_element(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<EsiBootstrapInfo, String> {
let mut bs = EsiBootstrapInfo::default();
let mut depth = 1u32;
loop {
buf.clear();
match reader.read_event_into(buf) {
Ok(Event::Start(ref e)) => {
let tag = local_name_str(e);
depth += 1;
match tag.as_str() {
"RecvMbxOffset" | "RecvMailboxOffset" => {
let text = read_text_content(reader, buf)?;
bs.recv_mailbox_offset =
parse_u16_hex_or_dec(&text).unwrap_or(0);
depth -= 1;
}
"RecvMbxSize" | "RecvMailboxSize" => {
let text = read_text_content(reader, buf)?;
bs.recv_mailbox_size =
parse_u16_hex_or_dec(&text).unwrap_or(0);
depth -= 1;
}
"SendMbxOffset" | "SendMailboxOffset" => {
let text = read_text_content(reader, buf)?;
bs.send_mailbox_offset =
parse_u16_hex_or_dec(&text).unwrap_or(0);
depth -= 1;
}
"SendMbxSize" | "SendMailboxSize" => {
let text = read_text_content(reader, buf)?;
bs.send_mailbox_size =
parse_u16_hex_or_dec(&text).unwrap_or(0);
depth -= 1;
}
_ => {}
}
}
Ok(Event::End(_)) => {
depth -= 1;
if depth == 0 {
break;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(format!("解析 BootStrap 错误: {}", e)),
_ => {}
}
}
Ok(bs)
}
pub fn extract_startup_parameters(xml: &str) -> Vec<EsiStartupParam> {
let mut params = Vec::new();
let mut reader = Reader::from_str(xml);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
loop {
buf.clear();
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e)) => {
let tag = local_name_str(e);
if tag == "InitCmd" {
if let Ok(Some(param)) =
parse_init_cmd_element(&mut reader, &mut buf)
{
params.push(param);
}
}
}
Ok(Event::Eof) => break,
Err(_) => break,
_ => {}
}
}
params
}
fn parse_init_cmd_element(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<Option<EsiStartupParam>, String> {
let mut index: Option<u16> = None;
let mut subindex: u8 = 0;
let mut data: Vec<u8> = Vec::new();
let mut transition: u8 = ffi::TRANS_PS;
let mut timing: u8 = ffi::TIMING_BEFORE;
let mut complete_access = false;
let mut depth = 1u32;
loop {
buf.clear();
match reader.read_event_into(buf) {
Ok(Event::Start(ref e)) => {
let tag = local_name_str(e);
depth += 1;
match tag.as_str() {
"Index" => {
let text = read_text_content(reader, buf)?;
index = parse_u16_hex_or_dec(&text);
depth -= 1;
}
"SubIndex" => {
let text = read_text_content(reader, buf)?;
subindex =
parse_uint_hex_or_dec(&text).unwrap_or(0) as u8;
depth -= 1;
}
"Data" => {
let text = read_text_content(reader, buf)?;
data = parse_hex_string(&text);
depth -= 1;
}
"Transition" => {
let text = read_text_content(reader, buf)?;
transition = match text.trim() {
"IP" => ffi::TRANS_IP,
"PS" => ffi::TRANS_PS,
"SO" => ffi::TRANS_SO,
"OS" => ffi::TRANS_OS,
"SP" => ffi::TRANS_SP,
"PI" => ffi::TRANS_PI,
_ => ffi::TRANS_PS,
};
depth -= 1;
}
"Timeout" | "Timing" => {
let text = read_text_content(reader, buf)?;
timing = match text.trim() {
"After" | "after" | "AFTER" => ffi::TIMING_AFTER,
_ => ffi::TIMING_BEFORE,
};
depth -= 1;
}
"CompleteAccess" => {
let text = read_text_content(reader, buf)?;
complete_access = matches!(
text.trim(),
"1" | "true" | "True" | "TRUE"
);
depth -= 1;
}
_ => {}
}
}
Ok(Event::End(_)) => {
depth -= 1;
if depth == 0 {
break;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(format!("解析 InitCmd 错误: {}", e)),
_ => {}
}
}
match index {
Some(idx) => Ok(Some(EsiStartupParam {
index: idx,
subindex,
data,
transition,
timing,
complete_access,
})),
None => Ok(None),
}
}
fn local_name_str(e: &BytesStart<'_>) -> String {
let full = e.name();
let local = full.local_name();
std::str::from_utf8(local.as_ref())
.unwrap_or("")
.to_string()
}
fn read_text_content(
reader: &mut Reader<&[u8]>,
buf: &mut Vec<u8>,
) -> Result<String, String> {
let mut text = String::new();
let mut depth = 1u32;
loop {
buf.clear();
match reader.read_event_into(buf) {
Ok(Event::Text(ref e)) => {
if let Ok(t) = e.unescape() {
text.push_str(&t);
}
}
Ok(Event::Start(_)) => {
depth += 1;
}
Ok(Event::End(_)) => {
depth -= 1;
if depth == 0 {
break;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(format!("读取文本内容错误: {}", e)),
_ => {}
}
}
Ok(text.trim().to_string())
}
fn parse_hex_string(hex_str: &str) -> Vec<u8> {
let hex = hex_str.trim();
let mut bytes = Vec::new();
let chars: Vec<char> = hex.chars().collect();
let mut i = 0;
while i + 1 < chars.len() {
if let (Some(high), Some(low)) = (chars[i].to_digit(16), chars[i + 1].to_digit(16)) {
bytes.push((high << 4 | low) as u8);
}
i += 2;
}
bytes
}