std :: string操作:空格,“newline escapes'\'”和注释#

时间:2010-05-22 14:49:35

标签: c++ algorithm string

有点在这里寻求肯定。我有一些手工编写的代码,我并不害羞地说我很自豪,它会读取文件,删除前导空格,处理换行符'\'并删除以#开头的注释。它还会删除所有空行(也是仅空白行)。有什么想法/建议?我可以用std :: runtime_errors替换一些std :: cout ......但这不是优先考虑的事情:)

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();
}

一些额外的信息:传递给ifstream的第一个JDBC缓冲区包含文件名,std :: stringinsuffer是一个类数据成员,因此是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 个答案:

答案 0 :(得分:9)

绝对放弃匈牙利的记谱法。

答案 1 :(得分:6)

这还不错,但我认为你将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)));
}

答案 2 :(得分:4)

我对修改参数的方法并不重视。为什么不返回string而不是修改输入参数?例如:

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

我个人认为这澄清了意图,并使该方法的作用更加明显。

答案 3 :(得分:4)

一些评论:

  • 另一个答案(来自我的+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 :(得分:1)

我会考虑使用boost :: regex代码替换所有处理代码(几乎所有你编写的代码)。

答案 5 :(得分:1)

一些评论:

  • 如果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();
    

答案 6 :(得分:0)

您可能希望查看Boost.String。它是一个简单的算法集合,用于处理流,特别是trim方法的特征:)

现在,关于评论本身:

不要费心去除匈牙利符号,如果是你的风格则使用它,但是你应该尝试改进方法和变量的名称。 processXXX绝对没有表明任何有用的东西......

从功能上讲,我担心你的假设:这里的主要问题是你不关心espace序列(例如\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

这是相当低效的(但我相信编译器的选择),但我相信它的行为是正确的,虽然它没有经过测试,所以请耐心等待。我主要关注的是解决功能问题,我所遵循的命名惯例与你的不同,但我认为它不重要。

答案 7 :(得分:0)

我想指出一个小而甜的版本,它缺少\支持但跳过空白行和注释。 (请注意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"