SUN JAVA STREAMING XML PARSER 심층 탐구

Java EE 2005/11/22 16:39 Posted by Sun
저자 Kim LiChong

2005년 4월 19일자 테크 팁 Sun Java Streaming XML Parser 소개에서는 SJSXP(Sun Java Streaming XML Parser)와 XML로 작업하기 위한 2개의 API 라이브러리, SAX(Simple API for XML)와 DOM(Document Object Model) 간의 차이점을 개괄한 바 있다. 내용을 요약해 보면, SJSXP는 StAX(Streaming API for XML) 파싱?JSR173?의 구현이며, StAX의 구현인 SJSXP는 XML infosets가 애플리케이션의 런타임 동안 순차적으로 전송 및 파싱될 수 있도록 도와주는 역할을 한다. SJSXP는 노드를 파서로부터 애플리케이션으로 ‘push’하는 대신 XML 문서로부터 노드를 ‘pull’할 수 있게 해주므로 SJSXP의 속도는 매우 빠른 편이다. Streaming APIs for XML Parsers(영문)라는 제목의 백서에서는 SJSXP의 성능을 다른 StAX 구현과 비교하는 내용을 다루고 있기도 하다. StAX는 JAXP 1.4에 포함되어 있으며, jaxp 프로젝트 페이지에서 JAXP 1.4 구현만 따로 다운로드할 수 있다.

SJSXP는 XML 프로세싱을 위해 StAX에 의해 정의되는 두 가지 API, 커서 API와 반복자(iterator) API를 모두 지원한다. SJSXP에 관한 이전 테크 팁에는 커서 API를 이용하여 XML 문서를 파싱하고 작성하는 방법을 보여주는 코드 예제가 포함되어 있었다. 이번 테크 팁에서는 반복자 API를 중점적으로 다루도록 하고. 아울러 반복자 API 사용법을 보여주는 코드 예제와 반복자 API의 사용 시점을 기술하는 일반적인 지침에 대해 알아보도록 한다.

커서 API와 반복자 API의 비교

커서 API는 SJSXP의 low-level 표현을 제공하는데, 문서의 시작부터 끝까지 커서를 이용하여 하나의 infoset 엘리먼트를 표시해 준다. 항시 커서는 문서에서 전방으로 움직이며, XML에 대한 이러한 커서 기반의 전진 전용(forward-only) 액세스를 위해 XMLStreamReaderXMLStreamWriter의 두 가지 인터페이스가 사용된다. XMLStreamReader의 다양한 메소드는 커서가 가리키는 곳에서 데이터를 pull할 수 있게 해준다. SJSXP에 관해 이전 팁에서 언급했던 것처럼, XMLInputStreamReader에는 커서가 가리키고 있는 XML 항목의 내용을 획득하는 데 사용할 수 있는 여러 가지 get 메소드가 있다. 예를 들어, 다음의 메소드

   public int getEventType()

는 파서가 커서 밑에서 발견한 이벤트의 타입을 식별하는 정수 코드를 반환한다. 이벤트의 예로는 XML 엘리먼트의 시작 또는 문서의 종료를 들 수 있다. 다음의 메소드

   public String getText();  

는 커서가 가리키고 있는 XML 항목에서 텍스트를 얻는다.

검색된 모든 XML 정보는 SAX에 의해 검색된 정보와 마찬가지로 문자열로 반환되고, 각 이벤트는 정수 상수로 표현된다. 예를 들어, XML 엘리먼트의 시작을 위한 상수는 XMLStreamConstants.START_ELEMENT이며, XML 엘리먼트의 종료를 위한 상수는 XMLStreamConstants.END_ELEMENT이다. 애플리케이션이 각각의 이벤트에 관한 정보를 획득하려면 해당 메소드를 호출해야 한다. 예)

   while(parser.hasNext()) {
             
         eventType = parser.next();
         switch (eventType) {

              case START_ELEMENT:
              //  Do something
              break;
              case END_ELEMENT:
              //  Do something
              break;
              //  And so on ...
         }
     }

XMLStreamWriter의 다양한 메소드는 노드 정보를 XML 문서에 작성할 수 있게 해주는데, 예를 들어 XMLStreamWriter.writeStartElement는 시작 태그를 작성하고 XMLStreamWriter.writeCharacters는 텍스트를 작성한다.

이와 대조적으로, 반복자 API는 XML 문서를 읽혀지는 순서대로 pull하는 일련의 분리된 오브젝트 이벤트로 표현한다. 이 이벤트 오브젝트들은 불변적이고 영속적이며, 특정 이벤트와 관련된 모든 정보를 캡슐화한다. 단, 각 이벤트 오브젝트를 생성할 때는 오버헤드가 일부 발생하므로 이 방식은 커서 API를 이용하는 것보다 다소 효율성은 떨어지는 편이다.

커서 API의 경우와 마찬가지로, 반복자 API는 읽기와 쓰기를 위한 2개의 API, XMLEventReaderXMLEventWriter를 가진다. 파싱의 경우, XMLEventReader.nextEvent() 메소드를 호출함으로써 노드에 액세스할 수 있으며, XMLEvent 인터페이스는 다양한 이벤트 타입을 나타내는 13개의 서브인터페이스를 가진다.

  • StartDocument
  • StartElement
  • EndElement
  • Characters
  • Comment
  • EndDocument
  • Attribute
  • Namespace
  • DTD
  • EntityReference
  • ProcessingInstruction
  • EntityDeclaration
  • NotationDeclaration

DTD, EntityReference, ProcessingInstruction, EntityDeclaration, NotationDeclaration 등의 이벤트는 문서에 DTD가 포함된 경우에만 생성된다는 점에 유의하도록 한다.

XMLEventReader 사용하기

이제 XMLEventReader를 사용하는 몇 가지 코드 예제를 살펴보자. 이 예제에서 타깃 XML 문서는 HockeyTeams.xml로 명명된다. 다음은 HockeyTeams.xml의 내용이다.

   <HockeyTeams xmlns="http://www.myhockey.net">
     <Team>
     <City>Toronto</City>
     <Nickname>Maple Leafs</Nickname>
     <Coach>Pat Quinn</Coach>
     <Captain>Mats Sundin</Captain>
     <Wins year="2003">45</Wins>
     <MarketValue currency="USD">280</MarketValue>   
     </Team>
   </HockeyTeams>

다음의 코드를 이용하여 XML 문서를 파싱할 수 있다.

   URL url = Class.forName("MyClassName").getResource(
          "HockeyTeams.xml");  
     InputStream in = url.openStream();
     XMLInputFactory factory = XMLInputFactory.newInstance();
     XMLEventReader r = factory.createXMLEventReader(in);

다음과 같은 구조의 코드를 반복할 수 있다.

    while(r.hasNext()) {
         XMLEvent e = r.nextEvent();
         System.out.println(e.toString());
    }

HockeyTeams.xml로부터 반환된 각 XMLEvent를 프린트하면 아래와 유사한 아웃풋을 얻게 된다.

 <<['http://www.myhockey.net']::
   HockeyTeams xmlns:='http://www.myhockey.net'>
   <['http://www.myhockey.net']::Team>
   <['http://www.myhockey.net']::City>
   Toronto
   </['http://www.myhockey.net']::City>
   <['http://www.myhockey.net']::Nickname>
   Maple Leafs
   </['http://www.myhockey.net']::Nickname>      
   <['http://www.myhockey.net']::Coach>
   Pat Quinn
   </['http://www.myhockey.net']::Coach>    
   <['http://www.myhockey.net']::Captain>
   Mats Sundin
   </['http://www.myhockey.net']::Captain>    
   <['http://www.myhockey.net']::Wins year='2003'>
   45
   </['http://www.myhockey.net']::Wins>               
   <['http://www.myhockey.net']::MarketValue currency='USD'>
   280
   </['http://www.myhockey.net']::MarketValue>
   </['http://www.myhockey.net']::Team>
   </['http://www.myhockey.net']::HockeyTeams>
   ENDDOCUMENT

XMLEvent는 해당 이벤트에 관한 모든 정보를 캡슐화한다. getEventType() 메소드를 이용하면 이벤트 타입을 지정하는 정수 코드를 얻을 수 있고, 그런 다음 이벤트에 관한 특정 정보(가령 개별적인 서브타입)를 얻을 수 있다. 예)

   if(event.getEventType() == event.CHARACTERS) {
      Characters chars = event.asCharacters();
      System.out.println("chars " + chars.getData() );
   }

또는 다음과 같이 이벤트로부터 엘리먼트 이름 또는 속성을 얻게 된다.

   if(event.getEventType() == event.START_ELEMENT) {
     StartElement startE = event.asStartElement();
     System.out.println("start" + startE.getName());
     Iterator it = startE.getAttributes();
      while (it.hasNext()) {
       System.out.println(" attributes " + it.next()); 
      }
    }

StartElement 오브젝트가 노드에 관한 정보(시작 태그의 로컬 이름, 그 접두어, 네임스페이스 URI, 속성, 네임스페이스 선언 등)를 가진다는 점에 유의한다. 특히, StartElement.getName()QName, 즉 XML 스펙에 정의된 Qualified Name을 반환하는데, QName 오브젝트를 조회하면 로컬 파트와 네임스페이스 URI를 얻을 수 있다. 일반적으로, 속성이나 네임스페이스 같은 이차적 이벤트에 액세스하기 전에 StartElement를 획득하게 되지만, 먼저 StartElement를 획득하지 않고서 독립적인 Attribute 또는 Namespace 이벤트를 보고할 수도 있다. 네임스페이스 이벤트는 StartElement 또는 해당 EndElement로부터 액세스할 수 있으며, StartDocument 오브젝트에는 인코딩, XML 버전, 독립적 속성 등을 비롯한 정보가 포함되어 있다.

XMLEventWriter 사용하기

쓰기에는 XMLEventWriter 인터페이스를 이용하는데, 여기에는 아웃풋 스트림에 이벤트를 추가하기 위한 메소드 XMLEventWriter.add(XMLEvent)가 포함되어 있다. 다음은 XMLEventWriter를 이용하여 아웃풋을 XML 문서로 유도하는 코드 예제이다.

   XMLEventFactory eventFactory = XMLEventFactory.newInstance();
   XMLOutputFactory output = XMLOutputFactory.newInstance();
   XMLEventWriter xmlwriter = 
     output.createXMLEventWriter(System.out);

커서 API와 반복자 API를 이용하는 데 따른 차이점에 유의하도록 한다. 커서 API의 경우에는 XMLStreamWriter의 다양한 메소드를 이용해서 속성, 특성 또는 엘리먼트를 작성하지만(인자를 String 오브젝트로 전송) 반복자 API의 XMLEventWriter에서는 먼저 유틸리티 팩토리 클래스 XMLEventFactory를 이용해서 오브젝트를 XMLEvent로 생성해야만 한다. 그런 다음 아웃풋 스트림을 지정하는 XMLEvent를 생성하고 XMLEventWriterXMLEvent를 추가한다. 이 예제에서 아웃풋 스트림은 System.out이다.

다음 코드 예제의 첫 행에서 EventFactoryStartDocument 인터페이스를 구현하는 오브젝트를 생성하고, StartDocument는 다시 XMLEvent 인터페이스를 확장한다. 2개의 인자는 인코딩과 XML 버전을 지정한다. 메소드 createStartElement()createAttribute()는 오버로드되므로 다양한 방식으로 XMLEvent를 생성할 수 있다. 예를 들어, 아래 예제에서 속성과 네임스페이스는 Iterator 오브젝트로서 추가된다.

   xmlwriter.add(eventFactory.createStartDocument("UTF-8","1.0");
   //for attributes
   Attribute att = eventFactory.createAttribute("year", "2003");
   ArrayList attArr = new ArrayList();
   attArr.add(att);
   //for namespaces
    Namespace namespace = 
      eventFactory.createNamespace("foo","http://www.foo.org");
    ArrayList nameArr = new ArrayList();
    nameArr.add(namespace);
    //order namespace, localname, prefix
    QName qname = new Qname (
            "http://www.foo.org","HockeyTeam","foo");

    //now create the start element
    xmlwriter.add(eventFactory.createStartElement(
            qname, attArr.iterator(), nameArr.iterator()));
    xmlwriter.add(eventFactory.createCharacters(
            "Los Angeles Kings"));
    xmlwriter.add(eventFactory.createEndElement(
            qname, nameArr.iterator()));
    xmlwriter.add(eventFactory.createEndDocument());
    xmlwriter.flush();
    xmlwriter.close();

커서 API와 반복자 API의 사용 경우 결정하기

Java Web Services Developer Pack 1.6 튜토리얼(영문)의 Streaming API for XML 내용에는 커서 API와 반복자 API의 선택에 있어서 고려해야 할 사항이 기재되어 있다. 다음은 고려사항을 요약한 것이다.

  • 커서 API를 이용하면 더 작고, 빠르고, 효율적인 코드를 만들 수 있다.
  • 파서가 다음 이벤트로 이동한 후에도, XMLEvent 서브클래스로부터 생성한 오브젝트를 애플리케이션의 어레이, 리스트, 맵으로 패스할 수 있는데, 그 이유는 이 오브젝트들이 불변(immutable)의 속성을 지니고 있기 때문이다.
  • 완전히 새로운 정보 항목이거나 기존 항목의 확장인 동시에 메소드가 추가된 XMLEvent의 서브타입을 생성할 수 있다.
  • 이벤트 스트림을 수정하거나 이벤트 스트림의 pluggable 프로세싱을 처리해야 하는 경우, 또는 XML 프로세싱 파이프라인을 생성할 필요가 있는 경우에는 반복자 API를 사용한다.
  • 일반적으로, 환경설정과 메모리에 큰 제약이 없고 속도가 문제되지 않는 경우에는 유연성과 확장성이 높은 반복자 API를 사용한다.

SJSXP에 관한 자세한 내용은 Java Web Services Developer Pack 1.6 튜토리얼의 제 3장 Streaming API for XML(영문)을 비롯하여 Sun Java Streaming XML Parser release notes(영문)를 참조하도록 한다.

예제코드 실행하기

본 테크팁에는 예제 패키지가 첨부되어 있다. 예제 패키지의 코드에는 팁에서 다룬 코드 예제의 일부(전부는 아님)가 포함되어 있으며, 이번 테크팁에서 소개한 기법의 일부에 대한 예시 내용도 함께 다루고 있다. 예제를 설치, 실행하려면 다음의 단계를 따르도록 한다.

  1. Java WSDP Downloads 페이지에서 Java Web Services Developer Pack (Java WSDP) 1.6을 다운로드하여 설치한다(아직 설치되어 있지 않은 경우).

  2. 예제 파일을 다운로드하여 $JWSDP_HOME/sjsxp/samples directory 디렉토리에 압축을 풀면 $JWSDP_HOME/sjsxp/samples/techtip 디렉토리가 새로 생긴다. 예를 들어, Java WSDP를 Windows의 C:\Sun\JWSDP-1.6에 설치할 경우, 새로 생성된 디렉토리는 C:\Sun\JWSDP-1.6\sjsxp\samples\techtip이 되어야 한다. 압축 파일을 이 위치에 푸는 것이 중요한 이유는, Java WSDP 번들과 함께 패키지로 포함된 build.xml 파일의 영향을 받는 예제 코드용 build.xml 파일이 있기 때문이다. classpathexecute 속성은 이미 build.xml 파일에 정의되어 있으므로 해당 타깃을 컴파일하고 실행할 수 있다.

  3. ant 타깃을 ${JWSDP_HOME}/sjsxp/samples/techtip 디렉토리에서 실행한다. ant 바이너리는 ${JWSDP_HOME}/apache-ant/bin 디렉토리에 위치한다.

  4. 4. 다음 명령어를 실행하여 컴파일을 수행한다.
         ant compile
    
    이 경우 아래와 유사한 아웃풋이 표시된다
         compile:
             [mkdir] Created dir: 
             C:\Sun\jwsdp-1.6\sjsxp\samples\build\classes
             [javac] Compiling 8 source files to 
             C:\Sun\jwsdp-1.6\sjsxp\samples\build\classes
             ...
         
         BUILD SUCCESSFUL     
    
  5. 5. 다음 명령어를 실행하여 이벤트 파싱 예제를 실행하면,
         ant techtip.Parse
    
    다음과 같이 시작되는 아웃풋을 얻게 된다.
         techtip.Parse:
         [echo] Current directory is C:\Sun\jwsdp-1.6\sjsxp\samples
         [echo]  Running EventParseExample Sample.
         [java] XMLEvent is <?xml version="1.0" encoding='UTF-8' 
         [java] standalone='no'?>
         [java] Its corresponding EventType Integer is 7 : 
         [java] START_DOCUMENT
         [java] Line Number 1
         [java] ----------------------------------------
         [java] XMLEvent is <!--
         [java]     Document   : HockeyTeams.xml
         [java]     Created on : September 19, 2005, 5:11 PM
         [java]     Author     : Administrator
         [java]     Description:
         [java]         Purpose of the document follows.
         [java] -->
         [java] Its corresponding EventType Integer is 5 : COMMENT
         [java] Line Number 9
         [java] ---------------------------------------- 
    
  6. 다음 명령어를 사용하여 이벤트 아웃풋 예제를 실행한다.
         ant techtip.Output   
    
    아웃풋 예제를 실행하면 $JWSDP_HOME/sjsxp/samples/techtip 디렉토리에 SampleOutput.xml 파일이 생성되는데, 이 SampleOutput.xml 파일은 다음과 같은 형태를 띠어야 한다.
         <!--
         This document will be a simplified version of the input 
         xml File to parse
         -->
         -
            <foo:MyHockeyTeams>
         -
            <foo:HockeyTeam>
         <foo:City>San Jose</foo:City>
         <NickName>Sharks</NickName>
         <Coach>Daryl Sutter</Coach>
         testerspace
         <myxml:MarketValue year="2003">35</myxml:MarketValue>
         </foo:HockeyTeam>
         </foo:MyHockeyTeams>
    

주: 이 예제들을 제공된 build.xml과 함께 실행하지 않는 경우에는 ${JWSDP_HOME}/sjsxp/lib 디렉토리에 위치한 sjsxp.jarjsr173_api.jar를 포함시키도록 할 것. 제공된 코드를 컴파일하고 실행하려면 이 jar 파일들이 반드시 필요함.

"Java EE" 카테고리의 다른 글

2005/11/22 16:39 2005/11/22 16:39

TRACKBACK :: http://blog.sdnkorea.com/blog/trackback/307

댓글을 달아 주세요

  1. 이우철  수정/삭제  댓글쓰기

    좋은 정보 감사합니다!!

    2007/09/07 20:24
  2. 박정숙  수정/삭제  댓글쓰기

    좋은 정보 감사해요~

    2007/09/19 04:57
[로그인][오픈아이디란?]

◀ Prev 1  ... 468 469 470 471 472 473 474 475 476  ... 626  Next ▶