Раскрываем 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, то есть мы поступили достаточно правильно, показав эти две технологии в связке, тем самым логично раскрыв суть технологии.

Мнения (33)

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

  • 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

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

  • Lioklio

    20 октября 2010 г.17:16

    Перерыла много сайтов, в поисках парсинга XML, тут помогло, спасибо ;)

  • Griffonn

    16 апреля 2011 г.12:44

    @Дин, при переборе .nextSibling неожиданно находятся несуществующие элементы: ,хотя естественно ничего подобного в XML нету. Дело ли в переносе строки и(ли) табуляции?

    Минуту спустя: Да, дело в переносах строк. Но без них XML сильно ухудшается в плане чтения из редактора, т.е. собственно редактирования. Что же делать? Перешагивать через два элемента плохо, не знаешь же, а вдруг в какой-нибудь 3287-ой строке не будет переноса, и элемент пропустится. Если устроить тотальную проверку типа if (cols.textContent != ' '){}, или любой другой if, то браузер крепко виснет.

  • Griffonn

    16 апреля 2011 г.12:45

    Парсер — лох! Элементы: TextNode textContent=" "

  • Progulchik

    26 мая 2011 г.01:23

    А можно вопросы! Можно ли все это делать без сервера? и нужен ли будет тогда аякс? можно ли удалять, добавлять записи в xml документе через js? и как? мне надо создать приложение для курсовой ввиде записной книжки, там пишешь дату, время, и саму запись, я че то не понимаю как добавлять и удалять записи из xml Никогда раньше не работал

  • Артём

    13 сентября 2012 г.21:28

    Класс то что надо

  • Артем

    09 ноября 2012 г.23:22

    А как наоборот сделать? т.е. сохранить данные в файл xml. Заранее спасибо!

  • Mischka

    02 декабря 2013 г.19:42

    Если внутри скрипта есть строковая переменная, содержащая хорошо сформированный XML, как ее преобразовать в объект document для дальнейшего разбора и издевательств?

  • Mischka

    02 декабря 2013 г.19:49

    Стоило задать вопрос, чтобы тут же самому найти решение. создаем например элемент document var doc = document.createElement('document') потом присваиваем ему в innerHTML строку, содержащую xml doc.innerHTML = XmlString всё, можно парсить doc, как захочется.

Я тоже знаю!

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

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