Раскрываем JavaScript: XML

31 января 2008 года, 18:24

Очень часто приходится сталкиваться с тем, что необходимо каким-либо образом обработать XML-данные в JavaScript. Для этого существуют специализированные функции. На первый взгляд, это достаточно сложно, но на самом-то деле всё очень легко, в чём мы сейчас и убедимся.

Методы

Для начала мы рассмотрим набор функций и свойств, которые предоставлены нам для оперирования XML-данными.

element.getElementsByTagName("element_name"); — получить массив (коллекцию) всех элементов с указанным именем element_name, находящихся в элементе element. Если element_name равен символу «*», то возвращаются рекурсивно все элементы, входящие в данный элемент и его потомков. Для обращения к конкретному элементу, мы можем использовать такую запись:

//Получаем все элементы div, находящиеся в element var elements = element.getElementsByTagName("div"); //Получаем первый элемент в коллекции var element_one = elements.item(0); //Получаем количество элементов в коллекции var element_count = elements.length; //Проходимся по всем элементам коллекции for (var i = 0; i < elements_count; i++) { //Производим необходимые действия }

document.getElementById("element_id") — получение элемента (единственного) по его идентификатору element_id. Используется чаще в оперировании элементами в самом (X)HTML-документе. document.getElementByName("element_name"); — получение элемента по атрибуту name. Используется достаточно редко, лучше использовать document.getElementById. document.createElement("element_type") — создание элемента element_type (будет создан элемент с таким тегом). Возвращает вновь созданный элемент.

Свойства

documentElement — получение родительского элемента из XML-документа. Возвращает полноценный документ-объект, с которым можно производить различные операции. node.nextSibling — получение следующего узла в коллекции. node.nodeName — получение имени узла. node.nodeValue — получение значения узла.

Практикуемся

Для практики мы возьмём не библиотеку музыки, книг или других обыденных примеров, а RSS, ведь он тоже является XML, только поверх него (XML) описаны необходимые элементы для унификации формата.

Берём наш RSS из примеров к спецификации формата (http://cyber.law.harvard.edu/rss/examples/rss2sample.xml):

<?xml version="1.0"?> <rss version="2.0"> <channel> <title>Liftoff News</title> <link>http://liftoff.msfc.nasa.gov/</link> <description>Liftoff to Space Exploration.</description> <language>en-us</language> <pubDate>Tue, 10 Jun 2003 04:00:00 GMT</pubDate> <lastBuildDate>Tue, 10 Jun 2003 09:41:01 GMT</lastBuildDate> <docs>http://blogs.law.harvard.edu/tech/rss</docs> <generator>Weblog Editor 2.0</generator> <managingEditor>editor@example.com</managingEditor> <webMaster>webmaster@example.com</webMaster> <item> <title>Star City</title> <link>http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp</link> <description>How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's <a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm">Star City</a>.</description> <pubDate>Tue, 03 Jun 2003 09:39:21 GMT</pubDate> <guid>http://liftoff.msfc.nasa.gov/2003/06/03.html#item573</guid> </item> <item> <description>Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a <a href="http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm"> partial eclipse of the Sun</a> on Saturday, May 31st.</description> <pubDate>Fri, 30 May 2003 11:06:42 GMT</pubDate> <guid>http://liftoff.msfc.nasa.gov/2003/05/30.html#item572</guid> </item> </channel> </rss>

Это обычный XML-документ. Мы его будем препарировать, словно на анатомическом вскрытии.

Перед оперированием XML-документа необходимо убедиться в том, что он валиден: присутствует маркировка (самая верхняя строка с версией и кодировкой) и единственный корневой элемент (в нашем случае — RSS). Помимо этого, должны соблюдаться и другие правила оформления XML-документа.

Поместим этот документ в файл rss.xml, он нам пригодится.

Мы будем загружать наш XML-файл через объект XMLHttpRequest. Поместим различные рутины в файл ajax.js. В конечном итоге он будет выглядеть так:

/* Функция создания XMLHttpRequest */ function CreateRequest() { var Request = false; if (window.XMLHttpRequest) { //Gecko-совместимые браузеры, Safari, Konqueror Request = new XMLHttpRequest(); } else if (window.ActiveXObject) { //Internet explorer Request = new ActiveXObject("Microsoft.XMLHTTP"); if (!Request) { HRequest = new ActiveXObject("Msxml2.XMLHTTP"); } } if (!Request) { alert("Невозможно создать XMLHttpRequest"); } return Request; } /* Функция посылки запроса к файлу на сервере r_method — тип запроса: GET или POST r_path — путь к файлу r_args — аргументы вида a=1&b=2&c=3... r_handler — функция-обработчик ответа от сервера */ function SendRequest(r_method, r_path, r_args, r_handler) { //Создаём запрос var Request = CreateRequest(); //Проверяем существование запроса еще раз if (!Request) { return; } //Назначаем пользовательский обработчик Request.onreadystatechange = function() { //Если обмен данными завершен if (Request.readyState == 4) { //Передаем управление обработчику пользователя r_handler(Request); } } //Проверяем, если требуется сделать GET-запрос if (r_method.toLowerCase() == "get" && r_args.length > 0) r_path += "?" + r_args; //Инициализируем соединение Request.open(r_method, r_path, true); if (r_method.toLowerCase() == "post") { //Если это POST-запрос //Устанавливаем заголовок Request.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=utf-8"); //Посылаем запрос Request.send(r_args); } else { //Если это GET-запрос //Посылаем нуль-запрос Request.send(null); } } /* Функция для получения файла filename — имя файла (относительный или абсолютный от корня Web-сайта) handler — функция-обработчик результата */ function GetXMLFile(filename, handler) { //Посылаем запрос SendRequest("GET",filename,"",handler); }

Костяк теперь у нас есть. В нём мы создаём основную структуру документа (только структуру) и подключаем наш behavior (действия) — ajax.js. Теперь мы будем оперировать в теге body.

Теперь начинаем оформлять файл index.html. Создадим его скелет:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> <title>XML Test</title> <script type="text/javascript" src="ajax.js"></script> </head> <body> </body> </html>

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

<h2 id="get_title"></h2> <div id="get_container"></div>

Контейнер с идентификатором get_title будет содеражть заголовок нашего RSS-документа, а контейнер get_container будет содержать его записи.

Сразу же после этих записей мы помещаем новый блок кода. В идеальном документе, он должен быть вынесен в отдельный файл и подключен через запись script. Это следует делать для соответствия модели «трёх китов»: разделение действий, структуры и репрезентации (представления) в документе.

<script type="text/javascript"> </script>

Приступим к работе с наши документом. Для начала, мы должны получить его через объект XMLHttpRequest.

//Функция-обработчик ответа function ParseXML(Response) { } //Получаем XML-файл GetXMLFile("rss.xml", ParseXML);

Всё достатоно просто: мы загружаем с сервера документ rss.xml посредством XMLHttpRequest и в тот момент, когда загрузка уже завершена, вызываем нашу функцию ParseXML. Ей передаётся один аргумент — Response — это тот самый XMLHttpRequest, но уже с результатом его работы. В нём есть свойство responseXML, с помощью которого можно получить доступ к XML-части ответа от сервера. Оно-то нам и понадобится. Мы сделаем из неё полноценный документ, с которым и будем оперировать дальше (теперь мы работаем с телом функции ParseXML):

//Получаем документ var doc = Response.responseXML.documentElement;

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

Для начала, мы заполним заголовок нашей RSS-ленты. Сделаем это таким образом:

//Получаем заголовок //Заголовок — это первый элемент title в документе $("get_title").innerHTML = doc.getElementsByTagName("title").item(0).firstChild.nodeValue;

Подробно рассмотрим приведённый выше код: Сначала мы получаем все элементы в документе с именем title, после этого выбираем первый из найденных элементов и получаем его значение. firstChild используется для того, чтобы получить доступ именно к текстовой части содержимого узла.

Теперь можно смело переходить к поиску и обработке элементы (items) нашей ленты.

//Проходимся по всем элементам-записям и составляем их репрезентацию var items = doc.getElementsByTagName("item"); for (var i = 0; i < items.length; i++) { //Создаём элемент для записи var item_record = document.createElement("div"); //item_record.innerHTML += "<p>" + items[i].firstChild.nextSibling.firstChild.nodeValue.toString() + "</p>"; //Отсчитываем с первого дочернего узла var f_child = items[i].firstChild; //Пока у нас есть соседние узлы, переключаем их while (f_child.nextSibling) { //Выбираем имя узла и в соответствии с этим выполняем необходимое действие switch (f_child.nodeName) { //Если это заголовок — оформляем как заголовок case "title": item_record.innerHTML += "<h3>" + f_child.firstChild.nodeValue + "</h3>"; break; //Если это описание, оформляем как описание case "description": item_record.innerHTML += "<p>" + f_child.firstChild.nodeValue + "</p>"; break; } //Устанавливаем следующий узел f_child = f_child.nextSibling; } //Добавляем запись в контейнер $("get_container").appendChild(item_record); }

Считаю, что код достаточно хорошо комментирован и разобраться в нём не будет большой проблемой.

Таким образом, мы на примере посмотрели процесс работы с XML-документом.

Итоги

На основе подобных возможностей JavaScript можно делать массу интересных приложений. Один из примеров — это аггрегация большого количества RSS-лент (сервер получает удалённую ленту, распределяет их особым образом по базе данных; репрезентация таких лент лежит уже на стороне клиента, что более естественно).

Можно задать вопрос: «Почему вы использовали AJAX?». На него есть довольно простой ответ: AJAX — это асинхронный JavaScript и XML, то есть мы поступили достаточно правильно, показав эти две технологии в связке, тем самым логично раскрыв суть технологии.

Мнения (25)

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

  • Irokez

    02 февраля 2008 г.09:29

    только вот XHR запрос нельзя посылать на другие домены..

  • Дин автор

    02 февраля 2008 г.09:37

    Да, но это можно сделать на серверной части, не так ли?

    Клиент запрашивает у сервера необходимый ресурс (например, передаёт параметр uri=http://web-zine.org/rss/general), сервер его загружает и возвращает данные XML клиенту, который их обрабатывает, как показано в статье.

    Главное, чтобы сервер позволял делать подобные удалённые запроса (для PHP — это allow_url_fopen и другие подобные переменные в конфигурации).

  • Irokez

    02 февраля 2008 г.10:08

    в таком случае на сервере обработать данные гораздо легче :)

  • Дин автор

    02 февраля 2008 г.10:22

    А клиенту что отдавать? Готовую (X)HTML разметку? XMLHttpRequest для этого не предназначен. Он предназначен для передачи данных в любом виде, без наложения на них (данных) репрезентативных функций. Я писал об этом.

    Сервер в данном случае выступает в роли промежуточного звена.

  • Irokez

    02 февраля 2008 г.10:23

    тогда и аякс тут ни к чему

  • Дин автор

    02 февраля 2008 г.10:33

    Для динамических rss-reader-application это очень даже кстати.

    Да и AJAX в данном конкретном случае является технологией номер один, поскольку раскрываем мы именно её (об этом написано в итогах к статье, последний абзац). А вот использовать AJAX для своих разработок или нет — решать уже не мне. Как хотите.

  • Mischka

    04 февраля 2008 г.09:44

    предлагаю

    for (var i = 0; i < elements.length; i++){
    //Производим необходимые действия
    }

    заменить на

    for(var i = 0, var count = elements.length; i < count; i++){
    // Производим необходимые действия
    alert(elements[i]); // доступ к элементу коллекции по индексу
    }

    ////////////////////////////////

    document.getElementByName("element_name"); — непонятный выкидыш жабаскрипта. name в элементах DOM, как известно используется только для отправки именованных данных в формах, а для доступа к элементам категорически рекомендуется всегда использовать id

    ////////////////////////////////

    document.createElement("element_name")
    лучше поменять на
    document.createElement("element_type")
    методически лучше будет указать, что создается элемент определенного типа, а не с определенным именем.

  • Дин автор

    04 февраля 2008 г.10:05

    Чем обосновано добавление переменной count?

    Я написал, что лучше использовать getElementById.

    Подразумеваю, что имя элемента есть название тега. Так, в принципе, и по спецификациям прослеживается.

  • Mischka

    05 февраля 2008 г.08:09

    > Чем обосновано добавление переменной count?
    Скоростью выполнения. Count вычисляется один раз в начале цикла, а elements.lenght — при каждой итерации вычисляется заново.
    Если планируется изменять количество элементов в коллекции, то и цикл нужно планировать совершенно по-другому. Так что count тут вполне оправданно.

    > Я написал, что лучше использовать getElementById.
    Угу, я лишь высказал свое категорическое согласие.

    > Подразумеваю, что имя элемента есть название тега. Так, в принципе, и по спецификациям прослеживается.
    Да, но... По соседству в тексте мы под именем элемента подразумевали name, и это было удобно. В данном случае, никто не станет спорить, что речь идет о типе элемента и об имени его типа. Поэтому мне кажется, что гораздо красивее использовать "element_type", чтобы не путать неподготовленного читателя. А для подготовленного можно написать и createElement("some_fucking_str") — он все равно поймет, о чем речь.

  • Дин автор

    07 февраля 2008 г.07:00

    Хорошо, всё ясно.

  • Дмитрий

    24 февраля 2009 г.10:34

    //Функция-обработчик ответа function ParseXML(Response) { } //Получаем XML-файл GetXMLFile("rss.xml", ParseXML); А что здесь есть переменная Response в функции ParseXML? Где она (в примерах) создавалась? В каких листингах? А если это просто указание на то, что здесь должен быть какой-то параметр, то почему не написали об этом? И почему в вызове функции GetXMLFile указан в его параметре вызов этой функции без параметра Response? Извините, но мне, как читателю — это не понятно. Спасибо.

  • Дин автор

    24 февраля 2009 г.14:48

    @Дмитрий, работа с XMLHttpRequest уже разбиралась в одной из записей блога (в одной из первых). К тому же, тут ничего нет сложного: если вы посмотрите на реализацию функции SendRequest, увидите параметр r_handler, который является функцией обратного вызова (callback-функция). Она вызывается тогда, когда значение статуса запроса будет таким, каков он нужен нам. Взгляните на обработку события onreadystatechange: там мы вызываем этот метод и передаём ему в качестве аргумента наш объект XMLHttpRequest.

    Вообще, всё, что вы написали, @Дмитрий, есть в исходном коде, который хорошо документирован.

  • Серж

    16 августа 2009 г.21:45

    Люди сделал 3 файла, поместил код как тут но ничего не вышло, кто может отправить архивчик этих файлов? заранее благодарю hininahi@gmail.com

  • Дин автор

    17 августа 2009 г.05:42

    @Серж, может быть напишете, какие ошибки вы получили? Если установлен Firefox + Firebug, там выводится список ошибок при исполнении скриптов.

  • Константин

    08 ноября 2009 г.18:20

    Нифига не работает!!!

  • 010101

    13 ноября 2009 г.03:16

    кстати да, реально что-то не работает. Архив с файлами в студию, Дин, выкладывай!

  • Влад

    27 ноября 2009 г.19:41

    думаю код: //Отсчитываем с первого дочернего узла var f_child = items[i].firstChild; //Пока у нас есть соседние узлы, переключаем их while (f_child.nextSibling) { }

    надо поменять на do { } while(f_child.nextSibling);

    иначе первый элемент может «пропуститься»

  • nohlp

    18 марта 2010 г.00:17

    Все работает и без архива, но вот косяк..текст выводится до первого знака «

  • nohlp

    18 марта 2010 г.00:20

    о блин и даже в посте не выводится после знака " ))))))))))))))))))))))))

  • nh

    19 марта 2010 г.17:36

    trhtrh

  • nh

    19 марта 2010 г.17:36

    rthtrh

  • nh

    19 марта 2010 г.17:36

    rerrrrrrrrrrrrr

  • Ahill

    07 апреля 2010 г.14:25

    "//Получаем документ var doc = Response.responseXML.documentElement;"

    Получаю ошибку, doc — определение отсутствует. В чем может быть проблема? (ошибка в IE6)

  • Yurtaev

    27 апреля 2010 г.05:59

    Момент с Response не понятен, мы ведь его не где создаем, и на моменте его использования вываливается с ошибкой, хотелось бы подробнее узнать этот момент.

  • Den

    13 мая 2010 г.01:34

    Можно получить архив?, а то у меня, что-то не работает…

Я тоже знаю!

Для обращения к человеку используйте символ @, после которого следует имя того, к кому обращаетесь (пробелы заменяются на знак подчёркивания). Если вам интересно, можете подписаться на комментарии по RSS или по эл. почте. Ведите себя достойно, вы же не роботы, правда?

Вы можете использовать следующие XHTML-элементы в разметке комментария: strong, em, span[class=crossline], a[href=uri], code[type=язык], blockquote, ul и ol. В качестве языка кода может быть указан, например, javascript или css.