Перейти на главную страничку сайта (список статей, файлы для скачивания)

ФОРУМ (здесь можно обсудить эту статью, а также любые проблемы программирования на различных макроязыках и в скриптовых средах)

Использование HTML DOM (Document Object Model) в скриптах на базе Windows Script Host

Иногда перед скриптописателем того или иного профиля встаёт задача разбора HTML-документов с различными целями. Это может быть "выдёргивание" той или иной информации непосредственно из Интернета, перелопачивание многочисленных HTML-файлов на жёстком диске компьютера для их модификации или просто "вычитывания" из них нужной информации, генерация HTML-файлов с различными целями и т.п. В связи с этим очень пригодится понимание так называемой объектной модели документов (DOM, Document Object Model), применение которой сделает процесс достаточно лёгким, логичным и прозрачным.

Данная статья будет полезна скриптописателям на базе Windows Script Host, а также начинающим веб-мастерам (ведь в клиентском JavaScript вовсю используется DOM) и программистам самых различных направлений (если ваша среда разработки в состоянии выступить в роли OLE-клиента, вы всегда можете использовать DOM). Все примеры кода в настоящей статье приводятся на языке VBScript для административных сценариев Windows Script Host. Для понимания изложенного материала желательно немного знать основы HTML и VBScript.

Что такое DOM

DOM, или объектная модель документов (Document Object Model), является способом моделирования HTML-документов. В рамках этой модели обеспечивается возможность доступа, навигации и манипулирования HTML-документами. DOM обеспечивает полный контроль над документом, предоставляя полный доступ и позволяя модифицировать содержимое документа.

Объектная модель документов представляет документ в виде дерева. Структура такого дерева полностью описывает весь HTML-документ, представляя каждый тег и его текстовое содержимое в виде узла дерева. В этом дереве определены отношения "родительский", "дочерний" и "родственный", позволяющие обойти всё дерево, т.е. весь HTML-документ. С помощью DOM можно манипулировать деревом документа любым требуемым образом, создавая новые узлы, удаляя существующие узлы и перемещая узлы в рамках дерева. Это эквивалентно добавлению новых, удалению и перемещению существующих тегов HTML и их текстового наполнения.

Имея дело с современными браузерами, вы можете быть уверены в полной поддержке DOM. Мы будем рассматривать DOM с позиций скриптописательства на базе Windows Script Host, поэтому нам в первую очередь интересен COM-объект "InternetExplorer.Application", который доступен через механизм OLE Automation. Этот объект предоставляет функциональность всего браузера, включая меню, панели инструментов, строку состояния и прочее, и находится в библиотеке "Microsoft Internet Controls" (shdocvw.dll в каталоге system32).

Структура DOM представляет собой иерархию объектов, похожую на объектую иерархию JavaScript или любого другого объектно-ориентированного языка. DOM предлагает свой полезный API, не привязанный к какому-либо конкретному языку программирования. Конечно же, в этой статье нет исчерпывающего перечня всех свойств и методов DOM. Полную информацию по этому вопросу вы можете почерпнуть на веб-узле Microsoft в библиотеке MSDN.

Создадим примерный HTML-документ, которым затем будем пользоваться в наших примерах скриптов:

<html><head>
<meta http-equiv="Content-Language" content="ru">
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<title>DOM</title>
<style type="text/css">
.paragraph {text-align:justify; text-indent:2em; margin-top:0; margin-bottom:0}
</style>
</head>
<body bgcolor=#E0E0E0>

<h2 id="headerA">Это заголовок A</h2>
<h2 id="headerB">Это заголовок B</h2>

<p class="paragraph" id="text">
Это <b>текст</b>.
</p>

<TABLE border="1" width="100%" cellspacing="0" id="myTable">
<TR>
<TD>Первая ячейка первой строки</TD>
<TD>Вторая ячейка первой строки</TD>
</TR>
<TR>
<TD>Первая ячейка второй строки</TD>
<TD>Вторая ячейка второй строки</TD>
</TR>
</TABLE>

</body></html>

Сохраните этот HTML-документ в файле C:\Temp\test.html

Все дальнейшие примеры в этой статье будут использовать этот HTML-документ, загружая его в объект браузера примерно такой строкой кода:

objIE.Navigate "C:\Temp\test.html"

Это ни в коем случае не означает, что вы можете работать только с локальными HTML-файлами. Вместо локального пути всегда можно использовать URL, наподобие такого:

objIE.Navigate "http://ya.ru/"

Программные точки входа в DOM

Если вы хотите начать навигацию по дереву с конкретного узла, то вы можете это сделать только от тех узлов, которым назначен атрибут id. Но, поскольку возмозжна произвольная навигация по дереву, вы можете достичь любого узла дерева.

Существует несколько "точек входа" в DOM через различные программные объекты.

Самая простая "точка входа" в DOM состоит в использовании свойства "all" объекта документа "InternetExplorer.Application":

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set tag = objIE.Document.all("myTable")
WScript.Echo tag.innerHTML

Не используйте вышеприведённый способ в клиентском JavaScript на веб-страницах, т.к. это может работать не во всех браузерах.

Способ, почти аналогичный предыдущему:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set tag = objIE.Document.getElementById("myTable")
WScript.Echo tag.innerHTML

Оба вышеприведённых способа получают необходимый узел дерева с помощью его id. Конечно, это требует, чтобы уникальный идентификатор элементу был присвоен.

Ещё одна "точка входа" - получение списка всех узлов дерева с указанным именем тега:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
For Each tag In objIE.Document.getElementsByTagName("td")
    WScript.Echo tag.innerHTML
Next

Ещё одна "точка входа" - получение списка всех узлов дерева с указанным атрибутом name или id:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
For Each tag In objIE.Document.getElementsByName("myTable")
    WScript.Echo tag.innerHTML
Next

И наконец, можно обходить документ, начиная с его корня:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set tag = objIE.Document.documentElement
WScript.Echo tag.innerHTML

Свойства и атрибуты узла дерева DOM

Объект узла дерева предоставляет несколько свойств (nodeName, nodeValue, tagName, nodeType), смысл которых понятен из их имён:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set tag = objIE.Document.getElementById("text")
WScript.Echo tag.nodeName
WScript.Echo tag.nodeValue
WScript.Echo tag.tagName
WScript.Echo tag.nodeType ' 1 - элемент, 3 - текст
WScript.Echo tag.firstChild.nodeName
WScript.Echo tag.firstChild.nodeType
WScript.Echo tag.firstChild.nodeValue

Значение указанного атрибута узла проще всего получить методом getAttribute:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set tag = objIE.Document.getElementById("myTable")
WScript.Echo tag.getAttribute("width")

Можно воспользоваться методом getAttributeNode, получив специальный объект атрибута:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set tag = objIE.Document.getElementById("text")
WScript.Echo tag.getAttributeNode("class").value

Для получения информации обо всех атрибутах узла можно воспользоваться коллекцией attributes:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set tag = objIE.Document.getElementById("text")
For Each attr In tag.attributes
    WScript.Echo attr.name, attr.value
Next

Навигация по дереву DOM

Для навигации по родственным узлам можно воспользоваться свойствами узла дерева nextSibling и previousSibling:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set tag = objIE.Document.getElementById("text")
While Not tag.previousSibling Is Nothing
    Set tag = tag.previousSibling
    WScript.Echo tag.nodeName
Wend
Set tag = objIE.Document.getElementById("text")
While Not tag.nextSibling Is Nothing
    Set tag = tag.nextSibling
    WScript.Echo tag.nodeName
Wend

Вышеприведённый пример получает узел с идентификатором "text" (в нашем примере - это тег абзаца), а затем получает предыдущий родственный узел до тех пор, пока это возможно (пока свойство previousSibling не вернёт пустой объект). Точно так же пример осуществляет навигацию и в противоположную сторону: пока свойство nextSibling не вернёт пустой объект.

Для навигации по дочерним узлам можно воспользоваться свойством childNodes, которое представляет из себя коллекцию дочерних узлов:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set tag = objIE.Document.getElementById("text")
If tag.hasChildNodes() Then
    For Each t In tag.childNodes
        WScript.Echo t.nodeName
    Next
End If

Вышеприведённый пример получает узел с идентификатором "text" (в нашем примере - это тег абзаца), затем с помощью метода hasChildNodes проверяет, есть ли у этого узла дочерние узлы, и, если есть, перебирает их.

Свойства firstChild и lastChild обеспечивают быстрый доступ к первому и последнему дочернему узлу:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set tag = objIE.Document.getElementById("text")
If tag.hasChildNodes() Then
    WScript.Echo tag.firstChild.nodeName
    WScript.Echo tag.lastChild.nodeName
End If

C помощью коллекции childNodes несложно рекурсивно обойти весь документ от его корня:

Sub TreeWalk(tag)
    For Each t In tag.childNodes
        WScript.Echo t.nodeName
        If t.hasChildNodes() Then TreeWalk(t)
    Next
End Sub

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set tag = objIE.Document.documentElement
If tag.hasChildNodes() Then
    TreeWalk(tag)
End If

И наконец, для перехода к родительскому узлу дерева можно воспользоваться его свойством parentNode:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set tag = objIE.Document.getElementById("text")
WScript.Echo tag.parentNode.nodeName

Модификация дерева DOM

Метод removeChild узла дерева предоставляет возможность простого удаления элементов. Этот метод принимает один аргумент - дочерний узел, который необходимо удалить из родительского узла:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set body = objIE.Document.getElementsByTagName("body")(0)
Set p = objIE.Document.getElementById("text")
body.removeChild p
objIE.Visible = True

Вышеприведённый пример удаляет тег абзаца ("p") из тела ("body") документа и после этого делает окно Internet Explorer видимым. Если вы обновите страницу в этом окне, абзац появится, т.к. файл C:\Temp\test.html будет перезагружен (а наш скрипт этот файл не изменяет). Впрочем, мы всегда можем записать изменённый HTML-документ, к примеру, в другой файл:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set body = objIE.Document.getElementsByTagName("body")(0)
Set p = objIE.Document.getElementById("text")
body.removeChild p
Set FSO = CreateObject("Scripting.FileSystemObject")
Set TextStream = FSO.OpenTextFile("C:\Temp\test2.html", 2, True)
TextStream.Write objIE.Document.documentElement.outerHTML
TextStream.Close

Метод removeAttribute позволяет удалить указанный атрибут узла:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set table = objIE.Document.getElementById("myTable")
table.removeAttribute "border"
objIE.Visible = True

Вышеприведённый пример удаляет атрибут "border" у таблицы в HTML-документе, и таблица отображается без обрамления.

Примечание: свойства и методы, описываемые в настоящем разделе, могут не работать одинаково "гладко" во всех браузерах. Например, при динамическом удалении атрибута в каком-то браузере на экране может ничего не измениться. В данной статье нас интересует DOM сам по себе, но не веб-мастеринг c клиентским JavaScript как таковой. Поэтому имейте в виду, что не все приводимые здесь способы модификации дерева DOM пригодны для буквального использования при вёрстке сайтов.

Создание нового элемента дерева DOM выглядит несколько сложнее, чем все предыдущие примеры:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set p = objIE.Document.createElement("p")
p.setAttribute "align", "right"
Set text = objIE.Document.createTextNode("ТЕКСТ, ВЫРОВНЕННЫЙ ВПРАВО")
p.appendChild text
Set body = objIE.Document.getElementsByTagName("body")(0)
body.appendChild p
objIE.Visible = True

В вышеприведённом примере происходит следующее, по шагам:

Таким образом, новые узлы дерева DOM создаются вначале как отдельные объекты, и только затем включаются в дерево тем или иным способом.

Метод appendChild всегда добавляет новый объект в конец списка дочерних узлов, и это, конечно, не единственный способ добавить новый узел в дерево. Метод insertBefore позволяет указать дочерний узел, перед которым должен быть вставлен новый узел. В качестве первого аргумента метода передаётся сам новый узел, а во втором аргументе передаётся узел, перед которым должна произойти вставка:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set p = objIE.Document.createElement("p")
p.setAttribute "align", "right"
Set text = objIE.Document.createTextNode("ТЕКСТ, ВЫРОВНЕННЫЙ ВПРАВО")
p.appendChild text
Set body = objIE.Document.getElementsByTagName("body")(0)
body.insertBefore p, body.firstChild
objIE.Visible = True

В вышеприведённом примере вставка нового абзаца происходит перед первым дочерним элементом тела документа ("body").

Ещё один метод - replaceChild - работает аналогично методу insertBefore, но не вставляет новый элемент перед указанным, а заменяет указанный элемент на новый. Для демонстрации вы можете просто заменить в предыдущем примере "insertBefore" на "replaceChild".

И наконец, метод cloneNode позволяет скопировать узел:

Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = False
objIE.Navigate "C:\Temp\test.html"
While objIE.Busy
    WScript.Sleep 200
Wend
Set body = objIE.Document.getElementsByTagName("body")(0)
Set p = objIE.Document.getElementById("text").cloneNode(True)
body.appendChild p
objIE.Visible = True

В вышеприведённом примере в конец тела документа ("body") добавляется копия существующего абзаца с идентификатором "text". Метод cloneNode принимает необязательный булев параметр, который указывает, что необходимо рекурсивно скопировать всех потомков данного узла (если параметр опущен или равен False, потомки не скопируются). Как правило, копировать необходимо именно рекурсивно, т.к. например, собственно текстовое содержимое абзаца является его дочерним узлом, и не будет скопировано, если это не указано явно.

Людоговский Александр, 15.06.2008г.

Перейти на главную страничку сайта (список статей, файлы для скачивания)

© 2007 http://www.script-coding.com При любом использовании материалов сайта обязательна ссылка на него как на источник информации, а также сохранение целостности и авторства материалов.