kicad/common/increment.cpp

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;
}