DOM (document object model)

Цель лабораторной работы:

1) Познакомиться c интерфейсом и объектной моделью DOM.

2) Научиться программировать приложения с использованием DOM на Java

DOM как API

XML DOM = XML Document Object Model. Является рекомендацией W3C. XML DOM позволяет: обходить узлы дерева элементов и атрибутов, манипулировать его структуры и содержании.

XML DOM платформенно-независимый стандарт. Можно создать сценарий в языках PERL, Java, Python и.т.д. После создания дерева объектов документ полностью загрузится в память, позволяя доступ к отдельным узлам. Синтаксический анализатор XML позволяет использовать готовые методы для управления элементов.

Список узлов доступных в DOM
Тип Описание
Nodeбазовой тип для других объектов
Documentдокумент
Elementэлемент
Attributeатрибут
Textтекстовое поле, дочерний узел элемента
Processing Instructionинструкции для отработка
СDATA Sectionтекст который парсер не транслирует
Document Fragmentчасть документа)
Entityстрока для подстановки
Entity Reference&Entity;

Модуляризация DOM означает для разработчика, что он должен знать, поддерживаются ли те возможности, которые он хочет использовать, той реализацией DOM, с которой он работает. W3C определил для DOM на сегодняшний день 3 уровня:

Уровень 1 включает в себя поддержку XML 1.0 и HTML, в которой каждый элемент HTML представляется как интерфейс. Он включает в себя методы для добавления, редактирования, перемещения и чтения информации, содержащейся в узлах и т.д. Он, однако, не включает в себя поддержку пространств имен XML.

Поддержка пространств была добавлена в DOM Уровня 2. Уровень 2 расширяет Уровень 1, позволяя разработчикам обнаруживать и использовать информацию пространств имен, которая может быть применима к узлу. Уровень 2 добавляет также несколько модулей поддержки CSS, событий и расширенных манипуляций с деревом.

DOM Уровня 3 включает в себя улучшенную поддержку объекта Document (предыдущие версии оставляли это на усмотрение приложений, что делало затруднительным создание родовых приложений), расширенную поддержку пространств имен, и новые модули для загрузки и сохранения документов, проверки правильности и XPath, средства для выбора узлов, используемые в XSL Transformations и других технологиях XML.

Общие сведения о программных интерфейсах XML

В предыдущих уроках мы с вами рассмотрели принципы проектирования правильно оформленных и допустимых (valid) XML документов. Нам с вами так же знаком тот факт, что браузеры не проверяют документы на допустимость, даже если с ними ассоциированны DTD или схема. Однако, в тех случаях, когда XML применяют не как средство разметки документов, а как средство структурирования данных, проверка на допустимость очень важна. Известно так же, что в популярнейший браузер Microsoft Internet Explorer встроен XML анализатор, проверяющий допустимость XML документов на уровне расширенной объектной модели. Эта одна из причин, по которой мы должны с ней познакомиться.

Еще одной причиной является использование XML не в браузерах, а, например, в программах, написанных на Visual С++. В этом случае приходится читать XML файлы и отображать данные вручную. Существует 2 типа анализаторов (парсеров) XML, использующих принципиально разный подход к разбору документов:

DOM парсеры

DOM - Document Object Model, объектная модель документа. Данный термин в течение длительного времени применялся к WEB-браузерам. Такие объекты, как окно, документ, история считались частью объектной модели браузера. Каждый, кто сталкивался с разработкой WEB-приложений, знает, что эти объекты в разных браузерах эти объекты реализованы по разному. Для создания более стандартизованного способа обращения и манипулирования структурами документов консорциум W3C предложил другую спецификацию, которая привела впоследствии к текущей модели W3C DOM.

W3C DOM представляет собой не зависящее от языка или платформы определение. Это означает, что для различных составляющих DOM объектов определены интерфейсы, но для них не предлагается никакой конкретной реализации, и ее можно осуществить на любом языке программирования. Эта модель помогает разработчику читать, находить, модифицировать, добавлять, и удалять объекты из документа. Модель HTML DOM является частным случаем W3C DOM.

В каких случаях случая следут использовать DOM?

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

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

SAX парсеры

SAX - Simple API for XML, простой программный интерфейс приложений для XML. SAX предоставляет последовательный доступ к элементам XML документа. Анализаторы такого типа читают XML документ и сообщают программе о встреченных символах по мере их появления. Например, сообщает, когда находит открывающий тэг элемента, символьные данные и закрывающий тэг. Такой программный интерфейс называется интерфейсом основанным на событиях. SAX является отличной альтернативой DOM в следующих случаях:

Возможно наибольшим преимуществом SAX является значительно меньшее использование памяти, по сравнению с DOM. При использовании SAX потребление памяти не возрастает с размером файла.

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

Для множества приложений нет необходимости читать XML документ целиком, для получения необходимых данных. SAX предоставляет такую возможность, позволяя снизить ресурсоемкость процесса разбора.

Можно привести еще несколько преимуществ SAX, однако существует и ряд ограничений:

DOM и Java

Разбор файла в документ, трехшаговый процесс

Чтобы работать с информацией в XML-файле, файл должен быть разобран для создания объекта Document.

Объект Document является интерфейсом, так что его экземпляр не может быть создан непосредственно, обычно вместо этого приложение использует фабрику. Подробности этого процесса различаются от реализации к реализации, но идеи одни и те же. (Опять-таки, Уровень 3 стандартизирует эту задачу.) Например, в среде Java разбор файла является 3-шаговым процессом:

  1. Создание DocumentBuilderFactory. Этот объект создает DocumentBuilder.
  2. Создание DocumentBuilder. DocumentBuilder действительно выполняет разбор для создания объекта Document.
  3. Разбор файла для создания объекта Document.

Теперь вы можете начать построение приложения.

Базовое приложение

Начнем с создания базового приложения, класса с именем OrderProcessor.

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import org.w3c.dom.Document;
public class OrderProcessor {
  public static void main (String args[]) {
    File docFile = new File("orders.xml");
    Document doc = null;
    try {
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      DocumentBuilder db = dbf.newDocumentBuilder();
      doc = db.parse(docFile);
    } catch (Exception e) {
      System.out.print("Problem parsing the file: "+e.getMessage());
    }
  }
}

Сначала Java-код импортирует необходимые классы, а затем создает приложение OrderProcessor. Примеры в этом учебнике рассматривают один файл, так что для краткости приложение содержит прямую ссылку на него.

Так как объект Document может быть использован позже, приложение определяет его вне блока try-catch.

В блоке try-catch приложение создает объект DocumentBuilderFactory, который затем используется для создания DocumentBuilder. Наконец, DocumentBuilder разбирает файл для создания Document.

Установки парсера

Одно из преимуществ создания парсеров при помощи DocumentBuilder состоит в управлении различными установками парсера, создаваемого при помощи DocumentBuilderFactory. Например, парсер может быть установлен на проверку правильности документа:

...
try {
  DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  <b>dbf.setValidating(true); </b>
  DocumentBuilder db = dbf.newDocumentBuilder();
  doc = db.parse(docFile);
} catch (Exception e) {
...

Java-реализация DOM Уровня 2 обеспечивает управление параметрами парсера через следующие методы:

Исключения парсера

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

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

...
try {
  DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  DocumentBuilder db = dbf.newDocumentBuilder();
  doc = db.parse(docFile);
} catch (javax.xml.parsers.ParserConfigurationException pce) {
  System.out.println("The parser was not configured correctly.");
  System.exit(1);
} catch (java.io.IOException ie) {
  System.out.println("Cannot read input file.");
  System.exit(1);
} catch (org.xml.sax.SAXException se) {
  System.out.println("Problem parsing the file.");
  System.exit(1);
} catch (java.lang.IllegalArgumentException ae) {
  System.out.println("Please specify an XML source.");
  System.exit(1);
}
...

Когда парсер создал документ, приложение может проходить через него для обработки данных.

Раздел 2. Прохождение по документу

Получение корневого элемента

Если документ разобран и создан Document, приложение может двигаться по структуре для обзора, поиска и отображения информации. Эта навигация является основой для многих операций, выполняемых на объекте Document.

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

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
public class OrderProcessor {
...
    System.exit(1);
  }
  //STEP 1: Get the root element
    Element root = doc.getDocumentElement();
    System.out.println("The root element is " + root.getNodeName());
  }
}

Компиляция и выполнение приложения выведет имя корневого элемента, orders.

Получение потомка элемента

Если приложение определило корневой элемент, оно выбирает список потомков корневого элемента как NodeList. Класс NodeList является серией компонентов, которую приложение может перебирать. В данном примере для краткости приложение получает дочерние узлы и проверяет выборку, показывая, сколько элементов появляется в результирующем NodeList.

Заметьте, что документ имеет только два элемента, но NodeList содержит пять потомков, включая три текстовых узла, которые содержат переводы строк.

...
import org.w3c.dom.NodeList;
...
    //STEP 1: Get the root element
    Element root = doc.getDocumentElement();
    System.out.println("The root element is "+root.getNodeName());
    //STEP 2: Get the children
    NodeList children = root.getChildNodes();
    System.out.println("There are "+children.getLength()
        +" nodes in this документ.");
  }
}

Использование getFirstChild() и getNextSibling()

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

На Шаге 3, цикл for начинается с первого потомка корня. Приложение перебирает "братьев" первого потомка, пока все они не будут обработаны. При выполнении каждой итерации цикла приложение выбирает объект Node, выводит его имя и значение. Заметьте, что в число пяти потомков orders входят элементы order и три текстовых узла. Заметьте также, что элементы имеют значение null, а не текст, как ожидалось. Эти текстовые узлы являются потомками элементов, которые имеют в качестве своих значений реальное содержимое.

...
import org.w3c.dom.Node;
...
    //STEP 3: Step through the children
    for (Node child = root.getFirstChild();
        child != null;
        child = child.getNextSibling())
    {
      System.out.println(start.getNodeName()+" = "
        +start.getNodeValue());
    }
  }
}
...

Рекурсия через многие уровни потомков

Код в подразделе Использование getFirstChild() и getNextSibling() показывает потомка первого уровня, но это трудно для всего документа. Чтобы увидеть все элементы, функциональность предыдущего примера должна быть оформлена в метод и должна вызываться рекурсивно.

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

...
public class OrderProcessor {
  private static void stepThrough (Node start) {
    System.out.println(start.getNodeName()+" = "+start.getNodeValue());
    for (Node child = start.getFirstChild();
          child != null;
          child = child.getNextSibling())
    {
      stepThrough(child);
    }
  }
  public static void main (String args[]) {
    File docFile = new File("orders.xml");
...
    System.out.println("There are " + children.getLength()
        +" nodes in this документ.");
    //STEP 4: Recurse this functionality
    stepThrough(root);
  }
}

Включение атрибутов

Метод stepThrough(), как он был написан до сих пор, может проходить через большинство типов узлов, но в нем полностью отсутствуют атрибуты, поскольку они не являются потомками никаких узлов. Чтобы показывать атрибуты, модифицируем метод stepThrough() для проверки элементных узлов на наличие атрибутов.

Приведенный ниже модифицированный код проверяет каждый узел на то, является ли он элементом, путем сравнения его nodeType с константой ELEMENT_NODE. Объект Node имеет в своем составе константы, которые представляют каждый тип узла, такие, как ELEMENT_NODE или ATTRIBUTE_NODE. Если nodeType соответствует ELEMENT_NODE, он является элементом.

Для каждого найденного им элемента приложение создает объект NamedNodeMap, содержащий все атрибуты элемента. Приложение может перебирать NamedNodeMap, печатая имя и значение каждого атрибута, так же, как оно перебирало NodeList.

...
import org.w3c.dom.NamedNodeMap;
...
  private static void stepThroughAll (Node start) {
    System.out.println(start.getNodeName()+" = "+start.getNodeValue());
    if (start.getNodeType() == start.ELEMENT_NODE) {
      NamedNodeMap startAttr = start.getAttributes();
      for (int i = 0; i < startAttr.getLength(); i++) {
        Node attr = startAttr.item(i);
        System.out.println(" Attribute: "+ attr.getNodeName()
            +" = "+attr.getNodeValue());
      }
    }
    for (Node child = start.getFirstChild();
        child != null;
        child = child.getNextSibling())
    {
      stepThroughAll(child);
    }
  }

Редактирование документа

Изменение значения в узле

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

Нашей целью является изменить значение текстового узла элемента, в данном случае установкой status для каждого order в "processed", а затем вывести новые значения на экран.

Метод changeOrder() вызывается с передачей ему начального узла (root) в качестве параметра, а также имени изменяемого элемента и измененного значения.

changeOrder() сначала проверяет имя узла, чтобы увидеть, тот ли элемент, который редактируется. Если это так, приложению нужно изменить значение не этого узла, а его первого потомка, поскольку этот первый потомок является текстовым узлом, который на самом деле содержит содержимое элемента.

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

Когда изменения выполнены, значение проверяются при помощи getElementsByTagName(). Этот метод возвращает список всех дочерних элементов с заданным именем, таким, как status. Приложение может затем проверить значения в списке, чтобы убедиться, что метод changeOrder() работает.

...
public class OrderProcessor {
  private static void changeOrder (Node start,
    String elemName, String elemValue) {
    if (start.getNodeName().equals(elemName)) {
      start.getFirstChild().setNodeValue(elemValue);
    }
    for (Node child = start.getFirstChild();
        child != null;
        child = child.getNextSibling()) {
      changeOrder(child, elemName, elemValue);
    }
  }

  public static void main (String args[]) {

  // Change text content
  changeOrder(root, "status", "processing");
  NodeList orders = root.getElementsByTagName("status");
  for (int orderNum = 0; orderNum < orders.getLength(); orderNum++) {
    System.out.println(orders.item(orderNum)
        .getFirstChild().getNodeValue());
    }
  }
}

Заметьте, что приложение выбирает узлы status даже, несмотря на то, что они являются "внуками" корневого элемента, а не непосредственными его "детьми". getElementsByTagName() проходит через документ и находит все элементы с определенным именем.

Добавление узлов: подготовка данных

Иногда необходимо не изменить существующий узел, а добавить узел, и у вас есть несколько способов сделать это. В нашем примере приложение вычисляет общую стоимость каждого заказа и добавляет в order элемент total. Оно получает общую стоимость, выбирая каждый заказ и проходя через все его составляющие, чтобы получить стоимость составляющей, а затем итоговую стоимость их всех. Затем приложение добавляет новый элемент в заказ (см. код ниже).

Сначала приложение выбирает элементы order так же, как оно выбирало элементы status. Затем перебирает каждый из этих элементов.

Для каждого из этих order приложению нужен NodeList из его составляющих item, так что приложение должно сначала преобразовать узел (Node) order в Element, чтобы использовать getElementsByTagName().

Приложение затем может перебрать составляющие item для выбранного order. Каждая из них преобразуется в Element, так что из него можно выбрать по имени price и qty. Приложение делает это при помощи метода getElementsByTagName(), и поскольку их всего по одному в каждом item, оно может прямо брать item(0), первую составляющую результирующего NodeList. Этот первый элемент представляет элемент price (или qty). Из него извлекается значение текстового узла.

Значение текстового узла имеет тип String, приложение затем преобразует его в double, чтобы сделать возможным вычисление.

Когда приложение заканчивает проверку всех составляющих для каждого заказа, total типа double представляет итоговое значение. Затем total преобразуется в String, так что оно может использоваться как содержимое нового элемента, <total>, который в конечном счете присоединяется к order.

...
  changeOrder(root, "status", "processing");
  NodeList orders = root.getElementsByTagName(" order ");
  for (int orderNum = 0; orderNum < orders.getLength(); orderNum++) {
    Element thisOrder = (Element)orders.item(orderNum);
    NodeList orderItems = thisOrder.getElementsByTagName("item");
    double total = 0;
    for (int itemNum = 0; itemNum < orderItems.getLength(); itemNum++) {
      // Total up cost for each item and
      // add to the order total
      //Get this item as an Element
      Element thisOrderItem = (Element)orderItems.item(itemNum);
      //Get pricing information for this Item
      String thisPrice =
        thisOrderItem.getElementsByTagName("price").item(0)
            .getFirstChild().getNodeValue();
      double thisPriceDbl = new Double(thisPrice).doubleValue();
      //Get quantity information for this Item
      String thisQty =
        thisOrderItem.getElementsByTagName("qty").item(0)
            .getFirstChild().getNodeValue();
      double thisQtyDbl = new Double(thisQty).doubleValue();
      double thisItemTotal = thisPriceDbl*thisQtyDbl;
      total = total + thisItemTotal;
    }
    String totalString = new Double(total).toString();
  }
...

Добавление узлов: добавление узлов в документ

Вы можете создать новый Node многими способами, и этот пример применяет несколько из них. Во-первых, объект Document может создавать новый текстовый узел со значением totalString. Новый Node теперь существует, но еще никуда не присоединен к Document. Новый элемент total создается аналогично, и он также поначалу свободный.

Другой способ добавление узла - применение appendChild(), как показано здесь для нового элемента total.

Наконец, приложение может использовать insertBefore() для добавления нового элемента Document, указывая новый Node и предшествующий Node.

Прохождение через документ проверяет изменения.

  changeOrder(root, "status", "processing");
  NodeList orders = root.getElementsByTagName("order");
  for (int orderNum = 0; orderNum < orders.getLength(); orderNum++) {
    String totalString = new Double(total).toString();
    Node totalNode = doc.createTextNode(totalString);
    Element totalElement = doc.createElement("total");
    totalElement.appendChild(totalNode);
    thisOrder.insertBefore(totalElement, thisOrder.getFirstChild());
  }
  stepThrough(root);

Удаление узла

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

Перед добавлением стоимости составляющей к итогу приложение проверяет значение атрибута instock. Если оно N, то вместо добавления к итогу составляющая полностью удаляется. Чтобы это сделать, приложение применяет метод removeChild(), но сначала определяет предка этого orderItem при помощи getParentNode(). code>Node удаляется из документа, но метод также возвращает его, так что, при желании, он может быть перемещен.

...
    //Get this item as an Element
    Element thisOrderItem = (Element)orderItems.item(itemNum);
    if (thisOrderItem.getAttributeNode("instock")
          .getNodeValue().equals("N")) {
      Node deadNode =
        thisOrderItem.getParentNode().removeChild(thisOrderItem);
    } else {
      //Get pricing information for this Item
      String thisPrice =
        thisOrderItem.getElementsByTagName("price").item(0)
              .getFirstChild().getNodeValue();
...
      total = total + thisItemTotal;
    }
  }
  String totalString = new Double(total).toString();
...

Замена узла

Конечно, не имеет смысла удалять компоненту заказа, если она не выполнена. Вместо этого приложение заменяет ее компонентой backordered.

Вместо removeChild() просто используйте replaceChild(). Заметьте, что в данном случае метод также возвращает старый узел, так что он может быть перенесен, если это необходимо, возможно, в новый Document, перечисляющий невыполненные компоненты.

Заметьте, что поскольку никакое содержимое не было добавлено в элемент, этот элемент является пустым. Пустой элемент не имеет содержимого и может быть записан в сокращенном виде: <backordered/>. Косая черта (/) устраняет необходимость в закрывающем теге (</backordered>).

...
  if (thisOrderItem.getAttributeNode("instock")
       .getNodeValue().equals("N")) {
    Element backElement = doc.createElement("backordered");
    Node deadNode = thisOrderItem.getParentNode()
        .replaceChild(backElement, thisOrderItem);
  } else {
...

Создание и установка атрибутов

Конечно, что хорошего в элементе backordered, если не видно, какую компоненту он представляет? Одним из способов исправить недостаток информации является добавление атрибутов в элемент.

Сначала приложение создает атрибут itemid. Затем оно определяет значение itemid из исходного элемента item, а затем устанавливает значение в сам атрибут. Наконец, оно добавляет элемент в документ, так же, как и раньше.

...
  if (thisOrderItem.getAttributeNode("instock")
         .getNodeValue().equals("N")) {
    Element backElement = doc.createElement("backordered");
    backElement.setAttributeNode(doc.createAttribute("itemid"));
    String itemIdString =
        thisOrderItem.getAttributeNode("itemid").getNodeValue();
    backElement.setAttribute("itemid", itemIdString);
    Node deadNode =
        thisOrderItem.getParentNode().replaceChild(backElement, thisOrderItem);
  } else {
...

Важно заметить, что setAttribute() создает узел атрибута, если атрибут с таким именем не существует, так что в этом случае приложение может совсем пропустить createAttribute().

Удаление атрибута

Приложение может также удалить атрибут. Например, может быть нежелательным показывать на выходе информацию о кредите покупателя, так что приложение может временно удалить ее из документа.

Удаление информации выполняется явным образом при помощи removeAttribute() для удаления данных.

...
  Element thisOrder = (Element)orders.item(orderNum);
  Element customer =
    (Element)thisOrder.getElementsByTagName("customerid")
        .item(0);
  customer.removeAttribute("limit");
  NodeList orderItems = thisOrder.getElementsByTagName("item");
...

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

Вывод документа

Подготовка данных

Мы в нашем учебнике рассмотрели, как выбирать, работать и манипулировать с XML-данными. Для завершения цикла вы должны также уметь выводить XML.

В случае этого учебника целевым выводом является файл, в котором просто перечисляются все заказы по мере того, как они поступают или отвергаются на основе кредита покупателя и customerid.

<?xml version="1.0" encoding="UTF-8"?>
<processedOrders>
 <order>
  <status>PROCESSED</status>
  <customerid>2341</customerid>
  <amount>874.00</amount>
 </order>
 <order>
  <status>REJECTED</status>
  <customerid>251222</customerid>
  <amount>200.00</amount>
 </order>
</processedOrders>

Сначала приложение создает объект Document для вывода. Для удобства тот же самый DocumentBuilder, который создавал исходный Document, может создавать и новый.

...
  public static void main (String args[]) {
    File docFile = new File("orders.xml");
    Document doc = null;
    Document newdoc = null;
    try {
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      DocumentBuilder db = dbf.newDocumentBuilder();
      doc = db.parse(docFile);
      newdoc = db.newDocument();
    } catch (Exception e) {
      System.out.print("Problem parsing the file: "+e.getMessage());
    }
...
    thisOrder.insertBefore(totalElement, thisOrder.getFirstChild());
  }
  Element newRoot = newdoc.createElement("processedOrders");
  NodeList processOrders = doc.getElementsByTagName("order");
  for (int orderNum = 0;
        orderNum < processOrders.getLength();
        orderNum++) {
    Element thisOrder = (Element)processOrders.item(orderNum);
    Element customerid =
      (Element)thisOrder.getElementsByTagName("customerid")
        .item(0);
    String limit = customerid.getAttributeNode("limit").getNodeValue();
    String total = thisOrder.getElementsByTagName("total").item(0)
        .getFirstChild().getNodeValue();
    double limitDbl = new Double(limit).doubleValue();
    double totalDbl = new Double(total).doubleValue();
    Element newOrder = newdoc.createElement("order");
    Element newStatus = newdoc.createElement("status");
    if (totalDbl > limitDbl) {
      newStatus.appendChild(newdoc.createTextNode("REJECTED"));
    } else {
      newStatus.appendChild(newdoc.createTextNode("PROCESSED"));
    }
    Element newCustomer = newdoc.createElement("customerid");
    String oldCustomer = customerid.getFirstChild().getNodeValue();
    newCustomer.appendChild(newdoc.createTextNode(oldCustomer));
    Element newTotal = newdoc.createElement("total");
    newTotal.appendChild(newdoc.createTextNode(total));
    newOrder.appendChild(newStatus);
    newOrder.appendChild(newCustomer);
    newOrder.appendChild(newTotal);
    newRoot.appendChild(newOrder);
  }
  newdoc.appendChild(newRoot);
  System.out.print(newRoot.toString());
...

После обработки orders.xml приложение создает новый элемент, processedOrders, который в конечном счете станет корневым элементом нового документа. Затем оно проходит через каждый заказ. Для каждого заказа оно выделяет информацию total и limit.

Далее приложение создает новые элементы для заказа: order, status, customerid и amount. Оно заполняет status на основе того, превышает ли итог кредит покупателя, и в соответствии с этим заполняет остальное.

После того, как приложение создало элементы, оно должно собрать их вместе. Сначала оно добавляет состояние, информацию о покупателе и итог в новый элемент order. Затем оно добавляет новый order в элемент newRoot.

Пока все это происходит, элемент newRoot на самом деле не присоединен к родительскому узлу. Когда приложение завершит обработку всех заказов, newRoot присоединяется к новому документу.

Наконец, приложение выводит данные, преобразовывая newRoot в String и просто посылая его в System.out.

Создание XML-файла

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

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

Важное обстоятельство, которое стоит здесь отметить, состоит в том, что поскольку XML-данные являются просто текстом, они могут быть форматированы любым способом. Например, вы можете создать разновидность stepThroughAll() которая будет создавать версию с отступами, красивую для печати. Только помните, что это будет создавать лишние узлы пропусков (текстовые).

...
import java.io.FileWriter;
...
  try {
    File newFile = new File("processedOrders.xml");
    FileWriter newFileStream = new FileWriter(newFile);
    newFileStream.write ("<?xml version=\"1.0\"?>");
    newFileStream.write ("<!DOCTYPE
        "+doc.getDoctype().getName()+" ");
    if (doc.getDoctype().getSystemId() != null) {
      newFileStream.write (" SYSTEM ");
      newFileStream.write (doc.getDoctype().getSystemId());
    }
    if (doc.getDoctype().getPublicId() != null) {
      newFileStream.write (" PUBLIC ");
      newFileStream.write (doc.getDoctype().getPublicId());
    }
    newFileStream.write (">");
    newFileStream.write (newRoot.toString());
    newFileStream.close();
  } catch (IOException e) {
  System.out.println("Can't write new file.");
}
...

Тождественные преобразования

В простом Document, подобном тому, который рассматривается в нашем учебнике, легко предположить, что выводимый XML прост, но помните, что следует рассматривать многие факторы, которые могут привести к усложнению - редко встречающиеся, такие, как файлы, содержимое которых определяется DTD или схемой. В большинстве случаев лучше полагаться на приложение, которое уже принимает все такие возможности во внимание.

Одним из способов, который разработчики часто выбирают для сериализации их объектов Document DOM состоит в создании тождественного преобразования. Это XSL Transformation, которая включает в себя таблицы стилей. Например:

...
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.FileOutputStream;
...
      newdoc.appendChild(newRoot);
      try {
        DOMSource source = new DOMSource(newdoc);
        StreamResult result =
          new StreamResult(new FileOutputStream("processed.xml"));
        TransformerFactory transFactory = TransformerFactory.newInstance();
        Transformer transformer = transFactory.newTransformer();
        transformer.transform(source, result);
      } catch (Exception e){
      e.printStackTrace();
    }
  }
}

Здесь вы создаете источник и результат, но поскольку вы работаете с тождественным преобразованием, вы не создаете объект для представления таблицы стилей. Если бы это было настоящее преобразование, в создании Transformer должна была бы использоваться таблица стилей. Вместо этого Transformer просто принимает источник (Document) и посылает его в результат (файл processed.xml).

DOM и Javascript

Рассмотрим работу с DOM на примере JavaScript. Работу с DOM при программировании приложений можно разделить на следующие выжные этапы (при использовании парсера MSXML):

Объект DOMDocument.

Как и любой ActiveX объект - объект DOMDocument создается при помощи оператора new:

var objDoc = new ActiveXObject("Msxml2.DOMDocument");

Ниже перечисленны наиболее выжные свойства и методы этого объекта:

Свойство Описание
async

Указывает, будет ли загрузка документа выполняться асинхронно (асинхронная загрузка может быть прерванна). true - асинхронная, false-нет.

documentElement

Указывает на элемент верхнего уровня в иерархии - корень дерева элементов.

resolveExternals

Указывает, будет ли документ принимать внешние объявления (DTD, внешние сущности и т.п.). Может принимать 2 значения: true - принимает, false - не принимает.

parseError содержит информацию о последней ошибке
url

Возвращает URL файла в виде, приведенном к каноническому.

validateOnParse

Указывает, будет ли документ проверяться на допустимость в процессе загрузки и анализа. Принимает 2 значения: true - проверка на допустимость осуществляется, false - нет.

Метод Описание
abort Прерывает асинхронную загрузку документа
load Загружает XML документ из указанного файла: xmlDoc.load("news.xml")
loadXML Загружает XML документ из переданной строки: xmlDoc.load("<news><item date='25.06.03'>In the ...</item></news>")

Итак, первый этап выглядит примерно так:

  var xmlDoc = new ActiveXObject("Msxml2.DOMDocument");
  xmlDoc.async = false;
  xmlDoc.resolveExternals = true; //принимать внешние определения
  xmlDoc.validateOnParse = true; //проверять на допустимость

  xmlDoc.load ("news.xml"); //загрузить файл

В данном примере будет загружен XML документ с именем books.xml, он может принимать внешние определения и будет проверен на допустимость.

Обработка ошибок: IXMLDOMParseError

Ошибки, возникающие в процессе анализа XML данных можно обработать после загрузки XML данных (вызова метода load или loadXML). Если не установлен флажок vlidateOnParse, проверяются только синтаксические ошибки, допущенные при составлении документа, в противном случае - все ошибки, в том числе и нарушающие допустимость XML документа. Информация об ошибке предоставляется свойством parseError, имеющим тип IXMLDOMParseError:

Свойство Описание
errorCode Код ошибки. В случае успеха равен 0.
line Номер строки исходного текста XML документа, в которой находится ошибка.
linepos Номер символа в строке исходного текста XML документа, в которой находится ошибка.
reason Текст сообщения об ошибке
srcText Возвращает полный исходный код строки, содержащей ошибку
url URL файла, содержащего ошибку.

С учетом вышесказанного проведем обработку ошибки загрузки документа XML:

  var xmlDoc = new ActiveXObject ("Msxml2.DOMDocument");
  xmlDoc.async = false;
  xmlDoc.resolveExternals = true; //принимать внешние определения
  xmlDoc.validateOnParse = true; //проверять на допустимость

  xmlDoc.load ("news_err.xml");
  var myErr = xmlDoc.parseError;
  if (myErr.errorCode != 0) { //далее следует обработка ошибки!
    alert("Ошибка: " + myErr.reason + "\nв файле " + myErr.url +
          "\nСтрока: " + myErr.line + "\nСимвол: " + myErr.linepos +
    "\nИсходный код: " + myErr.srcText);
  } else {
    alert ("Все нормально");
  }

Элементы: IXMLDOMElement

Элементы являются наиболее важной составляющей XML документов. Они выстраиваются в иерархическую структуру, в кторой для каждого элемента можно определить родительский элемент (parentNode), дочерние элементы (childNodes), соседние элементы слева и справа (previousSibling, nextSibling) и много другой полезной информации. Краткий перечень основных свойств и методов приведен ниже:

Свойство Описание
attributes Содержит список атрибутов данного узла (тип IXMLDOMNamedNodeMap)
baseName Базовое имя узла: для узла <nsp:elt ... > вернет elt
childNodes Содержит список дочерних узлов (тип IXMLDOMNodeList)
firstChild Первый дочерний элемент
lastChild Последний дочерний элемент
nextSibling Следующий соседний (правый) элемент
previousSibling Предыдущий соседний (левый) элемент
parent Родительский элемент
nodeName Возвращает полное имя узла: для узла <nsp:elt ... > вернет nsp:elt
tagName Имя элемента
prefix Префикс пространства имен: для узла <nsp:elt ... > вернет nsp
nodeType Возвращает тип узла:

§ 1 - элемент

§ 2 - атрибут

§ 3 - текст

§ 4 - секция CDATA

§ 5 - ссылка на сущность

§ 6 - сущность

§ 7 - инструкция по обработке XML документа

§ 8 - комментарий

§ 9 - документ (корень)

§ 10 - DTD

§ 11 - фрагмент документа

§ 12 - нотация

nodeValue Текст, ассоциированный с узлом
text Текст, ассоциированный с узлом, и дочерними узлами
xml Текст и разметка XML - содержимое данного элемента со всеми его дочерними элементами
Метод Описание
appendChild(newElem) Присоединить дочерний узел:
var newElem = xmlDoc.createElement ("name")
elem.appendChild (newElem);
cloneNode (flag) Создает копию текущего узла. Принимает логический аргумент, определяющий, клонируются ли его дочерние элементы
MyNewNode = currNode.cloneNode(true);
root.appendChild(MyNewNode);
getAttribute(name) Возвращает значение атрибута с указанным именем
getAttributeNode(name) Возвращает узел атрибута с указанным именем
hasChildNodes() Возвращает true, если у данного элемента есть дочерние элементы, и false-в противном случае.
removeChild(elem) Удаляет дочерний элемент
elem = xmlDoc.documnetElement.firstChild;
xmlDoc.documnetElement.removeChild (elem);
replaceChild(elem, newElem) Заменяет дочерний элемент
elem = xmlDoc.documnetElement.firstChild;
xmlDoc.documnetElement.replaceChild (elem, xmlDoc.documnetElement.lastChild);
setAttribute(name, val) Устанавливает значение атрибута с именем name

IXMLDOMNamedNodeMap

Свойство Описание
length Количество элементов в коллекции
Meтод Описание
getNamedItem(name) Возвращает элемент коллекции (узел) с указанным именем
nodeId = someNode.attributes.getNamedItem("id");
alert(nodeId.value);
item (i) Возвращает элемент коллекции (узел) с индексом i
for (var i=0; i<someNode.attributes.length; i++) {
    var attr=someNode.attributes.item(i);
}

IXMLDOMNodeList

Свойство Описание
length Количество элементов в коллекции
Meтод Описание
item (i) Возвращает элемент коллекции (узел) с индексом i
for (var i=0; i<someNode.attributes.length; i++) {
    var attr=someNode.attributes.item(i);
}
nextNode() Возвращает следующий узел коллекции
for (var i=0; i<someNode.childNodes.length-1; i++) {
    var next=someNode.childNodes.nextNode(i);
}
reset() Сброс счетчика

Задание на лабораторную работу

Необходимо реализовать скрипт для вывода XML данных из файла, созданного в первой лабораторной работе и дополненого данными, в виде таблицы с произвольным форматированием (в файл в формате html).

Практический пример: вывод дерева XML

function parse (node) {
    if (node.nodeType==1) {
      document.write ("<ul>"); //сдвигаем дочерние элементы
      //вывод стартового тэга
      document.write ("<" + node.tagName + "</span>");
      //выводим атрибуты
      for (var i=0; i < node.attributes.length; i++)
        document.write(" <span class='attr'>" +
            node.attributes.item(i).nodeName +
            "</span>=<span class='val'>\""+
            node.attributes.item(i).nodeValue+"\"</span>");

        //тэги завершаются иначе у элементов с содержимым
        if (node.hasChildNodes())
          document.write("<span class='tag'>&amp;gt;</span>");
        else  //... и без содержимого
          document.write("<span class='tag'> /&amp;gt;</span>");

        // перебираем дочерние элементы
        for (var i=0; i < node.childNodes.length; i++)
          parse (node.childNodes.item(i)); 
        //если были дочерние элементы, выводим заверш. тэг
        if (node.hasChildNodes())
            document.write("<span class='tag'></" + node.tagName + "></span>");
            document.write("</ul>");
        } else if  (node.nodeType==3) // текстовые узлы
          document.write ("<em>" + node.nodeValue + "</em>");
        // узлы других типов не обрабатываем:
        // (комментарии, ссылки на сущности и т.п.)
        else return;
  }

  var xmlDoc = new ActiveXObject ("Msxml2.DOMDocument.4.0");
  xmlDoc.async =false;
  xmlDoc.validateOnParse = false;
  xmlDoc.resolveExternals = false;

  var xmlFile = prompt("Input path to an XML file: ", "books.xml")
  xmlDoc.load(xmlFile);
  if (xmlDoc.parseError.errorCode != 0) {
    var myErr = xmlDoc.parseError;
    alert("Error: " + myErr.reason + "\nLine: "+myErr.line+
          "\nPos: " + myErr.linepos + "\nSource code: "+myErr.srcText);
  } else {
    var root = xmlDoc.documentElement;
    parse (root);  //начинаем разбор с корневого элемента
  }

Для обработки дерева в примере используется функция parse. Она вызывает рекурсивно сама себя. Это дает нам возможность обойти все дерево элементов, ввести их сатрибутами и текстом.

https://xerces.apache.org/xerces2-j/dom.html