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