504 lines
19 KiB
C#
504 lines
19 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Text.RegularExpressions;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace Szmedi.RevitToolkit.Approval.Assists;
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Security.Cryptography;
|
||
using System.Text.RegularExpressions;
|
||
using System.Windows.Markup;
|
||
|
||
using Autodesk.Revit.DB.ExtensibleStorage;
|
||
|
||
using Newtonsoft.Json;
|
||
|
||
using Szmedi.RevitToolkit.Approval.Models;
|
||
|
||
using static Dapper.SqlMapper;
|
||
|
||
/// <summary>
|
||
/// 用于保存从MVDLite文本中解析出的结构化数据的类。
|
||
/// </summary>
|
||
public class MvdData
|
||
{
|
||
/// <summary>
|
||
/// 文件头部的元数据。
|
||
/// 例如:# MVDLite_Version = '2.5.0.0'
|
||
/// </summary>
|
||
public Dictionary<string, string> Metadata { get; set; } = [];
|
||
|
||
/// <summary>
|
||
/// 路径别名定义列表。
|
||
/// 例如: "hasAssignments as (IfcObjectDefinition)->HasAssignments->RelatedObjectsType"
|
||
/// </summary>
|
||
public List<Alias> Aliases { get; set; } = new List<Alias>();
|
||
|
||
/// <summary>
|
||
/// 实体定义列表。
|
||
/// </summary>
|
||
public List<EntityDefinition> EntityDefinitions { get; set; } = [];
|
||
}
|
||
|
||
/// <summary>
|
||
/// 代表一个规则定义
|
||
/// </summary>
|
||
public class Alias
|
||
{
|
||
public string Name { get; set; }
|
||
public string SourceType { get; set; }
|
||
public string Path { get; set; }
|
||
public override string ToString() => $"{Name} as ({SourceType}){Path}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 代表一个在 "定义" 块中定义的实体。
|
||
/// </summary>
|
||
public class EntityDefinition
|
||
{
|
||
/// <summary>
|
||
/// 继承那一行的名称
|
||
/// </summary>
|
||
public string Name { get; set; }
|
||
/// <summary>
|
||
/// 定义后的第一行描述
|
||
/// </summary>
|
||
public string Description { get; set; }
|
||
/// <summary>
|
||
/// 继承自的名称
|
||
/// </summary>
|
||
public string InheritsFrom { get; set; }
|
||
|
||
/// <summary>
|
||
/// 新增:专门存放“类型标签”的值列表
|
||
/// </summary>
|
||
public List<string> TypeLabels { get; set; } = [];
|
||
/// <summary>
|
||
/// 类型判断
|
||
/// </summary>
|
||
public List<string> Conditions { get; set; } = [];
|
||
/// <summary>
|
||
/// 约束,每个约束就是一个属性条目
|
||
/// </summary>
|
||
public List<Constraint> Constraints { get; set; } = [];
|
||
public string IfcMap { get; set; } // 来自 "# IFC_MAP = 'IfcElement'" 这样的注释
|
||
public override string ToString() => $"Entity: {Name}" + (string.IsNullOrEmpty(InheritsFrom) ? "" : $" inherits {InheritsFrom}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 代表一个在 "约束" 块中定义的规则,即一个属性条目
|
||
/// </summary>
|
||
public class Constraint
|
||
{
|
||
/// <summary>
|
||
/// 描述约束规则,如:'预制剪力墙 的属性 \"编号\" 应满足值类型约束规则'
|
||
/// </summary>
|
||
public string Description { get; set; }
|
||
/// <summary>
|
||
/// 表达式,如:预制剪力墙->任意属性集->任意属性('编号')->属性值[Type]>='STRING'
|
||
/// </summary>
|
||
public string Expression { get; set; }
|
||
/// <summary>
|
||
///用于解析注释中的额外信息,例如: # PropName = '型号规格',key:PropName,DefaultValue:型号规格,Key:Range,DefaultValue:>=0
|
||
/// 固定只有两个,一个是属性名,一个取值范围
|
||
/// </summary>
|
||
public Dictionary<string, string> PropertyDictionary { get; set; } = [];
|
||
/// <summary>
|
||
/// 参数值类型
|
||
/// </summary>
|
||
public string ParameterType
|
||
{
|
||
get
|
||
{
|
||
if (ValueRange.Contains("实数值"))
|
||
{
|
||
return "Number";
|
||
}
|
||
return "Text";
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 参数的取值范围
|
||
/// </summary>
|
||
public string[] Values
|
||
{
|
||
get
|
||
{
|
||
var range = ValueRange.Replace(" ", "");
|
||
if (range.Contains("实数值") || range.Contains(">=0"))
|
||
{
|
||
return ["0"];
|
||
}
|
||
else if (range.Contains(">0"))
|
||
{
|
||
if (range.Contains("<=1"))
|
||
{
|
||
return ["1"];
|
||
}
|
||
return ["100"];
|
||
}
|
||
else if (range.Contains("字符串值"))
|
||
{
|
||
return [""];
|
||
}
|
||
|
||
return [.. Regex.Matches(range, @"\\""([^""]*)\\""").OfType<Match>().Select(m => m.Groups[1].Value)];
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 属性名
|
||
/// </summary>
|
||
public string PropName => PropertyDictionary.ContainsKey("PropName") ? PropertyDictionary["PropName"] : string.Empty;
|
||
/// <summary>
|
||
/// 取值范围
|
||
/// </summary>
|
||
public string ValueRange => PropertyDictionary.ContainsKey("Range") ? PropertyDictionary["Range"] : string.Empty;
|
||
public override string ToString() => $"Constraint: {PropName} => {ValueRange}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 一个静态工具类,用于将MVDLite格式的文本数据解析为结构化的C#对象。
|
||
/// </summary>
|
||
public class MvdLiteAssist
|
||
{
|
||
public static List<string> GetSigns(Major major, string entityName, string inheritsFrom)
|
||
{
|
||
var archi = IOAssists.GetMvdLiteContent(major);
|
||
var archiData = MvdLiteAssist.Parse(archi);
|
||
var archiDefinition = MvdLiteAssist.GetDefinitionByName(archiData, entityName, inheritsFrom);
|
||
var archiExpression = archiDefinition.Constraints.FirstOrDefault().Expression;
|
||
var archiSigns = Regex.Matches(archiExpression, @"'([^']*)'").OfType<Match>().Select(m => m.Groups[1].Value);
|
||
return [.. archiSigns];
|
||
}
|
||
public static List<string> GetSigns(string majorName, string entityName, string inheritsFrom)
|
||
{
|
||
var archi = IOAssists.GetMvdLiteContent(majorName);
|
||
var archiData = MvdLiteAssist.Parse(archi);
|
||
var archiDefinition = MvdLiteAssist.GetDefinitionByName(archiData, entityName, inheritsFrom);
|
||
var archiExpression = archiDefinition.Constraints.FirstOrDefault().Expression;
|
||
var archiSigns = Regex.Matches(archiExpression, @"'([^']*)'").OfType<Match>().Select(m => m.Groups[1].Value);
|
||
return [.. archiSigns];
|
||
}
|
||
private enum ParserState
|
||
{
|
||
Idle, // 用于解析元数据和别名
|
||
InDefinition,
|
||
InConstraint
|
||
}
|
||
/// <summary>
|
||
/// 获取所有直接包含属性的集合(不包含子类继承父类的属性)
|
||
/// </summary>
|
||
/// <param name="mvdData"></param>
|
||
/// <returns></returns>
|
||
public static List<AfcaArchiProperty> GetProperties(MvdData mvdData)
|
||
{
|
||
List<AfcaArchiProperty> properties = [];
|
||
List<EntityDefinition> definitions = mvdData.EntityDefinitions;
|
||
|
||
foreach (var entity in definitions)
|
||
{
|
||
var constraints = entity.Constraints;
|
||
foreach (var cons in constraints)
|
||
{
|
||
if (string.IsNullOrEmpty(cons.PropName) || string.IsNullOrEmpty(cons.Description))
|
||
{
|
||
continue;
|
||
}
|
||
var property = new AfcaArchiProperty
|
||
{
|
||
DefinitionName = entity.Name,
|
||
Name = cons.PropName,
|
||
ValueConstraints = cons.Values,
|
||
ValueRange = cons.ValueRange,
|
||
};
|
||
|
||
properties.Add(property);
|
||
//sb.AppendLine($"Entity: {entity.Name}, Property: {cons.PropName}, DefaultValue: {cons.ValueRange}");
|
||
}
|
||
}
|
||
return properties;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有构件标识列表
|
||
/// </summary>
|
||
/// <param name="mvdData"></param>
|
||
/// <returns></returns>
|
||
public static HashSet<string> GetElementSigns(MvdData mvdData, string ifcClass = "IfcElement")
|
||
{
|
||
HashSet<string> signs = [];
|
||
List<EntityDefinition> definitions = mvdData.EntityDefinitions;
|
||
foreach (var entity in definitions)
|
||
{
|
||
if (!string.IsNullOrEmpty(entity.Description) && entity.IfcMap == ifcClass)
|
||
{
|
||
foreach (var label in entity.TypeLabels)
|
||
{
|
||
signs.Add(label);
|
||
}
|
||
}
|
||
}
|
||
return signs;
|
||
}
|
||
/// <summary>
|
||
/// 解析包含MVDLite数据的文本字符串。
|
||
/// </summary>
|
||
/// <param name="textContent">要解析的文本内容。</param>
|
||
/// <returns>一个包含所有解析后数据的 MvdData 对象。</returns>
|
||
public static MvdData Parse(string textContent)
|
||
{
|
||
var mvdData = new MvdData();
|
||
if (string.IsNullOrWhiteSpace(textContent)) return mvdData;
|
||
|
||
var lines = textContent.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
|
||
|
||
ParserState state = ParserState.Idle;
|
||
EntityDefinition currentEntity = null;
|
||
Constraint currentConstraint = null;
|
||
|
||
// 用于匹配不同类型行的正则表达式
|
||
var metaRegex = new Regex(@"^#\s*([^=]+?)\s*=\s*'(.*)'");
|
||
var aliasRegex = new Regex(@"^(\S+)\s+as\s+\((.*?)\)(.*)");
|
||
var entityInheritRegex = new Regex(@"^(\S+)\s+继承\s+(\S+)");
|
||
var descriptionCommentRegex = new Regex(@"^#\s*'(.*)'");
|
||
var ifcMapCommentRegex = new Regex(@"^#\s*IFC_MAP\s*=\s*'(.*)'");
|
||
var propCommentRegex = new Regex(@"^#\s*(\w+)\s*=\s*'(.*)'");
|
||
// 新增:专门用于匹配“类型标签”的正则表达式
|
||
var typeLabelRegex = new Regex(@"^(\S+)->类型标签\s*=\s*(.*)$");
|
||
|
||
foreach (var line in lines)
|
||
{
|
||
var trimmedLine = line.Trim();
|
||
|
||
if (trimmedLine.StartsWith("定义"))
|
||
{
|
||
state = ParserState.InDefinition;
|
||
currentEntity = new EntityDefinition();
|
||
mvdData.EntityDefinitions.Add(currentEntity);
|
||
continue;
|
||
}
|
||
if (trimmedLine.StartsWith("约束"))
|
||
{
|
||
state = ParserState.InConstraint;
|
||
currentConstraint = null;
|
||
continue;
|
||
}
|
||
if (trimmedLine.StartsWith("#BLOCK") || string.IsNullOrWhiteSpace(trimmedLine))
|
||
{
|
||
if (state == ParserState.InConstraint && currentEntity != null && currentConstraint != null && !string.IsNullOrEmpty(currentConstraint.Expression))
|
||
{
|
||
currentEntity.Constraints.Add(currentConstraint);
|
||
}
|
||
currentConstraint = null;
|
||
if (string.IsNullOrWhiteSpace(trimmedLine)) continue;
|
||
if (trimmedLine.StartsWith("#BLOCK"))
|
||
{
|
||
state = ParserState.Idle;
|
||
currentEntity = null;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
switch (state)
|
||
{
|
||
case ParserState.Idle:
|
||
Match metaMatch = metaRegex.Match(trimmedLine);
|
||
if (metaMatch.Success)
|
||
{
|
||
mvdData.Metadata[metaMatch.Groups[1].Value.Trim()] = metaMatch.Groups[2].Value;
|
||
break;
|
||
}
|
||
Match aliasMatch = aliasRegex.Match(trimmedLine);
|
||
if (aliasMatch.Success)
|
||
{
|
||
mvdData.Aliases.Add(new Alias
|
||
{
|
||
Name = aliasMatch.Groups[1].Value,
|
||
SourceType = aliasMatch.Groups[2].Value,
|
||
Path = aliasMatch.Groups[3].Value.Trim()
|
||
});
|
||
}
|
||
break;
|
||
|
||
case ParserState.InDefinition:
|
||
Match descMatch = descriptionCommentRegex.Match(trimmedLine);
|
||
if (descMatch.Success)
|
||
{
|
||
currentEntity.Description = descMatch.Groups[1].Value;
|
||
break;
|
||
}
|
||
|
||
Match ifcMapMatch = ifcMapCommentRegex.Match(trimmedLine);
|
||
if (ifcMapMatch.Success)
|
||
{
|
||
currentEntity.IfcMap = ifcMapMatch.Groups[1].Value;
|
||
break;
|
||
}
|
||
|
||
// --- 新增逻辑:优先处理类型标签 ---
|
||
Match typeLabelMatch = typeLabelRegex.Match(trimmedLine);
|
||
if (typeLabelMatch.Success)
|
||
{
|
||
string allLabelsRaw = typeLabelMatch.Groups[2].Value;
|
||
// 使用正则表达式从长字符串中提取所有被单引号包裹的值
|
||
var labelMatches = Regex.Matches(allLabelsRaw, @"'([^']*)'");
|
||
var labels = labelMatches.Cast<Match>()
|
||
.Select(m => m.Groups[1].Value)
|
||
.ToList();
|
||
currentEntity.TypeLabels.AddRange(labels);
|
||
break; // 处理完毕,跳过此行后续逻辑
|
||
}
|
||
// --- 结束新增逻辑 ---
|
||
|
||
Match entityInheritMatch = entityInheritRegex.Match(trimmedLine);
|
||
if (entityInheritMatch.Success)
|
||
{
|
||
currentEntity.Name = entityInheritMatch.Groups[1].Value;
|
||
currentEntity.InheritsFrom = entityInheritMatch.Groups[2].Value;
|
||
}
|
||
else if (!trimmedLine.StartsWith("#"))
|
||
{
|
||
// 其他非注释行、非类型标签行,被视为普通条件
|
||
currentEntity.Conditions.Add(trimmedLine);
|
||
}
|
||
break;
|
||
|
||
case ParserState.InConstraint:
|
||
if (trimmedLine.StartsWith("#"))
|
||
{
|
||
Match constraintDescMatch = descriptionCommentRegex.Match(trimmedLine);
|
||
if (constraintDescMatch.Success)
|
||
{
|
||
if (currentEntity != null && currentConstraint != null && !string.IsNullOrEmpty(currentConstraint.Expression))
|
||
{
|
||
currentEntity.Constraints.Add(currentConstraint);
|
||
}
|
||
currentConstraint = new Constraint { Description = constraintDescMatch.Groups[1].Value };
|
||
}
|
||
else
|
||
{
|
||
Match propMatch = propCommentRegex.Match(trimmedLine);
|
||
if (propMatch.Success)
|
||
{
|
||
if (currentConstraint == null) currentConstraint = new Constraint();
|
||
currentConstraint.PropertyDictionary[propMatch.Groups[1].Value] = propMatch.Groups[2].Value;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (currentConstraint == null)
|
||
{
|
||
currentConstraint = new Constraint();
|
||
}
|
||
currentConstraint.Expression = trimmedLine;
|
||
|
||
if (currentEntity != null)
|
||
{
|
||
currentEntity.Constraints.Add(currentConstraint);
|
||
}
|
||
currentConstraint = null;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (currentEntity != null && currentConstraint != null && !string.IsNullOrEmpty(currentConstraint.Expression))
|
||
{
|
||
currentEntity.Constraints.Add(currentConstraint);
|
||
}
|
||
|
||
return mvdData;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取任意标识需要的所有属性
|
||
/// </summary>
|
||
/// <param name="data"></param>
|
||
/// <param name="labelName"></param>
|
||
/// <returns></returns>
|
||
public static List<Constraint> GetConstraintsByLabel(MvdData data, string labelName)
|
||
{
|
||
var result = new List<Constraint>();
|
||
var definitions = FindDefinitionsByTypeLabel(data, labelName);
|
||
foreach (var entity in definitions)
|
||
{
|
||
var constraints = entity.Constraints;
|
||
foreach (var cons in constraints)
|
||
{
|
||
if (string.IsNullOrEmpty(cons.PropName) || string.IsNullOrEmpty(cons.Description))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
result.Add(cons);
|
||
//sb.AppendLine($"Entity: {entity.Name}, Property: {cons.PropName}, DefaultValue: {cons.ValueRange}");
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
/// <summary>
|
||
/// 按定义名称查找定义
|
||
/// </summary>
|
||
/// <param name="mvdData"></param>
|
||
/// <param name="name"></param>
|
||
/// <returns></returns>
|
||
public static EntityDefinition GetDefinitionByName(MvdData mvdData, string name)
|
||
{
|
||
if (mvdData == null || mvdData.EntityDefinitions == null || string.IsNullOrEmpty(name))
|
||
{
|
||
return null;
|
||
}
|
||
return mvdData.EntityDefinitions.FirstOrDefault(def => def.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按定义名称查找定义
|
||
/// </summary>
|
||
/// <param name="mvdData"></param>
|
||
/// <param name="name"></param>
|
||
/// <returns></returns>
|
||
public static EntityDefinition GetDefinitionByName(MvdData mvdData, string name, string inheritsFrom)
|
||
{
|
||
if (mvdData == null || mvdData.EntityDefinitions == null || string.IsNullOrEmpty(name))
|
||
{
|
||
return null;
|
||
}
|
||
return mvdData.EntityDefinitions.FirstOrDefault(def => def.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && def.InheritsFrom.ToLower() == inheritsFrom.ToLower());
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 查找所有在其类型标签列表中包含指定名称的实体定义。
|
||
/// </summary>
|
||
/// <param name="mvdData">要搜索的数据源。</param>
|
||
/// <param name="labelToFind">要查找的类型标签名称。</param>
|
||
/// <param name="comparisonType">一个枚举值,指定比较字符串时是否区分大小写。默认为区分大小写。</param>
|
||
/// <returns>一个包含所有匹配的实体定义的列表。</returns>
|
||
public static List<EntityDefinition> FindDefinitionsByTypeLabel(
|
||
MvdData mvdData,
|
||
string labelToFind,
|
||
StringComparison comparisonType = StringComparison.Ordinal)
|
||
{
|
||
var allDefinitions = mvdData.EntityDefinitions;
|
||
// 如果输入为空,则返回一个空列表
|
||
if (allDefinitions == null || string.IsNullOrEmpty(labelToFind) || mvdData.EntityDefinitions.Count == 0)
|
||
{
|
||
return [];
|
||
}
|
||
|
||
// 使用 LINQ 进行高效查询:
|
||
// 遍历所有实体定义(def),
|
||
// 并在每个实体的 TypeLabels 列表中查找(Any)是否存在一个标签(label)
|
||
// 与要查找的标签(labelToFind)相等。
|
||
return [.. allDefinitions.Where(
|
||
def => def.TypeLabels.Any(label => label.Equals(labelToFind, comparisonType)))];
|
||
}
|
||
}
|