This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Ian Zepp <icszepp@islc.net>
SPDX-FileCopyrightText: 2006 Dominic Battre <dominic@battre.de>
SPDX-FileCopyrightText: 2006 Martin Pool <mbp@canonical.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kstringhandler.h"
#include <stdlib.h>
#include <QList>
#include <QRegularExpression>
QString KStringHandler::capwords(const QString &text)
{
if (text.isEmpty()) {
return text;
}
const QString strippedText = text.trimmed();
const QString space = QString(QLatin1Char(' '));
const QStringList words = capwords(strippedText.split(space));
QString result = text;
result.replace(strippedText, words.join(space));
return result;
}
QStringList KStringHandler::capwords(const QStringList &list)
{
QStringList tmp = list;
for (auto &str : tmp) {
str[0] = str.at(0).toUpper();
}
return tmp;
}
QString KStringHandler::lsqueeze(const QString &str, const int maxlen)
{
if (str.length() > maxlen) {
const int part = maxlen - 3;
return QLatin1String("...") + QStringView(str).right(part);
} else {
return str;
}
}
QString KStringHandler::csqueeze(const QString &str, const int maxlen)
{
if (str.length() > maxlen && maxlen > 3) {
const int part = (maxlen - 3) / 2;
const QStringView strView{str};
return strView.left(part) + QLatin1String("...") + strView.right(part);
} else {
return str;
}
}
QString KStringHandler::rsqueeze(const QString &str, const int maxlen)
{
if (str.length() > maxlen) {
const int part = maxlen - 3;
return QStringView(str).left(part) + QLatin1String("...");
} else {
return str;
}
}
QStringList KStringHandler::perlSplit(const QStringView sep, const QStringView str, int max)
{
const bool ignoreMax = max == 0;
const int sepLength = sep.size();
QStringList list;
int searchStart = 0;
int sepIndex = str.indexOf(sep, searchStart);
while (sepIndex != -1 && (ignoreMax || list.count() < max - 1)) {
const auto chunk = str.mid(searchStart, sepIndex - searchStart);
if (!chunk.isEmpty()) {
list.append(chunk.toString());
}
searchStart = sepIndex + sepLength;
sepIndex = str.indexOf(sep, searchStart);
}
const auto lastChunk = str.mid(searchStart, str.length() - searchStart);
if (!lastChunk.isEmpty()) {
list.append(lastChunk.toString());
}
return list;
}
QStringList KStringHandler::perlSplit(const QString &sep, const QString &s, int max)
{
return perlSplit(QStringView(sep), QStringView(s), max);
}
QStringList KStringHandler::perlSplit(const QChar &sep, const QString &str, int max)
{
return perlSplit(QStringView(&sep, 1), QStringView(str), max);
}
QStringList KStringHandler::perlSplit(const QRegularExpression &sep, const QString &str, int max)
{
if (str.isEmpty()) {
return QStringList();
}
const bool ignoreMax = max == 0;
QStringList list;
int start = 0;
const QStringView strView(str);
QRegularExpression separator(sep);
separator.setPatternOptions(QRegularExpression::UseUnicodePropertiesOption);
QRegularExpressionMatchIterator iter = separator.globalMatchView(strView);
QRegularExpressionMatch match;
while (iter.hasNext() && (ignoreMax || list.count() < max - 1)) {
match = iter.next();
const QStringView chunk = strView.mid(start, match.capturedStart() - start);
if (!chunk.isEmpty()) {
list.append(chunk.toString());
}
start = match.capturedEnd();
}
const QStringView lastChunk = strView.mid(start, strView.size() - start);
if (!lastChunk.isEmpty()) {
list.append(lastChunk.toString());
}
return list;
}
QString KStringHandler::tagUrls(const QString &text)
{
QString richText(text);
static const QRegularExpression urlEx(QStringLiteral(R"((www\.(?!\.)|(fish|ftp|http|https)://[\d\w./,:_~?=&;#@\-+%$()]+))"),
QRegularExpression::UseUnicodePropertiesOption);
richText.replace(urlEx, QStringLiteral("<a href=\"\\1\">\\1</a>"));
return richText;
}
QString KStringHandler::obscure(const QString &str)
{
QString result;
for (const QChar ch : str) {
const ushort uc = ch.unicode();
result += (uc <= 0x21) ? ch : QChar(0x1001F - uc);
}
return result;
}
static inline bool containsSpaces(const QString &text)
{
for (int i = 0; i < text.length(); i++) {
const QChar c = text[i];
if (c.isSpace()) {
return true;
}
}
return false;
}
QString KStringHandler::preProcessWrap(const QString &text)
{
const QChar zwsp(0x200b);
QString result;
result.reserve(text.length());
const bool containsSpaces = ::containsSpaces(text);
for (int i = 0; i < text.length(); i++) {
const QChar c = text[i];
const bool openingParens = (c == QLatin1Char('(') || c == QLatin1Char('{') || c == QLatin1Char('['));
const bool singleQuote = (c == QLatin1Char('\''));
const bool closingParens = (c == QLatin1Char(')') || c == QLatin1Char('}') || c == QLatin1Char(']'));
const bool breakAfter = (closingParens || c.isPunct() || c.isSymbol());
const bool isLastChar = i == (text.length() - 1);
const bool isLower = c.isLower();
const bool nextIsUpper = !isLastChar && text[i + 1].isUpper();
const bool nextIsSpace = isLastChar || text[i + 1].isSpace();
const bool prevIsSpace = (i == 0 || text[i - 1].isSpace() || result[result.length() - 1] == zwsp);
if (openingParens && !prevIsSpace) {
result += zwsp;
}
if (singleQuote && !prevIsSpace) {
result += QChar(0x2060);
}
result += c;
const bool isCamelCase = !containsSpaces && isLower && nextIsUpper;
if (isCamelCase || (breakAfter && !openingParens && !nextIsSpace && !singleQuote)) {
result += zwsp;
}
}
return result;
}
int KStringHandler::logicalLength(const QString &text)
{
int length = 0;
const auto chrs = text.toUcs4();
for (const auto chr : chrs) {
const auto script = QChar::script(chr);
if (script == QChar::Script_Han
|| script == QChar::Script_Hangul
|| script == QChar::Script_Hiragana
|| script == QChar::Script_Katakana
|| script == QChar::Script_Yi
|| QChar::isHighSurrogate(chr)) {
length += 2;
} else {
length += 1;
}
}
return length;
}