|
1 | | -using ObjectSemantics.NET.Engine.Extensions; |
2 | 1 | using ObjectSemantics.NET.Engine.Models; |
3 | | -using System; |
4 | | -using System.Collections; |
5 | | -using System.Collections.Concurrent; |
6 | 2 | using System.Collections.Generic; |
7 | | -using System.Linq; |
8 | | -using System.Reflection; |
9 | | -using System.Text; |
10 | | -using System.Text.RegularExpressions; |
11 | 3 |
|
12 | 4 | namespace ObjectSemantics.NET.Engine |
13 | 5 | { |
14 | 6 | internal static class EngineAlgorithim |
15 | 7 | { |
16 | | - private static readonly ConcurrentDictionary<Type, PropertyInfo[]> PropertyCache = new ConcurrentDictionary<Type, PropertyInfo[]>(); |
17 | | - |
18 | | - private static readonly Regex IfConditionRegex = new Regex(@"{{\s*#\s*if\s*\(\s*(?<param>\w+)\s*(?<operator>==|!=|>=|<=|>|<)\s*(?<value>[^)]+?)\s*\)\s*}}(?<code>[\s\S]*?)(?:{{\s*#\s*else\s*}}(?<else>[\s\S]*?))?{{\s*#\s*endif\s*}}", RegexOptions.IgnoreCase | RegexOptions.Compiled); |
19 | | - private static readonly Regex LoopBlockRegex = new Regex(@"{{\s*#\s*foreach\s*\(\s*(\w+)\s*\)\s*\}\}([\s\S]*?)\{\{\s*#\s*endforeach\s*}}", RegexOptions.IgnoreCase | RegexOptions.Compiled); |
20 | | - private static readonly Regex DirectParamRegex = new Regex(@"{{(.+?)}}", RegexOptions.IgnoreCase | RegexOptions.Compiled); |
21 | | - |
22 | 8 | public static string GenerateFromTemplate<T>(T record, EngineRunnerTemplate template, Dictionary<string, object> parameterKeyValues = null, TemplateMapperOptions options = null) where T : new() |
23 | 9 | { |
24 | | - List<ExtractedObjProperty> objProperties = GetObjectProperties(record, parameterKeyValues); |
25 | | - Dictionary<string, ExtractedObjProperty> propMap = objProperties.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); |
26 | | - |
27 | | - StringBuilder result = new StringBuilder(template.Template ?? string.Empty, (template.Template?.Length ?? 0) * 2); |
28 | | - |
29 | | - // ---- IF Conditions ---- |
30 | | - foreach (ReplaceIfOperationCode ifCondition in template.ReplaceIfConditionCodes) |
31 | | - { |
32 | | - if (!TryResolveProperty(propMap, ifCondition.IfPropertyName, out ExtractedObjProperty property)) |
33 | | - { |
34 | | - result.ReplaceFirstOccurrence(ifCondition.ReplaceRef, "[IF-CONDITION EXCEPTION]: unrecognized property: [" + ifCondition.IfPropertyName + "]"); |
35 | | - continue; |
36 | | - } |
37 | | - |
38 | | - bool conditionPassed = property.IsPropertyValueConditionPassed(ifCondition.IfOperationValue, ifCondition.IfOperationType); |
39 | | - string replacement; |
40 | | - |
41 | | - if (conditionPassed) |
42 | | - { |
43 | | - EngineRunnerTemplate trueContent = GenerateRunnerTemplate(ifCondition.IfOperationTrueTemplate); |
44 | | - replacement = GenerateFromTemplate(record, trueContent, parameterKeyValues, options); |
45 | | - } |
46 | | - else if (!string.IsNullOrEmpty(ifCondition.IfOperationFalseTemplate)) |
47 | | - { |
48 | | - EngineRunnerTemplate falseContent = GenerateRunnerTemplate(ifCondition.IfOperationFalseTemplate); |
49 | | - replacement = GenerateFromTemplate(record, falseContent, parameterKeyValues, options); |
50 | | - } |
51 | | - else |
52 | | - { |
53 | | - replacement = string.Empty; |
54 | | - } |
55 | | - |
56 | | - result.ReplaceFirstOccurrence(ifCondition.ReplaceRef, replacement); |
57 | | - } |
58 | | - |
59 | | - // ---- Object Loops ---- |
60 | | - foreach (ReplaceObjLoopCode objLoop in template.ReplaceObjLoopCodes) |
61 | | - { |
62 | | - if (!propMap.TryGetValue(objLoop.TargetObjectName, out ExtractedObjProperty targetObj) || !(targetObj.OriginalValue is IEnumerable enumerable)) |
63 | | - { |
64 | | - result.ReplaceFirstOccurrence(objLoop.ReplaceRef, string.Empty); |
65 | | - continue; |
66 | | - } |
67 | | - |
68 | | - StringBuilder loopResult = new StringBuilder(); |
69 | | - |
70 | | - foreach (object row in enumerable) |
71 | | - { |
72 | | - List<ExtractedObjProperty> rowProps = GetObjPropertiesFromUnknown(row); |
73 | | - Dictionary<string, ExtractedObjProperty> rowMap = rowProps.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); |
74 | | - |
75 | | - StringBuilder activeRow = new StringBuilder(objLoop.ObjLoopTemplate); |
76 | | - |
77 | | - foreach (ReplaceCode objLoopCode in objLoop.ReplaceObjCodes) |
78 | | - { |
79 | | - string propName = objLoopCode.GetTargetPropertyName(); |
80 | | - if (propName == ".") |
81 | | - { |
82 | | - ExtractedObjProperty tempProp = new ExtractedObjProperty |
83 | | - { |
84 | | - Name = ".", |
85 | | - Type = row.GetType(), |
86 | | - OriginalValue = row |
87 | | - }; |
88 | | - activeRow.ReplaceFirstOccurrence(objLoopCode.ReplaceRef, tempProp.GetPropertyDisplayString(objLoopCode.GetFormattingCommand(), options)); |
89 | | - } |
90 | | - else |
91 | | - { |
92 | | - if (TryResolveProperty(rowMap, propName, out ExtractedObjProperty p)) |
93 | | - activeRow.ReplaceFirstOccurrence(objLoopCode.ReplaceRef, p.GetPropertyDisplayString(objLoopCode.GetFormattingCommand(), options)); |
94 | | - else |
95 | | - activeRow.ReplaceFirstOccurrence(objLoopCode.ReplaceRef, objLoopCode.ReplaceCommand); |
96 | | - } |
97 | | - } |
98 | | - |
99 | | - loopResult.Append(activeRow); |
100 | | - } |
101 | | - |
102 | | - result.ReplaceFirstOccurrence(objLoop.ReplaceRef, loopResult.ToString()); |
103 | | - } |
104 | | - |
105 | | - // ---- Direct Replacements ---- |
106 | | - foreach (ReplaceCode replaceCode in template.ReplaceCodes) |
107 | | - { |
108 | | - if (TryResolveProperty(propMap, replaceCode.GetTargetPropertyName(), out ExtractedObjProperty property)) |
109 | | - result.ReplaceFirstOccurrence(replaceCode.ReplaceRef, property.GetPropertyDisplayString(replaceCode.GetFormattingCommand(), options)); |
110 | | - else |
111 | | - result.ReplaceFirstOccurrence(replaceCode.ReplaceRef, "{{ " + replaceCode.ReplaceCommand + " }}"); |
112 | | - } |
113 | | - |
114 | | - return result.ToString(); |
| 10 | + return EngineTemplateRenderer.Render(record, template, parameterKeyValues, options); |
115 | 11 | } |
116 | 12 |
|
117 | 13 | internal static EngineRunnerTemplate GenerateRunnerTemplate(string fileContent) |
118 | 14 | { |
119 | | - EngineRunnerTemplate templatedContent = new EngineRunnerTemplate { Template = fileContent }; |
120 | | - long key = 0; |
121 | | - |
122 | | - // ---- IF Conditions ---- |
123 | | - templatedContent.Template = IfConditionRegex.Replace(templatedContent.Template, m => |
124 | | - { |
125 | | - key++; |
126 | | - string refKey = "RIB_" + key; |
127 | | - templatedContent.ReplaceIfConditionCodes.Add(new ReplaceIfOperationCode |
128 | | - { |
129 | | - ReplaceRef = refKey, |
130 | | - IfPropertyName = m.Groups["param"].Value, |
131 | | - IfOperationType = m.Groups["operator"].Value, |
132 | | - IfOperationValue = m.Groups["value"].Value, |
133 | | - IfOperationTrueTemplate = m.Groups["code"].Value, |
134 | | - IfOperationFalseTemplate = m.Groups["else"].Success ? m.Groups["else"].Value : string.Empty |
135 | | - }); |
136 | | - return refKey; |
137 | | - }); |
138 | | - |
139 | | - // ---- FOREACH Loops ---- |
140 | | - templatedContent.Template = LoopBlockRegex.Replace(templatedContent.Template, m => |
141 | | - { |
142 | | - key++; |
143 | | - string refKey = "RLB_" + key; |
144 | | - ReplaceObjLoopCode objLoop = new ReplaceObjLoopCode |
145 | | - { |
146 | | - ReplaceRef = refKey, |
147 | | - TargetObjectName = m.Groups[1].Value?.Trim() ?? string.Empty |
148 | | - }; |
149 | | - |
150 | | - string loopBlock = m.Groups[2].Value; |
151 | | - loopBlock = DirectParamRegex.Replace(loopBlock, pm => |
152 | | - { |
153 | | - key++; |
154 | | - string loopRef = "RLBR_" + key; |
155 | | - objLoop.ReplaceObjCodes.Add(new ReplaceCode |
156 | | - { |
157 | | - ReplaceCommand = pm.Groups[1].Value.Trim(), |
158 | | - ReplaceRef = loopRef |
159 | | - }); |
160 | | - return loopRef; |
161 | | - }); |
162 | | - |
163 | | - objLoop.ObjLoopTemplate = loopBlock; |
164 | | - templatedContent.ReplaceObjLoopCodes.Add(objLoop); |
165 | | - return refKey; |
166 | | - }); |
167 | | - |
168 | | - // ---- Direct Parameters ---- |
169 | | - templatedContent.Template = DirectParamRegex.Replace(templatedContent.Template, m => |
170 | | - { |
171 | | - key++; |
172 | | - string refKey = "RP_" + key; |
173 | | - templatedContent.ReplaceCodes.Add(new ReplaceCode |
174 | | - { |
175 | | - ReplaceCommand = m.Groups[1].Value.Trim(), |
176 | | - ReplaceRef = refKey |
177 | | - }); |
178 | | - return refKey; |
179 | | - }); |
180 | | - |
181 | | - return templatedContent; |
182 | | - } |
183 | | - |
184 | | - private static List<ExtractedObjProperty> GetObjectProperties<T>(T value, Dictionary<string, object> parameters) where T : new() |
185 | | - { |
186 | | - Type type = typeof(T); |
187 | | - if (!PropertyCache.TryGetValue(type, out PropertyInfo[] props)) |
188 | | - { |
189 | | - props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); |
190 | | - PropertyCache[type] = props; |
191 | | - } |
192 | | - |
193 | | - List<ExtractedObjProperty> result = new List<ExtractedObjProperty>(props.Length + (parameters != null ? parameters.Count : 0)); |
194 | | - |
195 | | - foreach (PropertyInfo prop in props) |
196 | | - { |
197 | | - result.Add(new ExtractedObjProperty |
198 | | - { |
199 | | - Type = prop.PropertyType, |
200 | | - Name = prop.Name, |
201 | | - OriginalValue = value == null ? null : prop.GetValue(value, null) |
202 | | - }); |
203 | | - } |
204 | | - |
205 | | - if (parameters != null) |
206 | | - { |
207 | | - foreach (KeyValuePair<string, object> p in parameters) |
208 | | - result.Add(new ExtractedObjProperty { Type = p.Value.GetType(), Name = p.Key, OriginalValue = p.Value }); |
209 | | - } |
210 | | - |
211 | | - return result; |
212 | | - } |
213 | | - |
214 | | - private static List<ExtractedObjProperty> GetObjPropertiesFromUnknown(object value) |
215 | | - { |
216 | | - if (value == null) return new List<ExtractedObjProperty>(); |
217 | | - |
218 | | - Type type = value.GetType(); |
219 | | - if (!PropertyCache.TryGetValue(type, out PropertyInfo[] props)) |
220 | | - { |
221 | | - props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) |
222 | | - .Where(p => p.GetIndexParameters().Length == 0).ToArray(); |
223 | | - PropertyCache[type] = props; |
224 | | - } |
225 | | - |
226 | | - List<ExtractedObjProperty> result = new List<ExtractedObjProperty>(props.Length); |
227 | | - foreach (PropertyInfo prop in props) |
228 | | - { |
229 | | - result.Add(new ExtractedObjProperty |
230 | | - { |
231 | | - Type = prop.PropertyType, |
232 | | - Name = prop.Name, |
233 | | - OriginalValue = prop.GetValue(value, null) |
234 | | - }); |
235 | | - } |
236 | | - |
237 | | - return result; |
238 | | - } |
239 | | - |
240 | | - private static bool TryResolveProperty(Dictionary<string, ExtractedObjProperty> propMap, string propertyPath, out ExtractedObjProperty result) |
241 | | - { |
242 | | - result = null; |
243 | | - if (propMap == null || string.IsNullOrWhiteSpace(propertyPath)) |
244 | | - return false; |
245 | | - |
246 | | - string path = propertyPath.Trim(); |
247 | | - if (propMap.TryGetValue(path, out result)) |
248 | | - return true; |
249 | | - |
250 | | - int dotIndex = path.IndexOf('.'); |
251 | | - if (dotIndex < 0) |
252 | | - return false; |
253 | | - |
254 | | - string rootName = path.Substring(0, dotIndex).Trim(); |
255 | | - string nestedPath = path.Substring(dotIndex + 1).Trim(); |
256 | | - if (string.IsNullOrEmpty(rootName) || string.IsNullOrEmpty(nestedPath)) |
257 | | - return false; |
258 | | - |
259 | | - if (!propMap.TryGetValue(rootName, out ExtractedObjProperty rootProperty)) |
260 | | - return false; |
261 | | - |
262 | | - return TryResolveNestedProperty(rootProperty, nestedPath, path, out result); |
263 | | - } |
264 | | - |
265 | | - private static bool TryResolveNestedProperty(ExtractedObjProperty rootProperty, string nestedPath, string fullPath, out ExtractedObjProperty result) |
266 | | - { |
267 | | - result = null; |
268 | | - if (rootProperty == null || string.IsNullOrWhiteSpace(nestedPath)) |
269 | | - return false; |
270 | | - |
271 | | - string[] segments = nestedPath.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); |
272 | | - if (segments.Length == 0) |
273 | | - return false; |
274 | | - |
275 | | - object currentValue = rootProperty.OriginalValue; |
276 | | - Type currentType = rootProperty.Type; |
277 | | - |
278 | | - for (int i = 0; i < segments.Length; i++) |
279 | | - { |
280 | | - if (currentType == null) |
281 | | - return false; |
282 | | - |
283 | | - string segment = segments[i].Trim(); |
284 | | - if (string.IsNullOrEmpty(segment)) |
285 | | - return false; |
286 | | - |
287 | | - PropertyInfo nextProperty = currentType |
288 | | - .GetProperties(BindingFlags.Public | BindingFlags.Instance) |
289 | | - .FirstOrDefault(p => p.GetIndexParameters().Length == 0 && string.Equals(p.Name, segment, StringComparison.OrdinalIgnoreCase)); |
290 | | - |
291 | | - if (nextProperty == null) |
292 | | - return false; |
293 | | - |
294 | | - object nextValue = currentValue == null ? null : nextProperty.GetValue(currentValue, null); |
295 | | - if (i == segments.Length - 1) |
296 | | - { |
297 | | - result = new ExtractedObjProperty |
298 | | - { |
299 | | - Name = fullPath, |
300 | | - Type = nextProperty.PropertyType, |
301 | | - OriginalValue = nextValue |
302 | | - }; |
303 | | - return true; |
304 | | - } |
305 | | - |
306 | | - currentType = nextProperty.PropertyType; |
307 | | - currentValue = nextValue; |
308 | | - } |
309 | | - |
310 | | - return false; |
| 15 | + return EngineTemplateCache.GetOrAdd(fileContent, EngineTemplateParser.Parse); |
311 | 16 | } |
312 | 17 | } |
313 | 18 | } |
0 commit comments