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