237 lines
8.1 KiB
C#
237 lines
8.1 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 扫描配置 INI 序列化实现 | Scan config INI serialization implementation
|
||
/// 通过反射 + 自定义特性自动生成/解析 INI 内容
|
||
/// </summary>
|
||
public class ScanConfigSerializer : IScanConfigSerializer
|
||
{
|
||
/// <summary>
|
||
/// 将配置数据序列化为 INI 格式字符串
|
||
/// </summary>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将配置数据写入 INI 文件
|
||
/// </summary>
|
||
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));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 INI 文件读取配置数据
|
||
/// </summary>
|
||
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 序列化辅助方法
|
||
|
||
/// <summary>
|
||
/// 获取 ScanConfigData 中所有标注了 [IniSection] 的子对象
|
||
/// </summary>
|
||
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<IniSectionAttribute>();
|
||
if (sectionAttr != null)
|
||
{
|
||
result.Add((sectionAttr.SectionName, obj));
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 序列化单个 Section 的所有属性为 key=value 行
|
||
/// </summary>
|
||
private void SerializeSection(StringBuilder sb, object sectionObj)
|
||
{
|
||
foreach (var prop in sectionObj.GetType().GetProperties())
|
||
{
|
||
var keyAttr = prop.GetCustomAttribute<IniKeyAttribute>();
|
||
var keyName = keyAttr?.KeyName ?? prop.Name;
|
||
var value = prop.GetValue(sectionObj);
|
||
var valueStr = FormatValue(value);
|
||
|
||
sb.AppendLine($"{keyName}={valueStr}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 格式化属性值为 INI 字符串(使用 InvariantCulture)
|
||
/// </summary>
|
||
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 反序列化辅助方法
|
||
|
||
/// <summary>
|
||
/// 解析 INI 文件行为 Section → Key/Value 字典
|
||
/// </summary>
|
||
private Dictionary<string, Dictionary<string, string>> ParseIniLines(string[] lines)
|
||
{
|
||
var result = new Dictionary<string, Dictionary<string, string>>(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<string, string>(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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将 INI 数据反序列化到模型对象
|
||
/// </summary>
|
||
private void DeserializeSection(Dictionary<string, Dictionary<string, string>> iniData, object sectionObj)
|
||
{
|
||
var sectionAttr = sectionObj.GetType().GetCustomAttribute<IniSectionAttribute>();
|
||
if (sectionAttr == null) return;
|
||
|
||
if (!iniData.TryGetValue(sectionAttr.SectionName, out var sectionData))
|
||
return;
|
||
|
||
foreach (var prop in sectionObj.GetType().GetProperties())
|
||
{
|
||
var keyAttr = prop.GetCustomAttribute<IniKeyAttribute>();
|
||
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
|
||
{
|
||
// 转换失败时保留默认值
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将字符串值转换为目标类型
|
||
/// </summary>
|
||
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
|
||
}
|
||
}
|