미처 알아차리지 못했을 지도 모르지만, 우리가 평소에 보고있는 많은 정보들은 XML로 저장되어 있다. XML은 주로 엔터프라이즈 애플리케이션과 컨피규레이션 파일에 사용된다고 생각하기 쉽지만, XML과 유사한 형태가 다른 문서들에도 많이 사용되고 있다. 예를 들어 XML version of the periodic table of the elements를 찾아볼 수 있다. 이번 테크팁에서는 Simple API for XML (SAX) parser 를 이용하여XML 문서 안 특정 부분에 위치한 정보를 추출하는 방법에 대해 알아보자. 이번 테크팁은 자바 애플리케이션의 XML 작업이 처음인 개발자들에게 유용할 것이다. processing XML 중의 한 모델을 소개하고 SAX를 처음 이용하는 개발자들이 범하기 쉬운 실수에 대해서 짚어보도록 하자.
우선 allelements.xml 파일을 다운로드 하자. 다음과 같이 시작하는 파일을 보게 될 것이다.
<?xml version="1.0"?>
<PERIODIC_TABLE>
<ATOM>
<NAME>Actinium</NAME>
<ATOMIC_WEIGHT>227</ATOMIC_WEIGHT>
<ATOMIC_NUMBER>89</ATOMIC_NUMBER>
<OXIDATION_STATES>3</OXIDATION_STATES>
<BOILING_POINT UNITS="Kelvin">3470</BOILING_POINT>
<SYMBOL>Ac</SYMBOL>
<DENSITY UNITS="grams/cubic centimeter"><!-- At 300K -->
10.07
</DENSITY>
<ELECTRON_CONFIGURATION>[Rn] 6d1 7s2
</ELECTRON_CONFIGURATION>
<ELECTRONEGATIVITY>1.1</ELECTRONEGATIVITY>
<ATOMIC_RADIUS UNITS="Angstroms">1.88
</ATOMIC_RADIUS>
<ATOMIC_VOLUME UNITS="cubic centimeters/mole">
22.5
</ATOMIC_VOLUME>
<SPECIFIC_HEAT_CAPACITY
UNITS="Joules/gram/degree Kelvin">
0.12
</SPECIFIC_HEAT_CAPACITY>
<IONIZATION_POTENTIAL>5.17</IONIZATION_POTENTIAL>
<THERMAL_CONDUCTIVITY
UNITS="Watts/meter/degree Kelvin">
<!-- At 300K -->
12
</THERMAL_CONDUCTIVITY>
</ATOM>
<!-- remaining ATOM elements omitted -->
</PERIODIC_TABLE>
이전에 본 것을 기억하지 못하는 상태에서 이 파일을 검색(traverse)한다고 치자. 문서의 처음이나 끝, 특정 타입 요소의 처음이나 끝, 그 외 쉽게 알아볼 수 있는 부분에서는 동작을 실행할 수 있다. 그러나 그 과정에서 무엇을 보았는지 기억하기는 쉽지 않다. 예를 들어 Actinium의 SYMBOL요소를 얻고자 할 때, ATOMIC_NUMBER 요소의 컨텐츠가 text 89인 것은 기억하기 어렵다. 방금 상상했던 건 사실상 SAX parser의 스트리밍 프로세스 모델이다. SAX parser는 XML 문서를 선형 방식으로 읽는다. (반면에 Document Object Model(DOM) parser는 계층구조적 방식으로 검색한다.) 수정 작업이 이루어 질 때마다 액티브 컨텐츠의 메서드가 핸들러로써 호출된다.
적절한 메서드를 오버라이드함으로써 핸들러를 개별화할 수 있다. 이번 테크팁에서는 먼저 DefaultHandler를 사용하여 startElement(), endElement(), startDocument(), endDocument(), characters() 메서드를 오버라이드해보자.
원자의 요소, 기호, 무게를 추출하기 위해서 어떻게 이 파일을 프로세스할 지 생각해보라. 목적은 다음과 같은 결과를 도출하는 것이다.
Parsing allelements.xml ======================= Actinium (Ac) 89 Aluminum (Al) 13 Americium (Am) 95 Antimony (Sb) 51 Argon (Ar) 18 Arsenic (As) 33 ... Zinc (Zn) 30 Zirconium (Zr) 40 ============================ End Parse of allelements.xml
첫번째 과제는 문서를 파싱하기 시작할 때 헤더(header)를 출력하는 것이다. 다음 메서드를 이용해 실행할 수 있을 것이다.
public void startDocument() throws SAXException {
System.out.println("Parsing allelements.xml");
System.out.println("=======================");
}
이것은 콜백 메서드로써, parser가 문서 처음부분에 도달했을 때 호출된다. 이 때 문서의 서명 부분은 제외된다. (각 콜백에 이 메서드가 SAXException을 뜨로우한다는 것을 지정해 주어야 한다.)
정확한 리포트에는 세 개의 원자 요소마다 빈 줄이 삽입되어 있어야 한다. 이를 위해서는 요소의 첫부분을 경청하다가 카운터를 증가(increment)시키면 된다. ATOM 요소의 첫 부분을 증가시켜보자. 그러면 카운터가 세 번 증가될 때마다 빈 줄 하나를 출력할 수 있다. 다음은 이 실행작업의 코드이다.
public void startElement(String namespace,
String localName,
String qName,
Attributes atts)
throws SAXException {
if (localName.equals("ATOM")) {
if (count++ % 3 == 0) System.out.println("");
}
}
if(localName.equals("SYMBOL")을 검사함으로써 기호를 출력해보고 싶을 것이다. SYMBOL 요소의 첫 부분에서 namespace, localName, qualifiedName, 그리고 속성들에 대해서 알고있을 것이다. 그러나 그 요소의 컨텐츠에 대해서는 모른다. 따라서 요소의 마지막 부분에 이 값을 기록할 필요가 있다. 이는 다음과 같다
public void endElement(String uri, String localName,
String qName) throws SAXException {
if (localName.equals("NAME"))
name = temp;
else if (localName.equals("ATOMIC_NUMBER"))
number = temp;
else if (localName.equals("SYMBOL"))
symbol = temp;
else if (localName.equals("ATOM")) {
System.out.println("" + name + " (" +
symbol + ") " + number);
}
}
endElement() 메서드에서 NAME, ATOMIC_NUMBER, SYMBOL 요소들의 컨텐츠 값을 저장한 후, ATOM 요소의 마지막 부분에 도달했을 때 정확한 순서에 따라 이 값들을 스크린에 출력한다.
그 다음으로 temp 변수의 값이 어떻게 설정되었는지 검사해야 한다. 이 작업은 characters() 메서드를 이용하여 할 수 있다.
public void characters(char buf[],int offset,int len)
throws SAXException {
String temp2;
temp2 = new String(buf, offset, len).trim();
if (!temp2.equals("")) {
temp = temp2;
}
}
temp2라는 이름의 새로운 String을 생성하자. 이것은 요소의 컨텐츠를 읽고 처음과 마지막의 스페이스를 제거한다. 제거 후 빈 String이 아닌 어떤 것이 남겨져 있다면 temp에 저장해라. 각 요소의 마지막 부분에서 temp는 요소의 컨텐츠들로 구성된 문자 String을 담게 될 것이다.
SAXParser를 얻기 위해 사용하는 SAXParserFactory를 만드는 일만 남았다. SAXParser는 ParserAdapter를 리턴하기 위해 ParserAdapter 컨스트럭터로 변환된다. ContentHandler를 ParserAdapter와 연관시켜 생각해본 후 특정 파일은 파싱해보자. 이 작업의 코드는 다음과 같다.
SAXParserFactory spf =
SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
ParserAdapter pa =
new ParserAdapter(sp.getParser());
pa.setContentHandler(this);
pa.parse("file:allelements.xml");
다음은 XML 문서로부터 정보를 추출하고 원하는 형식으로 결과물을 만들어내는 전체프로그램이다. 이름은 AlphaList라고 한다.
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.ParserAdapter;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
public class AlphaList extends DefaultHandler {
private String name;
private String number;
private String symbol;
private String temp;
private int count;
public void startDocument() throws SAXException {
System.out.println("Parsing allelements.xml");
System.out.println("=======================");
}
public void startElement(String namespace,
String localName,
String qName,
Attributes atts)
throws SAXException {
if (localName.equals("ATOM")) {
if (count++ % 3 == 0) System.out.println("");
}
}
public void endElement(String uri, String localName,
String qName) throws SAXException {
if (localName.equals("NAME"))
name = temp;
else if (localName.equals("ATOMIC_NUMBER"))
number = temp;
else if (localName.equals("SYMBOL"))
symbol = temp;
else if (localName.equals("ATOM")) {
System.out.println("" + name + " (" +
symbol + ") " + number);
}
}
public void endDocument() throws SAXException {
System.out.println("============================");
System.out.println("End Parse of allelements.xml");
}
public void characters(char buf[],int offset,int len)
throws SAXException {
String temp2;
temp2 = new String(buf, offset, len).trim();
if (!temp2.equals("")) {
temp = temp2;
}
}
public void processWithSAX()
{
try {
SAXParserFactory spf =
SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
ParserAdapter pa =
new ParserAdapter(sp.getParser());
pa.setContentHandler(this);
pa.parse("file:allelements.xml");
} catch (ParserConfigurationException e) {
System.err.println("Problem configuring parser. "
+ e.getMessage());
} catch (SAXException e) {
System.err.println("Problem parsing file. "
+ e.getMessage());
} catch (IOException e) {
System.err.println("Problem reading file. "
+ e.getMessage());
}
}
public static void main(String[] args) {
new AlphaList().processWithSAX();
}
}
allelements.xml을 AlphaList로 컴파일하고 실행하자.
java AlphaList allelements.xml
다음과 같은 결과를 얻게 된다.
Parsing allelements.xml ======================= Antimony (Sb) 51 Argon (Ar) 18 Arsenic (As) 33 Astatine (At) 85 Gold (Au) 79 Boron (B) 5 Barium (Ba) 56 Beryllium (Be) 4 Bohrium (Bh) 107 Bismuth (Bi) 83 Berkelium (Bk) 97 Bromine (Br) 35
더 많은 문서들이 XML로 저장될수록 컴퓨터에 그 문서들을 분석하는 툴을 갖고있는 것이 유용할 것이다.
SAX 나 SAX parser에 대한 좀 더 많은 정보를 원한다면 J2EE 1.4 튜토리얼의 Chapter 5: Simple API for XML를 참고하기 바란다.
"Java SE" 카테고리의 다른 글
- JSSE 이용한 안전한 커뮤니케이션 (댓글 2개 / 트랙백 0개) 2004/08/31
- 3D 화면(scene)에 빛 효과 주기 (댓글 1개 / 트랙백 0개) 2004/07/30
- Java SE & Java SE for Business 지원 로드맵 (댓글 0개 / 트랙백 0개) 2009/09/11
- 클래스에서 enhanced For-Loop 사용 (댓글 0개 / 트랙백 1개) 2007/10/09
- VARIABLE CONTENT로 메세지 포맷하기 (댓글 7개 / 트랙백 2개) 2003/08/19
- CONTENTPANE 작업의 변화 (댓글 1개 / 트랙백 0개) 2004/11/11
- 새로운 포매터로 출력물 포맷하기 (댓글 1개 / 트랙백 0개) 2004/10/27
- 스윙 유저 인터페이스에서 컴포넌트의 방향성 (댓글 2개 / 트랙백 0개) 2003/09/26
- 쓰레드의 상태정보를 저장할 때 사용되는 THREADLOCAL 변수들 (댓글 4개 / 트랙백 0개) 2003/12/12
- 2개의 스트링이 같은 경우는? (댓글 2개 / 트랙백 0개) 2004/05/27
댓글을 달아 주세요
좋은 정보 감사해요~
2007/09/19 05:43ZzmXRy <a href="http://tjhsjgbhxlxj.com/">tjhsjgbhxlxj</a>, [url=http://fpvjbjwgqnug.com/]fpvjbjwgqnug[/url], [link=http://nnozkukwoqso.com/]nnozkukwoqso[/link], http://wpfnkdgbyivf.com/
2009/10/18 12:37bTMQ3F <a href="http://vqiryahadtdv.com/">vqiryahadtdv</a>, [url=http://pfvjavuxoacw.com/]pfvjavuxoacw[/url], [link=http://focwnheeuaqr.com/]focwnheeuaqr[/link], http://vioyfcylfeex.com/
2009/10/20 07:06