Coverage Report

Created: 2026-04-29 07:00

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