KiCad PCB EDA Suite
Loading...
Searching...
No Matches
string_utils.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, 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
28
29#include <clocale>
30#include <cmath>
31#include <map>
32#include <core/map_helpers.h>
33#include <fmt/core.h>
34#include <macros.h>
35#include <string_utils.h>
36#include <widgets/kistatusbar.h>
37#include <wx_filename.h>
38#include <fmt/chrono.h>
39#include <wx/log.h>
40#include <wx/regex.h>
41#include <wx/tokenzr.h>
43#include "locale_io.h"
44#include <wx/event.h>
45
46
52static constexpr std::string_view illegalFileNameChars = "\\/:\"<>|*?";
53
54static const wxChar defaultVariantName[] = wxT( "< Default >" );
55
56
57// Checks if a full filename is valid, i.e. does not contains illegal chars
58bool IsFullFileNameValid( const wxString& aFullFilename )
59{
60
61 // Test for forbidden chars in aFullFilename.
62 // '\'and '/' are allowed here because aFullFilename can be a full path, and
63 // ':' is allowed on Windows as second char in string.
64 // So remove allowed separators from string to test
65 wxString filtered_fullpath = aFullFilename;
66
67#ifdef __WINDOWS__
68 // On MSW, the list returned by wxFileName::GetForbiddenChars() contains separators
69 // '\'and '/'
70 filtered_fullpath.Replace( "/", "_" );
71 filtered_fullpath.Replace( "\\", "_" );
72
73 // A disk identifier is allowed, and therefore remove its separator
74 if( filtered_fullpath.Length() > 1 && filtered_fullpath[1] == ':' )
75 filtered_fullpath[1] = ' ';
76#endif
77
78 if( wxString::npos != filtered_fullpath.find_first_of( wxFileName::GetForbiddenChars() ) )
79 return false;
80
81 return true;
82}
83
84
85wxString ConvertToNewOverbarNotation( const wxString& aOldStr )
86{
87 wxString newStr;
88 bool inOverbar = false;
89
90 // Don't get tripped up by the legacy empty-string token.
91 if( aOldStr == wxT( "~" ) )
92 return aOldStr;
93
94 newStr.reserve( aOldStr.length() );
95
96 for( wxString::const_iterator chIt = aOldStr.begin(); chIt != aOldStr.end(); ++chIt )
97 {
98 if( *chIt == '~' )
99 {
100 wxString::const_iterator lookahead = chIt + 1;
101
102 if( lookahead != aOldStr.end() && *lookahead == '~' )
103 {
104 if( ++lookahead != aOldStr.end() && *lookahead == '{' )
105 {
106 // This way the subsequent opening curly brace will not start an
107 // overbar.
108 newStr << wxT( "~~{}" );
109 continue;
110 }
111
112 // Two subsequent tildes mean a tilde.
113 newStr << wxT( "~" );
114 ++chIt;
115 continue;
116 }
117 else if( lookahead != aOldStr.end() && *lookahead == '{' )
118 {
119 // Could mean the user wants "{" with an overbar, but more likely this
120 // is a case of double notation conversion. Bail out.
121 return aOldStr;
122 }
123 else
124 {
125 if( inOverbar )
126 {
127 newStr << wxT( "}" );
128 inOverbar = false;
129 }
130 else
131 {
132 newStr << wxT( "~{" );
133 inOverbar = true;
134 }
135
136 continue;
137 }
138 }
139 else if( ( *chIt == ' ' || *chIt == '}' || *chIt == ')' ) && inOverbar )
140 {
141 // Spaces were used to terminate overbar as well
142 newStr << wxT( "}" );
143 inOverbar = false;
144 }
145
146 newStr << *chIt;
147 }
148
149 // Explicitly end the overbar even if there was no terminating '~' in the aOldStr.
150 if( inOverbar )
151 newStr << wxT( "}" );
152
153 return newStr;
154}
155
156
157bool ConvertSmartQuotesAndDashes( wxString* aString )
158{
159 bool retVal = false;
160
161 for( wxString::iterator ii = aString->begin(); ii != aString->end(); ++ii )
162 {
163 if( *ii == L'\u00B4' || *ii == L'\u2018' || *ii == L'\u2019' )
164 {
165 *ii = '\'';
166 retVal = true;
167 }
168 if( *ii == L'\u201C' || *ii == L'\u201D' )
169 {
170 *ii = '"';
171 retVal = true;
172 }
173 if( *ii == L'\u2013' || *ii == L'\u2014' )
174 {
175 *ii = '-';
176 retVal = true;
177 }
178 }
179
180 return retVal;
181}
182
183
184wxString EscapeString( const wxString& aSource, ESCAPE_CONTEXT aContext )
185{
186 wxString converted;
187 std::vector<bool> braceStack; // true == formatting construct
188
189 converted.reserve( aSource.length() );
190
191 for( wxUniChar c: aSource )
192 {
193 if( aContext == CTX_NETNAME )
194 {
195 if( c == '/' )
196 converted += wxT( "{slash}" );
197 else if( c == '\n' || c == '\r' )
198 converted += wxEmptyString; // drop
199 else
200 converted += c;
201 }
202 else if( aContext == CTX_LIBID || aContext == CTX_LEGACY_LIBID )
203 {
204 // We no longer escape '/' in LIB_IDs, but we used to
205 if( c == '/' && aContext == CTX_LEGACY_LIBID )
206 converted += wxT( "{slash}" );
207 else if( c == '\\' )
208 converted += wxT( "{backslash}" );
209 else if( c == '<' )
210 converted += wxT( "{lt}" );
211 else if( c == '>' )
212 converted += wxT( "{gt}" );
213 else if( c == ':' )
214 converted += wxT( "{colon}" );
215 else if( c == '\"' )
216 converted += wxT( "{dblquote}" );
217 else if( c == '\n' || c == '\r' )
218 converted += wxEmptyString; // drop
219 else
220 converted += c;
221 }
222 else if( aContext == CTX_IPC )
223 {
224 if( c == '/' )
225 converted += wxT( "{slash}" );
226 else if( c == ',' )
227 converted += wxT( "{comma}" );
228 else if( c == '\"' )
229 converted += wxT( "{dblquote}" );
230 else
231 converted += c;
232 }
233 else if( aContext == CTX_QUOTED_STR )
234 {
235 if( c == '\"' )
236 converted += wxT( "{dblquote}" );
237 else
238 converted += c;
239 }
240 else if( aContext == CTX_JS_STR )
241 {
242 if( c >= 0x7F || c == '\'' || c == '\\' || c == '(' || c == ')' )
243 {
244 unsigned int code = c;
245 char buffer[16];
246 snprintf( buffer, sizeof(buffer), "\\u%4.4X", code );
247 converted += buffer;
248 }
249 else
250 {
251 converted += c;
252 }
253 }
254 else if( aContext == CTX_LINE )
255 {
256 if( c == '\n' || c == '\r' )
257 converted += wxT( "{return}" );
258 else
259 converted += c;
260 }
261 else if( aContext == CTX_FILENAME )
262 {
263 if( c == '/' )
264 converted += wxT( "{slash}" );
265 else if( c == '\\' )
266 converted += wxT( "{backslash}" );
267 else if( c == '\"' )
268 converted += wxT( "{dblquote}" );
269 else if( c == '<' )
270 converted += wxT( "{lt}" );
271 else if( c == '>' )
272 converted += wxT( "{gt}" );
273 else if( c == '|' )
274 converted += wxT( "{bar}" );
275 else if( c == ':' )
276 converted += wxT( "{colon}" );
277 else if( c == '\t' )
278 converted += wxT( "{tab}" );
279 else if( c == '\n' || c == '\r' )
280 converted += wxT( "{return}" );
281 else
282 converted += c;
283 }
284 else if( aContext == CTX_NO_SPACE )
285 {
286 if( c == ' ' )
287 converted += wxT( "{space}" );
288 else
289 converted += c;
290 }
291 else if( aContext == CTX_CSV )
292 {
293 if( c == ',' )
294 converted += wxT( "{comma}" );
295 else if( c == '\n' || c == '\r' )
296 converted += wxT( "{return}" );
297 else
298 converted += c;
299 }
300 else
301 {
302 converted += c;
303 }
304 }
305
306 return converted;
307}
308
309
310wxString UnescapeString( const wxString& aSource )
311{
312 size_t sourceLen = aSource.length();
313
314 // smallest escape string is three characters, shortcut everything else
315 if( sourceLen <= 2 )
316 {
317 return aSource;
318 }
319
320 wxString newbuf;
321 newbuf.reserve( sourceLen );
322
323 wxUniChar prev = 0;
324 wxUniChar ch = 0;
325
326 for( size_t i = 0; i < sourceLen; ++i )
327 {
328 prev = ch;
329 ch = aSource[i];
330
331 if( ch == '{' )
332 {
333 wxString token;
334 int depth = 1;
335 bool terminated = false;
336
337 for( i = i + 1; i < sourceLen; ++i )
338 {
339 ch = aSource[i];
340
341 if( ch == '{' )
342 depth++;
343 else if( ch == '}' )
344 depth--;
345
346 if( depth <= 0 )
347 {
348 terminated = true;
349 break;
350 }
351 else
352 {
353 token << ch;
354 }
355 }
356
357 if( !terminated )
358 {
359 newbuf << wxT( "{" ) << UnescapeString( token );
360 }
361 else if( prev == '$' || prev == '~' || prev == '^' || prev == '_' )
362 {
363 newbuf << wxT( "{" ) << UnescapeString( token ) << wxT( "}" );
364 }
365 else if( token == wxT( "dblquote" ) ) newbuf << wxT( "\"" );
366 else if( token == wxT( "quote" ) ) newbuf << wxT( "'" );
367 else if( token == wxT( "lt" ) ) newbuf << wxT( "<" );
368 else if( token == wxT( "gt" ) ) newbuf << wxT( ">" );
369 else if( token == wxT( "backslash" ) ) newbuf << wxT( "\\" );
370 else if( token == wxT( "slash" ) ) newbuf << wxT( "/" );
371 else if( token == wxT( "bar" ) ) newbuf << wxT( "|" );
372 else if( token == wxT( "comma" ) ) newbuf << wxT( "," );
373 else if( token == wxT( "colon" ) ) newbuf << wxT( ":" );
374 else if( token == wxT( "space" ) ) newbuf << wxT( " " );
375 else if( token == wxT( "dollar" ) ) newbuf << wxT( "$" );
376 else if( token == wxT( "tab" ) ) newbuf << wxT( "\t" );
377 else if( token == wxT( "return" ) ) newbuf << wxT( "\n" );
378 else if( token == wxT( "brace" ) ) newbuf << wxT( "{" );
379 else
380 {
381 newbuf << wxT( "{" ) << UnescapeString( token ) << wxT( "}" );
382 }
383 }
384 else
385 {
386 newbuf << ch;
387 }
388 }
389
390 return newbuf;
391}
392
393
394wxString TitleCaps( const wxString& aString )
395{
396 wxArrayString words;
397 wxString result;
398
399 wxStringSplit( aString, words, ' ' );
400
401 result.reserve( aString.length() );
402
403 for( const wxString& word : words )
404 {
405 if( !result.IsEmpty() )
406 result += wxT( " " );
407
408 result += word.Capitalize();
409 }
410
411 return result;
412}
413
414
415wxString InitialCaps( const wxString& aString )
416{
417 wxArrayString words;
418 wxString result;
419
420 wxStringSplit( aString, words, ' ' );
421
422 result.reserve( aString.length() );
423
424 for( const wxString& word : words )
425 {
426 if( result.IsEmpty() )
427 result += word.Capitalize();
428 else
429 result += wxT( " " ) + word.Lower();
430 }
431
432 return result;
433}
434
435
436int ReadDelimitedText( wxString* aDest, const char* aSource )
437{
438 std::string utf8; // utf8 but without escapes and quotes.
439 bool inside = false;
440 const char* start = aSource;
441 char cc;
442
443 while( (cc = *aSource++) != 0 )
444 {
445 if( cc == '"' )
446 {
447 if( inside )
448 break; // 2nd double quote is end of delimited text
449
450 inside = true; // first delimiter found, make note, do not copy
451 }
452
453 else if( inside )
454 {
455 if( cc == '\\' )
456 {
457 cc = *aSource++;
458
459 if( !cc )
460 break;
461
462 // do no copy the escape byte if it is followed by \ or "
463 if( cc != '"' && cc != '\\' )
464 utf8 += '\\';
465
466 utf8 += cc;
467 }
468 else
469 {
470 utf8 += cc;
471 }
472 }
473 }
474
475 *aDest = From_UTF8( utf8.c_str() );
476
477 return aSource - start;
478}
479
480
481int ReadDelimitedText( char* aDest, const char* aSource, int aDestSize )
482{
483 if( aDestSize <= 0 )
484 return 0;
485
486 bool inside = false;
487 const char* start = aSource;
488 char* limit = aDest + aDestSize - 1;
489 char cc;
490
491 while( ( cc = *aSource++ ) != 0 && aDest < limit )
492 {
493 if( cc == '"' )
494 {
495 if( inside )
496 break; // 2nd double quote is end of delimited text
497
498 inside = true; // first delimiter found, make note, do not copy
499 }
500 else if( inside )
501 {
502 if( cc == '\\' )
503 {
504 cc = *aSource++;
505
506 if( !cc )
507 break;
508
509 // do no copy the escape byte if it is followed by \ or "
510 if( cc != '"' && cc != '\\' )
511 *aDest++ = '\\';
512
513 if( aDest < limit )
514 *aDest++ = cc;
515 }
516 else
517 {
518 *aDest++ = cc;
519 }
520 }
521 }
522
523 *aDest = 0;
524
525 return aSource - start;
526}
527
528
529std::string EscapedUTF8( const wxString& aString )
530{
531 wxString str = aString;
532
533 // No new-lines allowed in quoted strings
534 str.Replace( wxT( "\r\n" ), wxT( "\r" ) );
535 str.Replace( wxT( "\n" ), wxT( "\r" ) );
536
537 std::string utf8 = TO_UTF8( aString );
538
539 std::string ret;
540
541 ret.reserve( utf8.length() + 2 );
542
543 ret += '"';
544
545 for( std::string::const_iterator it = utf8.begin(); it!=utf8.end(); ++it )
546 {
547 // this escaping strategy is designed to be compatible with ReadDelimitedText():
548 if( *it == '"' )
549 {
550 ret += '\\';
551 ret += '"';
552 }
553 else if( *it == '\\' )
554 {
555 ret += '\\'; // double it up
556 ret += '\\';
557 }
558 else
559 {
560 ret += *it;
561 }
562 }
563
564 ret += '"';
565
566 return ret;
567}
568
569
570wxString EscapeHTML( const wxString& aString )
571{
572 wxString converted;
573
574 converted.reserve( aString.length() );
575
576 for( wxUniChar c : aString )
577 {
578 if( c == '\"' )
579 converted += wxT( "&quot;" );
580 else if( c == '\'' )
581 converted += wxT( "&apos;" );
582 else if( c == '&' )
583 converted += wxT( "&amp;" );
584 else if( c == '<' )
585 converted += wxT( "&lt;" );
586 else if( c == '>' )
587 converted += wxT( "&gt;" );
588 else
589 converted += c;
590 }
591
592 return converted;
593}
594
595
596wxString UnescapeHTML( const wxString& aString )
597{
598 // clang-format off
599 static const std::map<wxString, wxString> c_replacements = {
600 { wxS( "quot" ), wxS( "\"" ) },
601 { wxS( "apos" ), wxS( "'" ) },
602 { wxS( "amp" ), wxS( "&" ) },
603 { wxS( "lt" ), wxS( "<" ) },
604 { wxS( "gt" ), wxS( ">" ) }
605 };
606 // clang-format on
607
608 // Construct regex
609 wxString regexStr = "&(#(\\d*)|#x([a-zA-Z0-9]{4})";
610
611 for( auto& [key, value] : c_replacements )
612 regexStr << '|' << key;
613
614 regexStr << ");";
615
616 wxRegEx regex( regexStr );
617
618 // Process matches
619 size_t start = 0;
620 size_t len = 0;
621
622 wxString result;
623 wxString str = aString;
624
625 while( regex.Matches( str ) )
626 {
627 std::vector<wxString> matches;
628 regex.GetMatch( &start, &len );
629
630 result << str.Left( start );
631
632 wxString code = regex.GetMatch( str, 1 );
633 wxString codeDec = regex.GetMatch( str, 2 );
634 wxString codeHex = regex.GetMatch( str, 3 );
635
636 if( !codeDec.IsEmpty() || !codeHex.IsEmpty() )
637 {
638 unsigned long codeVal = 0;
639
640 if( !codeDec.IsEmpty() )
641 codeDec.ToCULong( &codeVal );
642 else if( !codeHex.IsEmpty() )
643 codeHex.ToCULong( &codeVal, 16 );
644
645 if( codeVal != 0 )
646 result << wxUniChar( codeVal );
647 }
648 else if( auto val = get_opt( c_replacements, code ) )
649 {
650 result << *val;
651 }
652
653 str = str.Mid( start + len );
654 }
655
656 result << str;
657
658 return result;
659}
660
661
662wxString RemoveHTMLTags( const wxString& aInput )
663{
664 wxString str = aInput;
665 wxRegEx( wxS( "<[^>]*>" ) ).ReplaceAll( &str, wxEmptyString );
666
667 return str;
668}
669
670
671wxString LinkifyHTML( wxString aStr )
672{
673 static wxRegEx regex( wxS( "\\b(https?|ftp|file)://([-\\w+&@#/%?=~|!:,.;]*[^.,:;<>\\(\\)\\s\u00b6])" ),
674 wxRE_ICASE );
675
676 regex.ReplaceAll( &aStr, "<a href=\"\\0\">\\0</a>" );
677
678 return aStr;
679}
680
681
682bool IsURL( wxString aStr )
683{
684 static wxRegEx regex( wxS( "(https?|ftp|file)://([-\\w+&@#/%?=~|!:,.;]*[^.,:;<>\\s\u00b6])" ),
685 wxRE_ICASE );
686
687 regex.ReplaceAll( &aStr, "<a href=\"\\0\">\\0</a>" );
688
689 return regex.Matches( aStr );
690}
691
692
693bool NoPrintableChars( const wxString& aString )
694{
695 wxString tmp = aString;
696
697 return tmp.Trim( true ).Trim( false ).IsEmpty();
698}
699
700
701int PrintableCharCount( const wxString& aString )
702{
703 int char_count = 0;
704 int overbarDepth = -1;
705 int superSubDepth = -1;
706 int braceNesting = 0;
707
708 for( auto chIt = aString.begin(), end = aString.end(); chIt < end; ++chIt )
709 {
710 if( *chIt == '\t' )
711 {
712 // We don't format tabs in bitmap text (where this is currently used), so just
713 // drop them from the count.
714 continue;
715 }
716 else if( *chIt == '^' && superSubDepth == -1 )
717 {
718 auto lookahead = chIt;
719
720 if( ++lookahead != end && *lookahead == '{' )
721 {
722 chIt = lookahead;
723 superSubDepth = braceNesting;
724 braceNesting++;
725 continue;
726 }
727 }
728 else if( *chIt == '_' && superSubDepth == -1 )
729 {
730 auto lookahead = chIt;
731
732 if( ++lookahead != end && *lookahead == '{' )
733 {
734 chIt = lookahead;
735 superSubDepth = braceNesting;
736 braceNesting++;
737 continue;
738 }
739 }
740 else if( *chIt == '~' && overbarDepth == -1 )
741 {
742 auto lookahead = chIt;
743
744 if( ++lookahead != end && *lookahead == '{' )
745 {
746 chIt = lookahead;
747 overbarDepth = braceNesting;
748 braceNesting++;
749 continue;
750 }
751 }
752 else if( *chIt == '{' )
753 {
754 braceNesting++;
755 }
756 else if( *chIt == '}' )
757 {
758 if( braceNesting > 0 )
759 braceNesting--;
760
761 if( braceNesting == superSubDepth )
762 {
763 superSubDepth = -1;
764 continue;
765 }
766
767 if( braceNesting == overbarDepth )
768 {
769 overbarDepth = -1;
770 continue;
771 }
772 }
773
774 char_count++;
775 }
776
777 return char_count;
778}
779
780
781char* StrPurge( char* text )
782{
783 static const char whitespace[] = " \t\n\r\f\v";
784
785 if( text )
786 {
787 while( *text && strchr( whitespace, *text ) )
788 ++text;
789
790 char* cp = text + strlen( text ) - 1;
791
792 while( cp >= text && strchr( whitespace, *cp ) )
793 *cp-- = '\0';
794 }
795
796 return text;
797}
798
799
800char* GetLine( FILE* File, char* Line, int* LineNum, int SizeLine )
801{
802 do {
803 if( fgets( Line, SizeLine, File ) == nullptr )
804 return nullptr;
805
806 if( LineNum )
807 *LineNum += 1;
808
809 } while( Line[0] == '#' || Line[0] == '\n' || Line[0] == '\r' || Line[0] == 0 );
810
811 strtok( Line, "\n\r" );
812 return Line;
813}
814
815
817{
818 // on msys2 variant mingw64, in fmt::format the %z format
819 // (offset from UTC in the ISO 8601 format, e.g. -0430) does not work,
820 // and is in fact %Z (locale-dependent time zone name or abbreviation) and breaks our date.
821 // However, on msys2 variant ucrt64, it works (this is not the same code in fmt::format)
822#if defined(__MINGW32__) && !defined(_UCRT)
823 return fmt::format( "{:%FT%T}", fmt::localtime( std::time( nullptr ) ) );
824#else
825 return fmt::format( "{:%FT%T%z}", fmt::localtime( std::time( nullptr ) ) );
826#endif
827}
828
829
830int StrNumCmp( const wxString& aString1, const wxString& aString2, bool aIgnoreCase )
831{
832 int nb1 = 0, nb2 = 0;
833
834 auto str1 = aString1.begin();
835 auto str2 = aString2.begin();
836
837 while( str1 != aString1.end() && str2 != aString2.end() )
838 {
839 wxUniChar c1 = *str1;
840 wxUniChar c2 = *str2;
841
842 if( wxIsdigit( c1 ) && wxIsdigit( c2 ) ) // Both characters are digits, do numeric compare.
843 {
844 nb1 = 0;
845 nb2 = 0;
846
847 do
848 {
849 c1 = *str1;
850 nb1 = nb1 * 10 + (int) c1 - '0';
851 ++str1;
852 } while( str1 != aString1.end() && wxIsdigit( *str1 ) );
853
854 do
855 {
856 c2 = *str2;
857 nb2 = nb2 * 10 + (int) c2 - '0';
858 ++str2;
859 } while( str2 != aString2.end() && wxIsdigit( *str2 ) );
860
861 if( nb1 < nb2 )
862 return -1;
863
864 if( nb1 > nb2 )
865 return 1;
866
867 c1 = ( str1 != aString1.end() ) ? *str1 : wxUniChar( 0 );
868 c2 = ( str2 != aString2.end() ) ? *str2 : wxUniChar( 0 );
869 }
870
871 // Any numerical comparisons to here are identical.
872 if( aIgnoreCase )
873 {
874 if( c1 != c2 )
875 {
876 wxUniChar uc1 = wxToupper( c1 );
877 wxUniChar uc2 = wxToupper( c2 );
878
879 if( uc1 != uc2 )
880 return uc1 < uc2 ? -1 : 1;
881 }
882 }
883 else
884 {
885 if( c1 < c2 )
886 return -1;
887
888 if( c1 > c2 )
889 return 1;
890 }
891
892 if( str1 != aString1.end() )
893 ++str1;
894
895 if( str2 != aString2.end() )
896 ++str2;
897 }
898
899 if( str1 == aString1.end() && str2 != aString2.end() )
900 {
901 return -1; // Identical to here but aString1 is longer.
902 }
903 else if( str1 != aString1.end() && str2 == aString2.end() )
904 {
905 return 1; // Identical to here but aString2 is longer.
906 }
907
908 return 0;
909}
910
911
912bool WildCompareString( const wxString& pattern, const wxString& string_to_tst,
913 bool case_sensitive )
914{
915 const wxChar* cp = nullptr;
916 const wxChar* mp = nullptr;
917 const wxChar* wild = nullptr;
918 const wxChar* str = nullptr;
919 wxString _pattern, _string_to_tst;
920
921 if( case_sensitive )
922 {
923 wild = pattern.GetData();
924 str = string_to_tst.GetData();
925 }
926 else
927 {
928 _pattern = pattern;
929 _pattern.MakeUpper();
930 _string_to_tst = string_to_tst;
931 _string_to_tst.MakeUpper();
932 wild = _pattern.GetData();
933 str = _string_to_tst.GetData();
934 }
935
936 while( ( *str ) && ( *wild != '*' ) )
937 {
938 if( ( *wild != *str ) && ( *wild != '?' ) )
939 return false;
940
941 wild++;
942 str++;
943 }
944
945 while( *str )
946 {
947 if( *wild == '*' )
948 {
949 if( !*++wild )
950 return true;
951
952 mp = wild;
953 cp = str + 1;
954 }
955 else if( ( *wild == *str ) || ( *wild == '?' ) )
956 {
957 wild++;
958 str++;
959 }
960 else
961 {
962 wild = mp;
963 str = cp++;
964 }
965 }
966
967 while( *wild == '*' )
968 {
969 wild++;
970 }
971
972 return !*wild;
973}
974
975
976bool ApplyModifier( double& value, const wxString& aString )
977{
979 static const wxString modifiers( wxT( "afpnuµμmLRFkKMGTPE" ) );
980
981 if( !aString.length() )
982 return false;
983
984 wxChar modifier;
985 wxString units;
986
987 if( modifiers.Find( aString[ 0 ] ) >= 0 )
988 {
989 modifier = aString[ 0 ];
990 units = aString.Mid( 1 ).Trim();
991 }
992 else
993 {
994 modifier = ' ';
995 units = aString.Mid( 0 ).Trim();
996 }
997
998 if( units.length()
999 && !units.IsSameAs( wxT( "F" ), false )
1000 && !units.IsSameAs( wxT( "hz" ), false )
1001 && !units.IsSameAs( wxT( "W" ), false )
1002 && !units.IsSameAs( wxT( "V" ), false )
1003 && !units.IsSameAs( wxT( "A" ), false )
1004 && !units.IsSameAs( wxT( "H" ), false ) )
1005 {
1006 return false;
1007 }
1008
1009 // Note: most of these are SI, but some (L, R, F) are IEC 60062.
1010 if( modifier == 'a' )
1011 value *= 1.0e-18;
1012 else if( modifier == 'f' )
1013 value *= 1.0e-15;
1014 if( modifier == 'p' )
1015 value *= 1.0e-12;
1016 if( modifier == 'n' )
1017 value *= 1.0e-9;
1018 else if( modifier == 'u' || modifier == wxS( "µ" )[0] || modifier == wxS( "μ" )[0] )
1019 value *= 1.0e-6;
1020 else if( modifier == 'm' || modifier == 'L' )
1021 value *= 1.0e-3;
1022 else if( modifier == 'R' || modifier == 'F' )
1023 ; // unity scalar
1024 else if( modifier == 'k' || modifier == 'K' )
1025 value *= 1.0e3;
1026 else if( modifier == 'M' )
1027 value *= 1.0e6;
1028 else if( modifier == 'G' )
1029 value *= 1.0e9;
1030 else if( modifier == 'T' )
1031 value *= 1.0e12;
1032 else if( modifier == 'P' )
1033 value *= 1.0e15;
1034 else if( modifier == 'E' )
1035 value *= 1.0e18;
1036
1037 return true;
1038}
1039
1040
1041bool convertSeparators( wxString* value )
1042{
1043 // Note: fetching the decimal separator from the current locale isn't a silver bullet because
1044 // it assumes the current computer's locale is the same as the locale the schematic was
1045 // authored in -- something that isn't true, for instance, when sharing designs through
1046 // DIYAudio.com.
1047 //
1048 // Some values are self-describing: multiple instances of a single separator character must be
1049 // thousands separators; a single instance of each character must be a thousands separator
1050 // followed by a decimal separator; etc.
1051 //
1052 // Only when presented with an ambiguous value do we fall back on the current locale.
1053
1054 value->Replace( wxS( " " ), wxEmptyString );
1055
1056 wxChar ambiguousSeparator = '?';
1057 wxChar thousandsSeparator = '?';
1058 bool thousandsSeparatorFound = false;
1059 wxChar decimalSeparator = '?';
1060 bool decimalSeparatorFound = false;
1061 int digits = 0;
1062
1063 for( int ii = (int) value->length() - 1; ii >= 0; --ii )
1064 {
1065 wxChar c = value->GetChar( ii );
1066
1067 if( c >= '0' && c <= '9' )
1068 {
1069 digits += 1;
1070 }
1071 else if( c == '.' || c == ',' )
1072 {
1073 if( decimalSeparator != '?' || thousandsSeparator != '?' )
1074 {
1075 // We've previously found a non-ambiguous separator...
1076
1077 if( c == decimalSeparator )
1078 {
1079 if( thousandsSeparatorFound )
1080 return false; // decimal before thousands
1081 else if( decimalSeparatorFound )
1082 return false; // more than one decimal
1083 else
1084 decimalSeparatorFound = true;
1085 }
1086 else if( c == thousandsSeparator )
1087 {
1088 if( digits != 3 )
1089 return false; // thousands not followed by 3 digits
1090 else
1091 thousandsSeparatorFound = true;
1092 }
1093 }
1094 else if( ambiguousSeparator != '?' )
1095 {
1096 // We've previously found a separator, but we don't know for sure which...
1097
1098 if( c == ambiguousSeparator )
1099 {
1100 // They both must be thousands separators
1101 thousandsSeparator = ambiguousSeparator;
1102 thousandsSeparatorFound = true;
1103 decimalSeparator = c == '.' ? ',' : '.';
1104 }
1105 else
1106 {
1107 // The first must have been a decimal, and this must be a thousands.
1108 decimalSeparator = ambiguousSeparator;
1109 decimalSeparatorFound = true;
1110 thousandsSeparator = c;
1111 thousandsSeparatorFound = true;
1112 }
1113 }
1114 else
1115 {
1116 // This is the first separator...
1117
1118 // If it's preceded by a '0' (only), or if it's followed by some number of
1119 // digits not equal to 3, then it -must- be a decimal separator.
1120 //
1121 // In all other cases we don't really know what it is yet.
1122
1123 if( ( ii == 1 && value->GetChar( 0 ) == '0' ) || digits != 3 )
1124 {
1125 decimalSeparator = c;
1126 decimalSeparatorFound = true;
1127 thousandsSeparator = c == '.' ? ',' : '.';
1128 }
1129 else
1130 {
1131 ambiguousSeparator = c;
1132 }
1133 }
1134
1135 digits = 0;
1136 }
1137 else
1138 {
1139 digits = 0;
1140 }
1141 }
1142
1143 // If we found nothing definitive then we have to look at the current locale
1144 if( decimalSeparator == '?' && thousandsSeparator == '?' )
1145 {
1146 const struct lconv* lc = localeconv();
1147
1148 decimalSeparator = lc->decimal_point[0];
1149 thousandsSeparator = decimalSeparator == '.' ? ',' : '.';
1150 }
1151
1152 // Convert to C-locale
1153 value->Replace( thousandsSeparator, wxEmptyString );
1154 value->Replace( decimalSeparator, '.' );
1155
1156 return true;
1157}
1158
1159
1160int ValueStringCompare( const wxString& strFWord, const wxString& strSWord )
1161{
1162 // Compare unescaped text
1163 wxString fWord = UnescapeString( strFWord );
1164 wxString sWord = UnescapeString( strSWord );
1165
1166 // The different sections of the two strings
1167 wxString strFWordBeg, strFWordMid, strFWordEnd;
1168 wxString strSWordBeg, strSWordMid, strSWordEnd;
1169
1170 // Split the two strings into separate parts
1171 SplitString( fWord, &strFWordBeg, &strFWordMid, &strFWordEnd );
1172 SplitString( sWord, &strSWordBeg, &strSWordMid, &strSWordEnd );
1173
1174 // Compare the Beginning section of the strings
1175 int isEqual = strFWordBeg.CmpNoCase( strSWordBeg );
1176
1177 if( isEqual > 0 )
1178 {
1179 return 1;
1180 }
1181 else if( isEqual < 0 )
1182 {
1183 return -1;
1184 }
1185 else
1186 {
1187 // If the first sections are equal compare their digits
1188 double lFirstNumber = 0;
1189 double lSecondNumber = 0;
1190 bool endingIsModifier = false;
1191
1192 convertSeparators( &strFWordMid );
1193 convertSeparators( &strSWordMid );
1194
1195 strFWordMid.ToCDouble( &lFirstNumber );
1196 strSWordMid.ToCDouble( &lSecondNumber );
1197
1198 endingIsModifier |= ApplyModifier( lFirstNumber, strFWordEnd );
1199 endingIsModifier |= ApplyModifier( lSecondNumber, strSWordEnd );
1200
1201 if( lFirstNumber > lSecondNumber )
1202 return 1;
1203 else if( lFirstNumber < lSecondNumber )
1204 return -1;
1205 // If the first two sections are equal and the endings are modifiers then compare them
1206 else if( !endingIsModifier )
1207 return strFWordEnd.CmpNoCase( strSWordEnd );
1208 // Ran out of things to compare; they must match
1209 else
1210 return 0;
1211 }
1212}
1213
1214
1215int SplitString( const wxString& strToSplit,
1216 wxString* strBeginning,
1217 wxString* strDigits,
1218 wxString* strEnd )
1219{
1220 static const wxString separators( wxT( ".," ) );
1221 wxUniChar infix = 0;
1222
1223 // Clear all the return strings
1224 strBeginning->Empty();
1225 strDigits->Empty();
1226 strEnd->Empty();
1227
1228 // There no need to do anything if the string is empty
1229 if( strToSplit.length() == 0 )
1230 return 0;
1231
1232 // Starting at the end of the string look for the first digit
1233 int ii;
1234
1235 for( ii = (strToSplit.length() - 1); ii >= 0; ii-- )
1236 {
1237 if( wxIsdigit( strToSplit[ii] ) )
1238 break;
1239 }
1240
1241 // If there were no digits then just set the single string
1242 if( ii < 0 )
1243 {
1244 *strBeginning = strToSplit;
1245 }
1246 else
1247 {
1248 // Since there is at least one digit this is the trailing string
1249 *strEnd = strToSplit.substr( ii + 1 );
1250
1251 // Go to the end of the digits
1252 int position = ii + 1;
1253
1254 for( ; ii >= 0; ii-- )
1255 {
1256 double scale;
1257 wxUniChar c = strToSplit[ii];
1258
1259 if( wxIsdigit( c ) )
1260 {
1261 continue;
1262 }
1263 if( infix == 0 && NUMERIC_EVALUATOR::IsOldSchoolDecimalSeparator( c, &scale ) )
1264 {
1265 infix = c;
1266 continue;
1267 }
1268 else if( separators.Find( strToSplit[ii] ) >= 0 )
1269 {
1270 continue;
1271 }
1272 else
1273 {
1274 break;
1275 }
1276 }
1277
1278 // If all that was left was digits, then just set the digits string
1279 if( ii < 0 )
1280 {
1281 *strDigits = strToSplit.substr( 0, position );
1282 }
1283 // Otherwise everything else is part of the preamble
1284 else
1285 {
1286 *strDigits = strToSplit.substr( ii + 1, position - ii - 1 );
1287 *strBeginning = strToSplit.substr( 0, ii + 1 );
1288 }
1289
1290 if( infix > 0 )
1291 {
1292 strDigits->Replace( infix, '.' );
1293 *strEnd = infix + *strEnd;
1294 }
1295 }
1296
1297 return 0;
1298}
1299
1300
1301int GetTrailingInt( const wxString& aStr )
1302{
1303 int number = 0;
1304 int base = 1;
1305
1306 // Trim and extract the trailing numeric part
1307 int index = aStr.Len() - 1;
1308
1309 while( index >= 0 )
1310 {
1311 const char chr = aStr.GetChar( index );
1312
1313 if( chr < '0' || chr > '9' )
1314 break;
1315
1316 number += ( chr - '0' ) * base;
1317 base *= 10;
1318 index--;
1319 }
1320
1321 return number;
1322}
1323
1324
1326{
1327 return wxString::FromUTF8( illegalFileNameChars.data(), illegalFileNameChars.length() );
1328}
1329
1330
1331bool ReplaceIllegalFileNameChars( std::string& aName, int aReplaceChar )
1332{
1333 size_t first_illegal_pos = aName.find_first_of( illegalFileNameChars );
1334
1335 if( first_illegal_pos == std::string::npos )
1336 {
1337 return false;
1338 }
1339
1340 std::string result;
1341 // result will be at least equal to original, add 16 in case of hex replacements
1342 result.reserve( aName.length() + 16 );
1343 // append the valid part
1344 result.append( aName, 0, first_illegal_pos );
1345
1346 for( size_t i = first_illegal_pos; i < aName.length(); ++i )
1347 {
1348 char c = aName[i];
1349
1350 // Check if this specific char is illegal
1351 if( illegalFileNameChars.find( c ) != std::string_view::npos )
1352 {
1353 if( aReplaceChar )
1354 {
1355 result.push_back( aReplaceChar );
1356 }
1357 else
1358 {
1359 fmt::format_to( std::back_inserter( result ), "%{:02x}", static_cast<unsigned char>( c ) );
1360 }
1361 }
1362 else
1363 {
1364 result.push_back( c );
1365 }
1366 }
1367
1368 aName = std::move( result );
1369 return true;
1370}
1371
1372
1373bool ReplaceIllegalFileNameChars( wxString& aName, int aReplaceChar )
1374{
1375 bool changed = false;
1376 wxString result;
1377 result.reserve( aName.Length() );
1378 wxString illWChars = GetIllegalFileNameWxChars();
1379
1380 for( wxString::iterator it = aName.begin(); it != aName.end(); ++it )
1381 {
1382 if( illWChars.Find( *it ) != wxNOT_FOUND )
1383 {
1384 if( aReplaceChar )
1385 result += aReplaceChar;
1386 else
1387 result += wxString::Format( "%%%02x", *it );
1388
1389 changed = true;
1390 }
1391 else
1392 {
1393 result += *it;
1394 }
1395 }
1396
1397 if( changed )
1398 aName = std::move( result );
1399
1400 return changed;
1401}
1402
1403
1404void wxStringSplit( const wxString& aText, wxArrayString& aStrings, wxChar aSplitter )
1405{
1406 wxString tmp;
1407
1408 for( unsigned ii = 0; ii < aText.Length(); ii++ )
1409 {
1410 if( aText[ii] == aSplitter )
1411 {
1412 aStrings.Add( tmp );
1413 tmp.Clear();
1414 }
1415 else
1416 {
1417 tmp << aText[ii];
1418 }
1419 }
1420
1421 if( !tmp.IsEmpty() )
1422 aStrings.Add( tmp );
1423}
1424
1425
1426void StripTrailingZeros( wxString& aStringValue, unsigned aTrailingZeroAllowed )
1427{
1428 struct lconv* lc = localeconv();
1429 char sep = lc->decimal_point[0];
1430 unsigned sep_pos = aStringValue.Find( sep );
1431
1432 if( sep_pos > 0 )
1433 {
1434 // We want to keep at least aTrailingZeroAllowed digits after the separator
1435 unsigned min_len = sep_pos + aTrailingZeroAllowed + 1;
1436
1437 while( aStringValue.Len() > min_len )
1438 {
1439 if( aStringValue.Last() == '0' )
1440 aStringValue.RemoveLast();
1441 else
1442 break;
1443 }
1444 }
1445}
1446
1447
1448std::string FormatDouble2Str( double aValue )
1449{
1450 std::string buf;
1451
1452 if( aValue != 0.0 && std::fabs( aValue ) <= 0.0001 )
1453 {
1454 buf = fmt::format( "{:.16f}", aValue );
1455
1456 // remove trailing zeros (and the decimal marker if needed)
1457 while( !buf.empty() && buf[buf.size() - 1] == '0' )
1458 {
1459 buf.pop_back();
1460 }
1461
1462 // if the value was really small
1463 // we may have just stripped all the zeros after the decimal
1464 if( buf[buf.size() - 1] == '.' )
1465 {
1466 buf.pop_back();
1467 }
1468 }
1469 else
1470 {
1471 buf = fmt::format( "{:.10g}", aValue );
1472 }
1473
1474 return buf;
1475}
1476
1477
1478std::string UIDouble2Str( double aValue )
1479{
1480 char buf[50];
1481 int len;
1482
1483 if( aValue != 0.0 && std::fabs( aValue ) <= 0.0001 )
1484 {
1485 // For these small values, %f works fine,
1486 // and %g gives an exponent
1487 len = snprintf( buf, sizeof( buf ), "%.16f", aValue );
1488
1489 while( --len > 0 && buf[len] == '0' )
1490 buf[len] = '\0';
1491
1492 if( buf[len] == '.' || buf[len] == ',' )
1493 buf[len] = '\0';
1494 else
1495 ++len;
1496 }
1497 else
1498 {
1499 // For these values, %g works fine, and sometimes %f
1500 // gives a bad value (try aValue = 1.222222222222, with %.16f format!)
1501 len = snprintf( buf, sizeof( buf ), "%.10g", aValue );
1502 }
1503
1504 return std::string( buf, len );
1505}
1506
1507
1508wxString From_UTF8( const char* cstring )
1509{
1510 // Convert an expected UTF8 encoded C string to a wxString
1511 wxString line = wxString::FromUTF8( cstring );
1512
1513 if( line.IsEmpty() ) // happens when cstring is not a valid UTF8 sequence
1514 {
1515 line = wxConvCurrent->cMB2WC( cstring ); // try to use locale conversion
1516
1517 if( line.IsEmpty() )
1518 line = wxString::From8BitData( cstring ); // try to use native string
1519 }
1520
1521 return line;
1522}
1523
1524
1525wxString From_UTF8( const std::string& aString )
1526{
1527 // Convert an expected UTF8 encoded std::string to a wxString
1528 wxString line = wxString::FromUTF8( aString );
1529
1530 if( line.IsEmpty() ) // happens when aString is not a valid UTF8 sequence
1531 {
1532 line = wxConvCurrent->cMB2WC( aString.c_str() ); // try to use locale conversion
1533
1534 if( line.IsEmpty() )
1535 line = wxString::From8BitData( aString.c_str() ); // try to use native string
1536 }
1537
1538 return line;
1539}
1540
1541
1542wxString NormalizeFileUri( const wxString& aFileUri )
1543{
1544 wxString uriPathAndFileName;
1545
1546 wxCHECK( aFileUri.StartsWith( wxS( "file://" ), &uriPathAndFileName ), aFileUri );
1547
1548 wxString tmp = uriPathAndFileName;
1549 wxString retv = wxS( "file://" );
1550
1551 tmp.Replace( wxS( "\\" ), wxS( "/" ) );
1552 tmp.Replace( wxS( ":" ), wxS( "" ) );
1553
1554 if( !tmp.IsEmpty() && tmp[0] != '/' )
1555 tmp = wxS( "/" ) + tmp;
1556
1557 retv += tmp;
1558
1559 return retv;
1560}
1561
1562
1563namespace
1564{
1565 // Extract (prefix, numericValue) where numericValue = -1 if no numeric suffix
1566 std::pair<wxString, long> ParseAlphaNumericPin( const wxString& pinNum )
1567 {
1568 wxString prefix;
1569 long numValue = -1;
1570
1571 size_t numStart = pinNum.length();
1572 for( int i = static_cast<int>( pinNum.length() ) - 1; i >= 0; --i )
1573 {
1574 if( !wxIsdigit( pinNum[i] ) )
1575 {
1576 numStart = i + 1;
1577 break;
1578 }
1579 if( i == 0 )
1580 numStart = 0; // all digits
1581 }
1582
1583 if( numStart < pinNum.length() )
1584 {
1585 prefix = pinNum.Left( numStart );
1586 wxString numericPart = pinNum.Mid( numStart );
1587 numericPart.ToLong( &numValue );
1588 }
1589
1590 return { prefix, numValue };
1591 }
1592}
1593
1594std::vector<wxString> ExpandStackedPinNotation( const wxString& aPinName, bool* aValid )
1595{
1596 if( aValid )
1597 *aValid = true;
1598
1599 std::vector<wxString> expanded;
1600
1601 const bool hasOpenBracket = aPinName.Contains( wxT( "[" ) );
1602 const bool hasCloseBracket = aPinName.Contains( wxT( "]" ) );
1603
1604 if( hasOpenBracket || hasCloseBracket )
1605 {
1606 if( !aPinName.StartsWith( wxT( "[" ) ) || !aPinName.EndsWith( wxT( "]" ) ) )
1607 {
1608 if( aValid )
1609 *aValid = false;
1610 expanded.push_back( aPinName );
1611 return expanded;
1612 }
1613 }
1614
1615 if( !aPinName.StartsWith( wxT( "[" ) ) || !aPinName.EndsWith( wxT( "]" ) ) )
1616 {
1617 expanded.push_back( aPinName );
1618 return expanded;
1619 }
1620
1621 const wxString inner = aPinName.Mid( 1, aPinName.Length() - 2 );
1622
1623 size_t start = 0;
1624 while( start < inner.length() )
1625 {
1626 size_t comma = inner.find( ',', start );
1627 wxString part = ( comma == wxString::npos ) ? inner.Mid( start ) : inner.Mid( start, comma - start );
1628 part.Trim( true ).Trim( false );
1629 if( part.empty() )
1630 {
1631 start = ( comma == wxString::npos ) ? inner.length() : comma + 1;
1632 continue;
1633 }
1634
1635 int dashPos = part.Find( '-' );
1636 if( dashPos != wxNOT_FOUND )
1637 {
1638 wxString startTxt = part.Left( dashPos );
1639 wxString endTxt = part.Mid( dashPos + 1 );
1640 startTxt.Trim( true ).Trim( false );
1641 endTxt.Trim( true ).Trim( false );
1642
1643 auto [startPrefix, startVal] = ParseAlphaNumericPin( startTxt );
1644 auto [endPrefix, endVal] = ParseAlphaNumericPin( endTxt );
1645
1646 if( startPrefix != endPrefix || startVal == -1 || endVal == -1 || startVal > endVal )
1647 {
1648 if( aValid )
1649 *aValid = false;
1650 expanded.clear();
1651 expanded.push_back( aPinName );
1652 return expanded;
1653 }
1654
1655 for( long ii = startVal; ii <= endVal; ++ii )
1656 {
1657 if( startPrefix.IsEmpty() )
1658 expanded.emplace_back( wxString::Format( wxT( "%ld" ), ii ) );
1659 else
1660 expanded.emplace_back( wxString::Format( wxT( "%s%ld" ), startPrefix, ii ) );
1661 }
1662 }
1663 else
1664 {
1665 expanded.push_back( part );
1666 }
1667
1668 if( comma == wxString::npos )
1669 break;
1670 start = comma + 1;
1671 }
1672
1673 if( expanded.empty() )
1674 {
1675 expanded.push_back( aPinName );
1676 if( aValid )
1677 *aValid = false;
1678 }
1679
1680 return expanded;
1681}
1682
1683
1684int CountStackedPinNotation( const wxString& aPinName, bool* aValid )
1685{
1686 if( aValid )
1687 *aValid = true;
1688
1689 // Fast path: if no brackets, it's a single pin
1690 const bool hasOpenBracket = aPinName.Contains( wxT( "[" ) );
1691 const bool hasCloseBracket = aPinName.Contains( wxT( "]" ) );
1692
1693 if( hasOpenBracket || hasCloseBracket )
1694 {
1695 if( !aPinName.StartsWith( wxT( "[" ) ) || !aPinName.EndsWith( wxT( "]" ) ) )
1696 {
1697 if( aValid )
1698 *aValid = false;
1699 return 1;
1700 }
1701 }
1702
1703 if( !aPinName.StartsWith( wxT( "[" ) ) || !aPinName.EndsWith( wxT( "]" ) ) )
1704 return 1;
1705
1706 const wxString inner = aPinName.Mid( 1, aPinName.Length() - 2 );
1707
1708 int count = 0;
1709 size_t start = 0;
1710
1711 while( start < inner.length() )
1712 {
1713 size_t comma = inner.find( ',', start );
1714 wxString part = ( comma == wxString::npos ) ? inner.Mid( start ) : inner.Mid( start, comma - start );
1715 part.Trim( true ).Trim( false );
1716
1717 if( part.empty() )
1718 {
1719 start = ( comma == wxString::npos ) ? inner.length() : comma + 1;
1720 continue;
1721 }
1722
1723 int dashPos = part.Find( '-' );
1724 if( dashPos != wxNOT_FOUND )
1725 {
1726 wxString startTxt = part.Left( dashPos );
1727 wxString endTxt = part.Mid( dashPos + 1 );
1728 startTxt.Trim( true ).Trim( false );
1729 endTxt.Trim( true ).Trim( false );
1730
1731 auto [startPrefix, startVal] = ParseAlphaNumericPin( startTxt );
1732 auto [endPrefix, endVal] = ParseAlphaNumericPin( endTxt );
1733
1734 if( startPrefix != endPrefix || startVal == -1 || endVal == -1 || startVal > endVal )
1735 {
1736 if( aValid )
1737 *aValid = false;
1738
1739 return 1;
1740 }
1741
1742 // Count pins in the range
1743 count += static_cast<int>( endVal - startVal + 1 );
1744 }
1745 else
1746 {
1747 // Single pin
1748 ++count;
1749 }
1750
1751 if( comma == wxString::npos )
1752 break;
1753
1754 start = comma + 1;
1755 }
1756
1757 if( count == 0 )
1758 {
1759 if( aValid )
1760 *aValid = false;
1761
1762 return 1;
1763 }
1764
1765 return count;
1766}
1767
1768
1770{
1771 return wxString( defaultVariantName );
1772}
1773
1774
1775int SortVariantNames( const wxString& aLhs, const wxString& aRhs )
1776{
1777 if( ( aLhs == defaultVariantName ) && ( aRhs != defaultVariantName ) )
1778 return -1;
1779
1780 if( ( aLhs != defaultVariantName ) && ( aRhs == defaultVariantName ) )
1781 return 1;
1782
1783 return StrNumCmp( aLhs, aRhs );
1784}
1785
1786
1787std::vector<LOAD_MESSAGE> ExtractLibraryLoadErrors( const wxString& aErrorString, int aSeverity )
1788{
1789 std::vector<LOAD_MESSAGE> messages;
1790
1791 if( aErrorString.IsEmpty() )
1792 return messages;
1793
1794 // Errors are separated by newlines. We want to keep:
1795 // - Lines starting with "Library '" (library-level errors)
1796 // - Lines containing "Expecting" (file error location)
1797 // And strip:
1798 // - Lines starting with "from " (internal code location info)
1799 wxStringTokenizer tokenizer( aErrorString, wxS( "\n" ), wxTOKEN_STRTOK );
1800
1801 while( tokenizer.HasMoreTokens() )
1802 {
1803 wxString line = tokenizer.GetNextToken();
1804
1805 // Skip internal code location lines (e.g., "from pcb_io_kicad_sexpr_parser.cpp : ...")
1806 if( line.StartsWith( wxS( "from " ) ) )
1807 continue;
1808
1809 if( line.StartsWith( wxS( "Library '" ) ) || line.Contains( wxS( "Expecting" ) ) )
1810 messages.push_back( { line, static_cast<SEVERITY>( aSeverity ) } );
1811 }
1812
1813 return messages;
1814}
1815
int index
static bool IsOldSchoolDecimalSeparator(wxUniChar ch, double *siScaler)
This file contains miscellaneous commonly used macros and functions.
std::optional< V > get_opt(const std::map< wxString, V > &aMap, const wxString &aKey)
Definition map_helpers.h:34
SEVERITY
const int scale
int StrNumCmp(const wxString &aString1, const wxString &aString2, bool aIgnoreCase)
Compare two strings with alphanumerical content.
wxString RemoveHTMLTags(const wxString &aInput)
Removes HTML tags from a string.
wxString EscapeHTML(const wxString &aString)
Return a new wxString escaped for embedding in HTML.
bool WildCompareString(const wxString &pattern, const wxString &string_to_tst, bool case_sensitive)
Compare a string against wild card (* and ?) pattern using the usual rules.
wxString GetDefaultVariantName()
std::vector< wxString > ExpandStackedPinNotation(const wxString &aPinName, bool *aValid)
Expand stacked pin notation like [1,2,3], [1-4], [A1-A4], or [AA1-AA3,AB4,CD12-CD14] into individual ...
wxString ConvertToNewOverbarNotation(const wxString &aOldStr)
Convert the old ~...~ overbar notation to the new ~{...} one.
int GetTrailingInt(const wxString &aStr)
Gets the trailing int, if any, from a string.
wxString UnescapeString(const wxString &aSource)
wxString LinkifyHTML(wxString aStr)
Wraps links in HTML tags.
bool convertSeparators(wxString *value)
wxString GetIllegalFileNameWxChars()
wxString From_UTF8(const char *cstring)
std::vector< LOAD_MESSAGE > ExtractLibraryLoadErrors(const wxString &aErrorString, int aSeverity)
Parse library load error messages, extracting user-facing information while stripping internal code l...
int SortVariantNames(const wxString &aLhs, const wxString &aRhs)
bool ConvertSmartQuotesAndDashes(wxString *aString)
Convert curly quotes and em/en dashes to straight quotes and dashes.
static const wxChar defaultVariantName[]
void wxStringSplit(const wxString &aText, wxArrayString &aStrings, wxChar aSplitter)
Split aString to a string list separated at aSplitter.
int ReadDelimitedText(wxString *aDest, const char *aSource)
Copy bytes from aSource delimited string segment to aDest wxString.
wxString TitleCaps(const wxString &aString)
Capitalize the first letter in each word.
std::string UIDouble2Str(double aValue)
Print a float number without using scientific notation and no trailing 0 We want to avoid scientific ...
static constexpr std::string_view illegalFileNameChars
Illegal file name characters used to ensure file names will be valid on all supported platforms.
std::string FormatDouble2Str(double aValue)
Print a float number without using scientific notation and no trailing 0 This function is intended in...
bool ReplaceIllegalFileNameChars(std::string &aName, int aReplaceChar)
Checks aName for illegal file name characters.
int PrintableCharCount(const wxString &aString)
Return the number of printable (ie: non-formatting) chars.
std::string EscapedUTF8(const wxString &aString)
Return an 8 bit UTF8 string given aString in Unicode form.
char * GetLine(FILE *File, char *Line, int *LineNum, int SizeLine)
Read one line line from aFile.
bool ApplyModifier(double &value, const wxString &aString)
wxString InitialCaps(const wxString &aString)
Capitalize only the first word.
wxString NormalizeFileUri(const wxString &aFileUri)
Normalize file path aFileUri to URI convention.
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
wxString GetISO8601CurrentDateTime()
int ValueStringCompare(const wxString &strFWord, const wxString &strSWord)
Compare strings like the strcmp function but handle numbers and modifiers within the string text corr...
int SplitString(const wxString &strToSplit, wxString *strBeginning, wxString *strDigits, wxString *strEnd)
Break a string into three parts: he alphabetic preamble, the numeric part, and any alphabetic ending.
bool IsFullFileNameValid(const wxString &aFullFilename)
Checks if a full filename is valid, i.e.
void StripTrailingZeros(wxString &aStringValue, unsigned aTrailingZeroAllowed)
Remove trailing zeros from a string containing a converted float number.
int CountStackedPinNotation(const wxString &aPinName, bool *aValid)
Count the number of pins represented by stacked pin notation without allocating strings.
char * StrPurge(char *text)
Remove leading and training spaces, tabs and end of line chars in text.
bool NoPrintableChars(const wxString &aString)
Return true if the string is empty or contains only whitespace.
wxString UnescapeHTML(const wxString &aString)
Return a new wxString unescaped from HTML format.
bool IsURL(wxString aStr)
Performs a URL sniff-test on a string.
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
ESCAPE_CONTEXT
Escape/Unescape routines to safely encode reserved-characters in various contexts.
@ CTX_FILENAME
@ CTX_QUOTED_STR
@ CTX_LINE
@ CTX_NO_SPACE
@ CTX_LIBID
@ CTX_NETNAME
@ CTX_CSV
@ CTX_IPC
@ CTX_LEGACY_LIBID
@ CTX_JS_STR
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.