Files
iostreams/doc/tutorial.html
Jonathan Turkanis 5b886642b4 switched to double quotes in HTML attributes
[SVN r27367]
2005-02-14 01:51:10 +00:00

324 lines
22 KiB
HTML
Executable File

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<TITLE>Tutorial</TITLE>
<LINK REL="stylesheet" HREF="../../../boost.css">
<LINK REL="stylesheet" HREF="theme/iostreams.css">
</HEAD>
<BODY>
<!-- Begin Banner -->
<H1 CLASS="title">Tutorial</H1>
<HR CLASS="banner">
<!-- End Banner -->
<DL CLASS="page-index">
<DT><A HREF="#tutorial_source">Defining streams and stream buffers for a new data source</A></DT>
<DT><A HREF="#tutorial_sink">Defining streams and stream buffers for a new data sink</A></DT>
<DT><A HREF="#tutorial_input_filter">Filtering input</A></DT>
<DT><A HREF="#tutorial_output_filter">Filtering output</A></DT>
<DT><A HREF="#tutorial_multi_char_filters">Efficient Filtering: Multi-Character Filters</A></DT>
</DL>
<A NAME="tutorial_source"></A>
<H4>Defining Streams and Stream Buffers for a New Data Source</H4>
<P>
Suppose we want to define a standard stream buffer to read from a new type of input device. Typically this means deriving a new class from a specialization of <CODE>std::basic_streambuf</CODE> and overriding the <CODE>protected virtual</CODE> function <CODE>underflow</CODE>, and possibly also <CODE>uflow</CODE> and <CODE>xsgetn</CODE>. If we wish to be able to put back characters to the stream buffer, we also need to override <CODE>pbackfail</CODE>. If we wish to add buffering, we need to allocate a buffer and use the buffer manipulation functions <CODE>setg</CODE>, <CODE>eback</CODE>, <CODE>gptr</CODE>, <CODE>egptr</CODE> and <CODE>gbump</CODE>.
</P>
<P>
This process can be confusing to those who do not routinely work with the <CODE>protected virtual</CODE> interface of <CODE>std::basic_streambuf</CODE>. More importantly, most of the work involved has very little to do with the new
data source we wish to access. Very often, the relevant behavior of a new data source can be represented by a single member
function, prototyped as follows:
</P>
<PRE CLASS="broken_ie"> std::streamsize read(<SPAN CLASS="keyword">char</SPAN>* s, std::streamsize n);</PRE>
<P>
The function reads up to <CODE>n</CODE> characters into the buffer <CODE>s</CODE>, returning the number of characters read and throwing exceptions to indicate errors. If an integer less than <CODE>n</CODE> is
returned, the end of the stream has been reached.
</P>
<P>
The Iostreams Library allows users to create a standard input stream or stream buffer by defining a classes
with a single member function <CODE>read</CODE> and several member types.
Buffering and the ability to put back characters come for free. For a simple example, suppose we want to define a stream buffer to read a random sequence of characters.
</P>
<P>We first define a <A HREF="concepts/source.html">Source</A> as follows:</P>
<PRE CLASS="broken_ie"> <SPAN CLASS="preprocessor">#include</SPAN> <SPAN CLASS="literal">&lt;cstdlib&gt;</SPAN>
<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">// source</SPAN>
<SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> std;
<SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> boost::io;
<SPAN CLASS="keyword">struct</SPAN> random_source : <SPAN CLASS="keyword">public</SPAN> source {
random_source(<SPAN CLASS="keyword">unsigned</SPAN> <SPAN CLASS="keyword">int</SPAN> seed = <SPAN CLASS="numeric_literal">0</SPAN>) { srand(seed); }
std::streamsize read(<SPAN CLASS="keyword">char</SPAN>* s, std::streamsize n)
{
<SPAN CLASS="keyword">for</SPAN> (streamsize z = <SPAN CLASS="numeric_literal">0</SPAN>; z < n; ++z)
s[z] = (<SPAN CLASS="keyword">char</SPAN>) (rand() * <SPAN CLASS="numeric_literal">256</SPAN> / RAND_MAX);
<SPAN CLASS="keyword">return</SPAN> n;
}
};</PRE>
<P>
Here <A HREF="classes/device.html#synopsis"><CODE>source</CODE></A> is a convenience base class which provides several
member types as well as default implementations of several member functions. (The member functions are not needed here.) Given the above definition, we can construct a standard stream buffer as follows:
</P>
<PRE CLASS="broken_ie"> streambuf_facade&lt;random_source&gt; in(random_source(<SPAN CLASS="numeric_literal">5</SPAN>));
<SPAN CLASS="omitted">...</SPAN> <SPAN CLASS="comment">// read from stream</SPAN>
in.close(); <SPAN CLASS="comment">// close stream</SPAN>
in.open(random_source(<SPAN CLASS="numeric_literal">634875</SPAN>)); <SPAN CLASS="comment">// use a new seed</SPAN>
<SPAN CLASS="omitted">...</SPAN> <SPAN CLASS="comment">// read from stream</SPAN></PRE>
<P>We can also define a standard input stream:</P>
<PRE CLASS="broken_ie"> stream_facade&lt;random_source&gt; in(random_source(<SPAN CLASS="numeric_literal">5</SPAN>));</PRE>
<P>
Both <CODE>streambuf_facade</CODE> and <CODE>stream_facade</CODE> have constructors and overloads of open which forward their arguments to Filter or Device constructor. Therefore, the first example could have been written:
</P>
<PRE CLASS="broken_ie"> streambuf_facade&lt;random_source&gt; in(<SPAN CLASS="numeric_literal">5</SPAN>);
<SPAN CLASS="omitted">...</SPAN>
in.close();
in.open(<SPAN CLASS="numeric_literal">634875</SPAN>);
<SPAN CLASS="omitted">...</SPAN></PRE>
<A NAME="tutorial_sink"></A>
<H4>Defining Streams and Stream Buffers for a New Data Sink</H4>
<P>
The case is similar if we wish to define a standard stream buffer to write to a new type of output device. Instead of
overriding the <CODE>protected virtual</CODE> functions <CODE>overflow</CODE>, <CODE>xsputn</CODE> and <CODE>sync</CODE> and making use of the buffer manipulation functions <CODE>setp</CODE>, <CODE>pbase</CODE>, <CODE>pptr</CODE>, <CODE>epptr</CODE> and <CODE>pbump</CODE>, we simply define a class with several member types and a member function <CODE>write</CODE> prototyped as follows:
</P>
<PRE CLASS="broken_ie"> <SPAN CLASS="keyword">void</SPAN> write(<SPAN CLASS="keyword">const</SPAN> <SPAN CLASS="keyword">char</SPAN>* s, std::streamsize n);</PRE>
<P>
The function writes <CODE>n</CODE> characters to the output device, throwing exceptions to indicate errors.
For a simple example, suppose we want to define a stream buffer to write characters to the end of a standard <CODE>vector</CODE>. We can define a <A HREF="concepts/sink.html">Sink</A> as follows:
</P>
<PRE CLASS="broken_ie"> <SPAN CLASS="preprocessor">#include</SPAN> <SPAN CLASS="literal">&lt;vector&gt;</SPAN>
<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">// sink</SPAN>
<SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> std;
<SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> boost::io;
<SPAN CLASS="keyword">struct</SPAN> vector_sink : <SPAN CLASS="keyword">public</SPAN> sink {
vector_sink(vector&lt;<SPAN CLASS="keyword">char</SPAN>&gt;&amp; vec) : vec(vec) { }
<SPAN CLASS="keyword">void</SPAN> write(<SPAN CLASS="keyword">const</SPAN> <SPAN CLASS="keyword">char</SPAN>* s, std::streamsize n)
{
vec.insert(vec.end(), s, s + n);
}
vector&lt;<SPAN CLASS="keyword">char</SPAN>&gt;&amp; vec;
};</PRE>
<P>
Here <A HREF="classes/device.html#synopsis"><CODE>sink</CODE></A> is a convenience base class which provides several
member types as well as default implementations of several member functions. (The member functions are not needed here.) Given the above definition, we can construct a standard stream buffer as follows:
</P>
<PRE CLASS="broken_ie"> vector&lt;<SPAN CLASS="keyword">char</SPAN>&gt; v;
streambuf_facade&lt;vector_sink&gt; out(vector_sink(v)); <SPAN CLASS="comment">// stream buffer which appends to v</SPAN></PRE>
</P>
<P>
We can also define a standard output stream:
<PRE CLASS="broken_ie"> stream_facade&lt;vector_sink&gt; out(vector_sink(v));</PRE>
</P>
<P>It's tempting to write:</P>
<PRE CLASS="broken_ie"> vector&lt;<SPAN CLASS="keyword">char</SPAN>&gt; v;
streambuf_facade&lt;vector_sink&gt; out(v); <SPAN CLASS="comment">// error!!</SPAN></PRE>
<P>
Unfortunately, constructor-argument-forwarding only works for constuctors which take their arguments by value or by <CODE>const</CODE> reference; the <CODE>vector_sink</CODE> constructor takes a non-<CODE>const</CODE> reference.<SUP><A CLASS="footnote_ref" NAME="note_1_ref" HREF="#note_1">[1]</A></SUP>
</P>
<P>For easier ways to append to STL sequences, <I>see</I> <A HREF="adapters.html">STL Sequence Adapters</A>.</P>
<A NAME="tutorial_input_filter"></A>
<H4>Filtering Input</H4>
<P>
Suppose we want to filter data read from a standard input stream. Let us say we wish to read only the alphabetic
characters. We can do this by defining a component &#8212; an <A HREF="concepts/input_filter.html">InputFilter</A> &#8212; which reads data from a provided Source and modifies it before returning it to the user:
</P>
<PRE CLASS="broken_ie"> <SPAN CLASS="preprocessor">#include</SPAN> <SPAN CLASS="literal">&lt;ctype.h&gt;</SPAN> <SPAN CLASS="comment">// isalpha</SPAN>
<SPAN CLASS="preprocessor">#include</SPAN> <SPAN CLASS="literal">&lt;cstdio&gt;</SPAN> <SPAN CLASS="comment">// EOF</SPAN>
<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>
<SPAN CLASS="preprocessor">#include</SPAN> <A CLASS="header" HREF="../../../boost/iostreams/concepts.hpp"><SPAN CLASS="literal">&lt;boost/iostreams/operations.hpp&gt;</SPAN></A> <SPAN CLASS="comment">// get</SPAN>
<SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> std;
<SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> boost::io;
<SPAN CLASS="keyword">struct</SPAN> alphabetic_input_filter : <SPAN CLASS="keyword">public</SPAN> input_filter {
<SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Source&gt;
<SPAN CLASS="keyword">int</SPAN> get(Source& src)
{
<SPAN CLASS="keyword">int</SPAN> c;
<SPAN CLASS="keyword">while</SPAN> ((c = boost::iostreams::get(src)) != EOF && !isalpha(c))
;
<SPAN CLASS="keyword">return</SPAN> c;
}
};</PRE>
</P>
<P>
Here <A HREF="classes/filter.html#synopsis"><CODE>input_filter</CODE></A> is a convenience base class which provides several member types as well as default implementations of several member functions. (The member functions are not needed here.) The function <A HREF="functions/get.html"><CODE>boost::iostreams::get</CODE></A> reads a character from an arbitrary Source; it is provided by the Iostreams Library to allow all Sources to be accessed with a single interface.<A CLASS="footnote_ref" NAME="note_2_ref" HREF="#note_2"><SUP>[2]</SUP></A> Given the above definition, we can read filtered data from standard input as follows:
</P>
<PRE CLASS="broken_ie"> filtering_streambuf&lt;input&gt; in;
in.push(alphabetic_input_filter());
in.push(cin);
</PRE>
<P>
The last item added to the chain could be any <A HREF="concepts/source.html">Source</A>. <I>E.g.</I>, we could read a random stream of alpabetic characters using the class <CODE>random_source</CODE> defined above:
</P>
<PRE CLASS="broken_ie"> filtering_streambuf&lt;input&gt; in;
in.push(alphabetic_input_filter());
in.push(random_source());</PRE>
<P>
We could also add any number of <A HREF="concepts/input_filter.html">InputFilters</A> to the chain before adding the <A HREF="concepts/source.html">Source</A>.
</P>
<P>
These examples all remain valid if <CODE>filtering_stream&lt;input&gt;</CODE> is substituted everywhere for <CODE>filtering_streambuf&lt;input&gt;</CODE>. They could also use the following convenience <CODE>typedef</CODE>s:
</P>
<PRE CLASS="broken_ie"> <SPAN CLASS="keyword">typedef</SPAN> filtering_streambuf&lt;input&gt; filtering_istreambuf;
<SPAN CLASS="keyword">typedef</SPAN> filtering_stream&lt;input&gt; filtering_istream;</PRE>
<A NAME="tutorial_output_filter"></A>
<H4>Filtering Output</H4>
<P>
Suppose we want to filter data written to a standard output stream. Let us say we wish to transform each alphabetic
character to upper case. We can do this by defining a component &#8212; an <A HREF="concepts/output_filter.html">OutputFilter</A> &#8212; which modifies data provided by the user before writing it to a given Sink:
</P>
<PRE CLASS="broken_ie"> <SPAN CLASS="preprocessor">#include</SPAN> <SPAN CLASS="literal">&lt;ctype.h&gt;</SPAN> <SPAN CLASS="comment">// toupper</SPAN>
<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>
<SPAN CLASS="preprocessor">#include</SPAN> <A CLASS="header" HREF="../../../boost/iostreams/concepts.hpp"><SPAN CLASS="literal">&lt;boost/iostreams/operations.hpp&gt;</SPAN></A> <SPAN CLASS="comment">// put</SPAN>
<SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> std;
<SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> boost::io;
<SPAN CLASS="keyword">struct</SPAN> toupper_output_filter : <SPAN CLASS="keyword">public</SPAN> output_filter {
<SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Sink&gt;
<SPAN CLASS="keyword">void</SPAN> put(Sink& snk, <SPAN CLASS="keyword">char</SPAN> c)
{
boost::iostreams::put(snk, toupper(c));
}
};</PRE>
<P>
Here <A HREF="classes/filter.html#synopsis"><CODE>output_filter</CODE></A> is a convenience base class which provides several member types as well as default implementations of several member functions. (The member functions are not needed here.) The function <A HREF="functions/put.html"><CODE>boost::iostreams::put</CODE></A> writes a character to an arbitrary Sink; it is provided by the Iostreams Library to allow all Sinks to be accessed with a single interface.<A CLASS="footnote_ref" NAME="note_3_ref" HREF="#note_3"><SUP>[3]</SUP></A> Given the above definition, we can write filtered data to standard output as follows:
</P>
<PRE CLASS="broken_ie"> filtered_streambuf&lt;output&gt; out;
out.push(toupper_output_filter());
out.push(cout);</PRE>
<P>
The last item added to the chain could be any <A HREF="concepts/sink.html">Sink</A>. <I>E.g.</I>, we could append uppercase characters to a standard <CODE>vector</CODE> using the class <CODE>vector_sink</CODE> defined above:
</P>
<PRE CLASS="broken_ie"> vector&lt;<SPAN CLASS="keyword">char</SPAN>&gt; v;
filtered_streambuf&lt;output&gt; out;
out.push(toupper_output_filter());
out.push(vector_sink(v));
</PRE>
<P>
We could also add any number of <A HREF="concepts/output_filter.html">OutputFilters</A> to the chain before adding the <A HREF="concepts/sink.html">Sink</A>.
</P>
<P>
These examples all remain valid if <CODE>filtering_stream&lt;output&gt;</CODE> is substituted everywhere for <CODE>filtering_streambuf&lt;output&gt;</CODE>. They could also use the following convenience <CODE>typedef</CODE>s:
</P>
<PRE CLASS="broken_ie"> <SPAN CLASS="keyword">typedef</SPAN> filtering_streambuf&lt;output&gt; filtering_ostreambuf;
<SPAN CLASS="keyword">typedef</SPAN> filtering_stream&lt;output&gt; filtering_ostream;</PRE>
<A NAME="tutorial_multi_char_filters"></A>
<H4>Efficient Filtering: Multi-Character Filters</H4>
<P>
A complex filtering operation will often not be inlinable. In such cases, each time a character is
read from an ordinary <A HREF="concepts/input_filter.html">InputFilter</A> one incurs the overhead of a call to
its member function <CODE>get</CODE>. To avoid this overhead, the library allows
users to define <A HREF="concepts/multi-character.html">Multi-Character</A> Filters which can process several characters
at once by implementing a member function <CODE>read</CODE>. This function will typically be called only when the buffer associated with the Filter by the Iostreams Library becomes full. For example, the following is a multi-character implementation of the <CODE>alphabetic_input_filter</CODE> described <A HREF="#tutorial_input_filter">above</A>.
</P>
<PRE CLASS="broken_ie"> <SPAN CLASS="preprocessor">#include</SPAN> <SPAN CLASS="literal">&lt;ctype.h&gt;</SPAN> <SPAN CLASS="comment">// isalpha</SPAN>
<SPAN CLASS="preprocessor">#include</SPAN> <SPAN CLASS="literal">&lt;cstdio&gt;</SPAN> <SPAN CLASS="comment">// EOF</SPAN>
<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">// multi_char_input_filter</SPAN>
<SPAN CLASS="preprocessor">#include</SPAN> <A CLASS="header" HREF="../../../boost/iostreams/concepts.hpp"><SPAN CLASS="literal">&lt;boost/iostreams/operations.hpp&gt;</SPAN></A> <SPAN CLASS="comment">// get</SPAN>
<SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> std;
<SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> boost::io;
<SPAN CLASS="keyword">struct</SPAN> alphabetic_input_filter : <SPAN CLASS="keyword">public</SPAN> multi_char_input_filter {
<SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Source&gt;
streamsize read(Source& src, <SPAN CLASS="keyword">char</SPAN>* s, streamsize n)
{
<SPAN CLASS="keyword">int</SPAN> c;
<SPAN CLASS="keyword">char</SPAN>* first = s;
<SPAN CLASS="keyword">char</SPAN>* last = s + n;
<SPAN CLASS="keyword">while</SPAN> ( first != last &&
(c = boost::iostreams::get(src)) != EOF &&
isalpha(c) )
{
*first++ = c;
}
<SPAN CLASS="keyword">return</SPAN> <SPAN CLASS="keyword">static_cast</SPAN>&lt;streamsize&gt;(first - s);
}
};</PRE>
<!-- <P>
When a Filter is added to a filtering stream or stream buffer the instance of <CODE>streambuf_facade</CODE> created to hold it uses a moderately-sized character buffer by default. When a Multi-Character InputFilter is used, its member function <CODE>read</CODE> will generally be called only when this buffer become empty, and the value of <CODE>n</CODE> will generally be equal to the size of the buffer. This can significantly reduce the total number of function calls.
</P> -->
<P>OutputFilters can also be <A HREF="concepts/multi-character.html">Multi-Character</A>. For example:</P>
<PRE CLASS="broken_ie"> <SPAN CLASS="preprocessor">#include</SPAN> <SPAN CLASS="literal">&lt;ctype.h&gt;</SPAN> <SPAN CLASS="comment">// toupper</SPAN>
<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">// multichar_output_filter</SPAN>
<SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> std;
<SPAN CLASS="keyword">using</SPAN> <SPAN CLASS="keyword">namespace</SPAN> boost::io;
<SPAN CLASS="keyword">struct</SPAN> toupper_filter : <SPAN CLASS="keyword">public</SPAN> multichar_output_filter {
<SPAN CLASS="keyword">template</SPAN>&lt;<SPAN CLASS="keyword">typename</SPAN> Sink&gt;
<SPAN CLASS="keyword">void</SPAN> write(Sink& snk, <SPAN CLASS="keyword">const</SPAN> <SPAN CLASS="keyword">char</SPAN>* s, streamsize n)
{
<SPAN CLASS="keyword">while</SPAN> (n-- > <SPAN CLASS="numeric_literal">0</SPAN>)
boost::iostreams::put(snk, toupper(*s++));
}
};</PRE>
<!-- Begin Footnotes -->
<HR>
<P>
<A CLASS="footnote_ref" NAME="note_1" HREF="#note_1_ref"><SUP>[1]</SUP></A>This is an instance of a limitation of C++ known as the <I>forwarding problem</I> (<I>see</I> <A CLASS="bib_ref" HREF="bibliography.html#dimov">[Dimov]</A>).
</P>
<P>
<A CLASS="footnote_ref" NAME="note_2" HREF="#note_2_ref"><SUP>[2]</SUP></A>Technically, <CODE>boost::iostreams::get</CODE> and <CODE>boost::iostreams::read</CODE> require that a Source be <A HREF="concepts/direct.html"><I>indirect</I></A>.
</P>
<P>
<A CLASS="footnote_ref" NAME="note_3" HREF="#note_3_ref"><SUP>[3]</SUP></A>Technically, <CODE>boost::iostreams::put</CODE> requires that a Sink be <A HREF="concepts/direct.html"><I>indirect</I></A>.
</P>
<!-- End Footnotes -->
<!-- Begin Footer -->
<HR>
<P CLASS="copyright">Revised
<!--webbot bot="Timestamp" S-Type="EDITED" S-Format="%d %B, %Y" startspan -->
20 May, 2004
<!--webbot bot="Timestamp" endspan i-checksum="38504" -->
</P>
<P CLASS="copyright">&copy; Copyright Jonathan Turkanis, 2004</P>
<P CLASS="copyright">
Use, modification, and distribution are subject to the Boost Software License, Version 1.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>)
</P>
<!-- End Footer -->
</BODY>