C# 需要使用StreamReader.ReadLine()拾取行终止符

C# 需要使用StreamReader.ReadLine()拾取行终止符,c#,readline,streamreader,newline,C#,Readline,Streamreader,Newline,我编写了一个C#程序来读取Excel.xls/.xlsx文件并输出为CSV和Unicode文本。我编写了一个单独的程序来删除空白记录。这是通过使用StreamReader.ReadLine()读取每一行来完成的,然后逐字符遍历字符串,如果该行包含所有逗号(对于CSV)或所有制表符(对于Unicode文本),则不将该行写入输出 当Excel文件的单元格中包含嵌入的换行符(\x0A)时,就会出现此问题。我更改了XLS-to-CSV转换器以查找这些新行(因为它是逐单元格进行的),并将它们写入\x0A,

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

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

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


我甚至不确定是否可以读取\x0A,因为输入寄存器上的所有内容都是'\n'。我可以一个字符接一个字符,但这会破坏删除空行的逻辑。

您不能更改
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

我相信你可以用正则表达式来实现这一点,它可能会更有效,但我发现还有很长的路要走,更容易理解:)虽然需要进行全局替换,但这有点困难——希望有更多的信息,我们会想出更好的解决方案。

本质上,Excel中的硬返回(shift+enter或alt+enter,我不记得了)将与\x0A等效的换行符放入我用于编写CSV的默认编码中。当我写入CSV时,我使用StreamWriter.WriteLine(),它会输出一行加一个换行符(我认为是\r\n)

CSV是很好的,它能准确地看出Excel如何保存它,问题是当我把它读入空白记录删除器时,我使用的是RealLink(),它将用一个嵌入的新行作为一个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 Salich al-Numan“,当我写出它时,WriteLine()以CRLF结束该行。我丢失了原始行终止符。当我再次使用ReadLine()时,我得到以“1050a”开始的行


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

我建议您更改体系结构,使其更像编译器中的解析器

您需要创建一个lexer来返回令牌序列,然后创建一个解析器来读取令牌序列并处理它们

在您的情况下,代币将是:

  • 列数据
  • 逗号
  • 行尾
  • 您可以将“\n”(“\x0a”)本身视为嵌入的新行,因此将其作为列数据标记的一部分包含。一个“\r\n”将构成行结束标记

    这具有以下优点:

  • 只做1次数据传递
  • 最多只能存储1行数据
  • 尽可能重用内存(用于字符串生成器和列表)
  • 如果您的需求发生变化,那么很容易进行更改
  • 以下是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;
    }
    
    enum令牌类型
    {
    专栏数据,
    逗号,
    线路终端
    }
    类令牌
    {
    公共令牌类型类型{get;private set;}
    公共字符串数据{get;private set;}
    公共令牌(令牌类型)
    {
    类型=类型;
    }
    公共令牌(令牌类型、字符串数据)
    {
    类型=类型;
    数据=数据;
    }
    }
    私有IEnumerable GetTokens(文本阅读器)
    {
    var builder=新的StringBuilder();
    而(s.Peek()>=0)
    {
    var c=(char)s.Read();
    开关(c)
    {
    案例',':
    {
    如果(builder.Length>0)
    {
    产生返回新令牌(TokenType.ColumnData,ExtractText(builder));
    }
    返回新的令牌(TokenType.逗号);
    打破
    }
    案例'\r':
    {
    var next=s.Peek();
    如果(下一步=='\n')
    {
    s、 Read();
    }
    如果(builder.Length>0)
    {
    产生返回新令牌(TokenType.ColumnData,ExtractText(builder));
    }
    产生返回新令牌(令牌类型.LineTerminator);
    打破
    }
    违约:
    附加(c);
    打破
    }
    }
    s、 Read();
    如果(builder.Length>0)
    {
    产生返回新令牌(TokenType.ColumnData,ExtractText(builder));
    }
    }
    私有字符串提取文本(StringBuilder b)
    {
    var ret=b.ToString();
    b、 移除(0,b.长度);
    返回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);
        }
    }
    
    public void ConvertXLS(文本阅读器)
    {
    var columnData=新列表();
    bool lastWasColumnData=false;
    bool-seenAnyData=false;
    foreach(GetTokens中的var令牌)
    {
    交换机(token.T)
    
    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;
        }
        ......