Цель лабораторной работы:
1) Познакомиться c интерфейсом и объектной моделью DOM.
2) Научиться программировать приложения с использованием DOM на Java
XML DOM = XML Document Object Model. Является рекомендацией W3C. XML DOM позволяет: обходить узлы дерева элементов и атрибутов, манипулировать его структуры и содержании.
XML DOM платформенно-независимый стандарт. Можно создать сценарий в языках PERL, Java, Python и.т.д. После создания дерева объектов документ полностью загрузится в память, позволяя доступ к отдельным узлам. Синтаксический анализатор XML позволяет использовать готовые методы для управления элементов.
Тип | Описание |
---|---|
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.
В предыдущих уроках мы с вами рассмотрели принципы проектирования правильно оформленных и допустимых (valid) XML документов. Нам с вами так же знаком тот факт, что браузеры не проверяют документы на допустимость, даже если с ними ассоциированны DTD или схема. Однако, в тех случаях, когда XML применяют не как средство разметки документов, а как средство структурирования данных, проверка на допустимость очень важна. Известно так же, что в популярнейший браузер Microsoft Internet Explorer встроен XML анализатор, проверяющий допустимость XML документов на уровне расширенной объектной модели. Эта одна из причин, по которой мы должны с ней познакомиться.
Еще одной причиной является использование XML не в браузерах, а, например, в программах, написанных на Visual С++.
В этом случае приходится читать XML файлы и отображать данные вручную
. Существует 2 типа анализаторов (парсеров) XML, использующих принципиально разный подход к разбору документов:
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 - Simple API for XML, простой программный интерфейс приложений для XML. SAX предоставляет последовательный доступ к элементам XML документа. Анализаторы такого типа читают XML документ и сообщают программе о встреченных символах по мере их появления. Например, сообщает, когда находит открывающий тэг элемента, символьные данные и закрывающий тэг. Такой программный интерфейс называется интерфейсом основанным на событиях. SAX является отличной альтернативой DOM в следующих случаях:
Возможно наибольшим преимуществом SAX является значительно меньшее использование памяти, по сравнению с DOM. При использовании SAX потребление памяти не возрастает с размером файла.
SAX позволяет остановить процесс разбора в любой момент. Это позволяет использовать его для поиска уникальных данных. Например, необходимо найти товар в каталоге, после чего остановить поиск.
Для множества приложений нет необходимости читать XML документ целиком, для получения необходимых данных. SAX предоставляет такую возможность, позволяя снизить ресурсоемкость процесса разбора.
Можно привести еще несколько преимуществ SAX, однако существует и ряд ограничений:
Чтобы работать с информацией в XML-файле, файл должен быть разобран для создания объекта Document
.
Объект Document является интерфейсом, так что его экземпляр не может быть создан непосредственно, обычно вместо этого приложение использует фабрику. Подробности этого процесса различаются от реализации к реализации, но идеи одни и те же. (Опять-таки, Уровень 3 стандартизирует эту задачу.) Например, в среде Java разбор файла является 3-шаговым процессом:
DocumentBuilderFactory. Этот объект создает
DocumentBuilder.
DocumentBuilder.
DocumentBuilderдействительно выполняет разбор для создания объекта
Document.
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 обеспечивает управление параметрами парсера через следующие методы:
setCoalescing(): Определяет, превращает ли парсер узлы CDATA в текст и соединяет ли их с окружающими текстовыми узлами (если возможно). Значение по умолчанию -
false.
setExpandEntityReferences(): пределяет, расширяются ли внешние ссылки на сущности. Если
true, внешние данные вставляются в документ. Значение по умолчанию -
true.
setIgnoringComments(): Определяет, игнорируются ли комментарии в файле. Значение по умолчанию -
false.
setIgnoringElementContentWhitespace(): Определяет, игнорируются ли пропуски между элементами (аналогично тому, как браузер интерпретирует HTML). Значение по умолчанию -
false.
setNamespaceAware(): Определяет, обращает ли парсер внимание на информацию пространства имен. Значение по умолчанию -
false.
setValidating(): По умолчанию парсер не проверяет правильность документов. Установите здесь
trueдля проверки правильности.
При всех разнообразных возможностях в создании парсера
многие вещи могут быть неправильно сделаны. Как показано в примере,
приложение сводит все это в единственное родовое 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); } ...
Когда парсер создал документ, приложение может проходить через него для обработки данных.
Если документ разобран и создан 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 документ."); } }
Отношения предок-потомок и братские отношения предлагают альтернативный способ для перебора всех потомков данного узла, который может быть более подходящим в некоторых ситуациях, например, когда для понимания данных важны отношения и порядок, в котором появляются потомки.
На Шаге 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-данные являются просто
текстом, они могут быть форматированы любым способом. Например, вы
можете создать разновидность 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 при программировании приложений можно разделить на следующие выжные этапы (при использовании парсера MSXML):
Как и любой 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
, он может принимать внешние определения и будет проверен на допустимость.
Ошибки, возникающие в процессе анализа 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 ("Все нормально"); }
Элементы являются наиболее важной составляющей 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 |
Свойство | Описание |
---|---|
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);
}
|
Свойство | Описание |
---|---|
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).
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'>&gt;</span>"); else //... и без содержимого document.write("<span class='tag'> /&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. Она вызывает рекурсивно сама себя. Это дает нам возможность обойти все дерево элементов, ввести их сатрибутами и текстом.