문제

Excel .xls/.xlsx 파일을 읽고 CSV 및 유니 코드 텍스트에 대한 출력을 읽기 위해 C# 프로그램을 작성했습니다. 빈 레코드를 제거하기 위해 별도의 프로그램을 작성했습니다. 이것은 각 줄을 읽음으로써 달성됩니다 StreamReader.ReadLine(), 그런 다음 문자열을 통해 문자별로 문자로 이동하고 모든 쉼표 (CSV) 또는 모든 탭 (유니 코드 텍스트)이 포함 된 경우 출력에 선을 작성하지 않습니다.

문제는 Excel 파일에 셀 내부에 내장 된 Newlines ( x0a)가 포함될 때 발생합니다. XLS를 CSV 변환기로 변경하여 이러한 새로운 라인을 찾아서 (셀에 의해 셀에 의해 진행되기 때문에) x0a로 작성하고 일반 선은 streamWriter.writeline ()을 사용합니다.

빈 레코드를 제거하기 위해 별도의 프로그램에서 문제가 발생합니다. 내가 함께 읽을 때 StreamReader.ReadLine(), 정의상, 그것은 터미네이터가 아닌 줄로 문자열을 반환합니다. 임베디드 신성민은 두 개의 개별 라인으로 표시되므로 전체 레코드인지, 최종 파일에 글을 쓸 때 어떤 내장 된 신성 라인인지 알 수 없습니다.

입력의 모든 것이 ' n'으로 등록되기 때문에 x0a에서 읽을 수 있을지 확신조차 확실하지 않습니다. 캐릭터별로 갈 수는 있지만, 이것은 빈 줄을 제거하기 위해 내 논리를 파괴합니다.

도움이 되었습니까?

해결책

컴파일러의 파서처럼 작동하도록 아키텍처를 변경하는 것이 좋습니다.

당신은 일련의 토큰을 반환하는 렉서를 만들고, 일련의 토큰을 읽고 그들과 함께하는 소포를 만들고 싶습니다.

귀하의 경우 토큰은 다음과 같습니다.

  1. 열 데이터
  2. 반점
  3. 줄의 끝

당신은 ' n'( ' x0a')을 자체적으로 내장 된 새 라인으로 취급하여 열 데이터 토큰의 일부로 포함시킵니다. A ' r n'은 라인 토큰의 끝을 구성합니다.

이것은 다음의 장점이 있습니다.

  1. 데이터를 1 번만 통과합니다
  2. 최대 1 줄 상당의 데이터 저장 만 저장
  3. 가능한 한 많은 메모리 재사용 (문자열 빌더 및 목록의 경우)
  4. 요구 사항이 변경되면 변경하기 쉽습니다

다음은 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가 넘었으므로 열기에는 너무 커서 수동으로 수정했습니다. 나는 많은 포럼을 통해 스트림이 어떻게 작동하는지 이해하고 결국 파일의 각 캐릭터를 읽고 내가 추가 한 정의에 따라 줄을 뱉어내는 솔루션을 얻었습니다. 이것은 명령 줄 응용 프로그램에 사용하기위한 것입니다. 도움이 완료됩니다 :). 나는 이것이 다른 사람들에게 도움이되기를 바랍니다. 아이디어는이 포럼과 다른 사람들에서 영감을 받았지만 다른 곳에서는 그와 같은 솔루션을 찾지 못했습니다.

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

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top