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

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