Работа с HTML в 1С 8.3: загрузка, разбор и вывод на форму

· 9 мин

Содержание

HTML в 1С попадает тремя путями: лежит готовым файлом на диске, приходит ответом от стороннего сайта или сервиса по HTTP, либо хранится строкой в реквизите. Загрузить его означает одно из двух — либо прочитать как текст, чтобы разобрать и вытащить данные, либо отрисовать, чтобы человек увидел страницу внутри формы.

От задачи зависит объект платформы. Для текста хватит ТекстовыйДокумент или ЧтениеТекста. Чтобы сохранить разметку (жирный, таблицы, ссылки) — ФорматированныйДокумент с методом УстановитьHTML. Чтобы вытащить значения конкретных тегов — построитель DOM. Чтобы показать на экране — поле HTML-документа на управляемой форме. Ниже — рабочий код встроенного языка 8.3 под каждый сценарий.

Что именно нужно сделать с HTML?

Чтение HTML-файла в текстовую переменную

Самый частый случай — файл уже на диске, нужно его содержимое в строке. Два equivalent способа.

Через ТекстовыйДокумент — удобно, когда файл небольшой и нужен весь сразу:

ТекстДокумент = Новый ТекстовыйДокумент;
ТекстДокумент.Прочитать("C:\Обмен\page.html", КодировкаТекста.UTF8);
HTMLСтрока = ТекстДокумент.ПолучитьТекст();

Через ЧтениеТекста — то же самое чуть короче:

Чтение = Новый ЧтениеТекста("C:\Обмен\page.html", КодировкаТекста.UTF8);
HTMLСтрока = Чтение.Прочитать();
Чтение.Закрыть();

Второй параметр — кодировка. Если его не указать, платформа попытается определить её сама, но на старых страницах в windows-1251 автоопределение иногда промахивается и текст превращается в «кракозябры». Про это отдельный раздел ниже.

На веб-клиенте прямого доступа к файловой системе сервера нет. Там файл сначала помещают во временное хранилище (ПоместитьФайл), а уже потом читают по полученному адресу. В толстом и тонком клиенте с файлом на локальном диске работает код выше.

Если файл лежит в двоичных данных (реквизит, присоединённый файл), его сначала выгружают во временный файл методом ПолучитьФайл/Записать, а затем читают тем же ЧтениеТекста.

Перевод HTML в форматированный документ

Когда разметку надо сохранить — показать пользователю оформленный текст, дать редактировать или вывести на печать — подходит объект ФорматированныйДокумент. Его метод УстановитьHTML принимает строку с разметкой и разбирает её в структуру с абзацами, таблицами, ссылками и начертанием.

ФД = Новый ФорматированныйДокумент;
Картинки = Новый Соответствие;
ФД.УстановитьHTML(HTMLСтрока, Картинки);

Второй параметр Картинки — соответствие, где ключ это имя картинки из тега <img>, а значение — объект Картинка. Если изображений нет или они не нужны, передаётся пустое соответствие, как в примере.

Обратная операция — выгрузить содержимое поля обратно в HTML:

HTMLРезультат = "";
Картинки = Новый Соответствие;
ФД.ПолучитьHTML(HTMLРезультат, Картинки);

Здесь HTMLРезультат и Картинки — выходные параметры: после вызова в первом будет разметка, во втором — соответствие извлечённых картинок.

ФорматированныйДокумент понимает ограниченное подмножество HTML и CSS. Сложная вёрстка с flex, внешними стилями и скриптами упростится: часть оформления потеряется, скрипты отбросятся. Для точной отрисовки сложной страницы берите поле HTML-документа (последний раздел) — там работает полноценный движок.

Чтобы форматированный документ увидел пользователь, на управляемой форме заводят реквизит типа ФорматированныйДокумент и размещают его как элемент. Присвоение ФД этому реквизиту сразу отрисует содержимое. Такой подход удобен для писем, комментариев и описаний, где нужен минимальный набор форматирования без полноценной вёрстки.

Форматированный документ можно и напечатать: метод ПолучитьТекстовыйДокумент переводит его в обычный текст, а для оформленной печати содержимое переносят в макет табличного документа.

Получение HTML-страницы по HTTP и HTTPS

Если разметка не на диске, а на внешнем сайте или в сервисе, её забирают GET-запросом. Базовый объект — HTTPСоединение.

Соединение = Новый HTTPСоединение(
    "example.com",          // сервер без протокола
    443,                    // порт: 80 для http, 443 для https
    ,                       // пользователь
    ,                       // пароль
    ,                       // прокси
    15,                     // таймаут, сек
    Новый ЗащищённоеСоединениеOpenSSL); // для https

Запрос = Новый HTTPЗапрос("/catalog/page.html");
Ответ = Соединение.Получить(Запрос);

Если Ответ.КодСостояния = 200 Тогда
    HTMLСтрока = Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
Иначе
    Сообщить("Сервер вернул код " + Ответ.КодСостояния);
КонецЕсли;

Разберём ключевые моменты:

  • Для https обязателен седьмой параметр Новый ЗащищённоеСоединениеOpenSSL. Без него соединение по 443 порту упадёт с ошибкой, а начинающие часто ищут проблему в адресе, хотя дело в отсутствии защищённого соединения.
  • Адрес сервера передаётся без http:// и https:// — только домен. Протокол задаётся портом и признаком защищённого соединения.
  • Путь страницы (/catalog/page.html) идёт в HTTPЗапрос, а не в адрес сервера.
  • КодСостояния = 200 стоит проверять всегда: при 404 или 500 тело запроса окажется не той страницей, которую вы ждёте, а сообщением об ошибке.

Часть сайтов отвечает 403 Доступ запрещён, если запрос пришёл без заголовка User-Agent — так отсекают простые роботы. Заголовки задаются до вызова Получить:

Запрос = Новый HTTPЗапрос("/catalog/page.html");
Запрос.Заголовки.Вставить("User-Agent", "Mozilla/5.0");
Ответ = Соединение.Получить(Запрос);

Для разовой загрузки файла по ссылке есть готовая функция ПолучитьФайлИзИнтернета из библиотеки стандартных подсистем (БСП). Она сама разбирает адрес, протокол и прокси. Но в типовых конфигурациях её вызывают через обёртки БСП, а не напрямую, поэтому в универсальном коде надёжнее HTTPСоединение.

Если сервер отдаёт данные через POST (форма авторизации, API), в HTTPЗапрос заполняют тело методом УстановитьТелоИзСтроки и нужные заголовки, а вместо Получить вызывают ОтправитьДляОбработки.

Разбор HTML и извлечение данных через DOM

Строка с разметкой сама по себе бесполезна, если нужны конкретные значения — цены из таблицы, ссылки из меню, текст из нужного блока. Разбирать её регулярными выражениями или поиском по подстроке хрупко: любая смена вёрстки ломает код. Надёжнее построить дерево DOM и обходить его как объекты.

Связка — ЧтениеHTML плюс ПостроительDOM:

ЧтениеHTML = Новый ЧтениеHTML;
ЧтениеHTML.УстановитьСтроку(HTMLСтрока);

Построитель = Новый ПостроительDOM;
ДокументDOM = Построитель.Прочитать(ЧтениеHTML);

Дальше получаем нужные теги по имени и читаем их атрибуты и текст:

Ссылки = ДокументDOM.ПолучитьЭлементыПоИмениТега("a");
Для Каждого Элемент Из Ссылки Цикл
    Адрес = Элемент.ПолучитьАтрибут("href");
    Надпись = Элемент.ТекстовоеСодержимое;
    Сообщить(Надпись + " → " + Адрес);
КонецЦикла;

Что здесь важно:

  • ПолучитьЭлементыПоИмениТега возвращает коллекцию всех тегов с указанным именем — "a", "td", "img", "div".
  • ПолучитьАтрибут достаёт значение атрибута: href у ссылки, src у картинки, class у блока.
  • ТекстовоеСодержимое отдаёт весь видимый текст внутри тега без разметки.

Для адресной выборки по идентификатору блока есть ПолучитьЭлементПоИдентификатору:

Блок = ДокументDOM.ПолучитьЭлементПоИдентификатору("price");
Если Блок <> Неопределено Тогда
    Цена = Блок.ТекстовоеСодержимое;
КонецЕсли;

Проверяйте результат на Неопределено. Если тега с таким id на странице нет, ПолучитьЭлементПоИдентификатору вернёт Неопределено, и обращение к его свойствам вызовет ошибку. На «живых» сайтах вёрстка меняется — защита от пустого результата обязательна.

Частая задача — собрать данные из таблицы: прайс, список заказов, реестр. Таблица в HTML это тег <table>, строки — <tr>, ячейки — <td>. Обход идёт в два вложенных цикла:

Строки = ДокументDOM.ПолучитьЭлементыПоИмениТега("tr");
Для Каждого Строка Из Строки Цикл
    Ячейки = Строка.ПолучитьЭлементыПоИмениТега("td");
    Если Ячейки.Количество() >= 2 Тогда
        Товар = Ячейки[0].ТекстовоеСодержимое;
        Цена  = Ячейки[1].ТекстовоеСодержимое;
        Сообщить(Товар + ": " + Цена);
    КонецЕсли;
КонецЦикла;

Проверка Количество() >= 2 страхует от строки-заголовка или от строк с объединёнными ячейками, где нужных столбцов нет. Обращение к ячейкам идёт по индексу с нуля: Ячейки[0] — первый столбец, Ячейки[1] — второй.

ЧтениеHTML лоялен к «грязной» разметке: незакрытые теги и ошибки вёрстки он чаще всего переварит, в отличие от строгого ЧтениеXML, который на некорректном HTML просто упадёт.

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

Половина проблем с загрузкой HTML — не в коде, а в кодировке. Если на экране вместо русских букв «Ð¿Ñивеѻ или «ïðèâåò», значит текст прочитали не в той кодировке, в которой он записан.

Правило простое: читать надо в той кодировке, в которой файл сохранён.

  • Современные страницы — почти всегда UTF-8: КодировкаТекста.UTF8.
  • Старые русские сайты и выгрузки из 1С 7.7 — часто windows-1251: передавайте строку "windows-1251" вместо константы.
  • Кодировку страницы подсказывает тег в её «шапке»: <meta charset="utf-8"> или <meta http-equiv="Content-Type" content="text/html; charset=windows-1251">.

Для HTTP-ответа кодировку указывают прямо в ПолучитьТелоКакСтроку:

// если сайт в 1251
HTMLСтрока = Ответ.ПолучитьТелоКакСтроку("windows-1251");

Отдельная тема — HTML-мнемоники (сущности). В разметке неразрывный пробел пишется как &nbsp;, амперсанд как &amp;, кавычки как &quot;. Когда вы разбираете строку через ЧтениеHTML и DOM, платформа раскрывает их автоматически — в ТекстовоеСодержимое придёт нормальный символ.

А вот если вы режете HTML по подстроке «вручную» через СтрНайти и Сред, мнемоники останутся как есть, и их придётся заменять самому:

Текст = СтрЗаменить(Текст, "&nbsp;", " ");
Текст = СтрЗаменить(Текст, "&amp;", "&");
Текст = СтрЗаменить(Текст, "&quot;", """");

Это ещё один довод за разбор через DOM, а не через поиск по строке: декодирование сущностей, вложенность тегов и регистр атрибутов построитель берёт на себя.

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

Показ HTML на управляемой форме

Чтобы человек увидел страницу внутри 1С — с картинками, ссылками и стилями — используют элемент формы «Поле HTML-документа». Он рисует разметку встроенным движком браузера, а не упрощает её, как форматированный документ.

Порядок действий:

  1. В конфигураторе на форме добавьте реквизит типа Строка неограниченной длины, например HTMLТекст.
  2. Перетащите реквизит на форму как элемент. В свойствах элемента задайте вид «Поле HTML-документа».
  3. В коде присвойте реквизиту строку с разметкой — поле тут же её отрисует:
&НаСервере
Процедура ЗагрузитьСтраницу()
    Чтение = Новый ЧтениеТекста("C:\Обмен\page.html", КодировкаТекста.UTF8);
    HTMLТекст = Чтение.Прочитать();
    Чтение.Закрыть();
КонецПроцедуры

После того как поле отрисовало страницу, к её содержимому можно обратиться программно через свойство Документ элемента — это тот же DOM, что и при разборе построителем:

&НаКлиенте
Процедура ПослеЗагрузки()
    ДокументСтраницы = Элементы.ПолеHTML.Документ;
    // дальше читаем теги: ПолучитьЭлементыПоИмениТега и т.д.
КонецПроцедуры

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

Поле HTML-документа умеет открывать и внешние адреса: достаточно присвоить реквизиту не разметку, а ссылку вида https://.... Так в 1С встраивают, например, онлайн-карты или личные кабинеты сервисов.

Чем `ТекстовыйДокумент` отличается от `ФорматированныйДокумент` при загрузке HTML?
ТекстовыйДокумент даёт сырую строку с тегами — он нужен для разбора и извлечения данных. ФорматированныйДокумент разбирает разметку и сохраняет оформление: его показывают пользователю и дают редактировать.
Как достать из HTML значение одного конкретного поля, а не всё подряд?
Постройте DOM через ПостроительDOM, затем возьмите нужный узел по идентификатору методом ПолучитьЭлементПоИдентификатору или переберите теги ПолучитьЭлементыПоИмениТега. Текст узла — в свойстве ТекстовоеСодержимое.
Почему запрос по `https` не проходит, а по `http` работает?
Для защищённого соединения седьмым параметром HTTPСоединение нужно передать Новый ЗащищённоеСоединениеOpenSSL. Без него платформа пытается открыть обычное соединение на 443 порту и получает ошибку.
Можно ли разбирать HTML регулярными выражениями вместо DOM?
Технически да, через СтрНайти и СтрЗаменить, но это ненадёжно: любое изменение вёрстки ломает разбор, а HTML-сущности придётся раскрывать вручную. DOM устойчив к «грязной» разметке и декодирует сущности сам.
Как показать пользователю готовую HTML-страницу внутри формы?
Добавьте на форму элемент с видом «Поле HTML-документа», свяжите его с реквизитом типа Строка и присвойте этому реквизиту разметку или адрес страницы. Поле отрисует её встроенным браузером.