Skip to main content

Kotlin XML Processing

XML (eXtensible Markup Language) is a widely used format for storing and transporting data across different systems. As a beginner Kotlin programmer, understanding how to work with XML files is essential for tasks like reading configuration files, processing data feeds, or interacting with web services. In this tutorial, we'll explore different approaches to XML processing in Kotlin.

Introduction to XML Processing in Kotlin

XML processing involves parsing XML documents, navigating their structure, extracting data, manipulating elements, and creating new XML content. Kotlin provides several ways to work with XML:

  1. Using standard Java XML libraries (since Kotlin is fully interoperable with Java)
  2. Using Kotlin-specific XML libraries
  3. Using third-party libraries optimized for Kotlin

XML Structure Basics

Before diving into code, let's refresh our understanding of XML structure:

xml
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book category="fiction">
<title>Harry Potter</title>
<author>J.K. Rowling</author>
<price>29.99</price>
</book>
<book category="programming">
<title>Kotlin in Action</title>
<author>Dmitry Jemerov</author>
<price>39.99</price>
</book>
</bookstore>

This XML document represents a bookstore with books, each having attributes and child elements.

Parsing XML in Kotlin

Using DOM (Document Object Model)

DOM parsing loads the entire XML document into memory, creating a tree structure that you can navigate.

kotlin
import org.w3c.dom.Element
import org.w3c.dom.Node
import javax.xml.parsers.DocumentBuilderFactory
import java.io.File

fun parseXMLWithDOM(filePath: String) {
val factory = DocumentBuilderFactory.newInstance()
val builder = factory.newDocumentBuilder()
val document = builder.parse(File(filePath))

document.documentElement.normalize()

println("Root element: ${document.documentElement.nodeName}")

val bookList = document.getElementsByTagName("book")

for (i in 0 until bookList.length) {
val bookNode = bookList.item(i)

if (bookNode.nodeType == Node.ELEMENT_NODE) {
val element = bookNode as Element
val category = element.getAttribute("category")
val title = element.getElementsByTagName("title").item(0).textContent
val author = element.getElementsByTagName("author").item(0).textContent
val price = element.getElementsByTagName("price").item(0).textContent

println("\nBook ${i+1}:")
println("Category: $category")
println("Title: $title")
println("Author: $author")
println("Price: $price")
}
}
}

When you call this function with the path to our example XML file, you'll get output like:

Root element: bookstore

Book 1:
Category: fiction
Title: Harry Potter
Author: J.K. Rowling
Price: 29.99

Book 2:
Category: programming
Title: Kotlin in Action
Author: Dmitry Jemerov
Price: 39.99

Using SAX (Simple API for XML)

SAX is an event-based parser that processes XML sequentially without loading the entire document into memory—ideal for large XML files.

kotlin
import org.xml.sax.Attributes
import org.xml.sax.helpers.DefaultHandler
import javax.xml.parsers.SAXParserFactory
import java.io.File

fun parseXMLWithSAX(filePath: String) {
val factory = SAXParserFactory.newInstance()
val saxParser = factory.newSAXParser()

val handler = object : DefaultHandler() {
private var currentElement = ""
private var currentTitle = ""
private var currentAuthor = ""
private var currentPrice = ""
private var currentCategory = ""

override fun startElement(uri: String?, localName: String?, qName: String, attributes: Attributes) {
currentElement = qName
if (qName.equals("book")) {
currentCategory = attributes.getValue("category") ?: ""
}
}

override fun characters(ch: CharArray, start: Int, length: Int) {
val value = String(ch, start, length).trim()
if (value.isNotEmpty()) {
when (currentElement) {
"title" -> currentTitle = value
"author" -> currentAuthor = value
"price" -> currentPrice = value
}
}
}

override fun endElement(uri: String?, localName: String?, qName: String) {
if (qName.equals("book")) {
println("\nBook Details:")
println("Category: $currentCategory")
println("Title: $currentTitle")
println("Author: $currentAuthor")
println("Price: $currentPrice")
}
}
}

saxParser.parse(File(filePath), handler)
}

Using StAX (Streaming API for XML)

StAX is another event-based API but with more control over parsing:

kotlin
import javax.xml.stream.XMLInputFactory
import java.io.FileInputStream

fun parseXMLWithStAX(filePath: String) {
val factory = XMLInputFactory.newInstance()
val inputStream = FileInputStream(filePath)
val reader = factory.createXMLStreamReader(inputStream)

var currentElement = ""
var title = ""
var author = ""
var price = ""
var category = ""

while (reader.hasNext()) {
val event = reader.next()

when (event) {
javax.xml.stream.XMLStreamConstants.START_ELEMENT -> {
currentElement = reader.localName
if (currentElement == "book") {
category = reader.getAttributeValue(null, "category") ?: ""
}
}

javax.xml.stream.XMLStreamConstants.CHARACTERS -> {
val text = reader.text.trim()
if (text.isNotEmpty()) {
when (currentElement) {
"title" -> title = text
"author" -> author = text
"price" -> price = text
}
}
}

javax.xml.stream.XMLStreamConstants.END_ELEMENT -> {
if (reader.localName == "book") {
println("\nBook Information:")
println("Category: $category")
println("Title: $title")
println("Author: $author")
println("Price: $price")
}
}
}
}

reader.close()
inputStream.close()
}

Creating XML with Kotlin

Using DOM to Create XML

kotlin
import org.w3c.dom.Document
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import java.io.File

fun createXMLWithDOM(outputPath: String) {
val factory = DocumentBuilderFactory.newInstance()
val builder = factory.newDocumentBuilder()
val document = builder.newDocument()

// Create root element
val rootElement = document.createElement("bookstore")
document.appendChild(rootElement)

// Create first book
val book1 = document.createElement("book")
book1.setAttribute("category", "fantasy")
rootElement.appendChild(book1)

// Add book details
val title1 = document.createElement("title")
title1.appendChild(document.createTextNode("The Lord of the Rings"))
book1.appendChild(title1)

val author1 = document.createElement("author")
author1.appendChild(document.createTextNode("J.R.R. Tolkien"))
book1.appendChild(author1)

val price1 = document.createElement("price")
price1.appendChild(document.createTextNode("24.99"))
book1.appendChild(price1)

// Create second book
val book2 = document.createElement("book")
book2.setAttribute("category", "science")
rootElement.appendChild(book2)

// Add book details
val title2 = document.createElement("title")
title2.appendChild(document.createTextNode("A Brief History of Time"))
book2.appendChild(title2)

val author2 = document.createElement("author")
author2.appendChild(document.createTextNode("Stephen Hawking"))
book2.appendChild(author2)

val price2 = document.createElement("price")
price2.appendChild(document.createTextNode("19.95"))
book2.appendChild(price2)

// Write to file
val transformerFactory = TransformerFactory.newInstance()
val transformer = transformerFactory.newTransformer()
val source = DOMSource(document)
val result = StreamResult(File(outputPath))
transformer.transform(source, result)

println("XML file created successfully at $outputPath")
}

Using Kotlin XML Libraries

Using kotlinx.serialization

Kotlin provides its own serialization library that can simplify XML processing. First, add the dependency:

kotlin
// In build.gradle.kts
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.4.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-xml:0.85.0") // Use the latest version
}

Then create serializable classes and process XML:

kotlin
import kotlinx.serialization.*
import kotlinx.serialization.xml.*

@Serializable
@SerialName("bookstore")
data class Bookstore(
val books: List<Book> = listOf()
)

@Serializable
@SerialName("book")
data class Book(
@XmlAttribute val category: String,
val title: String,
val author: String,
val price: Double
)

fun serializeToXml() {
val bookstore = Bookstore(
books = listOf(
Book(
category = "fiction",
title = "The Great Gatsby",
author = "F. Scott Fitzgerald",
price = 15.99
),
Book(
category = "non-fiction",
title = "Sapiens",
author = "Yuval Noah Harari",
price = 22.50
)
)
)

val xml = Xml.encodeToString(bookstore)
println(xml)

// To deserialize
val deserializedBookstore = Xml.decodeFromString<Bookstore>(xml)
println(deserializedBookstore)
}

Real-World Example: Processing a Configuration File

Let's create a practical example where we read an XML configuration file for an application:

kotlin
import org.w3c.dom.Element
import javax.xml.parsers.DocumentBuilderFactory
import java.io.File

data class AppConfig(
val databaseSettings: DatabaseSettings,
val appSettings: AppSettings
)

data class DatabaseSettings(
val host: String,
val port: Int,
val username: String,
val password: String,
val dbName: String
)

data class AppSettings(
val appName: String,
val logLevel: String,
val maxConnections: Int
)

fun loadAppConfig(configPath: String): AppConfig {
val factory = DocumentBuilderFactory.newInstance()
val builder = factory.newDocumentBuilder()
val document = builder.parse(File(configPath))
document.documentElement.normalize()

// Parse database settings
val dbElement = document.getElementsByTagName("database").item(0) as Element
val dbSettings = DatabaseSettings(
host = dbElement.getElementsByTagName("host").item(0).textContent,
port = dbElement.getElementsByTagName("port").item(0).textContent.toInt(),
username = dbElement.getElementsByTagName("username").item(0).textContent,
password = dbElement.getElementsByTagName("password").item(0).textContent,
dbName = dbElement.getElementsByTagName("dbName").item(0).textContent
)

// Parse app settings
val appElement = document.getElementsByTagName("application").item(0) as Element
val appSettings = AppSettings(
appName = appElement.getElementsByTagName("name").item(0).textContent,
logLevel = appElement.getElementsByTagName("logLevel").item(0).textContent,
maxConnections = appElement.getElementsByTagName("maxConnections").item(0).textContent.toInt()
)

return AppConfig(dbSettings, appSettings)
}

fun main() {
try {
val config = loadAppConfig("app_config.xml")
println("App Configuration:")
println("App Name: ${config.appSettings.appName}")
println("Log Level: ${config.appSettings.logLevel}")
println("DB Host: ${config.databaseSettings.host}:${config.databaseSettings.port}")
println("DB Name: ${config.databaseSettings.dbName}")
} catch (e: Exception) {
println("Error loading configuration: ${e.message}")
}
}

Example XML configuration file (app_config.xml):

xml
<?xml version="1.0" encoding="UTF-8"?>
<config>
<database>
<host>localhost</host>
<port>5432</port>
<username>admin</username>
<password>secret123</password>
<dbName>myappdb</dbName>
</database>
<application>
<name>MyKotlinApp</name>
<logLevel>INFO</logLevel>
<maxConnections>10</maxConnections>
</application>
</config>

Handling XML Namespaces

XML namespaces are used to avoid element name conflicts. Here's how to handle them in Kotlin:

kotlin
import org.w3c.dom.Element
import javax.xml.parsers.DocumentBuilderFactory
import java.io.File

fun handleNamespacesXML(filePath: String) {
val factory = DocumentBuilderFactory.newInstance()
factory.isNamespaceAware = true // Important!
val builder = factory.newDocumentBuilder()
val document = builder.parse(File(filePath))

document.documentElement.normalize()

// Get elements with specific namespace
val nsURI = "http://example.org/books"
val bookElements = document.getElementsByTagNameNS(nsURI, "book")

for (i in 0 until bookElements.length) {
val bookElement = bookElements.item(i) as Element
val title = bookElement.getElementsByTagNameNS(nsURI, "title").item(0).textContent
println("Book title: $title")
}
}

Error Handling in XML Processing

Proper error handling is crucial when working with XML:

kotlin
import javax.xml.parsers.DocumentBuilderFactory
import java.io.File
import org.xml.sax.ErrorHandler
import org.xml.sax.SAXParseException

fun parseXMLWithErrorHandling(filePath: String) {
try {
val factory = DocumentBuilderFactory.newInstance()
val builder = factory.newDocumentBuilder()

// Custom error handler
builder.setErrorHandler(object : ErrorHandler {
override fun warning(e: SAXParseException) {
println("Warning: ${e.message} at line ${e.lineNumber}, column ${e.columnNumber}")
}

override fun error(e: SAXParseException) {
println("Error: ${e.message} at line ${e.lineNumber}, column ${e.columnNumber}")
}

override fun fatalError(e: SAXParseException) {
println("Fatal error: ${e.message} at line ${e.lineNumber}, column ${e.columnNumber}")
throw e
}
})

val document = builder.parse(File(filePath))
document.documentElement.normalize()

println("XML parsed successfully!")
// Process document here

} catch (e: Exception) {
println("Failed to parse XML: ${e.message}")
e.printStackTrace()
}
}

Summary

In this tutorial, we've explored various ways to process XML in Kotlin:

  1. Parsing XML using DOM, SAX, and StAX approaches
  2. Creating XML documents programmatically
  3. Using Kotlin serialization for XML processing
  4. Working with a real-world example of loading configuration
  5. Handling namespaces in XML documents
  6. Implementing error handling for XML processing

Each approach has its advantages:

  • DOM is great for small to medium files when you need random access to elements
  • SAX and StAX are better for large files and streaming processing
  • Kotlin serialization offers a more idiomatic way for Kotlin developers

Exercises

  1. Create an XML file representing a list of students with attributes like name, age, and grades, then write a Kotlin function to parse it.

  2. Extend the configuration file example to include more sections and nested elements.

  3. Write a function that takes a list of objects and serializes them to XML, then another function that reads that XML back into objects.

  4. Create a simple XML validator that checks if an XML document conforms to certain rules (e.g., required elements are present).

  5. Build a simple XML transformation system that converts one XML format to another.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)