Absorbed all netj.org stuffs into Git. Removed empty comment files. Embedded older comments. Removed RCS archives, annotated logs in headers. Decided to switch to Git instead of RCS. Translated to Markdown

최근 XML과 XSLT만으로 웹 프로그래밍을 아주 멋지게 할 수 있는 방법을 생각해냈다.

XML을 XSLT로 웹페이지로 바꾼다는 것은 기존의 생각들과 다름없다. 다만 약간의 새로운 시도라면, CGI의 쿼리 이름과 값들을 XSLT의 xsl:param으로 전달해준다는 것. 이렇게 하면 사용자의 입력에 따른 XML의 내용 검색과 같은 동적인 기능도 구현이 가능하다. 그러나 어찌되었든 원래 주어진 XML을 XSLT로 바꾼다는 점은 흔히들 쓰는 방식 그대로다.

역시 문제가 되는 것은 이미 존재하는 XML을 어떻게 업데이트하느냐다. 이 문제는 업데이트에 대해서는 아무것도 이야기하지 않는 XQuery를 쓴다고 해도 한 번은 부딪혀야하는 문제다. 아이디어는 간단하다. 원래 XML과 CGI 입력을 받아서 그 다음 XML을 내놓도록 XSLT를 만들면 되는 것. 이 XSLT에 의해 변환된 결과를 평소처럼 사용자에게 보내는 것이 아니라 미리 지정한 파일로 내보내면 되는 것이다. 그러면 CGI 입력에 따라서 정보를 덧붙이거나 바꾸거나 지울 수 있게 된다. 순수하게 XSLT만 가지고 말이다. :)

다음과 같은 xmlio.pl이라는 Perl CGI 프로그램이 위의 변환과 저장, 두 기능을 담당한다.

#!/usr/bin/perl
# XML I/O with CGI & mod_perl
# Author: Jaeho Shin <netj@ropas.snu.ac.kr>
# Created: 2005-01-05
# usage: ...

use strict;
use CGI;
use XML::LibXML;
use XML::LibXSLT;
use IO::File;
use Fcntl qw(:flock);

my $Q = new CGI;
my $XP = new XML::LibXML;
my $XSLT = new XML::LibXSLT;

my $wd = $ENV{REDIRECT_WD} || $ENV{WD};
exit 1 if not -d $wd;
chdir $wd;

my $xml = $Q->param("-xml");
my $xslt = $Q->param("-xslt");
my $update = $Q->param("-update");

if (my $src = $XP->parse_file($xml)) {
    if (my $xslt = $XSLT->parse_stylesheet($XP->parse_file($xslt))) {
        my $output = do {
            if ($update) {
                new IO::File($xml, "w")
            } else {
                \*STDOUT
            }
        };
        my %param = map { $_ => $Q->param($_) } grep /^\w+$/, $Q->param;
        if (not transform($xslt, $src, $output,
                XML::LibXSLT::xpath_to_string(%param),
                -prologue => sub {
                    if ($update) {
                        flock $output, LOCK_EX;
                    } else {
                        print $Q->header(
                            -type => $xslt->media_type,
                            -charset => $xslt->output_encoding
                        );
                    }
                },
                -epilogue => sub {
                    if ($update) {
                        flock $output, LOCK_UN;
                        print $Q->redirect($update);
                    }
                }
            )) {
            error(500, "transform error: $!");
        }
    } else {
        error(500, "$xslt: $!");
    }
} else {
    error(404, "$xml: $!");
}


sub transform {
    my $xslt = shift;
    my $src = shift;
    my $dst = shift;
    my %param = @_;
    my $prologue = $param{-prologue} || sub {};
    my $epilogue = $param{-epilogue} || sub {};
    if (my $results = $xslt->transform($src,
            map { $_ => $param{$_} } grep /^\w+$/, keys %param)) {
        $prologue->();
        $xslt->output_fh($results, $dst);
        $epilogue->();
        $results
    } else {
        undef
    }
}

sub error {
    my $code = shift;
    print $Q->header(-status=>$code, -type=>'text/plain');
    print @_;
}

물론 그냥 CGI로 돌리지 않고 mod_perl을 쓰면 응답 속도를 엄청나게 향상시킬 수 있다.

xmlio.pl에 -xml=...이나 -xslt=...와 같은 온갖 지저분한 쿼리를 붙여서 쓰는 것은 정신이 똑바로 박혀있는 사람이라면 절대로 내키지 않는 일이므로, Apache의 mod_rewrite을 이용하여 어떤 URL들이 어떤 XML과 XSLT를 사용해야 하는 지를 정의해주는 것이 좋겠다. 아래는 현재 로파스 도서관 시스템에 적용하면서 만든 .htaccess의 예:

RewriteEngine on
RewriteRule ^(papers|books) /cgi-bin/xmlio.pl?-xml=$1.xml&-xslt=search.xsl [L,QSA,E=WD:/ROPAS/www.ng/lib](L,QSA,E=WD:/ROPAS/www.ng/lib)
RewriteRule ^dock/(.+).bib$ /cgi-bin/xmlio.pl?key=$1&-xml=papers.xml&-xslt=bibtex-entry.xsl [L,QSA,E=WD:/ROPAS/www.ng/lib](L,QSA,E=WD:/ROPAS/www.ng/lib)

위의 방식을 이용하면 웹 프로그래밍을 엄청나게 깔끔하게 할 수 있다. mod_rewrite을 이용하여 제공하고자 하는 기능들을 URL로 이름붙이고 그들이 어떤 입력 XML을 쓰는지 정의한 다음, 각 기능들에서 해당 XML을 어떤 식으로 변환해야 하는지를 XSLT에 정의하는 것만으로 웹 프로그래밍을 끝마칠 수 있다. 관련 있는 기능들은 XSLT의 변환 결과로 만들 웹 페이지에다 해당 기능의 URL로 가는 링크 또는 웹 양식만 추가하면 되기 때문이다. 단순히 자료의 변환만으로 표현 할 수 없는 side-effect에 해당하는 일을 해야 한다면, 그 일을 별도의 프로그램에서 구현하고 XSLT 처리기의 확장 함수로 등록하고 XSLT에서 그 함수를 호출하는 식으로 가능할 것이다.

일단 지금은 연구실 홈페이지의 도서 검색, 세미나 자료 관리, 사진첩 등에 적용하면서 이 방식의 문제점이나 개선할 부분, 더 미세한 부분에서의 패턴/스타일 등에 관한 경험을 축적하고 있는 중이다. 앞으로 이 스타일을 적용한 예들이 많아지면, 이미 한 번 변환을 했던 내용은 재활용을 할 수 있도록 xmlio.pl에서 XML과 XSLT, 주어진 param에 따라서 캐싱도 해볼 생각이다.


web pgmg