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

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

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

我什至不确定我是否可以读取 \x0A,因为输入上的所有内容都注册为“ ”。我可以一个字符一个字符地进行操作,但这破坏了我删除空行的逻辑。

有帮助吗?

解决方案

我建议您更改架构,使其更像编译器中的解析器。

您想要创建一个返回标记序列的词法分析器,然后创建一个读取标记序列并对其进行处理的解析器。

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

  1. 列数据
  2. 逗号
  3. 行结束

您可以将 ' ' ('\x0a') 本身视为嵌入的新行,因此将其作为列数据标记的一部分。' ' 将构成行结束标记。

这样做的优点是:

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

以下是词法分析器的示例:

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

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

其他提示

您不能改变StreamReader返回行终止,并且你不能改变它使用行终止。

我不是这个问题完全清楚在什么逃脱你正在做的,尤其是在条款“并把它们写为\ X0A”的条款。该文件的示例可能会有所帮助。

这听起来像你的可以的需要通过文字工作角色,或者可能第一次加载整个文件,然后执行全局替换,e.g。

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中会保存它,但问题是,当我读入空白记录卸妆,我使用的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-努曼后嵌入新行。当我使用的ReadLine(),我得到“1050,“阿齐兹萨利赫AL-努曼”当我写出来,的WriteLine()结束该行以CRLF我。当我使用的ReadLine()再次失去了原有的行终止时,得到线以 '1050A'。

我可以读取整个文件,并替换它们,但后来我不得不更换他们回来之后。基本上我想要做的就是线路终端器,以确定是否其\ X0A或CRLF,然后如果\ X0A,我会用写(),并插入终止。

我知道我有点太迟了这里,但我有同样的问题,我的解决方案比最倾向于是简单了很多。

如果你能够确定哪些应该很容易做到,因为第一行通常是列标题的列数,你可以检查你列数与预期列数。如果列数不等于预期的列数,您只需连接具有无可比拟以前行当前行。例如:

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;
    }
    ......

感谢您这么多与您的代码和其他一些我想出了以下的解决方案!我已经在底部一些代码,我写了使用的一些逻辑从这个页面增加了一个链接。我想我会得到尊敬,荣誉是由于!谢谢!

下面是关于我需要什么样的解释: 试试这个,我写了这个,因为我有一些非常大的“|”有\ r \ n的一些列的内部,我需要使用分隔的文件\ r \ n作为行分隔符结束。我试图导入使用SSIS包的一些文件,但因为文件中有一些损坏的数据我无法。该文件超过500 GB,因此,它太大了打开和手动修复。我找到了答案,通过翻阅大量的论坛,了解流是如何工作的,并最终想出在一个文件中读取每个字符,并吐出了基于我加入到它的定义行的解决方案。这是一个命令行应用程序的使用,完全与帮助:)。我希望这可以帮助一些人出来,我还没有找到很喜欢它在其他位置上的解决方案,虽然想法是通过这个论坛和其他人的启发。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top