GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
GPALElementConverter.cs
1// =============================================================================
2// GPAL - Generally Positive Automation Library
3// Copyright © 2026 Software Decisions, Inc. All rights reserved.
4//
5// This file is part of GPAL.
6// Licensed under the Business Source License 1.1
7//
8// Primary development, architecture, and vision by Michael B. Vederman,
9// CEO of Software Decisions, Inc., Texas.
10//
11// Internal development maintained privately.
12// Public releases appear on GitHub: https://github.com/SoftwareDecisionsInc/GPAL.
13//
14// See LICENSE for full terms, including Additional Use Grant.
15// =============================================================================
16
17using System;
18using System.Collections.Generic;
19using System.Drawing;
20using System.Linq;
21using Newtonsoft.Json;
22using Newtonsoft.Json.Linq;
23using static GenerallyPositive.Enums;
24
25namespace GenerallyPositive
26{
27 internal class GPALElementConverter : JsonConverter
28 {
29 public override bool CanConvert(Type objectType)
30 {
31 return objectType == typeof(GPALElement) || objectType == typeof(List<GPALElement>);
32 }
33
34 public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
35 {
36 JToken token;
37 try
38 {
39 token = JToken.Load(reader);
40 }
41 catch (Exception ex)
42 {
43 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to load JSON token", null, GPALObjectType.None, ex);
44 return objectType == typeof(List<GPALElement>) ? new List<GPALElement>() : null;
45 }
46
47 if (token.Type == JTokenType.Array)
48 {
49 // Handle List<GPALElement>
50 var jsonArray = (JArray)token;
51 var result = new List<GPALElement>();
52
53 //GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $"Processing JSON array with [{jsonArray.Count}] elements");
54
55 foreach (JToken maybeWrapped in jsonArray)
56 {
57 JToken payload = maybeWrapped;
58
59 // 1. Unwrap extra array level if present (common for single-element queries)
60 if (maybeWrapped.Type == JTokenType.Array && maybeWrapped.Children().Count() == 1)
61 {
62 payload = maybeWrapped.First;
63 //GPAL.PublishSimpleEvent(Enums.GPALEventType.DEBUG, "Unwrapped extra array layer");
64 }
65
66 GPALElement element = null;
67
68 if (payload.Type == JTokenType.Object)
69 {
70 // Normal structured object > deserialize as usual
71 try
72 {
73 element = DeserializeSingleElement((JObject)payload, serializer);
74 }
75 catch (Exception ex)
76 {
77 GPAL.PublishSimpleEvent(Enums.GPALEventType.EXCEPTION,
78 "Normal object failed: " + ex.Message);
79 }
80 }
81 else if (payload.Type == JTokenType.String)
82 {
83 string raw = payload.Value<string>();
84 if (string.IsNullOrWhiteSpace(raw))
85 continue;
86
87 // the extension sends each element as its own JSON string
88 if (raw.TrimStart().StartsWith("{"))
89 {
90 try
91 {
92 var obj = JObject.Parse(raw);
93 element = DeserializeSingleElement(obj, serializer);
94 }
95 catch (JsonReaderException jex)
96 {
97 GPAL.PublishSimpleEvent(Enums.GPALEventType.WARNING,
98 "Element string failed parse: " + jex.Message);
99 }
100 }
101 }
102
103 // Final check — only add if we have something useful
104 if (element != null && element.Attributes != null && element.Attributes.Count > 0)
105 {
106 // commented: fired per element during bulk deserialize
107 //string tag = element.Attributes.ContainsKey("tagName")
108 // ? element.Attributes["tagName"].ToString()
109 // : "unknown";
110
111 //GPAL.PublishSimpleEvent(Enums.GPALEventType.DEEPDEBUG,
112 // "Added element - tag: " + tag + ", attributes: " + element.Attributes.Count);
113
114 result.Add(element);
115 }
116 else
117 {
118 GPAL.PublishSimpleEvent(Enums.GPALEventType.WARNING,
119 "Dropped item — no valid element created");
120 }
121 }
122
123 return result;
124 }
125 else if (token.Type == JTokenType.Object)
126 {
127 // Handle single GPALElement
128 try
129 {
130 var element = DeserializeSingleElement((JObject)token, serializer);
131 if (element != null && element.Attributes != null)
132 {
133 return element;
134 }
135 else
136 {
137 return null;
138 }
139 }
140 catch (Exception ex)
141 {
142 return null;
143 }
144 }
145 else if (token.Type == JTokenType.String)
146 {
147 // Handle single double-serialized GPALElement
148 try
149 {
150 var jsonString = token.Value<string>();
151 var parsedObject = JObject.Parse(jsonString);
152 var element = DeserializeSingleElement(parsedObject, serializer);
153 if (element != null && element.Attributes != null)
154 {
155 return element;
156 }
157 else
158 {
159 return null;
160 }
161 }
162 catch (Exception ex)
163 {
164 return objectType == typeof(List<GPALElement>) ? new List<GPALElement>() : null;
165 }
166 }
167
168 return objectType == typeof(List<GPALElement>) ? new List<GPALElement>() : null;
169 }
170 private GPALElement DeserializeSingleElement(JObject jsonObject, JsonSerializer serializer)
171 {
172 var gpalelement = new GPALElement();
173
174 // Deserialize Attributes dictionary
175 if (jsonObject["Attributes"] != null)
176 {
177 var attributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
178 var attributesJson = jsonObject["Attributes"] as JObject;
179
180 if (attributesJson != null)
181 {
182 foreach (var prop in attributesJson.Properties())
183 {
184 try
185 {
186 switch (prop.Name.ToLower())
187 {
188 case "location":
189 var location = prop.Value.ToObject<Point>(serializer);
190 attributes["location"] = location;
191 // commented: fired per element during bulk deserialize; the eager string build (and its stack capture) cost seconds over 90+ elements
192 //GPAL.PublishSimpleEvent(GPALEventType.DEEPDEBUG, $"Deserialized location: ([{location.X}], [{location.Y}])");
193 break;
194 case "size":
195 var size = prop.Value.ToObject<Size>(serializer);
196 attributes["size"] = size;
197 //GPAL.PublishSimpleEvent(GPALEventType.DEEPDEBUG, $"Deserialized size: ([{size.Width}], [{size.Height}])");
198 break;
199 case "boundingrect":
200 var boundingRect = prop.Value.ToObject<ClientRectangle>(serializer);
201 attributes["boundingRect"] = boundingRect;
202 //GPAL.PublishSimpleEvent(GPALEventType.DEEPDEBUG, $"Deserialized boundingRect: (width=[{boundingRect.Width}], height=[{boundingRect.Height}])");
203 break;
204 case "xpaths":
205 var xpaths = prop.Value.ToObject<List<XPathInfo>>(serializer);
206 attributes["xpaths"] = xpaths.FindAll(x => x != null).ToArray();
207 //GPAL.PublishSimpleEvent(GPALEventType.DEEPDEBUG, $"Deserialized attributes['xpaths']");
208 break;
209 default:
210 attributes[prop.Name] = prop.Value.ToObject<object>(serializer);
211 // commented: fired per PROPERTY per element; {prop.Value} also re-serializes the token to a string every time
212 //GPAL.PublishSimpleEvent(GPALEventType.DEEPDEBUG, $"Deserialized property [{prop.Name}]: [{prop.Value}]");
213 break;
214 }
215 }
216 catch (Exception ex)
217 {
218 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to deserialize property [{prop.Name}]", null, GPALObjectType.None, ex);
219 }
220 }
221 gpalelement.Attributes = attributes;
222 }
223 else
224 {
225 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "Attributes property is not a valid JObject");
226 }
227 }
228 else
229 {
230 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "Attributes property is missing or null");
231 }
232
233 // Deserialize ElementHandle
234 if (jsonObject["ElementHandle"] != null)
235 {
236 try
237 {
238 gpalelement.ElementHandle = jsonObject["ElementHandle"].ToObject<string>(serializer);
239 //GPAL.PublishSimpleEvent(GPALEventType.DEEPDEBUG, $"Deserialized ElementHandle: [{gpalelement.ElementHandle}]");
240 }
241 catch (Exception ex)
242 {
243 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to deserialize ElementHandle", null, GPALObjectType.None, ex);
244 }
245 }
246
247 return gpalelement;
248 }
249
250 public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
251 {
252 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Serialization not implemented");
253 }
254
255 public override bool CanWrite => false;
256 }
257
258 internal class PointConverter : JsonConverter
259 {
260 public override bool CanConvert(Type objectType)
261 {
262 return objectType == typeof(Point);
263 }
264
265 public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
266 {
267 try
268 {
269 JToken token = JToken.Load(reader);
270 if (token.Type == JTokenType.Object)
271 {
272 var obj = (JObject)token;
273 int x = obj["X"]?.Value<int>() ?? 0;
274 int y = obj["Y"]?.Value<int>() ?? 0;
275 return new Point(x, y);
276 }
277 else if (token.Type == JTokenType.String)
278 {
279 string[] parts = token.Value<string>().Split(',');
280 if (parts.Length == 2 && int.TryParse(parts[0], out int x) && int.TryParse(parts[1], out int y))
281 {
282 return new Point(x, y);
283 }
284 }
285 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Invalid Point format: [{token}]");
286 return Point.Empty;
287 }
288 catch (Exception ex)
289 {
290 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to deserialize Point", null, GPALObjectType.None, ex);
291 return Point.Empty;
292 }
293 }
294
295 public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
296 {
297 var point = (Point)value;
298 writer.WriteValue($"{point.X},{point.Y}");
299 }
300
301 public override bool CanWrite => true;
302 }
303
304 internal class SizeConverter : JsonConverter
305 {
306 public override bool CanConvert(Type objectType)
307 {
308 return objectType == typeof(Size);
309 }
310
311 public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
312 {
313 try
314 {
315 JToken token = JToken.Load(reader);
316 if (token.Type == JTokenType.Object)
317 {
318 var obj = (JObject)token;
319 int width = obj["Width"]?.Value<int>() ?? 0;
320 int height = obj["Height"]?.Value<int>() ?? 0;
321 return new Size(width, height);
322 }
323 else if (token.Type == JTokenType.String)
324 {
325 string[] parts = token.Value<string>().Split(',');
326 if (parts.Length == 2 && int.TryParse(parts[0], out int width) && int.TryParse(parts[1], out int height))
327 {
328 return new Size(width, height);
329 }
330 }
331 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Invalid Size format: [{token}]");
332 return Size.Empty;
333 }
334 catch (Exception ex)
335 {
336 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to deserialize Size", null, GPALObjectType.None, ex);
337 return Size.Empty;
338 }
339 }
340
341 public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
342 {
343 var size = (Size)value;
344 writer.WriteValue($"{size.Width},{size.Height}");
345 }
346
347 public override bool CanWrite => true;
348 }
349}