First code bits of record retrieval code
authorAdam Dickmeiss <adam@indexdata.dk>
Thu, 4 May 2006 20:00:45 +0000 (20:00 +0000)
committerAdam Dickmeiss <adam@indexdata.dk>
Thu, 4 May 2006 20:00:45 +0000 (20:00 +0000)
include/yaz/Makefile.am
include/yaz/record_conv.h
include/yaz/retrieval.h [new file with mode: 0644]
src/Makefile.am
src/record_conv.c
src/retrieval.c [new file with mode: 0644]
test/Makefile.am
test/tst_record_conv.c
test/tst_retrieval.c [new file with mode: 0644]

index e515f18..8d63316 100644 (file)
@@ -1,10 +1,10 @@
-## $Id: Makefile.am,v 1.29 2006-05-03 11:09:59 heikki Exp $
+## $Id: Makefile.am,v 1.30 2006-05-04 20:00: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 nfa.h \
  nmem.h odr.h \
  oid.h options.h otherinfo.h pquery.h prt-ext.h querytowrbuf.h \
 
 pkginclude_HEADERS= backend.h ccl.h cql.h comstack.h \
  diagbib1.h diagsrw.h sortspec.h log.h logrpn.h marcdisp.h nfa.h \
  nmem.h odr.h \
  oid.h options.h otherinfo.h pquery.h prt-ext.h querytowrbuf.h \
- readconf.h record_conv.h statserv.h \
+ readconf.h record_conv.h retrieval.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 \
  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 00df235..7595e65 100644 (file)
@@ -23,7 +23,7 @@
  * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
  * OF THIS SOFTWARE.
  *
  * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
  * OF THIS SOFTWARE.
  *
- * $Id: record_conv.h,v 1.2 2006-05-03 13:04:46 adam Exp $
+ * $Id: record_conv.h,v 1.3 2006-05-04 20:00:45 adam Exp $
  */
 /**
  * \file record_conv.h
  */
 /**
  * \file record_conv.h
@@ -63,33 +63,18 @@ YAZ_EXPORT void yaz_record_conv_destroy(yaz_record_conv_t p);
     \verbatim
     <convert>
       <xslt stylesheet="dc2marcxml.xsl"/>
     \verbatim
     <convert>
       <xslt stylesheet="dc2marcxml.xsl"/>
-      <xmltomarc charset="marc-8"/>
+      <marc inputformat="xml" outputformat="marcxml" outputcharset="marc-8"/>
     </convert>
     \endverbatim
 
     \verbatim
     <convert>
     </convert>
     \endverbatim
 
     \verbatim
     <convert>
-      <marctoxml charset="marc-8"/>
+      <marc inputformat="marc" outputformat="marcxml" inputcharset="marc-8"/>
       <xslt stylesheet="marcxml2mods.xsl"/>
       <xslt stylesheet="mods2dc.xsl"/>
     </convert>
     \endverbatim
 
       <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
 
 */
 YAZ_EXPORT
diff --git a/include/yaz/retrieval.h b/include/yaz/retrieval.h
new file mode 100644 (file)
index 0000000..8ef3a98
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * 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: retrieval.h,v 1.1 2006-05-04 20:00:45 adam Exp $
+ */
+/**
+ * \file retrieval.h
+ * \brief Retrieval Utility
+ */
+
+#ifndef YAZ_RETRIEVAL_H
+#define YAZ_RETRIEVAL_H
+
+#include <stddef.h>
+#include <yaz/wrbuf.h>
+#include <yaz/yconfig.h>
+
+#include <yaz/record_conv.h>
+
+YAZ_BEGIN_CDECL
+
+/** retrieval handle  */
+typedef struct yaz_retrieval_struct *yaz_retrieval_t;
+
+/** creates retrieval handle
+    \return retrieval handle
+*/
+YAZ_EXPORT yaz_retrieval_t yaz_retrieval_create(void);
+
+/** destroys retrieval handle
+    \param p retrieval handle
+*/
+YAZ_EXPORT void yaz_retrieval_destroy(yaz_retrieval_t p);
+
+/** configures retrieval
+    \param p retrieval handle
+    \param node xmlNode pointer (root element of XML config)
+    \retval 0 success
+    \retval -1 failure
+
+    On failure, use yaz_retrieval_get_error to get error string.
+    
+    For retrieval:
+    \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>
+            <marc inputformat="marc" outputformat="marcxml"
+                         inputcharset="marc-8"/>
+            <xslt stylesheet="marcxml2mods.xsl"/>
+            <xslt stylesheet="mods2dc.xsl"/>
+         </convert>
+       </retrieval>
+    </retrievalinfo>
+    \endverbatim
+*/
+YAZ_EXPORT
+int yaz_retrieval_configure(yaz_retrieval_t p, const void *node);
+
+
+/** performs retrieval request based on schema and format
+    \param p retrieval handle
+    \param schema record schema / element set name (Z39.50)
+    \param format record format (syntax)
+    \param rc record conversion reference (holds conversion upon success)
+    \retval 0 success
+    \retval -1 falure
+*/
+YAZ_EXPORT
+int yaz_retrieval_request(yaz_retrieval_t p, const char *schema,
+                          const char *format, yaz_record_conv_t *rc);
+
+/** returns error string (for last error)
+    \param p record conversion handle
+    \return error string
+*/    
+YAZ_EXPORT
+const char *yaz_retrieval_get_error(yaz_retrieval_t p);
+
+
+/** set path for opening stylesheets etc.
+    \param p record conversion handle
+    \param path file path (UNIX style with : / Windows with ;)
+*/    
+YAZ_EXPORT
+void yaz_retrieval_set_path(yaz_retrieval_t p, const char *path);
+
+YAZ_END_CDECL
+
+#endif
+/*
+ * Local variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim: shiftwidth=4 tabstop=8 expandtab
+ */
+
index 3ccaf39..27a493a 100644 (file)
@@ -1,6 +1,6 @@
 ## This file is part of the YAZ toolkit.
 ## Copyright (C) 1994-2006, Index Data, All rights reserved.
 ## This file is part of the YAZ toolkit.
 ## Copyright (C) 1994-2006, Index Data, All rights reserved.
-## $Id: Makefile.am,v 1.34 2006-05-03 09:04:33 heikki Exp $
+## $Id: Makefile.am,v 1.35 2006-05-04 20:00:45 adam Exp $
 
 YAZ_VERSION_INFO=2:1:0
 
 
 YAZ_VERSION_INFO=2:1:0
 
@@ -73,7 +73,7 @@ libyaz_la_SOURCES=version.c options.c log.c marcdisp.c oid.c wrbuf.c \
   xmlquery.c \
   mime.c mime.h \
   nfa.c \
   xmlquery.c \
   mime.c mime.h \
   nfa.c \
-  record_conv.c 
+  record_conv.c retrieval.c
 
 libyaz_la_LDFLAGS=-version-info $(YAZ_VERSION_INFO)
 
 
 libyaz_la_LDFLAGS=-version-info $(YAZ_VERSION_INFO)
 
index 7327ef4..2a60f21 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (C) 2005-2006, Index Data ApS
  * See the file LICENSE for details.
  *
  * Copyright (C) 2005-2006, Index Data ApS
  * See the file LICENSE for details.
  *
- * $Id: record_conv.c,v 1.3 2006-05-04 15:31:04 adam Exp $
+ * $Id: record_conv.c,v 1.4 2006-05-04 20:00:45 adam Exp $
  */
 /**
  * \file record_conv.c
  */
 /**
  * \file record_conv.c
 
 /** \brief The internal structure for yaz_record_conv_t */
 struct yaz_record_conv_struct {
 
 /** \brief The internal structure for yaz_record_conv_t */
 struct yaz_record_conv_struct {
-    /** memory for configuration */
+    /** \brief memory for configuration */
     NMEM nmem;
 
     NMEM nmem;
 
-    /** conversion rules (allocated using NMEM) */
+    /** \brief conversion rules (allocated using NMEM) */
     struct yaz_record_conv_rule *rules;
 
     struct yaz_record_conv_rule *rules;
 
-    /** pointer to last conversion rule pointer in chain */
+    /** \brief pointer to last conversion rule pointer in chain */
     struct yaz_record_conv_rule **rules_p;
 
     struct yaz_record_conv_rule **rules_p;
 
-    /** string buffer for error messages */
+    /** \brief string buffer for error messages */
     WRBUF wr_error;
 
     WRBUF wr_error;
 
-    /** path for opening files  */
+    /** \brief path for opening files  */
     char *path;
 };
 
     char *path;
 };
 
@@ -72,7 +72,7 @@ struct yaz_record_conv_rule {
     struct yaz_record_conv_rule *next;
 };
 
     struct yaz_record_conv_rule *next;
 };
 
-/** reset rules+configuration */
+/** \brief reset rules+configuration */
 static void yaz_record_conv_reset(yaz_record_conv_t p)
 {
     struct yaz_record_conv_rule *r;
 static void yaz_record_conv_reset(yaz_record_conv_t p)
 {
     struct yaz_record_conv_rule *r;
@@ -120,6 +120,7 @@ void yaz_record_conv_destroy(yaz_record_conv_t p)
     }
 }
 
     }
 }
 
+/** \brief adds a rule */
 static struct yaz_record_conv_rule *add_rule(yaz_record_conv_t p,
                                              enum YAZ_RECORD_CONV_RULE type)
 {
 static struct yaz_record_conv_rule *add_rule(yaz_record_conv_t p,
                                              enum YAZ_RECORD_CONV_RULE type)
 {
@@ -131,6 +132,7 @@ static struct yaz_record_conv_rule *add_rule(yaz_record_conv_t p,
     return r;
 }
 
     return r;
 }
 
+/** \brief parse 'xslt' conversion node */
 static int conv_xslt(yaz_record_conv_t p, const xmlNode *ptr)
 {
     struct _xmlAttr *attr;
 static int conv_xslt(yaz_record_conv_t p, const xmlNode *ptr)
 {
     struct _xmlAttr *attr;
@@ -179,6 +181,7 @@ static int conv_xslt(yaz_record_conv_t p, const xmlNode *ptr)
     return 0;
 }
 
     return 0;
 }
 
+/** \brief parse 'marc' conversion node */
 static int conv_marc(yaz_record_conv_t p, const xmlNode *ptr)
 {
     struct _xmlAttr *attr;
 static int conv_marc(yaz_record_conv_t p, const xmlNode *ptr)
 {
     struct _xmlAttr *attr;
@@ -435,13 +438,10 @@ const char *yaz_record_conv_get_error(yaz_record_conv_t p)
 
 void yaz_record_conv_set_path(yaz_record_conv_t p, const char *path)
 {
 
 void yaz_record_conv_set_path(yaz_record_conv_t p, const char *path)
 {
-    if (p)
-    {
-        xfree(p->path);
-        p->path = 0;
-        if (path)
-            p->path = xstrdup(path);
-    }
+    xfree(p->path);
+    p->path = 0;
+    if (path)
+        p->path = xstrdup(path);
 }
 #endif
 
 }
 #endif
 
diff --git a/src/retrieval.c b/src/retrieval.c
new file mode 100644 (file)
index 0000000..696e824
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2005-2006, Index Data ApS
+ * See the file LICENSE for details.
+ *
+ * $Id: retrieval.c,v 1.1 2006-05-04 20:00:45 adam Exp $
+ */
+/**
+ * \file retrieval.c
+ * \brief Retrieval utility
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <yaz/retrieval.h>
+#include <yaz/wrbuf.h>
+#include <yaz/xmalloc.h>
+#include <yaz/nmem.h>
+#include <yaz/tpath.h>
+
+#if HAVE_XSLT
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xinclude.h>
+#include <libxslt/xsltutils.h>
+#include <libxslt/transform.h>
+
+/** \brief The internal structure for yaz_retrieval_t */
+struct yaz_retrieval_struct {
+    /** \brief memory for configuration */
+    NMEM nmem;
+
+    /** \brief string buffer for error messages */
+    WRBUF wr_error;
+
+    /** \brief record conversion */
+    yaz_record_conv_t record_conv;
+};
+
+yaz_retrieval_t yaz_retrieval_create()
+{
+    yaz_retrieval_t p = xmalloc(sizeof(*p));
+    p->nmem = nmem_create();
+    p->wr_error = wrbuf_alloc();
+    p->record_conv = yaz_record_conv_create();
+    return p;
+}
+
+void yaz_retrieval_destroy(yaz_retrieval_t p)
+{
+    if (p)
+    {
+        nmem_destroy(p->nmem);
+        wrbuf_free(p->wr_error, 1);
+        yaz_record_conv_destroy(p->record_conv);
+        xfree(p);
+    }
+}
+
+int yaz_retrieval_configure(yaz_retrieval_t p, const void *node)
+{
+    wrbuf_rewind(p->wr_error);
+    wrbuf_printf(p->wr_error, "yaz_retrieval_request: not implemented");
+    return -1;
+}
+
+int yaz_retrieval_request(yaz_retrieval_t p, const char *schema,
+                          const char *format, yaz_record_conv_t *rc)
+{
+    wrbuf_rewind(p->wr_error);
+    wrbuf_printf(p->wr_error, "yaz_retrieval_request: not implemented");
+    return -1;
+}
+
+const char *yaz_retrieval_get_error(yaz_retrieval_t p)
+{
+    return wrbuf_buf(p->wr_error);
+}
+
+void yaz_retrieval_set_path(yaz_retrieval_t p, const char *path)
+{
+    yaz_record_conv_set_path(p->record_conv, path);
+}
+
+#endif
+
+/*
+ * Local variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim: shiftwidth=4 tabstop=8 expandtab
+ */
+
index 55bff58..205e189 100644 (file)
@@ -1,10 +1,10 @@
 ## Copyright (C) 1994-2006, Index Data ApS
 ## All rights reserved.
 ## Copyright (C) 1994-2006, Index Data ApS
 ## All rights reserved.
-## $Id: Makefile.am,v 1.17 2006-05-03 13:04:46 adam Exp $
+## $Id: Makefile.am,v 1.18 2006-05-04 20:00:45 adam Exp $
 
 check_PROGRAMS = tsticonv tstnmem tstmatchstr tstwrbuf tstodr tstccl tstlog \
  tstsoap1 tstsoap2 tstodrstack tstlogthread tstxmlquery tstpquery nfatest1 \
 
 check_PROGRAMS = tsticonv tstnmem tstmatchstr tstwrbuf tstodr tstccl tstlog \
  tstsoap1 tstsoap2 tstodrstack tstlogthread tstxmlquery tstpquery nfatest1 \
- tst_filepath tst_record_conv
+ tst_filepath tst_record_conv tst_retrieval
 check_SCRIPTS = tstcql.sh tstmarciso.sh tstmarcxml.sh
 
 TESTS = $(check_PROGRAMS) $(check_SCRIPTS)
 check_SCRIPTS = tstcql.sh tstmarciso.sh tstmarcxml.sh
 
 TESTS = $(check_PROGRAMS) $(check_SCRIPTS)
@@ -53,3 +53,4 @@ tstpquery_SOURCES = tstpquery.c
 nfatest1_SOURCES = nfatest1.c
 tst_filepath_SOURCES = tst_filepath.c
 tst_record_conv_SOURCES = tst_record_conv.c
 nfatest1_SOURCES = nfatest1.c
 tst_filepath_SOURCES = tst_filepath.c
 tst_record_conv_SOURCES = tst_record_conv.c
+tst_retrieval_SOURCES = tst_retrieval.c
index 8d14343..fcfb692 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (C) 2005-2006, Index Data ApS
  * See the file LICENSE for details.
  *
  * Copyright (C) 2005-2006, Index Data ApS
  * See the file LICENSE for details.
  *
- * $Id: tst_record_conv.c,v 1.3 2006-05-04 18:22:59 adam Exp $
+ * $Id: tst_record_conv.c,v 1.4 2006-05-04 20:00:45 adam Exp $
  *
  */
 #include <yaz/record_conv.h>
  *
  */
 #include <yaz/record_conv.h>
@@ -275,7 +275,7 @@ static void tst_convert()
 int main(int argc, char **argv)
 {
     YAZ_CHECK_INIT(argc, argv);
 int main(int argc, char **argv)
 {
     YAZ_CHECK_INIT(argc, argv);
-#if HAVE_XML2
+#if HAVE_XSLT
     tst_configure();
     tst_convert();
 #endif
     tst_configure();
     tst_convert();
 #endif
diff --git a/test/tst_retrieval.c b/test/tst_retrieval.c
new file mode 100644 (file)
index 0000000..7482535
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2005-2006, Index Data ApS
+ * See the file LICENSE for details.
+ *
+ * $Id: tst_retrieval.c,v 1.1 2006-05-04 20:00:45 adam Exp $
+ *
+ */
+#include <yaz/retrieval.h>
+#include <yaz/test.h>
+#include <yaz/wrbuf.h>
+#include <string.h>
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#if HAVE_XSLT
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+yaz_retrieval_t conv_configure(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_retrieval_t p = yaz_retrieval_create();
+
+        if (p)
+        {
+            const char *srcdir = getenv("srcdir");
+            if (srcdir)
+                yaz_retrieval_set_path(p, srcdir);
+        }
+        if (!ptr)
+        {
+            wrbuf_printf(w, "xmlDocGetRootElement");
+            yaz_retrieval_destroy(p);
+            p = 0;
+        }
+        else if (!p)
+        {
+            wrbuf_printf(w, "yaz_retrieval_create");
+        }
+        else
+        {
+            int r = yaz_retrieval_configure(p, ptr);
+            
+            if (r)
+            {
+                wrbuf_puts(w, yaz_retrieval_get_error(p));
+                yaz_retrieval_destroy(p);
+                p = 0;
+            }
+        }
+        xmlFreeDoc(doc);
+        return p;
+    }    
+}
+
+int conv_configure_test(const char *xmlstring, const char *expect_error,
+                        yaz_retrieval_t *pt)
+{
+    WRBUF w = wrbuf_alloc();
+    int ret;
+
+    yaz_retrieval_t p = conv_configure(xmlstring, w);
+
+    if (!p)
+    {
+        if (expect_error && !strcmp(wrbuf_buf(w), expect_error))
+            ret = 1;
+        else
+        {
+            ret = 0;
+            printf("%.*s\n", wrbuf_len(w), wrbuf_buf(w));
+        }
+    }
+    else
+    {
+        if (expect_error)
+        {
+            ret = 0;
+            yaz_retrieval_destroy(p);
+        }
+        else
+        {
+            if (pt)
+                *pt = p;
+            else
+                yaz_retrieval_destroy(p);
+            ret = 1;
+        }
+    }
+    wrbuf_free(w, 1);
+    return ret;
+}
+
+static void tst_configure()
+{
+    YAZ_CHECK(conv_configure_test("<bad", "xmlParseMemory", 0));
+#if 0
+    YAZ_CHECK(conv_configure_test("<bad/>", "Missing 'convert' element", 0));
+    YAZ_CHECK(conv_configure_test("<convert/>", 0, 0));
+    YAZ_CHECK(conv_configure_test("<convert><bad/></convert>",
+                                  "Bad element 'bad'."
+                                  "Expected marc, xslt, ..", 0));
+    YAZ_CHECK(conv_configure_test("<convert>"
+                                  "<xslt stylesheet=\"tst_record_conv.xsl\"/>"
+                                  "<marc"
+                                  " inputcharset=\"marc-8\""
+                                  " outputcharset=\"marc-8\""
+                                  "/>"
+                                  "</convert>",
+                                  "Attribute 'inputformat' required", 0));
+    YAZ_CHECK(conv_configure_test("<convert>"
+                                  "<xslt stylesheet=\"tst_record_conv.xsl\"/>"
+                                  "<marc"
+                                  " inputcharset=\"utf-8\""
+                                  " outputcharset=\"marc-8\""
+                                  " inputformat=\"xml\""
+                                  " outputformat=\"marc\""
+                                  "/>"
+                                  "</convert>",
+                                  0, 0));
+#endif
+}
+
+#endif
+
+int main(int argc, char **argv)
+{
+    YAZ_CHECK_INIT(argc, argv);
+#if HAVE_XSLT
+    tst_configure();
+#endif
+    YAZ_CHECK_TERM;
+}
+
+/*
+ * Local variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim: shiftwidth=4 tabstop=8 expandtab
+ */
+