Provide common error information for XML components.
More...
Detailed Description
- Outline
-
-
- Purpose:
- Provide common error information for XML components.
-
- Classes:
-
- See also:
- Component balxml_reader
-
- Description:
- This component provides an in-core value-semantic class,
balxml::ErrorInfo
, that contains the following diagnostic information: - severity (WARNING, ERROR, FATAL ERROR)
- line number
- column number
- document source (name of document containing the error)
- error message (error description)
Parsing components in the baexml
package make use of balxml::ErrorInfo
as a standard way to report errors and warnings back to the caller. The information contained within a balxml::ErrorInfo
object is sufficient to report a single diagnostic in an input document.
-
- Usage:
- In this example, we create a parser for a simple file of percentages. The file is formatted as a sequence of lines, with each line containing a decimal number in the range "0" to "100", inclusive. Leading whitespace and blank lines are ignored. When an error occurs during parsing, the error data is stored in a
balxml::ErrorInfo
object. Our parser's interface is as follows: #include <balxml_errorinfo.h>
#include <sstream>
#include <cstdlib>
#include <cstring>
#include <climits>
class PercentParser {
bsl::istream *d_input;
bsl::string d_docName;
int d_line;
public:
PercentParser(bsl::istream *input,
const bsl::string& docName = "INPUT");
int parseNext(balxml::ErrorInfo *errorInfo);
};
The constructor is straight-forward: PercentParser::PercentParser(bsl::istream *input,
const bsl::string& docName)
: d_input(input), d_docName(docName), d_line(0)
{
}
The parseNext
function begins by testing if a previous error occurred. By testing this condition, we can call parseNext
several times, knowing that the first error will stop the parse operation. int PercentParser::parseNext(balxml::ErrorInfo *errorInfo)
{
static const int MAX_LINE = 20;
if (errorInfo->isAnyError()) {
return -2;
}
The parser skips leading whitespace and lines containing only whitespace. It loops until a non-empty line is found: char buffer[MAX_LINE + 1];
buffer[0] = '\0';
int len = 0;
int startColumn = 0;
while (startColumn == len) {
++d_line;
d_input->getline(buffer, MAX_LINE + 1, '\n');
len = bsl::strlen(buffer);
The input stream reports that the input line is longer than MAX_LINE
by setting the fail() condition. In this case, we set the error object to a warning state, indicating the line and column where the problem occurred. Then we clear the stream condition and discard the rest of the line. if (MAX_LINE == len && d_input->fail()) {
errorInfo->setError(balxml::ErrorInfo::BAEXML_WARNING,
d_line, len, d_docName,
"Text after 20th column was discarded");
d_input->clear();
d_input->ignore(INT_MAX, '\n');
}
If we detect an EOF condition, we just return -1. Otherwise, we skip the leading whitespace and go on. else if (0 == len && d_input->eof()) {
return -1;
}
startColumn = bsl::strspn(buffer, " \t");
}
Now we perform two more error checks: one or superfluous characters after the integer, the other for an out-of-range integer. If the errorInfo
object is already in warning state, either of these errors will overwrite the existing warning with the new error condition. char *endp = 0;
long result = bsl::strtol(buffer + startColumn, &endp, 10);
int endColumn = endp - buffer;
if (endColumn < len) {
errorInfo->setError(balxml::ErrorInfo::BAEXML_ERROR,
d_line, endColumn + 1, d_docName,
"Bad input character");
return -2;
} else if (result < 0 || 100 < result) {
errorInfo->setError(balxml::ErrorInfo::BAEXML_ERROR,
d_line, startColumn + 1, d_docName,
"Value is not between 0 and 100");
return -2;
}
If there were no errors, return the result. Note that the errorInfo
object may contain a warning, but warnings typically do not cause a change in the error value. The main program uses the PercentParser
class to parse a list of values and compute the average. Typically, the data would be stored in a file, but we'll use a literal string for demonstration purposes: int main()
{
static const char INPUTS[] =
" 20\n"
" 30\n"
" \n"
"99x\n"
" 101\n"
" 1010\n";
We convert the string into a stream and initialize the parser. We name our input stream "Inputs" for the purpose of error handling. We also initialize our working variables: bsl::istringstream inputstream(INPUTS);
PercentParser parser(&inputstream, "Inputs");
int result;
int sum = 0;
int numValues = 0;
Any error in parsing will be stored in the errorInfo
object. When first constructed, it has a severity of BAEXML_NO_ERROR
. Normally, parsing would proceed in a loop. However, to illustrate the different error-handling situations, we have unrolled the loop below.
- The first parse succeeds, and no error is reported:
result = parser.parseNext(&errorInfo);
assert(20 == result);
assert(errorInfo.isNoError());
sum += result;
++numValues;
The next parse also succeeds but, because the input line was very long, a warning was generated: result = parser.parseNext(&errorInfo);
assert(3 == result);
assert(errorInfo.isWarning());
assert(2 == errorInfo.lineNumber());
assert(20 == errorInfo.columnNumber());
assert("Text after 20th column was discarded" == errorInfo.message());
sum += result;
++numValues;
After resetting the errorInfo
object, the we call nextParse
again. This time it fails with an error. The line, column, and source of the error are reported in the object. errorInfo.reset();
result = parser.parseNext(&errorInfo);
assert(-2 == result);
assert("Inputs" == errorInfo.source());
assert(errorInfo.isError());
assert(4 == errorInfo.lineNumber());
assert(3 == errorInfo.columnNumber());
assert("Bad input character" == errorInfo.message());
If the errorInfo
object is not reset, calling parseNext
becomes a no-op: result = parser.parseNext(&errorInfo);
assert(-2 == result);
assert(errorInfo.isError());
assert(4 == errorInfo.lineNumber());
assert(3 == errorInfo.columnNumber());
assert("Bad input character" == errorInfo.message());
After calling reset
, the next call to parseNext
produces a different error message: errorInfo.reset();
result = parser.parseNext(&errorInfo);
assert(-2 == result);
assert(errorInfo.isError());
assert(5 == errorInfo.lineNumber());
assert(6 == errorInfo.columnNumber());
assert("Value is not between 0 and 100" == errorInfo.message());
The last line of the file contains two problems: a long line, which would produce a warning, and a range error, which would produce an error. The warning message is overwritten by the error message because the error has a higher severity. Therefore, on return from parseNext
, only the error message is stored in errorInfo
and the warning is lost: errorInfo.reset();
result = parser.parseNext(&errorInfo);
assert(-2 == result);
assert(errorInfo.isError());
assert(6 == errorInfo.lineNumber());
assert(18 == errorInfo.columnNumber());
assert("Value is not between 0 and 100" == errorInfo.message());
Writing the errorInfo
object to a log or file will produce a readable error message: bsl::cerr << errorInfo << bsl::endl;
The resulting message to standard error looks as follows: Inputs:6.18: Error: Value is not between 0 and 100
Finally, we reach the end of the input stream and can compute our average. errorInfo.reset();
result = parser.parseNext(&errorInfo);
assert(-1 == result);
assert(errorInfo.isNoError());
int average = sum / numValues;
assert(11 == average);
return 0;
}