ALib C++ Library
Library Version: 2511 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
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 Formatter->FormatArgs( Buffer, args ); // may throw!
128
129 integer maxLineWidth;
131 DetectedMaxLineWidth= (std::max)(DetectedMaxLineWidth, maxLineWidth );
132
133 if ( Buffer.IsNotEmpty() && !Buffer.EndsWith( NEW_LINE ) ) {
134 #if defined( _WIN32 )
135 if( Buffer.CharAtEnd() == '\n' )
136 Buffer.DeleteEnd(1);
137 #endif
138 Buffer.NewLine();
139} }
140
141# include "ALib.Lang.CIFunctions.H"
142namespace {
143 [[ noreturn ]]
144void throwMarkerException( FMTExceptions eType, String& markedBuffer, integer errPos ) {
145 String64 actText;
146 integer exceptPos= 25;
147 integer exceptStart= errPos - 25;
148 if( exceptStart <= 0 ) {
149 exceptPos+= exceptStart;
150 exceptStart= 0;
151 } else {
152 actText._( A_CHAR("[...]") );
153 exceptPos+= 5;
154 }
155
156
157 actText._( markedBuffer, exceptStart, 50 );
158 if( markedBuffer.Length() > exceptStart + 50 )
159 actText._( A_CHAR("[...]") );
160 actText.SearchAndReplace( A_CHAR("\r"), A_CHAR("\\r"), exceptPos );
161 actText.SearchAndReplace( A_CHAR("\n"), A_CHAR("\\n"), exceptPos );
162 exceptPos+= actText.SearchAndReplace( A_CHAR( "\r"), A_CHAR( "\\r") );
163 exceptPos+= actText.SearchAndReplace( A_CHAR( "\n"), A_CHAR( "\\n") );
164
165 throw Exception( ALIB_CALLER_NULLED, eType, errPos, actText, exceptPos );
166}
167}
168# include "ALib.Lang.CIMethods.H"
169
170template<> void Paragraphs::AddMarked( boxing::TBoxes<MonoAllocator>& args ) {
171 character searchCharBuf[2];
172 searchCharBuf[0]= MarkerChar;
173 searchCharBuf[1]= '\n';
174 String searchChars(searchCharBuf, 2);
175
176 Formatter->FormatArgs( markedBuffer.Reset(), args ); // may throw
177
178 Substring parser = markedBuffer;
179 integer lastTextStart= Buffer.Length();
180
181 while( parser.IsNotEmpty() ) {
182 integer pos= parser.template IndexOfAny<lang::Inclusion::Include, NC>( searchChars );
183
184 // not found
185 if( pos < 0 ) {
186 Buffer << parser;
187 break;
188 }
189
190 // new line
191 if( parser.CharAt( pos ) == '\n' ) {
192 parser.template ConsumeChars<NC, lang::CurrentData::Keep>( pos, Buffer, 1 );
193 if (Buffer.CharAtEnd<NC>() == '\r')
194 Buffer.DeleteEnd<NC>(1);
195 Buffer.NewLine();
196 integer maxLineWidth;
198 maxLineWidth, IndentFirstLine, IndentOtherLines );
199 DetectedMaxLineWidth= (std::max)(DetectedMaxLineWidth, maxLineWidth );
200 lastTextStart= Buffer.Length();
201 continue;
202 }
203
204 parser.template ConsumeChars<NC, lang::CurrentData::Keep>( pos, Buffer, 1 );
205
206 // double marker: insert one symbol
207 if( parser.ConsumeChar( MarkerChar ) )
209
210 // Indent
211 else if( parser.ConsumeString(A_CHAR( ">'" )) )
212 PushIndent( parser.ConsumeToken( '\'' ) );
213
214 else if( parser.ConsumeString(A_CHAR( ">>" )) )
215 PushIndent( A_CHAR( " " ) );
216
217 else if( parser.ConsumeString(A_CHAR( "<<" )) ) {
218 if( IndentSizesFirstLine.empty() )
220 markedBuffer.Length() - parser.Length() - 3 );
221 PopIndent();
222 }
223
224 // bullets
225 else if( parser.ConsumeString(A_CHAR( "*>" )) ) {
226 if( markerBulletLevel > 0 ) {
227 IndentFirstLine .DeleteEnd( 2 )._( A_CHAR( " " ) );
228 IndentOtherLines.DeleteEnd( 2 )._( A_CHAR( " " ) );
229 }
231 IndentOtherLines._( " " );
233 }
234 else if( parser.ConsumeString(A_CHAR( "<*" )) ) {
235 if( markerBulletLevel == 0 )
237 markedBuffer.Length() - parser.Length() - 3 );
238
239 int deIndentCnt= markerBulletLevel > 1 ? 4 : 2;
240 IndentFirstLine .DeleteEnd( deIndentCnt );
241 IndentOtherLines.DeleteEnd( deIndentCnt );
242 if( --markerBulletLevel > 0 ) {
244 IndentOtherLines._( A_CHAR( " " ) );
245 } }
246
247 else if( parser.ConsumeChar('p' ) || parser.ConsumeChar('P') )
248 Buffer.NewLine();
249
250
251 // horizontal line
252 else if( parser.ConsumeString(A_CHAR( "HL" )) ) {
253 Buffer.InsertChars( parser.ConsumeChar() , LineWidth - IndentFirstLine.Length() )
254 .NewLine();
255 }
256
257 // not recognized
258 else {
259 throwMarkerException( FMTExceptions::UnknownMarker, markedBuffer,
260 markedBuffer.Length() - parser.Length() - 1 );
261 } }
262
263 if( lastTextStart < Buffer.Length() ) {
264 integer maxLineWidth;
265 Paragraphs::Format( Buffer, lastTextStart, LineWidth, JustifyChar, maxLineWidth,
267 DetectedMaxLineWidth= (std::max)(DetectedMaxLineWidth, maxLineWidth );
268 }
269
270 if ( Buffer.IsNotEmpty() && !Buffer.EndsWith( NEW_LINE ) )
271 Buffer.NewLine();
272}
273
274//! @endcond
275
276//##################################################################################################
277// The static formatter method
278//##################################################################################################
280 integer startIdx, integer lineWidth,
281 character justifyChar, integer& maxLineWidth,
282 const String& pIndentFirstLine,
283 const String& pIndentOtherLines ) {
284 maxLineWidth= 0;
285 String indentFirstLines= pIndentFirstLine .IsNotNull() ? pIndentFirstLine : EMPTY_STRING;
286 String indentOtherLines= pIndentOtherLines.IsNotNull() ? pIndentOtherLines : pIndentFirstLine;
287
288 bool isFirstLine= true;
289
290 String indent = nullptr;
291 bool indentAreJustSpaces= false;
292
293 // loop over lines
294 integer maxLineWidthDetectionStartIdx= startIdx;
295 bool hasNL= false;
296 for(;;) {
297 maxLineWidth= (std::max)( maxLineWidth, startIdx - maxLineWidthDetectionStartIdx
298 - ( !hasNL ? 0 :
299 #if defined( _WIN32 )
300 2
301 #else
302 1
303 #endif
304 )
305 );
306 if ( startIdx == text.Length() )
307 break;
308 maxLineWidthDetectionStartIdx= startIdx;
309 hasNL= false;
310
311 // skip lines beginning with newline characters, unless indent has non-space characters
312 int isWinNL= text[ startIdx ] == '\r' ? 1 : 0;
313 if ( text[ startIdx + isWinNL ] == '\n' ) {
314 hasNL= true;
315
316 // set indent and check if its just spaces
317 if( indent.IsNull() ) {
318 indent = isFirstLine ? indentFirstLines : indentOtherLines;
319 indentAreJustSpaces= (indent.template IndexOfAny<lang::Inclusion::Exclude>( A_CHAR( " " ) ) < 0 );
320 }
321
322 // insert indent if not just spaces
323 if ( !indentAreJustSpaces ) {
324 text.InsertAt( indent, startIdx );
325 startIdx+= indent.Length();
326 }
327
328 #if defined( _WIN32 )
329 if( !isWinNL ) {
330 text.template InsertChars<NC>('\r', 1, startIdx );
331 isWinNL= true;
332 }
333 #else
334 if( isWinNL ) {
335 text.template Delete<NC>(startIdx, 1);
336 isWinNL= false;
337 }
338 #endif
339
340
341 startIdx+= 1 + isWinNL;
342 if( isFirstLine ) {
343 isFirstLine= false;
344 indent= nullptr;
345 }
346
347 continue;
348 }
349
350 // insert indent
351 if( indent.IsNull() ) {
352 indent = isFirstLine ? indentFirstLines : indentOtherLines;
353 indentAreJustSpaces= (indent.template IndexOfAny<lang::Inclusion::Exclude>( A_CHAR( " " ) ) < 0 );
354 }
355 text.InsertAt( indent, startIdx );
356
357 integer idx = startIdx + indent.Length() - 1;
358
359 if( isFirstLine ) {
360 isFirstLine= false;
361 indent= nullptr;
362 }
363
364 // find next end of line. Remember last space in line
365 integer lastSpaceInLine = 0;
366 bool isLastLine = true;
367 bool exceeds = false;
368 while (++idx < text.Length() ) {
369 character c= text[idx];
370 if ( c == '\n' ) {
371 hasNL= true;
372 ++idx;
373 break;
374 }
375 exceeds= lineWidth > 0 && idx - startIdx >= lineWidth;
376
377 if( c == ' ' ) {
378 if(idx - startIdx <= lineWidth )
379 lastSpaceInLine= idx;
380
381 if( exceeds ) {
382 isLastLine= false;
383 break;
384 } } }
385
386 // correct newline
387 #if defined( _WIN32 )
388 if( text[idx-1] == '\n' && text[idx-2] != '\r' ) {
389 text.template InsertChars<NC>('\r', 1, idx-1 );
390 ++idx;
391 }
392 #else
393 if( text[idx-1] == '\n' && text[idx-2] == '\r' ) {
394 text.template Delete<NC>((idx-2), 1);
395 --idx;
396 }
397 #endif
398
399 // wrap line.
400 if( exceeds && ( lastSpaceInLine || !isLastLine ) ) {
401 integer wrapPos= lastSpaceInLine > 0 ? lastSpaceInLine : idx;
402 text.template ReplaceSubstring<NC>( NEW_LINE, wrapPos, 1 );
403 idx= wrapPos + NEW_LINE.Length();
404 hasNL= true;
405
406 // block justification
407 if( justifyChar != '\0' ) {
408 integer qtyInserts= lineWidth - (wrapPos - startIdx );
409 if( qtyInserts > 0 ) {
410 // search first non-space after indent.
411 integer leftInsertBoundary= startIdx + indent.Length();
412 while ( leftInsertBoundary < idx && text[leftInsertBoundary] == ' ' )
413 ++leftInsertBoundary;
414
415 if( leftInsertBoundary < idx ) {
416 while( qtyInserts > 0 ) {
417 integer actPos= idx - 1;
418 bool foundOne= false;
419 while( qtyInserts > 0 ) {
420 actPos= text.LastIndexOf( ' ', actPos );
421 if( actPos < leftInsertBoundary )
422 break;
423 foundOne= true;
424 text.InsertChars( justifyChar, 1, actPos );
425 ++idx;
426 --qtyInserts;
427 while( --actPos > leftInsertBoundary && text[actPos] == ' ' )
428 ;
429 }
430
431 if( !foundOne )
432 break;
433 } } } } }
434
435 startIdx= idx;
436} }
437
438
439#if !DOXYGEN
441 boxes.clear();
442 boxes.Add(args);
443 Add(boxes);
444}
445
446template<> void Paragraphs::Add( boxing::TBoxes<PoolAllocator>& args ) {
447 boxes.clear();
448 boxes.Add(args);
449 Add(boxes);
450}
452 boxes.clear();
453 boxes.Add(args);
455}
456
457template<> void Paragraphs::AddMarked( boxing::TBoxes<PoolAllocator>& args ) {
458 boxes.clear();
459 boxes.Add(args);
461}
462#endif // !DOXYGEN
463
464
465} // namespace [alib::format]
TBoxes & Add()
Definition boxes.inl:55
Formatter & FormatArgs(AString &target)
AString markedBuffer
Buffer for processing marked text.
StdVectorMA< character > MarkerBullets
ALIB_DLL 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 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:316
bool EndsWith(const TString &needle) const
Definition string.inl:780
constexpr bool IsNotNull() const
Definition string.inl:355
TChar CharAt(integer idx) const
Definition string.inl:415
constexpr bool IsNotEmpty() const
Definition string.inl:369
TChar CharAtEnd() const
Definition string.inl:452
constexpr bool IsNull() const
Definition string.inl:350
bool ConsumeString(const TString< TChar > &consumable)
TString< TChar > ConsumeToken(TChar separator=',', lang::Inclusion includeSeparator=lang::Inclusion::Include)
#define ALIB_CALLER_NULLED
Definition alib.inl:1027
#define A_CHAR(STR)
#define ALIB_DBG(...)
Definition alib.inl:853
#define ALIB_ASSERT_ERROR(cond, domain,...)
Definition alib.inl:1066
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:616
constexpr const String EMPTY_STRING
An empty string of the default character type.
Definition string.inl:2251
lang::integer integer
Type alias in namespace alib.
Definition integers.inl:149
exceptions::Exception Exception
Type alias in namespace alib.
strings::TString< character > String
Type alias in namespace alib.
Definition string.inl:2189
std::deque< T, StdMA< T > > StdDequeMA
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
strings::TSubstring< character > Substring
Type alias in namespace alib.