При интеграции 1С с внешними API, такими как выгрузки YML-прайсов от поставщиков, разработчики часто сталкиваются с двумя критическими проблемами. Первая — это получение пустого ответа или кода перенаправления 301 Moved Permanently вместо данных. Вторая — фатальная ошибка парсера EntityRef: expecting ';' при попытке прочитать полученный файл. В этой статье мы подробно разберем, как правильно настроить HTTP-соединение и «вылечить» битый XML перед его обработкой.
Рассмотрим типичную ситуацию: вы делаете запрос к серверу, используя стандартный объект HTTPСоединение, но сайт упорно возвращает код состояния 301. Это происходит потому, что современные веб-ресурсы принудительно переводят пользователей с протокола HTTP на HTTPS. Если в коде 1С не указано использование защищенного соединения, сервер просто сообщает вам адрес, по которому нужно «перейти», но сама платформа 1С автоматически по цепочке редиректов не следует.
Проанализируем правильный способ создания соединения. Для того чтобы успешно получить данные с адреса https://santrek.ru/api/yml, нам необходимо инициализировать ЗащищенноеСоединениеOpenSSL. Посмотрим на пример кода:
Сервер = "santrek.ru";
Ресурс = "/api/yml";
// Инициализируем защищенное соединение для работы по HTTPS
SSL = Новый ЗащищенноеСоединениеOpenSSL();
// Создаем соединение, указывая SSL
Соединение = Новый HTTPСоединение(Сервер, , , , , , SSL);
Запрос = Новый HTTPЗапрос(Ресурс);
Ответ = Соединение.Получить(Запрос);
Если Ответ.КодСостояния = 200 Тогда
// Обрабатываем результат
ТекстXML = Ответ.ПолучитьТелоКакСтроку();
Иначе
Сообщить("Ошибка запроса: " + Ответ.КодСостояния);
КонецЕсли;
Важный момент: использование объекта ЗащищенноеСоединениеOpenSSL без параметров заставляет 1С использовать сертификаты операционной системы. В большинстве случаев этого достаточно для работы с публичными API.
Выясним, как сделать наш запрос более надежным. Некоторые серверы блокируют запросы, если в них не указан User-Agent, или если они пытаются загрузить слишком объемные данные. Для работы с тяжелыми YML-файлами (прайс-листы могут весить сотни мегабайт) рекомендуется использовать потоковую запись на диск вместо загрузки в строку.
Разберем продвинутый пример запроса:
Запрос = Новый HTTPЗапрос(Ресурс);
// Имитируем браузер, чтобы сервер не отклонил запрос
Запрос.Заголовки.Вставить("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
// Запрашиваем сжатие, если платформа выше 8.3.10
Запрос.Заголовки.Вставить("Accept-Encoding", "gzip, deflate");
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xml");
// Сохраняем ответ сразу в файл, чтобы не перегружать оперативную память
Ответ = Соединение.Получить(Запрос, ИмяВременногоФайла);
Если Ответ.КодСостояния = 200 Тогда
// Файл успешно сохранен на диск
Иначе
Сообщить("Не удалось загрузить файл");
КонецЕсли;
Метод Соединение.Получить(Запрос, ИмяФайла) позволяет избежать ошибки Out of Memory, так как данные записываются непосредственно на жесткий диск, минуя переменную в оперативной памяти.
Часто после успешной загрузки файла метод ЧтениеXML.ОткрытьФайл() выдает ошибку: «Фатальная ошибка: EntityRef: expecting ';'». Посмотрим на причину: в XML-стандарте символ амперсанда & является служебным. Он начинает последовательность спецсимвола (мнемоники). Если в названии товара встречается текст типа «D&K», парсер ожидает увидеть после знака & код сущности и точку с запятой (например, &). Не найдя их, он прекращает работу.
Рассмотрим, как правильно очистить XML. Простая замена всех & на & — это плохая практика, так как если в файле уже были корректные последовательности (например, "), они превратятся в ", что также приведет к ошибке или порче данных.
Проанализируем ситуацию и применим более умный подход. Нам нужно заменить только те амперсанды, за которыми не следует стандартная мнемоника. Для этого идеально подходят регулярные выражения. Если вы работаете в 1С, можно воспользоваться объектом VBScript.RegExp или просто прочитать файл как текст и выполнить замену по логике.
Посмотрим на пример функции для «лечения» XML-строки:
Функция ИсправитьОшибкиXML(ТекстXML)
// Заменяем "одинокие" амперсанды, которые не являются частью сущностей
// Правильные сущности: & < > " '
// Сначала временно заменим уже правильные сущности на уникальные маркеры
Результат = СтрЗаменить(ТекстXML, "&", "___AMP___");
Результат = СтрЗаменить(Результат, "<", "___LT___");
Результат = СтрЗаменить(Результат, ">", "___GT___");
Результат = СтрЗаменить(Результат, """, "___QUOT___");
Результат = СтрЗаменить(Результат, "'", "___APOS___");
// Теперь все оставшиеся символы & - это ошибки, заменяем их на корректный код
Результат = СтрЗаменить(Результат, "&", "&");
// Возвращаем маркеры обратно
Результат = СтрЗаменить(Результат, "___AMP___", "&");
Результат = СтрЗаменить(Результат, "___LT___", "<");
Результат = СтрЗаменить(Результат, "___GT___", ">");
Результат = СтрЗаменить(Результат, "___QUOT___", """);
Результат = СтрЗаменить(Результат, "___APOS___", "'");
Возврат Результат;
КонецФункции
Для реализации надежной загрузки данных из API santrek.ru или любого другого YML-провайдера, рекомендуем придерживаться следующего алгоритма:
ЗащищенноеСоединениеOpenSSL для адресов https.Получить с указанием пути к файлу на диске. Это сэкономит память.ЧтениеТекста. Если в нем содержатся некорректные символы &, примените функцию очистки.ЧтениеXML.Пока ЧтениеXML.Прочитать() для последовательной обработки элементов offer.Проанализируем также необходимость удаления непечатных символов. В старых базах поставщиков могут встречаться символы с кодами ASCII 0–31 (кроме табуляции и переноса строк). Они также вызывают фатальные ошибки. Перед парсингом полезно очистить строку от таких символов, используя КодСимвола() в цикле или регулярное выражение [^\x09\x0A\x0D\x20-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}].
Соблюдение этих правил позволит вам создать отказоустойчивый механизм импорта данных, который не «упадет» при появлении в прайсе поставщика товара с необычным названием или спецсимволом.