Coverage Report

Created: 2026-09-28 08:21

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