JavaScript | Как создать объект DOM-документа из строки с HTML-разметкой на клиенте?

JavaScript | Как создать объект DOM-документа из строки с HTML-разметкой на клиенте?

Способ № 1 — Через интерфейс DOMParser

Стандарт DOM Parsing and Serializationhttps://www.w3.org/TR/DOM-Parsing/ описывает интерфейс DOMParser, у которого есть метод parseFromString(str, type)

Первым параметром метод принимает строку, которая по сути является полноценной разметкой HTML-страницы.

Вторым параметром метод принимает тип распознавания для будущего документа. Это один из:

  • «text/html«
  • «text/xml»
  • «application/xml»
  • «application/xhtml+xml»
  • «image/svg+xml»

В случае с созданием HTML-документа, нужно выбрать строковое представление «text/html«.

 

Пример работы

Есть строка — потенциальный фрагмент будущего документа:

let stroka = '<div><h1>Заголовок</h1><p>Текст1</p><p>Текст2</p><p>Текст3</p><ul><li>Красный</li><li>Синий</li><li>Коричневый</li></ul></div>'
Создали строку с HTML-разметкой из div p li и h1
Создали строку с HTML-разметкой из div p li и h1

Создаём новый объект DOMParser:

let ndp = new DOMParser()
Создали новый объект DOMParser - JavaScript
Создали новый объект DOMParser — JavaScript

Вызываем метод синтаксического анализа по строке:

let nDoc = ndp.parseFromString(stroka, "text/html")
Создали новый объект документа через метод parseFromString объекта DOMParser - JavaScript
Создали новый объект документа через метод parseFromString объекта DOMParser — JavaScript

На выходе получаем объект документа (document), который в элементе body содержит полную разметку, переданную нами в строке.

 

Более того, если мы передадим не просто HTML-фрагмент документа, а полную HTML-разметку с доктайпами, комментариями и эштемээлями, то на выходе документ будет также хорошо собран. Это важно!

 

Способ № 2 — Через HTML-элемент iframe

Мы создаём средствами JavaScript новый объект HTML-элемента iframe.

let nif = document.createElement('iframe');

Мы подсаживаем этот iframe в текущий открытый документ, чтобы наш iframe начал участвовать в рендеринге (визуализации) страницы.

document.body.append(nif);

Для атрибута srcdoc нашего iframe мы устанавливаем значение — нужную нам строку для парсинга в HTML-документ.

nif.srcdoc = stroka;

Далее мы дожидаемся события load для iframe, которое будет свидетельствовать о завершении построения объектной модели документа. Это значит, что можно будет работать по новому документу всеми стандартными методами DOM.

let nDoc;
nif.onload = (event)=>{
  // Выводим в консоль №1
  console.log(nif.contentDocument);
  // Выводим в консоль №2
  console.log(event.target.contentDocument);

  // ...или...

  // Сохраняем в перменную №1
  nDoc = nif.contentDocument
  // Сохраняем в перменную №2
  nDoc = event.target.contentDocument
}
Получили объект документа через iframe - JavaScript
Получили объект документа через iframe — JavaScript

Далее мы получаем новый объект документа. Внимание! Вложенного документа! Не исходного открытого во вкладке браузера, а вложенного в iframe. Только у отрисованного iframe свойство contentDocument будет содержать нужный нам документ. Если iframe не будет отрисован, а просто создан в дереве родительского документа, то свойство contentDocument вернёт null.

Процесс частично описан тут — JavaScript | Как дождаться загрузки iframe?

 

Дополнительная информация

Существует такое понятие, как «фрагмент документа«. Очень важно отличать его от URI-фрагмента, который обозначается решёткой # и имеет связь с элементом на странице по идентификатору id.

Так вот «фрагмент документа» подробно описан в стандарте DOM, в разделе «5.5. Interface Range«. Также имеется отдельный раздел в стандарте DOM Parsing and Serialization, который там называется «8. Extensions to the Range interface«.

Объекты, реализующие интерфейс Range, называются живыми диапазонами (live ranges).

Как это выглядит на практике? Создадим новый объект Range.

let nRange = new Range()
Создали новый экземпляр класса Range - JavaScript
Создали новый экземпляр класса Range — JavaScript

Вызовем на полученном объекте Range метод createContextualFragment(). Внутрь метода мы передаём нашу строку с HTML-разметкой:

let df = nRange.createContextualFragment(stroka)
Создали объект документ-фрагмент - JavaScript
Создали объект документ-фрагмент — JavaScript

По итогу нам возвращается новый объект «документ-фрагмент». Какие плюсы мы получаем?

 

Мы можем обходить вложенные элементы объекта «документ-фрагмент». Это очень круто т. к. по сути нам может быть не всегда нужен полный объект документа!

Например, методом querySelectorAll() с параметром ‘*’, мы можем получить все объекты элементов, отправленных в первоначальной строке

df.querySelectorAll('*')
Метод querySelectorAll() по объекту документ-фрагмент - JavaScript
Метод querySelectorAll() по объекту документ-фрагмент — JavaScript

Или мы можем отобрать все объекты по типу:

[...df.querySelectorAll('*')].filter(element=>element.nodeName=='P')

или

[...df.querySelectorAll('*')].filter(element=>element.tagName=='P')
Отобрали элементы по имени тега у объекта документ-фрагмент - JavaScript
Отобрали элементы по имени тега у объекта документ-фрагмент — JavaScript

К сожалению удобный для работы метод getElementsByTagName() не работает на объектах «документ-фрагмент»