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

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 "stringfilters.h"
0011 
0012 #include "util.h"
0013 
0014 #include <QRegularExpression>
0015 #include <QVariant>
0016 
0017 QVariant AddSlashesFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0018 {
0019     Q_UNUSED(argument)
0020     Q_UNUSED(autoescape)
0021     auto safeString = getSafeString(input);
0022     safeString.get()
0023         .replace(QLatin1Char('\\'), QStringLiteral("\\\\"))
0024         .get()
0025         .replace(QLatin1Char('\"'), QStringLiteral("\\\""))
0026         .get()
0027         .replace(QLatin1Char('\''), QStringLiteral("\\\'"));
0028     return safeString;
0029 }
0030 
0031 QVariant CapFirstFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0032 {
0033     Q_UNUSED(argument)
0034     Q_UNUSED(autoescape)
0035     auto safeString = getSafeString(input);
0036     if (safeString.get().isEmpty())
0037         return QString();
0038 
0039     return QVariant(safeString.get().at(0).toUpper() + static_cast<QString>(safeString.get().right(safeString.get().size() - 1)));
0040 }
0041 
0042 EscapeJsFilter::EscapeJsFilter() = default;
0043 
0044 static QList<std::pair<QString, QString>> getJsEscapes()
0045 {
0046     QList<std::pair<QString, QString>> jsEscapes;
0047     jsEscapes << std::pair<QString, QString>(QChar::fromLatin1('\\'), QStringLiteral("\\u005C"))
0048               << std::pair<QString, QString>(QChar::fromLatin1('\''), QStringLiteral("\\u0027"))
0049               << std::pair<QString, QString>(QChar::fromLatin1('\"'), QStringLiteral("\\u0022"))
0050               << std::pair<QString, QString>(QChar::fromLatin1('>'), QStringLiteral("\\u003E"))
0051               << std::pair<QString, QString>(QChar::fromLatin1('<'), QStringLiteral("\\u003C"))
0052               << std::pair<QString, QString>(QChar::fromLatin1('&'), QStringLiteral("\\u0026"))
0053               << std::pair<QString, QString>(QChar::fromLatin1('='), QStringLiteral("\\u003D"))
0054               << std::pair<QString, QString>(QChar::fromLatin1('-'), QStringLiteral("\\u002D"))
0055               << std::pair<QString, QString>(QChar::fromLatin1(';'), QStringLiteral("\\u003B"))
0056               << std::pair<QString, QString>(QChar(0x2028), QStringLiteral("\\u2028")) << std::pair<QString, QString>(QChar(0x2029), QStringLiteral("\\u2029"));
0057 
0058     for (auto i = 0; i < 32; ++i) {
0059         jsEscapes << std::pair<QString, QString>(QChar(i), QStringLiteral("\\u00") + QStringLiteral("%1").arg(i, 2, 16, QLatin1Char('0')).toUpper());
0060     }
0061     return jsEscapes;
0062 }
0063 
0064 QVariant EscapeJsFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0065 {
0066     Q_UNUSED(argument)
0067     Q_UNUSED(autoescape)
0068     QString retString = getSafeString(input);
0069 
0070     static const auto jsEscapes = getJsEscapes();
0071 
0072     for (auto &escape : jsEscapes) {
0073         retString = retString.replace(escape.first, escape.second);
0074     }
0075     return retString;
0076 }
0077 
0078 QVariant FixAmpersandsFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0079 {
0080     Q_UNUSED(argument)
0081     Q_UNUSED(autoescape)
0082     auto safeString = getSafeString(input);
0083 
0084     const QRegularExpression fixAmpersandsRegexp(QStringLiteral("&(?!(\\w+|#\\d+);)"));
0085 
0086     safeString.get().replace(fixAmpersandsRegexp, QStringLiteral("&amp;"));
0087 
0088     return safeString;
0089 }
0090 
0091 QVariant CutFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0092 {
0093     Q_UNUSED(autoescape)
0094     auto retString = getSafeString(input);
0095     auto argString = getSafeString(argument);
0096 
0097     auto inputSafe = retString.isSafe();
0098 
0099     retString.get().remove(argString);
0100 
0101     if (inputSafe && argString.get() != QChar::fromLatin1(';'))
0102         return markSafe(retString);
0103     return retString;
0104 }
0105 
0106 QVariant SafeFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0107 {
0108     Q_UNUSED(argument)
0109     Q_UNUSED(autoescape)
0110     return markSafe(getSafeString(input));
0111 }
0112 
0113 QVariant LineNumbersFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0114 {
0115     Q_UNUSED(argument)
0116     auto safeString = getSafeString(input);
0117     auto lines = safeString.get().split(QLatin1Char('\n'));
0118     auto width = QString::number(lines.size()).size();
0119 
0120     const auto shouldEscape = (autoescape && !safeString.isSafe());
0121     for (auto i = 0; i < lines.size(); ++i) {
0122         lines[i] = QStringLiteral("%1. %2").arg(i + 1, width).arg(shouldEscape ? QString(escape(lines.at(i))) : lines.at(i));
0123     }
0124 
0125     return markSafe(lines.join(QChar::fromLatin1('\n')));
0126 }
0127 
0128 QVariant LowerFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0129 {
0130     Q_UNUSED(argument)
0131     Q_UNUSED(autoescape)
0132     return getSafeString(input).get().toLower();
0133 }
0134 
0135 QVariant StringFormatFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0136 {
0137     Q_UNUSED(autoescape)
0138     SafeString a;
0139     if (isSafeString(input))
0140         a = getSafeString(input);
0141     else if (input.userType() == qMetaTypeId<QVariantList>()) {
0142         a = toString(input.value<QVariantList>());
0143     }
0144 
0145     return SafeString(getSafeString(argument).get().arg(a), getSafeString(input).isSafe());
0146 }
0147 
0148 QVariant TitleFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0149 {
0150     Q_UNUSED(argument)
0151     Q_UNUSED(autoescape)
0152 
0153     QString str = getSafeString(input);
0154 
0155     auto it = str.begin();
0156     const auto end = str.end();
0157 
0158     auto toUpper = true;
0159     for (; it != end; ++it) {
0160         if (toUpper)
0161             *it = it->toUpper();
0162         else
0163             *it = it->toLower();
0164         toUpper = it->isSpace();
0165     }
0166 
0167     return str;
0168 }
0169 
0170 QVariant TruncateWordsFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0171 {
0172     Q_UNUSED(autoescape)
0173     auto s = getSafeString(argument);
0174 
0175     bool ok;
0176     auto numWords = s.get().toInt(&ok);
0177 
0178     if (!ok) {
0179         return input.value<QString>();
0180     }
0181 
0182     QString inputString = getSafeString(input);
0183     auto words = inputString.split(QLatin1Char(' '), Qt::SkipEmptyParts);
0184 
0185     if (words.size() > numWords) {
0186         words = words.mid(0, numWords);
0187         if (!words.at(words.size() - 1).endsWith(QStringLiteral("..."))) {
0188             words << QStringLiteral("...");
0189         }
0190     }
0191     return words.join(QChar::fromLatin1(' '));
0192 }
0193 
0194 QVariant UpperFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0195 {
0196     Q_UNUSED(argument)
0197     Q_UNUSED(autoescape)
0198     return getSafeString(input).get().toUpper();
0199 }
0200 
0201 QVariant WordCountFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0202 {
0203     Q_UNUSED(argument)
0204     Q_UNUSED(autoescape)
0205     return QString::number(getSafeString(input).get().split(QLatin1Char(' ')).size());
0206 }
0207 
0208 QVariant LJustFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0209 {
0210     Q_UNUSED(autoescape)
0211     return getSafeString(input).get().leftJustified(getSafeString(argument).get().toInt());
0212 }
0213 
0214 QVariant RJustFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0215 {
0216     Q_UNUSED(autoescape)
0217     return getSafeString(input).get().rightJustified(getSafeString(argument).get().toInt());
0218 }
0219 
0220 QVariant CenterFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0221 {
0222     Q_UNUSED(autoescape)
0223     QString value = getSafeString(input);
0224     const auto valueWidth = value.size();
0225     const auto width = getSafeString(argument).get().toInt();
0226     const auto totalPadding = width - valueWidth;
0227     const auto rightPadding = totalPadding >> 1;
0228 
0229     return value.leftJustified(valueWidth + rightPadding).rightJustified(width);
0230 }
0231 
0232 QVariant EscapeFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0233 {
0234     Q_UNUSED(argument)
0235     Q_UNUSED(autoescape)
0236     return markForEscaping(getSafeString(input));
0237 }
0238 
0239 QVariant ForceEscapeFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0240 {
0241     Q_UNUSED(argument)
0242     Q_UNUSED(autoescape)
0243     return markSafe(escape(getSafeString(input)));
0244 }
0245 
0246 QVariant RemoveTagsFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0247 {
0248     Q_UNUSED(autoescape)
0249     const auto tags = getSafeString(argument).get().split(QLatin1Char(' '));
0250     const auto tagRe = QStringLiteral("(%1)").arg(tags.join(QChar::fromLatin1('|')));
0251     const QRegularExpression startTag(QStringLiteral("<%1(/?>|(\\s+[^>]*>))").arg(tagRe));
0252     const QRegularExpression endTag(QStringLiteral("</%1>").arg(tagRe));
0253 
0254     auto value = getSafeString(input);
0255     const auto safeInput = value.isSafe();
0256     value.get().remove(startTag);
0257     value.get().remove(endTag);
0258     if (safeInput)
0259         return markSafe(value);
0260     return value;
0261 }
0262 
0263 QVariant StripTagsFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0264 {
0265     Q_UNUSED(argument)
0266     Q_UNUSED(autoescape)
0267     static QRegularExpression tagRe(QStringLiteral("<[^>]*>"), QRegularExpression::InvertedGreedinessOption);
0268 
0269     QString value = getSafeString(input);
0270     value.remove(tagRe);
0271     return value;
0272 }
0273 
0274 QVariant WordWrapFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0275 {
0276     Q_UNUSED(autoescape)
0277     QString _input = getSafeString(input);
0278     auto width = argument.value<int>();
0279     auto partList = _input.split(QLatin1Char(' '), Qt::SkipEmptyParts);
0280     if (partList.isEmpty())
0281         return {};
0282     auto output = partList.takeFirst();
0283     auto pos = output.size() - output.lastIndexOf(QLatin1Char('\n')) - 1;
0284     for (const QString &part : std::as_const(partList)) {
0285         QStringList lines;
0286         if (part.contains(QLatin1Char('\n'))) {
0287             lines = part.split(QLatin1Char('\n'));
0288         } else {
0289             lines.append(part);
0290         }
0291         pos += lines.first().size() + 1;
0292         if (pos > width) {
0293             output.append(QLatin1Char('\n'));
0294             pos += lines.last().size();
0295         } else {
0296             output.append(QLatin1Char(' '));
0297             if (lines.size() > 1)
0298                 pos += lines.last().size();
0299         }
0300         output.append(part);
0301     }
0302     return output;
0303 }
0304 
0305 QVariant FloatFormatFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0306 {
0307     Q_UNUSED(autoescape)
0308     double inputDouble;
0309     switch (input.typeId()) {
0310     case QMetaType::Int:
0311     case QMetaType::UInt:
0312     case QMetaType::LongLong:
0313     case QMetaType::ULongLong:
0314     case QMetaType::Double:
0315         inputDouble = input.toDouble();
0316         break;
0317     default:
0318         inputDouble = getSafeString(input).get().toDouble();
0319     }
0320 
0321     int precision;
0322     if (argument.isValid())
0323         precision = getSafeString(argument).get().toInt();
0324     else
0325         precision = 1;
0326 
0327     return QString::number(inputDouble, 'f', precision);
0328 }
0329 
0330 QVariant SafeSequenceFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0331 {
0332     Q_UNUSED(argument)
0333     Q_UNUSED(autoescape)
0334     QVariantList list;
0335     if (input.userType() == qMetaTypeId<QVariantList>())
0336         for (const QVariant &item : input.value<QVariantList>())
0337             list << markSafe(getSafeString(item));
0338     return list;
0339 }
0340 
0341 QVariant LineBreaksFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0342 {
0343     Q_UNUSED(argument)
0344     auto inputString = getSafeString(input);
0345     static const QRegularExpression re(QStringLiteral("\n{2,}"));
0346     QStringList output;
0347 
0348     for (const QString &bit : inputString.get().split(re)) {
0349         auto _bit = SafeString(bit, inputString.isSafe());
0350         if (autoescape)
0351             _bit = conditionalEscape(_bit);
0352         _bit.get().replace(QLatin1Char('\n'), QStringLiteral("<br />"));
0353         output.append(QStringLiteral("<p>%1</p>").arg(_bit));
0354     }
0355     return markSafe(output.join(QStringLiteral("\n\n")));
0356 }
0357 
0358 QVariant LineBreaksBrFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0359 {
0360     Q_UNUSED(argument)
0361     auto inputString = getSafeString(input);
0362     if (autoescape && isSafeString(input)) {
0363         inputString = conditionalEscape(inputString);
0364     }
0365     return markSafe(inputString.get().replace(QLatin1Char('\n'), QStringLiteral("<br />")));
0366 }
0367 
0368 static QString nofailStringToAscii(const QString &input)
0369 {
0370     QString output;
0371     output.reserve(input.size());
0372 
0373     auto it = input.constBegin();
0374     const auto end = input.constEnd();
0375     static const QChar asciiEndPoint(128);
0376     for (; it != end; ++it)
0377         if (*it < asciiEndPoint)
0378             output.append(*it);
0379 
0380     return output;
0381 }
0382 
0383 QVariant SlugifyFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0384 {
0385     Q_UNUSED(argument)
0386     Q_UNUSED(autoescape)
0387     QString inputString = getSafeString(input);
0388     inputString = inputString.normalized(QString::NormalizationForm_KD);
0389     inputString = nofailStringToAscii(inputString);
0390     inputString = inputString.remove(QRegularExpression(QStringLiteral("[^\\w\\s-]"))).trimmed().toLower();
0391     return markSafe(inputString.replace(QRegularExpression(QStringLiteral("[-\\s]+")), QChar::fromLatin1('-')));
0392 }
0393 
0394 QVariant FileSizeFormatFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0395 {
0396     QVariant ret;
0397 
0398     Q_UNUSED(autoescape)
0399     const auto arg = getSafeString(argument);
0400     bool numberConvert = true;
0401 
0402     qreal size = 0.0F;
0403     if (input.canConvert<qreal>()) {
0404         size = input.toReal(&numberConvert);
0405         if (!numberConvert) {
0406             qWarning("%s", "Failed to convert input file size into floating point value.");
0407         }
0408     } else {
0409         size = getSafeString(input).get().toDouble(&numberConvert);
0410         if (!numberConvert) {
0411             qWarning("%s", "Failed to convert input file size into floating point value.");
0412         }
0413     }
0414 
0415     int unitSystem = 10;
0416     int precision = 2;
0417     qreal multiplier = 1.0F;
0418 
0419     if (!arg.get().isEmpty()) {
0420         const auto argList = arg.get().split(QLatin1Char(','), Qt::SkipEmptyParts);
0421         const auto numArgs = argList.size();
0422         if (numArgs > 0) {
0423             unitSystem = argList.at(0).toInt(&numberConvert);
0424             if (!numberConvert) {
0425                 qWarning("%s",
0426                          "Failed to convert filse size format unit system into "
0427                          "integer. Falling back to default 10.");
0428                 unitSystem = 10;
0429             }
0430         }
0431 
0432         if (numArgs > 1) {
0433             precision = argList.at(1).toInt(&numberConvert);
0434             if (!numberConvert) {
0435                 qWarning("%s",
0436                          "Failed to convert file size format decimal precision "
0437                          "into integer. Falling back to default 2.");
0438                 precision = 2;
0439             }
0440         }
0441 
0442         if (numArgs > 2) {
0443             multiplier = argList.at(2).toDouble(&numberConvert);
0444             if (!numberConvert) {
0445                 qWarning("%s",
0446                          "Failed to convert file size format multiplier into "
0447                          "double value. Falling back to default 1.0");
0448                 multiplier = 1.0F;
0449             } else {
0450                 if (multiplier == 0.0F) {
0451                     qWarning("%s",
0452                              "It makes no sense to multiply the file size by zero. "
0453                              "Using default value 1.0.");
0454                     multiplier = 1.0F;
0455                 }
0456             }
0457         }
0458     }
0459 
0460     const double sizeMult = size * multiplier;
0461 
0462     if (unitSystem == 10) {
0463         if ((sizeMult > -1000) && (sizeMult < 1000)) {
0464             precision = 0;
0465         }
0466     } else if (unitSystem == 2) {
0467         if ((sizeMult > -1024) && (sizeMult < 1024)) {
0468             precision = 0;
0469         }
0470     }
0471 
0472     const std::pair<qreal, QString> sizePair = calcFileSize(size, unitSystem, multiplier);
0473 
0474     const QString retString = QString::number(sizePair.first, 'f', precision) + QLatin1Char(' ') + sizePair.second;
0475 
0476     ret.setValue(retString);
0477 
0478     return ret;
0479 }
0480 
0481 QVariant TruncateCharsFilter::doFilter(const QVariant &input, const QVariant &argument, bool autoescape) const
0482 {
0483     Q_UNUSED(autoescape)
0484     QString retString = getSafeString(input);
0485     int count = getSafeString(argument).get().toInt();
0486 
0487     if (retString.length() < count)
0488         return retString;
0489     retString.truncate(count);
0490     retString.append(QStringLiteral("..."));
0491     return retString;
0492 }