Files
XplorePlane/XP.Scan/Services/ScanConfigSerializer.cs
T
2026-04-22 15:16:43 +08:00

237 lines
8.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
}