C# 需要使用StreamReader.ReadLine()拾取行终止符
我编写了一个C#程序来读取Excel.xls/.xlsx文件并输出为CSV和Unicode文本。我编写了一个单独的程序来删除空白记录。这是通过使用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,
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来返回令牌序列,然后创建一个解析器来读取令牌序列并处理它们 在您的情况下,代币将是:
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;
}
......