我编写了一个 C# 程序来读取 Excel .xls/.xlsx 文件并输出为 CSV 和 Unicode 文本。我编写了一个单独的程序来删除空白记录。这是通过读取每一行来完成的StreamReader.ReadLine()
,然后逐个字符地遍历字符串,如果该行包含所有逗号(对于 CSV)或所有制表符(对于 Unicode 文本),则不写入要输出的行。
当 Excel 文件的单元格内包含嵌入的换行符 (\x0A) 时,会出现此问题。我将 XLS 更改为 CSV 转换器来查找这些新行(因为它逐个单元地进行)并将它们写为 \x0A,而普通行只需使用 StreamWriter.WriteLine()。
问题出现在删除空白记录的单独程序中。当我读到StreamReader.ReadLine()
,根据定义,它仅返回带有行的字符串,而不返回终止符。由于嵌入的换行符显示为两个单独的行,因此当我将它们写入最终文件时,我无法分辨哪一个是完整记录,哪一个是嵌入式换行符。
我什至不确定我是否可以读取 \x0A,因为输入上的所有内容都注册为“\n”。我可以一个字符一个字符地进行操作,但这破坏了我删除空行的逻辑。
我建议您更改架构,使其更像编译器中的解析器。
您想要创建一个返回标记序列的词法分析器,然后创建一个读取标记序列并对其进行处理的解析器。
在你的情况下,令牌将是:
- 列数据
- Comma
- 行结束
您可以将 '\n' ('\x0a') 本身视为嵌入的新行,因此将其作为列数据标记的一部分。 '\r\n' 将构成行结束标记。
这样做的优点是:
- 仅对数据进行 1 次传递
- 最多只存储 1 行数据
- 重用尽可能多的内存(用于字符串生成器和列表)
- 如果您的需求发生变化,很容易改变
以下是词法分析器的示例:
免责声明:我什至还没有编译过这段代码,更不用说测试了,所以您需要清理它并确保它有效。
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);
}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)