std::out_of_range thrown when parsing a text file

I have the following code to read a text file.

const string FILENAME = PACKAGES_DIR + pname;
  //the arguments to ifstream is a cstring and hence the conversion must be made
  ifstream freader;
  freader.open(FILENAME.c_str(),ios::in);
  if(freader.is_open())
  {
    while(freader.good())
    {
      string line;
      getline(freader,line);
      cout<<line<<endl;
      if(line.find("PackageId:"))
      {
        cout<<line.substr(11)<<endl;
      }
      else if(line.find("Name:"))
      {
        cout<<line.substr(5)<<endl;
      }
      else if(line.find("Version:"))
      {
        cout<<line.find(8)<<endl;
      }
      else
      {
        cout<<line<<endl;
      }

    }
  }

The contents of the text file in question is

PackageId:994
Name:basket
Version:1.80-1
Deps:kdebase-runtime,libc0.1,libc0.1-udeb,libc6,libc6-udeb,libc6.1,libc6.1-udeb,libgcc1,libgpg-error0,libgpgme11,libkdecore5,libkdeui5,libkfile4,libkio5,libkparts4,libkutils4,libphonon4,libqimageblitz4,libqt4-dbus,libqt4-network,libqt4-qt3support,libqt4-svg,libqt4-xml,libqtcore4,libqtgui4,libstdc++6,libunwind7,libx11-6,phonon

The output that I get is

PackageId:994
geId:994
Name:basket

Version:1.80-1
0-1
Deps:kdebase-runtime,libc0.1,libc0.1-udeb,libc6,libc6-udeb,libc6.1,libc6.1-udeb,libgcc1,libgpg-error0,libgpgme11,libkdecore5,libkdeui5,libkfile4,libkio5,libkparts4,libkutils4,libphonon4,libqimageblitz4,libqt4-dbus,libqt4-network,libqt4-qt3support,libqt4-svg,libqt4-xml,libqtcore4,libqtgui4,libstdc++6,libunwind7,libx11-6,phonon
e-runtime,libc0.1,libc0.1-udeb,libc6,libc6-udeb,libc6.1,libc6.1-udeb,libgcc1,libgpg-error0,libgpgme11,libkdecore5,libkdeui5,libkfile4,libkio5,libkparts4,libkutils4,libphonon4,libqimageblitz4,libqt4-dbus,libqt4-network,libqt4-qt3support,libqt4-svg,libqt4-xml,libqtcore4,libqtgui4,libstdc++6,libunwind7,libx11-6,phonon

terminate called after throwing an instance of 'std::out_of_range'
  what():  basic_string::substr

The output that I wanted is:

PackageId:994
994
Name:basket
basket
Version:1.80-1
1.80-1
...

What have I done wrong?

Answers

Change

      else if(line.find("Version:"))
      {
        cout<<line.find(8)<<endl; //<--------------------
      }

To

      else if(line.find("Version:"))
      {
        cout<<line.substr(8)<<endl; //<----------------------
      }
Posted on by Jonathan M

Check the exception thrown, it clearly tells you what is wrong.
The what() of the exception thrown prints out:

what(): basic_string::substr

That tells you,
Exception is thrown by substr

See the documentation of string::substr:

string substr ( size_t pos = 0, size_t n = npos ) const;

Generate substring

Returns a string object with its contents initialized to a substring of the current object.

This substring is the character sequence that starts at character position pos and has a length of n characters.

Parameters

  • pos     Position of a character in the current string object to be used as starting character for the substring. If the position passed is past the end of the string, an out_of_range exception is thrown.

  • n     Length of the substring. If this value would make the substring to span past the end of the current string content, only

those characters until the end of the string are used. npos is a static member constant value with the greatest possible value for an element of type size_t, therefore, when this value is used, all the characters between pos and the end of the string are used as the initialization substring.

Posted on by Alok Save

The first problem that sticks out is that you try to read a line and use that line without checking if the read is successful. Instead of checking if the file is good in the while condition, you should change your loop to something like this:

string line;
while(getline(freader,line))
{
    // now you can safely process line
}

Posted on by Benjamin Lindley

Problem 1

while .good or while !.eof is almost always wrong. Throw away whatever book told you to do that, and do this instead.

In this case, the changed code looks a bit like this:

const string FILENAME = PACKAGES_DIR + pname;
//the arguments to ifstream is a cstring and hence the conversion must be made
ifstream freader(FILENAME.c_str(), ios::in);
if (freader) {
   string line;
   while (getline(freader,line)) {  // <-----
      cout << line << endl;

      if (line.find("PackageId:"))
         cout << line.substr(11) << endl;
      else if (line.find("Name:"))
         cout << line.substr(5) << endl;
      else if (line.find("Version:"))
         cout << line.find(8) << endl;
      else
         cout << line << endl;
   }
}


Problem 2

You're not using std::string::find correctly.

line.find("PackageId:") returns either "the position of the first occurrence in the string of the searched content", or the member value npos if the match is not found.

This combined with not performing bounds checks on the first parameter to std::string::substr is causing issues with your strings.

Instead, write:

if (line.find("PackageId:") != std::string::npos)


Problem 3

cout<<line.find(8)<<endl; should say substr, not find.


Your code with some of the above fixed:

const string FILENAME = PACKAGES_DIR + pname;
//the arguments to ifstream is a cstring and hence the conversion must be made
ifstream freader(FILENAME.c_str(), ios::in);
if (freader) {
   string line;
   while (getline(freader,line)) {  // <-----
      cout << line << endl;

      if (line.find("PackageId:")    != std::string::npos && line.size() > 11)
         cout << line.substr(11) << endl;
      else if (line.find("Name:")    != std::string::npos && line.size() > 5)
         cout << line.substr(5) << endl;
      else if (line.find("Version:") != std::string::npos && line.size() > 8)
         cout << line.substr(8) << endl;
      else
         cout << line << endl;
   }
}
Posted on by Lightness Races in Orbit