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