티스토리 뷰

OpenBiblio에 Z39.50 서버를 구축하기 위해서 상용 Z39.50 서버를 적용할 수도 있지만
각 도서 관리 시스템의 특성에 적절하게 대응하기 위해서  http://ftp.indexdata.dk/pub/yaz/win32/ 에서 제공하는 YAZ toolkit을 활용하는 방법을 살펴보고자 한다.
  

Z39.50기반 도서 검색 등록 기능 제작 - 오픈소스 OpenBiblio 활용


글 에서 Z39.50 클라이언트의 역할을 할 때는 YAZ toolkit에 있는 DLL 파일들을 이용하는 정도 였지만 
Z39.50 서버를 제작하기 위해서는 YAZ toolkit 설치시 "YAZ runtime" 뿐만아니라 "YAZ Development", "YAZ source"등도 모두 설치한다.
  
OpenBiblio에 Z39.50 서버 붙이기는 개발환경 구축>샘플 서버 테스트>MySQL 클라이언트 테스트>Openbiblio용 Z39.50서버 개발의 단계로 진행한다.
 

* Z39.50 서버 개발 환경 구축


YAZ 툴킷을 설치한 다음  D:\Program Files\YAZ\win 폴더로 이동하면 makefile을 볼수 있는데, 유닉스나 리눅스 개발자들은 익숙하겠지만 Visual studio(이하 VS) IDE 환경에 익숙한 개발자라면 조금 생소한 개발 환경 일 수도 있다. 그리 복잡하지 않으므로 텍스트 편집기로 makefile을 열어서 어떻게 YAZ toolkit을 빌드하는지 살펴 볼 것을 추천한다.
  
빌드는 nmake.exe, cl.exe, lib.exe, mt.exe, link.exe등을 사용하므로 자신의 환경에 맞도록 PATH 환경 변수 설정과 VS 및 SDK 위치 지정등이 필요하다. 아래는 VS10 버전에 따라 빌드에 필요한 라이브러리 위치 지정과 개발 환경 설정등을 보완한 makefile 내용의 일부이다.
 

SYSINC=/I"C:\Program Files\Microsoft Visual Studio 10.0\VC\include" /I"C:\Program Files\Microsoft SDKs\Windows\v7.0A\Include"

# iconv charcter conversion utility

HAVE_ICONV=1
ICONV_DIR = ..\extlib\iconv-1.9.2.win32

# icu charcter conversion utility
# get icu libraries from http://www.icu-project.org
HAVE_ICU=1
ICU_LIB = ..\extlib\icu\lib
ICU_BIN = ..\extlib\icu\bin
ICU_INCLUDE = ..\extlib\icu\include

# libxslt
HAVE_LIBXSLT=1
LIBXSLT_DIR=..\extlib\libxslt-1.1.26.win32

# libxml2
HAVE_LIBXML2=1
LIBXML2_DIR=..\extlib\libxml2-2.7.6.win32

# zlib compression (used by libxml2)
ZLIB_DIR = ..\extlib\zlib-1.2.3.win32



위의 makefile 내용에서도 알 수 있듯이 빌드에 꼭 필요한 라이브러리들이 있는데 이들은 다음과 같이 다운로드하여 빌드에 적절한 폴더에 배치한다. 예제에서는 D:\Program Files\YAZ\extlib 라는 폴더를 만들어 관련 라이브러리들을 배치해 두었다.
  
  

http://xmlsoft.org/sources/win32/ 에서 4가지 라이브러리를 받을 수 있다.
libxml2-2.7.6.win32.zip 다운로드후 루트 폴더에 압축 해제(libxml2-2.7.6.win32 폴더 아래 lib, include등의 폴더가 있도록. 루트 폴더에서 7zip 팝업 메뉴>Extract here의 방식으로 작업)
libxslt-1.1.26.win32.zip 다운로드후 루트 폴더에 압축 해제
zlib-1.2.3.win32.zip 다운로드후 루트 폴더에 압축 해제
iconv-1.9.2.win32.zip 다운로드후 루트 폴더에 압축 해제
  
http://site.icu-project.org/download/50 에서 최신 버전 라이브러리를 다운로드 한다.
icu4c-50_1_2-Win32-msvc10.zip 다운로드후 루트 폴더에 압축 해제(icu 폴더 아래 lib, include등의 폴더가 있도록. 루트 폴더에서 7zip 팝업 메뉴>Extract here의 방식으로 작업)
ICU와 관련해서도 makefile을 조금 보완해야 하는데 icu 라이브러리 버전에 따라 아래 예제와 같이 파일의 이름을 조정해 주어야 한다.
 

$(BINDIR)\icudt42.dll:
  copy "$(ICU_BIN)\icudt50.dll" $(BINDIR)
$(BINDIR)\icuin42.dll:
  copy "$(ICU_BIN)\icuin50.dll" $(BINDIR)
$(BINDIR)\icuuc42.dll:
  copy "$(ICU_BIN)\icuuc50.dll" $(BINDIR)




 

* 샘플 서버 테스트


YAZ toolkit에서는 yaz-ztest.exe 이란 샘플 서버를 제공한다. 이 샘플 서버는 프로그램에 내장한 배열에서 단순 결과를 보내주는 그야 말로 예제 서버인데, C언어로 되어 있는 이 예제 서버를 도서 관리 시스템의 DB 구조에 맞도록 적절하게 변형해서 운용해 보자는 생각이다. 샘플 서버의 코드는 D:\Program Files\YAZ\ztest에 위치해 있고 ztest.c가 핵심 모듈이다. 빌드는 D:\Program Files\YAZ\win 으로 이동하여 아래의 그림과 같이 "nmake ztest"라고 입력하면 코드를 빌드하여 D:\Program Files\YAZ\bin에 실행 파일을 생성한다.



  

성공적으로 빌드했으면 bin폴더에서 yaz-ztest.exe를 실행시킨다.





yaz-ztest를 실행시키면 위의 그림과 같이 TCP/9999 포트로 Z39.50 서버가 가동중임을 확인할 수 있다.

실제로 예제 서버가 동작하는지를 Mercury Z39.50 Client등으로 검색해 본다. 아래 그림은 Mercury Z39.50 Client에서 예제 서버의 주소와 포트를 입력하고 실제 검색 결과를 확인한 것이다.






위의 테스트 결과는 아래의 그림과 같이 Z39.50 예제 서버의 로그에서도 클라이언트 접속 및 쿼리 결과를 확인 할 수가 있다.





  

* MySQL 클라이언트 테스트


 YAZ toolkit에서 제공하는 yaz-ztest.exe는 프로그램 내부 배열에 MARC 정보를 미리 설정해 놓고 전달되는 클라이언트 요구에 관계없이 배열 내용을 그대로 보내주는 형태로 동작하지만 실제 서비스에서는 MySQL DB로 구성되어 있는 openbiblio DB를 접속하여 Z39.50 클라이언트에서 요구한 정보를 적절하게 검색하여 보내 주어야 하므로 C언어 기반의 yaz-ztest에 MySQL 클라이언트로 접속하는 것이 필수적이다.
  
  다른 포스팅에서 기반 시스템으로 선택한 XAMPP에 기본적으로 설치되는 MySQL에는 관련 라이브러리도 함께 배포 되고 있으므로 이것을 활용하여 MySQL 클라이언트 테스트를 진행하고자 한다. 
 

# Ztest
{$(ZTESTDIR)}.c{$(OBJDIR)}.obj:
$(CPP) $(COPT) -I"C:\xampp\mysql\include" $< /D"_CONSOLE"
......

$(ZTEST) : "$(BINDIR)" $(ZTEST_OBJS) $(YAZ_DLL)
$(LINK_PROGRAM) $(ZTEST_OBJS) "C:\xampp\mysql\lib\libmysql.lib" /out:$@
$(MT) -manifest $@.manifest -outputresource:$@;1


  
위의 makefile 예제와 같이 ztest 관련 코드들의 컴파일과 링크 과정에 mysql관련 헤더 파일과 라이브러리가 적용될 수 있도록 보완해주고 yaz-ztest.exe가 생성되는 bin 디렉토리에 libmysql.dll 파일을 복사해 두면 개발 및 테스트 환경은 준비가 끝난 것이다.
  
메인 코드인 ztest.c를 텍스트 편집기로 열어서 프로그램 시작 부분에 DB 연결을 검사하는 코드를 아래와 같이 삽입한다. 본 예제는 MySQL 클라이언트 테스트에 주안점을 두고 있으므로 DB 접속과 더불어 목록 테이블을 조회하는 코드도 추가했다.
 

#include <mysql.h>
......

MYSQL *conn;
void init_db()
{
    MYSQL_RES *result;
    MYSQL_ROW row;
    int num_fields, i;
    
    conn = mysql_init(NULL);

    if (conn == NULL) {
        printf("DB Init error %u: %s\n", mysql_errno(conn), mysql_error(conn));
        exit(1);
    }

    if (mysql_real_connect(conn, "192.168.123.115", "ro**", "d**", 
            "openbiblio", 0, NULL, 0) == NULL) {
        printf("DB connect error %u: %s\n", mysql_errno(conn), mysql_error(conn));
        exit(1);
    }
    
    mysql_query(conn, "SELECT * FROM biblio LIMIT 3");
    result = mysql_store_result(conn);
    num_fields = mysql_num_fields(result);
    while ((row = mysql_fetch_row(result))) {
        for(i = 0; i < num_fields; i++) {
            printf("%s ", row[i] ? row[i] : "NULL");
        }
        printf("\n");
    }
    mysql_free_result(result);
}

int main(int argc, char **argv)
{
    
    init_db();
    return statserv_main(argc, argv, bend_init, bend_close);
}


  
D:\Program Files\YAZ\win에서 nmake ztest로 빌드하면 수정한 코드만 컴파일 및 링크하여 실행 프로그램을 생성한다.


빌드한 프로그램으로 테스트한 결과는 아래와 같다.





  
 

* OpenBiblio용 Z39.50 서버 개발



서버 개발에 활용하는 yaz-ztest 프로그램의 기본적인 활용 전략은 주요 처리 함수에 Backend인 openbiblio DB 관련 코드를 반영하는 것이다.

  • bend_init : Z39.50 클라이언트의 요청이 들어오면 호출되는 곳으로  이곳에 DB 연결 코드를 추가한다. 인코딩 설정에 주의한다.
    mysql_set_character_set(conn, "utf8")
    ....
    q->query_charset = "UTF-8";
    q->records_in_same_charset = 1;
  • ztest_search : 질의의 상세 내용을 처리하기 위해 호출되는 곳으로 클라이언트의 질의 분석과 SQL을 통한 OpenBiblio 테이블 검색으로 총 건수를 클라이언트에 전달한다. ztest_search에서는 검색 대상 목록이 몇 건인지를 추출하는 것이 중요하므로 추가적인 MARC 필드 추출등은 ztest_fetch에서 다룬다.
  • ztest_fetch : 검색 결과에 전달된 건수 만큼 호출되며 클라이언트로 데이터를 전달하는 로직을 추가한다.
    DB 테이블 검색 결과를 MARC 레코드 형태로 변환하는 작업이 필요한 곳이다.
  • bend_close : 질의가 종료될때 호출되는 곳으로 이곳에 DB 연결 해제 코드를 추가한다.

 
  
서버 개발은 YAZ toolkit 과  함께  설치되는 문서를(D:\Program Files\YAZ\doc\index.html)를 참조한다.
아래는 4장 Generic Server 부분인데 이중에서 Backend function>Search and Retrieve를 중심으로 예제를 개발하고자 한다.
기존 yaz-ztest의 분석을 위하여 비주얼스튜디오로 실행중인 프로그램 디버깅하기 를 추천한다.
 

Chapter 4. Generic server
1. Introduction
2. The Database Frontend
3. The Backend API
4. Your main() Routine
5. The Backend Functions
5.1. Init
5.2. Search and Retrieve
5.3. Delete
5.4. Scan
6. Application Invocation
7. GFS Configuration and Virtual Hosts


Z39.50 서버에서 서비스 해야 할 여러가지 기능들이 있지만 예제에서는 제한된 검색과 자료 제공에 집중하여 서비스하고자 한다.
Z39.50 클라이언트에서 제목과 저자를 검색 조건으로 검색을 요청하면 쿼리 문장은 "@attrset Bib-1 @and @attr 1=4 title @attr 1=1003 name" 등으로 전달되지만 yaz4.dll에서 관련 문장의 파싱을 끝내고 사용자 루틴에 전달될 때는 아래의 예와 같은 RPN(Reverse Polish Notation) Parse tree 구조체 형태로 전달되기 때문에 개발자는 이 구조체를 따라서 SQL로 적절하게 전환시켜 OpenBiblio DB를 검색하면된다.
  
 

struct Z_SearchRequest {
Z_ReferenceId *referenceId; /* OPT */
Odr_int *smallSetUpperBound;
Odr_int *largeSetLowerBound;
Odr_int *mediumSetPresentNumber;
Odr_bool *replaceIndicator;
Z_InternationalString *resultSetName;
int num_databaseNames;
Z_DatabaseName **databaseNames;
Z_ElementSetNames *smallSetElementSetNames; /* OPT */
Z_ElementSetNames *mediumSetElementSetNames; /* OPT */
Odr_oid *preferredRecordSyntax; /* OPT */
Z_Query *query;
Z_OtherInformation *additionalSearchInfo; /* OPT */
Z_OtherInformation *otherInfo; /* OPT */
};

struct Z_Query {
int which;
union {
void *type_0;
Z_RPNQuery *type_1;
Odr_oct *type_2;
Odr_oct *type_100;
Z_RPNQuery *type_101;
Odr_oct *type_102;
Z_External *type_104;
#define Z_Query_type_0 1
#define Z_Query_type_1 2
#define Z_Query_type_2 3
#define Z_Query_type_100 4
#define Z_Query_type_101 5
#define Z_Query_type_102 6
#define Z_Query_type_104 7
} u;
};

struct Z_RPNQuery {
Z_AttributeSetId *attributeSetId;
Z_RPNStructure *RPNStructure;
};

struct Z_Complex {
Z_RPNStructure *s1;
Z_RPNStructure *s2;
Z_Operator *roperator;
};

struct Z_RPNStructure {
int which;
union {
Z_Operand *simple;
Z_Complex *complex;
#define Z_RPNStructure_simple 1
#define Z_RPNStructure_complex 2
} u;
};

struct Z_Operand {
int which;
union {
Z_AttributesPlusTerm *attributesPlusTerm;
Z_ResultSetId *resultSetId;
Z_ResultSetPlusAttributes *resultAttr;
#define Z_Operand_APT 1
#define Z_Operand_resultSetId 2
#define Z_Operand_resultAttr 3
} u;
};

struct Z_AttributesPlusTerm {
Z_AttributeList *attributes;
Z_Term *term;
};

struct Z_ResultSetPlusAttributes {
Z_ResultSetId *resultSet;
Z_AttributeList *attributes;
};

struct Z_AttributeList {
int num_attributes;
Z_AttributeElement **attributes;
};

struct Z_Term {
int which;
union {
Odr_oct *general;
Odr_int *numeric;
Z_InternationalString *characterString;
Odr_oid *oid;
char *dateTime;
Z_External *external;
Z_IntUnit *integerAndUnit;
Odr_null *null;
#define Z_Term_general 1
#define Z_Term_numeric 2
#define Z_Term_characterString 3
#define Z_Term_oid 4
#define Z_Term_dateTime 5
#define Z_Term_external 6
#define Z_Term_integerAndUnit 7
#define Z_Term_null 8
} u;
};

struct Z_Operator {
int which;
union {
Odr_null *op_and;
Odr_null *op_or;
Odr_null *and_not;
Z_ProximityOperator *prox;
#define Z_Operator_and 1
#define Z_Operator_or 2
#define Z_Operator_and_not 3
#define Z_Operator_prox 4
} u;
};

struct Z_ComplexAttribute {
int num_list;
Z_StringOrNumeric **list;
int num_semanticAction;
Odr_int **semanticAction; /* OPT */
};

struct Z_AttributeElement {
Z_AttributeSetId *attributeSet; /* OPT */
Odr_int *attributeType;
int which;
union {
Odr_int *numeric;
Z_ComplexAttribute *complex;
#define Z_AttributeValue_numeric 1
#define Z_AttributeValue_complex 2
} value;
};


위의 구조체 및 #define 정의는 include/yaz/z-core.h에 정의되어 있다. 아래의 그림은 실제 Z39.50 클라이언트 요청시 YAZ 서버에서 전달된 RPN parse tree 구조체의 모습을 디버거로 잡은 것이다.
 


  


위의 그림을 보면 RPN Parse Tree 각 노드의 성격을 which로 나타내고(which값의 의미는 z-core.h에 정의되어 있음) 각 노드의 성격에 따라  터미널 노드인 simple node일수도 있고 하위 노드로 확장하는 complex노드를 일수도 있다.
  
  
파스 트리 구조체를 분석해 가면서 지원하지 않는 기능은 아래의 예제와 같이 정상적인 오류 메시지가 클라이언트에 전달되도록 하고 지원하는 기능들에 대해서만 코드를 작성해 나가면 된다. 적절한 오류코드는 include/yaz/diagbib1.h에 정의되어 있다.
  

    if (rr->query->which != Z_Query_type_1) {
        rr->errcode = YAZ_BIB1_QUERY_TYPE_UNSUPP;
        rr->errstring = "Type1 is only supported";
        return 0;
    }


예제에서는 Query Type1만 처리하도록 했다.
  
  
파스 트리이므로 하위노드로 확장되는 경우를 감안하려면 노드 운행 기법상 재귀적 호출(Recursive call)을 사용하는 방법이 적절하다.
아래는 전달된 RPN 파스 트리를 재귀적으로 호출하는 함수의 일부분이다. 함수 내용 중에 보면 논리적 연산이 개입되는 경우 하위 노드에 대해서 재귀적 호출을 적용한 것을 확인할 수 있다.
 

char *rpn_to_sql(Z_RPNStructure *rpn, bend_search_rr *rr)
{
    int zi;
    char *attrval, sqlbuf[1024], *retstr, *s1, *s2;
    
    memset(sqlbuf, 0, sizeof(sqlbuf));
    if (rpn->which==Z_RPNStructure_complex) {
        s1 = rpn_to_sql(rpn->u.complex->s1, rr);
        if (s1 == NULL) return NULL;
        s2 = rpn_to_sql(rpn->u.complex->s2, rr);
        if (s2 == NULL) return NULL;
        switch (rpn->u.complex->roperator->which) {
        case Z_Operator_and :
            sprintf(sqlbuf," (%s AND %s) ", s1, s2);
            break;
        case Z_Operator_or :
            sprintf(sqlbuf," (%s OR %s) ", s1, s2);
            break;
        default :
            rr->errcode = YAZ_BIB1_OPERATOR_UNSUPP;
            rr->errstring = "AND/OR operators are only supported";
            return NULL;
        }
        
    } else {    
        /* Z_RPNStructure_simple */
        
        if (rpn->u.simple->which == Z_Operand_APT) {    /* Z_Operand.u.Z_AttributesPlusTerm */
            Z_AttributesPlusTerm *apt = rpn->u.simple->u.attributesPlusTerm;
            switch (apt->term->which) {
            case Z_Term_general:
                attrval = apt->term->u.general->buf; 
                if (apt->term->u.general->len > 128) attrval[128] = 0;  /* for safety */
                break;
            case Z_Term_numeric:
......


  
  
재귀적 호출을 통해 생성할 SQL은 검색 조건에 해당하는 WHERE 이후 부분이므로 SELECT절과 FROM절을 부가하여 검색을 위한 쿼리를 작성한다. Openbiblio DB는 목록 정보를 biblio 테이블에 두고 추가적인 MARC 정보는 biblio_field에 저장하므로 출판사 등 목록정보에는 없고 부가적인 MARC 정보를 검색해야 하는 경우를 대비하여 biblio테이블과 biblio_field 테이블을 LEFT OUTER JOIN으로 묶는다. 아래의 코드는 RPN 파스 트리를 통해 얻어진 검색조건과 SELECT/FROM절을 조합하여 쿼리를 생성하고 질의를 수행하여 결과 행수를 전달하는 과정이다.
 

    sql_where = rpn_to_sql(rr->query->u.type_1->RPNStructure, rr);
    if (sql_where == NULL) return 0;

    sprintf(sqlbuf, "SELECT distinct biblio.* FROM biblio LEFT OUTER JOIN biblio_field ON biblio.bibid=biblio_field.bibid WHERE %s", sql_where);    
    mysql_query(conn, sqlbuf);
    result = mysql_store_result(conn);
    rr->hits = mysql_num_rows(result);
    yaz_log(YLOG_LOG, "%s==>%d Rows", sqlbuf, rr->hits);


  
  
아래의 그림은 검색을 위한 쿼리와 결과를 로깅한 화면 예제이다.




위의 그림에서 첫번째는 클라이언트에서 제목을 "test"검색하여 2건의 결과를 얻은 화면이고, 두번째는 제목과 저자에 대한 복합 검색을 요청하여 0건의 검색 결과가 전달된 화면이다.

  
  
OpenBiblio 테이블 검색이 성공적이라면 나머지는 ztest_fetch에 각 레코드에 대한 실제 정보를 MARC 형태로 전달하는 것으로, DB 레코드를 읽어 MARC 레코드로 전환하여 전달하기 위한 MARC레코드 정보는 국립중앙도서관에서 배포하는 한국문헌자동화목록형식이나 MARC 21 Specifications for Record Structure, Character Sets, and Exchange Media 또는 http://www.loc.gov/marc/bibliographic/bdleader.html, http://www.nl.go.kr/kormarc/c2/page2.jsp를 참조한다. 
  
ztest_search에서의 검색은 해당 목록의 건수 추출하기 위하여 biblio 테이블을 중심으로 검색했으므로, 추가적인 MARC 정보는 biblio의 bibid 컬럼 값을 연결 고리로 biblio_field 테이블을 부가적으로 읽어 전달해야 한다.

  
MARC21-RECORD의 구조는 다음과 같은데 이들 구조에 맞도록 각 태그별로 묶어 정보를 생성한다.

  • LEADER : 레코드 길이 등 전체적인 정보를 담는 고정 길이 영역
  • DIRECTORY FT(0x1E) : 각 태그별로 길이와 버퍼상 위치등을 담는 영역
  • CONTROL_NUMBER_FIELD FT : 고정 길이의 컨트롤 태그 영역
  • CONTROL_FIELD_1 FT   ...   CONTROL_FIELD_n  FT  : 고정 길이의 컨트롤 태그 영역
  • DATA_FIELD_1  FT   ...   DATA_FIELD_n  FT   : 가변 길이의 데이터 태그 영역
  • RT(0x1D) : 레코드 종료 문자


    
  
다음은 테이블 정보를 기반으로 MARC 레코드를 생성하는 코드의 일부분이다.
 

    /* [고정길이 필드] : Data... FT */
    fldlen=0;
    dirlen=0;
    /* 001  제어번호(Control Number) */
    len = sprintf(&fldbuf[fldlen],"OB%010d%c", atoi(dbrow[0]), DEL_FT);
    dirlen += sprintf(&dirbuf[dirlen],"001%04d%05d", len, fldlen);
    fldlen += len;    
    /* 005  최종처리일시 (Date and Time of Latest Transaction) */
    len = sprintf(&fldbuf[fldlen],"%s%c", dbrow[1], DEL_FT);
    dirlen += sprintf(&dirbuf[dirlen],"005%04d%05d", len, fldlen);
    fldlen += len;    
    /* 008 - 부호화 정보필드(40자리) */                /*18-34*/           /*35-39*/  
    len = sprintf(&fldbuf[fldlen],"%.6ss%.4s    ulk" "a                " "kor  %c", 
                ((char *)dbrow[2])+2, dbrow[2], DEL_FT);
    dirlen += sprintf(&dirbuf[dirlen],"008%04d%05d", len, fldlen);
    fldlen += len;
    
    /* [가변길이 필드] : INDICATOR_1 INDICATOR_2 DELIMITER(0x1F) Subfield codes Data... FT */
    /* 245a - title */
    vlen = 0;
    vlen = vfld_add('a', dbrow[3], tmp, vlen);
    /* 245b - title_remainder */
    vlen = vfld_add('b', dbrow[4], tmp, vlen);
    /* 245 추가 확장 MARC */
    vlen = dbfld_add(dbrow[0], 245, tmp, vlen);    
    if (vlen > 0) {
        len = sprintf(&fldbuf[fldlen],"00%s%c", tmp, DEL_FT);
        dirlen += sprintf(&dirbuf[dirlen],"245%04d%05d", len, fldlen);
        fldlen += len;
    }
    
    /* 100a - author */
    vlen = 0;
    vlen = vfld_add('a', dbrow[5], tmp, vlen);
    /* 100 추가 확장 MARC */
    vlen = dbfld_add(dbrow[0], 100, tmp, vlen);    
    if (vlen > 0) {
        len = sprintf(&fldbuf[fldlen],"1 %s%c", tmp, DEL_FT);
        dirlen += sprintf(&dirbuf[dirlen],"100%04d%05d", len, fldlen);
        fldlen += len;
    }
    
    /* 650a - 주제명부출표목 - 일반주제명 topic1-topic5*/
    vlen = 0;
    vlen = vfld_add('a', dbrow[6], tmp, vlen);
    vlen = vfld_add('a', dbrow[7], tmp, vlen);
    vlen = vfld_add('a', dbrow[8], tmp, vlen);
    vlen = vfld_add('a', dbrow[9], tmp, vlen);
    vlen = vfld_add('a', dbrow[10], tmp, vlen);
    /* 650 추가 확장 MARC */
    vlen = dbfld_add(dbrow[0], 650, tmp, vlen);    
    if (vlen > 0) {
        len = sprintf(&fldbuf[fldlen]," 8%s%c", tmp, DEL_FT);
        dirlen += sprintf(&dirbuf[dirlen],"650%04d%05d", len, fldlen);
        fldlen += len;
    }
    
    /* 추가 MARC 필드(biblio_field) 추가 */
    sprintf(sqlbuf, "SELECT tag, `subfield_cd`, `field_data`,`ind1_cd`,`ind2_cd` FROM biblio_field "
            " WHERE tag NOT IN (100, 245, 650) AND bibid=%s ORDER BY tag", dbrow[0]);    
    mysql_query(conn, sqlbuf);
    extfld = mysql_store_result(conn);
    rowcnt = mysql_num_rows(extfld);
    if (rowcnt > 0) {



코드 작성후 정상 동작하는지 확인하는 방법은 위에서 언급한 Mercury client를 이용하거나 공공도서관의 Z39.50 서버를 검색할 당시에 사용했던 테스트 클라이언트에 로컬 서버를 추가하여 테스트하는 방법, 그리고 이전 포스팅에서 제작했던 Z39.50 검색을 통한 도서 자동등록 기능에 로컬 서버를 등록하여 테스트 하는 방법이 있다. 아래는 그 결과이다.



위의 그림은 Mecury client로 "tcp"를 검색한 결과이다.

  



  

위의 그림은 Z39.50 검색을 통한 도서 자동등록 기능에 로컬 서버와 미 의회 도서관을 설정한 상태에서 "fedora"를 검색한 결과이다.
  



위의 그림은 z39.50 테스트 프로그램에 로컬서버를 추가 반영하여 테스트한 결과이다.

  
  
이번에 만들어본 Z39.50서버는 원격지에 있는 Openbiblio 서버를 접속하여 정보를 가져오는 형태이기 때문에 Openbiblio DB 뿐만아니라 다른 도서 관리 프로그램의 테이블에 대한 Z39.50 서버 제작에도 응용할 수 있을 것으로 보인다. 개발 과정에서 느낀점은 난이도에 비하여 사용자가 그리 많지 않은 점이 아쉽다는 것이다.
  
  
  
(주)동운시스템 전화 041-358-3760

동운북스 소개 바로가기
[온라인 문의 및 견적요청]

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함