From: Anders S. Mortensen Date: Fri, 13 Oct 2000 14:03:35 +0000 (+0000) Subject: Added present service X-Git-Tag: release.0.0.8.lau~68 X-Git-Url: http://lists.indexdata.dk/?a=commitdiff_plain;h=2edffd526d9e0ced8031fb5e3fcb658b93a69b39;p=simpleserver-moved-to-github.git Added present service --- diff --git a/MANIFEST b/MANIFEST index ee73cb7..af6596f 100644 --- a/MANIFEST +++ b/MANIFEST @@ -6,4 +6,5 @@ SimpleServer.xs test.pl ztest.pl OID.pm +SQL.pm INSTALL diff --git a/Makefile b/Makefile index a678b84..fa47120 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ # DEFINE => q[] # INC => q[] -# LIBS => [q[-L/usr/local/lib -lyaz -lpthread -L/lib -lwrap -lnsl]] +# LIBS => [q[-L/usr/local/lib -lyaz]] # NAME => q[Net::Z3950::SimpleServer] # VERSION_FROM => q[SimpleServer.pm] @@ -148,10 +148,16 @@ EXPORT_LIST = PERL_ARCHIVE = TO_INST_PM = OID.pm \ + SQL.pm \ + SQL_test.pl \ SimpleServer.pm \ ztest.pl -PM_TO_BLIB = ztest.pl \ +PM_TO_BLIB = SQL_test.pl \ + $(INST_LIBDIR)/SQL_test.pl \ + SQL.pm \ + $(INST_LIBDIR)/SQL.pm \ + ztest.pl \ $(INST_LIBDIR)/ztest.pl \ SimpleServer.pm \ $(INST_LIBDIR)/SimpleServer.pm \ @@ -263,10 +269,10 @@ SPLIT = # Net::Z3950::SimpleServer might depend on some other libraries: # See ExtUtils::Liblist for details # -EXTRALIBS = -L/usr/local/lib -lyaz -lpthread -L/lib -lwrap -lnsl -LDLOADLIBS = -L/usr/local/lib -lyaz -lpthread -L/lib -lwrap -lnsl +EXTRALIBS = -L/usr/local/lib -lyaz +LDLOADLIBS = -L/usr/local/lib -lyaz BSLOADLIBS = -LD_RUN_PATH = /usr/local/lib:/lib:/usr/lib +LD_RUN_PATH = /usr/local/lib # --- MakeMaker const_cccmd section: @@ -491,7 +497,7 @@ realclean purge :: clean rm -rf $(INST_AUTODIR) $(INST_ARCHAUTODIR) rm -f $(INST_DYNAMIC) $(INST_BOOT) rm -f $(INST_STATIC) - rm -f $(INST_LIBDIR)/ztest.pl $(INST_LIBDIR)/SimpleServer.pm $(INST_LIBDIR)/OID.pm + rm -f $(INST_LIBDIR)/SQL_test.pl $(INST_LIBDIR)/SQL.pm $(INST_LIBDIR)/ztest.pl $(INST_LIBDIR)/SimpleServer.pm $(INST_LIBDIR)/OID.pm rm -rf Makefile Makefile.old diff --git a/SimpleServer.c b/SimpleServer.c index 43fa606..e22809a 100644 --- a/SimpleServer.c +++ b/SimpleServer.c @@ -167,8 +167,75 @@ WRBUF zquery2pquery(Z_Query *q) int bend_sort(void *handle, bend_sort_rr *rr) { - perl_call_sv(sort_ref, G_VOID | G_DISCARD | G_NOARGS); - return; + HV *href; + AV *aref; + SV **temp; + SV *err_code; + SV *err_str; + SV *status; + STRLEN len; + char *ptr; + char *ODR_err_str; + char **input_setnames; + Zfront_handle *zhandle = (Zfront_handle *)handle; + int i; + + dSP; + ENTER; + SAVETMPS; + + aref = newAV(); + input_setnames = rr->input_setnames; + for (i = 0; i < rr->num_input_setnames; i++) + { + av_push(aref, newSVpv(*input_setnames++, 0)); + } + href = newHV(); + hv_store(href, "INPUT", 5, newRV( (SV*) aref), 0); + hv_store(href, "OUTPUT", 6, newSVpv(rr->output_setname, 0), 0); + hv_store(href, "HANDLE", 6, zhandle->handle, 0); + hv_store(href, "STATUS", 6, newSViv(0), 0); + + PUSHMARK(sp); + + XPUSHs(sv_2mortal(newRV( (SV*) href))); + + PUTBACK; + + perl_call_sv(sort_ref, G_SCALAR | G_DISCARD); + + SPAGAIN; + + temp = hv_fetch(href, "ERR_CODE", 8, 1); + err_code = newSVsv(*temp); + + temp = hv_fetch(href, "ERR_STR", 7, 1); + err_str = newSVsv(*temp); + + temp = hv_fetch(href, "STATUS", 6, 1); + status = newSVsv(*temp); + + + + + PUTBACK; + FREETMPS; + LEAVE; + + hv_undef(href), + av_undef(aref); + rr->errcode = SvIV(err_code); + rr->sort_status = SvIV(status); + ptr = SvPV(err_str, len); + ODR_err_str = (char *)odr_malloc(rr->stream, len + 1); + strcpy(ODR_err_str, ptr); + rr->errstring = ODR_err_str; + + sv_free(err_code); + sv_free(err_str); + sv_free(status); + + return 0; } @@ -486,34 +553,42 @@ int bend_fetch(void *handle, bend_fetch_rr *rr) int bend_present(void *handle, bend_present_rr *rr) { - int n; HV *href; SV **temp; SV *err_code; SV *err_string; + SV *hits; + SV *point; STRLEN len; Z_RecordComposition *composition; Z_ElementSetNames *simple; char *ODR_errstr; char *ptr; + Zfront_handle *zhandle = (Zfront_handle *)handle; + +/* WRBUF oid_dotted; */ dSP; ENTER; SAVETMPS; href = newHV(); + hv_store(href, "HANDLE", 6, zhandle->handle, 0); hv_store(href, "ERR_CODE", 8, newSViv(0), 0); hv_store(href, "ERR_STR", 7, newSVpv("", 0), 0); hv_store(href, "START", 5, newSViv(rr->start), 0); hv_store(href, "SETNAME", 7, newSVpv(rr->setname, 0), 0); hv_store(href, "NUMBER", 6, newSViv(rr->number), 0); + /*oid_dotted = oid2dotted(rr->request_format_raw); + hv_store(href, "REQ_FORM", 8, newSVpv((char *)oid_dotted->buf, oid_dotted->pos), 0);*/ + hv_store(href, "HITS", 4, newSViv(0), 0); if (rr->comp) { composition = rr->comp; - if (composition->which == 1) + if (composition->which == Z_RecordComp_simple) { simple = composition->u.simple; - if (simple->which == 1) + if (simple->which == Z_ElementSetNames_generic) { hv_store(href, "COMP", 4, newSVpv(simple->u.generic, 0), 0); } @@ -536,7 +611,7 @@ int bend_present(void *handle, bend_present_rr *rr) PUTBACK; - n = perl_call_sv(present_ref, G_SCALAR | G_DISCARD); + perl_call_sv(present_ref, G_SCALAR | G_DISCARD); SPAGAIN; @@ -546,20 +621,30 @@ int bend_present(void *handle, bend_present_rr *rr) temp = hv_fetch(href, "ERR_STR", 7, 1); err_string = newSVsv(*temp); + temp = hv_fetch(href, "HITS", 4, 1); + hits = newSVsv(*temp); + + temp = hv_fetch(href, "HANDLE", 6, 1); + point = newSVsv(*temp); + PUTBACK; FREETMPS; LEAVE; hv_undef(href); rr->errcode = SvIV(err_code); + rr->hits = SvIV(hits); ptr = SvPV(err_string, len); ODR_errstr = (char *)odr_malloc(rr->stream, len + 1); strcpy(ODR_errstr, ptr); rr->errstring = ODR_errstr; - +/* wrbuf_free(oid_dotted, 1);*/ + zhandle->handle = point; + handle = zhandle; sv_free(err_code); sv_free(err_string); + sv_free(hits); sv_free( (SV*) href); return 0; @@ -613,7 +698,10 @@ bend_initresult *bend_init(bend_initrequest *q) { q->bend_search = bend_search; } - /*q->bend_present = present;*/ + if (present_ref) + { + q->bend_present = bend_present; + } /*q->bend_esrequest = bend_esrequest;*/ /*q->bend_delete = bend_delete;*/ if (fetch_ref) @@ -625,6 +713,7 @@ bend_initresult *bend_init(bend_initrequest *q) hv_store(href, "IMP_NAME", 8, newSVpv("", 0), 0); hv_store(href, "IMP_VER", 7, newSVpv("", 0), 0); hv_store(href, "ERR_CODE", 8, newSViv(0), 0); + hv_store(href, "PEER_NAME", 9, newSVpv(q->peer_name, 0), 0); hv_store(href, "HANDLE", 6, newSVsv(&sv_undef), 0); PUSHMARK(sp); @@ -709,7 +798,7 @@ void bend_close(void *handle) } -#line 713 "SimpleServer.c" +#line 802 "SimpleServer.c" XS(XS_Net__Z3950__SimpleServer_set_init_handler) { dXSARGS; @@ -717,9 +806,9 @@ XS(XS_Net__Z3950__SimpleServer_set_init_handler) croak("Usage: Net::Z3950::SimpleServer::set_init_handler(arg)"); { SV * arg = ST(0); -#line 709 "SimpleServer.xs" +#line 798 "SimpleServer.xs" init_ref = newSVsv(arg); -#line 723 "SimpleServer.c" +#line 812 "SimpleServer.c" } XSRETURN_EMPTY; } @@ -731,9 +820,9 @@ XS(XS_Net__Z3950__SimpleServer_set_close_handler) croak("Usage: Net::Z3950::SimpleServer::set_close_handler(arg)"); { SV * arg = ST(0); -#line 716 "SimpleServer.xs" +#line 805 "SimpleServer.xs" close_ref = newSVsv(arg); -#line 737 "SimpleServer.c" +#line 826 "SimpleServer.c" } XSRETURN_EMPTY; } @@ -745,9 +834,9 @@ XS(XS_Net__Z3950__SimpleServer_set_sort_handler) croak("Usage: Net::Z3950::SimpleServer::set_sort_handler(arg)"); { SV * arg = ST(0); -#line 723 "SimpleServer.xs" +#line 812 "SimpleServer.xs" sort_ref = newSVsv(arg); -#line 751 "SimpleServer.c" +#line 840 "SimpleServer.c" } XSRETURN_EMPTY; } @@ -759,9 +848,9 @@ XS(XS_Net__Z3950__SimpleServer_set_search_handler) croak("Usage: Net::Z3950::SimpleServer::set_search_handler(arg)"); { SV * arg = ST(0); -#line 729 "SimpleServer.xs" +#line 818 "SimpleServer.xs" search_ref = newSVsv(arg); -#line 765 "SimpleServer.c" +#line 854 "SimpleServer.c" } XSRETURN_EMPTY; } @@ -773,9 +862,9 @@ XS(XS_Net__Z3950__SimpleServer_set_fetch_handler) croak("Usage: Net::Z3950::SimpleServer::set_fetch_handler(arg)"); { SV * arg = ST(0); -#line 736 "SimpleServer.xs" +#line 825 "SimpleServer.xs" fetch_ref = newSVsv(arg); -#line 779 "SimpleServer.c" +#line 868 "SimpleServer.c" } XSRETURN_EMPTY; } @@ -787,9 +876,9 @@ XS(XS_Net__Z3950__SimpleServer_set_present_handler) croak("Usage: Net::Z3950::SimpleServer::set_present_handler(arg)"); { SV * arg = ST(0); -#line 743 "SimpleServer.xs" +#line 832 "SimpleServer.xs" present_ref = newSVsv(arg); -#line 793 "SimpleServer.c" +#line 882 "SimpleServer.c" } XSRETURN_EMPTY; } @@ -801,9 +890,9 @@ XS(XS_Net__Z3950__SimpleServer_set_esrequest_handler) croak("Usage: Net::Z3950::SimpleServer::set_esrequest_handler(arg)"); { SV * arg = ST(0); -#line 750 "SimpleServer.xs" +#line 839 "SimpleServer.xs" esrequest_ref = newSVsv(arg); -#line 807 "SimpleServer.c" +#line 896 "SimpleServer.c" } XSRETURN_EMPTY; } @@ -815,9 +904,9 @@ XS(XS_Net__Z3950__SimpleServer_set_delete_handler) croak("Usage: Net::Z3950::SimpleServer::set_delete_handler(arg)"); { SV * arg = ST(0); -#line 757 "SimpleServer.xs" +#line 846 "SimpleServer.xs" delete_ref = newSVsv(arg); -#line 821 "SimpleServer.c" +#line 910 "SimpleServer.c" } XSRETURN_EMPTY; } @@ -829,9 +918,9 @@ XS(XS_Net__Z3950__SimpleServer_set_scan_handler) croak("Usage: Net::Z3950::SimpleServer::set_scan_handler(arg)"); { SV * arg = ST(0); -#line 764 "SimpleServer.xs" +#line 853 "SimpleServer.xs" scan_ref = newSVsv(arg); -#line 835 "SimpleServer.c" +#line 924 "SimpleServer.c" } XSRETURN_EMPTY; } @@ -840,15 +929,15 @@ XS(XS_Net__Z3950__SimpleServer_start_server) { dXSARGS; { -#line 770 "SimpleServer.xs" +#line 859 "SimpleServer.xs" char **argv; char **argv_buf; char *ptr; int i; STRLEN len; -#line 850 "SimpleServer.c" +#line 939 "SimpleServer.c" int RETVAL; -#line 776 "SimpleServer.xs" +#line 865 "SimpleServer.xs" argv_buf = (char **)xmalloc((items + 1) * sizeof(char *)); argv = argv_buf; for (i = 0; i < items; i++) @@ -860,7 +949,7 @@ XS(XS_Net__Z3950__SimpleServer_start_server) *argv_buf = NULL; RETVAL = statserv_main(items, argv, bend_init, bend_close); -#line 864 "SimpleServer.c" +#line 953 "SimpleServer.c" ST(0) = sv_newmortal(); sv_setiv(ST(0), (IV)RETVAL); } diff --git a/SimpleServer.pm b/SimpleServer.pm index be1017c..f5b6866 100644 --- a/SimpleServer.pm +++ b/SimpleServer.pm @@ -64,6 +64,7 @@ sub new { $self->{SEARCH} = $args->{SEARCH} || croak "SimpleServer.pm: ERROR: Unspecified search handler"; $self->{FETCH} = $args->{FETCH} || croak "SimpleServer.pm: ERROR: Unspecified fetch handler"; $self->{CLOSE} = $args->{CLOSE}; + $self->{PRESENT} = $args->{PRESENT}; bless $self, $class; return $self; @@ -82,6 +83,9 @@ sub launch_server { if (defined($self->{CLOSE})) { set_close_handler($self->{CLOSE}); } + if (defined($self->{PRESENT})) { + set_present_handler($self->{PRESENT}); + } start_server(@args); } @@ -170,6 +174,7 @@ of events: - Initialize request - Search request + - Present request - Fetching of records - Closing down connection @@ -191,6 +196,7 @@ means of the the SimpleServer object constructor INIT => \&my_init_handler, CLOSE => \&my_close_handler, SEARCH => \&my_search_handler, + PRESENT => \&my_present_handler, FETCH => \&my_fetch_handler }); After the custom event handles are declared, the server is launched @@ -217,9 +223,9 @@ The argument hash passed to the init handler has the form $args = { ## Response parameters: - IMP_NAME => "" ## Z39.50 Implementation name - IMP_VER => "" ## Z39.50 Implementation version - ERR_CODE => 0 ## Error code, cnf. Z39.50 manual + IMP_NAME => "", ## Z39.50 Implementation name + IMP_VER => "", ## Z39.50 Implementation version + ERR_CODE => 0, ## Error code, cnf. Z39.50 manual HANDLE => undef ## Handler of Perl data structure }; @@ -248,17 +254,17 @@ mous hash. The structure is the following: $args = { ## Request parameters: - HANDLE => ref ## Your session reference. - SETNAME => "id" ## ID of the result set - REPL_SET => 0 ## Replace set if already existing? - DATABASES => ["xxx"] ## Reference to a list of data- + HANDLE => ref, ## Your session reference. + SETNAME => "id", ## ID of the result set + REPL_SET => 0, ## Replace set if already existing? + DATABASES => ["xxx"], ## Reference to a list of data- ## bases to search - QUERY => "query" ## The query expression + QUERY => "query", ## The query expression ## Response parameters: - ERR_CODE => 0 ## Error code (0=Succesful search) - ERR_STR => "" ## Error string + ERR_CODE => 0, ## Error code (0=Succesful search) + ERR_STR => "", ## Error string HITS => 0 ## Number of matches }; @@ -298,6 +304,46 @@ it is perfectly legal to not accept boolean operators, but you SHOULD try to return good error codes if you run into something you can't or won't support. +=head2 Present handler + +The presence of a present handler in a SimpleServer front-end is optional. +Each time a client wishes to retrieve records, the present service is +called. The present service allows the origin to request a certain number +of records retrieved from a given result set. +When the present handler is called, the front-end server should prepare a +result set for fetching. In practice, this means to get access to the +data from the backend database and store the data in a temporary fashion +for fast and efficient fetching. The present handler does *not* fetch +anything. This task is taken care of by the fetch handler, which will be +called the correct number of times by the YAZ library. More about this +below. +If no present handler is implemented in the front-end, the YAZ toolkit +will take care of a minimum of preparations itself. This default present +handler is sufficient in many situations, where only a small amount of +records are expected to be retrieved. If on the other hand, large result +sets are likely to occur, the implementation of a reasonable present +handler can gain performance significantly. + +The informations exchanged between client and present handle are: + + $args = { + ## Client/server request: + + HANDLE => ref, ## Reference to datastructure + SETNAME => "id", ## Result set ID + START => xxx, ## Start position + COMP => "", ## Desired record composition + NUMBER => yyy, ## Number of requested records + + + ## Respons parameters: + + HITS => zzz, ## Number of returned records + ERR_CODE => 0, ## Error code + ERR_STR => "" ## Error message + }; + + =head2 Fetch handler The fetch handler is asked to retrieve a SINGLE record from a given diff --git a/SimpleServer.xs b/SimpleServer.xs index 88056f8..e2db79f 100644 --- a/SimpleServer.xs +++ b/SimpleServer.xs @@ -158,8 +158,75 @@ WRBUF zquery2pquery(Z_Query *q) int bend_sort(void *handle, bend_sort_rr *rr) { - perl_call_sv(sort_ref, G_VOID | G_DISCARD | G_NOARGS); - return; + HV *href; + AV *aref; + SV **temp; + SV *err_code; + SV *err_str; + SV *status; + STRLEN len; + char *ptr; + char *ODR_err_str; + char **input_setnames; + Zfront_handle *zhandle = (Zfront_handle *)handle; + int i; + + dSP; + ENTER; + SAVETMPS; + + aref = newAV(); + input_setnames = rr->input_setnames; + for (i = 0; i < rr->num_input_setnames; i++) + { + av_push(aref, newSVpv(*input_setnames++, 0)); + } + href = newHV(); + hv_store(href, "INPUT", 5, newRV( (SV*) aref), 0); + hv_store(href, "OUTPUT", 6, newSVpv(rr->output_setname, 0), 0); + hv_store(href, "HANDLE", 6, zhandle->handle, 0); + hv_store(href, "STATUS", 6, newSViv(0), 0); + + PUSHMARK(sp); + + XPUSHs(sv_2mortal(newRV( (SV*) href))); + + PUTBACK; + + perl_call_sv(sort_ref, G_SCALAR | G_DISCARD); + + SPAGAIN; + + temp = hv_fetch(href, "ERR_CODE", 8, 1); + err_code = newSVsv(*temp); + + temp = hv_fetch(href, "ERR_STR", 7, 1); + err_str = newSVsv(*temp); + + temp = hv_fetch(href, "STATUS", 6, 1); + status = newSVsv(*temp); + + + + + PUTBACK; + FREETMPS; + LEAVE; + + hv_undef(href), + av_undef(aref); + rr->errcode = SvIV(err_code); + rr->sort_status = SvIV(status); + ptr = SvPV(err_str, len); + ODR_err_str = (char *)odr_malloc(rr->stream, len + 1); + strcpy(ODR_err_str, ptr); + rr->errstring = ODR_err_str; + + sv_free(err_code); + sv_free(err_str); + sv_free(status); + + return 0; } @@ -477,34 +544,42 @@ int bend_fetch(void *handle, bend_fetch_rr *rr) int bend_present(void *handle, bend_present_rr *rr) { - int n; HV *href; SV **temp; SV *err_code; SV *err_string; + SV *hits; + SV *point; STRLEN len; Z_RecordComposition *composition; Z_ElementSetNames *simple; char *ODR_errstr; char *ptr; + Zfront_handle *zhandle = (Zfront_handle *)handle; + +/* WRBUF oid_dotted; */ dSP; ENTER; SAVETMPS; href = newHV(); + hv_store(href, "HANDLE", 6, zhandle->handle, 0); hv_store(href, "ERR_CODE", 8, newSViv(0), 0); hv_store(href, "ERR_STR", 7, newSVpv("", 0), 0); hv_store(href, "START", 5, newSViv(rr->start), 0); hv_store(href, "SETNAME", 7, newSVpv(rr->setname, 0), 0); hv_store(href, "NUMBER", 6, newSViv(rr->number), 0); + /*oid_dotted = oid2dotted(rr->request_format_raw); + hv_store(href, "REQ_FORM", 8, newSVpv((char *)oid_dotted->buf, oid_dotted->pos), 0);*/ + hv_store(href, "HITS", 4, newSViv(0), 0); if (rr->comp) { composition = rr->comp; - if (composition->which == 1) + if (composition->which == Z_RecordComp_simple) { simple = composition->u.simple; - if (simple->which == 1) + if (simple->which == Z_ElementSetNames_generic) { hv_store(href, "COMP", 4, newSVpv(simple->u.generic, 0), 0); } @@ -527,7 +602,7 @@ int bend_present(void *handle, bend_present_rr *rr) PUTBACK; - n = perl_call_sv(present_ref, G_SCALAR | G_DISCARD); + perl_call_sv(present_ref, G_SCALAR | G_DISCARD); SPAGAIN; @@ -537,20 +612,30 @@ int bend_present(void *handle, bend_present_rr *rr) temp = hv_fetch(href, "ERR_STR", 7, 1); err_string = newSVsv(*temp); + temp = hv_fetch(href, "HITS", 4, 1); + hits = newSVsv(*temp); + + temp = hv_fetch(href, "HANDLE", 6, 1); + point = newSVsv(*temp); + PUTBACK; FREETMPS; LEAVE; hv_undef(href); rr->errcode = SvIV(err_code); + rr->hits = SvIV(hits); ptr = SvPV(err_string, len); ODR_errstr = (char *)odr_malloc(rr->stream, len + 1); strcpy(ODR_errstr, ptr); rr->errstring = ODR_errstr; - +/* wrbuf_free(oid_dotted, 1);*/ + zhandle->handle = point; + handle = zhandle; sv_free(err_code); sv_free(err_string); + sv_free(hits); sv_free( (SV*) href); return 0; @@ -604,7 +689,10 @@ bend_initresult *bend_init(bend_initrequest *q) { q->bend_search = bend_search; } - /*q->bend_present = present;*/ + if (present_ref) + { + q->bend_present = bend_present; + } /*q->bend_esrequest = bend_esrequest;*/ /*q->bend_delete = bend_delete;*/ if (fetch_ref) @@ -616,6 +704,7 @@ bend_initresult *bend_init(bend_initrequest *q) hv_store(href, "IMP_NAME", 8, newSVpv("", 0), 0); hv_store(href, "IMP_VER", 7, newSVpv("", 0), 0); hv_store(href, "ERR_CODE", 8, newSViv(0), 0); + hv_store(href, "PEER_NAME", 9, newSVpv(q->peer_name, 0), 0); hv_store(href, "HANDLE", 6, newSVsv(&sv_undef), 0); PUSHMARK(sp); diff --git a/ztest.pl b/ztest.pl index 573839a..c879071 100755 --- a/ztest.pl +++ b/ztest.pl @@ -5,6 +5,17 @@ use Net::Z3950::SimpleServer; use Net::Z3950::OID; use strict; + +sub dump_hash { + my $href = shift; + my $key; + + foreach $key (keys %$href) { + printf("%10s => %s\n", $key, $href->{$key}); + } +} + + sub my_init_handler { my $args = shift; my $session = {}; @@ -61,7 +72,6 @@ sub my_fetch_handler { my $href = $data->[$offset - 1]; $args->{REP_FORM} = Net::Z3950::OID::xml; - foreach $field (keys %$href) { $record .= "<" . $field . ">" . $href->{$field} . ""; }