@reactiive/ennio 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/EnnioCore.podspec +61 -0
  2. package/LICENSE +21 -0
  3. package/README.md +50 -0
  4. package/android/CMakeLists.txt +40 -0
  5. package/android/build.gradle +64 -0
  6. package/cpp/ElementMatcher.cpp +661 -0
  7. package/cpp/ElementMatcher.hpp +244 -0
  8. package/cpp/EnnioLog.hpp +182 -0
  9. package/cpp/HybridEnnio.cpp +1161 -0
  10. package/cpp/HybridEnnio.hpp +174 -0
  11. package/cpp/IdleMonitor.hpp +277 -0
  12. package/cpp/Protocol.cpp +135 -0
  13. package/cpp/Protocol.hpp +47 -0
  14. package/cpp/SelectorCriteria.hpp +281 -0
  15. package/cpp/SelectorParser.cpp +649 -0
  16. package/cpp/SelectorParser.hpp +94 -0
  17. package/cpp/ShadowTreeTraverser.cpp +305 -0
  18. package/cpp/ShadowTreeTraverser.hpp +142 -0
  19. package/cpp/TestIDRegistry.cpp +109 -0
  20. package/cpp/TestIDRegistry.hpp +84 -0
  21. package/dist/cli.js +16221 -0
  22. package/ios/EnnioAutoInit.mm +338 -0
  23. package/ios/EnnioDebugBanner.h +19 -0
  24. package/ios/EnnioDebugBanner.mm +178 -0
  25. package/ios/EnnioRuntimeHelper.h +264 -0
  26. package/ios/EnnioRuntimeHelper.mm +2443 -0
  27. package/lib/Ennio.nitro.d.ts +263 -0
  28. package/lib/Ennio.nitro.d.ts.map +1 -0
  29. package/lib/Ennio.nitro.js +2 -0
  30. package/lib/Ennio.nitro.js.map +1 -0
  31. package/lib/index.d.ts +16 -0
  32. package/lib/index.d.ts.map +1 -0
  33. package/lib/index.js +45 -0
  34. package/lib/index.js.map +1 -0
  35. package/nitro.json +24 -0
  36. package/nitrogen/generated/.gitattributes +1 -0
  37. package/nitrogen/generated/android/EnnioCore+autolinking.cmake +81 -0
  38. package/nitrogen/generated/android/EnnioCore+autolinking.gradle +27 -0
  39. package/nitrogen/generated/android/EnnioCoreOnLoad.cpp +49 -0
  40. package/nitrogen/generated/android/EnnioCoreOnLoad.hpp +34 -0
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/ennio/EnnioCoreOnLoad.kt +35 -0
  42. package/nitrogen/generated/ios/EnnioCore+autolinking.rb +62 -0
  43. package/nitrogen/generated/ios/EnnioCore-Swift-Cxx-Bridge.cpp +17 -0
  44. package/nitrogen/generated/ios/EnnioCore-Swift-Cxx-Bridge.hpp +27 -0
  45. package/nitrogen/generated/ios/EnnioCore-Swift-Cxx-Umbrella.hpp +38 -0
  46. package/nitrogen/generated/ios/EnnioCoreAutolinking.mm +35 -0
  47. package/nitrogen/generated/ios/EnnioCoreAutolinking.swift +16 -0
  48. package/nitrogen/generated/shared/c++/ExtendedElementInfo.hpp +118 -0
  49. package/nitrogen/generated/shared/c++/HybridEnnioSpec.cpp +44 -0
  50. package/nitrogen/generated/shared/c++/HybridEnnioSpec.hpp +93 -0
  51. package/nitrogen/generated/shared/c++/LayoutMetrics.hpp +103 -0
  52. package/nitrogen/generated/shared/c++/ScrollDirection.hpp +84 -0
  53. package/package.json +78 -0
  54. package/react-native.config.js +14 -0
  55. package/src/Ennio.nitro.ts +363 -0
  56. package/src/cli/hid-daemon.py +129 -0
  57. package/src/index.ts +72 -0
@@ -0,0 +1,649 @@
1
+ #include "SelectorParser.hpp"
2
+ #include "EnnioLog.hpp"
3
+ #include <stdexcept>
4
+ #include <sstream>
5
+ #include <algorithm>
6
+ #include <cctype>
7
+
8
+ namespace ennio {
9
+
10
+ static const char* LOG_TAG = "SelectorParser";
11
+
12
+ namespace {
13
+ // Helper to trim whitespace
14
+ std::string trim(const std::string& str) {
15
+ size_t start = str.find_first_not_of(" \t\n\r");
16
+ if (start == std::string::npos) return "";
17
+ size_t end = str.find_last_not_of(" \t\n\r");
18
+ return str.substr(start, end - start + 1);
19
+ }
20
+
21
+ // Find matching bracket/brace
22
+ size_t findMatchingBracket(const std::string& json, size_t start, char open, char close) {
23
+ int depth = 1;
24
+ bool inString = false;
25
+ bool escaped = false;
26
+
27
+ for (size_t i = start + 1; i < json.size(); i++) {
28
+ char c = json[i];
29
+
30
+ if (escaped) {
31
+ escaped = false;
32
+ continue;
33
+ }
34
+
35
+ if (c == '\\') {
36
+ escaped = true;
37
+ continue;
38
+ }
39
+
40
+ if (c == '"') {
41
+ inString = !inString;
42
+ continue;
43
+ }
44
+
45
+ if (!inString) {
46
+ if (c == open) depth++;
47
+ else if (c == close) {
48
+ depth--;
49
+ if (depth == 0) return i;
50
+ }
51
+ }
52
+ }
53
+ return std::string::npos;
54
+ }
55
+
56
+ // Locate `"key"` at the top level of the given JSON object. Skips
57
+ // occurrences nested inside child objects or arrays — without this,
58
+ // outer hasKey/extractString hits keys belonging to a nested
59
+ // selector (e.g. `rightOf: {id: ...}` would leak its `id` into the
60
+ // outer criteria.id and corrupt the match). Returns npos if absent.
61
+ size_t findTopLevelKey(const std::string& json, const std::string& key) {
62
+ std::string searchKey = "\"" + key + "\"";
63
+ int depth = 0;
64
+ bool inString = false;
65
+ bool escaped = false;
66
+ for (size_t i = 0; i < json.size(); i++) {
67
+ char c = json[i];
68
+ if (escaped) { escaped = false; continue; }
69
+ if (inString) {
70
+ if (c == '\\') { escaped = true; continue; }
71
+ if (c == '"') { inString = false; }
72
+ continue;
73
+ }
74
+ if (c == '"') {
75
+ if (depth == 1 &&
76
+ json.compare(i, searchKey.size(), searchKey) == 0) {
77
+ // Confirm the next non-space char is ':' (key, not value).
78
+ size_t after = i + searchKey.size();
79
+ while (after < json.size() && std::isspace(json[after])) after++;
80
+ if (after < json.size() && json[after] == ':') {
81
+ return i;
82
+ }
83
+ }
84
+ inString = true;
85
+ continue;
86
+ }
87
+ if (c == '{' || c == '[') depth++;
88
+ else if (c == '}' || c == ']') depth--;
89
+ }
90
+ return std::string::npos;
91
+ }
92
+
93
+ // Escape string for JSON output
94
+ std::string escapeString(const std::string& str) {
95
+ std::ostringstream oss;
96
+ for (char c : str) {
97
+ switch (c) {
98
+ case '"': oss << "\\\""; break;
99
+ case '\\': oss << "\\\\"; break;
100
+ case '\n': oss << "\\n"; break;
101
+ case '\r': oss << "\\r"; break;
102
+ case '\t': oss << "\\t"; break;
103
+ default: oss << c;
104
+ }
105
+ }
106
+ return oss.str();
107
+ }
108
+ }
109
+
110
+ SelectorCriteria SelectorParser::parse(const std::string& json) {
111
+ std::string trimmed = trim(json);
112
+
113
+ if (trimmed.empty()) {
114
+ throw std::runtime_error("Empty selector");
115
+ }
116
+
117
+ // Simple string selector (just testID)
118
+ if (trimmed[0] == '"') {
119
+ // Extract string value
120
+ size_t end = trimmed.find('"', 1);
121
+ if (end == std::string::npos) {
122
+ throw std::runtime_error("Invalid selector: unterminated string");
123
+ }
124
+ std::string id = trimmed.substr(1, end - 1);
125
+ return SelectorCriteria::fromId(id);
126
+ }
127
+
128
+ // Object selector
129
+ if (trimmed[0] == '{') {
130
+ return parseObject(trimmed);
131
+ }
132
+
133
+ // Plain string without quotes (legacy testID format)
134
+ return SelectorCriteria::fromId(trimmed);
135
+ }
136
+
137
+ SelectorCriteria SelectorParser::parseObject(const std::string& json) {
138
+ SelectorCriteria criteria;
139
+ ENNIO_LOG_TRACE(LOG_TAG, ENNIO_LOG_FMT("parseObject: json=" << json));
140
+
141
+ // Parse id
142
+ if (hasKey(json, "id")) {
143
+ criteria.id = extractString(json, "id");
144
+ ENNIO_LOG_DEBUG(LOG_TAG, ENNIO_LOG_FMT("parseObject: id=" << *criteria.id));
145
+ }
146
+
147
+ // Parse text (can be string or object with pattern/mode)
148
+ if (hasKey(json, "text")) {
149
+ std::string textValue = extractString(json, "text");
150
+ ENNIO_LOG_DEBUG(LOG_TAG, ENNIO_LOG_FMT("parseObject: text=" << textValue));
151
+ TextMatchMode mode = TextMatchMode::Exact;
152
+
153
+ // Check for textMatchMode
154
+ if (hasKey(json, "textMatchMode")) {
155
+ mode = parseTextMatchMode(extractString(json, "textMatchMode"));
156
+ }
157
+
158
+ criteria.text = TextMatcher{textValue, mode};
159
+ }
160
+
161
+ // Parse index
162
+ auto indexOpt = extractNumber(json, "index");
163
+ if (indexOpt) {
164
+ criteria.index = static_cast<int>(*indexOpt);
165
+ }
166
+
167
+ // Parse point
168
+ if (hasKey(json, "point")) {
169
+ std::string pointStr = extractString(json, "point");
170
+ if (!pointStr.empty()) {
171
+ criteria.point = parsePoint(pointStr);
172
+ } else {
173
+ // Try object format
174
+ std::string pointObj = extractObject(json, "point");
175
+ if (!pointObj.empty()) {
176
+ Point p;
177
+ auto x = extractNumber(pointObj, "x");
178
+ auto y = extractNumber(pointObj, "y");
179
+ if (x) p.x = static_cast<float>(*x);
180
+ if (y) p.y = static_cast<float>(*y);
181
+ p.isPercentage = false;
182
+ criteria.point = p;
183
+ }
184
+ }
185
+ }
186
+
187
+ // Parse state selectors
188
+ criteria.enabled = extractBool(json, "enabled");
189
+ criteria.checked = extractBool(json, "checked");
190
+ criteria.focused = extractBool(json, "focused");
191
+ criteria.selected = extractBool(json, "selected");
192
+
193
+ // Parse spatial selectors
194
+ criteria.below = parseNested(json, "below");
195
+ criteria.above = parseNested(json, "above");
196
+ criteria.leftOf = parseNested(json, "leftOf");
197
+ criteria.rightOf = parseNested(json, "rightOf");
198
+
199
+ // Parse hierarchical selectors
200
+ criteria.containsChild = parseNested(json, "containsChild");
201
+ criteria.childOf = parseNested(json, "childOf");
202
+
203
+ // Parse containsDescendants array
204
+ auto descendants = extractArray(json, "containsDescendants");
205
+ for (const auto& desc : descendants) {
206
+ auto parsed = std::make_shared<SelectorCriteria>(parse(desc));
207
+ criteria.containsDescendants.push_back(parsed);
208
+ }
209
+
210
+ // Parse dimension selectors
211
+ auto widthOpt = extractNumber(json, "width");
212
+ if (widthOpt) {
213
+ criteria.width = static_cast<float>(*widthOpt);
214
+ }
215
+
216
+ auto heightOpt = extractNumber(json, "height");
217
+ if (heightOpt) {
218
+ criteria.height = static_cast<float>(*heightOpt);
219
+ }
220
+
221
+ auto toleranceOpt = extractNumber(json, "tolerance");
222
+ if (toleranceOpt) {
223
+ criteria.tolerance = static_cast<float>(*toleranceOpt);
224
+ }
225
+
226
+ // Parse traits array
227
+ auto traitsArray = extractArray(json, "traits");
228
+ for (const auto& trait : traitsArray) {
229
+ std::string t = trim(trait);
230
+ // Remove quotes if present
231
+ if (t.size() >= 2 && t[0] == '"') {
232
+ t = t.substr(1, t.size() - 2);
233
+ }
234
+ criteria.traits.push_back(parseTrait(t));
235
+ }
236
+
237
+ return criteria;
238
+ }
239
+
240
+ SelectorCriteriaPtr SelectorParser::parseNested(const std::string& json, const std::string& key) {
241
+ std::string nested = extractObject(json, key);
242
+ if (nested.empty()) {
243
+ return nullptr;
244
+ }
245
+ return std::make_shared<SelectorCriteria>(parse(nested));
246
+ }
247
+
248
+ std::string SelectorParser::extractString(const std::string& json, const std::string& key) {
249
+ std::string searchKey = "\"" + key + "\"";
250
+ size_t keyPos = findTopLevelKey(json, key);
251
+ if (keyPos == std::string::npos) {
252
+ return "";
253
+ }
254
+
255
+ size_t colonPos = json.find(':', keyPos + searchKey.size());
256
+ if (colonPos == std::string::npos) {
257
+ return "";
258
+ }
259
+
260
+ // Find the value start
261
+ size_t valueStart = colonPos + 1;
262
+ while (valueStart < json.size() && std::isspace(json[valueStart])) {
263
+ valueStart++;
264
+ }
265
+
266
+ if (valueStart >= json.size()) {
267
+ return "";
268
+ }
269
+
270
+ // If it's a string value, parse with proper escape handling
271
+ if (json[valueStart] == '"') {
272
+ std::string result;
273
+ bool escaped = false;
274
+ for (size_t i = valueStart + 1; i < json.size(); i++) {
275
+ char c = json[i];
276
+ if (escaped) {
277
+ // Handle escape sequences
278
+ switch (c) {
279
+ case '"': result += '"'; break;
280
+ case '\\': result += '\\'; break;
281
+ case 'n': result += '\n'; break;
282
+ case 'r': result += '\r'; break;
283
+ case 't': result += '\t'; break;
284
+ default: result += c; break;
285
+ }
286
+ escaped = false;
287
+ } else if (c == '\\') {
288
+ escaped = true;
289
+ } else if (c == '"') {
290
+ // End of string
291
+ break;
292
+ } else {
293
+ result += c;
294
+ }
295
+ }
296
+ return result;
297
+ }
298
+
299
+ return "";
300
+ }
301
+
302
+ std::optional<bool> SelectorParser::extractBool(const std::string& json, const std::string& key) {
303
+ std::string searchKey = "\"" + key + "\"";
304
+ size_t keyPos = findTopLevelKey(json, key);
305
+ if (keyPos == std::string::npos) {
306
+ return std::nullopt;
307
+ }
308
+
309
+ size_t colonPos = json.find(':', keyPos + searchKey.size());
310
+ if (colonPos == std::string::npos) {
311
+ return std::nullopt;
312
+ }
313
+
314
+ size_t valueStart = colonPos + 1;
315
+ while (valueStart < json.size() && std::isspace(json[valueStart])) {
316
+ valueStart++;
317
+ }
318
+
319
+ if (json.compare(valueStart, 4, "true") == 0) {
320
+ return true;
321
+ }
322
+ if (json.compare(valueStart, 5, "false") == 0) {
323
+ return false;
324
+ }
325
+
326
+ return std::nullopt;
327
+ }
328
+
329
+ std::optional<double> SelectorParser::extractNumber(const std::string& json, const std::string& key) {
330
+ std::string searchKey = "\"" + key + "\"";
331
+ size_t keyPos = findTopLevelKey(json, key);
332
+ if (keyPos == std::string::npos) {
333
+ return std::nullopt;
334
+ }
335
+
336
+ size_t colonPos = json.find(':', keyPos + searchKey.size());
337
+ if (colonPos == std::string::npos) {
338
+ return std::nullopt;
339
+ }
340
+
341
+ size_t valueStart = colonPos + 1;
342
+ while (valueStart < json.size() && std::isspace(json[valueStart])) {
343
+ valueStart++;
344
+ }
345
+
346
+ // Check if it's a number
347
+ if (valueStart < json.size() && (std::isdigit(json[valueStart]) || json[valueStart] == '-' || json[valueStart] == '.')) {
348
+ size_t valueEnd = valueStart;
349
+ while (valueEnd < json.size() && (std::isdigit(json[valueEnd]) || json[valueEnd] == '.' || json[valueEnd] == '-' || json[valueEnd] == 'e' || json[valueEnd] == 'E')) {
350
+ valueEnd++;
351
+ }
352
+ std::string numStr = json.substr(valueStart, valueEnd - valueStart);
353
+ try {
354
+ return std::stod(numStr);
355
+ } catch (...) {
356
+ return std::nullopt;
357
+ }
358
+ }
359
+
360
+ return std::nullopt;
361
+ }
362
+
363
+ std::string SelectorParser::extractObject(const std::string& json, const std::string& key) {
364
+ std::string searchKey = "\"" + key + "\"";
365
+ size_t keyPos = findTopLevelKey(json, key);
366
+ if (keyPos == std::string::npos) {
367
+ return "";
368
+ }
369
+
370
+ size_t colonPos = json.find(':', keyPos + searchKey.size());
371
+ if (colonPos == std::string::npos) {
372
+ return "";
373
+ }
374
+
375
+ size_t valueStart = colonPos + 1;
376
+ while (valueStart < json.size() && std::isspace(json[valueStart])) {
377
+ valueStart++;
378
+ }
379
+
380
+ if (valueStart >= json.size() || json[valueStart] != '{') {
381
+ return "";
382
+ }
383
+
384
+ size_t valueEnd = findMatchingBracket(json, valueStart, '{', '}');
385
+ if (valueEnd == std::string::npos) {
386
+ return "";
387
+ }
388
+
389
+ return json.substr(valueStart, valueEnd - valueStart + 1);
390
+ }
391
+
392
+ std::vector<std::string> SelectorParser::extractArray(const std::string& json, const std::string& key) {
393
+ std::vector<std::string> result;
394
+
395
+ std::string searchKey = "\"" + key + "\"";
396
+ size_t keyPos = findTopLevelKey(json, key);
397
+ if (keyPos == std::string::npos) {
398
+ return result;
399
+ }
400
+
401
+ size_t colonPos = json.find(':', keyPos + searchKey.size());
402
+ if (colonPos == std::string::npos) {
403
+ return result;
404
+ }
405
+
406
+ size_t valueStart = colonPos + 1;
407
+ while (valueStart < json.size() && std::isspace(json[valueStart])) {
408
+ valueStart++;
409
+ }
410
+
411
+ if (valueStart >= json.size() || json[valueStart] != '[') {
412
+ return result;
413
+ }
414
+
415
+ size_t arrayEnd = findMatchingBracket(json, valueStart, '[', ']');
416
+ if (arrayEnd == std::string::npos) {
417
+ return result;
418
+ }
419
+
420
+ // Parse array elements
421
+ size_t pos = valueStart + 1;
422
+ while (pos < arrayEnd) {
423
+ // Skip whitespace
424
+ while (pos < arrayEnd && std::isspace(json[pos])) pos++;
425
+ if (pos >= arrayEnd) break;
426
+
427
+ // Skip comma
428
+ if (json[pos] == ',') {
429
+ pos++;
430
+ continue;
431
+ }
432
+
433
+ // Parse element
434
+ if (json[pos] == '{') {
435
+ size_t end = findMatchingBracket(json, pos, '{', '}');
436
+ if (end != std::string::npos) {
437
+ result.push_back(json.substr(pos, end - pos + 1));
438
+ pos = end + 1;
439
+ } else {
440
+ break;
441
+ }
442
+ } else if (json[pos] == '"') {
443
+ size_t end = pos + 1;
444
+ while (end < arrayEnd && !(json[end] == '"' && json[end - 1] != '\\')) {
445
+ end++;
446
+ }
447
+ result.push_back(json.substr(pos, end - pos + 1));
448
+ pos = end + 1;
449
+ } else {
450
+ // Skip to next comma or end
451
+ size_t end = pos;
452
+ while (end < arrayEnd && json[end] != ',' && json[end] != ']') {
453
+ end++;
454
+ }
455
+ std::string element = trim(json.substr(pos, end - pos));
456
+ if (!element.empty()) {
457
+ result.push_back(element);
458
+ }
459
+ pos = end;
460
+ }
461
+ }
462
+
463
+ return result;
464
+ }
465
+
466
+ bool SelectorParser::hasKey(const std::string& json, const std::string& key) {
467
+ return findTopLevelKey(json, key) != std::string::npos;
468
+ }
469
+
470
+ TextMatchMode SelectorParser::parseTextMatchMode(const std::string& mode) {
471
+ if (mode == "contains") return TextMatchMode::Contains;
472
+ if (mode == "regex") return TextMatchMode::Regex;
473
+ if (mode == "startsWith") return TextMatchMode::StartsWith;
474
+ if (mode == "endsWith") return TextMatchMode::EndsWith;
475
+ return TextMatchMode::Exact;
476
+ }
477
+
478
+ Trait SelectorParser::parseTrait(const std::string& trait) {
479
+ if (trait == "long-text" || trait == "longText") return Trait::LongText;
480
+ if (trait == "square") return Trait::Square;
481
+ return Trait::Text; // default
482
+ }
483
+
484
+ Point SelectorParser::parsePoint(const std::string& value) {
485
+ Point point{0, 0, false};
486
+
487
+ // Check if percentage format: "50%,50%"
488
+ if (value.find('%') != std::string::npos) {
489
+ point.isPercentage = true;
490
+ size_t comma = value.find(',');
491
+ if (comma != std::string::npos) {
492
+ std::string xStr = value.substr(0, comma);
493
+ std::string yStr = value.substr(comma + 1);
494
+
495
+ // Remove % signs
496
+ xStr.erase(std::remove(xStr.begin(), xStr.end(), '%'), xStr.end());
497
+ yStr.erase(std::remove(yStr.begin(), yStr.end(), '%'), yStr.end());
498
+
499
+ try {
500
+ point.x = std::stof(trim(xStr));
501
+ point.y = std::stof(trim(yStr));
502
+ } catch (...) {}
503
+ }
504
+ } else {
505
+ // Absolute format: "100,200"
506
+ size_t comma = value.find(',');
507
+ if (comma != std::string::npos) {
508
+ try {
509
+ point.x = std::stof(trim(value.substr(0, comma)));
510
+ point.y = std::stof(trim(value.substr(comma + 1)));
511
+ } catch (...) {}
512
+ }
513
+ }
514
+
515
+ return point;
516
+ }
517
+
518
+ std::string SelectorParser::toJSON(const SelectorCriteria& criteria) {
519
+ std::ostringstream oss;
520
+ oss << "{";
521
+
522
+ bool first = true;
523
+ auto addComma = [&]() {
524
+ if (!first) oss << ",";
525
+ first = false;
526
+ };
527
+
528
+ if (criteria.id) {
529
+ addComma();
530
+ oss << "\"id\":\"" << escapeString(*criteria.id) << "\"";
531
+ }
532
+
533
+ if (criteria.text) {
534
+ addComma();
535
+ oss << "\"text\":\"" << escapeString(criteria.text->pattern) << "\"";
536
+ if (criteria.text->mode != TextMatchMode::Exact) {
537
+ oss << ",\"textMatchMode\":\"";
538
+ switch (criteria.text->mode) {
539
+ case TextMatchMode::Contains: oss << "contains"; break;
540
+ case TextMatchMode::Regex: oss << "regex"; break;
541
+ case TextMatchMode::StartsWith: oss << "startsWith"; break;
542
+ case TextMatchMode::EndsWith: oss << "endsWith"; break;
543
+ default: oss << "exact";
544
+ }
545
+ oss << "\"";
546
+ }
547
+ }
548
+
549
+ if (criteria.index) {
550
+ addComma();
551
+ oss << "\"index\":" << *criteria.index;
552
+ }
553
+
554
+ if (criteria.enabled) {
555
+ addComma();
556
+ oss << "\"enabled\":" << (*criteria.enabled ? "true" : "false");
557
+ }
558
+
559
+ if (criteria.checked) {
560
+ addComma();
561
+ oss << "\"checked\":" << (*criteria.checked ? "true" : "false");
562
+ }
563
+
564
+ if (criteria.focused) {
565
+ addComma();
566
+ oss << "\"focused\":" << (*criteria.focused ? "true" : "false");
567
+ }
568
+
569
+ if (criteria.selected) {
570
+ addComma();
571
+ oss << "\"selected\":" << (*criteria.selected ? "true" : "false");
572
+ }
573
+
574
+ if (criteria.below) {
575
+ addComma();
576
+ oss << "\"below\":" << toJSON(*criteria.below);
577
+ }
578
+
579
+ if (criteria.above) {
580
+ addComma();
581
+ oss << "\"above\":" << toJSON(*criteria.above);
582
+ }
583
+
584
+ if (criteria.leftOf) {
585
+ addComma();
586
+ oss << "\"leftOf\":" << toJSON(*criteria.leftOf);
587
+ }
588
+
589
+ if (criteria.rightOf) {
590
+ addComma();
591
+ oss << "\"rightOf\":" << toJSON(*criteria.rightOf);
592
+ }
593
+
594
+ if (criteria.containsChild) {
595
+ addComma();
596
+ oss << "\"containsChild\":" << toJSON(*criteria.containsChild);
597
+ }
598
+
599
+ if (criteria.childOf) {
600
+ addComma();
601
+ oss << "\"childOf\":" << toJSON(*criteria.childOf);
602
+ }
603
+
604
+ if (!criteria.containsDescendants.empty()) {
605
+ addComma();
606
+ oss << "\"containsDescendants\":[";
607
+ for (size_t i = 0; i < criteria.containsDescendants.size(); i++) {
608
+ if (i > 0) oss << ",";
609
+ oss << toJSON(*criteria.containsDescendants[i]);
610
+ }
611
+ oss << "]";
612
+ }
613
+
614
+ if (criteria.width) {
615
+ addComma();
616
+ oss << "\"width\":" << *criteria.width;
617
+ }
618
+
619
+ if (criteria.height) {
620
+ addComma();
621
+ oss << "\"height\":" << *criteria.height;
622
+ }
623
+
624
+ if (criteria.tolerance) {
625
+ addComma();
626
+ oss << "\"tolerance\":" << *criteria.tolerance;
627
+ }
628
+
629
+ if (!criteria.traits.empty()) {
630
+ addComma();
631
+ oss << "\"traits\":[";
632
+ for (size_t i = 0; i < criteria.traits.size(); i++) {
633
+ if (i > 0) oss << ",";
634
+ oss << "\"";
635
+ switch (criteria.traits[i]) {
636
+ case Trait::Text: oss << "text"; break;
637
+ case Trait::LongText: oss << "long-text"; break;
638
+ case Trait::Square: oss << "square"; break;
639
+ }
640
+ oss << "\"";
641
+ }
642
+ oss << "]";
643
+ }
644
+
645
+ oss << "}";
646
+ return oss.str();
647
+ }
648
+
649
+ } // namespace ennio