Xpoint
   [напомнить пароль]

Работа с ECMAScript for XML (E4X)

Стандарт ECMAScript for XML (ECMA-357, далее будет называться E4X) предназначен для упрощения работы с XML из JavaScript. Этот подход значительно выигрывает в сравнении с DOM как по простоте, так и по интуитивности. На данный момент из популярных браузеров E4X поддерживает только Firefox 1.5.

Оглавление

Активизация E4X

В ранних бета-версиях Firefox 1.5 (до 2005-09-22) для включения E4X скрипту нужно было указать специальный MIME-тип. Это больше не требуется, конфликт синтаксиса с распространенной практикой заключения скриптов в комментарии решили другим способом. То есть, в последних версиях Firefox 1.5 такой код будет работать:

<script type="text/javascript">
var xml = <xml>test</xml>;
alert(xml);
</script>

Если ваш браузер поддерживает E4X, то этот код выдаст вам строку test.

Заметим, что браузеры, не поддерживающие E4X, выдадут здесь синтаксическую ошибку. Если требуется обратная совместимось, лучше явно использовать конструктор XML, предварительно проверив его существование:

<script type="text/javascript">
if (window.XML) {
  var xml = XML("<xml>test</xml>");
  ...
}
else
  alert("Ваш браузер не поддерживает E4X!");
</script>

Конструктор XML использует только стандартный синтаксис JavaScript, поэтому никаких проблем со старыми браузерами не возникает.

Создание XML-объекта

Для создания XML-объекта достаточно просто присвоить переменной XML-код:

var xml = <xml>
            <tag>value</tag>
          </xml>;
alert(xml.tag);  // Выдать содержимое тега tag, то есть "value"

Можно создать и список тегов без корневого элемента:

var xml = <><tag>value1</tag><tag>value2</tag></>;
alert(xml[0]);  // Выдать содержимое первого тега в списке, то есть "value1"

Часто, однако, части XML-кода создаются во время выполнения программы. Для этого случая в E4X предусмотрена возможность подставлять JavaScript-выражения в XML-код:

var t = 'tag';
var v = 'VALUE';
var xml = <xml><{t}>{v.toLowerCase()}</{t}></xml>;
alert(xml.tag);  // Выдать содержимое тега tag, то есть "value"

Все, что находится между фигурных скобок, считается JavaScript-выражением и заменяется на значение этого выражения. С помощью выражений можно определять содержимое тега, его имя, названия и значения атрибутов. Вставлять XML-код таким образом, однако, нельзя, значение 'va<l>ue' будет преобразовано в va&lt;l&gt;ue. Если же все-таки хочется преобразовать произвольную строку в XML-объект, то для этого существует функция XML():

var str = '<xml><tag>value</tag></xml>';
var xml = XML(str);
alert(xml.tag);

Доступ к данным XML-объекта

Доступ по имени тега

Проще всего E4X позволяет получать значение тега внутри объекта, для этого имя тега просто используется как свойство:

var xml = <user>
            <name>Вася</name>
            <address>
              <country>Россия</country>
              <city>Москва</city>
            </address>
          </user>;
alert(xml.name);             // Выдает "Вася"
alert(xml.address.country);  // Выдает "Россия"

Заметим, что возвращается всегда другой XML-объект, с которым можно работать дальше. Просто при преобразовании XML-объекта в строку берется его текст, если кроме текста в нем больше ничего нет. При желании можно и явно взять текст элемента и преобразовать его в строку:

alert(xml.name.text().toString());  // Все еще "Вася"

А если мы не знаем, внутри какого тега расположен тег city? Тогда в E4X можно искать тег во всех элементах XML-объекта:

alert(xml..city);            // Выдает "Москва"

То же самое можно сделать через методы elements() и descendants():

alert(xml.elements('name'));
alert(xml.elements('address').elements('country')); 
alert(xml.descendants('city'));

Но что происходит, если элементов с заданным именем тега несколько? Тогда вместо одно XML-элемента возвращается список, содержащий все элементы:

var xml = <users>
            <user>Вася</user>
            <user>Петя</user>
          </users>

alert(xml.user[0]);        // Выдает "Вася"
alert(xml.user.length());  // Выдает количество тегов user, то есть 2
for each (var user in xml.user)
  alert(user);             // Выдает поочередно всех пользователей

Следует обратить внимание на то, что, в отличие от стандартных массивов, length() здесь является методом, а не свойством (за свойствами зарезервирован другой смысл). Заметим также, что стандартный цикл for (... in ...) здесь тоже работает, но for each (... in ...) более удобен, поскольку дает сразу значения вместо индексов.

Вместо имени тега можно использовать символ *, который возвращает все теги:

for each (var tag in xml.*)
  alert(tag);       // Выдает поочередно все теги

Доступ к атрибутам

Чтение атрибутов тегов тоже похоже на чтение свойств объекта, только здесь перед именем атрибута стоит символ @:

var xml = <user name="Вася" address="Москва"/>;
alert(xml.@name);

Или, чтобы прочитать все атрибуты:

for each (var attr in xml.@*)
  alert(attr);      // Выдает поочередно значения всех атрибутов

Заметим, что атрибуты тоже являются объектами, просто их строковое представление определено как их значение. К примеру, всегда можно получить имя атрибута с помощью метода name():

alert(xml.@*[1].name());  // Выдает "address"

Для атрибутов тоже реализован альтернативный доступ в виде метода attribute():

alert(xml.attribute('address'));  // Выдает "Москва"

Фильтры

Для более гибкого выбора элементов существуют фильтры, с помощью которых можно выкинуть из списка все элементы, которые не удовлетворяют критериям:

var xml = <users>
            <user id="1">
              <name>Вася</name>
              <city>Москва</city>
            </user>
            <user id="2">
              <name>Петя</name>
              <city>Бобруйск</city>
            </user>
          </users>
alert(xml.user.(name.toLowerCase() == 'вася').city);  // Выдает "Москва"
alert(xml.user.(@id == 2).city);  // Выдает "Бобруйск"

Значение фильтра проверяется для всех элементов списка и оставляются лишь те, для которых фильтр возвращает true. В результате может остаться и пустой список (length() возвращает 0).

Изменение XML-объектов

E4X делает оператор присваивания очень мощным инструментом, большинство изменений можно сделать с его помощью. Следующий код выполняет всевозможные операции с оператором присваивания:

var xml = <users>
            <user id="1">
              <name>Вася</name>
              <city>Москва</city>
            </user>
            <user id="2">
              <name>Петя</name>
              <city>Бобруйск</city>
            </user>
          </users>

// Меняем значение тега name для Пети
xml.user.(name == 'Петя').name = 'Коля';

// Меняем атрибут id для второго пользователя
xml.user[1].@id = 8;

// Заменяем первого пользователя на Гришу
xml.user[0] = <user id="3"><name>Гриша</name></user>;

// Добавляем нового пользователя в конец
xml.user[xml.user.length()] = <user id="4"></user>

// Добавляем нового пользователя после второго
xml.user[1] += <user id="5"></user>;

Следует обратить внимание на то, что результат зависит от типа присваеваемого объекта. Если присвоить элементу строку, то эта строка станет новым содержимым элемента. Если присвоить XML-объект, то элемент заменится на этот объект. Заметим также, что с присваиванием следует быть осторожным — если присвоить что-то списку, то операция выполняется только для первого элемента списка, а все остальные просто удаляются.

Теперь нужна лишь операция для удаления элементов. Для этого используется стандартный оператор delete:

// Удаляем город для третьего пользователя
delete xml.user[2].city;

// Удаляем первого пользователя
delete xml.user[0];

// Удаляем атрибут id для второго пользователя
delete xml.user[1].@id;

// Удаляем всех пользователей
delete xml.user;

Тонкости

Связь E4X с DOM

Стандарт E4X предусматривает возможность преобразования DOM-объектов в XML-объекты, к примеру:

var xml = XML(document.body);

или:

var xml = new XML(document.body);

Разница между этими двумя вариантами в том, что в первом случае при изменении XML-объекта будет меняться связанный с ним DOM объект и наоборот, а во втором случае будут созданы два независимых объекта.

Обратное преобразование XML-объектов в DOM работает так:

var node = xml.domNode();
var nodeList = xml.domNodeList();

К сожалению, преобразования из XML в DOM и обратно в Firefox пока не реализованы. Как вариант на данный момент остается лишь сериализация объектов в строку и последующий парсинг этой строки. Преобразование из DOM в XML-объект тогда выглядит так:

var node = document.body;
var str = new XMLSerializer().serializeToString(node);
var xml = new XML(str);

И обратно:

var documentFragment = new DOMParser().parseFromString(xml, 'text/xml');
document.body.appendChild(documentFragment);

Пространства имен

По умолчанию всегда используется пространство имен, определенное директивой default xml namespace, к примеру:

default xml namespace = 'http://xpoint.ru/';

или:

default xml namespace = new Namespace('http://xpoint.ru/');

Если этой директивы нет, то стандартным пространством имен является пустая строка.

При доступе к тегам и атрибутам пространство имен всегда можно указать явно. Для этого используется переменная, содержащая Namespace-объект:

var ns = new Namespace('xp', 'http://xpoint.ru/');

// Пишем текст в тег xp:user
xml.ns::user.name = 'Вася';

// Устанавливаем значение атрибута xp:id
xml.@ns::id = 5;

Если нужно явно указать пространство имен при использовании метода elements() и подобных, нужно использовать объект QName (qualified name):

var user = new QName(ns, 'user');
xml.elements(user).name = 'Вася';

Настройки XML-конструктора

Конструктор XML-объектов содержит ряд интересных настроек, определяющих поведение при парсинге и сериализации XML-кода:

СвойствоЗначение
XML.ignoreWhitespaceЕсли установлено значение true (установка по умолчанию), то пробелы между тегами удаляются при парсинге.
XML.ignoreCommentsЕсли установлено значение true (установка по умолчанию), то XML-комментарии удаляются при парсинге.
XML.prettyPrintingЕсли установлено значение true (установка по умолчанию), то при выводе XML-кода будут вставлены пробелы и переводы строки, чтобы результат было легко читать.
XML.prettyIndentКоличество пробелов, которое использует для отступов, если включено XML.prettyPrinting. По умолчанию установлено значение 2.

Можно прочитать и установить все установки одновременно с помощью методов settings() и setSettings():

var s = XML.settings();
s.ignoreWhitespace = false;
s.ignoreComments = false;
s.prettyPrinting = false;
XML.setSettings(s);
Powered by POEM™ Engine Copyright © 2002-2005