Необходимо подобрать терминаторы строк с помощью StreamReader.ReadLine().

StackOverflow https://stackoverflow.com/questions/667771

Вопрос

Я написал программу на C# для чтения файла Excel .xls/.xlsx и вывода его в форматы CSV и Unicode.Я написал отдельную программу для удаления пустых записей.Это достигается путем чтения каждой строки с помощью StreamReader.ReadLine(), а затем посимвольно пройти по строке и не записывать строку для вывода, если она содержит все запятые (для CSV) или все табуляции (для текста в Юникоде).

Проблема возникает, когда файл Excel содержит встроенные символы новой строки (\x0A) внутри ячеек.Я изменил свой конвертер XLS в CSV, чтобы найти эти новые строки (поскольку они идут ячейка за ячейкой) и записать их как \x0A, а обычные строки просто используют StreamWriter.WriteLine().

Проблема возникает в отдельной программе по удалению пустых записей.Когда я читаю с StreamReader.ReadLine(), по определению он возвращает только строку со строкой, а не признак конца.Поскольку встроенные символы новой строки отображаются как две отдельные строки, я не могу сказать, какая из них является полной записью, а какая — встроенной новой строкой, когда я записываю их в окончательный файл.

Я даже не уверен, что смогу прочитать \x0A, потому что все входные данные регистрируются как ' '.Я мог бы идти посимвольно, но это разрушает мою логику удаления пустых строк.

Это было полезно?

Решение

Я бы порекомендовал вам изменить вашу архитектуру, чтобы она работала как парсер в компиляторе.

Вы хотите создать лексер, который возвращает последовательность токенов, а затем синтаксический анализатор, который считывает последовательность токенов и что-то с ними делает.

В вашем случае токены будут:

  1. Данные столбца
  2. Запятая
  3. Конец линии

Вы будете рассматривать ' ' ('\x0a') сам по себе как встроенную новую строку и, следовательно, включать ее как часть токена данных столбца.' ' будет представлять собой токен конца строки.

Это имеет следующие преимущества:

  1. Выполнение только 1 прохода по данным
  2. Сохранение максимум 1 строки данных.
  3. Повторное использование как можно большего объема памяти (для построителя строк и списка)
  4. Легко изменить, если ваши требования изменятся.

Вот пример того, как будет выглядеть Лексер:

Отказ от ответственности: Я даже не компилировал, не говоря уже о тестировании, этот код, так что вам придется его почистить и убедиться, что он работает.

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».Образец файла, вероятно, поможет.

Это похоже на тебя может нужно работать посимвольно или, возможно, сначала загрузить весь файл и выполнить глобальную замену, например.

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(), который выводит строку плюс новую строку (я считаю, что это ).

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, «Азиз Салих ан-Нуман», и когда я это записываю, WriteLine() завершает эту строку 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, но из-за поврежденных данных в файлах мне это не удалось.Размер файла превышал 5 ГБ, поэтому его нельзя было открыть и исправить вручную.Я нашел ответ, просматривая множество форумов, чтобы понять, как работают потоки, и в итоге нашел решение, которое считывает каждый символ в файле и выдает строку на основе добавленных мной определений.это для использования в приложении командной строки, в комплекте со справкой :).Я надеюсь, что это поможет некоторым другим людям, я больше нигде не нашел подобного решения, хотя идеи были вдохновлены этим форумом и другими.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top