E-MailRelay

gstringwrap.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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 struct G::StringWrap::Config /// Private implementation structure for G::StringWrap.
32{
33 string_view prefix_first ;
34 string_view prefix_other ;
35 std::size_t width_first ;
36 std::size_t width_other ;
37 bool preserve_spaces ;
38} ;
39
40 class G::StringWrap::WordWrapper /// Private implementation structure for G::StringWrap.
41{
42 public:
43 WordWrapper( std::ostream & , StringWrap::Config , const std::locale & ) ;
44 void emit( const std::string & word , std::size_t newlines , const std::string & prespace ) ;
45
46 public:
47 ~WordWrapper() = default ;
48 WordWrapper( const WordWrapper & ) = delete ;
49 WordWrapper( WordWrapper && ) = delete ;
50 void operator=( const WordWrapper & ) = delete ;
51 void operator=( WordWrapper && ) = delete ;
52
53 private:
54 string_view prefix() const ;
55
56 private:
57 std::size_t m_lines {0U} ;
58 std::size_t m_w {0U} ;
59 std::ostream & m_out ;
60 StringWrap::Config m_config ;
61 const std::locale & m_loc ;
62} ;
63
64// ==
65
66G::StringWrap::WordWrapper::WordWrapper( std::ostream & out , Config config , const std::locale & loc ) :
67 m_out(out) ,
68 m_config(config) ,
69 m_loc(loc)
70{
71}
72
73 G::string_view G::StringWrap::WordWrapper::prefix() const
74{
75 return m_lines ? m_config.prefix_other : m_config.prefix_first ;
76}
77
78 void G::StringWrap::WordWrapper::emit( const std::string & word , std::size_t newlines , const std::string & prespace )
79{
80 // emit words up to the configured maximum width
81 std::size_t wordsize = StringWrap::wordsize( word , m_loc ) ;
82 std::size_t spacesize = m_config.preserve_spaces ? prespace.size() : 1U ;
83 std::size_t width = ( newlines || m_lines > 1 ) ? m_config.width_other : m_config.width_first ;
84 bool start_new_line = newlines || m_w == 0 || (m_w+spacesize+wordsize) > width ;
85 if( start_new_line )
86 {
87 // emit a blank line for each of the counted newlines
88 bool first_line = m_w == 0 ;
89 for( std::size_t i = 1U ; i < newlines ; i++ )
90 {
91 m_out << (first_line?"":"\n") << prefix() ;
92 first_line = false ;
93 m_w = prefix().size() ;
94 m_lines++ ;
95 }
96
97 // emit the first word
98 m_out << (first_line?"":"\n") << prefix() << word ;
99 m_w = prefix().size() + wordsize ;
100 m_lines++ ;
101 }
102 else
103 {
104 if( m_config.preserve_spaces )
105 m_out << (prespace.empty()?std::string(1U,' '):prespace) << word ;
106 else
107 m_out << " " << word ;
108 m_w += (spacesize+wordsize) ;
109 }
110}
111
112// ==
113
114std::string G::StringWrap::wrap( const std::string & text_in ,
115 const std::string & prefix_first , const std::string & prefix_other ,
116 std::size_t width_first , std::size_t width_other ,
117 bool preserve_spaces , const std::locale & loc )
118{
119 StringWrap::Config config {
120 { prefix_first.data() , prefix_first.size() } ,
121 { prefix_other.data() , prefix_other.size() } ,
122 width_first , width_other?width_other:width_first ,
123 preserve_spaces
124 } ;
125
126 std::ostringstream out ;
127 WordWrapper wrapper( out , config , loc ) ;
128
129 imembuf buf( text_in.data() , text_in.size() ) ;
130 std::istream in( &buf ) ;
131 in.imbue( loc ) ;
132
133 wrap( in , wrapper ) ;
134
135 return out.str() ;
136}
137
138 void G::StringWrap::wrap( std::istream & in , WordWrapper & ww )
139{
140 // extract words while counting newlines within the intervening spaces
141 const auto & cctype = std::use_facet<std::ctype<char>>( in.getloc() ) ;
142 std::string word ;
143 std::size_t newlines = 0U ;
144 std::string prespace ;
145 char c = 0 ;
146 while( in.get(c) )
147 {
148 if( cctype.is( std::ctype_base::space , c ) )
149 {
150 // spit out the previous word (if any)
151 if( !word.empty() )
152 {
153 ww.emit( word , newlines , prespace ) ;
154 newlines = 0U ;
155 prespace.clear() ;
156 }
157
158 // start the new word
159 word.clear() ;
160 if( c == '\n' )
161 {
162 newlines++ ;
163 prespace.clear() ;
164 }
165 else
166 {
167 prespace.append( 1U , c ) ;
168 }
169 }
170 else
171 {
172 word.append( 1U , c ) ;
173 }
174 }
175 // spit out the trailing word (if any)
176 if( !word.empty() )
177 ww.emit( word , newlines , prespace ) ;
178}
179
180std::locale G::StringWrap::defaultLocale()
181{
182 try
183 {
184 // see also G::gettext_init()
185 return std::locale( std::setlocale(LC_CTYPE,nullptr) ) ;
186 }
187 catch( std::exception & )
188 {
189 return std::locale::classic() ;
190 }
191}
192
193std::size_t G::StringWrap::wordsize( const std::string & s , const std::locale & loc )
194{
195 return wordsize( string_view(s.data(),s.size()) , loc ) ;
196}
197
198std::size_t G::StringWrap::wordsize( G::string_view s , const std::locale & loc )
199{
200 try
201 {
202 // convert the input to 32-bit wide characters using codecvt::in() and count
203 // them -- this would be a lot easier if the character set was known to be either
204 // ISO-8859 or UTF-8, but in principle it could be some unknown MBCS set
205 // from the environment -- errors are ignored because returning the
206 // input string length is a good fallback -- note that the 'classic' locale
207 // will result in conversion errors if any character is more than 127
208 const auto & codecvt = std::use_facet<std::codecvt<char32_t,char,std::mbstate_t>>( loc ) ;
209 std::vector<char32_t> warray( s.size() ) ; // 'internal', in()'s 'to'
210 std::mbstate_t state {} ;
211 const char * cnext = nullptr ;
212 char32_t * wnext = nullptr ;
213 auto rc = codecvt.in( state ,
214 s.data() , s.end() , cnext ,
215 &warray[0] , &warray[0]+warray.size() , wnext ) ;
216 std::size_t din = cnext ? std::distance( s.data() , cnext ) : 0U ;
217 std::size_t dout = wnext ? std::distance( &warray[0] , wnext ) : 0U ;
218 return ( rc == std::codecvt_base::ok && din == s.size() && dout ) ? dout : s.size() ;
219 }
220 catch( std::exception & )
221 {
222 return s.size() ;
223 }
224}
225
G::StringWrap::WordWrapper
Private implementation structure for G::StringWrap.
Definition: gstringwrap.cpp:41
G::StringWrap::defaultLocale
static std::locale defaultLocale()
Returns a locale with at least the CTYPE and codecvt facets initialised according to the C locale's C...
Definition: gstringwrap.cpp:180
G::StringWrap::wordsize
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...
Definition: gstringwrap.cpp:193
G::StringWrap::wrap
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.
Definition: gstringwrap.cpp:114
G::basic_imembuf
An input streambuf that takes its data from a fixed-size const buffer.
Definition: gimembuf.h:53
G::basic_string_view
A class template like c++17's std::basic_string_view.
Definition: gstringview.h:73
gimembuf.h
gstr.h
gstringwrap.h
G::StringWrap::Config
Private implementation structure for G::StringWrap.
Definition: gstringwrap.cpp:32