powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Убрать #13#10 между " и "
25 сообщений из 28, страница 1 из 2
Убрать #13#10 между " и "
    #39524588
Фотография Romka-Fes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Всем здравствуйте!
Понадобилось CSV распарсить. Решил попробовать Грузить строки в TStringList методом LoadFromFile (файла небольшие, относительно) и разбиваю на строки значения с помощью SplitString. И всё бы работало хорошо, если бы не #13#10 внутри значений строк CSV - строка загружается не полностью, без учёта того что перевод строки внутри двойных кавычек.
Вопрос к тем кто сталкивался с подобной проблемой - как её малой кровью решить?
По склоняюсь к варианту предварительной подготовки файла - удаления переводов строк между " и ". Но это попахивает неким извратом + неизвестно в какой кодировке может прийти файл.

Может быть кто-то уже решал подобную задачу? Именно такого варианта проблемы я тут не нашёл (плохо искал?), иначе бы не создавал топик.

Спасибо!

p.s.

Пробовал задавать у TStringList QuoteChar := '"' - не помогло.




Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
...
type TValuesStingsArray = array of array of string;
...
procedure TfrmIMportEstimate.LoadCsvToArray (ACSVFile: TFileName;var ACSVValuesString: TValuesStingsArray;ADelimeter:char=';' );
var lslCsvStrings: TStringList;
     i,j:integer;
     lsDataString: string;
     ldaCsvStrValues :  TStringDynArray;
begin
  if not (FileExists(ACSVFile)) then
  begin
    raise Exception.Create('File ' + ACSVFile + ' not found');
    exit;
  end;

  try
     lslCsvStrings := TStringList.Create;
     lslCsvStrings.QuoteChar := '"';
     lslCsvStrings.Delimiter := ADelimeter;
     lslCsvStrings.StrictDelimiter := true;
     lslCsvStrings.LoadFromFile(ACSVFile);

     for i:= 1 {Skipping header} to lslCsvStrings.Count-1 do
     begin
       SetLength(ACSVValuesString,i);
       SetLength(ACSVValuesString[i-1],lslCsvStrings.Count);

       ldaCsvStrValues := SplitString(lslCsvStrings[i],ADelimeter);


...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39524606
Gerasimenko
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Romka-Fes,

StringReplace : убей все, что мешает.
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39524618
Valery_B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Romka-Fes,

Могу только посоветовать использовать АДО.
АДО откроет файл CSV в виде обычного датасета, и далее - работать с датасетом.
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39524652
Фотография Romka-Fes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Gerasimenko,

Для StringReplace нужна строка. Как я уже писал, строки я загружаю с помощью LoadFromFile и как раз если в значении между разделителями CSV , пусть даже и в double quotes, есть разрыв строки (#13#10) - то строка загружается не полная, а та её часть, которая до разрыва строки имеет место быть.

Как вариант - загрузить весь файл изначально в строку и с этой большой строкой работать.
Но удалить ВСЕ переносы строк нельзя. Иначе файл CSV станет весь одной большой строкой.

Попробовал извратиться так - получилось. Но пришлось тип строки на Ansi поменять: FileStr: AnsiString;
Наверное, надо курить TEncoding чтобы файлы с любыми кодировками нормально обрпабатывались?


#13#10 - мне нужно оставить в файле, а #10, без предшествующего символа #13 - надо заменить на пробел. #2 выбрал как временный маркер.

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
procedure TfrmIMportEstimate.FixCsvFile  (Acsv_file_name:TFileName);
var
  fs: TFileStream;
  FileStr: Ansistring;
begin
   if not FileExists(Acsv_file_name) then
   begin
     raise Exception.Create('File ' +  Acsv_file_name +' does not exists.');
   end;

  fs := TFileStream.Create(Acsv_file_name, fmOpenread or fmShareDenyNone);
  try
    SetLength(FileStr, fs.Size);
    fs.ReadBuffer(FileStr[1], fs.Size);
  finally
    fs.Free;
  end;

  FileStr  := StringReplace(FileStr, #13#10, #2, [rfReplaceAll, rfIgnoreCase]);
  FileStr  := StringReplace(FileStr, #10, #32, [rfReplaceAll, rfIgnoreCase]);
  FileStr  := StringReplace(FileStr, #2,#13#10 , [rfReplaceAll, rfIgnoreCase]);


  fs := TFileStream.Create(Acsv_file_name, fmCreate);
  try
     fs.Size := Length(FileStr);
    fs.WriteBuffer(FileStr[1], Length(FileStr));
  finally
    fs.Free;
  end;

end;


...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39524655
Фотография Romka-Fes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Valery_B,

Не хочется из пушки по воробьям стрелять как-то....
Есть куча всяких готовых компонентов, не проверял их, правда... Тут банальная работа с текстовым файлом....
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39524673
Gerasimenko
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Romka-FesGerasimenko,

Для StringReplace нужна строка. Как я уже писал, строки я загружаю с помощью LoadFromFile и как раз если в значении между разделителями CSV , пусть даже и в double quotes, есть разрыв строки (#13#10) - то строка загружается не полная, а та её часть, которая до разрыва строки имеет место быть.
...


http://www.delphibasics.ru/TStringList.php
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39524741
Valery_B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Romka-FesНе хочется из пушки по воробьям стрелять как-то....
Есть куча всяких готовых компонентов, не проверял их, правда... Тут банальная работа с текстовым файлом....
Потом сравни количество затраченного времени на "самопал", и говори о стрельбе по воробьям.
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39524874
schi
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Romka-Fes,

Написать простейший парсер за пять минут не предлагать ?
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39524879
schi
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Пройди по StringList после загрузки, если последний символ в какой-то строке кавычка, а в следующей строке первый символ кавычка, то склей их.
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39524904
Arioch
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39524999
Фотография Romka-Fes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Valery_BRomka-FesНе хочется из пушки по воробьям стрелять как-то....
Есть куча всяких готовых компонентов, не проверял их, правда... Тут банальная работа с текстовым файлом....
Потом сравни количество затраченного времени на "самопал", и говори о стрельбе по воробьям.

Сравнить его с чем именно?
TstringList.LoadFromFile и SplitString - это "самопал"? :)

Я же писал о том что файлы небольшие. Парсинг и валидация данных файла в пол метра проходит за 1-2 секунды. Это вписывается в требования.
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525005
Фотография Romka-Fes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Gerasimenko,

Пробовал задавать у TStringList QuoteChar := '"' - не помогло.
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525010
Фотография Romka-Fes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Arioch,

почти всё шароварь. Спасибо, не надо. Да и был я там не раз и ранее.
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525089
Фотография DarkMaster
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Romka-Fes,

Грузишь файл в список. Для каждой строки проверяешь условие: число " в строке должно быть четным. Если нечетное - лепишь к строке следующую с ее Trim() и пробелом в начале (чтобы слова не склеились) и опять проверяешь.
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525310
Фотография Romka-Fes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
DarkMaster,

А если в самой строке присутствует " ?
Опираться на чётность кол-ва " - не вариант.

Пока ничего лучше удаления #10 без предшествующего #13 не придумал.
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525362
Няшик
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Romka-Fesа если в самой строке присутствует " ?

Надо пойти, и почитать что такое CSV. Если есть начало " то мы должны найти конец " притом \" не будет считаться концом, это экранирование.

Если нет " то ищем конечную запятую. Что бы не нагружать копирование, можно сделать банальный while и делать inc(Len) и потом через move копировать.

...

Но для начала, надо найти конец строки. А потом сделать for до N позиции. И потом от N позиции найти второй конец строки.
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525365
Няшик
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Попил чаёк, и реализовал на скорую руку пример

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils, System.Generics.Collections;

Type
  TValues = TList<string>;
  TestBase = TList<TValues>;

function Test(const str: string): TestBase;
label _to;
var
  p: PChar;
  Skip, pos, posa, I, m, c: Integer;
  sc: Byte;
  value: string;
  values: TValues;
begin
  Result := TestBase.Create;
  p := Pointer(str);

_to:
  pos := 0;
  while ((p + pos)^ <> #0) do
  begin
    if (((p + pos)^ = #13) and ((p + pos + 1)^ = #10)) then
    begin
      Skip := 2;
      Break;
    end
    else if ((p + pos)^ = #13) or ((p + pos)^ = #10) then
    begin
      Skip := 1;
      Break;
    end;
    inc(pos);
  end;
  if pos <> 0 then
  begin
    values := TValues.Create;
    sc := 0;
    m := 0;
    for I := 0 to pos do
    begin
      case sc of
        1:
          if ((p[I] = '"') and (p[I - 1] <> '\')) or (I = pos) then
          begin
            c := I - m - 1;
            if c <> 0 then
            begin
              SetLength(value, c);
              Move(p[m + 1], value[1], c * SizeOf(char));
              values.Add(value);
            end;
            m := I + 2;
            sc := 2;
          end;
        2:
          if p[I] = ',' then
            sc := 0;
      else
        if (sc <> 3) and (sc <> 1) and (p[I] = '"') then
          sc := 1
        else if (p[I] = ',') or (I = pos) then
        begin
          c := I - m;
          if c <> 0 then
          begin
            SetLength(value, c);
            Move(p[m], value[1], c * SizeOf(char));
            values.Add(value);
          end;
          m := I + 1;
          sc := 0;
        end
        else
          sc := 3;
      end;
    end;

    inc(p, pos + Skip);

    Result.Add(values);

    goto _to;
  end;
end;

var
  TestB: TestBase;
  I, r: Integer;
  v: TValues;

begin
  try
    TestB := Test(

      '1997,Ford,E3"50,"ac, \" abs, moon",3000.00' + #13 +
      '1999,Chevy,"Venture «Extended Edition»","",4900.00' + #13 +
      '1996,Jeep,Grand Cherokee,"MUST SELL! air, moon roof, loaded",4799.00');

    for I := 0 to TestB.Count - 1 do
    begin
      v := TestB[I];
      for r := 0 to v.Count - 1 do
        Writeln(v[r]);
      Writeln('--------------');
    end;
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

end.




Подаём на вход
1997,Ford,E3"50,"ac, \" abs, moon",3000.00
1999,Chevy,"Venture «Extended Edition»","",4900.00
1996,Jeep,Grand Cherokee,"MUST SELL! air, moon roof, loaded",4799.00

Распечатываем массив, и получаем
1997
Ford
E3"50
ac, \" abs, moon
3000.00
--------------
1999
Chevy
Venture <Extended Edition>
4900.00
--------------
1996
Jeep
Grand Cherokee
MUST SELL! air, moon roof, loaded
4799.00
--------------
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525367
Няшик
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Кое чего не доглядел. Фикс
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils, System.Generics.Collections;

Type
  TValues = TList<string>;
  TestBase = TList<TValues>;

function Test(const str: string): TestBase;
label _to;
var
  p: PChar;
  Skip, pos, posa, I, m, c, len: Integer;
  sc: Byte;
  value: string;
  values: TValues;
begin
  Result := TestBase.Create;
  len := Length(str);
  p := Pointer(str);

_to:
  pos := 0;
  while ((p + pos)^ <> #0) do
  begin
    if (p + pos)^ = '"' then
      if sc = 1 then
        sc := 0
      else
        sc := 1;

    if (sc <> 1) and (((p + pos)^ = #13) and ((p + pos + 1)^ = #10)) then
    begin
      Skip := 2;
      Break;
    end
    else if (sc <> 1) and ((p + pos)^ = #13) or ((p + pos)^ = #10) then
    begin
      Skip := 1;
      Break;
    end;
    inc(pos);
  end;
  if pos <> 0 then
  begin
    values := TValues.Create;
    sc := 0;
    m := 0;
    for I := 0 to pos do
    begin
      case sc of
        1:
          if ((p[I] = '"') and (p[I - 1] <> '\')) or (I = pos) then
          begin
            c := I - m - 1;
            if c <> 0 then
            begin
              SetLength(value, c);
              Move(p[m + 1], value[1], c * SizeOf(char));
              values.Add(value);
            end;
            m := I + 2;
            sc := 2;
          end;
        2:
          if p[I] = ',' then
            sc := 0;
      else
        if (sc <> 3) and (sc <> 1) and (p[I] = '"') then
          sc := 1
        else if (p[I] = ',') or (I = pos) then
        begin
          c := I - m;
          if c <> 0 then
          begin
            SetLength(value, c);
            Move(p[m], value[1], c * SizeOf(char));
            values.Add(value);
          end;
          m := I + 1;
          sc := 0;
        end
        else
          sc := 3;
      end;
    end;

    inc(p, pos + Skip);
    dec(len, pos + Skip);

    Result.Add(values);

    if len >= 0 then

      goto _to;
  end;
end;

var
  TestB: TestBase;
  I, r: Integer;
  v: TValues;

begin
  try
    TestB := Test(

      '1997,Ford,E3"50,"ac, \" abs, moon",3000.00' + #13 +
      '1999,Chevy,"Venture «Extended Edition»","",4900.00' + #13 +
      '1996,Jeep,Grand Cherokee,"MUST SELL! air, moon roof, loaded",4799.00');

    for I := 0 to TestB.Count - 1 do
    begin
      v := TestB[I];
      for r := 0 to v.Count - 1 do
        Writeln(v[r]);
      Writeln('--------------');
    end;
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

end.

...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525371
rgreat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
"Venture «Extended Edition»","" -> Venture <Extended Edition>
Это как ?
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525387
Няшик
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rgreat"Venture «Extended Edition»","" -> Venture <Extended Edition>
Это как ?

В Консоль не правильно выводит. В дебаггере всё отлично показывает.
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525394
schi
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Няшикrgreat"Venture «Extended Edition»","" -> Venture <Extended Edition>
Это как ?

В Консоль не правильно выводит. В дебаггере всё отлично показывает.

В мемориз!
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525498
Dunkin
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Няшикпритом \" не будет считаться концом, это экранирование.
Что-то в https://tools.ietf.org/html/rfc4180 ни про символ \, ни про его код ничего нет. Там две двойных кавычки подряд ("").
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525514
Няшик
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
DunkinНяшикпритом \" не будет считаться концом, это экранирование.
Что-то в https://tools.ietf.org/html/rfc4180 ни про символ \, ни про его код ничего нет. Там две двойных кавычки подряд ("").


Потому что это экранирующий символ. Многие инструменты, в частности в PHP функция - str_getcsv позволяет менять этот символ \ на " и.т.д. К примеру, что бы экранирование было двумя "" -> "


Common usage of CSV is US-ASCII, but other character sets defined
by IANA for the "text" tree may be used in conjunction with the
"charset" parameter.
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525877
Фотография Romka-Fes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Няшик,

"Надо пойти, и почитать что такое CSV." - кому надо - тот почитает. Я вот читал. И то, как сохраняет тот же Excel в CSV, с разделителями таки ";" - это никак не соответствует стандартам. Но, надо исходить из реалий. А они таковы что пользователь может (случайно, специально, неважно) влепить " в самой строке в Excek и потом выдать такой файл, который далее должен будет импортироваться неким софтом.
Так что в контексте этих реалий я и писал о том что в случае наличия " в самой строке, вариант DarkMaster с проверкой чётности - таки не вариант.
Как бы там ни было - спасибо за идею и реализацию, но я таки оставлю свою. Файл уже будет откорректирован и при передаче другим пользователям/системам - этот нюанс с переводом строки , теоретически, не "всплывёт" вновь.
...
Рейтинг: 0 / 0
Убрать #13#10 между " и "
    #39525965
schi
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Romka-Fes И то, как сохраняет тот же Excel в CSV, с разделителями таки ";" - это никак не соответствует стандартам. Но, надо исходить из реалий.

Зайди в Control Panel, замени "разделитель элементов списка" на запятую и радуйся соответствию стандартам.
...
Рейтинг: 0 / 0
25 сообщений из 28, страница 1 из 2
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Убрать #13#10 между " и "
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]