XML中的Schema
对于``Schma`这个词我们可能了解得很少,但其实我们经常会用到,比如在Spring的配置文件中,在SpringMVC的配置文件中,一般我们创建一个Spring的配置文件都会在文件头写一段配置,比如Spring的配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
然后我们再在其中写我们的bean配置,但此时我们会注意到一个问题,<beans></beans>
标签里面只能写<bean></bean>,<alias></alias>
等事先规定好的标签,如果写其他的标签的话就会报错,这是为什么呢?
其实这就是Schema的作用。
1. Schema是什么?
XML Schema 的作用是定义 XML 文档的合法构建模块。XML Schema以XML语言为基础,也可以说XML Schema自身就是XML的一种应用。
XML Schema可定义的内容包括:
- 定义可出现在文档中的元素
- 定义可出现在文档中的属性
- 定义哪个元素是子元素
- 定义子元素的次序
- 定义子元素的数目
- 定义元素是否为空,或者是否可包含文本
- 定义元素和属性的数据类型
- 定义元素和属性的默认值以及固定值
2. 如何使用?
2.1 创建一个简单的XML文件
user.xml
<?xml version="1.0"?>
<user>
<name>jack</name>
<age>1</age>
<bir>2022-01-01</bir>
<desc>jack is a boy!</desc>
</user>
2.2 创建一个XSD文件
user.xsd,xsd后缀是Schema文件的后缀名称,该文件将定义user.xml文件的格式。
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://xuhao.com"
xmlns="http://xuhao.com"
elementFormDefault="qualified">
<xs:element name="note">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="age" type="xs:string"/>
<xs:element name="bir" type="xs:string"/>
<xs:element name="desc" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
2.3 将XML关联XSD文件
<?xml version="1.0" encoding="UTF-8"?>
<user xmlns="http://xuhao.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xuhao.com http://xuhao.com/user.xsd">
<name>jack</name>
<age>1</age>
<bir>2022-01-01</bir>
<desc>jack is a boy!</desc>
</user>
在user.xml文件中,我们关联了user.xsd文件,而我们在user.xsd文件中限制了可使用的标签,如果此时我们在user.xml文件中使用其他标签,此时文件就会报错。
3. Sechma中的标签解释
3.1 XSD文件中schema标签的解释
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://xuhao.com"
xmlns="http://xuhao.com"
elementFormDefault="qualified">
</xs:schema>
<schema>
元素是每一个XML Schema
的根元素,它可包含以下属性。
# xmlns:xs="http://www.w3.org/2001/XMLSchema"
显示 schema 中用到的元素和数据类型来自命名空间 "http://www.w3.org/2001/XMLSchema"。同时它还规定了来自命名空间 "http://www.w3.org/2001/XMLSchema" 的元素和数据类型应该使用前缀 xs:
# targetNamespace
显示在这个xsd文件中定义的标签来自哪一个命名空间,上述我们配置的是http://xuhao.com,一般命名规则为:http://公司域名/xsd文件名
# xmlns
默认的命名规则,一般和targetNamespace相同。
# elementFormDefault
任何 XML 实例文档所使用的且在此 schema 中声明过的元素必须被命名空间限定。
3.2 XML文件中引用schema标签的解释
<?xml version="1.0" encoding="UTF-8"?>
<user xmlns="http://xuhao.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xuhao.com http://xuhao.com/user.xsd">
<name>jack</name>
<age>1</age>
<bir>2022-01-01</bir>
<desc>jack is a boy!</desc>
</user>
# xmlns
规定了默认命名空间的声明。此声明会告知 schema 验证器,在此 XML 文档中使用的所有元素都被声明于 "http://xuhao.com" 这个命名空间,和xsd配置的xmln相对应。
# xmlns:xsi
xsi是http://www.w3.org/2001/XMLSchema-instance的别名。表示遵守w3的xml schema规范,xml解析器解析xml文件时,就明白按照什么规范解析了。xml schema规范有很多中,不止w3一家标准。
# xsi:schemaLocation
由一个URI引用对组成,两个URI之间以空白符分隔。第一个URI是名称空间的名字,第二个URI给出xsd文件的位置,模式处理器将从这个位置读取模式文档,该模式文档的目标名称空间必须与第一个URI相匹配。上面的schemaLocation意为:在http://xuhao.com命名空间下的user.xsd文件。
3.3 schema中常见标签解释
3.3.1 annotation
可以包含 appinfo 元素(由应用程序使用的信息)和 documentation 元素(由用户读取或使用的注释或文本)。
3.3.2 restriction
定义一个约束条件。
3.3.3 sequence
规定标签中子元素的顺序。
3.3.4 attribute
规定标签的属性。
3.3.5 simpleType
定义一个简单类型, 用来规定和约束具有纯文本内容的元素(不含子元素即为具有纯文本内容的元素)或属性的值。
使用场景
1. 规定一个元素纯文本部分的类型,或规定一个元素属性的所属类型。 注意是纯文本
2. 当我们对一个元素纯文本的值,元素属性的值需要一些限制的时候。可参考:https://www.w3school.com.cn/schema/schema_elements_ref.asp
用法
规定一个元素纯文本内容的类型:
<xsd:element name="description" type="xsd:string"/>
规定一个元素属性的所属类型:
<xsd:element name="worker">
<xsd:complexType>
<xsd:attribute name="id" type="idType"/>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="idType">
<xsd:restriction base="xsd:integer"/>
</xsd:simpleType>
我定义了一个worker
元素,并规定它具有一个id
属性,这个属性的类型为idType
,这是我自定义的一个simpleType
,这个simpleType
中规定了属性值的类型。
当然也可以直接将attribute
的type
属性设置为xsd:integer
,但我这样写是为了让大家更直观的看出simpleType
确实可以规定一个元素属性的数据类型。
若父标签是element,则用来约束父标签所定义元素的纯文本内容:
<xsd:element name="gender">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Male"/>
<xsd:enumeration value="Female"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
这个simpleType规定了它的父标签所定义的gender
元素的纯文本内容是string
类型,并且只能取Male
和Female
两个值,起到了约束的作用。
若父标签是attribute,则用来约束父标签所定义的属性值:
<xsd:element name="person">
<xsd:complexType>
<xsd:attribute name="gender">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Male"/>
<xsd:enumeration value="Female"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
这个simpleType规定了它的父标签所定义的person
元素的gender
属性值是string
类型的,且只能取Male
和Female
两个值。可以想象到spring配置文件中的是否设置为单例的开关的选项就是这样做的。
3.3.6 complexType
定义复杂类型,复杂类型的元素是包含其他元素和/或属性的 XML 元素。
用法
<student address="长沙">
<name>zhagnsan</name>
<age>14</age>
<bir>2000-01-01</bir>
<desc>未成年</desc>
</student>
<xs:element name="student">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="age" type="xs:string"/>
<xs:element name="bir" type="xs:string"/>
<xs:element name="desc" type="xs:string"/>
</xs:sequence>
<xsd:attribute name="addresss" type="xs:string"/>
</xs:complexType>
</xs:element>
这个complexType为student
元素定义了一个复杂类型,这个类型中包含了四个子元素:id,name,gender,addr,并且使用了sequence
指示器指定了这四个子元素的顺序,还包含了一个class属性。
simpleType和complexType的区别
simpleType
用来定义简单数据类型,其中可以包含对该类型内容的限制,可以指定某属性或元素属于某simpleType,从而起到规定元素文本内容和属性的作用。
complexType
定义复杂数据类型,包含复杂的结构,如属性、序列关系(sequence)、选择关系(choice)等等,一般用于定义元素内容,可以指定某元素属于某complexType。
4. 快速生成XSD文件
我们可以使用trang
工具来根据xml文件自动生成xsd文件,工具如下。
使用方法:
java -jar trang工具名称 xml文件位置 生成的xsd文件名称
示例
user.xml
<?xml version="1.0" encoding="UTF-8"?>
<data xmlns="http://fingard.com/quikeDev" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://fingard.com/quikeDev http://fingard.com/quikeDev/user.xsd">
<user address="湖南长沙">
<name>jack</name>
<age>1</age>
<bir>2022-01-01</bir>
<desc>jack is a boy!</desc>
</user>
<teacher name="李四" isBoy="true">
<age>12</sex>
</teacher>
</data>
执行工具(trang-2009111.jar和user.xml都在同一个目录下,生成的user.xsd也在同一个目录)
java -jar .\trang-20091111.jar user.xml user.xsd
查看生成的user.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
targetNamespace="http://fingard.com/quikeDev" xmlns:quikedev="http://fingard.com/quikeDev"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xs:import namespace="http://www.w3.org/2001/XMLSchema-instance"/>
<xs:element name="data">
<xs:complexType>
<xs:sequence>
<xs:element ref="quikedev:user"/>
<xs:element ref="quikedev:teacher"/>
</xs:sequence>
<xs:attribute ref="xsi:schemaLocation" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="user">
<xs:complexType>
<xs:sequence>
<xs:element ref="quikedev:name"/>
<xs:element ref="quikedev:age"/>
<xs:element ref="quikedev:bir"/>
<xs:element ref="quikedev:desc"/>
</xs:sequence>
<xs:attribute name="address" use="required" type="xs:NCName"/>
</xs:complexType>
</xs:element>
<xs:element name="name" type="xs:NCName"/>
<xs:element name="bir" type="xs:NMTOKEN"/>
<xs:element name="desc" type="xs:string"/>
<xs:element name="teacher">
<xs:complexType>
<xs:sequence>
<xs:element ref="quikedev:age"/>
</xs:sequence>
<xs:attribute name="isBoy" use="required" type="xs:boolean"/>
<xs:attribute name="name" use="required" type="xs:NCName"/>
</xs:complexType>
</xs:element>
<xs:element name="age" type="xs:integer"/>
</xs:schema>
可以看到生成的xsd文件基本满足我们的要求,但各别标签的type不是我们想要的,我们可以自行进行更改。
5. 为XSD文件添加注释
有些时候我们在进行xml文件编写时,一些标签会提示该标签是做什么的,如下图:
但我们自己编写的user.xml却没有提示,那么这个功能是怎么做到的呢?
5.1 为标签添加注释
我们可以在xsd的命名空间配置中添加支持注释的命名空间,并在具体的标签中添加注释元素。用例如下:
xmlns:xhtml="http://www.w3.org/1999/xhtml"
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
targetNamespace="http://fingard.com/quikeDev" xmlns:quikedev="http://fingard.com/quikeDev"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xs:element name="name" type="xs:string">
<xs:annotation>
<xs:documentation>
<xhtml:p>姓名</xhtml:p>
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:schema>
此时当我们在IDEA中将光标放在标签上时就会出现提示。
6. 解析XML,将XML转换为Java中的数据结构
代码中用到了dom4j,需要导入依赖。
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* @author 许浩
* @date 2022/6/24
*/
public class XmlTransUtil {
public static List<HashMap<String, List<HashMap<String, String>>>> trans(String xml) {
try {
Document document = DocumentHelper.parseText(xml);
Element rootElement = document.getRootElement();
Iterator<Element> iterator = rootElement.elementIterator();
List<HashMap<String, List<HashMap<String, String>>>> resultList = new ArrayList<>();
// 外层crud
while (iterator.hasNext()) {
Element element = iterator.next();
// 内部标签 pageCode,schema,tablePage,colConfig
List<Element> elementList = element.elements();
HashMap<String, List<HashMap<String, String>>> resultMap = new HashMap<>();
for (Element e : elementList) {
List<HashMap<String, String>> result = new ArrayList<>();
constructedObject(e, result, -1);
resultMap.put(e.getName(), result);
}
resultList.add(resultMap);
}
return resultList;
} catch (DocumentException e) {
throw new RuntimeException("XML解析异常");
}
}
private static void constructedObject(Element e, List<HashMap<String, String>> result, int level) {
List<Element> elementList = e.elements();
if (elementList.size() == 0) {
HashMap<String, String> fieldMap = new HashMap<>();
e.attributes().forEach(attr -> fieldMap.put(attr.getName(), attr.getValue()));
String textTrim = e.getTextTrim();
fieldMap.put(e.getName(), textTrim);
result.add(fieldMap);
}
Iterator<Element> iterator = e.elementIterator();
while (iterator.hasNext()) {
Element element = iterator.next();
if (level == -1) {
HashMap<String, String> fieldMap = new HashMap<>();
String text = element.getTextTrim();
if (!StringUtils.isEmpty(text)) {
fieldMap.put(element.getName(), text);
}
element.attributes().forEach(attr -> fieldMap.put(attr.getName(), attr.getValue()));
result.add(fieldMap);
if (element.getParent() != null) {
HashMap<String, String> map = result.get(result.size() - 1);
Element parent1 = element.getParent();
parent1.attributes().forEach(attr -> map.put(attr.getName(), attr.getValue()));
}
} else {
HashMap<String, String> map = result.get(result.size() - 1);
String text = element.getText();
if (!StringUtils.isEmpty(text)) {
map.put(element.getName(), text);
}
element.attributes().forEach(attr -> map.put(attr.getName(), attr.getValue()));
}
if (element.elements().size() > 0) {
constructedObject(element, result, level + 1);
}
}
}
}