Added nmem_text_node_cdata which takes xmlNode CDATA content and
authorAdam Dickmeiss <adam@indexdata.dk>
Tue, 2 May 2006 20:47:45 +0000 (20:47 +0000)
committerAdam Dickmeiss <adam@indexdata.dk>
Tue, 2 May 2006 20:47:45 +0000 (20:47 +0000)
creates NMEM string.
Added first parts of record conversion utility (yaz_record_conv_t).

include/yaz/Makefile.am
include/yaz/nmem.h
include/yaz/record_conv.h [new file with mode: 0644]
src/Makefile.am
src/marcdisp.c
src/nmemsdup.c
src/record_conv.c [new file with mode: 0644]
src/xmlquery.c
test/.cvsignore
test/Makefile.am
test/tst_record_conv.c [new file with mode: 0644]

index 3a71989..7f56a0b 100644 (file)
@@ -1,9 +1,9 @@
-## $Id: Makefile.am,v 1.27 2006-01-27 17:31:52 adam Exp $
+## $Id: Makefile.am,v 1.28 2006-05-02 20:47:45 adam Exp $
 
 pkginclude_HEADERS= backend.h ccl.h cql.h comstack.h \
  diagbib1.h diagsrw.h sortspec.h log.h logrpn.h marcdisp.h nmem.h odr.h \
  oid.h options.h otherinfo.h pquery.h prt-ext.h querytowrbuf.h \
- readconf.h statserv.h \
+ readconf.h record_conv.h statserv.h \
  tcpip.h test.h unix.h tpath.h wrbuf.h xmalloc.h \
  yaz-ccl.h yaz-iconv.h yaz-util.h yaz-version.h yconfig.h proto.h \
  xmlquery.h \
index bd4ff09..c0ebc9b 100644 (file)
@@ -23,7 +23,7 @@
  * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
  * OF THIS SOFTWARE.
  *
- * $Id: nmem.h,v 1.17 2005-06-25 15:46:03 adam Exp $
+ * $Id: nmem.h,v 1.18 2006-05-02 20:47:45 adam Exp $
  */
 
 /**
@@ -68,6 +68,7 @@ YAZ_EXPORT void nmem_strsplit_blank(NMEM nmem, const char *dstr,
 YAZ_EXPORT void nmem_strsplit(NMEM nmem, const char *delim,
                               const char *dstr,
                               char ***darray, int *num);
+YAZ_EXPORT char *nmem_text_node_cdata(const void *ptr_cdata, NMEM nmem);
 
 YAZ_EXPORT int *nmem_intdup (NMEM mem, int v);
 YAZ_EXPORT void nmem_transfer (NMEM dst, NMEM src);
diff --git a/include/yaz/record_conv.h b/include/yaz/record_conv.h
new file mode 100644 (file)
index 0000000..1ffae60
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2005-2006, Index Data ApS
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation, in whole or in part, for any purpose, is hereby granted,
+ * provided that:
+ *
+ * 1. This copyright and permission notice appear in all copies of the
+ * software and its documentation. Notices of copyright or attribution
+ * which appear at the beginning of any file must remain unchanged.
+ *
+ * 2. The name of Index Data or the individual authors may not be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED, OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ * IN NO EVENT SHALL INDEX DATA BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR
+ * NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
+ * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ *
+ * $Id: record_conv.h,v 1.1 2006-05-02 20:47:45 adam Exp $
+ */
+/**
+ * \file record_conv.h
+ * \brief Record Conversions Utility
+ */
+
+#ifndef YAZ_RECORD_CONV_H
+#define YAZ_RECORD_CONV_H
+
+#include <stddef.h>
+#include <yaz/yconfig.h>
+
+YAZ_BEGIN_CDECL
+
+/** record conversion handle  */
+typedef struct yaz_record_conv_struct *yaz_record_conv_t;
+
+/** creates record handle
+    \return record handle
+*/
+YAZ_EXPORT yaz_record_conv_t yaz_record_conv_create(void);
+
+/** destroys record handle
+    \param p record conversion handle
+*/
+YAZ_EXPORT void yaz_record_conv_destroy(yaz_record_conv_t p);
+
+
+/** configures record conversion
+    \param p record conversion handle
+    \param node xmlNode pointer (root element of XML config)
+    \retval 0 success
+    \retval -1 failure
+    
+    \verbatim
+    <convert>
+      <xslt stylesheet="dc2marcxml.xsl"/>
+      <xmltomarc charset="marc-8"/>
+    </convert>
+    \endverbatim
+
+    \verbatim
+    <convert>
+      <marctoxml charset="marc-8"/>
+      <xslt stylesheet="marcxml2mods.xsl"/>
+      <xslt stylesheet="mods2dc.xsl"/>
+    </convert>
+    \endverbatim
+
+    For retrieval (ignore here):
+    \verbatim
+    <retrievalinfo>
+       <retrieval syntax="usmarc" name="marcxml"
+            identifier="info:srw/schema/1/marcxml-v1.1"
+       >
+         <title>MARCXML</title>
+         <backend syntax="xml" name="dc" charset="utf-8"/>
+         <convert>
+           <xslt stylesheet="dc2marcxml.xsl"/>
+           <xmltomarc charset="marc-8"/>
+         </convert>
+       </retrieval>
+    </retrievalinfo>
+    \endverbatim
+*/
+YAZ_EXPORT
+int yaz_record_conv_configure(yaz_record_conv_t p, const void *node);
+
+
+/** returns error string (for last error)
+    \param p record conversion handle
+    \return error string
+*/    
+YAZ_EXPORT
+const char *yaz_record_conv_get_error(yaz_record_conv_t p);
+
+YAZ_END_CDECL
+
+#endif
+/*
+ * Local variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim: shiftwidth=4 tabstop=8 expandtab
+ */
+
index 27112a1..0a8ce04 100644 (file)
@@ -1,6 +1,6 @@
 ## This file is part of the YAZ toolkit.
 ## Copyright (C) 1994-2006, Index Data, All rights reserved.
-## $Id: Makefile.am,v 1.32 2006-04-21 12:54:36 marc Exp $
+## $Id: Makefile.am,v 1.33 2006-05-02 20:47:45 adam Exp $
 
 YAZ_VERSION_INFO=2:1:0
 
@@ -71,6 +71,7 @@ libyaz_la_SOURCES=version.c options.c log.c marcdisp.c oid.c wrbuf.c \
   eventl.c seshigh.c statserv.c requestq.c tcpdchk.c \
   eventl.h service.c service.h session.h test.c \
   xmlquery.c \
+  record_conv.c \
   mime.c mime.h
 
 libyaz_la_LDFLAGS=-version-info $(YAZ_VERSION_INFO)
index f56f280..da04f45 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (C) 1995-2006, Index Data ApS
  * See the file LICENSE for details.
  *
- * $Id: marcdisp.c,v 1.28 2006-04-20 20:35:02 adam Exp $
+ * $Id: marcdisp.c,v 1.29 2006-05-02 20:47:45 adam Exp $
  */
 
 /**
@@ -132,25 +132,6 @@ void yaz_marc_add_comment(yaz_marc_t mt, char *comment)
     n->u.comment = nmem_strdup(mt->nmem, comment);
 }
 
-#if HAVE_XML2
-static char *yaz_marc_get_xml_text(const xmlNode *ptr_cdata, NMEM nmem)
-{
-    char *cdata;
-    int len = 0;
-    const xmlNode *ptr;
-
-    for (ptr = ptr_cdata; ptr; ptr = ptr->next)
-        if (ptr->type == XML_TEXT_NODE)
-            len += xmlStrlen(ptr->content);
-    cdata = (char *) nmem_malloc(nmem, len+1);
-    *cdata = '\0';
-    for (ptr = ptr_cdata; ptr; ptr = ptr->next)
-        if (ptr->type == XML_TEXT_NODE)
-            strcat(cdata, (const char *) ptr->content);
-    return cdata;
-}
-#endif
-
 void yaz_marc_cprintf(yaz_marc_t mt, const char *fmt, ...)
 {
     va_list ap;
@@ -206,8 +187,8 @@ void yaz_marc_add_controlfield_xml(yaz_marc_t mt, const xmlNode *ptr_tag,
 {
     struct yaz_marc_node *n = yaz_marc_add_node(mt);
     n->which = YAZ_MARC_CONTROLFIELD;
-    n->u.controlfield.tag = yaz_marc_get_xml_text(ptr_tag, mt->nmem);
-    n->u.controlfield.data = yaz_marc_get_xml_text(ptr_data, mt->nmem);
+    n->u.controlfield.tag = nmem_text_node_cdata(ptr_tag, mt->nmem);
+    n->u.controlfield.data = nmem_text_node_cdata(ptr_data, mt->nmem);
 }
 #endif
 
@@ -231,7 +212,7 @@ void yaz_marc_add_datafield_xml(yaz_marc_t mt, const xmlNode *ptr_tag,
 {
     struct yaz_marc_node *n = yaz_marc_add_node(mt);
     n->which = YAZ_MARC_DATAFIELD;
-    n->u.datafield.tag = yaz_marc_get_xml_text(ptr_tag, mt->nmem);
+    n->u.datafield.tag = nmem_text_node_cdata(ptr_tag, mt->nmem);
     n->u.datafield.indicator =
         nmem_strdupn(mt->nmem, indicator, indicator_len);
     n->u.datafield.subfields = 0;
index 2c8e7d8..2c537f6 100644 (file)
@@ -1,8 +1,8 @@
 /*
- * Copyright (C) 1995-2005, Index Data ApS
+ * Copyright (C) 1995-2006, Index Data ApS
  * See the file LICENSE for details.
  *
- * $Id: nmemsdup.c,v 1.5 2005-06-25 15:46:04 adam Exp $
+ * $Id: nmemsdup.c,v 1.6 2006-05-02 20:47:45 adam Exp $
  */
 
 /**
@@ -16,6 +16,9 @@
 
 #include <string.h>
 #include <yaz/nmem.h>
+#if HAVE_XML2
+#include <libxml/tree.h>
+#endif
 
 char *nmem_strdup (NMEM mem, const char *src)
 {
@@ -78,6 +81,25 @@ void nmem_strsplit(NMEM nmem, const char *delim, const char *dstr,
     }
 }
 
+#if HAVE_XML2
+char *nmem_text_node_cdata(const void *ptr_cdata, NMEM nmem)
+{
+    char *cdata;
+    int len = 0;
+    const xmlNode *ptr;
+
+    for (ptr = (const xmlNode *) ptr_cdata; ptr; ptr = ptr->next)
+        if (ptr->type == XML_TEXT_NODE)
+            len += xmlStrlen(ptr->content);
+    cdata = (char *) nmem_malloc(nmem, len+1);
+    *cdata = '\0';
+    for (ptr = (const xmlNode *) ptr_cdata; ptr; ptr = ptr->next)
+        if (ptr->type == XML_TEXT_NODE)
+            strcat(cdata, (const char *) ptr->content);
+    return cdata;
+}
+#endif
+
 /*
  * Local variables:
  * c-basic-offset: 4
diff --git a/src/record_conv.c b/src/record_conv.c
new file mode 100644 (file)
index 0000000..4ceb023
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2005-2006, Index Data ApS
+ * See the file LICENSE for details.
+ *
+ * $Id: record_conv.c,v 1.1 2006-05-02 20:47:45 adam Exp $
+ */
+/**
+ * \file record_conv.c
+ * \brief Record Conversions utility
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#if HAVE_XML2
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#endif
+
+#include <string.h>
+
+#include <yaz/record_conv.h>
+#include <yaz/wrbuf.h>
+#include <yaz/xmalloc.h>
+#include <yaz/nmem.h>
+
+/** \brief The internal structure for yaz_record_conv_t */
+struct yaz_record_conv_struct {
+    /** memory for configuration */
+    NMEM nmem;
+
+    /** conversion rules (allocated using NMEM) */
+    struct yaz_record_conv_rule *rules;
+
+    /** pointer to last conversion rule pointer in chain */
+    struct yaz_record_conv_rule **rules_p;
+
+    /** string buffer for error messages */
+    WRBUF wr_error;
+};
+
+/** \brief tranformation types (rule types) */
+enum YAZ_RECORD_CONV_RULE 
+{
+    YAZ_RECORD_CONV_RULE_XSLT,
+    YAZ_RECORD_CONV_RULE_MARC_TO_XML,
+    YAZ_RECORD_CONV_RULE_XML_TO_MARC
+};
+
+/** \brief tranformation info (rule info) */
+struct yaz_record_conv_rule {
+    enum YAZ_RECORD_CONV_RULE which;
+    union {
+        struct {
+            const char *stylesheet;
+        } xslt;
+        struct {
+            const char *charset;
+        } marc_to_xml;
+        struct {
+            const char *charset;
+        } xml_to_marc;
+    } u;
+    struct yaz_record_conv_rule *next;
+};
+
+yaz_record_conv_t yaz_record_conv_create()
+{
+    yaz_record_conv_t p = xmalloc(sizeof(*p));
+    p->nmem = nmem_create();
+    p->wr_error = wrbuf_alloc();
+    return p;
+}
+
+void yaz_record_conv_destroy(yaz_record_conv_t p)
+{
+    if (p)
+    {
+        nmem_destroy(p->nmem);
+        wrbuf_free(p->wr_error, 1);
+        xfree(p);
+    }
+}
+
+#if HAVE_XML2
+static struct yaz_record_conv_rule *add_rule(yaz_record_conv_t p,
+                                             enum YAZ_RECORD_CONV_RULE type)
+{
+    struct yaz_record_conv_rule *r = nmem_malloc(p->nmem, sizeof(*r));
+    r->which = type;
+    r->next = 0;
+    *p->rules_p = r;
+    p->rules_p = &r->next;
+    return r;
+}
+
+static void yaz_record_conv_reset(yaz_record_conv_t p)
+{
+    wrbuf_rewind(p->wr_error);
+    nmem_reset(p->nmem);
+    p->rules = 0;
+    p->rules_p = &p->rules;
+}
+
+static int conv_xslt(yaz_record_conv_t p, const xmlNode *ptr)
+{
+    struct _xmlAttr *attr;
+    const char *stylesheet = 0;
+
+    for (attr = ptr->properties; attr; attr = attr->next)
+    {
+        if (!xmlStrcmp(attr->name, BAD_CAST "stylesheet") &&
+            attr->children && attr->children->type == XML_TEXT_NODE)
+            stylesheet = (const char *) attr->children->content;
+        else
+        {
+            wrbuf_printf(p->wr_error, "Bad attribute '%s'."
+                         "Expected stylesheet.", attr->name);
+            return -1;
+        }
+    }
+    if (stylesheet)
+    {
+        struct yaz_record_conv_rule *r =
+            add_rule(p, YAZ_RECORD_CONV_RULE_XSLT);
+        r->u.xslt.stylesheet = nmem_strdup(p->nmem, stylesheet);
+        return 0;
+    }
+    wrbuf_printf(p->wr_error, "Missing attribute 'stylesheet'");
+    return -1;
+}
+
+static int conv_marc_to_xml(yaz_record_conv_t p, const xmlNode *ptr)
+{
+    struct _xmlAttr *attr;
+    const char *charset = 0;
+    struct yaz_record_conv_rule *r;
+
+    for (attr = ptr->properties; attr; attr = attr->next)
+    {
+        if (!xmlStrcmp(attr->name, BAD_CAST "charset") &&
+            attr->children && attr->children->type == XML_TEXT_NODE)
+            charset = (const char *) attr->children->content;
+        else
+        {
+            wrbuf_printf(p->wr_error, "Bad attribute '%s'."
+                         "Expected charset.", attr->name);
+            return -1;
+        }
+    }
+    r = add_rule(p, YAZ_RECORD_CONV_RULE_MARC_TO_XML);
+    if (charset)
+        r->u.marc_to_xml.charset = nmem_strdup(p->nmem, charset);
+    else
+        r->u.marc_to_xml.charset = 0;
+    return 0;
+}
+
+static int conv_xml_to_marc(yaz_record_conv_t p, const xmlNode *ptr)
+{
+    struct _xmlAttr *attr;
+    const char *charset = 0;
+    struct yaz_record_conv_rule *r;
+
+    for (attr = ptr->properties; attr; attr = attr->next)
+    {
+        if (!xmlStrcmp(attr->name, BAD_CAST "charset") &&
+            attr->children && attr->children->type == XML_TEXT_NODE)
+            charset = (const char *) attr->children->content;
+        else
+        {
+            wrbuf_printf(p->wr_error, "Bad attribute '%s'."
+                         "Expected charset.", attr->name);
+            return -1;
+        }
+    }
+    r = add_rule(p, YAZ_RECORD_CONV_RULE_XML_TO_MARC);
+    if (charset)
+        r->u.xml_to_marc.charset = nmem_strdup(p->nmem, charset);
+    else
+        r->u.xml_to_marc.charset = 0;
+    return 0;
+}
+
+
+int yaz_record_conv_configure(yaz_record_conv_t p, const void *ptr_v)
+{
+    const xmlNode *ptr = ptr_v; 
+
+    yaz_record_conv_reset(p);
+
+    if (ptr && ptr->type == XML_ELEMENT_NODE &&
+        !strcmp((const char *) ptr->name, "convert"))
+    {
+        for (ptr = ptr->children; ptr; ptr = ptr->next)
+        {
+            if (ptr->type != XML_ELEMENT_NODE)
+                continue;
+            if (!strcmp((const char *) ptr->name, "xslt"))
+            {
+                if (conv_xslt(p, ptr))
+                    return -1;
+            }
+            else if (!strcmp((const char *) ptr->name, "marc_to_xml"))
+            {
+                if (conv_marc_to_xml(p, ptr))
+                    return -1;
+            }
+            else if (!strcmp((const char *) ptr->name, "xml_to_marc"))
+            {
+                if (conv_xml_to_marc(p, ptr))
+                    return -1;
+            }
+            else
+            {
+                wrbuf_printf(p->wr_error, "Bad element '%s'."
+                             "Expected xslt, marc_to_xml,...", ptr->name);
+                return -1;
+            }
+        }
+    }
+    else
+    {
+        wrbuf_printf(p->wr_error, "Missing 'convert' element");
+        return -1;
+    }
+    return 0;
+}
+
+#else
+/* HAVE_XML2 */
+int yaz_record_conv_configure(yaz_record_conv_t p, const void *ptr_v)
+{
+    wrbuf_rewind(p->wr_error);
+    wrbuf_printf(p->wr_error, "No XML support for yaz_record_conv");
+    return -1;
+}
+
+#endif
+
+const char *yaz_record_conv_get_error(yaz_record_conv_t p)
+{
+    return wrbuf_buf(p->wr_error);
+}
+
+/*
+ * Local variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim: shiftwidth=4 tabstop=8 expandtab
+ */
+
index 4b15a36..2323509 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (C) 1995-2006, Index Data ApS
  * All rights reserved.
  *
- * $Id: xmlquery.c,v 1.8 2006-04-20 20:50:51 adam Exp $
+ * $Id: xmlquery.c,v 1.9 2006-05-02 20:47:45 adam Exp $
  */
 
 /** \file xmlquery.c
@@ -500,19 +500,7 @@ void yaz_xml2query_attribute_element(const xmlNode *ptr,
 
 char *strVal(const xmlNode *ptr_cdata, ODR odr)
 {
-    char *cdata;
-    int len = 0;
-    const xmlNode *ptr;
-
-    for (ptr = ptr_cdata; ptr; ptr = ptr->next)
-        if (ptr->type == XML_TEXT_NODE)
-            len += xmlStrlen(ptr->content);
-    cdata = (char *) odr_malloc(odr, len+1);
-    *cdata = '\0';
-    for (ptr = ptr_cdata; ptr; ptr = ptr->next)
-        if (ptr->type == XML_TEXT_NODE)
-            strcat(cdata, (const char *) ptr->content);
-    return cdata;
+    return nmem_text_node_cdata(ptr_cdata, odr->mem);
 }
 
 void yaz_xml2query_term(const xmlNode *ptr,
index db75146..3280e94 100644 (file)
@@ -17,3 +17,5 @@ tstodrstack
 tstlogthread
 tstxmlquery
 tstpquery
+tst_filepath
+tst_record_conv
index 78756d6..7ddfe2d 100644 (file)
@@ -1,10 +1,10 @@
-## Copyright (C) 1994-2006, Index Data
+## Copyright (C) 1994-2006, Index Data ApS
 ## All rights reserved.
-## $Id: Makefile.am,v 1.14 2006-04-26 09:40:43 adam Exp $
+## $Id: Makefile.am,v 1.15 2006-05-02 20:47:46 adam Exp $
 
 check_PROGRAMS = tsticonv tstnmem tstmatchstr tstwrbuf tstodr tstccl tstlog \
  tstsoap1 tstsoap2 tstodrstack tstlogthread tstxmlquery tstpquery \
- tst_filepath
+ tst_filepath tst_record_conv
 check_SCRIPTS = tstcql.sh tstmarciso.sh tstmarcxml.sh
 
 TESTS = $(check_PROGRAMS) $(check_SCRIPTS)
@@ -50,4 +50,5 @@ tstlogthread_SOURCES = tstlogthread.c
 tstxmlquery_SOURCES = tstxmlquery.c
 tstpquery_SOURCES = tstpquery.c
 tst_filepath_SOURCES = tst_filepath.c
+tst_record_conv_SOURCES = tst_record_conv.c
 
diff --git a/test/tst_record_conv.c b/test/tst_record_conv.c
new file mode 100644 (file)
index 0000000..66460e0
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2005-2006, Index Data ApS
+ * See the file LICENSE for details.
+ *
+ * $Id: tst_record_conv.c,v 1.1 2006-05-02 20:47:46 adam Exp $
+ *
+ */
+#include <yaz/record_conv.h>
+#include <yaz/test.h>
+#include <yaz/wrbuf.h>
+#include <string.h>
+
+#if HAVE_XML2
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+yaz_record_conv_t conv_from_xml(const char *xmlstring, WRBUF w)
+{
+    xmlDocPtr doc = xmlParseMemory(xmlstring, strlen(xmlstring));
+    if (!doc)
+    {
+        wrbuf_printf(w, "xmlParseMemory");
+        return 0;
+    }
+    else
+    {
+        xmlNodePtr ptr = xmlDocGetRootElement(doc);
+        yaz_record_conv_t p = yaz_record_conv_create();
+        
+        if (!ptr)
+        {
+            wrbuf_printf(w, "xmlDocGetRootElement");
+            yaz_record_conv_destroy(p);
+            p = 0;
+        }
+        else if (!p)
+        {
+            wrbuf_printf(w, "yaz_record_conv_create");
+        }
+        else
+        {
+            int r = yaz_record_conv_configure(p, ptr);
+            
+            if (r)
+            {
+                wrbuf_puts(w, yaz_record_conv_get_error(p));
+                yaz_record_conv_destroy(p);
+                p = 0;
+            }
+        }
+        xmlFreeDoc(doc);
+        return p;
+    }    
+}
+
+int conv_from_xml_compare(const char *xmlstring, const char *expect_error,
+                          yaz_record_conv_t *pt)
+{
+    WRBUF w = wrbuf_alloc();
+    int ret;
+
+    yaz_record_conv_t p = conv_from_xml(xmlstring, w);
+
+    if (!p)
+    {
+        if (expect_error && !strcmp(wrbuf_buf(w), expect_error))
+            ret = 1;
+        else
+            ret = 0;
+    }
+    else
+    {
+        if (expect_error)
+        {
+            ret = 0;
+            yaz_record_conv_destroy(p);
+        }
+        else
+        {
+            if (pt)
+                *pt = p;
+            else
+                yaz_record_conv_destroy(p);
+            ret = 1;
+        }
+    }
+    wrbuf_free(w, 1);
+    return ret;
+}
+
+static void tst()
+{
+    YAZ_CHECK(conv_from_xml_compare("<bad", "xmlParseMemory", 0));
+    YAZ_CHECK(conv_from_xml_compare("<bad/>", "Missing 'convert' element", 0));
+    YAZ_CHECK(conv_from_xml_compare("<convert/>", 0, 0));
+    YAZ_CHECK(conv_from_xml_compare("<convert><bad/></convert>",
+                                    "Bad element 'bad'."
+                                    "Expected xslt, marc_to_xml,...", 0));
+    YAZ_CHECK(conv_from_xml_compare("<convert>"
+                                    "<xslt stylesheet=\"x.xsl\"/>"
+                                    "<marc_to_xml charset=\"marc-8\"/>"
+                                    "</convert>",
+                                    0, 0));
+}
+#endif
+
+int main(int argc, char **argv)
+{
+    YAZ_CHECK_INIT(argc, argv);
+#if HAVE_XML2
+    tst();
+#endif
+    YAZ_CHECK_TERM;
+}
+
+/*
+ * Local variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim: shiftwidth=4 tabstop=8 expandtab
+ */
+