需要使用StreamReader.ReadLine()获取行终止符

时间:2009-03-20 20:07:20

标签: c# readline streamreader newline

我写了一个C#程序来读取Excel .xls / .xlsx文件并输出到CSV和Unicode文本。我写了一个单独的程序来删除空白记录。这是通过用StreamReader.ReadLine()读取每一行来完成的,然后通过字符串逐字逐句,如果它包含所有逗号(对于CSV)或所有选项卡(对于Unicode文本),则不将该行写入输出。

当Excel文件在单元格内包含嵌入的换行符(\ x0A)时,会出现问题。我将XLS更改为CSV转换器以找到这些新行(因为它逐个单元格)并将它们写为\ x0A,而普通行只使用StreamWriter.WriteLine()。

问题出现在单独的程序中以删除空白记录。当我使用StreamReader.ReadLine()读入时,根据定义它只返回带有行的字符串,而不是终止符。由于嵌入的换行符显示为两个单独的行,我无法分辨哪个是完整记录,哪个是嵌入式换行符,当我将它们写入最终文件时。

我甚至不确定我能读到\ x0A,因为输入上的所有内容都注册为'\ n'。我可以逐字逐句,但这会破坏我删除空行的逻辑。

5 个答案:

答案 0 :(得分:13)

我建议您将架构更改为更像编译器中的解析器。

你想要创建一个返回标记序列的词法分析器,然后创建一个解析标记序列并使用它们填充它们的解析器。

在您的情况下,令牌将是:

  1. 列数据
  2. 逗号
  3. 行尾
  4. 您可以将'\ n'('\ x0a')视为嵌入的新行,因此将其作为列数据令牌的一部分包含在内。 '\ r \ n'将构成行尾令牌。

    这有以下优点:

    1. 只对数据进行1次传递
    2. 仅存储最多1行数据
    3. 尽可能多地重用内存(对于字符串生成器和列表)
    4. 如果您的要求发生变化,很容易改变
    5. 以下是Lexer的样子:

      免责声明:我甚至没有编译,更不用说测试这段代码了,所以你需要清理它并确保它有效。

      enum TokenType
      {
          ColumnData,
          Comma,
          LineTerminator
      }
      
      class Token
      {
          public TokenType Type { get; private set;}
          public string Data { get; private set;}
      
          public Token(TokenType type)
          {
              Type = type;
          }
      
          public Token(TokenType type, string data)
          {
              Type = type;
              Data = data;
          }
      }
      
      private  IEnumerable<Token> GetTokens(TextReader s)
      {
         var builder = new StringBuilder();
      
         while (s.Peek() >= 0)
         {
             var c = (char)s.Read();
             switch (c)
             {
                 case ',':
                 {
                     if (builder.Length > 0)
                     {
                         yield return new Token(TokenType.ColumnData, ExtractText(builder));
                     }
                     yield return new Token(TokenType.Comma);
                     break;
                 }
                 case '\r':
                 {
                      var next = s.Peek();
                      if (next == '\n')
                      {
                          s.Read();
                      }
      
                      if (builder.Length > 0)
                      {
                          yield return new Token(TokenType.ColumnData, ExtractText(builder));
                      }
                      yield return new Token(TokenType.LineTerminator);
                      break;
                 }
                 default:
                     builder.Append(c);
                     break;
             }
      
         }
      
         s.Read();
      
         if (builder.Length > 0)
         {
             yield return new Token(TokenType.ColumnData, ExtractText(builder));
         }
      }
      
      private string ExtractText(StringBuilder b)
      {
          var ret = b.ToString();
          b.Remove(0, b.Length);
          return ret;
      }
      

      您的“解析器”代码将如下所示:

      public void ConvertXLS(TextReader s)
      {
          var columnData = new List<string>();
          bool lastWasColumnData = false;
          bool seenAnyData = false;
      
          foreach (var token in GetTokens(s))
          {
              switch (token.Type)
              {
                  case TokenType.ColumnData:
                  {
                       seenAnyData = true;
                       if (lastWasColumnData)
                       {
                           //TODO: do some error reporting
                       }
                       else
                       {
                           lastWasColumnData = true;
                           columnData.Add(token.Data);
                       }
                       break;
                  }
                  case TokenType.Comma:
                  {
                      if (!lastWasColumnData)
                      {
                          columnData.Add(null);
                      }
                      lastWasColumnData = false;
                      break;
                  }
                  case TokenType.LineTerminator:
                  {
                      if (seenAnyData)
                      {
                          OutputLine(lastWasColumnData);
                      }
                      seenAnyData = false;
                      lastWasColumnData = false;
                      columnData.Clear();
                  }
              }
          }
      
          if (seenAnyData)
          {
              OutputLine(columnData);
          }
      }
      

答案 1 :(得分:4)

您无法更改StreamReader以返回行终止符,也无法更改用于行终止的内容。

关于逃避你正在做什么的问题,我并不完全清楚这个问题,特别是在“将它们写成\ x0A”方面。该文件的示例可能会有所帮助。

听起来你可能需要逐个字符地工作,或者可能首先加载整个文件并进行全局替换,例如

x.Replace("\r\n", "\u0000") // Or some other unused character
 .Replace("\n", "\\x0A") // Or whatever escaping you need
 .Replace("\u0000", "\r\n") // Replace the real line breaks

我确信你可以用正则表达式来做到这一点,它可能会更有效率,但我发现很容易理解的方法:)虽然有一些黑客不得不做全局替换 - 希望有更多我们将提供更好的解决方案。

答案 2 :(得分:1)

基本上,Excel中的硬回车(shift + enter或alt + enter,我记不住了)在我用来编写CSV的默认编码中放置一个等同于\ x0A的换行符。当我写入CSV时,我使用StreamWriter.WriteLine(),它输出行加上换行符(我相信是\ r \ n)。

CSV很好,并且确切地说出Excel将如何保存它,问题是当我将其读入空白记录移除器时,我正在使用ReadLine()来处理带有嵌入式换行符作为CRLF的记录。

以下是转换为CSV后的文件示例...

Reference,Name of Individual or Entity,Type,Name Type,Date of Birth,Place of Birth,Citizenship,Address,Additional Information,Listing Information,Control Date,Committees
1050,"Aziz Salih al-Numan
",Individual,Primary Name,1941 or 1945,An Nasiriyah,Iraqi,,Ba’th Party Regional Command Chairman; Former Governor of Karbala and An Najaf Former Minister of Agriculture and Agrarian Reform (1986-1987),Resolution 1483 (2003),6/27/2003,1518 (Iraq)
1050a,???? ???? ???????,Individual,Original script,1941 or 1945,An Nasiriyah,Iraqi,,Ba’th Party Regional Command Chairman; Former Governor of Karbala and An Najaf Former Minister of Agriculture and Agrarian Reform (1986-1987),Resolution 1483 (2003),6/27/2003,1518 (Iraq)

正如你所看到的,第一张唱片在al-Numan之后有一个嵌入的新线。当我使用ReadLine()时,我得到'1050,'Aziz Salih al-Numan',当我写出来时,WriteLine()以CRLF结束该行。我丢失了原来的行终止符。当我再次使用ReadLine()时,我得到的行以'1050a'开头。

我可以读取整个文件并替换它们,但之后我必须将它们替换回去。基本上我想做的是获取行终止符以确定它的\ x0a或CRLF,然后如果它的\ x0A,我将使用Write()并插入该终止符。

答案 3 :(得分:0)

我知道我这里的游戏有点晚了,但我遇到了同样的问题,而且我的解决方案比大多数情况要简单得多。

如果您能够确定易于进行的列数,因为第一行通常是列标题,您可以根据预期的列数检查列数。如果列数不等于预期的列数,则只需将当前行与先前不匹配的行连接起来。例如:

string sep = "\",\"";
int columnCount = 0;
while ((currentLine = sr.ReadLine()) != null)
{
    if (lineCount == 0)
    {
        lineData = inLine.Split(new string[] { sep }, StringSplitOptions.None);
        columnCount = lineData.length;
        ++lineCount;
        continue;
    }
    string thisLine = lastLine + currentLine;

    lineData = thisLine.Split(new string[] { sep }, StringSplitOptions.None);
    if (lineData.Length < columnCount)
    {
        lastLine += currentLine;
        continue;
    }
    else
    {
        lastLine = null;
    }
    ......

答案 4 :(得分:0)

非常感谢你的代码和其他一些我提出以下解决方案!我在底部添加了一个链接到我编写的一些代码,这些代码使用了本页面中的一些逻辑。我想我会在荣誉到期时给予荣誉!谢谢!

以下是我需要的解释: 试试这个,我写这个是因为我有一些非常大的'|'在某些列中包含\ r \ n的分隔文件,我需要使用\ r \ n作为行分隔符的结尾。我试图使用SSIS包导入一些文件,但由于我无法使用的文件中的一些损坏的数据。文件超过5 GB因此太大而无法打开并手动修复。我通过查看大量论坛找到了答案,以了解流如何工作,最终找到了一个解决方案,该解决方案读取文件中的每个字符并根据我添加到其中的定义吐出该行。这是在命令行应用程序中使用,完成帮助:)。我希望这可以帮助其他人,我没有找到其他任何地方的解决方案,尽管这些想法受到了这个论坛和其他人的启发。

https://stackoverflow.com/a/12640862/1582188