저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의
PDF 출판시 발생하는 한글 깨짐 문제를 처리하는 방법을 설명한다.

한글 출력시 폰트 깨짐 문제

코쿤 2.0을 이용하여 XML 문서를 PDF로 변환하여 출판하고자 하는 경우 한국의 개발자들이 부딪히는 문제가 바로 한글이 올바르게 출력되지 않는다는 점이다. 먼저 가장 간단한 예인 "Hello World!"의 우리말 버전 "안녕하세요!"를 PDF로 변환하여 출력해주는 예제를 작성해보자. 먼저 XML 데이터는 다음과 같다. (한글이 포함되어 있다.)

    <?xml version="1.0" encoding="euc-kr"?>
    
    <page>
     <title>안녕하세요?</title>
     <content>
      <para>이 페이지는 나의 첫번째 코쿤 페이지입니다!!</para>
     </content>
    </page>

위 XML 파일은 코쿤 배포판의 cocoon.war에 포함되어 있는 /samples/hello-world/content/xml/hello-page.xml 파일을 수정한 것이다.

이 XML 파일을 FO 형식으로 변환해줄 XSL 파일은 다음과 같다. 이 파일 역시 코쿤 배포판에 포함되어 있는 /samples/hello-world/style/xsl/simple-page2fo.xsl 파일의 일부 내용(폰트 부분)을 변경한 것이다.

    <?xml version="1.0" encoding="euc-kr"?>
    
    <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:fo="http://www.w3.org/1999/XSL/Format">
    
      <xsl:template match="/">
       <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
       
        <fo:layout-master-set>
         <fo:simple-page-master master-name="page"
                      page-height="29.7cm" 
                      page-width="21cm"
                      margin-top="1cm" 
                      margin-bottom="2cm" 
                      margin-left="2.5cm" 
                      margin-right="2.5cm">
           <fo:region-before extent="3cm"/>
           <fo:region-body margin-top="3cm"/>
           <fo:region-after extent="1.5cm"/>
         </fo:simple-page-master>
    
         <fo:page-sequence-master master-name="all">
           <fo:repeatable-page-master-alternatives>
         <fo:conditional-page-master-reference
                master-reference="page" page-position="first"/>
           </fo:repeatable-page-master-alternatives>
         </fo:page-sequence-master>
        </fo:layout-master-set>
    
        <fo:page-sequence master-reference="all">
          <fo:static-content flow-name="xsl-region-after">
        <fo:block text-align="center" 
                  font-size="10pt" 
                  <b>font-family="새굴림" </b>
              line-height="14pt">페이지 <fo:page-number/></fo:block>
          </fo:static-content> 
            
          <fo:flow flow-name="xsl-region-body">
            <xsl:apply-templates/>
          </fo:flow>
        </fo:page-sequence>
       </fo:root>
      </xsl:template>
    
      <xsl:template match="title">
        <fo:block font-size="36pt" <b>font-family="새굴림"</b>
            space-before.optimum="24pt"
            text-align="center"><xsl:apply-templates/></fo:block>
      </xsl:template>
    
      <xsl:template match="para">
        <fo:block font-size="12pt" <b>font-family="새굴림"</b>
            space-before.optimum="12pt"
            text-align="center"><xsl:apply-templates/></fo:block>
      </xsl:template>
    </xsl:stylesheet>

코쿤 예제에는 이미 hello-page.xml과 simple-page2fo.xsl을 사용하여 PDF로 변환해주도록 사이트맵에 설정되어 있으므로, http://host:8080/cocoon/samples/hello-world/hello.pdf를 웹브라우저의 주소란에 입력하면 PDF 변환 결과를 볼 수 있다. 한글 폰트 처리를 해주지 않은 상태에서 실행하게 되면 다음과 같이 한글 부분이 '#'으로 출력될 것이다.


이처럼 한글이 올바르게 출력되지 않는 이유는 PDF가 기본적으로 지원하는 폰트에 자주 사용하는 한글 폰트인 "굴림", "돋움", "바탕체" 등이 포함되어 있지 않기 때문이다. 따라서 PDF 출판시 한글이 올바르게 출력되도록 하기 위해서는 한글 폰트와 관련된 별도의 처리를 해 주어야 하는데, 폰트 처리를 하기 위해서는 http://xml.apache.org/fop/download.html에서 최신 FOP(Formatting Object Processor)를 다운로드 받아야 한다.

FOP 바이너리 버전을 다운로드 받아서 압축을 F:\fop-0.20.5rc2(필자는 현재 최신 버전인 0.20.5 rc2 버전을 사용하였다)에 풀었다고 가정하고서 이후 과정을 설명하도록 하겠다. 먼저 폰트 처리를 하기 전에 다음과 같은 jar 파일을 CLASSPATH에 추가하도록 하자. (참고로 버전을 나타내는 숫자는 다운로드 받은 FOP 버전에 따라 다를 수 있으며, 이 글에서는 0.20.5 rc2 버전을 기준으로 설명한다.)

  • F:\fop-0.20.5rc2\build\fop.jar
  • F:\fop-0.20.5rc2\lib\avalon-framework-cvs-20020806.jar
  • F:\fop-0.20.5rc2\lib\batik.jar
  • F:\fop-0.20.5rc2\lib\xalan-2.4.1.jar
  • F:\fop-0.20.5rc2\lib\xercesImpl-2.2.1.jar
  • F:\fop-0.20.5rc2\lib\xml-apis.jar
TTF 및 TTC 파일의 폰트 추출

c:\WINDOWS\Fonts (또는 c:\WINNT\Fonts) 디렉토리를 보면 현재 시스템에 설치되어 있는 폰트 파일이 저장되어 있는 것을 알 수 있다. 이 폰트 파일에는 확장자가 .TTF인 것과 .TTC인 것이 있는데, 확장자가 .TTF인 것은 한개의 트루타입 폰트를 저장하고 있는 폰트 파일이며 .TTC인 것은 여러 개의 트루타입 폰트를 저장하고 있는 폰트 파일이다. 이 글에서는 먼저 TTF 파일로부터 폰트 정보를 추출하는 것에 대해서 살펴보도록 하겠다.

TTF 파일로부터 폰트 메트릭스 정보 생성하기

TTF 파일로부터 폰트 정보를 추출하여 PDF 변환시 사용되는 XML 파일로 변환하려면 다음과 같이 org.apache.fop.fonts.apps.TTFReader 클래스를 사용하면 된다.

    java org.apache.fop.fonts.apps.TTFReader -fn "새굴림" C:\WINNT\Fontsngulim.ttf ngulim.xml
    
    TTF Reader v1.1.1
    
    Reading C:\WINNT\Fonts\ngulim.ttf...
    
    Number of glyphs in font: 23181
    Postscript format 3
    Glyph 65535 out of range: 23181
    Glyph 65535 out of range: 23181
    Creating xml font file...
    
    Creating CID encoded metrics
    Writing xml font file ngulim.xml...
    
    This font contains no embedding license restrictions

ngulim.ttf 파일은 '새굴림' 폰트 파일인데 위와 같이 TTFReader 클래스를 실행하면 ngulim.xml 파일에 '새굴림' 폰트에 대한 폰트 메트릭스 정보가 저장된다. 이렇게 생성된 폰트 메트릭스 파일은 FO XML 문서를 PDF로 변환할 때 폰트로 사용될 수 있게 된다.

TTC 파일로부터 폰트 메트릭스 정보 생성하기

TTC는 True Type Collection을 의미하는 것으로 TTC 폰트 파일은 한개 이상의 폰트를 저장하고 있다. TTFReader 클래스를 사용하면 TTC 폰트 파일에 저장되어 있는 폰트 목록을 볼 수 있는데, 앞에서 TTF 폰트로부터 폰트 메트릭스를 생성할 때와 똑같이 하면 된다.

    java org.apache.fop.fonts.apps.TTFReader C:\WINNT\Fonts\gulim.ttc gulim.xml
    TTF Reader v1.1.1
    
    Reading C:\WINNT\Fonts\gulim.ttc...
    
    This is a TrueType collection file with4 fonts
    Containing the following fonts:
     G u l i m     G u l i m C h e     D o t u m     D o t u m C h e    java.io.IOException: Failed to read font
            at org.apache.fop.fonts.TTFFile.readFont(TTFFile.java:372)
            at org.apache.fop.fonts.apps.TTFReader.loadTTF(TTFReader.java:178)
            at org.apache.fop.fonts.apps.TTFReader.main(TTFReader.java:140)

여기서 폰트 이름 부분을 굵게 표시하였다. (에러는 4개의 폰트 중 폰트 메트릭스를 표시할 폰트를 지정하지 않아서 발생하는 것이다.) 출력된 폰트 목록중에서 폰트를 추출하려면 TTFReader 클래스를 실행할 때 다음과 같이 -ttcname 옵션을 추가해야 한다.

    java org.apache.fop.fonts.apps.TTFReader -ttcname "Gulim"     C:\WINNT\Fonts\gulim.ttc gulim.xml
    
    TTF Reader v1.1.1
    
    Reading C:\WINNT\Fonts\gulim.ttc...
    
    This is a TrueType collection file with4 fonts
    Containing the following fonts:
     G u l i m
     G u l i m C h e
     D o t u m
     D o t u m C h e
    java.io.IOException: Failed to read font
            at org.apache.fop.fonts.TTFFile.readFont(TTFFile.java:372)
            at org.apache.fop.fonts.apps.TTFReader.loadTTF(TTFReader.java:178)
            at org.apache.fop.fonts.apps.TTFReader.main(TTFReader.java:140)

위의 결과를 보고서 약간 의아해할 것이다. -ttcname 옵션을 추가하나 하지 않나 실행 결과가 똑같기 때문이다. 이렇게 실행결과가 똑같은 이유는 -ttcname 옵션에 전달한 폰트 이름을 TTFReader 클래스가 올바르게 변환해주지 않기 때문이다. 앞에서 폰트 목록을 추출한 폰트의 이름을 보면 "Gulim"이 아니라 " G u l i m"인 것을 알 수 있는데, 이렇게 출력된 이유는 TTFReader 클래스가 유니코드로 된 폰트명 "Gulim"을 출력했기 때문이다.

따라서 명령행에서 -ttcname 옵션으로 폰트명을 전달할 때도 유니코드로 전달해야 TTFReader가 올바르게 추출할 폰트를 찾는데, 명령행에서 유니코드로 폰트명을 입력한다는 건 여간 귀찮은 일이 아니다. 그래서 필자는 다음과 같은 보조 클래스를 작성했다.

    import org.apache.fop.fonts.apps.TTFReader;
    
    public class FontReaderUtil {
    
        public static void main(String[] args) throws Exception {
            for (int i = 0 ; i < args.length ; i++) {
                if (args[i].compareTo("-ttcname") == 0) {
                    // 폰트이름을 유니코드에 해당하는 바이트 배열로 변환하여 전달
                    byte[] fontNameByte = args[i+1].getBytes("UTF-16BE");
                    String fontName = new String(fontNameByte);
                    args[i+1] = fontName;
                    System.out.println("FontName="+fontName);
                }
            }
            TTFReader.main(args);
        }
    }
    

이제 FontReaderUtil 클래스를 사용하여 gulim.ttc 파일로부터 "Gulim" 폰트에 해당하는 폰트 메트릭스를 생성할 수 있다.

    java FontReaderUtil -ttcname "Gulim" -fn "굴림" 
    C:\WINNT\Fonts\gulim.ttc gulim.xml (위의 줄과 같은 줄에 입력)
    
    FontName= G u l i m
    TTF Reader v1.1.1
    
    Reading C:\WINNT\Fonts\gulim.ttc...
    
    This is a TrueType collection file with4 fonts
    Containing the following fonts:
    *  G u l i m     G u l i m C h e
     D o t u m
     D o t u m C h e
    Number of glyphs in font: 40194
    Postscript format 3
    Glyph 65535 out of range: 40194
    Glyph 65535 out of range: 40194
    Creating xml font file...
    
    Creating CID encoded metrics
    Writing xml font file gulim.xml...
    
    This font contains no embedding license restrictions
    

폰트 설정 파일 작성 및 출판하기

폰트 파일(TTC나 TTF)로부터 폰트 메트릭스 파일을 생성했다면 fo2pdf Serializer가 사용할 폰트 설정 파일을 작성하면 된다. 예를 들어, 앞에서 작성한 폰트 메트릭스 파일들이 f:\fop-fonts 디렉토리에 저장되어 있다고 할 경우 다음과 같이 폰트 설정 파일을 작성하면 된다.

    <?xml version="1.0" encoding="euc-kr"?>
    
    <configuration>
      <fonts>
        <font metrics-file="F:/fop-fonts/ngulim.xml" 
              kerning="yes" embed-file="C:/WINNT/Fonts/NGULIM.TTF">
          <font-triplet name="새굴림" style="normal" weight="normal"/>
        </font>
        <font metrics-file="F:/fop-fonts/gulim.xml" 
              kerning="yes" embed-file="C:/WINNT/Fonts/GULIM.TTC">
          <font-triplet name="굴림" style="normal" weight="normal"/>
        </font>
        <font metrics-file="F:/fop-fonts/gulimche.xml" 
              kerning="yes" embed-file="C:/WINNT/Fonts/GULIM.TTC">
          <font-triplet name="굴림체" style="normal" weight="normal"/>
        </font>
      </fonts>
    </configuration>

위 파일을 f:\fop-fonts\ 디렉토리에 font-config.xml로 저장했다면, 이제 마지막으로 남은 일은 코쿤의 사이트맵 파일에서 fo2pdf Serializer를 명시한 부분에 다음과 같이 user-config 태그를 사용하여 폰트 설정 정보를 지정해주는 것이다.

    <map:serializer
            mime-type="application/pdf" 
            name="fo2pdf"
            src="org.apache.cocoon.serialization.FOPSerializer">
        <b><user-config src="F:/fop-fonts/font-config.xml"/></b>
    </map:serializer>

이제 남은 일은 XML을 FO 형태로 변환해주는 XSL 파일을 작성하고 사이트맵에 URL을 매핑시켜주는 것이다. XSL 파일을 작성할 때는 다음과 같이 font-family 속성을 사용하여 사용할 폰트를 지정해주면 된다. (CSS 및 FO에 대한 자세한 설명은 관련링크를 참고하기 바란다.)

    <fo:block text-align="center" font-size="10pt" <b>font-family="새굴림"</b>
        line-height="14pt">페이지 <fo:page-number/></fo:block>

이제 톰캣을 재가동한 후 다시 URL을 요청해보자. (캐시에 저장된 내용이 보여질 수 있으니 리로드해서 보도록 하자.) 그럼, 다음과 같이 한글이 올바르게 출력되는 것을 확인할 수 있을 것이다.



해결해야 할 문제점

아직 필자가 해결하지 못한 문제가 있는데, 그것은 바로 TTC 폰트집합에 포함되어 있는 글꼴을 사용하지 못한다는 것이다. FontReaderUtil 클래스를 작성하여 TTC에 포함되어 있는 폰트에 대한 폰트 메트릭스 정보를 추출하는데까지는 성공했는데, 실제로 그 폰트 메트릭스 정보를 참조해도 해당 글꼴을 사용하지 못하고 출력 결과에는 한글이 '#'으로 출력되었다. (즉, gulim.ttc 파일에 포함되어 있는 '굴림'이나 '굴림체'를 사용할 경우 한글이 '#'으로 출력된다.) 이 문제를 해결하게 되면 다음에 해결방안을 올리도록 하겠다.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 질문자 2010.08.19 17:28 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 필요에 의해 검색을 하다 블로그에 있는 내용을 보고 따라하다 중간에 오류메시지가 나서 이렇게 질문드립니다. 폰트 매트릭스 생성과정에서 java.io.IOException: Failed to read font 라는 오류메시지가 뜨면서 폰트매트릭스 생성이 되질 않고 있습니다. 글에 보면 ttc로 생성을 할경우 매트릭스 정보를 추출하시는데 성공하셨다고 하는데 전 추출 자체가 되질 않네요.. 어떤 문제가 있는건지 자세히 알고싶네요..

  2. 질문자 2010.08.19 17:38 신고  댓글주소  수정/삭제  댓글쓰기

    덫붙이자면 에러가 나는 부분은 보조 클래스를 만들고 실행을 했을때 font를 읽는데 실패 한것 같습니다...

    • 최범균 madvirus 2010.09.01 20:42 신고  댓글주소  수정/삭제

      아, 이글은 정말 오래된 글이라 지금 버전의 코쿤과 잘 맞을 지 모르겠습니다. 따로 확인해 보진 않았는데 혹시 해결하셨다면 댓글로 남겨주실 수 있을까요?

코쿤2를 이용한 웹출판의 핵심 요소인 사이트맵과 각 컴포넌트에 대해서 살펴보며, 하나의 XML 문서를 HTML과 PDF로 변환하는 에제를 작성해본다.

사이트맵과 컴포넌트

코쿤 2는 다양한 형태의 출판 방식을 지원하기 위해서 사이트맵이라는 새로운 개념을 도입하였다. 이 사이트맵의 정보는 sitemap.xmap 파일에 저장되는데, 이 파일은 웹어플리케이션 콘텍스트 디렉토리(cocoon.war를 배포하면 webapps/cocoon 디렉토리)에 위치하게 된다. sitemap.xmap 파일에는 클라이언트의 요청이 들어왔을 때 어떻게 처리할지에 대한 정보부터 사용하고자 하는 Generator, Transformer 등 코쿤을 사용하여 출판을 할 때 필요한 모든 정보가 명시되어 있다. 사이트맵 파일을 작성하는 것이 웹 출판의 모든 것이라고 생각해도 될 정도로 사이트맵 파일을 알맞게 작성하는 것이 중요하다.

sitemap.xmap 파일의 기본적인 구조는 다음과 같다. (이 글을 읽는 동안 코쿤 배포판에 기본적으로 포함되어 있는 sitemap.xmap 파일을 수시로 비교해가면서 읽어보면 내용을 이해하는 데 도움이 될 것이다.)

    <?xml version="1.0" encoding="UTF-8"?>
    <map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
        <map:components/>
        <map:views/>
        <map:resources/>
        <map:action-sets/>
        <map:pipelines/>
    </map:sitemap>    

sitemap.xmap 파일은 첫줄에서 알 수 있듯이 XML 문서이며, 루트 요소는 map:sitemap 이다. map:sitemap은 5개의 하위 요소를 포함하고 있는데, 이 글에서는 Matcher, Generator 등 코쿤을 이용한 웹 출판의 핵심 요소들을 설정하는 데 사용되는 map:components 요소와 이 핵심 정보를 조합하여 실제로 출판 방식을 지정하는 map:pipelines 요소에 대해서 살펴볼 것이다.

map:components 요소는 코쿤에서 사용될 컴포넌트를 명시할 때 사용되며, 기본적인 구성은 다음과 같다.

    <?xml version="1.0" encoding="UTF-8"?>
    <map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
        <map:components>
            <map:generators/>
            <map:transformers/>
            <map:serializers/>
            <map:readers/>
            <map:selectors/>
            <map:matchers/>
            <map:actions/>
        </map:components>
        <map:views/>
        <map:resources/>
        <map:action-sets/>
        <map:pipelines/>
    </map:sitemap>

코드에서 map:generators, map:transformers, map:serializers, map:matchers는 각각 Generator, Transformer, Serializer, Matcher를 명시하는 데 사용된다. 나머지 컴포넌트인 Reader, Selector, Action 등도 중요하나, 이번 글에서는 가장 핵심 요소에 해당하는 Generator, Transformer, Serializer, Matcher에 대해서만 살펴보기로 하고 나머지 컴포넌트에 대해서는 다음에 설명하도록 하겠다.

모든 컴포넌트의 공통 속성 및 요청 파라미터 사용유무

map:components 요소에 포함되는 모든 컴포넌트는 기본적으로 다음과 같은 두 개의 속성을 갖고 있다.

  • name - 컴포넌트를 나타내는 이름으로서 파이프라인에서 컴포넌트를 참조할 때 사용된다.
  • src - 컴포넌트를 구현한 클래스의 완전한 이름을 명시한다.
예를 들어, XSL/T를 사용하여 XML 문서를 변환할 때 사용되는 Transformer는 다음과 같이 지정한다.

    <map:transformers default="xslt">
        <map:transformer
                name="xslt"
                src="org.apache.cocoon.transformation.TraxTransformer"
                pool-grow="2" pool-max="32" pool-min="8"
                logger="sitemap.transformer.xslt">
            <use-request-parameters>false</use-request-parameters>
            <use-browser-capabilities-db>false</use-browser-capabilities-db>
            <use-deli>false</use-deli>
        </map:transformer>
    </map:transformers>

name과 src 이외에 나머지 속성들은 각 컴포넌트마다 알맞게 지정할 수 있다. 또한, 각 컴포넌트는 <use-request-parameters> 요소의 값을 true로 지정함으로써 클라이언트가 전송한 요청 파라미터를 사용할 수도 있다.

Matcher와 Generator

클라이언트의 요청을 처리하는 시발점이 되는 Matcher

Matcher는 클라이언트 요청을 사이트맵에 명시된 명령과 연관시킬 때 사용되는 코쿤의 핵심 컴포넌트이다. 먼저 Matcher를 사용하려면 사용할 Matcher를 map:components의 map:matchers 요소에 명시해야 한다. 예를 들어, 클라이언트가 요청한 URI를 사용하여 매칭 작업을 처리해주는 Matcher를 사용하려면 다음과 같이 해주면 된다.

    <map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
        <map:components>
            ...
            <map:matchers default="wildcard">
                <map:matcher
                        name="wildcard"
                        src="org.apache.cocoon.matching.WildcardURIMatcher"
                />
                <map:matcher
                        name="next-page"
                        src="org.apache.cocoon.matching.WildcardRequestParameterMatcher">
                    <map:parameter name="parameter-name" value="next-state"/>
                </map:matcher>
            </map:matchers>            ...
        </map:components>
        ...
    </map:sitemap>

위 코드는 name이 wildcard인 Matcher 컴포넌트를 지정하고 있으며, wildcard Matcher를 구현한 클래스는 WildcardURIMatcher 이다. 이 Matcher는 클라이언트의 요청 URI를 사용하여 매칭 작업을 처리해준다. 예를 들어, 다음 코드를 살펴보다.

    <map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
        <map:components>
            ...
            <map:matchers default="wildcard">
                <map:matcher
                        name="wildcard"
                        src="org.apache.cocoon.matching.WildcardURIMatcher"
                />
            </map:matchers>
            ...
        </map:components>
        ...
        <map:pipelines>
            <map:pipeline>
                <map:match pattern="body-todo.xml">
                    <map:generate type="file" src="xdocs/todo.xml"/>
                    <map:transform src="stylesheets/todo2document.xsl" label="content"/>
                    <map:transform src="stylesheets/document2html.xsl"/>
                    <map:serialize/>
                </map:match>
            </map:pipeline>
        </map:pipelines>
    </map:sitemap>

위 코드에서 map:pipeline에 속해 있는 map:match 요소를 살펴보자. map:match 요소는 map:matcher에서 명시한 Matcher 컴포넌트를 사용하여 매칭작업을 처리해주는데, 위 코드에 있는 map:match 요소는 wildcard Matcher 컴포넌트를 사용하여 매칭작업을 처리하며, 클라이언트 요청한 URI가 body-todo.xml인 경우 해당하는 작업을 한다. 즉, 클라이언트가 요청한 URI가 body-todo.xml일 경우 map:generate를 사용하여 XML 문서를 생성하고, map:transform을 통해서 두번의 변환 과정을 거치며, 마지막으로 map:serialize를 통해서 실제로 출력되는 문서를 생성한다.

위의 map:match 태그는 실제로 다음과 같이 type 속성의 값을 wildcard로 준것과 동일하다.

    <map:match type="wildcar" pattern="body-todo.xml">

map:match 요소의 type 속성을 사용해서 Matcher 컴포넌트를 지정해주는데, 만약 type 속성을 지정하지 않으면, map:matchers 요소의 default 속성에 명시한 Matcher 컴포넌트가 기본적으로 사용된다. 즉, 앞의 코드에서 <map:matchers default="wildcard">와 같이 기본으로 사용될 Matcher 컴포넌트를 wildcard로 지정했기 때문에, map:match 요소가 type 속성을 갖고 있지 않을 경우 wildcard Matcher 컴포넌트가 사용되는 것이다. 기본 컴포넌트를 사용하는 것은 map:matchers를 포함한 모든 컴포넌트에 대해서 같은 방식이 적용된다.

Matcher의 매핑 방법: 와일드카드와 정규표현식

앞에서 살펴본 map:match 요소는 정확하게 클라이언트가 요청한 URI가 정확하게 "body-todo.xml"인 경우에만 map:match 요소 내부에 있는 map:generate, map:transform 등을 실행한다. 하지만, 이처럼 정확하게 일치하는 경우가 아니라 특정 패턴을 지닌 URI를 같은 방법으로 처리하고 싶은 경우가 있을 것이다. 예를 들어, 클라이언트가 요청한 URI가 /publishing/doc1.pdf 이거나 /publishing/doc2.pdf 이거나 상관없이 /publishing/로 시작하고 확장자가 .pdf로 끝나는 URI일 때 같은 처리를 해주고 싶을 때가 있다.

코쿤이 기본적으로 제공하는 (앞에서 예제에 표시했었던 WildcardURIMatcher를 포함한 대부분의) Matcher들은 클라이언트의 요청 URI를 패턴과 비교해서 매칭시키는 기능을 제공하고 있다. 이때 패턴은 와일드카드(Wildcard)와 정규표현식(Regular Expression)의 조합으로 표시하며 pattern 속성을 사용하여 지정한다.

와일드카드와 관련해서 적용되는 규칙은 다음과 같다.

  • 별표('*')는 0개 또는 그 이상의 글자수와 일치하며 경로 구분 글자인 '/'가 나올때까지 적용된다. 예를 들어 클라이언트가 요청한 URI가 '/cocoon/docs/index.html'이고 패턴을 '/*/*/index.html'로 지정했다면, 첫번째 '*'는 'cocoon'에 해당하고 두번째 '*'는 'docs'에 해당된다. 만약 패턴이 '/*/*.index.html'일 경우 '/cocoon/docs/index.html'은 매칭되지 않는다.

  • 연속된 별포('**')는 0개 또는 그 이상의 글자수와 일치하며, 이때 경로 구분 글자까지 포함한다. 따라서 패턴이 '/**/*.html'일 경우, 요청 URI '/cocoon/docs/index.html'에서 처음의 연속된 별표인 '**'에는 'cocoon/docs'가 매칭되며 두번째 '*'는 'index'와 매칭된다.

  • 역슬래시('\')를 사용하여 별표 자체와 역슬래시 자체를 표시할 수 있다. 별포는 '\*' 문장을 사용하여 표시할 수 있으며, 역슬래시는 '\\' 문장을 사용하여 표시한다. 예를 들어, 패턴이 '**/a-\*-is-born.html'일 경우 'some/a-*-is-born.html'이나 '/a-*-is-born.html'은 매칭되지만, 'some/a-any-is-born.html'은 매칭되지 않는다.
정규표현식에 대한 설명은 http://jakarta.apache.org/regexp/apidocs/org/apache/regexp/RE.html를 참고하기 바란다.

XML 문서를 생성해주는 Generator

Matcher가 클라이언트의 요청에 따라 알맞은 처리 흐름을 선택하는 것이라면, Generator는 클라이언트의 요청을 처리하는 첫번째 단계가 된다. 클라이언트로부터 요청이 들어오면 map:match 요소는 Matcher를 사용하여 매칭되는 지 검사하며, 매칭될 경우 map:generate 요소를 사용하여 클라이언트의 요청과 관련된 XML 문서를 생성하게 된다. 이때, map:generate 요소는 XML 문서를 생성하기 위해서 Generator 컴포넌트를 사용하는데, 사용될 Generator 컴포넌트는 map:componenets 요소 안에 다음과 같이 선언하게 된다.

    <map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
        <map:components>
            <map:generators default="file">
                <map:generator
                        name="file"
                        src="org.apache.cocoon.generation.FileGenerator" />
                <map:generator
                        name="serverpages"
                        src="org.apache.cocoon.generation.ServerPagesGenerator"
                        pool-grow="2" pool-max="32" pool-min="4" />
            </map:generators>            ...
        </map:components>
        ...
    </map:sitemap>

위 코드에서 FileGenerator는 파일이나 네트워크자원(URL)으로부터 XML 데이터를 읽어와 XML 데이터를 생성해주는 기본 Generator 컴포넌트로서 가장 많이 사용되는 Generator이다.

클라이언트의 요청을 처리하는 파이프라인에서 map:match를 통해서 매칭이 이루어지면 가장 먼저 처리되는 것은 클라이언트 요청과 관련된 XML 데이터(데이터는 SAX 이벤트로 만들어진다)를 만드는 것인데, map:generate 요소가 XML 데이터를 생성한다. map:generate는 map:matcher와 마찬가지로 type 속성에 값을 지정하여 사용할 Generator 컴포넌트를 지정할 수 있으며, type 속성을 지정하지 않을 경우 map:generators의 default 속성에 명시한 Generator 컴포넌트가 사용된다.

    <map:match pattern="body-todo.xml">
        <map:generate type="file" src="xdocs/todo.xml"/>
        <map:transform src="stylesheets/document2html.xsl"/>
        <map:serialize/>
    </map:match>

위 코드와 같이 파이프라인에 매칭을 적용했다고 하자. 이 경우, 클라이언트가 body-todo.xml을 요청하면 map:match를 통해서 매칭이 이루어지며 map:generate가 실행된다. map:generate는 file Generator 컴포넌트를 사용하여 xdocs/todo.xml 파일을 읽어와 XML 데이터를 생성한다. 이렇게 해서 (SAX 이벤트로) 생성된 XML 데이터는 파이프라인의 다음 단계인 map:transform에 전달된다. map:transform은 이름에서 알 수 있듯이 map:generate로부터 전달받은 XML 데이터를 또다른 XML 데이터로 변환해주는 기능을 제공한다.

코쿤이 다양한 Generator를 제공하고 있기는 하지만, 웹 출판에서 비교적 많이 쓰일만한 Generator는 다음과 같다.

Name Class 설명
file FileGenerator 로컬 파일 시스템이나 URL 로부터 XML 문서를 읽어온다. src 속성을 사용하여 읽어올 XML 문서를 지정한다.
예) <map:generate src="document.xml" type="file"/>

html HTMLGenerator 로컬 파일 시스템이나 URL 로부터 HTML 문서를 읽어와 XHTML로 생성한다. src 속성을 사용하여 읽어올 HTML 문서를 지정한다.
예) <map:generate src="index.html" type="html"/>

directory DirectoryGenerator 디렉토리 목록을 XML 문서로 생성한다. 생성된 XML 문서는 다음과 같은 형태를 띈다.
 <dir:directory
    xmlns:dir="http://apache.org/cocoon/directory/2.0"
    name="stylesheets"
    lastModified="1019666489000"
    date="24.04.02 18:41"
    size="461"
    sort="name"
    reverse="false"
    requested="true">
    <dir:directory name="sites"
        lastModified="1019666489000"
        date="24.04.02 18:41" size="118"/>
    <dir:file name="dynamic-page2html.xsl"
        lastModified="1019666489000"
        date="24.04.02 18:41" size="1832"/>
    <dir:file name="simple-xml2html.xsl"
        lastModified="1019666489000"
        date="24.04.02 18:41" size="12676"/>
 </dir:directory>
        

생성된 XML 문서의 루트 노드는 directory이며, directory 노드와 file 노드를 사용하여 디렉토리와 파일을 표시한다. directory 노드와 file 노드는 다음과 같은 속성을 갖는다.
  • name : 디렉토리나 파일의 이름
  • lastModified : 최근 수정일. 1970년 1월 1일 0시 이후로 지난 시간(단위는 1/1000초)으로 표시한다.
  • size : 파일의 크기
  • date (옵션) : 최근 수정일을 숫자가 아닌 인간이 읽을 수 있는 형태로 표시.
다음과 같은 파라미터를 사용하여 원하는 형태로 디렉토리 목록을 생성할 수 있다.
  • depth (옵션) : 디렉토리 구조의 깊이를 지정한다. 기본값은 1이다. 1인 경우 지정한 디렉토리에 포함된 목록만 출력되고, 1 이상일 경우 알맞게 하위 디렉토리까지 목록을 생성한다.
  • dateFormat (옵션) : date 속성을 위한 양식을 지정한다. 이 양식은 java.text.SimpleDateFormat에서 사용되는 패턴을 사용한다.
  • root (옵션) : 루트 패턴. 정규표현식을 값으로 갖는다.
  • include (옵션) : 결과 목록에 포함할 디렉토리 또는 이름을 정규표현식으로 지정한다.
  • exclude (옵션) : 결과 목록에서 제외시킬 디렉토리 또는 이름을 정규표현식으로 지정한다.
  • sort (옵션) : 파일과 디렉토리 목록의 정렬 기준을 지정한다. 기본값은 시스템에 따라 다르다. 사용가능한 값은 "name", "size", "time", and "directory" 이다. "directory"와 "name"은 이름을 사용하여 정렬하는 것은 같지만, "directory"로 값을 지정할 경우 목록에서 디렉토리가 먼저 위치하게 된다.
  • reverse (옵션) : "true"일 경우 역순으로 목록을 생성한다.
imagedirectory ImageDirectoryGenerator DirectoryGenerator와 같은 기능을 제공하며, 추가적으로 파일이 이미지인지의 여부를 확인해준다. 이미지일 경우 생성된 XML 결과의 dir:file 요소에 width 속성과 height 속성이 추가된다. 사용가능한 파라미터는 동일하다.

* 표에 나열한 모든 Generator는 org.apache.cocoon.generation 패키지에 속해 있음

Transformer와 Serializer

Matcher를 사용하여 클라이언트의 요청과 매칭되는 파이프라인을 선택하고 Generator를 사용하여 XML 문서를 생성했다면, 이제 남은 일은 XML 데이터를 클라이언트에 알맞게 보여주어야 한다는 점이다. 클라이언트가 웹브라우저로서 HTML로 결과를 보기를 원한다면 XML 문서를 HTML로 변환해주어야 하며, 클라이언트가 Acrobat Reader라면 PDF로 변환해주어야 할 것이다. 이처럼 map:generate를 통해서 생성된 XML 데이터를 변환한 후 클라이언트에 전송해주는 작업은 Transformer와 Serializer를 통해서 처리된다.

XML 문서를 변환시켜주는 Transformer

Transformer는 Generator가 생성한 XML 데이터를 클라이언트의 요청에 알맞게 또 다른 XML 데이터로 변환해준다. Transformer 컴포넌트를 사용하기 위해서는 사이트맵의 다른 컴포넌트와 마찬가지로 먼저 map:components 안에 사용할 Transformer를 명시해주어야 한다. 다음은 Transformer 컴포넌트를 사이트맵에 명시한 예이다.

    <map:transformers default="xslt">
        <map:transformer
                name="xslt"
                src="org.apache.cocoon.transformation.TraxTransformer">
            <use-request-parameters>false</use-request-parameters>
            <use-browser-capabilities-db>false</use-browser-capabilities-db>
            <use-deli>false</use-deli>
        </map:transformer>        
        <map:transformer
                name="log"
                src="org.apache.cocoon.transformation.LogTransformer"/>
        
        <map:transformer
                name="encodeURL"
                src="org.apache.cocoon.transformation.EncodeURLTransformer"/>
    </map:transformers>

코쿤 2.0은 다양한 Transformer를 제공하고 있는데, 가장 많이 사용되는 Transformer는 XSLT Transformer이다. XSLT Transformer가 많이 사용되는 이유는 XML 데이터를 다양한 형식으로 변환할 수 있기 때문이다. 예를 들어, XML 데이터(주로 컨텐트)와 스타일시트(XSL) 파일을 사용하여 하나의 XML 문서를 HTML, WAP, PDF, PS 등 다양한 형태로 출력할 수 있다. XSLT Transformer를 사용하여 하나의 XML 문서를 다양한 형태로 출판하는 예제는 뒤에서 살펴볼 것이다.

Serializer를 사용한 출판

Matcher를 통해서 사용할 파이프라인이 선택되고, Generator가 생성한 XML 데이터를 Transformer를 통해서 알맞게 변환시키면, 이제 마지막으로 클라이언트가 볼 수 있도록 출판하는 일만 남게 된다. 이 출판 작업은 Serializer를 통해서 이루어진다. Serializer는 Generator와 Transformer를 거쳐 생성된 XML 데이터(SAX 이벤트)를 클라이언트가 사용할 수 있는 바이너리 스트림이나 캐릭터 스트림으로 변환하여 클라이언트에 전송한다.

Serializer도 다른 컴포넌트와 마찬가지로 먼저 사용할 Serializer를 map:serializers에 명시해주어야 한다. 다음 코드는 몇몇 Serializer를 지정해준 예를 보여주고 있다.

    <map:serializers default="html">
        <map:serializer
                name="xml"
                src="org.apache.cocoon.serialization.XMLSerializer"
                mime-type="text/xml" />
    
        <map:serializer
                name="html"
                src="org.apache.cocoon.serialization.HTMLSerializer"
                mime-type="text/html" pool-grow="4" pool-max="32" pool-min="4" >
            <buffer-size>1024</buffer-size>
        </map:serializer>
    
        <map:serializer 
                name="text"
                src="org.apache.cocoon.serialization.TextSerializer"
                mime-type="text/text"/>
    
        <map:serializer 
                name="fo2pdf"
                src="org.apache.cocoon.serialization.FOPSerializer"
                mime-type="application/pdf" >
            <user-config src="F:/fop-fonts/font-config.xml"/>
        </map:serializer>
    </map:serializers>

위와 같이 사용할 Serializer를 선택했다면, 파이프라인에서 다음과 같이 map:serialize를 사용하여 알맞게 XML 데이터를 출판하면 된다.

    <map:match pattern="hello.pdf">
        <map:generate src="content/xml/hello-page.xml"/>
        <map:transform src="style/xsl/simple-page2fo.xsl"/>
        &lt;map:serialize type="fo2pdf"/>
    </map:match>

위 코드는 클라이언트가 요청한 URI가 hello.pdf인 경우, hello-page.xml 파일로부터 XML 데이터를 생성하고, simple-page2fo.xsl을 사용하여 변환한 후, 마지막으로 fo2pdf Serializer를 사용하여 PDF로 출판한다.

Serializer의 종류는 여러가지가 있는데, 그 중에서 많이 사용될 만한 Serializer를 정리하면 다음 표와 같다.

Name Class 설명
html HtmlSerializer SAX 이벤트를 HTML로 출판한다.
xml XMLSerializer SAX 이벤트를 XML로 출판한다.
text TextSerializer SAX 이벤트를 텍스트 데이터로 출판한다.
fo2pdf FOPSerializer FOP 프로젝트를 사용하여 SAX 이벤트를 PDF로 변환한다.
wml XMLSerializer SAX 이벤트를 WML로 출판한다.
* 표에 나열한 모든 Generator는 org.apache.cocoon.serialization 패키지에 속해 있음

[위 표에 나열한 것 이외에 SVG(Scalable Vector Graphic)과 관련된 다양한 Serializer가 존재하니, 관심있는 독자는 코쿤 홈페이지(http://xml.apache.org./cocoon)를 방문해보라.]

파이프라인을 이용한 컴포넌트 조합 및 출판 예제

자, 이제 파이프라인에 대해서 살펴보도록 하자. 앞에서 살펴본 네 가지 기본 컴포넌트를 사이트맵 파일에 명시했다면, 이제 남은 것은 각각의 컴포넌트를 알맞게 조립하여 파이프라인을 만드는 것이다. 파이프라인은 클라이언트의 요청을 어떻게 처리할지 명시해 놓은 것이다. 다음은 파이프라인의 한 예이다.

    <map:pipelines>    
        <map:pipeline>
            <map:match pattern="**.source">
                <map:generate src="cocoon:/{1}" />
                <map:transform src="style/xsl/simple-xml2html.xsl"/>
                <map:serialize/>
            </map:match>
        </map:pipeline>        
        <map:pipeline>
            <map:match pattern="hello.html">
                <map:generate src="content/xml/hello-page.xml"/>
                <map:transform src="style/xsl/simple-page2html.xsl"/>
                <map:serialize type="html"/>
            </map:match>
            
            <map:match pattern="hello.txt">
                <map:generate src="content/xml/hello-page.xml"/>
                <map:serialize type="text"/>
            </map:match>
       
            <map:match pattern="hello.pdf">
                <map:generate src="content/xml/hello-page.xml"/>
                <map:transform src="style/xsl/simple-page2fo.xsl"/>
                <map:serialize type="fo2pdf"/>
            </map:match>
        </map:pipeline>        
    </map:pipelines>

하나의 사이트맵 파일에는 하나의 map:pipelines 요소를 가질 수 있으며, map:piplines는 위 코드에서 볼 수 있듯이 여러개의 map:pipeline 요소를 가질 수 있다. 하나의 map:pipeline는 하나의 파이프라인을 나타낸다. 하나의 파이프라인은 다음과 같은 형태를 취한다.

    <map:pipeline>
        <map:match ... >
            <map:generate type=".." src="..." />
            <map:transform ... />
            <map:serialize ... />
        </map:match>

        <map:match ... >
            <map:generate type=".." src="..." />
            <map:transform ... />
            <map:serialize ... />
        </map:match>
        
        ...
    </map:pipeline>

하나의 파이프라인은 여러 개의 map:match를 가질 수 있다. 따라서 일반적으로 관련된 URI를 처리하는 map:match를 하나의 map:pipeline에 함께 묶어서 관리하는 것이 편리하다. 각각의 map:match 요소는 map:generate로 시작해서 map:serialize로 끝을 맺는다. 즉, 클라이언트의 요청이 map:match와 매칭되면, map:generate로 XML SAX 이벤트를 생성해서 map:serialize로 클라이언트에 출판을 하는 것이 파이프라인의 기본 흐름이다. 이때 map:transform을 사용하여 map:generate가 생성한 XML 데이터를 알맞게 형변환할 수도 있다. (보통 XSLT Transformer를 사용하여 XML 원본 데이터를 map:serialize가 출판하려는 형태로 알맞게 변환한다.

map:match 순서는 중요하다. 예를 들어, 파이프라인에 map:match가 세개 있는데 클라이언트의 요청이 세개와 모두 매칭된다고 해 보자. 이 경우, 첫번째 매칭되는 map:match만 적용되며 나머지 매칭되는 map:match는 적용되지 않는다. 따라서, 파이프라인을 작성할 때는 map:match의 순서도 알맞게 위치시켜야 한다.

HTML, PDF로의 출판 예제

사이트맵은 사실 조합에 불과하다. 사이트 맵의 앞 부분에서 명시한 컴포넌트와 자원, 액션 등을 사용해서 클라이언트의 요청을 알맞게 처리할 수 있도록 조합하는 것에 불과한 것이다. 실제로 간단한 XML 데이터를 HTML과 PDF로 변환하는 예제를 만들어봄으로써 컴포넌트를 조립하는 과정을 살펴보도록 하겠다.

먼저 원본 데이터에 해당하는 XML 문서인 chapter.xml은 다음과 같다.

    <?xml version="1.0" encoding="euc-kr"?>
    
    <book>
        <cover>
            <title>JSP Professioanl Advanced</title>
            <author>최범균</author>
        </cover>
        
        <contents>
            <chapter title="들어가며" number="1">
                <paragraph>몇해전부터 대형 웹 어플리케이션을
                개발하는 데 주로 사용되는 기술이 자바 기반으로
                변화되었다. EJB, 서블릿, JSP 등 기반 기술들이 ... (중략)
                </paragraph>
    
                <paragraph>또한, Struct과 같은 프레임워크가
                개발되면서 JSP의 UI로서의 기능이 강화되고 있다.
                ... (중략)
                </paragraph>
            </chapter>
        </contents>
    </book>
    

이 절에서는 위 XML 문서를 HTML, PDF, TXT의 3가지 형태로 변환하여 출력하는 예제를 작성해볼 것이다. 먼저 테스트 환경은 다음과 같다.

  - Jakarta Tomcat 4.1.10 (JDK1.4용 LE 버전)
  - Cocoon 2.0.4 (JDK 1.4 버전)
  - 톰캣 설치 디렉토리 : F:\jakarta-tomcat-4.1.10-LE-jdk14

관련 자료에서 test.war를 다운로드 받았다면, test.war 파일을 톰캣의 webapps 디렉토리에 복사한 다음 톰캣을 재시작하자. 그러면 test.war가 자동으로 배포되면서 예제를 수행해볼 수 있을 것이다. 참고로 test.war는 cocoon.war 파일의 압축을 풀었을 때 생성되는 코쿤 관련 jar 파일을 그대로 포함하고 있다.

여기서 작성할 에제는 사용자가 http://locahost/test/chapter.html, chapter.pdf, chapter.txt를 요청할 경우 이를 알맞게 변환하여 출력해준다. 이 URL을 요청할 때 코쿤이 처리하도록 하려면 WEB-INF/web.xml 파일을 다음과 같이 작성해주어야 한다.

    <?xml version="1.0" encoding="euc-kr"?>
    
    <!DOCTYPE web-app
        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
        "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
    
    <web-app>
      <servlet>
        <servlet-name>Cocoon2</servlet-name>
        <display-name>Cocoon2</display-name>
        <description>The main Cocoon2 servlet</description>
    
        <servlet-class>
            org.apache.cocoon.servlet.CocoonServlet
        </servlet-class>        
        <!--
            나머지 부분은 에제 소스 코드 참고
        -->
        ...
        
        <load-on-startup>1</load-on-startup>
      </servlet>
      
      <servlet-mapping>
        <servlet-name>Cocoon2</servlet-name>
        <url-pattern>*.pdf</url-pattern>
      </servlet-mapping>    
      <servlet-mapping>
        <servlet-name>Cocoon2</servlet-name>
        <url-pattern>*.html</url-pattern>
      </servlet-mapping>    
      <servlet-mapping>
        <servlet-name>Cocoon2</servlet-name>
        <url-pattern>*.txt</url-pattern>
      </servlet-mapping>    
      <mime-mapping>
        <extension>css</extension>
        <mime-type>text/css</mime-type>
      </mime-mapping>
    
    </web-app>

위의 web.xml 파일을 보면 클라이언트의 웹출판 요청을 받게 될 CocoonServlet을 지정한 후, serlvet-mapping을 사용하여 클라이언트가 요청한 URI가 *.pdf, *.html, 그리고 *.txt인 경우 CocoonSerlvet이 처리하도록 매핑하였다.

web.xml 파일에 서블릿과 관련된 매핑을 추가했다면, 이제 사이트맵을 작성해나가면 된다. (예제 사이트 맵은 F:\jakarta-tomcat-4.1.10-LE-jdk14\webapps\test 디렉토리에 위치하게 된다.) 예제에서 사용한 사이트맵은 다음과 같다.

    <?xml version="1.0" encoding="euc-kr"?>
    
    <map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
        <map:components>
        
            <map:generators default="file">
                <map:generator name="file"
 
                        src="org.apache.cocoon.generation.FileGenerator"/>
            </map:generators>
    
            <map:transformers default="xslt">
                <map:transformer name="xslt"
 
                        src="org.apache.cocoon.transformation.TraxTransformer">
                    <use-request-parameters>false</use-request-parameters>
                    <use-browser-capabilities-db>false</use-browser-capabilities-db>
                    <use-deli>false</use-deli>
                </map:transformer>
            </map:transformers>
            
            <map:serializers default="html">
                <map:serializer name="html" mime-type="text/html"
                        src="org.apache.cocoon.serialization.HTMLSerializer">
                    <encoding>euc-kr</encoding>
                    <buffer-size>1024</buffer-size>
                </map:serializer>
                <map:serializer name="text" mime-type="text/plain"
                        src="org.apache.cocoon.serialization.TextSerializer"/>
                <map:serializer name="fo2pdf" mime-type="application/pdf"
 
                        src="org.apache.cocoon.serialization.FOPSerializer">
                    <user-config
 
        src="F:/jakarta-tomcat-4.1.10-LE-jdk14/webapps/test/WEB-INF/fop-fonts"/>
                </map:serializer>
                <map:serializer name="xml" mime-type="text/xml"
                        src="org.apache.cocoon.serialization.XMLSerializer">
                    <encoding>euc-kr</encoding>
                </map:serializer>
             </map:serializers>
    
            <map:matchers default="wildcard">
                <map:matcher name="wildcard" 
                        src="org.apache.cocoon.matching.WildcardURIMatcher"/>
            </map:matchers>
        
        </map:components>
        
        <map:pipelines>
            <map:pipeline>
                <map:match pattern="chapter.pdf">
                    <map:generate src="chapter.xml" />
                    <map:transform src="xsl/xml2fo.xsl" />
                    <map:serialize type="fo2pdf" />
                </map:match>
                
                <map:match pattern="chapter.html">
                    <map:generate src="chapter.xml" />
                    <map:transform src="xsl/xml2html.xsl" />
                    <map:serialize/>
                </map:match>
                
                <map:match pattern="chapter.txt">
                    <map:generate src="chapter.xml" />
                    <map:serialize type="text"/>
                </map:match>
                
                <map:match pattern="chapter.xml">
                    <map:generate src="chapter.xml" />
                    <map:serialize type="xml"/>
                <map:transform src="xsl/xml2html.xsl" />
                    <map:serialize/>
               

가장 먼저 사용할 컴포넌트를 명시하였다. Generator는 파일로부터 XML 데이터를 생성하는 FileGenerator를 사용하고, Transformer는 XSLT 변환을 해주는 TraxTransformer를 사용한다. Serializer는 XML 문서를 HTML, TXT, PDF로 변환해주므로 html, text, fo2pdf Transformer를 사용한다. html Serializer를 명시한 부분을 보면 <encoding> 태그를 사용하고 있는데, 이 태그를 사용하여 출판한 HTML 문서의 캐릭터셋을 지정할 수 있다. 위 예제에서는 한글을 출판하게 되므로 euc-kr로 지정하였다. fo2pdf Serializer가 PDF로 출판해주는 기능을 제공하는데, fo2pdf를 명시한 부분을 보면 <user-config> 태그를 사용한 것을 알 수 있다. 이때 user-config 태그의 src 속성은 PDF로 변환시 사용되는 폰트에 대한 담고 있는 파일의 경로를 값으로 갖는다. (이 속성을 사용함으로써 한글을 올바르게 출력할 수 있다. 이에 대한 내용은 다음글에서 살펴보도록 하자.)

기본적인 컴포넌트 설정을 마친 후에는 컴포넌트를 알맞게 조합해서 파이프라인을 생성하면 된다. 위 코드의 파이프라인을 보면 클라이언트가 요청한 URL인 chapter.html, chapter.txt, chapter.pdf에 대해서 각각 알맞은 처리를 해주는 것을 알 수 있다. PDF와 HTML로 출판할 때에는 먼저 XML 문서를 알맞게 변환해주는데, 해당하는 파이프라인의 map:transform 태그를 보면 src 속성에서 변환할 때 사용되는 XSL 문서를 명시한 것을 알 수 있다.

위와 같이 sitemap.xmap 파일을 작성했다면 남은 일은 XML을 HTML 그리고 XML을 FO(Formatting object) 형태로 변환할 때 사용되는 XSL 문서를 작성하는 일이 남았다. 이 두 파일은 예제 코드의 test/xsl/ 디렉토리에 존재하니 참고하기 바란다.

이제 남은 것은? 바로 테스트를 해 보는 것이다. 웹브라우저에서 http://localhost/test/chapter.html, chapter.txt, chapter.pdf를 입력해보면 차례대로 다음과 같은 결과 화면이 출력될 것이다.

그림1 XML->HTML로 변환한 결과 화면

그림2 XML->TEXT로 변환한 결과 화면

그림3 XML->PDF로 변환한 결과 화면

예제만 보더라도 코쿤 프레임워크를 사용하여 하나의 XML 문서를 매우 쉽게 다양한 양식으로 출판할 수 있다는 사실을 알 수 있다.

결론

이 글에서는 코쿤을 사용하여 웹 출판을 하는 데 필요한 기본 컴포넌트인 Matcher, Generator, Transformer, Serializer에 대해서 살펴보았으며, 사이트맵의 파이프라인을 사용하여 이들 컴포넌트를 조합하여 알맞게 XML 데이터를 출판하는 방법에 대해서도 살펴보았다. 또한, 예제를 통해서 어떻게 이들 컴포넌트를 사용하고 실제 파이프라인이 어떻게 동작하는 지 알 수 있었을 것이다. 또한, 예제를 통해서 XML 문서를 다양한 양식으로 손쉽게 출판할 수 있다는 것도 알게 되었다.

다음 3번째 글에서는 코쿤을 사용하여 PDF를 생성할 때 문제가 되는 한글처리에 대해서 살펴볼 것이다. 한글이 '#'이나 '?' 등으로 나와서 고생하고 있는 개발자들에게 많은 도움이 될테니 반드시 읽어보기 바란다.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요

코쿤이 무엇인지 살펴보고 설치방법을 설명한다.

코쿤은 무엇인가?

XML 관련 기술중에서 필자가 관심을 갖는 것 중의 하나가 웹 출판 프레임워크인데, 다양한 웹 출판 프레임워크 중에서도 특히 많은 관심을 갖고 있는 것이 바로 아파치의 XML 프로젝트에서 개발하고 있는 코쿤(Cocoon)이다. 코쿤은 웹 출판 프레임워크에 필요한 다양한 기능을 제공하고 있으며, 또한 지속된 버전업을 통해서 안전성면에서도 다른 프레임워크를 앞서가고 있다. 이번 글에서는 코쿤에 대한 개략적인 내용에 대해서 살펴볼 것이며, 계속해서 3-4 차례에 걸쳐서 코쿤을 사용하는 방법에 대해서 살펴보도록 할 것이다.

아파치 코쿤은 서버 어플리케이션에서 XML과 XSLT 기술의 사용방법을 한단계 끌어올려준 웹 출판 프레임워크이다. 파이프라인 처리를 사용하여 성능과 확장성에 주안점을 두어 설계된 코쿤은 컨텐트, 로직, 스타일을 분리하여 유연한 웹 출판 환경을 제공하고 있다. 중앙집중화된 설정 시스템과 정교한 캐싱 기능이 이를 가능하게 해주며, 이는 견고한 XML 서버 어플리케이션을 개발하고 배포하고 유지하는 데 도움을 준다.

코쿤은 파일시스템, RDBMS, LDAP, 네이티브 XML 데이터베이스, 네트워크 기반의 데이터 자원등 거의 모든 종류의 데이터 자원을 사용할 수 있다. 또한 코쿤은 하나의 컨텐트를 HTML, WML, PDF, SVG, RTF와 같이 서로 다른 포맷으로 전송할 수 있는 기능을 제공한다. 코쿤은 현재 서블릿으로 실행할 수 있으며 또한 명령행을 통해서도 실행할 수 있다. 코쿤은 추상화된 환경에 기반하여 설계되었기 때문에, 여러분은 요구사항에 알맞게 자신만의 코쿤 실행 환경을 구현할 수 있다.

코쿤의 개발 계기

지금까지 웹을 유지해온 HTML은 구조적인 한계를 갖고 있다. 예를 들어, Apache Jakarta 프로젝트에 관심이 있는 독자들은 한두번쯤은 http://jakarta.apache.org 사이트를 방문해보았을 것이다. 이 사이트를 보면 누구라도 쉽게 사이트가 상단부분, 왼쪽편 메뉴, 뉴스, 상태로 구성되어 있다는 것을 알 수 있을 것이다. 하지만, 사이트의 HTML 소스 코드를 보면 무수히 많은 TABLE 태그와 중첩된 태그들 때문에 정확하게 각 부분을 구분하는 것이 쉽지 않을 것이다.

이렇게 HTML 코드만으로 데이터의 각 영역을 구분하는 것이 어려운 이유는 HTML 모델이 단순히 화면을 보여주는 데 초점을 맞추고 있기 때문이다. 즉, HTML 문서는 문서의 구조나 데이터의 관계와 같이 데이터의 구조에 대한 정보를 포함하고 있지 않다. HTML 문서를 분석해서 문서 내에 있는 각 데이터 간의 관계를 추출해낸다는 것은 거의 불가능한 것이다. 이는 다시 말하면 HTML 문서로부터 어떤 '의미'를 분석해낸다는 것이 불가능하다는 것이다.

이렇듯 표현을 중심으로 설계된 HTML과 달리 XML 규약은 애초부터 데이터를 표현하는 데 초점을 맞춰 개발되었다. XML 문서는 XML 규약에 정의된 규칙을 반드시 따라야 하며, 트리 구조로 쉽게 표현할 수 있다는 장점을 갖고 있다. XML 문서는 트리 구조를 사용하여 데이터의 구조를 원하는데로 구성할 수 있고, 표현과 관계없이 데이터만을 저장하고 있기 때문에, 출판 시스템에서 출력할 데이터를 체계적으로 저장할 수 있다. 예를 들어, 웹사이트를 나타내는 XML 문서는 다음과 같이 표현할 수 있을 것이다.

    <?xml version="1.0" encoding="euc-kr"?>
    
    <!DOCTYPE ... >
    
    <site>
        <title>한국의 미</title>
        <top>
            <logo-image>km.gif"</logo-image>
            <top-menus>
            
            </top-menus>
        </top>
        
        <leftside>
            <menus>
                <menu-item label='한국에 대하여' link='/about/korea.html' />
                <menu-item label='2002 월드컵' link='/about/2002.html' />
            </menus>
        </leftside>
        
        <content>
        
        </content>
        
        <copyright>
            &copyright;
        </copyright>
    </site>

위 문서는 HTML과는 확연한 차이점을 나타내고 있다. 표현과 관련된 어떤 데이터도 포함하고 있지 않은 것을 알 수 있다. 위 문서의 의미는 누구나 어렵지 않게 분석해낼 수 있다. 그렇다면 이러한 XML 문서와 코쿤이 무슨 관계가 있을까? 코쿤은 바로 이 XML을 데이터로 사용하여 컨텐트를 다양한 방식으로 출판할 수 있도록 하고 있다. 이와 관련된 내용은 앞으로 글을 진행해나가면서 살펴볼 것이다.

역할 분담을 염두한 설계

코쿤이 갖는 가장 중요한 특징은 SoC(Separation of Concerns; 작업의 분리) 기반으로 설계했다는 점이다. SoC는 여러분들이 항상 고민해오던 것 중의 하나이다. (모든 사람이 같은 것은 아니면, 모든 사람이 같은 능력으로 같은 일을 수행할 수 있는 것도 아니다.) 같은 기술을 가진 사람을 같은 작업 그룹에 포함되도록 사람들을 분리함으로써 생산성을 증가시키고 관리 비용을 줄일 수 있다는 사실이 관찰되었는데, 물론, 이러한 효과는 각 그룹의 업무가 겹치지 않고 각 그룹의 능력과 업무를 명확하게 정의한 경우에만 나타난다.

코쿤은 웹 출판 시스템에 '계약 피라미드'를 적용하였다. 이 계약 피라미드는 다음 그림에서 볼 수 있듯이, 네 가지 주요 업무 영역과 각 업무 영역 사이의 5가지 관계로 표시된다.

* 이 이미지는 코쿤 매뉴얼에 있는 것을 사용한 것입니다.

코쿤은 단지 5가지의 관계를 통해서 이 네가지 작업을 구분할 수 있는 방법을 제공하도록 설계되었다. 주목할만한 점은 로직 부분과 스타일시트 부분은 아무런 관련도 없다는 것이다. 이렇게 함으로써 웹 사이트를 개발할 때 소요되는 디자이너와 개발자 사이의 상극 관계를 없애주었다.

실제로 코쿤은 개발자가 다루는 로직 부분과 디자이너가 다루는 스타일 부분을 서로 다른 파일에 저장하도록 하였으며, 이 두 그룹의 작업은 오직 가상의 라인(작업 사이의 관계)을 통해서만 연결될 수 있도록 하였다. 실제로 어떻게 코쿤이 각 작업들을 분리할 수 있도록 해 주는지는 앞으로 여러 차례에 걸쳐 구체적으로 살펴볼 것이다.

코쿤 설치하기

코쿤을 사용하려면, 먼저 코쿤을 설치해야 할 것이다. 코쿤의 설치 방법은 사용하는 서블릿 콘테이너에 따라, 또한 사용하는 JDK 버전에 따라 다르다. 따라서 이 글에서는 JDK 1.2와 1.3.1을 기준으로 설명할 것이다. JDK 1.4.0의 경우는 아직 코쿤 2가 올바르게 지원하고 있지 않기 때문에 JDK 1.4.0을 기준으로 한 설명은 생략하기로 한다. 또한 톰캣의 각 버전에 따라 설치하는 방법을 설명할 것이다.

시스템 요구사항

코쿤을 설치하기 위해서는 먼저 다음의 두 시스템이 설치되어 있어야 한다.

  • 1.2 이상의 버전과 호환되는 자바 가상 머신. 단 1.4.0 제외
  • 서블릿 2.2를 지원하는 서블릿 엔진
1.2 이상의 JVM과 서블릿 2.2를 지원하는 서블릿 엔진은 각각 http://java.sun.com/j2sehttp://jakarta.apache.org/tomcat에서 다운로드 받을 수 있다

코쿤 구하기

http://xml.apache.org/cocoon/dist에서 가장 최신 버전의 바이너리 배포판과 소스 코드 배포판을 다운로드 할 수 있으며 그리고 CVS를 이용하여 소스 코드를 다운로드 받을 수 있다. 이 글에서는 바이너리 배포판을 중심으로 설명할 것이므로 http://xml.apache.org/cocoon/dist 사이트에서 바이너리 버전을 다운로드 받기 바란다. 참고로, 이 글을 쓸 때는 코쿤 2.0.2 버전인 cocoon-2.0.2-bin.zip 파일을 사용하였다.

환경 설정하기

코쿤을 실행하기 위해서는 먼저 JAVA_HOME 환경 변수를 JDK가 설치된 루트 디렉토리로 지정해주어야 한다. 단순히 다음과 같이 지정해주면 된다.

    [Win32] C:\> SET JAVA_HOME=c:\jdk1.3.1
    [Linux] $ export JAVA_HOME=/usr/local/jdk1.3.1

코쿤 설치하기

서블릿 엔진에 따라서 차이가 있긴 하지만, 대부분의 서블릿 엔진에서 코쿤을 설치하는 과정은 단순히 파일을 복사하는 과정으로 끝이난다. 이 글에서는 쉽게 구할 수 있는 톰캣과 레신을 기준으로 설치 방법을 설명하도록 하겠다.

톰캣 3.2.x 버전에 설치하기

톰캣 3.2.x 버전과 코쿤 2.0.2가 사용하는 XML 관련 API는 서로 버전이 다르기 때문에, 코쿤 2.0.2를 설치하려면 약간의 편법을 써야 한다. 설치하는 순서는 다음과 같다.

  1. [톰캣]/lib/jaxp.jar 파일을 삭제한다.
  2. [톰캣]/lib/parser.jar 파일의 이름을 [톰캣]/lib/zparser.jar로 변경해준다.
  3. [코쿤소스코드버전배포판]/lib/core 디렉토리에 있는 xercesImpl-2.0.0.jar 파일과 xml-apis.jar 파일을 [톰캣]/lib 디렉토리에 복사해준다.
  4. [코쿤바이너리배포판]/cocoon.war 파일을 [톰캣]/webapps 디렉토리에 복사한다.
  5. 톰캣을 시작한다. 6. http://localhost:8080/cocoon에 연결해서 코쿤이 올바르게 실행되는 지 확인한다.
위 과정에서 '2번' 과정을 수행하는 이유는 톰캣이 사용하는 XML 파서 관련 클래스보다 코쿤이 사용하는 XML 파서 관련 클래스를 먼저 로딩하기 위해서이다. 톰캣은 시작될 때 알파벳 순서로 jar 파일을 로딩하기 때문에, 이름을 변경한 zparser.jar 보다 xercesImpl-2.0.0.jar를 먼저 로딩하며, 이렇게 함으로써 코쿤이 사용하는 파서 구현 클래스를 먼저 사용할 수 있게 된다.

톰캣 3.3.x 버전에 설치하

톰캣 3.3.x 버전에서는 매우 쉽게 코쿤을 설치할 수 있다. 간단히 다음과 같은 순서로 설치해주면 된다.

  1. [코쿤바이너리배포판]/cocoon.war 파일을 [톰캣]/webapps 디렉토리에 복사한다.
  2. 톰캣을 시작한다.
  3. http://localhost:8080/cocoon에 연결해서 코쿤이 올바르게 실행되는 지 확인한다.
톰캣 4.0 - 4.0.1 버전에 설치하기

톰캣 4.0과 톰캣 4.0.1 버전에 코쿤을 설치하는 과정은 톰캣 3.3.x 버전에 설치 과정과 완전히 같으며, 다음과 같은 순서로 코쿤을 설치하면 된다.

  1. [코쿤바이너리배포판]/cocoon.war 파일을 [톰캣]/webapps 디렉토리에 복사한다.
  2. 톰캣을 시작한다.
  3. http://localhost:8080/cocoon에 연결해서 코쿤이 올바르게 실행되는 지 확인한다.
톰캣 4.0.3 버전에 설치하기

톰캣 4.0.3에 코쿤을 설치하려면 톰캣이 사용하는 파서를 코쿤에 포함된 파서로 변경해주어야 한다. 톰캣 4.0.3에 코쿤 2.0.2를 설치하는 과정은 다음과 같다.

  1. [톰캣]/common/lib/xerces.jar 파일을 삭제한다.
  2. [코쿤소스코드배포판]/lib/core 디렉토리에서 다음의 파일을 [톰캣]/common/lib 디렉토리에 복사한다.
    • xalan-2.3.1.jar
    • xercesImpl-2.0.0.jar
    • xml-apis.jar
  3. [코쿤]/lib/optional/batik-all-1.5b1.jar 파일을 [톰캣]/common/lib 디렉토리에 복사한다.
  4. [코쿤소스코드배포판]/src/webapps/WEB-INF/web.xml 파일에 다음과 같이 extra-classpath 초기화 파라미터를 추가한다.
        <init-param>
          <param-name>extra-classpath</param-name>
          <param-value>D:\jakarta-tomcat-4.0.3\common\lib\xalan-2.3.1.jar;
          D:\jakarta-tomcat-4.0.3\common\lib\xercesImpl-2.0.0.jar;
          D:\jakarta-tomcat-4.0.3\common\lib\xml-apis.jar;
          D:\jakarta-tomcat-4.0.3\common\lib\batik-all-1.5b1.jar</param-value>
        </init-param>

    참고로 위에서 <param-value> 부분은 한 줄로 입력한 것이다.
  5. 다음과 같이 build.bat을 실행하여 cocoon.war 파일을 생성한다.
        build -Dinclude.webapp.libs=yes webapp

  6. [코쿤소스코드배포판]/build/cocoon/cocoon.war 파일을 [톰캣]/webapps 디렉토리에 복사한다.
  7. [톰캣]/webapps/cocoon 디렉토리를 생성한 후, [톰캣]/webapps/cocoon 디렉토리로 경로를 이동한다.
  8. 이 상태에서 다음과 같이 cocoon.war 파일의 압축을 [톰캣]/webapps/cocoon 디렉토리에 풀어준다.
        D:\jakarta-tomcat-4.0.3\webapps\cocoon> jar xvf ..\cocoon.war

  9. [톰캣]/webapps/cocoon/WEB-INF/lib 디렉토리에서 xalan-2.3.1, xercesImpl-2.0.0.jar, xml-apis.jar 그리고 batik-all-1.5b1.jar 파일을 삭제한다.
  10. 톰캣을 시작한다.
  11. http://localhost:8080/cocoon에 연결해서 코쿤이 올바르게 실행되는 지 확인한다.
조금 복잡하지만 그리 어렵지 않으므로 쉽게 할 수 있을 것이다.

레신 2.0.x 버전에 설치하기

레신 2.0.x 버전에서 설치하는 과정은 다음과 같다.

  1. [레신]/lib 디렉토리에 있는 jaxp.jar, dom.jar, sax.jar 파일을 삭제한다.
  2. [코쿤소스코드배포판]/lib/core 디렉토리의 xercesImpl-2.0.0.jar와 xml-apis.jar를 [레신]/lib 디렉토리에 복사한다.
  3. [코쿤바이너리배포판]/cocoon.war 파일을 [레신]/webapps 디렉토리에 복사한다.
  4. 레신을 시작한다.
  5. http://localhost:8080/cocoon에 연결해서 코쿤이 올바르게 실행되는 지 확인한다.
코쿤의 기본 메커니즘

지금까지 코쿤의 설치 방법에 대해서 살펴보았으므로, 이제 코쿤 자체에 대한 내용을 살펴보기로 하자. 이 글에서는 코쿤 1.x 버전에 대해서는 언급하지 않을 것이며, 코쿤 2에 대해서만 살펴볼 것이다.

앞에서 언급했듯이 코쿤은 업무에 따른 역할을 분담할 수 있도록 설계되었다. 즉, 컨텐츠, 로직, 스타일 그리고 이 전체의 관리의 네 가지 업무를 구분하는데 초점을 맞춰 설계되었다. 또한 코쿤은 XML, RDBSM 등의 다양한 데이터 자원을 사용하여 컨텐츠를 출판할 수 있도록 하고 있다.

이처럼 역할의 분리와 다양한 데이터 타입을 사용할 수 있도록 하기 위해서 코쿤은 4 가지의 주요 요소를 사용하는데, 이들 네 가지 요소는 다음과 같다.

  1. Generator - 컨텐츠, 로직쉬트, RDB, 객체를 사용하여 XML 문서를 생성한다.
  2. Transformer - XML 문서를 또 다른 XML이나 객체로 변환한다.
  3. Aggregator - XML 문서를 조합한다.
  4. Serializer - XML을 렌더링(rendering)한다.
이 네 요소의 관계를 표현하면 다음 그림과 같다.

* 이 이미지는 코쿤 매뉴얼에 있는 것을 사용한 것입니다.

위 그림을 보면서 차례대로 각 요소의 관계를 살펴보도록 하자. 먼저 클라이언트로부터 요청이 들어오면 Generator는 기존에 존재하는 자원(XML 문서, 로직 쉬트, RDB 등)으로부터 XML 문서를 생성한다. 이렇게 해서 생성된 XML 문서는 Transformer에 전해진다. Transformer는 XML 문서와 스타일시트를 사용하여 변환 과정을 처리한다. 이 과정에서 새로운 문서가 생성된다. 이때 생성된 문서는 Aggregator에 전달되며, Aggregator는 다양한 자원을 통합한 후 Serializer에 전달한다. 그러면 최종적으로 Serializer는 Aggregator로부터 받은 자원을 클라이언트가 볼 수 있는 형태로 렌더링하여 전송한다.

코쿤 2는 이미 다양한 종류의 자원으로부터 XML 문서를 생성할 수 있는 Generator를 제공하고 있으며, 또한 XML/HTML/PDF/SVG 등 다양한 포맷을 위한 Transformer와 Aggregator, Serializer를 제공하고 있다. 따라서 코쿤 2가 제공하는 기본 기능만으로도 훌륭한 출판 사이트를 제작할 수 있다.

코쿤을 구성하고 있는 Generator, Transformer, Aggregator, Serializer는 코쿤2에 새롭게 추가된 사이트맵을 통해서 명시할 수 있으며, 또한, 사이트맵을 통해서 네 가지 구성 요소를 조합할 수 있다. 예를 들면 A 라는 자원을 B 라는 Generator를 통해서 XML로 생성한 다음, C라는 Tranformer를 사용하여 변환하고 그 다음 D라는 Serializer를 사용하여 클라이언트에 최종 출판물을 전송한다는 식으로 일련의 과정을 사이트맵에서 지정할 수 있다. 보다 정확하게 말해서 이러한 일련의 처리 과정은 파이프라인을 통해서 처리되는데, 파이프라인은 사이트맵에 명시된다. 결국 사이트맵을 통해서 코쿤의 모든 출판 과정이 처리된다고 생각하면 된다.

다음의 시퀀스 다이어그램은 클라이언트의 요청이 코쿤에서 어떤 순서로 처리되는 지를 보여주고 있다.

* 이 이미지는 코쿤 매뉴얼에 있는 것을 사용한 것입니다.

SiteMap은 사이트맵을 나타내고 Pipeline은 클라이언트의 요청이 처리되는 일련의 과정을 나타낸다. 하나의 사이트맵에는 다수의 파이프라인이 존재할 수 있으며, 사이트맵은 클라이언트의 요청에 따라 알맞은 파이프라인을 선택하여 클라이언트에 응답 메시지를 전송하게 된다.

파이프라인을 개념적으로 도식화하면 다음과 같다.

* 이 이미지는 코쿤 매뉴얼에 있는 것을 사용한 것입니다.

위 그림을 보면 Generator와 Transformer 그리고 Serializer 사이에 SAX를 사용하여 데이터를 전달하는 것을 알 수 있는 데, 이렇게 SAX를 선택한 이유는 처리속도를 높이기 위해서이다.

사이트맵과 XSP

코쿤이 단순히 (XSLT를 사용하여) XML을 다양한 형식의 문서로 변환해주는 데 그쳤다면 코쿤은 그저 유틸리티 정도의 프레임워크에 그쳤을 것이다. 하지만, 코쿤은 거기서 멈추지 않았다. 코쿤은 로직과 표현을 완벽하게 분리해주는 XSP라는 걸출한 스크립트를 제공하고 있으며, 코쿤 2부터는 프로그래머가 아닌 사람도 로직 컴포넌트와 XML 문서만으로 웹 사이트와 웹 어플리케이션을 작성할 수 있도록 해 주는 사이트맵 기능을 제공하고 있다.

사이트맵(Sitemap)

사이트 맵은 웹 출판과 관련된 모든 것들을 정리해 놓은 XML 파일로서, 파일명은 sitemap.xmap이다. 이 파일은 다음과 같은 요소에 대한 정의를 포함하고 있으며 각 요소를 어떻게 조립할지를 명시할 수 있다.

  • 컴포넌트: Matcher, Generator, Transformer, Reader, Serializer, Selector, Action 등
  • 뷰: 처리과정에서 생성된 XML에 대한 뷰를 정의한다.
  • 리소스: 사이트맵 문서 내부에서만 사용가능하며, 파이프라인과 같은 방법으로 정의한다. 파이프라인은 필요에 따라 리소스를 사용할 수 있다.
  • 액션: 런타임에 파라미터를 처리하기 위한 모듈
  • 파이프라인: 파이프라인들을 정의한다.
사이트맵에 대해서는 다음 글에서 보다 자세하게 살펴볼 것이다. 이번 글에서는 단순히 위와 같은 구성 요소들을 지정하여 원하는 형태로 웹 사이트를 구축할 수 있다는 사실만 알아두도록 하자.

XSP(eXtensible Server Page)

XSP는 서버측의 스크립트로서 로직의 구현과 표현의 구현을 분리해주는 XML 문서이다. XSP의 처리 흐름은 다음 그림과 같다.

* 이 이미지는 코쿤 매뉴얼에 있는 것을 사용한 것입니다.

위 그림에서 page.xml은 데이터를 저장하고 있는 XML 문서이며, page-xsp.xsl은 로직을 저장하고 있는 로직시트 파일이다. 로직시트는 XSP에서 사용되는 로직 관련 태그를 사용하여 XML 데이터를 처리하며, XSP 처리 결과는 XML로 생성된다. XSP 처리 결과로 생성된 XML 파일은 스타일시트를 사용하여 실제로 출력될 포맷으로 변환된다. XSP에 대해서는 3번째 글에서 자세하게 살펴보도록 하겠다./p>

XML 변환 예제

지금까지 코쿤에 대한 기본 개념과 구조에 대해서 살펴봤는데 XML 문서를 HTML로 변환하는 과정을 통해서 간단하게나마 코쿤이 제공하는 출판 기능을 사용해보도록 하자. 먼저 사용할 XML 문서는 다음과 같다.

    <?xml version="1.0"?>
    <!DOCTYPE book SYSTEM "DTD/JavaXML.dtd">
    
    <!-- Java and XML Contents -->
    <book xmlns="http://www.oreilly.com/javaxml2"
          xmlns:ora="http://www.oreilly.com"
    >
      <title ora:series="Java">Java and XML</title>
    
      <!-- Chapter List -->
      <contents>
        <chapter title="Introduction" number="1">
          <topic name="XML Matters" />
          <topic name="What's Important" />
          <topic name="The Essentials" />
          <topic name="What&apos;s Next?" />
        </chapter>
        <chapter title="Nuts and Bolts" number="2">
          <topic name="The Basics" />
          <topic name="Constraints" />
          <topic name="Transformations" />
          <topic name="And More..." />
          <topic name="What&apos;s Next?" />
        </chapter>
        <chapter title="SAX" number="3">
          <topic name="Getting Prepared" />
          <topic name="SAX Readers" />
          <topic name="Content Handlers" />
          <topic name="Gotcha!" />
          <topic name="What&apos;s Next?" />
        </chapter> 
        <chapter title="Advanced SAX" number="4">
          <topic name="Properties and Features" />
          <topic name="More Handlers" />
          <topic name="Filters and Writers" />
          <topic name="Even More Handlers" />
          <topic name="Gotcha!" />
          <topic name="What&apos;s Next?" />
        </chapter>
        <chapter title="DOM" number="5">
          <topic name="The Document Object Model" />
          <topic name="Serialization" />
          <topic name="Mutability" />
          <topic name="Gotcha!" />
          <topic name="What&apos;s Next?" />
        </chapter>           
        <chapter title="Advanced DOM" number="6">
          <topic name="DOM and Mutation" />
          <topic name="Namespaces and DOM Level 2" />
          <topic name="DOM and HTML" />
          <topic name="DOM Level 3" />
          <topic name="Gotcha!" />
          <topic name="What&apos;s Next?" />
        </chapter>
        <chapter title="JDOM" number="7">
          <topic name="The Basics" />
          <topic name="PropsToXML" />
          <topic name="XMLProperties" />
          <topic name="Is JDOM a Standard?" />
          <topic name="Gotcha!" />
          <topic name="What&apos;s Next?" />
        </chapter>
        <chapter title="Advanced JDOM" number="8">
          <topic name="The Whole Ball of Wax" />
          <topic name="JDOM and Factories" />
          <topic name="Wrappers and Decorators" />
          <topic name="Gotcha!" />
          <topic name="What&apos;s Next?" />
        </chapter>
        <chapter title="JAXP" number="9">
          <topic name="API or Abstraction?" />
          <topic name="JAXP 1.0" />
          <topic name="JAXP 1.1" />
          <topic name="Gotcha!" />
          <topic name="What&apos;s Next?" />
        </chapter>
        <chapter title="Web Publishing Frameworks" number="10">
          <topic name="Selecting a Framework" />
          <topic name="Installation" />
          <topic name="Using a Publishing Framework" />
          <topic name="XSP" />
          <topic name="Cocoon 2.0 and Beyond" />
          <topic name="What&apos;s Next?" />
        </chapter>    
        <chapter title="XML-RPC" number="11">
          <topic name="RPC Versus RMI" />
          <topic name="Saying Hello" />
          <topic name="The Real World" />
          <topic name="What&apos;s Next?" />
        </chapter>
        <chapter title="SOAP" number="12">
          <topic name="Starting Out" />
          <topic name="Setting Up" />
          <topic name="Getting Dirty" />
          <topic name="Going Further" />
          <topic name="What&apos;s Next?" />
        </chapter>
        <chapter title="Web Services" number="13">
          <topic name="Web Services" />
          <topic name="UDDI" />
          <topic name="WSDL" />
          <topic name="Putting It All Together" />
          <topic name="What&apos;s Next?" />
        </chapter>
        <chapter title="Content Syndication" number="14">
          <topic name="The Foobar Public Library" />
          <topic name="mytechbooks.com" />
          <topic name="Push Versus Pull" />
          <topic name="What&apos;s Next?" />
        </chapter>
        <chapter title="XML Data Binding" number="15">
          <topic name="First Principles" />
          <topic name="Castor" />
          <topic name="Zeus" />
          <topic name="JAXB" />
          <topic name="What&apos;s Next?" />
        </chapter>
        <chapter title="Looking Forward" number="16">
          <topic name="XLink" />
          <topic name="XPointer" />
          <topic name="XML Schema Bindings" />
          <topic name="And the Rest..." />
          <topic name="What&apos;s Next?" />
        </chapter>
      </contents>
    
      <ora:copyright>&OReillyCopyright;</ora:copyright>
    </book>

참고로 위 예제는 O'Reilly에서 출판된 'Java and XML 2nd'에 있는 코드이다. 위 XML 문서는 책의 목차를 보여주고 있다는 것을 예상할 수 있을 것이다. 이 XML 문서를 HTML로 변환해줄 XSL 파일은 다음과 같다.

    <?xml version="1.0"?>
    
    <xsl:stylesheet xmlns:javaxml2="http://www.oreilly.com/javaxml2"
                    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                    xmlns:ora="http://www.oreilly.com"
                    version="1.0"
    >
    
      <xsl:template match="javaxml2:book">
      <xsl:processing-instruction 
        name="cocoon-format">type="text/html"</xsl:processing-instruction>
        <html>
          <head>
            <title><xsl:value-of select="javaxml2:title" /></title>
          </head>
          <body>
            <xsl:apply-templates select="*[not(self::javaxml2:title)]" />
          </body>
        </html>
      </xsl:template>
    
      <xsl:template match="javaxml2:contents">
        <center>
         <h2>Table of Contents</h2>
        </center>
        <hr />
        <ul>
         <xsl:for-each select="javaxml2:chapter">
          <b>
           Chapter <xsl:value-of select="@number" />.
           <xsl:text> </xsl:text>
           <xsl:value-of select="@title" />
          </b>
          <xsl:for-each select="javaxml2:topic">      
           <ul>
            <li><xsl:value-of select="@name" /></li>
           </ul>
          </xsl:for-each>
         </xsl:for-each>
        </ul>
      </xsl:template>
    
      <xsl:template match="ora:copyright">
        <p align="center"><font size="-1">
         <xsl:copy-of select="*" />
        </font></p>
      </xsl:template>
    
    </xsl:stylesheet>

앞의 XML 문서를 [톰캣]/webapps/cocoon 디렉토리에 contents.xml 로 저장하고, 뒤의 XSL 문서를 [톰캣]/webapps/cocoon/XSL/JavaXML.html.xsl로 저장하자.

contents.xml 파일에서 중요한 점은 contents.xml 파일을 처리할 때 사용되는 XSL 문서를 지정하고 있지 않다는 점이다. 실제로 XML 문서를 변환할 때 사용할 XSL 문서는 코쿤의 설정 파일인 sitemap.xmap 파일에서 지정하게 된다. 이 예제를 지정하기 위해서는 [톰캣]/webapps/cocoon/sitemap.xmap 파일에 다음의 내용을 추가하면 된다.

    <map:match pattern="contents.html">
      <map:generate src="contents.xml"/>
      <map:transform src="XSL/JavaXML.html.xsl"/>
      <map:serialize type="html"/>
    </map:match>

여기서 <map:match>는 클라이언트가 요청한 URI와의 매핑되는 조건을 입력하며, <map:generate>은 사용자의 요청에 대해서 사용할 Generator를 지정한다. src 속성의 값이 contents.xml 인데 이는 contents.xml을 사용하여 XML 문서를 생성한다는 것을 의미한다. <map:transform>은 Transformmer를 나타내는 것으로서 XML 문서를 변환할 때 사용할 XSL 문서를 지정한다. 마지막으로 <map:serialize>는 변환된 결과를 렌더링할때 사용되는 Serializer를 나타내는 것으로서, 여기서는 HTML 타입으로 출력해주는 Serializer를 선택하였다.

위 코드를 sitemap.xmap의 다음 부분에 삽입하도록 하자.

  <!-- main samples pipeline -->
  <map:pipeline>   
    <map:match pattern="">
      <map:generate src="welcome/welcome.xhtml"/>
      <map:serialize/>      
    </map:match>
    
    <map:match pattern="contents.html">
      <map:generate src="contents.xml"/>
      <map:transform src="XSL/JavaXML.html.xsl"/>
      <map:serialize type="html"/>
    </map:match>
 
    <map:match pattern="cocoon.gif">
      <map:read mime-type="image/gif" src="welcome/cocoon.gif"/>
    </map:match>
        .....
  </map:pipeline>

이제 필요한 설정은 끝났다. 톰캣을 실행하고 웹브라우저를 실행한 다음 웹브라우저의 주소란에 다음과 같은 URL을 입력해보자.

    http://localhost/cocoon/contents.html

그러면 다음과 같은 결과 화면이 출력될 것이다.


위 출력 결과를 보면 XML 데이터가 HTML로 변환되어 출력된 것을 알 수 있다. 이 출력 결과에서 우리가 알아야 할 점은 컨텐트(XML 문서)와 컨텐트의 출판 방식(XSL 문서)이 서로 분리되어 있다는 점이다. 즉, 컨텐트 제작자와 디자이너의 작업 결과물이 서로 분리되어 있는 것이다. 그리고, 앞으로 살펴볼 XSP를 통해서 로직 부분이 처리되기 때문에, 컨텐트 제작자, 디자이너, 개발자의 작업이 서로 겹치지 않게 된다. 또한, 웹 사이트 관리자는 사이트맵을 통해서 컨텐트, 스타일, XSP를 알맞게 조합할 수 있다. 즉, 코쿤을 사용함으로써 완벽한 업무 분담이 이루어질 수 있는 것이다.

참고로 웹브라우저에 입력한 URL을 보면 contents.xml이 아닌 contents.html을 요청한 것을 알 수 있다. 즉, 클라이언트는 응답결과가 XML을 변환한 문서라는 것을 알 수 없는 것이다. 이처럼 원본 데이터에 대한 정보를 숨길 수 있는 것도 웹 사이트를 운영하는데 있어 유용한 기능중의 하나이다.

결론

이번 글에서는 코쿤에 대한 개략적인 설명과 설치 방법, 그리고 간단하게 나마 예제를 통해서 데이터와 표현부를 분리할 수 있다는 것도 알게 되었다. 다음에는 코쿤이 기본적으로 제공하는 사이트맵 컴포넌트인 Matcher, Generator, Transformer, Serializer에 대해서 살펴보고, 실제로 하나의 컨텐트를 다양한 방식으로 출판하는 예제도 살펴보도록 하자.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요