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