【问题标题】:Parsing text file with symbol用符号解析文本文件
【发布时间】:2022-01-26 12:28:40
【问题描述】:

我无法解析 .txt 文件中的所有文本 但是当我运行我的代码时,我没有得到我想要的:(

我的代码:

int main()
{
  /* inside "logMsg.txt"
  1/ [111]{1}(text line from 111);
  2/ [222]{2}(text line from 222);
  3/ [333]{3}(text line from 333);
  */

  ifstream textfile("logMsg.txt");

  string log_line;
  string log_time;
  string log_type;
  string log_comment;

  // Get line number
  getline(textfile, log_line, '/');

  // Get Time by unix
  getline(textfile, log_time, '[');
  getline(textfile, log_time, ']');

  // Get type of the function
  getline(textfile, log_type, '{');
  getline(textfile, log_type, '}');

  // Get comment from data_
  getline(textfile, log_comment, '(');
  getline(textfile, log_comment, ')');

  cout << "Line Of the log: " << log_line << "\n";
  cout << "Type of the log: " << log_type << "\n";
  cout << "Time of the log: " << log_time << "\n";
  cout << "Comment of the log :" << log_comment << "\n";
}

控制台:

Line Of the log: 1
Type of the log: 1
Time of the log: 111
Comment of the log :text line from 111

我希望解析器读取所有行,但结果完全不同

我不明白我做错了什么,我试图得到以下结果:

Line Of the log: 1, 2, 3,
Type of the log: 1, 2, 3,
Time of the log: 111, 222, 333,
Comment of the log: text line from 111, text line from 222, text line from 333,

请帮忙:(

【问题讨论】:

  • getline 在调用时清除字符串。如果你调用它两次,你只剩下第二次读到的内容,即getline(textfile, log_time, ']'); 给你 ] 之前的最后一个字符,即 1。我还建议将获取行与解析它们分开。

标签: c++ parsing dialog


【解决方案1】:

我建议为此使用正则表达式:

  • 下面的示例只是使用给定的模式解析每一行。您可能需要对其进行修改,例如,允许在某些字段周围使用空格。

[Demo]

#include <fmt/core.h>
#include <iostream>  // cout
#include <regex>
#include <sstream>  // istringstream
#include <string>  // getline

int main() {
    const std::string input{"1/ [111]{1}(text line from 111);\n2/ [222]{2}(text line from 222);\n3/ [333]{3}(text line from 333);"};
    std::istringstream iss{input};

    std::string line{};
    while (std::getline(iss, line))
    {
        const std::regex pattern{R"(([\d+])/\s*\[(\d+)\]\{(\d+)\}\(([^)]+)\);)"};
        std::smatch matches{};
        if (std::regex_match(line, matches, pattern))
        {
            std::cout << fmt::format ("Line: {}, type: {}, time: {}, comment: {}\n",
                matches[1].str(), matches[2].str(), matches[3].str(), matches[4].str());
        }
    }
}

// Outputs:
//
//   Line: 1, type: 111, time: 1, comment: text line from 111
//   Line: 2, type: 222, time: 2, comment: text line from 222
//   Line: 3, type: 333, time: 3, comment: text line from 333
  • 如果您想要不同的输出(例如,您似乎在问题中建议,打印所有行号,然后是所有时间,等等),您可能需要使用 std::vectors 来保存该信息,并且最后打印出来。

[Demo]

#include <algorithm>  // copy
#include <iostream>  // cout
#include <iterator>  // ostream_iterator
#include <regex>
#include <sstream>  // istringstream
#include <string>  // getline

int main() {
    const std::string input{"1/ [111]{1}(text line from 111);\n2/ [222]{2}(text line from 222);\n3/ [333]{3}(text line from 333);"};
    std::istringstream iss{input};

    std::vector<std::string> numbers{};
    std::vector<std::string> types{};
    std::vector<std::string> times{};
    std::vector<std::string> comments{};
    std::string line{};
    while (std::getline(iss, line))
    {
        const std::regex pattern{R"(([\d+])/\s*\[(\d+)\]\{(\d+)\}\(([^)]+)\);)"};
        std::smatch matches{};
        if (std::regex_match(line, matches, pattern))
        {
            numbers.push_back(matches[1]);
            types.push_back(matches[2]);
            times.push_back(matches[3]);
            comments.push_back(matches[4]);
        }
    }
    std::cout << "Line of the log: ";
    std::copy(numbers.cbegin(), numbers.cend(), std::ostream_iterator<std::string>(std::cout, ", "));
    std::cout << "\n";
    std::cout << "Type of the log: ";
    std::copy(types.cbegin(), types.cend(), std::ostream_iterator<std::string>(std::cout, ", "));
    std::cout << "\n";
    std::cout << "Time of the log: ";
    std::copy(times.cbegin(), times.cend(), std::ostream_iterator<std::string>(std::cout, ", "));
    std::cout << "\n";
    std::cout << "Comment of the log: ";
    std::copy(comments.cbegin(), comments.cend(), std::ostream_iterator<std::string>(std::cout, ", "));
    std::cout << "\n";
}

// Outputs:
//
//   Line of the log: 1, 2, 3, 
//   Type of the log: 111, 222, 333, 
//   Time of the log: 1, 2, 3, 
//   Comment of the log: text line from 111, text line from 222, text line from 333, 
  • 虽然您最好使用structs 中的单个std::vector,并为数字、类型和时间使用适当的类型。在这种情况下,您需要将字符串从matches 转换为structints(您可以使用std::stoi 来实现)。

[Demo]

struct LineInfo
{
    int number{};
    int type{};
    int time{};
    std::string comment{};
};
std::vector<LineInfo> line_infos{};

【讨论】:

    【解决方案2】:

    阅读您的源代码后,我假设您正在学习 C++ iostream 功能。正则表达式和其他东西在开始时可能仍然太复杂。因此,让我们尝试使用正常的 iostream 功能来生存。

    基本上你应该知道有两种主要的输入类型:

    • 格式化输入
    • 无格式输入

    要获得概览,请阅读 CPP 参考中关于 here 的内容。

    您已经了解格式化的 IO,因为您已经在使用像 std::cout &lt;&lt; "Hello" &lt;&lt; '\n';std::cin &gt;&gt; word &gt;&gt; count 这样的语句。格式化输入函数从流中读取字符,解释它们(格式化)并将它们转换为目标类型。例如,如果你写int number; std::cin &gt;&gt; number;,然后按1、2、3,软件将读取字符'1'、'2'和'3',然后将其转换为整数。

    在幕后还有一点点,但这个解释现在很好。请注意,IO 操作符,如提取操作符&gt;&gt; 总是返回调用它们的流。这允许链接此类语句。 std::cin &gt;&gt; word &gt;&gt; count 的示例。首先执行std::cin &gt;&gt; word。将读取空格之前的所有字符,然后将其转换为字符串并存储在word 中。 &gt;&gt;operation 将返回调用它的流,在我们的例子中:std::cin。所以,在读完这个词之后,我们现在有了声明std::cin &gt;&gt; count。然后将读取计数。分隔空白将被忽略。这就是它基本工作的原因和方式。

    另一个重要的属性是&gt;&gt; 操作会跳过任何潜在的空白(包括换行符'\n')然后开始转换。如果读取的字符不能再转换为目标变量,它将停止从输入流中读取和使用字符。这又包括空白。

    如果流中有1/ [111]{1}(text line from 111); 并写入stream &gt;&gt; number;,那么它将读取字符1 并开始转换为整数。然后它会看到以下 '/' 字符。它停止读取并将“1”转换为数字 1,但是,它不会从流中提取“/”。它仍然存在并且可以并且必须阅读。因此,如果我们定义一个char 变量c1 并写入stream &gt;&gt; number &gt;&gt; c1,那么首先将数字读取到number,然后将“/”读取到c1。空格将被忽略。

    通过使用这种机制,您可以像这样读取字符串的第一部分:

    #include <iostream>
    #include <string>
    #include <sstream>
    #include <fstream>
    #include <iomanip>
    
    std::istringstream test{ R"(1/[111]{1}(text line from 111);)" };
    
    int main() {
        int line, time, type;
        char c1,c2,c3,c4,c5,c6;
        std::string comment;
    
        test >> line >> c1 >> c2 >> time >> c3 >> c4 >> type >> c5 >> c6;
    
        return 0;
    }
    

    (cx 变量只是虚拟变量)

    变量将是:

    这符合预期。空间不会以任何方式打扰这里。

    现在是下一部分,注释,即字符串中的text line from 111。如您所见,它包含空格。而且由于格式化输入函数在空白处停止转换,我们不能使用像&gt;&gt;这样的格式化输入函数将完整的文本读入一个字符串变量。

    现在我们需要使用像std::getline 这样的无格式输入函数。这将读取所有字符,直到给定分隔符,并将其存储在 std::string 变量中。分隔符通常是 '\n',因此std::getline 将读取一个完整的行,直到换行符 '\n'。但我们也可以指定不同的分隔符,例如 ')' 或其他。

    请记住,格式化输入函数会返回对给定字符串的引用。所以,test &gt;&gt; line &gt;&gt; c1 &gt;&gt; c2 &gt;&gt; time &gt;&gt; c3 &gt;&gt; c4 &gt;&gt; type &gt;&gt; c5 &gt;&gt; c6 将产生流test。由于std::getline 需要一个流作为第一个参数,我们可以将完整的长输入语句传递到std::getline 并写入:

    #include <iostream>
    #include <string>
    #include <sstream>
    #include <fstream>
    #include <iomanip>
    
    std::istringstream test{ R"(1/[111]{1}(text line from 111);)" };
    
    int main() {
        int line, time, type;
        char c1,c2,c3,c4,c5,c6;
        std::string comment;
    
        std::getline(test >> line >> c1 >> c2 >> time >> c3 >> c4 >> type >> c5 >> c6, comment, ')');
    
        return 0;
    }
    

    结果是

    你猜怎么着? std::getline 也返回流。知道了以上,我们就可以得出一条线的最终解决方案了。

    #include <iostream>
    #include <string>
    #include <sstream>
    #include <fstream>
    #include <iomanip>
    
    std::istringstream test{ R"(1/[111]{1}(text line from 111);)" };
    
    int main() {
        int line, time, type;
        char c1,c2,c3,c4,c5,c6, c7;
        std::string comment;
    
        std::getline(test >> line >> c1 >> c2 >> time >> c3 >> c4 >> type >> c5 >> c6, comment, ')') >> c7;
    
        return 0;
    }
    

    下一步:检查 IO 操作是否成功。

    我们应该经常检查任何 IO 操作的结果。但是怎么做?我已经解释过 IO 操作返回对流的引用。我们可以这样写:

    if (std::getline(test >> line >> c1 >> c2 >> time >> c3 >> c4 >> type >> c5 >> c6, comment, ')') >> c7) {
    ...
    }
    

    但是怎么做呢?正如所说。输入函数将返回对流的引用,然后我们离开了:if (test)。这是可行的,因为流的 bool 运算符被覆盖(请参阅here)。这个布尔运算符会告诉我们是否有错误。

    所以,要读取和输出所有数据,我们可以这样写:

    #include <iostream>
    #include <string>
    #include <sstream>
    #include <fstream>
    #include <iomanip>
    
    std::istringstream test{ R"(1/[111]{1}(text line from 111);
      2/ [222]{2}(text line from 222);
      3/ [333]{3}(text line from 333);)" };
    
    int main() {
        int line, time, type;
        char c1,c2,c3,c4,c5,c6, c7;
        std::string comment;
        while (std::getline(test >> line >> c1 >> c2 >> time >> c3 >> c4 >> type >> c5 >> c6, comment, ')') >> c7)
           std::cout << "Line: " << line << "\tTime: " << time << "\tType: " << type << "\tComment: " << comment << '\n';
    }
    

    这表明它基本上是如何工作的。


    但是,您需要不同的输出格式。这不是水平的,而是垂直的。因此,我们需要将我们读取的内容存储在某个动态容器中。我希望你知道std::vector。如果没有,请告诉我,我也会在这里为您提供帮助。

    所以我们不会读取变量,输出它们然后忘记它,而是存储它们并稍后使用它们。

    您可能听说过 C++ 是一种所谓的面向对象语言。在这里,我们将处理这些数据的方法中的数据放入classstruct 中。

    这可能看起来像:

    struct LogLine {
        int line{};
        long time{};
        int type{};
        std::string comment{};
    };
    

    这是数据部分。现在,我们需要这些数据的方法。我们唯一需要的就是输入。为此,我们可以将提取操作&gt;&gt; 添加到此结构中。语法是:

    std::istream& operator >> (std::istream& is, LineInfo& l) { ....
    

    结合我们上面学到的东西,我们现在可以写:

    #include <iostream>
    #include <string>
    #include <sstream>
    #include <fstream>
    #include <iomanip>
    
    std::istringstream test{ R"(1/[111]{1}(text line from 111);
      2/ [222]{2}(text line from 222);
      3/ [333]{3}(text line from 333);)" };
    
    struct LogLine {
        // Data
        int line{};
        long time{};
        int type{};
        std::string comment{};
    
        // Methods
        friend std::istream& operator >> (std::istream& is, LogLine& l) {
            char c1, c2, c3, c4, c5, c6, c7;
            return std::getline(is >> l.line >> c1 >> c2 >> l.time >> c3 >> c4 >> l.type >> c5 >> c6 >> std::ws, l.comment, ')') >> c7;
        }
        friend std::ostream& operator << (std::ostream& os, const LogLine& l) {
            return os << "Line: " << l.line << "\tTime: " << l.time << "\tType: " << l.type << "\tComment: " << l.comment;
        }
    };
    

    这要好得多。

    现在我们可以为这些数据创建一个std::vector,更好的是,将它封装在一个额外的类中。

    例如:

    #include <iostream>
    #include <string>
    #include <sstream>
    #include <fstream>
    #include <iomanip>
    #include <vector>
    
    std::istringstream test{ R"(1/[111]{1}(text line from 111);
      2/ [222]{2}(text line from 222);
      3/ [333]{3}(text line from 333);)" };
    
    struct LogLine {
        // Data
        int line{};
        long time{};
        int type{};
        std::string comment{};
    
        // Methods
        friend std::istream& operator >> (std::istream& is, LogLine& l) {
            char c1, c2, c3, c4, c5, c6, c7;
            return std::getline(is >> l.line >> c1 >> c2 >> l.time >> c3 >> c4 >> l.type >> c5 >> c6 >> std::ws, l.comment, ')') >> c7;
        }
        friend std::ostream& operator << (std::ostream& os, const LogLine& l) {
            char c1, c2, c3, c4, c5, c6, c7;
            return os << "Line: " << l.line << "\tTime: " << l.time << "\tType: " << l.type << "\tComment: " << l.comment;
        }
    };
    
    
    
    struct Log {
        // Data
        std::vector<LogLine> logLines;
        // Methods
        friend std::istream& operator >> (std::istream& is, Log& l) {
            l.logLines.clear(); LogLine tmp{};
            while (is >> tmp) l.logLines.push_back(tmp);
            return is;
        }
        friend std::ostream& operator << (std::ostream& os, const Log& l) {
            os << "\nLine Of the log : "; for (const LogLine& ll : l.logLines) os << ll.line << ", ";
            os << "\nType of the log : "; for (const LogLine& ll : l.logLines) os << ll.type << ", ";
            os << "\nTime of the log : "; for (const LogLine& ll : l.logLines) os << ll.time << ", ";
            os << "\nComment of the log : "; for (const LogLine& ll : l.logLines) os << ll.comment << ", ";
            return os;
        }
    };
    
    int main() {
        Log log;
        
        // Read and parse all data
        test >> log;
    
        // Show debug output
        std::cout << log;
    }
    

    这为您提供了惯用的正确解决方案。

    如您所见,我们将一个大问题分解为许多小问题。首先解决输入部分,然后将所有内容封装到结构中。

    结果是 main 中的一行用于读取和解析整个文件。

    如果您有任何问题,我很乐意回答。

    【讨论】:

      猜你喜欢
      • 2013-04-19
      • 2016-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-07
      • 1970-01-01
      • 2019-07-03
      • 2011-11-20
      相关资源
      最近更新 更多