Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Убрать #13#10 между " и " / 25 сообщений из 28, страница 1 из 2
22.09.2017, 07:40:25
    #39524588
Romka-Fes
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Убрать #13#10 между " и "
Всем здравствуйте!
Понадобилось 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
22.09.2017, 08:43:02
    #39524606
Gerasimenko
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Убрать #13#10 между " и "
Romka-Fes,

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

Могу только посоветовать использовать АДО.
АДО откроет файл CSV в виде обычного датасета, и далее - работать с датасетом.
...
Рейтинг: 0 / 0
22.09.2017, 09:58:22
    #39524652
Romka-Fes
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Убрать #13#10 между " и "
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
22.09.2017, 09:59:35
    #39524655
Romka-Fes
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Убрать #13#10 между " и "
Valery_B,

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

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


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

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

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

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

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

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

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

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

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

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

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

...

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

Код: 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
24.09.2017, 02:07:25
    #39525367
Няшик
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Убрать #13#10 между " и "
Кое чего не доглядел. Фикс
Код: 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
24.09.2017, 02:59:17
    #39525371
rgreat
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Убрать #13#10 между " и "
"Venture «Extended Edition»","" -> Venture <Extended Edition>
Это как ?
...
Рейтинг: 0 / 0
24.09.2017, 09:06:11
    #39525387
Няшик
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Убрать #13#10 между " и "
rgreat"Venture «Extended Edition»","" -> Venture <Extended Edition>
Это как ?

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

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

В мемориз!
...
Рейтинг: 0 / 0
24.09.2017, 19:58:45
    #39525498
Dunkin
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Убрать #13#10 между " и "
Няшикпритом \" не будет считаться концом, это экранирование.
Что-то в https://tools.ietf.org/html/rfc4180 ни про символ \, ни про его код ничего нет. Там две двойных кавычки подряд ("").
...
Рейтинг: 0 / 0
24.09.2017, 21:55:55
    #39525514
Няшик
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Убрать #13#10 между " и "
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
25.09.2017, 16:01:59
    #39525877
Romka-Fes
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Убрать #13#10 между " и "
Няшик,

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

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


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