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