XML与XML的Java解析

XML

可拓展标记语言(EXtensible Markup Language)是一种支持用户自定义的标记语言。原本作为替代HTML的技术对数据进行呈现,现主要用于对数据进行描述和存储。

可拓展:标签支持用户自定义。
标记语言:通过一系列的标记来对文档的语义、结构、格式进行定义。


XML作为一种具有特定格式的文档,现在通常具有以下功能和特点:

  1. 作为数据传输的标准。可读性、可扩展性、可维护性是数据传输标准的三个特性。XML具备这三个特性。
  2. 作为配置文件。比起.properties资源文件,XML具有更丰富的描述信息。
  3. 能够持久化数据。
  4. 可以作为数据库和数据文件。更快但不具备安全性。
  5. 兼容性更强,更容易跨平台和跨版本。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的标记,也被称为标签、节点,是成对出现的
属性为元素提供额外信息
文本元素中包裹的内容
实体&lt;转义的内容
注释<!-- 注释 -->为内容提供解释和说明
未解析字符数据<![CDATA[&amp;]]>不被解析器所解析的内容

只有处理指令、约束和注释允许出现在根标签之外。

主体内容

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)不能含子标签tomtom12
ANY任何tom12

修饰符包括:

| () | | | , | + | * | ? | |
| — | — | — | — | — | — | — |
| 分组 | 或 | 按顺序 | 1或n次 | 0或n次 | 0或1次 | 1次 |

约束属性和属性的值:

<!ATTLIST 标签名称
	属性名称 属性类型 属性特点
	属性名称 属性名称 ... 属性类型 属性特点
>

属性类型和特点包括:

属性类型描述属性特点描述
CDATA任意字符#REQUIRED必须拥有的属性
ID唯一#IMPLIED可选属性
IDREFID的引用#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();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值