Java SAX 파서 예제

자바의 SAX 파서는 XML 문서를 구문 분석하기 위한 API를 제공합니다. SAX 파서는 완전한 XML을 메모리에 로드하지 않고 순차적으로 XML 문서를 읽기 때문에 DOM 파서와는 다릅니다.

SAX 파서

javax.xml.parsers.SAXParser은 이벤트 핸들러를 사용하여 XML 문서를 구문 분석하는 방법을 제공합니다. 이 클래스는 XMLReader 인터페이스를 구현하고 parse() 메서드의 오버로드된 버전을 제공하여 파일, 입력 스트림, SAX InputSource 및 문자열 URI에서 XML 문서를 읽습니다. 실제 구문 분석은 Handler 클래스에 의해 수행됩니다. XML 문서를 구문 분석하기 위해 자체 핸들러 클래스를 만들어야 합니다. XML 문서를 구문 분석하기 위해 org.xml.sax.ContentHandler 인터페이스를 구현해야 합니다. 이 인터페이스에는 이벤트가 발생할 때 알림을 수신하는 콜백 메서드가 포함되어 있습니다. 예를 들어 StartDocument, EndDocument, StartElement, EndElement, CharacterData 등이 있습니다. org.xml.sax.helpers.DefaultHandlerContentHandler 인터페이스의 기본 구현을 제공하며 이 클래스를 확장하여 자체 핸들러를 만들 수 있습니다. 이 클래스를 확장하는 것이 좋습니다. 왜냐하면 구현해야 할 메서드가 몇 개뿐일 수 있기 때문입니다. 이 클래스를 확장하면 코드를 보다 깨끗하고 유지 관리할 수 있습니다.

SAX 구문 분석기 예제

이제 SAX 구문 분석기 예제 프로그램으로 이동해 보겠습니다. 나중에 다양한 기능을 자세히 설명하겠습니다. employees.xml

<?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>

우리는 파일 시스템 어딘가에 저장된 XML 파일을 가지고 있고, 그것을 보고 이 파일이 Employee 목록을 포함하고 있다는 결론을 내릴 수 있습니다. 각각의 Employee는 id 속성과 age, name, gender, role 필드를 가지고 있습니다. 우리는 이 XML을 파싱하고 Employee 객체의 목록을 만들기 위해 SAX 파서를 사용할 것입니다. 다음은 XML의 Employee 요소를 나타내는 Employee 객체입니다.

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;
    }
    
}

자신만의 SAX 파서 핸들러 클래스를 만들기 위해 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 {

	// Employee 객체를 보유하는 목록
	private List empList = null;
	private Employee emp = null;
	private StringBuilder data = null;

	// employee 목록에 대한 getter 메서드
	public List 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")) {
			// 새로운 Employee를 만들고 Map에 넣습니다
			String id = attributes.getValue("id");
			// Employee 객체를 초기화하고 id 속성을 설정합니다
			emp = new Employee();
			emp.setId(Integer.parseInt(id));
			// 목록을 초기화합니다
			if (empList == null)
				empList = new ArrayList<>();
		} else if (qName.equalsIgnoreCase("name")) {
			// 필드에 대한 불리언 값을 설정합니다. Employee 변수 설정에 사용될 것입니다
			bName = true;
		} else if (qName.equalsIgnoreCase("age")) {
			bAge = true;
		} else if (qName.equalsIgnoreCase("gender")) {
			bGender = true;
		} else if (qName.equalsIgnoreCase("role")) {
			bRole = true;
		}
		// 데이터 컨테이너를 생성합니다
		data = new StringBuilder();
	}

	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
		if (bAge) {
			// age 요소, 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")) {
			// Employee 객체를 목록에 추가합니다
			empList.add(emp);
		}
	}

	@Override
	public void characters(char ch[], int start, int length) throws SAXException {
		data.append(new String(ch, start, length));
	}
}

MyHandler는 getter 메서드만 있는 Employee 객체의 목록을 필드로 가지고 있습니다. Employee 객체는 이벤트 핸들러 메서드에서 추가됩니다. 또한, Employee 객체를 만들고 모든 필드가 설정되면 그것을 employee 목록에 추가하기 위해 사용될 Employee 필드가 있습니다.

오버라이드할 SAX 파서 메서드

중요한 오버라이드 메서드는 startElement(), endElement(), 그리고 characters()입니다. SAXParser는 문서 파싱을 시작하고 어떤 시작 엘리먼트를 찾으면 startElement() 메서드가 호출됩니다. 이 메서드를 오버라이드하여 엘리먼트를 식별하는 데 사용될 부울 변수를 설정합니다. 또한 이 메서드를 사용하여 Employee 시작 엘리먼트를 찾을 때마다 새로운 Employee 객체를 생성합니다. 여기서 id 속성이 읽히고 Employee 객체의 id 필드를 설정하는 방법을 확인하세요. characters() 메서드는 SAXParser가 엘리먼트 내에서 문자 데이터를 찾을 때 호출됩니다. SAX 파서는 데이터를 여러 청크로 나눌 수 있으며 characters() 메서드를 여러 번 호출할 수 있습니다(Read ContentHandler 클래스의 characters() 메서드 설명 참조). 그래서 우리는 StringBuilder를 사용하여 이 데이터를 append() 메서드를 사용하여 보관합니다. endElement()은 StringBuilder 데이터를 사용하여 employee 객체 속성을 설정하고 Employee 엘리먼트 종료 태그를 찾을 때마다 Employee 객체를 리스트에 추가하는 위치입니다. 아래는 MyHandler를 사용하여 위 XML을 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);
        //직원 목록 가져오기
        List empList = handler.getEmpList();
        //직원 정보 출력
        for(Employee emp : empList)
            System.out.println(emp);
    } catch (ParserConfigurationException | SAXException | IOException e) {
        e.printStackTrace();
    }
    }

}

위 프로그램의 출력은 다음과 같습니다.

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

SAXParserFactorySAXParser 인스턴스를 얻기 위한 팩토리 메서드를 제공합니다. MyHandler 인스턴스와 함께 parse 메서드에 File 객체를 전달하여 콜백 이벤트를 처리합니다. SAXParser는 처음에는 조금 혼란스러울 수 있지만 큰 XML 문서에서 작업 중이라면 DOM 파서보다 XML을 읽는 효율적인 방법을 제공합니다. 이로써 Java에서의 SAX 파서에 대한 설명이 마무리됩니다.

프로젝트를 GitHub 저장소에서 다운로드할 수 있습니다.

참조: SAXParser, DefaultHandler

Source:
https://www.digitalocean.com/community/tutorials/java-sax-parser-example