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("&")); 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 }