Если вы помните из моих предыдущих блогов, я освещаю различные подходы к синтаксическому анализу 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