elementFormDefault="qualified"规范意味着本方案中的所有非全局性元素也在目标命名空间中(只要它们没有明确说明位于其他的命名空间)。在模式文件中指定elementFormDefault="qualified"几乎总是一个好主意:你一般不想让同一个模式中的全局性和非全局性元素处于不同的命名空间。
< contact>总是包括名称字段,并且可以包含最多两个< mailing-address>元素和最多三个< phone-number>元素,这是可选的。
< mailing-address>必须总是按照顺序包含< address-line-1>、< city>、< state>和< zipcode>子元素。< mailing-address>可以在子元素< address-line-1>后紧跟一个子元素< address-line-2>。
< mailing-address>和< phone-number>元素都有位置属性,然而在这个模式中没有表示出来,目的是为了让位置容纳类似于"home"和"work"这样的值。
所有< area-code>、< local-phone-number>和< zipcode>元素的值被限制为特定的文本样式。
所有< state>元素的值被限制在50个州的缩写加上哥伦比亚区的列举范围内。
下面的XML文档遵照的是contactUsa.xsd模式:
< ?xml version="1.0"?>
< contact xmlns="http://dearjohn/address-book">
< family-name>Smithers< /family-name>
< given-name>Bill< /given-name>
< mailing-address location="home">
< address-line-1>1500 Dexter Ave.< /address-line-1>
< city>Seattle< /city>
< state>WA< /state>
< zipcode>98109< /zipcode>
< /mailing-address>
< mailing-address location="work">
< address-line-1>501 Pike St.< /address-line-1>
< address-line-2>Suite 2900< /address-line-2>
< city>Seattle< /city>
< state>WA< /state>
< zipcode>98101< /zipcode>
< /mailing-address>
< phone-number location="home">
< area-code>206< /area-code>
< local-phone-number>441-1695< /local-phone-number>
< /phone-number>
< phone-number location="mobile">
< area-code>206< /area-code>
< local-phone-number>778-9218< /local-phone-number>
< /phone-number>
< /contact>
addressBook.xsd模式
除了上面描述的contactUsa.xsd模式外,文中后面给出的地址簿的例子使用在addressBook.xsd中定义的如下模式:
< xs:schema targetNamespace="http://dearjohn/address-book"
xmlns:xs=http://www.w3.org/2001/XMLSchema
xmlns:address-book="http://dearjohn/address-book"
elementFormDefault="qualified">
< xs:include schemaLocation="contactUsa.xsd"/>
< xs:element name="address-book">
< xs:complexType>
< xs:sequence>
< xs:element ref="address-book:contact" minOccurs="0"
maxOccurs="unbounded"/>
< /xs:sequence>
< /xs:complexType>
< /xs:element>
< /xs:schema>
这个模式非常简单:它定义了一个< address-book>元素,此元素可以包括0或来自contactUsa.xsd模式的更多< contact>元素。
用XMLBeans服务生成XMLBeans
既然已经定义了这些模式,就可以使用在线XMLBeans服务来针对模式生成XMLBeans类型系统(Java API)。
由于有两个相关的模式文件,那么我可以把它们压缩到一个ZIP文件中去,以便上传。然后在XMLBeans服务Schema Upload Page中的"要上传的模式或ZIP文件"域指定ZIP文件。服务编译完模式之后,XMLBeans服务下载页就出现了,从这里我可以下载包括我的XMLBeans类型的JAR文件。默认文件名是xsdTypes.jar,但是在下载过程中,我将它重命名为bookTypes.jar。另外,我需要包含通用的XMLBeans支持代码的xmlbeans.jar文件。
那就它的全部东西了。你提供一个或多个模式,XMLBeans服务就返回一个包含你的类型的JAR文件。现在我准备创建一个地址簿应用,该应用处理来自于Java的XML地址簿和联系人。
AddressBookApp应用
AddressBookApp Java应用是一个简单的命令行应用。它提供了一个具有如下选项的菜单:
1. 打开地址簿
2. 打印所有联系人姓名
3. 打印所有联系人
4. 打印联系人
5. 根据姓名查找联系人
6. 根据地区码查找联系人
7. 根据州查找联系人
8. 从文件中添加联系人
9. 删除联系人
10.保存地址簿
11.退出
我将讲述上述这些功能的亮点,并利用代码片段来解释XMLBeans是如何被用来实现这些操作的。有些操作我没有提到,因为多个"打印"和"查找"操作彼此之间非常相似。
打开地址簿
此项操作的核心位于AddressBookApp的loadAddressBookFile方法中,其中一些代码如下所示:
File bookFile = null;
AddressBookDocument doc = null;
AddressBook book = null;
bookFile = new File(bookFilename);
doc = (AddressBookDocument) XmlLoader.load(bookFile);
book = doc.getAddressBook();
XmlLoader是基础XMLBeans类之一,它的加载方法是可以将XML文档读入XMLBeans类型系统的方式中的一种,利用这种方法可以通过XMLBeans API操作XML。上面的代码展示了一种使用XMLBeans加载XML文档的典型模式:调用XmlLoader的一个方法并且把结果强制转换到你正在加载的文档类型。在本例中是AddressBookDocument,这种类型代表一个与addressBook.xsd模式一致的XML文档。
当我第一次使用XMLBeans时,经常在这个地方遇到ClassCastException运行时异常的问题。这个异常总是意味着一种情况:指定的文档不是一个有效的模式实例,强制转换的目标类型(在我的例子中是AddressBookDocument)就是在从这里生成的。如果你遇到过这种错误,不要逐个模式地关注你尝试加载的实例文档的结构。要特别注意在模式和实例文档两者中的命名空间声明。利用像XML Spy这样的第三方模式敏感(schema-aware)XML工具非常有用。
一旦文档被成功地加载和强制转换,我就可以只调用AddressBookDocument的getAddressBook,来获得表示文件中< address-book>元素的XMLBean。我获得的AddressBook Java对象是应用程序中所有其他操作执行的对象。
注意,当我使用基于文件的方法加载XML文档时,XMLBeans也提供了从流中加载XML文档的方法。
打印所有联系人姓名
该操作调用AddressBookApp的 printAddressBookNames的方法,核心代码如下所示:
Contact contact = null;
int numRecs;
int i;
numRecs = book.sizeOfContactArray();
for (i=0; i< numRecs; i++)
{
contact = book.getContactArray(i);
System.out.println((i+1) + ": " +
getFormattedContactName(contact));
}
Contact是表示< contact>元素的XMLBeans Java类型。我调用AddressBook类的 sizeOfContactArray方法来找出有多少< contact>元素以< address-book>元素的子元素存在,其中< address-book>元素由book变量表示。然后对所有< contact>元素都照此办理,为每个联系人打印AddressBookApp的getFormattedContactName方法的结果。getFormattedContactName包括下列代码:
String familyName = contact.getFamilyName();
String givenName = contact.getGivenName();
return
new String(
((familyName == null) ? "[no family-name]" : familyName) +
", " +
((givenName == null) ? "[no given-name]" : givenName));
最重要的部分是头两行,在这两行里我调用Contact类的getFamilyName和getGivenName方法来获得< contact>元素的子元素< family-name> 和 < given-name>字符串的值。
联系人的列表在打印之前没有保存,把它留下给用户作为练习用。我可以给出一点提示,即XMLBeans的XQuery能力对于实现这一目的很有用。
希望你已经开始意识到XMLBeans是很直观的。生成的Java类型和在模式中定义的XML元素有同样的名称,这一点仅限于"java-fied"中。如果你熟悉XML文档的结构,那么就不难理解Java中XML文档的结构了。
根据名字查找联系人
这步操作会提示用户输入搜索字符串,然后打印出所有< address-book>中< contact>中的名字,在< contact>中包含了搜索字符串。代码如下:
inputStr = getStringInput("Enter search string:").toLowerCase();
if (inputStr != null)
{
numContacts = _currentBook.sizeOfContactArray();
for (i=0; i< numContacts; i++)
{
contact = _currentBook.getContactArray(i);
if (getFormattedContactName(contact).
toLowerCase().
indexOf(inputStr) != -1)
{
System.out.println((i+1) + ": " +
getFormattedContactName(contact));
}
}
}
这段代码非常简单。通过循环扫描< address-book>中的所有< contact>,检查对应各个联系人的getFormattedContactName返回的字符串是否包含了要搜索的那个字符串。这里之所以要使用getFormattedContactName函数,是因为这个函数可以方便地返回既包含了< family-name>元素又包含了< given-name>元素的字符串。注意,XMLBeans包含了XQuery功能,这样就可以使所有"查找"操作快速执行,不过,这些我要留到其他文章里讲。
从文件中添加联系人
这步操作会提示用户XML的文件名应该符合contactUsa.xsd模式,然后调用AddressBookApp的loadContactFile方法,这个方法和上面在"打开地址簿"部分所描述的loadAddressBookFile方法非常相似。
当新的< contact>元素被成功地载入到一个Contact XMLBeans对象中时,AddressBookApp的addNewContact方法会将其添加到< address-book>中,代码如下:
int numRecs;
int i;
MailingAddress addr, newAddr;
PhoneNumber phoneNum, newPhoneNum;
Contact newContact = _currentBook.addNewContact();
newContact.setFamilyName(contact.getFamilyName());
newContact.setGivenName(contact.getGivenName());
numRecs = contact.sizeOfMailingAddressArray();
for (i=0; i< numRecs; i++)
{
newAddr = contact.getMailingAddressArray(i);
addr = newContact.addNewMailingAddress();
if (newAddr.isSetLocation())
{
addr.setLocation(newAddr.getLocation());
}
if (newAddr.getAddressLine1() != null)
{
addr.setAddressLine1(newAddr.getAddressLine1());
}
... more of the same for other child elements
}
numRecs = contact.sizeOfPhoneNumberArray();
for (i=0; i< numRecs; i++)
{
newPhoneNum = contact.getPhoneNumberArray(i);
phoneNum = newContact.addNewPhoneNumber();
if (newPhoneNum.isSetLocation())
{
phoneNum.setLocation(newPhoneNum.getLocation());
}
if (newPhoneNum.getAreaCode() != null)
{
phoneNum.setAreaCode(newPhoneNum.getAreaCode());
}
if (newPhoneNum.getLocalPhoneNumber() != null)
{
phoneNum.setLocalPhoneNumber(
newPhoneNum.getLocalPhoneNumber());
}
}
这段代码用于替代XMLBeans的一个特性,在写这篇文章的时候,该特性还没有实现(XMLBeans当前还只有测试版的代码)。不过代码倒是很是简单:只是遍历树并传送来自元素及其属性的值,通过下面一行代码就可以最终实现这步操作:
book.setContact(newContact);
删除联系人
这是所有操作中最简单的操作。首先提示用户需要删除< contact>的索引,接着调用_currentBook.removeContact(index-1)函数,该函数将从< address-book>元素中删除指定的< contact>子元素。这里之所以要将用户提供索引减1,是因为为了方便起见,应用程序的用户接口的索引是基于1的。 保存地址簿
最后一步操作将< address-book>元素的当前状态写入到一个由用户提供名称的文件中。下面是相应的代码:
File bookFile = new File(inputStr);
PrintStream destStream =
new PrintStream(new FileOutputStream(bookFile),
false, "UTF-8");
destStream.println("");
HashMap propMap = new HashMap();
propMap.put(XmlOptions.SAVE_PRETTY_PRINT, null);
propMap.put(XmlOptions.SAVE_PRETTY_PRINT_INDENT, new Integer(2));
String xmlText = _currentBook.xmlText(propMap);
destStream.println(xmlText);
destStream.close();
这里我使用了一般的Java IO类创建了一个输出流,以后,XML文档将要写入到这个输入流中。注意,这里我指定了必须用编码("UTF-8")编写这个文档。XML文档的符号编码是个比较复杂的话题,所有这些都牵涉到国际化的问题。UTF是用于一种通用的安全的编码方式。
接下来打印< ?xml?>声明,这些声明都写在每个XML文档的开头,没有顺序要求。
下一步是生成实际的XML文本,该文本将被写入文件中。为完成这步操作,这里调用了XmlObject(所有XMLBeans类的基类)的 xmlText方法。xmlText方法通过选项的选项映像来控制它的行为。我使用SAVE_PRETTY_PRINT选项来请求人类可识别的格式化,并指定了SAVE_PRETTY_PRINT_INDENT选项,表示我希望有多种级别的XML结构以供选择,而不仅仅是只有两种选择。
最后,将XML文本写入到输出文件,接着关闭该文件。
编译AddressBookApp应用程序
运行应用程序是非常简单的:
javac -classpath "bookTypes.jar;xmlbeans.jar" address\AddressBookApp.java
(显然,在Unix/Linux中,符号"\"应改为"/")
在类路径中,惟一要添加的就是bookTypes.jar(即包含了按照我自己的模式生成的类型的JAR文件)和xmlbeans.jar(即包含了一般XMLBean支持的代码的JAR文件)。
注意,XMLBeans需要Java 2, Standard Edition (J2SE) 1.4环境的支持。
运行AddressBookApp应用程序
运行app也很简单:
java -cp ".;./bookTypes.jar;./xmlbeans.jar" address.AddressBookApp
ZIP文件里包含了实例代码,同时还包含了四个contact XML示例文件和两个示例地址名册:testbook.xml和fullbook.xml。testbook.xml仅包含在contactSmithers.xml中定义的联系人,fullbook.xml包含所有四个示例联系人。这些文件给了你足够的资料,通过这些资料你可以对应用程序中的所有可用操作进行试验。
结论
相信这个实例已经向我们展示了用XMLBeans处理XML是多么简单。我鼓励你下载实例代码并进行试验。然后用你自己的模式试一试。 我特意使这个实例相对简单一些,而更多地关注XMLBean根据你自己的模式所生成的特定模式的API。关于XMLBeans还有更多的内容,比如它可以操纵无相关模式的XML文档,可以使用XQuery在XML上执行复杂搜索和变换,还有其他一些功能。