XML được sử dụng rộng rãi và đóng vai trò hết sức quan trọng trong các ứng dụng hiện nay. XML được sử dụng để cấu hình ứng dụng, lưu trữ dữ liệu, định dạng và trao đổi dữ liệu trong nội bộ cũng như giữa các ứng dụng. Do đó, hầu hết các ứng dụng đều trực tiếp hoặc gián tiếp xử lý dữ liệu theo định dạng XML. Với Java có rất nhiều thư viện hỗ trợ việc đọc/ghi dữ liệu theo định dạng XML, bao gồm các thư viện dựng sẵn trong Java cũng như các thư viện bên ngoài như jdom, xerces, axiom, woodstox,… Bài viết này đề cập đến việc sử dụng các thư viện dựng sẵn trong Java để phân tách đọc nội dung XML bằng 3 cách: dựa trên mô hình DOM, sử dụng SAX và sử dụng StAX.
Để thuận tiện cho việc trình bày, chúng ta sẽ xem xét ví dụ minh họa có 3 lớp đối tượng xử lý XML tương ứng với 3 cách ở trên: DemoDOMParser, DemoSAXParser, DemoStAXParser. Các lớp đối tượng xử lý này đều áp dụng cho tệp XML với nội dung có dạng như sau:
<students>
<student id="1">
<fullname>Nguyen Anh Minh</fullname>
<email>minhna@gmail.com</email>
<address>345, Hung Vuong, Da Nang</address>
</student>
<student id="2">
<fullname>Tram Tu Thieng</fullname>
<email>ttt.thieng@gmail.com</email>
<address>450 Doi Can, Ha Noi</address>
</student>
<student id="3">
<fullname>Duong Minh Tuan</fullname>
<email>tuandm@gmail.com</email>
<address>144, Phan Dang Luu, Tp. Ho Chi Minh</address>
</student>
</students>
Lớp đối tượng java Student mô tả thông tin tương ứng trong thẻ <student> được định nghĩa như sau:
public class Student {
private String id;
private String fullname;
private String email;
private String address;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getFullname() {
return fullname;
}
public void setFullname(String fullname) {
this.fullname = fullname;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append("id:").append("\"").append(id).append("\",");
sb.append("fullname:").append("\"").append(fullname).append("\",");
sb.append("email:").append("\"").append(email).append("\",");
sb.append("address:").append("\"").append(address).append("\"");
sb.append("}");
return sb.toString();
}
}
Cả ba lớp đối tượng Parser đều sử dụng interface chung, có tên là DemoXmlParser, định nghĩa phương thức getAllStudents(). Phương thức này có chức năng đọc các phần tử
public interface DemoXmlParser {
List<Student> getAllStudents();
}
Ngoài phương thức chung getAllStudents(), cả ba bộ Parser đều có chung dữ liệu đầu vào giống nhau là đường dẫn đến tệp XML, được định nghĩa trong lớp đối tượng trừu tượng DemoAbstractParser:
public abstract class DemoAbstractParser implements DemoXmlParser {
public DemoAbstractParser(String filename) {
this.filename = filename;
}
protected String filename = null;
public String getFilename() {
return filename;
}
}
Các lớp đối tượng DemoDOMParser, DemoSAXParser, DemoStAXParser trong bài viết này đều kế thừa từ lớp DemoAbstractParser.
Phân tách nội dung XML theo mô hình DOM
Theo cách này, chúng ta sử dụng đối tượng DocumentBuilder sẵn có trong Java. Nhiệm vụ của DocumentBuilder là phân tách nội dung tệp XML và chuyển đổi nội dung này thành cấu trúc DOM (Document Object Model) lưu trữ trong bộ nhớ, cho phép người lập trình sử dụng các hàm đã định nghĩa sẵn duyệt (traversing) cấu trúc phân cấp DOM để truy vấn dữ liệu.
Để minh họa, chúng ta xem xét lớp đối tượng DemoDOMParser, sử dụng DocumentBuilder để phân tách nội dung XML thành đối tượng Document theo mô hình DOM:
public class DemoDOMParser extends DemoAbstractParser {
public DemoDOMParser(String filename) {
super(filename);
}
public List<Student> getAllStudents() {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(
ClassLoader.getSystemResourceAsStream(getFilename()));
List<Student> studentList = new ArrayList<>();
NodeList nodeList = document.getDocumentElement().getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof Element) {
Student student = new Student();
student.setId(node.getAttributes().getNamedItem("id").getNodeValue());
NodeList childNodes = node.getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++) {
Node childNode = childNodes.item(j);
if (childNode instanceof Element) {
String content = childNode.getLastChild().getTextContent().trim();
String nodeName = childNode.getNodeName();
if ("fullname".equals(nodeName)) {
student.setFullname(content);
} else if ("email".equals(nodeName)) {
student.setEmail(content);
} else if ("address".equals(nodeName)) {
student.setAddress(content);
}
}
}
studentList.add(student);
}
}
return studentList;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Phân tách nội dung XML bằng SAX
Khác với việc phân tách XML theo mô hình DOM, quá trình phân tách nội dung XML bằng SAX sẽ không lưu trữ toàn bộ nội dung của tài liệu XML vào bộ nhớ, thay vào đó mỗi khi đọc đến một vị trí đặc biệt chẳng hạn như đầu tài liệu (start document), đầu thẻ (start element), nội dung văn bản (characters) bên trong thẻ, cuối thẻ hoặc cuối tài liệu XML, bộ xử lý của SAX (SAX Parser) sẽ phát sinh một sự kiện tương ứng với vị trí đồng thời gọi đến phương thức xử lý sự kiện trong lớp đối tượng Handler. Người lập trình có thể kế thừa lớp đối tượng DefaultHandler và nạp đè (override) các phương thức xử lý sự kiện trong lớp đối tượng này để xử lý theo cách của mình.
Lớp đối tượng DemoSAXParser dưới đây minh họa cho việc phân tách nội dung tệp student.xml theo cơ chế SAX. Lưu ý bên trong lớp đối tượng DemoSAXParser có chứa định nghĩa lớp đối tượng SAXHandler. Lớp SAXHandler này kế thừa từ DefaultHandler và nạp đè các phương thức xử lý sự kiện của SAX.
public class DemoSAXParser extends DemoAbstractParser {
public DemoSAXParser(String filename) {
super(filename);
}
@Override
public List<Student> getAllStudents() {
try {
SAXParserFactory parserFactor = SAXParserFactory.newInstance();
SAXParser parser = parserFactor.newSAXParser();
SAXHandler handler = new SAXHandler();
parser.parse(ClassLoader.getSystemResourceAsStream(getFilename()),
handler);
return handler.getResult();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
class SAXHandler extends DefaultHandler {
List<Student> studentList = new ArrayList<>();
Student student = null;
String content = null;
@Override
public void startElement(String uri, String localName,
String qName, Attributes attributes) throws SAXException {
if ("student".equals(qName)) {
student = new Student();
student.setId(attributes.getValue("id"));
}
}
@Override
public void endElement(String uri, String localName,
String qName) throws SAXException {
if ("student".equals(qName)) {
studentList.add(student);
} else if ("fullname".equals(qName)) {
student.setFullname(content);
} else if ("email".equals(qName)) {
student.setEmail(content);
} else if ("address".equals(qName)) {
student.setAddress(content);
}
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
content = String.copyValueOf(ch, start, length).trim();
}
public List<Student> getResult() {
return studentList;
}
}
}
Phân tách nội dung XML bằng StAX
Cách thứ ba để xử lý tài liệu XML chính là StAX. Tương tự như SAX, StAX cũng áp dụng cơ chế xử lý sự kiện trong việc phân tách nội dung XML. Tuy nhiên, giữa SAX và StAX có sự khác biệt cơ bản: bộ xử lý SAX (SAX Parser) chủ động gọi và “đẩy” dữ liệu cho phương thức xử lý mỗi khi có sự kiện phát sinh, trong khi bộ xử lý StAX (StAX Reader) chờ chương trình gọi đến sự kiện tiếp theo và chuẩn bị sẵn dữ liệu để chương trình “kéo” dữ liệu ra xử lý.
Ta có thể so sánh SAX với StAX giống như cách nhà hàng phục vụ thực khách trong tiệc cưới với cách nhà hàng phục vụ thực khách đơn lẻ. Trường hợp tiệc cưới, giống như cơ chế SAX, món ăn nào cũng như thời điểm phục vụ món ăn đó đã được gia chủ định sẵn (giống như lập trình trước các phương thức xử lý sự kiện trong SAXHandler vậy), do đó đến giờ là nhà hàng “đẩy” đồ ăn ra bàn phục vụ một cách chủ động, không phải chờ gọi. Còn với thực khách đơn lẻ, sẽ có người phục vụ luôn lắng nghe, thụ động chờ khách gọi món như thể khách “kéo” món ăn ra mỗi khi thấy cần.
Lớp đối tượng DemoStAXParser dưới đây minh họa cho việc phân tách nội dung tệp student.xml theo mô hình StAX.
public class DemoStAXParser extends DemoAbstractParser {
public DemoStAXParser(String filename) {
super(filename);
}
@Override
public List<Student> getAllStudents() {
try {
List<Student> studentList = null;
Student current = null;
String tagContent = null;
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(
ClassLoader.getSystemResourceAsStream(getFilename()));
while (reader.hasNext()) {
int event = reader.next();
switch (event) {
case XMLStreamConstants.START_ELEMENT:
String localName = reader.getLocalName();
if ("students".equals(localName)) {
studentList = new ArrayList<Student>();
} else if ("student".equals(localName)) {
current = new Student();
current.setId(reader.getAttributeValue(0));
}
break;
case XMLStreamConstants.CHARACTERS:
tagContent = reader.getText().trim();
break;
case XMLStreamConstants.END_ELEMENT:
localName = reader.getLocalName();
if ("student".equals(localName)) {
studentList.add(current);
} else if ("fullname".equals(localName)) {
current.setFullname(tagContent);
} else if ("email".equals(localName)) {
current.setEmail(tagContent);
} else if ("address".equals(localName)) {
current.setAddress(tagContent);
}
break;
}
}
return studentList;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Áp dụng ba bộ phân tách cú pháp trên
Dưới đây là đoạn chương trình ngắn minh họa việc sử dụng ba bộ phân tách cú pháp XML trên để đọc nội dung tệp student.xml. Kết quả được in ra màn hình console để so sánh.
public class App {
public static void main( String[] args ) {
String xmlFilename = "xml/student.xml";
Map<String, DemoXmlParser> parsers = new LinkedHashMap<>();
parsers.put("DOMParser", new DemoDOMParser(xmlFilename));
parsers.put("SAXParser", new DemoSAXParser(xmlFilename));
parsers.put("StAXParser", new DemoStAXParser(xmlFilename));
for(Map.Entry<String, DemoXmlParser> entry:parsers.entrySet()) {
System.out.println("Parsing student.xml using " + entry.getKey() + ":");
List<Student> result = entry.getValue().getAllStudents();
for (Student student : result) {
System.out.println(student);
}
}
}
}
Kết quả chạy chương trình:
Parsing student.xml using DOMParser:
{id:"1",fullname:"Nguyen Anh Minh",email:"minhna@gmail.com",
address:"345, Hung Vuong, Da Nang"}
{id:"2",fullname:"Tram Tu Thieng",email:"ttt.thieng@gmail.com",
address:"450 Doi Can, Ha Noi"}
{id:"3",fullname:"Duong Minh Tuan",email:"tuandm@gmail.com",
address:"144, Phan Dang Luu, Tp. Ho Chi Minh"}
Parsing student.xml using SAXParser:
{id:"1",fullname:"Nguyen Anh Minh",email:"minhna@gmail.com",
address:"345, Hung Vuong, Da Nang"}
{id:"2",fullname:"Tram Tu Thieng",email:"ttt.thieng@gmail.com",
address:"450 Doi Can, Ha Noi"}
{id:"3",fullname:"Duong Minh Tuan",email:"tuandm@gmail.com",
address:"144, Phan Dang Luu, Tp. Ho Chi Minh"}
Parsing student.xml using StAXParser:
{id:"1",fullname:"Nguyen Anh Minh",email:"minhna@gmail.com",
address:"345, Hung Vuong, Da Nang"}
{id:"2",fullname:"Tram Tu Thieng",email:"ttt.thieng@gmail.com",
address:"450 Doi Can, Ha Noi"}
{id:"3",fullname:"Duong Minh Tuan",email:"tuandm@gmail.com",
address:"144, Phan Dang Luu, Tp. Ho Chi Minh"}
Các bạn có thể tải mã nguồn đầy đủ của ví dụ minh họa để chạy thử.