XML
一、简介
XML 指可扩展标记语言(EXtensible Markup Language)。
XML 是一种标记语言,很类似 HTML,也是使用标签来操作。
可扩展:HTML里面的标签是固定的,每个标签都有特定的含义。而XML里面的标签可以自己定义,甚至可以写中文的标签。
XML的用途:HTML是用于显示数据的,XML也是可以显示数据的,但它不是XML的主要功能,XML 的设计宗旨是传输数据,而非显示数据。
XML是两个版本,1.0和1.1。我们一般使用1.0版本,因为1.1版本不能向下兼容。
二、XML的应用
-
不同系统之间传输数据
最早的时候,消息发送信息的格式为字符串,比如:
字符串中我们用String str = "zhangsan:lisi:hello:2019-4-19";
:
作为分隔,每一段代表一个信息。 但是这样的代码,别人很难明白其中的含义,阅读性很差。 而且如果想扩展一下,添加一些新的信息,就会很困难。所以这种格式可维护性很差。
现在我们使用的新的方式来表示信息:
这样的话,可阅读性大大提高,而且扩展起来很方便,大大提高了程序的可维护性。String str = " <message id='1'> <sender>zhangsan</sender> <getter>lisi</getter> <content>hello</content> <date>2019-4-19</date> </message> ";
-
用来表示生活中有关系的数据
生活中的事物之间存在大量的关系,我们可以使用xml来表示这些有关系的数据。
例如:
-
经常用在配置文件
例如在java连接数据库的时候,需要数据库的URL、用户名和密码,我们把它存在xml中,如果想要修改,就不许需要修改源代码,便于修改和维护。
三、xml的语法
xml的文档说明
- 创建一个文件,后缀名是 .xml 。
- 如果写xml,第一步必须要有一个文档说明
<?xml version="1.0" encoding="gbk"?>
- 文档说明必须写在第一行第一列
-
属性:
version:xml的版本
encoding:xml编码、gbk、utf-8、iso8859-1(不包含中文)
standalone:是否需要依赖其他的文件 yes/no
如果 xml中文乱码问题,在保存 xml文件的时候把编码设置成跟文档声明一样的类型。
定义元素(标签)
标签的定义:
- 标签定义必须有开始有结束:
<person> </person>
- 标签没有内容,可以在标签内结束:
<aa/>
-
标签可以嵌套,但是必须合理嵌套
合理嵌套:
不合理嵌套:<aa> <bb></bb> </aa>
不正确!<aa> <bb></aa> </bb>
- 一个xml中只能有一个根标签!其他的标签都是这个标签下面的标签。
- 注意:在xml中,空格和换行都当成内容来解析,下面这两段代码的含义是不同的。
<aa> hello </aa>
- xml中标签的命名规则
- xml代码区分大小写,
<p>
这两个标签是不同的。<P>
- 标签名称不能以
或者数字
开头。下划线
<2a>
都不可允许。<_aa>
- 不能以
、xml
、XML
等开头。Xml
- 标签里面不能包含空格和冒号。
a b
都是不允许的。b:c
- 标签可以使用中文,但是不建议这样做。
- xml代码区分大小写,
定义属性
html是标记型文档,可以有属性。
xml也是标记型文档,也可以有属性。
属性定义的要求:
- 一个标签上可以有多个属性
<person id1="aaa" id2="bbb"></person>
- 属性名称不能相同。
- 属性名称与属性值之间用
连接,属性值要用引号括起来,可以是单引号,可以是双引号。=
- 属性的命名规范和元素的命名规范是一致的。
注释
写法:
<!-- xml的注释 -->
注意:注释是不能有嵌套的。
注释也不能放到第一行!
特殊字符
<a>a<b</a>
如果想在xml中显示 a<b ,是不能正常显示的,因为把<当成标签。
如果想要显示,就需要使用转义字符。
常用的转义字符:
<
< 小于
>
> 大于
&
& 和号
'
’ 单引号
"
" 引号
CDATA区
如果我们想要输入:
我们知道是显示不出来的,因为里面的小于号都需要转义。
而CDATA区可以解决多个字符都需要的转义的操作。
写法:
<![CDATA[ 内容 ]]>
上面的我们要修改为:
<![CDATA[ <aa>if(a<b && b<c && c<d){}</aa> ]]>
这样把特殊字符当做文本内容,而不是标签。
PI指令(处理指令)
可以在xml中设置样式。可以通过PI指令来引入外部css文件。
写法:
<?xml-stylesheet type="text/css" href="xxx.css" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" ?>
示例:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/css" href="xxx.css" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" ?>
<person>
<name>zhangsan</name>
<age>20</age>
</person>
注意:设置样式,只对于英文名称的标签起作用,对于中文名称的标签是不起作用的。
四、xml的约束
- 为什么需要约束?
比如我们现在定义一个person的xml文件,只想要在里面保存人的一些信息,比如name、age等,但是如果在xml文件中写入了一个<?xml version="1.0" encoding="utf-8"?> <person> <name>zhangsan</name> <age>20</age> </person>
的标签,发现它是可以正常显示的,因为符合语法规范。但是猫肯定不是人的信息,所以我们这个时候就需要一种技术来规定xml中只能出现的元素,这就叫xml的约束。<猫>
- xml中约束的技术:dtd约束和schema约束
dtd约束
我们创建一个文件,后缀名是
.dtd
。
步骤:
- 看xml中有多少元素,有几个元素,在dtd文件中写几个
<!ELEMENT>
-
判断这个元素是简单元素还是复杂元素。
复杂元素: 有子元素的元素
格式:
例如上面的person:<!ELEMENT 元素名称 (子元素)>
<!ELEMENT person (name,age)>
简单元素: 没有子元素
格式:
例如上面的age、name:<!ELEMENT 元素名称 (#PCDATA)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
- 需要在xml文件中引入dtd文件。
<!DOCTYPE 根元素名称 SYSTEM "dtd文件的路径">
如果使用浏览器打开 xml文件的话,浏览器只负责校验 xml的语法,不负责校验 xml的约束。
如果想要校验 xml的约束,需要使用工具,工具有很多种,例如 MyEclipse。
dtd的三种引入方式
- 引入外部的dtd文件
<!DOCTYPE 根元素 SYSTEM "dtd路径">
- 内部的dtd
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE person [ <!ELEMENT person (name,age)> <!ELEMENT name (#PCDATA)> <!ELEMENT age (#PCDATA)> ]> <person> <name>zhangsan</name> <age>20</age> </person>
- 使用外部的dtd文件(网络上的dtd文件)
在structs2 使用配置文件 使用 外部的dtd文件,需要用到这个知识。<!DOCTYPE 根元素 PUBLIC "DTD名称" "dtd文档的URL">
dtd定义元素
语法:
<!ELEMENT 元素名称 约束>
- 简单元素:
<!ELEMENT name (#PCDATA)>
:约束name是字符串类型(#PCDATA)
:元素为空,不能有内容。EMPTY
:任意,可以为空,也可以不为空ANY
- 复杂元素:
例如:<!ELEMENT 元素名称 (子元素)>
<!ELEMENT person (name,age)>
这样的格式下,name和age子元素只能出现一次
设置子元素出现的次数:
:出现最少一次,一次或者多次+
:最多出现一次,零次或者一次?
*
:任意次数都可以,零次、一次、多次都可以
示例:
子元素用<!ELEMENT person (name+,age?)>
,
隔开,也表示元素出现的顺序,必须按照先后顺序出现。
子元素用
隔开,表示只能出现其中的任意一个。|
dtd定义属性
使用dtd约束标签上的属性,语法:
<!ATTLIST 元素名称
属性名称 属性类型 属性的约束
>
属性类型
- 字符串类型:CDATA
<!ATTLIST person name CDATA #REQUIRED >
- 枚举类型:表示一定范围内只能出现其中一个。
<!ATTLIST person name (AA|BB|CC) #REQUIRED >
- ID 表示属性的取值不能重复,而且必须以字母或者下划线开头。
<!ATTLIST person name ID #REQUIRED >
属性的约束
#REQUIRED:属性必须存在
#IMPLIED:属性可有可无#REQUIRED
#FIXED:表示一个固定值#IMPLIED
直接值:表示属性默认值,当在标签上不写这个属性的时候,就使用直接值,如果写了属性,就使用写的值。#FIXED "AAA"
dtd定义引用实体
概念:在dtd中定义,在xml中使用。
语法:
<!ENTITY 实体名称 "实体内容">
引用方式:
&实体名称;
例如:
dtd中定义:
<!ENTITY copyright "veeja版权所有">
xml中引用:
©right;
定义的实体需要使用在内部的dtd中,如果写在外部的dtd中,在某些版本的浏览器,是不能正常使用的。
五、XML如何解析
DOM和SAX
xml有两种解析方式:dom和sax。
-
DOM解析XML:
与DOM解析HTML类似的是,dom解析的时候也会根据xml的层级结构,把xml中的每部分封装成一个对象,在内存中分配一个树形结构。
DOM的优点在于可以很方便的实现增删改的操作。
DOM的缺点在于解析xml文件的时候,如果xml文件过大,就会导致内存溢出。
-
sax解析xml:
sax解析采用事件驱动,一边读取一边解析,解析到某一个对象,把对象的名称返回。
使用sax方式不会造成内存溢出,也可以实现查询的操作。
但是使用sax方式,不能实现增删改的操作。
解析器
想要解析xml,首先需要解析器。
不同的公司和组织提供了针对dom和sax方式的解析器,通过api方式提供。
- SUN公司提供了针对dom和sax的解析器,叫 jaxp。
- dom4j组织,针对dom和sax解析,提供了 dom4j。这个解析器实际开发中应用的最广泛。
- jdom组织,针对dom和sax解析,提供了 jdom。
六、JAXP
JAXP:Java API for XMLProcessing,意为XML处理的Java API。它是JavaSE的一部分。
jaxp解析器在jdk的javax.xml.parsers包里面。
里面有四个类,分别是针对dom和sax解析使用的类。
针对DOM
DocumentBuilder:解析器类
这个类是一个抽象类,不能new。
但是可以使用
DocumentBuilderFactory.newDocumentBuilder()
方法获取实例。
可以使用方法来解析xml:
parse(xml的路径)
,返回的是一个Document接口。
方法:
Document是一个接口,父接口是Node,如果在Document中找不到相应的方法,就去Node里面去找。
Document里面的
getElementsByTagName(String tagname)
方法 可以得到标签,返回的是一个集合NodeList。
createElement(String tagName)
:创建标签
createTextNode(String data)
:创建文本
appendChild(Node newChild)
:将节点 newChild 添加到此节点的子节点列表的末尾。
removeChild(Node oldChild)
:从子节点列表中移除 oldChild 所指示的子节点,并将其返回。
replaceChild(Node newChild, Node oldChild)
:将子节点列表中的子节点 oldChild 替换为 newChild,并返回 oldChild 节点。
getParentNode()
:得到此节点的父节点。
getFirstChild()
:此节点的第一个子节点。
getLastChild()
:此节点的最后一个节点。
这些方法不存在兼容性问题,可以放心使用。
前面得到的NodeList对象,可以使用
getLength()
获取列表节点数,使用
item(int index)
返回集合中的第 index 个项。
例如:
NodeList list
for(int i = 0; i < list.getLength(); i++){
list.item(i);
}
DocumentBuilderFactory:解析器工厂
这个类也是一个抽象类,不能new,可以通过
newInstance()
方法获取 DocumentBuilderFactory 的实例。
针对SAX
SAXParser:解析器类
SAXParserFactory:解析器工厂
使用JAXP实现查询操作
- 创建一个xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<person>
<p1>
<name>weijia</name>
<age>21</age>
</p1>
<p2>
<name>lingjie</name>
<name>22</name>
</p2>
</person>
- 查询xml中所有的name元素的值。
- 创建解析器工厂类
- 根据解析器工厂类创建解析器
- 解析xml返回Document
- 得到所有的name元素
- 返回集合,遍历集合,得到每一个name元素
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document document = db.parse("src/person.xml"); NodeList nl = document.getElementsByTagName("name"); for (int i = 0; i < nl.getLength(); i++) { Node name1 = nl.item(i); System.out.println(name1.getTextContent()); }
使用JAXP实现查询某一个节点
查询xml中第一个name元素的值
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document d = db.parse("src/person.xml");
NodeList nl = d.getElementsByTagName("name");
Node node = nl.item(0);
System.out.println(node.getTextContent());
使用JAXP添加一个节点
我们p1标签添加一个子标签
<sex>
,并且标签内容设置为“女”。
public static void addSex() throws Exception {
// 创建解析器工厂
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// 创建解析器
DocumentBuilder db = dbf.newDocumentBuilder();
// 解析xml,得到document
Document d = db.parse("src/person.xml");
// 得到所有的p1
NodeList eles = d.getElementsByTagName("p1");
// 得到第一个p1
Node ele = eles.item(0);
// 创建sex标签
Element newElement = d.createElement("sex");
// 创建文本元素,内容为nv
Text text = d.createTextNode("nv");
// 把文本元素放入sex标签
newElement.appendChild(text);
// 把sex标签放入第一个p1标签
ele.appendChild(newElement);
// 回写xml文件
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.transform(new DOMSource(d), new StreamResult("src/person.xml"));
}
使用JAXP修改节点
把p1标签的sex标签内容设置为“nan”。
public static void modSex() throws Exception {
// 创建解析器工厂
DocumentBuilderFactory dbt = DocumentBuilderFactory.newInstance();
// 创建解析器
DocumentBuilder db = dbt.newDocumentBuilder();
// 解析xml,得到document
Document document = db.parse("src/person.xml");
// 得到sex1
Node sex1 = document.getElementsByTagName("sex").item(0);
// 设置为nan
sex1.setTextContent("man");
// 回写xml文件
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.transform(new DOMSource(document), new StreamResult("src/person.xml"));
}
使用JAXP删除节点
把sex标签删除掉。
public static void deleteSex() throws Exception {
// 创建解析器工厂
DocumentBuilderFactory dbt = DocumentBuilderFactory.newInstance();
// 创建解析器
DocumentBuilder db = dbt.newDocumentBuilder();
// 解析xml,得到document
Document document = db.parse("src/person.xml");
// 得到sex标签
NodeList sex = document.getElementsByTagName("sex");
// 判断是否有这个标签
if (sex.getLength() != 0) {
// 得到第一个sex标签
Node sex1 = sex.item(0);
// 得到sex1的父标签,也就是p1标签
Node p1 = sex1.getParentNode();
// 删除sex1标签
p1.removeChild(sex1);
// 回写xml文件
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.transform(new DOMSource(document), new StreamResult(
"src/person.xml"));
}
}
使用JAXP遍历节点
把xml中所有的元素名称打印出来。
使用递归来实现:
public static void list() throws Exception {
/*
* 1. 创建解析器工厂类 2. 根据解析器工厂类创建工厂类 3. 解析xml,返回document
*/
DocumentBuilderFactory builderFactory = DocumentBuilderFactory
.newInstance();
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
Document document = documentBuilder.parse("src/person.xml");
Node firstChild = document.getFirstChild();
/*
* ====使用递归来实现==== 得到根节点 得到根节点的子节点 得到子节点的子节点
*/
list1(firstChild);
}
private static void list1(Node node) {
// 判断一下是不是标签,如果是空格和换行的话,就跳过
if (node.getNodeType() == Node.ELEMENT_NODE) {
System.out.print("<");
System.out.print(node.getNodeName());
System.out.println(">");
}
NodeList list = node.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
Node node1 = list.item(i);
list1(node1);
}
}