Загрузка данных из внешних источников, в частности из XML-файлов, — одна из стандартных задач для программиста 1С. Для переноса данных между идентичными конфигурациями часто используют универсальную выгрузку и загрузку данных XML с дополнительными опциями, однако импорт курсов валют требует написания собственной логики обработки. Рассмотрим на конкретном примере, с какими сложностями можно столкнуться и как написать надежный и эффективный код. Проанализируем типичные ошибки начинающих разработчиков и разберем готовое, рабочее решение.
Давайте начнем с анализа исходного кода, который был предложен в вопросе. На его примере можно увидеть несколько распространенных ошибок. Также для чистоты кода и соблюдения стандартов 1С полезно использовать Code formatter для анализа и редактирования модулей.
Вот фрагмент кода, который мы будем анализировать:
// ...
Пока ЧтениеXML.Прочитать() Цикл
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.Имя = "NumCode" Тогда
КодВалюты = Справочник.НайтиПоКоду("840");
НаборЗаписей = РегистрСведений.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Валюта.Установить(КодВалюты.Ссылка);
НаборЗаписей.Прочитать();
НовыйКурс = НаборЗаписей.Добавить();
НовыйКурс.Период = Дата;
НовыйКурс.Валюта = КодВалюты.Ссылка;
Если ЧтениеXML.ТипУзла = ТипУзлаXML.Текст Тогда
ЧтениеXML.Прочитать();
НаборЗаписей.Прочитать(); // Ошибка!
НовыйКурс = НаборЗаписей.Добавить();
НовыйКурс.Курс = XmlЗначение(Тип("Число"),ЧтениеXML.Значение);
// ...
КонецЕсли;
НаборЗаписей.Записать(РежимЗамещения.Замещение);
КонецЕсли;
КонецЦикла;
Разберем основные проблемы этого подхода:
ЧтениеXML работает последовательно. Код пытается обработать все данные (код, курс) внутри одного блока Если, который срабатывает только при нахождении тега <NumCode>. Данные о курсе (тег <Value>) находятся в другом месте файла, и такая логика их просто не найдет.КодВалюты = Справочник.НайтиПоКоду("840"); означает, что код будет пытаться загрузить все курсы для одной и той же валюты (Доллар США), игнорируя реальные данные из XML. Код валюты нужно считывать из файла..Ссылка. Метод НайтиПоКоду() уже возвращает ссылку на элемент справочника. Поэтому в строках НаборЗаписей.Отбор.Валюта.Установить(КодВалюты.Ссылка); и НовыйКурс.Валюта = КодВалюты.Ссылка; свойство .Ссылка является лишним. Нужно использовать просто КодВалюты.НаборЗаписей.Прочитать(). Этот метод вызывается дважды. Первый раз он корректно читает существующие записи. Но второй вызов НаборЗаписей.Прочитать() внутри вложенного условия перезатирает набор записей, который был в памяти. Это значит, что запись, добавленная строкой НовыйКурс = НаборЗаписей.Добавить(); перед этим, будет утеряна. Это принципиальное отличие от простой выгрузки и загрузки движений документа XML, где наборы данных обычно переносятся целиком и корректно.КодВалюты будет содержать пустую ссылку, что может привести к ошибкам при записи в регистр.Как видим, последовательное чтение XML требует очень аккуратной и сложной логики для сбора данных по одной валюте из разных узлов файла. Существует более простой и надежный способ.
Еще одна типичная проблема, которая возникла в ходе обсуждения, — преобразование текстового значения курса в число. В XML-файле курс валюты часто представлен в виде строки с запятой в качестве десятичного разделителя, например, "51,7307".
Функция Число() в 1С по умолчанию ожидает точку в качестве разделителя. При попытке преобразовать строку с запятой напрямую произойдет ошибка.
Решение очень простое: перед преобразованием необходимо заменить запятую на точку с помощью функции СтрЗаменить().
ЗначениеИзXML = "51,7307"; // Тип Строка
СтрокаДляПреобразования = СтрЗаменить(ЗначениеИзXML, ",", "."); // Получим "51.7307"
КурсЧисло = Число(СтрокаДляПреобразования); // Успешное преобразование
Для повышения надежности кода, особенно при работе с внешними данными, которые могут быть некорректными, всю операцию преобразования рекомендуется оборачивать в блок Попытка...Исключение. Это позволит избежать аварийного завершения всей загрузки, если в файле попадется нечисловое значение.
Наиболее современным и удобным подходом для работы с 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));
Возврат Дата(Г, М, Д);
КонецФункции
Проанализируем этот код по шагам:
ЧтениеXML.ФабрикаXDTO. Ключевая строка ФабрикаXDTO.ПрочитатьXML(Чтение) выполняет всю "магию". Она парсит файл и создает объект Данные, структура которого повторяет структуру XML.Date корневого элемента. Мы легко получаем ее через Данные.Date.Valute. XDTO представляет их в виде коллекции, по которой можно удобно пройтись циклом Для Каждого.МенеджерЗаписи. Внутри цикла для каждой валюты создается свой МенеджерЗаписи — это оптимальный способ для добавления одной записи.ЭлементВалюты.NumCode, ЭлементВалюты.Value, ЭлементВалюты.Nominal. Это наглядно и просто.Запись.Записать() выполняется проверка, что валюта была найдена в справочнике и другие поля заполнены. Это защищает от ошибок во время выполнения и позволяет выводить осмысленные сообщения об ошибках.Этот подход не только короче и проще для понимания, но и гораздо более надежен и легко адаптируется под изменение структуры XML-файла. Он является рекомендуемой практикой для решения подобных задач в 1С.