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

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