/*
        testmsgr.cpp

        Test messenger
        Sends (randomized) XML messages to a designated server for testing or
        runs a mock server to test clients

--------------------------------------------------------------------------------
gSOAP XML Web services tools
Copyright (C) 2000,2024, Robert van Engelen, Genivia, Inc. All Rights Reserved.
This software is released under one of the following two licenses:
GPL.
--------------------------------------------------------------------------------
GPL license.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA

Author contact information:
engelen@genivia.com / engelen@acm.org
--------------------------------------------------------------------------------
A commercial use license is available from Genivia, Inc., contact@genivia.com
--------------------------------------------------------------------------------
*/

/*
        Build steps:

        soapcpp2 -CSL import/dom.h
        c++ -DWITH_OPENSSL -o testmsgr testmsgr.cpp soapC.cpp stdsoap2.cpp dom.cpp plugin/httpda.c plugin/smdevp.c -lssl -lcrypto

        without OpenSSL:
        c++ -o testmsgr testmsgr.cpp soapC.cpp stdsoap2.cpp dom.cpp
*/

#include "soapH.h"              // generated by soapcpp2 -CSL import/dom.h
#include <fstream>              // ifstream, ofstream
#include <bitset>
#include <vector>

#ifndef WIN32
#ifdef WITH_OPENSSL
#include "plugin/httpda.h"      // HTTP digest authentication, requires OpenSSL
#endif
#include <signal.h>             // SIGPIPE
#endif

typedef std::bitset<0x110000> charset; // Unicode character set 0x0000-0x10FFFF

static const unsigned int UNDEFINED_CHAR = 0xFFFD; // Unicode undefined error char

static void sigpipe_handler(int) { } // SIGPIPE handler

struct Namespace namespaces[] = {{NULL,NULL,NULL,NULL}}; // dummy namespace table

static size_t cidnum = 0; // cid:id# counter for MTOM attachments
static size_t idnum = 0;  // ID and IDREF value counter
static size_t idref = 0;  // IDREF value counter

static char *auth_userid = NULL;
static char *auth_passwd = NULL;
static char *proxy_userid = NULL;
static char *proxy_passwd = NULL;
static char *proxy_host = NULL;
static int proxy_port = 8080;

////////////////////////////////////////////////////////////////////////////////
//
//      Indicator tags
//
////////////////////////////////////////////////////////////////////////////////

#define OPT "???"
#define REP "__REPEAT"
#define SEL "__SELECT"
#define PER "__PERMUTE"

////////////////////////////////////////////////////////////////////////////////
//
//      Command line options
//
////////////////////////////////////////////////////////////////////////////////

static const char   *act   = NULL;      // -aact
static bool          blank = false;     // -b
static bool          cont  = false;     // -c
static ULONG64       dnum  = 0;         // -dnum
static bool          ferr  = false;     // -f
static bool          iind  = false;     // -i
static bool          jind  = false;     // -j
static unsigned long lmax  = 3;         // -lmax
static unsigned long mmax  = 10;        // -mmax
static LONG64        nlen  = -1;        // -nlen
static const char   *ofile = NULL;      // -ofile
static unsigned long pperc = 100;       // -pperc
static unsigned long qperc = 100;       // -qperc
static long          seed  = 0;         // -rseed
static int           tsec  = 1;         // -tsec
static bool          uchr  = false;     // -u
static bool          verb  = false;     // -v
static bool          mime  = false;     // -A
static bool          chnk  = false;     // -C
static bool          http  = false;     // -H
static bool          mtom  = false;     // -M
static const char   *opt   = OPT;       // -Oopt
static const char   *per   = PER;       // -Pper
static const char   *rep   = REP;       // -Rrep
static const char   *sel   = SEL;       // -Ssel
static const char   *ifile = NULL;      // [infile|-]
static const char   *URL   = NULL;      // [URL]
static unsigned long port  = 0;         // [port]

////////////////////////////////////////////////////////////////////////////////
//
//      Inline
//
////////////////////////////////////////////////////////////////////////////////

inline void gen_send(struct soap *ctx, const char *s, size_t k = std::string::npos)
{
  if (k == std::string::npos)
    k = strlen(s);
  if (soap_send_raw(ctx, s, k))
  {
    if (URL)
      fprintf(stderr, "testmsgr: failed to send to %s\n", URL);
    else
      fprintf(stderr, "testmsgr: failed to write to output\n");
    soap_print_fault(ctx, stderr);
    exit(EXIT_FAILURE);
  }
}

inline void gen_blank(struct soap *ctx)
{
  if (blank)
    gen_send(ctx, " ", 1);
}

inline void gen_send_verb(struct soap *ctx, const char *t, const char *s, size_t k = std::string::npos)
{
  if (verb)
  {
    if (k != std::string::npos)
      printf("%s %.*s\n", t, (int)k, s);
    else
      printf("%s %s\n", t, s);
  }
  gen_blank(ctx);
  gen_send(ctx, s, k);
  gen_blank(ctx);
}

inline void gen_indent(struct soap *ctx, size_t tab)
{
  if (iind)
  {
    gen_send(ctx, "\n", 1);
    for (size_t i = 0; i < tab; ++i)
      gen_send(ctx, " ", 1);
  }
  else if (jind)
  {
    gen_send(ctx, "\n", 1);
    for (size_t i = 0; i < tab; ++i)
      gen_send(ctx, "\t", 1);
  }
}

inline unsigned long gen_random()
{
#ifdef HAVE_RANDOM
  return (unsigned long)random();
#else
  return (unsigned long)rand();
#endif
}

inline void set_chars(charset& cs, unsigned int lo, unsigned int hi)
{
  cs |= (((charset().set() >>= lo) <<= lo + 0x10FFFF - hi) >>= 0x10FFFF - hi);
}

////////////////////////////////////////////////////////////////////////////////
//
//      Proto
//
////////////////////////////////////////////////////////////////////////////////

static void test_server(struct soap *ctx, xsd__anyType& dom);
static void test_client(struct soap *ctx, xsd__anyType& dom);
static void gen_test(struct soap *ctx, xsd__anyType& dom);
static void gen_random_test(struct soap *ctx, xsd__anyType& dom, size_t tab = 0);
static void gen_random_attachments(struct soap *ctx);
static void get_minmax(struct soap *ctx, xsd__anyType& dom, unsigned long& n, unsigned long& m);
static bool get_dim(struct soap *ctx, xsd__anyType& dom, unsigned long& d);
static void gen_value(struct soap *ctx, const char *s);
static bool get_range(const char *s, unsigned long& n, unsigned long& m);
static void gen_name(struct soap *ctx, ULONG64 x, ULONG64 n);
static void gen_ncname(struct soap *ctx, ULONG64 x, ULONG64 n);
static void gen_qname(struct soap *ctx, ULONG64 x, ULONG64 n);
static void gen_uri(struct soap *ctx, ULONG64 x, ULONG64 n);
static void gen_lang(struct soap *ctx, ULONG64 x, ULONG64 n);
static void gen_text(struct soap *ctx, ULONG64 x, ULONG64 n);
static void gen_hex(struct soap *ctx, ULONG64 x, ULONG64 n);
static void gen_base64(struct soap *ctx, ULONG64 x, ULONG64 n);
static void gen_id(struct soap *ctx);
static void gen_idref(struct soap *ctx);
static void gen_pattern(struct soap *ctx, const char *pattern, ULONG64 n, ULONG64 m);
static std::string get_reg(const char **pattern, ULONG64 l);
static std::string get_seq(const char **pattern, ULONG64 l);
static std::string get_rep(const char **pattern, ULONG64 l);
static std::string get_sym(const char **pattern, ULONG64 l);
static bool get_charset(const char **pattern, charset& cs);
static unsigned int get_char(const char **pattern);
static void append_char(std::string& s, unsigned int c);
static int receive_request(struct soap *ctx);
static void receive_response(struct soap *ctx);

////////////////////////////////////////////////////////////////////////////////
//
//      testmsgr main
//
////////////////////////////////////////////////////////////////////////////////

int main(int argc, char **argv)
{
  if (argc >= 2)
  {
    for (int i = 1; i < argc; i++)
    {
      char *a = argv[i];
      if ((a[0] == '-' && a[1])
#ifdef WIN32
        || a[0] == '/'
#endif
         )
      {
        bool f = true;
        while (f && *++a)
        {
          switch (*a)
          {
            case 'a':
              a++;
              f = false;
              if (*a)
                act = a;
              else if (i < argc)
                act = argv[++i];
              break;
            case 'b':
              blank = true;
              break;
            case 'c':
              cont = true;
              break;
            case 'd':
              a++;
              f = false;
              if (*a)
                dnum = soap_strtoull(a, NULL, 10);
              else if (i < argc)
                dnum = strtoull(argv[++i], NULL, 10);
              break;
            case 'f':
              ferr = true;
              break;
            case '?':
            case 'h':
              fprintf(stderr,
                  "Usage: testmsgr [-aact] [-b] [-c] [-dnum] [-f] [-h] [-i] [-j] [-lmax] [-mmax]\n"
                  "                [-nlen] [-pperc] [-qperc] [-rseed] [-tsec] [-u] [-v] [-A]\n"
                  "                [-C] [-H] [-M] [-Oopt] [-Pper] [-Rrep] [-Ssel]\n"
                  "                [-Xuid:pwd] [-Yhost[:port[:uid:pwd]]] [infile|-] [URL|port]\n\n"
                  "-aact   add a HTTP SOAP Action header with value \"act\"\n"
                  "-b      add random spacing to randomized values when using -r\n"
                  "-c      continuous testing until the service endpoint fails, when using a URL\n"
                  "-dnum   test a service endpoint num times or until it fails, when using a URL\n"
                  "-f      exit on any HTTP error or SOAP fault when using -c or -d\n"
                  "-h      display help message\n"
                  "-i      indent XML with spaces when using -r\n"
                  "-j      indent XML with tabs when using -r\n"
                  "-lmax   max number of randomized element repeats when using -r (default=3)\n"
                  "-mmax   max length of randomized text values generated (default=10)\n"
                  "-nlen   display the server response up to len bytes\n"
                  "-ofile  save to file instead of stdout\n"
                  "-pperc  percentage of XML kept in general when using -r (default=100)\n"
                  "-qperc  perc. XML kept of optional/repeat indicators when using -r (default=100)\n"
                  "-rseed  randomize XML messages from XML templates (use soapcpp2 -g)\n"
                  "-tsec   socket idle timeout seconds (default=1), to test response time\n"
                  "-u      include Unicode characters in randomized content\n"
                  "-v      verbose mode\n"
                  "-A      enable SOAP with MIME attachments (SwA)\n"
                  "-C      enable HTTP chunked transfers\n"
                  "-H      add HTTP headers when no URL is specified\n"
                  "-M      enable MTOM (application/xop+xml)\n"
                  "-Oopt   set the XML optional value indicator (default=" OPT ")\n"
                  "-Pper   set the XML element permutation indicator tag (default=" PER ")\n"
                  "-Rrep   set the XML element repetition indicator tag (default=" REP ")\n"
                  "-Ssel   set the XML element selection indicator tag (default=" SEL ")\n"
                  "-Xuid:pwd\n"
                  "        connect to server with authentication credentials uid and pwd\n"
                  "-Yhost[:port[:uid:pwd]]\n"
                  "        connect via proxy host, port, and proxy credentials uid and pwd\n"
                  "infile  XML message or XML message template with indicator annotations\n"
                  "-       read XML message or XML message template from standard input\n"
                  "URL     endpoint URL of service to test\n"
                  "port    stand-alone server port for clients to test against\n\n");
              exit(EXIT_SUCCESS);
            case 'i':
              iind = true;
              break;
            case 'j':
              jind = true;
              break;
            case 'l':
              a++;
              f = false;
              if (*a)
                lmax = soap_strtoul(a, NULL, 10);
              else if (i < argc)
                lmax = strtoul(argv[++i], NULL, 10);
              break;
            case 'm':
              a++;
              f = false;
              if (*a)
                mmax = soap_strtoul(a, NULL, 10);
              else if (i < argc)
                mmax = strtoul(argv[++i], NULL, 10);
              break;
            case 'n':
              a++;
              f = false;
              if (*a)
                nlen = soap_strtoll(a, NULL, 10);
              else if (i < argc)
                nlen = strtoll(argv[++i], NULL, 10);
              break;
            case 'o':
              a++;
              f = false;
              if (*a)
                ofile = a;
              else if (i < argc)
                ofile = argv[++i];
              break;
            case 'p':
              a++;
              f = false;
              if (*a)
                pperc = soap_strtoul(a, NULL, 10);
              else if (i < argc)
                pperc = strtoul(argv[++i], NULL, 10);
              if (pperc > 100)
              {
                fprintf(stderr, "testmsgr: -p option percentage value exceeds 100\n\n");
                exit(EXIT_FAILURE);
              }
              break;
            case 'q':
              a++;
              f = false;
              if (*a)
                qperc = soap_strtoul(a, NULL, 10);
              else if (i < argc)
                qperc = strtoul(argv[++i], NULL, 10);
              if (qperc > 100)
              {
                fprintf(stderr, "testmsgr: -q option percentage value exceeds 100\n\n");
                exit(EXIT_FAILURE);
              }
              break;
            case 'r':
              a++;
              f = false;
              if (*a)
                seed = soap_strtol(a, NULL, 10);
              else if (i < argc)
                seed = strtol(argv[++i], NULL, 10);
              break;
            case 't':
              a++;
              f = false;
              if (*a)
                tsec = soap_strtol(a, NULL, 10);
              else if (i < argc)
                tsec = strtol(argv[++i], NULL, 10);
              break;
            case 'u':
              uchr = true;
              break;
            case 'v':
              verb = true;
              break;
            case 'A':
              mime = true;
              http = true;
              break;
            case 'C':
              http = true;
              chnk = true;
              break;
            case 'H':
              http = true;
              break;
            case 'M':
              mtom = true;
              http = true;
              break;
            case 'O':
              a++;
              f = false;
              if (*a)
                opt = a;
              else if (i < argc)
                opt = argv[++i];
              if (!*opt)
              {
                fprintf(stderr, "testmsgr: Option -O requires a non-empty argument\n\n");
                exit(EXIT_FAILURE);
              }
              break;
            case 'P':
              a++;
              f = false;
              if (*a)
                per = a;
              else if (i < argc)
                per = argv[++i];
              if (!*per)
              {
                fprintf(stderr, "testmsgr: Option -P requires a non-empty argument\n\n");
                exit(EXIT_FAILURE);
              }
              break;
            case 'R':
              a++;
              f = false;
              if (*a)
                rep = a;
              else if (i < argc)
                rep = argv[++i];
              if (!*rep)
              {
                fprintf(stderr, "testmsgr: Option -R requires a non-empty argument\n\n");
                exit(EXIT_FAILURE);
              }
              break;
            case 'S':
              a++;
              f = false;
              if (*a)
                sel = a;
              else if (i < argc)
                sel = argv[++i];
              if (!*sel)
              {
                fprintf(stderr, "testmsgr: Option -S requires a non-empty argument\n\n");
                exit(EXIT_FAILURE);
              }
              break;
            case 'X':
              a++;
              f = false;
              if (*a)
                auth_userid = a;
              else if (i < argc && argv[++i])
                auth_userid = argv[i];
              else
                fprintf(stderr, "testmsgr: -X expects userid:passwd argument\n");
              if (auth_userid)
              {
                size_t l = strlen(auth_userid);
                char *s = (char*)malloc(l + 1);
                soap_strcpy(s, l + 1, auth_userid);
                auth_userid = s;
                s = strchr(auth_userid, ':');
                if (s)
                {
                  *s = '\0';
                  auth_passwd = s + 1;
                }
              }
              break;
            case 'Y':
              a++;
              f = false;
              if (*a)
                proxy_host = a;
              else if (i < argc && argv[++i])
                proxy_host = argv[i];
              else
                fprintf(stderr, "testmsgr: -Y expects proxy host:port:userid:passwd argument\n");
              if (proxy_host)
              {
                size_t l = strlen(proxy_host);
                char *s = (char*)malloc(l + 1);
                soap_strcpy(s, l + 1, proxy_host);
                proxy_host = s;
                s = strchr(proxy_host, ':');
                if (s)
                {
                  *s = '\0';
                  proxy_port = soap_strtol(s + 1, &s, 10);
                  if (s && *s == ':')
                  {
                    *s = '\0';
                    proxy_userid = s + 1;
                    s = strchr(proxy_userid, ':');
                    if (s)
                    {
                      *s = '\0';
                      proxy_passwd = s + 1;
                    }
                  }
                }
              }
              break;
            default:
              fprintf(stderr, "testmsgr: Unknown option %s\n\n", a);
              exit(EXIT_FAILURE);
          }
        }
      }
      else
      {
        if (!ifile)
        {
          ifile = argv[i];
        }
        else if (URL)
        {
          fprintf(stderr, "testmsgr: server URL already specified as %s, ignoring %s\n\n", URL, a);
        }
        else if (port)
        {
          fprintf(stderr, "testmsgr: port already specified as %lu, ignoring %s\n\n", port, a);
        }
        else
        {
          char *r = NULL;
          unsigned long n = strtoul(argv[i], &r, 10);
          if (r && !*r && n)
            port = n;
          else
            URL = argv[i];
        }
      }
    }
  }
  else
  {
    ifile = "-";
  }

  if (!URL)
  {
    cont = false;
    dnum = 0;
  }

#ifndef WIN32
  signal(SIGPIPE, sigpipe_handler);
#endif

  soap *ctx = soap_new1(SOAP_IO_KEEPALIVE | SOAP_C_UTFSTRING | SOAP_DOM_ASIS);

  ctx->send_timeout = tsec;
  ctx->recv_timeout = tsec;

  std::ifstream ifs;

  if (ifile)
  {
    if (!strcmp(ifile, "-"))
    {
      ctx->is = &std::cin;
    }
    else
    {
      ifs.open(ifile, std::ifstream::in);
      if (!ifs.is_open())
      {
        fprintf(stderr, "testmsgr: failed to open %s for reading\n", ifile);
        exit(EXIT_FAILURE);
      }
      ctx->is = &ifs;
    }
  }

  std::ofstream ofs;

  if (ofile)
  {
    ofs.open(ofile, std::ofstream::out);
    if (!ofs.is_open())
    {
      fprintf(stderr, "testmsgr: failed to open %s for writing\n", ofile);
      exit(EXIT_FAILURE);
    }
    ctx->os = &ofs;
  }

  xsd__anyType dom(ctx);

  if (ifile)
  {
    if (soap_read_xsd__anyType(ctx, &dom))
    {
      soap_print_fault(ctx, stderr);
      soap_print_fault_location(ctx, stderr);
      exit(EXIT_FAILURE);
    }
    if (!dom.tag())
    {
      fprintf(stderr, "testmsgr: No root element in %s\n", ifile);
      exit(EXIT_FAILURE);
    }
  }

  if (ifile && strcmp(ifile, "-"))
  {
    ctx->is = NULL;
    ifs.close();
  }

  if (chnk)
    soap_set_omode(ctx, SOAP_IO_CHUNK);
  else
    soap_set_omode(ctx, SOAP_IO_STORE);

  if (port)
    test_server(ctx, dom);
  else
    test_client(ctx, dom);

  if (ofile)
  {
    ctx->os = NULL;
    ofs.close();
  }

  soap_destroy(ctx);
  soap_end(ctx);
  soap_free(ctx);

  return 0;
}

static void test_server(struct soap *ctx, xsd__anyType& dom)
{
  struct soap *soap = soap_copy(ctx);

  SOAP_SOCKET m = soap_bind(soap, NULL, (int)port, 100);

  if (!soap_valid_socket(m))
  {
    soap_print_fault(soap, stderr);
  }
  else
  {
    for (;;)
    {
      SOAP_SOCKET s = soap_accept(soap);

      if (!soap_valid_socket(s))
      {
        soap_print_fault(soap, stderr);
        break;
      }

      if (receive_request(soap) == SOAP_OK)
      {
        if (mime)
        {
          soap_set_mime(soap, NULL, NULL);
        }
        else if (mtom)
        {
          soap_set_omode(soap, SOAP_ENC_MTOM);
          soap_set_mime(soap, NULL, NULL);
        }

        if (soap_response(soap, SOAP_OK))
        {
          fprintf(stderr, "testmsgr: failed to send response\n");
          soap_print_fault(soap, stderr);
        }
        else
        {
          gen_test(soap, dom);

          if (soap_end_send(soap))
          {
            fprintf(stderr, "testmsgr: failed to send response\n");
            soap_print_fault(soap, stderr);
          }
        }
      }

      soap_closesock(soap);
      soap_destroy(soap);
      soap_end(soap);
    }
  }

  soap_destroy(soap);
  soap_end(soap);
  soap_free(soap);
}

static void test_client(struct soap *ctx, xsd__anyType& dom)
{
  struct soap *soap = soap_copy(ctx);

  if (mime)
  {
    soap_set_mime(soap, NULL, NULL);
  }
  else if (mtom)
  {
    soap_set_omode(soap, SOAP_ENC_MTOM);
    soap_set_mime(soap, NULL, NULL);
  }

  if (URL && !strncmp(URL, "https://", 8))
  {
#ifdef WITH_OPENSSL
    if (soap_ssl_client_context(
          soap,
          SOAP_SSL_NO_AUTHENTICATION,
          NULL,
          NULL,
          NULL,
          NULL,
          NULL
          ))
    {
      fprintf(stderr, "testmsgr: failed to initialize OpenSSL\n");
      soap_print_fault(soap, stderr);
      exit(EXIT_FAILURE);
    }
#else
#ifdef WIN32
    fprintf(stderr, "\nCannot connect to https server: SSL/TLS support not enabled in this version. Visit https://www.genivia.com/downloads.html to download the secure version of testmsgr.exe that supports SSL/TLS to connect to https servers.\n");
#else
    fprintf(stderr, "\nCannot connect to https server: SSL/TLS support not enabled in this version with WITH_OPENSSL.\n");
#endif
    exit(EXIT_FAILURE);
#endif
  }

#ifdef HTTPDA_H
  struct http_da_info info;
  bool auth = false;
#endif
  size_t redirs = 0;
  ULONG64 i = 1;
  do
  {
    if (cont || dnum)
      fprintf(stderr, "Iteration " SOAP_ULONG_FORMAT "\n", i);

    if (URL)
    {
#ifdef HTTPDA_H
      if (auth)
        http_da_restore(soap, &info);
#else
      soap->userid = auth_userid;
      soap->passwd = auth_passwd;
#endif
      if (soap_connect(soap, URL, act))
      {
        if ((soap->error >= 301 && soap->error <= 303) || soap->error == 307)
        {
          URL = soap_strdup(soap, soap->endpoint);
          if (redirs++ < 10)
          {
            fprintf(stderr, "Redirected to '%s'...\n", URL);
            continue;
          }
        }
        else if (soap->error == 401)
        {
#ifdef HTTPDA_H
          if (!auth && auth_userid && auth_passwd)
          { 
            http_da_save(soap, &info, soap->authrealm, auth_userid, auth_passwd);
            auth = true;
            continue;
          }
          else
#endif
          {
            fprintf(stderr, "Failed to authenticate to '%s' realm '%s'\n", URL, soap->authrealm);
          }
        }
        fprintf(stderr, "testmsgr: failed to connect to %s\n", URL);
        soap_print_fault(soap, stderr);
        exit(EXIT_FAILURE);
      }
    }
    else if (http)
    {
      soap->userid = auth_userid;
      soap->passwd = auth_passwd;
      if (soap_connect(soap, "http://", act))
      {
        fprintf(stderr, "testmsgr: failed to write to output\n");
        soap_print_fault(soap, stderr);
        exit(EXIT_FAILURE);
      }
    }
    else
    {
      if (soap_begin_send(soap))
      {
        fprintf(stderr, "testmsgr: failed to write to output\n");
        soap_print_fault(soap, stderr);
        exit(EXIT_FAILURE);
      }
    }

    gen_test(soap, dom);

    if (soap_end_send(soap))
    {
      if (URL)
        fprintf(stderr, "testmsgr: failed to send to %s\n", URL);
      else
        fprintf(stderr, "testmsgr: failed to write to output\n");
      soap_print_fault(soap, stderr);
      exit(EXIT_FAILURE);
    }

    if (URL)
      receive_response(soap);

    soap_destroy(soap);
    soap_end(soap);

    ++i;

  } while (cont || (dnum && i <= dnum));

  soap_destroy(soap);
  soap_end(soap);
  soap_free(soap);
}

static void gen_test(struct soap *ctx, xsd__anyType& dom)
{
  soap_set_version(ctx, 0);

  // generate MIME protocol headers (no SOAP envelope)
  if (soap_envelope_begin_out(ctx))
  {
    if (URL)
      fprintf(stderr, "testmsgr: failed to send to %s\n", URL);
    else
      fprintf(stderr, "testmsgr: failed to write output\n");
    soap_print_fault(ctx, stderr);
    exit(EXIT_FAILURE);
  }

  if (seed)
  {
    fprintf(stderr, "Random seed = %lu\n", seed);
#ifdef HAVE_RANDOM
    srandom(seed);
    seed = random();
#else
    srand((int)seed);
    seed = rand();
#endif
    cidnum = 0;
    idnum = 0;
    idref = 0;
    gen_random_test(ctx, dom);
    gen_random_attachments(ctx);
  }
  else if (soap_out_xsd__anyType(ctx, NULL, 0, &dom, NULL))
  {
    if (URL)
      fprintf(stderr, "testmsgr: failed to send to %s\n", URL);
    else
      fprintf(stderr, "testmsgr: failed to write output\n");
    soap_print_fault(ctx, stderr);
    exit(EXIT_FAILURE);
  }
}

static void gen_random_test(struct soap *ctx, xsd__anyType& dom, size_t tab)
{
  if (pperc == 100 || gen_random() % 100 < pperc)
  {
    xsd__anyType::iterator i = dom.elt_begin();
    xsd__anyType::iterator end = dom.elt_end();

    if (dom.tag() && !strcmp(dom.tag(), per))
    {
      unsigned long n = (unsigned long)dom.elt_size();

      std::vector<xsd__anyType::iterator> p;
      for (unsigned long m = 0; m < n; ++m)
        p.push_back(i++);

      // generate a random permutation of dom.elt_size() child nodes using Knuth shuffles
      for (std::vector<xsd__anyType::iterator>::iterator j = p.begin(); j != p.end(); ++j)
      {
        std::vector<xsd__anyType::iterator>::iterator k = j;
        for (unsigned long m = gen_random() % n; m > 0; --m)
          ++k;
        if (j != k)
        {
          xsd__anyType::iterator tmp = *j;
          *j = *k;
          *k = tmp;
        }
        --n;
      }

      for (std::vector<xsd__anyType::iterator>::iterator j = p.begin(); j != p.end(); ++j)
      {
        if (j != p.begin())
          gen_indent(ctx, tab);
        gen_random_test(ctx, **j, tab);
      }
    }
    else if (dom.tag() && !strcmp(dom.tag(), rep))
    {
      unsigned long n, m;
      bool d = false;

      if (get_dim(ctx, dom, n))
      {
        m = n; // dimension that was set by arrayType/arraySize
      }
      else
      {
        get_minmax(ctx, dom, n, m);
      }
      for (unsigned long j = 0; j < m; ++j)
      {
        xsd__anyType::iterator k = i;
        if (j < n || qperc == 100 || gen_random() % 100 < qperc)
        {
          while (k != end)
          {
            if (d)
              gen_indent(ctx, tab);
            gen_random_test(ctx, *k++, tab);
            d = true;
          }
          if (dom.get_text())
            gen_value(ctx, dom.get_text());
        }
      }
    }
    else if (dom.tag() && !strcmp(dom.tag(), sel))
    {
      for (unsigned long n = gen_random() % (unsigned long)dom.elt_size(); n > 0; --n)
        ++i;
      gen_random_test(ctx, *i, tab);
    }
    else
    {
      const char *t = NULL;

      if (i != end)
        t = i->get_text();

      if (!t || strcmp(t, opt) || qperc == 100 || gen_random() % 100 < qperc)
      {
        if (dom.tag())
        {
          gen_send(ctx, "<");
          gen_send(ctx, dom.tag());

          for (xsd__anyAttribute::iterator j = dom.att_begin(); j != dom.att_end(); ++j)
          {
            if (!strcmp(j->tag(), "SOAP-ENC:arrayType") && j->ns() && !strcmp(j->ns(), "http://schemas.xmlsoap.org/soap/encoding/"))
            {
              std::string dims;
              xsd__anyType *ptr = &dom;
              do
              {
                xsd__anyType::iterator k = ptr->elt_begin();
                ptr = NULL;
                if (k != dom.elt_end() && !strcmp(k->tag(), rep))
                {
                  unsigned long n, m;
                  get_minmax(ctx, *k, n, m);
                  if (qperc != 100 && n != m)
                    m = n + (unsigned long)gen_random() % (m - n + 1);
                  std::string dim = soap_unsignedLong2s(ctx, m);
                  k->att("dim") = dim;
                  if (dims.empty())
                    dims = dim;
                  else
                    dims.append(",").append(dim);
                  ptr = &*k;
                }
              } while (ptr);
              std::string type = j->get_text();
              size_t k = type.find('[');
              if (k != std::string::npos)
                *j = type.substr(0, k + 1) + dims + "]";
            }
            else if (!strcmp(j->tag(), "SOAP-ENC:arraySize") && j->ns() && !strcmp(j->ns(), "http://www.w3.org/2003/05/soap-encoding"))
            {
              std::string dims;
              xsd__anyType *ptr = &dom;
              do
              {
                xsd__anyType::iterator k = ptr->elt_begin();
                ptr = NULL;
                if (k != dom.elt_end() && !strcmp(k->tag(), rep))
                {
                  unsigned long n, m;
                  get_minmax(ctx, *k, n, m);
                  if (qperc != 100 && n != m)
                    m = n + (unsigned long)gen_random() % (m - n + 1);
                  std::string dim = soap_unsignedLong2s(ctx, m);
                  k->att("dim") = dim;
                  if (dims.empty())
                    dims = dim;
                  else
                    dims.append(" ").append(dim);
                  ptr = &*k;
                }
              } while (ptr);
              *j = dims;
            }
            if (!strncmp(j->tag(), "xml", 3) || pperc == 100 || gen_random() % 100 < pperc)
            {
              const char *t = j->get_text();

              if (!t || strncmp(t, opt, strlen(opt)) || qperc == 100 || gen_random() % 100 < qperc)
              {
                gen_send(ctx, " ");
                gen_send(ctx, j->tag());

                if (t)
                {
                  gen_send(ctx, "=\"");
                  gen_value(ctx, t);
                  gen_send(ctx, "\"");
                }
              }
            }
          }
          t = dom.get_text();
          if ((mime || mtom) && t && !strcmp(t, "%[[BASE64]]%"))
          {
            if (mtom)
            {
              gen_send(ctx, ">");
              gen_indent(ctx, tab + 1);
              gen_send(ctx, "<xop:Include xmlns:xop=\"http://www.w3.org/2004/08/xop/include\"");
            }
            gen_send(ctx, " href=\"cid:id");
            gen_send(ctx, soap_ULONG642s(ctx, ++cidnum));
            gen_send(ctx, "\"/>");
            if (mtom)
            {
              gen_indent(ctx, tab);
              gen_send(ctx, "</");
              gen_send(ctx, dom.tag());
              gen_send(ctx, ">");
            }
          }
          else
          {
            gen_send(ctx, ">");

            while (i != end)
            {
              gen_indent(ctx, tab + 1);
              gen_random_test(ctx, *i++, tab + 1);
            }

            if (t)
              gen_value(ctx, t);
            else
              gen_indent(ctx, tab);

            gen_send(ctx, "</");
            gen_send(ctx, dom.tag());
            gen_send(ctx, ">");
          }
        }
        else
        {
          t = dom.get_text();
          if (t && strcmp(t, opt))
            gen_value(ctx, t);
        }
      }
    }
  }
}

static void gen_random_attachments(struct soap *ctx)
{
  for (size_t i = 0; i < cidnum; ++i)
  {
    struct soap_multipart content;
    content.type = "text/plain";
    content.encoding = SOAP_MIME_BINARY;
    (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), "<id%zu>", i);
    content.id = ctx->tmpbuf;
    content.location = NULL;
    content.description = NULL;
    if (soap_putmimehdr(ctx, &content))
    {
      if (URL)
        fprintf(stderr, "testmsgr: failed to send to %s\n", URL);
      else
        fprintf(stderr, "testmsgr: failed to write output\n");
      soap_print_fault(ctx, stderr);
      exit(EXIT_FAILURE);
    }
    gen_value(ctx, "%[[TEXT]]%");
  }
}

static void get_minmax(struct soap *ctx, xsd__anyType& dom, unsigned long& n, unsigned long& m)
{
  (void)ctx;
  n = m = 1;
  xsd__anyAttribute::iterator i;
  i = dom.att_find("min");
  if (i != dom.att_end())
    n = soap_strtoul(i->get_text(), NULL, 10);
  i = dom.att_find("max");
  if (i != dom.att_end())
  {
    const char *t = i->get_text();
    if (!strcmp(t, "unbounded"))
      m = 4294967295;
    else
      m = soap_strtoul(i->get_text(), NULL, 10);
  }
  if (n > m)
    m = n;
  if (m > lmax)
  {
    if (n > lmax)
      m = n;
    else
      m = lmax;
  }
}

static bool get_dim(struct soap *ctx, xsd__anyType& dom, unsigned long& n)
{
  (void)ctx;
  n = 0;
  xsd__anyAttribute::iterator i;
  i = dom.att_find("dim");
  if (i == dom.att_end())
    return false;
  n = soap_strtoul(i->get_text(), NULL, 10);
  return true;
}

static void gen_value(struct soap *ctx, const char *s)
{
  if (!strncmp(s, opt, strlen(opt)))
  {
    if (qperc < 100 && gen_random() % 100 >= qperc)
      return;
    s += 3;
  }

  if (s[0] == '%' && s[1] == '[')
  {
    long x = gen_random();

    if (!strcmp(s, "%[[BOOL]]%"))
    {
      gen_send_verb(ctx, s, x % 2 ? "true" : "false");
      return;
    }

    if (!strcmp(s, "%[[INT8]]%") || !strcmp(s, "%[[BYTE]]%"))
    {
      char y;
      switch (x % 4)
      {
        case 0:
          gen_send_verb(ctx, s, "0");
          return;
        case 1:
          gen_send_verb(ctx, s, "-128");
          return;
        case 2:
          gen_send_verb(ctx, s, "127");
          return;
        default:
          y = (char)x;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), "%d", (int)y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
      }
    }

    if (!strcmp(s, "%[[UINT8]]%") || !strcmp(s, "%[[UBYTE]]%"))
    {
      unsigned char y;
      switch (x % 4)
      {
        case 0:
          gen_send_verb(ctx, s, "0");
          return;
        case 1:
          gen_send_verb(ctx, s, "255");
          return;
        default:
          y = (unsigned char)x;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), "%u", (unsigned int)y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
      }
    }

    if (!strcmp(s, "%[[INT16]]%") || !strcmp(s, "%[[SHORT]]%"))
    {
      short y;
      switch (x % 4)
      {
        case 0:
          gen_send_verb(ctx, s, "0");
          return;
        case 1:
          gen_send_verb(ctx, s, "-32768");
          return;
        case 2:
          gen_send_verb(ctx, s, "32767");
          return;
        default:
          y = (short)x;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), "%d", (int)y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
      }
    }

    if (!strcmp(s, "%[[UINT16]]%") || !strcmp(s, "%[[USHORT]]%"))
    {
      unsigned short y;
      switch (x % 4)
      {
        case 0:
          gen_send_verb(ctx, s, "0");
          return;
        case 1:
          gen_send_verb(ctx, s, "65535");
          return;
        default:
          y = (unsigned short)x;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), "%d", (unsigned int)y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
      }
    }

    if (!strcmp(s, "%[[INT32]]%") || !strcmp(s, "%[[INT]]%"))
    {
      int y;
      switch (x % 4)
      {
        case 0:
          gen_send_verb(ctx, s, "0");
          return;
        case 1:
          gen_send_verb(ctx, s, "-2147483648");
          return;
        case 2:
          gen_send_verb(ctx, s, "2147483647");
          return;
        default:
          y = (int)x;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), "%d", y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
      }
    }

    if (!strcmp(s, "%[[UINT32]]%") || !strcmp(s, "%[[UINT]]%"))
    {
      unsigned int y;
      switch (x % 4)
      {
        case 0:
          gen_send_verb(ctx, s, "0");
          return;
        case 1:
          gen_send_verb(ctx, s, "4294967295");
          return;
        default:
          y = (unsigned int)x;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), "%u", y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
      }
    }

    if (!strcmp(s, "%[[INT64]]%") || !strcmp(s, "%[[LONG]]%"))
    {
      LONG64 y;
      switch (x % 4)
      {
        case 0:
          gen_send_verb(ctx, s, "0");
          return;
        case 1:
          gen_send_verb(ctx, s, "-9223372036854775808");
          return;
        case 2:
          gen_send_verb(ctx, s, "9223372036854775807");
          return;
        default:
          y = (LONG64)x;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), SOAP_LONG_FORMAT, y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
      }
    }

    if (!strcmp(s, "%[[UINT64]]%") || !strcmp(s, "%[[ULONG]]%"))
    {
      ULONG64 y;
      switch (x % 4)
      {
        case 0:
          gen_send_verb(ctx, s, "0");
          return;
        case 1:
          gen_send_verb(ctx, s, "18446744073709551615");
          return;
        default:
          y = (ULONG64)x;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), SOAP_ULONG_FORMAT, y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
      }
    }

    if (!strcmp(s, "%[[FLOAT]]%") || !strncmp(s, "%[[FLOAT%", 9))
    {
      char buf[16];
      const char *format = ctx->float_format;
      if (s[8] == '%')
      {
        // get %D.F format string
        size_t i = 0;
        for (const char *r = s + 8; *r != ']' && i < sizeof(buf) - 1; ++r)
          buf[i++] = *r;
        buf[i] = '\0';
        format = buf;
      }
      float y;
      switch (x % 10)
      {
        case 0:
          gen_send_verb(ctx, s, "NaN");
          return;
        case 1:
          gen_send_verb(ctx, s, "INF");
          return;
        case 2:
          gen_send_verb(ctx, s, "-INF");
          return;
        case 3:
          gen_send_verb(ctx, s, "0.0");
          return;
        case 4:
          gen_send_verb(ctx, s, "-0.0");
          return;
        case 5:
          y = -FLT_MAX;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), format, y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
        case 6:
          y = FLT_MAX;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), format, y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
        default:
          y = (float)x/(float)0xFFFF;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), format, y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
      }
    }

    if (!strcmp(s, "%[[DOUBLE]]%") || !strncmp(s, "%[[DOUBLE%", 10))
    {
      char buf[16];
      const char *format = ctx->double_format;
      if (s[9] == '%')
      {
        // get %D.F format string
        size_t i = 0;
        for (const char *r = s + 9; *r != ']' && i < sizeof(buf) - 1; ++r)
          buf[i++] = *r;
        buf[i] = '\0';
        format = buf;
      }
      double y;
      switch (x % 10)
      {
        case 0:
          gen_send_verb(ctx, s, "NaN");
          return;
        case 1:
          gen_send_verb(ctx, s, "INF");
          return;
        case 2:
          gen_send_verb(ctx, s, "-INF");
          return;
        case 3:
          gen_send_verb(ctx, s, "0.0");
          return;
        case 4:
          gen_send_verb(ctx, s, "-0.0");
          return;
        case 5:
          y = -DBL_MAX;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), format, y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
        case 6:
          y = DBL_MAX;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), format, y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
        default:
          y = (double)x/(double)0xFFFF;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), format, y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          return;
      }
    }

    if (!strcmp(s, "%[[DATETIME]]%"))
    {
      time_t t = (time_t)x;
      switch (x % 4)
      {
        case 0:
          gen_send_verb(ctx, s, "0000-01-00T00:00:00Z");
          return;
        case 1:
          gen_send_verb(ctx, s, "1969-12-31T23:59:59Z");
          return;
        default:
          t = (time_t)x;
          gen_send_verb(ctx, s, soap_dateTime2s(ctx, t));
          return;
      }
    }

    if (!strcmp(s, "%[[DATE]]%"))
    {
      time_t t = (time_t)x;
      char *r;
      switch (x % 4)
      {
        case 0:
          gen_send_verb(ctx, s, "0000-01-00Z");
          return;
        case 1:
          gen_send_verb(ctx, s, "1969-12-31Z");
          return;
        default:
          t = (time_t)x;
          soap_dateTime2s(ctx, t);
          r = strchr(ctx->tmpbuf, 'T');
          if (r)
          {
            r[0] = 'Z';
            r[1] = '\0';
            gen_send_verb(ctx, s, ctx->tmpbuf);
          }
          return;
      }
    }

    if (!strcmp(s, "%[[TIME]]%"))
    {
      time_t t = (time_t)x;
      switch (x % 4)
      {
        case 0:
          gen_send_verb(ctx, s, "00:00:00Z");
          return;
        default:
          t = (time_t)x;
          gen_send_verb(ctx, s, soap_dateTime2s(ctx, t) + 11);
          return;
      }
    }

    if (!strcmp(s, "%[[DURATION]]%"))
    {
      int y;
      switch (x % 4)
      {
        case 0:
          gen_send_verb(ctx, s, "PT0S");
          return;
        default:
          gen_send_verb(ctx, s, "PT");
          y = (int)x;
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), "%d", y);
          gen_send_verb(ctx, s, ctx->tmpbuf);
          gen_send(ctx, "S");
          return;
      }
    }

    if (!strncmp(s, "%[[HEX", 6))
    {
      if (s[6] == '[')
      {
        unsigned long n, m;
        if (get_range(s + 7, n, m))
        {
          gen_hex(ctx, x, n + x % (m - n + 1));
          return;
        }
      }
      else if (s[6] == ']' && s[7] == ']' && s[8] == '%' && !s[9])
      {
        ULONG64 n = x;
        switch (x % 6)
        {
          case 0:
            gen_hex(ctx, x, 0);
            return;
          case 1:
            n %= 16;
            if (n > mmax)
              n = mmax;
            gen_hex(ctx, x, n);
            return;
          case 2:
            n %= 256;
            if (n > mmax)
              n = mmax;
            gen_hex(ctx, x, n);
            return;
          case 3:
            n %= 65536;
            if (n > mmax)
              n = mmax;
            gen_hex(ctx, x, n);
            return;
          case 4:
            if (n > mmax)
              n = mmax;
            gen_hex(ctx, x, n);
            return;
          default:
            gen_hex(ctx, x, mmax);
            return;
        }
      }
    }

    if (!strncmp(s, "%[[BASE64", 9))
    {
      if (s[9] == '[')
      {
        unsigned long n, m;
        if (get_range(s + 10, n, m))
        {
          gen_base64(ctx, x, n + x % (m - n + 1));
          return;
        }
      }
      else if (s[9] == ']' && s[10] == ']' && s[11] == '%' && !s[12])
      {
        ULONG64 n = x;
        switch (x % 6)
        {
          case 0:
            gen_base64(ctx, x, 0);
            return;
          case 1:
            n %= 16;
            if (n > mmax)
              n = mmax;
            gen_base64(ctx, x, n);
            return;
          case 2:
            n %= 256;
            if (n > mmax)
              n = mmax;
            gen_base64(ctx, x, n);
            return;
          case 3:
            n %= 65536;
            if (n > mmax)
              n = mmax;
            gen_base64(ctx, x, n);
            return;
          case 4:
            if (n > mmax)
              n = mmax;
            gen_base64(ctx, x, n);
            return;
          default:
            gen_base64(ctx, x, mmax);
            return;
        }
      }
    }

    if (!strcmp(s, "%[[ID]]%"))
    {
      gen_id(ctx);
      return;
    }

    if (!strcmp(s, "%[[IDREF]]%"))
    {
      gen_idref(ctx);
      return;
    }

    if (!strncmp(s, "%[[ENTITY", 9) || !strncmp(s, "%[[NAME", 7) || !strncmp(s, "%[[NCNAME", 9) || !strncmp(s, "%[[NMTOKEN", 10) || !strncmp(s, "%[[TOKEN", 8))
    {
      size_t i = s[4] == 'C' ? 9 : s[4] == 'M' ? 10 : s[4] == 'A' ? 7 : s[3] == 'T' ? 8 : 9;
      if (s[i] == '[')
      {
        unsigned long n, m;
        if (get_range(s + i + 1, n, m))
        {
          gen_name(ctx, x, n + x % (m - n + 1));
          return;
        }
      }
      else if (s[i] == ']' && s[i + 1] == ']' && s[i + 2] == '%' && !s[i + 3])
      {
        ULONG64 n = x;
        if (n == 0)
          n = 1;
        switch (x % 6)
        {
          case 0:
            gen_name(ctx, x, 1);
            return;
          case 1:
            n %= 16;
            if (n > mmax)
              n = mmax;
            gen_name(ctx, x, n);
            return;
          case 2:
            n %= 256;
            if (n > mmax)
              n = mmax;
            gen_name(ctx, x, n);
            return;
          case 3:
            n %= 65536;
            if (n > mmax)
              n = mmax;
            gen_name(ctx, x, n);
            return;
          case 4:
            if (n > mmax)
              n = mmax;
            gen_name(ctx, x, n);
            return;
          default:
            gen_name(ctx, x, mmax);
            return;
        }
      }
    }

    if (!strncmp(s, "%[[LANG", 7))
    {
      if (s[7] == '[')
      {
        unsigned long n, m;
        if (get_range(s + 8, n, m))
        {
          gen_lang(ctx, x, n + x % (m - n + 1));
          return;
        }
      }
      else if (s[7] == ']' && s[8] == ']' && s[9] == '%' && !s[10])
      {
        static const char iso639_1[] = "aaasaeafisamanisasasayazbabebgbhNibmasasbrbscacechcocrcsbycvcydadedvdzeeeleneoeseteuasasfifjfofrasgagdglgngugvhahehihohrhthuhyhzbymsinigYiikofisitiujajvkakgkikjkkklorknkokrkskukvkwkylalblglilnloltaslvmgmhasmkmlmnasidmtmyasnoasnengnlnonnasnvnyocasomasospaasplpsptqurmasberurwasscsdsesgsiskassmsnsoinsrasstsusvswtatetgthtitktltntotrtsttakofugukuruzvevivowawoxhyiyozazhzu";
        switch (x % 4)
        {
          case 0:
            gen_send_verb(ctx, s, "en", 2);
            return;
          default:
            gen_send_verb(ctx, s, iso639_1 + 2 * (gen_random() % ((sizeof(iso639_1) - 1)/2)), 2);
            return;
        }
      }
    }

    if (!strcmp(s, "%[[URI]]%"))
    {
      switch (x % 2)
      {
        case 0:
          gen_send_verb(ctx, s, "http://www.example.com/schema/anyURI");
          return;
        default:
          gen_uri(ctx, x, x % 32);
          return;
      }
    }

    if (!strcmp(s, "%[[QNAME]]%"))
    {
      switch (x % 2)
      {
        case 0:
          gen_send_verb(ctx, s, "xsd:string");
          return;
        default:
          gen_qname(ctx, x, x % 32);
          return;
      }
    }

    if (!strncmp(s, "%[[TEXT", 7))
    {
      if (s[7] == '[')
      {
        unsigned long n, m;
        if (get_range(s + 8, n, m))
        {
          gen_text(ctx, x, n + x % (m - n + 1));
          return;
        }
      }
      else if (s[7] == ']' && s[8] == ']' && s[9] == '%' && !s[10])
      {
        ULONG64 n = x;
        switch (x % 6)
        {
          case 0:
            gen_text(ctx, x, 0);
            return;
          case 1:
            n %= 16;
            if (n > mmax)
              n = mmax;
            gen_text(ctx, x, n);
            return;
          case 2:
            n %= 256;
            if (n > mmax)
              n = mmax;
            gen_text(ctx, x, n);
            return;
          case 3:
            n %= 65536;
            if (n > mmax)
              n = mmax;
            gen_text(ctx, x, n);
            return;
          case 4:
            if (n > mmax)
              n = mmax;
            gen_text(ctx, x, n);
            return;
          default:
            gen_text(ctx, x, mmax);
            return;
        }
      }
    }

    if ((s[2] == '[' || s[2] == '(') && strchr(s, ':'))
    {
      // inclusive or exclusive numeric range %[[N:M]]% %[(N:M)]% %[(N:M]]% %[[N:M)]%
      char *r;
      double n = strtod(s + 3, &r);
      double m = n;
      char buf[16];
      const char *format = ctx->double_format;
      if (r && *r == ':')
        m = strtod(r + 1, &r);
      if (r && *r == '%')
      {
        // get %D.F format string after the range: %[[N:M%D.F]]%, %[(N:M%D.F]]%, %[[N:M%D.F)]%, %[(N:M%D.F)]%
        size_t i = 0;
        for (; *r && *r != ')' && *r != ']' && i < sizeof(buf) - 1; ++r)
          buf[i++] = *r;
        buf[i] = '\0';
        format = buf;
      }
      bool isint = !strchr(s, '.');
      if (r && (r[0] == ']' || r[0] == ')') && r[1] == ']' && r[2] == '%' && !r[3] &&
          ( (isint && s[2] == '(' && r[0] == ')' && n + 1 < m) ||
            (isint && s[2] == '(' && r[0] == ']' && n < m) ||
            (isint && s[2] == '[' && r[0] == ')' && n < m) ||
            (isint && s[2] == '[' && r[0] == ']' && n <= m) ||
            (!isint && n <= m)
          ))
      {
        double y;
        for (;;)
        {
          y = exp((double)x/(double)RAND_MAX * log(m - n)) + n;
          if (isint)
            y = round(y);
          if ((s[2] != '(' || y > n) && (r[0] != ')' || y < m))
            break;
          x = gen_random();
        }
        if (isint)
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), SOAP_LONG_FORMAT, (LONG64)y);
        else
          (SOAP_SNPRINTF(ctx->tmpbuf, sizeof(ctx->tmpbuf), 24), format, y);
        gen_send_verb(ctx, s, ctx->tmpbuf);
        return;
      }
    }

    // regex %[['pattern']]% and %[['pattern'[N:M]]]%
    if (s[2] == '[' && s[3] == '\'')
    {
      const char *r;
      for (r = s + 4; *r; ++r)
      {
        if (r[0] == '\\' && r[1] != '\0')
          ++r;
        else if (r[0] == '\'' && (r[1] == '[' || r[1] == ']'))
          break;
      }
      if (*r)
      {
        std::string pattern(s + 4, r - s - 4);
        unsigned long n = 0, m = mmax;
        if (r[1] == '[')
        {
          if (get_range(r + 2, n, m))
          {
            gen_pattern(ctx, pattern.c_str(), n, m);
            return;
          }
        }
        else
        {
          gen_pattern(ctx, pattern.c_str(), n, m);
          return;
        }
      }
    }

    // enumeration %[[A][B][C]]%
    if (s[2] == '[' && strstr(s, "]["))
    {
      size_t k = 1;
      const char *r = s + 1;
      while ((r = strstr(r + 2, "][")) != NULL)
        ++k;
      long y = x % k;
      r = s + 1;
      while (y-- > 0)
        r = strstr(r + 2, "][");
      r += 2;
      const char *t = strstr(r, "][");
      if (t)
      {
        k = t - r;
      }
      else
      {
        k = strlen(r);
        if (k >= 3)
          k -= 3;
      }
      gen_send_verb(ctx, s, r, k);
      return;
    }
  }

  gen_send(ctx, s);
}

static bool get_range(const char *s, unsigned long& n, unsigned long& m)
{
  char *r;
  m = n = soap_strtoul(s, &r, 10);
  if (r && *r == ':')
    m = soap_strtoul(r + 1, &r, 10);
  if (m > mmax)
  {
    m = mmax;
    if (m < n)
      m = n;
  }
  return r && r[0] == ']' && r[1] == ']' && r[2] == ']' && r[3] == '%' && !r[4];
}

static void gen_name(struct soap *ctx, ULONG64 x, ULONG64 n)
{
  if (verb)
    printf("%%[[NAME]]%% " SOAP_ULONG_FORMAT " bytes\n", n);
  gen_ncname(ctx, x, n);
}

static void gen_qname(struct soap *ctx, ULONG64 x, ULONG64 n)
{
  if (verb)
    printf("%%[[QNAME]]%% " SOAP_ULONG_FORMAT " bytes\n", n);
  ULONG64 m = n, k = n;
  if (n >= 4)
  {
    m = n/4;
    k = n - m;
  }
  gen_ncname(ctx, x, m);
  gen_send(ctx, ":");
  gen_ncname(ctx, x, k);
}

static void gen_uri(struct soap *ctx, ULONG64 x, ULONG64 n)
{
  if (verb)
    printf("%%[[URI]]%% " SOAP_ULONG_FORMAT " bytes\n", n);
  ULONG64 m = n, k = n;
  if (n >= 4)
  {
    m = n/4;
    k = n - m;
  }
  gen_send(ctx, "http://www.");
  gen_ncname(ctx, x, m);
  gen_send(ctx, ".com/");
  gen_ncname(ctx, x, k);
}

static void gen_ncname(struct soap *ctx, ULONG64 x, ULONG64 n)
{
  for (ULONG64 i = 0; i < n; ++i)
  {
    char c = (x ^ n ^ i) % 78 + 45;
    if (i == 0 && !isalpha(c))
      c = 'A' + c % 26 + (c & 0x20);
    else if (!isalnum(c) && c != '-' && c != '_')
      c = 'A' + c % 26 + (c & 0x20);
    if (soap_send_raw(ctx, &c, 1))
    {
      fprintf(stderr, "testmsgr: failed to send to %s\n", URL);
      soap_print_fault(ctx, stderr);
      exit(EXIT_FAILURE);
    }
  }
}

static void gen_lang(struct soap *ctx, ULONG64 x, ULONG64 n)
{
  if (verb)
    printf("%%[[LANG]]%%\n");
  for (ULONG64 i = 0; i < n; ++i)
  {
    char c = (x ^ n ^ i) % 26 + 'a';
    if (soap_send_raw(ctx, &c, 1))
    {
      fprintf(stderr, "testmsgr: failed to send to %s\n", URL);
      soap_print_fault(ctx, stderr);
      exit(EXIT_FAILURE);
    }
  }
}

static void gen_text(struct soap *ctx, ULONG64 x, ULONG64 n)
{
  if (verb)
    printf("%%[[TEXT]]%% " SOAP_ULONG_FORMAT " bytes\n", n);
  if (uchr)
  {
    std::string s;
    for (ULONG64 i = 0; i < n; ++i)
    {
      unsigned int c = (x ^ n ^ i) % 0xFFD0 + 32;
      if (c >= 0xD800 && c <= 0xDFFF)
        c = 32;
      append_char(s, c);
    }
    gen_send(ctx, s.c_str());
  }
  else
  {
    for (ULONG64 i = 0; i < n; ++i)
    {
      char c = (x ^ n ^ i) % 95 + 32;
      switch (c)
      {
        case '\'':
          gen_send(ctx, "&apos;");
          break;
        case '"':
          gen_send(ctx, "&quot;");
          break;
        case '&':
          gen_send(ctx, "&amp;");
          break;
        case '<':
          gen_send(ctx, "&lt;");
          break;
        case '>':
          gen_send(ctx, "&gt;");
          break;
        default:
          if (soap_send_raw(ctx, &c, 1))
          {
            fprintf(stderr, "testmsgr: failed to send to %s\n", URL);
            soap_print_fault(ctx, stderr);
            exit(EXIT_FAILURE);
          }
      }
    }
  }
}

static void gen_hex(struct soap *ctx, ULONG64 x, ULONG64 n)
{
  if (verb)
    printf("%%[[HEX]]%% " SOAP_ULONG_FORMAT " bytes\n", n);
  n /= 2;
  for (ULONG64 i = 0; i < n; ++i)
  {
    int c = (x ^ n ^ i) % 256;
    char h[2];
    h[0] = ((c >> 4) & 0xF) + '0';
    if (c > 0x9F)
      h[0] += 7;
    h[1] = (c & 0xF) + '0';
    if ((c & 0xF) > 9)
      h[1] += 7;
    if (soap_send_raw(ctx, h, 2))
    {
      fprintf(stderr, "testmsgr: failed to send to %s\n", URL);
      soap_print_fault(ctx, stderr);
      exit(EXIT_FAILURE);
    }
  }
}

static void gen_base64(struct soap *ctx, ULONG64 x, ULONG64 n)
{
  if (verb)
    printf("%%[[BASE64]]%% " SOAP_ULONG_FORMAT " bytes\n", n);
  unsigned long m;
  char b[4];
  for (ULONG64 i = 0; i < n; i += 3)
  {
    int c = (x ^ n ^ i) & 0xFFFFFF;
    m = (c >> 16) & 0xFF;
    m = (m << 8) | ((c >> 8) & 0xFF);
    m = (m << 8) | (c & 0xFF);
    for (int j = 4; j > 0; m >>= 6)
      b[--j] = soap_base64o[m & 0x3F];
    if (soap_send_raw(ctx, b, 4))
    {
      fprintf(stderr, "testmsgr: failed to send to %s\n", URL);
      soap_print_fault(ctx, stderr);
      exit(EXIT_FAILURE);
    }
  }
  n %= 3;
  if (n > 0)
  {
    int c = n & 0xFFFFFF;
    m = c & 0xFF;
    m <<= 8;
    if (n > 1)
      m |= (c >> 8) & 0xFF;
    m <<= 8;
    for (int j = (int)n; j > 0; m >>= 6)
      b[--j] = soap_base64o[m & 0x3F];
    for (int j = 3; j > (int)n; j--)
      b[j] = '=';
    if (soap_send_raw(ctx, b, 4))
    {
      fprintf(stderr, "testmsgr: failed to send to %s\n", URL);
      soap_print_fault(ctx, stderr);
      exit(EXIT_FAILURE);
    }
  }
}

static void gen_id(struct soap *ctx)
{
  if (verb)
    printf("%%[[ID]]%%\n");
  soap_send(ctx, "id");
  soap_send(ctx, soap_ULONG642s(ctx, ++idnum));
}

static void gen_idref(struct soap *ctx)
{
  if (verb)
    printf("%%[[IDREF]]%%\n");
  if (idref < idnum || idref < cidnum)
    ++idref;
  soap_send(ctx, "id");
  soap_send(ctx, soap_ULONG642s(ctx, idref));
}

static void gen_pattern(struct soap *ctx, const char *pattern, ULONG64 n, ULONG64 m)
{
  if (verb)
    printf("%%[['%s']]%%\n", pattern);
  ULONG64 l = m, k;
  for (k = 0; l; l >>= 1)
    ++k;
  l = 1;
  for (; k > 1; --k)
    l <<= 1;
  k = l >> 1;
  --l;
  std::string s;
  for (; k; k >>= 1)
  {
    const char *p = pattern;
    s = get_reg(&p, l);
    if (s.size() < n)
      l += k;
    else if (s.size() > m)
      l -= k;
    else
      break;
  }
  for (const char *t = s.c_str(); *t; ++t)
  {
    switch (*t)
    {
      case '\t':
        gen_send(ctx, "&#x9;");
        break;
      case '\'':
        gen_send(ctx, "&apos;");
        break;
      case '"':
        gen_send(ctx, "&quot;");
        break;
      case '&':
        gen_send(ctx, "&amp;");
        break;
      case '<':
        gen_send(ctx, "&lt;");
        break;
      case '>':
        gen_send(ctx, "&gt;");
        break;
      default:
        if (soap_send_raw(ctx, t, 1))
        {
          fprintf(stderr, "testmsgr: failed to send to %s\n", URL);
          soap_print_fault(ctx, stderr);
          exit(EXIT_FAILURE);
        }
    }
  }
}

static std::string get_reg(const char **pattern, ULONG64 l)
{
  std::vector<std::string> v;
  while (1)
  {
    v.push_back(get_seq(pattern, l));
    if (**pattern != '|')
      break;
    ++*pattern;
  }
  return v[gen_random() % v.size()];
}

static std::string get_seq(const char **pattern, ULONG64 l)
{
  std::string s;
  while (**pattern && **pattern != '|' && **pattern != ')')
    s.append(get_rep(pattern, l));
  return s;
}

static std::string get_rep(const char **pattern, ULONG64 l)
{
  unsigned long n, m;
  char *r = NULL;
  const char *p = *pattern;
  std::string s = get_sym(pattern, l);
  switch (**pattern)
  {
    case '?':
      ++*pattern;
      if (gen_random() % 2)
        s.clear();
      break;
    case '*':
      ++*pattern;
      if (gen_random() % 2)
        s.clear();
      for (unsigned long i = gen_random() % l + 1; i < l; ++i)
      {
        const char *t = p;
        s.append(get_sym(&t, l));
      }
      break;
    case '+':
      ++*pattern;
      for (unsigned long i = gen_random() % l + 1; i < l; ++i)
      {
        const char *t = p;
        s.append(get_sym(&t, l));
      }
      break;
    case '{':
      n = m = strtoul(*pattern + 1, &r, 10);
      if (r && *r == ',')
      {
        ++r;
        if (*r == '}')
          m = n < lmax ? lmax : n;
        else
          m = strtoul(r, &r, 10);
      }
      if (r && *r == '}')
      {
        *pattern = r + 1;
        for (unsigned long i = n < m ? gen_random() % (m - n) + 1 : 1; i < m; ++i)
        {
          const char *t = p;
          s.append(get_sym(&t, l));
        }
      }
      break;
    default:
      break;
  }
  return s;
}

static std::string get_sym(const char **pattern, ULONG64 l)
{
  std::string s;
  if (**pattern == '(')
  {
    ++*pattern;
    s = get_reg(pattern, l);
    if (**pattern == ')')
      ++*pattern;
  }
  else if (**pattern == '.' || **pattern == '[' || **pattern == '\\')
  {
    charset cs;
    get_charset(pattern, cs);
    if (cs.count())
    {
      long n = gen_random() % cs.count();
      for (unsigned int c = 0; c < (unsigned int)cs.size(); ++c)
      {
        if (cs.test(c) && n-- == 0)
        {
          append_char(s, c);
          break;
        }
      }
    }
  }
  else
  {
    append_char(s, get_char(pattern));
  }
  return s;
}

static bool get_charset(const char **pattern, charset& cs)
{
  bool iscat = false;
  bool isneg = false;
  unsigned int h = 0;
  switch (**pattern)
  {
    case '\0':
      break;
    case '.':
      ++*pattern;
      set_chars(cs, 0, 31);
      if (uchr)
        set_chars(cs, 0xD800, 0xDFFF);
      else
        set_chars(cs, 0x80, 0x10FFFF);
      cs.flip();
      iscat = true;
      break;
    case '[':
      ++*pattern;
      if (**pattern == '^')
      {
        isneg = true;
        ++*pattern;
      }
      do
      {
        if (**pattern == '[' || **pattern == '\\')
        {
          get_charset(pattern, cs);
          h = 0;
        }
        else if (h != 0 && **pattern == '-')
        {
          ++*pattern;
          set_chars(cs, h, get_char(pattern));
          h = 0;
        }
        else if (**pattern != '\0')
        {
          h = get_char(pattern);
          cs.set(h);
        }
      } while (**pattern && **pattern != ']');
      if (isneg)
      {
        set_chars(cs, 0, 31);
        if (!uchr)
          set_chars(cs, 0x80, 0x10FFFF);
        cs.flip();
      }
      if (**pattern == ']')
        ++*pattern;
      iscat = true;
      break;
    case '\\':
      ++*pattern;
      switch (**pattern)
      {
        case 'c':
          cs.set('-');
          cs.set('.');
          set_chars(cs, '0', '9');
          cs.set(':');
          set_chars(cs, 'A', 'Z');
          cs.set('_');
          set_chars(cs, 'a', 'z');
          if (uchr)
          {
            cs.set(0xB7);
            set_chars(cs, 0xC0, 0xD6);
            set_chars(cs, 0xD8, 0xF6);
            set_chars(cs, 0xF8, 0x37D);
            set_chars(cs, 0x37F, 0x1FFF);
            set_chars(cs, 0x200C, 0x200D);
            set_chars(cs, 0x203F, 0x2040);
            set_chars(cs, 0x2070, 0x218F);
            set_chars(cs, 0x2C00, 0x2FEF);
            set_chars(cs, 0x3001, 0xD7FF);
            set_chars(cs, 0xF900, 0xFDCF);
            set_chars(cs, 0xFDF0, 0xFFFD);
            set_chars(cs, 0x10000, 0xEFFFF);
          }
          iscat = true;
          break;
        case 'C':
          cs.set('-');
          cs.set('.');
          set_chars(cs, '0', '9');
          cs.set(':');
          set_chars(cs, 'A', 'Z');
          cs.set('_');
          set_chars(cs, 'a', 'z');
          if (uchr)
          {
            cs.set(0xB7);
            set_chars(cs, 0xC0, 0xD6);
            set_chars(cs, 0xD8, 0xF6);
            set_chars(cs, 0xF8, 0x37D);
            set_chars(cs, 0x37F, 0x1FFF);
            set_chars(cs, 0x200C, 0x200D);
            set_chars(cs, 0x203F, 0x2040);
            set_chars(cs, 0x2070, 0x218F);
            set_chars(cs, 0x2C00, 0x2FEF);
            set_chars(cs, 0x3001, 0xD7FF);
            set_chars(cs, 0xF900, 0xFDCF);
            set_chars(cs, 0xFDF0, 0xFFFD);
            set_chars(cs, 0x10000, 0xEFFFF);
          }
          else
          {
            set_chars(cs, 0x80, 0x10FFFF);
          }
          cs.flip();
          iscat = true;
          break;
        case 'd':
          set_chars(cs, '0', '9');
          iscat = true;
          break;
        case 'D':
          set_chars(cs, '0', '9');
          cs.flip();
          iscat = true;
          break;
        case 'i':
          cs.set(':');
          set_chars(cs, 'A', 'Z');
          cs.set('_');
          set_chars(cs, 'a', 'z');
          if (uchr)
          {
            set_chars(cs, 0xC0, 0xD6);
            set_chars(cs, 0xD8, 0xF6);
            set_chars(cs, 0xF8, 0x2FF);
            set_chars(cs, 0x370, 0x37D);
            set_chars(cs, 0x37F, 0x1FFF);
            set_chars(cs, 0x200C, 0x200D);
            set_chars(cs, 0x2070, 0x218F);
            set_chars(cs, 0x2C00, 0x2FEF);
            set_chars(cs, 0x3001, 0xD7FF);
            set_chars(cs, 0xF900, 0xFDCF);
            set_chars(cs, 0xFDF0, 0xFFFD);
            set_chars(cs, 0x10000, 0xEFFFF);
          }
          iscat = true;
          break;
        case 'I':
          cs.set(':');
          set_chars(cs, 'A', 'Z');
          cs.set('_');
          set_chars(cs, 'a', 'z');
          if (uchr)
          {
            set_chars(cs, 0xC0, 0xD6);
            set_chars(cs, 0xD8, 0xF6);
            set_chars(cs, 0xF8, 0x2FF);
            set_chars(cs, 0x370, 0x37D);
            set_chars(cs, 0x37F, 0x1FFF);
            set_chars(cs, 0x200C, 0x200D);
            set_chars(cs, 0x2070, 0x218F);
            set_chars(cs, 0x2C00, 0x2FEF);
            set_chars(cs, 0x3001, 0xD7FF);
            set_chars(cs, 0xF900, 0xFDCF);
            set_chars(cs, 0xFDF0, 0xFFFD);
            set_chars(cs, 0x10000, 0xEFFFF);
          }
          else
          {
            set_chars(cs, 0x80, 0x10FFFF);
          }
          cs.flip();
          iscat = true;
          break;
        case 'n':
          cs.set('\n');
          break;
        case 'p':
          // TODO not supported yet
          break;
        case 'P':
          // TODO not supported yet
          break;
        case 'r':
          cs.set('\r');
          break;
        case 's':
          cs.set(' ');
          cs.set('\t');
          cs.set('\n');
          cs.set('\r');
          iscat = true;
          break;
        case 'S':
          cs.set(' ');
          cs.set('\t');
          cs.set('\n');
          cs.set('\r');
          cs.flip();
          iscat = true;
          break;
        case 't':
          cs.set('\t');
          break;
        case 'w':
          set_chars(cs, '0', '9');
          set_chars(cs, 'A', 'Z');
          cs.set('_');
          set_chars(cs, 'a', 'z');
          if (uchr)
          {
            set_chars(cs, 48, 57);
            set_chars(cs, 65, 90);
            set_chars(cs, 95, 95);
            set_chars(cs, 97, 122);
            set_chars(cs, 170, 170);
            set_chars(cs, 181, 181);
            set_chars(cs, 186, 186);
            set_chars(cs, 192, 214);
            set_chars(cs, 216, 246);
            set_chars(cs, 248, 705);
            set_chars(cs, 710, 721);
            set_chars(cs, 736, 740);
            set_chars(cs, 748, 748);
            set_chars(cs, 750, 750);
            set_chars(cs, 880, 884);
            set_chars(cs, 886, 887);
            set_chars(cs, 890, 893);
            set_chars(cs, 895, 895);
            set_chars(cs, 902, 902);
            set_chars(cs, 904, 906);
            set_chars(cs, 908, 908);
            set_chars(cs, 910, 929);
            set_chars(cs, 931, 1013);
            set_chars(cs, 1015, 1153);
            set_chars(cs, 1162, 1327);
            set_chars(cs, 1329, 1366);
            set_chars(cs, 1369, 1369);
            set_chars(cs, 1377, 1415);
            set_chars(cs, 1488, 1514);
            set_chars(cs, 1520, 1522);
            set_chars(cs, 1568, 1610);
            set_chars(cs, 1632, 1641);
            set_chars(cs, 1646, 1647);
            set_chars(cs, 1649, 1747);
            set_chars(cs, 1749, 1749);
            set_chars(cs, 1765, 1766);
            set_chars(cs, 1774, 1788);
            set_chars(cs, 1791, 1791);
            set_chars(cs, 1808, 1808);
            set_chars(cs, 1810, 1839);
            set_chars(cs, 1869, 1957);
            set_chars(cs, 1969, 1969);
            set_chars(cs, 1984, 2026);
            set_chars(cs, 2036, 2037);
            set_chars(cs, 2042, 2042);
            set_chars(cs, 2048, 2069);
            set_chars(cs, 2074, 2074);
            set_chars(cs, 2084, 2084);
            set_chars(cs, 2088, 2088);
            set_chars(cs, 2112, 2136);
            set_chars(cs, 2208, 2226);
            set_chars(cs, 2308, 2361);
            set_chars(cs, 2365, 2365);
            set_chars(cs, 2384, 2384);
            set_chars(cs, 2392, 2401);
            set_chars(cs, 2406, 2415);
            set_chars(cs, 2417, 2432);
            set_chars(cs, 2437, 2444);
            set_chars(cs, 2447, 2448);
            set_chars(cs, 2451, 2472);
            set_chars(cs, 2474, 2480);
            set_chars(cs, 2482, 2482);
            set_chars(cs, 2486, 2489);
            set_chars(cs, 2493, 2493);
            set_chars(cs, 2510, 2510);
            set_chars(cs, 2524, 2525);
            set_chars(cs, 2527, 2529);
            set_chars(cs, 2534, 2545);
            set_chars(cs, 2565, 2570);
            set_chars(cs, 2575, 2576);
            set_chars(cs, 2579, 2600);
            set_chars(cs, 2602, 2608);
            set_chars(cs, 2610, 2611);
            set_chars(cs, 2613, 2614);
            set_chars(cs, 2616, 2617);
            set_chars(cs, 2649, 2652);
            set_chars(cs, 2654, 2654);
            set_chars(cs, 2662, 2671);
            set_chars(cs, 2674, 2676);
            set_chars(cs, 2693, 2701);
            set_chars(cs, 2703, 2705);
            set_chars(cs, 2707, 2728);
            set_chars(cs, 2730, 2736);
            set_chars(cs, 2738, 2739);
            set_chars(cs, 2741, 2745);
            set_chars(cs, 2749, 2749);
            set_chars(cs, 2768, 2768);
            set_chars(cs, 2784, 2785);
            set_chars(cs, 2790, 2799);
            set_chars(cs, 2821, 2828);
            set_chars(cs, 2831, 2832);
            set_chars(cs, 2835, 2856);
            set_chars(cs, 2858, 2864);
            set_chars(cs, 2866, 2867);
            set_chars(cs, 2869, 2873);
            set_chars(cs, 2877, 2877);
            set_chars(cs, 2908, 2909);
            set_chars(cs, 2911, 2913);
            set_chars(cs, 2918, 2927);
            set_chars(cs, 2929, 2929);
            set_chars(cs, 2947, 2947);
            set_chars(cs, 2949, 2954);
            set_chars(cs, 2958, 2960);
            set_chars(cs, 2962, 2965);
            set_chars(cs, 2969, 2970);
            set_chars(cs, 2972, 2972);
            set_chars(cs, 2974, 2975);
            set_chars(cs, 2979, 2980);
            set_chars(cs, 2984, 2986);
            set_chars(cs, 2990, 3001);
            set_chars(cs, 3024, 3024);
            set_chars(cs, 3046, 3055);
            set_chars(cs, 3077, 3084);
            set_chars(cs, 3086, 3088);
            set_chars(cs, 3090, 3112);
            set_chars(cs, 3114, 3129);
            set_chars(cs, 3133, 3133);
            set_chars(cs, 3160, 3161);
            set_chars(cs, 3168, 3169);
            set_chars(cs, 3174, 3183);
            set_chars(cs, 3205, 3212);
            set_chars(cs, 3214, 3216);
            set_chars(cs, 3218, 3240);
            set_chars(cs, 3242, 3251);
            set_chars(cs, 3253, 3257);
            set_chars(cs, 3261, 3261);
            set_chars(cs, 3294, 3294);
            set_chars(cs, 3296, 3297);
            set_chars(cs, 3302, 3311);
            set_chars(cs, 3313, 3314);
            set_chars(cs, 3333, 3340);
            set_chars(cs, 3342, 3344);
            set_chars(cs, 3346, 3386);
            set_chars(cs, 3389, 3389);
            set_chars(cs, 3406, 3406);
            set_chars(cs, 3424, 3425);
            set_chars(cs, 3430, 3439);
            set_chars(cs, 3450, 3455);
            set_chars(cs, 3461, 3478);
            set_chars(cs, 3482, 3505);
            set_chars(cs, 3507, 3515);
            set_chars(cs, 3517, 3517);
            set_chars(cs, 3520, 3526);
            set_chars(cs, 3558, 3567);
            set_chars(cs, 3585, 3632);
            set_chars(cs, 3634, 3635);
            set_chars(cs, 3648, 3654);
            set_chars(cs, 3664, 3673);
            set_chars(cs, 3713, 3714);
            set_chars(cs, 3716, 3716);
            set_chars(cs, 3719, 3720);
            set_chars(cs, 3722, 3722);
            set_chars(cs, 3725, 3725);
            set_chars(cs, 3732, 3735);
            set_chars(cs, 3737, 3743);
            set_chars(cs, 3745, 3747);
            set_chars(cs, 3749, 3749);
            set_chars(cs, 3751, 3751);
            set_chars(cs, 3754, 3755);
            set_chars(cs, 3757, 3760);
            set_chars(cs, 3762, 3763);
            set_chars(cs, 3773, 3773);
            set_chars(cs, 3776, 3780);
            set_chars(cs, 3782, 3782);
            set_chars(cs, 3792, 3801);
            set_chars(cs, 3804, 3807);
            set_chars(cs, 3840, 3840);
            set_chars(cs, 3872, 3881);
            set_chars(cs, 3904, 3911);
            set_chars(cs, 3913, 3948);
            set_chars(cs, 3976, 3980);
            set_chars(cs, 4096, 4138);
            set_chars(cs, 4159, 4169);
            set_chars(cs, 4176, 4181);
            set_chars(cs, 4186, 4189);
            set_chars(cs, 4193, 4193);
            set_chars(cs, 4197, 4198);
            set_chars(cs, 4206, 4208);
            set_chars(cs, 4213, 4225);
            set_chars(cs, 4238, 4238);
            set_chars(cs, 4240, 4249);
            set_chars(cs, 4256, 4293);
            set_chars(cs, 4295, 4295);
            set_chars(cs, 4301, 4301);
            set_chars(cs, 4304, 4346);
            set_chars(cs, 4348, 4680);
            set_chars(cs, 4682, 4685);
            set_chars(cs, 4688, 4694);
            set_chars(cs, 4696, 4696);
            set_chars(cs, 4698, 4701);
            set_chars(cs, 4704, 4744);
            set_chars(cs, 4746, 4749);
            set_chars(cs, 4752, 4784);
            set_chars(cs, 4786, 4789);
            set_chars(cs, 4792, 4798);
            set_chars(cs, 4800, 4800);
            set_chars(cs, 4802, 4805);
            set_chars(cs, 4808, 4822);
            set_chars(cs, 4824, 4880);
            set_chars(cs, 4882, 4885);
            set_chars(cs, 4888, 4954);
            set_chars(cs, 4992, 5007);
            set_chars(cs, 5024, 5108);
            set_chars(cs, 5121, 5740);
            set_chars(cs, 5743, 5759);
            set_chars(cs, 5761, 5786);
            set_chars(cs, 5792, 5866);
            set_chars(cs, 5873, 5880);
            set_chars(cs, 5888, 5900);
            set_chars(cs, 5902, 5905);
            set_chars(cs, 5920, 5937);
            set_chars(cs, 5952, 5969);
            set_chars(cs, 5984, 5996);
            set_chars(cs, 5998, 6000);
            set_chars(cs, 6016, 6067);
            set_chars(cs, 6103, 6103);
            set_chars(cs, 6108, 6108);
            set_chars(cs, 6112, 6121);
            set_chars(cs, 6160, 6169);
            set_chars(cs, 6176, 6263);
            set_chars(cs, 6272, 6312);
            set_chars(cs, 6314, 6314);
            set_chars(cs, 6320, 6389);
            set_chars(cs, 6400, 6430);
            set_chars(cs, 6470, 6509);
            set_chars(cs, 6512, 6516);
            set_chars(cs, 6528, 6571);
            set_chars(cs, 6593, 6599);
            set_chars(cs, 6608, 6617);
            set_chars(cs, 6656, 6678);
            set_chars(cs, 6688, 6740);
            set_chars(cs, 6784, 6793);
            set_chars(cs, 6800, 6809);
            set_chars(cs, 6823, 6823);
            set_chars(cs, 6917, 6963);
            set_chars(cs, 6981, 6987);
            set_chars(cs, 6992, 7001);
            set_chars(cs, 7043, 7072);
            set_chars(cs, 7086, 7141);
            set_chars(cs, 7168, 7203);
            set_chars(cs, 7232, 7241);
            set_chars(cs, 7245, 7293);
            set_chars(cs, 7401, 7404);
            set_chars(cs, 7406, 7409);
            set_chars(cs, 7413, 7414);
            set_chars(cs, 7424, 7615);
            set_chars(cs, 7680, 7957);
            set_chars(cs, 7960, 7965);
            set_chars(cs, 7968, 8005);
            set_chars(cs, 8008, 8013);
            set_chars(cs, 8016, 8023);
            set_chars(cs, 8025, 8025);
            set_chars(cs, 8027, 8027);
            set_chars(cs, 8029, 8029);
            set_chars(cs, 8031, 8061);
            set_chars(cs, 8064, 8116);
            set_chars(cs, 8118, 8124);
            set_chars(cs, 8126, 8126);
            set_chars(cs, 8130, 8132);
            set_chars(cs, 8134, 8140);
            set_chars(cs, 8144, 8147);
            set_chars(cs, 8150, 8155);
            set_chars(cs, 8160, 8172);
            set_chars(cs, 8178, 8180);
            set_chars(cs, 8182, 8188);
            set_chars(cs, 8255, 8256);
            set_chars(cs, 8276, 8276);
            set_chars(cs, 8305, 8305);
            set_chars(cs, 8319, 8319);
            set_chars(cs, 8336, 8348);
            set_chars(cs, 8450, 8450);
            set_chars(cs, 8455, 8455);
            set_chars(cs, 8458, 8467);
            set_chars(cs, 8469, 8469);
            set_chars(cs, 8473, 8477);
            set_chars(cs, 8484, 8484);
            set_chars(cs, 8486, 8486);
            set_chars(cs, 8488, 8488);
            set_chars(cs, 8490, 8493);
            set_chars(cs, 8495, 8505);
            set_chars(cs, 8508, 8511);
            set_chars(cs, 8517, 8521);
            set_chars(cs, 8526, 8526);
            set_chars(cs, 8579, 8580);
            set_chars(cs, 11264, 11310);
            set_chars(cs, 11312, 11358);
            set_chars(cs, 11360, 11492);
            set_chars(cs, 11499, 11502);
            set_chars(cs, 11506, 11507);
            set_chars(cs, 11520, 11557);
            set_chars(cs, 11559, 11559);
            set_chars(cs, 11565, 11565);
            set_chars(cs, 11568, 11623);
            set_chars(cs, 11631, 11631);
            set_chars(cs, 11648, 11670);
            set_chars(cs, 11680, 11686);
            set_chars(cs, 11688, 11694);
            set_chars(cs, 11696, 11702);
            set_chars(cs, 11704, 11710);
            set_chars(cs, 11712, 11718);
            set_chars(cs, 11720, 11726);
            set_chars(cs, 11728, 11734);
            set_chars(cs, 11736, 11742);
            set_chars(cs, 11823, 11823);
            set_chars(cs, 12293, 12294);
            set_chars(cs, 12337, 12341);
            set_chars(cs, 12347, 12348);
            set_chars(cs, 12353, 12438);
            set_chars(cs, 12445, 12447);
            set_chars(cs, 12449, 12538);
            set_chars(cs, 12540, 12543);
            set_chars(cs, 12549, 12589);
            set_chars(cs, 12593, 12686);
            set_chars(cs, 12704, 12730);
            set_chars(cs, 12784, 12799);
            set_chars(cs, 13312, 19893);
            set_chars(cs, 19968, 40908);
            set_chars(cs, 40960, 42124);
            set_chars(cs, 42192, 42237);
            set_chars(cs, 42240, 42508);
            set_chars(cs, 42512, 42539);
            set_chars(cs, 42560, 42606);
            set_chars(cs, 42623, 42653);
            set_chars(cs, 42656, 42725);
            set_chars(cs, 42775, 42783);
            set_chars(cs, 42786, 42888);
            set_chars(cs, 42891, 42894);
            set_chars(cs, 42896, 42925);
            set_chars(cs, 42928, 42929);
            set_chars(cs, 42999, 43009);
            set_chars(cs, 43011, 43013);
            set_chars(cs, 43015, 43018);
            set_chars(cs, 43020, 43042);
            set_chars(cs, 43072, 43123);
            set_chars(cs, 43138, 43187);
            set_chars(cs, 43216, 43225);
            set_chars(cs, 43250, 43255);
            set_chars(cs, 43259, 43259);
            set_chars(cs, 43264, 43301);
            set_chars(cs, 43312, 43334);
            set_chars(cs, 43360, 43388);
            set_chars(cs, 43396, 43442);
            set_chars(cs, 43471, 43481);
            set_chars(cs, 43488, 43492);
            set_chars(cs, 43494, 43518);
            set_chars(cs, 43520, 43560);
            set_chars(cs, 43584, 43586);
            set_chars(cs, 43588, 43595);
            set_chars(cs, 43600, 43609);
            set_chars(cs, 43616, 43638);
            set_chars(cs, 43642, 43642);
            set_chars(cs, 43646, 43695);
            set_chars(cs, 43697, 43697);
            set_chars(cs, 43701, 43702);
            set_chars(cs, 43705, 43709);
            set_chars(cs, 43712, 43712);
            set_chars(cs, 43714, 43714);
            set_chars(cs, 43739, 43741);
            set_chars(cs, 43744, 43754);
            set_chars(cs, 43762, 43764);
            set_chars(cs, 43777, 43782);
            set_chars(cs, 43785, 43790);
            set_chars(cs, 43793, 43798);
            set_chars(cs, 43808, 43814);
            set_chars(cs, 43816, 43822);
            set_chars(cs, 43824, 43866);
            set_chars(cs, 43868, 43871);
            set_chars(cs, 43876, 43877);
            set_chars(cs, 43968, 44002);
            set_chars(cs, 44016, 44025);
            set_chars(cs, 44032, 55203);
            set_chars(cs, 55216, 55238);
            set_chars(cs, 55243, 55291);
            set_chars(cs, 63744, 64109);
            set_chars(cs, 64112, 64217);
            set_chars(cs, 64256, 64262);
            set_chars(cs, 64275, 64279);
            set_chars(cs, 64285, 64285);
            set_chars(cs, 64287, 64296);
            set_chars(cs, 64298, 64310);
            set_chars(cs, 64312, 64316);
            set_chars(cs, 64318, 64318);
            set_chars(cs, 64320, 64321);
            set_chars(cs, 64323, 64324);
            set_chars(cs, 64326, 64433);
            set_chars(cs, 64467, 64829);
            set_chars(cs, 64848, 64911);
            set_chars(cs, 64914, 64967);
            set_chars(cs, 65008, 65019);
            set_chars(cs, 65075, 65076);
            set_chars(cs, 65101, 65103);
            set_chars(cs, 65136, 65140);
            set_chars(cs, 65142, 65276);
            set_chars(cs, 65296, 65305);
            set_chars(cs, 65313, 65338);
            set_chars(cs, 65343, 65343);
            set_chars(cs, 65345, 65370);
            set_chars(cs, 65382, 65470);
            set_chars(cs, 65474, 65479);
            set_chars(cs, 65482, 65487);
            set_chars(cs, 65490, 65495);
            set_chars(cs, 65498, 65500);
            set_chars(cs, 65536, 65547);
            set_chars(cs, 65549, 65574);
            set_chars(cs, 65576, 65594);
            set_chars(cs, 65596, 65597);
            set_chars(cs, 65599, 65613);
            set_chars(cs, 65616, 65629);
            set_chars(cs, 65664, 65786);
            set_chars(cs, 66176, 66204);
            set_chars(cs, 66208, 66256);
            set_chars(cs, 66304, 66335);
            set_chars(cs, 66352, 66368);
            set_chars(cs, 66370, 66377);
            set_chars(cs, 66384, 66421);
            set_chars(cs, 66432, 66461);
            set_chars(cs, 66464, 66499);
            set_chars(cs, 66504, 66511);
            set_chars(cs, 66560, 66717);
            set_chars(cs, 66720, 66729);
            set_chars(cs, 66816, 66855);
            set_chars(cs, 66864, 66915);
            set_chars(cs, 67072, 67382);
            set_chars(cs, 67392, 67413);
            set_chars(cs, 67424, 67431);
            set_chars(cs, 67584, 67589);
            set_chars(cs, 67592, 67592);
            set_chars(cs, 67594, 67637);
            set_chars(cs, 67639, 67640);
            set_chars(cs, 67644, 67644);
            set_chars(cs, 67647, 67669);
            set_chars(cs, 67680, 67702);
            set_chars(cs, 67712, 67742);
            set_chars(cs, 67840, 67861);
            set_chars(cs, 67872, 67897);
            set_chars(cs, 67968, 68023);
            set_chars(cs, 68030, 68031);
            set_chars(cs, 68096, 68096);
            set_chars(cs, 68112, 68115);
            set_chars(cs, 68117, 68119);
            set_chars(cs, 68121, 68147);
            set_chars(cs, 68192, 68220);
            set_chars(cs, 68224, 68252);
            set_chars(cs, 68288, 68295);
            set_chars(cs, 68297, 68324);
            set_chars(cs, 68352, 68405);
            set_chars(cs, 68416, 68437);
            set_chars(cs, 68448, 68466);
            set_chars(cs, 68480, 68497);
            set_chars(cs, 68608, 68680);
            set_chars(cs, 69635, 69687);
            set_chars(cs, 69734, 69743);
            set_chars(cs, 69763, 69807);
            set_chars(cs, 69840, 69864);
            set_chars(cs, 69872, 69881);
            set_chars(cs, 69891, 69926);
            set_chars(cs, 69942, 69951);
            set_chars(cs, 69968, 70002);
            set_chars(cs, 70006, 70006);
            set_chars(cs, 70019, 70066);
            set_chars(cs, 70081, 70084);
            set_chars(cs, 70096, 70106);
            set_chars(cs, 70144, 70161);
            set_chars(cs, 70163, 70187);
            set_chars(cs, 70320, 70366);
            set_chars(cs, 70384, 70393);
            set_chars(cs, 70405, 70412);
            set_chars(cs, 70415, 70416);
            set_chars(cs, 70419, 70440);
            set_chars(cs, 70442, 70448);
            set_chars(cs, 70450, 70451);
            set_chars(cs, 70453, 70457);
            set_chars(cs, 70461, 70461);
            set_chars(cs, 70493, 70497);
            set_chars(cs, 70784, 70831);
            set_chars(cs, 70852, 70853);
            set_chars(cs, 70855, 70855);
            set_chars(cs, 70864, 70873);
            set_chars(cs, 71040, 71086);
            set_chars(cs, 71168, 71215);
            set_chars(cs, 71236, 71236);
            set_chars(cs, 71248, 71257);
            set_chars(cs, 71296, 71338);
            set_chars(cs, 71360, 71369);
            set_chars(cs, 71840, 71913);
            set_chars(cs, 71935, 71935);
            set_chars(cs, 72384, 72440);
            set_chars(cs, 73728, 74648);
            set_chars(cs, 77824, 78894);
            set_chars(cs, 92160, 92728);
            set_chars(cs, 92736, 92766);
            set_chars(cs, 92768, 92777);
            set_chars(cs, 92880, 92909);
            set_chars(cs, 92928, 92975);
            set_chars(cs, 92992, 92995);
            set_chars(cs, 93008, 93017);
            set_chars(cs, 93027, 93047);
            set_chars(cs, 93053, 93071);
            set_chars(cs, 93952, 94020);
            set_chars(cs, 94032, 94032);
            set_chars(cs, 94099, 94111);
            set_chars(cs, 110592, 110593);
            set_chars(cs, 113664, 113770);
            set_chars(cs, 113776, 113788);
            set_chars(cs, 113792, 113800);
            set_chars(cs, 113808, 113817);
            set_chars(cs, 119808, 119892);
            set_chars(cs, 119894, 119964);
            set_chars(cs, 119966, 119967);
            set_chars(cs, 119970, 119970);
            set_chars(cs, 119973, 119974);
            set_chars(cs, 119977, 119980);
            set_chars(cs, 119982, 119993);
            set_chars(cs, 119995, 119995);
            set_chars(cs, 119997, 120003);
            set_chars(cs, 120005, 120069);
            set_chars(cs, 120071, 120074);
            set_chars(cs, 120077, 120084);
            set_chars(cs, 120086, 120092);
            set_chars(cs, 120094, 120121);
            set_chars(cs, 120123, 120126);
            set_chars(cs, 120128, 120132);
            set_chars(cs, 120134, 120134);
            set_chars(cs, 120138, 120144);
            set_chars(cs, 120146, 120485);
            set_chars(cs, 120488, 120512);
            set_chars(cs, 120514, 120538);
            set_chars(cs, 120540, 120570);
            set_chars(cs, 120572, 120596);
            set_chars(cs, 120598, 120628);
            set_chars(cs, 120630, 120654);
            set_chars(cs, 120656, 120686);
            set_chars(cs, 120688, 120712);
            set_chars(cs, 120714, 120744);
            set_chars(cs, 120746, 120770);
            set_chars(cs, 120772, 120779);
            set_chars(cs, 120782, 120831);
            set_chars(cs, 124928, 125124);
            set_chars(cs, 126464, 126467);
            set_chars(cs, 126469, 126495);
            set_chars(cs, 126497, 126498);
            set_chars(cs, 126500, 126500);
            set_chars(cs, 126503, 126503);
            set_chars(cs, 126505, 126514);
            set_chars(cs, 126516, 126519);
            set_chars(cs, 126521, 126521);
            set_chars(cs, 126523, 126523);
            set_chars(cs, 126530, 126530);
            set_chars(cs, 126535, 126535);
            set_chars(cs, 126537, 126537);
            set_chars(cs, 126539, 126539);
            set_chars(cs, 126541, 126543);
            set_chars(cs, 126545, 126546);
            set_chars(cs, 126548, 126548);
            set_chars(cs, 126551, 126551);
            set_chars(cs, 126553, 126553);
            set_chars(cs, 126555, 126555);
            set_chars(cs, 126557, 126557);
            set_chars(cs, 126559, 126559);
            set_chars(cs, 126561, 126562);
            set_chars(cs, 126564, 126564);
            set_chars(cs, 126567, 126570);
            set_chars(cs, 126572, 126578);
            set_chars(cs, 126580, 126583);
            set_chars(cs, 126585, 126588);
            set_chars(cs, 126590, 126590);
            set_chars(cs, 126592, 126601);
            set_chars(cs, 126603, 126619);
            set_chars(cs, 126625, 126627);
            set_chars(cs, 126629, 126633);
            set_chars(cs, 126635, 126651);
            set_chars(cs, 131072, 173782);
            set_chars(cs, 173824, 177972);
            set_chars(cs, 177984, 178205);
            set_chars(cs, 194560, 195101);
          }
          iscat = true;
          break;
        case 'W':
          set_chars(cs, '0', '9');
          set_chars(cs, 'A', 'Z');
          cs.set('_');
          set_chars(cs, 'a', 'z');
          if (uchr)
          {
            set_chars(cs, 48, 57);
            set_chars(cs, 65, 90);
            set_chars(cs, 95, 95);
            set_chars(cs, 97, 122);
            set_chars(cs, 170, 170);
            set_chars(cs, 181, 181);
            set_chars(cs, 186, 186);
            set_chars(cs, 192, 214);
            set_chars(cs, 216, 246);
            set_chars(cs, 248, 705);
            set_chars(cs, 710, 721);
            set_chars(cs, 736, 740);
            set_chars(cs, 748, 748);
            set_chars(cs, 750, 750);
            set_chars(cs, 880, 884);
            set_chars(cs, 886, 887);
            set_chars(cs, 890, 893);
            set_chars(cs, 895, 895);
            set_chars(cs, 902, 902);
            set_chars(cs, 904, 906);
            set_chars(cs, 908, 908);
            set_chars(cs, 910, 929);
            set_chars(cs, 931, 1013);
            set_chars(cs, 1015, 1153);
            set_chars(cs, 1162, 1327);
            set_chars(cs, 1329, 1366);
            set_chars(cs, 1369, 1369);
            set_chars(cs, 1377, 1415);
            set_chars(cs, 1488, 1514);
            set_chars(cs, 1520, 1522);
            set_chars(cs, 1568, 1610);
            set_chars(cs, 1632, 1641);
            set_chars(cs, 1646, 1647);
            set_chars(cs, 1649, 1747);
            set_chars(cs, 1749, 1749);
            set_chars(cs, 1765, 1766);
            set_chars(cs, 1774, 1788);
            set_chars(cs, 1791, 1791);
            set_chars(cs, 1808, 1808);
            set_chars(cs, 1810, 1839);
            set_chars(cs, 1869, 1957);
            set_chars(cs, 1969, 1969);
            set_chars(cs, 1984, 2026);
            set_chars(cs, 2036, 2037);
            set_chars(cs, 2042, 2042);
            set_chars(cs, 2048, 2069);
            set_chars(cs, 2074, 2074);
            set_chars(cs, 2084, 2084);
            set_chars(cs, 2088, 2088);
            set_chars(cs, 2112, 2136);
            set_chars(cs, 2208, 2226);
            set_chars(cs, 2308, 2361);
            set_chars(cs, 2365, 2365);
            set_chars(cs, 2384, 2384);
            set_chars(cs, 2392, 2401);
            set_chars(cs, 2406, 2415);
            set_chars(cs, 2417, 2432);
            set_chars(cs, 2437, 2444);
            set_chars(cs, 2447, 2448);
            set_chars(cs, 2451, 2472);
            set_chars(cs, 2474, 2480);
            set_chars(cs, 2482, 2482);
            set_chars(cs, 2486, 2489);
            set_chars(cs, 2493, 2493);
            set_chars(cs, 2510, 2510);
            set_chars(cs, 2524, 2525);
            set_chars(cs, 2527, 2529);
            set_chars(cs, 2534, 2545);
            set_chars(cs, 2565, 2570);
            set_chars(cs, 2575, 2576);
            set_chars(cs, 2579, 2600);
            set_chars(cs, 2602, 2608);
            set_chars(cs, 2610, 2611);
            set_chars(cs, 2613, 2614);
            set_chars(cs, 2616, 2617);
            set_chars(cs, 2649, 2652);
            set_chars(cs, 2654, 2654);
            set_chars(cs, 2662, 2671);
            set_chars(cs, 2674, 2676);
            set_chars(cs, 2693, 2701);
            set_chars(cs, 2703, 2705);
            set_chars(cs, 2707, 2728);
            set_chars(cs, 2730, 2736);
            set_chars(cs, 2738, 2739);
            set_chars(cs, 2741, 2745);
            set_chars(cs, 2749, 2749);
            set_chars(cs, 2768, 2768);
            set_chars(cs, 2784, 2785);
            set_chars(cs, 2790, 2799);
            set_chars(cs, 2821, 2828);
            set_chars(cs, 2831, 2832);
            set_chars(cs, 2835, 2856);
            set_chars(cs, 2858, 2864);
            set_chars(cs, 2866, 2867);
            set_chars(cs, 2869, 2873);
            set_chars(cs, 2877, 2877);
            set_chars(cs, 2908, 2909);
            set_chars(cs, 2911, 2913);
            set_chars(cs, 2918, 2927);
            set_chars(cs, 2929, 2929);
            set_chars(cs, 2947, 2947);
            set_chars(cs, 2949, 2954);
            set_chars(cs, 2958, 2960);
            set_chars(cs, 2962, 2965);
            set_chars(cs, 2969, 2970);
            set_chars(cs, 2972, 2972);
            set_chars(cs, 2974, 2975);
            set_chars(cs, 2979, 2980);
            set_chars(cs, 2984, 2986);
            set_chars(cs, 2990, 3001);
            set_chars(cs, 3024, 3024);
            set_chars(cs, 3046, 3055);
            set_chars(cs, 3077, 3084);
            set_chars(cs, 3086, 3088);
            set_chars(cs, 3090, 3112);
            set_chars(cs, 3114, 3129);
            set_chars(cs, 3133, 3133);
            set_chars(cs, 3160, 3161);
            set_chars(cs, 3168, 3169);
            set_chars(cs, 3174, 3183);
            set_chars(cs, 3205, 3212);
            set_chars(cs, 3214, 3216);
            set_chars(cs, 3218, 3240);
            set_chars(cs, 3242, 3251);
            set_chars(cs, 3253, 3257);
            set_chars(cs, 3261, 3261);
            set_chars(cs, 3294, 3294);
            set_chars(cs, 3296, 3297);
            set_chars(cs, 3302, 3311);
            set_chars(cs, 3313, 3314);
            set_chars(cs, 3333, 3340);
            set_chars(cs, 3342, 3344);
            set_chars(cs, 3346, 3386);
            set_chars(cs, 3389, 3389);
            set_chars(cs, 3406, 3406);
            set_chars(cs, 3424, 3425);
            set_chars(cs, 3430, 3439);
            set_chars(cs, 3450, 3455);
            set_chars(cs, 3461, 3478);
            set_chars(cs, 3482, 3505);
            set_chars(cs, 3507, 3515);
            set_chars(cs, 3517, 3517);
            set_chars(cs, 3520, 3526);
            set_chars(cs, 3558, 3567);
            set_chars(cs, 3585, 3632);
            set_chars(cs, 3634, 3635);
            set_chars(cs, 3648, 3654);
            set_chars(cs, 3664, 3673);
            set_chars(cs, 3713, 3714);
            set_chars(cs, 3716, 3716);
            set_chars(cs, 3719, 3720);
            set_chars(cs, 3722, 3722);
            set_chars(cs, 3725, 3725);
            set_chars(cs, 3732, 3735);
            set_chars(cs, 3737, 3743);
            set_chars(cs, 3745, 3747);
            set_chars(cs, 3749, 3749);
            set_chars(cs, 3751, 3751);
            set_chars(cs, 3754, 3755);
            set_chars(cs, 3757, 3760);
            set_chars(cs, 3762, 3763);
            set_chars(cs, 3773, 3773);
            set_chars(cs, 3776, 3780);
            set_chars(cs, 3782, 3782);
            set_chars(cs, 3792, 3801);
            set_chars(cs, 3804, 3807);
            set_chars(cs, 3840, 3840);
            set_chars(cs, 3872, 3881);
            set_chars(cs, 3904, 3911);
            set_chars(cs, 3913, 3948);
            set_chars(cs, 3976, 3980);
            set_chars(cs, 4096, 4138);
            set_chars(cs, 4159, 4169);
            set_chars(cs, 4176, 4181);
            set_chars(cs, 4186, 4189);
            set_chars(cs, 4193, 4193);
            set_chars(cs, 4197, 4198);
            set_chars(cs, 4206, 4208);
            set_chars(cs, 4213, 4225);
            set_chars(cs, 4238, 4238);
            set_chars(cs, 4240, 4249);
            set_chars(cs, 4256, 4293);
            set_chars(cs, 4295, 4295);
            set_chars(cs, 4301, 4301);
            set_chars(cs, 4304, 4346);
            set_chars(cs, 4348, 4680);
            set_chars(cs, 4682, 4685);
            set_chars(cs, 4688, 4694);
            set_chars(cs, 4696, 4696);
            set_chars(cs, 4698, 4701);
            set_chars(cs, 4704, 4744);
            set_chars(cs, 4746, 4749);
            set_chars(cs, 4752, 4784);
            set_chars(cs, 4786, 4789);
            set_chars(cs, 4792, 4798);
            set_chars(cs, 4800, 4800);
            set_chars(cs, 4802, 4805);
            set_chars(cs, 4808, 4822);
            set_chars(cs, 4824, 4880);
            set_chars(cs, 4882, 4885);
            set_chars(cs, 4888, 4954);
            set_chars(cs, 4992, 5007);
            set_chars(cs, 5024, 5108);
            set_chars(cs, 5121, 5740);
            set_chars(cs, 5743, 5759);
            set_chars(cs, 5761, 5786);
            set_chars(cs, 5792, 5866);
            set_chars(cs, 5873, 5880);
            set_chars(cs, 5888, 5900);
            set_chars(cs, 5902, 5905);
            set_chars(cs, 5920, 5937);
            set_chars(cs, 5952, 5969);
            set_chars(cs, 5984, 5996);
            set_chars(cs, 5998, 6000);
            set_chars(cs, 6016, 6067);
            set_chars(cs, 6103, 6103);
            set_chars(cs, 6108, 6108);
            set_chars(cs, 6112, 6121);
            set_chars(cs, 6160, 6169);
            set_chars(cs, 6176, 6263);
            set_chars(cs, 6272, 6312);
            set_chars(cs, 6314, 6314);
            set_chars(cs, 6320, 6389);
            set_chars(cs, 6400, 6430);
            set_chars(cs, 6470, 6509);
            set_chars(cs, 6512, 6516);
            set_chars(cs, 6528, 6571);
            set_chars(cs, 6593, 6599);
            set_chars(cs, 6608, 6617);
            set_chars(cs, 6656, 6678);
            set_chars(cs, 6688, 6740);
            set_chars(cs, 6784, 6793);
            set_chars(cs, 6800, 6809);
            set_chars(cs, 6823, 6823);
            set_chars(cs, 6917, 6963);
            set_chars(cs, 6981, 6987);
            set_chars(cs, 6992, 7001);
            set_chars(cs, 7043, 7072);
            set_chars(cs, 7086, 7141);
            set_chars(cs, 7168, 7203);
            set_chars(cs, 7232, 7241);
            set_chars(cs, 7245, 7293);
            set_chars(cs, 7401, 7404);
            set_chars(cs, 7406, 7409);
            set_chars(cs, 7413, 7414);
            set_chars(cs, 7424, 7615);
            set_chars(cs, 7680, 7957);
            set_chars(cs, 7960, 7965);
            set_chars(cs, 7968, 8005);
            set_chars(cs, 8008, 8013);
            set_chars(cs, 8016, 8023);
            set_chars(cs, 8025, 8025);
            set_chars(cs, 8027, 8027);
            set_chars(cs, 8029, 8029);
            set_chars(cs, 8031, 8061);
            set_chars(cs, 8064, 8116);
            set_chars(cs, 8118, 8124);
            set_chars(cs, 8126, 8126);
            set_chars(cs, 8130, 8132);
            set_chars(cs, 8134, 8140);
            set_chars(cs, 8144, 8147);
            set_chars(cs, 8150, 8155);
            set_chars(cs, 8160, 8172);
            set_chars(cs, 8178, 8180);
            set_chars(cs, 8182, 8188);
            set_chars(cs, 8255, 8256);
            set_chars(cs, 8276, 8276);
            set_chars(cs, 8305, 8305);
            set_chars(cs, 8319, 8319);
            set_chars(cs, 8336, 8348);
            set_chars(cs, 8450, 8450);
            set_chars(cs, 8455, 8455);
            set_chars(cs, 8458, 8467);
            set_chars(cs, 8469, 8469);
            set_chars(cs, 8473, 8477);
            set_chars(cs, 8484, 8484);
            set_chars(cs, 8486, 8486);
            set_chars(cs, 8488, 8488);
            set_chars(cs, 8490, 8493);
            set_chars(cs, 8495, 8505);
            set_chars(cs, 8508, 8511);
            set_chars(cs, 8517, 8521);
            set_chars(cs, 8526, 8526);
            set_chars(cs, 8579, 8580);
            set_chars(cs, 11264, 11310);
            set_chars(cs, 11312, 11358);
            set_chars(cs, 11360, 11492);
            set_chars(cs, 11499, 11502);
            set_chars(cs, 11506, 11507);
            set_chars(cs, 11520, 11557);
            set_chars(cs, 11559, 11559);
            set_chars(cs, 11565, 11565);
            set_chars(cs, 11568, 11623);
            set_chars(cs, 11631, 11631);
            set_chars(cs, 11648, 11670);
            set_chars(cs, 11680, 11686);
            set_chars(cs, 11688, 11694);
            set_chars(cs, 11696, 11702);
            set_chars(cs, 11704, 11710);
            set_chars(cs, 11712, 11718);
            set_chars(cs, 11720, 11726);
            set_chars(cs, 11728, 11734);
            set_chars(cs, 11736, 11742);
            set_chars(cs, 11823, 11823);
            set_chars(cs, 12293, 12294);
            set_chars(cs, 12337, 12341);
            set_chars(cs, 12347, 12348);
            set_chars(cs, 12353, 12438);
            set_chars(cs, 12445, 12447);
            set_chars(cs, 12449, 12538);
            set_chars(cs, 12540, 12543);
            set_chars(cs, 12549, 12589);
            set_chars(cs, 12593, 12686);
            set_chars(cs, 12704, 12730);
            set_chars(cs, 12784, 12799);
            set_chars(cs, 13312, 19893);
            set_chars(cs, 19968, 40908);
            set_chars(cs, 40960, 42124);
            set_chars(cs, 42192, 42237);
            set_chars(cs, 42240, 42508);
            set_chars(cs, 42512, 42539);
            set_chars(cs, 42560, 42606);
            set_chars(cs, 42623, 42653);
            set_chars(cs, 42656, 42725);
            set_chars(cs, 42775, 42783);
            set_chars(cs, 42786, 42888);
            set_chars(cs, 42891, 42894);
            set_chars(cs, 42896, 42925);
            set_chars(cs, 42928, 42929);
            set_chars(cs, 42999, 43009);
            set_chars(cs, 43011, 43013);
            set_chars(cs, 43015, 43018);
            set_chars(cs, 43020, 43042);
            set_chars(cs, 43072, 43123);
            set_chars(cs, 43138, 43187);
            set_chars(cs, 43216, 43225);
            set_chars(cs, 43250, 43255);
            set_chars(cs, 43259, 43259);
            set_chars(cs, 43264, 43301);
            set_chars(cs, 43312, 43334);
            set_chars(cs, 43360, 43388);
            set_chars(cs, 43396, 43442);
            set_chars(cs, 43471, 43481);
            set_chars(cs, 43488, 43492);
            set_chars(cs, 43494, 43518);
            set_chars(cs, 43520, 43560);
            set_chars(cs, 43584, 43586);
            set_chars(cs, 43588, 43595);
            set_chars(cs, 43600, 43609);
            set_chars(cs, 43616, 43638);
            set_chars(cs, 43642, 43642);
            set_chars(cs, 43646, 43695);
            set_chars(cs, 43697, 43697);
            set_chars(cs, 43701, 43702);
            set_chars(cs, 43705, 43709);
            set_chars(cs, 43712, 43712);
            set_chars(cs, 43714, 43714);
            set_chars(cs, 43739, 43741);
            set_chars(cs, 43744, 43754);
            set_chars(cs, 43762, 43764);
            set_chars(cs, 43777, 43782);
            set_chars(cs, 43785, 43790);
            set_chars(cs, 43793, 43798);
            set_chars(cs, 43808, 43814);
            set_chars(cs, 43816, 43822);
            set_chars(cs, 43824, 43866);
            set_chars(cs, 43868, 43871);
            set_chars(cs, 43876, 43877);
            set_chars(cs, 43968, 44002);
            set_chars(cs, 44016, 44025);
            set_chars(cs, 44032, 55203);
            set_chars(cs, 55216, 55238);
            set_chars(cs, 55243, 55291);
            set_chars(cs, 63744, 64109);
            set_chars(cs, 64112, 64217);
            set_chars(cs, 64256, 64262);
            set_chars(cs, 64275, 64279);
            set_chars(cs, 64285, 64285);
            set_chars(cs, 64287, 64296);
            set_chars(cs, 64298, 64310);
            set_chars(cs, 64312, 64316);
            set_chars(cs, 64318, 64318);
            set_chars(cs, 64320, 64321);
            set_chars(cs, 64323, 64324);
            set_chars(cs, 64326, 64433);
            set_chars(cs, 64467, 64829);
            set_chars(cs, 64848, 64911);
            set_chars(cs, 64914, 64967);
            set_chars(cs, 65008, 65019);
            set_chars(cs, 65075, 65076);
            set_chars(cs, 65101, 65103);
            set_chars(cs, 65136, 65140);
            set_chars(cs, 65142, 65276);
            set_chars(cs, 65296, 65305);
            set_chars(cs, 65313, 65338);
            set_chars(cs, 65343, 65343);
            set_chars(cs, 65345, 65370);
            set_chars(cs, 65382, 65470);
            set_chars(cs, 65474, 65479);
            set_chars(cs, 65482, 65487);
            set_chars(cs, 65490, 65495);
            set_chars(cs, 65498, 65500);
            set_chars(cs, 65536, 65547);
            set_chars(cs, 65549, 65574);
            set_chars(cs, 65576, 65594);
            set_chars(cs, 65596, 65597);
            set_chars(cs, 65599, 65613);
            set_chars(cs, 65616, 65629);
            set_chars(cs, 65664, 65786);
            set_chars(cs, 66176, 66204);
            set_chars(cs, 66208, 66256);
            set_chars(cs, 66304, 66335);
            set_chars(cs, 66352, 66368);
            set_chars(cs, 66370, 66377);
            set_chars(cs, 66384, 66421);
            set_chars(cs, 66432, 66461);
            set_chars(cs, 66464, 66499);
            set_chars(cs, 66504, 66511);
            set_chars(cs, 66560, 66717);
            set_chars(cs, 66720, 66729);
            set_chars(cs, 66816, 66855);
            set_chars(cs, 66864, 66915);
            set_chars(cs, 67072, 67382);
            set_chars(cs, 67392, 67413);
            set_chars(cs, 67424, 67431);
            set_chars(cs, 67584, 67589);
            set_chars(cs, 67592, 67592);
            set_chars(cs, 67594, 67637);
            set_chars(cs, 67639, 67640);
            set_chars(cs, 67644, 67644);
            set_chars(cs, 67647, 67669);
            set_chars(cs, 67680, 67702);
            set_chars(cs, 67712, 67742);
            set_chars(cs, 67840, 67861);
            set_chars(cs, 67872, 67897);
            set_chars(cs, 67968, 68023);
            set_chars(cs, 68030, 68031);
            set_chars(cs, 68096, 68096);
            set_chars(cs, 68112, 68115);
            set_chars(cs, 68117, 68119);
            set_chars(cs, 68121, 68147);
            set_chars(cs, 68192, 68220);
            set_chars(cs, 68224, 68252);
            set_chars(cs, 68288, 68295);
            set_chars(cs, 68297, 68324);
            set_chars(cs, 68352, 68405);
            set_chars(cs, 68416, 68437);
            set_chars(cs, 68448, 68466);
            set_chars(cs, 68480, 68497);
            set_chars(cs, 68608, 68680);
            set_chars(cs, 69635, 69687);
            set_chars(cs, 69734, 69743);
            set_chars(cs, 69763, 69807);
            set_chars(cs, 69840, 69864);
            set_chars(cs, 69872, 69881);
            set_chars(cs, 69891, 69926);
            set_chars(cs, 69942, 69951);
            set_chars(cs, 69968, 70002);
            set_chars(cs, 70006, 70006);
            set_chars(cs, 70019, 70066);
            set_chars(cs, 70081, 70084);
            set_chars(cs, 70096, 70106);
            set_chars(cs, 70144, 70161);
            set_chars(cs, 70163, 70187);
            set_chars(cs, 70320, 70366);
            set_chars(cs, 70384, 70393);
            set_chars(cs, 70405, 70412);
            set_chars(cs, 70415, 70416);
            set_chars(cs, 70419, 70440);
            set_chars(cs, 70442, 70448);
            set_chars(cs, 70450, 70451);
            set_chars(cs, 70453, 70457);
            set_chars(cs, 70461, 70461);
            set_chars(cs, 70493, 70497);
            set_chars(cs, 70784, 70831);
            set_chars(cs, 70852, 70853);
            set_chars(cs, 70855, 70855);
            set_chars(cs, 70864, 70873);
            set_chars(cs, 71040, 71086);
            set_chars(cs, 71168, 71215);
            set_chars(cs, 71236, 71236);
            set_chars(cs, 71248, 71257);
            set_chars(cs, 71296, 71338);
            set_chars(cs, 71360, 71369);
            set_chars(cs, 71840, 71913);
            set_chars(cs, 71935, 71935);
            set_chars(cs, 72384, 72440);
            set_chars(cs, 73728, 74648);
            set_chars(cs, 77824, 78894);
            set_chars(cs, 92160, 92728);
            set_chars(cs, 92736, 92766);
            set_chars(cs, 92768, 92777);
            set_chars(cs, 92880, 92909);
            set_chars(cs, 92928, 92975);
            set_chars(cs, 92992, 92995);
            set_chars(cs, 93008, 93017);
            set_chars(cs, 93027, 93047);
            set_chars(cs, 93053, 93071);
            set_chars(cs, 93952, 94020);
            set_chars(cs, 94032, 94032);
            set_chars(cs, 94099, 94111);
            set_chars(cs, 110592, 110593);
            set_chars(cs, 113664, 113770);
            set_chars(cs, 113776, 113788);
            set_chars(cs, 113792, 113800);
            set_chars(cs, 113808, 113817);
            set_chars(cs, 119808, 119892);
            set_chars(cs, 119894, 119964);
            set_chars(cs, 119966, 119967);
            set_chars(cs, 119970, 119970);
            set_chars(cs, 119973, 119974);
            set_chars(cs, 119977, 119980);
            set_chars(cs, 119982, 119993);
            set_chars(cs, 119995, 119995);
            set_chars(cs, 119997, 120003);
            set_chars(cs, 120005, 120069);
            set_chars(cs, 120071, 120074);
            set_chars(cs, 120077, 120084);
            set_chars(cs, 120086, 120092);
            set_chars(cs, 120094, 120121);
            set_chars(cs, 120123, 120126);
            set_chars(cs, 120128, 120132);
            set_chars(cs, 120134, 120134);
            set_chars(cs, 120138, 120144);
            set_chars(cs, 120146, 120485);
            set_chars(cs, 120488, 120512);
            set_chars(cs, 120514, 120538);
            set_chars(cs, 120540, 120570);
            set_chars(cs, 120572, 120596);
            set_chars(cs, 120598, 120628);
            set_chars(cs, 120630, 120654);
            set_chars(cs, 120656, 120686);
            set_chars(cs, 120688, 120712);
            set_chars(cs, 120714, 120744);
            set_chars(cs, 120746, 120770);
            set_chars(cs, 120772, 120779);
            set_chars(cs, 120782, 120831);
            set_chars(cs, 124928, 125124);
            set_chars(cs, 126464, 126467);
            set_chars(cs, 126469, 126495);
            set_chars(cs, 126497, 126498);
            set_chars(cs, 126500, 126500);
            set_chars(cs, 126503, 126503);
            set_chars(cs, 126505, 126514);
            set_chars(cs, 126516, 126519);
            set_chars(cs, 126521, 126521);
            set_chars(cs, 126523, 126523);
            set_chars(cs, 126530, 126530);
            set_chars(cs, 126535, 126535);
            set_chars(cs, 126537, 126537);
            set_chars(cs, 126539, 126539);
            set_chars(cs, 126541, 126543);
            set_chars(cs, 126545, 126546);
            set_chars(cs, 126548, 126548);
            set_chars(cs, 126551, 126551);
            set_chars(cs, 126553, 126553);
            set_chars(cs, 126555, 126555);
            set_chars(cs, 126557, 126557);
            set_chars(cs, 126559, 126559);
            set_chars(cs, 126561, 126562);
            set_chars(cs, 126564, 126564);
            set_chars(cs, 126567, 126570);
            set_chars(cs, 126572, 126578);
            set_chars(cs, 126580, 126583);
            set_chars(cs, 126585, 126588);
            set_chars(cs, 126590, 126590);
            set_chars(cs, 126592, 126601);
            set_chars(cs, 126603, 126619);
            set_chars(cs, 126625, 126627);
            set_chars(cs, 126629, 126633);
            set_chars(cs, 126635, 126651);
            set_chars(cs, 131072, 173782);
            set_chars(cs, 173824, 177972);
            set_chars(cs, 177984, 178205);
            set_chars(cs, 194560, 195101);
          }
          else
          {
            set_chars(cs, 0x80, 0x10FFFF);
          }
          cs.flip();
          iscat = true;
          break;
        default:
          if (**pattern != '\0')
            cs.set(get_char(pattern));
      }
      if (**pattern != '\0')
        ++*pattern;
      break;
    default:
      if (**pattern != '\0')
        cs.set(get_char(pattern));
      break;
  }
  if (iscat && **pattern == '-')
  {
    charset sub;
    const char *p = *pattern;
    ++*pattern;
    if (get_charset(pattern, sub))
    {
      cs &= sub.flip();
    }
    else
    {
      iscat = false;
      *pattern = p;
    }
  }
  return iscat;
}

static unsigned int get_char(const char **pattern)
{
  unsigned int c = (unsigned char)**pattern;
  if (!c)
    return 0;
  ++*pattern;
  if (c < 0x80)
    return c;
  unsigned int c1 = (unsigned char)**pattern;
  if (c <= 0xC1 || (c1 & 0xC0) != 0x80)
    return UNDEFINED_CHAR;
  ++*pattern;
  c1 &= 0x3F;
  if (c < 0xE0)
    return (((c & 0x1F) << 6) | c1);
  unsigned int c2 = (unsigned char)**pattern;
  if ((c == 0xE0 && c1 < 0x20) || (c2 & 0xC0) != 0x80)
    return UNDEFINED_CHAR;
  ++*pattern;
  c2 &= 0x3F;
  if (c < 0xF0)
    return (((c & 0x0F) << 12) | (c1 << 6) | c2);
  unsigned int c3 = (unsigned char)**pattern;
  if ((c == 0xF0 && c1 < 0x10) || (c == 0xF4 && c1 >= 0x10) || c >= 0xF5 || (c3 & 0xC0) != 0x80)
    return UNDEFINED_CHAR;
  ++*pattern;
  return (((c & 0x07) << 18) | (c1 << 12) | (c2 << 6) | (c3 & 0x3F));
}

static void append_char(std::string& s, unsigned int c)
{
  if (c < 0x80)
  {
    s.push_back(c);
  }
  else if ((c >= 0xA0 && c <= 0xD7FF) || (c >= 0xE000 && c <= 0xFFFD) || (c >= 0x10000 && c <= 0x10FFFF))
  {
    char buf[8], *t = buf;
    if (c < 0x0800)
    {
      *t++ = (char)(0xC0 | ((c >> 6) & 0x1F));
    }
    else
    {
      if (c < 0x010000)
      {
        *t++ = (char)(0xE0 | ((c >> 12) & 0x0F));
      }
      else
      {
        if (c < 0x200000)
        {
          *t++ = (char)(0xF0 | ((c >> 18) & 0x07));
        }
        else
        {
          if (c < 0x04000000)
          {
            *t++ = (char)(0xF8 | ((c >> 24) & 0x03));
          }
          else
          {
            *t++ = (char)(0xFC | ((c >> 30) & 0x01));
            *t++ = (char)(0x80 | ((c >> 24) & 0x3F));
          }
          *t++ = (char)(0x80 | ((c >> 18) & 0x3F));
        }
        *t++ = (char)(0x80 | ((c >> 12) & 0x3F));
      }
      *t++ = (char)(0x80 | ((c >> 6) & 0x3F));
    }
    *t++ = (char)(0x80 | (c & 0x3F));
    *t = '\0';
    s.append(buf);
  }
}

static int receive_request(struct soap *ctx)
{
  ULONG64 n = 0;
  if (soap_begin_recv(ctx))
  {
    fprintf(stderr, "testmsgr: failed to receive on port %lu\n", port);
    soap_print_fault(ctx, stderr);
    return ctx->error;
  }
  if (!(ctx->mode & SOAP_ENC_ZLIB) && (ctx->mode & SOAP_IO) != SOAP_IO_CHUNK && ctx->length == 0)
  {
    fprintf(stderr, "testmsgr: no HTTP content-length header in request message\n");
  }
  else if ((ctx->mode & SOAP_ENC_MIME))
  {
    // MTOM/MIME attachments: just execute soap_end_recv() to get them all
  }
  else
  {
    for (;;)
    {
      n++;
      if (ctx->length > 0 && n > ctx->length)
        break;
      if ((int)soap_getchar(ctx) == EOF)
        break;
    }
  }
  if (soap_end_recv(ctx))
  {
    fprintf(stderr, "testmsgr: failed to receive on port %lu\n", port);
    soap_print_fault(ctx, stderr);
    return ctx->error;
  }
  return SOAP_OK;
}

static void receive_response(struct soap *ctx)
{
  if (soap_begin_recv(ctx))
  {
    fprintf(stderr, "testmsgr: failed to receive from %s\n", URL);
    soap_print_fault(ctx, stderr);
  }
  else if (!(ctx->mode & SOAP_ENC_ZLIB) && (ctx->mode & SOAP_IO) != SOAP_IO_CHUNK && ctx->length == 0)
  {
    printf("Server responded with status %d and an empty body\n\n", ctx->status);
  }
  else if ((ctx->mode & SOAP_ENC_MIME))
  {
    printf("Server responded with status %d and MIME body:\n\n", ctx->status);
    if (soap_end_recv(ctx))
    {
      fprintf(stderr, "testmsgr: failed to receive from %s\n", URL);
      soap_print_fault(ctx, stderr);
    }
    if (soap_closesock(ctx) && ferr)
      exit(EXIT_FAILURE);
    for (soap_multipart::iterator i = ctx->mime.begin(); i != ctx->mime.end(); ++i)
    {
      printf("MIME id=\"%s\" type=\"%s\"\n---- begin ----\n", i->id ? i->id : "", i->type ? i->type : "");
      if (i->ptr)
      {
        size_t k = i->size;
        if (nlen > 0 && nlen < (LONG64)k)
          k = (size_t)nlen;
        fwrite(i->ptr, 1, k, stdout);
        if (k < i->size)
          printf("\n---- cut ----\n...");
      }
      printf("\n---- end ----\n\n");
    }
    return;
  }
  else
  {
    printf("Server responded with status %d and a message body:\n\n---- begin ----\n", ctx->status);
    ULONG64 n = 0;
    for (;;)
    {
      soap_wchar c;
      n++;
      if (ctx->length > 0 && n > ctx->length)
        break;
      c = soap_getchar(ctx);
      if ((int)c == EOF)
        break;
      if (nlen < 0 || n <= (ULONG64)nlen)
        putchar(c);
    }
    if (nlen > 0 && n > (ULONG64)nlen)
      printf("\n---- cut ----\n...");
    printf("\n---- end ----\n\n");
  }
  if (soap_end_recv(ctx))
  {
    fprintf(stderr, "testmsgr: failed to receive from %s\n", URL);
    soap_print_fault(ctx, stderr);
    if (ferr)
      exit(EXIT_FAILURE);
  }
  if (soap_closesock(ctx) && ferr)
    exit(EXIT_FAILURE);
}
