(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Вопросы XML: По ту сторону DOM

Дит Элза (Dethe Elza), Дэвид Мертц (David Mertz)

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

Очередной материал в рубрике представляет приглашенный ведущий колонки, мой друг и коллега, Дит Элза (Dethe Elza). У Дита большой опыт в разработке web-приложений, работающих с XML, поэтому я пригласил его, чтобы рассказать об XML-программировании с использованием DOM и ECMAScript. Следите за новостями колонки, чтобы не пропустить новых материалов Дита. -Дэвид Мертц (David Mertz)

DOM - это один из стандартных API для работы с XML и HTML. Его часто критикуют за слишком расточительное использование памяти, медленную работу и/или за излишнее многословие. Однако для многих приложений это самый правильный выбор, и, безусловно, работать с DOM проще, чем с другим основным API для XML, SAX. DOM получает все большее применение в таких инструментах, как web-браузеры, SVG-браузеры, программы OpenOffice и многие другие.

DOM хорош тем, что он сам является прекрасно реализованным стандартом, и кроме того, встроен в другие стандарты. Как стандарт, он всегда работает одинаково, независимо от используемого языка программирования (это может расцениваться и как достоинство, и как недостаток, но, по крайней мере, подразумевает стабильность). В настоящее время DOM встраивается во многие web-браузеры и является компонентом многих спецификаций, основанных на XML. Поскольку DOM и так уже является частью вашего инструментария и, вероятно, вы хотя бы время от времени им пользуетесь, возможно, самое время воспользоваться преимуществами, которые предлагает эта модель.

После того, как вы некоторое время поработаете с DOM, у вас появятся некоторые шаблоны - действия, которые вы захотите использовать снова и снова. Ярлыки помогут вам обойти многословность DOM и создать понятный и изящный код. Ниже представлена коллекция советов и приемов, которыми я пользуюсь чаще всего, с примерами на JavaScript.

Методы insertAfter и prependChild

Для первого приема не нужно никаких приемов. В DOM есть два метода, которые добавляют дочерние узлы в узел-контейнер (обычно Element, но это может быть также Document или DocumentFragment): appendChild(node) и insertBefore(node, referenceNode). Но кажется, здесь чего-то не хватает. Что если мне захочется вставить узел после опорного узла или до дочернего узла (сделать новый узел первым в списке)? Годами я писал вспомогательные функции наподобие следующей:

Листинг 1. Ошибочный способ вставки и предпосылки

function insertAfter(parent, node, referenceNode) {
    if(referenceNode.nextSibling) {
        parent.insertBefore(node, referenceNode.nextSibling);
    } else {
        parent.appendChild(node);
    }
}
function prependChild(parent, node) {
    if (parent.firstChild) {
        parent.insertBefore(node, parent.firstChild);
    } else {
        parent.appendChild(node);
    }
}

После выполнения этого кода функция insertBefore() обязательно вернется к методу appendChild(), если опорный узел равен нулю. Поэтому вместо этого можно воспользоваться или кодом листинга 2, или вообще отказаться от этих методов и использовать встроенные функции:

Листинг 2. Правильный способ вставки и предпосылки

function insertAfter(parent, node, referenceNode) {
    parent.insertBefore(node, referenceNode.nextSibling);
}
function prependChild(parent, node) {
    parent.insertBefore(node, parent.firstChild);
}

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

С учетом этой возможности, если у вас есть два соседних узла (назовем их node1 и node2), и вам нужно перенести их, воспользуйтесь одним из следующих выражений:

node1.parentNode.insertBefore(node2, node1);

или

node1.parentNode.insertBefore(node1.nextSibling, node1);

Что еще можно делать с DOM?

Поскольку существуют web-страницы, существует множество вариантов применения DOM. Если вы посетите сайты, посвященные процедурам-закладкам, "закладуркам" (bookmarklets) (см. раздел Ресурсы), вы можете обнаружить несколько небольших сценариев, которые воплощают альтернативное использование DOM для переформатирования страниц, извлечения ссылок, скрытия изображений или рекламных Flash-баннеров и много других вариантов.

Но первое есть первое. Поскольку Internet Explorer не описывает постоянные интерфейса Node, которые позволяют легко идентифицировать тип узла, одна из основных функций, которую можно выполнять в сценарии DOM для web-сайтов - это функция, позволяющая убедиться, что вы задали тип узла, который не отображается в браузере.

Листинг 3. Сценарий, позволяющий убедиться в том, что узел задан

if (!window['Node']) {
    window.Node = new Object();
    Node.ELEMENT_NODE = 1;
    Node.ATTRIBUTE_NODE = 2;
    Node.TEXT_NODE = 3;
    Node.CDATA_SECTION_NODE = 4;
    Node.ENTITY_REFERENCE_NODE = 5;
    Node.ENTITY_NODE = 6;
    Node.PROCESSING_INSTRUCTION_NODE = 7;
    Node.COMMENT_NODE = 8;
    Node.DOCUMENT_NODE = 9;
    Node.DOCUMENT_TYPE_NODE = 10;
    Node.DOCUMENT_FRAGMENT_NODE = 11;
    Node.NOTATION_NODE = 12;
}

В листинге 4 показано, как извлечь все текстовые узлы, содержащиеся в каком-либо узле:

Листинг 4. Внутренний текст

function innerText(node) {
    // is this a text or CDATA node?
    if (node.nodeType == 3 // node.nodeType == 4) {
        return node.data;
    }
    var i;
    var returnValue = [];
    for (i = 0; i < node.childNodes.length; i++) {
        returnValue.push(innerText(node.childNodes[i]));
    }
    return returnValue.join('');
}

Ярлыки

Распространенная претензия к DOM заключается в том, что он слишком многословен и требует вводить слишком много текста для описания простых моментов. Например, если вы хотите создать элемент <div>, который содержит некоторый текст, скажем, в ответ на нажатие кнопки, код может выглядеть примерно так:

Листинг 5. Создание элемента <div>, длинный вариант

function handle_button() {
    var parent = document.getElementById('myContainer');
    var div = document.createElement('div');
    div.className = 'myDivCSSClass';
    div.id = 'myDivId';
    div.style.position = 'absolute';
    div.style.left = '300px';
    div.style.top = '200px';
    var text = "This is the first text of the rest of this code";
    var textNode = document.createTextNode(text);
    div.appendChild(textNode);
    parent.appendChild(div);
}

Если вы часто создаете новые узлы этим способом, то сейчас убедитесь в том, что очень скоро такой способ написания программ устареет. Должен быть лучший способ -- и он есть. Существует утилита, которая поможет создавать элементы, задавать для них атрибуты, стили и добавлять текстовые дочерние узлы. Все аргументы за исключением name являются необязательными.

Листинг 6. Ярлык функции elem()

function elem(name, attrs, style, text) {
    var e = document.createElement(name);
    if (attrs) {
        for (key in attrs) {
            if (key == 'class') {
                e.className = attrs[key];
            } else if (key == 'id') {
                e.id = attrs[key];
            } else {
                e.setAttribute(key, attrs[key]);
            }
        }
    }
    if (style) {
        for (key in style) {
            e.style[key] = style[key];
        }
    }
    if (text) {
        e.appendChild(document.createTextNode(text));
    }
    return e;
}

Вооружившись этим ярлыком, вы можете создать элемент <div> из листинга 5 более кратким способом. Обратите внимание, что аргументы attrs и style задаются при помощи литеральных объектов JavaScript.

Листинг 7. Создание элемента <div> сокращенным способом

function handle_button() {
    var parent = document.getElementById('myContainer');
    parent.appendChild(elem('div',
      {class: 'myDivCSSClass', id: 'myDivId'},
      {position: 'absolute', left: '300px', top: '200px'},
      'This is the first text of the rest of this code'));
}

Если вы хотите создавать более сложные объекты DHTML на лету, то утилиты, подобные этой, могут существенно сэкономить время. Принцип такой: если вы собираетесь часто создавать какие-либо DOM-структуры, то создайте утилиты, которые будут делать это за вас. Это не только уменьшит количество набираемого кода, но также устранит повторные вырезки-вставки кода (а это основной источник ошибок) и сделает ваши намерения более очевидными при чтении кода впоследствии.

Какой узел следующий?

В DOM не всегда просто определить, какой узел является следующим в структуре документа. Вот некоторые утилиты, которые помогут вам перемещаться вперед и назад по узлам:

// return next node in document order
function nextNode(node) {
    if (!node) return null;
    if (node.firstChild){
        return node.firstChild;
    } else {
        return nextWide(node);
    }
}
// helper function for nextNode()
function nextWide(node) {
    if (!node) return null;
    if (node.nextSibling) {
        return node.nextSibling;
    } else {
        return nextWide(node.parentNode);
    }
}
// return previous node in document order
function prevNode(node) {
    if (!node) return null;
    if (node.previousSibling) {
      return previousDeep(node.previousSibling);
    }
    return node.parentNode;
}
// helper function for prevNode()
function previousDeep(node) {
    if (!node) return null;
    while (node.childNodes.length) {
        node = node.lastChild;
    }
    return node;
}

На прогулку с DOM

В некоторый момент вы, вероятно, захотите выполнить обход DOM, вызвать функцию в каждом узле или возвратить значение для каждого узла. По сути, всем известно, что стандарт DOM Level 2 включает расширение под названием DOM Traversal and Range, которое задает объекты и API для итерирования через узлы DOM, проходит DOM, чтобы применить функцию по всем узлам и выбирает диапазоны DOM. Поскольку эта функция не определяется в Internet Explorer (как минимум), для выполнения подобных функций можно использовать метод nextNode().

Идея этого метода - создать простые инструменты общего назначения, а затем комбинировать их различными способами, чтобы получить желаемые результаты. Если вы знакомы с функциональным программированием, то кое-что из описанного ниже покажется вам знакомым. Статья "Beyond JS library" (По ту сторону JS) продолжает развитие этой идеи.

Листинг 9. Функциональные утилиты DOM

    / return an Array of all nodes, starting at startNode and
    // continuing through the rest of the DOM tree
function listNodes(startNode) {
    var list = new Array();
    var node = startNode;
    while(node) {
        list.push(node);
        node = nextNode(node);
    }
    return list;
}
    // The same as listNodes(), but works backwards from startNode.
    // Note that this is not the same as running listNodes() and
    // reversing the list.
function listNodesReversed(startNode) {
    var list = new Array();
    var node = startNode;
    while(node) {
        list.push(node);
        node = prevNode(node);
    }
    return list;
}
    // apply func to each node in nodeList, return new list of results
function map(list, func) {
    var result_list = new Array();
    for (var i = 0; i < list.length; i++) {
        result_list.push(func(list[i]));
    }
    return result_list;
}
    // apply test to each node, return a new list of nodes for which
    // test(node) returns true
function filter(list, test) {
    var result_list = new Array();
    for (var i = 0; i < list.length; i++) {
        if (test(list[i])) result_list.push(list[i]);
    }
    return result_list;
}

Листинг 9 содержит четыре базовых инструмента. Функции listNodes() и listNodesReversed() могут быть расширены, чтобы они могли принимать дополнительную длину и работать с методом Array slice(), но этот эксперимент я оставляю вам. Еще один момент, который стоит отметить - это то, что функции map() и filter() являются функциями общего характера, так как работают с любым списком, а не только со списками узлов. Далее показаны несколько способов комбинирования инструментов.

Листинг 10. Применение функциональных утилит.

    // A list of all the element names in document order
function isElement(node) {
    return node.nodeType == Node.ELEMENT_NODE;
}
function nodeName(node) {
    return node.nodeName;
}
var elementNames = map(filter(listNodes(document),isElement), nodeName);
// All the text from the document (ignores CDATA)
function isText(node) {
    return node.nodeType == Node.TEXT_NODE;
}
function nodeValue(node) {
    return node.nodeValue;
}
var allText = map(filter(listNodes(document), isText), nodeValue);

Эти утилиты можно использовать для извлечения идентификаторов, изменения стилей, поиска определенных видов узлов, которые нужно удалить, и так далее. Поскольку API DOM Traversal и Range реализованы на многих платформах , их можно использовать для изменения дерева DOM без предварительного создания списка. Это очень эффективные инструменты, принцип работы которых аналогичен описанному выше.

Опасные зоны DOM

Обратите внимание на то, что ядро API DOM не предоставляет методов для синтаксического разбора данных XML в DOM или обратной сериализации модели DOM из XML. Эти методы описаны в расширении для DOM Level 3, "Load and Save", но это расширение не реализовано в достаточной степени, чтобы можно было на него рассчитывать. Каждая платформа (браузер или другое приложение, умеющее работать с DOM) имеет свои методы конверсии XML в DOM и из DOM, но в данной статье не рассматривается, как можно делать это на различных платформах.

DOM не вполне надежный инструмент -- в частности, при помощи API DOM можно создать дерево, которое невозможно будет сериализовать в XML. Никогда не используйте в одной программе API DOM1, не поддерживающие пространства имен, и их аналоги из DOM2, которые поддерживают пространство имен, (например, createElement - createElementNS). Если вы используете пространства имен, попробуйте хранить все ваши объявления пространств имен в корневом элементе, и никогда не меняйте префиксы пространств имен, потому что это совершенно гибельный путь. В общем, если у вас достаточно здравого смысла, вы не будете провоцировать крайние ситуации, которые могут привести к возникновению проблем.

Если вам приходится для выполнения синтаксического разбора полагаться на функции innerText и innerHTML Internet Explorer, лучше попробуйте вместо них использовать функцию elem(). Создав небольшое количество аналогичных утилит, вы можете получить максимум удобства, но при этом сохранить кроссплатформенность кода. Но ни в коем случае нельзя смешивать эти два подхода.

Отдельные символы Unicode не могут быть включены в XML. Реализация DOM позволяет сделать это, но результат будет несериализуемым. Он будет включать большинство управляющих символов и отдельные символы из пар для замены символов Unicode. Вы можете столкнуться с этим только в одном случае: если попытаетесь включить в документ двоичные данные, но это, возможно, другая проблемная ситуация.

Заключение

В статье рассматривается множество основных приемов, но DOM (и JavaScript) могут сделать для вас гораздо больше. Изучите материал, рассмотрите примеры и вы поймете, как можно применить эти принципы для решения проблем, которые иначе потребовали бы применения выполнения сценариев на стороне клиента, шаблонов или специальных API.

У DOM есть ограничения и недостатки, но есть и много преимуществ: Эта модель встроена во многие приложения; работает независимо от того, какой технологией вы пользуетесь - Java, Python или JavaScript; более удобна, чем SAX; а благодаря манипуляциям, продемонстрированным выше, может быть элегантной и эффективной в применении.. Большая часть приложений в последнее время начинают поддерживать DOM, в том числе, приложения на основе Mozillа, OpenOffice и XMetaL от Blast Radius. Большинство спецификаций требуют наличия DOM и создают для этой модели расширения (такие, как SVG), поэтому в ближайшее время DOM не сойдет со сцены. Неплохо было бы познакомиться с этим используемым на многих платформах инструментом.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 18.05.2007 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
ARCHICAD 21, локальная лицензия на 12 месяцев
Dr.Web Security Space, продление лицензии на 1 год, 1 ПК
Nero 2018 Platinum ESD
NauDoc Enterprise 10 рабочих мест
Quest Software. Toad for DBA Suite for Oracle
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Программирование на Microsoft Access
CASE-технологии
Компьютерный дизайн - Все графические редакторы
СУБД Oracle "с нуля"
Вопросы и ответы по MS SQL Server
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100