Pergunta

Eu escrevi um C # programa para ler um arquivo .xls Excel / .xlsx e saída para CSV e Unicode texto. Eu escrevi um programa separado para remover registros em branco. Isto é realizado através da leitura de cada linha com StreamReader.ReadLine(), e depois indo caractere por caractere através da corda e não escrever a linha de saída se ele contém todas as vírgulas (para o CSV) ou todas as guias (para o texto Unicode).

O problema ocorre quando o arquivo Excel contém novas linhas incorporadas (\ x0A) no interior das células. Mudei de XLS ao conversor de CSV para encontrar estas novas linhas (uma vez que vai célula por célula) e gravá-los como \ x0A, e as linhas normais apenas usar StreamWriter.WriteLine ().

O problema ocorre no programa separado para remover registros em branco. Quando eu li com StreamReader.ReadLine(), por definição, ele só retorna a string com a linha, não o terminador. Desde que as novas linhas incorporadas aparecer como duas linhas separadas, eu não posso dizer que é um registro completo e que é uma nova linha incorporado para quando eu escrevê-los para o arquivo final.

Eu não estou mesmo certo eu posso ler no \ x0A porque tudo nos registros de entrada como '\ n'. Eu poderia ir caractere por caractere, mas isso destrói a minha lógica para remover linhas em branco.

Foi útil?

Solução

Eu recomendaria que você mude sua arquitetura para o trabalho mais como um analisador de um compilador.

Você deseja criar um lexer que retorna uma sequência de tokens, e, em seguida, um analisador que lê a seqüência de tokens e faz coisas com eles.

No seu caso os tokens seria:

  1. Dados Coluna
  2. Comma
  3. Fim de Linha

Você trataria '\ n' ( '\ x0a') por sua auto como uma nova linha incorporado, e, portanto, incluí-lo como parte de um conjunto de dados coluna de token. A '\ r \ n' constituiria um Fim de sinal de linha.

Isto tem as vantagens de:

  1. Fazendo apenas 1 passagem sobre os dados
  2. Apenas armazenar um máximo de 1 linhas no valor de dados
  3. Reutilizar tanta memória quanto possível (para o construtor corda e lista)
  4. É fácil mudança deve sua mudança de requisitos

Aqui está uma amostra do que o Lexer seria parecido com:

Disclaimer:. Eu nem sequer compilado, muito menos testado, este código, então você precisa para limpá-lo e verifique se ele funciona

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

Seu código "parser", então esta aparência:

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

Outras dicas

Você não pode mudar StreamReader para retornar os terminadores de linha, e você não pode mudar o que ele usa para terminação de linha.

Eu não sou inteiramente claro sobre o problema em termos do que escapar você está fazendo, especialmente em termos de "e gravá-los como \ x0A". Uma amostra do arquivo seria provavelmente ajuda.

Parece que você pode necessidade de caráter trabalho de caráter, ou possivelmente carregar o arquivo inteiro em primeiro lugar e fazer uma substituição global, por exemplo.

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

Eu tenho certeza que você poderia fazer isso com um regex e provavelmente seria mais eficiente, mas acho que a longo caminho mais fácil de entender :) É um pouco de um truque ter que fazer uma substituição global, embora - espero que com mais informações nós vamos chegar a uma solução melhor.

Essencialmente, um hard-retorno no Excel (Shift + Enter ou Alt + Enter, eu não me lembro) coloca uma nova linha que é equivalente a \ x0A no padrão codificação eu uso para escrever o meu CSV. Quando eu escrevo para CSV, eu uso StreamWriter.WriteLine (), que gera a linha mais uma nova linha (que eu acredito que é \ r \ n).

O CSV está bem e sai exatamente como Excel iria salvá-lo, o problema é quando eu lê-lo para o removedor de registro em branco, estou usando ReadLine (), que irá tratar um registro com uma nova linha incorporado como um CRLF.

Aqui está um exemplo do arquivo depois que eu converter para 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)

Como você pode ver, o primeiro registro tem uma nova linha incorporado depois de al-Numan. Quando eu uso ReadLine (), recebo '1050, "Aziz Salih al-Numan' e quando eu escrevo isso, WriteLine () extremidades essa linha com um CRLF. Eu perco o terminador de linha original. Quando eu uso ReadLine () novamente , fico com a linha iniciada com '1050A'.

Eu poderia ler o arquivo inteiro e substituí-los, mas então eu teria que substituí-los de volta depois. Basicamente o que eu quero fazer é obter o terminador de linha para determinar se o seu \ x0a ou um CRLF, e, em seguida, se o seu \ x0A, vou usar Write () e inserir esse terminador.

Eu sei que sou um pouco tarde para o jogo aqui, mas eu estava tendo o mesmo problema e minha solução era muito mais simples do que a maioria dado.

Se você é capaz de determinar a contagem de coluna que deve ser fácil de fazer, já que a primeira linha é geralmente os títulos das colunas, você pode verificar a sua contagem de coluna contra a contagem de coluna esperado. Se a contagem de coluna não é igual a contagem de coluna esperado, você simplesmente concatenar a linha atual com as linhas ímpares anteriores. Por exemplo:

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

Muito obrigado com o seu código e alguns outros eu vim com a seguinte solução! Eu adicionei um link na parte inferior para algum código que eu escrevi que usou alguns dos lógica desta página. Pensei em dar honra onde a honra era devido! Obrigado!

Abaixo está uma explicação sobre o que eu precisava: Tente isso, eu escrevi isto porque eu tenho algumas muito grande '|' ficheiros que tenham \ r \ n dentro de algumas das colunas delimitado e eu precisava usar \ r \ n como o fim do delimitador de linha. Eu estava tentando importar alguns arquivos usando pacotes SSIS, mas por causa de alguns dados corrompidos nos arquivos eu era incapaz. O arquivo foi mais de 5 GB por isso era muito grande para abrir e correção manualmente. Eu encontrei a resposta através do olhar através de lotes de fóruns para entender como fluxos de trabalho e acabou chegando com uma solução que lê cada caractere em um arquivo e cospe para fora da linha com base nas definições que eu adicionei para ele. isto é para uso em um aplicativo de linha de comando, com ajuda :). Espero que isso ajude algumas outras pessoas, eu não encontrei uma solução bastante parecido em qualquer outro lugar, embora as idéias foram inspirados por este fórum e outros.

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top