1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2022 Nicolas Fella <nicolas.fella@gmx.de>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kfilefilter.h"
9
10#include <QDebug>
11#include <QMetaType>
12#include <QMimeDatabase>
13#include <algorithm>
14#include <qchar.h>
15
16#include "kiocoredebug.h"
17
18class KFileFilterPrivate : public QSharedData
19{
20public:
21 KFileFilterPrivate()
22 {
23 }
24
25 KFileFilterPrivate(const KFileFilterPrivate &other)
26 : QSharedData(other)
27 , m_label(other.m_label)
28 , m_filePatterns(other.m_filePatterns)
29 , m_mimePatterns(other.m_mimePatterns)
30 {
31 }
32
33 QString m_label;
34 QStringList m_filePatterns;
35 QStringList m_mimePatterns;
36 bool m_isValid = true;
37};
38
39QList<KFileFilter> KFileFilter::fromFilterString(const QString &filterString)
40{
41 int pos = filterString.indexOf(c: QLatin1Char('/'));
42
43 // Check for an un-escaped '/', if found
44 // interpret as a MIME filter.
45
46 if (pos > 0 && filterString[pos - 1] != QLatin1Char('\\')) {
47 const QStringList filters = filterString.split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts);
48
49 QList<KFileFilter> result;
50 result.reserve(asize: filters.size());
51
52 std::transform(first: filters.begin(), last: filters.end(), result: std::back_inserter(x&: result), unary_op: [](const QString &mimeType) {
53 return KFileFilter::fromMimeType(mimeType);
54 });
55
56 return result;
57 }
58
59 // Strip the escape characters from
60 // escaped '/' characters.
61
62 QString escapeRemoved(filterString);
63 for (pos = 0; (pos = escapeRemoved.indexOf(s: QLatin1String("\\/"), from: pos)) != -1; ++pos) {
64 escapeRemoved.remove(i: pos, len: 1);
65 }
66
67 const QStringList filters = escapeRemoved.split(sep: QLatin1Char('\n'), behavior: Qt::SkipEmptyParts);
68
69 QList<KFileFilter> result;
70
71 for (const QString &filter : filters) {
72 int separatorPos = filter.indexOf(c: QLatin1Char('|'));
73
74 QString label;
75 QStringList patterns;
76
77 if (separatorPos != -1) {
78 label = filter.mid(position: separatorPos + 1);
79 patterns = filter.left(n: separatorPos).split(sep: QLatin1Char(' '));
80 } else {
81 patterns = filter.split(sep: QLatin1Char(' '));
82 label = patterns.join(sep: QLatin1Char(' '));
83 }
84
85 result << KFileFilter(label, patterns, {});
86 }
87
88 return result;
89}
90
91KFileFilter::KFileFilter()
92 : d(new KFileFilterPrivate)
93{
94}
95
96KFileFilter::KFileFilter(const QString &label, const QStringList &filePatterns, const QStringList &mimePatterns)
97 : d(new KFileFilterPrivate)
98{
99 d->m_filePatterns = filePatterns;
100 d->m_mimePatterns = mimePatterns;
101 d->m_label = label;
102}
103
104KFileFilter::~KFileFilter() = default;
105
106KFileFilter::KFileFilter(const KFileFilter &other)
107 : d(other.d)
108{
109}
110
111KFileFilter &KFileFilter::operator=(const KFileFilter &other)
112{
113 if (this != &other) {
114 d = other.d;
115 }
116
117 return *this;
118}
119
120QString KFileFilter::label() const
121{
122 return d->m_label;
123}
124
125QStringList KFileFilter::filePatterns() const
126{
127 return d->m_filePatterns;
128}
129
130QStringList KFileFilter::mimePatterns() const
131{
132 return d->m_mimePatterns;
133}
134
135bool KFileFilter::operator==(const KFileFilter &other) const
136{
137 return d->m_label == other.d->m_label && d->m_filePatterns == other.d->m_filePatterns && d->m_mimePatterns == other.d->m_mimePatterns;
138}
139
140bool KFileFilter::isEmpty() const
141{
142 return d->m_filePatterns.isEmpty() && d->m_mimePatterns.isEmpty();
143}
144
145bool KFileFilter::isValid() const
146{
147 return d->m_isValid;
148}
149
150QString KFileFilter::toFilterString() const
151{
152 if (!d->m_filePatterns.isEmpty() && !d->m_mimePatterns.isEmpty()) {
153 qCWarning(KIO_CORE) << "KFileFilters with both mime and file patterns cannot be converted to filter strings";
154 return QString();
155 }
156
157 if (!d->m_mimePatterns.isEmpty()) {
158 return d->m_mimePatterns.join(sep: QLatin1Char(' '));
159 }
160
161 if (!d->m_label.isEmpty()) {
162 const QString patterns = d->m_filePatterns.join(sep: QLatin1Char(' '));
163 const QString escapedLabel = QString(d->m_label).replace(before: QLatin1String("/"), after: QLatin1String("\\/"));
164
165 if (patterns != d->m_label) {
166 return patterns + QLatin1Char('|') + escapedLabel;
167 } else {
168 return patterns;
169 }
170 } else {
171 return d->m_filePatterns.join(sep: QLatin1Char(' '));
172 }
173}
174
175KFileFilter KFileFilter::fromMimeType(const QString &mimeType)
176{
177 if (mimeType.isEmpty()) {
178 qCWarning(KIO_CORE) << "KFileFilter::fromMimeType() called with empty input";
179
180 KFileFilter filter;
181 filter.d->m_isValid = false;
182 return filter;
183 }
184
185 static QMimeDatabase db;
186 const QMimeType type = db.mimeTypeForName(nameOrAlias: mimeType);
187
188 if (type.isValid()) {
189 KFileFilter filter(type.comment(), {}, {mimeType});
190 return filter;
191 } else {
192 qCWarning(KIO_CORE) << "KFileFilter::fromMimeType() called with unknown MIME type" << mimeType;
193
194 KFileFilter filter;
195 filter.d->m_isValid = false;
196 return filter;
197 }
198}
199
200QList<KFileFilter> KFileFilter::fromMimeTypes(const QStringList &mimeTypes)
201{
202 QList<KFileFilter> ret;
203 ret.reserve(asize: mimeTypes.size());
204 for (const QString &type : mimeTypes) {
205 ret << KFileFilter::fromMimeType(mimeType: type);
206 }
207 return ret;
208}
209
210QDebug operator<<(QDebug dbg, const KFileFilter &filter)
211{
212 dbg << "KFileFilter(";
213
214 dbg << "MIME patterns: " << filter.mimePatterns();
215 dbg << " ";
216 dbg << "File patterns: " << filter.filePatterns();
217 dbg << " ";
218 dbg << "label: " << filter.label();
219
220 dbg << ")";
221 return dbg;
222}
223
224Q_DECLARE_METATYPE(KFileFilter);
225

source code of kio/src/core/kfilefilter.cpp