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