При работе со строками в Delphi разработчики часто сталкиваются с необходимостью очистки текста от нежелательных символов, особенно при обработке данных из внешних источников. В этой статье мы разберём распространённую ошибку доступа (access violation) при фильтрации строк и предложим несколько рабочих решений.
Проблема в исходном коде
Исходная функция для очистки строки выглядит следующим образом:
function MyCleanText(const AText: string): string;
var
I, Len, Count: Integer;
C: Char;
Buf: TArray<Char>;
begin
Len := Length(AText);
if Len = 0 then
Exit('');
SetLength(Buf, Len);
Count := 0;
for I := 1 to Len do
begin
C := AText[I];
// 1) Сохраняем табуляции (#9), переводы строк (#10) и возвраты каретки (#13)
if (C = #9) or (C = #10) or (C = #13) then
begin
Buf[Count] := C;
Inc(Count);
end
// 2) Преобразуем другие пробельные символы в обычный пробел
else if C.IsWhiteSpace then
begin
Buf[Count] := ' ';
Inc(Count);
end
// 3) Сохраняем ASCII/расширенный ASCII (#32..#255)
else if (Ord(C) >= 32) and (Ord(C) <= 255) then
begin
Buf[Count] := C;
Inc(Count);
end;
// 4) Остальные символы пропускаем
end;
SetLength(Buf, Count);
Result := String(Buf);
end;
Основная проблема в этом коде - некорректное преобразование массива символов TArray<Char> в строку через прямое приведение типов String(Buf). Это вызывает ошибку доступа, так как Delphi неправильно интерпретирует структуру данных.
Решение 1: Использование PChar с нуль-терминатором
function MyCleanText(const AText: string): string;
var
I, Len, Count: Integer;
C: Char;
Buf: TArray<Char>;
begin
Len := Length(AText);
if Len = 0 then
Exit('');
SetLength(Buf, Len + 1); // +1 для нуль-терминатора
Count := 0;
for I := 1 to Len do
begin
C := AText[I];
if (C = #9) or (C = #10) or (C = #13) then
begin
Buf[Count] := C;
Inc(Count);
end
else if C.IsWhiteSpace then
begin
Buf[Count] := ' ';
Inc(Count);
end
else if (Ord(C) >= 32) and (Ord(C) <= 255) then
begin
Buf[Count] := C;
Inc(Count);
end;
end;
Buf[Count] := #0; // Добавляем нуль-терминатор
Result := PChar(Buf);
end;
Решение 2: Использование SetString
Более элегантное решение с использованием SetString:
function MyCleanText(const AText: string): string;
var
I, Len, Count: Integer;
C: Char;
Buf: TArray<Char>;
begin
Len := Length(AText);
if Len = 0 then
Exit('');
SetLength(Buf, Len);
Count := 0;
for I := 1 to Len do
begin
C := AText[I];
if (C = #9) or (C = #10) or (C = #13) then
begin
Buf[Count] := C;
Inc(Count);
end
else if C.IsWhiteSpace then
begin
Buf[Count] := ' ';
Inc(Count);
end
else if (Ord(C) >= 32) and (Ord(C) <= 255) then
begin
Buf[Count] := C;
Inc(Count);
end;
end;
SetString(Result, PChar(Buf), Count);
end;
Решение 3: Модификация строки напрямую
Альтернативный подход без использования промежуточного массива:
function MyCleanText(const AText: string): string;
var
I, Len: Integer;
C: Char;
begin
Len := Length(AText);
if Len = 0 then
Exit('');
Result := AText;
I := 1;
while I <= Len do
begin
C := AText[I];
if (C = #9) or (C = #10) or (C = #13) or
((Ord(C) >= 32) and (Ord(C) <= 255)) then
begin
Inc(I);
end
else if C.IsWhiteSpace then
begin
Result[I] := ' ';
Inc(I);
end
else
begin
Delete(Result, I, 1);
Dec(Len);
end;
end;
end;
Улучшенная версия с поддержкой Unicode
Если вам нужно сохранять Unicode-символы (коды > 255), модифицируйте условие:
function MyCleanText(const AText: string): string;
var
I, Len, Count: Integer;
C: Char;
Buf: TArray<Char>;
begin
Len := Length(AText);
if Len = 0 then
Exit('');
SetLength(Buf, Len);
Count := 0;
for I := 1 to Len do
begin
C := AText[I];
// Сохраняем управляющие символы форматирования
if (C = #9) or (C = #10) or (C = #13) then
begin
Buf[Count] := C;
Inc(Count);
end
// Преобразуем другие пробельные символы
else if C.IsWhiteSpace then
begin
Buf[Count] := ' ';
Inc(Count);
end
// Сохраняем все видимые символы (включая Unicode)
else if not C.IsControl then
begin
Buf[Count] := C;
Inc(Count);
end;
end;
SetString(Result, PChar(Buf), Count);
end;
Заключение
Ошибка доступа в исходной функции возникала из-за неправильного преобразования массива символов в строку. Мы рассмотрели три рабочих решения:
Использование PChar с нуль-терминатором
Применение функции SetString
Прямая модификация строки
Для большинства случаев рекомендуется использовать второй вариант с SetString, как наиболее надежный и читаемый. Если же вам важна производительность при обработке очень длинных строк, стоит рассмотреть третий вариант.
Дополнительно мы улучшили функцию, добавив корректную обработку Unicode-символов через метод IsControl, что делает решение более универсальным.
В статье рассматривается проблема ошибки доступа при очистке строк в Delphi, предлагаются различные решения, включая использование PChar с нуль-терминатором, функции SetString и прямую модификацию строки, а также улучшенная версия с поддержкой Unicode.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.