// CS 509, Spring 2004
// Example solution for assignment 1.
#include "check.h"
#include <cassert>
#include <cctype>
class tag {
public:
enum tag_type { open, close };
// Create a tag of the given type with the given text.
tag(tag_type tt, const std::string & text)
: text(text), open_tag(tt == open) { }
// Return true iff this is a close tag.
bool is_close_tag() const {
return not open_tag;
}
// Return true iff this is an open tag.
bool is_open_tag() const {
return open_tag;
}
// Return true iff this is a end-of-file tag.
bool is_end_tag() const {
return is_close_tag() and text.empty();
}
// Return true iff this tag is a pair with the given tag.
bool match(const tag & t) const {
return (t.open_tag ^ open_tag) and (t.text == text);
}
private:
std::string text;
bool open_tag;
};
// Deja vu, c++ style.
static tag read_next_tag(std::istream &, std::string &);
static std::string trim(const std::string &);
static std::string
check_balanced_tags(std::istream & is, const tag & open_tag) {
// Make sure all the text in the given istream up to closing tag matching the
// given tag contains balanced tags. Return an error message if it doesn't;
// otherwise return the empty string.
assert(open_tag.is_open_tag());
std::string emsg;
while (true) {
tag t = read_next_tag(is, emsg);
if (!emsg.empty())
break;
if (t.is_open_tag()) {
emsg = check_balanced_tags(is, t);
if (!emsg.empty())
break;
}
else {
if (not open_tag.match(t))
if (t.is_end_tag())
emsg = "missing close tag";
else
emsg = "mismatch between open and close tags";
break;
}
}
return emsg;
}
std::string
check_balanced_tags(std::istream & is) {
// Make sure all the text in the given istream contains balanced tags.
// Return an error message if it doesn't; otherwise return the empty string.
std::string emsg;
while (true) {
tag t = read_next_tag(is, emsg);
if (not emsg.empty())
break;
if (t.is_end_tag())
break;
if (t.is_close_tag()) {
emsg = "close tag without earlier open tag";
break;
}
emsg = check_balanced_tags(is, t);
if (not emsg.empty())
break;
}
return emsg;
}
static tag
check_tag_text(const std::string & text, std::string & emsg) {
// Check the given text to make sure it's valid tag text and return the
// associated tag. If there's an error the return value is undefined and
// emsg contains an informative error message; if there's no error, emsg is
// unchanged.
std::string txt = trim(text);
if (txt.empty()) {
emsg = "tag contains no text";
return tag(tag::close, "");
}
const bool close_tag = txt[0] == '/';
if (close_tag) {
txt[0] = ' ';
txt = trim(txt);
}
if (txt.empty()) {
emsg = "tag contains no text";
return tag(tag::close, "");
}
for (std::string::size_type i = 0; i < txt.size(); i++)
if (not isalnum(txt[i])) {
emsg = "non-alphanumeric character in tag text";
break;
}
else
txt[i] = tolower(txt[i]);
return tag(close_tag ? tag::close : tag::open, txt);
}
static tag
read_next_tag(std::istream & is, std::string & emsg) {
// Read and return the next tag in the given input stream. If something goes
// wrong, store an informative error message in the given string; the return
// value is undefined; otherwise the given string is unchanged.
std::string str;
if (not getline(is, str, '<').good()) {
if (not is.eof())
emsg = "stream error during input";
return tag(tag::close, "");
}
if (not getline(is, str, '>').good()) {
if (is.eof())
emsg = "unterminated tag";
else
emsg = "stream error during input";
return tag(tag::close, "");
}
return check_tag_text(str, emsg);
}
static std::string
trim(const std::string & str) {
// Return the given string shorn of space characters front and back.
std::string::size_type
b = 0,
e = str.size();
while ((b < e) and isspace(str.at(b)))
b++;
while ((b < e) and isspace(str.at(e - 1)))
e--;
return str.substr(b, e - b);
}
#ifdef TESTING
// g++ -g -o testing-check -DTESTING -ansi -pedantic -Wall check.cc && ./testing-check
#include <sstream>
int
main() {
assert(trim("") == "");
assert(trim(" ") == "");
assert(trim("a") == "a");
assert(trim(" a") == "a");
assert(trim("a ") == "a");
assert(trim(" a ") == "a");
std::string emsg;
std::istringstream iss("< a >");
{tag t = read_next_tag(iss, emsg);
assert(emsg.empty() and ("a" == t.text) and (t.is_open_tag)); }
iss.str(" < / bl1h > ");
{tag t = read_next_tag(iss, emsg);
assert(emsg.empty() and ("bl1h" == t.text) and (t.is_close_tag())); }
iss.str("< / blah >");
{tag t = read_next_tag(iss, emsg);
assert(emsg.empty() and ("blah" == t.text) and (t.is_close_tag())); }
iss.str(" / blah >");
{tag t = read_next_tag(iss, emsg);
assert(emsg.empty() and ("" == t.text) and (t.is_close_tag())); }
iss.str(" < / blah ");
{tag t = read_next_tag(iss, emsg);
assert(not emsg.empty()); }
emsg.clear();
iss.clear();
iss.str(" < / bl!h > ");
{tag t = read_next_tag(iss, emsg);
assert(not emsg.empty()); }
}
#endif
// $Log: check.cc,v $
// Revision 1.2 2004/02/13 18:00:10 rclayton
// Redefine tags.
//
// Revision 1.1 2004/01/25 23:15:16 rclayton
// Initial revision
//
syntax highlighted by Code2HTML, v. 0.9