Files
create/src/App/IndexedName.cpp
Chris Hennes 45c0b634fa App: Clean up IndexedName and add tests
Fixes the matching algorithm when provided a vector of existing names:
The original algorithm was equivalent to a 'startswith' algorithm, when it should
have been testing for exact and complete string equality. This also does some
refactoring to rename and clarify variables and functions, simplifies some
functions by using standard library calls when possible, and addresses various
linter complaints. It also applies our current clang-format to the files.

Co-authored-by: Ajinkya Dahale <AjinkyaDahale@users.noreply.github.com>
2023-03-14 13:08:23 -05:00

123 lines
5.2 KiB
C++

// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* Copyright (c) 2022 Zheng, Lei (realthunder) <realthunder.dev@gmail.com>*
* Copyright (c) 2023 FreeCAD Project Association *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
// NOLINTNEXTLINE
#include "PreCompiled.h"
#ifndef _PreComp_
# include <cstdlib>
# include <unordered_set>
#endif
#include "IndexedName.h"
using namespace Data;
/// Check whether the input character is an underscore or an ASCII letter a-Z or A-Z
inline bool isInvalidChar(char test)
{
return test != '_' && (test < 'a' || test > 'z' ) && (test < 'A' || test > 'Z');
}
/// Get the integer suffix of name. Returns a tuple of (suffix, suffixPosition). Calling code
/// should check to ensure that suffixPosition is not equal to nameLength (in which case there was no
/// suffix).
///
/// \param name The name to check
/// \param nameLength The length of the string in name
/// \returns An integer pair of the suffix itself and the position of that suffix in name
std::pair<int,int> getIntegerSuffix(const char *name, int nameLength)
{
int suffixPosition {nameLength - 1};
for (; suffixPosition >= 0; --suffixPosition) {
// When we support C++20 we can use std::span<> to eliminate the clang-tidy warning
// NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic
if (!isdigit(name[suffixPosition])) {
break;
}
}
++suffixPosition;
int suffix {0};
if (suffixPosition < nameLength) {
// When we support C++20 we can use std::span<> to eliminate the clang-tidy warning
// NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic
suffix = std::atoi(name + suffixPosition);
}
return std::make_pair(suffix, suffixPosition);
}
void IndexedName::set(
const char* name,
int length,
const std::vector<const char*>& allowedNames,
bool allowOthers)
{
// Storage for names that we weren't given external storage for
static std::unordered_set<ByteArray, ByteArrayHasher> NameSet;
if (length < 0) {
length = static_cast<int>(std::strlen(name));
}
// Name typically ends with an integer: find that integer
auto [suffix, suffixPosition] = getIntegerSuffix(name, length);
if (suffixPosition < length) {
this->index = suffix;
}
// Make sure that every character is either an ASCII letter (upper or lowercase), or an
// underscore. If any other character appears, reject the entire string.
// When we support C++20 we can use std::span<> to eliminate the clang-tidy warning
// NOLINTNEXTLINE cppcoreguidelines-pro-bounds-pointer-arithmetic
if (std::any_of(name, name+suffixPosition, isInvalidChar)) {
this->type = "";
return;
}
// If a list of allowedNames was provided, see if our set name matches one of those allowedNames: if it
// does, reference that memory location and return.
for (const auto *typeName : allowedNames) {
if (std::strncmp(name, typeName, suffixPosition) == 0) {
this->type = typeName;
return;
}
}
// If the type was NOT in the list of allowedNames, but the caller has set the allowOthers flag to
// true, then add the new type to the static NameSet (if it is not already there).
if (allowOthers) {
auto res = NameSet.insert(ByteArray(QByteArray::fromRawData(name, suffixPosition)));
if (res.second /*The insert succeeded (the type was new)*/) {
// Make sure that the data in the set is a unique (unshared) copy of the text
res.first->ensureUnshared();
}
this->type = res.first->bytes.constData();
}
else {
// The passed-in type is not in the allowed list, and allowOthers was not true, so don't
// store the type
this->type = "";
}
}