280 lines
7.7 KiB
C++
280 lines
7.7 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include "increment.h"
|
|
|
|
#include <wx/wxcrt.h>
|
|
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <regex>
|
|
|
|
|
|
KICOMMON_API bool IncrementString( wxString& name, int aIncrement )
|
|
{
|
|
if( name.IsEmpty() )
|
|
return true;
|
|
|
|
wxString suffix;
|
|
wxString digits;
|
|
wxString outputFormat;
|
|
wxString outputNumber;
|
|
int ii = name.Len() - 1;
|
|
int dCount = 0;
|
|
|
|
while( ii >= 0 && !wxIsdigit( name.GetChar( ii ) ) )
|
|
{
|
|
suffix = name.GetChar( ii ) + suffix;
|
|
ii--;
|
|
}
|
|
|
|
while( ii >= 0 && wxIsdigit( name.GetChar( ii ) ) )
|
|
{
|
|
digits = name.GetChar( ii ) + digits;
|
|
ii--;
|
|
dCount++;
|
|
}
|
|
|
|
if( digits.IsEmpty() )
|
|
return true;
|
|
|
|
long number = 0;
|
|
|
|
if( digits.ToLong( &number ) )
|
|
{
|
|
number += aIncrement;
|
|
|
|
// Don't let result go below zero
|
|
|
|
if( number > -1 )
|
|
{
|
|
name.Remove( ii + 1 );
|
|
//write out a format string with correct number of leading zeroes
|
|
outputFormat.Printf( wxS( "%%0%dld" ), dCount );
|
|
//write out the number using the format string
|
|
outputNumber.Printf( outputFormat, number );
|
|
name << outputNumber << suffix;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
std::optional<wxString> STRING_INCREMENTER::Increment( const wxString& aStr, int aDelta,
|
|
size_t aRightIndex ) const
|
|
{
|
|
if( aStr.IsEmpty() )
|
|
return std::nullopt;
|
|
|
|
wxString remaining = aStr;
|
|
std::vector<std::pair<wxString, STRING_PART_TYPE>> parts;
|
|
size_t goodParts = 0;
|
|
|
|
// Keep popping chunks off the string until we have what we need
|
|
while( goodParts < ( aRightIndex + 1 ) && !remaining.IsEmpty() )
|
|
{
|
|
static const std::regex integerRegex( R"(\d+$)" );
|
|
// ABC or abc but not Abc
|
|
static const std::regex sameCaseAlphabetRegex( R"(([a-z]+|[A-Z]+)$)" );
|
|
// Skippables - for now anything that isn't a letter or number
|
|
static const std::regex skipRegex( R"([^a-zA-Z0-9]+$)" );
|
|
|
|
std::string remainingStr = remaining.ToStdString();
|
|
std::smatch match;
|
|
if( std::regex_search( remainingStr, match, integerRegex ) )
|
|
{
|
|
parts.push_back( { match.str(), STRING_PART_TYPE::INTEGER } );
|
|
remaining = remaining.Left( remaining.Len() - match.str().size() );
|
|
goodParts++;
|
|
}
|
|
else if( std::regex_search( remainingStr, match, sameCaseAlphabetRegex ) )
|
|
{
|
|
parts.push_back( { match.str(), STRING_PART_TYPE::ALPHABETIC } );
|
|
remaining = remaining.Left( remaining.Len() - match.str().size() );
|
|
goodParts++;
|
|
}
|
|
else if( std::regex_search( remainingStr, match, skipRegex ) )
|
|
{
|
|
parts.push_back( { match.str(), STRING_PART_TYPE::SKIP } );
|
|
remaining = remaining.Left( remaining.Len() - match.str().size() );
|
|
}
|
|
else
|
|
{
|
|
// Out of ideas
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Couldn't find the part we wanted
|
|
if( goodParts < aRightIndex + 1 )
|
|
return std::nullopt;
|
|
|
|
// Increment the part we wanted
|
|
bool didIncrement = incrementPart( parts.back().first, parts.back().second, aDelta );
|
|
|
|
if( !didIncrement )
|
|
return std::nullopt;
|
|
|
|
// Reassemble the string - the left-over part, then parts in reverse
|
|
wxString result = remaining;
|
|
for( auto it = parts.rbegin(); it != parts.rend(); ++it )
|
|
{
|
|
result << it->first;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
static bool containsIOSQXZ( const wxString& aStr )
|
|
{
|
|
static const wxString iosqxz = "IOSQXZ";
|
|
for( const wxUniChar& c : aStr )
|
|
{
|
|
if( iosqxz.Contains( c ) )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool STRING_INCREMENTER::incrementPart( wxString& aPart, STRING_PART_TYPE aType, int aDelta ) const
|
|
{
|
|
switch( aType )
|
|
{
|
|
case STRING_PART_TYPE::INTEGER:
|
|
{
|
|
long number = 0;
|
|
bool zeroPadded = aPart.StartsWith( '0' );
|
|
size_t oldLen = aPart.Len();
|
|
|
|
if( aPart.ToLong( &number ) )
|
|
{
|
|
number += aDelta;
|
|
|
|
// Going below zero makes things awkward
|
|
// and is not usually that useful.
|
|
if( number < 0 )
|
|
return false;
|
|
|
|
aPart.Printf( "%ld", number );
|
|
|
|
// If the number was zero-padded, we need to re-pad it
|
|
if( zeroPadded )
|
|
{
|
|
aPart = wxString( "0", oldLen - aPart.Len() ) + aPart;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case STRING_PART_TYPE::ALPHABETIC:
|
|
{
|
|
// Covert to uppercase
|
|
wxString upper = aPart.Upper();
|
|
bool wasUpper = aPart == upper;
|
|
|
|
static const wxString alphabetFull = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
static const wxString alphaNoIOSQXZ = "ABCDEFGHJKLMNPRTUVWY";
|
|
|
|
const wxString& alpha =
|
|
( m_SkipIOSQXZ & !containsIOSQXZ( aPart ) ) ? alphaNoIOSQXZ : alphabetFull;
|
|
|
|
int index = IndexFromAlphabetic( upper, alpha );
|
|
|
|
// Something was not in the alphabet
|
|
if( index == -1 )
|
|
return false;
|
|
|
|
// It's such a big number that we don't want to increment it
|
|
if( index > m_AlphabeticMaxIndex && m_AlphabeticMaxIndex >= 0 )
|
|
return false;
|
|
|
|
index += aDelta;
|
|
|
|
if( index < 0 )
|
|
return false;
|
|
|
|
wxString newStr = AlphabeticFromIndex( index, alpha, true );
|
|
|
|
if( !wasUpper )
|
|
newStr = newStr.Lower();
|
|
|
|
aPart = newStr;
|
|
|
|
return true;
|
|
}
|
|
case STRING_PART_TYPE::SKIP: break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
KICOMMON_API int IndexFromAlphabetic( const wxString& aStr, const wxString& aAlphabet )
|
|
{
|
|
int index = 0;
|
|
const int radix = aAlphabet.Length();
|
|
|
|
for( size_t i = 0; i < aStr.Len(); i++ )
|
|
{
|
|
int alphaIndex = aAlphabet.Find( aStr[i] );
|
|
|
|
if( alphaIndex == wxNOT_FOUND )
|
|
return -1;
|
|
|
|
if( i != aStr.Len() - 1 )
|
|
alphaIndex++;
|
|
|
|
index += alphaIndex * pow( radix, aStr.Len() - 1 - i );
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
|
|
wxString KICOMMON_API AlphabeticFromIndex( size_t aN, const wxString& aAlphabet,
|
|
bool aZeroBasedNonUnitCols )
|
|
{
|
|
wxString itemNum;
|
|
bool firstRound = true;
|
|
const int radix = aAlphabet.Length();
|
|
|
|
do
|
|
{
|
|
int modN = aN % radix;
|
|
|
|
if( aZeroBasedNonUnitCols && !firstRound )
|
|
modN--; // Start the "tens/hundreds/etc column" at "Ax", not "Bx"
|
|
|
|
itemNum.insert( 0, 1, aAlphabet[modN] );
|
|
|
|
aN /= radix;
|
|
firstRound = false;
|
|
} while( aN );
|
|
|
|
return itemNum;
|
|
}
|