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