streamreader.readline ()로 라인 터미네이터를 픽업해야합니다.
-
21-08-2019 - |
문제
Excel .xls/.xlsx 파일을 읽고 CSV 및 유니 코드 텍스트에 대한 출력을 읽기 위해 C# 프로그램을 작성했습니다. 빈 레코드를 제거하기 위해 별도의 프로그램을 작성했습니다. 이것은 각 줄을 읽음으로써 달성됩니다 StreamReader.ReadLine()
, 그런 다음 문자열을 통해 문자별로 문자로 이동하고 모든 쉼표 (CSV) 또는 모든 탭 (유니 코드 텍스트)이 포함 된 경우 출력에 선을 작성하지 않습니다.
문제는 Excel 파일에 셀 내부에 내장 된 Newlines ( x0a)가 포함될 때 발생합니다. XLS를 CSV 변환기로 변경하여 이러한 새로운 라인을 찾아서 (셀에 의해 셀에 의해 진행되기 때문에) x0a로 작성하고 일반 선은 streamWriter.writeline ()을 사용합니다.
빈 레코드를 제거하기 위해 별도의 프로그램에서 문제가 발생합니다. 내가 함께 읽을 때 StreamReader.ReadLine()
, 정의상, 그것은 터미네이터가 아닌 줄로 문자열을 반환합니다. 임베디드 신성민은 두 개의 개별 라인으로 표시되므로 전체 레코드인지, 최종 파일에 글을 쓸 때 어떤 내장 된 신성 라인인지 알 수 없습니다.
입력의 모든 것이 ' n'으로 등록되기 때문에 x0a에서 읽을 수 있을지 확신조차 확실하지 않습니다. 캐릭터별로 갈 수는 있지만, 이것은 빈 줄을 제거하기 위해 내 논리를 파괴합니다.
해결책
컴파일러의 파서처럼 작동하도록 아키텍처를 변경하는 것이 좋습니다.
당신은 일련의 토큰을 반환하는 렉서를 만들고, 일련의 토큰을 읽고 그들과 함께하는 소포를 만들고 싶습니다.
귀하의 경우 토큰은 다음과 같습니다.
- 열 데이터
- 반점
- 줄의 끝
당신은 ' n'( ' x0a')을 자체적으로 내장 된 새 라인으로 취급하여 열 데이터 토큰의 일부로 포함시킵니다. A ' 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;
}
"파서"코드는 다음과 같습니다.
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로 작성"하는 측면에서 문제에 대해 전적으로 명확하지 않습니다. 파일 샘플이 도움이 될 것입니다.
당신처럼 들립니다 5월 문자별로 캐릭터를 작동 시키거나 전체 파일을 먼저로드하고 전역 교체를 수행해야합니다.
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
나는 당신이 Regex로 그것을 할 수 있다고 확신하고 아마도 더 효율적일 것입니다. 그러나 나는 이해하기 쉬운 길을 찾는다. :) 그것은 글로벌 교체를 수행해야한다는 약간의 해킹이다. 더 나은 솔루션을 생각해보십시오.
본질적으로, Excel의 하드 리턴 (Shift+Enter 또는 Alt+Enter, 기억할 수 없음)은 CSV를 작성하는 데 사용하는 기본 인코딩에서 x0a와 동등한 새로운 라인을 넣습니다. CSV에 글을 쓸 때 StreamWriter.writeline ()을 사용하여 라인과 Newline을 출력합니다 ( 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)
보시다시피, 첫 번째 레코드에는 알-누만 이후 새로 내장 된 새 라인이 있습니다. readline ()을 사용하면 '1050, "Aziz Salih al-Numan'을 얻고 글을 쓸 때 CRLF와 함께 라인을 끝냅니다. 원래 라인 터미네이터를 잃습니다. Readline ()을 다시 사용할 때 다시 사용합니다. , 나는 '1050a'로 시작하는 줄을 얻는다.
전체 파일을 읽고 교체 할 수 있었지만 나중에 다시 교체해야합니다. 기본적으로 내가하고 싶은 것은 라인 터미네이터가 x0a 또는 crlf인지 판단하는 것입니다. 그런 다음 x0a 인 경우 write ()를 사용하고 해당 터미네이터를 삽입 할 것입니다.
나는 여기서 게임에 조금 늦었다는 것을 알고 있지만, 같은 문제가 있었고 내 솔루션은 대부분의 것보다 훨씬 간단했습니다.
첫 번째 줄이 일반적으로 열 제목이기 때문에 쉽게 수행 할 수있는 열 수를 결정할 수있는 경우 예상 열 수에 대해 열 수를 확인할 수 있습니다. 열 수가 예상 열 수와 같지 않으면 이전 라인을 이전의 타의 추종을 불허하는 선과 동의합니다. 예를 들어:
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 패키지를 사용하여 일부 파일을 가져 오려고했지만 파일의 손상된 데이터 때문에 할 수 없었습니다. 파일은 5GB가 넘었으므로 열기에는 너무 커서 수동으로 수정했습니다. 나는 많은 포럼을 통해 스트림이 어떻게 작동하는지 이해하고 결국 파일의 각 캐릭터를 읽고 내가 추가 한 정의에 따라 줄을 뱉어내는 솔루션을 얻었습니다. 이것은 명령 줄 응용 프로그램에 사용하기위한 것입니다. 도움이 완료됩니다 :). 나는 이것이 다른 사람들에게 도움이되기를 바랍니다. 아이디어는이 포럼과 다른 사람들에서 영감을 받았지만 다른 곳에서는 그와 같은 솔루션을 찾지 못했습니다.