KiCad PCB EDA Suite
Loading...
Searching...
No Matches
increment.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "increment.h"
21
22#include <wx/wxcrt.h>
23
24#include <cmath>
25#include <iostream>
26#include <limits>
27#include <regex>
28
29
30KICOMMON_API bool IncrementString( wxString& name, int aIncrement )
31{
32 if( name.IsEmpty() )
33 return true;
34
35 wxString suffix;
36 wxString digits;
37 wxString outputFormat;
38 wxString outputNumber;
39 int ii = name.Len() - 1;
40 int dCount = 0;
41
42 while( ii >= 0 && !wxIsdigit( name.GetChar( ii ) ) )
43 {
44 suffix = name.GetChar( ii ) + suffix;
45 ii--;
46 }
47
48 while( ii >= 0 && wxIsdigit( name.GetChar( ii ) ) )
49 {
50 digits = name.GetChar( ii ) + digits;
51 ii--;
52 dCount++;
53 }
54
55 if( digits.IsEmpty() )
56 return true;
57
58 long number = 0;
59
60 if( digits.ToLong( &number ) )
61 {
62 number += aIncrement;
63
64 // Don't let result go below zero
65 if( number > -1 )
66 {
67 name.Remove( ii + 1 );
68
69 //write out a format string with correct number of leading zeroes
70 outputFormat.Printf( wxS( "%%0%dld" ), dCount );
71
72 //write out the number using the format string
73 outputNumber.Printf( outputFormat, number );
74 name << outputNumber << suffix;
75 return true;
76 }
77 }
78
79 return false;
80}
81
82
83std::optional<wxString> STRING_INCREMENTER::Increment( const wxString& aStr, int aDelta,
84 size_t aRightIndex ) const
85{
86 if( aStr.IsEmpty() )
87 return std::nullopt;
88
89 // Slice the UTF-8 encoding, not the wxString - the regexes report byte offsets, and mixing
90 // the two underflows the length arithmetic on non-ASCII input and never shortens the string
91 std::string remaining = aStr.utf8_string();
92 std::vector<std::pair<std::string, STRING_PART_TYPE>> parts;
93 size_t goodParts = 0;
94
95 // Keep popping chunks off the string until we have what we need
96 // (compare against aRightIndex directly so a SIZE_MAX index can't wrap the target to zero)
97 while( goodParts <= aRightIndex && !remaining.empty() )
98 {
99 static const std::regex integerRegex( R"(\d+$)" );
100
101 // ABC or abc but not Abc
102 static const std::regex sameCaseAlphabetRegex( R"(([a-z]+|[A-Z]+)$)" );
103
104 // Skippables - for now anything that isn't a letter or number
105 static const std::regex skipRegex( R"([^a-zA-Z0-9]+$)" );
106
107 std::smatch match;
108
109 if( std::regex_search( remaining, match, integerRegex ) )
110 {
111 parts.push_back( { match.str(), STRING_PART_TYPE::INTEGER } );
112 goodParts++;
113 }
114 else if( std::regex_search( remaining, match, sameCaseAlphabetRegex ) )
115 {
116 parts.push_back( { match.str(), STRING_PART_TYPE::ALPHABETIC } );
117 goodParts++;
118 }
119 else if( std::regex_search( remaining, match, skipRegex ) )
120 {
121 parts.push_back( { match.str(), STRING_PART_TYPE::SKIP } );
122 }
123 else
124 {
125 // Out of ideas
126 break;
127 }
128
129 remaining.erase( remaining.size() - match.str().size() );
130 }
131
132 // Couldn't find the part we wanted
133 if( goodParts <= aRightIndex )
134 return std::nullopt;
135
136 // The incrementable parts are ASCII by construction, so the round trip is lossless
137 wxString part = wxString::FromUTF8( parts.back().first );
138
139 if( !incrementPart( part, parts.back().second, aDelta ) )
140 return std::nullopt;
141
142 parts.back().first = part.utf8_string();
143
144 // Reassemble the string - the left-over part, then parts in reverse
145 std::string result = remaining;
146
147 for( auto it = parts.rbegin(); it != parts.rend(); ++it )
148 {
149 result += it->first;
150 }
151
152 return wxString::FromUTF8( result );
153}
154
155
156static bool containsIOSQXZ( const wxString& aStr )
157{
158 static const wxString iosqxz = "IOSQXZ";
159
160 for( const wxUniChar& c : aStr )
161 {
162 if( iosqxz.Contains( c ) )
163 return true;
164 }
165
166 return false;
167}
168
169
170bool STRING_INCREMENTER::incrementPart( wxString& aPart, STRING_PART_TYPE aType, int aDelta ) const
171{
172 switch( aType )
173 {
175 {
176 long number = 0;
177 bool zeroPadded = aPart.StartsWith( '0' );
178 size_t oldLen = aPart.Len();
179
180 if( aPart.ToLong( &number ) )
181 {
182 // Test the sum before forming it; signed overflow is UB and the compiler is free
183 // to discard the range check below
184 if( aDelta > 0 && number > std::numeric_limits<long>::max() - aDelta )
185 return false;
186
187 number += aDelta;
188
189 // Going below zero makes things awkward
190 // and is not usually that useful.
191 if( number < 0 )
192 return false;
193
194 aPart.Printf( "%ld", number );
195
196 // If the number was zero-padded, we need to re-pad it
197 // (carrying into a wider number drops the padding rather than underflowing)
198 if( zeroPadded && aPart.Len() < oldLen )
199 aPart.Prepend( wxString( '0', oldLen - aPart.Len() ) );
200
201 return true;
202 }
203
204 break;
205 }
207 {
208 // Covert to uppercase
209 wxString upper = aPart.Upper();
210 bool wasUpper = aPart == upper;
211
212 static const wxString alphabetFull = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
213 static const wxString alphaNoIOSQXZ = "ABCDEFGHJKLMNPRTUVWY";
214
215 const wxString& alpha =
216 ( m_SkipIOSQXZ & !containsIOSQXZ( aPart ) ) ? alphaNoIOSQXZ : alphabetFull;
217
218 int index = IndexFromAlphabetic( upper, alpha );
219
220 // Something was not in the alphabet
221 if( index == -1 )
222 return false;
223
224 // It's such a big number that we don't want to increment it
226 return false;
227
228 // Widen before adding; index is unbounded once m_AlphabeticMaxIndex is disabled
229 const long long nextIndex = static_cast<long long>( index ) + aDelta;
230
231 if( nextIndex < 0 || nextIndex > std::numeric_limits<int>::max() )
232 return false;
233
234 wxString newStr = AlphabeticFromIndex( static_cast<size_t>( nextIndex ), alpha, true );
235
236 if( !wasUpper )
237 newStr = newStr.Lower();
238
239 aPart = newStr;
240
241 return true;
242 }
243 case STRING_PART_TYPE::SKIP: break;
244 }
245
246 return false;
247}
248
249
250KICOMMON_API int IndexFromAlphabetic( const wxString& aStr, const wxString& aAlphabet )
251{
252 int index = 0;
253 const int radix = aAlphabet.Length();
254
255 for( size_t i = 0; i < aStr.Len(); i++ )
256 {
257 int alphaIndex = aAlphabet.Find( aStr[i] );
258
259 if( alphaIndex == wxNOT_FOUND )
260 return -1;
261
262 if( i != aStr.Len() - 1 )
263 alphaIndex++;
264
265 index += alphaIndex * pow( radix, aStr.Len() - 1 - i );
266 }
267
268 return index;
269}
270
271
272wxString KICOMMON_API AlphabeticFromIndex( size_t aN, const wxString& aAlphabet,
273 bool aZeroBasedNonUnitCols )
274{
275 wxString itemNum;
276 bool firstRound = true;
277 const int radix = aAlphabet.Length();
278
279 do
280 {
281 int modN = aN % radix;
282
283 if( aZeroBasedNonUnitCols && !firstRound )
284 modN--; // Start the "tens/hundreds/etc column" at "Ax", not "Bx"
285
286 itemNum.insert( 0, 1, aAlphabet[modN] );
287
288 aN /= radix;
289 firstRound = false;
290 } while( aN );
291
292 return itemNum;
293}
int index
const char * name
bool incrementPart(wxString &aPart, STRING_PART_TYPE aType, int aDelta) const
std::optional< wxString > Increment(const wxString &aStr, int aDelta, size_t aRightIndex) const
Increment the n-th part from the right of the given string.
Definition increment.cpp:83
KICOMMON_API int IndexFromAlphabetic(const wxString &aStr, const wxString &aAlphabet)
Attempt to convert a string to an integer, assuming it is an alphabetic string like "A",...
wxString KICOMMON_API AlphabeticFromIndex(size_t aN, const wxString &aAlphabet, bool aZeroBasedNonUnitCols)
Get an alphabetic string like A, B, ... Z, AA, AB, ... ZZ, AAA, ...
static bool containsIOSQXZ(const wxString &aStr)
KICOMMON_API bool IncrementString(wxString &name, int aIncrement)
Generic string incrementer.
Definition increment.cpp:30
KICOMMON_API wxString AlphabeticFromIndex(size_t aN, const wxString &aAlphabet, bool aZeroBasedNonUnitCols)
Get an alphabetic string like A, B, ... Z, AA, AB, ... ZZ, AAA, ...
KICOMMON_API int IndexFromAlphabetic(const wxString &aStr, const wxString &aAlphabet)
Attempt to convert a string to an integer, assuming it is an alphabetic string like "A",...
#define KICOMMON_API
Definition kicommon.h:27
wxString result
Test unit parsing edge cases and error handling.