Added ZOOM.
authorMike Taylor <mike@indexdata.com>
Thu, 8 Aug 2002 13:31:54 +0000 (13:31 +0000)
committerMike Taylor <mike@indexdata.com>
Thu, 8 Aug 2002 13:31:54 +0000 (13:31 +0000)
zoom/Changes [new file with mode: 0644]
zoom/Makefile [new file with mode: 0644]
zoom/README [new file with mode: 0644]
zoom/master-header [new file with mode: 0644]
zoom/zclient.cpp [new file with mode: 0644]
zoom/zconn.cpp [new file with mode: 0644]
zoom/zerr.cpp [new file with mode: 0644]
zoom/zquery.cpp [new file with mode: 0644]
zoom/zrec.cpp [new file with mode: 0644]
zoom/zrs.cpp [new file with mode: 0644]

diff --git a/zoom/Changes b/zoom/Changes
new file mode 100644 (file)
index 0000000..266869a
--- /dev/null
@@ -0,0 +1,38 @@
+Changes between the current version of the C++ binding specification
+(http://zoom.z3950.org/bind/cplusplus/zoom-1.0g.hh) and the
+specification generated in "interface.h" in this directory (which I
+expect to become version 1.3a of the official specification.)
+
+--
+
+Add #include <stddef.h> for size_t
+
+Add comment about G++'s rejection of throw(ZOOM:error) clause
+
+Add destructor declaration to connection class.
+
+Remove errcode(), errmsg() and addinfo() methods from the connection
+and resultSet classes, since exceptions should be used in these
+enlightened days.
+
+Rename the record::recordSyntax enumeration to record::syntax, and add
+an UNKNOWN element.
+
+Remove "virtual" from all the record class's methods, including its
+destructor, since we no longer expect to derive record subclasses
+representing records expressed in specific record-syntaxes -- see
+version 1.3 of the ZOOM AAPI.
+
+Remove the nfields() and field() methods from the record class --
+again, see v1.3 of the AAPI.
+
+Add some substance to the error base class: it can now be created
+(with an error-code specified), and the error-code may be both fetched
+and rendered as a human-readable string.  This is necessary so that
+it's possible to meaningfully catch(error e).
+
+Add the missing char *errmsg() method to the systemError and bib1Error
+classes.
+
+Add a new error subclass, queryError, for reporting malformed query
+strings etc.
diff --git a/zoom/Makefile b/zoom/Makefile
new file mode 100644 (file)
index 0000000..bdcc128
--- /dev/null
@@ -0,0 +1,36 @@
+# $Header: /home/cvsroot/yaz++/zoom/Attic/Makefile,v 1.1 2002-08-08 13:31:54 mike Exp $
+
+CCC = g++      # ... until I figure out what the standard
+               # Make macro name is for the C++ compiler.
+CPPFLAGS := -Wall -g
+L = libzoom.a
+OBJ = $L(zerr.o) $L(zconn.o) $L(zquery.o) $L(zrs.o) $L(zrec.o)
+
+all: interface.h zclient
+
+zclient: zclient.o $L
+       @echo CCC = $(CCC)
+       $(CCC) $(CPPFLAGS) -o zclient zclient.o $L -lyaz
+
+test: zclient
+       ./zclient bagel.indexdata.dk 210 gils '@and mineral epicenter'
+
+$L: $(OBJ)
+       ranlib $L
+
+$(OBJ): zoom++.h
+
+zclient.o: zoom++.h
+
+zoom++.h: master-header
+       rm -f $@
+       sed 's/^*       /       /; s/^*/ /' $< > $@
+       chmod -w $@
+
+interface.h: master-header
+       rm -f $@
+       grep -v '^*' $< > $@
+       chmod -w $@
+
+clean:
+       rm -f zoom++.h interface.h zclient *.[ao] core
diff --git a/zoom/README b/zoom/README
new file mode 100644 (file)
index 0000000..729a4d6
--- /dev/null
@@ -0,0 +1,28 @@
+This is an initial implementation of the ZOOM C++ binding
+(http://zoom.z3950.org/bind/cplusplus/) for the Yaz toolkit.
+It's a rather obvious thin layer on top of Yaz's ZOOM-C
+implementation.
+
+The build environment will no doubt need to be tweaked to fit
+in with the way that the rest of Yaz++ is built.
+
+Only one wrinkle, really: we want the ZOOM C++ header file for two
+different purposes: one is to function as an interface specification
+that can go on the ZOOM web-site, and one is to actually build
+against.  The requirements of these two manifestations of the header
+file are rather different in that the latter needs to include
+implementation details that the former very explicitly does _not_
+want.  Accordingly, we automatically generate both versions from a
+master copy in which the implementation-dependent lines are preceded
+by asterisks(*).  So we have:
+
+       master-header   The master copy, which may be edited.
+       interface.h     The read-only, automatically-generated file
+                       that can be considered a formal specification
+                       of the ZOOM C++ interface.
+       zoom++.h        The read-only, automatically-generated file
+                       that is actually used in the build process,
+                       and ought quite possibly to be moved into
+                       ../include/yaz++/zoom.h
+
+Good luck!
diff --git a/zoom/master-header b/zoom/master-header
new file mode 100644 (file)
index 0000000..c573e33
--- /dev/null
@@ -0,0 +1,131 @@
+// $Header: /home/cvsroot/yaz++/zoom/master-header,v 1.1 2002-08-08 13:31:54 mike Exp $
+//
+// ZOOM C++ Binding.
+// The ZOOM homepage is at http://zoom.z3950.org/
+//
+// Derived from version 1.0g at
+//     http://zoom.z3950.org/bind/cplusplus/zoom-1.0g.hh
+
+#include <stddef.h>            // for size_t
+
+*/*
+* * This is a bit stupid.  The fact that our ZOOM-C++ implementation is
+* * based on the ZOOM-C implementation is our Dirty Little Secret, and
+* * there is in principle no reason why client code need be bothered
+* * with it.  Except of course that the public class declarations in
+* * C++ have to lay their private parts out for the world to see
+* * (oo-er).  Hence the inclusion of <yaz/zoom.h>
+* */
+*#include <yaz/zoom.h>
+*
+namespace ZOOM {
+  // Forward declarations for type names.
+  class query;
+  class resultSet;
+  class record;
+
+  const char *option (const char *key);
+  const char *option (const char *key, const char *val);
+  int errcode ();
+  char *errmsg ();
+  char *addinfo ();
+
+  class connection {
+*   ZOOM_connection c;
+  public:
+    connection (const char *hostname, int portnum);
+    // ### I would like to add a ``throw (ZOOM::error)'' clause
+    // here, but it looks like G++ 2.95.2 doesn't recognise it.
+    ~connection ();
+    const char *option (const char *key) const;
+    const char *option (const char *key, const char *val);
+*   ZOOM_connection _getYazConnection() const { return c; } // package-private
+  };
+
+  class query {
+      // pure virtual class: derive concrete subclasses from it.
+* protected:
+*   ZOOM_query q;
+  public:
+    virtual ~query ();
+*   ZOOM_query _getYazQuery() const { return q; } // package-private
+  };
+
+  class prefixQuery : public query {
+  public:
+    prefixQuery (const char *pqn);
+    ~prefixQuery();
+  };
+
+  class CCLQuery : public query {
+  public:
+    CCLQuery (const char *ccl, void *qualset);
+    ~CCLQuery();
+  };
+
+  class resultSet {
+*   connection &owner;
+*   ZOOM_resultset rs;
+  public:
+    resultSet (connection &c, const query &q);
+    ~resultSet ();
+    const char *option (const char *key) const;
+    const char *option (const char *key, const char *val);
+    size_t size () const;
+    const record *getRecord (size_t i) const;
+  };
+
+  class record {
+*   const resultSet *owner;
+*   ZOOM_record r;
+  public:
+*   record::record(const resultSet *rs, ZOOM_record rec):
+*      owner(rs), r(rec) {}
+    ~record ();
+    enum syntax {
+      UNKNOWN, GRS1, SUTRS, USMARC, UKMARC, XML
+    };
+    record *clone () const;
+    syntax recsyn () const;
+    const char *render () const;
+    const char *rawdata () const;
+  };
+
+  class error {
+* protected:
+*   int code;
+  public:
+    error (int code);
+    int errcode () const;
+    const char *errmsg () const;
+  };
+
+  class systemError: public error {
+  public:
+    systemError ();
+    int errcode () const;
+    const char *errmsg () const;
+  };
+
+  class bib1Error: public error {
+*   const char *info;
+  public:
+*   ~bib1Error();
+    bib1Error (int errcode, const char *addinfo);
+    int errcode () const;
+    const char *errmsg () const;
+    const char *addinfo () const;
+  };
+
+  class queryError: public error {
+*   const char *q;
+  public:
+*   ~queryError();
+    static const int PREFIX = 1;
+    static const int CCL = 2;
+    queryError (int qtype, const char *source);
+    int errcode () const;
+    const char *errmsg () const;
+    const char *addinfo () const;
+  };
+}
diff --git a/zoom/zclient.cpp b/zoom/zclient.cpp
new file mode 100644 (file)
index 0000000..8a1bb95
--- /dev/null
@@ -0,0 +1,54 @@
+// $Header: /home/cvsroot/yaz++/zoom/zclient.cpp,v 1.1 2002-08-08 13:31:54 mike Exp $
+
+// Trivial sample client
+
+#include <stdlib.h>            // for atoi()
+#include <iostream.h>
+#include "zoom++.h"
+
+int main(int argc, char **argv)
+{
+    if (argc != 5) {
+       cerr << "Usage: " << argv[0] <<
+           " <host> <port> <db> <@prefix-search>\n";
+       return 1;
+    }
+
+    const char *hostname = argv[1];
+    const int port = atoi(argv[2]);
+    const char *dbname = argv[3];
+    const char *searchSpec = argv[4];
+
+    ZOOM::connection *conn;
+    try {
+       conn = new ZOOM::connection(hostname, port);
+    } catch(ZOOM::bib1Error err) {
+       cerr << argv[0] << ": connect: " <<
+           err.errmsg() << " (" << err.addinfo() << ")\n";
+       return 2;
+    } catch(ZOOM::error err) {
+       cerr << argv[0] << ": connect: " << err.errmsg() << "\n";
+       return 2;
+    }
+
+    conn->option("databaseName", dbname);
+    ZOOM::prefixQuery pq(searchSpec);
+    ZOOM::resultSet *rs;
+    try {
+       rs = new ZOOM::resultSet(*conn, pq);
+    } catch(ZOOM::bib1Error err) {
+       cerr << argv[0] << ": searchSpec: " <<
+           err.errmsg() << " (" << err.addinfo() << ")\n";
+       return 3;
+    }
+
+    size_t n = rs->size();
+    cout << "found " << n << " records:\n";
+    for (size_t i = 0; i < n; i++) {
+       const ZOOM::record *rec = rs->getRecord(i);
+       cout << "=== record " << i+1 << " (recsyn " << rec->recsyn()
+            << ") ===\n" << rec->render();
+    }
+
+    return 0;
+}
diff --git a/zoom/zconn.cpp b/zoom/zconn.cpp
new file mode 100644 (file)
index 0000000..e6f9dc9
--- /dev/null
@@ -0,0 +1,34 @@
+// $Header: /home/cvsroot/yaz++/zoom/zconn.cpp,v 1.1 2002-08-08 13:31:54 mike Exp $
+
+// Z39.50 Connection class
+
+#include "zoom++.h"
+
+
+namespace ZOOM {
+    connection::connection(const char *hostname, int portnum) {
+       c = ZOOM_connection_new(hostname, portnum);
+
+       int errcode;
+       const char *errmsg;     // unused: carries same info as `errcode'
+       const char *addinfo;
+       if ((errcode = ZOOM_connection_error(c, &errmsg, &addinfo)) != 0) {
+           throw bib1Error(errcode, addinfo);
+       }
+    }
+
+    const char *connection::option(const char *key) const {
+       return ZOOM_connection_option_get(c, key);
+    }
+
+    const char *connection::option(const char *key, const char *val) {
+       // ### There may be memory-management issues here.
+       const char *old = ZOOM_connection_option_get(c, key);
+       ZOOM_connection_option_set(c, key, val);
+       return old;
+    }
+
+    connection::~connection() {
+       ZOOM_connection_destroy(c);
+    }
+}
diff --git a/zoom/zerr.cpp b/zoom/zerr.cpp
new file mode 100644 (file)
index 0000000..e418003
--- /dev/null
@@ -0,0 +1,93 @@
+// $Header: /home/cvsroot/yaz++/zoom/Attic/zerr.cpp,v 1.1 2002-08-08 13:31:54 mike Exp $
+
+// Z39.50 Error classes
+
+#include <errno.h>
+#include <string.h>            // for strerror(), strlen(), strcpy()
+#include <stdio.h>             // for sprintf()
+#include <yaz/diagbib1.h>
+#include "zoom++.h"
+
+
+namespace ZOOM {
+    error::error(int errcode) {
+       code = errcode;
+    }
+
+    int error::errcode() const {
+       return code;
+    }
+
+    const char *error::errmsg() const {
+       static char buf[40];
+       sprintf(buf, "error #%d", code);
+       return buf;
+    }
+
+
+
+    systemError::systemError() : error::error(errno){
+       code = errno;
+    }
+
+    int systemError::errcode() const {
+       return code;
+    }
+
+    const char *systemError::errmsg() const {
+       return strerror(code);
+    }
+
+
+
+    bib1Error::bib1Error(int errcode, const char *addinfo) :
+       error::error(errcode) {
+       info = new char[strlen(addinfo)+1];
+       strcpy((char*) info, addinfo);
+    }
+
+    bib1Error::~bib1Error() {
+       delete info;
+    }
+
+    int bib1Error::errcode() const {
+       return code;
+    }
+
+    const char *bib1Error::errmsg() const {
+       return diagbib1_str(code);
+    }
+
+    const char *bib1Error::addinfo() const {
+       return info;
+    }
+
+
+
+    queryError::queryError(int qtype, const char *source) :
+       error::error(qtype) {
+       q = new char[strlen(source)+1];
+       strcpy((char*) q, source);
+    }
+
+    queryError::~queryError() {
+       delete q;
+    }
+
+    int queryError::errcode() const {
+       return code;
+    }
+
+    const char *queryError::errmsg() const {
+       switch (code) {
+       case PREFIX: return "bad prefix search";
+       case CCL: return "bad CCL search";
+       default: break;
+       }
+       return "bad search (unknown type)";
+    }
+
+    const char *queryError::addinfo() const {
+       return q;
+    }
+}
diff --git a/zoom/zquery.cpp b/zoom/zquery.cpp
new file mode 100644 (file)
index 0000000..77c0d93
--- /dev/null
@@ -0,0 +1,54 @@
+// $Header: /home/cvsroot/yaz++/zoom/zquery.cpp,v 1.1 2002-08-08 13:31:54 mike Exp $
+
+// Z39.50 Query classes
+
+#include "zoom++.h"
+
+
+namespace ZOOM {
+    query::~query() {
+       ZOOM_query_destroy(q);
+       q = 0;
+    }
+
+
+
+    prefixQuery::prefixQuery(const char *pqn) {
+       q = ZOOM_query_create();
+       if (ZOOM_query_prefix(q, pqn) == -1) {
+           ZOOM_query_destroy(q);
+           throw queryError(queryError::PREFIX, pqn);
+       }
+    }
+
+    // The binding specification says we have to have destructors for
+    // the query subclasses, so in they go -- even though they don't
+    // actually _do_ anything that inheriting the base query type's
+    // destructor wouldn't do.  It's an irritant of C++ that the
+    // declaration of a subclass has to express explicitly the
+    // implementation detail of whether destruction is implemented
+    // by a specific destructor or by inheritance.  Oh well.
+    //
+    // ### Not sure whether I need to do nothing at all, and the
+    // superclass destructor gets called anyway (I think that only
+    // works when you _don't_ define a destructor so that the default
+    // one pertains) or whether I need to duplicate the functionality
+    // of that destructor.  Let's play safe by assuming the latter and
+    // zeroing what we free so that we get bitten if we're wrong.
+    //
+    prefixQuery::~prefixQuery() {
+       ZOOM_query_destroy(q);
+       q = 0;
+    }
+
+
+
+    CCLQuery::CCLQuery(const char *ccl, void *qualset) {
+       throw "Oops.  No CCL support in ZOOM-C yet.  Sorry.";
+    }
+
+    CCLQuery::~CCLQuery() {
+       ZOOM_query_destroy(q);
+       q = 0;
+    }
+}
diff --git a/zoom/zrec.cpp b/zoom/zrec.cpp
new file mode 100644 (file)
index 0000000..18e34be
--- /dev/null
@@ -0,0 +1,69 @@
+// $Header: /home/cvsroot/yaz++/zoom/zrec.cpp,v 1.1 2002-08-08 13:31:54 mike Exp $
+
+// Z39.50 Record class
+
+#include "zoom++.h"
+#include <string.h>            // for strcasecmp()
+
+
+namespace ZOOM {
+    record::~record() {
+       if (owner == 0) {
+           // Must have been clone()d
+           ZOOM_record_destroy(r);
+       }
+    }
+
+    // ### Would this operation be better expressed as a copy constructor?
+    record *record::clone() const {
+       // It's tempting just to replace `r' with a clone, and return
+       // `this', but probably more honest to allocate a new C++
+       // record object.
+
+       record *rec = new record(0, 0);
+       if ((rec->r = ZOOM_record_clone(r)) == 0) {
+           // Presumably an out-of-memory error
+           throw systemError();
+       }
+
+       return rec;
+    }
+
+    // It's tempting to modify this method just to return either the
+    // string that ZOOM_record_get("syntax") gives us, or the VAL_*
+    // value from Yaz's OID database, but we'd break the nominal
+    // plug-compatibility of competing C++ binding implementations
+    // if we did that.
+    //
+    record::syntax record::recsyn() const {
+       const char *syn = ZOOM_record_get(r, "syntax", 0);
+
+       // These string constants are from yaz/util/oid.c
+       if (!strcasecmp(syn, "xml"))
+           return XML;
+       else if (!strcasecmp(syn, "GRS-1"))
+           return GRS1;
+       else if (!strcasecmp(syn, "SUTRS"))
+           return SUTRS;
+       else if (!strcasecmp(syn, "USmarc"))
+           return USMARC;
+       else if (!strcasecmp(syn, "UKmarc"))
+           return UKMARC;
+       else if (!strcasecmp(syn, "XML") ||
+                !strcasecmp(syn, "text-XML") ||
+                !strcasecmp(syn, "application-XML"))
+           return XML;
+
+       return UNKNOWN;
+    }
+
+    const char *record::render() const {
+       int len;
+       return ZOOM_record_get(r, "render", &len);
+    }
+
+    const char *record::rawdata() const {
+       int len;
+       return ZOOM_record_get(r, "raw", &len);
+    }
+}
diff --git a/zoom/zrs.cpp b/zoom/zrs.cpp
new file mode 100644 (file)
index 0000000..39d0422
--- /dev/null
@@ -0,0 +1,59 @@
+// $Header: /home/cvsroot/yaz++/zoom/zrs.cpp,v 1.1 2002-08-08 13:31:54 mike Exp $
+
+// Z39.50 Result Set class
+
+#include "zoom++.h"
+
+
+namespace ZOOM {
+    resultSet::resultSet(connection &c, const query &q) : owner(c) {
+       ZOOM_connection yazc = c._getYazConnection();
+       rs = ZOOM_connection_search(yazc, q._getYazQuery());
+       int errcode;
+       const char *errmsg;     // unused: carries same info as `errcode'
+       const char *addinfo;
+
+       if ((errcode = ZOOM_connection_error(yazc, &errmsg, &addinfo)) != 0) {
+           throw bib1Error(errcode, addinfo);
+       }
+    }
+
+    resultSet::~resultSet() {
+       ZOOM_resultset_destroy(rs);
+    }
+
+    const char *resultSet::option(const char *key) const {
+       return ZOOM_resultset_option_get(rs, key);
+    }
+
+    const char *resultSet::option(const char *key, const char *val) {
+       // ### There may be memory-management issues here.
+       const char *old = ZOOM_resultset_option_get(rs, key);
+       ZOOM_resultset_option_set(rs, key, val);
+       return old;
+    }
+
+    size_t resultSet::size() const {
+       return ZOOM_resultset_size(rs);
+    }
+
+    const record *resultSet::getRecord(size_t i) const {
+       ZOOM_record rec;
+       if ((rec = ZOOM_resultset_record(rs, i)) == 0) {
+           const char *errmsg; // unused: carries same info as `errcode'
+           const char *addinfo;
+           int errcode = ZOOM_connection_error(owner._getYazConnection(),
+                                               &errmsg, &addinfo);
+           throw bib1Error(errcode, addinfo);
+       }
+
+       // Memory management is odd here.  The ZOOM-C record we've
+       // just fetched (`rec') is owned by the ZOOM-C result-set we
+       // fetched it from (`rs'), so all we need to allocate is a
+       // ZOOM-C++ wrapper for it, which is destroyed at the
+       // appropriate time -- but the underlying (ZOOM-C) record is
+       // _not_ destroyed at that time, because it's done when the
+       // underlying result-set is deleted.
+       return new record(this, rec);
+    }
+}