Bộ phân tích (parser) SAX trong Java cung cấp API để phân tích cú pháp các tài liệu XML. SAX parser khác với DOM parser ở chỗ nó không tải toàn bộ XML vào bộ nhớ mà sẽ đọc tài liệu theo tuần tự.
SAX Parser
javax.xml.parsers.SAXParser
cung cấp phương thức để phân tích tài liệu XML bằng các bộ xử lý sự kiện (event handler). Class này triển khai interface XMLReader
và cung cấp các phiên bản nạp chồng của phương thức parse()
để đọc tài liệu XML từ file, input stream, input nguồn của SAX và URI dạng chuỗi.
Việc phân tích cú pháp thực tế được thực hiện bởi class Handler. Ta cần phải tạo class handler của riêng mình để phân tích tài liệu XML. Để làm điều này, ta cần triển khai interface ContentHandler
. Nó chứa các hàm callback (gọi lại) sẽ nhận thông báo khi có một sự kiện xảy ra, ví dụ như StartDocument, EndDocument, StartElement, EndElement, CharacterData, v.v.
org.xml.sax.helpers.DefaultHandler
cung cấp một triển khai mặc định của interface ContentHandler. Chúng ta có thể kế thừa (extend) class này để tạo handler riêng vì ta thường chỉ cần triển khai một vài phương thức mà thôi, giúp cho code của chúng ta gọn gàng và dễ bảo trì hơn.
Ví dụ SAX parser
Bây giờ, chúng ta hãy bắt đầu với ví dụ về SAX parser. Các tính năng chi tiết sẽ được giải thích chi tiết ở phần sau.
<?xml version="1.0" encoding="UTF-8"?>
<Employees>
<Employee id="1">
<age>29</age>
<name>Pankaj</name>
<gender>Male</gender>
<role>Java Developer</role>
</Employee>
<Employee id="2">
<age>35</age>
<name>Lisa</name>
<gender>Female</gender>
<role>CEO</role>
</Employee>
<Employee id="3">
<age>40</age>
<name>Tom</name>
<gender>Male</gender>
<role>Manager</role>
</Employee>
<Employee id="4">
<age>25</age>
<name>Meghna</name>
<gender>Female</gender>
<role>Manager</role>
</Employee>
</Employees>
Giả sử ta có một file XML như trên (employees.xml
) được lưu đâu đó trong hệ thống file. Nhìn vào đó, ta có thể thấy rằng nó chứa một danh sách các Employee. Mỗi Employee có một thuộc tính id
và các trường name
, gender
và role
.
Chúng ta sẽ dùng SAX parser để phân tích file này và tạo ra một danh sách các đối tượng Employee đại diện cho phần tử Employee từ file XML gốc.
package com.journaldev.xml;
public class Employee {
private int id;
private String name;
private String gender;
private int age;
private String role;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
@Override
public String toString() {
return "Employee:: ID="+this.id+" Name=" + this.name + " Age=" + this.age + " Gender=" + this.gender +
" Role=" + this.role;
}
}
Hãy cùng tạo class SAX Parser handler của riêng mình bằng cách kế thừa (extend) class DefaultHandler.
package com.journaldev.xml.sax;
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.journaldev.xml.Employee;
public class MyHandler extends DefaultHandler {
// List to hold Employees object
private List<Employee> empList = null;
private Employee emp = null;
private StringBuilder data = null;
// getter method for employee list
public List<Employee> getEmpList() {
return empList;
}
boolean bAge = false;
boolean bName = false;
boolean bGender = false;
boolean bRole = false;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (qName.equalsIgnoreCase("Employee")) {
// create a new Employee and put it in Map
String id = attributes.getValue("id");
// initialize Employee object and set id attribute
emp = new Employee();
emp.setId(Integer.parseInt(id));
// initialize list
if (empList == null)
empList = new ArrayList<>();
} else if (qName.equalsIgnoreCase("name")) {
// set boolean values for fields, will be used in setting Employee variables
bName = true;
} else if (qName.equalsIgnoreCase("age")) {
bAge = true;
} else if (qName.equalsIgnoreCase("gender")) {
bGender = true;
} else if (qName.equalsIgnoreCase("role")) {
bRole = true;
}
// create the data container
data = new StringBuilder();
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (bAge) {
// age element, set Employee age
emp.setAge(Integer.parseInt(data.toString()));
bAge = false;
} else if (bName) {
emp.setName(data.toString());
bName = false;
} else if (bRole) {
emp.setRole(data.toString());
bRole = false;
} else if (bGender) {
emp.setGender(data.toString());
bGender = false;
}
if (qName.equalsIgnoreCase("Employee")) {
// add Employee object to list
empList.add(emp);
}
}
@Override
public void characters(char ch[], int start, int length) throws SAXException {
data.append(new String(ch, start, length));
}
}
MyHandler chứa một danh sách các đối tượng Employee
dưới dạng một trường và chỉ có phương thức getter. Các đối tượng Employee
được thêm vào danh sách bên trong các phương thức xử lý sự kiện. Ta cũng có một trường Employee được dùng để tạo đối tượng Employee. Một khi tất cả các trường của nó được thiết lập, đối tượng này sẽ được thêm vào danh sách.
Các phương thức của SAX parser cần ghi đè
Các phương thức quan trọng cần ghi đè là startElement()
, endElement()
và characters()
. Khi SAX parser chạy và gặp bất kỳ phần tử mở đầu nào, phương thức startElement()
sẽ được gọi. Ta ghi đè phương thức này để thiết lập các biến boolean sẽ được dùng để xác định phần tử. Ta cũng dùng phương thức này để tạo một đối tượng Employee mới mỗi khi tìm thấy phần tử bắt đầu của Employee. Hãy xem cách thuộc tính id được đọc ở đây để thiết lập trường id
cho đối tượng Employee.
Phương thức characters()
được gọi khi SAX parser tìm thấy dữ liệu ký tự bên trong một phần tử. Lưu ý rằng SAX parser có thể chia dữ liệu thành nhiều đoạn (chunk) và gọi phương thức characters()
nhiều lần (Hãy đọc tài liệu về phương thức characters()
của class ContentHandler để hiểu về điều này). Đó là lý do tại sao ta dùng StringBuilder để lưu giữ dữ liệu này bằng phương thức append()
.
endElement()
là nơi ta dùng dữ liệu từ StringBuilder để thiết lập các thuộc tính cho đối tượng Employee và thêm nó vào danh sách mỗi khi tìm thấy thẻ kết thúc của Employee trong file XML.
Dưới đây là chương trình test sử dụng MyHandler
để phân tích file XML ở trên thành một danh sách các đối tượng Employee.
package com.journaldev.xml.sax;
import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.SAXException;
import com.journaldev.xml.Employee;
public class XMLParserSAX {
public static void main(String[] args) {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
try {
SAXParser saxParser = saxParserFactory.newSAXParser();
MyHandler handler = new MyHandler();
saxParser.parse(new File("/Users/pankaj/employees.xml"), handler);
//Get Employees list
List<Employee> empList = handler.getEmpList();
//print employee information
for(Employee emp : empList)
System.out.println(emp);
} catch (ParserConfigurationException | SAXException | IOException e) {
e.printStackTrace();
}
}
}
Đây là output của nó.
Employee:: ID=1 Name=Pankaj Age=29 Gender=Male Role=Java Developer
Employee:: ID=2 Name=Lisa Age=35 Gender=Female Role=CEO
Employee:: ID=3 Name=Tom Age=40 Gender=Male Role=Manager
Employee:: ID=4 Name=Meghna Age=25 Gender=Female Role=Manager
SAXParserFactory
cung cấp các phương thức factory để lấy một instance của SAXParser
. Ta truyền đối tượng File vào phương thức parse()
cùng với một instance của MyHandler để xử lý các sự kiện callback.
SAXParser ban đầu có thể hơi khó hiểu. Nhưng nếu bạn làm việc với một tài liệu XML lớn, nó sẽ cung cấp một cách đọc XML hiệu quả hơn DOM parser, giúp ứng dụng của bạn không bị quá tải. Bạn có thể tải project ví dụ ở trên từ GitHub để tìm hiểu thêm về công cụ này.