манипулирование std :: string: пробелы, "перевод строки" \ '"и комментарии # - PullRequest
1 голос
/ 22 мая 2010

Вид поиска подтверждения здесь. У меня есть некоторый рукописный код, которым я не стесняюсь сказать, которым я горжусь, который читает файл, удаляет начальные пробелы, обрабатывает экранирование новой строки '\' и удаляет комментарии, начинающиеся с #. Он также удаляет все пустые строки (также только пробельные). Есть мысли / рекомендации? Я мог бы заменить некоторые std :: cout на std :: runtime_errors ... но это не является приоритетом:)

const int RecipeReader::readRecipe()
{
    ifstream is_recipe(s_buffer.c_str());
    if (!is_recipe)
        cout << "unable to open file" << endl;
    while (getline(is_recipe, s_buffer))
    {
        // whitespace+comment
        removeLeadingWhitespace(s_buffer);
        processComment(s_buffer);
        // newline escapes + append all subsequent lines with '\'
        processNewlineEscapes(s_buffer, is_recipe);
        // store the real text line
        if (!s_buffer.empty())
            v_s_recipe.push_back(s_buffer);
        s_buffer.clear();
    }
    is_recipe.close();
    return 0;
}

void RecipeReader::processNewlineEscapes(string &s_string, ifstream &is_stream)
{
    string s_temp;
    size_t sz_index = s_string.find_first_of("\\");
    while (sz_index <= s_string.length())
    {
        if (getline(is_stream,s_temp))
        {
            removeLeadingWhitespace(s_temp);
            processComment(s_temp);
            s_string = s_string.substr(0,sz_index-1) + " " + s_temp;
        }
        else
            cout << "Error: newline escape '\' found at EOF" << endl;
        sz_index = s_string.find_first_of("\\");
    }
}

void RecipeReader::processComment(string &s_string)
{
    size_t sz_index = s_string.find_first_of("#");
    s_string = s_string.substr(0,sz_index);
}

void RecipeReader::removeLeadingWhitespace(string &s_string)
{
    const size_t sz_length = s_string.size();
    size_t sz_index = s_string.find_first_not_of(" \t");
    if (sz_index <= sz_length)
    s_string = s_string.substr(sz_index);
    else if ((sz_index > sz_length) && (sz_length != 0)) // "empty" lines with only whitespace
        s_string.clear();
}

Некоторая дополнительная информация: первый s_buffer, переданный в ifstream, содержит имя файла, std :: string s_buffer - член данных класса, как и std :: vector v_s_recipe. Любой комментарий приветствуется:)

ОБНОВЛЕНИЕ: ради того, чтобы не быть неблагодарным, вот моя замена, многофункциональная функция, которая делает то, что я хочу сейчас (будущее держит: скобки, возможно, кавычки ...):

void readRecipe(const std::string &filename)
{
    string buffer;
    string line;
    size_t index;
    ifstream file(filename.c_str());
    if (!file)
        throw runtime_error("Unable to open file.");

    while (getline(file, line))
    {
        // whitespace removal
        line.erase(0, line.find_first_not_of(" \t\r\n\v\f"));
        // comment removal TODO: store these for later output
        index = line.find_first_of("#");
        if (index != string::npos)
            line.erase(index, string::npos);
        // ignore empty buffer
        if (line.empty())
            continue;
        // process newline escapes
        index = line.find_first_of("\\");
        if (index != string::npos)
        {
            line.erase(index,string::npos); // ignore everything after '\'
            buffer += line;
            continue; // read next line
        }
        else // no newline escapes found
        {
            buffer += line;
            recipe.push_back(buffer);
            buffer.clear();
        }
    }
}

Ответы [ 8 ]

9 голосов
/ 22 мая 2010

Определенно отбросьте венгерские обозначения.

6 голосов
/ 22 мая 2010

Это не плохо, но я думаю, что вы думаете о std::basic_string<T> слишком много как строка и недостаточно как контейнер STL. Например:

void RecipeReader::removeLeadingWhitespace(string &s_string)
{
    s_string.erase(s_string.begin(), 
        std::find_if(s_string.begin(), s_string.end(), std::not1(isspace)));
}
4 голосов
/ 22 мая 2010

Несколько комментариев:

  • Как еще один ответ (+1 от меня) сказал - угробить венгерскую нотацию. Это действительно ничего не делает, но добавляет ненужный мусор в каждую строку. Кроме того, ifstream с префиксом is_ выглядит ужасно. is_ обычно обозначает логическое значение.
  • Обозначение функции с помощью processXXX дает очень мало информации о том, что она на самом деле делает. Используйте removeXXX, как вы сделали с функцией RemoveLeadingWhitespace.
  • Функция processComment выполняет ненужное копирование и назначение. Используйте s.erase(index, string::npos); (по умолчанию npos, но это более очевидно).
  • Непонятно, что делает ваша программа, но вы можете подумать о том, чтобы сохранить другой формат файла (например, html или xml), если вам нужно постобработать ваши файлы следующим образом. Это будет зависеть от цели.
  • Использование find_first_of('#') может дать вам некоторые ложные срабатывания. Если он присутствует в кавычках, это не обязательно указывает на комментарий. (Но опять же, это зависит от формата вашего файла)
  • использование find_first_of(c) с одним символом может быть упрощено до find(c).
  • Функция processNewlineEscapes дублирует некоторые функции из функции readRecipe. Вы можете подумать о рефакторинге примерно так:

-

string s_buffer;
string s_line;
while (getline(is_recipe, s_line)) {
  // Sanitize the raw line.
  removeLeadingWhitespace(s_line);
  removeComments(s_line);
  // Skip empty lines.
  if (s_line.empty()) continue;
  // Add the raw line to the buffer.
  s_buffer += s_line;
  // Collect buffer across all escaped lines.
  if (*s_line.rbegin() == '\\') continue;
  // This line is not escaped, now I can process the buffer.
  v_s_recipe.push_back(s_buffer);
  s_buffer.clear();
}
4 голосов
/ 22 мая 2010

Я не очень разбираюсь в методах, которые изменяют параметры. Почему бы не вернуть string s вместо изменения входных аргументов? Например:

string RecipeReader::processComment(const string &s)
{
    size_t index = s.find_first_of("#");
    return s_string.substr(0, index);
}

Я лично чувствую, что это проясняет намерение и делает более очевидным, что делает метод.

1 голос
/ 22 мая 2010

Несколько комментариев:

  • Если s_buffer содержит открываемое имя файла, оно должно иметь лучшее имя, например s_filename.
  • Переменная-член s_buffer не должна использоваться повторно для хранения временных данных от чтения файла. Локальная переменная в функции тоже подойдет, нет необходимости, чтобы буфер был переменной-членом.
  • Если нет необходимости хранить имя файла в качестве переменной-члена, его можно просто передать в качестве параметра readRecipe()

  • processNewlineEscapes() следует проверить, что найденный обратный слеш находится в конце строки перед добавлением следующей строки. В настоящий момент любой обратный слеш в любой позиции вызывает добавление следующей строки в положение обратного слэша. Также, если есть несколько обратных слешей, find_last_of(), вероятно, будет проще в использовании, чем find_first_of().

  • При проверке результата find_first_of() в processNewlineEscapes() и removeLeadingWhitespace() было бы лучше сравнить с string::npos, чтобы проверить, было ли что-либо найдено.

  • Логика в конце removeLeadingWhitespace() может быть упрощена:

    size_t sz_index = s_string.find_first_not_of(" \t");
    if (sz_index != s_string.npos)
       s_string = s_string.substr(sz_index);
    else // "empty" lines with only whitespace
       s_string.clear();
    
1 голос
/ 22 мая 2010

Я бы рассмотрел замену всего вашего кода обработки (почти всего, что вы написали) на код boost :: regex.

0 голосов
/ 10 ноября 2012

Я хочу отметить маленькую и приятную версию, в которой отсутствует поддержка \, но пропускаются пробелы и комментарии. (Обратите внимание на std::ws в вызове std::getline.

#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>

int main()
{
  std::stringstream input(
      "    # blub\n"
      "# foo bar\n"
      " foo# foo bar\n"
      "bar\n"
      );

  std::string line;
  while (std::getline(input >> std::ws, line)) {
    line.erase(std::find(line.begin(), line.end(), '#'), line.end());

    if (line.empty()) {
      continue;
    }

    std::cout << "line: \"" << line << "\"\n";
  }
}

Выход:

line: "foo"
line: "bar"
0 голосов
/ 22 мая 2010

Возможно, вы захотите взглянуть на Boost.String . Это простая коллекция алгоритмов для работы с потоками, в частности функции trim методы:)

Теперь перейдем к самому обзору:

Не беспокойтесь об удалении венгерской нотации, если это ваш стиль, тогда используйте ее, однако вы должны попытаться улучшить имена методов и переменных. processXXX определенно ничего не значит полезного ...

Функционально меня беспокоят ваши предположения: главная проблема здесь в том, что вам не нужны последовательности пробелов (\n использует, например, обратную косую черту), и вы не беспокоитесь о наличии строк символов: std::cout << "Process #" << pid << std::endl; приведет к неверной строке из-за вашей предварительной обработки "комментария"

Кроме того, поскольку вы удаляете комментарии перед обработкой экранирования новой строки:

i = 3; # comment \
         running comment

будет проанализирован как

i = 3; running comment

, что синтаксически неверно.

С точки зрения интерфейса: нет необходимости иметь здесь методы, являющиеся членами класса, вам не нужен экземпляр RecipeReader на самом деле ...

И, наконец, мне неловко, когда два потока читают из потока.

Моя маленькая мозоль: возвращение на const значение не служит никакой цели.

Вот моя собственная версия, так как я считаю, что показывать легче, чем обсуждать:

// header file

std::vector<std::string> readRecipe(const std::string& fileName);

std::string extractLine(std::ifstream& file);

std::pair<std:string,bool> removeNewlineEscape(const std::string& line);
std::string removeComment(const std::string& line);

// source file

#include <boost/algorithm/string.hpp>

std::vector<std::string> readRecipe(const std::string& fileName)
{
  std::vector<std::string> result;

  ifstream file(fileName.c_str());
  if (!file) std::cout << "Could not open: " << fileName << std::endl;

  std::string line = extractLine(file);
  while(!line.empty())
  {
    result.push_back(line);
    line = extractLine(file);
  } // looping on the lines

  return result;
} // readRecipe


std::string extractLine(std::ifstream& file)
{
  std::string line, buffer;
  while(getline(file, buffer))
  {
    std::pair<std::string,bool> r = removeNewlineEscape(buffer);
    line += boost::trim_left_copy(r.first); // remove leading whitespace
                                            // based on the current locale
    if (!r.second) break;
    line += " "; // as we append, we insert a whitespace
                 // in order unintended token concatenation
  }

  return removeComment(line);
} // extractLine

//< Returns the line, minus the '\' character
//<         if it was the last significant one
//< Returns a boolean indicating whether or not the line continue
//<         (true if it's necessary to concatenate with the next line)
std::pair<std:string,bool> removeNewlineEscape(const std::string& line)
{
  std::pair<std::string,bool> result;
  result.second = false;

  size_t pos = line.find_last_not_of(" \t");
  if (std::string::npos != pos && line[pos] == '\')
  {
    result.second = true;
    --pos; // we don't want to have this '\' character in the string
  }

  result.first = line.substr(0, pos);
  return result;
} // checkNewlineEscape

//< The main difficulty here is NOT to confuse a # inside a string
//< with a # signalling a comment
//< assuming strings are contained within "", let's roll
std::string removeComment(const std::string& line)
{
  size_t pos = line.find_first_of("\"#");
  while(std::string::npos != pos)
  {
    if (line[pos] == '"')
    {
      // We have detected the beginning of a string, we move pos to its end
      // beware of the tricky presence of a '\' right before '"'...
      pos = line.find_first_of("\"", pos+1);
      while (std::string::npos != pos && line[pos-1] == '\')
        pos = line.find_first_of("\"", pos+1);
    }
    else // line[pos] == '#'
    {
      // We have found the comment marker in a significant position
      break;
    }
    pos = line.find_first_of("\"#", pos+1);
  } // looking for comment marker

  return line.substr(0, pos);
} // removeComment

Это довольно неэффективно (но я доверяю компилятору для оптимизации), но я считаю, что он ведет себя правильно, хотя и не тестировался, поэтому возьмите его с крошкой соли. Я сосредоточился в основном на решении функциональных проблем, соглашение об именах, которому я следую, отличается от вашего, но я не думаю, что это должно иметь значение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...