注册

使用 Kotlin 对 XML 文件解析、修改及创建

一 XML 基本概念


XML 全称 ExtensibleMarkupLanguage,中文称可扩展标记语言。它是一种通用的数据交换格式,具有平台无关性、语言无关性、系统无关性的优点,给数据集成与交互带来了极大的方便。XML 在不同的语言环境中解析方式都是一样的,只不过实现的语法不同而已。


XML 可用来描述数据、存储数据、传输数据/交换数据。


XML 文档形成了一种树结构,它从"根部"开始,然后扩展到"枝叶"。DOM 又是基于树形结构的 XML 解析方式,能很好地呈现这棵树的样貌。XML 文档节点的类型主要有:


各节点定义:




















































Node描述子节点
DocumentXML 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)进行操作。


优点:



  1. 允许随机读取访问数据,因为整个 Dom 树都加载到内存中
  2. 允许随机的对文档结构进行增删

缺点:



  1. 耗时,整个 XML 文档必须一次性解析完
  2. 占内存,整个 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 处理的特点是基于事件流的。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点。


优点:



  1. 访问能够立即进行,不需要等待所有数据被加载
  2. 只在读取数据时检查数据,不需要保存在内存中
  3. 占用内存少,不需要将整个数据都加载到内存中
  4. 允许注册多个 Handler,可以用来解析文档内容,DTD 约束等等

缺点:



  1. 需要应用程序自己负责 TAG 的处理逻辑(例如维护父/子关系等),文档越复杂程序就越复杂
  2. 单向导航,无法定位文档层次,很难同时访问同一文档的不同部分数据,不支持 XPath
  3. 不能随机访问 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
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录或注册