xbintsc 0.3.15 → 0.3.18
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.
- package/dist/src/cli/main.js +4 -4
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/state.js +8 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/tables.js +8 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/graph.d.ts +8 -0
- package/dist/src/driver/bundler/graph.js +85 -0
- package/dist/src/driver/bundler/graph.js.map +1 -0
- package/dist/src/driver/bundler/merge.d.ts +11 -0
- package/dist/src/driver/bundler/merge.js +288 -0
- package/dist/src/driver/bundler/merge.js.map +1 -0
- package/dist/src/driver/bundler/resolve.d.ts +12 -0
- package/dist/src/driver/bundler/resolve.js +246 -0
- package/dist/src/driver/bundler/resolve.js.map +1 -0
- package/dist/src/driver/bundler/symbols.d.ts +15 -0
- package/dist/src/driver/bundler/symbols.js +51 -0
- package/dist/src/driver/bundler/symbols.js.map +1 -0
- package/dist/src/driver/bundler/types.d.ts +36 -0
- package/dist/src/driver/bundler/types.js +5 -0
- package/dist/src/driver/bundler/types.js.map +1 -0
- package/dist/src/driver/modules.d.ts +9 -16
- package/dist/src/driver/modules.js +8 -644
- package/dist/src/driver/modules.js.map +1 -1
- package/dist/src/extensions/catalog.js +2 -1
- package/dist/src/extensions/catalog.js.map +1 -1
- package/dist/src/extensions/gui/index.d.ts +24 -0
- package/dist/src/extensions/gui/index.js +101 -0
- package/dist/src/extensions/gui/index.js.map +1 -0
- package/dist/tests/e2e/compile-basics.test.d.ts +4 -0
- package/dist/tests/e2e/compile-basics.test.js +134 -0
- package/dist/tests/e2e/compile-basics.test.js.map +1 -0
- package/dist/tests/e2e/compile-builtins.test.d.ts +4 -0
- package/dist/tests/e2e/compile-builtins.test.js +191 -0
- package/dist/tests/e2e/compile-builtins.test.js.map +1 -0
- package/dist/tests/e2e/compile-control-flow.test.d.ts +4 -0
- package/dist/tests/e2e/compile-control-flow.test.js +190 -0
- package/dist/tests/e2e/compile-control-flow.test.js.map +1 -0
- package/dist/tests/e2e/compile-modules.test.d.ts +4 -0
- package/dist/tests/e2e/compile-modules.test.js +48 -0
- package/dist/tests/e2e/compile-modules.test.js.map +1 -0
- package/dist/tests/e2e/compile-objects-classes.test.d.ts +4 -0
- package/dist/tests/e2e/compile-objects-classes.test.js +362 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -0
- package/dist/tests/e2e/differential-helpers.d.ts +12 -0
- package/dist/tests/e2e/differential-helpers.js +16 -0
- package/dist/tests/e2e/differential-helpers.js.map +1 -0
- package/dist/tests/e2e/differential-language.test.d.ts +4 -0
- package/dist/tests/e2e/{differential.test.js → differential-language.test.js} +5 -287
- package/dist/tests/e2e/differential-language.test.js.map +1 -0
- package/dist/tests/e2e/differential-symbols.test.d.ts +4 -0
- package/dist/tests/e2e/differential-symbols.test.js +92 -0
- package/dist/tests/e2e/differential-symbols.test.js.map +1 -0
- package/dist/tests/e2e/differential-values.test.d.ts +4 -0
- package/dist/tests/e2e/differential-values.test.js +197 -0
- package/dist/tests/e2e/differential-values.test.js.map +1 -0
- package/dist/tests/e2e/gui.test.d.ts +12 -0
- package/dist/tests/e2e/gui.test.js +493 -0
- package/dist/tests/e2e/gui.test.js.map +1 -0
- package/dist/tests/e2e/runtime-coverage-buffer.test.d.ts +4 -0
- package/dist/tests/e2e/runtime-coverage-buffer.test.js +97 -0
- package/dist/tests/e2e/runtime-coverage-buffer.test.js.map +1 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.d.ts +4 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.js +149 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -0
- package/dist/tests/e2e/runtime-coverage-node-modules.test.d.ts +4 -0
- package/dist/tests/e2e/runtime-coverage-node-modules.test.js +177 -0
- package/dist/tests/e2e/runtime-coverage-node-modules.test.js.map +1 -0
- package/dist/tests/e2e/runtime-coverage-streams.test.d.ts +4 -0
- package/dist/tests/e2e/runtime-coverage-streams.test.js +161 -0
- package/dist/tests/e2e/runtime-coverage-streams.test.js.map +1 -0
- package/dist/tests/e2e/runtime-coverage-system.test.d.ts +4 -0
- package/dist/tests/e2e/runtime-coverage-system.test.js +91 -0
- package/dist/tests/e2e/runtime-coverage-system.test.js.map +1 -0
- package/dist/tests/e2e/timers.test.d.ts +7 -0
- package/dist/tests/e2e/timers.test.js +63 -0
- package/dist/tests/e2e/timers.test.js.map +1 -0
- package/package.json +2 -1
- package/runtime/ext_gui/css.cpp +743 -0
- package/runtime/ext_gui/css.h +98 -0
- package/runtime/ext_gui/document.cpp +264 -0
- package/runtime/ext_gui/document.h +89 -0
- package/runtime/ext_gui/dom.cpp +472 -0
- package/runtime/ext_gui/dom.h +71 -0
- package/runtime/ext_gui/gui.cpp +457 -0
- package/runtime/ext_gui/gui_engine.h +81 -0
- package/runtime/ext_gui/image.cpp +125 -0
- package/runtime/ext_gui/image.h +42 -0
- package/runtime/ext_gui/layout.cpp +748 -0
- package/runtime/ext_gui/layout.h +93 -0
- package/runtime/ext_gui/paint.cpp +137 -0
- package/runtime/ext_gui/paint.h +75 -0
- package/runtime/ext_gui/renderer.cpp +959 -0
- package/runtime/ext_gui/renderer.h +68 -0
- package/runtime/ext_gui/stb_image.h +7988 -0
- package/runtime/ext_gui/style.cpp +771 -0
- package/runtime/ext_gui/style.h +117 -0
- package/runtime/ext_gui/text.cpp +378 -0
- package/runtime/ext_gui/text.h +78 -0
- package/runtime/ext_gui/window.cpp +405 -0
- package/runtime/ext_node/fs/fs_common.h +13 -527
- package/runtime/ext_node/fs/parts/buffer-bridge.inc +6 -0
- package/runtime/ext_node/fs/parts/builtins.inc +69 -0
- package/runtime/ext_node/fs/parts/dir.inc +47 -0
- package/runtime/ext_node/fs/parts/encoding.inc +186 -0
- package/runtime/ext_node/fs/parts/errors.inc +66 -0
- package/runtime/ext_node/fs/parts/flags.inc +39 -0
- package/runtime/ext_node/fs/parts/platform.inc +86 -0
- package/runtime/ext_node/fs/parts/promisify.inc +15 -0
- package/runtime/ext_node/fs/parts/time.inc +9 -0
- package/runtime/rt.h +33 -0
- package/runtime/xt_builtins.c +4 -0
- package/runtime/xt_containers/access.inc +168 -0
- package/runtime/xt_containers/arrays.inc +108 -0
- package/runtime/xt_containers/boxes.inc +11 -0
- package/runtime/xt_containers/objects.inc +326 -0
- package/runtime/xt_containers.c +8 -618
- package/runtime/xt_io/base64.inc +87 -0
- package/runtime/xt_io/console.inc +119 -0
- package/runtime/xt_io/exception.inc +142 -0
- package/runtime/xt_io/inspect.inc +212 -0
- package/runtime/xt_io.c +8 -553
- package/runtime/xt_loop.c +262 -24
- package/runtime/xt_regex/api.inc +58 -0
- package/runtime/xt_regex/helpers.inc +68 -0
- package/runtime/xt_regex/matcher.inc +248 -0
- package/runtime/xt_regex/parser.inc +236 -0
- package/runtime/xt_regex/types.inc +58 -0
- package/runtime/xt_regex.h +9 -672
- package/runtime/xt_values/arithmetic.inc +150 -0
- package/runtime/xt_values/comparison.inc +112 -0
- package/runtime/xt_values/constructors.inc +41 -0
- package/runtime/xt_values/conversions.inc +115 -0
- package/runtime/xt_values/number-format.inc +180 -0
- package/runtime/xt_values/strings.inc +22 -0
- package/runtime/xt_values.c +10 -626
- package/scripts/build-gui.ts +317 -0
- package/src/cli/main.ts +4 -3
- package/src/codegen/generator/state.ts +8 -0
- package/src/codegen/generator/tables.ts +8 -0
- package/src/driver/bundler/graph.ts +95 -0
- package/src/driver/bundler/merge.ts +312 -0
- package/src/driver/bundler/resolve.ts +240 -0
- package/src/driver/bundler/symbols.ts +55 -0
- package/src/driver/bundler/types.ts +37 -0
- package/src/driver/modules.ts +9 -693
- package/src/extensions/catalog.ts +2 -1
- package/src/extensions/gui/index.ts +107 -0
- package/dist/tests/e2e/compile.test.d.ts +0 -8
- package/dist/tests/e2e/compile.test.js +0 -893
- package/dist/tests/e2e/compile.test.js.map +0 -1
- package/dist/tests/e2e/differential.test.d.ts +0 -7
- package/dist/tests/e2e/differential.test.js.map +0 -1
- package/dist/tests/e2e/runtime-coverage.test.d.ts +0 -11
- package/dist/tests/e2e/runtime-coverage.test.js +0 -646
- package/dist/tests/e2e/runtime-coverage.test.js.map +0 -1
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — CSS parser, selector matcher and value parser.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include "css.h"
|
|
6
|
+
|
|
7
|
+
#include <cctype>
|
|
8
|
+
#include <cmath>
|
|
9
|
+
#include <cstdlib>
|
|
10
|
+
#include <cstring>
|
|
11
|
+
#include <unordered_map>
|
|
12
|
+
|
|
13
|
+
namespace xtgui {
|
|
14
|
+
|
|
15
|
+
/* -- string helpers ------------------------------------------------------- */
|
|
16
|
+
|
|
17
|
+
bool xt_is_css_space(char c) {
|
|
18
|
+
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
std::string xt_css_trim(const std::string &text) {
|
|
22
|
+
size_t begin = 0;
|
|
23
|
+
size_t end = text.size();
|
|
24
|
+
while (begin < end && xt_is_css_space(text[begin])) begin++;
|
|
25
|
+
while (end > begin && xt_is_css_space(text[end - 1])) end--;
|
|
26
|
+
return text.substr(begin, end - begin);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
std::string xt_css_lower(const std::string &text) {
|
|
30
|
+
std::string out = text;
|
|
31
|
+
for (char &c : out) c = (char)std::tolower((unsigned char)c);
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
std::vector<std::string> xt_css_split_whitespace(const std::string &text) {
|
|
36
|
+
std::vector<std::string> parts;
|
|
37
|
+
size_t i = 0;
|
|
38
|
+
while (i < text.size()) {
|
|
39
|
+
while (i < text.size() && xt_is_css_space(text[i])) i++;
|
|
40
|
+
size_t start = i;
|
|
41
|
+
while (i < text.size() && !xt_is_css_space(text[i])) i++;
|
|
42
|
+
if (i > start) parts.push_back(text.substr(start, i - start));
|
|
43
|
+
}
|
|
44
|
+
return parts;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static bool isIdentStart(char c) {
|
|
48
|
+
return std::isalpha((unsigned char)c) || c == '_' || c == '-' || (unsigned char)c >= 0x80;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static bool isIdentChar(char c) {
|
|
52
|
+
return std::isalnum((unsigned char)c) || c == '_' || c == '-' || (unsigned char)c >= 0x80;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static std::string stripComments(const std::string &text) {
|
|
56
|
+
std::string out;
|
|
57
|
+
out.reserve(text.size());
|
|
58
|
+
for (size_t i = 0; i < text.size(); i++) {
|
|
59
|
+
if (text[i] == '/' && i + 1 < text.size() && text[i + 1] == '*') {
|
|
60
|
+
size_t end = text.find("*/", i + 2);
|
|
61
|
+
i = (end == std::string::npos) ? text.size() : end + 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
out.push_back(text[i]);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* -- numbers / lengths / colors ------------------------------------------- */
|
|
70
|
+
|
|
71
|
+
bool xt_css_parse_number(const std::string &value, float *out) {
|
|
72
|
+
std::string text = xt_css_trim(value);
|
|
73
|
+
if (text.empty()) return false;
|
|
74
|
+
char *end = nullptr;
|
|
75
|
+
float number = strtof(text.c_str(), &end);
|
|
76
|
+
if (end == text.c_str()) return false;
|
|
77
|
+
while (end != nullptr && *end != '\0' && xt_is_css_space(*end)) end++;
|
|
78
|
+
if (end != nullptr && *end != '\0') return false;
|
|
79
|
+
*out = number;
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Length xt_css_parse_length(const std::string &value) {
|
|
84
|
+
Length length;
|
|
85
|
+
std::string text = xt_css_lower(xt_css_trim(value));
|
|
86
|
+
if (text.empty()) {
|
|
87
|
+
length.unit = Unit::Invalid;
|
|
88
|
+
return length;
|
|
89
|
+
}
|
|
90
|
+
if (text == "auto") {
|
|
91
|
+
length.unit = Unit::Auto;
|
|
92
|
+
return length;
|
|
93
|
+
}
|
|
94
|
+
if (text == "none") {
|
|
95
|
+
length.unit = Unit::None;
|
|
96
|
+
return length;
|
|
97
|
+
}
|
|
98
|
+
char *end = nullptr;
|
|
99
|
+
float number = strtof(text.c_str(), &end);
|
|
100
|
+
if (end == text.c_str()) {
|
|
101
|
+
length.unit = Unit::Invalid;
|
|
102
|
+
return length;
|
|
103
|
+
}
|
|
104
|
+
std::string unit = end;
|
|
105
|
+
while (!unit.empty() && xt_is_css_space(unit[0])) unit.erase(unit.begin());
|
|
106
|
+
length.value = number;
|
|
107
|
+
if (unit.empty() || unit == "px") length.unit = Unit::Px;
|
|
108
|
+
else if (unit == "%") length.unit = Unit::Percent;
|
|
109
|
+
else if (unit == "em") length.unit = Unit::Em;
|
|
110
|
+
else if (unit == "rem") length.unit = Unit::Rem;
|
|
111
|
+
else if (unit == "vw") length.unit = Unit::Vw;
|
|
112
|
+
else if (unit == "vh") length.unit = Unit::Vh;
|
|
113
|
+
else if (unit == "pt") { length.value = number * (96.0f / 72.0f); length.unit = Unit::Px; }
|
|
114
|
+
else if (unit == "pc") { length.value = number * 16.0f; length.unit = Unit::Px; }
|
|
115
|
+
else if (unit == "in") { length.value = number * 96.0f; length.unit = Unit::Px; }
|
|
116
|
+
else if (unit == "cm") { length.value = number * (96.0f / 2.54f); length.unit = Unit::Px; }
|
|
117
|
+
else if (unit == "mm") { length.value = number * (96.0f / 25.4f); length.unit = Unit::Px; }
|
|
118
|
+
else if (unit == "q") { length.value = number * (96.0f / 101.6f); length.unit = Unit::Px; }
|
|
119
|
+
else length.unit = Unit::Invalid;
|
|
120
|
+
return length;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
namespace {
|
|
124
|
+
|
|
125
|
+
int hexDigit(char c) {
|
|
126
|
+
if (c >= '0' && c <= '9') return c - '0';
|
|
127
|
+
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
128
|
+
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
129
|
+
return -1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
bool parseHexColor(const std::string &text, Color *color) {
|
|
133
|
+
std::string hex = text.substr(1);
|
|
134
|
+
if (hex.size() != 3 && hex.size() != 4 && hex.size() != 6 && hex.size() != 8) return false;
|
|
135
|
+
for (char c : hex) {
|
|
136
|
+
if (hexDigit(c) < 0) return false;
|
|
137
|
+
}
|
|
138
|
+
int values[8] = {0};
|
|
139
|
+
if (hex.size() == 3 || hex.size() == 4) {
|
|
140
|
+
for (size_t i = 0; i < hex.size(); i++) {
|
|
141
|
+
int v = hexDigit(hex[i]);
|
|
142
|
+
values[i] = v * 17;
|
|
143
|
+
}
|
|
144
|
+
color->r = values[0] / 255.0f;
|
|
145
|
+
color->g = values[1] / 255.0f;
|
|
146
|
+
color->b = values[2] / 255.0f;
|
|
147
|
+
color->a = (hex.size() == 4) ? values[3] / 255.0f : 1.0f;
|
|
148
|
+
} else {
|
|
149
|
+
for (size_t i = 0; i < hex.size() / 2; i++) {
|
|
150
|
+
values[i] = hexDigit(hex[i * 2]) * 16 + hexDigit(hex[i * 2 + 1]);
|
|
151
|
+
}
|
|
152
|
+
color->r = values[0] / 255.0f;
|
|
153
|
+
color->g = values[1] / 255.0f;
|
|
154
|
+
color->b = values[2] / 255.0f;
|
|
155
|
+
color->a = (hex.size() == 8) ? values[3] / 255.0f : 1.0f;
|
|
156
|
+
}
|
|
157
|
+
color->valid = true;
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
bool parseFunctionColor(const std::string &text, Color *color) {
|
|
162
|
+
size_t open = text.find('(');
|
|
163
|
+
size_t close = text.rfind(')');
|
|
164
|
+
if (open == std::string::npos || close == std::string::npos || close < open) return false;
|
|
165
|
+
std::string name = xt_css_lower(xt_css_trim(text.substr(0, open)));
|
|
166
|
+
std::string args = text.substr(open + 1, close - open - 1);
|
|
167
|
+
std::vector<std::string> parts;
|
|
168
|
+
std::string current;
|
|
169
|
+
for (char c : args) {
|
|
170
|
+
if (c == ',' || c == '/' || xt_is_css_space(c)) {
|
|
171
|
+
if (!current.empty()) parts.push_back(current);
|
|
172
|
+
current.clear();
|
|
173
|
+
} else {
|
|
174
|
+
current.push_back(c);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!current.empty()) parts.push_back(current);
|
|
178
|
+
if (parts.size() < 3) return false;
|
|
179
|
+
float values[4] = {0, 0, 0, 1};
|
|
180
|
+
for (size_t i = 0; i < parts.size() && i < 4; i++) {
|
|
181
|
+
const std::string &part = parts[i];
|
|
182
|
+
if (i < 3) {
|
|
183
|
+
if (name == "hsl" || name == "hsla") return false; // hsl not supported yet
|
|
184
|
+
if (!part.empty() && part.back() == '%') {
|
|
185
|
+
values[i] = strtof(part.c_str(), nullptr) / 100.0f;
|
|
186
|
+
} else {
|
|
187
|
+
values[i] = strtof(part.c_str(), nullptr) / 255.0f;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
if (!part.empty() && part.back() == '%') values[i] = strtof(part.c_str(), nullptr) / 100.0f;
|
|
191
|
+
else values[i] = strtof(part.c_str(), nullptr);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
(void)name;
|
|
195
|
+
color->r = values[0] < 0 ? 0 : (values[0] > 1 ? 1 : values[0]);
|
|
196
|
+
color->g = values[1] < 0 ? 0 : (values[1] > 1 ? 1 : values[1]);
|
|
197
|
+
color->b = values[2] < 0 ? 0 : (values[2] > 1 ? 1 : values[2]);
|
|
198
|
+
color->a = values[3] < 0 ? 0 : (values[3] > 1 ? 1 : values[3]);
|
|
199
|
+
color->valid = true;
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const std::unordered_map<std::string, unsigned int> &namedColors() {
|
|
204
|
+
static const std::unordered_map<std::string, unsigned int> colors = {
|
|
205
|
+
{"transparent", 0x00000000u}, {"black", 0x000000ffu}, {"white", 0xffffffffu},
|
|
206
|
+
{"red", 0xff0000ffu}, {"green", 0x008000ffu}, {"lime", 0x00ff00ffu},
|
|
207
|
+
{"blue", 0x0000ffffu}, {"yellow", 0xffff00ffu}, {"cyan", 0x00ffffffu},
|
|
208
|
+
{"aqua", 0x00ffffffu}, {"magenta", 0xff00ffffu}, {"fuchsia", 0xff00ffffu},
|
|
209
|
+
{"gray", 0x808080ffu}, {"grey", 0x808080ffu}, {"silver", 0xc0c0c0ffu},
|
|
210
|
+
{"maroon", 0x800000ffu}, {"olive", 0x808000ffu}, {"navy", 0x000080ffu},
|
|
211
|
+
{"teal", 0x008080ffu}, {"purple", 0x800080ffu}, {"orange", 0xffa500ffu},
|
|
212
|
+
{"pink", 0xffc0cbffu}, {"brown", 0xa52a2affu}, {"gold", 0xffd700ffu},
|
|
213
|
+
{"indigo", 0x4b0082ffu}, {"violet", 0xee82eeffu}, {"crimson", 0xdc143cffu},
|
|
214
|
+
{"salmon", 0xfa8072ffu}, {"tomato", 0xff6347ffu}, {"khaki", 0xf0e68cffu},
|
|
215
|
+
{"beige", 0xf5f5dcffu}, {"ivory", 0xfffff0ffu}, {"linen", 0xfaf0e6ffu},
|
|
216
|
+
{"coral", 0xff7f50ffu}, {"turquoise", 0x40e0d0ffu}, {"plum", 0xdda0ddffu},
|
|
217
|
+
{"orchid", 0xda70d6ffu}, {"skyblue", 0x87ceebffu}, {"steelblue", 0x4682b4ffu},
|
|
218
|
+
{"darkgray", 0xa9a9a9ffu}, {"darkgrey", 0xa9a9a9ffu}, {"lightgray", 0xd3d3d3ffu},
|
|
219
|
+
{"lightgrey", 0xd3d3d3ffu}, {"slategray", 0x708090ffu}, {"slategrey", 0x708090ffu},
|
|
220
|
+
};
|
|
221
|
+
return colors;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
} // namespace
|
|
225
|
+
|
|
226
|
+
Color xt_css_parse_color(const std::string &value) {
|
|
227
|
+
Color color;
|
|
228
|
+
std::string text = xt_css_lower(xt_css_trim(value));
|
|
229
|
+
if (text.empty()) return color;
|
|
230
|
+
if (text[0] == '#') {
|
|
231
|
+
parseHexColor(text, &color);
|
|
232
|
+
return color;
|
|
233
|
+
}
|
|
234
|
+
if (text.rfind("rgb", 0) == 0 || text.rfind("rgba", 0) == 0 || text.rfind("hsl", 0) == 0 ||
|
|
235
|
+
text.rfind("hsla", 0) == 0) {
|
|
236
|
+
parseFunctionColor(text, &color);
|
|
237
|
+
return color;
|
|
238
|
+
}
|
|
239
|
+
auto it = namedColors().find(text);
|
|
240
|
+
if (it != namedColors().end()) {
|
|
241
|
+
unsigned int rgba = it->second;
|
|
242
|
+
color.r = ((rgba >> 24) & 0xff) / 255.0f;
|
|
243
|
+
color.g = ((rgba >> 16) & 0xff) / 255.0f;
|
|
244
|
+
color.b = ((rgba >> 8) & 0xff) / 255.0f;
|
|
245
|
+
color.a = (rgba & 0xff) / 255.0f;
|
|
246
|
+
color.valid = true;
|
|
247
|
+
}
|
|
248
|
+
return color;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* -- selector parsing ----------------------------------------------------- */
|
|
252
|
+
|
|
253
|
+
namespace {
|
|
254
|
+
|
|
255
|
+
class SelectorParser {
|
|
256
|
+
public:
|
|
257
|
+
explicit SelectorParser(const std::string &text) : text_(text) {}
|
|
258
|
+
|
|
259
|
+
std::vector<ComplexSelector> parseList() {
|
|
260
|
+
std::vector<ComplexSelector> selectors;
|
|
261
|
+
size_t start = 0;
|
|
262
|
+
int depth = 0;
|
|
263
|
+
for (size_t i = 0; i <= text_.size(); i++) {
|
|
264
|
+
char c = (i < text_.size()) ? text_[i] : ',';
|
|
265
|
+
if (c == '(' || c == '[') depth++;
|
|
266
|
+
else if (c == ')' || c == ']') depth--;
|
|
267
|
+
else if (c == ',' && depth == 0) {
|
|
268
|
+
std::string part = text_.substr(start, i - start);
|
|
269
|
+
ComplexSelector selector;
|
|
270
|
+
if (parseComplex(part, &selector) && !selector.compounds.empty()) {
|
|
271
|
+
selectors.push_back(std::move(selector));
|
|
272
|
+
}
|
|
273
|
+
start = i + 1;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return selectors;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private:
|
|
280
|
+
const std::string &text_;
|
|
281
|
+
|
|
282
|
+
std::string readIdent(const std::string &text, size_t *index) {
|
|
283
|
+
size_t start = *index;
|
|
284
|
+
while (*index < text.size() && isIdentChar(text[*index])) (*index)++;
|
|
285
|
+
return text.substr(start, *index - start);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Parse `div.foo#bar[a="b"]:pseudo` into one compound selector. */
|
|
289
|
+
bool parseCompound(const std::string &text, size_t *index, CompoundSelector *compound) {
|
|
290
|
+
bool any = false;
|
|
291
|
+
while (*index < text.size()) {
|
|
292
|
+
char c = text[*index];
|
|
293
|
+
if (xt_is_css_space(c) || c == '>' || c == '+' || c == '~' || c == ',') break;
|
|
294
|
+
if (c == '*') {
|
|
295
|
+
(*index)++;
|
|
296
|
+
any = true;
|
|
297
|
+
} else if (c == '.') {
|
|
298
|
+
(*index)++;
|
|
299
|
+
std::string name = readIdent(text, index);
|
|
300
|
+
if (name.empty()) return false;
|
|
301
|
+
compound->classes.push_back(name);
|
|
302
|
+
any = true;
|
|
303
|
+
} else if (c == '#') {
|
|
304
|
+
(*index)++;
|
|
305
|
+
compound->id = readIdent(text, index);
|
|
306
|
+
any = true;
|
|
307
|
+
} else if (c == '[') {
|
|
308
|
+
if (!parseAttribute(text, index, compound)) return false;
|
|
309
|
+
any = true;
|
|
310
|
+
} else if (c == ':') {
|
|
311
|
+
(*index)++;
|
|
312
|
+
bool pseudoElement = false;
|
|
313
|
+
if (*index < text.size() && text[*index] == ':') {
|
|
314
|
+
pseudoElement = true;
|
|
315
|
+
(*index)++;
|
|
316
|
+
}
|
|
317
|
+
std::string name = xt_css_lower(readIdent(text, index));
|
|
318
|
+
if (name.empty()) return false;
|
|
319
|
+
if (!pseudoElement && *index < text.size() && text[*index] == '(') {
|
|
320
|
+
int depth = 0;
|
|
321
|
+
size_t start = *index;
|
|
322
|
+
while (*index < text.size()) {
|
|
323
|
+
if (text[*index] == '(') depth++;
|
|
324
|
+
else if (text[*index] == ')') {
|
|
325
|
+
depth--;
|
|
326
|
+
if (depth == 0) {
|
|
327
|
+
(*index)++;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
(*index)++;
|
|
332
|
+
}
|
|
333
|
+
name += text.substr(start, *index - start);
|
|
334
|
+
}
|
|
335
|
+
compound->pseudoClasses.push_back(name);
|
|
336
|
+
any = true;
|
|
337
|
+
} else if (isIdentStart(c)) {
|
|
338
|
+
compound->tag = xt_css_lower(readIdent(text, index));
|
|
339
|
+
any = true;
|
|
340
|
+
} else {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return any;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
bool parseAttribute(const std::string &text, size_t *index, CompoundSelector *compound) {
|
|
348
|
+
(*index)++; // '['
|
|
349
|
+
while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
|
|
350
|
+
AttrSelector attr;
|
|
351
|
+
attr.name = xt_css_lower(readIdent(text, index));
|
|
352
|
+
if (attr.name.empty()) return false;
|
|
353
|
+
while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
|
|
354
|
+
if (*index < text.size() && text[*index] == ']') {
|
|
355
|
+
(*index)++;
|
|
356
|
+
compound->attrs.push_back(attr);
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
if (*index < text.size() && (text[*index] == '=' || text[*index] == '~' || text[*index] == '|' ||
|
|
360
|
+
text[*index] == '^' || text[*index] == '$' || text[*index] == '*')) {
|
|
361
|
+
std::string op(1, text[*index]);
|
|
362
|
+
(*index)++;
|
|
363
|
+
if (*index < text.size() && text[*index] == '=') {
|
|
364
|
+
op += '=';
|
|
365
|
+
(*index)++;
|
|
366
|
+
} else if (op != "=") {
|
|
367
|
+
/* Only `~= |= ^= $= *=` need a trailing `=`; a lone `=` is complete. */
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
|
|
371
|
+
std::string value;
|
|
372
|
+
if (*index < text.size() && (text[*index] == '"' || text[*index] == '\'')) {
|
|
373
|
+
char quote = text[(*index)++];
|
|
374
|
+
size_t start = *index;
|
|
375
|
+
while (*index < text.size() && text[*index] != quote) (*index)++;
|
|
376
|
+
value = text.substr(start, *index - start);
|
|
377
|
+
if (*index < text.size()) (*index)++;
|
|
378
|
+
} else {
|
|
379
|
+
value = readIdent(text, index);
|
|
380
|
+
}
|
|
381
|
+
attr.op = op;
|
|
382
|
+
attr.value = value;
|
|
383
|
+
}
|
|
384
|
+
while (*index < text.size() && xt_is_css_space(text[*index])) (*index)++;
|
|
385
|
+
if (*index >= text.size() || text[*index] != ']') return false;
|
|
386
|
+
(*index)++;
|
|
387
|
+
compound->attrs.push_back(attr);
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
bool parseComplex(const std::string &text, ComplexSelector *selector) {
|
|
392
|
+
size_t index = 0;
|
|
393
|
+
while (index < text.size() && xt_is_css_space(text[index])) index++;
|
|
394
|
+
while (index < text.size()) {
|
|
395
|
+
CompoundSelector compound;
|
|
396
|
+
if (!parseCompound(text, &index, &compound)) return false;
|
|
397
|
+
selector->compounds.push_back(std::move(compound));
|
|
398
|
+
bool hadSpace = false;
|
|
399
|
+
while (index < text.size() && xt_is_css_space(text[index])) {
|
|
400
|
+
hadSpace = true;
|
|
401
|
+
index++;
|
|
402
|
+
}
|
|
403
|
+
if (index >= text.size() || text[index] == ',') break;
|
|
404
|
+
char c = text[index];
|
|
405
|
+
if (c == '>' || c == '+' || c == '~') {
|
|
406
|
+
index++;
|
|
407
|
+
while (index < text.size() && xt_is_css_space(text[index])) index++;
|
|
408
|
+
selector->combinators.push_back(c == '>' ? Combinator::Child
|
|
409
|
+
: c == '+' ? Combinator::Adjacent
|
|
410
|
+
: Combinator::Sibling);
|
|
411
|
+
} else if (hadSpace) {
|
|
412
|
+
selector->combinators.push_back(Combinator::Descendant);
|
|
413
|
+
} else {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (selector->combinators.size() + 1 != selector->compounds.size()) {
|
|
418
|
+
/* Ensure the invariant holds even if parsing stopped early. */
|
|
419
|
+
selector->combinators.resize(selector->compounds.size() > 0 ? selector->compounds.size() - 1 : 0);
|
|
420
|
+
}
|
|
421
|
+
selector->specificity = computeSpecificity(*selector);
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
static int computeCompoundSpecificity(const CompoundSelector &compound) {
|
|
426
|
+
/* `:not(x)` is approximated as a class; pseudo-elements would count as a
|
|
427
|
+
* type but are not modelled yet. Good enough for a UI stylesheet. */
|
|
428
|
+
return (int)(compound.classes.size() + compound.attrs.size() + compound.pseudoClasses.size());
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
static int computeSpecificity(const ComplexSelector &selector) {
|
|
432
|
+
int ids = 0;
|
|
433
|
+
int classes = 0;
|
|
434
|
+
int types = 0;
|
|
435
|
+
for (const CompoundSelector &compound : selector.compounds) {
|
|
436
|
+
if (!compound.id.empty()) ids++;
|
|
437
|
+
classes += computeCompoundSpecificity(compound);
|
|
438
|
+
if (!compound.tag.empty()) types++;
|
|
439
|
+
}
|
|
440
|
+
return (ids << 20) | (classes << 10) | types;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
} // namespace
|
|
445
|
+
|
|
446
|
+
std::vector<ComplexSelector> xt_css_parse_selector_list(const std::string &text) {
|
|
447
|
+
SelectorParser parser(text);
|
|
448
|
+
return parser.parseList();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/* -- declaration / stylesheet parsing ------------------------------------- */
|
|
452
|
+
|
|
453
|
+
namespace {
|
|
454
|
+
|
|
455
|
+
/** Find the first `needle` at nesting depth zero (parens/brackets/quotes). */
|
|
456
|
+
size_t findTopLevel(const std::string &text, char needle, size_t from) {
|
|
457
|
+
int depth = 0;
|
|
458
|
+
char quote = 0;
|
|
459
|
+
for (size_t i = from; i < text.size(); i++) {
|
|
460
|
+
char c = text[i];
|
|
461
|
+
if (quote != 0) {
|
|
462
|
+
if (c == quote) quote = 0;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (c == '"' || c == '\'') quote = c;
|
|
466
|
+
else if (c == '(' || c == '[') depth++;
|
|
467
|
+
else if (c == ')' || c == ']') depth--;
|
|
468
|
+
else if (c == needle && depth == 0) return i;
|
|
469
|
+
}
|
|
470
|
+
return std::string::npos;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
std::string stripImportant(std::string value, bool *important) {
|
|
474
|
+
size_t pos = value.rfind('!');
|
|
475
|
+
if (pos == std::string::npos) return value;
|
|
476
|
+
std::string flag = xt_css_lower(xt_css_trim(value.substr(pos + 1)));
|
|
477
|
+
if (flag == "important") {
|
|
478
|
+
*important = true;
|
|
479
|
+
return xt_css_trim(value.substr(0, pos));
|
|
480
|
+
}
|
|
481
|
+
return value;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
} // namespace
|
|
485
|
+
|
|
486
|
+
std::vector<Declaration> xt_css_parse_declarations(const std::string &text) {
|
|
487
|
+
std::vector<Declaration> declarations;
|
|
488
|
+
size_t start = 0;
|
|
489
|
+
while (start < text.size()) {
|
|
490
|
+
size_t end = findTopLevel(text, ';', start);
|
|
491
|
+
if (end == std::string::npos) end = text.size();
|
|
492
|
+
std::string part = text.substr(start, end - start);
|
|
493
|
+
start = end + 1;
|
|
494
|
+
size_t colon = findTopLevel(part, ':', 0);
|
|
495
|
+
if (colon == std::string::npos) continue;
|
|
496
|
+
Declaration declaration;
|
|
497
|
+
declaration.property = xt_css_lower(xt_css_trim(part.substr(0, colon)));
|
|
498
|
+
declaration.value = stripImportant(xt_css_trim(part.substr(colon + 1)), &declaration.important);
|
|
499
|
+
if (!declaration.property.empty() && !declaration.value.empty()) {
|
|
500
|
+
declarations.push_back(std::move(declaration));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return declarations;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
StyleSheet xt_css_parse_stylesheet(const std::string &text) {
|
|
507
|
+
StyleSheet sheet;
|
|
508
|
+
std::string clean = stripComments(text);
|
|
509
|
+
size_t index = 0;
|
|
510
|
+
int order = 0;
|
|
511
|
+
while (index < clean.size()) {
|
|
512
|
+
while (index < clean.size() && xt_is_css_space(clean[index])) index++;
|
|
513
|
+
if (index >= clean.size()) break;
|
|
514
|
+
if (clean[index] == '@') {
|
|
515
|
+
size_t brace = clean.find('{', index);
|
|
516
|
+
size_t semi = clean.find(';', index);
|
|
517
|
+
if (brace != std::string::npos && (semi == std::string::npos || brace < semi)) {
|
|
518
|
+
int depth = 0;
|
|
519
|
+
size_t i = brace;
|
|
520
|
+
for (; i < clean.size(); i++) {
|
|
521
|
+
if (clean[i] == '{') depth++;
|
|
522
|
+
else if (clean[i] == '}') {
|
|
523
|
+
depth--;
|
|
524
|
+
if (depth == 0) break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
index = (i < clean.size()) ? i + 1 : clean.size();
|
|
528
|
+
} else {
|
|
529
|
+
index = (semi == std::string::npos) ? clean.size() : semi + 1;
|
|
530
|
+
}
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
size_t brace = clean.find('{', index);
|
|
534
|
+
if (brace == std::string::npos) break;
|
|
535
|
+
int depth = 0;
|
|
536
|
+
size_t close = std::string::npos;
|
|
537
|
+
for (size_t i = brace; i < clean.size(); i++) {
|
|
538
|
+
if (clean[i] == '{') depth++;
|
|
539
|
+
else if (clean[i] == '}') {
|
|
540
|
+
depth--;
|
|
541
|
+
if (depth == 0) {
|
|
542
|
+
close = i;
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (close == std::string::npos) break;
|
|
548
|
+
Rule rule;
|
|
549
|
+
rule.selectors = xt_css_parse_selector_list(clean.substr(index, brace - index));
|
|
550
|
+
rule.declarations = xt_css_parse_declarations(clean.substr(brace + 1, close - brace - 1));
|
|
551
|
+
rule.order = order++;
|
|
552
|
+
if (!rule.selectors.empty() && !rule.declarations.empty()) sheet.rules.push_back(std::move(rule));
|
|
553
|
+
index = close + 1;
|
|
554
|
+
}
|
|
555
|
+
return sheet;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/* -- matching ------------------------------------------------------------- */
|
|
559
|
+
|
|
560
|
+
namespace {
|
|
561
|
+
|
|
562
|
+
bool hasClass(const Node *node, const std::string &name) {
|
|
563
|
+
const std::string *value = node->attr("class");
|
|
564
|
+
if (value == nullptr) return false;
|
|
565
|
+
for (const std::string &token : xt_css_split_whitespace(*value)) {
|
|
566
|
+
if (token == name) return true;
|
|
567
|
+
}
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
bool matchAttribute(const Node *node, const AttrSelector &attr) {
|
|
572
|
+
const std::string *value = node->attr(attr.name);
|
|
573
|
+
if (value == nullptr) return false;
|
|
574
|
+
if (attr.op.empty()) return true;
|
|
575
|
+
if (attr.op == "=") return *value == attr.value;
|
|
576
|
+
if (attr.op == "~=") {
|
|
577
|
+
for (const std::string &token : xt_css_split_whitespace(*value)) {
|
|
578
|
+
if (token == attr.value) return true;
|
|
579
|
+
}
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
if (attr.op == "|=") {
|
|
583
|
+
return *value == attr.value || value->rfind(attr.value + "-", 0) == 0;
|
|
584
|
+
}
|
|
585
|
+
if (attr.op == "^=") return !attr.value.empty() && value->rfind(attr.value, 0) == 0;
|
|
586
|
+
if (attr.op == "$=") {
|
|
587
|
+
return !attr.value.empty() && value->size() >= attr.value.size() &&
|
|
588
|
+
value->compare(value->size() - attr.value.size(), attr.value.size(), attr.value) == 0;
|
|
589
|
+
}
|
|
590
|
+
if (attr.op == "*=") {
|
|
591
|
+
return !attr.value.empty() && value->find(attr.value) != std::string::npos;
|
|
592
|
+
}
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** Parse the `an+b` micro-syntax of `:nth-child`. */
|
|
597
|
+
bool parseNth(const std::string &expression, int *a, int *b) {
|
|
598
|
+
std::string text;
|
|
599
|
+
for (char c : xt_css_lower(xt_css_trim(expression))) {
|
|
600
|
+
if (!xt_is_css_space(c)) text.push_back(c);
|
|
601
|
+
}
|
|
602
|
+
if (text == "odd") { *a = 2; *b = 1; return true; }
|
|
603
|
+
if (text == "even") { *a = 2; *b = 0; return true; }
|
|
604
|
+
size_t nPos = text.find('n');
|
|
605
|
+
if (nPos == std::string::npos) {
|
|
606
|
+
*a = 0;
|
|
607
|
+
*b = atoi(text.c_str());
|
|
608
|
+
return !text.empty();
|
|
609
|
+
}
|
|
610
|
+
std::string aPart = text.substr(0, nPos);
|
|
611
|
+
std::string bPart = text.substr(nPos + 1);
|
|
612
|
+
if (aPart.empty() || aPart == "+") *a = 1;
|
|
613
|
+
else if (aPart == "-") *a = -1;
|
|
614
|
+
else *a = atoi(aPart.c_str());
|
|
615
|
+
if (bPart.empty()) *b = 0;
|
|
616
|
+
else *b = atoi(bPart.c_str());
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
bool matchesNth(int index, const std::string &expression) {
|
|
621
|
+
int a = 0;
|
|
622
|
+
int b = 0;
|
|
623
|
+
if (!parseNth(expression, &a, &b)) return false;
|
|
624
|
+
if (a == 0) return index == b;
|
|
625
|
+
int diff = index - b;
|
|
626
|
+
if (a > 0) return diff >= 0 && diff % a == 0;
|
|
627
|
+
return diff <= 0 && (-diff) % (-a) == 0;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
bool isHoverTarget(const Node *node, const Node *hover) {
|
|
631
|
+
for (const Node *current = hover; current != nullptr; current = current->parent) {
|
|
632
|
+
if (current == node) return true;
|
|
633
|
+
}
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
bool matchPseudo(const Node *node, const std::string &pseudo, const MatchState &state) {
|
|
638
|
+
if (pseudo == "first-child") return node->elementIndex() == 0 && node->elementSiblingCount() > 0;
|
|
639
|
+
if (pseudo == "last-child") return node->elementIndex() == node->elementSiblingCount() - 1;
|
|
640
|
+
if (pseudo == "only-child") return node->elementSiblingCount() == 1;
|
|
641
|
+
if (pseudo == "root") return node->parent != nullptr && node->parent->isDocument();
|
|
642
|
+
if (pseudo == "hover") return isHoverTarget(node, state.hover);
|
|
643
|
+
if (pseudo == "focus") return node == state.focus;
|
|
644
|
+
if (pseudo == "empty") {
|
|
645
|
+
for (const std::unique_ptr<Node> &child : node->children) {
|
|
646
|
+
if (child->isElement()) return false;
|
|
647
|
+
if (child->isText() && !xt_css_trim(child->text).empty()) return false;
|
|
648
|
+
}
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
if (pseudo == "disabled") return node->attr("disabled") != nullptr;
|
|
652
|
+
if (pseudo == "checked") return node->attr("checked") != nullptr;
|
|
653
|
+
if (pseudo.rfind("nth-child(", 0) == 0) {
|
|
654
|
+
return matchesNth(node->elementIndex() + 1, pseudo.substr(10, pseudo.size() - 11));
|
|
655
|
+
}
|
|
656
|
+
if (pseudo.rfind("not(", 0) == 0) {
|
|
657
|
+
std::string inner = xt_css_trim(pseudo.substr(4, pseudo.size() - 5));
|
|
658
|
+
std::vector<ComplexSelector> parsed = xt_css_parse_selector_list(inner);
|
|
659
|
+
if (parsed.empty() || parsed[0].compounds.size() != 1) return false;
|
|
660
|
+
const CompoundSelector &compound = parsed[0].compounds[0];
|
|
661
|
+
for (const AttrSelector &attr : compound.attrs) {
|
|
662
|
+
if (!matchAttribute(node, attr)) return true;
|
|
663
|
+
}
|
|
664
|
+
if (!compound.id.empty()) {
|
|
665
|
+
const std::string *id = node->attr("id");
|
|
666
|
+
if (id == nullptr || *id != compound.id) return true;
|
|
667
|
+
}
|
|
668
|
+
if (!compound.tag.empty() && node->tag != compound.tag) return true;
|
|
669
|
+
for (const std::string &name : compound.classes) {
|
|
670
|
+
if (!hasClass(node, name)) return true;
|
|
671
|
+
}
|
|
672
|
+
for (const std::string &nested : compound.pseudoClasses) {
|
|
673
|
+
if (!matchPseudo(node, nested, state)) return true;
|
|
674
|
+
}
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
/* Truly unknown / unsupported pseudo-classes never match. */
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
bool matchCompound(const Node *node, const CompoundSelector &compound, const MatchState &state) {
|
|
682
|
+
if (!node->isElement()) return false;
|
|
683
|
+
if (!compound.tag.empty() && node->tag != compound.tag) return false;
|
|
684
|
+
if (!compound.id.empty()) {
|
|
685
|
+
const std::string *id = node->attr("id");
|
|
686
|
+
if (id == nullptr || *id != compound.id) return false;
|
|
687
|
+
}
|
|
688
|
+
for (const std::string &name : compound.classes) {
|
|
689
|
+
if (!hasClass(node, name)) return false;
|
|
690
|
+
}
|
|
691
|
+
for (const AttrSelector &attr : compound.attrs) {
|
|
692
|
+
if (!matchAttribute(node, attr)) return false;
|
|
693
|
+
}
|
|
694
|
+
for (const std::string &pseudo : compound.pseudoClasses) {
|
|
695
|
+
if (!matchPseudo(node, pseudo, state)) return false;
|
|
696
|
+
}
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
bool matchAt(const Node *node, const ComplexSelector &selector, size_t index,
|
|
701
|
+
const MatchState &state) {
|
|
702
|
+
if (!matchCompound(node, selector.compounds[index], state)) return false;
|
|
703
|
+
if (index == 0) return true;
|
|
704
|
+
Combinator combinator = selector.combinators[index - 1];
|
|
705
|
+
if (combinator == Combinator::Child) {
|
|
706
|
+
return node->parent != nullptr && node->parent->isElement() &&
|
|
707
|
+
matchAt(node->parent, selector, index - 1, state);
|
|
708
|
+
}
|
|
709
|
+
if (combinator == Combinator::Descendant) {
|
|
710
|
+
for (const Node *parent = node->parent; parent != nullptr; parent = parent->parent) {
|
|
711
|
+
if (parent->isElement() && matchAt(parent, selector, index - 1, state)) return true;
|
|
712
|
+
}
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
if (node->parent == nullptr) return false;
|
|
716
|
+
if (combinator == Combinator::Adjacent) {
|
|
717
|
+
const Node *previous = nullptr;
|
|
718
|
+
for (const std::unique_ptr<Node> &sibling : node->parent->children) {
|
|
719
|
+
if (sibling.get() == node) break;
|
|
720
|
+
if (sibling->isElement()) previous = sibling.get();
|
|
721
|
+
}
|
|
722
|
+
return previous != nullptr && matchAt(previous, selector, index - 1, state);
|
|
723
|
+
}
|
|
724
|
+
/* General sibling combinator (`~`): any previous element sibling. */
|
|
725
|
+
for (const std::unique_ptr<Node> &sibling : node->parent->children) {
|
|
726
|
+
if (sibling.get() == node) break;
|
|
727
|
+
if (sibling->isElement() && matchAt(sibling.get(), selector, index - 1, state)) return true;
|
|
728
|
+
}
|
|
729
|
+
return false;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
} // namespace
|
|
733
|
+
|
|
734
|
+
bool xt_css_match(const Node *node, const ComplexSelector &selector) {
|
|
735
|
+
return xt_css_match(node, selector, MatchState());
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
bool xt_css_match(const Node *node, const ComplexSelector &selector, const MatchState &state) {
|
|
739
|
+
if (selector.compounds.empty()) return false;
|
|
740
|
+
return matchAt(node, selector, selector.compounds.size() - 1, state);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
} // namespace xtgui
|