Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component balxml_errorinfo
[Package balxml]

Provide common error information for XML components. More...

Namespaces

namespace  balxml

Detailed Description

Outline
Purpose:
Provide common error information for XML components.
Classes:
balxml::ErrorInfo details of XML parsing errors
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 {
      // 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'.
  };
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;
      }
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.
              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()) {
              // 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.
          errorInfo->setError(balxml::ErrorInfo::BAEXML_ERROR,
                              d_line, endColumn + 1, d_docName,
                              "Bad input character");
          return -2;
      } else if (result < 0 || 100 < result) {
          // Range error.
          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.
      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;
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());
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;
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);  // (20 + 3) / 2

      return 0;
  }