File indexing completed on 2025-02-09 04:28:37

0001 /*
0002   This file is part of the KTextTemplate library
0003 
0004   SPDX-FileCopyrightText: 2009, 2010 Stephen Kelly <steveire@gmail.com>
0005 
0006   SPDX-License-Identifier: LGPL-2.1-or-later
0007 
0008 */
0009 
0010 #include "filterexpression.h"
0011 
0012 #include <QRegularExpression>
0013 
0014 #include "exception.h"
0015 #include "filter.h"
0016 #include "parser.h"
0017 #include "util.h"
0018 
0019 using ArgFilter = std::pair<QSharedPointer<KTextTemplate::Filter>, KTextTemplate::Variable>;
0020 
0021 namespace KTextTemplate
0022 {
0023 
0024 class FilterExpressionPrivate
0025 {
0026     FilterExpressionPrivate(FilterExpression *fe)
0027         : q_ptr(fe)
0028     {
0029     }
0030 
0031     Variable m_variable;
0032     QList<ArgFilter> m_filters;
0033     QStringList m_filterNames;
0034 
0035     Q_DECLARE_PUBLIC(FilterExpression)
0036     FilterExpression *const q_ptr;
0037 };
0038 }
0039 
0040 using namespace KTextTemplate;
0041 
0042 static const char FILTER_SEPARATOR = '|';
0043 static const char FILTER_ARGUMENT_SEPARATOR = ':';
0044 
0045 static QRegularExpression getFilterRegexp()
0046 {
0047     const QString filterSep(QRegularExpression::escape(QChar::fromLatin1(FILTER_SEPARATOR)));
0048     const QString argSep(QRegularExpression::escape(QChar::fromLatin1(FILTER_ARGUMENT_SEPARATOR)));
0049 
0050     const QLatin1String varChars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.");
0051     const QLatin1String numChars(R"([-+\.]?\d[\d\.e]*)");
0052     const QString i18nOpen(QRegularExpression::escape(QStringLiteral("_(")));
0053     const QLatin1String doubleQuoteStringLiteral(R"("[^"\\]*(?:\\.[^"\\]*)*")");
0054     const QLatin1String singleQuoteStringLiteral(R"('[^'\\]*(?:\\.[^'\\]*)*')");
0055     const QString i18nClose(QRegularExpression::escape(QStringLiteral(")")));
0056     const QString variable = QLatin1Char('[') + varChars + QStringLiteral("]+");
0057 
0058     const QString localizedExpression = QStringLiteral("(?:") + i18nOpen + doubleQuoteStringLiteral + i18nClose + QLatin1Char('|') + i18nOpen
0059         + singleQuoteStringLiteral + i18nClose + QLatin1Char('|') + i18nOpen + numChars + i18nClose + QLatin1Char('|') + i18nOpen + variable + i18nClose
0060         + QLatin1Char(')');
0061 
0062     const QString constantString = QStringLiteral("(?:") + doubleQuoteStringLiteral + QLatin1Char('|') + singleQuoteStringLiteral + QLatin1Char(')');
0063 
0064     const QString filterRawString = QLatin1Char('^') + constantString + QLatin1Char('|') + QLatin1Char('^') + localizedExpression + QLatin1Char('|')
0065         + QLatin1Char('^') + variable + QLatin1Char('|') + numChars + QLatin1Char('|') + filterSep + QStringLiteral("\\w+|") + argSep + QStringLiteral("(?:")
0066         + constantString + QLatin1Char('|') + localizedExpression + QLatin1Char('|') + variable + QLatin1Char('|') + numChars + QLatin1Char('|') + filterSep
0067         + QStringLiteral("\\w+)");
0068 
0069     return QRegularExpression(filterRawString);
0070 }
0071 
0072 FilterExpression::FilterExpression(const QString &varString, Parser *parser)
0073     : d_ptr(new FilterExpressionPrivate(this))
0074 {
0075     Q_D(FilterExpression);
0076 
0077     auto pos = 0;
0078     auto lastPos = 0;
0079     int len;
0080     QString subString;
0081 
0082     static const auto sFilterRe = getFilterRegexp();
0083 
0084     // This is one fo the few constructors that can throw so we make sure to
0085     // delete its d->pointer.
0086     try {
0087         auto i = sFilterRe.globalMatch(varString);
0088         while (i.hasNext()) {
0089             auto match = i.next();
0090             len = match.capturedLength();
0091             pos = match.capturedStart();
0092             subString = match.captured();
0093             const auto ssSize = subString.size();
0094 
0095             if (pos != lastPos) {
0096                 throw KTextTemplate::Exception(TagSyntaxError, QStringLiteral("Could not parse some characters: \"%1\"").arg(varString.mid(lastPos, pos)));
0097             }
0098 
0099             if (subString.startsWith(QLatin1Char(FILTER_SEPARATOR))) {
0100                 subString = subString.right(ssSize - 1);
0101                 auto f = parser->getFilter(subString);
0102 
0103                 Q_ASSERT(f);
0104 
0105                 d->m_filterNames << subString;
0106                 d->m_filters << std::make_pair(f, Variable());
0107 
0108             } else if (subString.startsWith(QLatin1Char(FILTER_ARGUMENT_SEPARATOR))) {
0109                 if (d->m_filters.isEmpty() || d->m_filters.at(d->m_filters.size() - 1).second.isValid()) {
0110                     const auto remainder = varString.right(varString.size() - lastPos);
0111                     throw KTextTemplate::Exception(TagSyntaxError, QStringLiteral("Could not parse the remainder, %1 from %2").arg(remainder, varString));
0112                 }
0113                 subString = subString.right(ssSize - 1);
0114                 const auto lastFilter = d->m_filters.size();
0115                 if (subString.startsWith(QLatin1Char(FILTER_SEPARATOR)))
0116                     throw KTextTemplate::Exception(EmptyVariableError, QStringLiteral("Missing argument to filter: %1").arg(d->m_filterNames[lastFilter - 1]));
0117 
0118                 d->m_filters[lastFilter - 1].second = Variable(subString);
0119             } else {
0120                 // Token is _("translated"), or "constant", or a variable;
0121                 d->m_variable = Variable(subString);
0122             }
0123 
0124             pos += len;
0125             lastPos = pos;
0126         }
0127 
0128         const auto remainder = varString.right(varString.size() - lastPos);
0129         if (!remainder.isEmpty()) {
0130             throw KTextTemplate::Exception(TagSyntaxError, QStringLiteral("Could not parse the remainder, %1 from %2").arg(remainder, varString));
0131         }
0132     } catch (...) {
0133         delete d_ptr;
0134         throw;
0135     }
0136 }
0137 
0138 FilterExpression::FilterExpression(const FilterExpression &other)
0139     : d_ptr(new FilterExpressionPrivate(this))
0140 {
0141     *this = other;
0142 }
0143 
0144 FilterExpression::FilterExpression()
0145     : d_ptr(new FilterExpressionPrivate(this))
0146 {
0147 }
0148 
0149 bool FilterExpression::isValid() const
0150 {
0151     Q_D(const FilterExpression);
0152     return d->m_variable.isValid();
0153 }
0154 
0155 FilterExpression::~FilterExpression()
0156 {
0157     delete d_ptr;
0158 }
0159 
0160 Variable FilterExpression::variable() const
0161 {
0162     Q_D(const FilterExpression);
0163     return d->m_variable;
0164 }
0165 
0166 FilterExpression &FilterExpression::operator=(const FilterExpression &other)
0167 {
0168     if (&other == this)
0169         return *this;
0170     d_ptr->m_variable = other.d_ptr->m_variable;
0171     d_ptr->m_filters = other.d_ptr->m_filters;
0172     d_ptr->m_filterNames = other.d_ptr->m_filterNames;
0173     return *this;
0174 }
0175 
0176 QVariant FilterExpression::resolve(OutputStream *stream, Context *c) const
0177 {
0178     Q_D(const FilterExpression);
0179     auto var = d->m_variable.resolve(c);
0180 
0181     auto it = d->m_filters.constBegin();
0182     const auto end = d->m_filters.constEnd();
0183     for (; it != end; ++it) {
0184         auto filter = it->first;
0185         filter->setStream(stream);
0186         const auto argVar = it->second;
0187         auto arg = argVar.resolve(c);
0188 
0189         if (arg.isValid()) {
0190             KTextTemplate::SafeString argString;
0191             if (arg.userType() == qMetaTypeId<KTextTemplate::SafeString>()) {
0192                 argString = arg.value<KTextTemplate::SafeString>();
0193             } else if (arg.userType() == qMetaTypeId<QString>()) {
0194                 argString = KTextTemplate::SafeString(arg.value<QString>());
0195             }
0196             if (argVar.isConstant()) {
0197                 argString = markSafe(argString);
0198             }
0199             if (!argString.get().isEmpty()) {
0200                 arg = argString;
0201             }
0202         }
0203 
0204         const auto varString = getSafeString(var);
0205 
0206         var = filter->doFilter(var, arg, c->autoEscape());
0207 
0208         if (var.userType() == qMetaTypeId<KTextTemplate::SafeString>() || var.userType() == qMetaTypeId<QString>()) {
0209             if (filter->isSafe() && varString.isSafe()) {
0210                 var = markSafe(getSafeString(var));
0211             } else if (varString.needsEscape()) {
0212                 var = markForEscaping(getSafeString(var));
0213             } else {
0214                 var = getSafeString(var);
0215             }
0216         }
0217     }
0218     (*stream) << getSafeString(var).get();
0219     return var;
0220 }
0221 
0222 QVariant FilterExpression::resolve(Context *c) const
0223 {
0224     OutputStream _dummy;
0225     return resolve(&_dummy, c);
0226 }
0227 
0228 QVariantList FilterExpression::toList(Context *c) const
0229 {
0230     const auto var = resolve(c);
0231     if (!var.canConvert<QVariantList>())
0232         return {};
0233     return var.value<QVariantList>();
0234 }
0235 
0236 bool FilterExpression::isTrue(Context *c) const
0237 {
0238     return variantIsTrue(resolve(c));
0239 }
0240 
0241 QStringList FilterExpression::filters() const
0242 {
0243     Q_D(const FilterExpression);
0244     return d->m_filterNames;
0245 }