Как корректно импортировать числа с неразрывными пробелами из Excel в 1С?

Программист 1С v8.3 (Управляемые формы)
← На главную

В процессе интеграции данных из внешних источников, таких как Microsoft Excel, в информационную систему 1С:Предприятие, разработчики часто сталкиваются с неочевидными, но критически важными нюансами. Одна из таких распространенных проблем возникает при попытке прочитать числовые значения из ячеек Excel, которые содержат разделители групп разрядов. Вместо ожидаемого числа, например, 1002, 1С может получать строку "1 002", где вместо обычного пробела используется неразрывный пробел (НПП). Эта ситуация приводит к тому, что стандартные методы работы с числами, такие как преобразование строки в число с помощью функции Число() или поиск по числовому реквизиту с помощью метода НайтиПоРеквизиту, перестают работать корректно. Данная проблема может серьезно повлиять на целостность импортируемых данных и автоматизацию процессов.

Давайте вместе рассмотрим эту проблему максимально подробно и найдем эффективные способы ее решения, опираясь на опыт сообщества 1С, а также расширенные подходы к обработке данных.

Подробный анализ возникновения проблемы: Неразрывные пробелы и чтение из Excel

Начнем с того, что выясним, почему возникает данная ситуация, когда число 1002 при чтении из Excel превращается в строку "1 002", и почему это мешает дальнейшей работе. Основной причиной является использование неразрывных пробелов (НПП), которые в 1С представлены как Символы.НПП, а по коду символа соответствуют Символ(160). Эти пробелы часто используются в типографике и форматировании для разделения групп разрядов в больших числах, чтобы число целиком воспринималось как единое целое и не переносилось на новую строку. Например, число "один миллион" часто отображается как "1 000 000".

Когда мы считываем содержимое ячейки Excel с помощью конструкции типа:


РВ.Колонка3 = ТабДок.ПолучитьОбласть("R" + Формат(Номер, "ЧГ=0") + "C" + 3).ТекущаяОбласть.Текст;

мы получаем строковое представление содержимого ячейки Excel, а не ее числовое значение. Если Excel отформатировал число в ячейке с использованием неразрывных пробелов (что весьма распространено, особенно при региональных настройках, использующих пробел как разделитель групп), эти пробелы будут включены в получаемую строку. Таким образом, вместо чистого "1002" мы получаем "1 002".

Далее, когда мы пытаемся использовать эту строку, например, присвоить ее числовому реквизиту объекта 1С или выполнить поиск по числовому полю (как в случае с НайтиПоРеквизиту), система 1С сталкивается с проблемой. Функция Число(), отвечающая за преобразование строки в число, а также внутренние механизмы 1С для сравнения чисел, не могут автоматически преобразовать строку "1 002" в число 1002, потому что она содержит неожидаемый, нечисловой символ (неразрывный пробел). В результате, поиск не срабатывает, или возникают ошибки преобразования типов. Именно это и вызвало вопросительный знак "???" в сообщении автора темы, символизирующий недоумение по поводу некорректного поведения системы.

Первое решение: Прямое получение числового значения из Excel

Прежде чем приступать к манипуляциям со строками, рассмотрим самый простой и часто наиболее предпочтительный подход: попытаться получить числовое значение ячейки напрямую, а не ее текстовое представление. Объекты 1С для работы с внешними табличными документами (например, через COM-объект Excel или типовые обработки импорта) часто предоставляют доступ не только к форматированному тексту ячейки, но и к ее базовому значению.

Вместо использования свойства .Текст, которое возвращает форматированное строковое представление содержимого ячейки, мы можем попытаться использовать свойство .Значение. Это свойство предназначено для возврата данных в их "сыром" виде, то есть, если Excel хранит в ячейке число, то .Значение должно вернуть 1С-число, дату как 1С-дату и так далее, при условии, что Excel правильно определил тип данных в ячейке. Если ячейка в Excel действительно имела числовой формат и содержала число, то .Значение, скорее всего, вернет его без всяких неразрывных пробелов.

Преимущества этого подхода:

  1. Чистота данных: Мы получаем число сразу в числовом формате, минуя этап строковых преобразований и связанных с ними рисков (например, ошибок при очистке).
  2. Меньше кода: Не требуется дополнительная логика для очистки строки.
  3. Устойчивость к локализации: Меньше зависимость от региональных настроек форматирования чисел.

Недостатки:

  1. Зависимость от формата Excel: Работает только в том случае, если Excel действительно хранит данные как числа. Если ячейка в Excel была отформатирована как "Текстовый" формат, даже если в ней содержатся только цифры, .Значение может вернуть строку.
  2. Возможные ошибки: Чтение через COM-объект иногда может вызывать исключения, поэтому важно использовать блок Попытка...Исключение.

Посмотрим на пример реализации:


Для Номер = 1 По КоличествоНомеров Цикл
    РВ = Объект.ТЧРВ.Добавить();
    Попытка
        // Попытаемся получить значение ячейки напрямую как число
        ЗначениеЯчейки = ТабДок.ПолучитьОбласть("R" + Формат(Номер, "ЧГ=0") + "C" + 3).ТекущаяОбласть.Значение;

        Если ТипЗнч(ЗначениеЯчейки) = Тип("Число") Тогда
            // Если успешно получили число, присваиваем его реквизиту
            РВ.Колонка3 = ЗначениеЯчейки;
        Иначе
            // Если получили что-то другое (например, строку, дату или пусто),
            // это может быть проблемой или требовать дальнейшей обработки.
            // Например, здесь можно перейти к альтернативному методу (Решение 2)
            // или предпринять другие действия по обработке ошибки.
            Сообщить("В строке " + Номер + " колонке 3 ожидалось число, но получено значение типа: " + ТипЗнч(ЗначениеЯчейки) + ". Значение: " + Строка(ЗначениеЯчейки));
            // Пример перехода к Решению 2:
            // СтрокаДляОбработки = ТабДок.ПолучитьОбласть("R" + Формат(Номер, "ЧГ=0") + "C" + 3).ТекущаяОбласть.Текст;
            // ЧисловоеЗначениеИзСтроки = ОбработатьЧисловуюСтроку(СтрокаДляОбработки); // предполагаемая функция из Решения 2
            // РВ.Колонка3 = ЧисловоеЗначениеИзСтроки;
        КонецЕсли;
    Исключение
        // Обработка ошибок, которые могут возникнуть при попытке чтения значения ячейки
        Сообщить("Ошибка при чтении значения ячейки в строке " + Номер + ": " + ОписаниеОшибки());
        // Можно установить значение по умолчанию или пропустить строку
        РВ.Колонка3 = 0;
    КонецПопытки;
КонецЦикла;

Мы настоятельно рекомендуем использовать блок Попытка...Исключение даже при работе со свойством .Значение. Чтение данных из внешних COM-объектов (таких как Excel) иногда может вызывать непредвиденные ошибки, особенно если файл поврежден, ячейка содержит специфический контент или отсутствует доступ. Такой подход делает ваш код более устойчивым и надежным.

Второе решение: Очистка строкового представления от неразрывных пробелов и преобразование

Если прямое чтение значения (свойство .Значение) не дает желаемого результата (например, если Excel хранит числа как текст или возвращает некорректный тип), или если мы хотим более универсальный подход, то нам потребуется вручную очистить строку от нежелательных символов перед преобразованием ее в число. Это наиболее универсальный и часто используемый метод, так как он позволяет контролировать процесс очистки.

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

Пошаговая инструкция по очистке и преобразованию

  1. Считывание форматированной строки: Сначала мы получаем текст ячейки с помощью свойства .Текст. Этот шаг позволяет нам получить строку "как есть", со всеми форматирующими символами, включая неразрывные пробелы.
  2. Идентификация и удаление неразрывных пробелов: Мы используем функцию СтрЗаменить(), чтобы найти все неразрывные пробелы (Символы.НПП или Символ(160)) в полученной строке и заменить их на пустую строку "". Это эффективно удаляет их из числового представления.
  3. Преобразование очищенной строки в число: После того как строка очищена от всех нечисловых разделителей, мы используем функцию Число() для преобразования ее в числовой тип. Этот шаг также требует особого внимания к возможным ошибкам, поэтому мы рекомендуем использовать блок Попытка...Исключение.

Давайте посмотрим на пример кода, реализующего этот подход, с учетом всех необходимых проверок:


Для Номер = 1 По КоличествоНомеров Цикл
    РВ = Объект.ТЧРВ.Добавить();
    // Шаг 1: Считываем строковое представление из Excel, которое может содержать неразрывные пробелы
    СтрокаСЧисломИзExcel = ТабДок.ПолучитьОбласть("R" + Формат(Номер, "ЧГ=0") + "C" + 3).ТекущаяОбласть.Текст;

    // Очень важно проверить строку на пустоту перед дальнейшей обработкой
    // Пустая строка не может быть преобразована в число и вызовет ошибку
    Если ПустаяСтрока(СтрокаСЧисломИзExcel) Тогда
        Сообщить("Ячейка в строке " + Номер + " колонке 3 пуста или содержит пустое строковое представление. Устанавливаем 0.");
        РВ.Колонка3 = 0; // Устанавливаем 0, или можно пропустить строку, или присвоить другое значение по умолчанию
        Продолжить; // Переходим к следующей итерации цикла
    КонецЕсли;

    // Шаг 2: Удаляем неразрывные пробелы из строки
    // Символы.НПП - это стандартная константа 1С для неразрывного пробела.
    // Это наиболее корректный способ, как было предложено в обсуждении.
    СтрокаБезНПП = СтрЗаменить(СтрокаСЧисломИзExcel, Символы.НПП, "");

    // Дополнительно: Если есть вероятность наличия обычных пробелов (Символ(32)),
    // которые также могут мешать преобразованию, их тоже можно удалить:
    // СтрокаБезНПП = СтрЗаменить(СтрокаБезНПП, " ", "");

    // Шаг 3: Преобразуем очищенную строку в число
    // Используем блок Попытка...Исключение для надежной обработки возможных ошибок преобразования
    Попытка
        ЧисловоеЗначение = Число(СтрокаБезНПП);
        РВ.Колонка3 = ЧисловоеЗначение;
    Исключение
        // Обработка ошибок при преобразовании:
        // Например, если строка все еще содержит нечисловые символы,
        // или если после очистки она стала пустой (хотя мы уже проверили на ПустаяСтрока)
        Сообщить("Ошибка при преобразовании строки '" + СтрокаСЧисломИзExcel + "' (очищенная: '" + СтрокаБезНПП + "') в число в строке " + Номер + ". Подробности: " + ОписаниеОшибки());
        РВ.Колонка3 = 0; // В случае ошибки устанавливаем 0 или другое значение по умолчанию, чтобы избежать прерывания работы
    КонецПопытки;
КонецЦикла;

Важный момент: Мы обернули преобразование в Число() в блок Попытка...Исключение. Это позволяет корректно обрабатывать ситуации, когда строка после очистки все еще не является числом (например, если там были другие нечисловые символы, не учтенные в СтрЗаменить(), или если ячейка изначально содержала текст, который не имеет числового эквивалента). Без этого блока, любая ошибка преобразования приведет к остановке выполнения кода, что недопустимо для массового импорта данных. Функция Число() требует, чтобы строка была абсолютно "чистой" – состояла только из цифр, возможно, знака минуса и десятичного разделителя.

Сопутствующие проблемы и расширенные подходы к очистке

Хотя удаление неразрывного пробела является основной задачей, при работе с импортом данных из Excel могут возникнуть и другие нюансы, требующие более комплексного подхода к очистке строк.

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

Помимо неразрывных пробелов, в строке могут присутствовать обычные пробелы (Символ(32)), табуляции (Символы.Таб или Символ(9)), символы валют (например, "руб.", "$"), скобки, текстовые комментарии и другие знаки, которые помешают корректному преобразованию в число. В таких случаях может потребоваться более универсальная функция для очистки строки. Такая функция должна удалять все символы, которые не являются цифрами, знаком минуса или разрешенным десятичным разделителем.

Рассмотрим пример такой универсальной функции, которая поможет подготовить строку для функции Число():


// Универсальная функция для очистки строки от всех нечисловых символов,
// кроме цифр, знака минуса и заданного десятичного разделителя.
// Десятичный разделитель может быть точкой или запятой, в зависимости от локали.
Функция ОчиститьЧисловуюСтроку(ИсходнаяСтрока, ДесятичныйРазделитель = ".") Экспорт
    Если ТипЗнч(ИсходнаяСтрока) <> Тип("Строка") Или ПустаяСтрока(ИсходнаяСтрока) Тогда
        Возврат "";
    КонецЕсли;

    // Сначала удаляем все известные виды пробелов: неразрывные, обычные, табуляции.
    СтрокаБезПробелов = СтрЗаменить(ИсходнаяСтрока, Символы.НПП, "");
    СтрокаБезПробелов = СтрЗаменить(СтрокаБезПробелов, " ", ""); // Обычный пробел
    СтрокаБезПробелов = СтрЗаменить(СтрокаБезПробелов, Символы.Таб, ""); // Символ табуляции

    Результат = "";
    // Формируем список допустимых символов: цифры и десятичный разделитель.
    СписокДопустимыхСимволов = "0123456789" + ДесятичныйРазделитель;

    // Проверяем наличие знака минуса в начале строки для отрицательных чисел.
    // Знак минуса должен быть только один и в самом начале.
    Если Лев(СтрокаБезПробелов, 1) = "-" Тогда
        Результат = "-";
        СтрокаБезПробелов = Сред(СтрокаБезПробелов, 2); // Отсекаем первый символ
    КонецЕсли;

    // Проходим по оставшейся части строки и добавляем только допустимые символы.
    Для Каждого Символ Из СтрокаБезПробелов Цикл
        Если Найти(СписокДопустимыхСимволов, Символ) > 0 Тогда
            Результат = Результат + Символ;
        КонецЕсли;
    КонецЦикЛа;

    // Если десятичный разделитель в исходной строке был запятой,
    // а для Число() в 1С требуется точка (что часто бывает по умолчанию),
    // выполняем замену.
    Если ДесятичныйРазделитель = "," Тогда
        Результат = СтрЗаменить(Результат, ",", ".");
    КонецЕсли;

    Возврат Результат;
КонецФункции

// Пример использования универсальной функции в цикле импорта:
// Для Номер = 1 По КоличествоНомеров Цикл
//     РВ = Объект.ТЧРВ.Добавить();
//     СтрокаСЧисломИзExcel = ТабДок.ПолучитьОбласть("R" + Формат(Номер, "ЧГ=0") + "C" + 3).ТекущаяОбласть.Текст;
//
//     // Очищаем строку, предполагая, что в Excel десятичный разделитель - запятая
//     ОчищеннаяСтрока = ОчиститьЧисловуюСтроку(СтрокаСЧисломИзExcel, ",");
//
//     Попытка
//         ЧисловоеЗначение = Число(ОчищеннаяСтрока);
//         РВ.Колонка3 = ЧисловоеЗначение;
//     Исключение
//         Сообщить("Ошибка преобразования очищенной строки '" + ОчищеннаяСтрока + "' в число в строке " + Номер + ". " + ОписаниеОшибки());
//         РВ.Колонка3 = 0;
//     КонецПопытки;
// КонецЦикла;

Использование такой функции делает процесс импорта значительно более устойчивым к разнообразным форматам чисел в исходном файле Excel. Важно уделить особое внимание десятичному разделителю. В разных региональных установках это может быть как точка (.), так и запятая (,). Убедитесь, что ваш код обрабатывает нужный разделитель или приводит его к единообразному виду, который понимает функция Число().

Влияние региональных настроек

Преобразование чисел в строку и обратно в 1С 8.3 "Предприятии" по умолчанию выполняется в соответствии с национальными (региональными) установками информационной базы. Эти настройки можно просмотреть и изменить в Конфигураторе, перейдя в раздел "Администрирование" – "Региональные установки информационной базы". Здесь можно настроить параметры форматирования чисел, включая группировку разрядов и символ десятичного разделителя. Если региональные настройки 1С отличаются от тех, что используются в Excel, это может вызвать дополнительные проблемы. Мы рекомендуем обеспечить согласованность настроек или явно обрабатывать возможные различия в коде (как показано в функции ОчиститьЧисловуюСтроку).

Функция Формат() и ее назначение

В одном из сообщений форума упоминалась функция Формат(Значение, "ЧГ="). Важно понимать, что эта функция используется для форматирования уже существующего числа в строку без разделителей групп, то есть она берет *уже имеющееся числовое значение* и представляет его в виде строки без неразрывных пробелов. Она не предназначена для *очистки входящей строки*, которая содержит НПП, чтобы затем преобразовать ее в число. Это функция для вывода, а не для парсинга.

Рассмотрим пример использования Формат():


ИсходноеЧисло = 1234567.89;

// Форматирование числа в строку с разделителями групп (например, для вывода в отчет)
// Результат будет зависеть от региональных настроек, например: "1 234 567,89" или "1,234,567.89"
СтрокаСГруппировкой = Формат(ИсходноеЧисло, "ЧГ=Истина"); 
Сообщить("С группировкой: " + СтрокаСГруппировкой);

// Форматирование числа в строку без разделителей групп (например, для записи в базу данных или XML)
// Результат: "1234567,89" или "1234567.89"
СтрокаБезГруппировки = Формат(ИсходноеЧисло, "ЧГ="); 
Сообщить("Без группировки: " + СтрокаБезГруппировки);

// Как видим, Формат() работает с уже числовым значением.
// Для очистки входной строки "1 002" из Excel он не подходит.

Таким образом, Формат(..., "ЧГ=") полезна, если у вас уже есть число и вы хотите его отобразить или сохранить без разделителей, но она не поможет решить проблему с неразрывными пробелами во входящих строках, полученных из Excel.

Итак, мы видим, что проблема с неразрывными пробелами при импорте чисел из Excel в 1С вполне решаема. Мы можем либо попытаться прочитать числовое значение напрямую с помощью свойства .Значение, либо, что чаще всего требуется для обеспечения максимальной надежности, очистить строковое представление ячейки от неразрывных пробелов с помощью СтрЗаменить(Строка, Символы.НПП, "") (и, возможно, других символов), а затем преобразовать ее в число с помощью Число(). Всегда помните о необходимости обработки возможных ошибок при преобразовании типов с помощью блока Попытка...Исключение, чтобы сделать ваш код максимально устойчивым и функциональным. Тестируйте ваши импортные рутины с разнообразными данными для обеспечения их надежности.

← На главную