ALib C++ Library
Library Version: 2510 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
paragraphs.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 ======================================
16// =========================================== Module ==========================================
17#if ALIB_C20_MODULES
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 )
50{
51 text.SetBuffer(2048);
52 MarkerBullets= {'*', '-', '*', '-', '*', '-' };
53 IndentFirstLine .SetBuffer(20);
54 IndentOtherLines.SetBuffer(20);
55}
56
58: allocator(ALIB_DBG("Paragraphs",) 16)
59, Buffer(externalBuffer)
60, Formatter( Formatter::Default )
67{
68 MarkerBullets= {'*', '-', '*', '-', '*', '-' };
69 IndentFirstLine .SetBuffer(20);
70 IndentOtherLines.SetBuffer(20);
71}
72
73
74
75Paragraphs& Paragraphs::PushIndent( const String& indentFirstLine,
76 const String& pIndentOtherLines )
77{
78 String indentOtherLines= pIndentOtherLines.IsNull() ? indentFirstLine
79 : pIndentOtherLines;
80
81 IndentFirstLine ._( indentFirstLine );
82 IndentOtherLines._( indentOtherLines );
83 IndentSizesFirstLine .push( indentFirstLine.Length() );
84
85 IndentSizesOtherLines.push( indentOtherLines.Length() );
86 return *this;
87}
88
89
91{
92 IndentFirstLine .InsertChars( fillChar, integer( qty ) );
93 IndentOtherLines.InsertChars( fillChar, integer( qty ) );
94 IndentSizesFirstLine .push( integer( qty ) );
95 IndentSizesOtherLines.push( integer( qty ) );
96 return *this;
97}
98
99
101{
102 ALIB_ASSERT_ERROR( !IndentSizesFirstLine.empty(), "FORMAT",
103 "Paragraphs: PopIndent without prior push." )
104 IndentFirstLine.DeleteEnd( integer( IndentSizesFirstLine.top() ) );
106
107 ALIB_ASSERT_ERROR( !IndentSizesOtherLines.empty(), "FORMAT",
108 "Paragraphs: PopIndent without prior push." )
109 IndentOtherLines.DeleteEnd( integer( IndentSizesOtherLines.top() ) );
111 return *this;
112}
113
114
116{
117 Buffer.Reset();
118 while( IndentSizesFirstLine .size() ) IndentSizesFirstLine .pop();
119 while( IndentSizesOtherLines.size() ) IndentSizesOtherLines.pop();
120 IndentFirstLine .Reset();
121 IndentOtherLines.Reset();
124 return *this;
125}
126
127
128//! @cond NO_DOX
129
130template<>
132{
133 integer startIdx= Buffer.Length();
134 Formatter->FormatArgs( Buffer, args ); // may throw!
135
136 integer maxLineWidth;
138 DetectedMaxLineWidth= (std::max)(DetectedMaxLineWidth, maxLineWidth );
139
141 {
142 #if defined( _WIN32 )
143 if( Buffer.CharAtEnd() == '\n' )
144 Buffer.DeleteEnd(1);
145 #endif
146 Buffer.NewLine();
147 }
148}
149
150# include "ALib.Lang.CIFunctions.H"
151namespace {
152 [[ noreturn ]]
153 void throwMarkerException( FMTExceptions eType, String& markedBuffer, integer errPos )
154 {
155 String64 actText;
156 integer exceptPos= 25;
157 integer exceptStart= errPos - 25;
158 if( exceptStart <= 0 )
159 {
160 exceptPos+= exceptStart;
161 exceptStart= 0;
162 }
163 else
164 {
165 actText._( A_CHAR("[...]") );
166 exceptPos+= 5;
167 }
168
169
170 actText._( markedBuffer, exceptStart, 50 );
171 if( markedBuffer.Length() > exceptStart + 50 )
172 actText._( A_CHAR("[...]") );
173 actText.SearchAndReplace( A_CHAR("\r"), A_CHAR("\\r"), exceptPos );
174 actText.SearchAndReplace( A_CHAR("\n"), A_CHAR("\\n"), exceptPos );
175 exceptPos+= actText.SearchAndReplace( A_CHAR( "\r"), A_CHAR( "\\r") );
176 exceptPos+= actText.SearchAndReplace( A_CHAR( "\n"), A_CHAR( "\\n") );
177
178 throw Exception( ALIB_CALLER_NULLED, eType, errPos, actText, exceptPos );
179 }
180}
181# include "ALib.Lang.CIMethods.H"
182
183template<> void Paragraphs::AddMarked( boxing::TBoxes<MonoAllocator>& args )
184{
185 character searchCharBuf[2];
186 searchCharBuf[0]= MarkerChar;
187 searchCharBuf[1]= '\n';
188 String searchChars(searchCharBuf, 2);
189
190 Formatter->FormatArgs( markedBuffer.Reset(), args ); // may throw
191
192 Substring parser = markedBuffer;
193 integer lastTextStart= Buffer.Length();
194
195 while( parser.IsNotEmpty() )
196 {
197 integer pos= parser.template IndexOfAny<lang::Inclusion::Include, NC>( searchChars );
198
199 // not found
200 if( pos < 0 )
201 {
202 Buffer << parser;
203 break;
204 }
205
206 // new line
207 if( parser.CharAt( pos ) == '\n' )
208 {
209 parser.template ConsumeChars<NC, lang::CurrentData::Keep>( pos, Buffer, 1 );
210 if (Buffer.CharAtEnd<NC>() == '\r')
211 Buffer.DeleteEnd<NC>(1);
212 Buffer.NewLine();
213 integer maxLineWidth;
215 maxLineWidth, IndentFirstLine, IndentOtherLines );
216 DetectedMaxLineWidth= (std::max)(DetectedMaxLineWidth, maxLineWidth );
217 lastTextStart= Buffer.Length();
218 continue;
219 }
220
221 parser.template ConsumeChars<NC, lang::CurrentData::Keep>( pos, Buffer, 1 );
222
223 // double marker: insert one symbol
224 if( parser.ConsumeChar( MarkerChar ) )
226
227 // Indent
228 else if( parser.ConsumeString(A_CHAR( ">'" )) )
229 PushIndent( parser.ConsumeToken( '\'' ) );
230
231 else if( parser.ConsumeString(A_CHAR( ">>" )) )
232 PushIndent( A_CHAR( " " ) );
233
234 else if( parser.ConsumeString(A_CHAR( "<<" )) )
235 {
236 if( IndentSizesFirstLine.empty() )
238 markedBuffer.Length() - parser.Length() - 3 );
239 PopIndent();
240 }
241
242 // bullets
243 else if( parser.ConsumeString(A_CHAR( "*>" )) )
244 {
245 if( markerBulletLevel > 0 )
246 {
247 IndentFirstLine .DeleteEnd( 2 )._( A_CHAR( " " ) );
248 IndentOtherLines.DeleteEnd( 2 )._( A_CHAR( " " ) );
249 }
251 IndentOtherLines._( " " );
253 }
254 else if( parser.ConsumeString(A_CHAR( "<*" )) )
255 {
256 if( markerBulletLevel == 0 )
258 markedBuffer.Length() - parser.Length() - 3 );
259
260 int deIndentCnt= markerBulletLevel > 1 ? 4 : 2;
261 IndentFirstLine .DeleteEnd( deIndentCnt );
262 IndentOtherLines.DeleteEnd( deIndentCnt );
263 if( --markerBulletLevel > 0 )
264 {
266 IndentOtherLines._( A_CHAR( " " ) );
267 }
268 }
269
270 else if( parser.ConsumeChar('p' ) || parser.ConsumeChar('P') )
271 Buffer.NewLine();
272
273
274 // horizontal line
275 else if( parser.ConsumeString(A_CHAR( "HL" )) )
276 {
277 Buffer.InsertChars( parser.ConsumeChar() , LineWidth - IndentFirstLine.Length() )
278 .NewLine();
279 }
280
281 // not recognized
282 else
283 {
284 throwMarkerException( FMTExceptions::UnknownMarker, markedBuffer,
285 markedBuffer.Length() - parser.Length() - 1 );
286 }
287 }
288
289 if( lastTextStart < Buffer.Length() )
290 {
291 integer maxLineWidth;
292 Paragraphs::Format( Buffer, lastTextStart, LineWidth, JustifyChar, maxLineWidth,
294 DetectedMaxLineWidth= (std::max)(DetectedMaxLineWidth, maxLineWidth );
295 }
296
297 if ( Buffer.IsNotEmpty() && !Buffer.EndsWith( NEW_LINE ) )
298 Buffer.NewLine();
299}
300
301//! @endcond
302
303// #################################################################################################
304// The static formatter method
305// #################################################################################################
307 integer startIdx, integer lineWidth,
308 character justifyChar, integer& maxLineWidth,
309 const String& pIndentFirstLine,
310 const String& pIndentOtherLines )
311{
312 maxLineWidth= 0;
313 String indentFirstLines= pIndentFirstLine .IsNotNull() ? pIndentFirstLine : EMPTY_STRING;
314 String indentOtherLines= pIndentOtherLines.IsNotNull() ? pIndentOtherLines : pIndentFirstLine;
315
316 bool isFirstLine= true;
317
318 String indent = nullptr;
319 bool indentAreJustSpaces= false;
320
321 // loop over lines
322 integer maxLineWidthDetectionStartIdx= startIdx;
323 bool hasNL= false;
324 for(;;)
325 {
326 maxLineWidth= (std::max)( maxLineWidth, startIdx - maxLineWidthDetectionStartIdx
327 - ( !hasNL ? 0 :
328 #if defined( _WIN32 )
329 2
330 #else
331 1
332 #endif
333 )
334 );
335 if ( startIdx == text.Length() )
336 break;
337 maxLineWidthDetectionStartIdx= startIdx;
338 hasNL= false;
339
340 // skip lines beginning with newline characters, unless indent has non-space characters
341 int isWinNL= text[ startIdx ] == '\r' ? 1 : 0;
342 if ( text[ startIdx + isWinNL ] == '\n' )
343 {
344 hasNL= true;
345
346 // set indent and check if its just spaces
347 if( indent.IsNull() )
348 {
349 indent = isFirstLine ? indentFirstLines : indentOtherLines;
350 indentAreJustSpaces= (indent.template IndexOfAny<lang::Inclusion::Exclude>( A_CHAR( " " ) ) < 0 );
351 }
352
353 // insert indent if not just spaces
354 if ( !indentAreJustSpaces )
355 {
356 text.InsertAt( indent, startIdx );
357 startIdx+= indent.Length();
358 }
359
360 #if defined( _WIN32 )
361 if( !isWinNL )
362 {
363 text.template InsertChars<NC>('\r', 1, startIdx );
364 isWinNL= true;
365 }
366 #else
367 if( isWinNL )
368 {
369 text.template Delete<NC>(startIdx, 1);
370 isWinNL= false;
371 }
372 #endif
373
374
375 startIdx+= 1 + isWinNL;
376 if( isFirstLine )
377 {
378 isFirstLine= false;
379 indent= nullptr;
380 }
381
382 continue;
383 }
384
385 // insert indent
386 if( indent.IsNull() )
387 {
388 indent = isFirstLine ? indentFirstLines : indentOtherLines;
389 indentAreJustSpaces= (indent.template IndexOfAny<lang::Inclusion::Exclude>( A_CHAR( " " ) ) < 0 );
390 }
391 text.InsertAt( indent, startIdx );
392
393 integer idx = startIdx + indent.Length() - 1;
394
395 if( isFirstLine )
396 {
397 isFirstLine= false;
398 indent= nullptr;
399 }
400
401 // find next end of line. Remember last space in line
402 integer lastSpaceInLine = 0;
403 bool isLastLine = true;
404 bool exceeds = false;
405 while (++idx < text.Length() )
406 {
407 character c= text[idx];
408 if ( c == '\n' )
409 {
410 hasNL= true;
411 ++idx;
412 break;
413 }
414 exceeds= lineWidth > 0 && idx - startIdx >= lineWidth;
415
416 if( c == ' ' )
417 {
418 if(idx - startIdx <= lineWidth )
419 lastSpaceInLine= idx;
420
421 if( exceeds )
422 {
423 isLastLine= false;
424 break;
425 }
426 }
427 }
428
429 // correct newline
430 #if defined( _WIN32 )
431 if( text[idx-1] == '\n' && text[idx-2] != '\r' )
432 {
433 text.template InsertChars<NC>('\r', 1, idx-1 );
434 ++idx;
435 }
436 #else
437 if( text[idx-1] == '\n' && text[idx-2] == '\r' )
438 {
439 text.template Delete<NC>((idx-2), 1);
440 --idx;
441 }
442 #endif
443
444 // wrap line.
445 if( exceeds && ( lastSpaceInLine || !isLastLine ) )
446 {
447 integer wrapPos= lastSpaceInLine > 0 ? lastSpaceInLine : idx;
448 text.template ReplaceSubstring<NC>( NEW_LINE, wrapPos, 1 );
449 idx= wrapPos + NEW_LINE.Length();
450 hasNL= true;
451
452 // block justification
453 if( justifyChar != '\0' )
454 {
455 integer qtyInserts= lineWidth - (wrapPos - startIdx );
456 if( qtyInserts > 0 )
457 {
458 // search first non-space after indent.
459 integer leftInsertBoundary= startIdx + indent.Length();
460 while ( leftInsertBoundary < idx && text[leftInsertBoundary] == ' ' )
461 ++leftInsertBoundary;
462
463 if( leftInsertBoundary < idx )
464 {
465 while( qtyInserts > 0 )
466 {
467 integer actPos= idx - 1;
468 bool foundOne= false;
469 while( qtyInserts > 0 )
470 {
471 actPos= text.LastIndexOf( ' ', actPos );
472 if( actPos < leftInsertBoundary )
473 break;
474 foundOne= true;
475 text.InsertChars( justifyChar, 1, actPos );
476 ++idx;
477 --qtyInserts;
478 while( --actPos > leftInsertBoundary && text[actPos] == ' ' )
479 ;
480 }
481
482 if( !foundOne )
483 break;
484 }
485 }
486 }
487 }
488 }
489
490 startIdx= idx;
491 }
492}
493
494
495#if !DOXYGEN
497{
498 boxes.clear();
499 boxes.Add(args);
500 Add(boxes);
501}
502
503template<> void Paragraphs::Add( boxing::TBoxes<PoolAllocator>& args )
504{
505 boxes.clear();
506 boxes.Add(args);
507 Add(boxes);
508}
510{
511 boxes.clear();
512 boxes.Add(args);
514}
515
516template<> void Paragraphs::AddMarked( boxing::TBoxes<PoolAllocator>& args )
517{
518 boxes.clear();
519 boxes.Add(args);
521}
522#endif // !DOXYGEN
523
524
525} // namespace [alib::format]
TBoxes & Add()
Definition boxes.inl:61
Formatter & FormatArgs(AString &target)
std::stack< integer, StdDequeMono< integer > > IndentSizesFirstLine
AString markedBuffer
Buffer for processing marked text.
StdVectorMono< character > MarkerBullets
ALIB_DLL Paragraphs & PushIndent(uinteger qty, character fillChar=' ')
size_t markerBulletLevel
Buffer for processing marked text.
void Add(boxing::TBoxes< TAllocatorArgs > &args)
void AddMarked(boxing::TBoxes< TAllocatorArgs > &args)
std::stack< integer, StdDequeMono< integer > > IndentSizesOtherLines
static ALIB_DLL 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.
ALIB_DLL Paragraphs & Clear()
ALIB_DLL Paragraphs & PopIndent()
TAString & DeleteEnd(integer regionLength)
ALIB_DLL integer SearchAndReplace(TChar needle, TChar replacement, integer startIdx=0, integer endIdx=strings::MAX_LEN)
TAString & _(const TAppendable &src)
constexpr integer Length() const
Definition string.inl:318
bool EndsWith(const TString &needle) const
Definition string.inl:805
constexpr bool IsNotNull() const
Definition string.inl:357
TChar CharAt(integer idx) const
Definition string.inl:421
constexpr bool IsNotEmpty() const
Definition string.inl:371
TChar CharAtEnd() const
Definition string.inl:460
constexpr bool IsNull() const
Definition string.inl:352
bool ConsumeString(const TString< TChar > &consumable)
TString< TChar > ConsumeToken(TChar separator=',', lang::Inclusion includeSeparator=lang::Inclusion::Include)
#define ALIB_CALLER_NULLED
Definition alib.inl:1010
#define A_CHAR(STR)
#define ALIB_DBG(...)
Definition alib.inl:836
#define ALIB_ASSERT_ERROR(cond, domain,...)
Definition alib.inl:1049
strings::TAString< character, lang::HeapAllocator > AString
Type alias in namespace alib.
LocalString< 64 > String64
Type alias name for TLocalString<character,64>.
constexpr CString NEW_LINE
A zero-terminated string containing the new-line character sequence.
Definition cstring.inl:644
constexpr const String EMPTY_STRING
An empty string of the default character type.
Definition string.inl:2443
lang::integer integer
Type alias in namespace alib.
Definition integers.inl:149
std::deque< T, SCAMono< T > > StdDequeMono
Type alias in namespace alib.
exceptions::Exception Exception
Type alias in namespace alib.
strings::TString< character > String
Type alias in namespace alib.
Definition string.inl:2381
characters::character character
Type alias in namespace alib.
lang::uinteger uinteger
Type alias in namespace alib.
Definition integers.inl:152
strings::TSubstring< character > Substring
Type alias in namespace alib.