BDE 4.14.0 Production release
Loading...
Searching...
No Matches
balxml_errorinfo

Detailed Description

Outline

Purpose

Provide common error information for XML components.

Classes

See also
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

This section illustrates intended use of this component.

Example 1: Basic 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 <sstream>
#include <cstdlib>
#include <cstring>
#include <climits>
class PercentParser {
// Parse a document stream consisting of a sequence of integral
// percentages (0 to 100) in decimal text format, one per line.
bsl::istream *d_input; // Input document stream
bsl::string d_docName; // Document name
int d_line; // Current line number
public:
PercentParser(bsl::istream *input,
const bsl::string& docName = "INPUT");
// Construct a parser to parse the data in the specified 'input'
// stream having the (optional) specified 'docName'. A valid
// 'input' stream contains a sequence of integers in the range 0
// to 100, one per line, in decimal text format. Each line may
// contain leading but not trailing tabs and spaces. Characters
// after the 20th character on each line are ignored and will
// generate a warning.
int parseNext(balxml::ErrorInfo *errorInfo);
// Read and parse the next percentage in the input stream and
// return the percentage or -1 on eof or -2 on error. Set the
// value of the specified 'errorInfo' structure on error or
// warning and leave it unchanged otherwise. Do nothing and
// return -2 if 'errorInfo->severity()' >= 'BAEXML_ERROR'.
};
Definition balxml_errorinfo.h:353
Definition bslstl_string.h:1281

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()) {
// Don't advance if errorInfo shows a previous error.
return -2;
}
bool isAnyError() const
Definition balxml_errorinfo.h:548

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';
// Skip empty lines empty line
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()) {
// 20 characters read without encountering newline.
// Warn about long line and discard rest of line.
d_line, len, d_docName,
"Text after 20th column was discarded");
d_input->clear();
d_input->ignore(INT_MAX, '\n');
}
@ BAEXML_WARNING
Definition balxml_errorinfo.h:377
void setError(Severity severity, int lineNumber, int columnNumber, const bsl::string_view &source, const bsl::string_view &errorMsg)

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()) {
// Encountered eof before any other characters.
return -1;
}
// Skip leading whitespace
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) {
// Conversion did not consume rest of buffer.
d_line, endColumn + 1, d_docName,
"Bad input character");
return -2;
} else if (result < 0 || 100 < result) {
// Range error.
d_line, startColumn + 1, d_docName,
"Value is not between 0 and 100");
return -2;
}
@ BAEXML_ERROR
Definition balxml_errorinfo.h:378

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.

return result;
}

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" // OK
" 30\n" // Warning ('0' truncated)
" \n" // Skipped: empty line
"99x\n" // Error: bad character
" 101\n" // Error: out of range
" 1010\n"; // Out-of-range overrides warning

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;
Definition bslstl_istringstream.h:176

Any error in parsing will be stored in the errorInfo object. When first constructed, it has a severity of BAEXML_NO_ERROR.

balxml::ErrorInfo errorInfo;
assert(errorInfo.isNoError());
bool isNoError() const
Definition balxml_errorinfo.h:524

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); // Truncated at 20th column
assert(errorInfo.isWarning());
assert(2 == errorInfo.lineNumber());
assert(20 == errorInfo.columnNumber());
assert("Text after 20th column was discarded" == errorInfo.message());
sum += result;
++numValues;
bool isWarning() const
Definition balxml_errorinfo.h:530
int columnNumber() const
Definition balxml_errorinfo.h:560
int lineNumber() const
Definition balxml_errorinfo.h:554
const bsl::string & message() const
Return the string describing the error or warning.
Definition balxml_errorinfo.h:572

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());
const bsl::string & source() const
Return the string that identifies the document being parsed.
Definition balxml_errorinfo.h:566
bool isError() const
Definition balxml_errorinfo.h:536

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); // (20 + 3) / 2
return 0;
}