Электронный магазин на Java и XML

       

Класс Product


Элемент product сложнее, чем элементы catalog и product_line, которые мы уже рассматривали. Ниже приводится DTD для этого элемента:

<!ELEMENT product (name.author*,artist*.description,price, quantity_in_stock,image*,onsale_date?,clip*)> <!ATTLIST product id ID #REQUIRED> <!ATTLIST product keywords CDATA #IMPLIED>

Элемент product может содержать до девяти различных типов дочерних элементов, многие из которых могут повторяться либо, напротив, отсутствовать. Этот элемент имеет два атрибута. В листинге 6.17 определены поля для каждого из этих элементов и атрибутов, во многом аналогично тому, как это было сделано для предыдущих классов. По-прежнему для повторяющихся элементов используется вектор Vector. Большая часть других элементов имеет тип Stri ng, но здесь есть несколько исключений. Так как мы знаем, что элемент price определяет цену, то есть доллары и центы, то для его представления используется тип double. Элемент quantity_in_stock содержит целое число, поэтому для его представления требуется тип int. Элемент on_sale_date представлен классом Date.

Листинг 6.17. Начало кода класса Product (Product.java)

package com.XmlEcomBook.Chap06;

import java.io.*; import java.util.*; import java.text.*; import org.w3c.dom.*;

public class Product {

private String id; private String keywords; private String name; private Vector authors = new Vector(); private Vector artists = new Vector(); private String description; private double price; private Integer discount; private int quantityInStock; private Vector images = new Vector(); private DateTime onSaleDate = new DateTime(); private Vector clips = new Vector();

Посмотрев на определения полей, вы можете заметить, что одно из них вам незнакомо. Для поля discount не имеется соответствующего дочернего элемента или атрибута в элементе product. Так получилось потому, что элемент price довольно-таки просто устроен — он содержит данные типа PCDATA и один атрибут discount, как показано ниже:

<!ELEMENT price (#PCDATA)>


<!ATTLIST price discount CDATA #IMPLIED>

Вместо того чтобы создавать для этого элемента новый класс всего лишь с двумя полями, мы включим оба этих поля прямо в класс Product. Поле discount имеет тип Integer. В данном случае мы не можем задействовать примитивный тип int, так как элемент discount является необязательным и может отсутствовать. В такой ситуации нужно воспользоваться объектом null типа Integer.



Другое отличие связано с полем description. В DTD это поле было представлено следующим образом:

<!ENTITY % running_text "(#PCDATA|bold|italics|

quote|link|general)*">

<!ELEMENT description (paragraph|general)* >

<!ELEMENT paragraph %running_text;>

<!ELEMENT bold (#PCDATA)>

<!ELEMENT italics (#PCDATA)>

<! ELEMENT quote (#PCDATA)>

<!ATTLIST quote attrib CDATA #IMPLIED>

<!ELEMENT link (#PCDATA)>

<!ATTLIST link href CDATA «REQUIRED

alt CDATA #IMPLIED>

<!ELEMENT general (#PCDATA)>

<!ATTLIST general type CDATA #REQUIRED>

Это сложная модель содержимого, включающая большое количество дочерних элементов, которые могут повторяться в произвольном порядке, что приводит к следующим двум затруднениям. Первое связано с представлением поля description в Java-программе. Вам нужно было бы создать класс description, которому можно передать объекты paragraph и general, а затем этот класс снова должен был бы получать к ним доступ. Сам класс Paragraph был бы классом сложного типа, способным содержать любой из своих пяти дочерних элементов и текстовых элементов. Второе затруднение связано с отображением всех этих данных в виде, допускающем редактирование. Было бы очень трудно отобразить эту структуру в формате HTML. Мы решили упростить обработку этих элементов.

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



Для класса Product предусмотрены два конструктора, показанные в листинге 6.18. Первый из них не имеет никаких аргументов и используется для создания нового элемента Product. Другому конструктору передается объект DOM Element, и на основе исходного кода XML конструктор создает объект Product. Конструктор сначала получает два атрибута, id и keywords, из элемента product. Для остальных полей созданы методы для извлечения всех элементов, дочерних по отношению к элементу product.

Листинг 6.18. Конструкторы класса Product (Product.java)

public Product() { }

public Product( Element productElement ) { id = productElement.getAttribute( "id" ); keywords = productElement.getAttribute( "keywords" ); extractName( productElement ); extractAuthors( productElement ); extractArtists( productElement ); extractDescription( productElement ); extractPrice( productElement ); extractQuantityInStock( productElement ); extractImages( productElement ); extractDate( productElement ); extractClips( productElement ); }

Метод extractName устроен достаточно просто. Он показан в листинге 6.19. В нем используется служебный метод extractTextFrom, который извлекает текст из элемента name. В методах extractAuthors и extractArti sts также используется этот служебный метод. Оба этих метода получают список элементов, дочерних по отношению к элементу product, а затем осуществляют цикл по этим элементам, извлекая из каждого содержащийся в нем текст и вызывая соответствующий метод для добавления элемента к объекту.



Листинг 6.19. Методы extractName, extractAuthors и extractArtists (Product.java)

private void extractName( Element productElement ) { name = Util.extractTextFrom( "name", productElement ); }

private void extractAuthors( Element productElement ) { NodeList authorList = productElement.getElementsByTagName( "author" ); for( int i = 0; i < authorList.getLength(); i++ ) { Element author = (Element)authorList.item(i); addAuthor( Util.extractTextFrom( "name", author ) ); } }



private void extractArtists( Element productElement ) { NodeList authorList = productElement.getElementsByTagName( "artist" ); for( int i = 0; i < authorList.getLength(); i++ ) { Element author = (Element)authorList.item(i); addArtist( Util.extractTextFrom( "name", author ) ); } }

Теперь мы перейдем к рассмотрению метода extractDescription. Он приведен в листинге 6.20. Этот метод получает элемент description из элемента product. Поскольку в интерфейсе API модели DOM не предусмотрена возможность получения только одного элемента с определенным именем, требуется метод getEle- mentsByTagName, который возвращает коллекцию узлов. Затем нужно проверить длину этой коллекции. Если эта длина больше нуля, в ней имеется элемент description. В таком случае нужно извлечь первый подходящий элемент, поскольку элемент description в коллекции только один. Наконец, мы используем служебный метод extractMarkupAsText для того, чтобы получить содержимое элемента description.



Листинг 6.20. Метод extractDescription (Product.java)

private void extractDescription( Element productElement ) { NodeList desc = productElement.getElementsByTagName( "description" ); if( desc.getLength() > 0 ) { NodeList contents = desc.item(0).getChildNodes(); description = Util.extractMarkupAsText( contents ); } }

Метод extractPrice должен извлечь содержимое элемента price, что определит фактическую цену, а также значение атрибута discount для этого элемента. В первой строке этого метода извлекается текстовое содержимое элемента price с помощью служебного метода. Полученный текст преобразуется к типу double с помощью объекта NumberFormat. Этот объект способен проанализировать полученный текст и извлечь из строки, содержащей дополнительные символы (в частности, знак $), само число, представляющее цену. Затем нам нужно получить информацию о скидках. Для этого извлекается коллекция элемента price, а из нее — атрибут discount. Затем на основе строки (значения атрибута discount) создается объект типа Integer. Этот метод показан в листинге 6.21.





Листинг 6.21. Метод extractPrice (Product.java)

private void extractPrice( Element productElement ) { String s = Util.extractTextFrom( "price", productElement ); NumberFormat nf = NumberFormat.getCurrencyInstance(); try { Number n = nf.parse( s ); price = n.doubleValue(); } catch( ParseException pe ) { price = 0; } NodeList priceNodes = productElement.getElementsByTagName( "price" ); if( priceNodes.getLength() > 0 ) { Element price = (Element)priceNodes.item(0); if( price != null ){ String d = price.getAttribute( "discount" ); if( d != null && !d.equals( "" ) ) { discount = new Integer( d ); } } } }

Извлечь информацию из элемента quantity_in_stock сравнительно легко. Используется знакомый нам метод extractTextFrom для получения строки, которая затем анализируется и преобразуется к типу i nt, как показано в листинге 6.22.



Листинг 6.22. Метод extractQuantitylnStock (Product.java)

private void extractQuantityInStock( Element productElement ) { String s = Util.extractTextFrom ( "quantity_in_stock", productElement ); quantityInStock = Integer.parseInt( s ); }

Элемент image может встречаться несколько раз, поэтому нам необходимо извлечь все дочерние элементы product с именем image, а затем использовать метод addlmage для добавления их к объекту product. Метод extractClips практически идентичен методу extractlmages, только он извлекает не изображения, а клипы. Оба эти метода приведены в листинге 6.23.

Листинг 6.23. Методы extractlmages и extractClips (Product.java)

private void extractImages( Element productElement ) { NodeList descNode = productElement.getElementsByTagName( "image" ); for( int i = 0; i < descNode.getLength(); i++ ) { addImage( new Image( (Element)descNode.item(i) ) ); } }

private void extractClips( Element productElement ) { NodeList descNode = productElement.getElementsByTagName( "clip" ); for( int i = 0; i < descNode.getLength(); i++ ) { addClip( new Clip( (Element)descNode.item(i) ) ); } }



Последний метод, который мы рассмотрим в связи с извлечением данных, — это метод extractDate, приведенный в листинге 6.24. В нем используется метод DOM getElementsByTagName для извлечения коллекции узлов с именем onsale_date. В случае обнаружения такого узла именно он используется для создания нового объекта Date (поскольку существует один и только один элемент onsale_date).



Листинг 6.24. Метод extractDate (Product.java)

private void extractDate( Element productElement ) { NodeList date = productElement.getElementsByTagName( "onsale_date" ); if( date.getLength() > 0 ) { onSaleDate = new DateTime( (Element)date.item( 0 ) ); } }

Теперь нам нужны методы для извлечения и задания значений всех полей. Большинство из этих методов достаточно просты, они похожи на методы getName и setName из листинга 6.25. Для полей id, keywords, description, price, discount, quantitylnStock и onSaleDate существуют аналогичные методы. Мы не приводим их здесь из соображений экономии места, но все они включены в исходный код примеров, находящихся на прилагаемом к книге компакт-диске.



Листинг 6.25. Методы getName и setName (Product.java)

public String getName() { return name; }

public void setName( String newName ) { name = newName; }

Поле authors относится к типу Vector, поэтому его нужно обрабатывать иными методами, чем приведенные выше методы getXxx и setXxx. У нас имеется метод addAuthor, который добавляет строку к вектору authors с помощью метода addElement. Чтобы удалить авторов из списка, имеется метод removeAll Authors, который удаляет всех авторов из элемента Product. Таким образом, можно заменять список авторов, для этого требуется удалить весь предыдущий список и добавить новый. Имеется также метод getAuthors, который возвращает перечень всех авторов. Для удаления, добавления и получения элементов artist, image и clip используются такие же методы, как и для элементов author, только все операции выполняются над соответствующими векторами. Эти методы показаны в листинге 6.26.





Листинг 6.26. Операции над элементами Author и Artist (Product.java)

public void addAuthor( String newAuthor ) { authors.addElement( newAuthor ); }

public void removeAllAuthors() { authors.removeAllElements(); }

public Enumeration getAuthors() { return authors.elements(); }

Как и другие объекты связывания данных, объекты Product преобразуются в формат XML с помощью метода toXML, приведенного в листинге 6.27. Этот метод начинается с записи открывающего тега для элемента product. Атрибут id является обязательным, поэтому его нужно записывать всегда. Поскольку элемент keywords является необязательным, всегда нужно проверять его наличие, прежде чем записывать. Затем мы записываем дочерние элементы, поэтому нужно сделать отступ. Дочерний элемент name не вызывает никаких затруднений, так как это просто строка, то есть объект типа String. Вам нужно просто записать открывающий тег, саму строку и закрывающий тег. Элементы author и artist являются повторяющимися, поэтому нужно выполнить цикл по вектору Vector и записать каждый элемент по отдельности. Элементы description, price и quantity_in_stock представляют собой простые строки, так что они записываются тем же способом, что и элемент name. Вектор изображений содержит экземпляры класса Image, поэтому нужно сделать цикл по этому вектору и к каждому изображению применить метод toXML К объекту onSaleDate также применяется метод toXML, а вектор, содержащий элементы clip, обрабатывается так же, как и другие вектора, то есть с помощью цикла. К каждому из составляющих вектор объектов clip поочередно применяется метод toXML. Наконец, нужно установить отступ равным исходному и написать закрывающий тег элемента product.



Листинг 6.27. Преобразование Product в XML (Product.java)

public void toXML( XMLWriter writer ) throws IOException { writer.write( "<product id='" + id + "'" ); if( keywords != null ) writer.write( " keywords='" + keywords + "'" ); writer.writeln( ">" ); writer.indent(); writer.writeln( "<name>" + name + "</name>" ); for( int i = 0; i < authors.size(); i++ ) { writer.writeln( "<author>" + authors.elementAt( i ) + "</author>" ); } for( int i = 0; i < artists.size(); i++ ) { writer.writeln( "<artist>" + artists.elementAt( i ) + "</artist>" ); } writer.writeln( "<description>" + description + "</description>" ); writer.writeln( "<price>" + price + "</price>" ); writer.writeln( "<quantity_in_stock>" + quantityInStock + "</quantity_in_stock>" ); for( int i = 0; i < images.size(); i++ ) { Image image = (Image)images.elementAt( i ); image.toXML( writer ); } if( onSaleDate != null ) onSaleDate.toXML( writer ); for( int i = 0; i < clips.size(); i++ ) { Clip clip = (Clip)clips.elementAt( i ); clip.toXML( writer ); } writer.unindent(); writer.writeln( "</product>" ); }

}






Содержание раздела