---
title: XML Examples
description: Complete examples for XML file processing with Spring Batch RS
sidebar:
order: 3
---
import { Tabs, TabItem, Card, CardGrid, Aside } from '@astrojs/starlight/components';
<Aside type="tip">
View the complete source: [examples/xml_processing.rs](https://github.com/sboussekeyt/spring-batch-rs/blob/main/examples/xml_processing.rs)
</Aside>
This page provides comprehensive examples for working with XML files using Spring Batch RS.
## Setup
Add the XML feature to your `Cargo.toml`:
```toml
[dependencies]
spring-batch-rs = { version = "0.1", features = ["xml"] }
serde = { version = "1.0", features = ["derive"] }
```
---
## Basic XML Reading
### Reading Elements by Tag
```rust
use spring_batch_rs::{
core::{step::StepBuilder, item::PassThroughProcessor},
item::xml::XmlItemReaderBuilder,
item::logger::LoggerWriter,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename = "book")]
struct Book {
#[serde(rename = "@id")]
id: String,
title: String,
author: String,
year: i32,
price: f64,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let reader = XmlItemReaderBuilder::<Book>::new()
.tag("book") // Extract <book> elements
.capacity(1024)
.from_path("books.xml")?;
let writer = LoggerWriterBuilder::<Vehicle>::new().build();
let processor = PassThroughProcessor::<Book>::new();
let step = StepBuilder::new("read-xml")
.chunk::<Book, Book>(10)
.reader(&reader)
.processor(&processor)
.writer(&writer)
.build();
let mut execution = spring_batch_rs::core::step::StepExecution::new("read-xml");
step.execute(&mut execution)?;
Ok(())
}
```
**Input file (`books.xml`):**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="1">
<title>The Rust Programming Language</title>
<author>Steve Klabnik</author>
<year>2018</year>
<price>39.99</price>
</book>
<book id="2">
<title>Programming Rust</title>
<author>Jim Blandy</author>
<year>2021</year>
<price>49.99</price>
</book>
</books>
```
<Aside type="tip">
Use `#[serde(rename = "@field")]` for XML attributes and `#[serde(rename = "$value")]` for text content.
</Aside>
---
## XML Attributes
### Reading Attributes and Elements
```rust
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename = "vehicle")]
struct Vehicle {
#[serde(rename = "@type")]
vehicle_type: String,
#[serde(rename = "@id")]
id: String,
make: String,
model: String,
year: i32,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let reader = XmlItemReaderBuilder::<Vehicle>::new()
.tag("vehicle")
.from_path("vehicles.xml")?;
Ok(())
}
```
**Input:**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<vehicles>
<vehicle type="car" id="v1">
<make>Toyota</make>
<model>Camry</model>
<year>2023</year>
</vehicle>
<vehicle type="truck" id="v2">
<make>Ford</make>
<model>F-150</model>
<year>2024</year>
</vehicle>
</vehicles>
```
---
## Complex Nested Structures
### Nested Objects and Arrays
```rust
#[derive(Debug, Deserialize, Serialize, Clone)]
struct Displacement {
#[serde(rename = "@unit")]
unit: String,
#[serde(rename = "$value")]
value: String,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
struct Engine {
#[serde(rename = "@cylinders")]
cylinders: i32,
#[serde(rename = "type")]
engine_type: String,
displacement: Displacement,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
struct Features {
#[serde(rename = "feature", default)]
items: Vec<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename = "vehicle")]
#[serde(rename_all = "camelCase")]
struct ComplexVehicle {
#[serde(rename = "@type")]
vehicle_type: String,
#[serde(rename = "@id")]
id: String,
make: String,
model: String,
year: i32,
engine: Engine,
features: Features,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let reader = XmlItemReaderBuilder::<ComplexVehicle>::new()
.tag("vehicle")
.capacity(2048)
.from_path("complex_vehicles.xml")?;
Ok(())
}
```
**Input:**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<vehicles>
<vehicle type="car" id="1">
<make>Toyota</make>
<model>Camry</model>
<year>2023</year>
<engine cylinders="4">
<type>Inline</type>
<displacement unit="L">2.5</displacement>
</engine>
<features>
<feature>Bluetooth</feature>
<feature>Backup Camera</feature>
<feature>Lane Assist</feature>
</features>
</vehicle>
</vehicles>
```
---
## Basic XML Writing
### Writing XML Documents
```rust
use spring_batch_rs::item::xml::XmlItemWriterBuilder;
#[derive(Serialize)]
#[serde(rename = "product")]
struct Product {
#[serde(rename = "@id")]
id: u32,
name: String,
price: f64,
category: String,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let writer = XmlItemWriterBuilder::new()
.root_tag("products")
.item_tag("product")
.from_path("output.xml")?;
Ok(())
}
```
**Output:**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<products>
<product id="1">
<name>Laptop</name>
<price>999.99</price>
<category>Electronics</category>
</product>
<product id="2">
<name>Mouse</name>
<price>29.99</price>
<category>Electronics</category>
</product>
</products>
```
---
## XML to JSON Transformation
Convert XML to JSON format:
```rust
use spring_batch_rs::{
core::{job::JobBuilder, step::StepBuilder, item::PassThroughProcessor},
item::{
xml::XmlItemReaderBuilder,
json::JsonItemWriterBuilder,
},
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let reader = XmlItemReaderBuilder::<Vehicle>::new()
.tag("vehicle")
.from_path("vehicles.xml")?;
let writer = JsonItemWriterBuilder::<Vehicle>::new()
.pretty_formatter(true)
.from_path("vehicles.json")?;
let processor = PassThroughProcessor::<Vehicle>::new();
let step = StepBuilder::new("xml-to-json")
.chunk::<Vehicle, Vehicle>(50)
.reader(&reader)
.processor(&processor)
.writer(&writer)
.build();
let job = JobBuilder::new().start(&step).build();
job.run()?;
Ok(())
}
```
---
## CSV to XML Transformation
Convert CSV to XML format:
```rust
use spring_batch_rs::{
item::csv::CsvItemReaderBuilder,
core::item::PassThroughProcessor,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename = "employee")]
struct Employee {
#[serde(rename = "@id")]
id: u32,
first_name: String,
last_name: String,
department: String,
salary: f64,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let reader = CsvItemReaderBuilder::<Employee>::new()
.has_headers(true)
.from_path("employees.csv")?;
let writer = XmlItemWriterBuilder::new()
.root_tag("employees")
.item_tag("employee")
.from_path("employees.xml")?;
let processor = PassThroughProcessor::<Employee>::new();
let step = StepBuilder::new("csv-to-xml")
.chunk::<Employee, Employee>(100)
.reader(&reader)
.processor(&processor)
.writer(&writer)
.build();
let job = JobBuilder::new().start(&step).build();
job.run()?;
Ok(())
}
```
---
## XML Processing with Transformation
### Data Enrichment
```rust
use spring_batch_rs::core::item::{ItemProcessor, ItemProcessorResult};
use std::collections::HashMap;
#[derive(Deserialize, Clone)]
#[serde(rename = "order")]
struct OrderInput {
#[serde(rename = "@id")]
order_id: String,
product_id: u32,
quantity: u32,
}
#[derive(Serialize)]
#[serde(rename = "enriched_order")]
struct EnrichedOrder {
#[serde(rename = "@id")]
order_id: String,
product_id: u32,
product_name: String,
quantity: u32,
unit_price: f64,
total: f64,
}
struct OrderEnricher {
catalog: HashMap<u32, (String, f64)>,
}
impl ItemProcessor<OrderInput, EnrichedOrder> for OrderEnricher {
fn process(&self, item: &OrderInput) -> ItemProcessorResult<EnrichedOrder> {
let (product_name, unit_price) = self.catalog
.get(&item.product_id)
.cloned()
.ok_or_else(|| spring_batch_rs::error::BatchError::ItemProcessor(
format!("Unknown product: {}", item.product_id)
))?;
let total = unit_price * item.quantity as f64;
Ok(EnrichedOrder {
order_id: item.order_id.clone(),
product_id: item.product_id,
product_name,
quantity: item.quantity,
unit_price,
total,
})
}
}
```
---
## Validation and Filtering
### Validate XML Data
```rust
#[derive(Deserialize, Clone)]
#[serde(rename = "record")]
struct RawRecord {
#[serde(rename = "@id")]
id: String,
value: String,
status: String,
}
#[derive(Serialize)]
#[serde(rename = "validated_record")]
struct ValidatedRecord {
#[serde(rename = "@id")]
id: u32,
value: f64,
status: String,
}
struct RecordValidator;
impl ItemProcessor<RawRecord, ValidatedRecord> for RecordValidator {
fn process(&self, item: &RawRecord) -> ItemProcessorResult<ValidatedRecord> {
// Validate ID
let id = item.id.parse::<u32>()
.map_err(|_| spring_batch_rs::error::BatchError::ItemProcessor(
format!("Invalid ID: {}", item.id)
))?;
// Validate value
let value = item.value.parse::<f64>()
.map_err(|_| spring_batch_rs::error::BatchError::ItemProcessor(
format!("Invalid value: {}", item.value)
))?;
// Validate status
if !["active", "pending", "completed"].contains(&item.status.as_str()) {
return Err(spring_batch_rs::error::BatchError::ItemProcessor(
format!("Invalid status: {}", item.status)
));
}
Ok(ValidatedRecord {
id,
value,
status: item.status.clone(),
})
}
}
```
---
## CDATA and Special Characters
### Handling CDATA Sections
```rust
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename = "article")]
struct Article {
#[serde(rename = "@id")]
id: String,
title: String,
#[serde(rename = "$value")]
content: String, // Can contain CDATA
}
```
**Input:**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<articles>
<article id="1">
<title>Sample Article</title>
<content><![CDATA[
This content can contain <special> characters & symbols.
Line breaks are preserved.
]]></content>
</article>
</articles>
```
---
## Real-World Example: RSS Feed Processing
Process RSS-like feed format:
```rust
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename = "item")]
struct FeedItem {
title: String,
link: String,
description: String,
#[serde(rename = "pubDate")]
pub_date: String,
category: String,
}
#[derive(Serialize)]
struct ProcessedFeedItem {
title: String,
url: String,
summary: String,
published: String,
tags: Vec<String>,
}
struct FeedProcessor;
impl ItemProcessor<FeedItem, ProcessedFeedItem> for FeedProcessor {
fn process(&self, item: &FeedItem) -> ItemProcessorResult<ProcessedFeedItem> {
// Clean HTML from description
let summary = item.description
.replace("<p>", "")
.replace("</p>", "")
.replace("<br>", " ");
// Parse categories into tags
let tags: Vec<String> = item.category
.split(',')
.map(|s| s.trim().to_string())
.collect();
Ok(ProcessedFeedItem {
title: item.title.clone(),
url: item.link.clone(),
summary,
published: item.pub_date.clone(),
tags,
})
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let reader = XmlItemReaderBuilder::<FeedItem>::new()
.tag("item")
.capacity(2048)
.from_path("feed.xml")?;
let processor = FeedProcessor;
let writer = JsonItemWriterBuilder::<Vehicle>::new()
.pretty_formatter(true)
.from_path("processed_feed.json")?;
let step = StepBuilder::new("process-feed")
.chunk::<FeedItem, ProcessedFeedItem>(50)
.reader(&reader)
.processor(&processor)
.writer(&writer)
.build();
let job = JobBuilder::new().start(&step).build();
job.run()?;
Ok(())
}
```
---
## Common Patterns
### Text Content with Attributes
```rust
#[derive(Deserialize, Serialize, Clone)]
struct Price {
#[serde(rename = "@currency")]
currency: String,
#[serde(rename = "$value")]
amount: f64,
}
```
**XML:**
```xml
<price currency="USD">99.99</price>
```
### Optional Elements
```rust
#[derive(Deserialize, Serialize, Clone)]
#[serde(rename = "product")]
struct Product {
name: String,
#[serde(default)]
description: Option<String>,
price: f64,
}
```
### Default Values
```rust
#[derive(Deserialize, Serialize, Clone)]
struct Config {
#[serde(default = "default_timeout")]
timeout: u32,
}
fn default_timeout() -> u32 {
30
}
```
---
## Performance Tips
<CardGrid>
<Card title="Buffer Size" icon="rocket">
Increase `capacity()` for documents with large elements (default: 8192 bytes)
</Card>
<Card title="Tag Selection" icon="star">
Choose specific tag names to extract only needed elements
</Card>
<Card title="Memory Usage" icon="warning">
XML parsing is streaming - memory usage is proportional to element size, not file size
</Card>
<Card title="Chunk Size" icon="setting">
Use moderate chunk sizes (50-100) for XML due to parsing overhead
</Card>
</CardGrid>
## Troubleshooting
<Aside type="caution">
**Common Issues:**
1. **Attribute not found**: Add `#[serde(rename = "@field")]` for XML attributes
2. **Text content missing**: Use `#[serde(rename = "$value")]` for element text
3. **Parsing errors**: Check XML is well-formed and matches struct definition
4. **Buffer overflow**: Increase `capacity()` for large elements
</Aside>
## Next Steps
- [JSON Examples](/spring-batch-rs/examples/json/) - Process JSON files
- [Database Examples](/spring-batch-rs/examples/database/) - Read/write databases
- [API Reference](/spring-batch-rs/api/item-reader/) - Complete API documentation