1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qcssparser_p.h"
5
6#include <QtCore/qmap.h>
7#include <qdebug.h>
8#include <qicon.h>
9#include <qcolor.h>
10#include <qfont.h>
11#include <qfileinfo.h>
12#include <qfontmetrics.h>
13#include <qbrush.h>
14#include <qimagereader.h>
15
16#include <algorithm>
17
18#ifndef QT_NO_CSSPARSER
19
20QT_BEGIN_NAMESPACE
21
22using namespace Qt::StringLiterals;
23
24QT_IMPL_METATYPE_EXTERN_TAGGED(QCss::BackgroundData, QCss__BackgroundData)
25QT_IMPL_METATYPE_EXTERN_TAGGED(QCss::LengthData, QCss__LengthData)
26QT_IMPL_METATYPE_EXTERN_TAGGED(QCss::BorderData, QCss__BorderData)
27
28#include "qcssscanner.cpp"
29
30using namespace QCss;
31
32struct QCssKnownValue
33{
34 const char name[28];
35 quint64 id;
36};
37
38static const QCssKnownValue properties[NumProperties - 1] = {
39 { .name: "-qt-background-role", .id: QtBackgroundRole },
40 { .name: "-qt-block-indent", .id: QtBlockIndent },
41 { .name: "-qt-fg-texture-cachekey", .id: QtForegroundTextureCacheKey },
42 { .name: "-qt-line-height-type", .id: QtLineHeightType },
43 { .name: "-qt-list-indent", .id: QtListIndent },
44 { .name: "-qt-list-number-prefix", .id: QtListNumberPrefix },
45 { .name: "-qt-list-number-suffix", .id: QtListNumberSuffix },
46 { .name: "-qt-paragraph-type", .id: QtParagraphType },
47 { .name: "-qt-style-features", .id: QtStyleFeatures },
48 { .name: "-qt-table-type", .id: QtTableType },
49 { .name: "-qt-user-state", .id: QtUserState },
50 { .name: "accent-color", .id: QtAccent },
51 { .name: "alternate-background-color", .id: QtAlternateBackground },
52 { .name: "background", .id: Background },
53 { .name: "background-attachment", .id: BackgroundAttachment },
54 { .name: "background-clip", .id: BackgroundClip },
55 { .name: "background-color", .id: BackgroundColor },
56 { .name: "background-image", .id: BackgroundImage },
57 { .name: "background-origin", .id: BackgroundOrigin },
58 { .name: "background-position", .id: BackgroundPosition },
59 { .name: "background-repeat", .id: BackgroundRepeat },
60 { .name: "border", .id: Border },
61 { .name: "border-bottom", .id: BorderBottom },
62 { .name: "border-bottom-color", .id: BorderBottomColor },
63 { .name: "border-bottom-left-radius", .id: BorderBottomLeftRadius },
64 { .name: "border-bottom-right-radius", .id: BorderBottomRightRadius },
65 { .name: "border-bottom-style", .id: BorderBottomStyle },
66 { .name: "border-bottom-width", .id: BorderBottomWidth },
67 { .name: "border-collapse", .id: BorderCollapse },
68 { .name: "border-color", .id: BorderColor },
69 { .name: "border-image", .id: BorderImage },
70 { .name: "border-left", .id: BorderLeft },
71 { .name: "border-left-color", .id: BorderLeftColor },
72 { .name: "border-left-style", .id: BorderLeftStyle },
73 { .name: "border-left-width", .id: BorderLeftWidth },
74 { .name: "border-radius", .id: BorderRadius },
75 { .name: "border-right", .id: BorderRight },
76 { .name: "border-right-color", .id: BorderRightColor },
77 { .name: "border-right-style", .id: BorderRightStyle },
78 { .name: "border-right-width", .id: BorderRightWidth },
79 { .name: "border-style", .id: BorderStyles },
80 { .name: "border-top", .id: BorderTop },
81 { .name: "border-top-color", .id: BorderTopColor },
82 { .name: "border-top-left-radius", .id: BorderTopLeftRadius },
83 { .name: "border-top-right-radius", .id: BorderTopRightRadius },
84 { .name: "border-top-style", .id: BorderTopStyle },
85 { .name: "border-top-width", .id: BorderTopWidth },
86 { .name: "border-width", .id: BorderWidth },
87 { .name: "bottom", .id: Bottom },
88 { .name: "color", .id: Color },
89 { .name: "float", .id: Float },
90 { .name: "font", .id: Font },
91 { .name: "font-family", .id: FontFamily },
92 { .name: "font-kerning", .id: FontKerning },
93 { .name: "font-size", .id: FontSize },
94 { .name: "font-style", .id: FontStyle },
95 { .name: "font-variant", .id: FontVariant },
96 { .name: "font-weight", .id: FontWeight },
97 { .name: "height", .id: Height },
98 { .name: "icon", .id: QtIcon },
99 { .name: "image", .id: QtImage },
100 { .name: "image-position", .id: QtImageAlignment },
101 { .name: "left", .id: Left },
102 { .name: "letter-spacing", .id: LetterSpacing },
103 { .name: "line-height", .id: LineHeight },
104 { .name: "list-style", .id: ListStyle },
105 { .name: "list-style-type", .id: ListStyleType },
106 { .name: "margin" , .id: Margin },
107 { .name: "margin-bottom", .id: MarginBottom },
108 { .name: "margin-left", .id: MarginLeft },
109 { .name: "margin-right", .id: MarginRight },
110 { .name: "margin-top", .id: MarginTop },
111 { .name: "max-height", .id: MaximumHeight },
112 { .name: "max-width", .id: MaximumWidth },
113 { .name: "min-height", .id: MinimumHeight },
114 { .name: "min-width", .id: MinimumWidth },
115 { .name: "outline", .id: Outline },
116 { .name: "outline-bottom-left-radius", .id: OutlineBottomLeftRadius },
117 { .name: "outline-bottom-right-radius", .id: OutlineBottomRightRadius },
118 { .name: "outline-color", .id: OutlineColor },
119 { .name: "outline-offset", .id: OutlineOffset },
120 { .name: "outline-radius", .id: OutlineRadius },
121 { .name: "outline-style", .id: OutlineStyle },
122 { .name: "outline-top-left-radius", .id: OutlineTopLeftRadius },
123 { .name: "outline-top-right-radius", .id: OutlineTopRightRadius },
124 { .name: "outline-width", .id: OutlineWidth },
125 { .name: "padding", .id: Padding },
126 { .name: "padding-bottom", .id: PaddingBottom },
127 { .name: "padding-left", .id: PaddingLeft },
128 { .name: "padding-right", .id: PaddingRight },
129 { .name: "padding-top", .id: PaddingTop },
130 { .name: "page-break-after", .id: PageBreakAfter },
131 { .name: "page-break-before", .id: PageBreakBefore },
132 { .name: "placeholder-text-color", .id: QtPlaceHolderTextColor },
133 { .name: "position", .id: Position },
134 { .name: "right", .id: Right },
135 { .name: "selection-background-color", .id: QtSelectionBackground },
136 { .name: "selection-color", .id: QtSelectionForeground },
137 { .name: "spacing", .id: QtSpacing },
138 { .name: "subcontrol-origin", .id: QtOrigin },
139 { .name: "subcontrol-position", .id: QtPosition },
140 { .name: "text-align", .id: TextAlignment },
141 { .name: "text-decoration", .id: TextDecoration },
142 { .name: "text-decoration-color", .id: TextDecorationColor },
143 { .name: "text-indent", .id: TextIndent },
144 { .name: "text-transform", .id: TextTransform },
145 { .name: "text-underline-style", .id: TextUnderlineStyle },
146 { .name: "top", .id: Top },
147 { .name: "vertical-align", .id: VerticalAlignment },
148 { .name: "white-space", .id: Whitespace },
149 { .name: "width", .id: Width },
150 { .name: "word-spacing", .id: WordSpacing }
151};
152
153static const QCssKnownValue values[NumKnownValues - 1] = {
154 { .name: "active", .id: Value_Active },
155 { .name: "alternate-base", .id: Value_AlternateBase },
156 { .name: "always", .id: Value_Always },
157 { .name: "auto", .id: Value_Auto },
158 { .name: "base", .id: Value_Base },
159 { .name: "bold", .id: Value_Bold },
160 { .name: "bottom", .id: Value_Bottom },
161 { .name: "bright-text", .id: Value_BrightText },
162 { .name: "button", .id: Value_Button },
163 { .name: "button-text", .id: Value_ButtonText },
164 { .name: "center", .id: Value_Center },
165 { .name: "circle", .id: Value_Circle },
166 { .name: "dark", .id: Value_Dark },
167 { .name: "dashed", .id: Value_Dashed },
168 { .name: "decimal", .id: Value_Decimal },
169 { .name: "disabled", .id: Value_Disabled },
170 { .name: "disc", .id: Value_Disc },
171 { .name: "dot-dash", .id: Value_DotDash },
172 { .name: "dot-dot-dash", .id: Value_DotDotDash },
173 { .name: "dotted", .id: Value_Dotted },
174 { .name: "double", .id: Value_Double },
175 { .name: "groove", .id: Value_Groove },
176 { .name: "highlight", .id: Value_Highlight },
177 { .name: "highlighted-text", .id: Value_HighlightedText },
178 { .name: "inset", .id: Value_Inset },
179 { .name: "italic", .id: Value_Italic },
180 { .name: "large", .id: Value_Large },
181 { .name: "left", .id: Value_Left },
182 { .name: "light", .id: Value_Light },
183 { .name: "line-through", .id: Value_LineThrough },
184 { .name: "link", .id: Value_Link },
185 { .name: "link-visited", .id: Value_LinkVisited },
186 { .name: "lower-alpha", .id: Value_LowerAlpha },
187 { .name: "lower-roman", .id: Value_LowerRoman },
188 { .name: "lowercase", .id: Value_Lowercase },
189 { .name: "medium", .id: Value_Medium },
190 { .name: "mid", .id: Value_Mid },
191 { .name: "middle", .id: Value_Middle },
192 { .name: "midlight", .id: Value_Midlight },
193 { .name: "native", .id: Value_Native },
194 { .name: "none", .id: Value_None },
195 { .name: "normal", .id: Value_Normal },
196 { .name: "nowrap", .id: Value_NoWrap },
197 { .name: "oblique", .id: Value_Oblique },
198 { .name: "off", .id: Value_Off },
199 { .name: "on", .id: Value_On },
200 { .name: "outset", .id: Value_Outset },
201 { .name: "overline", .id: Value_Overline },
202 { .name: "pre", .id: Value_Pre },
203 { .name: "pre-line", .id: Value_PreLine },
204 { .name: "pre-wrap", .id: Value_PreWrap },
205 { .name: "ridge", .id: Value_Ridge },
206 { .name: "right", .id: Value_Right },
207 { .name: "selected", .id: Value_Selected },
208 { .name: "shadow", .id: Value_Shadow },
209 { .name: "small" , .id: Value_Small },
210 { .name: "small-caps", .id: Value_SmallCaps },
211 { .name: "solid", .id: Value_Solid },
212 { .name: "square", .id: Value_Square },
213 { .name: "sub", .id: Value_Sub },
214 { .name: "super", .id: Value_Super },
215 { .name: "text", .id: Value_Text },
216 { .name: "top", .id: Value_Top },
217 { .name: "transparent", .id: Value_Transparent },
218 { .name: "underline", .id: Value_Underline },
219 { .name: "upper-alpha", .id: Value_UpperAlpha },
220 { .name: "upper-roman", .id: Value_UpperRoman },
221 { .name: "uppercase", .id: Value_Uppercase },
222 { .name: "wave", .id: Value_Wave },
223 { .name: "window", .id: Value_Window },
224 { .name: "window-text", .id: Value_WindowText },
225 { .name: "x-large", .id: Value_XLarge },
226 { .name: "xx-large", .id: Value_XXLarge }
227};
228
229//Map id to strings as they appears in the 'values' array above
230static const short indexOfId[NumKnownValues] = { 0, 41, 48, 42, 49, 50, 55, 35, 26, 71, 72, 25, 43, 5, 64, 48,
231 29, 59, 60, 27, 52, 62, 6, 10, 39, 56, 19, 13, 17, 18, 20, 21, 51, 24, 46, 68, 37, 3, 2, 40, 63, 16,
232 11, 58, 14, 32, 65, 33, 66, 56, 67, 34, 70, 8, 28, 38, 12, 36, 61, 7, 9, 4, 69, 54, 22, 23, 30, 31,
233 1, 15, 0, 53, 45, 44 };
234
235QString Value::toString() const
236{
237 if (type == KnownIdentifier) {
238 return QLatin1StringView(values[indexOfId[variant.toInt()]].name);
239 } else {
240 return variant.toString();
241 }
242}
243
244static const QCssKnownValue pseudos[NumPseudos - 1] = {
245 { .name: "active", .id: PseudoClass_Active },
246 { .name: "adjoins-item", .id: PseudoClass_Item },
247 { .name: "alternate", .id: PseudoClass_Alternate },
248 { .name: "bottom", .id: PseudoClass_Bottom },
249 { .name: "checked", .id: PseudoClass_Checked },
250 { .name: "closable", .id: PseudoClass_Closable },
251 { .name: "closed", .id: PseudoClass_Closed },
252 { .name: "default", .id: PseudoClass_Default },
253 { .name: "disabled", .id: PseudoClass_Disabled },
254 { .name: "edit-focus", .id: PseudoClass_EditFocus },
255 { .name: "editable", .id: PseudoClass_Editable },
256 { .name: "enabled", .id: PseudoClass_Enabled },
257 { .name: "exclusive", .id: PseudoClass_Exclusive },
258 { .name: "first", .id: PseudoClass_First },
259 { .name: "flat", .id: PseudoClass_Flat },
260 { .name: "floatable", .id: PseudoClass_Floatable },
261 { .name: "focus", .id: PseudoClass_Focus },
262 { .name: "has-children", .id: PseudoClass_Children },
263 { .name: "has-siblings", .id: PseudoClass_Sibling },
264 { .name: "horizontal", .id: PseudoClass_Horizontal },
265 { .name: "hover", .id: PseudoClass_Hover },
266 { .name: "indeterminate" , .id: PseudoClass_Indeterminate },
267 { .name: "last", .id: PseudoClass_Last },
268 { .name: "left", .id: PseudoClass_Left },
269 { .name: "maximized", .id: PseudoClass_Maximized },
270 { .name: "middle", .id: PseudoClass_Middle },
271 { .name: "minimized", .id: PseudoClass_Minimized },
272 { .name: "movable", .id: PseudoClass_Movable },
273 { .name: "next-selected", .id: PseudoClass_NextSelected },
274 { .name: "no-frame", .id: PseudoClass_Frameless },
275 { .name: "non-exclusive", .id: PseudoClass_NonExclusive },
276 { .name: "off", .id: PseudoClass_Unchecked },
277 { .name: "on", .id: PseudoClass_Checked },
278 { .name: "only-one", .id: PseudoClass_OnlyOne },
279 { .name: "open", .id: PseudoClass_Open },
280 { .name: "pressed", .id: PseudoClass_Pressed },
281 { .name: "previous-selected", .id: PseudoClass_PreviousSelected },
282 { .name: "read-only", .id: PseudoClass_ReadOnly },
283 { .name: "right", .id: PseudoClass_Right },
284 { .name: "selected", .id: PseudoClass_Selected },
285 { .name: "top", .id: PseudoClass_Top },
286 { .name: "unchecked" , .id: PseudoClass_Unchecked },
287 { .name: "vertical", .id: PseudoClass_Vertical },
288 { .name: "window", .id: PseudoClass_Window }
289};
290
291static const QCssKnownValue origins[NumKnownOrigins - 1] = {
292 { .name: "border", .id: Origin_Border },
293 { .name: "content", .id: Origin_Content },
294 { .name: "margin", .id: Origin_Margin }, // not in css
295 { .name: "padding", .id: Origin_Padding }
296};
297
298static const QCssKnownValue repeats[NumKnownRepeats - 1] = {
299 { .name: "no-repeat", .id: Repeat_None },
300 { .name: "repeat-x", .id: Repeat_X },
301 { .name: "repeat-xy", .id: Repeat_XY },
302 { .name: "repeat-y", .id: Repeat_Y }
303};
304
305static const QCssKnownValue tileModes[NumKnownTileModes - 1] = {
306 { .name: "repeat", .id: TileMode_Repeat },
307 { .name: "round", .id: TileMode_Round },
308 { .name: "stretch", .id: TileMode_Stretch },
309};
310
311static const QCssKnownValue positions[NumKnownPositionModes - 1] = {
312 { .name: "absolute", .id: PositionMode_Absolute },
313 { .name: "fixed", .id: PositionMode_Fixed },
314 { .name: "relative", .id: PositionMode_Relative },
315 { .name: "static", .id: PositionMode_Static }
316};
317
318static const QCssKnownValue attachments[NumKnownAttachments - 1] = {
319 { .name: "fixed", .id: Attachment_Fixed },
320 { .name: "scroll", .id: Attachment_Scroll }
321};
322
323static const QCssKnownValue styleFeatures[NumKnownStyleFeatures - 1] = {
324 { .name: "background-color", .id: StyleFeature_BackgroundColor },
325 { .name: "background-gradient", .id: StyleFeature_BackgroundGradient },
326 { .name: "none", .id: StyleFeature_None }
327};
328
329static bool operator<(const QString &name, const QCssKnownValue &prop)
330{
331 return QString::compare(s1: name, s2: QLatin1StringView(prop.name), cs: Qt::CaseInsensitive) < 0;
332}
333
334static bool operator<(const QCssKnownValue &prop, const QString &name)
335{
336 return QString::compare(s1: QLatin1StringView(prop.name), s2: name, cs: Qt::CaseInsensitive) < 0;
337}
338
339static quint64 findKnownValue(const QString &name, const QCssKnownValue *start, int numValues)
340{
341 const QCssKnownValue *end = start + numValues - 1;
342 const QCssKnownValue *prop = std::lower_bound(first: start, last: end, val: name);
343 if ((prop == end) || (name < *prop))
344 return 0;
345 return prop->id;
346}
347
348static inline bool isInheritable(Property propertyId)
349{
350 switch (propertyId) {
351 case Font:
352 case FontKerning:
353 case FontFamily:
354 case FontSize:
355 case FontStyle:
356 case FontWeight:
357 case TextIndent:
358 case Whitespace:
359 case ListStyleType:
360 case ListStyle:
361 case TextAlignment:
362 case FontVariant:
363 case TextTransform:
364 case LineHeight:
365 case LetterSpacing:
366 case WordSpacing:
367 return true;
368 default:
369 break;
370 }
371 return false;
372}
373
374///////////////////////////////////////////////////////////////////////////////
375// Value Extractor
376ValueExtractor::ValueExtractor(const QList<Declaration> &decls, const QPalette &pal)
377: declarations(decls), adjustment(0), fontExtracted(false), pal(pal)
378{
379}
380
381LengthData ValueExtractor::lengthValue(const Value& v)
382{
383 const QString str = v.variant.toString();
384 QStringView s(str);
385 LengthData data;
386 data.unit = LengthData::None;
387 if (s.endsWith(s: u"px", cs: Qt::CaseInsensitive))
388 data.unit = LengthData::Px;
389 else if (s.endsWith(s: u"ex", cs: Qt::CaseInsensitive))
390 data.unit = LengthData::Ex;
391 else if (s.endsWith(s: u"em", cs: Qt::CaseInsensitive))
392 data.unit = LengthData::Em;
393
394 if (data.unit != LengthData::None)
395 s.chop(n: 2);
396
397 data.number = s.toDouble();
398 return data;
399}
400
401static int lengthValueFromData(const LengthData& data, const QFont& f)
402{
403 const int scale = (data.unit == LengthData::Ex ? QFontMetrics(f).xHeight()
404 : data.unit == LengthData::Em ? QFontMetrics(f).height() : 1);
405 // raised lower limit due to the implementation of qRound()
406 return qRound(d: qBound(min: double(INT_MIN) + 0.1, val: scale * data.number, max: double(INT_MAX)));
407}
408
409int ValueExtractor::lengthValue(const Declaration &decl)
410{
411 if (decl.d->parsed.isValid())
412 return lengthValueFromData(data: qvariant_cast<LengthData>(v: decl.d->parsed), f);
413 if (decl.d->values.size() < 1)
414 return 0;
415 LengthData data = lengthValue(v: decl.d->values.at(i: 0));
416 decl.d->parsed = QVariant::fromValue<LengthData>(value: data);
417 return lengthValueFromData(data,f);
418}
419
420void ValueExtractor::lengthValues(const Declaration &decl, int *m)
421{
422 if (decl.d->parsed.isValid()) {
423 QList<QVariant> v = decl.d->parsed.toList();
424 Q_ASSERT(v.size() == 4);
425 for (int i = 0; i < 4; i++)
426 m[i] = lengthValueFromData(data: qvariant_cast<LengthData>(v: v.at(i)), f);
427 return;
428 }
429
430 LengthData datas[4];
431 int i;
432 for (i = 0; i < qMin(a: decl.d->values.size(), b: 4); i++)
433 datas[i] = lengthValue(v: decl.d->values[i]);
434
435 if (i == 0) {
436 LengthData zero = {.number: 0.0, .unit: LengthData::None};
437 datas[0] = datas[1] = datas[2] = datas[3] = zero;
438 } else if (i == 1) {
439 datas[3] = datas[2] = datas[1] = datas[0];
440 } else if (i == 2) {
441 datas[2] = datas[0];
442 datas[3] = datas[1];
443 } else if (i == 3) {
444 datas[3] = datas[1];
445 }
446
447 QList<QVariant> v;
448 v.reserve(asize: 4);
449 for (i = 0; i < 4; i++) {
450 v += QVariant::fromValue<LengthData>(value: datas[i]);
451 m[i] = lengthValueFromData(data: datas[i], f);
452 }
453 decl.d->parsed = v;
454}
455
456bool ValueExtractor::extractGeometry(int *w, int *h, int *minw, int *minh, int *maxw, int *maxh)
457{
458 extractFont();
459 bool hit = false;
460 for (int i = 0; i < declarations.size(); i++) {
461 const Declaration &decl = declarations.at(i);
462 switch (decl.d->propertyId) {
463 case Width: *w = lengthValue(decl); break;
464 case Height: *h = lengthValue(decl); break;
465 case MinimumWidth: *minw = lengthValue(decl); break;
466 case MinimumHeight: *minh = lengthValue(decl); break;
467 case MaximumWidth: *maxw = lengthValue(decl); break;
468 case MaximumHeight: *maxh = lengthValue(decl); break;
469 default: continue;
470 }
471 hit = true;
472 }
473
474 return hit;
475}
476
477bool ValueExtractor::extractPosition(int *left, int *top, int *right, int *bottom, QCss::Origin *origin,
478 Qt::Alignment *position, QCss::PositionMode *mode, Qt::Alignment *textAlignment)
479{
480 extractFont();
481 bool hit = false;
482 for (int i = 0; i < declarations.size(); i++) {
483 const Declaration &decl = declarations.at(i);
484 switch (decl.d->propertyId) {
485 case Left: *left = lengthValue(decl); break;
486 case Top: *top = lengthValue(decl); break;
487 case Right: *right = lengthValue(decl); break;
488 case Bottom: *bottom = lengthValue(decl); break;
489 case QtOrigin: *origin = decl.originValue(); break;
490 case QtPosition: *position = decl.alignmentValue(); break;
491 case TextAlignment: *textAlignment = decl.alignmentValue(); break;
492 case Position: *mode = decl.positionValue(); break;
493 default: continue;
494 }
495 hit = true;
496 }
497
498 return hit;
499}
500
501bool ValueExtractor::extractBox(int *margins, int *paddings, int *spacing)
502{
503 extractFont();
504 bool hit = false;
505 for (int i = 0; i < declarations.size(); i++) {
506 const Declaration &decl = declarations.at(i);
507 switch (decl.d->propertyId) {
508 case PaddingLeft: paddings[LeftEdge] = lengthValue(decl); break;
509 case PaddingRight: paddings[RightEdge] = lengthValue(decl); break;
510 case PaddingTop: paddings[TopEdge] = lengthValue(decl); break;
511 case PaddingBottom: paddings[BottomEdge] = lengthValue(decl); break;
512 case Padding: lengthValues(decl, m: paddings); break;
513
514 case MarginLeft: margins[LeftEdge] = lengthValue(decl); break;
515 case MarginRight: margins[RightEdge] = lengthValue(decl); break;
516 case MarginTop: margins[TopEdge] = lengthValue(decl); break;
517 case MarginBottom: margins[BottomEdge] = lengthValue(decl); break;
518 case Margin: lengthValues(decl, m: margins); break;
519 case QtSpacing: if (spacing) *spacing = lengthValue(decl); break;
520
521 default: continue;
522 }
523 hit = true;
524 }
525
526 return hit;
527}
528
529int ValueExtractor::extractStyleFeatures()
530{
531 int features = StyleFeature_None;
532 for (int i = 0; i < declarations.size(); i++) {
533 const Declaration &decl = declarations.at(i);
534 if (decl.d->propertyId == QtStyleFeatures)
535 features = decl.styleFeaturesValue();
536 }
537 return features;
538}
539
540QSize ValueExtractor::sizeValue(const Declaration &decl)
541{
542 if (decl.d->parsed.isValid()) {
543 QList<QVariant> v = decl.d->parsed.toList();
544 return QSize(lengthValueFromData(data: qvariant_cast<LengthData>(v: v.at(i: 0)), f),
545 lengthValueFromData(data: qvariant_cast<LengthData>(v: v.at(i: 1)), f));
546 }
547
548 LengthData x[2] = { {.number: 0, .unit: LengthData::None }, {.number: 0, .unit: LengthData::None} };
549 if (decl.d->values.size() > 0)
550 x[0] = lengthValue(v: decl.d->values.at(i: 0));
551 if (decl.d->values.size() > 1)
552 x[1] = lengthValue(v: decl.d->values.at(i: 1));
553 else
554 x[1] = x[0];
555 QList<QVariant> v;
556 v << QVariant::fromValue<LengthData>(value: x[0]) << QVariant::fromValue<LengthData>(value: x[1]);
557 decl.d->parsed = v;
558 return QSize(lengthValueFromData(data: x[0], f), lengthValueFromData(data: x[1], f));
559}
560
561void ValueExtractor::sizeValues(const Declaration &decl, QSize *radii)
562{
563 radii[0] = sizeValue(decl);
564 for (int i = 1; i < 4; i++)
565 radii[i] = radii[0];
566}
567
568bool ValueExtractor::extractBorder(int *borders, QBrush *colors, BorderStyle *styles,
569 QSize *radii)
570{
571 extractFont();
572 bool hit = false;
573 for (int i = 0; i < declarations.size(); i++) {
574 const Declaration &decl = declarations.at(i);
575 switch (decl.d->propertyId) {
576 case BorderLeftWidth: borders[LeftEdge] = lengthValue(decl); break;
577 case BorderRightWidth: borders[RightEdge] = lengthValue(decl); break;
578 case BorderTopWidth: borders[TopEdge] = lengthValue(decl); break;
579 case BorderBottomWidth: borders[BottomEdge] = lengthValue(decl); break;
580 case BorderWidth: lengthValues(decl, m: borders); break;
581
582 case BorderLeftColor: colors[LeftEdge] = decl.brushValue(pal); break;
583 case BorderRightColor: colors[RightEdge] = decl.brushValue(pal); break;
584 case BorderTopColor: colors[TopEdge] = decl.brushValue(pal); break;
585 case BorderBottomColor: colors[BottomEdge] = decl.brushValue(pal); break;
586 case BorderColor: decl.brushValues(c: colors, pal); break;
587
588 case BorderTopStyle: styles[TopEdge] = decl.styleValue(); break;
589 case BorderBottomStyle: styles[BottomEdge] = decl.styleValue(); break;
590 case BorderLeftStyle: styles[LeftEdge] = decl.styleValue(); break;
591 case BorderRightStyle: styles[RightEdge] = decl.styleValue(); break;
592 case BorderStyles: decl.styleValues(s: styles); break;
593
594 case BorderTopLeftRadius: radii[0] = sizeValue(decl); break;
595 case BorderTopRightRadius: radii[1] = sizeValue(decl); break;
596 case BorderBottomLeftRadius: radii[2] = sizeValue(decl); break;
597 case BorderBottomRightRadius: radii[3] = sizeValue(decl); break;
598 case BorderRadius: sizeValues(decl, radii); break;
599
600 case BorderLeft:
601 borderValue(decl, width: &borders[LeftEdge], style: &styles[LeftEdge], color: &colors[LeftEdge]);
602 break;
603 case BorderTop:
604 borderValue(decl, width: &borders[TopEdge], style: &styles[TopEdge], color: &colors[TopEdge]);
605 break;
606 case BorderRight:
607 borderValue(decl, width: &borders[RightEdge], style: &styles[RightEdge], color: &colors[RightEdge]);
608 break;
609 case BorderBottom:
610 borderValue(decl, width: &borders[BottomEdge], style: &styles[BottomEdge], color: &colors[BottomEdge]);
611 break;
612 case Border:
613 borderValue(decl, width: &borders[LeftEdge], style: &styles[LeftEdge], color: &colors[LeftEdge]);
614 borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge];
615 styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge];
616 colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge];
617 break;
618
619 default: continue;
620 }
621 hit = true;
622 }
623
624 return hit;
625}
626
627bool ValueExtractor::extractOutline(int *borders, QBrush *colors, BorderStyle *styles,
628 QSize *radii, int *offsets)
629{
630 extractFont();
631 bool hit = false;
632 for (int i = 0; i < declarations.size(); i++) {
633 const Declaration &decl = declarations.at(i);
634 switch (decl.d->propertyId) {
635 case OutlineWidth: lengthValues(decl, m: borders); break;
636 case OutlineColor: decl.brushValues(c: colors, pal); break;
637 case OutlineStyle: decl.styleValues(s: styles); break;
638
639 case OutlineTopLeftRadius: radii[0] = sizeValue(decl); break;
640 case OutlineTopRightRadius: radii[1] = sizeValue(decl); break;
641 case OutlineBottomLeftRadius: radii[2] = sizeValue(decl); break;
642 case OutlineBottomRightRadius: radii[3] = sizeValue(decl); break;
643 case OutlineRadius: sizeValues(decl, radii); break;
644 case OutlineOffset: lengthValues(decl, m: offsets); break;
645
646 case Outline:
647 borderValue(decl, width: &borders[LeftEdge], style: &styles[LeftEdge], color: &colors[LeftEdge]);
648 borders[TopEdge] = borders[RightEdge] = borders[BottomEdge] = borders[LeftEdge];
649 styles[TopEdge] = styles[RightEdge] = styles[BottomEdge] = styles[LeftEdge];
650 colors[TopEdge] = colors[RightEdge] = colors[BottomEdge] = colors[LeftEdge];
651 break;
652
653 default: continue;
654 }
655 hit = true;
656 }
657
658 return hit;
659}
660
661static Qt::Alignment parseAlignment(const QCss::Value *values, int count)
662{
663 Qt::Alignment a[2] = { { }, { } };
664 for (int i = 0; i < qMin(a: 2, b: count); i++) {
665 if (values[i].type != Value::KnownIdentifier)
666 break;
667 switch (values[i].variant.toInt()) {
668 case Value_Left: a[i] = Qt::AlignLeft; break;
669 case Value_Right: a[i] = Qt::AlignRight; break;
670 case Value_Top: a[i] = Qt::AlignTop; break;
671 case Value_Bottom: a[i] = Qt::AlignBottom; break;
672 case Value_Center: a[i] = Qt::AlignCenter; break;
673 default: break;
674 }
675 }
676
677 if (a[0] == Qt::AlignCenter && a[1] != 0 && a[1] != Qt::AlignCenter)
678 a[0] = (a[1] == Qt::AlignLeft || a[1] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter;
679 if ((a[1] == 0 || a[1] == Qt::AlignCenter) && a[0] != Qt::AlignCenter)
680 a[1] = (a[0] == Qt::AlignLeft || a[0] == Qt::AlignRight) ? Qt::AlignVCenter : Qt::AlignHCenter;
681 return a[0] | a[1];
682}
683
684static ColorData parseColorValue(QCss::Value v)
685{
686 if (v.type == Value::Identifier || v.type == Value::String) {
687 v.variant.convert(type: QMetaType::fromType<QColor>());
688 v.type = Value::Color;
689 }
690
691 if (v.type == Value::Color)
692 return qvariant_cast<QColor>(v: v.variant);
693
694 if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent)
695 return QColor(Qt::transparent);
696
697 if (v.type != Value::Function)
698 return ColorData();
699
700 QStringList lst = v.variant.toStringList();
701 if (lst.size() != 2)
702 return ColorData();
703
704 const QString &identifier = lst.at(i: 0);
705 if ((identifier.compare(other: "palette"_L1, cs: Qt::CaseInsensitive)) == 0) {
706 int role = findKnownValue(name: lst.at(i: 1).trimmed(), start: values, numValues: NumKnownValues);
707 if (role >= Value_FirstColorRole && role <= Value_LastColorRole)
708 return (QPalette::ColorRole)(role-Value_FirstColorRole);
709
710 return ColorData();
711 }
712
713 const bool rgb = identifier.startsWith(s: "rgb"_L1);
714 const bool hsv = !rgb && identifier.startsWith(s: "hsv"_L1);
715 const bool hsl = !rgb && !hsv && identifier.startsWith(s: "hsl"_L1);
716
717 if (!rgb && !hsv && !hsl)
718 return ColorData();
719
720 const bool hasAlpha = identifier.size() == 4 && identifier.at(i: 3) == u'a';
721 if (identifier.size() > 3 && !hasAlpha)
722 return ColorData();
723
724 Parser p(lst.at(i: 1));
725 if (!p.testExpr())
726 return ColorData();
727
728 QList<QCss::Value> colorDigits;
729 if (!p.parseExpr(values: &colorDigits))
730 return ColorData();
731 const int tokenCount = colorDigits.size();
732
733 for (int i = 0; i < qMin(a: tokenCount, b: 7); i += 2) {
734 if (colorDigits.at(i).type == Value::Percentage) {
735 const qreal maxRange = (rgb || i != 0) ? 255. : 359.;
736 colorDigits[i].variant = colorDigits.at(i).variant.toReal() * (maxRange / 100.);
737 colorDigits[i].type = Value::Number;
738 } else if (colorDigits.at(i).type != Value::Number) {
739 return ColorData();
740 }
741 }
742
743
744 if (tokenCount < 5)
745 return ColorData();
746
747 if (hasAlpha && tokenCount != 7) {
748 qWarning(msg: "QCssParser::parseColorValue: Specified color with alpha value but no alpha given: '%s'", qPrintable(lst.join(u' ')));
749 return ColorData();
750 }
751 if (!hasAlpha && tokenCount != 5) {
752 qWarning(msg: "QCssParser::parseColorValue: Specified color without alpha value but alpha given: '%s'", qPrintable(lst.join(u' ')));
753 return ColorData();
754 }
755
756 int v1 = colorDigits.at(i: 0).variant.toInt();
757 int v2 = colorDigits.at(i: 2).variant.toInt();
758 int v3 = colorDigits.at(i: 4).variant.toInt();
759 int alpha = 255;
760 if (tokenCount == 7) {
761 int alphaValue = colorDigits.at(i: 6).variant.toInt();
762 if (alphaValue <= 1)
763 alpha = colorDigits.at(i: 6).variant.toReal() * 255.;
764 else
765 alpha = alphaValue;
766 }
767
768 if (rgb)
769 return QColor::fromRgb(r: v1, g: v2, b: v3, a: alpha);
770 if (hsv)
771 return QColor::fromHsv(h: v1, s: v2, v: v3, a: alpha);
772 return QColor::fromHsl(h: v1, s: v2, l: v3, a: alpha);
773}
774
775static QColor colorFromData(const ColorData& c, const QPalette &pal)
776{
777 if (c.type == ColorData::Color) {
778 return c.color;
779 } else if (c.type == ColorData::Role) {
780 return pal.color(cr: c.role);
781 }
782 return QColor();
783}
784
785static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal)
786{
787 ColorData c = parseColorValue(v);
788 if (c.type == ColorData::Color) {
789 return QBrush(c.color);
790 } else if (c.type == ColorData::Role) {
791 return c.role;
792 }
793
794 if (v.type != Value::Function)
795 return BrushData();
796
797 QStringList lst = v.variant.toStringList();
798 if (lst.size() != 2)
799 return BrushData();
800
801 QStringList gradFuncs;
802 gradFuncs << "qlineargradient"_L1 << "qradialgradient"_L1 << "qconicalgradient"_L1 << "qgradient"_L1;
803 int gradType = -1;
804
805 if ((gradType = gradFuncs.indexOf(str: lst.at(i: 0).toLower())) == -1)
806 return BrushData();
807
808 QHash<QString, qreal> vars;
809 QList<QGradientStop> stops;
810
811 int spread = -1;
812 QStringList spreads;
813 spreads << "pad"_L1 << "reflect"_L1 << "repeat"_L1;
814
815 bool dependsOnThePalette = false;
816 Parser parser(lst.at(i: 1));
817 while (parser.hasNext()) {
818 parser.skipSpace();
819 if (!parser.test(t: IDENT))
820 return BrushData();
821 QString attr = parser.lexem();
822 parser.skipSpace();
823 if (!parser.test(t: COLON))
824 return BrushData();
825 parser.skipSpace();
826 if (attr.compare(other: "stop"_L1, cs: Qt::CaseInsensitive) == 0) {
827 QCss::Value stop, color;
828 parser.next();
829 if (!parser.parseTerm(value: &stop)) return BrushData();
830 parser.skipSpace();
831 parser.next();
832 if (!parser.parseTerm(value: &color)) return BrushData();
833 ColorData cd = parseColorValue(v: color);
834 if (cd.type == ColorData::Role)
835 dependsOnThePalette = true;
836 stops.append(t: QGradientStop(stop.variant.toReal(), colorFromData(c: cd, pal)));
837 } else {
838 parser.next();
839 QCss::Value value;
840 (void)parser.parseTerm(value: &value);
841 if (attr.compare(other: "spread"_L1, cs: Qt::CaseInsensitive) == 0) {
842 spread = spreads.indexOf(str: value.variant.toString());
843 } else {
844 vars[attr] = value.variant.toReal();
845 }
846 }
847 parser.skipSpace();
848 (void)parser.test(t: COMMA);
849 }
850
851 if (gradType == 0) {
852 QLinearGradient lg(vars.value(key: "x1"_L1), vars.value(key: "y1"_L1),
853 vars.value(key: "x2"_L1), vars.value(key: "y2"_L1));
854 lg.setCoordinateMode(QGradient::ObjectBoundingMode);
855 lg.setStops(stops);
856 if (spread != -1)
857 lg.setSpread(QGradient::Spread(spread));
858 BrushData bd = QBrush(lg);
859 if (dependsOnThePalette)
860 bd.type = BrushData::DependsOnThePalette;
861 return bd;
862 }
863
864 if (gradType == 1) {
865 QRadialGradient rg(vars.value(key: "cx"_L1), vars.value(key: "cy"_L1),
866 vars.value(key: "radius"_L1), vars.value(key: "fx"_L1),
867 vars.value(key: "fy"_L1));
868 rg.setCoordinateMode(QGradient::ObjectBoundingMode);
869 rg.setStops(stops);
870 if (spread != -1)
871 rg.setSpread(QGradient::Spread(spread));
872 BrushData bd = QBrush(rg);
873 if (dependsOnThePalette)
874 bd.type = BrushData::DependsOnThePalette;
875 return bd;
876 }
877
878 if (gradType == 2) {
879 QConicalGradient cg(vars.value(key: "cx"_L1), vars.value(key: "cy"_L1), vars.value(key: "angle"_L1));
880 cg.setCoordinateMode(QGradient::ObjectBoundingMode);
881 cg.setStops(stops);
882 if (spread != -1)
883 cg.setSpread(QGradient::Spread(spread));
884 BrushData bd = QBrush(cg);
885 if (dependsOnThePalette)
886 bd.type = BrushData::DependsOnThePalette;
887 return bd;
888 }
889
890 return BrushData();
891}
892
893static QBrush brushFromData(const BrushData& c, const QPalette &pal)
894{
895 if (c.type == BrushData::Role) {
896 return pal.color(cr: c.role);
897 } else {
898 return c.brush;
899 }
900}
901
902static BorderStyle parseStyleValue(const QCss::Value &v)
903{
904 if (v.type == Value::KnownIdentifier) {
905 switch (v.variant.toInt()) {
906 case Value_None:
907 return BorderStyle_None;
908 case Value_Dotted:
909 return BorderStyle_Dotted;
910 case Value_Dashed:
911 return BorderStyle_Dashed;
912 case Value_Solid:
913 return BorderStyle_Solid;
914 case Value_Double:
915 return BorderStyle_Double;
916 case Value_DotDash:
917 return BorderStyle_DotDash;
918 case Value_DotDotDash:
919 return BorderStyle_DotDotDash;
920 case Value_Groove:
921 return BorderStyle_Groove;
922 case Value_Ridge:
923 return BorderStyle_Ridge;
924 case Value_Inset:
925 return BorderStyle_Inset;
926 case Value_Outset:
927 return BorderStyle_Outset;
928 case Value_Native:
929 return BorderStyle_Native;
930 default:
931 break;
932 }
933 }
934
935 return BorderStyle_Unknown;
936}
937
938void ValueExtractor::borderValue(const Declaration &decl, int *width, QCss::BorderStyle *style, QBrush *color)
939{
940 if (decl.d->parsed.isValid()) {
941 BorderData data = qvariant_cast<BorderData>(v: decl.d->parsed);
942 *width = lengthValueFromData(data: data.width, f);
943 *style = data.style;
944 *color = data.color.type != BrushData::Invalid ? brushFromData(c: data.color, pal) : QBrush(QColor());
945 return;
946 }
947
948 *width = 0;
949 *style = BorderStyle_None;
950 *color = QColor();
951
952 if (decl.d->values.isEmpty())
953 return;
954
955 BorderData data;
956 data.width.number = 0;
957 data.width.unit = LengthData::None;
958 data.style = BorderStyle_None;
959
960 int i = 0;
961 if (decl.d->values.at(i).type == Value::Length || decl.d->values.at(i).type == Value::Number) {
962 data.width = lengthValue(v: decl.d->values.at(i));
963 *width = lengthValueFromData(data: data.width, f);
964 if (++i >= decl.d->values.size()) {
965 decl.d->parsed = QVariant::fromValue<BorderData>(value: data);
966 return;
967 }
968 }
969
970 data.style = parseStyleValue(v: decl.d->values.at(i));
971 if (data.style != BorderStyle_Unknown) {
972 *style = data.style;
973 if (++i >= decl.d->values.size()) {
974 decl.d->parsed = QVariant::fromValue<BorderData>(value: data);
975 return;
976 }
977 } else {
978 data.style = BorderStyle_None;
979 }
980
981 data.color = parseBrushValue(v: decl.d->values.at(i), pal);
982 *color = brushFromData(c: data.color, pal);
983 if (data.color.type != BrushData::DependsOnThePalette)
984 decl.d->parsed = QVariant::fromValue<BorderData>(value: data);
985}
986
987static void parseShorthandBackgroundProperty(const QList<QCss::Value> &values, BrushData *brush, QString *image, Repeat *repeat, Qt::Alignment *alignment, const QPalette &pal)
988{
989 *brush = BrushData();
990 *image = QString();
991 *repeat = Repeat_XY;
992 *alignment = Qt::AlignTop | Qt::AlignLeft;
993
994 for (int i = 0; i < values.size(); ++i) {
995 const QCss::Value &v = values.at(i);
996 if (v.type == Value::Uri) {
997 *image = v.variant.toString();
998 continue;
999 } else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_None) {
1000 *image = QString();
1001 continue;
1002 } else if (v.type == Value::KnownIdentifier && v.variant.toInt() == Value_Transparent) {
1003 *brush = QBrush(Qt::transparent);
1004 }
1005
1006 Repeat repeatAttempt = static_cast<Repeat>(findKnownValue(name: v.variant.toString(),
1007 start: repeats, numValues: NumKnownRepeats));
1008 if (repeatAttempt != Repeat_Unknown) {
1009 *repeat = repeatAttempt;
1010 continue;
1011 }
1012
1013 if (v.type == Value::KnownIdentifier) {
1014 const int start = i;
1015 int count = 1;
1016 if (i < values.size() - 1
1017 && values.at(i: i + 1).type == Value::KnownIdentifier) {
1018 ++i;
1019 ++count;
1020 }
1021 Qt::Alignment a = parseAlignment(values: values.constData() + start, count);
1022 if (int(a) != 0) {
1023 *alignment = a;
1024 continue;
1025 }
1026 i -= count - 1;
1027 }
1028
1029 *brush = parseBrushValue(v, pal);
1030 }
1031}
1032
1033bool ValueExtractor::extractBackground(QBrush *brush, QString *image, Repeat *repeat,
1034 Qt::Alignment *alignment, Origin *origin, Attachment *attachment,
1035 Origin *clip)
1036{
1037 bool hit = false;
1038 for (int i = 0; i < declarations.size(); ++i) {
1039 const Declaration &decl = declarations.at(i);
1040 if (decl.d->values.isEmpty())
1041 continue;
1042 const QCss::Value &val = decl.d->values.at(i: 0);
1043 switch (decl.d->propertyId) {
1044 case BackgroundColor:
1045 *brush = decl.brushValue();
1046 break;
1047 case BackgroundImage:
1048 if (val.type == Value::Uri)
1049 *image = val.variant.toString();
1050 break;
1051 case BackgroundRepeat:
1052 if (decl.d->parsed.isValid()) {
1053 *repeat = static_cast<Repeat>(decl.d->parsed.toInt());
1054 } else {
1055 *repeat = static_cast<Repeat>(findKnownValue(name: val.variant.toString(),
1056 start: repeats, numValues: NumKnownRepeats));
1057 decl.d->parsed = *repeat;
1058 }
1059 break;
1060 case BackgroundPosition:
1061 *alignment = decl.alignmentValue();
1062 break;
1063 case BackgroundOrigin:
1064 *origin = decl.originValue();
1065 break;
1066 case BackgroundClip:
1067 *clip = decl.originValue();
1068 break;
1069 case Background:
1070 if (decl.d->parsed.isValid()) {
1071 BackgroundData data = qvariant_cast<BackgroundData>(v: decl.d->parsed);
1072 *brush = brushFromData(c: data.brush, pal);
1073 *image = data.image;
1074 *repeat = data.repeat;
1075 *alignment = data.alignment;
1076 } else {
1077 BrushData brushData;
1078 parseShorthandBackgroundProperty(values: decl.d->values, brush: &brushData, image, repeat, alignment, pal);
1079 *brush = brushFromData(c: brushData, pal);
1080 if (brushData.type != BrushData::DependsOnThePalette) {
1081 BackgroundData data = { .brush: brushData, .image: *image, .repeat: *repeat, .alignment: *alignment };
1082 decl.d->parsed = QVariant::fromValue<BackgroundData>(value: data);
1083 }
1084 }
1085 break;
1086 case BackgroundAttachment:
1087 *attachment = decl.attachmentValue();
1088 break;
1089 default: continue;
1090 }
1091 hit = true;
1092 }
1093 return hit;
1094}
1095
1096static bool setFontSizeFromValue(QCss::Value value, QFont *font, int *fontSizeAdjustment)
1097{
1098 if (value.type == Value::KnownIdentifier) {
1099 bool valid = true;
1100 switch (value.variant.toInt()) {
1101 case Value_Small: *fontSizeAdjustment = -1; break;
1102 case Value_Medium: *fontSizeAdjustment = 0; break;
1103 case Value_Large: *fontSizeAdjustment = 1; break;
1104 case Value_XLarge: *fontSizeAdjustment = 2; break;
1105 case Value_XXLarge: *fontSizeAdjustment = 3; break;
1106 default: valid = false; break;
1107 }
1108 return valid;
1109 }
1110 if (value.type != Value::Length)
1111 return false;
1112
1113 bool valid = false;
1114 QString s = value.variant.toString();
1115 if (s.endsWith(s: "pt"_L1, cs: Qt::CaseInsensitive)) {
1116 s.chop(n: 2);
1117 value.variant = s;
1118 if (value.variant.convert(type: QMetaType::fromType<qreal>())) {
1119 font->setPointSizeF(qBound(min: qreal(0), val: value.variant.toReal(), max: qreal(1 << 24) - 1));
1120 valid = true;
1121 }
1122 } else if (s.endsWith(s: "px"_L1, cs: Qt::CaseInsensitive)) {
1123 s.chop(n: 2);
1124 value.variant = s;
1125 if (value.variant.convert(type: QMetaType::fromType<int>())) {
1126 font->setPixelSize(qBound(min: 0, val: value.variant.toInt(), max: (1 << 24) - 1));
1127 valid = true;
1128 }
1129 }
1130 return valid;
1131}
1132
1133static bool setFontStyleFromValue(const QCss::Value &value, QFont *font)
1134{
1135 if (value.type != Value::KnownIdentifier)
1136 return false ;
1137 switch (value.variant.toInt()) {
1138 case Value_Normal: font->setStyle(QFont::StyleNormal); return true;
1139 case Value_Italic: font->setStyle(QFont::StyleItalic); return true;
1140 case Value_Oblique: font->setStyle(QFont::StyleOblique); return true;
1141 default: break;
1142 }
1143 return false;
1144}
1145
1146static bool setFontKerningFromValue(const QCss::Value &value, QFont *font)
1147{
1148 if (value.type != Value::KnownIdentifier)
1149 return false ;
1150 switch (value.variant.toInt()) {
1151 case Value_Normal: font->setKerning(true); return true;
1152 case Value_None: font->setKerning(false); return true;
1153 case Value_Auto: return true;
1154 default: break;
1155 }
1156 return false;
1157}
1158
1159static bool setFontWeightFromValue(const QCss::Value &value, QFont *font)
1160{
1161 if (value.type == Value::KnownIdentifier) {
1162 switch (value.variant.toInt()) {
1163 case Value_Normal: font->setWeight(QFont::Normal); return true;
1164 case Value_Bold: font->setWeight(QFont::Bold); return true;
1165 default: break;
1166 }
1167 return false;
1168 }
1169 if (value.type != Value::Number)
1170 return false;
1171 // .toInt() would call qRound64() and might overflow the long long there
1172 font->setWeight(QFont::Weight(qRound(d: qBound(min: 0.0, val: value.variant.toDouble(), max: 1001.0))));
1173 return true;
1174}
1175
1176/** \internal
1177 * parse the font family from the values (starting from index \a start)
1178 * and set it the \a font
1179 * The function returns \c true if a family was extracted.
1180 */
1181static bool setFontFamilyFromValues(const QList<QCss::Value> &values, QFont *font, int start = 0)
1182{
1183 QString family;
1184 QStringList families;
1185 bool shouldAddSpace = false;
1186 for (int i = start; i < values.size(); ++i) {
1187 const QCss::Value &v = values.at(i);
1188 if (v.type == Value::TermOperatorComma) {
1189 families << family;
1190 family.clear();
1191 shouldAddSpace = false;
1192 continue;
1193 }
1194 const QString str = v.variant.toString();
1195 if (str.isEmpty())
1196 break;
1197 if (shouldAddSpace)
1198 family += u' ';
1199 family += str;
1200 shouldAddSpace = true;
1201 }
1202 if (!family.isEmpty())
1203 families << family;
1204 if (families.isEmpty())
1205 return false;
1206 font->setFamilies(families);
1207 return true;
1208}
1209
1210static void setTextDecorationFromValues(const QList<QCss::Value> &values, QFont *font)
1211{
1212 for (int i = 0; i < values.size(); ++i) {
1213 if (values.at(i).type != Value::KnownIdentifier)
1214 continue;
1215 switch (values.at(i).variant.toInt()) {
1216 case Value_Underline: font->setUnderline(true); break;
1217 case Value_Overline: font->setOverline(true); break;
1218 case Value_LineThrough: font->setStrikeOut(true); break;
1219 case Value_None:
1220 font->setUnderline(false);
1221 font->setOverline(false);
1222 font->setStrikeOut(false);
1223 break;
1224 default: break;
1225 }
1226 }
1227}
1228
1229static void setLetterSpacingFromValue(const QCss::Value &value, QFont *font)
1230{
1231 QString s = value.variant.toString();
1232 qreal val;
1233 bool ok = false;
1234 if (s.endsWith(s: "em"_L1, cs: Qt::CaseInsensitive)) {
1235 s.chop(n: 2);
1236 val = s.toDouble(ok: &ok);
1237 if (ok)
1238 font->setLetterSpacing(type: QFont::PercentageSpacing, spacing: (val + 1.0) * 100);
1239 } else if (s.endsWith(s: "px"_L1, cs: Qt::CaseInsensitive)) {
1240 s.chop(n: 2);
1241 val = s.toDouble(ok: &ok);
1242 if (ok)
1243 font->setLetterSpacing(type: QFont::AbsoluteSpacing, spacing: val);
1244 }
1245}
1246
1247static void setWordSpacingFromValue(const QCss::Value &value, QFont *font)
1248{
1249 QString s = value.variant.toString();
1250 if (s.endsWith(s: "px"_L1, cs: Qt::CaseInsensitive)) {
1251 s.chop(n: 2);
1252 qreal val;
1253 bool ok = false;
1254 val = s.toDouble(ok: &ok);
1255 if (ok)
1256 font->setWordSpacing(val);
1257 }
1258}
1259
1260static void parseShorthandFontProperty(const QList<QCss::Value> &values, QFont *font, int *fontSizeAdjustment)
1261{
1262 font->setStyle(QFont::StyleNormal);
1263 font->setWeight(QFont::Normal);
1264 *fontSizeAdjustment = -255;
1265
1266 int i = 0;
1267 while (i < values.size()) {
1268 if (setFontStyleFromValue(value: values.at(i), font)
1269 || setFontWeightFromValue(value: values.at(i), font))
1270 ++i;
1271 else
1272 break;
1273 }
1274
1275 if (i < values.size()) {
1276 setFontSizeFromValue(value: values.at(i), font, fontSizeAdjustment);
1277 ++i;
1278 }
1279
1280 if (i < values.size()) {
1281 setFontFamilyFromValues(values, font, start: i);
1282 }
1283}
1284
1285static void setFontVariantFromValue(const QCss::Value &value, QFont *font)
1286{
1287 if (value.type == Value::KnownIdentifier) {
1288 switch (value.variant.toInt()) {
1289 case Value_Normal: font->setCapitalization(QFont::MixedCase); break;
1290 case Value_SmallCaps: font->setCapitalization(QFont::SmallCaps); break;
1291 default: break;
1292 }
1293 }
1294}
1295
1296static void setTextTransformFromValue(const QCss::Value &value, QFont *font)
1297{
1298 if (value.type == Value::KnownIdentifier) {
1299 switch (value.variant.toInt()) {
1300 case Value_None: font->setCapitalization(QFont::MixedCase); break;
1301 case Value_Uppercase: font->setCapitalization(QFont::AllUppercase); break;
1302 case Value_Lowercase: font->setCapitalization(QFont::AllLowercase); break;
1303 default: break;
1304 }
1305 }
1306}
1307
1308bool ValueExtractor::extractFont(QFont *font, int *fontSizeAdjustment)
1309{
1310 if (fontExtracted) {
1311 *font = f;
1312 *fontSizeAdjustment = adjustment;
1313 return fontExtracted == 1;
1314 }
1315
1316 bool hit = false;
1317 for (int i = 0; i < declarations.size(); ++i) {
1318 const Declaration &decl = declarations.at(i);
1319 if (decl.d->values.isEmpty())
1320 continue;
1321 const QCss::Value &val = decl.d->values.at(i: 0);
1322 switch (decl.d->propertyId) {
1323 case FontSize: setFontSizeFromValue(value: val, font, fontSizeAdjustment); break;
1324 case FontStyle: setFontStyleFromValue(value: val, font); break;
1325 case FontWeight: setFontWeightFromValue(value: val, font); break;
1326 case FontFamily: setFontFamilyFromValues(values: decl.d->values, font); break;
1327 case FontKerning: setFontKerningFromValue(value: val, font); break;
1328 case TextDecoration: setTextDecorationFromValues(values: decl.d->values, font); break;
1329 case Font: parseShorthandFontProperty(values: decl.d->values, font, fontSizeAdjustment); break;
1330 case FontVariant: setFontVariantFromValue(value: val, font); break;
1331 case TextTransform: setTextTransformFromValue(value: val, font); break;
1332 case LetterSpacing: setLetterSpacingFromValue(value: val, font); break;
1333 case WordSpacing: setWordSpacingFromValue(value: val, font); break;
1334 default: continue;
1335 }
1336 hit = true;
1337 }
1338
1339 f = *font;
1340 adjustment = *fontSizeAdjustment;
1341 fontExtracted = hit ? 1 : 2;
1342 return hit;
1343}
1344
1345bool ValueExtractor::extractPalette(QBrush *foreground,
1346 QBrush *selectedForeground,
1347 QBrush *selectedBackground,
1348 QBrush *alternateBackground,
1349 QBrush *placeHolderTextForeground,
1350 QBrush *accent)
1351{
1352 bool hit = false;
1353 for (int i = 0; i < declarations.size(); ++i) {
1354 const Declaration &decl = declarations.at(i);
1355 switch (decl.d->propertyId) {
1356 case Color: *foreground = decl.brushValue(pal); break;
1357 case QtSelectionForeground: *selectedForeground = decl.brushValue(pal); break;
1358 case QtSelectionBackground: *selectedBackground = decl.brushValue(pal); break;
1359 case QtAlternateBackground: *alternateBackground = decl.brushValue(pal); break;
1360 case QtPlaceHolderTextColor: *placeHolderTextForeground = decl.brushValue(pal); break;
1361 case QtAccent: *accent = decl.brushValue(pal); break;
1362 default: continue;
1363 }
1364 hit = true;
1365 }
1366 return hit;
1367}
1368
1369void ValueExtractor::extractFont()
1370{
1371 if (fontExtracted)
1372 return;
1373 int dummy = -255;
1374 extractFont(font: &f, fontSizeAdjustment: &dummy);
1375}
1376
1377bool ValueExtractor::extractImage(QIcon *icon, Qt::Alignment *a, QSize *size)
1378{
1379 bool hit = false;
1380 for (int i = 0; i < declarations.size(); ++i) {
1381 const Declaration &decl = declarations.at(i);
1382 switch (decl.d->propertyId) {
1383 case QtImage:
1384 *icon = decl.iconValue();
1385 if (decl.d->values.size() > 0 && decl.d->values.at(i: 0).type == Value::Uri) {
1386 // try to pull just the size from the image...
1387 QImageReader imageReader(decl.d->values.at(i: 0).variant.toString());
1388 if ((*size = imageReader.size()).isNull()) {
1389 // but we'll have to load the whole image if the
1390 // format doesn't support just reading the size
1391 *size = imageReader.read().size();
1392 }
1393 }
1394 break;
1395 case QtImageAlignment: *a = decl.alignmentValue(); break;
1396 default: continue;
1397 }
1398 hit = true;
1399 }
1400 return hit;
1401}
1402
1403bool ValueExtractor::extractIcon(QIcon *icon, QSize *size)
1404{
1405 // Find last declaration that specifies an icon
1406 const auto declaration = std::find_if(
1407 first: declarations.rbegin(), last: declarations.rend(),
1408 pred: [](const Declaration &decl) { return decl.d->propertyId == QtIcon; });
1409 if (declaration == declarations.rend())
1410 return false;
1411
1412 *icon = declaration->iconValue();
1413
1414 // If the value contains a URI, try to get the size of the icon
1415 if (declaration->d->values.isEmpty())
1416 return true;
1417
1418 const auto &propertyValue = declaration->d->values.constFirst();
1419 if (propertyValue.type != Value::Uri)
1420 return true;
1421
1422 // First try to read just the size from the image without loading it
1423 const QString url(propertyValue.variant.toString());
1424 QImageReader imageReader(url);
1425 *size = imageReader.size();
1426 if (!size->isNull())
1427 return true;
1428
1429 // Get the size by loading the image instead
1430 *size = imageReader.read().size();
1431 return true;
1432}
1433
1434///////////////////////////////////////////////////////////////////////////////
1435// Declaration
1436QColor Declaration::colorValue(const QPalette &pal) const
1437{
1438 if (d->values.size() != 1)
1439 return QColor();
1440
1441 if (d->parsed.isValid()) {
1442 switch (d->parsed.typeId()) {
1443 case qMetaTypeId<QColor>():
1444 return qvariant_cast<QColor>(v: d->parsed);
1445 case qMetaTypeId<int>():
1446 return pal.color(cr: (QPalette::ColorRole)(d->parsed.toInt()));
1447 case qMetaTypeId<QList<QVariant>>():
1448 if (d->parsed.toList().size() == 1) {
1449 auto parsedList = d->parsed.toList();
1450 const auto &value = parsedList.at(i: 0);
1451 return qvariant_cast<QColor>(v: value);
1452 }
1453 break;
1454 }
1455 }
1456
1457 ColorData color = parseColorValue(v: d->values.at(i: 0));
1458 if (color.type == ColorData::Role) {
1459 d->parsed = QVariant::fromValue<int>(value: color.role);
1460 return pal.color(cr: (QPalette::ColorRole)(color.role));
1461 } else {
1462 d->parsed = QVariant::fromValue<QColor>(value: color.color);
1463 return color.color;
1464 }
1465}
1466
1467QBrush Declaration::brushValue(const QPalette &pal) const
1468{
1469 if (d->values.size() != 1)
1470 return QBrush();
1471
1472 if (d->parsed.isValid()) {
1473 if (d->parsed.userType() == QMetaType::QBrush)
1474 return qvariant_cast<QBrush>(v: d->parsed);
1475 if (d->parsed.userType() == QMetaType::Int)
1476 return pal.color(cr: (QPalette::ColorRole)(d->parsed.toInt()));
1477 }
1478
1479 BrushData data = parseBrushValue(v: d->values.at(i: 0), pal);
1480
1481 if (data.type == BrushData::Role) {
1482 d->parsed = QVariant::fromValue<int>(value: data.role);
1483 return pal.color(cr: (QPalette::ColorRole)(data.role));
1484 } else {
1485 if (data.type != BrushData::DependsOnThePalette)
1486 d->parsed = QVariant::fromValue<QBrush>(value: data.brush);
1487 return data.brush;
1488 }
1489}
1490
1491void Declaration::brushValues(QBrush *c, const QPalette &pal) const
1492{
1493 int needParse = 0x1f; // bits 0..3 say if we should parse the corresponding value.
1494 // the bit 4 say we need to update d->parsed
1495 int i = 0;
1496 if (d->parsed.isValid()) {
1497 needParse = 0;
1498 Q_ASSERT(d->parsed.metaType() == QMetaType::fromType<QList<QVariant>>());
1499 QList<QVariant> v = d->parsed.toList();
1500 for (i = 0; i < qMin(a: v.size(), b: 4); i++) {
1501 if (v.at(i).userType() == QMetaType::QBrush) {
1502 c[i] = qvariant_cast<QBrush>(v: v.at(i));
1503 } else if (v.at(i).userType() == QMetaType::Int) {
1504 c[i] = pal.color(cr: (QPalette::ColorRole)(v.at(i).toInt()));
1505 } else {
1506 needParse |= (1<<i);
1507 }
1508 }
1509 }
1510 if (needParse != 0) {
1511 QList<QVariant> v;
1512 for (i = 0; i < qMin(a: d->values.size(), b: 4); i++) {
1513 if (!(needParse & (1<<i)))
1514 continue;
1515 BrushData data = parseBrushValue(v: d->values.at(i), pal);
1516 if (data.type == BrushData::Role) {
1517 v += QVariant::fromValue<int>(value: data.role);
1518 c[i] = pal.color(cr: (QPalette::ColorRole)(data.role));
1519 } else {
1520 if (data.type != BrushData::DependsOnThePalette) {
1521 v += QVariant::fromValue<QBrush>(value: data.brush);
1522 } else {
1523 v += QVariant();
1524 }
1525 c[i] = data.brush;
1526 }
1527 }
1528 if (needParse & 0x10)
1529 d->parsed = v;
1530 }
1531 if (i == 0) c[0] = c[1] = c[2] = c[3] = QBrush();
1532 else if (i == 1) c[3] = c[2] = c[1] = c[0];
1533 else if (i == 2) c[2] = c[0], c[3] = c[1];
1534 else if (i == 3) c[3] = c[1];
1535}
1536
1537bool Declaration::realValue(qreal *real, const char *unit) const
1538{
1539 if (d->values.size() != 1)
1540 return false;
1541 const Value &v = d->values.at(i: 0);
1542 if (unit && v.type != Value::Length)
1543 return false;
1544 const QString str = v.variant.toString();
1545 QStringView s(str);
1546 if (unit) {
1547 const QLatin1StringView unitStr(unit);
1548 if (!s.endsWith(s: unitStr, cs: Qt::CaseInsensitive))
1549 return false;
1550 s.chop(n: unitStr.size());
1551 }
1552 bool ok = false;
1553 qreal val = s.toDouble(ok: &ok);
1554 if (ok)
1555 *real = val;
1556 return ok;
1557}
1558
1559static bool intValueHelper(const QCss::Value &v, int *i, const char *unit)
1560{
1561 if (unit && v.type != Value::Length)
1562 return false;
1563 const QString str = v.variant.toString();
1564 QStringView s(str);
1565 if (unit) {
1566 const QLatin1StringView unitStr(unit);
1567 if (!s.endsWith(s: unitStr, cs: Qt::CaseInsensitive))
1568 return false;
1569 s.chop(n: unitStr.size());
1570 }
1571 bool ok = false;
1572 int val = s.toInt(ok: &ok);
1573 if (ok)
1574 *i = val;
1575 return ok;
1576}
1577
1578bool Declaration::intValue(int *i, const char *unit) const
1579{
1580 if (d->values.size() != 1)
1581 return false;
1582 return intValueHelper(v: d->values.at(i: 0), i, unit);
1583}
1584
1585QSize Declaration::sizeValue() const
1586{
1587 if (d->parsed.isValid())
1588 return qvariant_cast<QSize>(v: d->parsed);
1589
1590 int x[2] = { 0, 0 };
1591 const int count = d->values.size();
1592 for (int i = 0; i < count; ++i) {
1593 if (i > 1) {
1594 qWarning(msg: "QCssParser::sizeValue: Too many values provided");
1595 break;
1596 }
1597 const auto &value = d->values.at(i);
1598 const QString valueString = value.variant.toString();
1599 if (valueString.endsWith(s: u"pt", cs: Qt::CaseInsensitive)) {
1600 intValueHelper(v: value, i: &x[i], unit: "pt");
1601 // according to https://www.w3.org/TR/css3-values/#absolute-lengths
1602 // 1pt = 1/72th of 1 inch, and 1px = 1/96th of 1 inch
1603 x[i] = (x[i] * 72) / 96;
1604 } else {
1605 // by default we use 'px'
1606 intValueHelper(v: value, i: &x[i], unit: "px");
1607 }
1608 }
1609 if (count == 1)
1610 x[1] = x[0];
1611 QSize size(x[0], x[1]);
1612 d->parsed = QVariant::fromValue<QSize>(value: size);
1613 return size;
1614}
1615
1616QRect Declaration::rectValue() const
1617{
1618 if (d->values.size() != 1)
1619 return QRect();
1620
1621 if (d->parsed.isValid())
1622 return qvariant_cast<QRect>(v: d->parsed);
1623
1624 const QCss::Value &v = d->values.at(i: 0);
1625 if (v.type != Value::Function)
1626 return QRect();
1627 const QStringList func = v.variant.toStringList();
1628 if (func.size() != 2 || func.at(i: 0).compare(other: "rect"_L1) != 0)
1629 return QRect();
1630 const auto args = QStringView{func[1]}.split(sep: u' ', behavior: Qt::SkipEmptyParts);
1631 if (args.size() != 4)
1632 return QRect();
1633 QRect rect(args[0].toInt(), args[1].toInt(), args[2].toInt(), args[3].toInt());
1634 d->parsed = QVariant::fromValue<QRect>(value: rect);
1635 return rect;
1636}
1637
1638void Declaration::colorValues(QColor *c, const QPalette &pal) const
1639{
1640 int i;
1641 if (d->parsed.isValid()) {
1642 QList<QVariant> v = d->parsed.toList();
1643 for (i = 0; i < qMin(a: d->values.size(), b: 4); i++) {
1644 if (v.at(i).userType() == QMetaType::QColor) {
1645 c[i] = qvariant_cast<QColor>(v: v.at(i));
1646 } else {
1647 c[i] = pal.color(cr: (QPalette::ColorRole)(v.at(i).toInt()));
1648 }
1649 }
1650 } else {
1651 QList<QVariant> v;
1652 for (i = 0; i < qMin(a: d->values.size(), b: 4); i++) {
1653 ColorData color = parseColorValue(v: d->values.at(i));
1654 if (color.type == ColorData::Role) {
1655 v += QVariant::fromValue<int>(value: color.role);
1656 c[i] = pal.color(cr: (QPalette::ColorRole)(color.role));
1657 } else {
1658 v += QVariant::fromValue<QColor>(value: color.color);
1659 c[i] = color.color;
1660 }
1661 }
1662 d->parsed = v;
1663 }
1664
1665 if (i == 0) c[0] = c[1] = c[2] = c[3] = QColor();
1666 else if (i == 1) c[3] = c[2] = c[1] = c[0];
1667 else if (i == 2) c[2] = c[0], c[3] = c[1];
1668 else if (i == 3) c[3] = c[1];
1669}
1670
1671BorderStyle Declaration::styleValue() const
1672{
1673 if (d->values.size() != 1)
1674 return BorderStyle_None;
1675 return parseStyleValue(v: d->values.at(i: 0));
1676}
1677
1678void Declaration::styleValues(BorderStyle *s) const
1679{
1680 int i;
1681 for (i = 0; i < qMin(a: d->values.size(), b: 4); i++)
1682 s[i] = parseStyleValue(v: d->values.at(i));
1683 if (i == 0) s[0] = s[1] = s[2] = s[3] = BorderStyle_None;
1684 else if (i == 1) s[3] = s[2] = s[1] = s[0];
1685 else if (i == 2) s[2] = s[0], s[3] = s[1];
1686 else if (i == 3) s[3] = s[1];
1687}
1688
1689Repeat Declaration::repeatValue() const
1690{
1691 if (d->parsed.isValid())
1692 return static_cast<Repeat>(d->parsed.toInt());
1693 if (d->values.size() != 1)
1694 return Repeat_Unknown;
1695 int v = findKnownValue(name: d->values.at(i: 0).variant.toString(),
1696 start: repeats, numValues: NumKnownRepeats);
1697 d->parsed = v;
1698 return static_cast<Repeat>(v);
1699}
1700
1701Origin Declaration::originValue() const
1702{
1703 if (d->parsed.isValid())
1704 return static_cast<Origin>(d->parsed.toInt());
1705 if (d->values.size() != 1)
1706 return Origin_Unknown;
1707 int v = findKnownValue(name: d->values.at(i: 0).variant.toString(),
1708 start: origins, numValues: NumKnownOrigins);
1709 d->parsed = v;
1710 return static_cast<Origin>(v);
1711}
1712
1713PositionMode Declaration::positionValue() const
1714{
1715 if (d->parsed.isValid())
1716 return static_cast<PositionMode>(d->parsed.toInt());
1717 if (d->values.size() != 1)
1718 return PositionMode_Unknown;
1719 int v = findKnownValue(name: d->values.at(i: 0).variant.toString(),
1720 start: positions, numValues: NumKnownPositionModes);
1721 d->parsed = v;
1722 return static_cast<PositionMode>(v);
1723}
1724
1725Attachment Declaration::attachmentValue() const
1726{
1727 if (d->parsed.isValid())
1728 return static_cast<Attachment>(d->parsed.toInt());
1729 if (d->values.size() != 1)
1730 return Attachment_Unknown;
1731 int v = findKnownValue(name: d->values.at(i: 0).variant.toString(),
1732 start: attachments, numValues: NumKnownAttachments);
1733 d->parsed = v;
1734 return static_cast<Attachment>(v);
1735}
1736
1737int Declaration::styleFeaturesValue() const
1738{
1739 Q_ASSERT(d->propertyId == QtStyleFeatures);
1740 if (d->parsed.isValid())
1741 return d->parsed.toInt();
1742 int features = StyleFeature_None;
1743 for (int i = 0; i < d->values.size(); i++) {
1744 features |= static_cast<int>(findKnownValue(name: d->values.value(i).variant.toString(),
1745 start: styleFeatures, numValues: NumKnownStyleFeatures));
1746 }
1747 d->parsed = features;
1748 return features;
1749}
1750
1751QString Declaration::uriValue() const
1752{
1753 if (d->values.isEmpty() || d->values.at(i: 0).type != Value::Uri)
1754 return QString();
1755 return d->values.at(i: 0).variant.toString();
1756}
1757
1758Qt::Alignment Declaration::alignmentValue() const
1759{
1760 if (d->parsed.isValid())
1761 return Qt::Alignment(d->parsed.toInt());
1762 if (d->values.isEmpty() || d->values.size() > 2)
1763 return Qt::AlignLeft | Qt::AlignTop;
1764
1765 Qt::Alignment v = parseAlignment(values: d->values.constData(), count: d->values.size());
1766 d->parsed = int(v);
1767 return v;
1768}
1769
1770void Declaration::borderImageValue(QString *image, int *cuts,
1771 TileMode *h, TileMode *v) const
1772{
1773 const DeclarationData *d = this->d.data(); // make it const and shadow d
1774 *image = uriValue();
1775 for (int i = 0; i < 4; i++)
1776 cuts[i] = -1;
1777 *h = *v = TileMode_Stretch;
1778
1779 if (d->values.size() < 2)
1780 return;
1781
1782 if (d->values.at(i: 1).type == Value::Number) { // cuts!
1783 int i;
1784 for (i = 0; i < qMin(a: d->values.size()-1, b: 4); i++) {
1785 const Value& v = d->values.at(i: i+1);
1786 if (v.type != Value::Number)
1787 break;
1788 cuts[i] = v.variant.toString().toInt();
1789 }
1790 if (i == 0) cuts[0] = cuts[1] = cuts[2] = cuts[3] = 0;
1791 else if (i == 1) cuts[3] = cuts[2] = cuts[1] = cuts[0];
1792 else if (i == 2) cuts[2] = cuts[0], cuts[3] = cuts[1];
1793 else if (i == 3) cuts[3] = cuts[1];
1794 }
1795
1796 if (d->values.last().type == Value::Identifier) {
1797 *v = static_cast<TileMode>(findKnownValue(name: d->values.last().variant.toString(),
1798 start: tileModes, numValues: NumKnownTileModes));
1799 }
1800 if (d->values[d->values.size() - 2].type == Value::Identifier) {
1801 *h = static_cast<TileMode>
1802 (findKnownValue(name: d->values[d->values.size()-2].variant.toString(),
1803 start: tileModes, numValues: NumKnownTileModes));
1804 } else
1805 *h = *v;
1806}
1807
1808bool Declaration::borderCollapseValue() const
1809{
1810 if (d->values.size() != 1)
1811 return false;
1812 else
1813 return d->values.at(i: 0).toString() == "collapse"_L1;
1814}
1815
1816QIcon Declaration::iconValue() const
1817{
1818 if (d->parsed.isValid())
1819 return qvariant_cast<QIcon>(v: d->parsed);
1820
1821 QIcon icon;
1822 for (int i = 0; i < d->values.size();) {
1823 const Value &value = d->values.at(i: i++);
1824 if (value.type != Value::Uri)
1825 break;
1826 QString uri = value.variant.toString();
1827 QIcon::Mode mode = QIcon::Normal;
1828 QIcon::State state = QIcon::Off;
1829 for (int j = 0; j < 2; j++) {
1830 if (i != d->values.size() && d->values.at(i).type == Value::KnownIdentifier) {
1831 switch (d->values.at(i).variant.toInt()) {
1832 case Value_Disabled: mode = QIcon::Disabled; break;
1833 case Value_Active: mode = QIcon::Active; break;
1834 case Value_Selected: mode = QIcon::Selected; break;
1835 case Value_Normal: mode = QIcon::Normal; break;
1836 case Value_On: state = QIcon::On; break;
1837 case Value_Off: state = QIcon::Off; break;
1838 default: break;
1839 }
1840 ++i;
1841 } else {
1842 break;
1843 }
1844 }
1845
1846 // QIcon is soo broken
1847 if (icon.isNull())
1848 icon = QIcon(uri);
1849 else
1850 icon.addPixmap(pixmap: uri, mode, state);
1851
1852 if (i == d->values.size())
1853 break;
1854
1855 if (d->values.at(i).type == Value::TermOperatorComma)
1856 i++;
1857 }
1858
1859 d->parsed = QVariant::fromValue<QIcon>(value: icon);
1860 return icon;
1861}
1862
1863///////////////////////////////////////////////////////////////////////////////
1864// Selector
1865int Selector::specificity() const
1866{
1867 int val = 0;
1868 for (int i = 0; i < basicSelectors.size(); ++i) {
1869 const BasicSelector &sel = basicSelectors.at(i);
1870 if (!sel.elementName.isEmpty())
1871 val += 1;
1872
1873 val += (sel.pseudos.size() + sel.attributeSelectors.size()) * 0x10;
1874 val += sel.ids.size() * 0x100;
1875 }
1876 return val;
1877}
1878
1879QString Selector::pseudoElement() const
1880{
1881 const BasicSelector& bs = basicSelectors.last();
1882 if (!bs.pseudos.isEmpty() && bs.pseudos.at(i: 0).type == PseudoClass_Unknown)
1883 return bs.pseudos.at(i: 0).name;
1884 return QString();
1885}
1886
1887quint64 Selector::pseudoClass(quint64 *negated) const
1888{
1889 const BasicSelector& bs = basicSelectors.last();
1890 if (bs.pseudos.isEmpty())
1891 return PseudoClass_Unspecified;
1892 quint64 pc = PseudoClass_Unknown;
1893 for (int i = !pseudoElement().isEmpty(); i < bs.pseudos.size(); i++) {
1894 const Pseudo &pseudo = bs.pseudos.at(i);
1895 if (pseudo.type == PseudoClass_Unknown)
1896 return PseudoClass_Unknown;
1897 if (!pseudo.negated)
1898 pc |= pseudo.type;
1899 else if (negated)
1900 *negated |= pseudo.type;
1901 }
1902 return pc;
1903}
1904
1905///////////////////////////////////////////////////////////////////////////////
1906// StyleSheet
1907void StyleSheet::buildIndexes(Qt::CaseSensitivity nameCaseSensitivity)
1908{
1909 QList<StyleRule> universals;
1910 for (int i = 0; i < styleRules.size(); ++i) {
1911 const StyleRule &rule = styleRules.at(i);
1912 QList<Selector> universalsSelectors;
1913 for (int j = 0; j < rule.selectors.size(); ++j) {
1914 const Selector& selector = rule.selectors.at(i: j);
1915
1916 if (selector.basicSelectors.isEmpty())
1917 continue;
1918
1919 if (selector.basicSelectors.at(i: 0).relationToNext == BasicSelector::NoRelation) {
1920 if (selector.basicSelectors.size() != 1)
1921 continue;
1922 } else if (selector.basicSelectors.size() <= 1) {
1923 continue;
1924 }
1925
1926 const BasicSelector &sel = selector.basicSelectors.at(i: selector.basicSelectors.size() - 1);
1927
1928 if (!sel.ids.isEmpty()) {
1929 StyleRule nr;
1930 nr.selectors += selector;
1931 nr.declarations = rule.declarations;
1932 nr.order = i;
1933 idIndex.insert(key: sel.ids.at(i: 0), value: nr);
1934 } else if (!sel.elementName.isEmpty()) {
1935 StyleRule nr;
1936 nr.selectors += selector;
1937 nr.declarations = rule.declarations;
1938 nr.order = i;
1939 QString name = sel.elementName;
1940 if (nameCaseSensitivity == Qt::CaseInsensitive)
1941 name = std::move(name).toLower();
1942 nameIndex.insert(key: name, value: nr);
1943 } else {
1944 universalsSelectors += selector;
1945 }
1946 }
1947 if (!universalsSelectors.isEmpty()) {
1948 StyleRule nr;
1949 nr.selectors = universalsSelectors;
1950 nr.declarations = rule.declarations;
1951 nr.order = i;
1952 universals << nr;
1953 }
1954 }
1955 styleRules = universals;
1956}
1957
1958///////////////////////////////////////////////////////////////////////////////
1959// StyleSelector
1960StyleSelector::~StyleSelector()
1961{
1962}
1963
1964bool StyleSelector::nodeNameEquals(NodePtr node, const QString& nodeName) const
1965{
1966 return nodeNames(node).contains(str: nodeName, cs: nameCaseSensitivity);
1967}
1968
1969QStringList StyleSelector::nodeIds(NodePtr node) const
1970{
1971 return QStringList(attributeValue(node, aSelector: QCss::AttributeSelector{.name: "id"_L1, .value: {}, .valueMatchCriterium: AttributeSelector::NoMatch}));
1972}
1973
1974bool StyleSelector::selectorMatches(const Selector &selector, NodePtr node)
1975{
1976 if (selector.basicSelectors.isEmpty())
1977 return false;
1978
1979 if (selector.basicSelectors.at(i: 0).relationToNext == BasicSelector::NoRelation) {
1980 if (selector.basicSelectors.size() != 1)
1981 return false;
1982 return basicSelectorMatches(rule: selector.basicSelectors.at(i: 0), node);
1983 }
1984 if (selector.basicSelectors.size() <= 1)
1985 return false;
1986
1987 int i = selector.basicSelectors.size() - 1;
1988 node = duplicateNode(node);
1989 bool match = true;
1990
1991 BasicSelector sel = selector.basicSelectors.at(i);
1992 do {
1993 match = basicSelectorMatches(rule: sel, node);
1994 if (!match) {
1995 if (i == selector.basicSelectors.size() - 1) // first element must always match!
1996 break;
1997 if (sel.relationToNext != BasicSelector::MatchNextSelectorIfAncestor &&
1998 sel.relationToNext != BasicSelector::MatchNextSelectorIfIndirectAdjecent)
1999 break;
2000 }
2001
2002 if (match || (sel.relationToNext != BasicSelector::MatchNextSelectorIfAncestor &&
2003 sel.relationToNext != BasicSelector::MatchNextSelectorIfIndirectAdjecent))
2004 --i;
2005
2006 if (i < 0)
2007 break;
2008
2009 sel = selector.basicSelectors.at(i);
2010 if (sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor
2011 || sel.relationToNext == BasicSelector::MatchNextSelectorIfParent) {
2012
2013 NodePtr nextParent = parentNode(node);
2014 freeNode(node);
2015 node = nextParent;
2016 } else if (sel.relationToNext == BasicSelector::MatchNextSelectorIfDirectAdjecent
2017 || sel.relationToNext == BasicSelector::MatchNextSelectorIfIndirectAdjecent) {
2018 NodePtr previousSibling = previousSiblingNode(node);
2019 freeNode(node);
2020 node = previousSibling;
2021 }
2022 if (isNullNode(node)) {
2023 match = false;
2024 break;
2025 }
2026 } while (i >= 0 && (match || sel.relationToNext == BasicSelector::MatchNextSelectorIfAncestor
2027 || sel.relationToNext == BasicSelector::MatchNextSelectorIfIndirectAdjecent));
2028
2029 freeNode(node);
2030
2031 return match;
2032}
2033
2034bool StyleSelector::basicSelectorMatches(const BasicSelector &sel, NodePtr node)
2035{
2036 if (!sel.attributeSelectors.isEmpty()) {
2037 if (!hasAttributes(node))
2038 return false;
2039
2040 for (int i = 0; i < sel.attributeSelectors.size(); ++i) {
2041 const QCss::AttributeSelector &a = sel.attributeSelectors.at(i);
2042
2043 const QString attrValue = attributeValue(node, aSelector: a);
2044 if (attrValue.isNull())
2045 return false;
2046
2047 switch (a.valueMatchCriterium) {
2048 case QCss::AttributeSelector::NoMatch:
2049 break;
2050 case QCss::AttributeSelector::MatchEqual:
2051 if (attrValue != a.value)
2052 return false;
2053 break;
2054 case QCss::AttributeSelector::MatchIncludes: {
2055 const auto lst = QStringView{attrValue}.tokenize(needle: u' ');
2056 bool found = false;
2057 for (auto s : lst) {
2058 if (s == a.value) {
2059 found = true;
2060 break;
2061 }
2062 }
2063 if (!found)
2064 return false;
2065 break;
2066 }
2067 case QCss::AttributeSelector::MatchDashMatch: {
2068 const QString dashPrefix = a.value + u'-';
2069 if (attrValue != a.value && !attrValue.startsWith(s: dashPrefix))
2070 return false;
2071 break;
2072 }
2073 case QCss::AttributeSelector::MatchBeginsWith:
2074 if (!attrValue.startsWith(s: a.value))
2075 return false;
2076 break;
2077 case QCss::AttributeSelector::MatchEndsWith:
2078 if (!attrValue.endsWith(s: a.value))
2079 return false;
2080 break;
2081 case QCss::AttributeSelector::MatchContains:
2082 if (!attrValue.contains(s: a.value))
2083 return false;
2084 break;
2085 }
2086 }
2087 }
2088
2089 if (!sel.elementName.isEmpty()
2090 && !nodeNameEquals(node, nodeName: sel.elementName))
2091 return false;
2092
2093 if (!sel.ids.isEmpty()
2094 && sel.ids != nodeIds(node))
2095 return false;
2096
2097 return true;
2098}
2099
2100void StyleSelector::matchRule(NodePtr node, const StyleRule &rule, StyleSheetOrigin origin,
2101 int depth, QMultiMap<uint, StyleRule> *weightedRules)
2102{
2103 for (int j = 0; j < rule.selectors.size(); ++j) {
2104 const Selector& selector = rule.selectors.at(i: j);
2105 if (selectorMatches(selector, node)) {
2106 uint weight = rule.order
2107 + selector.specificity() *0x100
2108 + (uint(origin) + depth)*0x100000;
2109 StyleRule newRule = rule;
2110 if (rule.selectors.size() > 1) {
2111 newRule.selectors.resize(size: 1);
2112 newRule.selectors[0] = selector;
2113 }
2114 //We might have rules with the same weight if they came from a rule with several selectors
2115 weightedRules->insert(key: weight, value: newRule);
2116 }
2117 }
2118}
2119
2120// Returns style rules that are in ascending order of specificity
2121// Each of the StyleRule returned will contain exactly one Selector
2122QList<StyleRule> StyleSelector::styleRulesForNode(NodePtr node)
2123{
2124 QList<StyleRule> rules;
2125 if (styleSheets.isEmpty())
2126 return rules;
2127
2128 QMultiMap<uint, StyleRule> weightedRules; // (spec, rule) that will be sorted below
2129
2130 //prune using indexed stylesheet
2131 for (int sheetIdx = 0; sheetIdx < styleSheets.size(); ++sheetIdx) {
2132 const StyleSheet &styleSheet = styleSheets.at(i: sheetIdx);
2133 for (int i = 0; i < styleSheet.styleRules.size(); ++i) {
2134 matchRule(node, rule: styleSheet.styleRules.at(i), origin: styleSheet.origin, depth: styleSheet.depth, weightedRules: &weightedRules);
2135 }
2136
2137 if (!styleSheet.idIndex.isEmpty()) {
2138 QStringList ids = nodeIds(node);
2139 for (int i = 0; i < ids.size(); i++) {
2140 const QString &key = ids.at(i);
2141 QMultiHash<QString, StyleRule>::const_iterator it = styleSheet.idIndex.constFind(key);
2142 while (it != styleSheet.idIndex.constEnd() && it.key() == key) {
2143 matchRule(node, rule: it.value(), origin: styleSheet.origin, depth: styleSheet.depth, weightedRules: &weightedRules);
2144 ++it;
2145 }
2146 }
2147 }
2148 if (!styleSheet.nameIndex.isEmpty()) {
2149 QStringList names = nodeNames(node);
2150 for (int i = 0; i < names.size(); i++) {
2151 QString name = names.at(i);
2152 if (nameCaseSensitivity == Qt::CaseInsensitive)
2153 name = std::move(name).toLower();
2154 QMultiHash<QString, StyleRule>::const_iterator it = styleSheet.nameIndex.constFind(key: name);
2155 while (it != styleSheet.nameIndex.constEnd() && it.key() == name) {
2156 matchRule(node, rule: it.value(), origin: styleSheet.origin, depth: styleSheet.depth, weightedRules: &weightedRules);
2157 ++it;
2158 }
2159 }
2160 }
2161 if (!medium.isEmpty()) {
2162 for (int i = 0; i < styleSheet.mediaRules.size(); ++i) {
2163 if (styleSheet.mediaRules.at(i).media.contains(str: medium, cs: Qt::CaseInsensitive)) {
2164 for (int j = 0; j < styleSheet.mediaRules.at(i).styleRules.size(); ++j) {
2165 matchRule(node, rule: styleSheet.mediaRules.at(i).styleRules.at(i: j), origin: styleSheet.origin,
2166 depth: styleSheet.depth, weightedRules: &weightedRules);
2167 }
2168 }
2169 }
2170 }
2171 }
2172
2173 rules.reserve(asize: weightedRules.size());
2174 QMultiMap<uint, StyleRule>::const_iterator it = weightedRules.constBegin();
2175 for ( ; it != weightedRules.constEnd() ; ++it)
2176 rules += *it;
2177
2178 return rules;
2179}
2180
2181// for qtexthtmlparser which requires just the declarations with Enabled state
2182// and without pseudo elements
2183QList<Declaration> StyleSelector::declarationsForNode(NodePtr node, const char *extraPseudo)
2184{
2185 QList<Declaration> decls;
2186 QList<StyleRule> rules = styleRulesForNode(node);
2187 for (int i = 0; i < rules.size(); i++) {
2188 const Selector& selector = rules.at(i).selectors.at(i: 0);
2189 const QString pseudoElement = selector.pseudoElement();
2190
2191 if (extraPseudo && pseudoElement == QLatin1StringView(extraPseudo)) {
2192 decls += rules.at(i).declarations;
2193 continue;
2194 }
2195
2196 if (!pseudoElement.isEmpty()) // skip rules with pseudo elements
2197 continue;
2198 quint64 pseudoClass = selector.pseudoClass();
2199 if (pseudoClass == PseudoClass_Enabled || pseudoClass == PseudoClass_Unspecified)
2200 decls += rules.at(i).declarations;
2201 }
2202 return decls;
2203}
2204
2205static inline bool isHexDigit(const char c)
2206{
2207 return (c >= '0' && c <= '9')
2208 || (c >= 'a' && c <= 'f')
2209 || (c >= 'A' && c <= 'F')
2210 ;
2211}
2212
2213QString Scanner::preprocess(const QString &input, bool *hasEscapeSequences)
2214{
2215 QString output = input;
2216
2217 if (hasEscapeSequences)
2218 *hasEscapeSequences = false;
2219
2220 int i = 0;
2221 while (i < output.size()) {
2222 if (output.at(i) == u'\\') {
2223
2224 ++i;
2225 // test for unicode hex escape
2226 int hexCount = 0;
2227 const int hexStart = i;
2228 while (i < output.size()
2229 && isHexDigit(c: output.at(i).toLatin1())
2230 && hexCount < 7) {
2231 ++hexCount;
2232 ++i;
2233 }
2234 if (hexCount == 0) {
2235 if (hasEscapeSequences)
2236 *hasEscapeSequences = true;
2237 continue;
2238 }
2239
2240 hexCount = qMin(a: hexCount, b: 6);
2241 bool ok = false;
2242 const char16_t code = QStringView{output}.mid(pos: hexStart, n: hexCount).toUShort(ok: &ok, base: 16);
2243 if (ok) {
2244 output.replace(i: hexStart - 1, len: hexCount + 1, after: code);
2245 i = hexStart;
2246 } else {
2247 i = hexStart;
2248 }
2249 } else {
2250 ++i;
2251 }
2252 }
2253 return output;
2254}
2255
2256int QCssScanner_Generated::handleCommentStart()
2257{
2258 while (pos < input.size() - 1) {
2259 if (input.at(i: pos) == u'*' && input.at(i: pos + 1) == u'/') {
2260 pos += 2;
2261 break;
2262 }
2263 ++pos;
2264 }
2265 return S;
2266}
2267
2268void Scanner::scan(const QString &preprocessedInput, QList<Symbol> *symbols)
2269{
2270 QCssScanner_Generated scanner(preprocessedInput);
2271 Symbol sym;
2272 int tok = scanner.lex();
2273 while (tok != -1) {
2274 sym.token = static_cast<QCss::TokenType>(tok);
2275 sym.text = scanner.input;
2276 sym.start = scanner.lexemStart;
2277 sym.len = scanner.lexemLength;
2278 symbols->append(t: sym);
2279 tok = scanner.lex();
2280 }
2281}
2282
2283QString Symbol::lexem() const
2284{
2285 QString result;
2286 if (len > 0)
2287 result.reserve(asize: len);
2288 for (int i = 0; i < len; ++i) {
2289 if (text.at(i: start + i) == u'\\' && i < len - 1)
2290 ++i;
2291 result += text.at(i: start + i);
2292 }
2293 return result;
2294}
2295
2296Parser::Parser(const QString &css, bool isFile)
2297{
2298 init(css, file: isFile);
2299}
2300
2301Parser::Parser()
2302{
2303 index = 0;
2304 errorIndex = -1;
2305 hasEscapeSequences = false;
2306}
2307
2308void Parser::init(const QString &css, bool isFile)
2309{
2310 QString styleSheet = css;
2311 if (isFile) {
2312 QFile file(css);
2313 if (file.open(flags: QFile::ReadOnly)) {
2314 sourcePath = QFileInfo(styleSheet).absolutePath() + u'/';
2315 QTextStream stream(&file);
2316 styleSheet = stream.readAll();
2317 } else {
2318 qWarning() << "QCss::Parser - Failed to load file " << css;
2319 styleSheet.clear();
2320 }
2321 } else {
2322 sourcePath.clear();
2323 }
2324
2325 hasEscapeSequences = false;
2326 symbols.clear();
2327 symbols.reserve(asize: 8);
2328 Scanner::scan(preprocessedInput: Scanner::preprocess(input: styleSheet, hasEscapeSequences: &hasEscapeSequences), symbols: &symbols);
2329 index = 0;
2330 errorIndex = -1;
2331}
2332
2333bool Parser::parse(StyleSheet *styleSheet, Qt::CaseSensitivity nameCaseSensitivity)
2334{
2335 if (testTokenAndEndsWith(t: ATKEYWORD_SYM, str: "charset"_L1)) {
2336 while (test(t: S) || test(t: CDO) || test(t: CDC)) {}
2337 if (!next(t: STRING)) return false;
2338 if (!next(t: SEMICOLON)) return false;
2339 }
2340
2341 while (test(t: S) || test(t: CDO) || test(t: CDC)) {}
2342
2343 while (testImport()) {
2344 ImportRule rule;
2345 if (!parseImport(importRule: &rule)) return false;
2346 styleSheet->importRules.append(t: rule);
2347 while (test(t: S) || test(t: CDO) || test(t: CDC)) {}
2348 }
2349
2350 do {
2351 if (testMedia()) {
2352 MediaRule rule;
2353 if (!parseMedia(mediaRule: &rule)) return false;
2354 styleSheet->mediaRules.append(t: rule);
2355 } else if (testPage()) {
2356 PageRule rule;
2357 if (!parsePage(pageRule: &rule)) return false;
2358 styleSheet->pageRules.append(t: rule);
2359 } else if (testRuleset()) {
2360 StyleRule rule;
2361 if (!parseRuleset(styleRule: &rule)) return false;
2362 styleSheet->styleRules.append(t: rule);
2363 } else if (test(t: ATKEYWORD_SYM)) {
2364 if (!until(target: RBRACE)) return false;
2365 } else if (hasNext()) {
2366 return false;
2367 }
2368 while (test(t: S) || test(t: CDO) || test(t: CDC)) {}
2369 } while (hasNext());
2370 styleSheet->buildIndexes(nameCaseSensitivity);
2371 return true;
2372}
2373
2374Symbol Parser::errorSymbol()
2375{
2376 if (errorIndex == -1) return Symbol();
2377 return symbols.at(i: errorIndex);
2378}
2379
2380static inline void removeOptionalQuotes(QString *str)
2381{
2382 if (!str->startsWith(c: u'\'') && !str->startsWith(c: u'\"'))
2383 return;
2384 str->remove(i: 0, len: 1);
2385 str->chop(n: 1);
2386}
2387
2388bool Parser::parseImport(ImportRule *importRule)
2389{
2390 skipSpace();
2391
2392 if (test(t: STRING)) {
2393 importRule->href = lexem();
2394 } else {
2395 if (!testAndParseUri(uri: &importRule->href)) return false;
2396 }
2397 removeOptionalQuotes(str: &importRule->href);
2398
2399 skipSpace();
2400
2401 if (testMedium()) {
2402 if (!parseMedium(media: &importRule->media)) return false;
2403
2404 while (test(t: COMMA)) {
2405 skipSpace();
2406 if (!parseNextMedium(media: &importRule->media)) return false;
2407 }
2408 }
2409
2410 if (!next(t: SEMICOLON)) return false;
2411
2412 skipSpace();
2413 return true;
2414}
2415
2416bool Parser::parseMedia(MediaRule *mediaRule)
2417{
2418 do {
2419 skipSpace();
2420 if (!parseNextMedium(media: &mediaRule->media)) return false;
2421 } while (test(t: COMMA));
2422
2423 if (!next(t: LBRACE)) return false;
2424 skipSpace();
2425
2426 while (testRuleset()) {
2427 StyleRule rule;
2428 if (!parseRuleset(styleRule: &rule)) return false;
2429 mediaRule->styleRules.append(t: rule);
2430 }
2431
2432 if (!next(t: RBRACE)) return false;
2433 skipSpace();
2434 return true;
2435}
2436
2437bool Parser::parseMedium(QStringList *media)
2438{
2439 media->append(t: lexem());
2440 skipSpace();
2441 return true;
2442}
2443
2444bool Parser::parsePage(PageRule *pageRule)
2445{
2446 skipSpace();
2447
2448 if (testPseudoPage())
2449 if (!parsePseudoPage(selector: &pageRule->selector)) return false;
2450
2451 skipSpace();
2452 if (!next(t: LBRACE)) return false;
2453
2454 do {
2455 skipSpace();
2456 Declaration decl;
2457 if (!parseNextDeclaration(declaration: &decl)) return false;
2458 if (!decl.isEmpty())
2459 pageRule->declarations.append(t: decl);
2460 } while (test(t: SEMICOLON));
2461
2462 if (!next(t: RBRACE)) return false;
2463 skipSpace();
2464 return true;
2465}
2466
2467bool Parser::parsePseudoPage(QString *selector)
2468{
2469 if (!next(t: IDENT)) return false;
2470 *selector = lexem();
2471 return true;
2472}
2473
2474bool Parser::parseNextOperator(Value *value)
2475{
2476 if (!hasNext()) return true;
2477 switch (next()) {
2478 case SLASH: value->type = Value::TermOperatorSlash; skipSpace(); break;
2479 case COMMA: value->type = Value::TermOperatorComma; skipSpace(); break;
2480 default: prev(); break;
2481 }
2482 return true;
2483}
2484
2485bool Parser::parseCombinator(BasicSelector::Relation *relation)
2486{
2487 *relation = BasicSelector::NoRelation;
2488 if (lookup() == S) {
2489 *relation = BasicSelector::MatchNextSelectorIfAncestor;
2490 skipSpace();
2491 } else {
2492 prev();
2493 }
2494 if (test(t: PLUS)) {
2495 *relation = BasicSelector::MatchNextSelectorIfDirectAdjecent;
2496 } else if (test(t: GREATER)) {
2497 *relation = BasicSelector::MatchNextSelectorIfParent;
2498 } else if (test(t: TILDE)) {
2499 *relation = BasicSelector::MatchNextSelectorIfIndirectAdjecent;
2500 }
2501 skipSpace();
2502 return true;
2503}
2504
2505bool Parser::parseProperty(Declaration *decl)
2506{
2507 decl->d->property = lexem();
2508 decl->d->propertyId = static_cast<Property>(findKnownValue(name: decl->d->property, start: properties, numValues: NumProperties));
2509 decl->d->inheritable = isInheritable(propertyId: decl->d->propertyId);
2510 skipSpace();
2511 return true;
2512}
2513
2514bool Parser::parseRuleset(StyleRule *styleRule)
2515{
2516 Selector sel;
2517 if (!parseSelector(sel: &sel)) return false;
2518 styleRule->selectors.append(t: sel);
2519
2520 while (test(t: COMMA)) {
2521 skipSpace();
2522 Selector sel;
2523 if (!parseNextSelector(sel: &sel)) return false;
2524 styleRule->selectors.append(t: sel);
2525 }
2526
2527 skipSpace();
2528 if (!next(t: LBRACE)) return false;
2529 const int declarationStart = index;
2530
2531 do {
2532 skipSpace();
2533 Declaration decl;
2534 const int rewind = index;
2535 if (!parseNextDeclaration(declaration: &decl)) {
2536 index = rewind;
2537 const bool foundSemicolon = until(target: SEMICOLON);
2538 const int semicolonIndex = index;
2539
2540 index = declarationStart;
2541 const bool foundRBrace = until(target: RBRACE);
2542
2543 if (foundSemicolon && semicolonIndex < index) {
2544 decl = Declaration();
2545 index = semicolonIndex - 1;
2546 } else {
2547 skipSpace();
2548 return foundRBrace;
2549 }
2550 }
2551 if (!decl.isEmpty())
2552 styleRule->declarations.append(t: decl);
2553 } while (test(t: SEMICOLON));
2554
2555 if (!next(t: RBRACE)) return false;
2556 skipSpace();
2557 return true;
2558}
2559
2560bool Parser::parseSelector(Selector *sel)
2561{
2562 BasicSelector basicSel;
2563 if (!parseSimpleSelector(basicSel: &basicSel)) return false;
2564 while (testCombinator()) {
2565 if (!parseCombinator(relation: &basicSel.relationToNext)) return false;
2566
2567 if (!testSimpleSelector()) break;
2568 sel->basicSelectors.append(t: basicSel);
2569
2570 basicSel = BasicSelector();
2571 if (!parseSimpleSelector(basicSel: &basicSel)) return false;
2572 }
2573 sel->basicSelectors.append(t: basicSel);
2574 return true;
2575}
2576
2577bool Parser::parseSimpleSelector(BasicSelector *basicSel)
2578{
2579 int minCount = 0;
2580 if (lookupElementName()) {
2581 if (!parseElementName(name: &basicSel->elementName)) return false;
2582 } else {
2583 prev();
2584 minCount = 1;
2585 }
2586 bool onceMore;
2587 int count = 0;
2588 do {
2589 onceMore = false;
2590 if (test(t: HASH)) {
2591 QString theid = lexem();
2592 // chop off leading #
2593 theid.remove(i: 0, len: 1);
2594 basicSel->ids.append(t: theid);
2595 onceMore = true;
2596 } else if (testClass()) {
2597 onceMore = true;
2598 AttributeSelector a;
2599 a.name = "class"_L1;
2600 a.valueMatchCriterium = AttributeSelector::MatchIncludes;
2601 if (!parseClass(name: &a.value)) return false;
2602 basicSel->attributeSelectors.append(t: a);
2603 } else if (testAttrib()) {
2604 onceMore = true;
2605 AttributeSelector a;
2606 if (!parseAttrib(attr: &a)) return false;
2607 basicSel->attributeSelectors.append(t: a);
2608 } else if (testPseudo()) {
2609 onceMore = true;
2610 Pseudo ps;
2611 if (!parsePseudo(pseudo: &ps)) return false;
2612 basicSel->pseudos.append(t: ps);
2613 }
2614 if (onceMore) ++count;
2615 } while (onceMore);
2616 return count >= minCount;
2617}
2618
2619bool Parser::parseClass(QString *name)
2620{
2621 if (!next(t: IDENT)) return false;
2622 *name = lexem();
2623 return true;
2624}
2625
2626bool Parser::parseElementName(QString *name)
2627{
2628 switch (lookup()) {
2629 case STAR: name->clear(); break;
2630 case IDENT: *name = lexem(); break;
2631 default: return false;
2632 }
2633 return true;
2634}
2635
2636bool Parser::parseAttrib(AttributeSelector *attr)
2637{
2638 skipSpace();
2639 if (!next(t: IDENT)) return false;
2640 attr->name = lexem();
2641 skipSpace();
2642
2643 if (test(t: EQUAL)) {
2644 attr->valueMatchCriterium = AttributeSelector::MatchEqual;
2645 } else if (test(t: INCLUDES)) {
2646 attr->valueMatchCriterium = AttributeSelector::MatchIncludes;
2647 } else if (test(t: DASHMATCH)) {
2648 attr->valueMatchCriterium = AttributeSelector::MatchDashMatch;
2649 } else if (test(t: BEGINSWITH)) {
2650 attr->valueMatchCriterium = AttributeSelector::MatchBeginsWith;
2651 } else if (test(t: ENDSWITH)) {
2652 attr->valueMatchCriterium = AttributeSelector::MatchEndsWith;
2653 } else if (test(t: CONTAINS)) {
2654 attr->valueMatchCriterium = AttributeSelector::MatchContains;
2655 } else {
2656 return next(t: RBRACKET);
2657 }
2658
2659 skipSpace();
2660
2661 if (!test(t: IDENT) && !test(t: STRING)) return false;
2662 attr->value = unquotedLexem();
2663
2664 skipSpace();
2665 return next(t: RBRACKET);
2666}
2667
2668bool Parser::parsePseudo(Pseudo *pseudo)
2669{
2670 (void)test(t: COLON);
2671 pseudo->negated = test(t: EXCLAMATION_SYM);
2672 if (test(t: IDENT)) {
2673 pseudo->name = lexem();
2674 pseudo->type = static_cast<quint64>(findKnownValue(name: pseudo->name, start: pseudos, numValues: NumPseudos));
2675 return true;
2676 }
2677 if (!next(t: FUNCTION)) return false;
2678 pseudo->function = lexem();
2679 // chop off trailing parenthesis
2680 pseudo->function.chop(n: 1);
2681 skipSpace();
2682 if (!test(t: IDENT)) return false;
2683 pseudo->name = lexem();
2684 skipSpace();
2685 return next(t: RPAREN);
2686}
2687
2688bool Parser::parseNextDeclaration(Declaration *decl)
2689{
2690 if (!testProperty())
2691 return true; // not an error!
2692 if (!parseProperty(decl)) return false;
2693 if (!next(t: COLON)) return false;
2694 skipSpace();
2695 if (!parseNextExpr(values: &decl->d->values)) return false;
2696 if (testPrio())
2697 if (!parsePrio(declaration: decl)) return false;
2698 return true;
2699}
2700
2701bool Parser::testPrio()
2702{
2703 const int rewind = index;
2704 if (!test(t: EXCLAMATION_SYM)) return false;
2705 skipSpace();
2706 if (!test(t: IDENT)) {
2707 index = rewind;
2708 return false;
2709 }
2710 if (lexem().compare(other: "important"_L1, cs: Qt::CaseInsensitive) != 0) {
2711 index = rewind;
2712 return false;
2713 }
2714 return true;
2715}
2716
2717bool Parser::parsePrio(Declaration *declaration)
2718{
2719 declaration->d->important = true;
2720 skipSpace();
2721 return true;
2722}
2723
2724bool Parser::parseExpr(QList<Value> *values)
2725{
2726 Value val;
2727 if (!parseTerm(value: &val)) return false;
2728 values->append(t: val);
2729 bool onceMore;
2730 do {
2731 onceMore = false;
2732 val = Value();
2733 if (!parseNextOperator(value: &val)) return false;
2734 if (val.type != QCss::Value::Unknown)
2735 values->append(t: val);
2736 if (testTerm()) {
2737 onceMore = true;
2738 val = Value();
2739 if (!parseTerm(value: &val)) return false;
2740 values->append(t: val);
2741 }
2742 } while (onceMore);
2743 return true;
2744}
2745
2746bool Parser::testTerm()
2747{
2748 return test(t: PLUS) || test(t: MINUS)
2749 || test(t: NUMBER)
2750 || test(t: PERCENTAGE)
2751 || test(t: LENGTH)
2752 || test(t: STRING)
2753 || test(t: IDENT)
2754 || testHexColor()
2755 || testFunction();
2756}
2757
2758bool Parser::parseTerm(Value *value)
2759{
2760 QString str = lexem();
2761 bool haveUnary = false;
2762 if (lookup() == PLUS || lookup() == MINUS) {
2763 haveUnary = true;
2764 if (!hasNext()) return false;
2765 next();
2766 str += lexem();
2767 }
2768
2769 value->variant = str;
2770 value->type = QCss::Value::String;
2771 switch (lookup()) {
2772 case NUMBER:
2773 value->type = Value::Number;
2774 value->variant.convert(type: QMetaType::fromType<double>());
2775 break;
2776 case PERCENTAGE:
2777 value->type = Value::Percentage;
2778 str.chop(n: 1); // strip off %
2779 value->variant = str;
2780 break;
2781 case LENGTH:
2782 value->type = Value::Length;
2783 break;
2784
2785 case STRING:
2786 if (haveUnary) return false;
2787 value->type = Value::String;
2788 str.chop(n: 1);
2789 str.remove(i: 0, len: 1);
2790 value->variant = str;
2791 break;
2792 case IDENT: {
2793 if (haveUnary) return false;
2794 value->type = Value::Identifier;
2795 const int theid = findKnownValue(name: str, start: values, numValues: NumKnownValues);
2796 if (theid != 0) {
2797 value->type = Value::KnownIdentifier;
2798 value->variant = theid;
2799 }
2800 break;
2801 }
2802 default: {
2803 if (haveUnary) return false;
2804 prev();
2805 if (testHexColor()) {
2806 QColor col;
2807 if (!parseHexColor(col: &col)) return false;
2808 value->type = Value::Color;
2809 value->variant = col;
2810 } else if (testFunction()) {
2811 QString name, args;
2812 if (!parseFunction(name: &name, args: &args)) return false;
2813 if (name == "url"_L1) {
2814 value->type = Value::Uri;
2815 removeOptionalQuotes(str: &args);
2816 if (QFileInfo(args).isRelative() && !sourcePath.isEmpty()) {
2817 args.prepend(s: sourcePath);
2818 }
2819 value->variant = args;
2820 } else {
2821 value->type = Value::Function;
2822 value->variant = QStringList() << name << args;
2823 }
2824 } else {
2825 return recordError();
2826 }
2827 return true;
2828 }
2829 }
2830 skipSpace();
2831 return true;
2832}
2833
2834bool Parser::parseFunction(QString *name, QString *args)
2835{
2836 *name = lexem();
2837 name->chop(n: 1);
2838 // until(RPAREN) needs FUNCTION token at index-1 to work properly
2839 int start = index;
2840 skipSpace();
2841 std::swap(a&: start, b&: index);
2842 if (!until(target: RPAREN)) return false;
2843 for (int i = start; i < index - 1; ++i)
2844 args->append(s: symbols.at(i).lexem());
2845 /*
2846 if (!nextExpr(&arguments)) return false;
2847 if (!next(RPAREN)) return false;
2848 */
2849 skipSpace();
2850 return true;
2851}
2852
2853bool Parser::parseHexColor(QColor *col)
2854{
2855 *col = QColor::fromString(name: lexem());
2856 if (!col->isValid()) {
2857 qWarning(msg: "QCssParser::parseHexColor: Unknown color name '%s'",lexem().toLatin1().constData());
2858 return false;
2859 }
2860 skipSpace();
2861 return true;
2862}
2863
2864bool Parser::testAndParseUri(QString *uri)
2865{
2866 const int rewind = index;
2867 if (!testFunction()) return false;
2868
2869 QString name, args;
2870 if (!parseFunction(name: &name, args: &args)) {
2871 index = rewind;
2872 return false;
2873 }
2874 if (name.compare(other: "url"_L1, cs: Qt::CaseInsensitive) != 0) {
2875 index = rewind;
2876 return false;
2877 }
2878 *uri = args;
2879 removeOptionalQuotes(str: uri);
2880 return true;
2881}
2882
2883bool Parser::testSimpleSelector()
2884{
2885 return testElementName()
2886 || (test(t: HASH))
2887 || testClass()
2888 || testAttrib()
2889 || testPseudo();
2890}
2891
2892bool Parser::next(QCss::TokenType t)
2893{
2894 if (hasNext() && next() == t)
2895 return true;
2896 return recordError();
2897}
2898
2899bool Parser::test(QCss::TokenType t)
2900{
2901 if (index >= symbols.size())
2902 return false;
2903 if (symbols.at(i: index).token == t) {
2904 ++index;
2905 return true;
2906 }
2907 return false;
2908}
2909
2910QString Parser::unquotedLexem() const
2911{
2912 QString s = lexem();
2913 if (lookup() == STRING) {
2914 s.chop(n: 1);
2915 s.remove(i: 0, len: 1);
2916 }
2917 return s;
2918}
2919
2920QString Parser::lexemUntil(QCss::TokenType t)
2921{
2922 QString lexem;
2923 while (hasNext() && next() != t)
2924 lexem += symbol().lexem();
2925 return lexem;
2926}
2927
2928bool Parser::until(QCss::TokenType target, QCss::TokenType target2)
2929{
2930 int braceCount = 0;
2931 int brackCount = 0;
2932 int parenCount = 0;
2933 if (index) {
2934 switch(symbols.at(i: index-1).token) {
2935 case LBRACE: ++braceCount; break;
2936 case LBRACKET: ++brackCount; break;
2937 case FUNCTION:
2938 case LPAREN: ++parenCount; break;
2939 default: ;
2940 }
2941 }
2942 while (index < symbols.size()) {
2943 QCss::TokenType t = symbols.at(i: index++).token;
2944 switch (t) {
2945 case LBRACE: ++braceCount; break;
2946 case RBRACE: --braceCount; break;
2947 case LBRACKET: ++brackCount; break;
2948 case RBRACKET: --brackCount; break;
2949 case FUNCTION:
2950 case LPAREN: ++parenCount; break;
2951 case RPAREN: --parenCount; break;
2952 default: break;
2953 }
2954 if ((t == target || (target2 != NONE && t == target2))
2955 && braceCount <= 0
2956 && brackCount <= 0
2957 && parenCount <= 0)
2958 return true;
2959
2960 if (braceCount < 0 || brackCount < 0 || parenCount < 0) {
2961 --index;
2962 break;
2963 }
2964 }
2965 return false;
2966}
2967
2968bool Parser::testTokenAndEndsWith(QCss::TokenType t, QLatin1StringView str)
2969{
2970 if (!test(t)) return false;
2971 if (!lexem().endsWith(s: str, cs: Qt::CaseInsensitive)) {
2972 prev();
2973 return false;
2974 }
2975 return true;
2976}
2977
2978QT_END_NAMESPACE
2979#endif // QT_NO_CSSPARSER
2980

source code of qtbase/src/gui/text/qcssparser.cpp