Listing 2: Implementation of class HTMLstream

#include "HTMLstream.h"

#include <fstream>
#include <sstream>
#include <cctype>
#include <algorithm>
#include <stdexcept>
using namespace std;

#include "cgi.h"


HTMLstream::HTMLstream (const string & filename)
{
    open (filename);
}


void HTMLstream::open (const string & filename)
{
    ifstream html_file (filename.c_str());

    if (!html_file)
    {
        throw invalid_argument
            ("Could not open data file: " + filename);
    }

    string line = "";
    while (getline (html_file, line))
    {
        html_code += line;
        html_code += '\n';
    }
}


void HTMLstream::send (ostream & output) const
{
    output << "Content-type: text/html\n\n\n";
    output << html_code << flush;
}


void
HTMLstream::set_field (const string & field,
    const string & content)
{
    // position of last partial-tag found (for the loop)
    int pos_end = 0;        
    string search_tag = "<a name=\"" + field + "\"></a>";

    bool tag_found = false;
    while (!tag_found)
    {
        // Find partial tag
        int field_position =
            html_code.find ("\"" + field + "\"", pos_end);
                
        if (field_position == string::npos)
        {
            throw invalid_argument
                ("Target not found: `" + field + "'");
        }

        // find immediately preceding character <
        // (opening the HTML tag)
        int pos_start = html_code.rfind ('<', field_position);

        if (pos_start == string::npos)
        {
            throw invalid_argument
                ("Target not found: `" + field + "'");
        }

        pos_end = html_code.find ('>', field_position);
        // find the second ocurence of the character >
        // (closing the /a tag)
        pos_end = html_code.find ('>', pos_end + 1);
                
        if (pos_end == string::npos)
        {
            throw invalid_argument
                ("Target not found: `" + field + "'");
        }

        string tag_in_html_code =
            html_code.substr (pos_start, pos_end - pos_start + 1);

        if (html_substrings_equal (tag_in_html_code, search_tag))
        {
            tag_found = true;
            html_code.replace (pos_start,
                tag_in_html_code.length(), content);
        }
        else
        {
            // so that next pass starts looking right after the
            // field, not after the '>' character found somewhere
            // after the field.
            pos_end = field_position + field.length() + 2;
        }
    }
}


void
HTMLstream::add_option (const string & select_field,
    const string & option)
{
    int pos = tag_position ("select", select_field);

    if (pos == tag_not_found)
    {
        throw invalid_argument
            ("Select field not found: `" + select_field + "'");
    }

    // Now look for the </select> matching tag
    int pos_end = lcase(html_code).find ("</select>", pos);

    if (pos_end == string::npos)
    {
        throw invalid_argument
            ("Select field `" + select_field +
                "' misses matching &lt;/select&gt; tag");
    }

    html_code.insert (pos_end, "<option> " + option + "\n");
}


void HTMLstream::remove_table_row (const string & table, int row)
{
    int table_tag_pos = tag_position ("table", table);

    if (table_tag_pos == tag_not_found)
    {
        throw invalid_argument
            ("Table not found: `" + table + "'");
    }

    // Now look for the closing tag (</table>)
    size_t table_end_pos =
        lcase(html_code).find ("</table>", table_tag_pos);

    if (table_end_pos == string::npos)
    {
        throw invalid_argument
            ("Table `" + table + "' doesn't have closing tag");
    }

    size_t row_pos = table_tag_pos,
           row_end_pos;
    bool row_found = false;

    while (!row_found && row_pos < table_end_pos)
    {
        row_pos = lcase(html_code).find ("<tr", row_pos + 1);
        row_end_pos = lcase(html_code).find ("</tr>", row_pos);

        if (row_end_pos == string::npos)
        {
            throw invalid_argument
                ("Table row doesn't have closing tag");
        }

        string row_content =
            removed_separators
                 (lcase
                     (html_code.substr
                         (row_pos, row_end_pos - row_pos)));
        ostringstream search_row_id;
        search_row_id << "<aname=\"" << row;
        if (row_content.find(search_row_id.str())!=string::npos)
        {
            row_found = true;
        }
    }

    if (! row_found)
    {
        ostringstream error_msg;
        error_msg << "Table row not found: table `"
                  << table << "', row " << row;
        throw invalid_argument (error_msg.str());
    }

    html_code.erase (row_pos, row_end_pos - row_pos + 5);
}


HTMLstream & HTMLstream::operator<< (const string & s)
{
    html_code += s;

    return *this;
}


void HTMLstream::save (const string & filename) const
{
    ofstream file (filename.c_str());

    if (!file)
    {
        throw runtime_error
            ("Error saving HTML file `" + filename + "'");
    }

    file << html_code;
}


bool
HTMLstream::html_substrings_equal (const string & s1,
    const string & s2)
{
    return lcase(removed_separators(s1)) ==
           lcase(removed_separators(s2));
}


int
HTMLstream::tag_position (const string & tag,
    const string & name) const
{
    int pos_start = tag_not_found;
    // position of last partial-tag found (for the loop)
    int pos_end = 0;        

    string search_tag = "<" + tag + " name=\"" + name + "\"";

    bool tag_found = false;
    while (!tag_found)
    {
        // Find partial tag
        int field_position =
            lcase(html_code).find ("\"" + lcase(name) + "\"",
                pos_end);
                
        if (field_position == string::npos)
        {
            return tag_not_found;
        }

        // find immediately preceding character <
        // (opening the HTML tag)
        pos_start = html_code.rfind ('<', field_position);
                
        if (pos_start == string::npos)
        {
            return tag_not_found;
        }

        pos_end = field_position + name.length() + 1;

        string tag_in_html_code =
            html_code.substr (pos_start,
                pos_end - pos_start + 1);

        if (html_substrings_equal (tag_in_html_code, search_tag))
        {
            tag_found = true;
        }
        else
        {
            // so that next pass starts looking right after
            // the field, not after the '>' character found
            // somewhere after the field.
            pos_end++;
        }
    }

    return pos_start;
}