Как правильно загрузить курсы валют из XML-файла в 1С?

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

Загрузка данных из внешних источников, в частности из XML-файлов, — одна из стандартных задач для программиста 1С. Для переноса данных между идентичными конфигурациями часто используют универсальную выгрузку и загрузку данных XML с дополнительными опциями, однако импорт курсов валют требует написания собственной логики обработки. Рассмотрим на конкретном примере, с какими сложностями можно столкнуться и как написать надежный и эффективный код. Проанализируем типичные ошибки начинающих разработчиков и разберем готовое, рабочее решение.

Анализ ошибок в первоначальном коде

Давайте начнем с анализа исходного кода, который был предложен в вопросе. На его примере можно увидеть несколько распространенных ошибок. Также для чистоты кода и соблюдения стандартов 1С полезно использовать Code formatter для анализа и редактирования модулей.

Вот фрагмент кода, который мы будем анализировать:


// ...
Пока ЧтениеXML.Прочитать() Цикл  
            
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.Имя = "NumCode" Тогда    
    КодВалюты = Справочник.НайтиПоКоду("840"); 
    НаборЗаписей = РегистрСведений.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Валюта.Установить(КодВалюты.Ссылка);
    НаборЗаписей.Прочитать();
    НовыйКурс = НаборЗаписей.Добавить();
    НовыйКурс.Период = Дата;
    НовыйКурс.Валюта = КодВалюты.Ссылка;  
                
    Если ЧтениеXML.ТипУзла = ТипУзлаXML.Текст Тогда 
        ЧтениеXML.Прочитать();
        НаборЗаписей.Прочитать(); // Ошибка!
        НовыйКурс = НаборЗаписей.Добавить(); 
        НовыйКурс.Курс = XmlЗначение(Тип("Число"),ЧтениеXML.Значение);
    // ...
    КонецЕсли;
    НаборЗаписей.Записать(РежимЗамещения.Замещение);
КонецЕсли;                
            
КонецЦикла;

Разберем основные проблемы этого подхода:

  1. Неверная логика чтения XML. Метод ЧтениеXML работает последовательно. Код пытается обработать все данные (код, курс) внутри одного блока Если, который срабатывает только при нахождении тега <NumCode>. Данные о курсе (тег <Value>) находятся в другом месте файла, и такая логика их просто не найдет.
  2. Жестко заданный код валюты. Строка КодВалюты = Справочник.НайтиПоКоду("840"); означает, что код будет пытаться загрузить все курсы для одной и той же валюты (Доллар США), игнорируя реальные данные из XML. Код валюты нужно считывать из файла.
  3. Избыточное использование .Ссылка. Метод НайтиПоКоду() уже возвращает ссылку на элемент справочника. Поэтому в строках НаборЗаписей.Отбор.Валюта.Установить(КодВалюты.Ссылка); и НовыйКурс.Валюта = КодВалюты.Ссылка; свойство .Ссылка является лишним. Нужно использовать просто КодВалюты.
  4. Критическая ошибка с НаборЗаписей.Прочитать(). Этот метод вызывается дважды. Первый раз он корректно читает существующие записи. Но второй вызов НаборЗаписей.Прочитать() внутри вложенного условия перезатирает набор записей, который был в памяти. Это значит, что запись, добавленная строкой НовыйКурс = НаборЗаписей.Добавить(); перед этим, будет утеряна. Это принципиальное отличие от простой выгрузки и загрузки движений документа XML, где наборы данных обычно переносятся целиком и корректно.
  5. Отсутствие проверки найденной валюты. Если валюты с кодом "840" не окажется в справочнике, переменная КодВалюты будет содержать пустую ссылку, что может привести к ошибкам при записи в регистр.

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

Проблема преобразования строки в число

Еще одна типичная проблема, которая возникла в ходе обсуждения, — преобразование текстового значения курса в число. В XML-файле курс валюты часто представлен в виде строки с запятой в качестве десятичного разделителя, например, "51,7307".

Функция Число() в 1С по умолчанию ожидает точку в качестве разделителя. При попытке преобразовать строку с запятой напрямую произойдет ошибка.

Решение очень простое: перед преобразованием необходимо заменить запятую на точку с помощью функции СтрЗаменить().


ЗначениеИзXML = "51,7307"; // Тип Строка
СтрокаДляПреобразования = СтрЗаменить(ЗначениеИзXML, ",", "."); // Получим "51.7307"
КурсЧисло = Число(СтрокаДляПреобразования); // Успешное преобразование

Для повышения надежности кода, особенно при работе с внешними данными, которые могут быть некорректными, всю операцию преобразования рекомендуется оборачивать в блок Попытка...Исключение. Это позволит избежать аварийного завершения всей загрузки, если в файле попадется нечисловое значение.

Итоговое решение с использованием XDTO

Наиболее современным и удобным подходом для работы с XML-файлами известной структуры является использование механизма XDTO (XML Data Transfer Objects). Вместо последовательного чтения узлов, этот метод загружает весь XML-документ в память в виде структуры объектов 1С. В сложных интеграциях может потребоваться предварительная конвертация схемы XSD в модель XDTO, но для простых файлов структура определяется автоматически.

Также для записи одиночных записей в регистр сведений более производительным и правильным является МенеджерЗаписи, а не НаборЗаписей.

Рассмотрим итоговый, рабочий код, который решает поставленную задачу эффективно и надежно.

Сначала посмотрим на структуру XML-файла, с которым мы работаем.


<ValCurs Date="02.03.2002" name="Foreign Currency Market">
	<Valute ID="R01235">
		<NumCode>840</NumCode>
		<CharCode>USD</CharCode>
		<Nominal>1</Nominal>
		<Name>Доллар США</Name>
		<Value>30,9436</Value>
	</Valute>
    <!-- ... другие валюты ... -->
</ValCurs>

А вот и сам код процедуры загрузки:


Процедура ЗагрузитьКурсы()
	
    Путь = "C:\ПутьКФайлу\rates.xml"; // Укажите свой путь к файлу
    Чтение = Новый ЧтениеXML;
    Чтение.ОткрытьФайл(Путь);
	
    // Считываем весь XML в объект XDTO
    Данные = ФабрикаXDTO.ПрочитатьXML(Чтение); 
    Чтение.Закрыть();
	
    // Получаем дату курсов из атрибута корневого элемента
    ДатаКурсов  = СтрокуВДату(Данные.Date);
	
    // Обходим коллекцию валют
    Для Каждого ЭлементВалюты Из Данные.Valute Цикл
        
        // Создаем менеджер для записи одной записи в регистр
        Запись = РегистрыСведений.КурсыВалют.СоздатьМенеджерЗаписи();
        Запись.Период = ДатаКурсов;
        
        // Ищем валюту в справочнике по коду из XML
        Запись.Валюта = Справочники.Валюты.НайтиПоКоду(ЭлементВалюты.NumCode);
        
        // Преобразуем курс, заменяя запятую на точку
        Запись.Курс = Число(СтрЗаменить(ЭлементВалюты.Value,",","."));
        
        // Устанавливаем кратность из тега Nominal
        Запись.Кратность = Число(ЭлементВалюты.Nominal);
		
        // Проверяем, что все ключевые данные заполнены, прежде чем записывать
        Если ЗначениеЗаполнено(Запись.Валюта) 
            И ЗначениеЗаполнено(Запись.Период) 
            И ЗначениеЗаполнено(Запись.Курс) Тогда

            Запись.Записать();
			
        Иначе
            Сообщить("Не удалось загрузить курс для валюты с кодом " + ЭлементВалюты.NumCode + ". Возможно, она отсутствует в справочнике.");
        КонецЕсли; 
        
    КонецЦикла; 
		
КонецПроцедуры

// Вспомогательная функция для преобразования даты из строки формата "ДД.ММ.ГГГГ"
Функция СтрокуВДату(ДатаСтрокой)
    Д = Число(Сред(ДатаСтрокой, 1, 2));
    М = Число(Сред(ДатаСтрокой, 4, 2));
    Г = Число(Сред(ДатаСтрокой, 7, 4));
    Возврат Дата(Г, М, Д);    
КонецФункции

Проанализируем этот код по шагам:

  1. Чтение файла. Сначала мы открываем XML-файл с помощью объекта ЧтениеXML.
  2. Использование ФабрикаXDTO. Ключевая строка ФабрикаXDTO.ПрочитатьXML(Чтение) выполняет всю "магию". Она парсит файл и создает объект Данные, структура которого повторяет структуру XML.
  3. Получение даты. Дата курсов находится в атрибуте Date корневого элемента. Мы легко получаем ее через Данные.Date.
  4. Цикл по валютам. В XML есть повторяющийся элемент Valute. XDTO представляет их в виде коллекции, по которой можно удобно пройтись циклом Для Каждого.
  5. Использование МенеджерЗаписи. Внутри цикла для каждой валюты создается свой МенеджерЗаписи — это оптимальный способ для добавления одной записи.
  6. Заполнение полей. Мы обращаемся к данным каждой валюты как к свойствам объекта: ЭлементВалюты.NumCode, ЭлементВалюты.Value, ЭлементВалюты.Nominal. Это наглядно и просто.
  7. Надежная запись. Перед вызовом метода Запись.Записать() выполняется проверка, что валюта была найдена в справочнике и другие поля заполнены. Это защищает от ошибок во время выполнения и позволяет выводить осмысленные сообщения об ошибках.

Этот подход не только короче и проще для понимания, но и гораздо более надежен и легко адаптируется под изменение структуры XML-файла. Он является рекомендуемой практикой для решения подобных задач в 1С.

← На главную