2009년 1월 29일 목요일

com.oreilly.servlet (JAVA LIB)


Home of com.oreilly.servlet

There's no sense in reinventing the wheel--here are some servlet support classes I wrote that you can use. Most famous is the file upload package MultipartRequest and MultipartParser. Please read the license before use.

View the Readme
View the License
View the FAQ


View class documentation

Class Index

Exception Index



LIB 다운로드 : http://www.servlets.com/cos/

2009년 1월 9일 금요일

LAMP(Linux, Apache, PHP, Mysql) 설정법


우선 APM (Apache, PHP5, Mysql)이 3가지가 필요 합니다.
흔히들 LAPM이라고 하죠,ㅎ 리눅스 아파치 php mysql

  
$sudo apt-get install apache2                Apache 설치

   $sudo apt-get install libapache2-mod-auth-mysql        mysql 인증 모듈

   $sudo apt-get install mysql-server mysql-client         MySQL 설치
    MYSQL은 설치 후 자동으로 실행 됩니다.^-^

   $sudo apt-get install php5-common php5 libapache2-mod-php5      PHP5 설치

   $sudo apt-get install php5-mysql          MySQL과 php5를 연동하기 위한 모듈

   $sudo /etc/init.d/apache2 restart          아파치 웹서버를 재시작

APM 설치는 끝났습니다.ㅎ 이로써 LAPM환경을 구축 완료

아파치의 기본 경로는 /var/www 로 잡힙니다.^^
이 안에 index.html을 만든다면 되겠죠?ㅎ

html에 보시면 한글이 깨져서 나오는데 이 부분은 직접 인코딩을 바꿔주시거나 아니면 아파치 설정에 들어가셔서
    $sudo gedit /etc/apache2/apache2.conf  

마지막 부분에 AddDefaultCharset utf-8
를 추가합니다

그러면 한글 홈페이지도 인코딩 완료~

아참~ 매번 실행 시킬 때마다 아파치를 직접 켜시기 귀찬으신 분이라면 ~
세션메뉴를 선택해서~ 명령어 추가를 해주시면 됩니다~
sudo /etc/init.d/apache2 start를 넣어주시면 되겠네요~ㅎ
그럼 부팅 할 때 마다 알아서 아파치 써버가 열립니다~^^ㅋ

2009년 1월 6일 화요일

Xshell Tutorial


본 Tutorial에서는 Telnet/Rlogin 또는 SSH 세션을 생성하는 방법과 Linux/Unix 호스트에 접속하기 위한 방법을 설명합니다. Telnet 세션은 매우 간단하지만 SSH 세션은 서버에 연결하기 전에 사용자 키를 서버에 등록해야 할 필요가 있을 수 있습니다. 만약 사용자 키 등록이 필요하다면 공개키 사용자 인증 을 참조하십시오.
세션 만들기
원격 호스트에 연결하기
문제 해결

Telnet, Rlogin 및 SSH 프로토콜 세션을 생성할 수 있습니다.
  1. [파일] 메뉴에서, [새로 만들기]를 선택합니다. 새 세션 등록 정보 대화 상자가 나타납니다.
  2. [이름] 상자에 세션 이름을 입력합니다.
  3. [연결 방법] 리스트에서 적절한 프로토콜을 선택합니다.
  4. [호스트] 상자에 서버의 호스트 이름 또는 IP 주소를 입력합니다.
  5. [포트 번호] 상자에 원격 서버에 의해 사용되는 포트 번호를 입력합니다.
  6. [사용자 인증] 분류에서 [사용자 이름] 상자에 서버의 사용자 계정을 입력합니다.
    참고: SSH 프로토콜에서 사용자 이름이 없는 경우, 서버에 연결 시 사용자 이름을 묻는 대화 상자가 나타납니다.
  7. [확인]을 선택합니다.
Telnet, Rlogin 또는 SSH 세션이 생성되어 있다면, 이제 원격 호스트에 접속할 수 있습니다. 만약 생성된 세션이 없다면,
세션 만들기 섹션을 참조하십시오.
  1. [파일] 메뉴에서, [열기]를 선택합니다.
  2. 세션 리스트 중에서, 연결할 세션을 선택하고 연결 버튼을 클릭하면 SSH 사용자 인증 대화 상자가 나타납니다.
  3. 비밀번호 인증을 시도하시려면, [비밀번호] 상자에 비밀번호를 입력한 후 [확인]을 선택합니다.
  4. 공개키 인증을 시도하시려면, 먼저 리스트에서 사용자 키를 선택합니다. 선택된 사용자 키의 암호를 [암호] 상자에 입력한 후 [확인]을 선택합니다.
    참고: 만약 사용자 키가 없다면, 공개키 사용자 인증 섹션을 참고하십시오.
  5. Keyboard Interactive 인증을 시도하시려면, [확인] 버튼을 선택한 후 그 뒤에 질문 되는 물음들에 답하십시오.
지금까지 Xshell을 통해 원격 호스트에 접속을 시도했습니다. 만약 연결 중 문제가 있었다면, 문제 해결 섹션을 참조하십시오.
호스트 연결에 실패한 후 다음과 같은 에러 메시지가 나오는 경우가 있습니다.

"Could not connect to 'hostname': Unknown host."

호스트 이름이 올바른지 확인하십시오. 호스트 이름 대신 IP 주소를 입력해야 할 수도 있습니다.

"Could not connect to 'hostname' (port 22): Connection failed."

호스트가 준비되었는지 확인하십시오. 또한 포트 번호가 서버가 사용하는 번호인지 확인해 보십시오

2009년 1월 5일 월요일

CMS(Content Management System)


A content management system (CMS) is a computer application used to create, edit, manage, search and publish various kinds of digital media and electronic text.[1] CMSs are frequently used for storing, controlling, versioning, and publishing industry-specific documentation such as news articles, operators' manuals, technical manuals, sales guides, and marketing brochures. The content managed may include computer files, image media, audio files, video files, electronic documents, and Web content.

A CMS may support the following features:

  • identification of all key users and their content management roles;
  • the ability to assign roles and responsibilities to different content categories or types;
  • definition of workflow tasks for collaborative creation, often coupled with event messaging so that content managers are alerted to changes in content (For example, a content creator submits a story, which is published only after the copy editor revises it and the editor-in-chief approves it.);
  • the ability to track and manage multiple versions of a single instance of content;
  • the ability to capture content (e.g. scanning);
  • the ability to publish the content to a repository to support access to the content (Increasingly, the repository is an inherent part of the system, and incorporates enterprise search and retrieval.);
  • separation of content's semantic layer from its layout (For example, the CMS may automatically set the color, fonts, or emphasis of text.).

There are three main categories of CMS, with their respective domains of use:

Contents

[hide]

Enterprise content management systems

An enterprise content management (ECM) system is concerned with content and documents related to the organizational processes of an enterprise. The purpose is to manage the organization's unstructured information content, with all its diversity of format and location.

 Web content management systems

A web content management (WCM) system is a CMS designed to simplify the publication of Web content to Web sites, in particular allowing content creators to submit content without requiring technical knowledge of HTML or the uploading of files.

Component content management systems

A component content management (CCM) system is concerned with the content within documents. It can locate and link content at any level of organization, and it is used to build publications out of re-usable fragments of content. Whereas ECM and WCM systems frequently manage unstructured content (word processor and other desktop publishing files, rendered PDF and HTML, etc.), a CCM system manages structured content (usually XML), from which such documents are rendered and typically delivered to ECM and WCM systems.

See also

References

External links

저작물 관리 시스템은 저작물 관리에 사용하는 소프트웨어이다. 여기에서 지칭하는 저작물이란 사진, 음성, 전자문서 그와 유사한 컴퓨터 파일이다.

저작물 관리 시스템의 아이디어의 이면에는 웹을 통하는 것과 마찬가지로 임의의 장소에서도 콘텐츠 파일들을 관리하자는 의도이다. 저작물 관리 시스템은 기존문서 관리에도 종종 사용된다.

많은 회사들은 저작물 관리 시스템을 비지적재산권 형식으로 파일을 저장하는 데 사용한다. 사내에서 대개 서버 기반형 소프트웨어를 사용하는 것과 같이 파일을 쉽게 공유할 수 있고 더 나아가 파일의 가용성을 증대시킨다.

아래에 보인 것과 같이 다수의 저작물 관리 시스템은 웹 저작물 관리기능을 가지고 있으며 그 중에 일부는 워크플로우 처리에 대한 특징들을 가지고 있다.

워크플로우(영어: Workflow)는 다자간 결제 혹은 저작물의 추가를 위한 전자문서 이동의 한 방법이다.

일부 저작물 관리 시스템에서는 이메일 통지 및 자동 경로 설정과 같은 워크플로우 도구들을 가지고 있다. 이들은 작업의 협업성에 있어 매우 이상적인 것이다.

또한, 저작물 관리 시스템의 도구들은 이미지와 멀티미디어 자료들 혹은 방대한 문서들을 조직하고, 통제하고, 그리고 발행하는 역할을 한다.

웹 저작물 관리 시스템은 웹 사이트에 웹 저작물들을 발행하는데 필요한 작업들을 수월하게 해준다. 웹 저작물 관리 시스템은 다음과 같은 문서들에 대한 저장, 통제, 개정, 그리고 발행에 자주 사용된다.

  • 뉴스기사
  • 사용자 매뉴얼
  • 기술 문서
  • 판매 가이드
  • 광고 책자

또한, 저작물 관리 시스템은 다음과 같은 특징을 지니고 있다.

  • 멀티미디어 자료 혹은 일반문서의 생성 및 가져오기
  • 핵심 사용자 및 그들의 콘텐츠 관리 권한에 대한 인증
  • 역할의 배정에 관한 권한 및 이종의 콘텐츠 타입이나 카테고리 분류
  • 저작물 워크플로우 작업에 대한 정의 (관리자에게 저작물 변경 통지와 같은 이벤트 알림과 연관되어)
  • 단일 저작물에 대한 다중 판올림 관리 및 추적

2008년 12월 10일 수요일

JAVA의 Collections.sort


리스트를 분류하고 섞기 위한 COLLECTIONS의 메소드 이용하기

java.util.Collections 클래스에는 List 인터페이스를 구현하는 객체를 실행하기 위해 만들어진 메소드들이 많이 있다. 이 유틸리티 클래스에는 특정 객체 리스트에서의 이진법적 검색, 한 리스트에서 다른 리스트로의 요소(elements) 복사, 리스트의 특정 위치로의 요소 변환 등을 실행하는 메소드가 포함되어 있다. 이번 테크팁에서는 Collections 클래스 중 shuffle()과 rotate()라는 두가지 간단한 메소드와, 리스트 안의 아이템들을 분류하는 sort() 메소드에 대해 알아본다. 아이템의 분류를 위해선 반드시 대상을 비교할 방법이 있어야 하는데, 여기서는 Comparable 과 Comparator를 사용하게 될 것이다.

다음 예제를 보자. 0부터 5까지 쓰여진 6장의 카드 한 벌이 있다. 처음 카드 상태를 보여주고 카드를2개씩 옮기게 되는데, 이것은 즉 rotate(2)를 호출하는 것이다. 이는 맨 밑의 카드 두 장을 맨 위로 올려놓게 된다. n개의 카드가 있을 때 n보다 작은 수 r개의 카드를 옮긴다. 결과적으로 n-r개의 카드를 맨 위에서 맨 밑으로 보내는 셈이 된다.

rotate(-r)를 대신 호출하면 r개의 카드를 맨 위에서 맨 아래로 옮기게 된다. 이것은 r개의 카드를 반대로 옮긴다고 생각하거나, rotate(n-r)이 rotate(-r)과 같다고 여길 수 있다.

이 때, 카드를 섞으면 다른 결과를 얻게 된다. Collections 클래스의 자바다큐멘테이션을 보면, 동일한 가능성을 갖는 모든 가능한 순열을 생성해내는 것으로 카드를 섞는 알고리즘을 설명하고 있다.

다음은 한 벌의 카드를 초기화하고 섞는 간단한 프로그램이다.

   import java.util.List;
   import java.util.ArrayList;
   import java.util.Collections;

   public class CutAndShuffle {   

     private static List miniDeck = new ArrayList(6);

     private static void initializeDeck() {
       for (int i = 0; i < 6; i++) {
         miniDeck.add(new Integer(i));
       }
     }

     private static void printDeck(String message) {
       System.out.println(message + "\n");
       for (int i = 0; i < 6; i++) {
         System.out.println("card " + i +
                            " = " + miniDeck.get(i));
       }
       System.out.println("============");
     }

     public static void main(String[] args) {
       initializeDeck();
       printDeck("Initialized Deck:");
       Collections.rotate(miniDeck, 2);
       printDeck("Deck rotated by 2:");
       Collections.rotate(miniDeck, -2);
       printDeck("Deck rotated back by 2:");
       Collections.shuffle(miniDeck);
       printDeck("Deck Shuffled:");
     }
   }

CutAndShuffle 프로그램을 실행하면 다음과 유사한 결과를 얻을 것이다. 앞에서 말해듯이, shuffle() 명령어를 호출한 결과는 다를 것이다.

 Initialized Deck:

   card 0 = 0
   card 1 = 1
   card 2 = 2
   card 3 = 3
   card 4 = 4
   card 5 = 5
   ============
   Deck rotated by 2:

   card 0 = 4
   card 1 = 5
   card 2 = 0
   card 3 = 1
   card 4 = 2
   card 5 = 3
   ============
   Deck rotated back by 2:

   card 0 = 0
   card 1 = 1
   card 2 = 2
   card 3 = 3
   card 4 = 4
   card 5 = 5
   ============
   Deck Shuffled:

   card 0 = 1
   card 1 = 2
   card 2 = 4
   card 3 = 5
   card 4 = 0
   card 5 = 3
   ============

Collections의 shuffle()과 rotate() 메소드는 요소들을 서로 비교할 수 있는지 없는지의 가능성 여부와 관계가 없다. 그러나 분류는 요소들을 비교하는 방법이 필요하다. sort() 메소드를 구현하기 위해서는 먼저 서로 다른 두개의 객체 중 누가 우선순위인지를 판단해야한다. 이를 위해선 다음과 같은 두가지 방법이 있다.

  • 분류된 대상의 타입이 java.lang.Comparable 인터페이스를 구현하는지 확인한다. 이를 위해선 다음과 같은 메소드가 들어있는 클래스가 필요하다.
         public int compareTo(Object  o)
    
  • Comparator를 실행하는 클래스를 만들고 특정 타입의 두 대상을 비교하는 규칙을 캡슐화한다.

첫번째로, o1과 o2가 Comparable를 구현하는 클래스라면, 두 대상이 동일할 때 o1.compareTo(o2)는 0이 된다. o1이 o2보다 작으면 음수가 되고 o2보다 크면 양수가 된다. 이 때, 숫자의 값보다 음수인지 양수인지가 중요하다. 따라서 o1.compareTo(o2)와 o2.compareTo(o1)의 부호는 서로 달라야 한다. 세 개의 객체가 있을 때에도 compareTo()는 기본적인 이행성 규칙을 따른다. 다시 말해 o1.compareTo(o2)>0 이고 o2.compareTo(o3)>0이면, o1.compareTo(o3)>0이다.

두번째 접근법은 Integer 클래스를 포함하고 Comparable 인터페이스를 실행하는 래퍼 클래스의 장점을 이용하는 것이다. 결과적으로 Collections.sort() 메소드를 사용하여 Integer타입의 클래스들을 포함한 List를 쉽게 분류할 수 있다. 그 예는 다음과 같다.

   import java.util.List;
   import java.util.ArrayList;
   import java.util.Collections;

   public class ShuffleAndSort {
     List miniDeck = new ArrayList(6);

     void initializeDeck() {
      for (int i = 0; i < 6; i++) {
         createCard(i);
       }
     }

     void printDeck(String message) {
       System.out.println(message);
       for (int i = 0; i < 6; i++) {
         System.out.println("card " + i +
           " = " + miniDeck.get(i));
       }
       System.out.println("============");
     }

     void createCard(int i) {
       miniDeck.add(new Integer(i));
     }

     void sort(){
       Collections.sort(miniDeck);
     }


     void exerciseDeck() {
       initializeDeck();
       Collections.shuffle(miniDeck);
       printDeck("Deck Shuffled:");
       sort();
       printDeck("Deck Sorted:");
     }


     public static void main(String[] args) {
       new ShuffleAndSort().exerciseDeck();
     }
   }

ShuffleAndCode 프로그램은 초과된 메소드를 갖는 것처럼 보인다. 물론 createCard()과 sort()는 반드시 포함되어야 한다. 각각의 메소드들을 분리해서 만드는 것은 ShuffleAndSort 클래스를 상속하기 쉽게 만들어준다. 어떤 객체들이 카드들에 추가되고, 카드들이 어떻게 분류되는지 수정하기 위해서는 이러한 메소드들을 오버라이딩함으로써 가능하게 된다. 이 경우 카드들이 섞이는 결과를 얻는다. 그런 다음 Collections.sort(miniDeck) 라고 불리는 메소드를 호출하여 카드를 분류할 수 있다.

ShuffleAndSort에서 나온 결과물은 반드시 다음과 같아야한다.

   Deck Shuffled:
   card 0 = 4
   card 1 = 2
   card 2 = 1
   card 3 = 3
   card 4 = 5
   card 5 = 0
   ============
   Deck Sorted:
   card 0 = 0
   card 1 = 1
   card 2 = 2
   card 3 = 3
   card 4 = 4
   card 5 = 5
   ============ 

Integer 클래스가 Comparable 인터페이스를 구현하므로 miniDeck은 자체적으로 분류가 가능하다. 또한, sort() 메소드의 다른 구현을 통해 다르게 분류할 수도 있다. 이 다른 구현은 List를 첫번째 인수로써 분류하고 Comparator을 두 번째 객체로 분류한다.ShuffleAndSort의 sort() 메소드를 다음과 같이 바꿀 수 있다.

   void sort(){
       Collections.sort(miniDeck,
                        Collections.reverseOrder());
   }  

이번에는 서열이 높은 카드부터 낮은 카드까지 분류해보자.

Comparable를 구현하지 않는 타입의 객체들을 분류하는 것을 상상해보자. 예를 들어 다음과 같은 Rank 클래스를 만들자.

public class Rank {

     private int rank;

     public Rank(int rank) {
       this.rank = rank;
     }

     public int getRankInt(){
       return rank;
     }

     public String toString() {
       return "" + rank;
     }
   }

이제 ShuffleAndSort에서 상속되고, Rank 타입의 객체들을 생성하는 클래스를 만들자. 이는 다음과 같다.

   public class UseRank extends ShuffleAndSort {

     void createCard(int i) {
       miniDeck.add(new Rank(i));
     }

     public static void main(String[] args) {
       new UseRank().exerciseDeck();
     }
   }

UseRank을 컴파일하고 실행하면 ClassCastException이 발생한다. 이 문제를 해결하는 데에는 두가지 방법이 있다.

  • Comparable 버전의 Rank를 만든다.
  • int타입으로 랭크를 축적하고 직접 compareTo()를 구현한다.

첫번째 해결책은 Integer클래스가 Comparable을 실행한다는 사실을 이용하여 실제 랭크를 int가 아닌 Integer로 축적하는 것이다. 그러면 compareTo() 메소드가 Integer 클래스의 compareTo()를 불러온다. 이 접근법은 다음과 같다.

   public class ComparableRank implements Comparable {

     private Integer rank;

     public ComparableRank(int rank) {
       this.rank = new Integer(rank);
     }

     public String toString() {
       return "" + rank;
     }

     public int compareTo(Object o) {
       ComparableRank cr = (ComparableRank) o;
       return (rank.compareTo(cr.rank));
     }
   }

두번째 해결책으로는, int로 랭크를 저장하고 다음과 같이 compareTo()를 구현하는 방법이 있다.

   public int compareTo(Object o) {
     ComparableRank2 cr = (ComparableRank2) o;
     return (rank - cr.rank);
   }

이 팁의 목적은 rank와 cr.rank 간의 차이를 잘 돌려준다는 것을 보여주는 것이다. rank의 애트리뷰트는 음수가 아니며, 카드의 사이즈가 231-1보다 작아야만 한다. 다음은 compareTo()를 구현하는 좀 더 심화된 예이다.

  public int compareTo(Object o) {
     ComparableRank2 cr = (ComparableRank2) o;
     if (rank < cr.rank) return -1;
     else return 1;
   }

이것으로 한 벌의 카드 안에서 rank 애트리뷰트의 값이 고유하다는 것을 추측해 볼 수 있다. 이와 같은 비교를 확장하여 동일성도 확인해볼 수 있게 만들 수 있다. 즉 이런 경우에는 0을 리턴시키면 된다.

두 가지 접근법에서 모두 toString()을 오버라이딩함으로써 앞에서 제시했던 예시들의 포맷과 맞는 결과를 얻을 수 있도록 해야한다. 다음은 두번째 접근법에 대한 예시이다. :

   public class ComparableRank2 implements Comparable {

     private int rank;

     public ComparableRank2(int rank) {
       this.rank = rank;
     }

     public String toString() {
       return "" + rank;
     }

     public int compareTo(Object o) {
      ComparableRank2 cr = (ComparableRank2) o;
       return (rank - cr.rank);
     }
   }

이 버전들은 createCard() 메소드의 내용을 변경하여 ComparableRank타입 또는 ComparableRank2타입의 객체를 로딩함으로써 구동될 수 있다.

두 개의 동등하지 않은 대상물이 최우선적으로 분류되도록 했던 두 번째 방법을 되새겨보자. Comparator를 실행하는 클래스를 만들고 특정 타입의 두 대상을 비교하기위한 규칙을 캡슐화한다. Rank타입의 객체를 가지고 이 방법을 사용해보자. 먼저 그 타입의 객체들을 만든다. 그리고 나서 객체들을 분류하기 위해 사용하는 Comparator를 만든다. 이 솔루션은 변환할 수 없거나 하위 분류하기 어려운 클래스들을 사용하는데 유용하다. 다음에 제시되는 RankComparator 클래스의 compareTo() 메소드는 ComparableRank2의 Comparator 메소드처럼 필수적이다. 이 compare() 클래스에서 지원하는 객체타입으로 캐스팅한 후 비교로직을 구현해라.

   import java.util.Comparator;

   public class RankComparator implements Comparator {
     public int compare(Object o1, Object o2) {
       Rank r1 = (Rank) o1;
       Rank r2 = (Rank) o2;
       return r1.getRankInt() - r2.getRankInt();
     }
   }

이 새로운 RankComparator를 사용하기 위해서는 UseRank의 sort() 메소드를 오버라이딩하고 두 번째 인수로 RankComparator를 패스하기만 하면 된다.

   import java.util.Collections;

   public class UseRankComparator extends UseRank {

     void sort() {
       Collections.sort(miniDeck, new RankComparator());
     }

     public static void main(String[] args) {
       new UseRankComparator().exerciseDeck();
     }
   }

UseRankComparator의 결과는 다음과 같아야한다.

   Deck Shuffled:
   card 0 = 0
   card 1 = 4
   card 2 = 5
   card 3 = 3
   card 4 = 1
   card 5 = 2
   ============
   Deck Sorted:
   card 0 = 0
   card 1 = 1
   card 2 = 2
   card 3 = 3
   card 4 = 4
   card 5 = 5
   ============   

List를 분류하기 위해서는 그 리스트 안의 아이템들이 반드시 비교될 수 있어야 한다는 사실을 배웠다. Comparable를 구현하는 타입의 아이템들을 사용할 수 있다. 다행히, 이것은 단순한 타입들을 랩핑하는 것을 포함한다. 두번째 접근법은 Comparator를 실행하는 클래스를 만들고 특정타입의 두 대상을 비교하기 위한 규칙을 캡슐화한다.

Collections에 대한 좀 더 많은 정보를 얻으려면, 자바 튜토이얼의 Collections trail를 참고하기 바란다.

2008년 11월 13일 목요일

XML의 소개

XML의 소개

Extensible Markup Language (XML)은 웹 브라우져를 통해 한 페이지의 내용 요소들을 구분하는 방법입니다. XML의 문법은 HTML과 비슷합니다. 사실, XML은 HTML이 쓰이는 여러 곳에서 사용될 수 있습니다. 아래에 예가 있습니다. JDC Tech Tip의 목차가 HTML이 아니라 XML로 저장되어 있다고 가정합시다. 아래와 같은 HTML 코드 대신

<html>
<body>
<h1>JDC Tech Tip Index</h1>
<ol><li>
<a
href="http://developer.java.sun.com/developer/TechTips/2000/tt0509.html#tip1">
Random Access for Files
</a>
</li></ol>
</body>
</html>

아래와 같이 나타납니다.

<?xml version="1.0" encoding="UTF-8"?>
<tips>
<author id="glen" fullName="Glen McCluskey"/>
<tip title="Random Access for Files"
     author="glen"
     htmlURL="http://developer.java.sun.com/developer/TechTips/2000/tt0509.html#tip1"
     textURL="http://developer.java.sun.com/developer/TechTips/txtarchive/May00_GlenM.txt">
</tip>
</tips>

XML과 HTML의 코드 사이의 유사성을 보십시오. 두가지 모두 문서가 계층적 엘리먼트로 구성되어 있으며, 얼레먼트는 꺾음 괄호로 구분되어 있습니다.HTML 엘리먼트가 거의 그렇듯이, XML 엘리먼트는 시작 태그와 그 뒤에 나타나는 데이터, 그리고 끝 태그로 구성되어 있습니다.

<element>element data</element>

역시 HTML과 마찬가지로, XML 엘리먼트는 속성을 지정할 수 있습니다. 위의 XML의 예에서, 각 <tip> 엘리먼트는 몇개의 속성을 갖고 있습니다. 'title' 속성은 팁의 이름을 나타내며, 'author' 속성은 저자의 이름을 나타냅니다. 그리고 'htmlURL'과 'textURL' 속성은 두가지의 다른 형식으로 저장된 팁의 링크를 나타냅니다.

두개의 마크업 언어 사이의 유사점은 HTML에서 XML로 옮겨가는데 아주 중요한 장점입니다. 어렵게 습득한 HTML 기술들을 그대로 이용할 수 있기 때문입니다. 그러나, "왜 XML로 바꾸라고 해서 고생하게 만들지?"라는 의문이 들것입니다. 이 의문에 대한 대답으로, 위의 XML 예를 다시 한번 보십시오. 그리고 문법적인 측면이 아니라 의미적인 측면에서 살펴보십시오. HTML은 문서를 어떻게 형식화하는지를 말해주지만, XML은 문서의 내용에 대해 말해줍니다. 이 능력은 매우 강력한 것입니다. XML에서, 클라이언트는 그 데이터를 자신에게 가장 적합한 형식으로 재구성할 수 있습니다. 서버가 제공하는 출력 양식에 구애받지 않습니다. 중요한것은, XML 형식은 가독성을 희생시키지 않으면서도 파서의 편의를 위해 설계되었다는 것입니다. XML은 문서의 구조에 대해 반드시 지켜야 할 약속을 가지고 있습니다. 몇가지를 보자면, 시작 태그는 언제나 끝 태그를 가지고 있어야 하며, 엘리먼트는 정확히 내포관계를 가져야 하고, 모든 속성은 값을 가져야 합니다. 이런 엄격함은 XML 문서의 파싱과 변환을 HTML의 변환에 비해서 높은 신뢰성을 갖게 합니다.

XML과 HTML의 유사성은 그 시작이 갖기 때문에 생깁니다. HTML은 강력한 마크업 언어인 SGML을 간략화한 것입니다. SGML은 개발자가 자신만의 어휘, 즉 문법/태그 등을 만들어낼 수도 있는, 거의 무엇이든지 할 수 있는 "주방의 싱크대"같은 마크업 언어입니다. HTML은 미리 정의된 어휘를 가진, SGML에 대한 아주 작은 부분입니다. 따라서 HTML은 대략 1992년 경에나 쓸만했던, 표현을 위한 요소들입니다. SGML과 HTML은 모두 문제가 있습니다. SGML은 모든것을 합니다. 그래서 아주 복잡합니다. HTML은 간단합니다. 그러나 파싱 규칙이 엉성하고, 어휘는 확장을 할 수 없습니다. 이에 비해 XML은 SGML을 합리적으로 간략화했습니다. 이것의 목적은 복잡하지 않으면서도 SGML의 중요한 목적을 모두 지원합니다. SGML이 "주방의 싱크대"라면 XML은 "스위스 아미 나이프-맥가이버칼... -_-" 입니다.

이런 장점 때문에, XML은 몇가지 응용 분야에서는 HTML을 대체하는것 이상의 일을 할 수 있습니다. 이것은 SGML을 대체할 수도 있으며, SGML의 복잡성 때문에 적용이 불가능했던 분야에 대한 새로운 방법이 될 수 있습니다. XML을 어디에 쓸 것인지에 관계없이, 프로그래밍 언어는 자바를 이용할 것입니다. 자바 언어는 Simple API for XML (SAX)와 Document Object Model (DOM) 인터페이스를 이용하여 XML 문서를 파싱하기 위한 고수준의 도구를 제공하기 때문에, XML을 직접 파싱하기 위한 자신만의 코드를 작성할 수 있습니다. SAX와 DOM 파서는 다른 몇가지 언어에서도 구현한 표준입니다. 자바 프로그래밍 언어에서, Java(tm) API for XML Parsing (JAXP)를 이용해서 이런 파서들을 인스턴스화 할 수 있습니다.

이 팁의 코드를 실행하려면, http://java.sun.com/xml/download.html에서 JAXP와 SAX/DOM 파서를 다운로드 받아야 합니다. 그리고 http://www.megginson.com/SAX/Java에서 SAX 2.0도 다운로드 받아야 합니다. 그리고 classpath를 jaxp, 파서, 그리고 sax2의 JAR 파일을 포함하도록 고치는 것을 잊지 마십시오.

 


 

 SAX API의 이용

SAX API는 XML 문서를 다루기 위한 직력 메커니즘을 제공합니다. 이것은 XML-DEV 메일링 리스트의 멤버들에 의해 서로 다른 벤더들이 구현할 수 있는 표준 인터페이스의 집합으로 개발되었습니다. SAX 모델은 파서가 문서를 읽어나가면서 마크업을 만나면 이벤트 핸들러를 호출하는 형식으로 파싱하도록 합니다. SAX의 첫번째 구현은 1998년 5월에 나왔습니다. 그리고 SAX 2.0은 2000년 5월에 나왔습니다. (이 팁에서 사용된 코드들은 SAX2용입니다.)

마크업 발생에 대한 신호를 위해 SAX2를 사용할 때 할 일은, 몇개의 메소드와 인터페이스를 작성하는 것입니다. 이 인터페이스들에서 ContentHandler 인터페이스가 가장 중요합니다. 이것은 XML 문서를 파싱하기 위한 각각의 단계들을 위한 몇개의 메소드를 선언합니다. 많은 경우, 이런 메소드 중에서 몇개만 이용하게 됩니다. 예를 들어, 아래의 코드는 하나의 ContentHandler 메소드 (startElement)만을 이용하며, 이것을 이용하여 XML Tech Tip 목록에서 HTML 페이지를 생성합니다.

    import java.io.*;
    import java.net.*;
    import java.util.*;
    import javax.xml.parsers.*;
    import org.xml.sax.*;
    import org.xml.sax.helpers.*;
    /**
     * 팁의 제목들의 목록을 보여주고 이에 대한 HTML과 text 버젼의 문서에
     * 연결하는 간단한 HTML 페이지를 생성한다.
     */
    public class UseSAX2 extends DefaultHandler
    {
        StringBuffer htmlOut;

        public String toString()
        {
            if (htmlOut != null)
                return htmlOut.toString();
            return super.toString();    
        }
    
        public void startElement(String namespace, String localName, String qName, Attributes atts)
        {
            if (localName.equals("tip")) 
            {
                String title = atts.getValue("title");
                String html = atts.getValue("htmlURL");
                String text = atts.getValue("textURL");
                htmlOut.append("<br>");
                htmlOut.append("<A HREF=");
                htmlOut.append(html);
                htmlOut.append(">HTML</A> <A HREF=");
                htmlOut.append(text);
                htmlOut.append(">TEXT</A> ");
                htmlOut.append(title);
            }
        }

        public void processWithSAX(String urlString) throws Exception
        {
            System.out.println("Processing URL " + urlString);
            htmlOut = new StringBuffer("<HTML><BODY><H1>JDC Tech Tips Archive</H1>");
            SAXParserFactory spf = SAXParserFactory.newInstance();
            SAXParser sp = spf.newSAXParser();
            ParserAdapter pa = new ParserAdapter(sp.getParser());
            pa.setContentHandler(this);
            pa.parse(urlString);
            htmlOut.append("</BODY></HTML>");
        }

        public static void main(String[] args)
        {
            try
            {
                UseSAX2 us = new UseSAX2();
                us.processWithSAX(args[0]);
                String output = us.toString();
                System.out.println("Saving result to " + args[1]);
                FileWriter fw = new FileWriter(args[1]);
                fw.write(output, 0, output.length());
                fw.flush();
            }
            catch (Throwable t)
            {
                t.printStackTrace();
            }
        }
    }

이 프로그램을 테스트하기 위해, 이 팁의 앞부분의 XML의 소개에 있는 XML 문서를 이용하거나, http://staff.develop.com/halloway/TechTips/TechTipArchive.xml에 있는 조금 더 긴 문서를 이용할 수 있습니다. 이렇게 받은 XML 문서를 컴퓨터의 로컬 디렉토리에 TechtipArchive.xml로 저장하십시오. 아래와 같은 명령어로 HTML 문서를 생성할 수 있습니다.

    java UseSAX2 file:TechTipArchive.xml SimpleList.html

그리고 SimpleList.html을 웹브라우져를 통해서 보고, 최근 팁의 텍스트 버젼과 HTML 버젼의 링크를 따라가 보십시오. (실제 시나리오는 위의 코드를 클라이언트의 브라우져나 서버의 서블릿/jsp 안에 합쳐 넣어야 할것입니다.)

위 코드에는 몇가지 흥미로운 부분이 있습니다. 파서를 생성하는 부분을 보십시오.

    SAXParserFactory spf = SAXParserFactory.newInstance();
    SAXParser sp = spf.newSAXParser();

JAXP에서, SAXParser 클래스는 직접 생성하지 않고, factory 메소드인 newSAXParser()를 이용합니다. 이것은 서로 다르게 구현된 코드들이 소스코드의 변경 없이 끼워넣기가 가능하도록 하기 위한 것입니다. factory는 이 외에도 namespace의 지원, 인증 등의 향상된 파싱 요소들을 지원합니다.JAXP 파서의 인스턴스를 가진 뒤에라도, 아직은 파싱할 준비가 되지 않았습니다. 현재의 JAXP 파서는 SAX 1.0만 지원합니다. SAX 2.0을 지원하도록 하기 위해서, 파서를 ParserAdapter로 감싸야 합니다.

    ParserAdapter pa = new ParserAdapter(sp.getParser());

ParserAdapter 클래스는 SAX2 다운로드의 일부분이며, 지금 있는 SAX1 파서에 SAX2의 기능을 추가합니다.

ContentHandler 인터페이스를 구현하는 대신, UseSAX는 DefaultHandler 클래스를 상속받습니다. DefaultHandler는 모든 ContentHandler의 메소드에 대해 아무것도 하지 않는 비어있는 구현을 제공하는 어댑터 클래스입니다. 따라서 오버라이드 하고싶은 메소드만 구현하면 됩니다.

startElement() 메소드가 실제 작업을 수행합니다. 프로그램은 팁을 제목 목록을 보여주면 되기 때문에, <tip> 엘리먼트만 있으면 되며, <tips>, <author> 엘리먼트는 무시합니다. startElement 메소드는 엘리먼트의 이름을 체크하고 현재 엘리먼트가 <tip>일 때에만 계속해서 작업을 수행합니다. 또 이 메소드는 엘리먼트의 속성을 Attributes 레퍼런스를 통해 접근하도록 합니다. 따라서 팁의 이름과 name, htmlURL, textURL을 추출하는 것이 쉽습니다.

이 예의 결과는 최근의 Tech Tips의 목록을 보여주는 HTML 문서입니다. HTML을 직접 만들어도 됩니다. 하지만 이것을 XML로 하고, SAX 코드를 작성하면 추가적인 유연성을 제공할 수 있습니다. 다른 누군가 Tech Tip을 날짜별로, 저자별로 정렬하거나 몇가지 제약사항을 가지고 필터링을 하고자 한다면, 각각에 대한 파싱 코드를 이용하여 하나의 XML 문서에서 해당 문서를 생성할 수 있습니다.

불행히도, XML 데이터가 복잡해짐에 따라, 위의 샘플도 점점 코딩하고 수정하기가 어려워집니다. 위의 예는 두가지 문제를 가지고 있습니다. 첫번째는, HTML 문서를 생성하기 위한 코드가 단지 문자열을 조작하는 것 뿐이고, 따라서 어딘가에 있을 '>', '/'를 놓지게 된다는 것입니다. 두번째로, SAX API는 많은 것을 기억하지 못합니다. 즉, 앞의 어딘가에 있었던 엘리먼트를 참조하고자 한다면, 앞에서 파싱한 엘리먼트를 저장하기 위한 자신만의 state machine을 만들어야 한다는 뜻입니다.

Document Object Model (DOM) API가 이 두가지 문제점을 해결합니다.

 


 

DOM API의 이용

DOM API는 SAX API와는 완전히 다른 문서 처리 모델을 기반으로 하고 있습니다. (SAX와 같이) 한번에 문서의 한 부분을 읽는 대신, DOM 파서는 문서 전체를 읽습니다. 그리고 읽고 수정할 수 있는 프로그램 코드를 만들 수 있도록 문서 전체를 트리 구조로 만듭니다. SAX와 DOM의 차이점을 간단히 말하자면, 순차적/읽기 전용 접근과 임의/읽기/쓰기 접근의 차이입니다.

DOM API의 핵심은 Document와 Node 인터페이스입니다. Document는 XML 문서를 나타내는 최상위 객체입니다. Document는 데이터를 Node의 트리 형태로 저장하고 있으며, Node는 element, attribute, 또는 다른 형태의 내용물을 저장하는 기본 타입입니다. 또, Document는 새로운 Node를 생성하기 위한 factory로도 동작합니다. Node는 트리에서의 하나의 데이터를 표현하며, 트리에 있어서의 필요한 동작을 제공합니다. 노드에 대해 그 부모/형제/자식 노드를 찾을 수 있습니다. 또 Node를 추가/삭제함으로써 문서를 수정할 수 있습니다.

DOM API의 예제를 보기 위해, 위의 SAX에서 보여졌던 XML 문서를 처리해 봅시다. 이번에는 저자별로 묶어서 출력합니다. 이것은 몇가지의 작업을 더 해야 합니다. 아래에 코드가 있습니다.

    //UseDOM.java
    import java.io.*;
    import java.net.*;
    import java.util.*;
    import javax.xml.parsers.*;
    import org.w3c.dom.*;

    public class UseDOM
    {
        private Document outputDoc;
        private Element body;
        private Element html;

        private HashMap authors = new HashMap ();
    
        public String toString ()
        {
            if (html != null)
            {
                return html.toString ();
            }
            return super.toString();
        }

        public void processWithDOM (String urlString)
            throws Exception
        {
            System.out.println ("Processing URL " + urlString);
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance ();
            DocumentBuilder db = dbf.newDocumentBuilder ();
            Document doc = db.parse (urlString);
            Element elem = doc.getDocumentElement ();
            NodeList nl = elem.getElementsByTagName ("author");
            for (int n=0; n<nl.getLength (); n++)
            {
                Element author = (Element)nl.item (n);
                String id = author.getAttribute ("id");
                String fullName = author.getAttribute ("fullName");
                Element h2 = outputDoc.createElement ("H2");
                body.appendChild (h2);
                h2.appendChild (outputDoc.createTextNode ("by " + fullName));
                Element list = outputDoc.createElement ("OL");
                body.appendChild (list);
                authors.put (id, list);
            }
            NodeList nlTips = elem.getElementsByTagName ("tip");
            for (int i=0; <nlTips.getLength (); i++)
            {
                Element tip = (Element)nlTips.item (i);
                String title = tip.getAttribute ("title");
                String htmlURL = tip.getAttribute ("htmlURL");
                String author = tip.getAttribute ("author");
                Node list = (Node) authors.get (author);
                Node item = list.appendChild (outputDoc.createElement ("LI"));
                Element a = outputDoc.createElement ("A");
                item.appendChild (a);
                a.appendChild(outputDoc.createTextNode (title));
                a.setAttribute ("HREF", htmlURL);
            }
        }

        public void createHTMLDoc (String heading)
            throws ParserConfigurationException  
        {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance ();
            DocumentBuilder db = dbf.newDocumentBuilder ();
            outputDoc = db.newDocument ();
            html = outputDoc.createElement ("HTML");
            outputDoc.appendChild (html);
            body = outputDoc.createElement ("BODY"); 
            html.appendChild (body);
            Element h1 = outputDoc.createElement ("H1");
            body.appendChild (h1);
            h1.appendChild (outputDoc.createTextNode (heading));
        }

        public static void main (String[] args)
        {
            try
            {
                UseDOM ud = new UseDOM ();
                ud.createHTMLDoc ("JDC Tech Tips Archive");
                ud.processWithDOM (args[0]);
                String htmlOut = ud.toString ();
                System.out.println ("Saving result to " + args[1]);
                FileWriter fw = new FileWriter (args[1]);
                fw.write (htmlOut, 0, htmlOut.length ());
                fw.flush ();
            }
            catch (Throwable t)
            {
                t.printStackTrace ();
            }
        }
    }

XML을 TechTipArchive.xml로 저장했다고 가정합시다. 아래와 같은 명령어로 코드를 실행할 수 있습니다.

    java UseDOM file:TechTipArchive.xml ListByAuthor.html

그리고 웹브라우져로 ListByAuthor.html을 열어서 저자별로 구성된 팁의 목록을 봅니다.

코드가 동작하는 방법을 알기 위해, createHTMLDoc 메소드부터 살펴봅시다. 이 메소드는 outputDoc Document를 생성합니다. 이것은 나중에 HTML 출력을 만드는데 이용됩니다. SAX와 마찬가지로, 파서는 factory 메소드를 통해 생성됩니다. 그러나 여기서는 factory 메소드가 DocumentBuilderFactory 클래스에 있습니다. createHTMLDoc의 아래쪽 반은 HTML의 기본적인 요소들을 생성합니다.

    outputDoc.appendChild (html);
    body = outputDoc.createElement ("BODY"); 
    html.appendChild (body);
    Element h1 = outputDoc.createElement ("H1");
    body.appendChild (h1);
    h1.appendChild (outputDoc.createTextNode (heading));

이 코드를 SAX 예제에 있는 HTML의 요소를 생성하는 코드와 비교해 보십시오.

    //direct string manipulation from SAX example
    htmlOut = new StringBuffer ("<HTML><BODY><H1>JDC Tech Tips Archive</H1>");

DOM API를 이용하여 문서를 생성하는 것은 문자열을 직접 다루는 것만큼 간단하거나 빠르지 않습니다. 그러나 (특히 큰 문서에 대해서) 에러의 발생이 적습니다.

useDOM 예제에서 가장 중요한 부분은 processWithDOM 메소드입니다. 이 메소드는 두가지 일을 합니다. (1) 저자 엘리먼트를 찾고 이것을 출력으로 제공합니다. (2) 팁을 찾고 저자별로 구분하여 출력으로 제공합니다. 이 단계들은 각각 문서의 최상위 엘리먼트에 접근할 수 있어야 합니다. 이것은 getDocumentElement () 메소드를 통해 수행됩니다. 저자 정보는 <author> 엘리먼트에 들어있습니다. 이런 엘리먼트는 최상위 레벨에서 getElementsByTagName ("author") 메소드를 호출하면 찾을 수 있습니다. getElementsByTagName 메소드는 NodeList를 리턴합니다. 이것은 간단한 Node의 컬렉션입니다. 그리고 각각의 Node는 getAttribute 메소드를 호출하기 위해 Element로 형변환됩니다. getAttribute 메소드는 저자의 아이디와 이름을 입력으로 받습니다. 각각의 저자는 2수준의 머리말로 목록이 만들어집니다. 출력 문서는 저자의 이름을 담고 있는 <H2> 요소를 생성해 냅니다. Node를 추가하기 위해서는 2개의 단계가 필요합니다. 첫번째로 출력 문서에서 createElement와 같은 factory 메소드를 통해 Node를 생성합니다. 다음으로 노드가 appendChild 메소드를 통해 추가됩니다. 노드는 자신을 생성한 문서에만 추가될 수 있습니다.

저자 머리말이 제 위치에 놓이게 되면, 각각의 팁에 링크를 생성할 차례입니다. <tip> 요소를 <author> 엘리먼트를 찾을때와 같은 방법으로 getElementsByTagName을 이용해 찾습니다. 팁 속성을 추출하는 방법도 비슷합니다. 한가지 차이점은 노드를 어디에 추가할 것인가를 결정하는 것입니다. 저자가 다르면 다른 목록에 추가되어야 합니다. 이 작업을 위한 기초 작업으로 저자 엘리먼트를 처리할 때에 <OL> 노드를 추가하고 저자의 아이디로 HashMap 인덱싱을 했습니다. 이제, 팁의 저자 아이디 속성을 팁이 추가될 적당한 <OL> 노드를 찾는데 이용할 수 있습니다.

XML에 대해 더 깊이 다룬 내용을 보시려면, 2000년에 Addison-Wesley에서 출판한 Neil Bradley의 The XML Companion을 참고하십시오. JAXP에 대한 더 자세한 정보는 http://java.sun.com/xml/index.html의 Java(tm) Technology와 XML 페이지를 참고하십시오. SAX2에 대한 더 자세한 정보는 http://www.megginson.com/SAX/index.html을 참고하십시오. DOM 표준은 http://www.w3.org/TR/REC-DOM-Level-1에 있습니다.