Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qmimetypeparser.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#define QT_NO_CAST_FROM_ASCII
5
7
8#include "qmimetype_p.h"
10
11#include <QtCore/QCoreApplication>
12#include <QtCore/QDebug>
13#include <QtCore/QDir>
14#include <QtCore/QXmlStreamReader>
15#include <QtCore/QXmlStreamWriter>
16#include <QtCore/QStack>
17
18QT_BEGIN_NAMESPACE
19
20using namespace Qt::StringLiterals;
21
22// XML tags in MIME files
23static const char mimeInfoTagC[] = "mime-info";
24static const char mimeTypeTagC[] = "mime-type";
25static const char mimeTypeAttributeC[] = "type";
26static const char subClassTagC[] = "sub-class-of";
27static const char commentTagC[] = "comment";
28static const char genericIconTagC[] = "generic-icon";
29static const char iconTagC[] = "icon";
30static const char nameAttributeC[] = "name";
31static const char globTagC[] = "glob";
32static const char globDeleteAllTagC[] = "glob-deleteall";
33static const char aliasTagC[] = "alias";
34static const char patternAttributeC[] = "pattern";
35static const char weightAttributeC[] = "weight";
36static const char caseSensitiveAttributeC[] = "case-sensitive";
37static const char localeAttributeC[] = "xml:lang";
38
39static const char magicTagC[] = "magic";
40static const char priorityAttributeC[] = "priority";
41
42static const char matchTagC[] = "match";
43static const char matchValueAttributeC[] = "value";
44static const char matchTypeAttributeC[] = "type";
45static const char matchOffsetAttributeC[] = "offset";
46static const char matchMaskAttributeC[] = "mask";
47
48/*!
49 \class QMimeTypeParser
50 \inmodule QtCore
51 \internal
52 \brief The QMimeTypeParser class parses MIME types, and builds a MIME database hierarchy by adding to QMimeDatabase.
53
54 Populates QMimeDataBase
55
56 \sa QMimeDatabase, QMimeMagicRuleMatcher, MagicRule, MagicStringRule, MagicByteRule, GlobPattern
57 \sa QMimeTypeParser
58*/
59
61 = default;
62
63/*!
64 \class QMimeTypeParserBase
65 \inmodule QtCore
66 \internal
67 \brief The QMimeTypeParserBase class parses for a sequence of <mime-type> in a generic way.
68
69 Calls abstract handler function process for QMimeType it finds.
70
71 \sa QMimeDatabase, QMimeMagicRuleMatcher, MagicRule, MagicStringRule, MagicByteRule, GlobPattern
72 \sa QMimeTypeParser
73*/
74
75QMimeTypeParserBase::~QMimeTypeParserBase()
76 = default;
77
78/*!
79 \fn virtual bool QMimeTypeParserBase::process(const QMimeType &t, QString *errorMessage) = 0;
80 Overwrite to process the sequence of parsed data
81*/
82
83QMimeTypeParserBase::ParseState QMimeTypeParserBase::nextState(ParseState currentState, QStringView startElement)
84{
85 switch (currentState) {
86 case ParseBeginning:
87 if (startElement == QLatin1StringView(mimeInfoTagC))
88 return ParseMimeInfo;
89 if (startElement == QLatin1StringView(mimeTypeTagC))
90 return ParseMimeType;
91 return ParseError;
92 case ParseMimeInfo:
93 return startElement == QLatin1StringView(mimeTypeTagC) ? ParseMimeType : ParseError;
94 case ParseMimeType:
95 case ParseComment:
96 case ParseGenericIcon:
97 case ParseIcon:
98 case ParseGlobPattern:
99 case ParseGlobDeleteAll:
100 case ParseSubClass:
101 case ParseAlias:
102 case ParseOtherMimeTypeSubTag:
103 case ParseMagicMatchRule:
104 if (startElement == QLatin1StringView(mimeTypeTagC)) // Sequence of <mime-type>
105 return ParseMimeType;
106 if (startElement == QLatin1StringView(commentTagC))
107 return ParseComment;
108 if (startElement == QLatin1StringView(genericIconTagC))
109 return ParseGenericIcon;
110 if (startElement == QLatin1StringView(iconTagC))
111 return ParseIcon;
112 if (startElement == QLatin1StringView(globTagC))
113 return ParseGlobPattern;
114 if (startElement == QLatin1StringView(globDeleteAllTagC))
115 return ParseGlobDeleteAll;
116 if (startElement == QLatin1StringView(subClassTagC))
117 return ParseSubClass;
118 if (startElement == QLatin1StringView(aliasTagC))
119 return ParseAlias;
120 if (startElement == QLatin1StringView(magicTagC))
121 return ParseMagic;
122 if (startElement == QLatin1StringView(matchTagC))
123 return ParseMagicMatchRule;
124 return ParseOtherMimeTypeSubTag;
125 case ParseMagic:
126 if (startElement == QLatin1StringView(matchTagC))
127 return ParseMagicMatchRule;
128 break;
129 case ParseError:
130 break;
131 }
132 return ParseError;
133}
134
135// Parse int number from an (attribute) string
136bool QMimeTypeParserBase::parseNumber(QStringView n, int *target, QString *errorMessage)
137{
138 bool ok;
139 *target = n.toInt(&ok);
140 if (Q_UNLIKELY(!ok)) {
141 if (errorMessage)
142 *errorMessage = "Not a number '"_L1 + n + "'."_L1;
143 return false;
144 }
145 return true;
146}
147
148#if QT_CONFIG(xmlstreamreader)
149struct CreateMagicMatchRuleResult
150{
151 QString errorMessage; // must be first
152 QMimeMagicRule rule;
153
154 CreateMagicMatchRuleResult(QStringView type, QStringView value, QStringView offsets, QStringView mask)
155 : errorMessage(), rule(type.toString(), value.toUtf8(), offsets.toString(), mask.toLatin1(), &errorMessage)
156 {
157
158 }
159};
160
161static CreateMagicMatchRuleResult createMagicMatchRule(const QXmlStreamAttributes &atts)
162{
163 const auto type = atts.value(QLatin1StringView(matchTypeAttributeC));
164 const auto value = atts.value(QLatin1StringView(matchValueAttributeC));
165 const auto offsets = atts.value(QLatin1StringView(matchOffsetAttributeC));
166 const auto mask = atts.value(QLatin1StringView(matchMaskAttributeC));
167 return CreateMagicMatchRuleResult(type, value, offsets, mask);
168}
169#endif // feature xmlstreamreader
170
171bool QMimeTypeParserBase::parse(QIODevice *dev, const QString &fileName, QString *errorMessage)
172{
173#if QT_CONFIG(xmlstreamreader)
174 QMimeTypeXMLData data;
175 int priority = 50;
176 QStack<QMimeMagicRule *> currentRules; // stack for the nesting of rules
177 QList<QMimeMagicRule> rules; // toplevel rules
178 QXmlStreamReader reader(dev);
179 ParseState ps = ParseBeginning;
180 while (!reader.atEnd()) {
181 switch (reader.readNext()) {
182 case QXmlStreamReader::StartElement: {
183 ps = nextState(ps, reader.name());
184 const QXmlStreamAttributes atts = reader.attributes();
185 switch (ps) {
186 case ParseMimeType: { // start parsing a MIME type name
187 const QString name = atts.value(QLatin1StringView(mimeTypeAttributeC)).toString();
188 if (name.isEmpty()) {
189 reader.raiseError(QStringLiteral("Missing 'type'-attribute"));
190 } else {
191 data.name = name;
192 }
193 }
194 break;
195 case ParseGenericIcon:
196 data.genericIconName = atts.value(QLatin1StringView(nameAttributeC)).toString();
197 break;
198 case ParseIcon:
199 data.iconName = atts.value(QLatin1StringView(nameAttributeC)).toString();
200 break;
201 case ParseGlobPattern: {
202 const QString pattern = atts.value(QLatin1StringView(patternAttributeC)).toString();
203 unsigned weight = atts.value(QLatin1StringView(weightAttributeC)).toInt();
204 const bool caseSensitive = atts.value(QLatin1StringView(caseSensitiveAttributeC)) == "true"_L1;
205
206 if (weight == 0)
207 weight = QMimeGlobPattern::DefaultWeight;
208
209 Q_ASSERT(!data.name.isEmpty());
210 const QMimeGlobPattern glob(pattern, data.name, weight, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
211 if (!process(glob, errorMessage)) // for actual glob matching
212 return false;
213 data.addGlobPattern(pattern); // just for QMimeType::globPatterns()
214 }
215 break;
216 case ParseGlobDeleteAll:
217 data.globPatterns.clear();
218 data.hasGlobDeleteAll = true;
219 break;
220 case ParseSubClass: {
221 const QString inheritsFrom = atts.value(QLatin1StringView(mimeTypeAttributeC)).toString();
222 if (!inheritsFrom.isEmpty())
223 processParent(data.name, inheritsFrom);
224 }
225 break;
226 case ParseComment: {
227 // comments have locale attributes.
228 QString locale = atts.value(QLatin1StringView(localeAttributeC)).toString();
229 const QString comment = reader.readElementText();
230 if (locale.isEmpty())
231 locale = QString::fromLatin1("default");
232 data.localeComments.insert(locale, comment);
233 }
234 break;
235 case ParseAlias: {
236 const QString alias = atts.value(QLatin1StringView(mimeTypeAttributeC)).toString();
237 if (!alias.isEmpty())
238 processAlias(alias, data.name);
239 }
240 break;
241 case ParseMagic: {
242 priority = 50;
243 const auto priorityS = atts.value(QLatin1StringView(priorityAttributeC));
244 if (!priorityS.isEmpty()) {
245 if (!parseNumber(priorityS, &priority, errorMessage))
246 return false;
247
248 }
249 currentRules.clear();
250 //qDebug() << "MAGIC start for mimetype" << data.name;
251 }
252 break;
253 case ParseMagicMatchRule: {
254 auto result = createMagicMatchRule(atts);
255 if (Q_UNLIKELY(!result.rule.isValid()))
256 qWarning("QMimeDatabase: Error parsing %ls\n%ls",
257 qUtf16Printable(fileName), qUtf16Printable(result.errorMessage));
258 QList<QMimeMagicRule> *ruleList;
259 if (currentRules.isEmpty())
260 ruleList = &rules;
261 else // nest this rule into the proper parent
262 ruleList = &currentRules.top()->m_subMatches;
263 ruleList->append(std::move(result.rule));
264 //qDebug() << " MATCH added. Stack size was" << currentRules.size();
265 currentRules.push(&ruleList->last());
266 break;
267 }
268 case ParseError:
269 reader.raiseError("Unexpected element <"_L1 + reader.name() + u'>');
270 break;
271 default:
272 break;
273 }
274 }
275 break;
276 // continue switch QXmlStreamReader::Token...
277 case QXmlStreamReader::EndElement: // Finished element
278 {
279 const auto elementName = reader.name();
280 if (elementName == QLatin1StringView(mimeTypeTagC)) {
281 if (!process(data, errorMessage))
282 return false;
283 data.clear();
284 } else if (elementName == QLatin1StringView(matchTagC)) {
285 // Closing a <match> tag, pop stack
286 currentRules.pop();
287 //qDebug() << " MATCH closed. Stack size is now" << currentRules.size();
288 } else if (elementName == QLatin1StringView(magicTagC)) {
289 //qDebug() << "MAGIC ended, we got" << rules.count() << "rules, with prio" << priority;
290 // Finished a <magic> sequence
291 QMimeMagicRuleMatcher ruleMatcher(data.name, priority);
292 ruleMatcher.addRules(rules);
293 processMagicMatcher(ruleMatcher);
294 rules.clear();
295 ps = ParseOtherMimeTypeSubTag; // in case of an empty glob tag
296 }
297 break;
298 }
299 default:
300 break;
301 }
302 }
303
304 if (Q_UNLIKELY(reader.hasError())) {
305 if (errorMessage) {
306 *errorMessage = QString::asprintf("An error has been encountered at line %lld of %ls: %ls:",
307 reader.lineNumber(),
308 qUtf16Printable(fileName),
309 qUtf16Printable(reader.errorString()));
310 }
311 return false;
312 }
313
314 return true;
315#else
316 Q_UNUSED(dev);
317 if (errorMessage)
318 *errorMessage = "QXmlStreamReader is not available, cannot parse '%1'."_L1.arg(fileName);
319 return false;
320#endif // feature xmlstreamreader
321}
322
324{
325 hasGlobDeleteAll = false;
326 name.clear();
327 localeComments.clear();
328 genericIconName.clear();
329 iconName.clear();
330 globPatterns.clear();
331}
332
333void QMimeTypeXMLData::addGlobPattern(const QString &pattern)
334{
335 globPatterns.append(pattern);
336}
337
338QT_END_NAMESPACE
bool parse(QIODevice *dev, const QString &fileName, QString *errorMessage)
\inmodule QtCore
~QMimeTypeParser() override
void addGlobPattern(const QString &pattern)
static const char matchMaskAttributeC[]
static const char matchValueAttributeC[]
static const char localeAttributeC[]
static const char iconTagC[]
static const char nameAttributeC[]
static const char caseSensitiveAttributeC[]
static const char globDeleteAllTagC[]
static const char mimeTypeTagC[]
static const char globTagC[]
static const char subClassTagC[]
static const char mimeTypeAttributeC[]
static const char patternAttributeC[]
static const char commentTagC[]
static const char aliasTagC[]
static const char matchTagC[]
static const char mimeInfoTagC[]
static const char matchTypeAttributeC[]
static const char matchOffsetAttributeC[]
static const char priorityAttributeC[]
static const char magicTagC[]
static const char weightAttributeC[]
static const char genericIconTagC[]