XML
可拓展标记语言(EXtensible Markup Language)是一种支持用户自定义的标记语言。原本作为替代HTML的技术对数据进行呈现,现主要用于对数据进行描述和存储。
可拓展:标签支持用户自定义。
标记语言:通过一系列的标记来对文档的语义、结构、格式进行定义。
XML作为一种具有特定格式的文档,现在通常具有以下功能和特点:
- 作为数据传输的标准。可读性、可扩展性、可维护性是数据传输标准的三个特性。XML具备这三个特性。
- 作为配置文件。比起.properties资源文件,XML具有更丰富的描述信息。
- 能够持久化数据。
- 可以作为数据库和数据文件。更快但不具备安全性。
- 兼容性更强,更容易跨平台和跨版本。XML数据以文本格式存储,这使得XML在不损失数据的情况下,更容易扩展或升级到新的操作系统、新的应用程序或新的浏览器,平台变更的成本更低。
文档元素
XML文档由以下几个部分组成:
处理指令 | <?XML version="1.0" encoding="utf-8"?> <?xml-stylesheet href="class.css" type="text/css"?> | 用于指导解析引擎如何解析XML文档内容,可引入CSS样式表、指定编码等 |
---|---|---|
约束 | <!ELEMENT students (stu*)> <!ELEMENT stu (#PCDATA|id|name|age)*> | 用于对文件中的元素及属性进行约定 |
元素(标签) | <student id="001"> <name>张三</name> <age>20</age> </student> | XML的标记,也被称为标签、节点,是成对出现的 |
属性 | 为元素提供额外信息 | |
文本 | 元素中包裹的内容 | |
实体 | < | 转义的内容 |
注释 | <!-- 注释 --> | 为内容提供解释和说明 |
未解析字符数据 | <![CDATA[&]]> | 不被解析器所解析的内容 |
只有处理指令、约束和注释允许出现在根标签之外。
主体内容
XML的主体内容由一个的根标签包裹。标签的特点:
- 不能使用:< & > ’ ",需要使用实体进行转义
- 大小写敏感
- 不推荐使用- . :作为连接,推荐使用_
- 标签成对出现
- 标签可以嵌套,但不允许交叉嵌套
- 根标签只允许出现一个,且包裹所有其它元素
标签内可以拥有属性,作为标签的元数据,为元素提供额外的信息。属性的特点:
- 单个标签的属性不可重复
- 属性值可以使用单引号包裹也可使用双引号包裹
属性和元素相比:
- 属性不能包含多个值(元素可以)
- 属性不能包含树结构(元素可以)
- 属性不容易扩展
- 属性难以阅读和维护。
故尽量使用元素来描述数据,而使用属性来提供与数据无关的信息。
对于不能使用的字符,可以使用实体进行转义:
< | > | & | " | ' |
---|---|---|---|---|
< | > | & | " | ’ |
可以通过约束对实体进行约定和声明:
<!DOCTYPE 根元素名称[
<!ENTITY 实体名 实体内容>
]>
<!DOCTYPE root[
<!ENTITY company "杰普软件科技有限公司">
]>
<root>&company;</root>
若想避免对实体的解析,可以通过<![CDATA[实体]]>
来避免编码。
XML的注释为<!-- 注释 -->
。
处理指令
处理指令,简称PI(Processing Instruction),用于指挥解析引擎如何解析XML文档内容。包括文档声明和CSS引入声明等。
处理指令由<?指令 属性?>所组成。
命名空间
命名空间是根标签的一种特殊的标记,用于区分不同的XML。根标签的xmlns:命名空间
属性用于对命名空间进行说明和进一步区分,一般是一个网址。
以下是综合上面的一个例子:
<?xml version="1.0" encoding="UTF-8"?><!-- 文档声明,说明版本和编码 -->
<?xml-stylesheet href="class.css" type="text/css"?><!-- 引入CSS -->
<linja:class xmlns:linja="http://www.linja18.ml"><!-- 根标签,声明了命名空间linja -->
<student id="001">
<name>张三</name>
<age>20</age>
</student>
<student id="002">
<name>李四</name>
<age>20</age>
</student>
</linja:class>
约束
XML约束能够对元素和属性进行规定。常用的约束技术有DTD和Schema。这两种方式没有很大区别,只是Schema能够约束得更严格。以下对DTD约束进行说明。
DTD约束能够直接写在XML文档的根标签之前(内部DTD),也可以通过引入.dtd文件的方式进行。
- 本地约束文件的引入:
<!DOCTYPE 根标签 SYSTEM "约束文件的路径">
- 公共约束文件的引入:
<!DOCTYPE 根标签 PUBLIC "DTD的Key" "DTD的URL">
约束元素的内容:<!ELEMENT 标签名 (内容模式)>
内容模式包括:
👌 | ❌ | ||
---|---|---|---|
EMPTY | 空标签 | tom | |
(#PCDATA) | 不能含子标签 | tom | tom12 |
ANY | 任何 | tom12 |
修饰符包括:
| () | | | , | + | * | ? | |
| — | — | — | — | — | — | — |
| 分组 | 或 | 按顺序 | 1或n次 | 0或n次 | 0或1次 | 1次 |
约束属性和属性的值:
<!ATTLIST 标签名称
属性名称 属性类型 属性特点
属性名称 属性名称 ... 属性类型 属性特点
>
属性类型和特点包括:
属性类型 | 描述 | 属性特点 | 描述 |
---|---|---|---|
CDATA | 任意字符 | #REQUIRED | 必须拥有的属性 |
ID | 唯一 | #IMPLIED | 可选属性 |
IDREF | ID的引用 | #FIXED | 属性值固定的可选属性 |
IDREFS | 多个ID的引用 | default-value 默认值 | 拥有默认值的属性 |
enumerated (枚举值1 | 枚举值2 | 枚举值3…) | 枚举 |
以下是这些约束的一个例子:
<!ELEMENT students (student+)>
<!ELEMENT student (name,age?,score*)>
<!ATTLIST student id CDATA #REQUIRED>
<!ELEMENT name (#PCDATA)>
<!ATTLIST name firstName CDATA #IMPLIED>
<!ELEMENT age (#PCDATA)>
<!ATTLIST age xuAge CDATA #FIXED "20">
<!ELEMENT score (#PCDATA)>
<!ATTLIST score sel (60|80|100) #REQUIRED>
XML解析
通过对XML文件进行解析,可以对XML进行读取、写入和展示。
文档解析
XML解析分为两种方式:DOM解析和SAX解析。
DOM方式
文档对象模型(Document Object Model)是XML标准的制定者W3C组织推荐的处理XML的一种方式。
DOM要求解析器把整个XML文档装载到一个Document对象中,Document对象包含文档元素,即根元素,根元素包含N个子元素,包括文本、属性、注释等元素,呈树形结构。
一个XML文档解析后对应一个Document对象,对象中元素之间存在结构关系,能够从文档元素出发找到任何一个元素,进行随机读写。但如果XML文档过大,则可能会出现内存溢出的现象。
SAX方式
SAX(Simple API for XML)不是官方标准,但它是 XML 社区事实上的标准,几乎所有的XML解析器都支持它。
SAX会按行解析XML文档,但不会保留元素之间的关系,且解析完成后,不会保留元素的信息,只会在解析到某个元素时会触发一个事件(方法)。因此SAX不会占用大量内存来保存XML文档数据,效率高,但只能对XML文档进行一次性顺序读取,而不能随机读取或写入。
XML解析器
目前主流的XML解析器包含以下三个:
- Crimson(Sun):JDK1.5之前,Java使用的解析器。性能差。
- Xerces(IBM):IBM开发的DOM、SAX解析器,现由Apache基金会维护。是JDK1.5之后采用的解析器,也是JAXP的默认解析器。但不过在JDK中的包名与原版不太一样。
- Aelfred2(dom4j):DOM4J默认解析器。
JAXP(Java API for XML Processing)是Java SE的一部分,它由javax.xml包、org.w3c.dom包、org.xml.sax包及其子包组成。
Xerces解析器
在javax.xml.parsers包中,定义了几个工厂类。通过这些类的DOM或SAX解析器对象,可以对XML文档进行解析。如下面的XML文档(eg.xml):
<linja:class xmlns:linja="http://www.linja18.ml">
<student id="001">
<name>张三</name>
<age>20</age>
</student>
<student id="002">
<name>李四</name>
<age>20</age>
</student>
</linja:class>
DOM解析
DOM解析器首先获取一个文档对象,然后获取文档节点的所有子节点(getChildNodes()),包括属性/元素/文本等,不同类型的节点对应一个枚举的数值。通过对子节点的遍历,并对节点类型进行判断,可以进而获得节点的名称、Key、Vaule等。
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance(); // 得到解析器工厂对象
DocumentBuilder documentBuilder =
factory.newDocumentBuilder(); // 得到解析器
Document document =
documentBuilder.parse(new File("eg.xml")); // 解析xml文件,获得文档对象
System.out.println(document.getNodeType()); // 获取节点类型的枚举数值,9(即文档节点)
NodeList nodeList = document.getChildNodes(); // 获取文档节点的所有子节点
System.out.println(nodeList.getLength()); // 获取子节点个数,1(即linja:class)
for(int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i); // 获取当前下标的子节点
}
DOM写入
DOM解析器不但可以读取,也可以写入。写入时,先写入几个节点,再将节点的关系进行连接。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.newDocument(); // 通过解析器获取新文档对象
Element teachersEle = document.createElement("teachers"); // 创建元素
Element teacherEle = document.createElement("teacher");
Attr idAttr = document.createAttribute("id"); // 创建属性
idAttr.setValue("x11"); // 给属性赋值
Element nameEle = document.createElement("name");
Text text = document.createTextNode("wangzh"); // 创建文本
Comment comment = document.createComment("这是一个注释"); // 创建注释
document.appendChild(comment); // 给文档对象添加注释
document.appendChild(teachersEle); // 给文档对象添加一个元素teachers
teachersElemer.appendChild(teacherEle); // 给teachers添加元素teacher
teacherlement.setAttributeNode(idAttr); // 给teacher添加属性
teacherlement.setAttribute("desc", "ABCBoy"); // 另一种直接添加属性的方法
teacherlement.appendChild(nameEle); // 给teacher添加name元素
nameElemetnt.appendChild(text); // 给name添加文本
TransformerFactory factory2 = TransformerFactory.newInstance(); // 得到转换工厂
Transformer transformer = factory2.newTransformer(); // 得到转换器
transformer.setOutputProperty(OutputKeys.ENCODING,"UTF-8"); // 设置转换时的编码
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); // 设置转换时是否添加换行
Source xmlSource = new DOMSource(document); // 设置转换源
Result outputTarget = new StreamResult(
new FileOutputStream("src/teacher.xml")); // 设置转换输出
transformer.transform(xmlSource, outputTarget); // 转换器开始转换
SAX解析
SAX解析器会对XML文件进行按行解析,在文档开头、解析到开标签、文本、关标签和文档结尾时,会分别调用相应的方法,并提供这些节点相应的名称、属性对象等。这些方法重写了org.xml.sax.helpers.DefaultHandler类中的方法。SAX方法不支持写入。
class MyHandler extends DefaultHandler {
public void startDocument() throws SAXException {} // 开始解析时调用的方法
public void startElement(String uri, String localName,
String qName, Attributes attributes) throws SAXException {
// 解析到元素开标签时调用的方法
// uri:命名空间、localName:本地名称(不带前缀)
// qName:标签带前缀全称、attributes属性列表
System.out.println(attributes.getIndex("id")); // 获取id属性的下标
System.out.println(attributes.getType("id")); // 获取id属性的类型(CDATA)
System.out.println(attributes.getValue("id")); // 获取id属性的值
System.out.println(attributes.getQName(1)); // 获取下标为1的属性的属性名
}
public void endElement(String uri, String localName,String qName) throws SAXException {}
// 解析到元素关标签时调用的方法
public void characters(char[] ch, int start, int length) throws SAXException {
// 解析到文本时调用的方法
String string = new String(ch, start, length); // 获取解析到的文本
System.out.println(string);
}
public void endDocument() throws SAXException {} // 结束解析时调用的方法
}
public class SaxTest {
public static void main(String[] args) throws Exception {
SAXParserFactory factory = SAXParserFactory.newInstance(); // 获取工厂对象
SAXParser saxParser = factory.newSAXParser(); // 获取解析器
saxParser.parse(new File("src/student.xml"),
new MyHandler()); // 开始解析,同时在解析到某些元素时调用相应的方法
}
}
Aelfred2解析器
Aelfred2是开源项目DOM4J的默认解析器。这种解析器会一次性直接将整个XML文档装入一个dom4j.Document对象中,不需要进行手动解析。通过Document对象,可以直接获取其中的节点和子节点,还可以通过节点路径直接获取相应的节点。这个解析器也可以写。
DOM解析
public void testRead() throws Exception {
SAXReader saxReader = new SAXReader(); // 获取解析器对象
Document document = saxReader.read("src/student.xml"); // 获取文档对象
Element rootElement = document.getRootElement(); // 获取根节点
System.out.println(rootElement.getName()); // 打印根节点名字
List<Element> elements = rootElement.elements(); // 获取根节点的全部子节点列表
elements.forEach(t -> { // 遍历子节点
List<Attribute> list = t.attributes(); // 获取当前节点的属性
list.forEach(p -> { // 遍历属性
System.out.println(p.getName() + ":" + p.getValue()); // 当前属性键值对
System.out.println(p.getNamespaceURI());
System.out.println(p.getNamespacePrefix());
});
List<Element> list2 = t.elements(); // 获取当前节点的子节点
list2.forEach(l -> {
System.out.println(l.getText()); // 获取当前节点的文本
System.out.println(l.getTextTrim()); // 获取当前节点去除了空白字符的文本
});
});
List<Node> list = document.selectNodes("//student/name"); // 获取所有student下的name节点
list.forEach(t-> {
System.out.println(t.getText());
});
Node node = document.selectSingleNode("/class/student/name"); // 获取这个路径的第一个节点
}
DOM写入
public void testWrite() throws IOException {
Document document = DocumentHelper.createDocument();
document.addElement("person")
.addElement("name")
.addText("小李");
OutputFormat format = new OutputFormat();
format.setEncoding("utf-8");
format.setIndent(true);
format.setNewlines(true);
XMLWriter writer = new XMLWriter(
new PrintWriter("src/person.xml"), format);
writer.write(document);
writer.flush();
writer.close();
}