2010년 6월 6일 일요일

SAX를 사용한 XML 파싱 (Parsing XML document using SAX)



SAX를 사용해서 XML 문서를 파싱하려면 먼저 DefaultHandler를 확장한 클래스가 필요하다.

import org.xml.sax.helpers.DefaultHandler;

public class MySAXApp extends DefaultHandler
{
  public MySAXApp()
  {
    super();
  }
}

또한 JAVA 어플리케이션에서 동적으로 SAX드라이버를 사용하기 위해 XMLReaderFactory 클래스의 createXMLReader 메소드를 사용해서 XML reader를 만들어 줘야 한다.

public static void main(String args[])
{
  XMLReader xr = XMLReaderFactory.createXMLReader();
}

여기서 만든 오브젝트를 사용하면 XML문서를 파싱할 수 있는데, 먼저 XMLReader 인터페이스에서 setContentHandler와 setErrorHandler 메소드를 사용해 파싱결과와 에러가 발생했을 때 리포트하는데 사용되는 핸들러를 등록해 줘야 한다. 일반적으로 실제 복잡한 어플리케이션에서는 핸들러를 별도의 오브젝트로 만들지만 여기서는 간단한 데모를 위해 부모 클래스에 포함시켰기 때문에 단순히 클래스를 인스턴스화 해서 XML reader에 등록하면 된다.

public static void main(String args[])
{
  XMLReader xr = XMLReaderFactory.createXMLReader();
  MySAXApp handler = new MySAXApp();
  xr.setContentHandler(handler);
  xr.setErrorHandler(handler);
}

위의 예제 코드는 파싱 이벤트를 처리할 MySAXApp 인스턴스를 만들어 그 인스턴스를 XML reader의 정상적인 컨텐트 이벤트와 에러 이벤트 핸들러로 등록한다.

public static void main(String args[])
{
  XMLReader xr = XMLReaderFactory.createXMLReader();
  MySAXApp handler = new MySAXApp();
  xr.setContentHandler(handler);
  xr.setErrorHandler(handler);

  for (int i=0; i<args.length;i++) {
    FileReader r = new FileReader(args[i]);
    xr.parse(new InputSource(r));
  }
}

지금까지 설명한 예제의 전체 코드는 다음과 같다.

import java.io.FileReader;

import org.xml.sax.XMLReader;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.DefaultHandler;

public class MySAXApp extends DefaultHandler
{
  public static void main (String args[])
    throws Exception {
      XMLReader xr = XMLReaderFactory.createXMLReader();
      MySAXApp handler = new MySAXApp();
      xr.setContentHandler(handler);
      xr.setErrorHandler(handler);

      // Parse each file provided on the
      // command line.
      for (int i = 0; i < args.length; i++) {
        FileReader r = new FileReader(args[i]);
        xr.parse(new InputSource(r));
      }
    }

  public MySAXApp ()
  {
    super();
  }
}

예제 코드를 컴파일해서 실행시키면 XML문서를 파싱하지만 그에 따라 발생하는 이벤트를 처리하는 코드가 빠져있기 때문에 XML문서의 포맷이 틀려 에러가 발생하지 않으면 아무 출력도 없이 실행이 종료된다.


이벤트 처리

XML 문서를 처리하려면 파싱하면서 발생하는 이벤트를 처리해주는 핸들러를 만들어 줘야 한다. 먼저 가장 중요한 이벤트로 문서의 시작과 끝, 엘리먼트의 시작과 끝, 문자데이터가 있다.
클라이언트 어플리케이션이 문서의 시작과 끝을 발견하려면 startDocument/endDocument 메소드를 구현해줘야 한다.

public void startDocument()
{
  System.out.println("Start document");
}

public void endDocument()
{
  System.out.println("End document");
}

startDocument/endDocument 핸들러는 별도 argument를 가지지 않는다. SAX드라이버가 문서의 시작을 발견하면 startDocument 메소드를 호출한다. 또한 문서의 끝을 발견하면 (심지어 문서 내에 에러가 있더라도) endDocument 메소드를 호출한다.

예제 코드에서는 단순히 stdout에 메시지를 출력하지만 실제 어플리케이션에서는 핸들러 내에 어떤 코드도 넣어줄 수 있다. 가장 빈번히 볼 수 있는 코드는 메모리 상주 트리 생성, 내용 출력, 데이터베이스 생성/추가, 정보추출등을 수행한다.

SAX드라이버는 엘리먼트의 시작과 끝도 동일한 방법을 사용해 알려준다. 다만 startElement와 endElement 메소드는 몇개의 argument를 같이 넘겨준다.

public void startElement(String uri, String name, String qName, Attributes atts)
{
  if ("".equals(uri)) System.out.println("Start elements: " + qName);
  else System.out.println("Start elements: {" + uri + "}" + name);
}

public void endElement(String uri, String name, String qName)
{
  if ("".equals(uri)) System.out.println("End elements: " + qName);
  else System.out.println("End elements: {" + uri + "}" + name);
}

이 메소드들은 엘리먼트의 시작과 끝을 알려주는데 엘리먼트에 Namespace URI가 정의되어 있으면 '{URI namespace}이름' 형태로 출력하고 그렇지 않은 경우는 단순히 '이름' 형태로 출력해준다. qName에 XML 1.0 이름이 들어가 있기 때문에 namespace URI가 없는 엘리먼트의 경우 이 이름을 사용해줘야만 한다.

마지막으로 SAX2는 일반 문자데이터는 characters 메소드를 통해 리포트한다. 아래의 코드는 모든 문자데이터를 화면에 출력하는데 특수문자를 escape-character를 사용해 표현하기 때문에 보기좋게 출력하기 위해 코드가 좀 길다.

public void characters (char ch[], int start, int length)
{
  System.out.print("Characters:    \"");
  for (int i = start; i < start + length; i++) {
    switch (ch[i]) {
      case '\\':
        System.out.print("\\\\");
        break;
      case '"':
        System.out.print("\\\"");
        break;
      case '\n':
        System.out.print("\\n");
        break;
      case '\r':
        System.out.print("\\r");
        break;
      case '\t':
        System.out.print("\\t");
        break;
      default:
        System.out.print(ch[i]);
        break;
    }
  }
  System.out.print("\"\n");
}

지금까지 설명된 내용을 모두 추가한 예제 프로그램이다.

import java.io.FileReader;

import org.xml.sax.XMLReader;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.DefaultHandler;

public class MySAXApp extends DefaultHandler
{
    public static void main (String args[])
        throws Exception
    {
        XMLReader xr = XMLReaderFactory.createXMLReader();
        MySAXApp handler = new MySAXApp();
        xr.setContentHandler(handler);
        xr.setErrorHandler(handler);

        // Parse each file provided on the command line.
        for (int i = 0; i < args.length; i++) {
            FileReader r = new FileReader(args[i]);
            xr.parse(new InputSource(r));
        }
    }

    public MySAXApp ()
    {
        super();
    }

    ////////////////////////////////////////////////////////////////////
    // Event handlers.
    ////////////////////////////////////////////////////////////////////

    public void startDocument ()
    {
        System.out.println("Start document");
    }

    public void endDocument ()
    {
        System.out.println("End document");
    }

    public void startElement (String uri, String name,
                              String qName, Attributes atts)
    {
        if ("".equals (uri))
            System.out.println("Start element: " + qName);
        else
            System.out.println("Start element: {" + uri + "}" + name);
    }

    public void endElement (String uri, String name, String qName)
    {
        if ("".equals (uri))
            System.out.println("End element: " + qName);
        else
            System.out.println("End element:   {" + uri + "}" + name);
    }

    public void characters (char ch[], int start, int length)
    {
        System.out.print("Characters:    \"");
        for (int i = start; i < start + length; i++) {
            switch (ch[i]) {
            case '\\':
                System.out.print("\\\\");
                break;
            case '"':
                System.out.print("\\\"");
                break;
            case '\n':
                System.out.print("\\n");
                break;
            case '\r':
                System.out.print("\\r");
                break;
            case '\t':
                System.out.print("\\t");
                break;
            default:
                System.out.print(ch[i]);
                break;
            }
        }
        System.out.print("\"\n");
    }
}

샘플 출력

예제에 사용할 XML파일이다.

<?xml version="1.0"?>

<poem xmlns="http://www.megginson.com/ns/exp/poetry">
<title>Roses are Red</title>
<l>Roses are red,</l>
<l>Violets are blue;</l>
<l>Sugar is sweet,</l>
<l>And I love you.</l>
</poem>

이 문서파일 이름을 roses.xml이라고 하고 com.example.xml.SAXDriver 클래스 패스에 SAX2 드라이버가 있으면 샘플 어플리케이션은 다음과 같이 실행하면 된다.

java -Dorg.xml.sax.driver=com.example.xml.SAXDriver MySAXApp roses.xml

실행 결과는 다음과 같다.

Start documentStart element: {http://www.megginson.com/ns/exp/poetry}poem
Characters:    "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}title
Characters:    "Roses are Red"
End element:   {http://www.megginson.com/ns/exp/poetry}title
Characters:    "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}l
Characters:    "Roses are red,"
End element:   {http://www.megginson.com/ns/exp/poetry}l
Characters:    "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}l
Characters:    "Violets are blue;"
End element:   {http://www.megginson.com/ns/exp/poetry}l
Characters:    "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}l
Characters:    "Sugar is sweet,"
End element:   {http://www.megginson.com/ns/exp/poetry}l
Characters:    "\n"
Start element: {http://www.megginson.com/ns/exp/poetry}l
Characters:    "And I love you."
End element:   {http://www.megginson.com/ns/exp/poetry}l
Characters:    "\n"
End element:   {http://www.megginson.com/ns/exp/poetry}poem
End document

이렇게 짧은 문서라도 (최소) 25개의 이벤트가 발생하는걸 볼 수 있다. 문서의 시작과 끝에 각각 하나씩, 6개의 엘리먼트마다 시작과 끝, 11개의 문자데이터(엘리먼트 사이의 공백을 포함)모든 엘리먼트의 네임스페이스를 선언하기 위한 xmlns="http://www.magginson.com/ns/exp/poetry" 속성이 포함되어 있으면 위와 같은 결과가 나오지만 xmlns 속성이 정의되어 있지 않았을때의 출력은 다음과 같다.

Start document
Start element: poem
Characters:    "\n"
Start element: title
Characters:    "Roses are Red"
End element:   title
Characters:    "\n"
Start element: l
Characters:    "Roses are red,"
End element:   l
Characters:    "\n"
Start element: l
Characters:    "Violets are blue;"
End element:   l
Characters:    "\n"
Start element: l
Characters:    "Sugar is sweet,"
End element:   l
Characters:    "\n"
Start element: l
Characters:    "And I love you."
End element:   l
Characters:    "\n"
End element:   poem
End document

어플리케이션은 XML 네임스페이스가 있는 경우와 없는 경우 두가지 타입의 문서를 모두 사용하게 될 수 있다. 또한 문서 내 일부 엘리먼트는 네임스페이스를 가지고 있고 나머지는 그렇지 않은 문서를 다룰수도 있기 때문에 항상 네임스페이스가 있거나 또는 없다고 가정하지 말고 코드내에서 실제로 네임스페이스 URI를 확인해야만 한다.

댓글 없음:

댓글 쓰기