1、XML格式介绍
XML是一种能够表示层级结构的数据表现格式,下面是使用XML的使用要点:
- XML是大小写敏感的。
- XML的结束标签可以写成:/> 但是绝对不能省略。
- XML中,属性值不管是什么类型的,都必须用引号括起来。
- XML中,属性值必须有值。
- 一条常用的经验法则:属性只应该用来修改值得解释,而不是用来指定值。
2、解析XML文档
要处理XML文档,就要先解析它。解析器是这样一个程序:它读入一个文件,确认这个文件具有正确的格式,然后将其分解成各种元素,使得程序员能够访问这些元素。java库提供了两种XML解析器:
- 像文档对象模型(Document Object Model,DOM)解析器这样的树形解析器,它们将读入的XML文档转成树型结构放入内存中(若XML特别大,要考虑内存问题)。
- 像XML简单API(Simple API for XML,SAX)解析器这样的流机制解析器,他们在读入文档是会触发相应的事件。
我们先来介绍DOM解析器:
test.xml结构:
<?xml version="1.0" encoding="UTF-8"?>
<XML>
<HEAD>
<PAGESIZE SIZE="页">15</PAGESIZE>
<ORGID>00</ORGID>
</HEAD>
<RESULT>
<RST>
<CODE>000000</CODE>
<MSG>交易成功</MSG>
</RST>
<FONT>
<NAME>Times Roman</NAME>
<SIZE UNITE="ps">15</SIZE>
</FONT>
</RESULT>
</XML>
DOM代码:
public class XMLForDomLearn {
/**
* 采用递归的方式展示所有DOM节点
* @param ele 需要展示的节点
* @param has 是否有子节点
*/
public static void showXML(Element ele ,boolean has){
if(has){
//获取所有子节点
NodeList children = ele.getChildNodes();
for(int i = 0;i<children.getLength();i++){
Node child = children.item(i);
//去除空白
if(child instanceof Element){
Element childElement = (Element) child;
//获取属性列表
String atList = getAttributeList(childElement);
if(hasNodes(childElement)){
System.out.println("<"+childElement.getTagName()+atList+">");
showXML(childElement,true);
System.out.println("<"+childElement.getTagName()+"/>");
}else{
showXML(childElement,false);
}
}
}
}else{
String atList = getAttributeList(ele);
Text textNode = (Text)ele.getFirstChild();
System.out.println("<"+ele.getTagName()+atList+">"+textNode.getData().trim()+"<"+ele.getTagName()+"/>");
}
}
/**
* 展示每一个节点的属性列表
* @param ele 属性所在节点
* @return 属性列表
*/
public static String getAttributeList(Element ele ){
NamedNodeMap nm;
String atList = "";
//获得属性列表并遍历
for(int j =0;j< (nm = ele.getAttributes()).getLength();j++){
Node attribute = nm.item(j);
String name = attribute.getNodeName();
String value = attribute.getNodeValue();
atList += " "+name+":"+value;
}
return atList;
}
/**
* 查看当前节点是否有子节点
* @param ele 需要查看的节点
* @return true:有 false:无
*/
public static boolean hasNodes(Element ele){
boolean flag = false;
NodeList children = ele.getChildNodes();
for(int i = 0;i<children.getLength();i++){
Node child = children.item(i);
if(child instanceof Element){
flag = true;
}
}
return flag;
}
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
//使用工厂模式创建DOM解析器
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
File xmlF = new File("doc/test.xml");
//通过文件获得DOM对象
Document docFile = builder.parse(xmlF);
System.out.println(docFile);
//通过io流获得DOM对象
Document docIn = builder.parse(new FileInputStream(xmlF));
System.out.println(docIn);
//得到根节点 XML
Element root = docFile.getDocumentElement();
System.out.println("<"+root.getTagName()+">");
//展示所有节点
if(hasNodes(root)){
showXML(root,true);
}
System.out.println("<"+root.getTagName()+"/>");
}
}
结果:
[#document: null]
[#document: null]
<XML>
<HEAD>
<PAGESIZE SIZE:页>15<PAGESIZE/>
<ORGID>00<ORGID/>
<HEAD/>
<RESULT>
<RST>
<CODE>000000<CODE/>
<MSG>交易成功<MSG/>
<RST/>
<FONT>
<NAME>Times Roman<NAME/>
<SIZE UNITE:ps>15<SIZE/>
<FONT/>
<RESULT/>
<XML/>
3、文档类型定义:DTD
DTD文件是用来对XML文档进行约束规范的。
<FONT>
<NAME>Times Roman</NAME>
<SIZE UNITE="ps">15</SIZE>
</FONT>
当解析上面XML时,取得节点的子节点时,会得到5个节点:
- 和之间的空白字符。
- 节点。
- 和之间的空白字符。
- 节点。
- 和之间的空白字符。
这也是我为什么要用if(child instanceof Element)
此行代码进行空白符的过滤,但是我们使用DTD文档类型定义文件的话,就不会出现这个问题。
关于文档类型定义DTD语法的学习,请点击此链接;
使用dtd文件后,不要忘记将工厂的验证特性打开。
factory.setValidating(true);
要想去掉空白字符的话,需要下面代码。
factory.setIgnoringElementContentWhitespace(true);
4、XML Schema
XML Schema与DTD文档的作用一致,都是描述XML文档的目录结构的,只是语法等有一些差别,在这里我就不进行详细的介绍了。
关于文档类型定义XML Schema语法的学习,请点击此链接;
解析带有Schema的XML文件和解析带有DTD的文件相似,但是有3点差别:
必须打开对命名空间的支持,即使在XML文件里可能不会用到它。
factory.setNamespaceAware(true);
必须通过如下的“魔咒”来准备好处理Schema的工厂。
final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com.xml/jaxp/properties/schemaLaguage"; final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; factory.setAttribute(JAXP_SCHEMA_LANGUAGE,W3C_XML_SCHEMA);
解析器不会丢弃元素中的空白字符。
5、使用XPth来定位信息
如果要定位某个XML文档中的一段特定信息,那么,通过遍历DOM树的众多节点来进行查找会显得有些麻烦。XPath语言使得访问树节点变得很容易。例如有如下XML文档:
test.xml:
<XML>
<DATA>
<USERNAME>admin</USERNAME>
<PASSWORD>abc123</PASSWORD>
</DATA>
<RESULT>
<RST>
<HEAD>
<CODE>000000</CODE>
<MSG color="green">交易成功</MSG>
</HEAD>
</RST>
<RST>
<HEAD>
<CODE>000001</CODE>
<MSG color="red">交易失败</MSG>
</HEAD>
</RST>
</RESULT>
</XML>
我们可以通过XPath表达式/XML/DATA/USERNAME来获得USERNAME的值。
public class XPathLearn {
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
File f = new File("doc/test.xml");
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.parse(f);
//创建XPath对象
XPath path = XPathFactory.newInstance().newXPath();
//获得/XML/DATA/USERNAME的text文本
String username = path.evaluate("/XML/DATA/USERNAME", doc);
System.out.println("username:"+username);
// /XML/RESULT/RST[1] 表示/XML/RESULT下的第一个RST子元素,下标从1开始
String succedCode = "/XML/RESULT/RST[1]/HEAD/CODE";
//MSG/@color 表示MSG元素的属性color
String color = "/XML/RESULT/RST[1]/HEAD/MSG/@color";
succedCode = path.evaluate(succedCode, doc);
System.out.println("succedCode:"+succedCode);
color = path.evaluate(color, doc);
System.out.println("color:"+color);
//获得一组节点
NodeList nodes = (NodeList)path.evaluate("/XML/RESULT/RST", doc, XPathConstants.NODESET);
for(int i=0;i<nodes.getLength();i++){
Node node = nodes.item(i);
if(node instanceof Element){
System.out.println(((Element) node).getTagName());
}
}
//获得一组节点,若结果只有一个,则以XPathConstants.NODE替代。
Node node = (Node)path.evaluate("/XML/DATA", doc,XPathConstants.NODE);
if(node instanceof Element){
System.out.println(((Element) node).getTagName());
}
//若结果是一个数字,那么用XPathConstants。NUMBER替代。
int count = ((Number)path.evaluate("count(/XML/RESULT/RST)", doc,XPathConstants.NUMBER)).intValue();
System.out.println("count:"+count);
}
}
结果:
username:admin
succedCode:000000
color:green
RST
RST
DATA
count:2
关于XPath还有很多有用的函数,请结合API文档使用。
6、流机制解析器
DOM解析器会完整的读入XML文档,然后将其转换成一个树形的数据结构。对于大多数应用,DOM都运行的很好。但是,如果文档很大,并且处理算法又非常简单,可以在运行时解析节点,而不必看到完整的树形结构,那么DOM可能就会显得效率低下了。在这种情况下,我们应该使用流机制解析器。
6.1、使用SAX解析器
SAX解析器是解析XML输入数据的各个组成部分时会报告事件,但不会以任何方式存储文档,而是由事件处理器建立相应的数据结构。实际上,DOM解析器是在SAX解析器的基础上构建的,它在接收到解析器事件时构造DOM树。
在使用SAX解析器时,需要一个处理器来为各种解析器事件定义事件动作。ContentHandler接口定义了若干个在解析文档时解析器会调用的回调方法。下面是最重要的几个:
- startElement 和endElement在每当遇到起始或终止标签时调用。
- characters在每当遇到字符数据时调用。
- startDocument和endDocument分别在文档开始和结束时各调用一次。
例如,在解析以下片段时:test.xml
<DATA>
<USERNAME type="MD5">admin</USERNAME>
</DATA>
解析器会产生以下回调:
- startElement,元素名:DATA
- startElement,元素名:USERNAME
- characters,内容:admin
- endElement,元素名:USERNAME
- endElement,元素名:DATA
处理器必须覆盖这些方法,让它们执行我们想要让它们执行的动作。
public class XMLForSAXLearn {
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
File f = new File("doc/test.xml");
//获得SAX解析器实例
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
//定义处理器 该类为ContentHandler、DTDHandler、EntityResolver、ErrorHandler接口定义了空的方法,只需要覆盖即可
DefaultHandler handler = new DefaultHandler(){
@Override
public void startDocument() throws SAXException {
System.out.println("开始解析test.xml文档!");
}
@Override
public void endDocument() throws SAXException {
System.out.println("解析test.xml文档结束!");
}
@Override
public void startElement(String uri, String localName,
String qName, Attributes attributes) throws SAXException {
System.out.print("uri:"+uri+" localName:"+localName+" qName:"+qName);
for(int i =0;i<attributes.getLength();i++){
String name = attributes.getLocalName(i);
String value = attributes.getValue(i);
System.out.print(" name:"+name+" value:"+value);
}
System.out.println();
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
System.out.println("uri:"+uri+" localName:"+localName+" qName:"+qName);
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
System.out.println(new String(ch,start,length).trim());
}
};
parser.parse(f, handler);
}
}
结果:
开始解析test.xml文档!
uri: localName: qName:DATA
uri: localName: qName:USERNAME name:type value:MD5
admin
uri: localName: qName:USERNAME
uri: localName: qName:DATA
解析test.xml文档结束!
7、生成XML文档
现在已经知道怎样编写取读XML的java程序了。下面就让我们开始介绍它的反向过程,即产生XML输出:用文档的内容构建一颗DOM树,然后写出该树的所有内容。
public class WriterXML {
/**
* 不带命名空间生成DOM文档
* @return DOM 文档
* @throws ParserConfigurationException
*/
public static Document getDOMWithoutNS() throws ParserConfigurationException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
//创建一个空白DOM
Document doc = builder.newDocument();
//创建根节点 XML
Element XML = doc.createElement("XML");
//创建子节点 HEAD
Element HEAD = doc.createElement("HEAD");
//创建文本节点
Text HEADText = doc.createTextNode("这是不带命名空间的头节点的内容!");
//生成DOM树
doc.appendChild(XML);
XML.appendChild(HEAD);
HEAD.appendChild(HEADText);
//为HEAD 添加属性 color="red"
HEAD.setAttribute("color", "red");
return doc;
}
/**
* 带命名空间生成DOM文档
* @return DOM 文档
* @throws ParserConfigurationException
*/
public static Document getDOMWithNS() throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//设置命名空间感知
factory.setNamespaceAware(true);
//创建空白DOM
Document doc = factory.newDocumentBuilder().newDocument();
String nameSpace = "http://www。w3.org/2000/svg";
//创建根节点 XML 带命名空间,注意使用NS的方法
Element XML = doc.createElementNS(nameSpace, "XML");
Element HEAD = doc.createElement("HEAD");
Text HEADText = doc.createTextNode("这是带命名空间的头节点的内容");
//生成DOM树
doc.appendChild(XML);
XML.appendChild(HEAD);
HEAD.appendChild(HEADText);
//为HEAD 添加属性 color="red" 带有命名空间
HEAD.setAttributeNS(nameSpace,"color", "red");
return doc;
}
/**
* 写出XML 就是这种格式
* @param doc
* @param name
* @throws IOException
*/
public static void writeXML(Document doc, String name) throws IOException{
//这是一个魔咒,就要这样写,
DOMImplementation imp = doc.getImplementation();
DOMImplementationLS impLS = (DOMImplementationLS) imp.getFeature("LS", "3.0");
LSSerializer ser = impLS.createLSSerializer();
//设置XML中的空格和换行
ser.getDomConfig().setParameter("format-pretty-print", true);
//得到XML字符串
String str = ser.writeToString(doc);
System.out.println(str);
//将XML输出到文件中
LSOutput out = impLS.createLSOutput();
//设置字符编码集
out.setEncoding("UTF-8");
out.setByteStream(Files.newOutputStream(Paths.get("doc", name+".xml")));
ser.write(doc, out);
}
public static void main(String[] args) throws ParserConfigurationException, IOException{
Document docWithoutNS = getDOMWithoutNS();
Document docWithNS = getDOMWithNS();
writeXML(docWithoutNS,"docWithoutNS");
writeXML(docWithNS,"docWithNS");
}
}
结果生成:
docWithoutNS.xml文档:
<?xml version="1.0" encoding="UTF-8"?>
<XML>
<HEAD color="red">这是不带命名空间的头节点的内容!</HEAD>
</XML>
docWithNS.xml文档:
<?xml version="1.0" encoding="UTF-8"?>
<XML xmlns="http://www。w3.org/2000/svg">
<HEAD xmlns:NS1="http://www。w3.org/2000/svg" NS1:color="red">这是带命名空间的头节点的内容</HEAD>
</XML>
8、XSL转换
XSL转换(XSLT)机制可以指定将XML文档转换为其他格式的规则,例如,转换为纯文本、XHTML或任何其他的XML格式。XSLT通常用来将各种机器可读的XML格式转译为另一种机器可读的XML格式,或者将XML转译为适用于人类阅读的表示格式。
假如我们想要把有雇员记录的XML文件转换成HTML文件。
XML文件:
<staff>
<employee>
<name>Carl Cracker</name>
<salary>75000</salary>
<hiredate year="1987" moth="12" day="15"/>
</employee>
<employee>
<name>Harry Hacker</name>
<salary>50000</salary>
<hiredate year= "1989" month="10" day="1"/>
</employee>
<employee>
<name>Tony Tester</name>
<salary>40000</salary>
<hiredate year="1990" month="3" day="15"/>
</employee>
</staff>
我们希望的输出是一张HTML表格:
<table border="1">
<tr>
<td>Carl Cracker</td><td>$75000</td><td>1987-12-15</td>
</tr>
<tr>
<td>Harry Hacker</td><td>$50000</td><td>1989-10-1</td>
</tr>
<tr>
<td>Tony Tester</td><td>$40000</td><td>1990-3-15</td>
</tr>
</table>
具有转换模板的样式表形式如下:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html"/>
<xsl:template match="/staff">
<table border="1"><xsl:apply-templates/></table>
</xsl:template>
<xsl:template match="/staff/employee">
<tr><xsl:apply-templates/></tr>
</xsl:template>
<xsl:template match="/staff/employee/name">
<td><xsl:apply-templates/></td>
</xsl:template>
<xsl:template match="/staff/employee/salary">
<td>$<xsl:apply-templates/></td>
</xsl:template>
<xsl:template match="/staff/employee/hiredate">
<td><xsl:value-of select="@year"/>-<xsl:value-of select="@month"/>-<xsl:value-of select="@day"/></td>
</xsl:template>
</xsl:stylesheet>
在我们的例子中,xsl:output元素将方法设定为HTML。而其他有效的方法为:xml和text。
一个典型的模板(取代上面模板的template):
<xsl:template match="/staff/employee">
<tr><xsl:apply-templates/></tr>
</xsl:template>
match属性的值是一个XPathc表达式。改模板声明没看到XPath集/staff/employee中的一个节点时,将做以下操作:
- 产生字符串。
- 在处理其子节点时,持续应用该模板。
- 当处理完所有子节点时,产生字符串。
XSLT处理器以检查根元素开始其处理过程。每当一个节点匹配某个模板时,就会应用该模板(如果匹配多个模板,就会使用最佳匹配的那个)如果没有匹配的模板,处理器会执行默认操作。对于文本节点,默认操作是把它的内容囊括到输出中去。对于元素,默认操作是不产生任何输出,但会继续处理其子节点。
public class XMLTOHTML {
public static void main(String[] args) throws TransformerFactoryConfigurationError, TransformerException {
//获取样式表
File styleSheet = new File("doc/xmlToHtml.xsl");
StreamSource styleSource = new StreamSource(styleSheet);
//获取样式表转换器
Transformer t = TransformerFactory.newInstance().newTransformer(styleSource);
//获取要转换有几种方式:1、StreamSource 2、DOMSource 3、SAXSource 4、StAXSource
StreamSource source = new StreamSource(new File("doc/test.xml"));
//要转换为的方式有几种:1、DOMResult 2、SAXResult 3、StreamResult
StreamResult result = new StreamResult(new File("doc/result.html"));
t.transform(source, result);
}
}
结果生成result.html:
<table border="1">
<tr>
<td>Carl Cracker</td>
<td>$75000</td>
<td>1987--15</td>
</tr>
<tr>
<td>Harry Hacker</td>
<td>$50000</td>
<td>1989-10-1</td>
</tr>
<tr>
<td>Tony Tester</td>
<td>$40000</td>
<td>1990-3-15</td>
</tr>
</table>
关于文档转换XSLT语法的学习,请点击此链接;