Если вы помните из моих предыдущих блогов, я освещаю различные подходы к синтаксическому анализу XML-сообщений с использованием невероятно банального сценария
Pete’s Perfect Pizza , компании по производству пиццы с большими идеями. В этой истории вы являетесь сотрудником Pete’s, и вас попросили внедрить систему для отправки заказов со стойки регистрации на кухню, и у вас возникла идея использовать XML. У вас только что работает ваш SAX Parser, но Пит становится глобальным, открывая кухни по всему миру, принимая заказы через Интернет.
Но подожди минутку … разве я не сказал это в своем последнем блоге? Дежавю? Сегодняшний блог является версией альтернативной реальности моего блога JAXB, поскольку сценарий остается тем же, но решение меняется. Вместо демонстрации JAXB я буду исследовать XMLBeans.
Итак, Пит нанял нескольких консультантов, которые придумали план расширения вашего удобного XML-сообщения, и они определили его с помощью схемы. Они также улучшили ваше сообщение, добавив одну из своих собственных схем клиентов. В результате следующие XSD-файлы попадают в ваш почтовый ящик, и вам нужно заняться …
<?xml version="1.0" encoding="UTF-8"?>
<!-- edited with XMLSpy v2011 sp1 (http://www.altova.com) by Roger Hughes (Marin Solutions Ltd) -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ppp="http://www.petesperfectpizza.com" xmlns:cust="http://customer.dets" targetNamespace="http://www.petesperfectpizza.com" elementFormDefault="qualified" attributeFormDefault="unqualified" version="1.00">
<!-- Import the Namespaces required -->
<xs:import namespace="http://customer.dets" schemaLocation="customer.xsd"/>
<!-- The Root Node -->
<xs:element name="PizzaOrder">
<xs:annotation>
<xs:documentation>A wrapper around the customer and the pizza order</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="orderID" type="ppp:CorrelationIdentifierType"/>
<xs:element name="date" type="ppp:DateType"/>
<xs:element name="time" type="ppp:TimeType"/>
<xs:element name="Customer" type="cust:CustomerType"/>
<xs:element ref="ppp:pizzas"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- The Pizza Order-->
<xs:element name="pizzas">
<xs:annotation>
<xs:documentation>This is a list of pizzas ordered by the customer</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="pizza" type="ppp:PizzaType" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="PizzaType">
<xs:sequence>
<xs:element name="name" type="ppp:PizzaNameType">
<xs:annotation>
<xs:documentation>The type of pizza on the menu</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="base" type="ppp:BaseType">
<xs:annotation>
<xs:documentation>type of base</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="quantity" type="ppp:QuantityType">
<xs:annotation>
<xs:documentation>quantity of pizzas</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="PizzaNameType">
<xs:restriction base="xs:token">
<xs:enumeration value="Margherita">
<xs:annotation>
<xs:documentation>Plain and Simple</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Marinara">
<xs:annotation>
<xs:documentation>Garlic Pizza...</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Prosciutto e Funghi">
<xs:annotation>
<xs:documentation>Ham and Musheroom</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Capricciosa">
<xs:annotation>
<xs:documentation>with an egg</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="BaseType">
<xs:restriction base="xs:token">
<xs:enumeration value="thin">
<xs:annotation>
<xs:documentation>thin base traditional</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="thick">
<xs:annotation>
<xs:documentation>Thick base</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="QuantityType">
<xs:restriction base="xs:nonNegativeInteger"/>
</xs:simpleType>
<xs:simpleType name="CorrelationIdentifierType">
<xs:restriction base="xs:token">
<xs:maxLength value="44"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="DateType">
<xs:annotation>
<xs:documentation>The date is in the Common Era (minus sign in years is not permitted)</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:date">
<xs:pattern value="\d{4}-\d{2}-\d{2}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="TimeType">
<xs:annotation>
<xs:documentation>The time zone although not included UTC is implied</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:time">
<xs:pattern value="\d{2}:\d{2}:\d{2}(\.\d+)?"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
<?xml version="1.0" encoding="UTF-8"?>
<!-- edited with XMLSpy v2011 sp1 (http://www.altova.com) by Roger Hughes (Marin Solutions Ltd) -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:cust="http://customer.dets" targetNamespace="http://customer.dets" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name="Customer" type="cust:CustomerType">
<xs:annotation>
<xs:documentation>Generic Customer Definition</xs:documentation>
</xs:annotation>
</xs:element>
<xs:complexType name="CustomerType">
<xs:sequence>
<xs:element name="name" type="cust:NameType"/>
<xs:element name="phone" type="cust:PhoneNumberType"/>
<xs:element name="address" type="cust:AddressType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="NameType">
<xs:sequence>
<xs:element name="firstName" type="cust:FirstNameType"/>
<xs:element name="lastName" type="cust:LastNameType"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="FirstNameType">
<xs:annotation>
<xs:documentation>The Customer's first name</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:token">
<xs:maxLength value="16"/>
<xs:pattern value=".{1,16}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="LastNameType">
<xs:annotation>
<xs:documentation>The Customer's surname</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:token">
<xs:pattern value=".{1,48}"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="AddressType">
<xs:sequence>
<xs:element name="houseNumber" type="cust:HouseNumberType"/>
<xs:element name="street" type="cust:AddressLineType"/>
<xs:element name="town" type="cust:AddressLineType" minOccurs="0"/>
<xs:element name="area" type="cust:AddressLineType" minOccurs="0"/>
<xs:element name="postCode" type="cust:PostCodeType"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="HouseNumberType">
<xs:annotation>
<xs:documentation>The house number</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:nonNegativeInteger"/>
</xs:simpleType>
<xs:simpleType name="AddressLineType">
<xs:annotation>
<xs:documentation>A line of an address</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:token">
<xs:pattern value=".{1,100}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="PhoneNumberType">
<xs:restriction base="xs:token">
<xs:maxLength value="18"/>
<xs:pattern value=".{1,18}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="PostCodeType">
<xs:restriction base="xs:token">
<xs:maxLength value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
Вы понимаете, что с таким уровнем сложности вы будете долго возиться с SAX, и вы также можете сделать несколько ошибок. Должен быть лучший способ, верно? В конце концов, XML существует уже какое-то время, поэтому большинство из них может быть полезным. Немного погуглив, вы натолкнетесь на XMLBeans и поймете, что есть …
XMLBeans использует специальный компилятор для преобразования схемы XML в набор связанных классов Java, которые определяют типы, необходимые для доступа к элементам XML, атрибутам и другому контенту в типобезопасный способ. Этот блог не является учебником, охватывающим все тонкости XMLBeans, который можно найти
здесь, от ApacheЗа исключением того, что ключевая идея синтаксического анализа или демаршаллинга XML заключается в том, что вы компилируете свои классы Java с использованием XMLBeans, а затем используете эти классы в своем приложении.
При использовании любой XML-схемы для компилятора классов Java, самый лучший подход — поместить все ваши схемы и компилятор в отдельный файл JAR. Вы можете смешивать их с исходным кодом вашего приложения, но это обычно затуманивает основание кода, усложняя обслуживание. При создании файла JAR XMLBeans вы можете создать файл POM, который выглядит примерно так:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.captaindebug</groupId>
<artifactId>xml-tips-xmlbeans</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>XML Beans for Pete's Perfect Pizza</name>
<dependencies>
<dependency>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
<version>2.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>xmlbeans-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>xmlbeans</goal>
</goals>
</execution>
</executions>
<inherited>true</inherited>
<configuration>
<schemaDirectory>src/main/resources</schemaDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
… что очень прямо вперед. Итак, возвращаясь к
идеальной пицце Пита , вы создали JAR-файл XMLBeans, и все, что осталось сделать, — это изучить, как он работает, как показано в тестах JUnit ниже:
public class PizzaXmlBeansTest {
private PizzaOrderDocument instance;
@Test
public void testLoadPizzaOrderXml() throws IOException, XmlException {
String xml = loadResource("/pizza-order1.xml");
instance = PizzaOrderDocument.Factory.parse(xml);
PizzaOrder order = instance.getPizzaOrder();
String orderId = order.getOrderID();
assertEquals("123w3454r5", orderId);
// Check the customer details...
CustomerType customerType = order.getCustomer();
NameType nameType = customerType.getName();
String firstName = nameType.getFirstName();
assertEquals("John", firstName);
String lastName = nameType.getLastName();
assertEquals("Miggins", lastName);
AddressType address = customerType.getAddress();
assertEquals(new BigInteger("15"), address.getHouseNumber());
assertEquals("Credability Street", address.getStreet());
assertEquals("Any Town", address.getTown());
assertEquals("Any Where", address.getArea());
assertEquals("AW12 3WS", address.getPostCode());
Pizzas pizzas = order.getPizzas();
PizzaType[] pizzasOrdered = pizzas.getPizzaArray();
assertEquals(3, pizzasOrdered.length);
// Check the pizza order...
for (PizzaType pizza : pizzasOrdered) {
PizzaNameType.Enum pizzaName = pizza.getName();
if ((PizzaNameType.CAPRICCIOSA == pizzaName) || (PizzaNameType.MARINARA == pizzaName)) {
assertEquals(BaseType.THICK, pizza.getBase());
assertEquals(new BigInteger("1"), pizza.getQuantity());
} else if (PizzaNameType.PROSCIUTTO_E_FUNGHI == pizzaName) {
assertEquals(BaseType.THIN, pizza.getBase());
assertEquals(new BigInteger("2"), pizza.getQuantity());
} else {
fail("Whoops, can't find pizza type");
}
}
}
private String loadResource(String filename) throws IOException {
InputStream is = getClass().getResourceAsStream(filename);
if (is == null) {
throw new IOException("Can't find the file: " + filename);
}
return toString(is);
}
private String toString(InputStream is) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
copyStreams(is, bos);
return bos.toString();
}
private void copyStreams(InputStream is, OutputStream os) throws IOException {
byte[] buf = new byte[1024];
int c;
while ((c = is.read(buf, 0, 1024)) != -1) {
os.write(buf, 0, c);
os.flush();
}
}
}
Приведенный выше код может выглядеть длинным и сложным, но на самом деле он состоит только из трех шагов: во-первых, превратить тестовый файл в подходящий тип, такой как String или InputStream (XMLBeans может обрабатывать несколько различных типов ввода). Затем используйте вложенный класс Factory для обработки вашего источника XML, превращающегося в объект документа. Наконец, используйте возвращенный объект документа, чтобы проверить, что ваши результаты соответствуют ожидаемым (это самый большой шаг). Как только вы довольны стандартным использованием XMLBeans, вы добавляете его в код XML-анализатора вашей
кухни Pete Perfect Pizza и распространяете по всему миру среди множества пиццерий Pete.
Одна из сильных сторон использования такой инфраструктуры, как XMLBeans, заключается в том, что если когда-либо вносятся изменения в схему, то все, что требуется для включения этих изменений, — это перекомпилировать, соответственно исправляя код клиента. Это может показаться головной болью, но это гораздо меньше головной боли, чем попытка переделать SAX-парсер. С другой стороны, XMLBeans критиковался за медлительность, но у меня никогда не было слишком много проблем. Теоретически он будет использовать больше памяти, чем SAX — это может или не может быть правдой — он создает наборы классов, но, опять же, то же самое делают некоторые производные классы SAX ContentHandler.
Наконец, следует отметить, что с 2009 года не было выпуска XMLBeans, что может или не может быть хорошей вещью в зависимости от вашей точки зрения, хотя я должен подчеркнуть, что этот код определенно не является избыточным, поскольку, по моему мнению, знания, он до сих пор широко используется в ряде крупных проектов.
Исходный код доступен на GitHub по адресу:
git: //github.com/roghughe/captaindebug.git
С http://www.captaindebug.com/2012/01/approaches-to-xml-part-4-xmlbeans.html