E-MailRelay
gstringwrap.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gstringwrap.cpp
19///
20
21#include "gdef.h"
22#include "gstringwrap.h"
23#include "gimembuf.h"
24#include "gstr.h"
25#include <string>
26#include <sstream>
27#include <algorithm>
28#include <clocale>
29#include <locale>
30
31#ifdef emit
32#undef emit
33#endif
34
35struct G::StringWrap::Config /// Private implementation structure for G::StringWrap.
36{
37 string_view prefix_first ;
38 string_view prefix_other ;
39 std::size_t width_first ;
40 std::size_t width_other ;
41 bool preserve_spaces ;
42} ;
43
44class G::StringWrap::WordWrapper /// Private implementation structure for G::StringWrap.
45{
46public:
47 WordWrapper( std::ostream & , StringWrap::Config , const std::locale & ) ;
48 void emit( const std::string & word , std::size_t newlines , const std::string & prespace ) ;
49
50public:
51 ~WordWrapper() = default ;
52 WordWrapper( const WordWrapper & ) = delete ;
53 WordWrapper( WordWrapper && ) = delete ;
54 WordWrapper & operator=( const WordWrapper & ) = delete ;
55 WordWrapper & operator=( WordWrapper && ) = delete ;
56
57private:
58 string_view prefix() const ;
59
60private:
61 std::size_t m_lines {0U} ;
62 std::size_t m_w {0U} ;
63 std::ostream & m_out ;
64 StringWrap::Config m_config ;
65 const std::locale & m_loc ;
66} ;
67
68// ==
69
70G::StringWrap::WordWrapper::WordWrapper( std::ostream & out , Config config , const std::locale & loc ) :
71 m_out(out) ,
72 m_config(config) ,
73 m_loc(loc)
74{
75}
76
77G::string_view G::StringWrap::WordWrapper::prefix() const
78{
79 return m_lines ? m_config.prefix_other : m_config.prefix_first ;
80}
81
82void G::StringWrap::WordWrapper::emit( const std::string & word , std::size_t newlines , const std::string & prespace )
83{
84 // emit words up to the configured maximum width
85 std::size_t wordsize = StringWrap::wordsize( word , m_loc ) ;
86 std::size_t spacesize = m_config.preserve_spaces ? prespace.size() : 1U ;
87 std::size_t width = ( newlines || m_lines > 1 ) ? m_config.width_other : m_config.width_first ;
88 bool start_new_line = newlines || m_w == 0 || (m_w+spacesize+wordsize) > width ;
89 if( start_new_line )
90 {
91 // emit a blank line for each of the counted newlines
92 bool first_line = m_w == 0 ;
93 for( std::size_t i = 1U ; i < newlines ; i++ )
94 {
95 m_out << (first_line?"":"\n") << prefix() ;
96 first_line = false ;
97 m_w = prefix().size() ;
98 m_lines++ ;
99 }
100
101 // emit the first word
102 m_out << (first_line?"":"\n") << prefix() << word ;
103 m_w = prefix().size() + wordsize ;
104 m_lines++ ;
105 }
106 else
107 {
108 if( m_config.preserve_spaces )
109 m_out << (prespace.empty()?std::string(1U,' '):prespace) << word ;
110 else
111 m_out << " " << word ;
112 m_w += (spacesize+wordsize) ;
113 }
114}
115
116// ==
117
118std::string G::StringWrap::wrap( const std::string & text_in ,
119 const std::string & prefix_first , const std::string & prefix_other ,
120 std::size_t width_first , std::size_t width_other ,
121 bool preserve_spaces , const std::locale & loc )
122{
123 StringWrap::Config config {
124 prefix_first ,
125 prefix_other ,
126 width_first , width_other?width_other:width_first ,
127 preserve_spaces
128 } ;
129
130 std::ostringstream out ;
131 WordWrapper wrapper( out , config , loc ) ;
132
133 imembuf buf( text_in.data() , text_in.size() ) ;
134 std::istream in( &buf ) ;
135 in.imbue( loc ) ;
136
137 wrap( in , wrapper ) ;
138
139 return out.str() ;
140}
141
142void G::StringWrap::wrap( std::istream & in , WordWrapper & ww )
143{
144 // extract words while counting newlines within the intervening spaces
145 const auto & cctype = std::use_facet<std::ctype<char>>( in.getloc() ) ;
146 std::string word ;
147 std::size_t newlines = 0U ;
148 std::string prespace ;
149 char c = 0 ;
150 while( in.get(c) )
151 {
152 if( cctype.is( std::ctype_base::space , c ) )
153 {
154 // spit out the previous word (if any)
155 if( !word.empty() )
156 {
157 ww.emit( word , newlines , prespace ) ;
158 newlines = 0U ;
159 prespace.clear() ;
160 }
161
162 // start the new word
163 word.clear() ;
164 if( c == '\n' )
165 {
166 newlines++ ;
167 prespace.clear() ;
168 }
169 else
170 {
171 prespace.append( 1U , c ) ;
172 }
173 }
174 else
175 {
176 word.append( 1U , c ) ;
177 }
178 }
179 // spit out the trailing word (if any)
180 if( !word.empty() )
181 ww.emit( word , newlines , prespace ) ;
182}
183
185{
186 try
187 {
188 // see also G::gettext_init()
189 return std::locale( std::setlocale(LC_CTYPE,nullptr) ) ;
190 }
191 catch( std::exception & )
192 {
193 return std::locale::classic() ;
194 }
195}
196
197std::size_t G::StringWrap::wordsize( const std::string & s , const std::locale & loc )
198{
199 return wordsize( string_view(s.data(),s.size()) , loc ) ;
200}
201
202std::size_t G::StringWrap::wordsize( G::string_view s , const std::locale & loc )
203{
204 try
205 {
206 // convert the input to 32-bit wide characters using codecvt::in() and count
207 // them -- this would be a lot easier if the character set was known to be either
208 // ISO-8859 or UTF-8, but in principle it could be some unknown MBCS set
209 // from the environment -- errors are ignored because returning the
210 // input string length is a good fallback -- note that the 'classic' locale
211 // will result in conversion errors if any character is more than 127
212 const auto & codecvt = std::use_facet<std::codecvt<char32_t,char,std::mbstate_t>>( loc ) ;
213 std::vector<char32_t> warray( s.size() ) ; // 'internal', in()'s 'to'
214 std::mbstate_t state {} ;
215 const char * cnext = nullptr ;
216 char32_t * wnext = nullptr ;
217 auto rc = codecvt.in( state ,
218 s.data() , s.end() , cnext ,
219 &warray[0] , &warray[0]+warray.size() , wnext ) ;
220 std::size_t din = cnext ? std::distance( s.data() , cnext ) : 0U ;
221 std::size_t dout = wnext ? std::distance( &warray[0] , wnext ) : 0U ;
222 return ( rc == std::codecvt_base::ok && din == s.size() && dout ) ? dout : s.size() ;
223 }
224 catch( std::exception & )
225 {
226 return s.size() ;
227 }
228}
229
Private implementation structure for G::StringWrap.
Definition: gstringwrap.cpp:45
static std::locale defaultLocale()
Returns a locale with at least the CTYPE and codecvt facets initialised according to the C locale's C...
static std::size_t wordsize(const std::string &mbcs, const std::locale &)
Returns the number of wide characters after converting the input string using the locale's codecvt fa...
static std::string wrap(const std::string &text, const std::string &prefix_first, const std::string &prefix_other, std::size_t width_first=70U, std::size_t width_other=0U, bool preserve_spaces=false, const std::locale &=defaultLocale())
Does word-wrapping.
An input streambuf that takes its data from a fixed-size const buffer.
Definition: gimembuf.h:52
A class like c++17's std::string_view.
Definition: gstringview.h:51
Private implementation structure for G::StringWrap.
Definition: gstringwrap.cpp:36