Frage

Ich schrieb Programm eines C # eine Excel XLS / XLSX-Datei und Ausgabe in CSV und Unicode-Text zu lesen. Ich schrieb ein separates Programm leere Datensätze zu entfernen. Dies wird erreicht, indem jede Zeile mit StreamReader.ReadLine() zu lesen, und dann von Zeichen durch die Zeichenfolge und nicht das Schreiben die Zeile zur Ausgabe geht Charakter, wenn sie alle Kommas (für die CSV) enthalten oder alle Registerkarten (für den Unicode-Text).

Das Problem tritt auf, wenn die Excel-Datei eingebettete Zeilenumbrüche enthält (\ x0A) innerhalb der Zellen. Ich änderte meine XLS in das CSV-Konverter diese neuen Linien zu finden (da es Zelle für Zelle geht) und schreibt sie als \ x0A und Normallinien verwenden nur StreamWriter.WriteLine ().

Das Problem tritt in dem separaten Programm leere Datensätze zu entfernen. Als ich mit StreamReader.ReadLine() lesen, per Definition gibt es nur die Zeichenfolge mit der Linie, nicht der Terminator. Da die eingebetteten Zeilenumbrüche als zwei getrennte Linien zeigen, kann ich nicht sagen, welche eine vollständige Aufzeichnung und welches ist eine eingebettete Newline, wenn ich sie in die endgültigen Datei schreiben.

Ich bin nicht einmal sicher, ob ich in dem \ x0A lesen kann, weil alles auf dem Eingangsregister als ‚\ n‘. Ich konnte zeichenweise gehen, aber diese zerstört meine Logik Leerzeilen zu entfernen.

War es hilfreich?

Lösung

Ich würde empfehlen, dass Sie Ihre Architektur ändern eher wie ein Parser in einem Compiler zu arbeiten.

Sie möchten einen Lexer erstellen, die eine Folge von Token zurückgibt, und dann einen Parser, der die Folge von Token liest und tut Dinge mit ihnen.

In Ihrem Fall die Token wären:

  1. Spaltendaten
  2. Comma
  3. End of Line

Sie würden durch ihre Selbst als eingebettete neue Linie, und deshalb sind sie als Teil einer Spalte Daten-Token ‚\ n‘ ( ‚\ x0a‘) behandeln. A '\ r \ n' würde ein Ende der Zeile Token dar.

Dies hat folgende Vorteile:

  1. Doing nur 1 Durchlauf über die Daten
  2. Nur ein Maximum von 1 Linien im Wert von Daten zu speichern
  3. so viel Speicher wie möglich wiederverwendet (für den String-Builder und der Liste)
  4. Es ist einfach zu ändern sollten Ihre Anforderungen ändern

Hier ist ein Beispiel, was die Lexer aussehen würde:

Haftungsausschluss:. Ich habe nicht einmal kompiliert, geschweige denn getestet, dieser Code, so dass Sie es zu reinigen brauchen, und stellen Sie sicher, dass es funktioniert

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

Ihr "Parser" Code würde dann wie folgt aussehen:

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

Andere Tipps

Sie können nicht StreamReader ändern, um die Linie Terminator zurückzukehren, und Sie können nicht ändern, was es für Leitungsabschluss verwendet.

Ich bin mir nicht ganz klar über das Problem in Bezug auf was zu entkommen Sie tun, vor allem in Bezug auf die „und schreiben sie als \ x0A“. Eine Probe der Datei würde wahrscheinlich helfen.

Es klingt wie Sie können müssen zeichenweise arbeiten, oder möglicherweise die gesamte Datei laden erste und ersetzen ein globales, z.

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

Ich bin sicher, dass mit einem regex tun könnte und es wäre wahrscheinlich effizienter sein, aber ich finde den langen Weg leichter zu verstehen :) Es ist ein bisschen wie ein Hack, obwohl eine globale ersetzen zu tun haben - hoffentlich mit mehr Informationen, die wir mit einer besseren Lösung kommen werden.

Im Wesentlichen eine harte Rückkehr in Excel (Shift + Enter oder Alt + Enter, ich kann mich nicht erinnern) legt eine neue Zeile, die auf \ x0A in der Standard-Kodierung entspricht ich verwenden, um meine CSV zu schreiben. Als ich in CSV schreiben, verwende ich StreamWriter.WriteLine (), die die Zeile plus eine neue Zeile ausgibt (die ich glaube, ist r \ n \).

Die CSV ist in Ordnung und kommt genau wie Excel speichern würde, das Problem ist, wenn ich es in die leeren Datensatz Entferner lesen, ich bin mit Readline (), die einen Datensatz mit einem eingebetteten Newline als CRLF behandeln.

Hier ist ein Beispiel für die Datei, nachdem ich in CSV ...

konvertieren
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)

Wie Sie sehen können, der erste Datensatz hat eine eingebettete new-line nach al-Numan. Als ich Readline () verwenden, erhalte ich ‚1050," Aziz Salih al-Numan‘und, wenn ich das schreiben, Writeline () endet diese Linie mit einem CRLF. Ich verliere die ursprüngliche Linie Terminator. Wenn ich Readline (wieder) , erhalte ich die Zeile beginnend mit '1050a'.

Ich kann die ganze Datei eingelesen und ersetzen Sie sie, aber dann würde ich sie danach wieder ersetzen. Im Grunde, was ich tun möchte, ist den Leitungsabschluss zu erhalten, um festzustellen, ob sein \ x0a oder ein CRLF, und dann, wenn sein \ x0A, werde ich verwenden Write () und dass der Terminator eingefügt werden.

Ich weiß, ich bin ein wenig zu spät, um das Spiel hier, aber ich habe das gleiche Problem und meine Lösung war viel einfacher als die meisten angegeben.

Wenn Sie sind in der Lage, die Spaltenanzahl zu bestimmen, die leicht sein sollten, da die erste Zeile zu tun, ist in der Regel die Spaltentitel, können Sie Ihre Spalte mit der erwarteten Spaltenanzahl zählen überprüfen. Wenn die Spaltenanzahl nicht die erwartete Spaltenanzahl gleich, verketten Sie einfach die aktuelle Zeile mit den bisherigen unerreichten Linien. Zum Beispiel:

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

Vielen Dank mit Ihrem Code und einige andere, die ich mit der folgenden Lösung kam! Ich habe einen Link am Ende zu einem gewissen Code habe ich geschrieben, dass einige der Logik von dieser Seite verwendet. Ich dachte, ich würde geben Ehre, wem Ehre gebührt war! Dank!

Im Folgenden finden Sie eine Erklärung über das, was ich brauchte: Try This, schrieb ich das, weil ich einige sehr große haben ‚|‘ getrennte Dateien, die innerhalb einiger der Spalten \ r \ n und I benötigt verwenden \ r \ n als das Ende der Zeilentrennzeichen. Ich habe versucht, einige Dateien unter Verwendung von SSIS-Paketen zu importieren, aber wegen einiger beschädigten Daten in den Dateien, die ich war nicht in der Lage. Die Datei wurde über 5 GB, so dass es zu groß war, zu öffnen und manuell zu beheben. Ich fand die Antwort durch auf der Suche durch viele Foren zu verstehen, wie Streams arbeiten und endete mit einer Lösung kommen up, die jedes Zeichen in einer Datei liest und die Linie basiert auf den Definitionen spuckt ich in sie aufgenommen. dies ist für die Verwendung in einer Befehlszeilenanwendung, komplett mit Hilfe :). Ich hoffe, das einige andere Menschen hilft aus, ich habe keine Lösung ganz wie es anderswo gefunden, obwohl die Ideen, die von diesem Forum und andere inspiriert wurden.

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

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top