增加扫描模式配置参数功能
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
using XP.Scan.Models;
|
||||
|
||||
namespace XP.Scan.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 扫描配置 INI 序列化接口 | Scan config INI serialization interface
|
||||
/// </summary>
|
||||
public interface IScanConfigSerializer
|
||||
{
|
||||
/// <summary>将配置数据序列化为 INI 格式字符串 | Serialize config to INI string</summary>
|
||||
string Serialize(ScanConfigData config);
|
||||
|
||||
/// <summary>将配置数据写入 INI 文件 | Save config to INI file</summary>
|
||||
void SaveToFile(ScanConfigData config, string filePath);
|
||||
|
||||
/// <summary>从 INI 文件读取配置数据 | Load config from INI file</summary>
|
||||
ScanConfigData LoadFromFile(string filePath);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user