ALib C++ Library
Library Version: 2510 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
formatterpythonstyle.cpp
1// #################################################################################################
2// ALib C++ Library
3//
4// Copyright 2013-2025 A-Worx GmbH, Germany
5// Published under 'Boost Software License' (a free software license, see LICENSE.txt)
6// #################################################################################################
7#include "alib_precompile.hpp"
8#if !defined(ALIB_C20_MODULES) || ((ALIB_C20_MODULES != 0) && (ALIB_C20_MODULES != 1))
9# error "Symbol ALIB_C20_MODULES has to be given to the compiler as either 0 or 1"
10#endif
11#if ALIB_C20_MODULES
12 module;
13#endif
14// ====================================== Global Fragment ======================================
15#include "alib/alib.inl"
16// =========================================== Module ==========================================
17#if ALIB_C20_MODULES
19 import ALib.Lang;
20 import ALib.EnumOps;
21 import ALib.Strings;
22 import ALib.Exceptions;
23# if ALIB_CAMP
24 import ALib.Camp.Base;
25# endif
26#else
27# include "ALib.Lang.H"
28# include "ALib.Strings.H"
29# include "ALib.Exceptions.H"
31# include "ALib.Camp.Base.H"
32#endif
33// ====================================== Implementation =======================================
34using namespace alib::strings;
35
36namespace alib::format {
37
38
40: FormatterStdImpl( A_CHAR( "FormatterPythonStyle" ) )
42{
43 // set number format to python defaults
44 DefaultNumberFormat.Flags-= NumberFormatFlags::ForceDecimalPoint;
45 DefaultNumberFormat.Flags+= NumberFormatFlags::WriteExponentPlusSign;
46}
47
48
50{
51 // create a clone
52 SPFormatter clone;
54
55 // create a clone of #Next, in the case that next is derived from std base class
56 if( Next )
57 clone->Next= Next->Clone();
58
59 // copy my settings, that's it
60 clone->CloneSettings( *this );
61 return clone;
62}
63
65{
66 // first invoke parent's setting...
68
69 // ...then make some "python like" adjustments
70 placeholderPS.Conversion = nullptr;
71 placeholderPS.ConversionPos = -1;
72
73 placeholder.NF.Flags -= NumberFormatFlags::ForceDecimalPoint;
74 placeholder.NF.ExponentSeparator = AlternativeNumberFormat.ExponentSeparator; // choose lower case as default
75 placeholder.NF.INFLiteral = AlternativeNumberFormat.INFLiteral; // choose lower case as default
76 placeholder.NF.NANLiteral = AlternativeNumberFormat.NANLiteral; // choose lower case as default
77 placeholder.NF.Flags += NumberFormatFlags::OmitTrailingFractionalZeros;
78
79 placeholderPS.Precision = -1;
80 placeholderPS.PrecisionPos = -1;
81 placeholderPS.DefaultPrecision = 6;
82}
83
84
86{
87 integer idx= 0;
88 while( (idx= parser.IndexOf('{', idx )) >= 0
89 && parser.CharAt( idx + 1 ) == '{' )
90 {
91 // double ESC found (escaped {{)
92 idx+= + 2;
93 }
94 return idx;
95}
96
97
99{
100 // read position
101 if( isdigit( parser.CharAtStart() ) )
102 {
103 int argNo;
104 parser.ConsumeDecDigits( argNo );
105 setArgument( argNo );
106 }
107
108 // read conversion
109 if( parser.CharAtStart() == '!' )
110 {
111 placeholderPS.ConversionPos = int( formatString.Length()
112 - parser.Length() - 1 );
113 integer endConversion= parser.IndexOfAny<lang::Inclusion::Include>( A_CHAR( ":}" ) );
114 if( endConversion < 0 )
116 formatString, placeholderPS.ConversionPos );
117
118 parser.ConsumeChars( endConversion, &placeholderPS.Conversion );
119 }
120
121 // read colon and format spec
122 if( parser.CharAtStart() == ':' )
123 {
124 parser.ConsumeChars(1);
125
126 // find end of format spec: allow "\}", "\{" and nested "{..}" in format string
127 Substring subParser= parser;
128 int depth= 0;
129 while( subParser.IsNotEmpty() )
130 {
131 if( subParser.CharAtStart() == '}' )
132 {
133 if(depth == 0 )
134 break;
135
136 --depth;
137 subParser.ConsumeChars<NC>(1);
138 continue;
139 }
140
141 if( subParser.CharAtStart() == '\\' ) { subParser.ConsumeChars(2); continue; }
142 if( subParser.CharAtStart() == '{' ) ++depth;
143 subParser.ConsumeChars<NC>(1);
144 }
145
146 if ( subParser.IsEmpty() )
149 formatString.Length() );
150
151 // extract format spec to separate substring
152 parser.ConsumeChars( parser.Length() - subParser.Length(), &(placeholder.FormatSpec) ) ;
153 }
154
155 // check for closing brace
156 if( parser.CharAtStart() != '}' )
159 formatString.Length() - parser.Length() );
160
161 parser.ConsumeChars(1);
162 return true;
163}
164
165
166
168{
169 // parse alignment and fill character. This is a bit tricky to shorten the code.
170 // The alignment token <,>,^ or = can be on first or second position. If second, then the
171 // first was a fill character. Therefore , we loop from 0 to 1....
172 integer charsToConsume= 1;
173 placeholder.AlignmentSpecified= true;
174 Substring& formatSpec= placeholder.FormatSpec;
175
176
177 int cNo= 0;
178 do switch ( formatSpec.CharAt( cNo++ ) )
179 {
180 case '<': placeholder.ValueAlignment= lang::Alignment::Left; break;
181 case '>': placeholder.ValueAlignment= lang::Alignment::Right; break;
182 case '^': placeholder.ValueAlignment= lang::Alignment::Center; break;
183 case '=': placeholder.ValueAlignment= lang::Alignment::Right;
184 placeholder.SignPaddingMode= true; break;
185
186 default:
187 if(cNo == 1 )
188 {
189 charsToConsume= 2;
190 placeholder.FillChar= formatSpec.CharAtStart();
191 }
192 else
193 {
194 charsToConsume= 0;
195 placeholder.FillChar= ' ';
196 placeholder.AlignmentSpecified= false;
197 }
198 break;
199 }
200 while( cNo < 2 && charsToConsume != 1 );
201 formatSpec.ConsumeChars( charsToConsume );
202
203 // other things to parse
204 character actChar;
205 while( (actChar= formatSpec.CharAtStart()) != '\0' )
206 {
207 // width
208 if( isdigit( actChar ) )
209 {
210 // Python doc says:
211 // When no explicit alignment is given, preceding the width field by a zero ('0') character
212 // enables sign-aware zero-padding for numeric types.
213 // This is equivalent to a fill character of '0' with an alignment type of '='.
214 if ( actChar == '0' )
215 placeholder.SignPaddingMode= true;
216 formatSpec.ConsumeDecDigits( placeholder.Width );
217
218 continue; // not consume below
219 }
220
221 // precision. Python doc says:
222 // "The precision is a decimal number indicating how many digits should be displayed
223 // after the decimal point for a floating point value formatted with 'f' and 'F', or
224 // before and after the decimal point for a floating point value formatted with 'g' or 'G'.
225 // For non-number types the field indicates the maximum field size - in other words,
226 // how many characters will be used from the field content. The precision is not
227 // allowed for integral values."
228 else if( actChar == '.' )
229 {
230 placeholderPS.PrecisionPos= int( formatString.Length()
231 - parser.Length()
232 - formatSpec.Length() -1 );
233 formatSpec.ConsumeChars( 1 );
234 if ( ! formatSpec.ConsumeDecDigits( placeholderPS.Precision ) )
237 formatString.Length()
238 - parser.Length()
239 - formatSpec.Length() - 1 );
240 continue;
241 }
242
243
244 // ----------------------------- Types -------------------------------------
245 else if (String(A_CHAR( "sdcboxXeEfFngGhHB%" )).IndexOf( actChar ) >= 0 )
246 {
247 if ( placeholder.TypeCode != '\0' )
249 actChar, placeholder.TypeCode,
250 placeholder.Arg->TypeID(),
252 formatString.Length() - parser.Length()
253 - formatSpec.Length() - 1 );
254
255 placeholder.TypeCode= actChar;
256 placeholder.TypeCodePosition= int( formatString.Length()
257 - parser.Length()
258 - formatSpec.Length() - 1 );
259
260 if( String(A_CHAR( "EGF" )).IndexOf( actChar ) >= 0 )
261 {
262 placeholder.NF.ExponentSeparator= DefaultNumberFormat.ExponentSeparator;
263 placeholder.NF.INFLiteral= DefaultNumberFormat.INFLiteral;
264 placeholder.NF.NANLiteral= DefaultNumberFormat.NANLiteral;
265 }
266
267 switch( actChar )
268 {
269 case 's': placeholder.Type= PHTypes::String; break;
270 case 'd': placeholder.Type= PHTypes::IntBase10; break;
271 case 'c': placeholder.Type= PHTypes::Character; break;
272 case 'b': placeholder.Type= PHTypes::IntBinary; break;
273 case 'o': placeholder.Type= PHTypes::IntOctal; break;
274
275 case 'X': placeholder.NF.Flags-= NumberFormatFlags::HexLowerCase; ALIB_FALLTHROUGH
276 case 'x': placeholder.Type= PHTypes::IntHex; break;
277
278 case 'H': placeholder.NF.Flags-= NumberFormatFlags::HexLowerCase; ALIB_FALLTHROUGH
279 case 'h': placeholder.Type= PHTypes::HashCode; break;
280 case 'B': placeholder.Type= PHTypes::Bool; break;
281
282 // float types
283 case 'E':
284 case 'e': placeholder.Type= PHTypes::Float;
285 placeholder.NF.Flags+= NumberFormatFlags::ForceScientific; break;
286
287 case '%': placeholder.IsPercentage= true; ALIB_FALLTHROUGH
288 case 'f':
289 case 'F': placeholder.NF.Flags-= NumberFormatFlags::OmitTrailingFractionalZeros;
290 placeholder.Type= PHTypes::Float; break;
291
292 case 'n': placeholder.NF.DecimalPointChar= AlternativeNumberFormat.DecimalPointChar;
293 placeholder.NF.ThousandsGroupChar= AlternativeNumberFormat.ThousandsGroupChar;
295 case 'G': ALIB_FALLTHROUGH
296 case 'g': placeholder.Type= PHTypes::Float;
297 placeholderPS.DefaultPrecision= -1;
298 break;
299
300 default: ALIB_ERROR( "FORMAT", "Unhandled character in choices string above" )
301 return false;
302
303 }//switch
304 }
305
306
307 // others
308 else switch( actChar )
309 {
310 // sign
311 case '+': placeholder.NF.PlusSign= '+' ; break;
312 case '-': placeholder.NF.PlusSign= '\0'; break;
313 case ' ': placeholder.NF.PlusSign= ' ' ; break;
314
315 // alternate version ('#')
316 case '#': placeholder.WriteBinOctHexPrefix= true;
317 placeholder.NF.Flags+= NumberFormatFlags::ForceDecimalPoint;
318 placeholder.NF.Flags-= NumberFormatFlags::OmitTrailingFractionalZeros;
319 break;
320
321 // Enable grouping
322 case ',': placeholder.NF.Flags+= NumberFormatFlags::WriteGroupChars;
323 break;
324
325
326 default:
328 actChar,
330 formatString.Length()
331 - parser.Length()
332 - formatSpec.Length() - 1,
333 placeholder.Arg->TypeID()
334 );
335 }
336
337 formatSpec.ConsumeChars(1);
338 }
339
340 return true;
341}
342
343
345{
346 if( length == 0)
347 return;
348
349 targetString->EnsureRemainingCapacity( length );
350 auto* src = parser.Buffer();
351 auto* dest= targetString->VBuffer() + targetString->Length();
352 parser.ConsumeChars( length );
353
354 character c1;
355 character c2= *src;
356 while( length > 1 )
357 {
358 c1= c2;
359 c2= *++src;
360
361 if( c1 == '\n' ) // reset auto-sizes when act ual character is '\n'
362 Sizes->Restart();
363
364 else if( ( c1 == '{' && c2 =='{')
365 || ( c1 == '}' && c2 =='}')
366 || c1 == '\\' )
367 {
368 if( c1 == '\\' )
369 switch(c2)
370 {
371 case 'r': c1= '\r' ; break;
372 case 'n': c1= '\n' ;
373 // reset auto-sizes with \n
374 targetStringStartLength= dest - targetString->VBuffer() + 1;
375 Sizes->Restart();
376 break;
377 case 't': c1= '\t' ; break;
378 case 'a': c1= '\a' ; break;
379 case 'b': c1= '\b' ; break;
380 case 'v': c1= '\v' ; break;
381 case 'f': c1= '\f' ; break;
382 case '"': c1= '"' ; break;
383 default: c1= '?' ; break;
384 }
385
386 c2= *++src;
387 --length;
388 }
389
390 *dest++= c1;
391 --length;
392 }
393
394 // copy last character and adjust target string length:
395 // Note: length usually is 1. Only if last character is an escape sequence, it is 0.
396 if( length == 1)
397 {
398 *dest= *src;
399 // reset auto-sizes when last character is '\n'
400 if( *dest == '\n' )
401 Sizes->Restart();
402 }
403 targetString->SetLength( dest - targetString->VBuffer() + length);
404}
405
406
408{
409 bool isPreProcess= startIdx < 0;
410 bool isPostProcess= startIdx>= 0 && target == nullptr;
411 Substring conversion= placeholderPS.Conversion;
412 ++placeholderPS.ConversionPos;
413
414 while( conversion.IsNotEmpty() )
415 {
416 if( !conversion.ConsumeChar('!') )
418 placeholder.Arg->TypeID(),
419 formatString, placeholderPS.ConversionPos
420 + placeholderPS.Conversion.Length()
421 - conversion.Length() );
422
423 if( conversion.ConsumePartOf( A_CHAR( "Xtinguish" ) ) > 0 ) { return false; }
424 if( conversion.ConsumePartOf( A_CHAR( "Upper" ) ) > 0 ) { if (isPostProcess) targetString->ToUpper( startIdx ); }
425 else if( conversion.ConsumePartOf( A_CHAR( "Lower" ) ) > 0 ) { if (isPostProcess) targetString->ToLower( startIdx ); }
426 else if( conversion.ConsumePartOf( A_CHAR( "str" ) ) > 0
427 ||conversion.ConsumePartOf( A_CHAR( "Quote" ) ) > 0 )
428 {
429 String8 open = A_CHAR( '"' );
430 String8 close= A_CHAR( '"' );
431 if (conversion.IsNotEmpty() && conversion.CharAtStart() != A_CHAR('!'))
432 {
433 open .Reset( conversion.ConsumeChar() );
434 close.Reset( conversion.IsNotEmpty() && conversion.CharAtStart() != A_CHAR('!')
435 ? conversion.ConsumeChar()
436 : open.CharAtStart() );
437 }
438
439 if (isPostProcess)
440 targetString->InsertAt<NC>( open, startIdx ) .Append<NC>( close );
441 }
442
443 else if( conversion.ConsumePartOf( A_CHAR( "Fill" ) ) > 0 )
444 {
446 placeholder.FillChar= conversion.ConsumeChar<lang::Case::Ignore>('C' ) && conversion.Length() > 0
447 ? conversion.ConsumeChar<NC>()
448 : ' ';
449
450 }
451
452 else if( conversion.ConsumePartOf( A_CHAR( "Tab" ) ) > 0 )
453 {
454 character tabChar= conversion.ConsumeChar<lang::Case::Ignore>('C') && conversion.Length() > 0
455 ? conversion.ConsumeChar<NC>()
456 : ' ';
457 int tabSize;
458 if( !conversion.ConsumeDecDigits<int>( tabSize ) )
459 tabSize= 8;
460
461 if( isPreProcess )
462 targetString->_<NC>( strings::TTab<character>( tabSize, -1, 1, tabChar ) );
463
464 }
465
466 else if( conversion.ConsumePartOf( A_CHAR( "ATab" ), 2 ) > 0 )
467 {
468
469 if( conversion.ConsumePartOf( A_CHAR( "Reset") ) > 0 )
470 {
471 if( isPreProcess )
472 Sizes->Reset();
473 }
474 else
475 {
476 character tabChar= conversion.ConsumeChar<lang::Case::Ignore>('C' ) && conversion.Length() > 0
477 ? conversion.ConsumeChar<NC>()
478 : ' ';
479
480 int growth;
481 if( !conversion.ConsumeDecDigits<int>( growth ) )
482 growth= 3;
483
484 if( isPreProcess )
485 {
486 integer actPos= targetString->Length() - targetStringStartLength;
487 integer tabStop= Sizes->Next( AutoSizes::Types::Tabstop, actPos , growth );
488 targetString->InsertChars<NC>( tabChar, tabStop - actPos );
489 }
490 }
491 }
492
493 else if( conversion.ConsumePartOf( A_CHAR( "AWidth" ), 2 ) > 0 )
494 {
495 if( conversion.ConsumePartOf( A_CHAR( "Reset" ) ) > 0 )
496 {
497 if( isPreProcess )
498 Sizes->Reset();
499 }
500 else
501 {
502 int extraPadding;
503 conversion.ConsumeDecDigits<int>( extraPadding );
504
505 if( isPreProcess )
506 placeholder.Width= int( Sizes->Actual( AutoSizes::Types::Field, 0, extraPadding ) );
507 else if ( isPostProcess )
508 Sizes->Next( AutoSizes::Types::Field, targetString->Length() - startIdx, extraPadding );
509 }
510 }
511
512 else if( conversion.ConsumePartOf( A_CHAR( "Esc" ) ) > 0
513 || conversion.ConsumePartOf( A_CHAR( "A" ) ) > 0 )
514 {
516 conversion.ConsumeChar('<');
517 if(conversion.ConsumeChar('>') )
518 toESC= lang::Switch::Off;
519
520 if( isPostProcess )
521 targetString->_<NC>( typename strings::TEscape<character>( toESC, startIdx ) );
522 }
523
524 else if( conversion.ConsumePartOf( A_CHAR( "Replace" ), 2 ) > 0 )
525 {
526 String search = conversion.ConsumeField( '<', '>' );
527 String replace= conversion.ConsumeField( '<', '>' );
528 if( search.IsNull() || replace.IsNull() )
530 placeholder.Arg->TypeID(),
531 formatString, placeholderPS.ConversionPos
532 + placeholderPS.Conversion.Length()
533 - conversion.Length() );
534
535 if( target != nullptr )
536 {
537 // special case: fill empty fields
538 if( search.IsEmpty() && target->Length() - startIdx == 0 )
539 {
540 *target << replace;
541 }
542 else
543 target->SearchAndReplace( search, replace, startIdx );
544 }
545 }
546
547 // error (not recognized)
548 else
550 conversion, placeholder.Arg->TypeID(),
551 formatString, placeholderPS.ConversionPos
552 + placeholderPS.Conversion.Length()
553 - conversion.Length() );
554 }
555
556 return true;
557}
558
559
561{
562 bool wasFloat= placeholder.Type == PHTypes::Float;
563 if( wasFloat )
564 {
565 if ( placeholderPS.Precision >= 0 )
566 placeholder.NF.FractionalPartWidth= int8_t( placeholderPS.Precision );
567 else if( placeholder.NF.FractionalPartWidth < 0 )
568 placeholder.NF.FractionalPartWidth= int8_t( placeholderPS.DefaultPrecision);
569 }
570
572
573 if( !wasFloat && placeholder.Type == PHTypes::Float )
574 {
575 if ( placeholderPS.Precision >= 0 )
576 placeholder.NF.FractionalPartWidth= int8_t( placeholderPS.Precision );
577 }
578
579 if( placeholder.Type == PHTypes::String
580 || placeholder.Type == PHTypes::Bool )
581 placeholder.CutContent= placeholderPS.Precision;
582 else if ( placeholderPS.Precision >= 0
583 && placeholder.Type != PHTypes::Float )
585 placeholder.Arg->TypeID(),
586 formatString, placeholderPS.PrecisionPos );
587 return result;
588
589}
590
591
592} // namespace [alib::format]
593
void InsertDerived(TArgs &&... args)
virtual ALIB_DLL integer findPlaceholder() override
virtual ALIB_DLL void writeStringPortion(integer length) override
virtual ALIB_DLL void resetPlaceholder() override
virtual ALIB_DLL bool parseStdFormatSpec() override
virtual ALIB_DLL bool preAndPostProcess(integer startIdx, AString *target) override
virtual ALIB_DLL bool checkStdFieldAgainstArgument() override
AutoSizes SizesDefaultInstance
The default instance of field Sizes. This might be replaced with an external object.
PlaceholderAttributesPS placeholderPS
The extended placeholder attributes.
virtual ALIB_DLL SPFormatter Clone() override
virtual ALIB_DLL bool parsePlaceholder() override
AString * targetString
The target string as provided with method Format.
FormatterStdImpl(const String &formatterClassName)
Substring parser
The current (remaining) format string.
integer targetStringStartLength
The length of the target string before adding the formatted contents.
@ Float
Outputs a number in floating point format.
@ IntBinary
Outputs a given number in base 2.
@ IntBase10
Outputs a given number in base 10. The default.
@ IntHex
Outputs a given number in base 16.
@ IntOctal
Outputs a given number in base 8.
String formatString
The format string as provided with method Format.
NumberFormat DefaultNumberFormat
Definition formatter.inl:96
virtual ALIB_DLL void CloneSettings(Formatter &reference)
NumberFormat AlternativeNumberFormat
SharedPtr< Formatter > Next
ALIB_DLL integer SearchAndReplace(TChar needle, TChar replacement, integer startIdx=0, integer endIdx=strings::MAX_LEN)
constexpr integer Length() const
Definition string.inl:318
constexpr bool IsEmpty() const
Definition string.inl:367
TChar CharAtStart() const
Definition string.inl:440
TChar CharAt(integer idx) const
Definition string.inl:421
constexpr bool IsNotEmpty() const
Definition string.inl:371
constexpr bool IsNull() const
Definition string.inl:352
TString< TChar > ConsumeField(TChar startChar, TChar endChar)
integer ConsumePartOf(const TString< TChar > &consumable, int minChars=1)
bool ConsumeDecDigits(std::integral auto &result)
integer ConsumeChars(integer regionLength, TSubstring *target=nullptr)
@ Field
denotes a field width entry.
Definition autosizes.inl:58
@ Tabstop
denotes a tab stop entry.
Definition autosizes.inl:57
#define ALIB_FALLTHROUGH
Definition alib.inl:708
#define ALIB_CALLER_NULLED
Definition alib.inl:1010
#define A_CHAR(STR)
#define ALIB_ERROR(domain,...)
Definition alib.inl:1045
@ Center
Chooses centered alignment.
@ Right
Chooses right alignment.
@ Left
Chooses left alignment.
Switch
Denotes if sth. is switched on or off.
@ On
Switch it on, switched on, etc.
@ Off
Switch it off, switched off, etc.
@ Include
Chooses inclusion.
strings::TAString< character, lang::HeapAllocator > AString
Type alias in namespace alib.
lang::integer integer
Type alias in namespace alib.
Definition integers.inl:149
LocalString< 8 > String8
Type alias name for TLocalString<character,8>.
exceptions::Exception Exception
Type alias in namespace alib.
containers::SharedPtr< format::Formatter > SPFormatter
Definition formatter.inl:42
characters::character character
Type alias in namespace alib.
strings::TSubstring< character > Substring
Type alias in namespace alib.