Невидимые символы в строках 1С: поиск по коду, регулярные выражения и чистка данных

· 10 мин

Содержание

Найти спецсимвол в строке 1С проще всего по его коду: пройти строку циклом и для каждой позиции вызвать КодСимвола(Стр, Позиция). Всё, что меньше 32, а также коды 160, 173, 8203–8207 и 65279 — кандидаты в «невидимки». Для частых случаев хватает СтрНайти(Стр, Символы.ПС) или СтрНайти(Стр, Символы.НПП): функция вернёт позицию первого вхождения или 0.

Частое заблуждение — что СокрЛП() решает проблему. Она убирает незначащие символы только по краям строки. Неразрывный пробел в середине ИНН, табуляция внутри наименования или символ нулевой ширины, скопированный с сайта, останутся на месте. Поиск и проверку спецсимволов в 1С приходится делать явно — ниже код, который можно взять в общий модуль.

Всё описанное относится к платформе 8.3 и не зависит от конфигурации: одинаково работает в Бухгалтерии, ЗУП, УТ и в самописных базах.

Откуда в строках 1С берутся спецсимволы

Строка в 1С хранится в Юникоде. Любой символ, который пришёл извне, попадает в базу как есть. Пользователь его не видит, а программа видит.

Типичные источники:

  • 📋 Копирование из Word, Excel и браузера — неразрывные пробелы, мягкие переносы, символы нулевой ширины.
  • 📄 Загрузка из файлов — табуляция в CSV, пара «возврат каретки + перевод строки» из Windows-файлов, метка BOM в начале UTF-8.
  • 🔄 Обмены и интеграции — управляющие символы из чужих систем, которые ломают XML.
  • ⌨️ Ручной ввод в многострочные поля — переносы строк в «Комментарии» или «Адресе».
  • 🏦 Банковские выписки и выгрузки из госсервисов — лишние пробелы и спецсимволы в реквизитах.

Последствия знакомы каждому, кто сопровождает базу. Поиск контрагента по ИНН ничего не находит, хотя ИНН «на экране совпадает». Дубли в справочнике номенклатуры отличаются одним невидимым символом. Выгрузка в XML падает на стороне получателя. Число() не может преобразовать «12 345» из Excel.

Таблица кодов, которые стоит знать

КодЧто за символГде встречается
9Горизонтальная табуляцияCSV, копирование из таблиц
10Перевод строки (LF)Многострочные поля, файлы
13Возврат каретки (CR)Файлы из Windows, письма
11, 12Вертикальная табуляция, перевод страницыВыгрузки из Word, старые системы
0–8, 14–31, 127Прочие управляющиеБитые интеграции, бинарный мусор
160Неразрывный пробелExcel, Word, сайты
173Мягкий переносWord, вёрстка сайтов
8203–8207Нулевая ширина и метки направленияКопирование из браузера и мессенджеров
65279BOM / неразрывный пробел нулевой шириныНачало файлов UTF-8

Коды 0–8, 11, 12 и 14–31 недопустимы в XML 1.0. Если обмен через XML ломается «на ровном месте», начинайте поиск именно с них.

Перечисление Символы: шесть готовых констант платформы

Для самых частых спецсимволов в платформе есть системное перечисление Символы. Его значения подставляют вместо магических кодов, код читается лучше.

КонстантаКодАнглийское имя
Символы.ПС10Chars.LF
Символы.ВК13Chars.CR
Символы.Таб9Chars.Tab
Символы.ВТаб11Chars.VTab
Символы.ПФ12Chars.FF
Символы.НПП160Chars.NBSp

Проверка «есть ли в строке перенос или табуляция» укладывается в одно условие:

Если СтрНайти(Стр, Символы.ПС) > 0
	Или СтрНайти(Стр, Символы.ВК) > 0
	Или СтрНайти(Стр, Символы.Таб) > 0 Тогда
	// строка содержит переносы или табуляцию
КонецЕсли;

СтрНайти() доступна с платформы 8.3.6. Она умеет искать с конца строки и с заданной позиции. В старом коде вместо неё встречается Найти() — она тоже работает, но без этих параметров.

Для символов, которых нет в перечислении, используйте Символ(Код). Например, мягкий перенос — Символ(173), BOM — Символ(65279). Удобно завести их как переменные в начале процедуры, чтобы не повторять вызов в цикле.

Поиск спецсимволов по коду: КодСимвола и Символ

КодСимвола(Строка, Позиция) возвращает Юникод-код символа в указанной позиции. Символ(Код) делает обратное. Эта пара — основной инструмент, когда нужно найти «что-то невидимое» без знания, что именно.

Найти позицию первого подозрительного символа

Функция ПозицияПервогоСпецсимвола(Стр) Экспорт

	Для Позиция = 1 По СтрДлина(Стр) Цикл
		Код = КодСимвола(Стр, Позиция);
		Если Код < 32 Или Код = 127 Или Код = 160 Или Код = 173
			Или (Код >= 8203 И Код <= 8207) Или Код = 65279 Тогда
			Возврат Позиция;
		КонецЕсли;
	КонецЦикла;

	Возврат 0;

КонецФункции

Функция возвращает номер позиции или 0, если строка чистая. Табуляцию и переносы она тоже считает спецсимволами. Если для многострочного поля переносы допустимы — исключите коды 10 и 13 из условия.

Вывести коды всех символов строки

Когда непонятно, чем две «одинаковые» строки отличаются, разложите их на коды:

Функция КодыСимволов(Стр) Экспорт

	Коды = Новый Массив;
	Для Позиция = 1 По СтрДлина(Стр) Цикл
		Коды.Добавить(Формат(КодСимвола(Стр, Позиция), "ЧГ=0"));
	КонецЦикла;

	Возврат СтрСоединить(Коды, " ");

КонецФункции

Для строки «12 345», скопированной из Excel, результат будет 49 50 160 51 52 53. Число 160 в середине — неразрывный пробел вместо обычного (код 32).

Формат(..., "ЧГ=0") здесь обязателен. Без него код 65279 превратится в «65 279» с разделителем групп, и вывод станет нечитаемым.

Переносы строк и табуляция: поиск, подсчёт и разбор

Переносы и табуляция — самые частые спецсимволы, и для них в платформе есть отдельные функции.

Сколько строк в тексте

  • СтрЧислоСтрок(Стр) — количество строк в многострочном тексте.
  • СтрПолучитьСтроку(Стр, Номер) — строка по номеру.
  • СтрЧислоВхождений(Стр, Символы.ПС) — сколько именно символов перевода строки.

Разница между первым и третьим вариантом в единицу: текст из трёх строк содержит два перевода. Ещё одно расхождение даёт перенос в самом конце текста — его легко не заметить.

CR, LF и их пара

В Windows-файлах строки разделяются парой ВК + ПС (коды 13 и 10). В 1С внутри многострочного поля обычно хранится только ПС. При чтении файла через объект ЧтениеТекста пара по умолчанию приводится к одному ПС. А вот если текст пришёл из HTTP-ответа или COM-объекта, «голые» ВК могут остаться.

Признак проблемы: в табличном документе или при печати появляется лишний пустой интервал между строками. Нормализуйте переносы до любой другой обработки:

Стр = СтрЗаменить(Стр, Символы.ВК + Символы.ПС, Символы.ПС);
Стр = СтрЗаменить(Стр, Символы.ВК, Символы.ПС);

Порядок важен: сначала заменяется пара, потом одиночный ВК. Если сделать наоборот, получится двойной перенос.

Разбор строки по табуляции

Строку из CSV с табуляцией в качестве разделителя разбирает СтрРазделить() (с 8.3.6):

Поля = СтрРазделить(СтрокаФайла, Символы.Таб, Истина);
ИНН = СокрЛП(Поля[0]);
Наименование = СокрЛП(Поля[1]);

Третий параметр Истина сохраняет пустые значения. Без него пустая колонка «схлопнется», и все следующие поля съедут на одну позицию.

Регулярные выражения для поиска спецсимволов

Цикл по КодСимвола надёжен, но громоздок. Когда нужно найти или удалить целый класс символов за одну операцию, удобнее регулярное выражение.

Встроенные функции платформы

В платформе 8.3.23 появились функции встроенного языка для работы с регулярными выражениями, в том числе СтрНайтиПоРегулярномуВыражению() и СтрЗаменитьПоРегулярномуВыражению(). Если ваша база работает на этой платформе или новее и режим совместимости позволяет, начинайте с них: они работают и на сервере под Linux, и в веб-клиенте. Полный список параметров и поддерживаемый синтаксис шаблонов сверьте в синтакс-помощнике своей версии.

COM-объект VBScript.RegExp

На платформе ниже 8.3.23 под Windows регулярные выражения подключают через COM:

&НаСервере
Функция УдалитьНевидимыеСимволы(Стр)

	Рег = Новый COMОбъект("VBScript.RegExp");
	Рег.Global = Истина;
	Рег.Pattern = "[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\xAD​-‏]";

	Возврат Рег.Replace(Стр, "");

КонецФункции

Чтобы только проверить наличие, используйте Рег.Test(Стр) — метод вернёт Истина или Ложь.

Ограничения способа:

  • 🪟 работает только там, где есть Windows Script Host, — сервер 1С под Linux и веб-клиент его не поддерживают;
  • 🐢 создание COM-объекта не бесплатно — для обработки 100 000 строк создайте объект один раз, а не в каждой итерации;
  • 🔒 на некоторых серверах COM запрещён политиками безопасности.

Поиск спецсимволов в запросе

Регулярных выражений в языке запросов нет, но ПОДОБНО с параметром находит записи с конкретным символом прямо в базе:

Запрос = Новый Запрос;
Запрос.Текст =
	"ВЫБРАТЬ
	|	Контрагенты.Ссылка,
	|	Контрагенты.ИНН
	|ИЗ
	|	Справочник.Контрагенты КАК Контрагенты
	|ГДЕ
	|	(Контрагенты.ИНН ПОДОБНО &ШаблонНПП
	|		ИЛИ Контрагенты.ИНН ПОДОБНО &ШаблонПробел)";
Запрос.УстановитьПараметр("ШаблонНПП", "%" + Символы.НПП + "%");
Запрос.УстановитьПараметр("ШаблонПробел", "% %");
Выборка = Запрос.Выполнить().Выбрать();

Так за секунды находятся все контрагенты с «грязным» ИНН. Для реквизитов неограниченной длины приведите поле через ВЫРАЗИТЬ(Поле КАК СТРОКА(1000)).

Очистка строки и замена найденных символов

Найти мало — нужно решить, что делать с каждым видом символа. Удалять всё подряд нельзя: табуляция между словами после удаления склеит их.

СимволЧто сделатьПочему
Неразрывный пробел (160)Заменить на обычный пробелЭто пробел по смыслу
Табуляция (9)Заменить на пробелИначе слова склеятся
CR + LF, CRПривести к LF или заменить на пробелЗависит от поля
Мягкий перенос (173)УдалитьНе несёт смысла
Нулевая ширина, BOMУдалитьНе несут смысла
Прочие управляющие (0–31, 127)УдалитьЛомают XML и печать

Универсальная функция очистки

Функция ОчиститьСтроку(Знач Стр, СохранятьПереносы = Ложь) Экспорт

	Стр = СтрЗаменить(Стр, Символы.ВК + Символы.ПС, Символы.ПС);
	Стр = СтрЗаменить(Стр, Символы.ВК, Символы.ПС);
	Стр = СтрЗаменить(Стр, Символы.НПП, " ");
	Стр = СтрЗаменить(Стр, Символы.Таб, " ");

	Части = Новый Массив;
	Для Позиция = 1 По СтрДлина(Стр) Цикл
		Код = КодСимвола(Стр, Позиция);
		Если Код = 10 Тогда
			Части.Добавить(?(СохранятьПереносы, Символы.ПС, " "));
		ИначеЕсли Код < 32 Или Код = 127 Или Код = 173
			Или (Код >= 8203 И Код <= 8207) Или Код = 65279 Тогда
			Продолжить;
		Иначе
			Части.Добавить(Сред(Стр, Позиция, 1));
		КонецЕсли;
	КонецЦикла;
	Стр = СтрСоединить(Части, "");

	Пока СтрНайти(Стр, "  ") > 0 Цикл
		Стр = СтрЗаменить(Стр, "  ", " ");
	КонецЦикла;

	Возврат СокрЛП(Стр);

КонецФункции

Сборка через массив и СтрСоединить() заметно быстрее, чем Результат = Результат + Символ в цикле: на длинных строках конкатенация каждый раз создаёт новую строку.

Для ИНН, КПП, БИК и номеров счетов правило жёстче — оставьте только цифры:

Функция ТолькоЦифры(Стр) Экспорт

	Части = Новый Массив;
	Для Позиция = 1 По СтрДлина(Стр) Цикл
		Код = КодСимвола(Стр, Позиция);
		Если Код >= 48 И Код <= 57 Тогда
			Части.Добавить(Сред(Стр, Позиция, 1));
		КонецЕсли;
	КонецЦикла;

	Возврат СтрСоединить(Части, "");

КонецФункции

Коды 48–57 — это цифры от 0 до 9. Строка «77 01 234567» превратится в «7701234567».

Массовая чистка уже записанных данных

Если невидимые символы уже в справочнике, исправляйте их обработкой: запрос из предыдущего раздела находит элементы, функция очистки готовит новое значение, объект записывается.

Перед массовой записью сделайте резервную копию базы. Запись элемента справочника запускает его обработчики: может измениться дата версии, сработать регистрация к обмену и проверка на дубли. Сначала прогоните обработку на копии и сравните результат на 10–20 элементах.

Не меняйте «на лету» реквизиты, по которым идёт сопоставление в обмене (ИНН, артикул, код). Если в другой базе значение осталось «грязным», после очистки объекты перестанут сопоставляться, и в обмене появятся дубли. Чистите обе стороны согласованно.

Как увидеть невидимый символ при отладке

Самый быстрый способ диагностики — отладчик Конфигуратора или EDT. Поставьте точку останова там, где строка уже получена, и откройте окно вычисления выражения (в Конфигураторе — Shift+F9).

Полезные выражения для этого окна:

  • СтрДлина(Стр) — если длина больше видимого числа символов, внутри есть «невидимка»;
  • КодСимвола(Стр, 5) — код конкретной позиции;
  • СтрЗаменить(СтрЗаменить(Стр, Символы.НПП, "[НПП]"), Символы.Таб, "[ТАБ]") — подсветка символов маркерами;
  • КодыСимволов(Стр) — если функция из общего модуля доступна в этом контексте.

Без отладчика то же самое показывает Сообщить() с маркерами. Выведите строку в квадратных скобках: Сообщить("[" + Стр + "]") — пробелы и переносы на краях сразу станут видны.

Диагностика: выберите симптом

Частые вопросы

Убирает ли СокрЛП переносы строк и табуляцию?
Только по краям строки. Внутри строки они остаются. Для середины строки нужны СтрЗаменить() или цикл по кодам.
Как вставить перенос строки в текст сообщения или запроса?
Склеить через Символы.ПС: «Строка 1» + Символы.ПС + «Строка 2». В тексте запроса для переноса используется символ | в начале продолжения строки.
Чем Символы.НПП отличается от обычного пробела?
Это другой символ с кодом 160. Визуально он выглядит как пробел, но СтрЗаменить(Стр, " “, “”) его не удаляет, а сравнение строк считает их разными.
Можно ли запретить ввод спецсимволов в поле формы?
Да. В обработчике ПриИзменении поля вызовите функцию очистки и запишите результат обратно в реквизит. Для ИНН и кодов лучше сразу оставлять только цифры.
Почему в файле, выгруженном из 1С, первый символ «невидимый»?
Скорее всего, это метка BOM (код 65279): при записи текста в кодировке UTF-8 её обычно добавляют в начало файла. Если получатель её не понимает, удалите символ при чтении или выгружайте без BOM — как именно, зависит от объекта записи, сверьте его параметры в синтакс-помощнике.