注册

使用 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 个评论

要回复文章请先登录注册