using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Text; using XP.Scan.Attributes; using XP.Scan.Models; namespace XP.Scan.Services { /// /// 扫描配置 INI 序列化实现 | Scan config INI serialization implementation /// 通过反射 + 自定义特性自动生成/解析 INI 内容 /// public class ScanConfigSerializer : IScanConfigSerializer { /// /// 将配置数据序列化为 INI 格式字符串 /// public string Serialize(ScanConfigData config) { if (config == null) throw new ArgumentNullException(nameof(config)); var sb = new StringBuilder(); var sections = GetSectionObjects(config); foreach (var (sectionName, sectionObj) in sections) { sb.AppendLine($"[{sectionName}]"); SerializeSection(sb, sectionObj); sb.AppendLine(); } return sb.ToString().TrimEnd(); } /// /// 将配置数据写入 INI 文件 /// public void SaveToFile(ScanConfigData config, string filePath) { if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException(nameof(filePath)); var directory = Path.GetDirectoryName(filePath); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } var content = Serialize(config); File.WriteAllText(filePath, content, new UTF8Encoding(false)); } /// /// 从 INI 文件读取配置数据 /// public ScanConfigData LoadFromFile(string filePath) { if (!File.Exists(filePath)) throw new FileNotFoundException($"INI file not found: {filePath}", filePath); var lines = File.ReadAllLines(filePath, Encoding.UTF8); var iniData = ParseIniLines(lines); var config = new ScanConfigData(); DeserializeSection(iniData, config.ProjectInfo); DeserializeSection(iniData, config.XRay); DeserializeSection(iniData, config.Detector); DeserializeSection(iniData, config.MoveControl); DeserializeSection(iniData, config.ScanSettings); DeserializeSection(iniData, config.Correction); return config; } #region 序列化辅助方法 /// /// 获取 ScanConfigData 中所有标注了 [IniSection] 的子对象 /// private List<(string SectionName, object SectionObj)> GetSectionObjects(ScanConfigData config) { var result = new List<(string, object)>(); foreach (var prop in typeof(ScanConfigData).GetProperties()) { var obj = prop.GetValue(config); if (obj == null) continue; var sectionAttr = obj.GetType().GetCustomAttribute(); if (sectionAttr != null) { result.Add((sectionAttr.SectionName, obj)); } } return result; } /// /// 序列化单个 Section 的所有属性为 key=value 行 /// private void SerializeSection(StringBuilder sb, object sectionObj) { foreach (var prop in sectionObj.GetType().GetProperties()) { var keyAttr = prop.GetCustomAttribute(); var keyName = keyAttr?.KeyName ?? prop.Name; var value = prop.GetValue(sectionObj); var valueStr = FormatValue(value); sb.AppendLine($"{keyName}={valueStr}"); } } /// /// 格式化属性值为 INI 字符串(使用 InvariantCulture) /// private string FormatValue(object? value) { if (value == null) return string.Empty; return value switch { double d => d.ToString(CultureInfo.InvariantCulture), float f => f.ToString(CultureInfo.InvariantCulture), _ => value.ToString() ?? string.Empty }; } #endregion #region 反序列化辅助方法 /// /// 解析 INI 文件行为 Section → Key/Value 字典 /// private Dictionary> ParseIniLines(string[] lines) { var result = new Dictionary>(StringComparer.OrdinalIgnoreCase); string currentSection = string.Empty; foreach (var rawLine in lines) { var line = rawLine.Trim(); // 跳过空行和注释 if (string.IsNullOrEmpty(line) || line.StartsWith(";") || line.StartsWith("#")) continue; // Section 头 if (line.StartsWith("[") && line.EndsWith("]")) { currentSection = line.Substring(1, line.Length - 2).Trim(); if (!result.ContainsKey(currentSection)) { result[currentSection] = new Dictionary(StringComparer.OrdinalIgnoreCase); } continue; } // Key=Value var eqIndex = line.IndexOf('='); if (eqIndex > 0 && !string.IsNullOrEmpty(currentSection)) { var key = line.Substring(0, eqIndex).Trim(); var val = line.Substring(eqIndex + 1).Trim(); result[currentSection][key] = val; } } return result; } /// /// 将 INI 数据反序列化到模型对象 /// private void DeserializeSection(Dictionary> iniData, object sectionObj) { var sectionAttr = sectionObj.GetType().GetCustomAttribute(); if (sectionAttr == null) return; if (!iniData.TryGetValue(sectionAttr.SectionName, out var sectionData)) return; foreach (var prop in sectionObj.GetType().GetProperties()) { var keyAttr = prop.GetCustomAttribute(); var keyName = keyAttr?.KeyName ?? prop.Name; if (!sectionData.TryGetValue(keyName, out var valueStr)) continue; try { var convertedValue = ConvertValue(valueStr, prop.PropertyType); if (convertedValue != null) { prop.SetValue(sectionObj, convertedValue); } } catch { // 转换失败时保留默认值 } } } /// /// 将字符串值转换为目标类型 /// private object? ConvertValue(string value, Type targetType) { if (targetType == typeof(string)) return value; if (targetType == typeof(int)) return int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var i) ? i : null; if (targetType == typeof(double)) return double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var d) ? d : null; if (targetType == typeof(float)) return float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var f) ? f : null; if (targetType == typeof(bool)) return bool.TryParse(value, out var b) ? b : null; return null; } #endregion } }