dictionary_filters.html 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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. <!-- Begin Banner -->
  10. <H1 CLASS="title">Tutorial</H1>
  11. <HR CLASS="banner">
  12. <!-- End Banner -->
  13. <!-- Begin Nav -->
  14. <DIV CLASS='nav'>
  15. <A HREF='tab_expanding_filters.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/prev.png'></A>
  16. <A HREF='tutorial.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/up.png'></A>
  17. <A HREF='unix2dos_filters.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/next.png'></A>
  18. </DIV>
  19. <!-- End Nav -->
  20. <H2>2.2.6. Dictionary Filters</H2>
  21. <P>
  22. A <SPAN CLASS='term'>dictionary filter</SPAN> is a Filter which performs text substitution in the following manner. It maintains a collection of pairs of strings whose first components are words and whose second components represent replacement text &#8212; I'll call such a collection a <SPAN CLASS='term'>dictionary</SPAN>, and refer to the pairs it contains as <SPAN CLASS='term'>definitions</SPAN>. When a dictionary filter encounters a word which appears as the first component of a definition, it forwards the replacement text instead of the original word. Other words, whitespace and punctuation are forwarded unchanged.
  23. </P>
  24. <P>
  25. The basic algorithm is as follows: You examine characters one at a time, appending them to a string which I'll call the <SPAN CLASS='term'>current word</SPAN>. When you encounter a non-alphabetic character, you consult the dictionary to determine whether the current word appears as the first component of a definition. If it does, you forward the replacement text followed by the non-alphabetic character. Otherwise, you forward the current word followed by the non-alphabetic character. When the end-of-stream is reached, you consult the dictionary again and forward either the curent word or its replacement, as appropriate.
  26. </P>
  27. <P>
  28. In the following 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/dictionary_filter.hpp"><CODE>&lt;libs/iostreams/example/dictionary_filter.hpp&gt;</CODE></A>.
  29. </P>
  30. <A NAME="dictionary"></A>
  31. <H4><CODE>dictionary</CODE></H4>
  32. <P>You can represent a dictionary using the following class:</P>
  33. <PRE class="broken_ie"><SPAN CLASS='preprocessor'>#include</SPAN> <SPAN CLASS="literal">&lt;map&gt;</SPAN>
  34. <SPAN CLASS="preprocessor">#include</SPAN> <SPAN CLASS="literal">&lt;string&gt;</SPAN>
  35. <SPAN CLASS='keyword'>namespace</SPAN> boost { <SPAN CLASS='keyword'>namespace</SPAN> iostreams { <SPAN CLASS='keyword'>namespace</SPAN> example {
  36. <SPAN CLASS="keyword">class</SPAN> dictionary {
  37. <SPAN CLASS="keyword">public</SPAN>:
  38. <SPAN CLASS="keyword">void</SPAN> add(std::string key, <SPAN CLASS="keyword">const</SPAN> std::string&amp; value);
  39. <SPAN CLASS="keyword">void</SPAN> replace(std::string&amp; key);
  40. <SPAN CLASS='comment'>/* ... */</SPAN>
  41. };
  42. } } } <SPAN CLASS="comment">// End namespace boost::iostreams:example</SPAN></PRE>
  43. <P>
  44. The member function <CODE>add</CODE> converts <CODE>key</CODE> to lower case and adds the pair <CODE>key</CODE>, <CODE>value</CODE> to the dictionary. The member function <CODE>replace</CODE> searches for a definition whose first component is equal to the result of converting <CODE>key</CODE> to lower case. If it finds such a definition, it assigns the replacement text to <CODE>key</CODE>, adjusting the case of the first character to match the case of the first character of <CODE>key</CODE>. Otherwise, it does nothing.
  45. </P>
  46. <A NAME="dictionary_stdio_filter"></A>
  47. <H4><CODE>dictionary_stdio_filter</CODE></H4>
  48. <P>You can express a dictionary filter as a <A HREF="../classes/stdio_filter.html"><CODE>stdio_filter</CODE></A> as follows:</P>
  49. <PRE class="broken_ie"><SPAN CLASS='preprocessor'>#include</SPAN> <SPAN CLASS="literal">&lt;cstdio&gt;</SPAN> <SPAN CLASS="comment">// EOF</SPAN>
  50. <SPAN CLASS="preprocessor">#include</SPAN> <SPAN CLASS="literal">&lt;iostream&gt;</SPAN> <SPAN CLASS="comment">// cin, cout</SPAN>
  51. <SPAN CLASS="preprocessor">#include</SPAN> <A CLASS="header" HREF="../../../../boost/iostreams/filter/stdio.hpp"><SPAN CLASS="literal">&lt;boost/iostreams/filter/stdio.hpp&gt;</SPAN></A>
  52. <SPAN CLASS='keyword'>namespace</SPAN> boost { <SPAN CLASS='keyword'>namespace</SPAN> iostreams { <SPAN CLASS='keyword'>namespace</SPAN> example {
  53. <SPAN CLASS="keyword">class</SPAN> dictionary_stdio_filter : <SPAN CLASS="keyword"><SPAN CLASS="keyword"><SPAN CLASS="keyword">public</SPAN></SPAN></SPAN> stdio_filter {
  54. <SPAN CLASS="keyword">public</SPAN>:
  55. dictionary_stdio_filter(dictionary&amp; d) : dictionary_(d) { }
  56. <SPAN CLASS="keyword">private</SPAN>:
  57. <SPAN CLASS="keyword">void</SPAN> do_filter()
  58. {
  59. <SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> std;
  60. <SPAN CLASS="keyword">while</SPAN> (<SPAN CLASS="keyword">true</SPAN>) {
  61. <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c = std::cin.get();
  62. <SPAN CLASS="keyword">if</SPAN> (c == <SPAN CLASS="numeric_literal">EOF</SPAN> || !std::isalpha((<SPAN CLASS="keyword">unsigned</SPAN> <SPAN CLASS="keyword">char</SPAN>) c)) {
  63. dictionary_.replace(current_word_);
  64. cout.write( current_word_.data(),
  65. <SPAN CLASS="keyword">static_cast</SPAN>&lt;streamsize&gt;(current_word_.size()) );
  66. current_word_.erase();
  67. <SPAN CLASS="keyword">if</SPAN> (c == <SPAN CLASS="numeric_literal">EOF</SPAN>)
  68. break;
  69. cout.put(c);
  70. } <SPAN CLASS="keyword">else</SPAN> {
  71. current_word_ += c;
  72. }
  73. }
  74. }
  75. dictionary&amp; dictionary_;
  76. std::string current_word_;
  77. };
  78. } } } <SPAN CLASS="comment">// End namespace boost::iostreams:example</SPAN></PRE>
  79. <P>
  80. The implementation of <CODE>do_filter</CODE> simply loops, reading characters from <CODE>std::cin</CODE> and <CODE>appending</CODE> them to the member variable <CODE>current_word_</CODE> until a non-alphabetic character or end-of-stream indication is encountered. When this occurs it uses its dictionary, stored in the member variable <CODE>dictionary_</CODE>, to replace the current word if necessary. Finally, it writes the current word, followed by the non-alphabetic character, if any, to <CODE>std::cout</CODE>.
  81. </P>
  82. <A NAME="dictionary_input_filter"></A>
  83. <H4><CODE>dictionary_input_filter</CODE></H4>
  84. <P>You can express a dictionary filter as an <A HREF="../concepts/input_filter.html">InputFilter</A> as follows:</P>
  85. <PRE class="broken_ie"><SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="header" HREF="../../../../boost/iostreams/char_traits.hpp"><SPAN CLASS="literal">&lt;boost/iostreams/char_traits.hpp&gt;</SPAN></A> <SPAN CLASS="comment">// EOF, WOULD_BLOCK</SPAN>
  86. <SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="header" HREF="../../../../boost/iostreams/concepts.hpp"><SPAN CLASS="literal">&lt;boost/iostreams/concepts.hpp&gt;</SPAN></A> <SPAN CLASS="comment">// input_filter</SPAN>
  87. <SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="header" HREF="../../../../boost/iostreams/operations.hpp"><SPAN CLASS="literal">&lt;boost/iostreams/operations.hpp&gt;</SPAN></A> <SPAN CLASS="comment">// get</SPAN>
  88. <SPAN CLASS='keyword'>namespace</SPAN> boost { <SPAN CLASS='keyword'>namespace</SPAN> iostreams { <SPAN CLASS='keyword'>namespace</SPAN> example {
  89. <SPAN CLASS="keyword">class</SPAN> dictionary_input_filter : <SPAN CLASS="keyword"><SPAN CLASS="keyword"><SPAN CLASS="keyword">public</SPAN></SPAN></SPAN> input_filter {
  90. <SPAN CLASS="keyword">public</SPAN>:
  91. dictionary_input_filter(dictionary&amp; d)
  92. : dictionary_(d), off_(std::string::npos), eof_(<SPAN CLASS="keyword">false</SPAN>)
  93. { }
  94. <SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Source&gt;
  95. <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> get(Source&amp; src);
  96. <SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Source&gt;
  97. <SPAN CLASS="keyword">void</SPAN> close(Source&amp;);
  98. <SPAN CLASS="keyword">private</SPAN>:
  99. dictionary&amp; dictionary_;
  100. std::string current_word_;
  101. std::string::size_type off_;
  102. <SPAN CLASS="keyword"><SPAN CLASS="keyword">bool</SPAN></SPAN> eof_;
  103. };
  104. } } } <SPAN CLASS="comment">// End namespace boost::iostreams:example</SPAN></PRE>
  105. <P>The function <CODE>get</CODE> is implemented as follows:</P>
  106. <PRE class="broken_ie"> <SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Source&gt;
  107. <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> get(Source&amp; src)
  108. {
  109. <SPAN CLASS='comment'>// Handle unfinished business.</SPAN>
  110. <SPAN CLASS="keyword">if</SPAN> (off_ != std::string::npos &amp;&amp; off_ &lt; current_word_.size())
  111. <SPAN CLASS="keyword">return</SPAN> current_word_[off_++];
  112. <SPAN CLASS="keyword">if</SPAN> (off_ == current_word_.size()) {
  113. current_word_.erase();
  114. off_ = std::string::npos;
  115. }
  116. <SPAN CLASS="keyword">if</SPAN> (eof_)
  117. <SPAN CLASS="keyword">return</SPAN> <SPAN CLASS="numeric_literal">EOF</SPAN>;
  118. <SPAN CLASS='comment'>// Compute curent word.</SPAN>
  119. <SPAN CLASS="keyword">while</SPAN> (<SPAN CLASS="keyword">true</SPAN>) {
  120. <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c;
  121. <SPAN CLASS="keyword">if</SPAN> ((c = iostreams::get(src)) == WOULD_BLOCK)
  122. <SPAN CLASS="keyword">return</SPAN> WOULD_BLOCK;
  123. <SPAN CLASS="keyword">if</SPAN> (c == <SPAN CLASS="numeric_literal">EOF</SPAN> || !std::isalpha((<SPAN CLASS="keyword">unsigned</SPAN> <SPAN CLASS="keyword">char</SPAN>) c)) {
  124. dictionary_.replace(current_word_);
  125. off_ = 0;
  126. <SPAN CLASS="keyword">if</SPAN> (c == <SPAN CLASS="numeric_literal">EOF</SPAN>)
  127. eof_ = <SPAN CLASS="keyword">true</SPAN>;
  128. <SPAN CLASS="keyword">else</SPAN>
  129. current_word_ += c;
  130. break;
  131. } <SPAN CLASS="keyword">else</SPAN> {
  132. current_word_ += c;
  133. }
  134. }
  135. <SPAN CLASS="keyword">return</SPAN> this-&gt;get(src); <SPAN CLASS='comment'>// Note: current_word_ is not empty.</SPAN>
  136. }</PRE>
  137. <P>
  138. You first check to see whether there are any characters which remain from a previous invocation of <CODE>get</CODE>. If so, you update some book keeping information and return the first such character.
  139. </P>
  140. <P>
  141. The <CODE>while</CODE> loop is very similar to that of <A HREF="#dictionary_stdio_filter"><CODE>dictionary_stdio_filter::do_filter</CODE></A>: it reads characters from the <A HREF="../concepts/source.html">Source</A> <CODE>src</CODE>, appending them to <CODE>current_word_</CODE> until a non-alphabetic character, <CODE>EOF</CODE> or <CODE>WOULD_BLOCK</CODE> is encountered. The value <CODE>WOULD_BLOCK</CODE> is passed on to the caller. In the remaining cases, the dictionary is consulted to determine the appropriate replacement text.
  142. </P>
  143. <P>Finally, <CODE>get</CODE> is called recursively to return the first character of the current word.</P>
  144. <P>As usual, the function <CODE>close</CODE> resets the Filter's state:</P>
  145. <PRE class="broken_ie"> <SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Source&gt;
  146. <SPAN CLASS="keyword">void</SPAN> close(Source&amp;)
  147. {
  148. current_word_.erase();
  149. off_ = std::string::npos;
  150. eof_ = <SPAN CLASS="keyword">false</SPAN>;
  151. }</PRE>
  152. <A NAME="dictionary_output_filter"></A>
  153. <H4><CODE>dictionary_output_filter</CODE></H4>
  154. <P>You can express a dictionary filter as an <A HREF="../concepts/output_filter.html">OutputFilter</A> as follows:</P>
  155. <PRE class="broken_ie"><SPAN CLASS='preprocessor'>#include</SPAN> <SPAN CLASS='literal'>&lt;algorithm&gt;</SPAN> <SPAN CLASS='comment'>// swap</SPAN>
  156. <SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="header" HREF="../../../../boost/iostreams/concepts.hpp"><SPAN CLASS="literal">&lt;boost/iostreams/concepts.hpp&gt;</SPAN></A> <SPAN CLASS="comment">// output_filter</SPAN>
  157. <SPAN CLASS='preprocessor'>#include</SPAN> <A CLASS="header" HREF="../../../../boost/iostreams/operations.hpp"><SPAN CLASS="literal">&lt;boost/iostreams/operations.hpp&gt;</SPAN></A> <SPAN CLASS="comment">// write</SPAN>
  158. <SPAN CLASS='keyword'>namespace</SPAN> boost { <SPAN CLASS='keyword'>namespace</SPAN> iostreams { <SPAN CLASS='keyword'>namespace</SPAN> example {
  159. <SPAN CLASS="keyword">class</SPAN> dictionary_output_filter : <SPAN CLASS="keyword"><SPAN CLASS="keyword"><SPAN CLASS="keyword">public</SPAN></SPAN></SPAN> output_filter {
  160. <SPAN CLASS="keyword">public</SPAN>:
  161. <SPAN CLASS="keyword">typedef</SPAN> std::map&lt;std::string, std::string&gt; map_type;
  162. dictionary_output_filter(dictionary&amp; d)
  163. : dictionary_(d), off_(std::string::npos)
  164. { }
  165. <SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Sink&gt;
  166. <SPAN CLASS="keyword"><SPAN CLASS="keyword">bool</SPAN></SPAN> put(Sink&amp; dest, <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c);
  167. <SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Sink&gt;
  168. <SPAN CLASS="keyword">void</SPAN> close(Sink&amp; dest);
  169. <SPAN CLASS="keyword">private</SPAN>:
  170. <SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Sink&gt;
  171. <SPAN CLASS="keyword"><SPAN CLASS="keyword">bool</SPAN></SPAN> write_current_word(Sink&amp; dest);
  172. dictionary&amp; dictionary_;
  173. std::string current_word_;
  174. std::string::size_type off_;
  175. };
  176. } } } <SPAN CLASS="comment">// End namespace boost::iostreams:example</SPAN></PRE>
  177. <P>Let's look first at the helper function <CODE>write_current_word</CODE>:</P>
  178. <PRE class="broken_ie"> <SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Sink&gt;
  179. <SPAN CLASS="keyword"><SPAN CLASS="keyword">bool</SPAN></SPAN> write_current_word(Sink&amp; dest)
  180. {
  181. <SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> std;
  182. streamsize amt = <SPAN CLASS="keyword">static_cast</SPAN>&lt;streamsize&gt;(current_word_.size() - off_);
  183. streamsize result =
  184. iostreams::write(dest, current_word_.data() + off_, amt);
  185. <SPAN CLASS="keyword">if</SPAN> (result == amt) {
  186. current_word_.erase();
  187. off_ = string::npos;
  188. <SPAN CLASS="keyword">return</SPAN> <SPAN CLASS="keyword">true</SPAN>;
  189. } <SPAN CLASS="keyword">else</SPAN> {
  190. off_ += <SPAN CLASS="keyword">static_cast</SPAN>&lt;string::size_type&gt;(result);
  191. <SPAN CLASS="keyword">return</SPAN> <SPAN CLASS="keyword">false</SPAN>;
  192. }
  193. }</PRE>
  194. <P>
  195. This function attempts to write <CODE>current_word_</CODE>, beginning at the offset <CODE>off_</CODE>, to the provided <A HREF="../concepts/sink.html">Sink</A>. If the entire sequence is successfully written, <CODE>current_word_</CODE> is cleared and the function returns <CODE>true</CODE>. Otherwise the member variable <CODE>off_</CODE> is updated to point to the first unwritten character and the function fails.
  196. </P>
  197. <P>Using <CODE>write_current_word</CODE> you can implement <CODE>put</CODE> as follows:</P>
  198. <PRE class="broken_ie"> <SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Sink&gt;
  199. <SPAN CLASS="keyword"><SPAN CLASS="keyword">bool</SPAN></SPAN> put(Sink&amp; dest, <SPAN CLASS="keyword"><SPAN CLASS="keyword">int</SPAN></SPAN> c)
  200. {
  201. <SPAN CLASS="keyword">if</SPAN> (off_ != std::string::npos &amp;&amp; !write_current_word(dest))
  202. <SPAN CLASS="keyword">return</SPAN> <SPAN CLASS="keyword">false</SPAN>;
  203. <SPAN CLASS="keyword">if</SPAN> (!std::isalpha((<SPAN CLASS="keyword">unsigned</SPAN> <SPAN CLASS="keyword">char</SPAN>) c)) {
  204. dictionary_.replace(current_word_);
  205. off_ = 0;
  206. }
  207. current_word_ += c;
  208. <SPAN CLASS="keyword">return</SPAN> <SPAN CLASS="keyword">true</SPAN>;
  209. }</PRE>
  210. <P>
  211. As in the implementation of <A HREF="#dictionary_input_filter"><CODE>dictionary_input_filter::get</CODE></A>, you first check to see whether there are any characters from a previous invocation of <CODE>put</CODE> which remain to be written. If so, you attempt to write these characters using <CODE>write_current_word</CODE>. If successful, you next examine the given character <CODE>c</CODE>. If it is a non-alphabetic character, you consult the dictionary to determine the appropriate replacement text. In any case, you append <CODE>c</CODE> to <CODE>current_word_</CODE> and return <CODE>true</CODE>.
  212. </P>
  213. <P>The function <CODE>close</CODE> has more work to do in this case than simply reseting the Filter's state. Unless the last character of the unfiltered sequence happened to be a non-alphabetic character, the contents of current_word_ will not yet have been written:</P>
  214. <PRE class="broken_ie"> <SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Sink&gt;
  215. void close(Sink&amp; dest)
  216. {
  217. <SPAN CLASS='comment'>// Reset current_word_ and off_, saving old values.</SPAN>
  218. std::string current_word;
  219. std::string::size_type off = <SPAN CLASS='numeric_literal'>0</SPAN>;
  220. current_word.swap(current_word_);
  221. std::swap(off, off_);
  222. <SPAN CLASS='comment'>// Write remaining characters to dest.</SPAN>
  223. <SPAN CLASS="keyword">if</SPAN> (off == std::string::npos) {
  224. dictionary_.replace(current_word);
  225. off = <SPAN CLASS='numeric_literal'>0</SPAN>;
  226. }
  227. <SPAN CLASS="keyword">if</SPAN> (!current_word.empty())
  228. iostreams::write(
  229. dest,
  230. current_word.data() + off,
  231. <SPAN CLASS="keyword">static_cast</SPAN>&lt;std::streamsize&gt;(current_word.size() - off)
  232. );
  233. }</PRE>
  234. <P>Note that you may assume that the template argument is a <A HREF="../concepts/blocking.html">Blocking</A> <A HREF="../concepts/sink.html">Sink</A>, and that you must reset the values of <CODE>current_word_</CODE> and <CODE>off_</CODE> before calling <A HREF="../functions/write.html"><CODE>write</CODE></A>, in case <A HREF="../functions/write.html"><CODE>write</CODE></A> throws an exception.</P>
  235. <!-- Begin Nav -->
  236. <DIV CLASS='nav'>
  237. <A HREF='tab_expanding_filters.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/prev.png'></A>
  238. <A HREF='tutorial.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/up.png'></A>
  239. <A HREF='unix2dos_filters.html'><IMG BORDER=0 WIDTH=19 HEIGHT=19 SRC='../../../../doc/src/images/next.png'></A>
  240. </DIV>
  241. <!-- End Nav -->
  242. <!-- Begin Footer -->
  243. <HR>
  244. <P CLASS="copyright">&copy; Copyright 2008 <a href="http://www.coderage.com/" target="_top">CodeRage, LLC</a><br/>&copy; Copyright 2004-2007 <a href="https://www.boost.org/users/people/jonathan_turkanis.html" target="_top">Jonathan Turkanis</a></P>
  245. <P CLASS="copyright">
  246. 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>)
  247. </P>
  248. <!-- End Footer -->
  249. </BODY>