Каталог решений - Получение html-кода страницы. JS из 1С

Получение html-кода страницы. JS из 1С

Получение html-кода страницы. JS из 1С

В наличии

Получение исходника страницы, выполнение произвольного js-кода. Теперь с WebKit от 1С.

Категория:

Описание

Всё нижеописанное уже так или иначе общеизвестно и опубликовано. Поскольку я ни разу не знаток javascript, данная заметка не претендует на "статью" и носит скорее антисклерозный характер. Основная задача — парсинг сайтов (кто о чём, а я всё о том же), а значит, получение исходного текста страницы.

 

Мы имеем дело с HTML5 и спецификациями этого поколения, и с WebKit, которое с релиза 8.3.14.1565 вместо старого IE (подробнее об эпичном переходе здесь). Но приходится учитывать различные "особенности реализации" в 1С, которые иногда приводят к неожиданным результатам.

Работа ведётся на клиенте 1С в управляемой форме.

Работа с HTML-документом (далее "документ") может осуществляться либо через com-объект HTMLFile, либо через элемент формы HTML-документа (не путать с ActiveX или ОболочкаHTMLДокумента). В первом случае это делается так:

// ПолучитьCOMОбъект не применять, чаще вызывает ошибку создания объекта по классу
хтмл=Новый COMОбъект("HTMLFile");
хттп=Новый COMОбъект("winhttp.winhttprequest.5.1");
хттп.Open("GET", "httрs://infostart.ru");
хттп.Send();
хтмл.Write(хттп.Responsetext); // синхронная обработка
стрТело=хтмл.body.outerText; // и так далее, обрабатываем как COM-объект

Следует учитывать, что используемые объекты зависят от MSIE и зарегистрированного класса, обрабатывается всё средствами ОС и может быть устаревшей версии; ну и COM не кроссплатформенно к тому же. Поэтому далее мы такое не рассматриваем.

Во втором случае на форме размещается реквизит типа "Строка" неограниченной длины, с видом "Поле HTML документа", и ему присваивается локальный или сетевой URL. В момент присвоения начинается загрузка документа. Здесь и далее документ определяется как

хтмл=Элементы.ПолеХТМЛ.Документ;

Окончание загрузки правильнее всего определять по совокупности двух факторов: возникновения события ЭУ 1С "ДокументСформирован" и по выставлении свойства "readyState", равному "complete". Замечено, что, несмотря на теорию, в текущих релизах эти события не синонимичны; полагаю, потому, что событие 1С срабатывает при "interactive", когда уже загружена страница и построено DOM-дерево, но картинки и айфреймы ещё догружаются (т.е. вызвано не Load, а DOMContentLoaded). Либо, возможны новые изменения-догрузки сразу после первой загрузки. Разумно делать обработчик ожидания с выключением в момент полной загрузки.

Мы можем оперировать DOM-моделью документа, методами документа и его элементов. Проверено, что все методы HTML5 работают корректно, с двумя скверными особенностями — они далеко не всегда вызывают ошибки (так, "removeAttribute" при указании несуществующего атрибута никак не ругается) и "пустое" значение js не синонимично пустому в 1С (так, правильнее проверять не "ЗначениеЗаполнено(хтмл)", а "ТипЗнч(хтмл)=Тип("Неопределено") и т.д.)

Мы не можем работать с глобальными переменными — они не сохраняются между сеансами обращения к документу. Т.е. в некоем контексте, в т.ч. контексте документа в целом, можно объявить переменную, и в рамках фрагмента кода js она будет, но следующее обращение столкнётся даже не с пустой переменной, а с её отсутствием.

Не рекомендую ни в каком месте js-кода использовать this — оно или пусто, или некорректно. Возможно, это исправят в других релизах, но надёжнее указывать полный путь к объекту. Если объект создан динамически, тоже пишите его явно.

Вместо "parentWindow" используем "DefaultView", хорошо известное как Document.Window, и можем вызывать скрипты страницы. Также, можем добавлять собственные скрипты (что мне представляется более верным и менее травматичным для документа, нежели подвешивание кусков кода на события и вызов этих событий, как предложено в статье 2016 года). Делается это так:

&НаКлиенте
Функция ДобавитьСкрипт(рТекстСкрипта,рИдСкрипта="")
	хтмлДокумент=Элементы.ПолеХТМЛ.Документ;
	Если ПустаяСтрока(рИдСкрипта) Тогда
		рИдСкрипта="Add"+Строка(хтмлДокумент.scripts.length+1);
	КонецЕсли;
	//
	хтмлСкрипт=хтмлДокумент.getElementById(рИдСкрипта);
	Если ТипЗнч(хтмлСкрипт)=Тип("Неопределено") Тогда
		хтмлСкрипт=хтмлДокумент.createElement("script");
		хтмлСкрипт.id=рИдСкрипта;
		хтмлСкрипт.setAttribute("type","text/javascript"); // в хтмл5 не надо, но пусть...
		хтмлСкрипт.setAttribute("async",Ложь); // т.к. по умолчанию создаёт Истина
		хтмлСкрипт.innerText=рТекстСкрипта;
		// вносим
		хтмлДокумент.head.appendChild(хтмлСкрипт);
	КонецЕсли;	
	//
	Возврат хтмлСкрипт;
КонецФункции

Можно вносить не в скрипты head’a, а в конец body. Документ уже загрузился, всякие асинхроны своё отрабатывают без учёта наших скриптов, Defer всё равно ни на что не влияет. Перезагрузка страницы при этом не происходит, designMode="on" включать не обязательно. Но важно учитывать, что, если текст скрипта не обёрнут в функцию, то он выполнился сразу в момент срабатывания appendChild, поэтому советую в скрипты класть именованные функции js или осознанно применять эту фичу. А вообще см. тут.

Поместив в код js функцию, например вида "function Math1(a, b) {return a+b;}", мы далее в любой момент этого и другого фрагментов кодов можем обращаться к этой функции:

рез=Элементы.ПолеХТМЛ.Документ.DefaultView.Math1(100,50); // и получим 150

Это удобнее, чем присваивать результаты неким свойствам неких объектов и вычитывать их оттуда, или ловить в параметрах событий через createEventObject() и брать из Event.data. Кроме того, не все типы данных могут пережить это преобразование (так, для ArrayBuffer или Blob у меня не сработало, да и про объекты js есть сомнения). А так мы имеем прямое обращение к функции js из языка 1С. Для входных параметров ограничений или искажений экспериментально не обнаружено.

Замечу, что можно многократно добавлять функцию с тем же именем, js просто перезаписывает её код поверх старого (и, кстати, известная разница между "a=func1 и a=func1()" наблюдается и в 1С). Также замечу, что можно добавить одним куском кода в один скрипт сразу несколько функций. Если скрипт добавлен, а обращение по имени вызывает ошибку (1С пишет, что метод не найден), значит, где-то в коде js ошибка, он не скомпилировался и не добавился — так можно себя проверять "на лету".

Задействовать Eval мне не удалось — ошибки не происходит, но и ничего не делает.

 

Теперь переходим к решению заявленной задачи.

1. Можно обратиться к свойству outerHTML:

// получим всё, кроме самых базовых объявлений и узлов вроде DOCUMENT_TYPE_NODE
стрИсходник=Элементы.ПолеХТМЛ.Документ.documentElement.outerHTML;

// или так:
// получим только head и body
стрИсходник=Элементы.ПолеХТМЛ.Документ.head.outerHTML+Элементы.ПолеХТМЛ.Документ.body.outerHTML;

// или так (хотя не лучший вариант)
стрИсходник=Элементы.ПолеХТМЛ.Документ.getElementsByTagName('html')[0].innerHTML;

Но нам-то надо получить вообще всё, все объявления.

Конечно, можно кропотливо собрать эту информацию по свойствам и подузлам, вроде Документ.contentType, doctype.name, по nodeType, но, например, коллекции вроде doctype.childNodes пусты, да и вообще есть шанс что-то потерять.

2. Можно использовать Node.js — сильно могучую штуковину, имеющую возможности различной сериализации и выгрузки, которую, однако, надо инсталлировать; потому отпадает. Тем, кто соберётся: обязательно всюду, где только можно, явно указывайте всякие encoding и charset по Документ.inputEncoding, взятому из документа, иначе жесть.

3. webBrowser.DocumentText неприменимо, т.к. нет такого ЭУ — webBrowser (если, конечно, не мучиться долго и старательно с обёртками и актив-иксинами), и опять же, он не кроссплатформенный. Можно, конечно, сделать com-объект MSIE.Application и дёргать его, но зачем?..

4. Можно так:

рАсинхронно=Истина;
рОбъект=Новый COMОбъект("MSXML2.XMLHTTP");
рОбъект.Open("GET","httрs://infostart.ru", рАсинхронно);

Но капризничает, если неправильно написан адрес в части префиксов, доменов, протоколов; и любит, чтоб сайт был по возможности статичным, чей хтмл известен уже прямо целиком, что в наше время редкость.

 

5. Можно использовать (и мне понравился этот способ) объект XMLSerializer. Это часть современной js, и у неё есть метод сериализации всея документа в строку:

var S1 = new XMLSerializer();
var StrSource = S1.serializeToString(document); // или любой элемент-подузел

В этом случае будет возвращено всё, включая декларацию <!DOCTYPE html>, хтмл с его объявлениями пространств имён, скриптами и т.д., словом, то, что надо. Кодировка UTF-8, и, судя по утверждениям гуру с хабра, с кодировкой он не лажает. Правда, немного жаль, что для этого же сериализатора метод serializeToStream, описанный здесь, более не работает. Кстати, теперь у нас есть возможность обработать двоичный поток, и можно было бы сделать нечто такое:

рЧтение=Новый ЧтениеHTML;
рЧтение.ОткрытьПоток(рПоток,"UTF-8"); // где рПоток напрямую получен из js-функции, а там из serializeToStream

но увы, это уже не поддерживается.

Правда, можно поиграть с XMLHttpRequest, вместо .responseType="text" и responseText читая результат из собственно response в виде arraybuffer или blob (в 1С уже есть инструменты работы с ними), или даже в виде "ms-stream", если только для IE. Но: этот объект требует асинхронного вызова, а с этим у js-в-1С некоторые трудности. Проще говоря, ни fetch, ни промисы адекватно не отрабатывают либо даже не компилируются. Мне не удалось даже подвесить функцию на событие OnLoad для ХHR, и ничего лучше тупого цикла по статусу реквеста не взлетело.

Таким образом, синхронное получение исходного текста документа загруженной страницы html это:

function getContentAsString(elem) {return new XMLSerializer().serializeToString(elem);}

Добавляем скрипт с этой функцией. Вызываем:

рИсходныйКод=Элементы.ПолеХТМЛ.Документ.DefaultView.getContentAsString(Элементы.ПолеХТМЛ.Документ);

Наблюдаем свежедобавленный скрипт среди прочих в head…

Вот, собственно, и всё. В прежние времена писали ОболочкаХТМЛ.ПолучитьТекст() и были счастливы, но прогресс не стоит)

 

…и я грустно пошёл переделывать свои старые публикации под новую механику… Потому что беда общая. Буде кто готов сию пещерную дикость развить и дополнить, всецело приветствую.

 

P.S. Если вдруг захочется использовать возможности js, можно сделать совсем пустой документ (благо, теперь это всего лишь 6 тегов без хитрых объявлений), и добавлять вышеописанным способом свои скрипты в него. По сути, динамически набросать модуль из js-функций, сохранить в файл и юзать по необходимости.

Опыты проводились на 1С х86 8.3.16.1063

has been added to your cart:
Оформление заказа