增加扫描模式配置参数功能

This commit is contained in:
TianSong
2026-04-22 15:16:43 +08:00
parent faf58b2768
commit 14b027b44f
12 changed files with 920 additions and 0 deletions
+236
View File
@@ -0,0 +1,236 @@
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
}
}