ALib C++ Framework
by
Library Version: 2511 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
paragraphs.cpp
1//##################################################################################################
2// ALib C++ Framework
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 "Configuration MACRO 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 ========================================
16//============================================== Module ============================================
17#if ALIB_C20_MODULES
18 module ALib.Format.Paragraphs;
19 import ALib.Lang;
20 import ALib.Strings;
21 import ALib.Exceptions;
22# if ALIB_CAMP
23 import ALib.Camp.Base;
24# endif
25#else
26# include "ALib.Lang.H"
27# include "ALib.Strings.H"
28# include "ALib.Exceptions.H"
30# include "ALib.Camp.Base.H"
31#endif
32//========================================== Implementation ========================================
33using namespace alib::strings;
34namespace alib::format {
35
36
37//##################################################################################################
38// Non-static methods (if used with instance).
39//##################################################################################################
41: allocator (ALIB_DBG("Paragraphs",) 16)
42, Buffer (text)
43, Formatter ( Formatter::DEFAULT )
49, boxes (allocator) {
50 text.SetBuffer(2048);
51 MarkerBullets= {'*', '-', '*', '-', '*', '-' };
52 IndentFirstLine .SetBuffer(20);
53 IndentOtherLines.SetBuffer(20);
54}
55
57: allocator(ALIB_DBG("Paragraphs",) 16)
58, Buffer (externalBuffer)
59, Formatter ( Formatter::DEFAULT )
66 MarkerBullets= {'*', '-', '*', '-', '*', '-' };
67 IndentFirstLine .SetBuffer(20);
68 IndentOtherLines.SetBuffer(20);
69}
70
71
72
73Paragraphs& Paragraphs::PushIndent( const String& indentFirstLine,
74 const String& pIndentOtherLines ) {
75 String indentOtherLines= pIndentOtherLines.IsNull() ? indentFirstLine
76 : pIndentOtherLines;
77
78 IndentFirstLine ._( indentFirstLine );
79 IndentOtherLines._( indentOtherLines );
80 IndentSizesFirstLine .push( indentFirstLine.Length() );
81
82 IndentSizesOtherLines.push( indentOtherLines.Length() );
83 return *this;
84}
85
86
88 IndentFirstLine .InsertChars( fillChar, integer( qty ) );
89 IndentOtherLines.InsertChars( fillChar, integer( qty ) );
90 IndentSizesFirstLine .push( integer( qty ) );
91 IndentSizesOtherLines.push( integer( qty ) );
92 return *this;
93}
94
95
97 ALIB_ASSERT_ERROR( !IndentSizesFirstLine.empty(), "FORMAT",
98 "Paragraphs: PopIndent without prior push." )
99 IndentFirstLine.DeleteEnd( integer( IndentSizesFirstLine.top() ) );
101
102 ALIB_ASSERT_ERROR( !IndentSizesOtherLines.empty(), "FORMAT",
103 "Paragraphs: PopIndent without prior push." )
104 IndentOtherLines.DeleteEnd( integer( IndentSizesOtherLines.top() ) );
106 return *this;
107}
108
109
111 Buffer.Reset();
112 while( IndentSizesFirstLine .size() ) IndentSizesFirstLine .pop();
113 while( IndentSizesOtherLines.size() ) IndentSizesOtherLines.pop();
114 IndentFirstLine .Reset();
115 IndentOtherLines.Reset();
118 return *this;
119}
120
121
122//! @cond NO_DOX
123
124template<>
126 integer startIdx= Buffer.Length();
127 #if !ALIB_SINGLE_THREADED
130 : nullptr
132 #endif
133 Formatter->FormatArgs( Buffer, args ); // may throw!
134
135 integer maxLineWidth;
137 DetectedMaxLineWidth= (std::max)(DetectedMaxLineWidth, maxLineWidth );
138
139 if ( Buffer.IsNotEmpty() && !Buffer.EndsWith( NEW_LINE ) ) {
140 #if defined( _WIN32 )
141 if( Buffer.CharAtEnd() == '\n' )
142 Buffer.DeleteEnd(1);
143 #endif
144 Buffer.NewLine();
145} }
146
147# include "ALib.Lang.CIFunctions.H"
148namespace {
149[[ noreturn ]]
150void throwMarkerException( FMTExceptions eType, String& markedBuffer, integer errPos ) {
151 String64 actText;
152 integer exceptPos= 25;
153 integer exceptStart= errPos - 25;
154 if( exceptStart <= 0 ) {
155 exceptPos+= exceptStart;
156 exceptStart= 0;
157 } else {
158 actText._( A_CHAR("[...]") );
159 exceptPos+= 5;
160 }
161
162
163 actText._( markedBuffer, exceptStart, 50 );
164 if( markedBuffer.Length() > exceptStart + 50 )
165 actText._( A_CHAR("[...]") );
166 actText.SearchAndReplace( A_CHAR("\r"), A_CHAR("\\r"), exceptPos );
167 actText.SearchAndReplace( A_CHAR("\n"), A_CHAR("\\n"), exceptPos );
168 exceptPos+= actText.SearchAndReplace( A_CHAR( "\r"), A_CHAR( "\\r") );
169 exceptPos+= actText.SearchAndReplace( A_CHAR( "\n"), A_CHAR( "\\n") );
170
171 throw Exception( ALIB_CALLER_NULLED, eType, errPos, actText, exceptPos );
172}
173}
174# include "ALib.Lang.CIMethods.H"
175
176template<> void Paragraphs::AddMarked( boxing::TBoxes<MonoAllocator>& args ) {
177 character searchCharBuf[2];
178 searchCharBuf[0]= MarkerChar;
179 searchCharBuf[1]= '\n';
180 String searchChars(searchCharBuf, 2);
181
182 #if !ALIB_SINGLE_THREADED
183 lang::OwnerRecursive<RecursiveLock, true> lock(
185 : nullptr
187 #endif
188 Formatter->FormatArgs( markedBuffer.Reset(), args ); // may throw
189
190 Substring parser = markedBuffer;
191 integer lastTextStart= Buffer.Length();
192
193 while( parser.IsNotEmpty() ) {
194 integer pos= parser.template IndexOfAny<lang::Inclusion::Include, NC>( searchChars );
195
196 // not found
197 if( pos < 0 ) {
198 Buffer << parser;
199 break;
200 }
201
202 // new line
203 if( parser.CharAt( pos ) == '\n' ) {
204 parser.template ConsumeChars<NC, lang::CurrentData::Keep>( pos, Buffer, 1 );
205 if (Buffer.CharAtEnd() == '\r')
206 Buffer.DeleteEnd<NC>(1);
207 Buffer.NewLine();
208 integer maxLineWidth;
210 maxLineWidth, IndentFirstLine, IndentOtherLines );
211 DetectedMaxLineWidth= (std::max)(DetectedMaxLineWidth, maxLineWidth );
212 lastTextStart= Buffer.Length();
213 continue;
214 }
215
216 parser.template ConsumeChars<NC, lang::CurrentData::Keep>( pos, Buffer, 1 );
217
218 // double marker: insert one symbol
219 if( parser.ConsumeChar( MarkerChar ) )
221
222 // Indent
223 else if( parser.ConsumeString(A_CHAR( ">'" )) )
224 PushIndent( parser.ConsumeToken( '\'' ) );
225
226 else if( parser.ConsumeString(A_CHAR( ">>" )) )
227 PushIndent( A_CHAR( " " ) );
228
229 else if( parser.ConsumeString(A_CHAR( "<<" )) ) {
230 if( IndentSizesFirstLine.empty() )
232 markedBuffer.Length() - parser.Length() - 3 );
233 PopIndent();
234 }
235
236 // bullets
237 else if( parser.ConsumeString(A_CHAR( "*>" )) ) {
238 if( markerBulletLevel > 0 ) {
239 IndentFirstLine .DeleteEnd( 2 )._( A_CHAR( " " ) );
240 IndentOtherLines.DeleteEnd( 2 )._( A_CHAR( " " ) );
241 }
243 IndentOtherLines._( " " );
245 }
246 else if( parser.ConsumeString(A_CHAR( "<*" )) ) {
247 if( markerBulletLevel == 0 )
249 markedBuffer.Length() - parser.Length() - 3 );
250
251 int deIndentCnt= markerBulletLevel > 1 ? 4 : 2;
252 IndentFirstLine .DeleteEnd( deIndentCnt );
253 IndentOtherLines.DeleteEnd( deIndentCnt );
254 if( --markerBulletLevel > 0 ) {
256 IndentOtherLines._( A_CHAR( " " ) );
257 } }
258
259 else if( parser.ConsumeChar('p' ) || parser.ConsumeChar('P') )
260 Buffer.NewLine();
261
262
263 // horizontal line
264 else if( parser.ConsumeString(A_CHAR( "HL" )) ) {
265 Buffer.InsertChars( parser.ConsumeChar() , LineWidth - IndentFirstLine.Length() )
266 .NewLine();
267 }
268
269 // not recognized
270 else {
271 throwMarkerException( FMTExceptions::UnknownMarker, markedBuffer,
272 markedBuffer.Length() - parser.Length() - 1 );
273 } }
274
275 if( lastTextStart < Buffer.Length() ) {
276 integer maxLineWidth;
277 Paragraphs::Format( Buffer, lastTextStart, LineWidth, JustifyChar, maxLineWidth,
279 DetectedMaxLineWidth= (std::max)(DetectedMaxLineWidth, maxLineWidth );
280 }
281
282 if ( Buffer.IsNotEmpty() && !Buffer.EndsWith( NEW_LINE ) )
283 Buffer.NewLine();
284}
285
286//! @endcond
287
288//##################################################################################################
289// The static formatter method
290//##################################################################################################
292 integer startIdx, integer lineWidth,
293 character justifyChar, integer& maxLineWidth,
294 const String& pIndentFirstLine,
295 const String& pIndentOtherLines ) {
296 maxLineWidth= 0;
297 String indentFirstLines= pIndentFirstLine .IsNotNull() ? pIndentFirstLine : EMPTY_STRING;
298 String indentOtherLines= pIndentOtherLines.IsNotNull() ? pIndentOtherLines : pIndentFirstLine;
299
300 bool isFirstLine= true;
301
302 String indent = nullptr;
303 bool indentAreJustSpaces= false;
304
305 // loop over lines
306 integer maxLineWidthDetectionStartIdx= startIdx;
307 bool hasNL= false;
308 for(;;) {
309 maxLineWidth= (std::max)( maxLineWidth, startIdx - maxLineWidthDetectionStartIdx
310 - ( !hasNL ? 0 :
311 #if defined( _WIN32 )
312 2
313 #else
314 1
315 #endif
316 )
317 );
318 if ( startIdx == text.Length() )
319 break;
320 maxLineWidthDetectionStartIdx= startIdx;
321 hasNL= false;
322
323 // skip lines beginning with newline characters, unless indent has non-space characters
324 int isWinNL= text[ startIdx ] == '\r' ? 1 : 0;
325 if ( text[ startIdx + isWinNL ] == '\n' ) {
326 hasNL= true;
327
328 // set indent and check if its just spaces
329 if( indent.IsNull() ) {
330 indent = isFirstLine ? indentFirstLines : indentOtherLines;
331 indentAreJustSpaces= (indent.template IndexOfAny<lang::Inclusion::Exclude>( A_CHAR( " " ) ) < 0 );
332 }
333
334 // insert indent if not just spaces
335 if ( !indentAreJustSpaces ) {
336 text.InsertAt( indent, startIdx );
337 startIdx+= indent.Length();
338 }
339
340 #if defined( _WIN32 )
341 if( !isWinNL ) {
342 text.template InsertChars<NC>('\r', 1, startIdx );
343 isWinNL= true;
344 }
345 #else
346 if( isWinNL ) {
347 text.template Delete<NC>(startIdx, 1);
348 isWinNL= false;
349 }
350 #endif
351
352
353 startIdx+= 1 + isWinNL;
354 if( isFirstLine ) {
355 isFirstLine= false;
356 indent= nullptr;
357 }
358
359 continue;
360 }
361
362 // insert indent
363 if( indent.IsNull() ) {
364 indent = isFirstLine ? indentFirstLines : indentOtherLines;
365 indentAreJustSpaces= (indent.template IndexOfAny<lang::Inclusion::Exclude>( A_CHAR( " " ) ) < 0 );
366 }
367 text.InsertAt( indent, startIdx );
368
369 integer idx = startIdx + indent.Length() - 1;
370
371 if( isFirstLine ) {
372 isFirstLine= false;
373 indent= nullptr;
374 }
375
376 // find next end of line. Remember last space in line
377 integer lastSpaceInLine = 0;
378 bool isLastLine = true;
379 bool exceeds = false;
380 while (++idx < text.Length() ) {
381 character c= text[idx];
382 if ( c == '\n' ) {
383 hasNL= true;
384 ++idx;
385 break;
386 }
387 exceeds= lineWidth > 0 && idx - startIdx >= lineWidth;
388
389 if( c == ' ' ) {
390 if(idx - startIdx <= lineWidth )
391 lastSpaceInLine= idx;
392
393 if( exceeds ) {
394 isLastLine= false;
395 break;
396 } } }
397
398 // correct newline
399 #if defined( _WIN32 )
400 if( text[idx-1] == '\n' && text[idx-2] != '\r' ) {
401 text.template InsertChars<NC>('\r', 1, idx-1 );
402 ++idx;
403 }
404 #else
405 if( text[idx-1] == '\n' && text[idx-2] == '\r' ) {
406 text.template Delete<NC>((idx-2), 1);
407 --idx;
408 }
409 #endif
410
411 // wrap line.
412 if( exceeds && ( lastSpaceInLine || !isLastLine ) ) {
413 integer wrapPos= lastSpaceInLine > 0 ? lastSpaceInLine : idx;
414 text.template ReplaceSubstring<NC>( NEW_LINE, wrapPos, 1 );
415 idx= wrapPos + NEW_LINE.Length();
416 hasNL= true;
417
418 // block justification
419 if( justifyChar != '\0' ) {
420 integer qtyInserts= lineWidth - (wrapPos - startIdx );
421 if( qtyInserts > 0 ) {
422 // search first non-space after indent.
423 integer leftInsertBoundary= startIdx + indent.Length();
424 while ( leftInsertBoundary < idx && text[leftInsertBoundary] == ' ' )
425 ++leftInsertBoundary;
426
427 if( leftInsertBoundary < idx ) {
428 while( qtyInserts > 0 ) {
429 integer actPos= idx - 1;
430 bool foundOne= false;
431 while( qtyInserts > 0 ) {
432 actPos= text.LastIndexOf( ' ', actPos );
433 if( actPos < leftInsertBoundary )
434 break;
435 foundOne= true;
436 text.InsertChars( justifyChar, 1, actPos );
437 ++idx;
438 --qtyInserts;
439 while( --actPos > leftInsertBoundary && text[actPos] == ' ' )
440 ;
441 }
442
443 if( !foundOne )
444 break;
445 } } } } }
446
447 startIdx= idx;
448} }
449
450
451#if !DOXYGEN
453 boxes.clear();
454 boxes.Add(args);
455 Add(boxes);
456}
457
458template<> void Paragraphs::Add( boxing::TBoxes<PoolAllocator>& args ) {
459 boxes.clear();
460 boxes.Add(args);
461 Add(boxes);
462}
464 boxes.clear();
465 boxes.Add(args);
467}
468
469template<> void Paragraphs::AddMarked( boxing::TBoxes<PoolAllocator>& args ) {
470 boxes.clear();
471 boxes.Add(args);
473}
474#endif // !DOXYGEN
475
476
477} // namespace [alib::format]
#define ALIB_COMMA_CALLER_PRUNED
Definition alib.inl:1103
#define ALIB_CALLER_NULLED
Definition alib.inl:1105
#define A_CHAR(STR)
Definition alib.inl:1325
#define ALIB_DBG(...)
Definition alib.inl:931
#define ALIB_ASSERT_ERROR(cond, domain,...)
Definition alib.inl:1144
TBoxes & Add()
Definition boxes.inl:55
static threads::RecursiveLock DEFAULT_LOCK
Formatter & FormatArgs(AString &target)
static SPFormatter DEFAULT
AString markedBuffer
Buffer for processing marked text.
StdVectorMA< character > MarkerBullets
Paragraphs & PushIndent(uinteger qty, character fillChar=' ')
size_t markerBulletLevel
Buffer for processing marked text.
std::stack< integer, StdDequeMA< integer > > IndentSizesFirstLine
void Add(boxing::TBoxes< TAllocatorArgs > &args)
std::stack< integer, StdDequeMA< integer > > IndentSizesOtherLines
void AddMarked(boxing::TBoxes< TAllocatorArgs > &args)
static void Format(AString &text, integer startIdx, integer lineWidth, character justifyChar, integer &maxLineWidth, const String &indentFirstLine=nullptr, const String &indentOtherLines=nullptr)
MonoAllocator allocator
Allocator used for internal container types.
integer LineWidth
Used as parameter lineWidth of static method invocations.
BoxesMA boxes
Internally reused list of boxes.
AString text
Internal buffer, used for field #"Paragraphs", if no external string object was given.
Paragraphs & PopIndent()
integer SearchAndReplace(TChar needle, TChar replacement, integer startIdx=0, integer endIdx=strings::MAX_LEN)
TAString & DeleteEnd(integer regionLength)
TAString & _(const TAppendable &src)
constexpr integer Length() const
Definition string.inl:304
bool EndsWith(const TString &needle) const
Definition string.inl:768
constexpr bool IsNotNull() const
Definition string.inl:343
TChar CharAt(integer idx) const
Definition string.inl:403
constexpr bool IsNotEmpty() const
Definition string.inl:357
TChar CharAtEnd() const
Definition string.inl:440
constexpr bool IsNull() const
Definition string.inl:338
bool ConsumeString(const TString< TChar > &consumable)
TString< TChar > ConsumeToken(TChar separator=',', lang::Inclusion includeSeparator=lang::Inclusion::Include)
std::deque< T, StdMA< T > > StdDequeMA
Type alias in namespace alib.
LocalString< 64 > String64
Type alias name for #"TLocalString;TLocalString<character,64>".
constexpr CString NEW_LINE
A zero-terminated string containing the new-line character sequence.
Definition cstring.inl:540
constexpr const String EMPTY_STRING
An empty string of the default character type.
Definition string.inl:2234
lang::integer integer
Type alias in namespace alib.
Definition integers.inl:149
strings::TString< character > String
Type alias in namespace alib.
Definition string.inl:2172
strings::TSubstring< character > Substring
Type alias in namespace alib.
exceptions::Exception Exception
Type alias in namespace alib.
strings::TAString< character, lang::HeapAllocator > AString
Type alias in namespace alib.
characters::character character
Type alias in namespace alib.
lang::uinteger uinteger
Type alias in namespace alib.
Definition integers.inl:152