使用 Kotlin 对 XML 文件解析、修改及创建
一 XML 基本概念
XML 全称 ExtensibleMarkupLanguage,中文称可扩展标记语言。它是一种通用的数据交换格式,具有平台无关性、语言无关性、系统无关性的优点,给数据集成与交互带来了极大的方便。XML 在不同的语言环境中解析方式都是一样的,只不过实现的语法不同而已。
XML 可用来描述数据、存储数据、传输数据/交换数据。
XML 文档形成了一种树结构,它从"根部"开始,然后扩展到"枝叶"。DOM 又是基于树形结构的 XML 解析方式,能很好地呈现这棵树的样貌。XML 文档节点的类型主要有:
各节点定义:
Node | 描述 | 子节点 |
---|---|---|
Document | XML document 的根节点 | Element, ProcessingInstruction, DocumentType, Comment |
DocumentType | 文档属性 | No children |
Element | 元素 | Element, Text, Comment, ProcessingInstruction, CDATASection, EntityReference |
Attr | 属性 | Text, EntityReference |
ProcessingInstruction | 处理指令 | No children |
Comment | 注释 | No children |
Text | 文本 | No children |
Entity | 实体类型项目 | Element, Text, Comment, ProcessingInstruction, CDATASection, EntityReference |
二 XML 解析方式
一个 XML 文档的生命周期应该包括两部分:
- 解析文档
- 操作文档数据
那么接下来介绍如何来解析 XML 以及解析之后如何使用。
根据底层原理的不同,解析 XML 文件一般分为两种形式,一种是基于树形结构来解析的称为 DOM;另一种是基于事件流的形式称为 SAX。
2.1 DOM(Document Object Model)
DOM 是用与平台和语言无关的方式表示 XML 文档的官方 W3C 标准。是基于树形结构的 XML 解析方式,它会将整个 XML 文档读入内存并构建一个 DOM 树,基于这棵树形结构对各个节点(Node)进行操作。
优点:
- 允许随机读取访问数据,因为整个 Dom 树都加载到内存中
- 允许随机的对文档结构进行增删
缺点:
- 耗时,整个 XML 文档必须一次性解析完
- 占内存,整个 Dom 树都要加载到内存中
适用于:文档较小,且需要修改文档内容
2.1.1 DOM 解析 XML
第一步:建立一个 Stuff.xml 文件
<?xml version="1.0"?>
<company>
<staff id="1001">
<firstname>Jack</firstname>
<lastname>Ma</lastname>
<nickname>Hui Chuang A Li</nickname>
<salary currency="USD">100000</salary>
</staff>
<staff id="2001">
<firstname>Pony</firstname>
<lastname>Ma</lastname>
<nickname>Pu Tong Jia Ting</nickname>
<salary currency="RMB">200000</salary>
</staff>
</company>
第二步:DOM 解析
package com.elijah.kotlinlearning
import org.w3c.dom.Element
import org.w3c.dom.Node
import org.w3c.dom.NodeList
import java.io.File
import javax.xml.parsers.DocumentBuilderFactory
fun main(args: Array<String>) {
// Instantiate the Factory
val dbf = DocumentBuilderFactory.newInstance()
try {
// parse XML file
val xlmFile = File("${projectPath}/src/res/Staff.xml")
val xmlDoc= dbf.newDocumentBuilder().parse(xlmFile)
xmlDoc.documentElement.normalize()
println("Root Element :" + xmlDoc.documentElement.nodeName)
println("--------")
// get <staff>
val staffList: NodeList = xmlDoc.getElementsByTagName("staff")
for (i in 0 until staffList.length) {
var staffNode = staffList.item(i)
if (staffNode.nodeType === Node.ELEMENT_NODE) {
val element = staffNode as Element
// get staff's attribute
val id = element.getAttribute("id")
// get text
val firstname = element.getElementsByTagName("firstname").item(0).textContent
val lastname = element.getElementsByTagName("lastname").item(0).textContent
val nickname = element.getElementsByTagName("nickname").item(0).textContent
val salaryNodeList = element.getElementsByTagName("salary")
val salary = salaryNodeList.item(0).textContent
// get salary's attribute
val currency = salaryNodeList.item(0).attributes.getNamedItem("currency").textContent
println("Current Element : ${staffNode.nodeName}")
println("Staff Id : $id")
println("First Name: $firstname")
println("Last Name: $lastname")
println("Nick Name: $nickname")
println("Salary [Currency] : ${salary.toLong()} [$currency]")
}
}
} catch (e: Throwable) {
e.printStackTrace()
}
}
第三步:解析结果输出
Root Element :company
--------
Current Element : staff
Staff Id : 1001
First Name: Jack
Last Name: Ma
Nick Name: Hui Chuang A Li
Salary [Currency] : 100000 [USD]
Current Element : staff
Staff Id : 2001
First Name: Pony
Last Name: Ma
Nick Name: Pu Tong Jia Ting
Salary [Currency] : 200000 [RMB]
2.1.2 DOM 创建、生成 XML
第一步:创建新的 XML 并填充内容
package com.elijah.kotlinlearning
import org.w3c.dom.Document
import org.w3c.dom.Element
import java.io.File
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
fun main(args: Array<String>) {
// Instantiate the Factory
val docFactory = DocumentBuilderFactory.newInstance()
try {
// root elements
val docBuilder = docFactory.newDocumentBuilder()
val doc = docBuilder.newDocument()
val rootElement: Element = doc.createElement("company")
doc.appendChild(rootElement)
// add xml elements: staff 1001
val staff = doc.createElement("staff")
staff.setAttribute("id", "1001")
// set staff 1001's attribute
val firstname = doc.createElement("firstname")
firstname.textContent = "Jack"
staff.appendChild(firstname)
val lastname = doc.createElement("lastname")
lastname.textContent = "Ma"
staff.appendChild(lastname)
val nickname = doc.createElement("nickname")
nickname.textContent = "Hui Chuang A Li"
staff.appendChild(nickname)
val salary: Element = doc.createElement("salary")
salary.setAttribute("currency", "USD")
salary.textContent = "100000"
staff.appendChild(salary)
rootElement.appendChild(staff)
// add xml elements: staff 1002
val staff2: Element = doc.createElement("staff")
rootElement.appendChild(staff2)
staff2.setAttribute("id", "1002")
// set staff 1002's attribute
val firstname2 = doc.createElement("firstname")
firstname2.textContent = "Pony"
staff2.appendChild(firstname2)
val lastname2 = doc.createElement("lastname")
lastname2.textContent = "Ma"
staff2.appendChild(lastname2)
val nickname2 = doc.createElement("nickname")
nickname2.textContent = "Pu Tong Jia Ting"
staff2.appendChild(nickname2)
val salary2= doc.createElement("salary")
salary2.setAttribute("currency", "RMB")
salary2.textContent = "200000"
staff2.appendChild(salary2)
rootElement.appendChild(staff2)
val newXmlFile = File("${projectPath}/src/res/", "generatedXml.xml")
// write doc to new xml file
generateXml(doc, newXmlFile)
} catch (e: Throwable) {
e.printStackTrace()
}
}
// write doc to new xml file
private fun generateXml(doc: Document, file: File) {
// Instantiate the Transformer
val transformerFactory = TransformerFactory.newInstance()
val transformer = transformerFactory.newTransformer()
// pretty print
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
val source = DOMSource(doc)
val result = StreamResult(file)
transformer.transform(source, result)
}
第二步:生成 XML 文件 generatedXml.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<company>
<staff id="1001">
<firstname>Jack</firstname>
<lastname>Ma</lastname>
<nickname>Hui Chuang A Li</nickname>
<salary currency="USD">100000</salary>
</staff>
<staff id="1002">
<firstname>Pony</firstname>
<lastname>Ma</lastname>
<nickname>Pu Tong Jia Ting</nickname>
<salary currency="RMB">200000</salary>
</staff>
</company>
2.2 SAX(Simple API for XML)
SAX 处理的特点是基于事件流的。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点。
优点:
- 访问能够立即进行,不需要等待所有数据被加载
- 只在读取数据时检查数据,不需要保存在内存中
- 占用内存少,不需要将整个数据都加载到内存中
- 允许注册多个 Handler,可以用来解析文档内容,DTD 约束等等
缺点:
- 需要应用程序自己负责 TAG 的处理逻辑(例如维护父/子关系等),文档越复杂程序就越复杂
- 单向导航,无法定位文档层次,很难同时访问同一文档的不同部分数据,不支持 XPath
- 不能随机访问 xml 文档,不支持原地修改 xml
适用于: 文档较大,只需要读取文档数据。
2.2.1 SAX 解析 XML
第一步:新建 ContentHandler 解析类
package com.elijah.kotlinlearning
import org.xml.sax.Attributes
import org.xml.sax.helpers.DefaultHandler
class ContentHandler: DefaultHandler(){
private var nodeName :String? = null // 当前节点名
private lateinit var firstname: StringBuilder // 属性:firstname
private lateinit var lastname: StringBuilder // 属性:lastname
private lateinit var nickname: StringBuilder // 属性:nickname
private lateinit var salary: StringBuilder // 属性:salary
// 开始解析文档
override fun startDocument() {
firstname = StringBuilder()
lastname = StringBuilder()
nickname = StringBuilder()
salary = StringBuilder()
}
// 开始解析节点
override fun startElement(
uri: String?,
localName: String?,
qName: String?,
attributes: Attributes?
) {
nodeName = localName
}
// 开始解析字符串
override fun characters(ch: CharArray?, start: Int, length: Int) {
// 判断节点名称
when (nodeName) {
"firstname" -> {
firstname.append(ch, start, length)
}
"lastname" -> {
lastname.append(ch, start, length)
}
"nickname" -> {
nickname.append(ch, start, length)
}
"salary" -> {
salary.append(ch, start, length)
}
}
}
// 结束解析节点
override fun endElement(uri: String?, localName: String?, qName: String?) {
// 打印出来解析结果
if (localName == "staff") {
println("Staff is : $nodeName")
println("First Name: ${firstname.toString()}")
println("Last Name: ${lastname.toString()}")
println("Nick Name: ${nickname.toString()}")
println("Salary [Currency] : ${salary.toString()}")
// 清空, 不妨碍下一个 staff 节点的解析
firstname.clear()
lastname.clear()
nickname.clear()
salary.clear()
}
}
// 结束解析文档
override fun endDocument() {
super.endDocument()
}
}
第二步:新建解析器对指定 XML 进行解析
package com.elijah.kotlinlearning
import org.xml.sax.InputSource
import java.io.File
import javax.xml.parsers.SAXParserFactory
fun main(args: Array<String>) {
try{
// 新建解析器工厂
val saxParserFactory = SAXParserFactory.newInstance()
// 通过解析器工厂获得解析器对象
val saxParser = saxParserFactory.newSAXParser()
// 获得 xmlReader
val xmlReader = saxParser.xmlReader
// 设置解析器中的解析类
xmlReader.contentHandler = ContentHandler()
// 设置解析内容
val inputStream = File("${projectPath}/src/res/Staff.xml").inputStream()
xmlReader.parse(InputSource(inputStream))
} catch(e: Throwable){
e.printStackTrace()
}
}
作者:话唠扇贝
链接:https://juejin.cn/post/7131018900002570276
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。