Revision control

Copy as Markdown

// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant
#include "qqmlsignalnames_p.h"
#include <iterator>
#include <algorithm>
#include <optional>
#include <string>
QT_BEGIN_NAMESPACE
using namespace Qt::Literals;
static constexpr const QLatin1String On("on");
static constexpr const QLatin1String Changed("Changed");
static constexpr const qsizetype StrlenOn = On.length();
static constexpr const qsizetype StrlenChanged = Changed.length();
static std::optional<qsizetype> firstLetterIdx(QStringView name, qsizetype removePrefix = 0,
qsizetype removeSuffix = 0)
{
auto end = std::prev(name.cend(), removeSuffix);
auto result = std::find_if(std::next(name.cbegin(), removePrefix), end,
[](const QChar &c) { return c.isLetter(); });
if (result != end)
return std::distance(name.begin(), result);
return {};
}
static std::optional<QChar> firstLetter(QStringView name, qsizetype removePrefix = 0,
qsizetype removeSuffix = 0)
{
if (auto idx = firstLetterIdx(name, removePrefix, removeSuffix))
return name[*idx];
return {};
}
enum ChangeCase { ToUpper, ToLower };
static void changeCaseOfFirstLetter(QString &name, ChangeCase option, qsizetype removePrefix = 0,
qsizetype removeSuffix = 0)
{
auto idx = firstLetterIdx(name, removePrefix, removeSuffix);
if (!idx)
return;
QChar &changeMe = name[*idx];
changeMe = option == ToUpper ? changeMe.toUpper() : changeMe.toLower();
};
static std::optional<QString> toQStringData(std::optional<QStringView> view)
{
if (view)
return view->toString();
return std::nullopt;
}
static QByteArray toUtf8Data(QUtf8StringView view)
{
return QByteArray(view.data(), view.size());
}
static std::optional<QByteArray> toUtf8Data(std::optional<QUtf8StringView> view)
{
if (view)
return toUtf8Data(*view);
return std::nullopt;
}
/*!
\internal
\class QQmlSignalNames
QQmlSignalNames contains a list of helper methods to manipulate signal names.
Always try to use the most specific one, as combining them might lead to incorrect
results like wrong upper/lower case, for example.
*/
/*!
\internal
Concatenate a prefix to a property name and uppercases the first letter of the property name.
*/
QString QQmlSignalNames::addPrefixToPropertyName(QStringView prefix, QStringView propertyName)
{
QString result = prefix.toString().append(propertyName);
changeCaseOfFirstLetter(result, ToUpper, prefix.size());
return result;
}
QString QQmlSignalNames::propertyNameToChangedSignalName(QStringView property)
{
return property.toString().append(Changed);
}
QByteArray QQmlSignalNames::propertyNameToChangedSignalName(QUtf8StringView property)
{
return toUtf8Data(property).append(QByteArrayView(Changed));
}
QString QQmlSignalNames::propertyNameToChangedHandlerName(QStringView property)
{
return propertyNameToChangedSignalName(signalNameToHandlerName(property));
}
template<typename View>
std::optional<View> changedSignalNameToPropertyNameTemplate(View changeSignal)
{
const qsizetype changeSignalSize = changeSignal.size();
if (changeSignalSize < StrlenChanged || changeSignal.last(StrlenChanged).compare(Changed) != 0)
return std::nullopt;
const View result = changeSignal.sliced(0, changeSignalSize - StrlenChanged);
if (!result.isEmpty())
return result;
return {};
}
/*!
\internal
Obtain a propertyName from its changed signal handler.
Do not call this on a value obtained from handlerNameToSignalName! Instead use
changedHandlerNameToPropertyName() directly. Otherwise you might end up with a wrong
capitalization of _Changed for "on_Changed", for example.
*/
std::optional<QString> QQmlSignalNames::changedSignalNameToPropertyName(QStringView signalName)
{
return toQStringData(changedSignalNameToPropertyNameTemplate(signalName));
}
std::optional<QByteArray>
QQmlSignalNames::changedSignalNameToPropertyName(QUtf8StringView signalName)
{
return toUtf8Data(changedSignalNameToPropertyNameTemplate(signalName));
}
/*!
\internal
Returns a property name from \a changedHandler.
This fails for property names starting with an upper-case letter, as it will lower-case it in the
process.
*/
std::optional<QString> QQmlSignalNames::changedHandlerNameToPropertyName(QStringView handler)
{
if (!isChangedHandlerName(handler))
return {};
if (auto withoutChangedSuffix = changedSignalNameToPropertyName(handler)) {
return handlerNameToSignalName(*withoutChangedSuffix);
}
return {};
}
QString QQmlSignalNames::signalNameToHandlerName(QAnyStringView signal)
{
QString handlerName;
handlerName.reserve(StrlenOn + signal.length());
handlerName.append(On);
signal.visit([&handlerName](auto &&s) { handlerName.append(s); });
changeCaseOfFirstLetter(handlerName, ToUpper, StrlenOn);
return handlerName;
}
enum HandlerType { ChangedHandler, Handler };
template<HandlerType type>
static std::optional<QString> handlerNameToSignalNameHelper(QStringView handler)
{
if (!QQmlSignalNames::isHandlerName(handler))
return {};
QString signalName = handler.sliced(StrlenOn).toString();
Q_ASSERT(!signalName.isEmpty());
changeCaseOfFirstLetter(signalName, ToLower, 0, type == ChangedHandler ? StrlenChanged : 0);
return signalName;
}
/*!
\internal
Returns a signal name from \a handlerName string. Do not use it on changed handlers, see
changedHandlerNameToSignalName for that!
*/
std::optional<QString> QQmlSignalNames::handlerNameToSignalName(QStringView handler)
{
return handlerNameToSignalNameHelper<Handler>(handler);
}
/*!
\internal
Returns a signal name from \a handlerName string. Do not use it on changed handlers, see
changedHandlerNameToSignalName for that! Accepts improperly capitalized handler names and
incorrectly resolves signal names that start with '_' or '$'.
*/
std::optional<QString> QQmlSignalNames::badHandlerNameToSignalName(QStringView handler)
{
if (handler.size() <= StrlenOn || !handler.startsWith(On))
return {};
QString signalName = handler.sliced(StrlenOn).toString();
// This is quite wrong. But we need it for backwards compatibility.
signalName.front() = signalName.front().toLower();
return signalName;
}
/*!
\internal
Returns a signal name from \a changedHandlerName string. Makes sure not to lowercase the 'C' from
Changed.
*/
std::optional<QString> QQmlSignalNames::changedHandlerNameToSignalName(QStringView handler)
{
return handlerNameToSignalNameHelper<ChangedHandler>(handler);
}
bool QQmlSignalNames::isChangedSignalName(QStringView signalName)
{
if (signalName.size() <= StrlenChanged || !signalName.endsWith(Changed))
return false;
if (auto letter = firstLetter(signalName, 0, StrlenChanged))
return letter->isLower();
return true;
}
bool QQmlSignalNames::isChangedHandlerName(QStringView signalName)
{
if (signalName.size() <= (StrlenOn + StrlenChanged)
|| !signalName.startsWith(On)
|| !signalName.endsWith(Changed)) {
return false;
}
if (auto letter = firstLetter(signalName, StrlenOn, StrlenChanged))
return letter->isUpper();
return true;
}
bool QQmlSignalNames::isHandlerName(QStringView signalName)
{
if (signalName.size() <= StrlenOn || !signalName.startsWith(On))
return false;
if (auto letter = firstLetter(signalName, StrlenOn))
return letter->isUpper();
return true;
}
QT_END_NAMESPACE