KiCad PCB EDA Suite
Loading...
Searching...
No Matches
string_utils.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
29#include <clocale>
30#include <cmath>
31#include <map>
32#include <core/map_helpers.h>
33#include <fmt/core.h>
34#include <macros.h>
35#include <richio.h> // StrPrintf
36#include <string_utils.h>
37#include <wx_filename.h>
38#include <fmt/chrono.h>
39#include <wx/log.h>
40#include <wx/regex.h>
41#include "locale_io.h"
42
43
49static const char illegalFileNameChars[] = "\\/:\"<>|*?";
50
51
52// Checks if a full filename is valid, i.e. does not contains illegal chars
53bool IsFullFileNameValid( const wxString& aFullFilename )
54{
55
56 // Test for forbidden chars in aFullFilename.
57 // '\'and '/' are allowed here because aFullFilename can be a full path, and
58 // ':' is allowed on Windows as second char in string.
59 // So remove allowed separators from string to test
60 wxString filtered_fullpath = aFullFilename;
61
62#ifdef __WINDOWS__
63 // On MSW, the list returned by wxFileName::GetForbiddenChars() contains separators
64 // '\'and '/'
65 filtered_fullpath.Replace( "/", "_" );
66 filtered_fullpath.Replace( "\\", "_" );
67
68 // A disk identifier is allowed, and therefore remove its separator
69 if( filtered_fullpath.Length() > 1 && filtered_fullpath[1] == ':' )
70 filtered_fullpath[1] = ' ';
71#endif
72
73 if( wxString::npos != filtered_fullpath.find_first_of( wxFileName::GetForbiddenChars() ) )
74 return false;
75
76 return true;
77}
78
79
80wxString ConvertToNewOverbarNotation( const wxString& aOldStr )
81{
82 wxString newStr;
83 bool inOverbar = false;
84
85 // Don't get tripped up by the legacy empty-string token.
86 if( aOldStr == wxT( "~" ) )
87 return aOldStr;
88
89 newStr.reserve( aOldStr.length() );
90
91 for( wxString::const_iterator chIt = aOldStr.begin(); chIt != aOldStr.end(); ++chIt )
92 {
93 if( *chIt == '~' )
94 {
95 wxString::const_iterator lookahead = chIt + 1;
96
97 if( lookahead != aOldStr.end() && *lookahead == '~' )
98 {
99 if( ++lookahead != aOldStr.end() && *lookahead == '{' )
100 {
101 // This way the subsequent opening curly brace will not start an
102 // overbar.
103 newStr << wxT( "~~{}" );
104 continue;
105 }
106
107 // Two subsequent tildes mean a tilde.
108 newStr << wxT( "~" );
109 ++chIt;
110 continue;
111 }
112 else if( lookahead != aOldStr.end() && *lookahead == '{' )
113 {
114 // Could mean the user wants "{" with an overbar, but more likely this
115 // is a case of double notation conversion. Bail out.
116 return aOldStr;
117 }
118 else
119 {
120 if( inOverbar )
121 {
122 newStr << wxT( "}" );
123 inOverbar = false;
124 }
125 else
126 {
127 newStr << wxT( "~{" );
128 inOverbar = true;
129 }
130
131 continue;
132 }
133 }
134 else if( ( *chIt == ' ' || *chIt == '}' || *chIt == ')' ) && inOverbar )
135 {
136 // Spaces were used to terminate overbar as well
137 newStr << wxT( "}" );
138 inOverbar = false;
139 }
140
141 newStr << *chIt;
142 }
143
144 // Explicitly end the overbar even if there was no terminating '~' in the aOldStr.
145 if( inOverbar )
146 newStr << wxT( "}" );
147
148 return newStr;
149}
150
151
152bool ConvertSmartQuotesAndDashes( wxString* aString )
153{
154 bool retVal = false;
155
156 for( wxString::iterator ii = aString->begin(); ii != aString->end(); ++ii )
157 {
158 if( *ii == L'\u00B4' || *ii == L'\u2018' || *ii == L'\u2019' )
159 {
160 *ii = '\'';
161 retVal = true;
162 }
163 if( *ii == L'\u201C' || *ii == L'\u201D' )
164 {
165 *ii = '"';
166 retVal = true;
167 }
168 if( *ii == L'\u2013' || *ii == L'\u2014' )
169 {
170 *ii = '-';
171 retVal = true;
172 }
173 }
174
175 return retVal;
176}
177
178
179wxString EscapeString( const wxString& aSource, ESCAPE_CONTEXT aContext )
180{
181 wxString converted;
182 std::vector<bool> braceStack; // true == formatting construct
183
184 converted.reserve( aSource.length() );
185
186 for( wxUniChar c: aSource )
187 {
188 if( aContext == CTX_NETNAME )
189 {
190 if( c == '/' )
191 converted += wxT( "{slash}" );
192 else if( c == '\n' || c == '\r' )
193 converted += wxEmptyString; // drop
194 else
195 converted += c;
196 }
197 else if( aContext == CTX_LIBID || aContext == CTX_LEGACY_LIBID )
198 {
199 // We no longer escape '/' in LIB_IDs, but we used to
200 if( c == '/' && aContext == CTX_LEGACY_LIBID )
201 converted += wxT( "{slash}" );
202 else if( c == '\\' )
203 converted += wxT( "{backslash}" );
204 else if( c == '<' )
205 converted += wxT( "{lt}" );
206 else if( c == '>' )
207 converted += wxT( "{gt}" );
208 else if( c == ':' )
209 converted += wxT( "{colon}" );
210 else if( c == '\"' )
211 converted += wxT( "{dblquote}" );
212 else if( c == '\n' || c == '\r' )
213 converted += wxEmptyString; // drop
214 else
215 converted += c;
216 }
217 else if( aContext == CTX_IPC )
218 {
219 if( c == '/' )
220 converted += wxT( "{slash}" );
221 else if( c == ',' )
222 converted += wxT( "{comma}" );
223 else if( c == '\"' )
224 converted += wxT( "{dblquote}" );
225 else
226 converted += c;
227 }
228 else if( aContext == CTX_QUOTED_STR )
229 {
230 if( c == '\"' )
231 converted += wxT( "{dblquote}" );
232 else
233 converted += c;
234 }
235 else if( aContext == CTX_JS_STR )
236 {
237 if( c >= 0x7F || c == '\'' || c == '\\' || c == '(' || c == ')' )
238 {
239 unsigned int code = c;
240 char buffer[16];
241 snprintf( buffer, sizeof(buffer), "\\u%4.4X", code );
242 converted += buffer;
243 }
244 else
245 {
246 converted += c;
247 }
248 }
249 else if( aContext == CTX_LINE )
250 {
251 if( c == '\n' || c == '\r' )
252 converted += wxT( "{return}" );
253 else
254 converted += c;
255 }
256 else if( aContext == CTX_FILENAME )
257 {
258 if( c == '/' )
259 converted += wxT( "{slash}" );
260 else if( c == '\\' )
261 converted += wxT( "{backslash}" );
262 else if( c == '\"' )
263 converted += wxT( "{dblquote}" );
264 else if( c == '<' )
265 converted += wxT( "{lt}" );
266 else if( c == '>' )
267 converted += wxT( "{gt}" );
268 else if( c == '|' )
269 converted += wxT( "{bar}" );
270 else if( c == ':' )
271 converted += wxT( "{colon}" );
272 else if( c == '\t' )
273 converted += wxT( "{tab}" );
274 else if( c == '\n' || c == '\r' )
275 converted += wxT( "{return}" );
276 else
277 converted += c;
278 }
279 else if( aContext == CTX_NO_SPACE )
280 {
281 if( c == ' ' )
282 converted += wxT( "{space}" );
283 else
284 converted += c;
285 }
286 else if( aContext == CTX_CSV )
287 {
288 if( c == ',' )
289 converted += wxT( "{comma}" );
290 else if( c == '\n' || c == '\r' )
291 converted += wxT( "{return}" );
292 else
293 converted += c;
294 }
295 else
296 {
297 converted += c;
298 }
299 }
300
301 return converted;
302}
303
304
305wxString UnescapeString( const wxString& aSource )
306{
307 size_t sourceLen = aSource.length();
308
309 // smallest escape string is three characters, shortcut everything else
310 if( sourceLen <= 2 )
311 {
312 return aSource;
313 }
314
315 wxString newbuf;
316 newbuf.reserve( sourceLen );
317
318 wxUniChar prev = 0;
319 wxUniChar ch = 0;
320
321 for( size_t i = 0; i < sourceLen; ++i )
322 {
323 prev = ch;
324 ch = aSource[i];
325
326 if( ch == '{' )
327 {
328 wxString token;
329 int depth = 1;
330 bool terminated = false;
331
332 for( i = i + 1; i < sourceLen; ++i )
333 {
334 ch = aSource[i];
335
336 if( ch == '{' )
337 depth++;
338 else if( ch == '}' )
339 depth--;
340
341 if( depth <= 0 )
342 {
343 terminated = true;
344 break;
345 }
346 else
347 {
348 token << ch;
349 }
350 }
351
352 if( !terminated )
353 {
354 newbuf << wxT( "{" ) << UnescapeString( token );
355 }
356 else if( prev == '$' || prev == '~' || prev == '^' || prev == '_' )
357 {
358 newbuf << wxT( "{" ) << UnescapeString( token ) << wxT( "}" );
359 }
360 else if( token == wxT( "dblquote" ) ) newbuf << wxT( "\"" );
361 else if( token == wxT( "quote" ) ) newbuf << wxT( "'" );
362 else if( token == wxT( "lt" ) ) newbuf << wxT( "<" );
363 else if( token == wxT( "gt" ) ) newbuf << wxT( ">" );
364 else if( token == wxT( "backslash" ) ) newbuf << wxT( "\\" );
365 else if( token == wxT( "slash" ) ) newbuf << wxT( "/" );
366 else if( token == wxT( "bar" ) ) newbuf << wxT( "|" );
367 else if( token == wxT( "comma" ) ) newbuf << wxT( "," );
368 else if( token == wxT( "colon" ) ) newbuf << wxT( ":" );
369 else if( token == wxT( "space" ) ) newbuf << wxT( " " );
370 else if( token == wxT( "dollar" ) ) newbuf << wxT( "$" );
371 else if( token == wxT( "tab" ) ) newbuf << wxT( "\t" );
372 else if( token == wxT( "return" ) ) newbuf << wxT( "\n" );
373 else if( token == wxT( "brace" ) ) newbuf << wxT( "{" );
374 else
375 {
376 newbuf << wxT( "{" ) << UnescapeString( token ) << wxT( "}" );
377 }
378 }
379 else
380 {
381 newbuf << ch;
382 }
383 }
384
385 return newbuf;
386}
387
388
389wxString TitleCaps( const wxString& aString )
390{
391 wxArrayString words;
392 wxString result;
393
394 wxStringSplit( aString, words, ' ' );
395
396 result.reserve( aString.length() );
397
398 for( const wxString& word : words )
399 {
400 if( !result.IsEmpty() )
401 result += wxT( " " );
402
403 result += word.Capitalize();
404 }
405
406 return result;
407}
408
409
410wxString InitialCaps( const wxString& aString )
411{
412 wxArrayString words;
413 wxString result;
414
415 wxStringSplit( aString, words, ' ' );
416
417 result.reserve( aString.length() );
418
419 for( const wxString& word : words )
420 {
421 if( result.IsEmpty() )
422 result += word.Capitalize();
423 else
424 result += wxT( " " ) + word.Lower();
425 }
426
427 return result;
428}
429
430
431int ReadDelimitedText( wxString* aDest, const char* aSource )
432{
433 std::string utf8; // utf8 but without escapes and quotes.
434 bool inside = false;
435 const char* start = aSource;
436 char cc;
437
438 while( (cc = *aSource++) != 0 )
439 {
440 if( cc == '"' )
441 {
442 if( inside )
443 break; // 2nd double quote is end of delimited text
444
445 inside = true; // first delimiter found, make note, do not copy
446 }
447
448 else if( inside )
449 {
450 if( cc == '\\' )
451 {
452 cc = *aSource++;
453
454 if( !cc )
455 break;
456
457 // do no copy the escape byte if it is followed by \ or "
458 if( cc != '"' && cc != '\\' )
459 utf8 += '\\';
460
461 utf8 += cc;
462 }
463 else
464 {
465 utf8 += cc;
466 }
467 }
468 }
469
470 *aDest = From_UTF8( utf8.c_str() );
471
472 return aSource - start;
473}
474
475
476int ReadDelimitedText( char* aDest, const char* aSource, int aDestSize )
477{
478 if( aDestSize <= 0 )
479 return 0;
480
481 bool inside = false;
482 const char* start = aSource;
483 char* limit = aDest + aDestSize - 1;
484 char cc;
485
486 while( ( cc = *aSource++ ) != 0 && aDest < limit )
487 {
488 if( cc == '"' )
489 {
490 if( inside )
491 break; // 2nd double quote is end of delimited text
492
493 inside = true; // first delimiter found, make note, do not copy
494 }
495 else if( inside )
496 {
497 if( cc == '\\' )
498 {
499 cc = *aSource++;
500
501 if( !cc )
502 break;
503
504 // do no copy the escape byte if it is followed by \ or "
505 if( cc != '"' && cc != '\\' )
506 *aDest++ = '\\';
507
508 if( aDest < limit )
509 *aDest++ = cc;
510 }
511 else
512 {
513 *aDest++ = cc;
514 }
515 }
516 }
517
518 *aDest = 0;
519
520 return aSource - start;
521}
522
523
524std::string EscapedUTF8( const wxString& aString )
525{
526 wxString str = aString;
527
528 // No new-lines allowed in quoted strings
529 str.Replace( wxT( "\r\n" ), wxT( "\r" ) );
530 str.Replace( wxT( "\n" ), wxT( "\r" ) );
531
532 std::string utf8 = TO_UTF8( aString );
533
534 std::string ret;
535
536 ret.reserve( utf8.length() + 2 );
537
538 ret += '"';
539
540 for( std::string::const_iterator it = utf8.begin(); it!=utf8.end(); ++it )
541 {
542 // this escaping strategy is designed to be compatible with ReadDelimitedText():
543 if( *it == '"' )
544 {
545 ret += '\\';
546 ret += '"';
547 }
548 else if( *it == '\\' )
549 {
550 ret += '\\'; // double it up
551 ret += '\\';
552 }
553 else
554 {
555 ret += *it;
556 }
557 }
558
559 ret += '"';
560
561 return ret;
562}
563
564
565wxString EscapeHTML( const wxString& aString )
566{
567 wxString converted;
568
569 converted.reserve( aString.length() );
570
571 for( wxUniChar c : aString )
572 {
573 if( c == '\"' )
574 converted += wxT( "&quot;" );
575 else if( c == '\'' )
576 converted += wxT( "&apos;" );
577 else if( c == '&' )
578 converted += wxT( "&amp;" );
579 else if( c == '<' )
580 converted += wxT( "&lt;" );
581 else if( c == '>' )
582 converted += wxT( "&gt;" );
583 else
584 converted += c;
585 }
586
587 return converted;
588}
589
590
591wxString UnescapeHTML( const wxString& aString )
592{
593 wxString converted = aString;
594
595 // clang-format off
596 static const std::map<wxString, wxString> c_replacements = {
597 { wxS( "quot" ), wxS( "\"" ) },
598 { wxS( "apos" ), wxS( "'" ) },
599 { wxS( "amp" ), wxS( "&" ) },
600 { wxS( "lt" ), wxS( "<" ) },
601 { wxS( "gt" ), wxS( ">" ) }
602 };
603 // clang-format on
604
605 // Construct regex
606 wxString regexStr = "&(#(\\d*)|#x([a-zA-Z0-9]{4})";
607
608 for( auto& [key, value] : c_replacements )
609 regexStr << '|' << key;
610
611 regexStr << ");";
612
613 wxRegEx regex( regexStr );
614
615 // Process matches
616 size_t start = 0;
617 size_t len = 0;
618
619 wxString result;
620 wxString str = converted;
621
622 while( regex.Matches( str ) )
623 {
624 std::vector<wxString> matches;
625 regex.GetMatch( &start, &len );
626
627 result << str.Left( start );
628
629 wxString code = regex.GetMatch( str, 1 );
630 wxString codeDec = regex.GetMatch( str, 2 );
631 wxString codeHex = regex.GetMatch( str, 3 );
632
633 if( !codeDec.IsEmpty() || !codeHex.IsEmpty() )
634 {
635 unsigned long codeVal = 0;
636
637 if( !codeDec.IsEmpty() )
638 codeDec.ToCULong( &codeVal );
639 else if( !codeHex.IsEmpty() )
640 codeHex.ToCULong( &codeVal, 16 );
641
642 if( codeVal != 0 )
643 result << wxUniChar( codeVal );
644 }
645 else if( auto val = get_opt( c_replacements, code ) )
646 {
647 result << *val;
648 }
649
650 str = str.Mid( start + len );
651 }
652
653 result << str;
654
655 return result;
656}
657
658
659wxString RemoveHTMLTags( const wxString& aInput )
660{
661 wxString str = aInput;
662 wxRegEx( wxS( "<[^>]*>" ) ).ReplaceAll( &str, wxEmptyString );
663
664 return str;
665}
666
667
668wxString LinkifyHTML( wxString aStr )
669{
670 static wxRegEx regex( wxS( "\\b(https?|ftp|file)://([-\\w+&@#/%?=~|!:,.;]*[^.,:;<>\\(\\)\\s\u00b6])" ),
671 wxRE_ICASE );
672
673 regex.ReplaceAll( &aStr, "<a href=\"\\0\">\\0</a>" );
674
675 return aStr;
676}
677
678
679bool IsURL( wxString aStr )
680{
681 static wxRegEx regex( wxS( "(https?|ftp|file)://([-\\w+&@#/%?=~|!:,.;]*[^.,:;<>\\s\u00b6])" ),
682 wxRE_ICASE );
683
684 regex.ReplaceAll( &aStr, "<a href=\"\\0\">\\0</a>" );
685
686 return regex.Matches( aStr );
687}
688
689
690bool NoPrintableChars( const wxString& aString )
691{
692 wxString tmp = aString;
693
694 return tmp.Trim( true ).Trim( false ).IsEmpty();
695}
696
697
698int PrintableCharCount( const wxString& aString )
699{
700 int char_count = 0;
701 int overbarDepth = -1;
702 int superSubDepth = -1;
703 int braceNesting = 0;
704
705 for( auto chIt = aString.begin(), end = aString.end(); chIt < end; ++chIt )
706 {
707 if( *chIt == '\t' )
708 {
709 // We don't format tabs in bitmap text (where this is currently used), so just
710 // drop them from the count.
711 continue;
712 }
713 else if( *chIt == '^' && superSubDepth == -1 )
714 {
715 auto lookahead = chIt;
716
717 if( ++lookahead != end && *lookahead == '{' )
718 {
719 chIt = lookahead;
720 superSubDepth = braceNesting;
721 braceNesting++;
722 continue;
723 }
724 }
725 else if( *chIt == '_' && superSubDepth == -1 )
726 {
727 auto lookahead = chIt;
728
729 if( ++lookahead != end && *lookahead == '{' )
730 {
731 chIt = lookahead;
732 superSubDepth = braceNesting;
733 braceNesting++;
734 continue;
735 }
736 }
737 else if( *chIt == '~' && overbarDepth == -1 )
738 {
739 auto lookahead = chIt;
740
741 if( ++lookahead != end && *lookahead == '{' )
742 {
743 chIt = lookahead;
744 overbarDepth = braceNesting;
745 braceNesting++;
746 continue;
747 }
748 }
749 else if( *chIt == '{' )
750 {
751 braceNesting++;
752 }
753 else if( *chIt == '}' )
754 {
755 if( braceNesting > 0 )
756 braceNesting--;
757
758 if( braceNesting == superSubDepth )
759 {
760 superSubDepth = -1;
761 continue;
762 }
763
764 if( braceNesting == overbarDepth )
765 {
766 overbarDepth = -1;
767 continue;
768 }
769 }
770
771 char_count++;
772 }
773
774 return char_count;
775}
776
777
778char* StrPurge( char* text )
779{
780 static const char whitespace[] = " \t\n\r\f\v";
781
782 if( text )
783 {
784 while( *text && strchr( whitespace, *text ) )
785 ++text;
786
787 char* cp = text + strlen( text ) - 1;
788
789 while( cp >= text && strchr( whitespace, *cp ) )
790 *cp-- = '\0';
791 }
792
793 return text;
794}
795
796
797char* GetLine( FILE* File, char* Line, int* LineNum, int SizeLine )
798{
799 do {
800 if( fgets( Line, SizeLine, File ) == nullptr )
801 return nullptr;
802
803 if( LineNum )
804 *LineNum += 1;
805
806 } while( Line[0] == '#' || Line[0] == '\n' || Line[0] == '\r' || Line[0] == 0 );
807
808 strtok( Line, "\n\r" );
809 return Line;
810}
811
812
814{
815 // on msys2 variant mingw64, in fmt::format the %z format
816 // (offset from UTC in the ISO 8601 format, e.g. -0430) does not work,
817 // and is in fact %Z (locale-dependent time zone name or abbreviation) and breaks our date.
818 // However, on msys2 variant ucrt64, it works (this is not the same code in fmt::format)
819#if defined(__MINGW32__) && !defined(_UCRT)
820 return fmt::format( "{:%FT%T}", fmt::localtime( std::time( nullptr ) ) );
821#else
822 return fmt::format( "{:%FT%T%z}", fmt::localtime( std::time( nullptr ) ) );
823#endif
824}
825
826
827int StrNumCmp( const wxString& aString1, const wxString& aString2, bool aIgnoreCase )
828{
829 int nb1 = 0, nb2 = 0;
830
831 auto str1 = aString1.begin();
832 auto str2 = aString2.begin();
833
834 while( str1 != aString1.end() && str2 != aString2.end() )
835 {
836 wxUniChar c1 = *str1;
837 wxUniChar c2 = *str2;
838
839 if( wxIsdigit( c1 ) && wxIsdigit( c2 ) ) // Both characters are digits, do numeric compare.
840 {
841 nb1 = 0;
842 nb2 = 0;
843
844 do
845 {
846 c1 = *str1;
847 nb1 = nb1 * 10 + (int) c1 - '0';
848 ++str1;
849 } while( str1 != aString1.end() && wxIsdigit( *str1 ) );
850
851 do
852 {
853 c2 = *str2;
854 nb2 = nb2 * 10 + (int) c2 - '0';
855 ++str2;
856 } while( str2 != aString2.end() && wxIsdigit( *str2 ) );
857
858 if( nb1 < nb2 )
859 return -1;
860
861 if( nb1 > nb2 )
862 return 1;
863
864 c1 = ( str1 != aString1.end() ) ? *str1 : wxUniChar( 0 );
865 c2 = ( str2 != aString2.end() ) ? *str2 : wxUniChar( 0 );
866 }
867
868 // Any numerical comparisons to here are identical.
869 if( aIgnoreCase )
870 {
871 if( c1 != c2 )
872 {
873 wxUniChar uc1 = wxToupper( c1 );
874 wxUniChar uc2 = wxToupper( c2 );
875
876 if( uc1 != uc2 )
877 return uc1 < uc2 ? -1 : 1;
878 }
879 }
880 else
881 {
882 if( c1 < c2 )
883 return -1;
884
885 if( c1 > c2 )
886 return 1;
887 }
888
889 if( str1 != aString1.end() )
890 ++str1;
891
892 if( str2 != aString2.end() )
893 ++str2;
894 }
895
896 if( str1 == aString1.end() && str2 != aString2.end() )
897 {
898 return -1; // Identical to here but aString1 is longer.
899 }
900 else if( str1 != aString1.end() && str2 == aString2.end() )
901 {
902 return 1; // Identical to here but aString2 is longer.
903 }
904
905 return 0;
906}
907
908
909bool WildCompareString( const wxString& pattern, const wxString& string_to_tst,
910 bool case_sensitive )
911{
912 const wxChar* cp = nullptr;
913 const wxChar* mp = nullptr;
914 const wxChar* wild = nullptr;
915 const wxChar* str = nullptr;
916 wxString _pattern, _string_to_tst;
917
918 if( case_sensitive )
919 {
920 wild = pattern.GetData();
921 str = string_to_tst.GetData();
922 }
923 else
924 {
925 _pattern = pattern;
926 _pattern.MakeUpper();
927 _string_to_tst = string_to_tst;
928 _string_to_tst.MakeUpper();
929 wild = _pattern.GetData();
930 str = _string_to_tst.GetData();
931 }
932
933 while( ( *str ) && ( *wild != '*' ) )
934 {
935 if( ( *wild != *str ) && ( *wild != '?' ) )
936 return false;
937
938 wild++;
939 str++;
940 }
941
942 while( *str )
943 {
944 if( *wild == '*' )
945 {
946 if( !*++wild )
947 return true;
948
949 mp = wild;
950 cp = str + 1;
951 }
952 else if( ( *wild == *str ) || ( *wild == '?' ) )
953 {
954 wild++;
955 str++;
956 }
957 else
958 {
959 wild = mp;
960 str = cp++;
961 }
962 }
963
964 while( *wild == '*' )
965 {
966 wild++;
967 }
968
969 return !*wild;
970}
971
972
973bool ApplyModifier( double& value, const wxString& aString )
974{
976 static const wxString modifiers( wxT( "pnuµμmkKM" ) );
977
978 if( !aString.length() )
979 return false;
980
981 wxChar modifier;
982 wxString units;
983
984 if( modifiers.Find( aString[ 0 ] ) >= 0 )
985 {
986 modifier = aString[ 0 ];
987 units = aString.Mid( 1 ).Trim();
988 }
989 else
990 {
991 modifier = ' ';
992 units = aString.Mid( 0 ).Trim();
993 }
994
995 if( units.length()
996 && !units.IsSameAs( wxT( "F" ), false )
997 && !units.IsSameAs( wxT( "hz" ), false )
998 && !units.IsSameAs( wxT( "W" ), false )
999 && !units.IsSameAs( wxT( "V" ), false )
1000 && !units.IsSameAs( wxT( "A" ), false )
1001 && !units.IsSameAs( wxT( "H" ), false ) )
1002 {
1003 return false;
1004 }
1005
1006 if( modifier == 'p' )
1007 value *= 1.0e-12;
1008 if( modifier == 'n' )
1009 value *= 1.0e-9;
1010 else if( modifier == 'u' || modifier == wxS( "µ" )[0] || modifier == wxS( "μ" )[0] )
1011 value *= 1.0e-6;
1012 else if( modifier == 'm' )
1013 value *= 1.0e-3;
1014 else if( modifier == 'k' || modifier == 'K' )
1015 value *= 1.0e3;
1016 else if( modifier == 'M' )
1017 value *= 1.0e6;
1018 else if( modifier == 'G' )
1019 value *= 1.0e9;
1020
1021 return true;
1022}
1023
1024
1025bool convertSeparators( wxString* value )
1026{
1027 // Note: fetching the decimal separator from the current locale isn't a silver bullet because
1028 // it assumes the current computer's locale is the same as the locale the schematic was
1029 // authored in -- something that isn't true, for instance, when sharing designs through
1030 // DIYAudio.com.
1031 //
1032 // Some values are self-describing: multiple instances of a single separator character must be
1033 // thousands separators; a single instance of each character must be a thousands separator
1034 // followed by a decimal separator; etc.
1035 //
1036 // Only when presented with an ambiguous value do we fall back on the current locale.
1037
1038 value->Replace( wxS( " " ), wxEmptyString );
1039
1040 wxChar ambiguousSeparator = '?';
1041 wxChar thousandsSeparator = '?';
1042 bool thousandsSeparatorFound = false;
1043 wxChar decimalSeparator = '?';
1044 bool decimalSeparatorFound = false;
1045 int digits = 0;
1046
1047 for( int ii = (int) value->length() - 1; ii >= 0; --ii )
1048 {
1049 wxChar c = value->GetChar( ii );
1050
1051 if( c >= '0' && c <= '9' )
1052 {
1053 digits += 1;
1054 }
1055 else if( c == '.' || c == ',' )
1056 {
1057 if( decimalSeparator != '?' || thousandsSeparator != '?' )
1058 {
1059 // We've previously found a non-ambiguous separator...
1060
1061 if( c == decimalSeparator )
1062 {
1063 if( thousandsSeparatorFound )
1064 return false; // decimal before thousands
1065 else if( decimalSeparatorFound )
1066 return false; // more than one decimal
1067 else
1068 decimalSeparatorFound = true;
1069 }
1070 else if( c == thousandsSeparator )
1071 {
1072 if( digits != 3 )
1073 return false; // thousands not followed by 3 digits
1074 else
1075 thousandsSeparatorFound = true;
1076 }
1077 }
1078 else if( ambiguousSeparator != '?' )
1079 {
1080 // We've previously found a separator, but we don't know for sure which...
1081
1082 if( c == ambiguousSeparator )
1083 {
1084 // They both must be thousands separators
1085 thousandsSeparator = ambiguousSeparator;
1086 thousandsSeparatorFound = true;
1087 decimalSeparator = c == '.' ? ',' : '.';
1088 }
1089 else
1090 {
1091 // The first must have been a decimal, and this must be a thousands.
1092 decimalSeparator = ambiguousSeparator;
1093 decimalSeparatorFound = true;
1094 thousandsSeparator = c;
1095 thousandsSeparatorFound = true;
1096 }
1097 }
1098 else
1099 {
1100 // This is the first separator...
1101
1102 // If it's preceded by a '0' (only), or if it's followed by some number of
1103 // digits not equal to 3, then it -must- be a decimal separator.
1104 //
1105 // In all other cases we don't really know what it is yet.
1106
1107 if( ( ii == 1 && value->GetChar( 0 ) == '0' ) || digits != 3 )
1108 {
1109 decimalSeparator = c;
1110 decimalSeparatorFound = true;
1111 thousandsSeparator = c == '.' ? ',' : '.';
1112 }
1113 else
1114 {
1115 ambiguousSeparator = c;
1116 }
1117 }
1118
1119 digits = 0;
1120 }
1121 else
1122 {
1123 digits = 0;
1124 }
1125 }
1126
1127 // If we found nothing definitive then we have to look at the current locale
1128 if( decimalSeparator == '?' && thousandsSeparator == '?' )
1129 {
1130 const struct lconv* lc = localeconv();
1131
1132 decimalSeparator = lc->decimal_point[0];
1133 thousandsSeparator = decimalSeparator == '.' ? ',' : '.';
1134 }
1135
1136 // Convert to C-locale
1137 value->Replace( thousandsSeparator, wxEmptyString );
1138 value->Replace( decimalSeparator, '.' );
1139
1140 return true;
1141}
1142
1143
1144int ValueStringCompare( const wxString& strFWord, const wxString& strSWord )
1145{
1146 // Compare unescaped text
1147 wxString fWord = UnescapeString( strFWord );
1148 wxString sWord = UnescapeString( strSWord );
1149
1150 // The different sections of the two strings
1151 wxString strFWordBeg, strFWordMid, strFWordEnd;
1152 wxString strSWordBeg, strSWordMid, strSWordEnd;
1153
1154 // Split the two strings into separate parts
1155 SplitString( fWord, &strFWordBeg, &strFWordMid, &strFWordEnd );
1156 SplitString( sWord, &strSWordBeg, &strSWordMid, &strSWordEnd );
1157
1158 // Compare the Beginning section of the strings
1159 int isEqual = strFWordBeg.CmpNoCase( strSWordBeg );
1160
1161 if( isEqual > 0 )
1162 {
1163 return 1;
1164 }
1165 else if( isEqual < 0 )
1166 {
1167 return -1;
1168 }
1169 else
1170 {
1171 // If the first sections are equal compare their digits
1172 double lFirstNumber = 0;
1173 double lSecondNumber = 0;
1174 bool endingIsModifier = false;
1175
1176 convertSeparators( &strFWordMid );
1177 convertSeparators( &strSWordMid );
1178
1179 LOCALE_IO toggle; // toggles on, then off, the C locale.
1180
1181 strFWordMid.ToDouble( &lFirstNumber );
1182 strSWordMid.ToDouble( &lSecondNumber );
1183
1184 endingIsModifier |= ApplyModifier( lFirstNumber, strFWordEnd );
1185 endingIsModifier |= ApplyModifier( lSecondNumber, strSWordEnd );
1186
1187 if( lFirstNumber > lSecondNumber )
1188 return 1;
1189 else if( lFirstNumber < lSecondNumber )
1190 return -1;
1191 // If the first two sections are equal and the endings are modifiers then compare them
1192 else if( !endingIsModifier )
1193 return strFWordEnd.CmpNoCase( strSWordEnd );
1194 // Ran out of things to compare; they must match
1195 else
1196 return 0;
1197 }
1198}
1199
1200
1201int SplitString( const wxString& strToSplit,
1202 wxString* strBeginning,
1203 wxString* strDigits,
1204 wxString* strEnd )
1205{
1206 static const wxString separators( wxT( ".," ) );
1207
1208 // Clear all the return strings
1209 strBeginning->Empty();
1210 strDigits->Empty();
1211 strEnd->Empty();
1212
1213 // There no need to do anything if the string is empty
1214 if( strToSplit.length() == 0 )
1215 return 0;
1216
1217 // Starting at the end of the string look for the first digit
1218 int ii;
1219
1220 for( ii = (strToSplit.length() - 1); ii >= 0; ii-- )
1221 {
1222 if( wxIsdigit( strToSplit[ii] ) )
1223 break;
1224 }
1225
1226 // If there were no digits then just set the single string
1227 if( ii < 0 )
1228 {
1229 *strBeginning = strToSplit;
1230 }
1231 else
1232 {
1233 // Since there is at least one digit this is the trailing string
1234 *strEnd = strToSplit.substr( ii + 1 );
1235
1236 // Go to the end of the digits
1237 int position = ii + 1;
1238
1239 for( ; ii >= 0; ii-- )
1240 {
1241 if( !wxIsdigit( strToSplit[ii] ) && separators.Find( strToSplit[ii] ) < 0 )
1242 break;
1243 }
1244
1245 // If all that was left was digits, then just set the digits string
1246 if( ii < 0 )
1247 *strDigits = strToSplit.substr( 0, position );
1248
1249 /* We were only looking for the last set of digits everything else is
1250 * part of the preamble */
1251 else
1252 {
1253 *strDigits = strToSplit.substr( ii + 1, position - ii - 1 );
1254 *strBeginning = strToSplit.substr( 0, ii + 1 );
1255 }
1256 }
1257
1258 return 0;
1259}
1260
1261
1262int GetTrailingInt( const wxString& aStr )
1263{
1264 int number = 0;
1265 int base = 1;
1266
1267 // Trim and extract the trailing numeric part
1268 int index = aStr.Len() - 1;
1269
1270 while( index >= 0 )
1271 {
1272 const char chr = aStr.GetChar( index );
1273
1274 if( chr < '0' || chr > '9' )
1275 break;
1276
1277 number += ( chr - '0' ) * base;
1278 base *= 10;
1279 index--;
1280 }
1281
1282 return number;
1283}
1284
1285
1287{
1289}
1290
1291
1292bool ReplaceIllegalFileNameChars( std::string* aName, int aReplaceChar )
1293{
1294 bool changed = false;
1295 std::string result;
1296 result.reserve( aName->length() );
1297
1298 for( std::string::iterator it = aName->begin(); it != aName->end(); ++it )
1299 {
1300 if( strchr( illegalFileNameChars, *it ) )
1301 {
1302 if( aReplaceChar )
1303 StrPrintf( &result, "%c", aReplaceChar );
1304 else
1305 StrPrintf( &result, "%%%02x", *it );
1306
1307 changed = true;
1308 }
1309 else
1310 {
1311 result += *it;
1312 }
1313 }
1314
1315 if( changed )
1316 *aName = result;
1317
1318 return changed;
1319}
1320
1321
1322bool ReplaceIllegalFileNameChars( wxString& aName, int aReplaceChar )
1323{
1324 bool changed = false;
1325 wxString result;
1326 result.reserve( aName.Length() );
1327 wxString illWChars = GetIllegalFileNameWxChars();
1328
1329 for( wxString::iterator it = aName.begin(); it != aName.end(); ++it )
1330 {
1331 if( illWChars.Find( *it ) != wxNOT_FOUND )
1332 {
1333 if( aReplaceChar )
1334 result += aReplaceChar;
1335 else
1336 result += wxString::Format( "%%%02x", *it );
1337
1338 changed = true;
1339 }
1340 else
1341 {
1342 result += *it;
1343 }
1344 }
1345
1346 if( changed )
1347 aName = result;
1348
1349 return changed;
1350}
1351
1352
1353void wxStringSplit( const wxString& aText, wxArrayString& aStrings, wxChar aSplitter )
1354{
1355 wxString tmp;
1356
1357 for( unsigned ii = 0; ii < aText.Length(); ii++ )
1358 {
1359 if( aText[ii] == aSplitter )
1360 {
1361 aStrings.Add( tmp );
1362 tmp.Clear();
1363 }
1364 else
1365 {
1366 tmp << aText[ii];
1367 }
1368 }
1369
1370 if( !tmp.IsEmpty() )
1371 aStrings.Add( tmp );
1372}
1373
1374
1375void StripTrailingZeros( wxString& aStringValue, unsigned aTrailingZeroAllowed )
1376{
1377 struct lconv* lc = localeconv();
1378 char sep = lc->decimal_point[0];
1379 unsigned sep_pos = aStringValue.Find( sep );
1380
1381 if( sep_pos > 0 )
1382 {
1383 // We want to keep at least aTrailingZeroAllowed digits after the separator
1384 unsigned min_len = sep_pos + aTrailingZeroAllowed + 1;
1385
1386 while( aStringValue.Len() > min_len )
1387 {
1388 if( aStringValue.Last() == '0' )
1389 aStringValue.RemoveLast();
1390 else
1391 break;
1392 }
1393 }
1394}
1395
1396
1397std::string FormatDouble2Str( double aValue )
1398{
1399 std::string buf;
1400
1401 if( aValue != 0.0 && std::fabs( aValue ) <= 0.0001 )
1402 {
1403 buf = fmt::format( "{:.16f}", aValue );
1404
1405 // remove trailing zeros (and the decimal marker if needed)
1406 while( !buf.empty() && buf[buf.size() - 1] == '0' )
1407 {
1408 buf.pop_back();
1409 }
1410
1411 // if the value was really small
1412 // we may have just stripped all the zeros after the decimal
1413 if( buf[buf.size() - 1] == '.' )
1414 {
1415 buf.pop_back();
1416 }
1417 }
1418 else
1419 {
1420 buf = fmt::format( "{:.10g}", aValue );
1421 }
1422
1423 return buf;
1424}
1425
1426
1427std::string UIDouble2Str( double aValue )
1428{
1429 char buf[50];
1430 int len;
1431
1432 if( aValue != 0.0 && std::fabs( aValue ) <= 0.0001 )
1433 {
1434 // For these small values, %f works fine,
1435 // and %g gives an exponent
1436 len = snprintf( buf, sizeof( buf ), "%.16f", aValue );
1437
1438 while( --len > 0 && buf[len] == '0' )
1439 buf[len] = '\0';
1440
1441 if( buf[len] == '.' || buf[len] == ',' )
1442 buf[len] = '\0';
1443 else
1444 ++len;
1445 }
1446 else
1447 {
1448 // For these values, %g works fine, and sometimes %f
1449 // gives a bad value (try aValue = 1.222222222222, with %.16f format!)
1450 len = snprintf( buf, sizeof( buf ), "%.10g", aValue );
1451 }
1452
1453 return std::string( buf, len );
1454}
1455
1456
1457wxString From_UTF8( const char* cstring )
1458{
1459 // Convert an expected UTF8 encoded C string to a wxString
1460 wxString line = wxString::FromUTF8( cstring );
1461
1462 if( line.IsEmpty() ) // happens when cstring is not a valid UTF8 sequence
1463 {
1464 line = wxConvCurrent->cMB2WC( cstring ); // try to use locale conversion
1465
1466 if( line.IsEmpty() )
1467 line = wxString::From8BitData( cstring ); // try to use native string
1468 }
1469
1470 return line;
1471}
1472
1473
1474wxString From_UTF8( const std::string& aString )
1475{
1476 // Convert an expected UTF8 encoded std::string to a wxString
1477 wxString line = wxString::FromUTF8( aString );
1478
1479 if( line.IsEmpty() ) // happens when aString is not a valid UTF8 sequence
1480 {
1481 line = wxConvCurrent->cMB2WC( aString.c_str() ); // try to use locale conversion
1482
1483 if( line.IsEmpty() )
1484 line = wxString::From8BitData( aString.c_str() ); // try to use native string
1485 }
1486
1487 return line;
1488}
1489
1490
1491wxString NormalizeFileUri( const wxString& aFileUri )
1492{
1493 wxString uriPathAndFileName;
1494
1495 wxCHECK( aFileUri.StartsWith( wxS( "file://" ), &uriPathAndFileName ), aFileUri );
1496
1497 wxString tmp = uriPathAndFileName;
1498 wxString retv = wxS( "file://" );
1499
1500 tmp.Replace( wxS( "\\" ), wxS( "/" ) );
1501 tmp.Replace( wxS( ":" ), wxS( "" ) );
1502
1503 if( !tmp.IsEmpty() && tmp[0] != '/' )
1504 tmp = wxS( "/" ) + tmp;
1505
1506 retv += tmp;
1507
1508 return retv;
1509}
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:49
This file contains miscellaneous commonly used macros and functions.
std::optional< V > get_opt(const std::map< wxString, V > &aMap, const wxString &aKey)
Definition: map_helpers.h:34
int StrPrintf(std::string *result, const char *format,...)
This is like sprintf() but the output is appended to a std::string instead of to a character array.
Definition: richio.cpp:71
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 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)
static const char illegalFileNameChars[]
Illegal file name characters used to ensure file names will be valid on all supported platforms.
wxString LinkifyHTML(wxString aStr)
Wraps links in HTML tags.
bool convertSeparators(wxString *value)
wxString GetIllegalFileNameWxChars()
wxString From_UTF8(const char *cstring)
bool ConvertSmartQuotesAndDashes(wxString *aString)
Convert curly quotes and em/en dashes to straight quotes and dashes.
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.
bool ReplaceIllegalFileNameChars(std::string *aName, int aReplaceChar)
Checks aName for illegal file name characters.
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 ...
std::string FormatDouble2Str(double aValue)
Print a float number without using scientific notation and no trailing 0 This function is intended in...
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.
char * StrPurge(char *text)
Remove leading and training spaces, tabs and end of line chars in text.
bool NoPrintableChars(const wxString &aString)
Return true if the string is empty or contains only whitespace.
wxString UnescapeHTML(const wxString &aString)
Return a new wxString unescaped from HTML format.
bool IsURL(wxString aStr)
Performs a URL sniff-test on a string.
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: string_utils.h:408
ESCAPE_CONTEXT
Escape/Unescape routines to safely encode reserved-characters in various contexts.
Definition: string_utils.h:52
@ CTX_FILENAME
Definition: string_utils.h:61
@ CTX_QUOTED_STR
Definition: string_utils.h:57
@ CTX_LINE
Definition: string_utils.h:59
@ CTX_NO_SPACE
Definition: string_utils.h:62
@ CTX_LIBID
Definition: string_utils.h:54
@ CTX_NETNAME
Definition: string_utils.h:53
@ CTX_CSV
Definition: string_utils.h:60
@ CTX_IPC
Definition: string_utils.h:56
@ CTX_LEGACY_LIBID
Definition: string_utils.h:55
@ CTX_JS_STR
Definition: string_utils.h:58
VECTOR2I end