changeset 3:8b4c49c92451

Add initial implementation that handles choices
author Andre Heinecke <andre.heinecke@intevation.de>
date Tue, 22 Mar 2016 10:39:19 +0100
parents 4926d626fe15
children 5c256892042c
files src/cconvert_options.h src/constants.h src/converter.cpp src/converter.h src/main.cpp src/strhelp.c src/strhelp.h
diffstat 7 files changed, 999 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cconvert_options.h	Tue Mar 22 10:39:19 2016 +0100
@@ -0,0 +1,43 @@
+#ifndef CCONVERT_OPTIONS_H
+#define CCONVERT_OPTIONS_H
+/* Copyright (C) 2016 by ETH Zürich
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU GPL (v>=2)
+ * and comes with ABSOLUTELY NO WARRANTY!
+ * See LICENSE.txt for details.
+ */
+
+/** @file Commandline options for clickerconvert */
+
+#include <QCommandLineParser>
+#include <QObject>
+
+static void cconvert_options(QCommandLineParser &parser)
+{
+    QList<QCommandLineOption> options;
+
+    options << QCommandLineOption(QStringList() << QStringLiteral("pdf")
+                                  << QStringLiteral("p"),
+                                  QObject::tr("Output as pdf (default xlsx)"))
+            << QCommandLineOption(QStringList() << QStringLiteral("output")
+                                  << QStringLiteral("o"),
+                                  QObject::tr("write output to file (default stdout)"),
+                                  QObject::tr("[file]"))
+            << QCommandLineOption(QStringList() << QStringLiteral("title")
+                                  << QStringLiteral("t"),
+                                  QObject::tr("Set the title of the document."),
+                                  QObject::tr("\"The Title\""))
+            << QCommandLineOption(QStringList() << QStringLiteral("debug"),
+                                  QObject::tr("Print debug output."));
+
+    Q_FOREACH (const QCommandLineOption &opt, options)
+        parser.addOption(opt);
+    parser.addVersionOption();
+    parser.addHelpOption();
+
+    parser.addPositionalArgument(QStringLiteral("file"),
+                                 QObject::tr("File to process (default stdin)"),
+                                 QObject::tr("[file]"));
+}
+#endif // CCONVERT_OPTIONS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/constants.h	Tue Mar 22 10:39:19 2016 +0100
@@ -0,0 +1,78 @@
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+/* Copyright (C) 2016 by ETH Zürich
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU GPL (v>=2)
+ * and comes with ABSOLUTELY NO WARRANTY!
+ * See LICENSE.txt for details.
+ */
+
+/** @file constants.h
+ * @brief Common definitions for this project.
+ *
+ * @details This file can be included to pull in common
+ * definitions in the application. */
+
+/**
+ * @brief The version of the software package.
+ *
+ * Usually defined as Build parameter.
+ */
+#ifndef VERSION
+#define VERSION "0.0.1"
+#endif
+
+/**
+* @brief The user visible application name.
+ *
+ * Usually defined as Build parameter.
+ **/
+#ifndef APPNAME
+#define APPNAME "unknown"
+#endif
+
+/**
+* @brief Short command line description. */
+#define DESCRIPTION "Convert output of clicker questionaires to different formats"
+
+/**
+* @brief Short copyright notice to show users. */
+#define COPYRIGHT "Copyright (C) 2016 ETH Zürich \n\n" \
+                  "This file is Free Software under the GNU GPL (v>=2)\n" \
+                  "and comes with ABSOLUTELY NO WARRANTY!\n"
+
+/**
+ * @brief The number of columns the document uses. */
+#define COLUMN_CNT 3
+/**
+ * @brief The width of the columns in characters. */
+#define COLUMN_WIDTHS { 45, 30, 30 }
+
+/**
+ * @brief Regular expression to define a question.
+ *
+ * A new question is the first unquoted string after the position
+ * that is followed after a newline by the word "Answer"
+ */
+#define QUESTION_PATTERN "([^\"]+)^(Answer.*$)", QRegularExpression::MultilineOption
+
+/**
+ * @brief Identifiying line that shows a question is a choice question. */
+#define CHOICE_IDENTIFIER "Answer,Votes,Percent"
+
+/**
+ * @brief The pattern used to match a multiple choice answer. */
+#define CHOICE_PATTERN "\"(.*)\",(\\d+),(\\d+\\.\\d+)"
+
+/**
+ * @brief The pattern used to match a free text answer. */
+#define FREETXT_PATTERN "\"([^\"]*)\"", QRegularExpression::MultilineOption
+
+#define TITLE_ROW_HEIGHT 30
+
+#define CHOICE_ROW_HEIGHT 30
+
+#define TEXT_IDENTIFIER "Answer"
+
+#endif // CONSTANTS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/converter.cpp	Tue Mar 22 10:39:19 2016 +0100
@@ -0,0 +1,183 @@
+/* Copyright (C) 2016 by ETH Zürich
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU GPL (v>=2)
+ * and comes with ABSOLUTELY NO WARRANTY!
+ * See LICENSE.txt for details.
+ */
+
+#include "converter.h"
+#include <QDebug>
+#include <QRegularExpression>
+#include <QRegularExpressionMatch>
+
+#include "xlsxdocument.h"
+#include "xlsxconditionalformatting.h"
+
+#include "constants.h"
+
+QTXLSX_USE_NAMESPACE
+
+Converter::Converter(const QString &input, const QString &output,
+                     ConvertFormat fmt, const QString &title):
+    QThread(Q_NULLPTR),
+    mInput(input),
+    mOutput(output),
+    mFmt(fmt),
+    mTitle(title)
+{
+    mTitleFmt.setFontUnderline(Format::FontUnderlineSingle);
+    mTitleFmt.setFontSize(18);
+    mTitleFmt.setFontName("Calibri");
+    mTitleFmt.setFontBold(true);
+    mTitleFmt.setVerticalAlignment(Format::AlignTop);
+
+    mQuestionFmt.setFontSize(11);
+    mQuestionFmt.setFontName("Calibri");
+    mQuestionFmt.setFontBold(true);
+    mQuestionFmt.setTopBorderStyle(Format::BorderThin);
+    mQuestionFmt.setTextWarp(true);
+
+    mAnswerChoiceFmt.setFontSize(11);
+    mAnswerChoiceFmt.setFontName("Calibri");
+    mAnswerChoiceFmt.setHorizontalAlignment(Format::AlignRight);
+
+    mChoiceTextFmt = mAnswerChoiceFmt;
+    mChoiceTextFmt.setVerticalAlignment(Format::AlignVCenter);
+
+    mChoiceBarFmt = mChoiceTextFmt;
+    mChoiceBarFmt.setFontName("Webdings");
+    mChoiceBarFmt.setFontSize(9);
+    mChoiceBarFmt.setFontColor(QColor(0xFF, 0x99, 0x33));
+
+    mChoiceBarInactiveFmt = mChoiceBarFmt;
+    mChoiceBarInactiveFmt.setFontColor(QColor(0xD9, 0xD9, 0xD9));
+}
+
+void Converter::run()
+{
+    QFile infile;
+
+    if (mInput.isEmpty()) {
+        if (!infile.open(stdin, QIODevice::ReadOnly)) {
+            mErrors << tr("Failed to open standard input and no input file provided.");
+            return;
+        }
+    } else {
+        infile.setFileName(mInput);
+        if (!infile.open(QIODevice::ReadOnly)) {
+            mErrors << tr("Failed to open %1 for reading.").arg(mInput);
+            return;
+        }
+    }
+    QTextStream instream(&infile);
+
+    QFile outfile;
+    if (mOutput.isEmpty()) {
+        if (!outfile.open(stdout, QIODevice::WriteOnly)) {
+            mErrors << tr("Failed to open standard output and no output file provided.");
+            return;
+        }
+    } else {
+        outfile.setFileName(mOutput);
+        if (!outfile.open(QIODevice::WriteOnly)) {
+            mErrors << tr("Failed to open %1 for writing.").arg(mOutput);
+            return;
+        }
+    }
+    convertToXSLX(instream, outfile);
+}
+
+void Converter::convertToXSLX(QTextStream& instream, QFile &output)
+{
+    Document xlsx;
+
+    ConditionalFormatting bars;
+
+    bars.addDataBarRule(QColor(0xFF, 0x99, 0x33), ConditionalFormatting::VOT_Num, "0", ConditionalFormatting::VOT_Num, "100", false);
+
+    const double colWidth[] = COLUMN_WIDTHS;
+    for (int i = 1; i <= COLUMN_CNT; i++) {
+        xlsx.setColumnWidth(i, colWidth[i-1]);
+    }
+
+    int row = 1;
+    if (!mTitle.isEmpty()) {
+        // Set the title of the Questionaire
+        xlsx.write(row++, 1, mTitle, mTitleFmt);
+        xlsx.mergeCells("A1:C1");
+        xlsx.setRowHeight(1, TITLE_ROW_HEIGHT);
+    }
+
+    const QString input = instream.readAll();
+
+    QRegularExpression questionEx(QUESTION_PATTERN);
+    QRegularExpression choiceEx(CHOICE_PATTERN);
+    QRegularExpression freetxtEx (FREETXT_PATTERN);
+
+    QRegularExpressionMatch match = questionEx.match(input);
+    bool foundSomething = false;
+    int cursor = match.capturedEnd();
+    while (match.hasMatch() && cursor != -1) {
+        /* We've matched a question pattern. With the answer
+           line */
+        if (!match.lastCapturedIndex() == 2) {
+            /* Should not happen without misconfiguration. */
+            mErrors << "Internal parser error.";
+            return;
+        }
+        foundSomething = true;
+        const QString question = match.captured(1).trimmed();
+        const QString answerLine = match.captured(2).trimmed();
+        xlsx.write(row++, 1, question, mQuestionFmt);
+
+        if (answerLine == QStringLiteral(CHOICE_IDENTIFIER)) {
+            QRegularExpressionMatch choiceMatch = choiceEx.match(input, cursor);
+            xlsx.setRowHeight(row, CHOICE_ROW_HEIGHT);
+            xlsx.write(row++, 1, tr("Answer"), mAnswerChoiceFmt);
+            int firstChoiceRow = row;
+            int lastChoiceRow = row;
+            while (choiceMatch.hasMatch() && choiceMatch.capturedStart() == cursor + 1) {
+                /* We use the cursor here to keep track of the state. Only if an answer
+                   follows immediately behind the last answer we treat it as valid as
+                   otherwise we can't figure out when the next question begins. */
+                cursor = choiceMatch.capturedEnd();
+
+                /* Write the values */
+                xlsx.write(row, 1, choiceMatch.captured(1), mChoiceTextFmt);
+                bool ok;
+                double percent = choiceMatch.captured(3).toDouble(&ok);
+                if (!ok) {
+                    mErrors << "Unparsable number in string: " + choiceMatch.captured();
+                }
+                xlsx.write(row, 2, percent);
+                xlsx.write(row, 3, tr("%1% | %2 Number of votes").
+                           arg(choiceMatch.captured(3)).arg(choiceMatch.captured(2)),
+                           mChoiceVotesFmt);
+                xlsx.setRowHeight(row, CHOICE_ROW_HEIGHT);
+                /* As long as we can match a choice which is either before the next question
+                   or before the end of the document */
+                choiceMatch = choiceEx.match(input, cursor);
+                row++;
+                lastChoiceRow++;
+            }
+            bars.addRange(QString("B%1:B%2").arg(firstChoiceRow).arg(lastChoiceRow));
+        } else if (answerLine == QStringLiteral(TEXT_IDENTIFIER)) {
+            
+        }
+        /* Insert Empty row. */
+        xlsx.setRowHeight(row++, CHOICE_ROW_HEIGHT);
+        match = questionEx.match(input, cursor);
+        cursor = match.capturedEnd();
+    }
+    xlsx.addConditionalFormatting(bars);
+
+    if (!foundSomething) {
+        mErrors << tr("Failed to parse input document.");
+    }
+
+    if (!xlsx.saveAs(&output)) {
+        mErrors << tr("Saving the XLSX document failed.");
+        return;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/converter.h	Tue Mar 22 10:39:19 2016 +0100
@@ -0,0 +1,81 @@
+#ifndef CONVERTER_H
+#define CONVERTER_H
+/* Copyright (C) 2016 by ETH Zürich
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU GPL (v>=2)
+ * and comes with ABSOLUTELY NO WARRANTY!
+ * See LICENSE.txt for details.
+ */
+
+#include <QThread>
+#include <QString>
+#include <QStringList>
+#include <QTextStream>
+#include <QFile>
+
+#include "xlsxformat.h"
+
+/** @file Declaration of the Converter class.
+ */
+
+/**
+ * @enum ConvertFormat
+ * @brief Possible output format values.
+ */
+enum ConvertFormat {
+    /*! XLSX (default). */
+    Format_XLSX,
+    /*! PDF */
+    Format_PDF
+};
+
+/** @brief Base class of Convert operations.
+ *
+ * Set up an instance of this using the ctor and according setters and
+ * start it.
+ */
+class Converter : public QThread
+{
+    Q_OBJECT
+
+public:
+    /** Construct a new Converter object.
+     *
+     * If input is empty stdin is used. If output
+     * is empty stdout is used.
+     *
+     * @param input input filename.
+     * @param output output filename.
+     * @param format the format of this.
+     */
+    Converter(const QString &input, const QString &output,
+              ConvertFormat fmt = Format_XLSX,
+              const QString &title = QString());
+
+    /** Check for errors
+     *
+     * @returns Empty stringlist on success. Translated errors otherwise.*/
+    const QStringList & errors() {return mErrors;}
+
+protected:
+    void convertToXSLX(QTextStream &instream, QFile &output);
+    void run();
+
+    QString mInput, mOutput;
+    ConvertFormat mFmt;
+    QStringList mErrors;
+    QString mTitle;
+
+    QXlsx::Format mTitleFmt,
+                  mQuestionFmt,
+                  mAnswerChoiceFmt,
+                  mAnswerTextFmt,
+                  mFreeTextFmt,
+                  mChoiceTextFmt,
+                  mChoiceBarFmt,
+                  mChoiceBarInactiveFmt,
+                  mChoiceVotesFmt;
+};
+
+#endif // CONVERTER_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main.cpp	Tue Mar 22 10:39:19 2016 +0100
@@ -0,0 +1,164 @@
+/* Copyright (C) 2016 by ETH Zürich
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU GPL (v>=2)
+ * and comes with ABSOLUTELY NO WARRANTY!
+ * See LICENSE.txt for details.
+ */
+
+/** @file Main entry point for the application.
+ *
+ * This file is the wrapper around the qt application.
+ * Does command line parsing and preparation of the QCoreApplication.
+ */
+#include "constants.h"
+
+#include "strhelp.h"
+#include "converter.h"
+#include "cconvert_options.h"
+
+#include <QCoreApplication>
+#include <QCommandLineParser>
+#include <QtPlugin>
+#include <QDebug>
+#include <QTranslator>
+#include <QSettings>
+#include <QTimer>
+
+#ifdef Q_OS_WIN
+
+ #include <windows.h>
+ #include <shlobj.h>
+
+ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
+#elif defined(Q_OS_MAC)
+ Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)
+#else
+/* this is only necessary if we build statically for GNU/Linux */
+// Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
+#endif
+
+#ifdef IS_TAG_BUILD
+bool g_debug = false;
+#else
+bool g_debug = true;
+#endif
+
+QtMessageHandler g_default_msg_handler = NULL;
+
+void filterDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+    if (!g_debug && type == QtDebugMsg) {
+        return;
+    }
+
+    if (g_default_msg_handler) {
+        (*g_default_msg_handler)(type, context, msg);
+    }
+}
+
+int realMain(int argc, char **argv);
+
+#if defined(WIN32) && defined(UNICODE)
+
+/** @brief Unicode entry point.
+ *
+ * Converts arguments to UTF-8 and executes the real
+ * entry point realMain.
+ */
+int wmain(int argc, wchar_t **argv, wchar_t **envp)
+{
+    char **utf8args = NULL;
+
+    utf8args = (char**) xmalloc0 ((argc + 1) * sizeof(char*));
+
+    for (int i = 0; i < argc; i++) {
+        utf8args[i] = wchar_to_utf8(argv[i], wcslen(argv[i]));
+        if (utf8args[i] == NULL) {
+            printf ("Fatal: could not convert arguments to UTF-8.\n");
+            exit(-1);
+        }
+    }
+    int ret = realMain(argc, utf8args);
+    strv_free(utf8args);
+
+    return ret;
+}
+#else
+int main(int argc, char **argv)
+{
+    return realMain(argc, argv);
+}
+#endif
+
+/** @brief The real entry point to the application.
+ *
+ * @param [in] argc the count of the arguments.
+ * @param [in] argv On GNU/Linux this function expects argv to be in the
+ * native system encoding. On Windows the arguments
+ * shall be UTF-8
+ *
+ * @returns 0 on success an error code otherwise. */
+int realMain(int argc, char **argv)
+{
+    /* QCoreApplication setup */
+    QCoreApplication app (argc, argv);
+    QCoreApplication::setOrganizationName(QStringLiteral(APPNAME));
+    QCoreApplication::setApplicationName(QStringLiteral(APPNAME));
+    QCoreApplication::setApplicationVersion(QStringLiteral(VERSION));
+//    QSettings::setDefaultFormat(QSettings::IniFormat);
+
+    /* Setup translations */
+    QTranslator translator;
+    if (QLocale::system().name() == "C") {
+        /* Useful for testing / development as the primary target is german */
+        translator.load(":/l10n/main_de_DE");
+    } else {
+        translator.load(":/l10n/main_" + QLocale::system().name());
+    }
+    app.installTranslator(&translator);
+
+    /* Parse the command line */
+    QCommandLineParser parser;
+    cconvert_options(parser);
+
+    parser.process(app);
+
+#ifdef IS_TAG_BUILD
+    g_debug = parser.isSet(debugOpt);
+#else
+    g_debug = true;
+#endif
+    g_default_msg_handler = qInstallMessageHandler(filterDebugOutput);
+
+
+    const QStringList args = parser.positionalArguments();
+    if (args.size() > 1) {
+        parser.showHelp(1);
+    }
+
+    ConvertFormat fmt = Format_XLSX;
+    /* Initialize the converter. */
+    if (parser.isSet(QStringLiteral("pdf"))) {
+        fmt = Format_PDF;
+    }
+    QString infile;
+    if (args.size()) {
+        infile = args.first();
+    }
+    Converter conv(infile, parser.value("output"), fmt,
+                   parser.value("title"));
+
+    conv.start();
+    conv.wait();
+
+    const QStringList errors = conv.errors();
+    if (errors.isEmpty()) {
+        return 0;
+    } else {
+        Q_FOREACH (const QString err, errors) {
+            qCritical() << err;
+        }
+    }
+    return 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/strhelp.c	Tue Mar 22 10:39:19 2016 +0100
@@ -0,0 +1,277 @@
+/* Copyright (C) 2015 by ETH Zürich
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU GPL (v>=2)
+ * and comes with ABSOLUTELY NO WARRANTY!
+ * See LICENSE.txt for details.
+ */
+/* Needed to get asprintf */
+#define _GNU_SOURCE 1
+
+/** @file See strhelp.h for documentation. */
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+static void
+out_of_core(void)
+{
+  fputs("\nfatal: out of memory\n", stderr);
+  exit(2);
+}
+
+void *
+xmalloc( size_t n )
+{
+  void *p = malloc( n );
+  if( !p )
+    out_of_core();
+  return p;
+}
+
+void *
+xmalloc0( size_t n )
+{
+  void *p = malloc( n );
+  if( !p )
+    out_of_core();
+  memset (p, 0, n);
+  return p;
+}
+
+void *
+xrealloc( void *a, size_t n )
+{
+  void *p = realloc( a, n );
+  if( !p )
+    out_of_core();
+  return p;
+}
+
+char *
+xstrndup( const char *string, const size_t len )
+{
+  char *p = xmalloc( len + 1 );
+  memcpy( p, string, len );
+  p[len] = '\0';
+  return p;
+}
+
+unsigned int
+strv_length (char **str_array)
+{
+  unsigned int i = 0;
+
+  if (!str_array)
+    return 0;
+
+  while (str_array[i])
+    ++i;
+
+  return i;
+}
+
+void strv_append (char ***pArray, const char *string, const size_t len)
+{
+  unsigned int old_len = 0;
+
+  if (!*pArray)
+    {
+      *pArray = xmalloc(2 * sizeof(char*));
+      (*pArray)[0] = xstrndup(string, len);
+      (*pArray)[1] = NULL;
+      return;
+    }
+  old_len = strv_length(*pArray);
+  *pArray = xrealloc(*pArray, sizeof(char**) * (old_len + 2));
+
+  (*pArray)[old_len] = xstrndup(string, len);
+  (*pArray)[old_len + 1] = NULL;
+}
+
+void
+str_append_str(char **pDst, size_t *dst_len, const char *appendage, const size_t len)
+{
+  if (!appendage)
+    return;
+
+  if (!(*pDst))
+    {
+      *pDst = xstrndup(appendage, len);
+      *dst_len = len;
+    }
+  else
+    {
+      size_t new_size = (*dst_len) + len + 1;
+      char *p_old = *pDst;
+      *pDst = xmalloc(new_size);
+      strncpy(*pDst, p_old, *dst_len);
+      strncpy(*pDst + *dst_len, appendage, len);
+      *dst_len = new_size - 1;
+      (*pDst)[*dst_len] = '\0';
+      free (p_old);
+    }
+}
+
+void
+strv_free (char **str_array)
+{
+  if (str_array)
+    {
+      int i;
+
+      for (i = 0; str_array[i] != NULL; i++)
+        free (str_array[i]);
+
+      free (str_array);
+    }
+}
+
+bool
+str_equal (char *s1, char *s2)
+{
+  size_t l1 = strlen(s1);
+  size_t l2 = strlen(s2);
+  if ((l1 == l2) &&
+      (strcmp(s1, s2) == 0))
+    return true;
+  else
+    return false;
+}
+
+bool
+str_starts_with (char *s1, char *s2)
+{
+  size_t l2 = strlen(s2);
+  if (strncmp(s1, s2, l2) == 0)
+    return true;
+  else
+    return false;
+}
+
+void
+str_trim (char **s)
+{
+  size_t i;
+  if (*s != NULL)
+    {
+      while (isspace(**s))
+        (*s)++;
+      i = strlen(*s);
+      while (isspace((*s)[--i]))
+        (*s)[i] = '\0';
+    }
+}
+
+void
+xfree (void *p)
+{
+  if (p)
+    free (p);
+}
+
+int
+xasprintf (char **strp, const char *fmt, ...)
+{
+  int ret;
+  va_list ap;
+  va_start(ap, fmt);
+  ret = vasprintf(strp, fmt, ap);
+  va_end(ap);
+
+  if (ret == -1)
+    out_of_core();
+
+  return ret;
+}
+
+#ifdef WIN32
+/* Adapted from GPGOL rev. e512053 */
+char *
+wchar_to_utf8 (const wchar_t *string, size_t len)
+{
+  int n, ilen;
+  char *result;
+
+  ilen = (int) len;
+  if (ilen < 0)
+    return NULL;
+
+  /* Note, that CP_UTF8 is not defined in Windows versions earlier
+     than NT.*/
+  n = WideCharToMultiByte (CP_UTF8, 0, string, ilen, NULL, 0, NULL, NULL);
+  if (n < 0)
+    return NULL;
+
+  result = xmalloc ((size_t)n+1);
+  n = WideCharToMultiByte (CP_UTF8, 0, string, ilen, result, n, NULL, NULL);
+  if (n < 0)
+    {
+      xfree (result);
+      return NULL;
+    }
+  result[n] = 0;
+  return result;
+}
+
+/* Adapted from GPGOL rev. e512053 */
+wchar_t *
+utf8_to_wchar (const char *string, size_t len)
+{
+  int n, ilen;
+  wchar_t *result;
+
+  ilen = (int) len;
+  if (ilen < 0)
+    return NULL;
+
+  n = MultiByteToWideChar (CP_UTF8, 0, string, ilen, NULL, 0);
+  if (n < 0 || n + 1 < 0)
+    return NULL;
+
+  result = xmalloc ((size_t)(n+1) * sizeof *result);
+  n = MultiByteToWideChar (CP_UTF8, 0, string, ilen, result, n);
+  if (n < 0)
+    {
+      xfree (result);
+      return NULL;
+    }
+  result[n] = 0;
+  return result;
+}
+
+wchar_t
+*acp_to_wchar (const char *string, size_t len)
+{
+  int n, ilen;
+  wchar_t *result;
+
+  ilen = (int) len;
+  if (ilen < 0)
+    return NULL;
+
+  n = MultiByteToWideChar (CP_ACP, 0, string, ilen, NULL, 0);
+  if (n < 0 || n + 1 < 0)
+    return NULL;
+
+  result = xmalloc ((size_t)(n+1) * sizeof *result);
+  n = MultiByteToWideChar (CP_ACP, 0, string, ilen, result, n);
+  if (n < 0)
+    {
+      xfree (result);
+      return NULL;
+    }
+  result[n] = 0;
+  return result;
+}
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/strhelp.h	Tue Mar 22 10:39:19 2016 +0100
@@ -0,0 +1,173 @@
+/* Copyright (C) 2015 by ETH Zürich
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU GPL (v>=2)
+ * and comes with ABSOLUTELY NO WARRANTY!
+ * See LICENSE.txt for details.
+ */
+#ifndef STRHELP_H
+#define STRHELP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+#include <stddef.h>
+
+/**
+ * @file  strhelp.h
+ * @brief Helper functions for c strings and memory management
+ * @details strhelp contains terminating memory allocation functions and
+ * some conveniance functions to work with c strings or arrays of c
+ * strings.
+ */
+
+/** @def To avoid that a compiler optimizes certain memset calls away */
+#define wipememory2(_ptr,_set,_len) do { \
+              volatile char *_vptr=(volatile char *)(_ptr); \
+              size_t _vlen=(_len); \
+              while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \
+                  } while(0)
+/** @def To avoid that a compiler optimizes certain memset calls away */
+#define wipememory(_ptr,_len) wipememory2(_ptr,0,_len)
+
+
+void *xmalloc( size_t n );
+/** @brief like malloc but initalizes the values with 0 */
+void *xmalloc0( size_t n );
+void *xrealloc( void *a, size_t n );
+void *xcalloc( size_t n, size_t m );
+char *xstrndup( const char *string, const size_t len );
+void xfree ( void *p );
+
+/**
+ * @brief Terminating variant of asprintf
+ *
+ * This function behaves exactly like asprintf(3) but will terminate
+ * when an error occures (usally that means that memoy allocation
+ * failed).
+ */
+int xasprintf (char **strp, const char *fmt, ...);
+
+/**
+ * @brief Returns the length of the given %NULL-terminated
+ * string array str_array.
+ * @param[in] str_array a %NULL-terminated array of strings
+ * @returns length of str_array.
+ */
+unsigned int strv_length (char **str_array);
+
+/**
+ * @brief append a string to a NULL terminated array of strings.
+ *
+ * @param[in,out] pArray pointer to the NULL terminated list of string pointers.
+ * @param[in] string pointer to the string to append to the list.
+ * @param[in] len length of the string to append to the list
+ */
+void strv_append (char ***pArray, const char *string, const size_t len);
+
+/**
+ * @brief append a string to another string.
+ *
+ * @param[in,out] pDst pointer to the string to be extended.
+ * @param[in,out] dst_len length of the dst string. Will be modified.
+ * @param[in] appendage pointer to the string to append.
+ * @param[in] len length of the string to append.
+ */
+void str_append_str (char **pDst, size_t *dst_len, const char *appendage,
+                    const size_t len);
+
+/**
+ * @brief Frees the given %NULL-terminated string array.
+ * @param[in,out] str_array a %NULL-terminated array of strings
+ */
+void strv_free (char **str_array);
+
+/**
+ * @brief Checks whether two strings exactly match
+ * @param[in] s1 the first string
+ * @param[in] s2 the second string
+ * @returns true if s1 and s2 are equal
+ */
+bool str_equal (char *s1, char *s2);
+
+/**
+ * @brief Checks whether s2 exactly matches the beginning of s1.
+ * @param[in] s1 the string who's beginning is searched
+ * @param[in] s2 the string which is searched for
+ * @returns true if s1 starts with s2, false otherwise
+ */
+bool str_starts_with (char *s1, char *s2);
+
+/**
+ * @brief Trims all white space from the start and end of string.
+ * @details the start of the string is trimmed by setting *s to the
+ * first non white space character.  The end is trimmed by setting the
+ * first character after the last non white space character to \0.
+ * @param[in,out] s ponter to the string to strip
+ */
+bool str_trim (char **s);
+
+/** @brief decode base64 encoded data
+ *
+ * The memory allocated for dest needs to be free'd by the
+ * caller.
+ *
+ * _Input warning:_
+ * If the input contains invalid base64 characters an error
+ * is returned.
+ *
+ * If the input is invalid base64 but consists of valid
+ * base64 characters _no error_ is returned and dst contains
+ * the valid input up to the error.
+ *
+ * @param [out] dst Pointer to the destination. Needs to be NULL
+ * @param [out] dst_size Size allocated for the destination.
+ * @param [in] src Pointer to the base64 encoded data.
+ * @param [in] src_size Size of the encoded data.
+ *
+ * @returns 0 on success a polarssl error or -1 otherwise
+ */
+int str_base64_decode(char **dst, size_t *dst_size, char *src,
+                      size_t src_size);
+
+#ifdef WIN32
+
+/** @brief convert a utf8 string to utf16 wchar
+ *
+ * @param[in] string utf8 string. Must be at least len characters long.
+ * @param[in] len number of characters to be converted.
+ *
+ * @returns pointer to a newly allocated wchar array. NULL on error.
+ *
+ **/
+wchar_t *utf8_to_wchar (const char *string, size_t len);
+
+/** @brief convert a local 8 bit (acp) string to utf16 wchar
+ *
+ * @param[in] string acp string. Must be at least len characters long.
+ * @param[in] len number of characters to be converted.
+ *
+ * @returns pointer to a newly allocated wchar array. NULL on error.
+ *
+ **/
+wchar_t *acp_to_wchar (const char *string, size_t len);
+
+/** @brief convert a utf16 string to utf8
+ *
+ * @param[in] string utf16 string. Must be at least len characters long.
+ * @param[in] len number of characters to be converted.
+ *
+ * @returns pointer to a newly allocated char array. NULL on error.
+ *
+ **/
+char *wchar_to_utf8 (const wchar_t *string, size_t len);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)