You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

288 lines
7.7 KiB

  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, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include "increment.h"
  24. #include <wx/wxcrt.h>
  25. #include <cmath>
  26. #include <iostream>
  27. #include <regex>
  28. KICOMMON_API bool IncrementString( wxString& name, int aIncrement )
  29. {
  30. if( name.IsEmpty() )
  31. return true;
  32. wxString suffix;
  33. wxString digits;
  34. wxString outputFormat;
  35. wxString outputNumber;
  36. int ii = name.Len() - 1;
  37. int dCount = 0;
  38. while( ii >= 0 && !wxIsdigit( name.GetChar( ii ) ) )
  39. {
  40. suffix = name.GetChar( ii ) + suffix;
  41. ii--;
  42. }
  43. while( ii >= 0 && wxIsdigit( name.GetChar( ii ) ) )
  44. {
  45. digits = name.GetChar( ii ) + digits;
  46. ii--;
  47. dCount++;
  48. }
  49. if( digits.IsEmpty() )
  50. return true;
  51. long number = 0;
  52. if( digits.ToLong( &number ) )
  53. {
  54. number += aIncrement;
  55. // Don't let result go below zero
  56. if( number > -1 )
  57. {
  58. name.Remove( ii + 1 );
  59. //write out a format string with correct number of leading zeroes
  60. outputFormat.Printf( wxS( "%%0%dld" ), dCount );
  61. //write out the number using the format string
  62. outputNumber.Printf( outputFormat, number );
  63. name << outputNumber << suffix;
  64. return true;
  65. }
  66. }
  67. return false;
  68. }
  69. std::optional<wxString> STRING_INCREMENTER::Increment( const wxString& aStr, int aDelta,
  70. size_t aRightIndex ) const
  71. {
  72. if( aStr.IsEmpty() )
  73. return std::nullopt;
  74. wxString remaining = aStr;
  75. std::vector<std::pair<wxString, STRING_PART_TYPE>> parts;
  76. size_t goodParts = 0;
  77. // Keep popping chunks off the string until we have what we need
  78. while( goodParts < ( aRightIndex + 1 ) && !remaining.IsEmpty() )
  79. {
  80. static const std::regex integerRegex( R"(\d+$)" );
  81. // ABC or abc but not Abc
  82. static const std::regex sameCaseAlphabetRegex( R"(([a-z]+|[A-Z]+)$)" );
  83. // Skippables - for now anything that isn't a letter or number
  84. static const std::regex skipRegex( R"([^a-zA-Z0-9]+$)" );
  85. std::string remainingStr = remaining.ToStdString();
  86. std::smatch match;
  87. if( std::regex_search( remainingStr, match, integerRegex ) )
  88. {
  89. parts.push_back( { match.str(), STRING_PART_TYPE::INTEGER } );
  90. remaining = remaining.Left( remaining.Len() - match.str().size() );
  91. goodParts++;
  92. }
  93. else if( std::regex_search( remainingStr, match, sameCaseAlphabetRegex ) )
  94. {
  95. parts.push_back( { match.str(), STRING_PART_TYPE::ALPHABETIC } );
  96. remaining = remaining.Left( remaining.Len() - match.str().size() );
  97. goodParts++;
  98. }
  99. else if( std::regex_search( remainingStr, match, skipRegex ) )
  100. {
  101. parts.push_back( { match.str(), STRING_PART_TYPE::SKIP } );
  102. remaining = remaining.Left( remaining.Len() - match.str().size() );
  103. }
  104. else
  105. {
  106. // Out of ideas
  107. break;
  108. }
  109. }
  110. // Couldn't find the part we wanted
  111. if( goodParts < aRightIndex + 1 )
  112. return std::nullopt;
  113. // Increment the part we wanted
  114. bool didIncrement = incrementPart( parts.back().first, parts.back().second, aDelta );
  115. if( !didIncrement )
  116. return std::nullopt;
  117. // Reassemble the string - the left-over part, then parts in reverse
  118. wxString result = remaining;
  119. for( auto it = parts.rbegin(); it != parts.rend(); ++it )
  120. {
  121. result << it->first;
  122. }
  123. return result;
  124. }
  125. static bool containsIOSQXZ( const wxString& aStr )
  126. {
  127. static const wxString iosqxz = "IOSQXZ";
  128. for( const wxUniChar& c : aStr )
  129. {
  130. if( iosqxz.Contains( c ) )
  131. return true;
  132. }
  133. return false;
  134. }
  135. bool STRING_INCREMENTER::incrementPart( wxString& aPart, STRING_PART_TYPE aType, int aDelta ) const
  136. {
  137. switch( aType )
  138. {
  139. case STRING_PART_TYPE::INTEGER:
  140. {
  141. long number = 0;
  142. bool zeroPadded = aPart.StartsWith( '0' );
  143. size_t oldLen = aPart.Len();
  144. if( aPart.ToLong( &number ) )
  145. {
  146. number += aDelta;
  147. // Going below zero makes things awkward
  148. // and is not usually that useful.
  149. if( number < 0 )
  150. return false;
  151. aPart.Printf( "%ld", number );
  152. // If the number was zero-padded, we need to re-pad it
  153. if( zeroPadded )
  154. {
  155. aPart = wxString( "0", oldLen - aPart.Len() ) + aPart;
  156. }
  157. return true;
  158. }
  159. break;
  160. }
  161. case STRING_PART_TYPE::ALPHABETIC:
  162. {
  163. // Covert to uppercase
  164. wxString upper = aPart.Upper();
  165. bool wasUpper = aPart == upper;
  166. static const wxString alphabetFull = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  167. static const wxString alphaNoIOSQXZ = "ABCDEFGHJKLMNPRTUVWY";
  168. const wxString& alpha =
  169. ( m_SkipIOSQXZ & !containsIOSQXZ( aPart ) ) ? alphaNoIOSQXZ : alphabetFull;
  170. int index = IndexFromAlphabetic( upper, alpha );
  171. // Something was not in the alphabet
  172. if( index == -1 )
  173. return false;
  174. // It's such a big number that we don't want to increment it
  175. if( index > m_AlphabeticMaxIndex && m_AlphabeticMaxIndex >= 0 )
  176. return false;
  177. index += aDelta;
  178. if( index < 0 )
  179. return false;
  180. wxString newStr = AlphabeticFromIndex( index, alpha, true );
  181. if( !wasUpper )
  182. newStr = newStr.Lower();
  183. aPart = newStr;
  184. return true;
  185. }
  186. case STRING_PART_TYPE::SKIP: break;
  187. }
  188. return false;
  189. }
  190. KICOMMON_API int IndexFromAlphabetic( const wxString& aStr, const wxString& aAlphabet )
  191. {
  192. int index = 0;
  193. const int radix = aAlphabet.Length();
  194. for( size_t i = 0; i < aStr.Len(); i++ )
  195. {
  196. int alphaIndex = aAlphabet.Find( aStr[i] );
  197. if( alphaIndex == wxNOT_FOUND )
  198. return -1;
  199. if( i != aStr.Len() - 1 )
  200. alphaIndex++;
  201. index += alphaIndex * pow( radix, aStr.Len() - 1 - i );
  202. }
  203. return index;
  204. }
  205. wxString KICOMMON_API AlphabeticFromIndex( size_t aN, const wxString& aAlphabet,
  206. bool aZeroBasedNonUnitCols )
  207. {
  208. wxString itemNum;
  209. bool firstRound = true;
  210. const int radix = aAlphabet.Length();
  211. do
  212. {
  213. int modN = aN % radix;
  214. if( aZeroBasedNonUnitCols && !firstRound )
  215. modN--; // Start the "tens/hundreds/etc column" at "Ax", not "Bx"
  216. itemNum.insert( 0, 1, aAlphabet[modN] );
  217. aN /= radix;
  218. firstRound = false;
  219. } while( aN );
  220. return itemNum;
  221. }