]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
2 | <HTML> | |
3 | <HEAD> | |
4 | <TITLE>Tutorial</TITLE> | |
5 | <LINK REL="stylesheet" HREF="../../../../boost.css"> | |
6 | <LINK REL="stylesheet" HREF="../theme/iostreams.css"> | |
7 | </HEAD> | |
8 | <BODY> | |
9 | ||
10 | <!-- Begin Banner --> | |
11 | ||
12 | <H1 CLASS="title">Tutorial</H1> | |
13 | <HR CLASS="banner"> | |
14 | ||
15 | <!-- End Banner --> | |
16 | ||
17 | <!-- Begin Nav --> | |
18 | ||
19 | <DIV CLASS='nav'> | |
20 | <A HREF='line_wrapping_filters.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/prev.png'></A> | |
21 | <A HREF='tutorial.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/up.png'></A> | |
22 | <A HREF='dictionary_filters.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/next.png'></A> | |
23 | </DIV> | |
24 | ||
25 | <!-- End Nav --> | |
26 | ||
27 | <A NAME="tab_expanding"></A> | |
28 | <H2>2.2.5. Tab-Expanding Filters</H2> | |
29 | ||
30 | <P> | |
31 | Suppose you want to write a filter which replaces each tab character with one or more space characters in such a way that the document appears unchanged when displayed. The basic algorithm is as follows: You examine characters one at a time, forwarding them <I>as-is</I> and keeping track of the current column number. When you encounter a tab character, you replace it with a sequence of space characters whose length depends on the current column count. When you encounter a newline character, you forward it and reset the column count. | |
32 | </P> | |
33 | ||
34 | <P> | |
35 | In the next three sections, I'll express this algorithm as a <A HREF="../classes/stdio_filter.html"><CODE>stdio_filter</CODE></A>, an <A HREF="../concepts/input_filter.html">InputFilter</A> and an <A HREF="../concepts/output_filter.html">OutputFilter</A>. The source code can be found in the header <A HREF="../../example/tab_expanding_filter.hpp"><CODE><libs/iostreams/example/tab_expanding_filter.hpp></CODE></A>. These examples were inspired by James Kanze's <CODE>ExpandTabsInserter.hh</CODE> (<I>see</I> <A CLASS="bib_ref" HREF="../bibliography.html#kanze">[Kanze]</A>). | |
36 | </P> | |
37 | ||
38 | <A NAME="tab_expanding_stdio_filter"></A> | |
39 | <H4><CODE>tab_expanding_stdio_filter</CODE></H4> | |
40 | ||
41 | <P>You can express a tab-expanding Filter as a <A HREF="../classes/stdio_filter.html"><CODE>stdio_filter</CODE></A> as follows:</P> | |
42 | ||
43 | <PRE class="broken_ie"><SPAN CLASS='preprocessor'>#include</SPAN> <SPAN CLASS="literal"><cstdio></SPAN> <SPAN CLASS="comment">// EOF</SPAN> | |
44 | <SPAN CLASS="preprocessor">#include</SPAN> <SPAN CLASS="literal"><iostream></SPAN> <SPAN CLASS="comment">// cin, cout</SPAN> | |
45 | <SPAN CLASS="preprocessor">#include</SPAN> <A CLASS="header" HREF="../../../../boost/iostreams/filter/stdio.hpp"><SPAN CLASS="literal"><boost/iostreams/filter/stdio.hpp></SPAN></A> | |
46 | ||
47 | <SPAN CLASS="keyword">class</SPAN> tab_expanding_stdio_filter : <SPAN CLASS="keyword"><SPAN CLASS="keyword"><SPAN CLASS="keyword">public</SPAN></SPAN></SPAN> stdio_filter { | |
48 | <SPAN CLASS="keyword">public</SPAN>: | |
49 | explicit tab_expanding_stdio_filter(<SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> tab_size = 8) | |
50 | : tab_size_(tab_size), col_no_(0) | |
51 | { | |
52 | assert(tab_size > 0); | |
53 | } | |
54 | <SPAN CLASS="keyword">private</SPAN>: | |
55 | <SPAN CLASS="keyword">void</SPAN> do_filter(); | |
56 | <SPAN CLASS="keyword">void</SPAN> do_close(); | |
57 | <SPAN CLASS="keyword">void</SPAN> put_char(<SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c); | |
58 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> tab_size_; | |
59 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> col_no_; | |
60 | }; | |
61 | ||
62 | } } } <SPAN CLASS="comment">// End namespace boost::iostreams:example</SPAN></PRE> | |
63 | ||
64 | <P>The helper function <CODE>put_char</CODE> is identical to <A HREF="line_wrapping_filters.html#line_wrapping_stdio_filter"><CODE>line_wrapping_stdio_filter::put_char</CODE></A>. It writes a character to <CODE>std::cout</CODE> and updates the column count:</P> | |
65 | ||
66 | <PRE class="broken_ie"> <SPAN CLASS="keyword">void</SPAN> put_char(<SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c) | |
67 | { | |
68 | std::cout.put(c); | |
69 | <SPAN CLASS="keyword">if</SPAN> (c == <SPAN CLASS="literal">'\n'</SPAN>) { | |
70 | col_no_ = 0; | |
71 | } <SPAN CLASS="keyword">else</SPAN> { | |
72 | ++col_no_; | |
73 | } | |
74 | }</PRE> | |
75 | ||
76 | <P>Using <CODE>put_char</CODE> you can implement <CODE>do_filter</CODE> as follows:</P> | |
77 | ||
78 | <PRE class="broken_ie"> <SPAN CLASS="keyword">void</SPAN> do_filter() | |
79 | { | |
80 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c; | |
81 | <SPAN CLASS="keyword">while</SPAN> ((c = std::cin.get()) != <SPAN CLASS="numeric_literal">EOF</SPAN>) { | |
82 | <SPAN CLASS="keyword">if</SPAN> (c == '\t') { | |
83 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> spaces = tab_size_ - (col_no_ % tab_size_); | |
84 | for (; spaces > 0; --spaces) | |
85 | put_char(' '); | |
86 | } <SPAN CLASS="keyword">else</SPAN> { | |
87 | put_char(c); | |
88 | } | |
89 | } | |
90 | }</PRE> | |
91 | ||
92 | <P>The <CODE>while</CODE> loop reads a character from <CODE>std::cin</CODE> and writes it to <CODE>std::cout</CODE>, unless it is a tab character, in which case it writes an appropriate number of space characters to <CODE>std::cout</CODE>.</P> | |
93 | ||
94 | <P>As with <A HREF="line_wrapping_filters.html#line_wrapping_stdio_filter"><CODE>line_wrapping_stdio_filter</CODE></A>, the <CODE>virtual</CODE> function <CODE>do_close</CODE> resets the Filter's state:</P> | |
95 | ||
96 | <PRE class="broken_ie"> <SPAN CLASS="keyword">void</SPAN> do_close() { col_no_ = 0; }</PRE> | |
97 | ||
98 | <A NAME="tab_expanding_input_filter"></A> | |
99 | <H4><CODE>tab_expanding_input_filter</CODE></H4> | |
100 | ||
101 | <P>You can express a tab-expanding Filter as an <A HREF="../concepts/input_filter.html">InputFilter</A> as follows:</P> | |
102 | ||
103 | <PRE class="broken_ie"><SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="header" HREF="../../../../boost/iostreams/char_traits.hpp"><SPAN CLASS="literal"><boost/iostreams/char_traits.hpp></SPAN></A> <SPAN CLASS="comment">// EOF, WOULD_BLOCK</SPAN> | |
104 | <SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="header" HREF="../../../../boost/iostreams/concepts.hpp"><SPAN CLASS="literal"><boost/iostreams/concepts.hpp></SPAN></A> <SPAN CLASS="comment">// input_filter</SPAN> | |
105 | <SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="header" HREF="../../../../boost/iostreams/operations.hpp"><SPAN CLASS="literal"><boost/iostreams/operations.hpp></SPAN></A> <SPAN CLASS="comment">// get</SPAN> | |
106 | ||
107 | <SPAN CLASS='keyword'>namespace</SPAN> boost { <SPAN CLASS='keyword'>namespace</SPAN> iostreams { <SPAN CLASS='keyword'>namespace</SPAN> example { | |
108 | ||
109 | <SPAN CLASS="keyword">class</SPAN> tab_expanding_input_filter : <SPAN CLASS="keyword"><SPAN CLASS="keyword"><SPAN CLASS="keyword">public</SPAN></SPAN></SPAN> input_filter { | |
110 | <SPAN CLASS="keyword">public</SPAN>: | |
111 | explicit tab_expanding_input_filter(<SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> tab_size = 8) | |
112 | : tab_size_(tab_size), col_no_(0), spaces_(0) | |
113 | { | |
114 | assert(tab_size > 0); | |
115 | } | |
116 | ||
117 | <SPAN CLASS="keyword">template</SPAN><<SPAN CLASS="keyword">typename</SPAN> Source> | |
118 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> get(Source& src); | |
119 | ||
120 | <SPAN CLASS="keyword">template</SPAN><<SPAN CLASS="keyword">typename</SPAN> Source> | |
121 | <SPAN CLASS="keyword">void</SPAN> close(Source&); | |
122 | <SPAN CLASS="keyword">private</SPAN>: | |
123 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> get_char(<SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c); | |
124 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> tab_size_; | |
125 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> col_no_; | |
126 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> spaces_; | |
127 | }; | |
128 | ||
129 | } } } <SPAN CLASS="comment">// End namespace boost::iostreams:example</SPAN></PRE> | |
130 | ||
131 | <P>Let's look first at the helper function <CODE>get_char</CODE>:</P> | |
132 | ||
133 | <PRE class="broken_ie"> <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> get_char(<SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c) | |
134 | { | |
135 | <SPAN CLASS="keyword">if</SPAN> (c == <SPAN CLASS="literal">'\n'</SPAN>) { | |
136 | col_no_ = 0; | |
137 | } <SPAN CLASS="keyword">else</SPAN> { | |
138 | ++col_no_; | |
139 | } | |
140 | <SPAN CLASS="keyword">return</SPAN> c; | |
141 | }</PRE> | |
142 | ||
143 | <P>This function updates the column count based on the given character <CODE>c</CODE> and returns <CODE>c</CODE>. Using <CODE>get_char</CODE> you can implement <CODE>get</CODE> as follows:</P> | |
144 | ||
145 | <PRE class="broken_ie"> <SPAN CLASS="keyword">template</SPAN><<SPAN CLASS="keyword">typename</SPAN> Source> | |
146 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> get(Source& src) | |
147 | { | |
148 | <SPAN CLASS="keyword">if</SPAN> (spaces_ > 0) { | |
149 | --spaces_; | |
150 | <SPAN CLASS="keyword">return</SPAN> get_char(' '); | |
151 | } | |
152 | ||
153 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c; | |
154 | <SPAN CLASS="keyword">if</SPAN> ((c = iostreams::get(src)) == <SPAN CLASS="numeric_literal">EOF</SPAN> || c == WOULD_BLOCK) | |
155 | <SPAN CLASS="keyword">return</SPAN> c; | |
156 | ||
157 | <SPAN CLASS="keyword">if</SPAN> (c != '\t') | |
158 | <SPAN CLASS="keyword">return</SPAN> get_char(c); | |
159 | ||
160 | <SPAN CLASS='comment'>// Found a tab. Call this filter recursively.</SPAN> | |
161 | spaces_ = tab_size_ - (col_no_ % tab_size_); | |
162 | <SPAN CLASS="keyword">return</SPAN> this->get(src); | |
163 | }</PRE> | |
164 | ||
165 | <P> | |
166 | The implementation is similar to that of <A HREF="line_wrapping_filters.html#line_wrapping_input_filter"><CODE>line_wrapping_input_filter::get</CODE></A>. Since <CODE>get</CODE> can only return a single character at a time, whenever a tab character must be replaced by a sequence of space character, only the first space character can be returned. The rest must be returned by subsequent invocations of <CODE>get</CODE>. The member variable <CODE>spaces_</CODE> is used to store the number of such space characters. | |
167 | </P> | |
168 | <P> | |
169 | The implementation begins by checking whether any space characters remain to be returned. If so, it decrements <CODE>spaces_</CODE> and returns a space. Otherwise, a character is read from <CODE>src</CODE>. Ordinary characters, as well as the special values <CODE>EOF</CODE> and <CODE>WOULD_BLOCK</CODE>, are returned <I>as-is</I>. When a tab character is encountered, the number of spaces which must be returned by future invocations of get is recorded, and a space character is returned. | |
170 | </P> | |
171 | ||
172 | <P>As usual, the function <CODE>close</CODE> resets the Filter's state:</P> | |
173 | ||
174 | <PRE class="broken_ie"> <SPAN CLASS="keyword">void</SPAN> close(Source&) | |
175 | { | |
176 | col_no_ = 0; | |
177 | spaces_ = 0; | |
178 | }</PRE> | |
179 | ||
180 | <A NAME="tab_expanding_output_filter"></A> | |
181 | <H4><CODE>tab_expanding_output_filter</CODE></H4> | |
182 | ||
183 | <P>You can express a tab-expanding Filter as an <A HREF="../concepts/output_filter.html">OutputFilter</A> as follows:</P> | |
184 | ||
185 | <PRE class="broken_ie"><SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="header" HREF="../../../../boost/iostreams/concepts.hpp"><SPAN CLASS="literal"><boost/iostreams/concepts.hpp></SPAN></A> <SPAN CLASS="comment">// output_filter</SPAN> | |
186 | <SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="header" HREF="../../../../boost/iostreams/operations.hpp"><SPAN CLASS="literal"><boost/iostreams/operations.hpp></SPAN></A> <SPAN CLASS="comment">// put</SPAN> | |
187 | ||
188 | <SPAN CLASS='keyword'>namespace</SPAN> boost { <SPAN CLASS='keyword'>namespace</SPAN> iostreams { <SPAN CLASS='keyword'>namespace</SPAN> example { | |
189 | ||
190 | <SPAN CLASS="keyword">class</SPAN> tab_expanding_output_filter : <SPAN CLASS="keyword"><SPAN CLASS="keyword"><SPAN CLASS="keyword">public</SPAN></SPAN></SPAN> output_filter { | |
191 | <SPAN CLASS="keyword">public</SPAN>: | |
192 | explicit tab_expanding_output_filter(<SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> tab_size = 8) | |
193 | : tab_size_(tab_size), col_no_(0), spaces_(0) | |
194 | { | |
195 | assert(tab_size > 0); | |
196 | } | |
197 | ||
198 | <SPAN CLASS="keyword">template</SPAN><<SPAN CLASS="keyword">typename</SPAN> Sink> | |
199 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">bool</SPAN></SPAN> put(Sink& dest, <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c); | |
200 | ||
201 | <SPAN CLASS="keyword">template</SPAN><<SPAN CLASS="keyword">typename</SPAN> Sink> | |
202 | <SPAN CLASS="keyword">void</SPAN> close(Sink&); | |
203 | <SPAN CLASS="keyword">private</SPAN>: | |
204 | <SPAN CLASS="keyword">template</SPAN><<SPAN CLASS="keyword">typename</SPAN> Sink> | |
205 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">bool</SPAN></SPAN> put_char(Sink& dest, <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c); | |
206 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> tab_size_; | |
207 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> col_no_; | |
208 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> spaces_; | |
209 | }; | |
210 | ||
211 | } } } <SPAN CLASS="comment">// End namespace boost::iostreams:example</SPAN></PRE> | |
212 | ||
213 | <P>The implemenation helper function <CODE>put_char</CODE> is the same as that of <A HREF="line_wrapping_filters.html#line_wrapping_output_filter"><CODE>line_wrapping_output_filter::put_char</CODE></A>: it writes the given character to <CODE>std::cout</CODE> and increments the column number, unless the character is a newline, in which case the column number is reset.</P> | |
214 | ||
215 | <PRE class="broken_ie"> <SPAN CLASS="keyword">template</SPAN><<SPAN CLASS="keyword">typename</SPAN> Sink> | |
216 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">bool</SPAN></SPAN> put_char(Sink& dest, <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c) | |
217 | { | |
218 | <SPAN CLASS="keyword">if</SPAN> (!iostreams::put(dest, c)) | |
219 | <SPAN CLASS="keyword">return</SPAN> <SPAN CLASS="keyword">false</SPAN>; | |
220 | <SPAN CLASS="keyword">if</SPAN> (c != <SPAN CLASS="literal">'\n'</SPAN>) | |
221 | ++col_no_; | |
222 | <SPAN CLASS="keyword">else</SPAN> | |
223 | col_no_ = 0; | |
224 | <SPAN CLASS="keyword">return</SPAN> <SPAN CLASS="keyword">true</SPAN>; | |
225 | }</PRE> | |
226 | ||
227 | <P>Using <CODE>put_char</CODE> you can implement <CODE>put</CODE> as follows:</P> | |
228 | ||
229 | <PRE class="broken_ie"> <SPAN CLASS="keyword">template</SPAN><<SPAN CLASS="keyword">typename</SPAN> Sink> | |
230 | <SPAN CLASS="keyword"><SPAN CLASS="keyword">bool</SPAN></SPAN> put(Sink& dest, <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c) | |
231 | { | |
232 | for (; spaces_ > 0; --spaces_) | |
233 | <SPAN CLASS="keyword">if</SPAN> (!put_char(dest, ' ')) | |
234 | <SPAN CLASS="keyword">return</SPAN> <SPAN CLASS="keyword">false</SPAN>; | |
235 | ||
236 | <SPAN CLASS="keyword">if</SPAN> (c == '\t') { | |
237 | spaces_ = tab_size_ - (col_no_ % tab_size_) - 1; | |
238 | <SPAN CLASS="keyword">return</SPAN> this->put(dest, ' '); | |
239 | } | |
240 | ||
241 | <SPAN CLASS="keyword">return</SPAN> put_char(dest, c); | |
242 | }</PRE> | |
243 | ||
244 | <P> | |
245 | The implementation begins by attempting to write any space characters left over from previously encountered tabs. If successful, it examine the given character <CODE>c</CODE>. If <CODE>c</CODE> is not a tab character, it attempts to write it to <CODE>dest</CODE>. Otherwise, it calculates the number of spaces which must be inserted and calls itself recursively. Using recursion here saves us from having to decrement the member variable <CODE>spaces_</CODE> at two different points in the code. | |
246 | </P> | |
247 | <P> | |
248 | Note that after a tab character is encountered, get will return false until all the associated space characters have been written. | |
249 | </P> | |
250 | ||
251 | <P>As usual, the function <CODE>close</CODE> resets the Filter's state:</P> | |
252 | ||
253 | <PRE class="broken_ie"> <SPAN CLASS="keyword">void</SPAN> close(Source&) | |
254 | { | |
255 | col_no_ = 0; | |
256 | spaces_ = 0; | |
257 | }</PRE> | |
258 | ||
259 | <!-- Begin Nav --> | |
260 | ||
261 | <DIV CLASS='nav'> | |
262 | <A HREF='line_wrapping_filters.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/prev.png'></A> | |
263 | <A HREF='tutorial.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/up.png'></A> | |
264 | <A HREF='dictionary_filters.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/next.png'></A> | |
265 | </DIV> | |
266 | ||
267 | <!-- End Nav --> | |
268 | ||
269 | <!-- Begin Footer --> | |
270 | ||
271 | <HR> | |
272 | ||
273 | ||
274 | <P CLASS="copyright">© Copyright 2008 <a href="http://www.coderage.com/" target="_top">CodeRage, LLC</a><br/>© Copyright 2004-2007 <a href="http://www.coderage.com/turkanis/" target="_top">Jonathan Turkanis</a></P> | |
275 | <P CLASS="copyright"> | |
276 | Use, modification, and distribution are subject to the Boost Software License, Version 2.0. (See accompanying file <A HREF="../../../../LICENSE_1_0.txt">LICENSE_1_0.txt</A> or copy at <A HREF="http://www.boost.org/LICENSE_1_0.txt">http://www.boost.org/LICENSE_1_0.txt</A>) | |
277 | </P> | |
278 | <!-- End Footer --> | |
279 | ||
280 | </BODY> |