/*
 * Copyright (c) 2006 Kazuaki ODA.
 * Copyright (c) 2006 Mode2 inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the company nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: ctr_html_conv.c 438 2006-11-11 15:09:48Z oda $
 */

#include "httpd.h"
#include "http_log.h"
#include "apr_lib.h"
#include "apr_strings.h"
#include "ctr_hdml_support.h"
#include "ctr_html_conv.h"
#include "ctr_img_support.h"
#include "ctr_util.h"

#define CTR_HTML_BR_ON(_cvinfo) do {                                    \
    ctr_html_dat_t *_dat = (ctr_html_dat_t *)(_cvinfo)->udata;          \
    _dat->brout = TRUE;                                                 \
    _dat->last_is_space = TRUE;                                         \
} while (0)
#define CTR_HTML_BR_OFF(_cvinfo) do {                                   \
    ctr_html_dat_t *_dat = (ctr_html_dat_t *)(_cvinfo)->udata;          \
    _dat->brout = FALSE;                                                \
    _dat->last_is_space = FALSE;                                        \
} while (0)

#define CTR_HTML_DECO_OUT(_cvinfo) do {                                 \
    apr_status_t _rv;                                                   \
    if ((_cvinfo)->isxml) {                                             \
        if ((_rv = ctr_xhtml_deco_out(_cvinfo)) != APR_SUCCESS)         \
            return _rv;                                                 \
    } else {                                                            \
        if ((_rv = ctr_html_deco_out(_cvinfo)) != APR_SUCCESS)          \
            return _rv;                                                 \
    }                                                                   \
} while (0)
#define CTR_HTML_DECO_CLOSE(_cvinfo) do {                               \
    apr_status_t _rv;                                                   \
    if ((_cvinfo)->isxml) {                                             \
        if ((_rv = ctr_xhtml_deco_close(_cvinfo)) != APR_SUCCESS)       \
            return _rv;                                                 \
    } else {                                                            \
        if ((_rv = ctr_html_deco_close(_cvinfo)) != APR_SUCCESS)        \
            return _rv;                                                 \
    }                                                                   \
} while (0)

#define CTR_HDML_DAT(_cvinfo)                                           \
    ((ctr_hdml_dat_t *)_cvinfo->udata)

#define CTR_HDML_STAT_LAST(_cvinfo)                                     \
    ((ctr_hdml_stat_t *)CTR_ARRAY_LAST(CTR_HDML_DAT(_cvinfo)->stats))

#define CTR_HDML_STAT_PUSH(_cvinfo, _lo, _licnt, _top) do {             \
    ctr_hdml_stat_t *_stat =                                            \
        apr_array_push(CTR_HDML_DAT(_cvinfo)->stats);                   \
    _stat->lo = (_lo);                                                  \
    _stat->brout = TRUE;                                                \
    _stat->last_is_space = TRUE;                                        \
    _stat->adjusted = TRUE;                                             \
    _stat->charcnt = 0;                                                 \
    _stat->anc_in_line = FALSE;                                         \
    _stat->licnt = _licnt;                                              \
    _stat->top = (_top);                                                \
} while (0)
#define CTR_HDML_STAT_POP(_cvinfo) do {                                 \
    apr_array_pop(CTR_HDML_DAT(_cvinfo)->stats);                        \
} while (0)

#define CTR_HDML_INIT_ADD(_cvinfo, _key, _val)                          \
    (apr_table_add(CTR_HDML_DAT(_cvinfo)->init, (_key), (_val)))

#define CTR_HDML_FDAT_EMPTY(_cvinfo)                                    \
    (CTR_HDML_DAT(_cvinfo)->fdat->nelts == 0)
#define CTR_HDML_FDAT_LAST(_cvinfo)                                     \
    ((ctr_hdml_fdat_t *)CTR_ARRAY_LAST(CTR_HDML_DAT(_cvinfo)->fdat))
#define CTR_HDML_FDAT_DATA_ADD(_cvinfo, _key, _val) do {                \
    if (!CTR_HDML_FDAT_EMPTY(_cvinfo))                                  \
        apr_table_add(CTR_HDML_FDAT_LAST(_cvinfo)->tbl, (_key), (_val));\
} while (0)

#define CTR_HDML_CDAT_LAST(_cvinfo)                                     \
    ((ctr_hdml_cdat_t *)CTR_ARRAY_LAST(CTR_HDML_DAT(_cvinfo)->cdat))

#define CTR_HDML_BR_ON(_cvinfo) do {                                    \
    ctr_hdml_stat_t *_stat = CTR_HDML_STAT_LAST(_cvinfo);               \
    _stat->brout = TRUE;                                                \
    _stat->last_is_space = TRUE;                                        \
    _stat->adjusted = TRUE;                                             \
    _stat->charcnt = 0;                                                 \
    _stat->anc_in_line = FALSE;                                         \
} while (0)
#define CTR_HDML_BR_OFF(_cvinfo) do {                                   \
    ctr_hdml_stat_t *_stat = CTR_HDML_STAT_LAST(_cvinfo);               \
    _stat->brout = FALSE;                                               \
    _stat->last_is_space = FALSE;                                       \
} while (0)

#define CTR_HDML_BR_OUT(_cvinfo) do {                                   \
    ctr_hdml_stat_t *_stat = CTR_HDML_STAT_LAST(_cvinfo);               \
    apr_status_t _rv;                                                   \
    if (!_stat->brout) {                                                \
        if ((_rv = ctr_cvputs((_cvinfo), "<br>\n")) != APR_SUCCESS)     \
            return _rv;                                                 \
        _stat->brout = TRUE;                                            \
        _stat->last_is_space = TRUE;                                    \
        _stat->adjusted = TRUE;                                         \
        _stat->charcnt = 0;                                             \
        _stat->anc_in_line = FALSE;                                     \
    }                                                                   \
} while (0)
#define CTR_HDML_LO_OUT(_cvinfo) do {                                   \
    ctr_hdml_stat_t *_stat = CTR_HDML_STAT_LAST(_cvinfo);               \
    apr_status_t _rv;                                                   \
    if (_stat->brout) {                                                 \
        if ((_rv = ctr_hdmllo_out(_stat->lo,                            \
                                  (_cvinfo))) != APR_SUCCESS)           \
            return _rv;                                                 \
    }                                                                   \
} while (0)

typedef struct _ctr_conv_info ctr_conv_info_t;
typedef enum _ctr_decostat ctr_decostat_t;
typedef struct _ctr_decoinfo ctr_decoinfo_t;
typedef struct _ctr_html_dat ctr_html_dat_t;
typedef enum _ctr_hdml_lo ctr_hdml_lo_t;
typedef struct _ctr_hdml_stat ctr_hdml_stat_t;
typedef struct _ctr_hdml_fdat ctr_hdml_fdat_t;
typedef struct _ctr_hdml_edat ctr_hdml_edat_t;
typedef struct _ctr_hdml_celm ctr_hdml_celm_t;
typedef struct _ctr_hdml_cdat ctr_hdml_cdat_t;
typedef struct _ctr_hdml_dat ctr_hdml_dat_t;
typedef struct _ctr_table_join_dat ctr_table_join_dat_t;

struct _ctr_conv_info {
    const request_rec *r;
    int isxml;
    ctr_lang_t lang;
    ctr_carrier_t carrier;
    int exp_subcards;
    ctr_browser_t *brw;
    apr_hash_t *emojihash;
    void *udata;
    char *buf;
    apr_size_t bufsz;
    apr_size_t buflen;
};

enum _ctr_decostat {
    DS_NONE,            /* no style is defined */
    DS_INIT,            /* new style that is not outputted */
    DS_OUT_INLINE,      /* style was outputted by a inline element */
    DS_OUT_DERIVED,     /* style is derived from the style already outputted */
    DS_OUT_DIV          /* style was outputted by <div> tag */
};

struct _ctr_decoinfo {
    int txtcolor;
    ctr_decostat_t txtcolor_stat;
    int bgcolor;
    ctr_decostat_t bgcolor_stat;
    int blink;
    ctr_decostat_t blink_stat;
    ctr_fontsize_t fsize;
    ctr_decostat_t fsize_stat;
};

struct _ctr_html_dat {
    int brout;
    int last_is_space;
    apr_array_header_t *decoinfos;
    CTR_CLASS_NAME(form) *curfrm;
    int linest; /* only for ixhtml */
};

enum _ctr_hdml_lo {
    LO_NONE, LO_CENTER, LO_RIGHT
};

struct _ctr_hdml_stat {
    ctr_hdml_lo_t lo;
    int brout;
    int last_is_space;
    int adjusted;
    int charcnt;
    int anc_in_line;
    int licnt;
    int top;
};

struct _ctr_hdml_fdat {
    int no;
    const apr_uri_t *act;
    ctr_form_mtd_t mtd;
    apr_table_t *tbl;
};

struct _ctr_hdml_edat {
    int no;
    const char *fmt;
    int echo;
};

struct _ctr_hdml_celm {
    const char *val;
    const char *rettxt;
    const char *txt;
};

struct _ctr_hdml_cdat {
    int no;
    apr_array_header_t *elms;
    int valvno;
    int txtvno;
    enum {
        SS_FIRST, SS_FALSE, SS_TRUE
    } selstat;
    const char *selval;
    const char *seltxt;
};

struct _ctr_hdml_dat {
    int indent;
    apr_table_t *init;
    apr_array_header_t *stats;
    apr_array_header_t *fdat;
    apr_array_header_t *edat;
    apr_array_header_t *cdat;
    ctr_radio_info_t *rinfos;
    ctr_radio_info_t *last_rinfo;
    int chkon_no;
    int chkoff_no;
    int valcnt;
    int cardcnt;
};

struct _ctr_table_join_dat {
    apr_pool_t *pool;
    const char *elmsep;
    const char *valsep;
    char *s;
};

static const ctr_wcomp_visitor_t ctr_ihtml_conv;
static const ctr_wcomp_visitor_t ctr_ixhtml_conv;
static const ctr_wcomp_visitor_t ctr_auxhtml_conv;
static const ctr_wcomp_visitor_t ctr_shtml_conv;
static const ctr_wcomp_visitor_t ctr_sxhtml_conv;
static const ctr_wcomp_visitor_t ctr_hdml_conv;
static const ctr_wcomp_visitor_t ctr_whtml_conv;
static const ctr_wcomp_visitor_t ctr_full_conv;

static const char *au_fsizes[] = {
    "", "x-small", "x-small", "x-small", "medium", "x-large", "x-large",
    "x-large"
};
static const char *gen_xfsizes[] = {
    "", "xx-small", "x-small", "small", "medium", "large", "x-large",
    "xx-large"
};
static const char *gen_fsizes[] = {
    "", "1", "1", "2", "3", "4", "5", "6"
};

static const unsigned char CTR_GO_MSG[] = { 0x88, 0xda, 0x93, 0xae, 0 };
static const unsigned char CTR_INPUT_MSG[] = { 0x93, 0xfc, 0x97, 0xcd, 0 };
static const unsigned char CTR_TEL_MSG[] = { 0x93, 0x64, 0x98, 0x62, 0 };
static const unsigned char CTR_MAIL_MSG[] = { 0xd2, 0xb0, 0xd9, 0 };
static const unsigned char CTR_SEL_MSG[] = { 0x91, 0x49, 0x91, 0xf0, 0 };
static const unsigned char CTR_CHG_MSG[] = { 0x90, 0xd8, 0x91, 0xd6, 0 };
static const unsigned char CTR_POST_MSG[] = { 0x91, 0x97, 0x90, 0x4d, 0 };
static const unsigned char CTR_RST_MSG[] = { 0xd8, 0xbe, 0xaf, 0xc4, 0 };
static const unsigned char CTR_POINT_MSG[] = { 0xa5, 0 };
static const unsigned char CTR_FULLDOT_MSG[] = { 0x81, 0x44, 0 };
static const unsigned char CTR_FULLSPC_MSG[] = { 0x81, 0x40, 0 };

static apr_status_t ctr_html_deco_out(ctr_conv_info_t *cvinfo);
static apr_status_t ctr_xhtml_deco_out(ctr_conv_info_t *cvinfo);
static apr_status_t ctr_html_deco_close(ctr_conv_info_t *cvinfo);
static apr_status_t ctr_xhtml_deco_close(ctr_conv_info_t *cvinfo);
static apr_status_t ctr_cvprintf(ctr_conv_info_t *cvinfo,
                                 const char *fmt, ...);
static apr_status_t ctr_cvputs(ctr_conv_info_t *cvinfo, const char *s);
static apr_status_t ctr_cvputvs(ctr_conv_info_t *cvinfo, ...);
static void ctr_buf_expand(ctr_conv_info_t *cvinfo);

apr_bucket *ctr_page_convert(CTR_CLASS_NAME(page) *page, int flg,
                             ctr_browser_t *brw, apr_hash_t *emojihash,
                             request_rec *r)
{
    ctr_conv_info_t *cvinfo;
    const ctr_wcomp_visitor_t *conv;
    char *ctype;
    char *target;

    if (brw->carrier == CR_UNKNOWN && !(flg & CF_CONV_UNKNOWN))
        return NULL;

    switch (brw->carrier) {
    case CR_DOCOMO:
        cvinfo = apr_palloc(r->pool, sizeof(*cvinfo));
        cvinfo->carrier = CA_DOCOMO;
        if (brw->spec.docomo.xhtmlver > 0.0) {
            conv = &ctr_ixhtml_conv;
            cvinfo->isxml = TRUE;
            cvinfo->lang = LG_IXHTML;
            ctype = "application/xhtml+xml";
            target = "ixhtml";
        } else {
            conv = &ctr_ihtml_conv;
            cvinfo->isxml = FALSE;
            cvinfo->lang = LG_IHTML;
            ctype = "text/html; charset=Shift_JIS";
            target = "ihtml";
        }
        break;
    case CR_KDDI:
        cvinfo = apr_palloc(r->pool, sizeof(*cvinfo));
        cvinfo->carrier = CA_KDDI;
        if (brw->spec.kddi.iswap20 && !(flg & CF_HDML_WAP20)) {
            conv = &ctr_auxhtml_conv;
            cvinfo->isxml = TRUE;
            cvinfo->lang = LG_AUXHTML;
            ctype = "text/html; charset=Shift_JIS";
            target = "auxhtml";
        } else {
            conv = &ctr_hdml_conv;
            cvinfo->isxml = FALSE;
            cvinfo->lang = LG_HDML;
            ctype = "text/x-hdml; charset=Shift_JIS";
            target = "hdml";
        }
        break;
    case CR_SOFTBANK:
        cvinfo = apr_palloc(r->pool, sizeof(*cvinfo));
        cvinfo->carrier = CA_SOFTBANK;
        if (brw->spec.softbank.isxhtml) {
            conv = &ctr_sxhtml_conv;
            cvinfo->isxml = TRUE;
            cvinfo->lang = LG_SXHTML;
            ctype = "text/html; charset=Shift_JIS";
            target = "sxhtml";
        } else {
            conv = &ctr_shtml_conv;
            cvinfo->isxml = FALSE;
            cvinfo->lang = LG_SHTML;
            ctype = "text/html; charset=Shift_JIS";
            target = "shtml";
        }
        break;
    case CR_WILLCOM:
        conv = &ctr_whtml_conv;
        cvinfo = apr_palloc(r->pool, sizeof(*cvinfo));
        cvinfo->isxml = FALSE;
        cvinfo->lang = LG_WHTML;
        cvinfo->carrier = CA_WILLCOM;
        ctype = "text/html; charset=Shift_JIS";
        target = "whtml";
        break;
    default:
        conv = &ctr_full_conv;
        cvinfo = apr_palloc(r->pool, sizeof(*cvinfo));
        cvinfo->isxml = TRUE;
        cvinfo->lang = LG_FULL;
        cvinfo->carrier = CA_UNKNOWN;
        ctype = "text/html; charset=Shift_JIS";
        target = "full";
        break;
    }

    cvinfo->r = r;
    cvinfo->emojihash = emojihash;
    if (brw->carrier == CR_KDDI &&
        !brw->spec.kddi.iswap20 && brw->cachesize <= 1492)
        cvinfo->exp_subcards = TRUE;
    else
        cvinfo->exp_subcards = flg & CF_HDML_EXPSUBCARDS;
    cvinfo->brw = brw;
    cvinfo->buf = apr_palloc(r->pool, 4);
    cvinfo->bufsz = 4;
    cvinfo->buflen = 0;
    cvinfo->udata = NULL;

    if (flg & CF_LOG_CONVEDTYPE) {
        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
                      "mod_cthree: converting into the type [%s]", target);
    }

    if (conv->CTR_VISITOR_MEMBER_NAME(page)(page, conv, cvinfo) != APR_SUCCESS)
        return NULL;

    r->content_type = ctype;
    return apr_bucket_pool_create(cvinfo->buf, cvinfo->buflen,
                                  r->pool, r->connection->bucket_alloc);
}

static apr_status_t ctr_gen_c3if_conv(CTR_CLASS_NAME(c3if) *c3if,
                                      const ctr_wcomp_visitor_t *vt,
                                      void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_browser_t *brw = cvinfo->brw;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    int result;
    apr_status_t rv;

    result =
        (c3if->lang & cvinfo->lang) &&
        (c3if->carrier & cvinfo->carrier) &&
        (c3if->names == NULL || apr_table_get(c3if->names, brw->name)) &&
        (c3if->groups == NULL || apr_table_get(c3if->groups, brw->group)) &&
        (c3if->isfoma == -1 ||
         (c3if->isfoma == 1 &&
          brw->carrier == CR_DOCOMO && brw->spec.docomo.isfoma) ||
         (c3if->isfoma == 0 &&
          (brw->carrier != CR_DOCOMO || (brw->carrier == CR_DOCOMO &&
                                         !brw->spec.docomo.isfoma)))) &&
        (c3if->istuka == -1 ||
         (c3if->istuka == 1 &&
          brw->carrier == CR_KDDI && brw->spec.kddi.istuka) ||
         (c3if->istuka == 0 &&
          (brw->carrier != CR_KDDI || (brw->carrier == CR_KDDI &&
                                       !brw->spec.kddi.istuka)))) &&
        (c3if->isxml == -1 ||
         (c3if->isxml == 1 && cvinfo->isxml) ||
         (c3if->isxml == 0 && !cvinfo->isxml));

    if (c3if->not)
        result = !result;

    if (result) {
        CTR_FOREACH_CHILD(c3if, wcont, wcomp) {
            if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
                return rv;
        }
    }

    return APR_SUCCESS;
}

static ctr_decoinfo_t *ctr_html_block_decoinfo_add(ctr_html_dat_t *dat)
{
    ctr_decoinfo_t *old_dinfo, *dinfo;

    if (apr_is_empty_array(dat->decoinfos)) {
        dinfo = apr_array_push(dat->decoinfos);
        dinfo->txtcolor = -1;
        dinfo->txtcolor_stat = DS_NONE;
        dinfo->bgcolor = -1;
        dinfo->bgcolor_stat = DS_NONE;
        dinfo->blink = FALSE;
        dinfo->blink_stat = DS_NONE;
        dinfo->fsize = FS_NONE;
        dinfo->fsize_stat = DS_NONE;
    } else {
        dinfo = apr_array_push(dat->decoinfos);
        old_dinfo = dinfo - 1;
        dinfo->txtcolor = old_dinfo->txtcolor;
        dinfo->txtcolor_stat = old_dinfo->txtcolor_stat;
        dinfo->bgcolor = old_dinfo->bgcolor;
        dinfo->bgcolor_stat = old_dinfo->bgcolor_stat;
        dinfo->blink = old_dinfo->blink;
        dinfo->blink_stat = old_dinfo->blink_stat;
        dinfo->fsize = old_dinfo->fsize;
        dinfo->fsize_stat = old_dinfo->fsize_stat;
    }

    return dinfo;
}

static apr_status_t ctr_html_page_conv(CTR_CLASS_NAME(page) *page,
                                       const ctr_wcomp_visitor_t *vt,
                                       void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat;
    char *fmt;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    /*
     * Initialize html specific data.
     */
    dat = apr_palloc(pool, sizeof(*dat));
    dat->brout = TRUE;
    dat->last_is_space = TRUE;
    dat->decoinfos = apr_array_make(pool, 1, sizeof(ctr_decoinfo_t));
    dat->curfrm = NULL;
    cvinfo->udata = dat;

    if ((rv = ctr_cvputvs(cvinfo,
                          "<html>\n"
                          "<head>\n"
                          "<title>",
                          ctr_html_escape(page->title, pool),
                          "</title>\n"
                          "</head>\n", NULL)) != APR_SUCCESS)
        return rv;
    fmt = apr_pstrcat(pool,
                      "<body",
                      page->bgcolor != -1 ? " bgcolor=\"#%06x\"" : "%s",
                      page->txtcolor2 != -1 || page->txtcolor != -1 ?
                      " text=\"#%06x\"" : "%s",
                      page->lnkcolor != -1 ? " link=\"#%06x\"" : "%s",
                      ">\n"
                      "<div>\n", NULL);
    if ((rv = ctr_cvprintf(cvinfo, fmt,
                           page->bgcolor != -1 ?
                           (intptr_t)page->bgcolor : (intptr_t)"",
                           page->txtcolor2 != -1 ? (intptr_t)page->txtcolor2 :
                           page->txtcolor != -1 ?
                           (intptr_t)page->txtcolor : (intptr_t)"",
                           page->lnkcolor != -1 ?
                           (intptr_t)page->lnkcolor : (intptr_t)""))
        != APR_SUCCESS)
        return rv;

    CTR_FOREACH_CHILD(page, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    return ctr_cvputvs(cvinfo,
                       !dat->brout ? "\n" : "",
                       "</div>\n"
                       "</body>\n"
                       "</html>\n", NULL);
}

static apr_status_t ctr_html_block_conv(CTR_CLASS_NAME(block) *blk,
                                        const ctr_wcomp_visitor_t *vt,
                                        void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    if (blk->marquee) {
        CTR_HTML_DECO_CLOSE(cvinfo);

        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "<marquee",
                              blk->mqedirect == DL_RIGHT ?
                              " direction=\"right\"" : "",
                              blk->mqebehav == BH_SLIDE ?
                              " behavior=\"slide\"" :
                              blk->mqebehav == BH_ALTERNATE ?
                              " behavior=\"alternate\"" : "",
                              " loop=\"", apr_itoa(pool, blk->mqeloop), "\"",
                              ">\n", NULL)) != APR_SUCCESS)
            return rv;
    } else if (blk->align != AL_NONE) {
        CTR_HTML_DECO_CLOSE(cvinfo);

        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "<div align=\"",
                              blk->align == AL_LEFT ? "left" :
                              blk->align == AL_CENTER ? "center" : "right",
                              "\">\n", NULL)) != APR_SUCCESS)
            return rv;
    } else if (!dat->brout) {
        if ((rv = ctr_cvputs(cvinfo, "<br>\n")) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(blk, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    if (blk->marquee) {
        CTR_HTML_DECO_CLOSE(cvinfo);

        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "</marquee>\n", NULL)) != APR_SUCCESS)
            return rv;
    } else if (blk->align != AL_NONE) {
        CTR_HTML_DECO_CLOSE(cvinfo);

        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "</div>\n", NULL)) != APR_SUCCESS)
            return rv;
    } else if (!dat->brout) {
        if ((rv = ctr_cvputs(cvinfo, "<br>\n")) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_html_form_conv(CTR_CLASS_NAME(form) *frm,
                                       const ctr_wcomp_visitor_t *vt,
                                       void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_browser_t *brw = cvinfo->brw;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    char *act;
    char *mtd;
    char *utn;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if (frm->act != NULL)
        act = apr_uri_unparse(pool, frm->act, 0);
    else
        act = NULL;

    if (brw->carrier == CR_SOFTBANK && brw->spec.softbank.isgetonly)
        mtd = " method=\"get\"";
    else if (frm->mtd == FM_POST)
        mtd = " method=\"post\"";
    else
        mtd = "";

    if (brw->carrier == CR_DOCOMO && frm->utn) {
        if (cvinfo->isxml)
            utn = " utn=\"utn\"";
        else
            utn = " utn";
    } else
        utn = "";

    if (brw->carrier == CR_KDDI && !dat->brout) {
        /*
         * It does not seem that KDDI WAP 2.0 browsers consider <form> as a
         * block element.  So output <br> if the previous component is not a
         * block element.
         */
        if ((rv = ctr_cvputs(cvinfo, "<br />\n")) != APR_SUCCESS)
            return rv;

        CTR_HTML_BR_ON(cvinfo);
    }

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<form",
                          act ? " action=\"" : "",
                          act ?
                          cvinfo->isxml ?
                          ctr_html_escape(act, pool) : act : "",
                          act ? "\"" : "",
                          mtd, utn, ">\n", NULL)) != APR_SUCCESS)
        return rv;

    if (cvinfo->isxml) {
        ctr_decoinfo_t *dinfo;

        dinfo = ctr_html_block_decoinfo_add(dat);

        if (dinfo->bgcolor_stat == DS_INIT || frm->align != AL_NONE) {
            char *fmt;

            fmt = apr_pstrcat(pool,
                              "<div style=\"",
                              dinfo->bgcolor_stat == DS_INIT ?
                              "background-color:#%06x;" : "%s",
                              frm->align == AL_LEFT ? "text-align:left;" :
                              frm->align == AL_CENTER ? "text-align:center;" :
                              frm->align == AL_RIGHT ?
                              "text-align:right;" : "",
                              "\">\n", NULL);
            if ((rv = ctr_cvprintf(cvinfo, fmt,
                                   dinfo->bgcolor_stat == DS_INIT ?
                                   (intptr_t)dinfo->bgcolor : (intptr_t)""))
                != APR_SUCCESS)
                return rv;

            if (dinfo->bgcolor_stat == DS_INIT)
                dinfo->bgcolor_stat = DS_OUT_DIV;
        } else {
            if ((rv = ctr_cvputs(cvinfo, "<div>\n")) != APR_SUCCESS)
                return rv;
        }
    } else {
        if (frm->align != AL_NONE) {
            if ((rv = ctr_cvputvs(cvinfo,
                                  "<div align=\"",
                                  frm->align == AL_LEFT ? "left" :
                                  frm->align == AL_CENTER ? "center" : "right",
                                  "\">\n", NULL)) != APR_SUCCESS)
                return rv;
        } else {
            if ((rv = ctr_cvputs(cvinfo, "<div>\n")) != APR_SUCCESS)
                return rv;
        }
    }

    CTR_HTML_BR_ON(cvinfo);

    dat->curfrm = frm;

    CTR_FOREACH_CHILD(frm, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    dat->curfrm = NULL;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if (cvinfo->isxml)
        apr_array_pop(dat->decoinfos);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "</div>\n"
                          "</form>\n", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_html_heading_conv(CTR_CLASS_NAME(heading) *hdg,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_browser_t *brw = cvinfo->brw;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if (brw->carrier == CR_SOFTBANK) {
        /*
         * SoftBank's HTML does not support the align attribute in <Hn> tag, so
         * we use <div> tag to align text in <Hn> tag.
         */
        if (hdg->align != AL_NONE) {
            if ((rv = ctr_cvputvs(cvinfo,
                                  !dat->brout ? "\n" : "",
                                  "<div",
                                  hdg->align != AL_NONE ? " align=\"" : "",
                                  hdg->align == AL_LEFT ? "left\"" :
                                  hdg->align == AL_CENTER ? "center\"" :
                                  hdg->align == AL_RIGHT ? "right\"" : "",
                                  ">\n", NULL)) != APR_SUCCESS)
                return rv;
            CTR_HTML_BR_ON(cvinfo);
        }

        if ((rv = ctr_cvprintf(cvinfo, "%s<h%d>",
                               !dat->brout ? "\n" : "",
                               hdg->type)) != APR_SUCCESS)
            return rv;
    } else {
        if ((rv = ctr_cvprintf(cvinfo, "%s<h%d%s%s>",
                               !dat->brout ? "\n" : "",
                               hdg->type,
                               hdg->align != AL_NONE ? " align=\"" : "",
                               hdg->align == AL_LEFT ? "left\"" :
                               hdg->align == AL_CENTER ? "center\"" :
                               hdg->align == AL_RIGHT ? "right\"" : ""))
            != APR_SUCCESS)
            return rv;
    }

    /* XXX: may have to output <font size="x"> tag */

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(hdg, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    /* XXX: may have to output </font> tag */

    if ((rv = ctr_cvprintf(cvinfo, "</h%d>\n",
                           hdg->type)) != APR_SUCCESS)
        return rv;

    if (brw->carrier == CR_SOFTBANK && hdg->align != AL_NONE) {
        if ((rv = ctr_cvputs(cvinfo, "</div>\n")) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_html_list_conv(CTR_CLASS_NAME(list) *lst,
                                       const ctr_wcomp_visitor_t *vt,
                                       void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<",
                          lst->ordered ? "ol" : "ul",
                          ">\n", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(lst, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "</",
                          lst->ordered ? "ol" : "ul",
                          ">\n", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_html_listitem_conv(CTR_CLASS_NAME(listitem) *li,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((dinfo = (ctr_decoinfo_t *)CTR_ARRAY_LAST(dat->decoinfos)) != NULL &&
        (dinfo->txtcolor_stat == DS_INIT || dinfo->fsize_stat == DS_INIT)) {
        char *fmt;

        fmt = apr_pstrcat(pool, "<font",
                          dinfo->txtcolor_stat == DS_INIT ?
                          " color=\"#%06x\"" : "%s",
                          dinfo->fsize_stat == DS_INIT ? " size=\"" : "",
                          dinfo->fsize_stat == DS_INIT ?
                          gen_fsizes[dinfo->fsize] : "",
                          dinfo->fsize_stat == DS_INIT ? "\"" : "",
                          ">", NULL);
        if ((rv = ctr_cvprintf(cvinfo, fmt,
                               dinfo->txtcolor_stat == DS_INIT ?
                               (intptr_t)dinfo->txtcolor : (intptr_t)""))
            != APR_SUCCESS)
            return rv;

        if (dinfo->txtcolor_stat == DS_INIT)
            dinfo->txtcolor_stat = DS_OUT_INLINE;
        if (dinfo->fsize_stat == DS_INIT)
            dinfo->fsize_stat = DS_OUT_INLINE;
    }

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<li>", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(li, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputs(cvinfo,
                         !dat->brout ? "\n" : "")) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_html_deflist_conv(CTR_CLASS_NAME(deflist) *dlt,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<dl>\n", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(dlt, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "</dl>\n", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_html_defterm_conv(CTR_CLASS_NAME(defterm) *dtm,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<dt>", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(dtm, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputs(cvinfo,
                         !dat->brout ? "\n" : "")) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_html_defdesc_conv(CTR_CLASS_NAME(defdesc) *ddc,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<dd>", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(ddc, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputs(cvinfo,
                         !dat->brout ? "\n" : "")) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_html_deco_conv(CTR_CLASS_NAME(deco) *dec,
                                       const ctr_wcomp_visitor_t *vt,
                                       void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    int txtcolor;
    ctr_decoinfo_t *old_dinfo, *new_dinfo;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    if (cvinfo->isxml) {
        if ((txtcolor = dec->txtcolor) == -1)
            txtcolor = dec->txtcolor2;
    } else {
        if ((txtcolor = dec->txtcolor2) == -1)
            txtcolor = dec->txtcolor;
    }

    if ((old_dinfo =
         (ctr_decoinfo_t *)CTR_ARRAY_LAST(dat->decoinfos)) != NULL) {
        int same;

        same = TRUE;
        if ((txtcolor != -1 && txtcolor != old_dinfo->txtcolor) ||
            (dec->bgcolor != -1 && dec->bgcolor != old_dinfo->bgcolor) ||
            (dec->blink && !old_dinfo->blink) ||
            (dec->fsize != FS_NONE && dec->fsize != old_dinfo->fsize))
            same = FALSE;

        /*
         * If the previous decoration information has all properties that new
         * decoration information will add, it is useless to add a new one.
         */
        if (same) {
            CTR_FOREACH_CHILD(dec, wcont, wcomp) {
                if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
                    return rv;
            }

            return APR_SUCCESS;
        }

        new_dinfo = apr_array_push(dat->decoinfos);

        /*
         * Get the pointer to the previous decoration information again since
         * apr_array_push() may change the location of the element.
         */
        old_dinfo = new_dinfo - 1;
    } else
        new_dinfo = apr_array_push(dat->decoinfos);

    if (txtcolor != -1) {
        new_dinfo->txtcolor = txtcolor;
        if (old_dinfo == NULL || txtcolor != old_dinfo->txtcolor ||
            (txtcolor == old_dinfo->txtcolor &&
             old_dinfo->txtcolor_stat == DS_INIT))
            new_dinfo->txtcolor_stat = DS_INIT;
        else
            new_dinfo->txtcolor_stat = DS_OUT_DERIVED;
    } else {
        if (old_dinfo == NULL) {
            new_dinfo->txtcolor = -1;
            new_dinfo->txtcolor_stat = DS_NONE;
        } else {
            new_dinfo->txtcolor = old_dinfo->txtcolor;
            if (old_dinfo->txtcolor_stat == DS_OUT_INLINE)
                new_dinfo->txtcolor_stat = DS_OUT_DERIVED;
            else
                new_dinfo->txtcolor_stat = old_dinfo->txtcolor_stat;
        }
    }

    if (dec->bgcolor != -1) {
        new_dinfo->bgcolor = dec->bgcolor;
        if (old_dinfo == NULL || dec->bgcolor != old_dinfo->bgcolor ||
            (dec->bgcolor == old_dinfo->bgcolor &&
             old_dinfo->bgcolor_stat == DS_INIT))
            new_dinfo->bgcolor_stat = DS_INIT;
        else
            new_dinfo->bgcolor_stat = DS_OUT_DERIVED;
    } else {
        if (old_dinfo == NULL) {
            new_dinfo->bgcolor = -1;
            new_dinfo->bgcolor_stat = DS_NONE;
        } else {
            new_dinfo->bgcolor = old_dinfo->bgcolor;
            if (old_dinfo->bgcolor_stat == DS_OUT_INLINE)
                new_dinfo->bgcolor_stat = DS_OUT_DERIVED;
            else
                new_dinfo->bgcolor_stat = old_dinfo->bgcolor_stat;
        }
    }

    if (dec->blink) {
        new_dinfo->blink = TRUE;
        if (old_dinfo == NULL || !old_dinfo->blink ||
            (old_dinfo->blink && old_dinfo->blink_stat == DS_INIT))
            new_dinfo->blink_stat = DS_INIT;
        else
            new_dinfo->blink_stat = DS_OUT_DERIVED;
    } else {
        if (old_dinfo == NULL) {
            new_dinfo->blink = FALSE;
            new_dinfo->blink_stat = DS_NONE;
        } else {
            new_dinfo->blink = old_dinfo->blink;
            if (old_dinfo->blink_stat == DS_OUT_INLINE)
                new_dinfo->blink_stat = DS_OUT_DERIVED;
            else
                new_dinfo->blink_stat = old_dinfo->blink_stat;
        }
    }

    if (dec->fsize != FS_NONE) {
        new_dinfo->fsize = dec->fsize;
        if (old_dinfo == NULL || dec->fsize != old_dinfo->fsize ||
            (dec->fsize == old_dinfo->fsize &&
             old_dinfo->fsize_stat == DS_INIT))
            new_dinfo->fsize_stat = DS_INIT;
        else
            new_dinfo->fsize_stat = DS_OUT_DERIVED;
    } else {
        if (old_dinfo == NULL) {
            new_dinfo->fsize = FS_NONE;
            new_dinfo->fsize_stat = DS_NONE;
        } else {
            new_dinfo->fsize = old_dinfo->fsize;
            if (old_dinfo->fsize_stat == DS_OUT_INLINE)
                new_dinfo->fsize_stat = DS_OUT_DERIVED;
            else
                new_dinfo->fsize_stat = old_dinfo->fsize_stat;
        }
    }

    CTR_FOREACH_CHILD(dec, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    new_dinfo = apr_array_pop(dat->decoinfos);

    if (cvinfo->isxml) {
        int outputted;

        outputted = FALSE;

        if (new_dinfo->fsize_stat == DS_OUT_INLINE) {
            if ((rv = ctr_cvputs(cvinfo, "</span>")) != APR_SUCCESS)
                return rv;

            outputted = TRUE;
        }

        if (new_dinfo->blink_stat == DS_OUT_INLINE ||
            new_dinfo->bgcolor_stat == DS_OUT_INLINE ||
            new_dinfo->txtcolor_stat == DS_OUT_INLINE) {
            if ((rv = ctr_cvputs(cvinfo, "</span>")) != APR_SUCCESS)
                return rv;

            outputted = TRUE;
        }

        if (dat->brout && outputted) {
            if ((rv = ctr_cvputs(cvinfo, "\n")) != APR_SUCCESS)
                return rv;
        }
    } else {
        int outputted;

        outputted = FALSE;

        if (new_dinfo->blink_stat == DS_OUT_INLINE) {
            if ((rv = ctr_cvputs(cvinfo, "</blink>")) != APR_SUCCESS)
                return rv;

            outputted = TRUE;
        }

        if (new_dinfo->txtcolor_stat == DS_OUT_INLINE ||
            new_dinfo->fsize_stat == DS_OUT_INLINE) {
            if ((rv = ctr_cvputs(cvinfo, "</font>")) != APR_SUCCESS)
                return rv;

            outputted = TRUE;
        }

        if (dat->brout && outputted) {
            if ((rv = ctr_cvputs(cvinfo, "\n")) != APR_SUCCESS)
                return rv;
        }
    }

    return APR_SUCCESS;
}

static apr_status_t ctr_html_anchor_conv(CTR_CLASS_NAME(anchor) *anc,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_browser_t *brw = cvinfo->brw;
    static const char *akeys[] = {
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "#"
    };
    char *href;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_OUT(cvinfo);

    if (anc->href != NULL)
        href = apr_uri_unparse(pool, anc->href, 0);
    else
        href = NULL;

    if ((rv = ctr_cvputvs(cvinfo,
                          "<a",
                          href ? " href=\"" : "",
                          href ?
                          cvinfo->isxml ?
                          ctr_html_escape(href, pool) : href : "",
                          href ? "\"" : "",
                          anc->akey != AC_NONE && brw->carrier != CR_UNKNOWN ?
                          " accesskey=\"" : "",
                          anc->akey != AC_NONE && brw->carrier != CR_UNKNOWN ?
                          akeys[anc->akey] : "",
                          anc->akey != AC_NONE && brw->carrier != CR_UNKNOWN ?
                          "\"" : "",
                          brw->carrier == CR_DOCOMO && anc->utn ?
                          cvinfo->isxml ? " utn=\"utn\"" : " utn" : "",
                          ">", NULL)) != APR_SUCCESS)
        return rv;

    CTR_FOREACH_CHILD(anc, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    return ctr_cvputs(cvinfo, "</a>");
}

static apr_status_t ctr_html_select_conv(CTR_CLASS_NAME(select) *sel,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_browser_t *brw = cvinfo->brw;
    char *color;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    if (brw->carrier == CR_SOFTBANK && cvinfo->isxml)
        color = " style=\"color:#000000;\"";
    else
        color = "";

    if ((rv = ctr_cvputvs(cvinfo,
                          "<select",
                          sel->name ? " name=\"" : "",
                          sel->name ? ctr_html_escape(sel->name, pool) : "",
                          sel->name ? "\"" : "",
                          color,
                          ">", NULL)) != APR_SUCCESS)
        return rv;

    CTR_FOREACH_CHILD(sel, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    return ctr_cvputs(cvinfo, "</select>");
}

/* ARGSUSED */
static apr_status_t ctr_html_hrline_conv(CTR_CLASS_NAME(hrline) *hr,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_browser_t *brw = cvinfo->brw;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    char *fmt;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<hr",
                          hr->align == AL_LEFT ? " align=\"left\"" :
                          hr->align == AL_RIGHT ? " align=\"right\"" : "",
                          hr->size != -1 ? " size=\"" : "",
                          hr->size != -1 ? apr_itoa(pool, hr->size) : "",
                          hr->size != -1 ? "\"" : "",
                          hr->width ? " width=\"" : "",
                          hr->width ? hr->width : "",
                          hr->width ? "\"" : "", NULL)) != APR_SUCCESS)
        return rv;
    if (brw->carrier == CR_UNKNOWN) {
        fmt = apr_pstrcat(pool,
                          hr->color != -1 ?
                          " style=\"background-color:#%06x;\"" : "%s",
                          " />\n", NULL);
    } else {
        fmt = apr_pstrcat(pool,
                          hr->color != -1 ? " color=\"#%06x\"" : "%s",
                          cvinfo->isxml ? " /" : "",
                          ">\n", NULL);
    }

    CTR_HTML_BR_ON(cvinfo);

    return ctr_cvprintf(cvinfo, fmt,
                        hr->color != -1 ? (intptr_t)hr->color : (intptr_t)"");
}

/* ARGSUSED */
static apr_status_t ctr_html_newline_conv(CTR_CLASS_NAME(newline) *nl,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;

    CTR_HTML_BR_ON(cvinfo);

    if (cvinfo->isxml)
        return ctr_cvputs(cvinfo, "<br />\n");
    else
        return ctr_cvputs(cvinfo, "<br>\n");
}

/* ARGSUSED */
static apr_status_t ctr_html_image_conv(CTR_CLASS_NAME(image) *img,
                                        const ctr_wcomp_visitor_t *vt,
                                        void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    char *uri;

    CTR_HTML_BR_OFF(cvinfo);

    ctr_imguri_conv(img, cvinfo->brw, pool);
    if (img->src != NULL)
        uri = apr_uri_unparse(pool, img->src, 0);
    else
        uri = NULL;

    return ctr_cvputvs(cvinfo,
                       "<img",
                       uri ? " src=\"" : "",
                       uri ?
                       cvinfo->isxml ? ctr_html_escape(uri, pool) : uri : "",
                       uri ? "\"" : "",
                       img->alt ? " alt=\"" : "",
                       img->alt ? ctr_html_escape(img->alt, pool) : "",
                       img->alt ? "\"" : "",
                       cvinfo->isxml ? " /" : "",
                       ">", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_html_editbox_conv(CTR_CLASS_NAME(editbox) *ebox,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_browser_t *brw = cvinfo->brw;

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    return ctr_cvputvs(cvinfo,
                       "<input type=\"text\"",
                       ebox->name ? " name=\"" : "",
                       ebox->name ? ctr_html_escape(ebox->name, pool) : "",
                       ebox->name ? "\"" : "",
                       ebox->val ? " value=\"" : "",
                       ebox->val ? ctr_html_escape(ebox->val, pool) : "",
                       ebox->val ? "\"" : "",
                       ebox->size >= 0 ? " size=\"" : "",
                       ebox->size >= 0 ? apr_itoa(pool, ebox->size) : "",
                       ebox->size >= 0 ? "\"" : "",
                       ebox->max >= 0 ? " maxlength=\"" : "",
                       ebox->max >= 0 ? apr_itoa(pool, ebox->max) : "",
                       ebox->max >= 0 ? "\"" : "",
                       ebox->style != ST_FULLKANA &&
                       brw->carrier != CR_UNKNOWN ?
                       " istyle=\"" : "",
                       ebox->style != ST_FULLKANA &&
                       brw->carrier != CR_UNKNOWN ?
                       apr_itoa(pool, ebox->style) : "",
                       ebox->style != ST_FULLKANA &&
                       brw->carrier != CR_UNKNOWN ?
                       "\"" : "",
                       cvinfo->isxml ? " /" : "",
                       ">", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_html_passwd_conv(CTR_CLASS_NAME(passwd) *pwd,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    return ctr_cvputvs(cvinfo,
                       "<input type=\"password\"",
                       pwd->name ? " name=\"" : "",
                       pwd->name ? ctr_html_escape(pwd->name, pool) : "",
                       pwd->name ? "\"" : "",
                       pwd->val ? " value=\"" : "",
                       pwd->val ? ctr_html_escape(pwd->val, pool) : "",
                       pwd->val ? "\"" : "",
                       pwd->size >= 0 ? " size=\"" : "",
                       pwd->size >= 0 ? apr_itoa(pool, pwd->size) : "",
                       pwd->size >= 0 ? "\"" : "",
                       pwd->max >= 0 ? " maxlength=\"" : "",
                       pwd->max >= 0 ? apr_itoa(pool, pwd->max) : "",
                       pwd->max >= 0 ? "\"" : "",
                       cvinfo->isxml ? " /" : "",
                       ">", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_html_radio_conv(CTR_CLASS_NAME(radio) *rdo,
                                        const ctr_wcomp_visitor_t *vt,
                                        void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    int chk;

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    if (rdo->rinfo != NULL) {
        if (rdo->rinfo->chk == rdo->no)
            chk = TRUE;
        else
            chk = FALSE;
    } else
        chk = rdo->chk;

    return ctr_cvputvs(cvinfo,
                       "<input type=\"radio\"",
                       rdo->name ? " name=\"" : "",
                       rdo->name ? ctr_html_escape(rdo->name, pool) : "",
                       rdo->name ? "\"" : "",
                       rdo->val ? " value=\"" : "",
                       rdo->val ? ctr_html_escape(rdo->val, pool) : "",
                       rdo->val ? "\"" : "",
                       chk ?
                       (cvinfo->isxml ?
                        " checked=\"checked\"" : " checked") : "",
                       cvinfo->isxml ? " /" : "",
                       ">", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_html_chkbox_conv(CTR_CLASS_NAME(chkbox) *cbox,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    return ctr_cvputvs(cvinfo,
                       "<input type=\"checkbox\"",
                       cbox->name ? " name=\"" : "",
                       cbox->name ? ctr_html_escape(cbox->name, pool) : "",
                       cbox->name ? "\"" : "",
                       cbox->val ? " value=\"" : "",
                       cbox->val ? ctr_html_escape(cbox->val, pool) : "",
                       cbox->val ? "\"" : "",
                       cbox->chk ?
                       (cvinfo->isxml ?
                        " checked=\"checked\"" : " checked") : "",
                       cvinfo->isxml ? " /" : "",
                       ">", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_html_hidden_conv(CTR_CLASS_NAME(hidden) *hdn,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;

    return ctr_cvputvs(cvinfo,
                       "<input type=\"hidden\"",
                       hdn->name ? " name=\"" : "",
                       hdn->name ? ctr_html_escape(hdn->name, pool) : "",
                       hdn->name ? "\"" : "",
                       hdn->val ? " value=\"" : "",
                       hdn->val ? ctr_html_escape(hdn->val, pool) : "",
                       hdn->val ? "\"" : "",
                       cvinfo->isxml ? " /" : "",
                       ">\n", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_html_submit_conv(CTR_CLASS_NAME(submit) *smt,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    return ctr_cvputvs(cvinfo,
                       "<input type=\"submit\"",
                       smt->name ? " name=\"" : "",
                       smt->name ? ctr_html_escape(smt->name, pool) : "",
                       smt->name ? "\"" : "",
                       " value=\"",
                       smt->val && strlen(smt->val) > 0 ?
                       ctr_html_escape(smt->val, pool) : (char *)CTR_POST_MSG,
                       "\"",
                       cvinfo->isxml ? " /" : "",
                       ">", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_html_reset_conv(CTR_CLASS_NAME(reset) *rst,
                                        const ctr_wcomp_visitor_t *vt,
                                        void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_browser_t *brw = cvinfo->brw;
    char *color;

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    if (brw->carrier == CR_SOFTBANK && cvinfo->isxml)
        color = " style=\"color:#000000;\"";
    else
        color = "";

    return ctr_cvputvs(cvinfo,
                       "<input type=\"reset\"",
                       rst->name ? " name=\"" : "",
                       rst->name ? ctr_html_escape(rst->name, pool) : "",
                       rst->name ? "\"" : "",
                       " value=\"",
                       rst->val && strlen(rst->val) > 0 ?
                       ctr_html_escape(rst->val, pool) : (char *)CTR_RST_MSG,
                       "\"",
                       color,
                       cvinfo->isxml ? " /" : "",
                       ">", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_html_option_conv(CTR_CLASS_NAME(option) *opt,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;

    return ctr_cvputvs(cvinfo,
                       "<option",
                       opt->val ? " value=\"" : "",
                       opt->val ? ctr_html_escape(opt->val, pool) : "",
                       opt->val ? "\"" : "",
                       opt->sel ?
                       (cvinfo->isxml ?
                        " selected=\"selected\"" : " selected") : "",
                       ">",
                       opt->txt ? ctr_html_escape(opt->txt, pool) : "",
                       cvinfo->isxml ? "</option>" : "",
                       NULL);
}

/* ARGSUSED */
static apr_status_t ctr_html_textarea_conv(CTR_CLASS_NAME(textarea) *txa,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_browser_t *brw = cvinfo->brw;
    static const char *ixmodes[] = {
        "", "ja:hk", "ja:en", "ja:n"
    };
    static const char *vmodes[] = {
        "", "hankakukana", "alphabet", "numeric"
    };
    char *style;
    char *color;

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    if (txa->style != ST_FULLKANA) {
        if (brw->carrier == CR_DOCOMO && cvinfo->isxml) {
            style = apr_pstrcat(pool,
                               " style=\"-wap-input-format:&quot;*&lt;",
                               ixmodes[txa->style - 1],
                                "&gt;&quot;;\"", NULL);
        } else if (brw->carrier == CR_SOFTBANK) {
            style = apr_psprintf(pool, " mode=\"%s\" istyle=\"%d\"",
                                 vmodes[txa->style - 1], txa->style);
        } else if (brw->carrier != CR_UNKNOWN)
            style = apr_psprintf(pool, " istyle=\"%d\"", txa->style);
        else
            style = "";
    } else
        style = "";

    if (brw->carrier == CR_SOFTBANK && cvinfo->isxml)
        color = " style=\"color:#000000;\"";
    else
        color = "";

    return ctr_cvputvs(cvinfo,
                       "<textarea",
                       txa->name ? " name=\"" : "",
                       txa->name ? ctr_html_escape(txa->name, pool) : "",
                       txa->name ? "\"" : "",
                       txa->cols >= 0 ? " cols=\"" : "",
                       txa->cols >= 0 ? apr_itoa(pool, txa->cols) : "",
                       txa->cols >= 0 ? "\"" : "",
                       txa->rows >= 0 ? " rows=\"" : "",
                       txa->rows >= 0 ? apr_itoa(pool, txa->rows) : "",
                       txa->rows >= 0 ? "\"" : "",
                       style, color,
                       ">",
                       txa->txt ? ctr_html_escape(txa->txt, pool) : "",
                       "</textarea>", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_html_text_conv(CTR_CLASS_NAME(text) *txt,
                                       const ctr_wcomp_visitor_t *vt,
                                       void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_browser_t *brw = cvinfo->brw;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    char *msg;
    apr_status_t rv;

    msg = apr_pstrdup(pool, txt->txt);

    if (dat->last_is_space) {
        /*
         * All white spaces at the top of a line after a block element or <br>
         * tag are ignored.  If the previous string ends with spaces, they are
         * ignored too.
         */
        msg = ctr_trim_head(msg);
    }

    /*
     * All white spaces in the middle of a line are united into a ' '
     * character.
     */
    msg = ctr_unite_spaces(msg);

    if (*msg == '\0')
        return APR_SUCCESS;

    CTR_HTML_DECO_OUT(cvinfo);

    dat->brout = FALSE;

    if (msg[strlen(msg) - 1] == ' ')
        dat->last_is_space = TRUE;
    else
        dat->last_is_space = FALSE;

    if (brw->carrier == CR_KDDI) {
        const unsigned char *p, *q;
        int in1byte;

        q = p = (unsigned char *)msg;
        in1byte = FALSE;
        while (*p) {
            if ((((0x81 <= *p && *p <= 0x9f) || (0xe0 <= *p && *p <= 0xfc)) &&
                ((0x40 <= *(p + 1) && *(p + 1) <= 0x7e) ||
                 (0x80 <= *(p + 1) && *(p + 1) <= 0xfc))) || *p == ' ') {
                /* 2 bytes character or ' ' */
                if (in1byte) {
                    char *s;

                    s = apr_pstrndup(pool, (char *)q, p - q);
                    if ((rv = ctr_cvputs(cvinfo, ctr_html_escape(s, pool)))
                        != APR_SUCCESS)
                        return rv;
                    if ((rv = ctr_cvputs(cvinfo, "</span>")) != APR_SUCCESS)
                        return rv;
                    q = p;
                    in1byte = FALSE;
                }
                if (*p != ' ')
                    p += 2;
                else
                    p++;
                continue;
            }

            /* 1 byte character */

            if (!in1byte) {
                char *s;

                s = apr_pstrndup(pool, (char *)q, p - q);
                if ((rv = ctr_cvputs(cvinfo, ctr_html_escape(s, pool)))
                    != APR_SUCCESS)
                    return rv;
                if (in1byte) {
                    if ((rv = ctr_cvputs(cvinfo, "</span>")) != APR_SUCCESS)
                        return rv;
                }
                if ((rv = ctr_cvputs(cvinfo, "<span>")) != APR_SUCCESS)
                    return rv;
                q = p;
                in1byte = TRUE;
            }
            p++;
        }

        if ((rv = ctr_cvputs(cvinfo, ctr_html_escape((char *)q, pool)))
            != APR_SUCCESS)
            return rv;
        if (in1byte) {
            if ((rv = ctr_cvputs(cvinfo, "</span>")) != APR_SUCCESS)
                return rv;
        }
        return APR_SUCCESS;
    } else
        return ctr_cvputs(cvinfo, ctr_html_escape(msg, pool));
}

/* ARGSUSED */
static apr_status_t ctr_html_rawtext_conv(CTR_CLASS_NAME(rawtext) *rtxt,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_browser_t *brw = cvinfo->brw;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    apr_status_t rv;

    if (rtxt->txt == NULL)
        return APR_SUCCESS;

    if (rtxt->type == RT_INLINE || !cvinfo->isxml) {
        CTR_HTML_DECO_OUT(cvinfo);
        return ctr_cvputs(cvinfo, rtxt->txt);
    } else {
        ctr_decoinfo_t *dinfo;
        int div_out;
        int fsize_out;

        CTR_HTML_DECO_CLOSE(cvinfo);
        dinfo = ctr_html_block_decoinfo_add(dat);

        div_out = FALSE;
        fsize_out = FALSE;

        if (dinfo != NULL &&
            (dinfo->txtcolor_stat == DS_INIT ||
             dinfo->bgcolor_stat == DS_INIT ||
             dinfo->blink_stat == DS_INIT)) {
            char *fmt;

            fmt = apr_pstrcat(pool, !dat->brout ? "\n" : "",
                              "<div style=\"",
                              dinfo->txtcolor_stat == DS_INIT ?
                              "color:#%06x;" : "%s",
                              dinfo->bgcolor_stat == DS_INIT ?
                              "background-color:#%06x;" : "%s",
                              dinfo->blink_stat == DS_INIT ?
                              "text-decoration:blink;" : "",
                              "\">\n", NULL);
            if ((rv = ctr_cvprintf(cvinfo, fmt,
                                   dinfo->txtcolor_stat == DS_INIT ?
                                   (intptr_t)dinfo->txtcolor : (intptr_t)"",
                                   dinfo->bgcolor_stat == DS_INIT ?
                                   (intptr_t)dinfo->bgcolor : (intptr_t)""))
                != APR_SUCCESS)
                return rv;

            CTR_HTML_BR_ON(cvinfo);
            div_out = TRUE;
        }

        if (dinfo != NULL && dinfo->fsize_stat == DS_INIT) {
            if ((rv = ctr_cvputvs(cvinfo,
                                  !dat->brout ? "\n" : "",
                                  "<div style=\"font-size:",
                                  brw->carrier == CR_KDDI ?
                                  au_fsizes[dinfo->fsize] :
                                  gen_xfsizes[dinfo->fsize],
                                  "\">\n", NULL)) != APR_SUCCESS)
                return rv;

            fsize_out = TRUE;
        }

        if ((rv = ctr_cvputs(cvinfo, rtxt->txt)) != APR_SUCCESS)
            return rv;

        if (fsize_out) {
            if ((rv = ctr_cvputs(cvinfo, "</div>\n")) != APR_SUCCESS)
                return rv;
        }

        if (div_out) {
            if ((rv = ctr_cvputs(cvinfo, "</div>\n")) != APR_SUCCESS)
                return rv;
        }

        apr_array_pop(dat->decoinfos);
        CTR_HTML_BR_ON(cvinfo);
        return APR_SUCCESS;
    }
}

/* ARGSUSED */
static apr_status_t ctr_html_brank_conv(CTR_CLASS_NAME(brank) *brk,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_browser_t *brw = cvinfo->brw;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    apr_status_t rv;

    if (!dat->brout) {
        if ((rv = ctr_cvputvs(cvinfo, "<br",
                              cvinfo->isxml ? " /" : "",
                              ">\n", NULL)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    return ctr_cvputvs(cvinfo,
                       brw->carrier == CR_SOFTBANK ? "&nbsp;" : "",
                       "<br",
                       cvinfo->isxml ? " /" : "",
                       ">\n", NULL);
}

static apr_status_t ctr_xhtml_block_conv(CTR_CLASS_NAME(block) *blk,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_browser_t *brw = cvinfo->brw;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    int div_out;
    int marquee_out;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    dinfo = (ctr_decoinfo_t *)CTR_ARRAY_LAST(dat->decoinfos);
    div_out = FALSE;
    marquee_out = FALSE;

    if ((dinfo != NULL &&
         dinfo->bgcolor_stat != DS_NONE &&
         dinfo->bgcolor_stat != DS_OUT_DIV) ||
        blk->align != AL_NONE ||
        (blk->marquee && brw->carrier == CR_DOCOMO)) {
        char *fmt;

        CTR_HTML_DECO_CLOSE(cvinfo);
        dinfo = ctr_html_block_decoinfo_add(dat);

        fmt = apr_pstrcat(pool,
                          !dat->brout ? "\n" : "",
                          "<div style=\"",
                          dinfo->bgcolor_stat == DS_INIT ?
                          "background-color:#%06x;" : "%s",
                          blk->align == AL_LEFT ? "text-align:left;" :
                          blk->align == AL_CENTER ? "text-align:center;" :
                          blk->align == AL_RIGHT ? "text-align:right;" : "",
                          blk->marquee && brw->carrier == CR_DOCOMO ?
                          "display:-wap-marquee;" : "",
                          blk->marquee && brw->carrier == CR_DOCOMO ?
                          blk->mqedirect == DL_RIGHT ?
                          "-wap-marquee-dir:ltr;" : "" : "",
                          blk->marquee && brw->carrier == CR_DOCOMO ?
                          blk->mqebehav == BH_SLIDE ?
                          "-wap-marquee-style:slide;" :
                          blk->mqebehav == BH_ALTERNATE ?
                          "-wap-marquee-style:alternate;" : "" : "",
                          blk->marquee && brw->carrier == CR_DOCOMO ?
                          "-wap-marquee-loop:%d;" : "%s",
                          "\">\n", NULL);
        if ((rv = ctr_cvprintf(cvinfo, fmt,
                               dinfo->bgcolor_stat == DS_INIT ?
                               (intptr_t)dinfo->bgcolor : (intptr_t)"",
                               blk->marquee && brw->carrier == CR_DOCOMO ?
                               (intptr_t)blk->mqeloop : (intptr_t)""))
            != APR_SUCCESS)
            return rv;

        if (dinfo->bgcolor_stat == DS_INIT)
            dinfo->bgcolor_stat = DS_OUT_DIV;

        div_out = TRUE;
        CTR_HTML_BR_ON(cvinfo);
    } else if (!blk->marquee && !dat->brout) {
        if ((rv = ctr_cvputs(cvinfo, "<br />\n")) != APR_SUCCESS)
            return rv;

        CTR_HTML_BR_ON(cvinfo);
    }

    if (blk->marquee && brw->carrier != CR_DOCOMO) {
        if (!div_out) {
            CTR_HTML_DECO_CLOSE(cvinfo);
            dinfo = ctr_html_block_decoinfo_add(dat);
        }

        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "<marquee",
                              blk->mqedirect == DL_RIGHT ?
                              " direction=\"right\"" : "",
                              blk->mqebehav == BH_SLIDE ?
                              " behavior=\"slide\"" :
                              blk->mqebehav == BH_ALTERNATE ?
                              " behavior=\"alternate\"" : "",
                              " loop=\"", apr_itoa(pool, blk->mqeloop), "\"",
                              ">\n", NULL)) != APR_SUCCESS)
            return rv;
        marquee_out = TRUE;
    }

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(blk, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    if (marquee_out) {
        CTR_HTML_DECO_CLOSE(cvinfo);
        apr_array_pop(dat->decoinfos);

        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "</marquee>\n", NULL)) != APR_SUCCESS)
            return rv;

        CTR_HTML_BR_ON(cvinfo);
    }

    if (div_out) {
        if (!marquee_out) {
            CTR_HTML_DECO_CLOSE(cvinfo);
            apr_array_pop(dat->decoinfos);
        }

        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "</div>\n", NULL)) != APR_SUCCESS)
            return rv;
    } else if (!dat->brout) {
        if ((rv = ctr_cvputs(cvinfo, "<br />\n")) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_xhtml_heading_conv(CTR_CLASS_NAME(heading) *hdg,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    dinfo = ctr_html_block_decoinfo_add(dat);

    /*
     * Adjust the font size to get the look like i-mode browsers.
     * XXX: SoftBank's browsers have not been tested yet.
     */
    dinfo->fsize_stat = DS_INIT;
    if (hdg->type <= 3)
        dinfo->fsize = FS_XLARGE;
    else if (hdg->type == 4)
        dinfo->fsize = FS_MEDIUM;
    else
        dinfo->fsize = FS_XSMALL;

    if (dinfo->bgcolor_stat == DS_INIT || hdg->align != AL_NONE) {
        char *fmt;

        fmt = apr_pstrcat(pool,
                          !dat->brout ? "\n" : "",
                          "<h%d style=\"",
                          dinfo->bgcolor_stat == DS_INIT ?
                          "background-color:#%06x;" : "%s",
                          hdg->align == AL_LEFT ? "text-align:left;" :
                          hdg->align == AL_CENTER ? "text-align:center;" :
                          hdg->align == AL_RIGHT ? "text-align:right;" : "",
                          "\">", NULL);
        if ((rv = ctr_cvprintf(cvinfo, fmt,
                               hdg->type,
                               dinfo->bgcolor_stat == DS_INIT ?
                               (intptr_t)dinfo->bgcolor : (intptr_t)""))
            != APR_SUCCESS)
            return rv;

        if (dinfo->bgcolor_stat == DS_INIT)
            dinfo->bgcolor_stat = DS_OUT_DIV;
    } else {
        if ((rv = ctr_cvprintf(cvinfo, "%s<h%d>",
                               !dat->brout ? "\n" : "",
                               hdg->type)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(hdg, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    apr_array_pop(dat->decoinfos);

    if ((rv = ctr_cvprintf(cvinfo, "</h%d>\n",
                           hdg->type)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_xhtml_list_conv(CTR_CLASS_NAME(list) *lst,
                                        const ctr_wcomp_visitor_t *vt,
                                        void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    dinfo = ctr_html_block_decoinfo_add(dat);

    if (dinfo->bgcolor_stat == DS_INIT) {
        if ((rv = ctr_cvprintf(cvinfo,
                               "%s<%s style=\"background-color:#%06x;\">\n",
                               !dat->brout ? "\n" : "",
                               lst->ordered ? "ol" : "ul",
                               dinfo->bgcolor)) != APR_SUCCESS)
            return rv;

        dinfo->bgcolor_stat = DS_OUT_DIV;
    } else {
        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "<",
                              lst->ordered ? "ol" : "ul",
                              ">\n", NULL)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(lst, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    apr_array_pop(dat->decoinfos);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "</",
                          lst->ordered ? "ol" : "ul",
                          ">\n", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_xhtml_listitem_conv(CTR_CLASS_NAME(listitem) *li,
                                            const ctr_wcomp_visitor_t *vt,
                                            void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    dinfo = ctr_html_block_decoinfo_add(dat);

    if (dinfo->txtcolor_stat == DS_INIT || dinfo->bgcolor_stat == DS_INIT ||
        dinfo->fsize_stat == DS_INIT) {
        char *fmt;

        fmt = apr_pstrcat(pool,
                          !dat->brout ? "\n" : "",
                          "<li style=\"",
                          dinfo->txtcolor_stat == DS_INIT ?
                          "color:#%06x;" : "%s",
                          dinfo->bgcolor_stat == DS_INIT ?
                          "background-color:#%06x;" : "%s",
                          dinfo->fsize_stat == DS_INIT ?
                          "font-size:%s;" : "%s",
                          "\">", NULL);
        if ((rv = ctr_cvprintf(cvinfo, fmt,
                               dinfo->txtcolor_stat == DS_INIT ?
                               (intptr_t)dinfo->txtcolor : (intptr_t)"",
                               dinfo->bgcolor_stat == DS_INIT ?
                               (intptr_t)dinfo->bgcolor : (intptr_t)"",
                               dinfo->fsize_stat == DS_INIT ?
                               gen_xfsizes[dinfo->fsize] : ""))
            != APR_SUCCESS)
            return rv;

        if (dinfo->txtcolor_stat == DS_INIT)
            dinfo->txtcolor_stat = DS_OUT_DIV;
        if (dinfo->bgcolor_stat == DS_INIT)
            dinfo->bgcolor_stat = DS_OUT_DIV;
        if (dinfo->fsize_stat == DS_INIT)
            dinfo->fsize_stat = DS_OUT_DIV;
    } else {
        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "<li>", NULL)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(li, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    apr_array_pop(dat->decoinfos);

    if ((rv = ctr_cvputs(cvinfo, "</li>\n")) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_xhtml_deflist_conv(CTR_CLASS_NAME(deflist) *dlt,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    dinfo = ctr_html_block_decoinfo_add(dat);

    if (dinfo->bgcolor_stat == DS_INIT) {
        if ((rv = ctr_cvprintf(cvinfo,
                               "%s<dl style=\"background-color:#%06x;\">",
                               !dat->brout ? "\n" : "",
                               dinfo->bgcolor)) != APR_SUCCESS)
            return rv;

        dinfo->bgcolor_stat = DS_OUT_DIV;
    } else {
        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "<dl>\n", NULL)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(dlt, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    apr_array_pop(dat->decoinfos);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "</dl>\n", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_xhtml_defterm_conv(CTR_CLASS_NAME(defterm) *dtm,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    dinfo = ctr_html_block_decoinfo_add(dat);

    if (dinfo->bgcolor_stat == DS_INIT) {
        if ((rv = ctr_cvprintf(cvinfo,
                               "%s<dt style=\"background-color:#%06x;\">",
                               !dat->brout ? "\n" : "",
                               dinfo->bgcolor)) != APR_SUCCESS)
            return rv;

        dinfo->bgcolor_stat = DS_OUT_DIV;
    } else {
        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "<dt>", NULL)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(dtm, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    apr_array_pop(dat->decoinfos);

    if ((rv = ctr_cvputs(cvinfo, "</dt>\n")) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_xhtml_defdesc_conv(CTR_CLASS_NAME(defdesc) *ddc,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    dinfo = ctr_html_block_decoinfo_add(dat);

    if (dinfo->bgcolor_stat == DS_INIT) {
        if ((rv = ctr_cvprintf(cvinfo,
                               "%s<dd style=\"background-color:#%06x;\">",
                               !dat->brout ? "\n" : "",
                               dinfo->bgcolor)) != APR_SUCCESS)
            return rv;

        dinfo->bgcolor_stat = DS_OUT_DIV;
    } else {
        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "<dd>", NULL)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(ddc, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    apr_array_pop(dat->decoinfos);

    if ((rv = ctr_cvputs(cvinfo, "</dd>\n")) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

/* ARGSUSED */
static apr_status_t ctr_ihtml_submit_conv(CTR_CLASS_NAME(submit) *smt,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;

    if (dat->curfrm->act != NULL &&
        ctr_startwith(dat->curfrm->act->path, "tel:")) {
        /*
         * It seems that mova browsers don't support <form action="tel:xxx">,
         * so change the submit button into an anchor.
         */
        CTR_HTML_BR_OFF(cvinfo);
        return ctr_cvputvs(cvinfo,
                           "<a href=\"", dat->curfrm->act->path, "\">",
                           smt->val ? ctr_html_escape(smt->val, pool) : "",
                           "</a>", NULL);
    }

    return ctr_html_submit_conv(smt, vt, udata);
}

/* ARGSUSED */
static apr_status_t ctr_ihtml_emoji_conv(CTR_CLASS_NAME(emoji) *emj,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_emoji_info_t *info;
    uint16_t v;

    if (cvinfo->emojihash == NULL)
        return APR_SUCCESS;

    info = CTR_HASH_GET(cvinfo->emojihash, emj->id);
    if (info == NULL || info->ihtml == NULL) {
        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, cvinfo->r,
                      "mod_cthree: failed to get a ihtml emoji (%s) "
                      "from emoji xml, cannot display", emj->id);
        return APR_SUCCESS;
    }

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    if ((v = (uint16_t)apr_strtoi64(info->ihtml, NULL, 16)) <= 0)
        return ctr_cvputs(cvinfo, info->ihtml);
    else
        return ctr_cvprintf(cvinfo, "%c%c", v >> 8, v & 0xff);
}

static apr_status_t ctr_ixhtml_page_conv(CTR_CLASS_NAME(page) *page,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat;
    char *fmt;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    /*
     * Initialize html specific data.
     */
    dat = apr_palloc(pool, sizeof(*dat));
    dat->brout = TRUE;
    dat->last_is_space = TRUE;
    dat->decoinfos = apr_array_make(pool, 1, sizeof(ctr_decoinfo_t));
    dat->curfrm = NULL;
    dat->linest = 0;
    cvinfo->udata = dat;

    fmt = apr_pstrcat(pool,
                      "<?xml version=\"1.0\" encoding=\"Shift_JIS\"?>\n"
                      "<!DOCTYPE html PUBLIC "
                      "\"-//i-mode group (ja)//DTD XHTML "
                      "i-XHTML(Locale/Ver.=ja/1.1) 1.0//EN\" "
                      "\"i-xhtml_4ja_10.dtd\">\n"
                      "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
                      "<head>\n"
                      "<meta http-equiv=\"content-type\" "
                      "content=\"application/xhtml+xml;"
                      "charset=Shift_JIS\" />\n"
                      "<title>",
                      ctr_html_escape(page->title, pool),
                      "</title>\n",
                      page->lnkcolor != -1 ?
                      "<style type=\"text/css\">\n"
                      "<![CDATA[\n"
                      "a:link{color:#%06x}\n"
                      "]]>\n"
                      "</style>\n" : "",
                      "</head>\n", NULL);
    if ((rv = ctr_cvprintf(cvinfo, fmt,
                           page->lnkcolor != -1 ?
                           (intptr_t)page->lnkcolor : (intptr_t)""))
        != APR_SUCCESS)
        return rv;
    if (page->bgcolor != -1 || page->txtcolor != -1 || page->txtcolor2 != -1) {
        fmt = apr_pstrcat(pool,
                          "<body style=\"",
                          page->bgcolor != -1 ?
                          "background-color:#%06x;" : "%s",
                          page->txtcolor != -1 || page->txtcolor2 != -1 ?
                          "color:#%06x;" : "%s",
                          "\">\n"
                          "<div>\n", NULL);
        if ((rv = ctr_cvprintf(cvinfo, fmt,
                               page->bgcolor != -1 ?
                               (intptr_t)page->bgcolor : (intptr_t)"",
                               page->txtcolor != -1 ?
                               (intptr_t)page->txtcolor :
                               page->txtcolor2 != -1 ?
                               (intptr_t)page->txtcolor2 : (intptr_t)""))
            != APR_SUCCESS)
            return rv;
    } else {
        if ((rv = ctr_cvputs(cvinfo,
                             "<body>\n"
                             "<div>\n")) != APR_SUCCESS)
            return rv;
    }

    CTR_FOREACH_CHILD(page, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    return ctr_cvputvs(cvinfo,
                       !dat->brout ? "\n" : "",
                       "</div>\n"
                       "</body>\n"
                       "</html>\n", NULL);
}
static apr_status_t ctr_ixhtml_heading_conv(CTR_CLASS_NAME(heading) *hdg,
                                            const ctr_wcomp_visitor_t *vt,
                                            void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    int div_out;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    dinfo = ctr_html_block_decoinfo_add(dat);

    /*
     * Clear the information of font size here.  That should not affect the
     * size of font inside <Hn> tags.
     */
    dinfo->fsize_stat = DS_NONE;
    dinfo->fsize = FS_NONE;

    div_out = FALSE;

    if (dinfo->bgcolor_stat == DS_INIT) {
        if ((rv = ctr_cvprintf(cvinfo,
                               "%s<div style=\"background-color:#%06x;\">\n",
                               !dat->brout ? "\n" : "",
                               dinfo->bgcolor)) != APR_SUCCESS)
            return rv;

        dinfo->bgcolor_stat = DS_OUT_DIV;
        div_out = TRUE;
        CTR_HTML_BR_ON(cvinfo);
    }

    if ((rv = ctr_cvprintf(cvinfo, "%s<h%d%s%s>",
                           !dat->brout ? "\n" : "",
                           hdg->type,
                           hdg->align != AL_NONE ? " style=\"text-align:" : "",
                           hdg->align == AL_LEFT ? "left\"" :
                           hdg->align == AL_CENTER ? "center\"" :
                           hdg->align == AL_RIGHT ? "right\"" : ""))
        != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(hdg, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvprintf(cvinfo, "</h%d>\n",
                           hdg->type)) != APR_SUCCESS)
        return rv;

    apr_array_pop(dat->decoinfos);

    if (div_out) {
        if ((rv = ctr_cvputs(cvinfo, "</div>\n")) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_ixhtml_list_conv(CTR_CLASS_NAME(list) *lst,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    int div_out;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if (dat->linest > 0) {
        if ((rv = ctr_cvputs(cvinfo, "</li>\n")) != APR_SUCCESS)
            return rv;

        dat->linest--;
        CTR_HTML_BR_ON(cvinfo);
    }

    dinfo = ctr_html_block_decoinfo_add(dat);

    div_out = FALSE;

    if (dinfo->bgcolor_stat == DS_INIT) {
        if ((rv = ctr_cvprintf(cvinfo,
                               "%s<div style=\"background-color:#%06x;\">\n",
                               !dat->brout ? "\n" : "",
                               dinfo->bgcolor)) != APR_SUCCESS)
            return rv;

        dinfo->bgcolor_stat = DS_OUT_DIV;
        div_out = TRUE;
        CTR_HTML_BR_ON(cvinfo);
    }

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<",
                          lst->ordered ? "ol" : "ul",
                          ">\n", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(lst, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "</",
                          lst->ordered ? "ol" : "ul",
                          ">\n", NULL)) != APR_SUCCESS)
        return rv;

    apr_array_pop(dat->decoinfos);

    if (div_out) {
        if ((rv = ctr_cvputs(cvinfo, "</div>\n")) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_ixhtml_listitem_conv(CTR_CLASS_NAME(listitem) *li,
                                             const ctr_wcomp_visitor_t *vt,
                                             void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    int div_out;
    int fsize_out;
    int linest;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    dinfo = ctr_html_block_decoinfo_add(dat);

    div_out = FALSE;
    fsize_out = FALSE;

    if (dinfo->txtcolor_stat == DS_INIT || dinfo->bgcolor_stat == DS_INIT) {
        char *fmt;

        fmt = apr_pstrcat(pool,
                          !dat->brout ? "\n" : "",
                          "<div style=\"",
                          dinfo->txtcolor_stat == DS_INIT ?
                          "color:#%06x;" : "%s",
                          dinfo->bgcolor_stat == DS_INIT ?
                          "background-color:#%06x;" : "%s",
                          "\">\n", NULL);
        if ((rv = ctr_cvprintf(cvinfo, fmt,
                               dinfo->txtcolor_stat == DS_INIT ?
                               (intptr_t)dinfo->txtcolor : (intptr_t)"",
                               dinfo->bgcolor_stat == DS_INIT ?
                               (intptr_t)dinfo->bgcolor : (intptr_t)""))
            != APR_SUCCESS)
            return rv;

        if (dinfo->txtcolor_stat == DS_INIT)
            dinfo->txtcolor_stat = DS_OUT_DIV;
        if (dinfo->bgcolor_stat == DS_INIT)
            dinfo->bgcolor_stat = DS_OUT_DIV;

        div_out = TRUE;
        CTR_HTML_BR_ON(cvinfo);
    }

    if (dinfo->fsize_stat == DS_INIT) {
        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "<div style=\"font-size:",
                              gen_xfsizes[dinfo->fsize],
                              "\">\n", NULL)) != APR_SUCCESS)
            return rv;

        fsize_out = TRUE;
        CTR_HTML_BR_ON(cvinfo);
    }

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<li>", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    linest = ++dat->linest;

    CTR_FOREACH_CHILD(li, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    if (linest == dat->linest) {
        if ((rv = ctr_cvputs(cvinfo, "</li>\n")) != APR_SUCCESS)
            return rv;

        dat->linest--;
    }

    apr_array_pop(dat->decoinfos);

    if (fsize_out) {
        if ((rv = ctr_cvputs(cvinfo, "</div>\n")) != APR_SUCCESS)
            return rv;
    }

    if (div_out) {
        if ((rv = ctr_cvputs(cvinfo, "</div>\n")) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_ixhtml_deflist_conv(CTR_CLASS_NAME(deflist) *dlt,
                                            const ctr_wcomp_visitor_t *vt,
                                            void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    int div_out;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    dinfo = ctr_html_block_decoinfo_add(dat);

    div_out = FALSE;

    if (dinfo->bgcolor_stat == DS_INIT) {
        if ((rv = ctr_cvprintf(cvinfo,
                               "%s<div style=\"background-color:#%06x;\">\n",
                               !dat->brout ? "\n" : "",
                               dinfo->bgcolor)) != APR_SUCCESS)
            return rv;

        dinfo->bgcolor_stat = DS_OUT_DIV;
        div_out = TRUE;
        CTR_HTML_BR_ON(cvinfo);
    }

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<dl>\n", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(dlt, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "</dl>\n", NULL)) != APR_SUCCESS)
        return rv;

    apr_array_pop(dat->decoinfos);

    if (div_out) {
        if ((rv = ctr_cvputs(cvinfo, "</div>\n")) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_ixhtml_defterm_conv(CTR_CLASS_NAME(defterm) *dtm,
                                            const ctr_wcomp_visitor_t *vt,
                                            void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    int div_out;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    dinfo = ctr_html_block_decoinfo_add(dat);

    div_out = FALSE;

    if (dinfo->bgcolor_stat == DS_INIT) {
        if ((rv = ctr_cvprintf(cvinfo,
                               "%s<div style=\"background-color:#%06x;\">\n",
                               !dat->brout ? "\n" : "",
                               dinfo->bgcolor)) != APR_SUCCESS)
            return rv;

        dinfo->bgcolor_stat = DS_OUT_DIV;
        div_out = TRUE;
        CTR_HTML_BR_ON(cvinfo);
    }

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<dt>", NULL)) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(dtm, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputs(cvinfo, "</dt>\n")) != APR_SUCCESS)
        return rv;

    apr_array_pop(dat->decoinfos);

    if (div_out) {
        if ((rv = ctr_cvputs(cvinfo, "</div>\n")) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_ixhtml_defdesc_conv(CTR_CLASS_NAME(defdesc) *ddc,
                                            const ctr_wcomp_visitor_t *vt,
                                            void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    int div_out;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<dd>", NULL)) != APR_SUCCESS)
        return rv;

    dinfo = ctr_html_block_decoinfo_add(dat);

    div_out = FALSE;

    if (dinfo->bgcolor_stat == DS_INIT) {
        if ((rv = ctr_cvprintf(cvinfo,
                               "\n<div style=\"background-color:#%06x;\">\n",
                               dinfo->bgcolor)) != APR_SUCCESS)
            return rv;

        dinfo->bgcolor_stat = DS_OUT_DIV;
        div_out = TRUE;
    }

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(ddc, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    apr_array_pop(dat->decoinfos);

    if (div_out) {
        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "</div>\n", NULL)) != APR_SUCCESS)
            return rv;
    }

    if ((rv = ctr_cvputs(cvinfo, "</dd>\n")) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

/* ARGSUSED */
static apr_status_t ctr_ixhtml_hrline_conv(CTR_CLASS_NAME(hrline) *hr,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    char *fmt;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if (hr->align != AL_CENTER || hr->size != -1 ||
        hr->width != NULL || hr->color != -1) {
        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "<br />\n" : "",
                              "<hr style=\"border-style:solid;",
                              hr->align == AL_LEFT ? "float:left;" :
                              hr->align == AL_RIGHT ? "float:right;" : "",
                              hr->size != -1 ? "height:" : "",
                              hr->size != -1 ? apr_itoa(pool, hr->size) : "",
                              hr->size != -1 ? ";" : "",
                              hr->width ? "width:" : "",
                              hr->width ? hr->width : "",
                              hr->width ? ";" : "", NULL)) != APR_SUCCESS)
            return rv;
        fmt = apr_pstrcat(pool,
                          hr->color != -1 ? "border-color:#%06x;" : "%s",
                          hr->color != -1 ? "background-color:#%06x;" : "%s",
                          "\" />\n",
                          hr->align != AL_CENTER ?
                          "<span style=\"font-size:xx-small\"><br />"
                          "</span>\n" : "", NULL);
        if ((rv = ctr_cvprintf(cvinfo, fmt,
                               hr->color != -1 ?
                               (intptr_t)hr->color : (intptr_t)"",
                               hr->color != -1 ?
                               (intptr_t)hr->color : (intptr_t)""))
            != APR_SUCCESS)
            return rv;
    } else {
        if ((rv = ctr_cvputvs(cvinfo, !dat->brout ? "\n" : "",
                              "<hr style=\"border-style:solid\" />\n",
                              NULL)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

/* ARGSUSED */
static apr_status_t ctr_ixhtml_editbox_conv(CTR_CLASS_NAME(editbox) *ebox,
                                            const ctr_wcomp_visitor_t *vt,
                                            void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    static const char *styles[] = {
        "", "ja:hk", "ja:en", "ja:n"
    };

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    return ctr_cvputvs(cvinfo,
                       "<input type=\"text\"",
                       ebox->name ? " name=\"" : "",
                       ebox->name ? ctr_html_escape(ebox->name, pool) : "",
                       ebox->name ? "\"" : "",
                       ebox->val ? " value=\"" : "",
                       ebox->val ? ctr_html_escape(ebox->val, pool) : "",
                       ebox->val ? "\"" : "",
                       ebox->size >= 0 ? " size=\"" : "",
                       ebox->size >= 0 ? apr_itoa(pool, ebox->size) : "",
                       ebox->size >= 0 ? "\"" : "",
                       ebox->max >= 0 ? " maxlength=\"" : "",
                       ebox->max >= 0 ? apr_itoa(pool, ebox->max) : "",
                       ebox->max >= 0 ? "\"" : "",
                       ebox->style != ST_FULLKANA ?
                       " style=\"-wap-input-format:&quot;*&lt;" : "",
                       ebox->style != ST_FULLKANA ?
                       styles[ebox->style - 1] : "",
                       ebox->style != ST_FULLKANA ? "&gt;&quot;;\"" : "",
                       " />", NULL);
}

static apr_status_t ctr_auxhtml_page_conv(CTR_CLASS_NAME(page) *page,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat;
    char *fmt;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    /*
     * Initialize html specific data.
     */
    dat = apr_palloc(pool, sizeof(*dat));
    dat->brout = TRUE;
    dat->last_is_space = TRUE;
    dat->decoinfos = apr_array_make(pool, 1, sizeof(ctr_decoinfo_t));
    dat->curfrm = NULL;
    cvinfo->udata = dat;

    if ((rv = ctr_cvputvs(cvinfo,
                          "<?xml version=\"1.0\" encoding=\"Shift_JIS\"?>\n"
                          "<!DOCTYPE html PUBLIC "
                          "\"-//OPENWAVE//DTD XHTML 1.0//EN\"\n"
                          "\"http://www.openwave.com/DTD/xhtml-basic.dtd\">\n"
                          "<html>\n"
                          "<head>\n"
                          "<title>",
                          ctr_html_escape(page->title, pool),
                          "</title>\n"
                          "</head>\n", NULL)) != APR_SUCCESS)
        return rv;
    fmt = apr_pstrcat(pool,
                      "<body",
                      page->bgcolor != -1 ? " bgcolor=\"#%06x\"" : "%s",
                      page->txtcolor != -1 || page->txtcolor2 != -1 ?
                      " text=\"#%06x\"" : "%s",
                      page->lnkcolor != -1 ? " link=\"#%06x\"" : "%s",
                      ">\n"
                      "<div>\n", NULL);
    if ((rv = ctr_cvprintf(cvinfo, fmt,
                           page->bgcolor != -1 ?
                           (intptr_t)page->bgcolor : (intptr_t)"",
                           page->txtcolor != -1 ? (intptr_t)page->txtcolor :
                           page->txtcolor2 != -1 ?
                           (intptr_t)page->txtcolor2 : (intptr_t)"",
                           page->lnkcolor != -1 ?
                           (intptr_t)page->lnkcolor : (intptr_t)""))
        != APR_SUCCESS)
        return rv;

    CTR_FOREACH_CHILD(page, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    return ctr_cvputvs(cvinfo,
                       !dat->brout ? "\n" : "",
                       "</div>\n"
                       "</body>\n"
                       "</html>\n", NULL);
}

static apr_status_t ctr_auxhtml_listitem_conv(CTR_CLASS_NAME(listitem) *li,
                                              const ctr_wcomp_visitor_t *vt,
                                              void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    int div_out;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    dinfo = ctr_html_block_decoinfo_add(dat);

    if (dinfo->txtcolor_stat == DS_INIT || dinfo->fsize_stat == DS_INIT) {
        char *fmt;

        fmt = apr_pstrcat(pool,
                          !dat->brout ? "\n" : "",
                          "<li style=\"",
                          dinfo->txtcolor_stat == DS_INIT ?
                          "color:#%06x;" : "%s",
                          dinfo->fsize_stat == DS_INIT ?
                          "font-size:%s;" : "%s",
                          "\">", NULL);
        if ((rv = ctr_cvprintf(cvinfo, fmt,
                               dinfo->txtcolor_stat == DS_INIT ?
                               (intptr_t)dinfo->txtcolor : (intptr_t)"",
                               dinfo->fsize_stat == DS_INIT ?
                               au_fsizes[dinfo->fsize] : ""))
            != APR_SUCCESS)
            return rv;

        if (dinfo->txtcolor_stat == DS_INIT)
            dinfo->txtcolor_stat = DS_OUT_DIV;
        if (dinfo->fsize_stat == DS_INIT)
            dinfo->fsize_stat = DS_OUT_DIV;
    } else {
        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "<li>", NULL)) != APR_SUCCESS)
            return rv;
    }

    div_out = FALSE;

    if (dinfo->bgcolor_stat == DS_INIT) {
        if ((rv = ctr_cvprintf(cvinfo,
                               "\n<div style=\"background-color:#%06x;\">\n",
                               dinfo->bgcolor)) != APR_SUCCESS)
            return rv;

        dinfo->bgcolor_stat = DS_OUT_DIV;
        div_out = TRUE;
    }

    CTR_HTML_BR_ON(cvinfo);

    CTR_FOREACH_CHILD(li, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HTML_DECO_CLOSE(cvinfo);

    apr_array_pop(dat->decoinfos);

    if (div_out) {
        if ((rv = ctr_cvputvs(cvinfo,
                              !dat->brout ? "\n" : "",
                              "</div>\n", NULL)) != APR_SUCCESS)
            return rv;
    }

    if ((rv = ctr_cvputs(cvinfo, "</li>\n")) != APR_SUCCESS)
        return rv;

    CTR_HTML_BR_ON(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_auxhtml_defdesc_conv(CTR_CLASS_NAME(defdesc) *ddc,
                                             const ctr_wcomp_visitor_t *vt,
                                             void *udata)
{
    return ctr_ixhtml_defdesc_conv(ddc, vt, udata);
}

/* ARGSUSED */
static apr_status_t ctr_auxhtml_hrline_conv(CTR_CLASS_NAME(hrline) *hr,
                                            const ctr_wcomp_visitor_t *vt,
                                            void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    char *fmt;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<div style=\"margin-top:5;margin-bottom:5;\">\n"
                          "<hr",
                          hr->align == AL_LEFT ? " align=\"left\"" :
                          hr->align == AL_RIGHT ? " align=\"right\"" : "",
                          hr->size != -1 ? " size=\"" : "",
                          hr->size != -1 ? apr_itoa(pool, hr->size) : "",
                          hr->size != -1 ? "\"" : "",
                          hr->width ? " width=\"" : "",
                          hr->width ? hr->width : "",
                          hr->width ? "\"" : "", NULL)) != APR_SUCCESS)
        return rv;
    fmt = apr_pstrcat(pool,
                      hr->color != -1 ? " color=\"#%06x\"" : "%s",
                      " />\n"
                      "</div>\n", NULL);

    CTR_HTML_BR_ON(cvinfo);

    return ctr_cvprintf(cvinfo, fmt,
                        hr->color != -1 ? (intptr_t)hr->color : (intptr_t)"");
}

/* ARGSUSED */
static apr_status_t ctr_auxhtml_passwd_conv(CTR_CLASS_NAME(passwd) *pwd,
                                            const ctr_wcomp_visitor_t *vt,
                                            void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    return ctr_cvputvs(cvinfo,
                       "<input type=\"password\"",
                       pwd->name ? " name=\"" : "",
                       pwd->name ? ctr_html_escape(pwd->name, pool) : "",
                       pwd->name ? "\"" : "",
                       pwd->val ? " value=\"" : "",
                       pwd->val ? ctr_html_escape(pwd->val, pool) : "",
                       pwd->val ? "\"" : "",
                       pwd->size >= 0 ? " size=\"" : "",
                       pwd->size >= 0 ? apr_itoa(pool, pwd->size) : "",
                       pwd->size >= 0 ? "\"" : "",
                       pwd->max >= 0 ? " maxlength=\"" : "",
                       pwd->max >= 0 ? apr_itoa(pool, pwd->max) : "",
                       pwd->max >= 0 ? "\"" : "",
                       " istyle=\"4\" />", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_auxhtml_emoji_conv(CTR_CLASS_NAME(emoji) *emj,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_emoji_info_t *info;

    if (cvinfo->emojihash == NULL)
        return APR_SUCCESS;

    info = CTR_HASH_GET(cvinfo->emojihash, emj->id);
    if (info == NULL || info->auxhtml == NULL) {
        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, cvinfo->r,
                      "mod_cthree: failed to get a auxhtml emoji (%s) "
                      "from emoji xml, cannot display", emj->id);
        return APR_SUCCESS;
    }

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    if (apr_atoi64(info->auxhtml) <= 0)
        return ctr_cvputs(cvinfo, info->auxhtml);
    else {
        return ctr_cvputvs(cvinfo,
                           "<img localsrc=\"", info->auxhtml, "\" />", NULL);
    }
}

static apr_status_t ctr_shtml_anchor_conv(CTR_CLASS_NAME(anchor) *anc,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    static const char *akeys[] = {
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "#"
    };
    apr_pool_t *pool = cvinfo->r->pool;
    char *p, *last;
    const char *dst;
    char *mbody = NULL;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    if (anc->href != NULL) {
        if (ctr_startwith(anc->href->path, "mailto:")) {
            dst = anc->href->path;
            if (anc->href->query != NULL) {
                for (p = apr_strtok(anc->href->query, "&", &last); p != NULL;
                     p = apr_strtok(NULL, "&", &last)) {
                    if (ctr_startwith(ctr_tolower_str(p), "body=")) {
                        mbody = p + 5;
                        ap_unescape_url(mbody);
                        mbody = ctr_html_escape(mbody, pool);
                        break;
                    }
                }
            }
        } else
            dst = apr_uri_unparse(pool, anc->href, 0);
    } else
        dst = NULL;

    CTR_HTML_DECO_OUT(cvinfo);

    /*
     * Ignore utn attribute and mail subject.
     */

    if ((rv = ctr_cvputvs(cvinfo,
                          "<a",
                          dst ? " href=\"" : "",
                          dst ? dst : "",
                          dst ? "\"" : "",
                          anc->akey != AC_NONE ? " accesskey=\"" : "",
                          anc->akey != AC_NONE ? akeys[anc->akey] : "",
                          anc->akey != AC_NONE ? "\"" : "",
                          mbody ? " mailbody=\"" : "",
                          mbody ? mbody : "",
                          mbody ? "\"" : "",
                          ">", NULL)) != APR_SUCCESS)
        return APR_SUCCESS;

    CTR_FOREACH_CHILD(anc, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    return ctr_cvputs(cvinfo, "</a>");
}

/* ARGSUSED */
static apr_status_t ctr_shtml_newline_conv(CTR_CLASS_NAME(newline) *nl,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    apr_status_t rv;

    if (dat->brout) {
        if ((rv = ctr_cvputs(cvinfo, "&nbsp;")) != APR_SUCCESS)
            return rv;
    }

    return ctr_html_newline_conv(nl, vt, udata);
}

/* ARGSUSED */
static apr_status_t ctr_shtml_editbox_conv(CTR_CLASS_NAME(editbox) *ebox,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    static const char *modes[] = {
        "", "hankakukana", "alphabet", "numeric"
    };
    apr_pool_t *pool = cvinfo->r->pool;

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    return ctr_cvputvs(cvinfo,
                       "<input type=\"text\"",
                       ebox->name ? " name=\"" : "",
                       ebox->name ? ctr_html_escape(ebox->name, pool) : "",
                       ebox->name ? "\"" : "",
                       ebox->val ? " value=\"" : "",
                       ebox->val ? ctr_html_escape(ebox->val, pool) : "",
                       ebox->val ? "\"" : "",
                       ebox->size >= 0 ? " size=\"" : "",
                       ebox->size >= 0 ? apr_itoa(pool, ebox->size) : "",
                       ebox->size >= 0 ? "\"" : "",
                       ebox->max >= 0 ? " maxlength=\"" : "",
                       ebox->max >= 0 ? apr_itoa(pool, ebox->max) : "",
                       ebox->max >= 0 ? "\"" : "",
                       ebox->style != ST_FULLKANA ? " mode=\"" : "",
                       ebox->style != ST_FULLKANA ?
                       modes[ebox->style - 1] : "",
                       ebox->style != ST_FULLKANA ? "\"" : "",
                       ebox->style != ST_FULLKANA ? " istyle=\"" : "",
                       ebox->style != ST_FULLKANA ?
                       apr_itoa(pool, ebox->style) : "",
                       ebox->style != ST_FULLKANA ? "\"" : "",
                       ">", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_shtml_passwd_conv(CTR_CLASS_NAME(passwd) *pwd,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    return ctr_cvputvs(cvinfo,
                       "<input type=\"password\"",
                       pwd->name ? " name=\"" : "",
                       pwd->name ? ctr_html_escape(pwd->name, pool) : "",
                       pwd->name ? "\"" : "",
                       pwd->val ? " value=\"" : "",
                       pwd->val ? ctr_html_escape(pwd->val, pool) : "",
                       pwd->val ? "\"" : "",
                       pwd->size >= 0 ? " size=\"" : "",
                       pwd->size >= 0 ? apr_itoa(pool, pwd->size) : "",
                       pwd->size >= 0 ? "\"" : "",
                       pwd->max >= 0 ? " maxlength=\"" : "",
                       pwd->max >= 0 ? apr_itoa(pool, pwd->max) : "",
                       pwd->max >= 0 ? "\"" : "",
                       " mode=\"numeric\" istyle=\"4\">", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_shtml_hidden_conv(CTR_CLASS_NAME(hidden) *hdn,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;

    if (dat->curfrm->act != NULL &&
        ctr_startwith(dat->curfrm->act->path, "mailto:") &&
        hdn->name != NULL &&
        strcasecmp(hdn->name, "body") == 0)
        hdn->name = "mailbody";

    return ctr_html_hidden_conv(hdn, vt, udata);
}

/* ARGSUSED */
static apr_status_t ctr_shtml_submit_conv(CTR_CLASS_NAME(submit) *smt,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    apr_status_t rv;

    if (dat->curfrm->act != NULL &&
        ctr_startwith(dat->curfrm->act->path, "tel:")) {
        /*
         * It seems that SoftBank's browsers don't support
         * <form action="tel:xxx">, so change the submit button into an anchor.
         */
        CTR_HTML_BR_OFF(cvinfo);
        return ctr_cvputvs(cvinfo,
                           "<a href=\"", dat->curfrm->act->path, "\">",
                           smt->val ? ctr_html_escape(smt->val, pool) : "",
                           "</a>", NULL);
    }

    /*
     * This is for buggy Motorola browsers, sigh...
     */
    if (cvinfo->isxml) {
        if ((rv = ctr_cvputs(cvinfo, "<span style=\"color:#000000;\">"))
            != APR_SUCCESS)
            return rv;
    }

    if ((rv = ctr_html_submit_conv(smt, vt, udata)) != APR_SUCCESS)
        return rv;

    if (cvinfo->isxml)
        return ctr_cvputs(cvinfo, "</span>");

    return APR_SUCCESS;
}

/* ARGSUSED */
static apr_status_t ctr_shtml_emoji_conv(CTR_CLASS_NAME(emoji) *emj,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_emoji_info_t *info;

    if (cvinfo->emojihash == NULL)
        return APR_SUCCESS;

    info = CTR_HASH_GET(cvinfo->emojihash, emj->id);
    if (info == NULL || info->shtml == NULL) {
        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, cvinfo->r,
                      "mod_cthree: failed to get a shtml emoji (%s) "
                      "from emoji xml, cannot display", emj->id);
        return APR_SUCCESS;
    }

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    if (*info->shtml == '\0' || *info->shtml != '$')
        return ctr_cvputs(cvinfo, info->shtml);
    else
        return ctr_cvprintf(cvinfo, "%c%s%c", 0x1b, info->shtml, 0x0f);
}

static apr_status_t ctr_sxhtml_page_conv(CTR_CLASS_NAME(page) *page,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat;
    char *fmt;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    /*
     * Initialize html specific data.
     */
    dat = apr_palloc(pool, sizeof(*dat));
    dat->brout = TRUE;
    dat->last_is_space = TRUE;
    dat->decoinfos = apr_array_make(pool, 1, sizeof(ctr_decoinfo_t));
    dat->curfrm = NULL;
    cvinfo->udata = dat;

    if ((rv = ctr_cvputvs(cvinfo,
                          "<?xml version=\"1.0\" encoding=\"Shift_JIS\"?>\n"
                          "<!DOCTYPE html PUBLIC "
                          "\"-//J-PHONE//DTD XHTML Basic 1.0 Plus//EN\" "
                          "\"xhtml-basic10-plus.dtd\">\n"
                          "<html>\n"
                          "<head>\n"
                          "<title>",
                          ctr_html_escape(page->title, pool),
                          "</title>\n"
                          "</head>\n", NULL)) != APR_SUCCESS)
        return rv;
    fmt = apr_pstrcat(pool,
                      "<body",
                      page->bgcolor != -1 ? " bgcolor=\"#%06x\"" : "%s",
                      page->txtcolor != -1 || page->txtcolor2 != -1 ?
                      " text=\"#%06x\"" : "%s",
                      page->lnkcolor != -1 ? " link=\"#%06x\"" : "%s",
                      ">\n"
                      "<div>\n", NULL);
    if ((rv = ctr_cvprintf(cvinfo, fmt,
                           page->bgcolor != -1 ?
                           (intptr_t)page->bgcolor : (intptr_t)"",
                           page->txtcolor != -1 ? (intptr_t)page->txtcolor :
                           page->txtcolor2 != -1 ?
                           (intptr_t)page->txtcolor2 : (intptr_t)"",
                           page->lnkcolor != -1 ?
                           (intptr_t)page->lnkcolor : (intptr_t)""))
        != APR_SUCCESS)
        return rv;

    CTR_FOREACH_CHILD(page, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    return ctr_cvputvs(cvinfo,
                       !dat->brout ? "\n" : "",
                       "</div>\n"
                       "</body>\n"
                       "</html>\n", NULL);
}

static apr_status_t ctr_sxhtml_anchor_conv(CTR_CLASS_NAME(anchor) *anc,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    static const char *akeys[] = {
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "#"
    };
    char *href;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HTML_DECO_OUT(cvinfo);

    if (anc->href != NULL)
        href = apr_uri_unparse(pool, anc->href, 0);
    else
        href = NULL;

    /*
     * Ignore utn attribute.
     */

    /*
     * It seems that SoftBank's browsers have a bug that does not treat subject
     * and body attributes correctly when the two attributes are joined by
     * "&amp;".  So we join them by "&" although that breaks well-formed XML.
     */

    if ((rv = ctr_cvputvs(cvinfo,
                          "<a",
                          href ? " href=\"" : "",
                          href ? href : "",
                          href ? "\"" : "",
                          anc->akey != AC_NONE ? " accesskey=\"" : "",
                          anc->akey != AC_NONE ? akeys[anc->akey] : "",
                          anc->akey != AC_NONE ? "\"" : "",
                          ">", NULL)) != APR_SUCCESS)
        return rv;

    CTR_FOREACH_CHILD(anc, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    return ctr_cvputs(cvinfo, "</a>");
}

/* ARGSUSED */
static apr_status_t ctr_sxhtml_hrline_conv(CTR_CLASS_NAME(hrline) *hr,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    char *fmt;
    apr_status_t rv;

    CTR_HTML_DECO_CLOSE(cvinfo);

    if ((rv = ctr_cvputvs(cvinfo,
                          !dat->brout ? "\n" : "",
                          "<hr",
                          hr->align == AL_LEFT ? " align=\"left\"" :
                          hr->align == AL_RIGHT ? " align=\"right\"" : "",
                          hr->size != -1 ? " size=\"" : "",
                          hr->size != -1 ? apr_itoa(pool, hr->size) : "",
                          hr->size != -1 ? "\"" : "",
                          hr->width ? " width=\"" : "",
                          hr->width ? hr->width : "",
                          hr->width ? "\"" : "", NULL)) != APR_SUCCESS)
        return rv;
    fmt = apr_pstrcat(pool,
                      hr->color != -1 ?
                      " color=\"#%06x\" style=\"border-color:#%06x;\"" :
                      "%s%s",
                      " />\n", NULL);

    CTR_HTML_BR_ON(cvinfo);

    return ctr_cvprintf(cvinfo, fmt,
                        hr->color != -1 ? (intptr_t)hr->color : (intptr_t)"",
                        hr->color != -1 ? (intptr_t)hr->color : (intptr_t)"");
}

/* ARGSUSED */
static apr_status_t ctr_sxhtml_editbox_conv(CTR_CLASS_NAME(editbox) *ebox,
                                            const ctr_wcomp_visitor_t *vt,
                                            void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    static const char *modes[] = {
        "", "hankakukana", "alphabet", "numeric"
    };
    apr_pool_t *pool = cvinfo->r->pool;

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    return ctr_cvputvs(cvinfo,
                       "<input type=\"text\"",
                       ebox->name ? " name=\"" : "",
                       ebox->name ? ctr_html_escape(ebox->name, pool) : "",
                       ebox->name ? "\"" : "",
                       ebox->val ? " value=\"" : "",
                       ebox->val ? ctr_html_escape(ebox->val, pool) : "",
                       ebox->val ? "\"" : "",
                       ebox->size >= 0 ? " size=\"" : "",
                       ebox->size >= 0 ? apr_itoa(pool, ebox->size) : "",
                       ebox->size >= 0 ? "\"" : "",
                       ebox->max >= 0 ? " maxlength=\"" : "",
                       ebox->max >= 0 ? apr_itoa(pool, ebox->max) : "",
                       ebox->max >= 0 ? "\"" : "",
                       ebox->style != ST_FULLKANA ? " mode=\"" : "",
                       ebox->style != ST_FULLKANA ?
                       modes[ebox->style - 1] : "",
                       ebox->style != ST_FULLKANA ? "\"" : "",
                       ebox->style != ST_FULLKANA ? " istyle=\"" : "",
                       ebox->style != ST_FULLKANA ?
                       apr_itoa(pool, ebox->style) : "",
                       ebox->style != ST_FULLKANA ? "\"" : "",
                       " style=\"color:#000000;\" />", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_sxhtml_passwd_conv(CTR_CLASS_NAME(passwd) *pwd,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    return ctr_cvputvs(cvinfo,
                       "<input type=\"password\"",
                       pwd->name ? " name=\"" : "",
                       pwd->name ? ctr_html_escape(pwd->name, pool) : "",
                       pwd->name ? "\"" : "",
                       pwd->val ? " value=\"" : "",
                       pwd->val ? ctr_html_escape(pwd->val, pool) : "",
                       pwd->val ? "\"" : "",
                       pwd->size >= 0 ? " size=\"" : "",
                       pwd->size >= 0 ? apr_itoa(pool, pwd->size) : "",
                       pwd->size >= 0 ? "\"" : "",
                       pwd->max >= 0 ? " maxlength=\"" : "",
                       pwd->max >= 0 ? apr_itoa(pool, pwd->max) : "",
                       pwd->max >= 0 ? "\"" : "",
                       " mode=\"numeric\" istyle=\"4\""
                       " style=\"color:#000000;\" />", NULL);
}

static apr_status_t ctr_hdmllo_out(ctr_hdml_lo_t lo, ctr_conv_info_t *cvinfo)
{
    if (lo == LO_CENTER)
        return ctr_cvputs(cvinfo, "<center>");
    else if (lo == LO_RIGHT)
        return ctr_cvputs(cvinfo, "<right>");
    return APR_SUCCESS;
}

static int ctr_hdml_charwidth_get(ctr_conv_info_t *cvinfo)
{
    int cw;

    cw = cvinfo->brw->charwidth;
    if (cw <= 0 || cw >= 100) {
        /* protect against client's attack */
        cw = 20;
    }

    return cw;
}

static apr_status_t ctr_hdml_indent_out(ctr_conv_info_t *cvinfo, int adjust)
{
    ctr_hdml_dat_t *dat = CTR_HDML_DAT(cvinfo);
    ctr_hdml_stat_t *stat = CTR_HDML_STAT_LAST(cvinfo);
    int i;
    apr_status_t rv;

    CTR_HDML_BR_OUT(cvinfo);
    CTR_HDML_LO_OUT(cvinfo);
    CTR_HDML_BR_OFF(cvinfo);

    for (i = dat->indent + adjust; i > 0;) {
        if (i >= 2) {
            if ((rv = ctr_cvputs(cvinfo,
                                 (char *)CTR_FULLSPC_MSG)) != APR_SUCCESS)
                return rv;
            i -= 2;
            stat->charcnt += 2;
        } else {
            if ((rv = ctr_cvputs(cvinfo, "&nbsp;")) != APR_SUCCESS)
                return rv;
            i -= 1;
            stat->charcnt++;
        }
    }

    return APR_SUCCESS;
}

static apr_status_t ctr_hdml_adj_cvputs(ctr_conv_info_t *cvinfo,
                                        const char *msg, int istext)
{
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_hdml_dat_t *dat = CTR_HDML_DAT(cvinfo);
    ctr_hdml_stat_t *stat = CTR_HDML_STAT_LAST(cvinfo);
    int cw;
    const unsigned char *p;
    int bytes;
    char *s;
    apr_status_t rv;

    if (!stat->adjusted) {
        if (istext)
            return ctr_cvputs(cvinfo, ctr_hdml_text_escape(msg, pool));
        else
            return ctr_cvputs(cvinfo, ctr_hdml_escape(msg, pool));
    }

    cw = ctr_hdml_charwidth_get(cvinfo);

    p = (unsigned char *)msg;
    while (*p) {
        if (((0x81 <= *p && *p <= 0x9f) || (0xe0 <= *p && *p <= 0xfc)) &&
            ((0x40 <= *(p + 1) && *(p + 1) <= 0x7e) ||
             (0x80 <= *(p + 1) && *(p + 1) <= 0xfc))) {
            /* 2 bytes character */
            bytes = 2;
        } else {
            /* 1 byte character */
            bytes = 1;

            if (dat->indent == stat->charcnt && apr_isspace(*p)) {
                /*
                 * Ignore white spaces at the top of line.
                 */
                p++;
                continue;
            }
        }

        if (cw - stat->charcnt < bytes) {
            if (bytes == 1 && apr_isspace(*p)) {
                /*
                 * Ignore white spaces at the top of line.
                 */
                p++;
                continue;
            }

            if ((rv = ctr_hdml_indent_out(cvinfo, 0)) != APR_SUCCESS)
                return rv;
        }

        s = apr_pstrndup(pool, (char *)p, bytes);
        if (istext) {
            if ((rv = ctr_cvputs(cvinfo, ctr_hdml_text_escape(s, pool)))
                != APR_SUCCESS)
                return rv;
        } else {
            if ((rv = ctr_cvputs(cvinfo, ctr_hdml_escape(s, pool)))
                != APR_SUCCESS)
                return rv;
        }
        stat->charcnt += bytes;

        p += bytes;
    }

    return APR_SUCCESS;
}

static apr_status_t ctr_hdml_anc_open_adjust(ctr_conv_info_t *cvinfo)
{
    ctr_hdml_stat_t *stat = CTR_HDML_STAT_LAST(cvinfo);
    int cw;
    apr_status_t rv;

    if (stat->adjusted) {
        cw = ctr_hdml_charwidth_get(cvinfo);
        if (cw - stat->charcnt < 1 || stat->anc_in_line) {
            if ((rv = ctr_hdml_indent_out(cvinfo, 0)) != APR_SUCCESS)
                return rv;
        }

        stat->charcnt++;
        stat->anc_in_line = TRUE;
    }

    return APR_SUCCESS;
}

static apr_status_t ctr_hdml_anc_close_adjust(ctr_conv_info_t *cvinfo)
{
    ctr_hdml_stat_t *stat = CTR_HDML_STAT_LAST(cvinfo);
    int cw;
    apr_status_t rv;

    if (stat->adjusted) {
        cw = ctr_hdml_charwidth_get(cvinfo);
        if (cw - stat->charcnt < 1) {
            if ((rv = ctr_hdml_indent_out(cvinfo, 0)) != APR_SUCCESS)
                return rv;
        }

        stat->charcnt++;
    }

    return APR_SUCCESS;
}

static ctr_hdml_edat_t *ctr_hdml_edat_add(ctr_conv_info_t *cvinfo,
                                          const char *fmt, int echo)
{
    ctr_hdml_dat_t *dat = CTR_HDML_DAT(cvinfo);
    ctr_hdml_edat_t *edat;

    /*
     * Insert a new entry data object,
     */
    edat = apr_array_push(dat->edat);
    edat->no = dat->cardcnt++;
    edat->fmt = fmt;
    edat->echo = echo;

    return edat;
}

static int ctr_table_join_cb(void *rec, const char *key, const char *val)
{
    ctr_table_join_dat_t *dat = rec;

    if (val == NULL)
        val = "";
    if (dat->s != NULL) {
        dat->s = apr_pstrcat(dat->pool, dat->s, dat->elmsep, key, dat->valsep,
                             val, NULL);
    } else
        dat->s = apr_pstrcat(dat->pool, key, dat->valsep, val, NULL);

    return TRUE;
}

static apr_status_t ctr_hdml_page_conv(CTR_CLASS_NAME(page) *page,
                                       const ctr_wcomp_visitor_t *vt,
                                       void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    const request_rec *r = cvinfo->r;
    apr_pool_t *pool = r->pool;
    ctr_hdml_dat_t *dat;
    const char *p;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    ctr_table_join_dat_t jdat;
    ctr_radio_info_t *rinfo;
    int i;
    apr_status_t rv;

    /*
     * Initialize hdml specific data.
     */
    dat = apr_palloc(pool, sizeof(*dat));
    dat->indent = 0;
    dat->init = apr_table_make(pool, 1);
    dat->stats = apr_array_make(pool, 1, sizeof(ctr_hdml_stat_t));
    dat->fdat = apr_array_make(pool, 1, sizeof(ctr_hdml_fdat_t));
    dat->edat = apr_array_make(pool, 1, sizeof(ctr_hdml_edat_t));
    dat->cdat = apr_array_make(pool, 1, sizeof(ctr_hdml_cdat_t));
    dat->rinfos = NULL;
    dat->last_rinfo = NULL;
    dat->chkon_no = -1;
    dat->chkoff_no = -1;
    dat->valcnt = 0;
    dat->cardcnt = 3;
    cvinfo->udata = dat;

    /*
     * Print deck and main card (display card).
     */
    CTR_HDML_STAT_PUSH(cvinfo, LO_NONE, 0, TRUE);
    if (r->path_info != NULL) {
        p = apr_pstrcat(pool, r->uri, r->path_info,
                        r->args != NULL ? "?" : NULL, r->args, NULL);
    } else {
        p = apr_pstrcat(pool, r->uri,
                        r->args != NULL ? "?" : NULL, r->args, NULL);
    }
    if ((rv = ctr_cvputvs(cvinfo,
                          "<hdml version=3.0 ttl=0 markable=true>\n"
                          "<nodisplay name=c0>\n"
                          "<action type=accept task=go dest=#c2>\n"
                          "</nodisplay>\n"
                          "<display name=c1 title=\"",
                          ctr_hdml_text_escape(page->title, pool),
                          "\" bookmark=", p, ">\n"
                          "<action type=accept task=noop label=\" \">\n",
                          NULL)) != APR_SUCCESS)
        return rv;
    CTR_FOREACH_CHILD(page, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }
    if ((rv = ctr_cvputvs(cvinfo,
                          *(cvinfo->buf + cvinfo->buflen - 1) != '\n' ?
                          "\n" : "",
                          "</display>\n", NULL)) != APR_SUCCESS)
        return rv;

    jdat.pool = pool;
    jdat.elmsep = "&";
    jdat.valsep = "=";

    /*
     * Print init card (nodisplay card).
     */
    jdat.s = NULL;
    apr_table_do(ctr_table_join_cb, &jdat, dat->init, NULL);
    if ((rv = ctr_cvputvs(cvinfo,
                          "<nodisplay name=c2>\n"
                          "<action type=accept task=go dest=#c1",
                          jdat.s ? " vars=\"" : "",
                          jdat.s ? jdat.s : "",
                          jdat.s ? "\"" : "",
                          ">\n"
                          "</nodisplay>\n", NULL)) != APR_SUCCESS)
        return rv;

    /*
     * Print form cards (nodisplay cards).
     */
    for (i = 0; i < dat->fdat->nelts; i++) {
        ctr_hdml_fdat_t *fdat;

        fdat = &((ctr_hdml_fdat_t *)dat->fdat->elts)[i];
        if (fdat->act != NULL) {
            if (!ctr_startwith(fdat->act->path, "tel:") &&
                !ctr_startwith(fdat->act->path, "mailto:")) {
                jdat.s = NULL;
                apr_table_do(ctr_table_join_cb, &jdat, fdat->tbl, NULL);
                if ((rv = ctr_cvputvs(cvinfo,
                                      "<nodisplay name=c",
                                      apr_itoa(pool, fdat->no), ">\n"
                                      "<action type=accept task=go dest=\"",
                                      apr_uri_unparse(pool, fdat->act, 0),
                                      fdat->mtd == FM_POST ? "\" postdata=\"" :
                                      fdat->act->query ? "&" : "?",
                                      CTR_UNIQ_PARAM_PREFIX "=",
                                      ctr_uniqstr_get(pool),
                                      "&" CTR_SUBMIT_PARAM_PREFIX "$n=$v",
                                      jdat.s ? "&" :  "",
                                      jdat.s ? jdat.s :  "", "\">\n"
                                      "</nodisplay>\n", NULL)) != APR_SUCCESS)
                    return rv;
            }
        } else {
            if ((rv = ctr_cvputvs(cvinfo,
                                  "<nodisplay name=c",
                                  apr_itoa(pool, fdat->no), ">\n"
                                  "<action type=accept task=go dest=#c1>\n"
                                  "</nodisplay>\n", NULL)) != APR_SUCCESS)
                return rv;
        }
    }

    /*
     * Print entry cards.
     */
    for (i = 0; i < dat->edat->nelts; i++) {
        ctr_hdml_edat_t *edat;

        edat = &((ctr_hdml_edat_t *)dat->edat->elts)[i];
        if ((rv = ctr_cvputvs(cvinfo,
                              "<entry name=c",
                              apr_itoa(pool, edat->no),
                              " key=v default=$v",
                              edat->fmt ? " format=" : "",
                              edat->fmt ? edat->fmt : "",
                              edat->echo ? "" : " noecho=true",
                              " markable=false>\n"
                              "<action type=accept task=return retvals=$v>\n"
                              "</entry>\n", NULL)) != APR_SUCCESS)
            return rv;
    }

    /*
     * Print choice cards.
     */
    if (!cvinfo->exp_subcards) {
        for (i = 0; i < dat->cdat->nelts; i++) {
            ctr_hdml_cdat_t *cdat;
            int j;

            cdat = &((ctr_hdml_cdat_t *)dat->cdat->elts)[i];
            if ((rv = ctr_cvputvs(cvinfo,
                                  "<choice name=c", apr_itoa(pool, cdat->no),
                                  " key=v default=$v method=alpha"
                                  " markable=false>\n", NULL)) != APR_SUCCESS)
                return rv;
            for (j = 0; j < cdat->elms->nelts; j++) {
                ctr_hdml_celm_t *celm;

                celm = &((ctr_hdml_celm_t *)cdat->elms->elts)[j];
                if ((rv = ctr_cvputvs(cvinfo,
                                      "<ce task=return"
                                      " value=\"", celm->val, "\""
                                      " retvals=\"$v;", celm->rettxt, "\">",
                                      celm->txt, "\n", NULL)) != APR_SUCCESS)
                    return rv;
            }
            if ((rv = ctr_cvputs(cvinfo, "</choice>\n")) != APR_SUCCESS)
                return rv;
        }
    }

    /*
     * Print check on and off cards (nodisplay cards)
     */
    if (dat->chkon_no != -1) {
        if ((rv = ctr_cvputvs(cvinfo,
                              "<nodisplay name=c",
                              apr_itoa(pool, dat->chkon_no), ">\n"
                              "<action type=accept task=return "
                              "retvals=c", apr_itoa(pool, dat->chkoff_no),
                              ";X;$v>\n"
                              "</nodisplay>\n", NULL)) != APR_SUCCESS)
            return rv;
    }
    if (dat->chkoff_no != -1) {
        if ((rv = ctr_cvputvs(cvinfo,
                              "<nodisplay name=c",
                              apr_itoa(pool, dat->chkoff_no), ">\n"
                              "<action type=accept task=return "
                              "retvals=c", apr_itoa(pool, dat->chkon_no),
                              ";_;>\n"
                              "</nodisplay>\n", NULL)) != APR_SUCCESS)
            return rv;
    }

    /*
     * Print radio change cards (nodisplay cards).
     */
    if (!cvinfo->exp_subcards) {
        for (rinfo = dat->rinfos; rinfo != NULL; rinfo = rinfo->siblings) {
            int i;

            p = apr_pstrdup(pool, "$v");
            for (i = 0; i < rinfo->cnt; i++) {
                if (i == 0)
                    p = apr_pstrcat(pool, p, ";*", NULL);
                else
                    p = apr_pstrcat(pool, p, ";_", NULL);
            }
            if ((rv = ctr_cvputvs(cvinfo,
                                  "<nodisplay name=c",
                                  apr_itoa(pool, rinfo->cardno), ">\n"
                                  "<action type=accept task=return "
                                  "retvals=", p, ">\n"
                                  "</nodisplay>\n", NULL)) != APR_SUCCESS)
                return rv;
        }
    }

    return ctr_cvputs(cvinfo, "</hdml>\n");
}

static apr_status_t ctr_hdml_block_conv(CTR_CLASS_NAME(block) *blk,
                                        const ctr_wcomp_visitor_t *vt,
                                        void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_hdml_stat_t *stat = CTR_HDML_STAT_LAST(cvinfo);
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HDML_BR_OUT(cvinfo);

    if (blk->align != AL_NONE) {
        CTR_HDML_STAT_PUSH(cvinfo,
                           blk->align == AL_LEFT ? LO_NONE :
                           blk->align == AL_CENTER ? LO_CENTER : LO_RIGHT,
                           stat->licnt, FALSE);
    }

    if (blk->marquee && blk->mqebehav == BH_SLIDE) {
        /*
         * Put children at the best position according to direct attribute if
         * the value of behavior attribute is "slide".
         */
        CTR_HDML_STAT_PUSH(cvinfo,
                           blk->mqedirect == DL_LEFT ? LO_NONE : LO_RIGHT,
                           stat->licnt, FALSE);
    }

    CTR_FOREACH_CHILD(blk, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HDML_BR_OUT(cvinfo);

    if (blk->marquee && blk->mqebehav == BH_SLIDE)
        CTR_HDML_STAT_POP(cvinfo);

    if (blk->align != AL_NONE)
        CTR_HDML_STAT_POP(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_hdml_form_conv(CTR_CLASS_NAME(form) *frm,
                                       const ctr_wcomp_visitor_t *vt,
                                       void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_hdml_dat_t *dat = CTR_HDML_DAT(cvinfo);
    ctr_hdml_fdat_t *fdat;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    /*
     * Insert a new form data object.
     */
    fdat = apr_array_push(dat->fdat);
    fdat->no = dat->cardcnt++;
    fdat->act = frm->act;
    fdat->mtd = frm->mtd;
    fdat->tbl = apr_table_make(pool, 1);

    CTR_HDML_BR_OUT(cvinfo);

    if (frm->align != AL_NONE) {
        CTR_HDML_STAT_PUSH(cvinfo,
                           frm->align == AL_LEFT ? LO_NONE :
                           frm->align == AL_CENTER ? LO_CENTER : LO_RIGHT,
                           CTR_HDML_STAT_LAST(cvinfo)->licnt, FALSE);
    }

    CTR_FOREACH_CHILD(frm, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HDML_BR_OUT(cvinfo);

    if (frm->align != AL_NONE)
        CTR_HDML_STAT_POP(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_hdml_heading_conv(CTR_CLASS_NAME(heading) *hdg,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HDML_BR_OUT(cvinfo);

    if (hdg->align != AL_NONE) {
        CTR_HDML_STAT_PUSH(cvinfo,
                           hdg->align == AL_LEFT ? LO_NONE :
                           hdg->align == AL_CENTER ? LO_CENTER : LO_RIGHT,
                           CTR_HDML_STAT_LAST(cvinfo)->licnt, FALSE);
    }

    CTR_FOREACH_CHILD(hdg, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HDML_BR_OUT(cvinfo);

    if (hdg->align != AL_NONE)
        CTR_HDML_STAT_POP(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_hdml_list_conv(CTR_CLASS_NAME(list) *lst,
                                       const ctr_wcomp_visitor_t *vt,
                                       void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_hdml_dat_t *dat = CTR_HDML_DAT(cvinfo);
    ctr_hdml_stat_t *stat = CTR_HDML_STAT_LAST(cvinfo);
    int indentadd;
    int cw;
    int indented;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HDML_BR_OUT(cvinfo);

    if (lst->ordered)
        indentadd = 4;
    else
        indentadd = 2;

    cw = ctr_hdml_charwidth_get(cvinfo);
    if (cw - (dat->indent + indentadd) < 2)
        indented = FALSE;
    else {
        dat->indent += indentadd;
        indented = TRUE;
    }

    if (lst->ordered)
        CTR_HDML_STAT_PUSH(cvinfo, stat->lo, 1, FALSE);
    else
        CTR_HDML_STAT_PUSH(cvinfo, stat->lo, 0, FALSE);

    CTR_FOREACH_CHILD(lst, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HDML_BR_OUT(cvinfo);

    CTR_HDML_STAT_POP(cvinfo);

    if (indented)
        dat->indent -= indentadd;

    return APR_SUCCESS;
}

static apr_status_t ctr_hdml_listitem_conv(CTR_CLASS_NAME(listitem) *li,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_hdml_stat_t *stat = CTR_HDML_STAT_LAST(cvinfo);
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    if (stat->licnt > 0) {
        char *s;

        if ((rv = ctr_hdml_indent_out(cvinfo, stat->licnt >= 10 ? -4 : -3))
            != APR_SUCCESS)
            return rv;
        s = apr_psprintf(pool, "%d", stat->licnt);
        if (stat->licnt >= 10)
            s += strlen(s) - 2;
        if ((rv = ctr_cvputvs(cvinfo, s,
                              CTR_FULLDOT_MSG, NULL)) != APR_SUCCESS)
            return rv;
        stat->charcnt += strlen(s) + 2;
        stat->licnt++;
    } else {
        if ((rv = ctr_hdml_indent_out(cvinfo, -1)) != APR_SUCCESS)
            return rv;
        if ((rv = ctr_cvputs(cvinfo,
                             (const char *)CTR_POINT_MSG)) != APR_SUCCESS)
            return rv;
        stat->charcnt++;
    }

    CTR_FOREACH_CHILD(li, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HDML_BR_OUT(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_hdml_deflist_conv(CTR_CLASS_NAME(deflist) *dlt,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HDML_BR_OUT(cvinfo);

    CTR_FOREACH_CHILD(dlt, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HDML_BR_OUT(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_hdml_defterm_conv(CTR_CLASS_NAME(defterm) *dtm,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    if ((rv = ctr_hdml_indent_out(cvinfo, 0)) != APR_SUCCESS)
        return rv;

    CTR_FOREACH_CHILD(dtm, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HDML_BR_OUT(cvinfo);

    return APR_SUCCESS;
}

static apr_status_t ctr_hdml_defdesc_conv(CTR_CLASS_NAME(defdesc) *ddc,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_hdml_dat_t *dat = CTR_HDML_DAT(cvinfo);
    int cw;
    int indented;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    cw = ctr_hdml_charwidth_get(cvinfo);
    if (cw - (dat->indent + 2) < 2)
        indented = FALSE;
    else {
        dat->indent += 2;
        indented = TRUE;
    }

    if ((rv = ctr_hdml_indent_out(cvinfo, 0)) != APR_SUCCESS)
        return rv;

    CTR_FOREACH_CHILD(ddc, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    CTR_HDML_BR_OUT(cvinfo);

    if (indented)
        dat->indent -= 2;

    return APR_SUCCESS;
}

static apr_status_t ctr_hdml_deco_conv(CTR_CLASS_NAME(deco) *dec,
                                       const ctr_wcomp_visitor_t *vt,
                                       void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    /*
     * Ignore all attributes.
     */

    CTR_FOREACH_CHILD(dec, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    return APR_SUCCESS;
}

static apr_status_t ctr_hdml_anchor_conv(CTR_CLASS_NAME(anchor) *anc,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    static const char *akeys[] = {
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "0"
    };
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    /*
     * Ignore utn attribute and mail subject and mail body.
     */

    if (anc->href != NULL) {
        CTR_HDML_LO_OUT(cvinfo);
        CTR_HDML_BR_OFF(cvinfo);

        if ((rv = ctr_hdml_anc_open_adjust(cvinfo)) != APR_SUCCESS)
            return rv;

        if (ctr_startwith(anc->href->path, "tel:")) {
            if ((rv = ctr_cvputvs(cvinfo,
                                  "<a task=call label=",
                                  CTR_TEL_MSG,
                                  " number=\"", anc->href->path + 4, "\"",
                                  AC_ONE <= anc->akey && anc->akey <= AC_NINE ?
                                  " accesskey=" : "",
                                  AC_ONE <= anc->akey && anc->akey <= AC_NINE ?
                                  akeys[anc->akey] : "",
                                  ">" , NULL)) != APR_SUCCESS)
                return rv;
        } else if (ctr_startwith(anc->href->path, "mailto:")) {
            if ((rv = ctr_cvputvs(cvinfo,
                                  "<a task=go label=",
                                  CTR_MAIL_MSG,
                                  " dest=\"", anc->href->path, "\"",
                                  AC_ONE <= anc->akey && anc->akey <= AC_NINE ?
                                  " accesskey=" : "",
                                  AC_ONE <= anc->akey && anc->akey <= AC_NINE ?
                                  akeys[anc->akey] : "",
                                  ">" , NULL)) != APR_SUCCESS)
                return rv;
        } else {
            if ((rv = ctr_cvputvs(cvinfo,
                                  "<a task=go label=",
                                  CTR_GO_MSG,
                                  " dest=\"",
                                  apr_uri_unparse(pool, anc->href, 0),
                                  anc->href->query != NULL ? "&" : "?",
                                  CTR_UNIQ_PARAM_PREFIX "=",
                                  ctr_uniqstr_get(pool), "\"",
                                  AC_ONE <= anc->akey && anc->akey <= AC_NINE ?
                                  " accesskey=" : "",
                                  AC_ONE <= anc->akey && anc->akey <= AC_NINE ?
                                  akeys[anc->akey] : "",
                                  ">" , NULL)) != APR_SUCCESS)
                return rv;
        }
    }

    CTR_FOREACH_CHILD(anc, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    if (anc->href != NULL) {
        CTR_HDML_LO_OUT(cvinfo);
        CTR_HDML_BR_OFF(cvinfo);

        if ((rv = ctr_hdml_anc_close_adjust(cvinfo)) != APR_SUCCESS)
            return rv;

        return ctr_cvputs(cvinfo, "</a>");
    } else
        return APR_SUCCESS;
}

static apr_status_t ctr_hdml_select_conv(CTR_CLASS_NAME(select) *sel,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_hdml_dat_t *dat = CTR_HDML_DAT(cvinfo);
    ctr_hdml_cdat_t *cdat;
    char *valvname;
    char *txtvname;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    CTR_HDML_STAT_LAST(cvinfo)->adjusted = FALSE;

    /*
     * Initialize a choice data object, and insert it at the tail of list.
     */
    cdat = apr_array_push(dat->cdat);
    if (!cvinfo->exp_subcards)
        cdat->no = dat->cardcnt++;
    else
        cdat->no = 0;
    cdat->elms = apr_array_make(pool, 1, sizeof(ctr_hdml_celm_t));
    cdat->valvno = dat->valcnt++;
    cdat->txtvno = dat->valcnt++;
    cdat->selstat = SS_FIRST;
    cdat->selval = NULL;
    cdat->seltxt = NULL;

    valvname = apr_psprintf(pool, "v%d", cdat->valvno);
    txtvname = apr_psprintf(pool, "v%d", cdat->txtvno);

    if (sel->name != NULL) {
        char *name = ctr_uri_escape(sel->name, pool);
        char *dvname = apr_pstrcat(pool, "$", valvname, NULL);
        CTR_HDML_FDAT_DATA_ADD(cvinfo, name, dvname);
    }

    CTR_HDML_LO_OUT(cvinfo);
    CTR_FOREACH_CHILD(sel, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    if (cdat->selval != NULL) {
        /*
         * If cdat->selval is not NULL, cdat->seltxt is not too. See
         * ctr_hdml_option_conv() function.
         */
        char *vval;

        vval = ctr_hdml_vars_escape(cdat->selval, pool);
        CTR_HDML_INIT_ADD(cvinfo, valvname, vval);

        vval = ctr_hdml_vars_escape(cdat->seltxt, pool);
        CTR_HDML_INIT_ADD(cvinfo, txtvname, vval);
    }

    CTR_HDML_BR_OFF(cvinfo);
    if (!cvinfo->exp_subcards) {
        return ctr_cvputvs(cvinfo,
                           "<a task=gosub label=", CTR_SEL_MSG,
                           " dest=#c", apr_itoa(pool, cdat->no),
                           " vars=\"v=$", valvname, "\""
                           " receive=", valvname, ";", txtvname, ">$",
                           txtvname, "</a>", NULL);
    } else {
        char *s = NULL;
        int i;

        for (i = 0; i < cdat->elms->nelts; i++) {
            ctr_hdml_celm_t *celm;
            char *q;

            celm = &((ctr_hdml_celm_t *)cdat->elms->elts)[i];

            q = apr_pstrcat(pool, celm->val, "=", celm->txt, NULL);
            if (s == NULL)
                s = q;
            else
                s = apr_pstrcat(pool, s, "&", q, NULL);
        }
        return ctr_cvputvs(cvinfo,
                           "<a task=gosub label=", CTR_SEL_MSG,
                           " dest=", CTR_CHOICE_EXPORT,
                           " postdata=\"", s, "\""
                           " vars=\"v=$", valvname, "\""
                           " receive=", valvname, ";", txtvname, ">$",
                           txtvname, "</a>", NULL);
    }
}

/* ARGSUSED */
static apr_status_t ctr_hdml_textarea_conv(CTR_CLASS_NAME(textarea) *txa,
                                           const ctr_wcomp_visitor_t *vt,
                                           void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    static const char *fmtc = "MMmN";
    apr_pool_t *pool = cvinfo->r->pool;
    char *fmt;
    ctr_hdml_edat_t *edat = NULL;
    char *vname;
    char *vval;

    CTR_HDML_STAT_LAST(cvinfo)->adjusted = FALSE;

    fmt = apr_psprintf(pool, "*%c", fmtc[txa->style - 1]);

    /*
     * If it is not needed to export hdml subcards, add an entry data object.
     */
    if (!cvinfo->exp_subcards)
        edat = ctr_hdml_edat_add(cvinfo, fmt, TRUE);

    vname = apr_psprintf(pool, "v%d", CTR_HDML_DAT(cvinfo)->valcnt++);
    if (txa->txt != NULL)
        vval = ctr_hdml_vars_escape(txa->txt, pool);
    else
        vval = NULL;

    CTR_HDML_INIT_ADD(cvinfo, vname, vval);
    if (txa->name != NULL) {
        char *name = ctr_uri_escape(txa->name, pool);
        char *dvname = apr_pstrcat(pool, "$", vname, NULL);
        CTR_HDML_FDAT_DATA_ADD(cvinfo, name, dvname);
    }

    CTR_HDML_LO_OUT(cvinfo);
    CTR_HDML_BR_OFF(cvinfo);
    if (!cvinfo->exp_subcards) {
        return ctr_cvputvs(cvinfo,
                           "<a task=gosub label=", CTR_INPUT_MSG,
                           " dest=#c", apr_itoa(pool, edat->no),
                           " vars=\"v=$", vname, "\""
                           " receive=", vname, ">[$", vname,
                           "]</a>", NULL);
    } else {
        return ctr_cvputvs(cvinfo,
                           "<a task=gosub label=", CTR_INPUT_MSG,
                           " dest=", CTR_ENTRY_EXPORT, "?fmt=", fmt,
                           " vars=\"v=$", vname, "\""
                           " receive=", vname, ">[$", vname,
                           "]</a>", NULL);
    }
}

/* ARGSUSED */
static apr_status_t ctr_hdml_hrline_conv(CTR_CLASS_NAME(hrline) *hr,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    int cw;
    int len;
    int i;
    apr_status_t rv;

    cw = cvinfo->brw->charwidth;
    if (cw > 0 && cw < 100)     /* protect against client's attack */
        len = cw - 1;
    else
        len = 20 - 1;   /* 20 is the shortest width */

    if (hr->width != NULL) {
        if (hr->width[strlen(hr->width) - 1] == '%') {
            char *s = apr_pstrndup(pool, hr->width, strlen(hr->width) - 1);
            len = len * (apr_atoi64(s) * 0.01);
        } else {
            int pw;

            pw = cvinfo->brw->pixwidth;
            if (pw > 0) {
                double per = (double)apr_atoi64(hr->width) / pw;
                if (per < 1.0)
                    len = len  * per;
            }
        }
    }

    CTR_HDML_BR_OUT(cvinfo);
    if (hr->align == AL_CENTER) {
        if ((rv = ctr_cvputs(cvinfo, "<center>")) != APR_SUCCESS)
            return rv;
    } else if (hr->align == AL_RIGHT) {
        if ((rv = ctr_cvputs(cvinfo, "<right>")) != APR_SUCCESS)
            return rv;
    }
    for (i = 0; i < len; i++) {
        if ((rv = ctr_cvputs(cvinfo, "-")) != APR_SUCCESS)
            return rv;
    }
    return ctr_cvputs(cvinfo, "<br>\n");
}

/* ARGSUSED */
static apr_status_t ctr_hdml_newline_conv(CTR_CLASS_NAME(newline) *nl,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_hdml_stat_t *stat = CTR_HDML_STAT_LAST(cvinfo);

    CTR_HDML_BR_ON(cvinfo);
    if (stat->top) {
        stat->top = FALSE;      /* XXX? */
        return ctr_cvputs(cvinfo, "&nbsp;<br>\n");
    } else
        return ctr_cvputs(cvinfo, "<br>\n");
}

/* ARGSUSED */
static apr_status_t ctr_hdml_image_conv(CTR_CLASS_NAME(image) *img,
                                        const ctr_wcomp_visitor_t *vt,
                                        void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    char *uniq;

    CTR_HDML_STAT_LAST(cvinfo)->adjusted = FALSE;

    ctr_imguri_conv(img, cvinfo->brw, pool);

    uniq = ctr_uniqstr_get(pool);
    if (img->src != NULL) {
        img->src->query = apr_pstrcat(pool,
                                      img->src->query ? img->src->query : "",
                                      img->src->query ? "&" : "",
                                      CTR_UNIQ_PARAM_PREFIX, "=", uniq, NULL);
    }

    CTR_HDML_LO_OUT(cvinfo);
    CTR_HDML_BR_OFF(cvinfo);
    return ctr_cvputvs(cvinfo,
                       "<img",
                       img->src ? " src=" : "",
                       img->src ? apr_uri_unparse(pool, img->src, 0) : "",
                       img->alt ? " alt=\"" : "",
                       img->alt ? ctr_hdml_text_escape(img->alt, pool) : "",
                       img->alt ? "\"" : "",
                       ">", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_hdml_editbox_conv(CTR_CLASS_NAME(editbox) *ebox,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    static const char *fmtc = "MMmN";
    apr_pool_t *pool = cvinfo->r->pool;
    char *fmt;
    ctr_hdml_edat_t *edat = NULL;
    char *vname;
    char *vval;

    CTR_HDML_STAT_LAST(cvinfo)->adjusted = FALSE;

    if (ebox->max > 0)
        fmt = apr_psprintf(pool, "%d%c", ebox->max, fmtc[ebox->style - 1]);
    else
        fmt = apr_psprintf(pool, "*%c", fmtc[ebox->style - 1]);

    /*
     * If it is not needed to export hdml subcards, add an entry data object.
     */
    if (!cvinfo->exp_subcards)
        edat = ctr_hdml_edat_add(cvinfo, fmt, TRUE);

    vname = apr_psprintf(pool, "v%d", CTR_HDML_DAT(cvinfo)->valcnt++);
    if (ebox->val != NULL)
        vval = ctr_hdml_vars_escape(ebox->val, pool);
    else
        vval = NULL;

    CTR_HDML_INIT_ADD(cvinfo, vname, vval);
    if (ebox->name != NULL) {
        char *name = ctr_uri_escape(ebox->name, pool);
        char *dvname = apr_pstrcat(pool, "$", vname, NULL);
        CTR_HDML_FDAT_DATA_ADD(cvinfo, name, dvname);
    }

    CTR_HDML_LO_OUT(cvinfo);
    CTR_HDML_BR_OFF(cvinfo);
    if (!cvinfo->exp_subcards) {
        return ctr_cvputvs(cvinfo,
                           "<a task=gosub label=", CTR_INPUT_MSG,
                           " dest=#c", apr_itoa(pool, edat->no),
                           " vars=\"v=$", vname, "\""
                           " receive=", vname, ">[$", vname,
                           "]</a>", NULL);
    } else {
        return ctr_cvputvs(cvinfo,
                           "<a task=gosub label=", CTR_INPUT_MSG,
                           " dest=", CTR_ENTRY_EXPORT, "?fmt=", fmt,
                           " vars=\"v=$", vname, "\""
                           " receive=", vname, ">[$", vname,
                           "]</a>", NULL);
    }
}

/* ARGSUSED */
static apr_status_t ctr_hdml_passwd_conv(CTR_CLASS_NAME(passwd) *pwd,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    char *fmt;
    ctr_hdml_edat_t *edat = NULL;
    char *vname;
    char *vval;

    CTR_HDML_STAT_LAST(cvinfo)->adjusted = FALSE;

    if (pwd->max > 0)
        fmt = apr_psprintf(pool, "%dN", pwd->max);
    else
        fmt = "*N";

    /*
     * If it is not needed to export hdml subcards, add an entry data object.
     */
    if (!cvinfo->exp_subcards)
        edat = ctr_hdml_edat_add(cvinfo, fmt, FALSE);

    vname = apr_psprintf(pool, "v%d", CTR_HDML_DAT(cvinfo)->valcnt++);
    if (pwd->val != NULL)
        vval = ctr_hdml_vars_escape(pwd->val, pool);
    else
        vval = NULL;

    CTR_HDML_INIT_ADD(cvinfo, vname, vval);
    if (pwd->name != NULL) {
        char *name = ctr_uri_escape(pwd->name, pool);
        char *dvname = apr_pstrcat(pool, "$", vname, NULL);
        CTR_HDML_FDAT_DATA_ADD(cvinfo, name, dvname);
    }

    CTR_HDML_LO_OUT(cvinfo);
    CTR_HDML_BR_OFF(cvinfo);
    if (!cvinfo->exp_subcards) {
        return ctr_cvputvs(cvinfo,
                           "<a task=gosub label=", CTR_INPUT_MSG,
                           " dest=#c", apr_itoa(pool, edat->no),
                           " vars=\"v=$", vname, "\""
                           " receive=", vname, ">[$", vname,
                           "]</a>", NULL);
    } else {
        return ctr_cvputvs(cvinfo,
                           "<a task=gosub label=", CTR_INPUT_MSG,
                           " dest=", CTR_ENTRY_EXPORT, "?fmt=", fmt,
                           "&noecho=1"
                           " vars=\"v=$", vname, "\""
                           " receive=", vname, ">[$", vname,
                           "]</a>", NULL);
    }
}

/* ARGSUSED */
static apr_status_t ctr_hdml_radio_conv(CTR_CLASS_NAME(radio) *rdo,
                                        const ctr_wcomp_visitor_t *vt,
                                        void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_radio_info_t *rinfo = rdo->rinfo;
    apr_status_t rv;

    CTR_HDML_LO_OUT(cvinfo);
    CTR_HDML_BR_OFF(cvinfo);

    if ((rv = ctr_hdml_anc_open_adjust(cvinfo)) != APR_SUCCESS)
        return rv;

    if (rinfo != NULL) {
        apr_pool_t *pool = cvinfo->r->pool;
        ctr_hdml_dat_t *dat = CTR_HDML_DAT(cvinfo);
        char *vname, *vval, *myvname, *name, *dvname;
        char *s;
        int i;

        if (rinfo->cardno == -1) {
            /*
             * This is a first radio button in a group (radio buttons that have
             * the same name belong to the same group).  So Initialize some
             * variables that are used by buttons in the same group.
             */
            if (!cvinfo->exp_subcards)
                rinfo->cardno = dat->cardcnt++;
            else
                rinfo->cardno = 0;
            rinfo->vno = dat->valcnt++;

            if (dat->last_rinfo == NULL)
                dat->rinfos = rinfo;
            else
                dat->last_rinfo->siblings = rinfo;
            dat->last_rinfo = rinfo;
        }

        vname = apr_psprintf(pool, "v%d", rinfo->vno);
        if (rdo->val != NULL)
            vval = ctr_hdml_vars_escape(rdo->val, pool);
        else
            vval = NULL;
        myvname = apr_psprintf(pool, "v%d_%d", rinfo->vno, rdo->no);

        /* rinfo != NULL means name != NULL */
        name = apr_psprintf(pool, CTR_CHECK_PARAM_PREFIX "%s",
                            ctr_uri_escape(rdo->name, pool));
        dvname = apr_pstrcat(pool, "$", vname, NULL);

        if (rinfo->chk == -1 && rdo->no == 0) {
            CTR_HDML_FDAT_DATA_ADD(cvinfo, name, dvname);
            CTR_HDML_INIT_ADD(cvinfo, vname, "");
            CTR_HDML_INIT_ADD(cvinfo, myvname, "_");
        } else if (rinfo->chk == rdo->no) {
            CTR_HDML_FDAT_DATA_ADD(cvinfo, name, dvname);
            CTR_HDML_INIT_ADD(cvinfo, vname, vval);
            CTR_HDML_INIT_ADD(cvinfo, myvname, "*");
        } else
            CTR_HDML_INIT_ADD(cvinfo, myvname, "_");

        s = myvname;
        for (i = 0; i < rinfo->cnt; i++) {
            if (i != rdo->no) {
                s = apr_pstrcat(pool, s,
                                apr_psprintf(pool, ";v%d_%d", rinfo->vno, i),
                                NULL);
            }
        }

        if (!cvinfo->exp_subcards) {
            if ((rv = ctr_cvputvs(cvinfo,
                                  "<a task=gosub label=", CTR_SEL_MSG,
                                  " dest=#c", apr_itoa(pool, rinfo->cardno),
                                  " vars=\"v=", rdo->val ? vval : "", "\""
                                  " receive=", vname, ";", s, ">",
                                  NULL)) != APR_SUCCESS)
                return rv;
        } else {
            if ((rv = ctr_cvputvs(cvinfo,
                                  "<a task=gosub label=", CTR_SEL_MSG,
                                  " dest=", CTR_RADIO_EXPORT,
                                  "?n=", apr_itoa(pool, rinfo->cnt),
                                  " vars=\"v=", rdo->val ? vval : "", "\"",
                                  " receive=", vname, ";", s, ">",
                                  NULL)) != APR_SUCCESS)
                return rv;
        }

        if ((rv = ctr_hdml_anc_close_adjust(cvinfo)) != APR_SUCCESS)
            return rv;

        if ((rv = ctr_cvputvs(cvinfo, "$", myvname, NULL)) != APR_SUCCESS)
            return rv;
    } else {
        /*
         * Since this radio button has no name or not in a form, we display
         * a dummy.  Dummy button cannot change the status of selection.
         */
        if ((rv = ctr_cvputvs(cvinfo,
                              "<a task=noop label=", CTR_SEL_MSG, ">",
                              NULL)) != APR_SUCCESS)
            return rv;

        if ((rv = ctr_hdml_anc_close_adjust(cvinfo)) != APR_SUCCESS)
            return rv;

        if ((rv = ctr_cvputs(cvinfo, rdo->chk ? "*" : "_")) != APR_SUCCESS)
            return rv;
    }

    if ((rv = ctr_hdml_anc_close_adjust(cvinfo)) != APR_SUCCESS)
        return rv;

    return ctr_cvputs(cvinfo, "</a>");
}

/* ARGSUSED */
static apr_status_t ctr_hdml_chkbox_conv(CTR_CLASS_NAME(chkbox) *cbox,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_hdml_dat_t *dat = CTR_HDML_DAT(cvinfo);
    char *dstvname = apr_psprintf(pool, "v%d", dat->valcnt++);
    char *mrkvname = apr_psprintf(pool, "v%d", dat->valcnt++);
    char *valvname = apr_psprintf(pool, "v%d", dat->valcnt++);
    apr_status_t rv;

    /*
     * If checkbox is found for the first time, get the id number for nodisplay
     * cards for checkboxes.
     */
    if (dat->chkon_no == -1 && !cvinfo->exp_subcards) {
        dat->chkon_no = dat->cardcnt++;
        dat->chkoff_no = dat->cardcnt++;
    }

    if (cbox->chk) {
        char *vval;

        if (!cvinfo->exp_subcards)
            vval = apr_psprintf(pool, "c%d", dat->chkoff_no);
        else
            vval = CTR_CHKOFF_EXPORT;
        CTR_HDML_INIT_ADD(cvinfo, dstvname, vval);

        CTR_HDML_INIT_ADD(cvinfo, mrkvname, "X");

        if (cbox->val != NULL)
            vval = ctr_hdml_vars_escape(cbox->val, pool);
        else
            vval = NULL;
        CTR_HDML_INIT_ADD(cvinfo, valvname, vval);
    } else {
        char *vval;

        if (!cvinfo->exp_subcards)
            vval = apr_psprintf(pool, "c%d", dat->chkon_no);
        else
            vval = CTR_CHKON_EXPORT;
        CTR_HDML_INIT_ADD(cvinfo, dstvname, vval);
        CTR_HDML_INIT_ADD(cvinfo, mrkvname, "_");
        CTR_HDML_INIT_ADD(cvinfo, valvname, NULL);
    }
    if (cbox->name != NULL) {
        char *name = apr_psprintf(pool, CTR_CHECK_PARAM_PREFIX "%s",
                                  ctr_uri_escape(cbox->name, pool));
        char *dvname = apr_pstrcat(pool, "$", valvname, NULL);
        CTR_HDML_FDAT_DATA_ADD(cvinfo, name, dvname);
    }

    CTR_HDML_LO_OUT(cvinfo);
    CTR_HDML_BR_OFF(cvinfo);

    if ((rv = ctr_hdml_anc_open_adjust(cvinfo)) != APR_SUCCESS)
        return rv;

    if ((rv = ctr_cvputvs(cvinfo,
                          "<a task=gosub label=", CTR_CHG_MSG,
                          " dest=", cvinfo->exp_subcards ? "" : "#",
                          "$", dstvname,
                          " vars=\"v=",
                          cbox->val ?
                          ctr_hdml_vars_escape(cbox->val, pool) : "",
                          "\""
                          " receive=", dstvname, ";", mrkvname, ";", valvname,
                          ">", NULL)) != APR_SUCCESS)
        return APR_SUCCESS;

        if ((rv = ctr_hdml_anc_close_adjust(cvinfo)) != APR_SUCCESS)
            return rv;

        if ((rv = ctr_cvputvs(cvinfo, "$", mrkvname, NULL)) != APR_SUCCESS)
            return rv;

        if ((rv = ctr_hdml_anc_close_adjust(cvinfo)) != APR_SUCCESS)
            return rv;

        return ctr_cvputs(cvinfo, "</a>");
}

/* ARGSUSED */
static apr_status_t ctr_hdml_hidden_conv(CTR_CLASS_NAME(hidden) *hdn,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    char *name;
    char *val;

    /*
     * If hdn->name is NULL, nothing to be done.
     */
    if (hdn->name == NULL)
        return APR_SUCCESS;

    name = ctr_uri_escape(hdn->name, pool);
    if (hdn->val != NULL)
        val = ctr_uri_escape(hdn->val, pool);
    else
        val = NULL;
    CTR_HDML_FDAT_DATA_ADD(cvinfo, name, val);

    return APR_SUCCESS;
}

/* ARGSUSED */
static apr_status_t ctr_hdml_submit_conv(CTR_CLASS_NAME(submit) *smt,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_hdml_fdat_t *fdat = CTR_HDML_FDAT_LAST(cvinfo);
    const char *val;
    apr_status_t rv;

    if (smt->val != NULL && strlen(smt->val) > 0)
        val = smt->val;
    else
        val = (const char *)CTR_POST_MSG;

    CTR_HDML_LO_OUT(cvinfo);
    CTR_HDML_BR_OFF(cvinfo);

    if ((rv = ctr_hdml_anc_open_adjust(cvinfo)) != APR_SUCCESS)
        return rv;

    if (fdat->act != NULL && ctr_startwith(fdat->act->path, "tel:")) {
        if ((rv = ctr_cvputvs(cvinfo,
                              "<a task=call label=",
                              CTR_TEL_MSG,
                              " number=\"", fdat->act->path, "\">",
                              NULL)) != APR_SUCCESS)
            return rv;
    } else if (fdat->act != NULL &&
               ctr_startwith(fdat->act->path, "mailto:")) {
        if ((rv = ctr_cvputvs(cvinfo,
                              "<a task=go label=",
                              CTR_MAIL_MSG,
                              " dest=\"",
                              apr_uri_unparse(pool, fdat->act, 0), "\">",
                              NULL)) != APR_SUCCESS)
            return rv;
    } else {
        if ((rv = ctr_cvputvs(cvinfo,
                              "<a task=go label=", CTR_POST_MSG,
                              " dest=#c", apr_itoa(pool, fdat->no),
                              " vars=\"n=",
                              smt->name != NULL ?
                              ctr_hdml_vars_escape(smt->name, pool) : "",
                              "&v=",
                              smt->name != NULL ?
                              ctr_hdml_vars_escape(val, pool) : "",
                              "\">", NULL)) != APR_SUCCESS)
            return rv;
    }

    if ((rv = ctr_hdml_adj_cvputs(cvinfo, val, FALSE)) != APR_SUCCESS)
        return rv;

    CTR_HDML_LO_OUT(cvinfo);
    CTR_HDML_BR_OFF(cvinfo);

    if ((rv = ctr_hdml_anc_close_adjust(cvinfo)) != APR_SUCCESS)
        return rv;

    return ctr_cvputs(cvinfo, "</a>");
}

/* ARGSUSED */
static apr_status_t ctr_hdml_reset_conv(CTR_CLASS_NAME(reset) *rst,
                                        const ctr_wcomp_visitor_t *vt,
                                        void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_status_t rv;

    CTR_HDML_LO_OUT(cvinfo);
    CTR_HDML_BR_OFF(cvinfo);

    if ((rv = ctr_hdml_anc_open_adjust(cvinfo)) != APR_SUCCESS)
        return rv;

    if ((rv = ctr_cvputvs(cvinfo,
                          "<a task=go label=", CTR_RST_MSG, " dest=#c2>",
                          NULL)) != APR_SUCCESS)
        return rv;

    if ((rv = ctr_hdml_adj_cvputs(cvinfo,
                                  rst->val && strlen(rst->val) > 0 ?
                                  rst->val : (const char *)CTR_RST_MSG,
                                  FALSE)) != APR_SUCCESS)
        return rv;

    CTR_HDML_LO_OUT(cvinfo);
    CTR_HDML_BR_OFF(cvinfo);

    if ((rv = ctr_hdml_anc_close_adjust(cvinfo)) != APR_SUCCESS)
        return rv;

    return ctr_cvputs(cvinfo, "</a>");
}

/* ARGSUSED */
static apr_status_t ctr_hdml_option_conv(CTR_CLASS_NAME(option) *opt,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_hdml_cdat_t *cdat = CTR_HDML_CDAT_LAST(cvinfo);
    ctr_hdml_celm_t *celm;
    const char *val = opt->val ? opt->val : "";
    const char *txt = opt->txt ? opt->txt : "";

    /*
     * Insert a new choice element object.
     */
    celm = apr_array_push(cdat->elms);
    if (!cvinfo->exp_subcards) {
        celm->val = ctr_hdml_escape(val, pool);
        celm->rettxt = ctr_hdml_vars_escape(txt, pool);
        celm->txt = ctr_hdml_escape(txt, pool);
    } else {
        celm->val = ctr_uri_escape(val, pool);
        celm->rettxt = NULL;
        celm->txt = ctr_uri_escape(txt, pool);
    }

    /*
     * Manage selected attribute properly.  Because hdml does not support
     * multi-selection in a choice card, we make a first element selected, and
     * if really selected element is found later, make it selected and make a
     * first element unselected.  Once a selected element is found, ignore the
     * selected attribute which we found in another element.
     */
    if (cdat->selstat == SS_FIRST) {
        if (opt->sel)
            cdat->selstat = SS_TRUE;
        else
            cdat->selstat = SS_FALSE;
        cdat->selval = val;
        cdat->seltxt = txt;
    } else if (cdat->selstat == SS_FALSE && opt->sel) {
        cdat->selstat = SS_TRUE;
        cdat->selval = val;
        cdat->seltxt = txt;
    }

    return APR_SUCCESS;
}

/* ARGSUSED */
static apr_status_t ctr_hdml_text_conv(CTR_CLASS_NAME(text) *txt,
                                       const ctr_wcomp_visitor_t *vt,
                                       void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_hdml_stat_t *stat = CTR_HDML_STAT_LAST(cvinfo);
    char *msg;

    msg = apr_pstrdup(pool, txt->txt);

    if (stat->last_is_space) {
        /*
         * All white spaces at the top of a line after a block element or <br>
         * tag are ignored.  If the previous string ends with spaces, they are
         * ignored too.
         */
        msg = ctr_trim_head(msg);
    }

    /*
     * All white spaces in the middle of a line are united into a ' '
     * character.
     */
    msg = ctr_unite_spaces(msg);

    if (*msg == '\0')
        return APR_SUCCESS;

    CTR_HDML_LO_OUT(cvinfo);

    stat->brout = FALSE;

    if (msg[strlen(msg) - 1] == ' ')
        stat->last_is_space = TRUE;
    else
        stat->last_is_space = FALSE;

    return ctr_hdml_adj_cvputs(cvinfo, msg, TRUE);
}

/* ARGSUSED */
static apr_status_t ctr_hdml_emoji_conv(CTR_CLASS_NAME(emoji) *emj,
                                        const ctr_wcomp_visitor_t *vt,
                                        void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_hdml_stat_t *stat = CTR_HDML_STAT_LAST(cvinfo);
    ctr_emoji_info_t *info;
    apr_status_t rv;

    if (cvinfo->emojihash == NULL)
        return APR_SUCCESS;

    info = CTR_HASH_GET(cvinfo->emojihash, emj->id);
    if (info == NULL || info->hdml == NULL) {
        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, cvinfo->r,
                      "mod_cthree: failed to get a hdml emoji (%s) "
                      "from emoji xml, cannot display", emj->id);
        return APR_SUCCESS;
    }

    CTR_HDML_LO_OUT(cvinfo);
    CTR_HDML_BR_OFF(cvinfo);

    if (apr_atoi64(info->hdml) <= 0)
        return ctr_cvputs(cvinfo, info->hdml);
    else {
        if (stat->adjusted) {
            int cw;

            cw = ctr_hdml_charwidth_get(cvinfo);
            if (cw - stat->charcnt < 2) {
                if ((rv = ctr_hdml_indent_out(cvinfo, 0)) != APR_SUCCESS)
                    return rv;
            }

            stat->charcnt += 2;
        }

        return ctr_cvputvs(cvinfo, "<img icon=", info->hdml, ">", NULL);
    }
}

/* ARGSUSED */
static apr_status_t ctr_hdml_rawtext_conv(CTR_CLASS_NAME(rawtext) *rtxt,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;

    if (rtxt->txt == NULL)
        return APR_SUCCESS;

    CTR_HDML_STAT_LAST(cvinfo)->adjusted = FALSE;

    return ctr_cvputs(cvinfo, rtxt->txt);
}
/* ARGSUSED */

static apr_status_t ctr_hdml_brank_conv(CTR_CLASS_NAME(brank) *brk,
                                          const ctr_wcomp_visitor_t *vt,
                                          void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_hdml_stat_t *stat = CTR_HDML_STAT_LAST(cvinfo);

    CTR_HDML_BR_OUT(cvinfo);

    if (stat->top) {
        stat->top = FALSE;      /* XXX? */
        return ctr_cvputs(cvinfo, "&nbsp;<br>\n");
    } else
        return ctr_cvputs(cvinfo, "<br>\n");
}

/* ARGSUSED */
static apr_status_t ctr_whtml_emoji_conv(CTR_CLASS_NAME(emoji) *emj,
                                         const ctr_wcomp_visitor_t *vt,
                                         void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    ctr_emoji_info_t *info;
    uint16_t v;

    if (cvinfo->emojihash == NULL)
        return APR_SUCCESS;

    info = CTR_HASH_GET(cvinfo->emojihash, emj->id);
    if (info == NULL || info->whtml == NULL) {
        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, cvinfo->r,
                      "mod_cthree: failed to get a whtml emoji (%s) "
                      "from emoji xml, cannot display", emj->id);
        return APR_SUCCESS;
    }

    CTR_HTML_DECO_OUT(cvinfo);
    CTR_HTML_BR_OFF(cvinfo);

    if ((v = (uint16_t)apr_strtoi64(info->whtml, NULL, 16)) <= 0)
        return ctr_cvputs(cvinfo, info->ihtml);
    else
        return ctr_cvprintf(cvinfo, "%c%c", v >> 8, v & 0xff);
}

static apr_status_t ctr_full_page_conv(CTR_CLASS_NAME(page) *page,
                                       const ctr_wcomp_visitor_t *vt,
                                       void *udata)
{
    ctr_conv_info_t *cvinfo = udata;
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat;
    char *fmt;
    ctr_wcontainer_t *wcont;
    ctr_wcomp_t *wcomp;
    apr_status_t rv;

    /*
     * Initialize html specific data.
     */
    dat = apr_palloc(pool, sizeof(*dat));
    dat->brout = TRUE;
    dat->last_is_space = TRUE;
    dat->decoinfos = apr_array_make(pool, 1, sizeof(ctr_decoinfo_t));
    dat->curfrm = NULL;
    cvinfo->udata = dat;

    fmt = apr_pstrcat(pool,
                      "<?xml version=\"1.0\" encoding=\"Shift_JIS\"?>\n"
                      "<!DOCTYPE html PUBLIC "
                      "\"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
                      "    \"http://www.w3.org/TR/xhtml1/DTD/"
                      "xhtml1-transitional.dtd\">\n"
                      "<html xmlns=\"http://www.w3.org/1999/xhtml\" "
                      "xml:lang=\"ja\" lang=\"ja\">\n"
                      "<head>\n"
                      "<meta http-equiv=\"content-type\" "
                      "content=\"text/html; charset=Shift_JIS\" />\n"
                      "<meta http-equiv=\"content-style-type\" "
                      "content=\"text/css\" />\n"
                      "<title>",
                      ctr_html_escape(page->title, pool),
                      "</title>\n",
                      page->lnkcolor != -1 ?
                      "<style type=\"text/css\">\n"
                      "a:link {color:#%06x;}\n"
                      "</style>\n" : "",
                      "</head>\n", NULL);
    if ((rv = ctr_cvprintf(cvinfo, fmt,
                           page->lnkcolor != -1 ?
                           (intptr_t)page->lnkcolor : (intptr_t)""))
        != APR_SUCCESS)
        return rv;
    if (page->bgcolor != -1 || page->txtcolor != -1 || page->txtcolor2 != -1) {
        fmt = apr_pstrcat(pool,
                          "<body style=\"",
                          page->bgcolor != -1 ?
                          "background-color:#%06x;" : "%s",
                          page->txtcolor != -1 || page->txtcolor2 != -1 ?
                          "color:#%06x;" : "%s",
                          "\">\n"
                          "<div>\n", NULL);
        if ((rv = ctr_cvprintf(cvinfo, fmt,
                               page->bgcolor != -1 ?
                               (intptr_t)page->bgcolor : (intptr_t)"",
                               page->txtcolor != -1 ?
                               (intptr_t)page->txtcolor :
                               page->txtcolor2 != -1 ?
                               (intptr_t)page->txtcolor2 : (intptr_t)""))
            != APR_SUCCESS)
            return rv;
    } else {
        if ((rv = ctr_cvputs(cvinfo,
                             "<body>\n"
                             "<div>\n")) != APR_SUCCESS)
            return rv;
    }

    CTR_FOREACH_CHILD(page, wcont, wcomp) {
        if ((rv = wcomp->accept(wcomp, vt, cvinfo)) != APR_SUCCESS)
            return rv;
    }

    return ctr_cvputvs(cvinfo,
                       !dat->brout ? "\n" : "",
                       "</div>\n"
                       "</body>\n"
                       "</html>\n", NULL);
}

/* ARGSUSED */
static apr_status_t ctr_full_emoji_conv(CTR_CLASS_NAME(emoji) *emj,
                                        const ctr_wcomp_visitor_t *vt,
                                        void *udata)
{
    /*
     * Full browsers don't support any emojis, right?
     */

    return APR_SUCCESS;
}

static apr_status_t ctr_html_deco_out(ctr_conv_info_t *cvinfo)
{
    apr_pool_t *pool = cvinfo->r->pool;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    apr_status_t rv;

    if ((dinfo = (ctr_decoinfo_t *)CTR_ARRAY_LAST(dat->decoinfos)) == NULL)
        return APR_SUCCESS;


    if (dinfo->txtcolor_stat == DS_INIT || dinfo->fsize_stat == DS_INIT) {
        char *fmt;

        fmt = apr_pstrcat(pool, "<font",
                          dinfo->txtcolor_stat == DS_INIT ?
                          " color=\"#%06x\"" : "%s",
                          dinfo->fsize_stat == DS_INIT ? " size=\"" : "",
                          dinfo->fsize_stat == DS_INIT ?
                          gen_fsizes[dinfo->fsize] : "",
                          dinfo->fsize_stat == DS_INIT ? "\"" : "",
                          ">", NULL);
        if ((rv = ctr_cvprintf(cvinfo, fmt,
                               dinfo->txtcolor_stat == DS_INIT ?
                               (intptr_t)dinfo->txtcolor : (intptr_t)""))
            != APR_SUCCESS)
            return rv;

        if (dinfo->txtcolor_stat == DS_INIT)
            dinfo->txtcolor_stat = DS_OUT_INLINE;
        if (dinfo->fsize_stat == DS_INIT)
            dinfo->fsize_stat = DS_OUT_INLINE;
    }

    if (dinfo->blink_stat == DS_INIT) {
        if ((rv = ctr_cvputs(cvinfo, "<blink>")) != APR_SUCCESS)
            return rv;
        dinfo->blink_stat = DS_OUT_INLINE;
    }

    return APR_SUCCESS;
}

static apr_status_t ctr_xhtml_deco_out(ctr_conv_info_t *cvinfo)
{
    ctr_browser_t *brw = cvinfo->brw;
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfo;
    apr_status_t rv;

    if ((dinfo = (ctr_decoinfo_t *)CTR_ARRAY_LAST(dat->decoinfos)) == NULL)
        return APR_SUCCESS;

    if (dinfo->txtcolor_stat == DS_INIT || dinfo->bgcolor_stat == DS_INIT ||
        dinfo->blink_stat == DS_INIT) {
        if ((rv = ctr_cvputs(cvinfo, "<span style=\"")) != APR_SUCCESS)
            return rv;

        if (dinfo->txtcolor_stat == DS_INIT) {
            if ((rv = ctr_cvprintf(cvinfo, "color:#%06x;",
                                   dinfo->txtcolor)) != APR_SUCCESS)
                return rv;
            dinfo->txtcolor_stat = DS_OUT_INLINE;
        }

        if (dinfo->bgcolor_stat == DS_INIT) {
            if ((rv = ctr_cvprintf(cvinfo, "background-color:#%06x;",
                                   dinfo->bgcolor)) != APR_SUCCESS)
                return rv;
            dinfo->bgcolor_stat = DS_OUT_INLINE;
        }

        if (dinfo->blink_stat == DS_INIT) {
            if ((rv = ctr_cvputs(cvinfo,
                                 "text-decoration:blink;")) != APR_SUCCESS)
                return rv;
            dinfo->blink_stat = DS_OUT_INLINE;
        }

        if ((rv = ctr_cvputs(cvinfo, "\">")) != APR_SUCCESS)
            return rv;
    }

    if (dinfo->fsize_stat == DS_INIT) {
        if ((rv = ctr_cvputvs(cvinfo,
                              "<span style=\"font-size:",
                              brw->carrier == CR_KDDI ?
                              au_fsizes[dinfo->fsize] :
                              gen_xfsizes[dinfo->fsize],
                              "\">", NULL)) != APR_SUCCESS)
            return rv;
        dinfo->fsize_stat = DS_OUT_INLINE;
    }

    return APR_SUCCESS;
}

static apr_status_t ctr_html_deco_close(ctr_conv_info_t *cvinfo)
{
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfos = (ctr_decoinfo_t *)dat->decoinfos->elts;
    int outputted;
    int i;
    apr_status_t rv;

    outputted = FALSE;

    for (i = dat->decoinfos->nelts - 1; i >= 0; i--) {
        if (dinfos[i].blink_stat == DS_OUT_INLINE) {
            if ((rv = ctr_cvputs(cvinfo, "</blink>")) != APR_SUCCESS)
                return rv;

            outputted = TRUE;
        }

        if (dinfos[i].txtcolor_stat == DS_OUT_INLINE ||
            dinfos[i].fsize_stat == DS_OUT_INLINE) {
            if ((rv = ctr_cvputs(cvinfo, "</font>")) != APR_SUCCESS)
                return rv;

            outputted = TRUE;
        }

        if (dinfos[i].blink_stat == DS_OUT_INLINE ||
            dinfos[i].blink_stat == DS_OUT_DERIVED)
            dinfos[i].blink_stat = DS_INIT;

        if (dinfos[i].fsize_stat == DS_OUT_INLINE ||
            dinfos[i].fsize_stat == DS_OUT_DERIVED)
            dinfos[i].fsize_stat = DS_INIT;

        if (dinfos[i].txtcolor_stat == DS_OUT_INLINE ||
            dinfos[i].txtcolor_stat == DS_OUT_DERIVED)
            dinfos[i].txtcolor_stat = DS_INIT;
    }

    if (dat->brout && outputted) {
        if ((rv = ctr_cvputs(cvinfo, "\n")) != APR_SUCCESS)
            return rv;
    }

    return APR_SUCCESS;
}

static apr_status_t ctr_xhtml_deco_close(ctr_conv_info_t *cvinfo)
{
    ctr_html_dat_t *dat = (ctr_html_dat_t *)cvinfo->udata;
    ctr_decoinfo_t *dinfos = (ctr_decoinfo_t *)dat->decoinfos->elts;
    int outputted;
    int i;
    apr_status_t rv;

    outputted = FALSE;

    for (i = dat->decoinfos->nelts - 1; i >= 0; i--) {
        if (dinfos[i].fsize_stat == DS_OUT_INLINE) {
            if ((rv = ctr_cvputs(cvinfo, "</span>")) != APR_SUCCESS)
                return rv;

            outputted = TRUE;
        }

        if (dinfos[i].blink_stat == DS_OUT_INLINE ||
            dinfos[i].bgcolor_stat == DS_OUT_INLINE ||
            dinfos[i].txtcolor_stat == DS_OUT_INLINE) {
            if ((rv = ctr_cvputs(cvinfo, "</span>")) != APR_SUCCESS)
                return rv;

            outputted = TRUE;
        }

        if (dinfos[i].fsize_stat == DS_OUT_INLINE ||
            dinfos[i].fsize_stat == DS_OUT_DERIVED)
            dinfos[i].fsize_stat = DS_INIT;

        if (dinfos[i].blink_stat == DS_OUT_INLINE ||
            dinfos[i].blink_stat == DS_OUT_DERIVED)
            dinfos[i].blink_stat = DS_INIT;

        if (dinfos[i].bgcolor_stat == DS_OUT_INLINE ||
            dinfos[i].bgcolor_stat == DS_OUT_DERIVED)
            dinfos[i].bgcolor_stat = DS_INIT;

        if (dinfos[i].txtcolor_stat == DS_OUT_INLINE ||
            dinfos[i].txtcolor_stat == DS_OUT_DERIVED)
            dinfos[i].txtcolor_stat = DS_INIT;
    }

    if (dat->brout && outputted) {
        if ((rv = ctr_cvputs(cvinfo, "\n")) != APR_SUCCESS)
            return rv;
    }

    return APR_SUCCESS;
}

static apr_status_t ctr_cvprintf(ctr_conv_info_t *cvinfo,
                                 const char *fmt, ...)
{
    va_list ap;
    apr_size_t maxlen;
    apr_size_t len;

    va_start(ap, fmt);
    for (;;) {
        maxlen = cvinfo->bufsz - cvinfo->buflen;
        len = apr_vsnprintf(cvinfo->buf + cvinfo->buflen, maxlen, fmt, ap);
        if (len >= maxlen)
            ctr_buf_expand(cvinfo);
        else {
            cvinfo->buflen += len;
            break;
        }
    }
    va_end(ap);

    return APR_SUCCESS;
}

static apr_status_t ctr_cvputs(ctr_conv_info_t *cvinfo, const char *s)
{
    while ((cvinfo->buf[cvinfo->buflen] = *s++)) {
        cvinfo->buflen++;
        if (cvinfo->buflen >= cvinfo->bufsz)
            ctr_buf_expand(cvinfo);
    }

    return APR_SUCCESS;
}

static apr_status_t ctr_cvputvs(ctr_conv_info_t *cvinfo, ...)
{
    va_list ap;
    char *s;
    apr_status_t rv;

    va_start(ap, cvinfo);
    while ((s = va_arg(ap, char *)) != NULL) {
        if (*s != '\0' && (rv = ctr_cvputs(cvinfo, s)) != APR_SUCCESS)
            return rv;
    }
    va_end(ap);

    return APR_SUCCESS;
}

static void ctr_buf_expand(ctr_conv_info_t *cvinfo)
{
    char *p;

    p = cvinfo->buf;
    cvinfo->bufsz *= 2;
    cvinfo->buf = apr_palloc(cvinfo->r->pool, cvinfo->bufsz);
    strncpy(cvinfo->buf, p, cvinfo->buflen);
}

static const ctr_wcomp_visitor_t ctr_ihtml_conv = {
    ctr_html_page_conv,
    ctr_html_block_conv,
    ctr_html_form_conv,
    ctr_html_heading_conv,
    ctr_html_list_conv,
    ctr_html_listitem_conv,
    ctr_html_deflist_conv,
    ctr_html_defterm_conv,
    ctr_html_defdesc_conv,
    ctr_html_deco_conv,
    ctr_html_anchor_conv,
    ctr_html_select_conv,
    ctr_gen_c3if_conv,
    ctr_html_hrline_conv,
    ctr_html_newline_conv,
    ctr_html_image_conv,
    ctr_html_editbox_conv,
    ctr_html_passwd_conv,
    ctr_html_radio_conv,
    ctr_html_chkbox_conv,
    ctr_html_hidden_conv,
    ctr_ihtml_submit_conv,
    ctr_html_reset_conv,
    ctr_html_option_conv,
    ctr_html_textarea_conv,
    ctr_html_text_conv,
    ctr_ihtml_emoji_conv,
    ctr_html_rawtext_conv,
    ctr_html_brank_conv
};

static const ctr_wcomp_visitor_t ctr_ixhtml_conv = {
    ctr_ixhtml_page_conv,
    ctr_xhtml_block_conv,
    ctr_html_form_conv,
    ctr_ixhtml_heading_conv,
    ctr_ixhtml_list_conv,
    ctr_ixhtml_listitem_conv,
    ctr_ixhtml_deflist_conv,
    ctr_ixhtml_defterm_conv,
    ctr_ixhtml_defdesc_conv,
    ctr_html_deco_conv,
    ctr_html_anchor_conv,
    ctr_html_select_conv,
    ctr_gen_c3if_conv,
    ctr_ixhtml_hrline_conv,
    ctr_html_newline_conv,
    ctr_html_image_conv,
    ctr_ixhtml_editbox_conv,
    ctr_html_passwd_conv,
    ctr_html_radio_conv,
    ctr_html_chkbox_conv,
    ctr_html_hidden_conv,
    ctr_html_submit_conv,
    ctr_html_reset_conv,
    ctr_html_option_conv,
    ctr_html_textarea_conv,
    ctr_html_text_conv,
    ctr_ihtml_emoji_conv,
    ctr_html_rawtext_conv,
    ctr_html_brank_conv
};

static const ctr_wcomp_visitor_t ctr_auxhtml_conv = {
    ctr_auxhtml_page_conv,
    ctr_xhtml_block_conv,
    ctr_html_form_conv,
    ctr_xhtml_heading_conv,
    ctr_xhtml_list_conv,
    ctr_auxhtml_listitem_conv,
    ctr_xhtml_deflist_conv,
    ctr_xhtml_defterm_conv,
    ctr_auxhtml_defdesc_conv,
    ctr_html_deco_conv,
    ctr_html_anchor_conv,
    ctr_html_select_conv,
    ctr_gen_c3if_conv,
    ctr_auxhtml_hrline_conv,
    ctr_html_newline_conv,
    ctr_html_image_conv,
    ctr_html_editbox_conv,
    ctr_auxhtml_passwd_conv,
    ctr_html_radio_conv,
    ctr_html_chkbox_conv,
    ctr_html_hidden_conv,
    ctr_html_submit_conv,
    ctr_html_reset_conv,
    ctr_html_option_conv,
    ctr_html_textarea_conv,
    ctr_html_text_conv,
    ctr_auxhtml_emoji_conv,
    ctr_html_rawtext_conv,
    ctr_html_brank_conv
};

static const ctr_wcomp_visitor_t ctr_shtml_conv = {
    ctr_html_page_conv,
    ctr_html_block_conv,
    ctr_html_form_conv,
    ctr_html_heading_conv,
    ctr_html_list_conv,
    ctr_html_listitem_conv,
    ctr_html_deflist_conv,
    ctr_html_defterm_conv,
    ctr_html_defdesc_conv,
    ctr_html_deco_conv,
    ctr_shtml_anchor_conv,
    ctr_html_select_conv,
    ctr_gen_c3if_conv,
    ctr_html_hrline_conv,
    ctr_shtml_newline_conv,
    ctr_html_image_conv,
    ctr_shtml_editbox_conv,
    ctr_shtml_passwd_conv,
    ctr_html_radio_conv,
    ctr_html_chkbox_conv,
    ctr_shtml_hidden_conv,
    ctr_shtml_submit_conv,
    ctr_html_reset_conv,
    ctr_html_option_conv,
    ctr_html_textarea_conv,
    ctr_html_text_conv,
    ctr_shtml_emoji_conv,
    ctr_html_rawtext_conv,
    ctr_html_brank_conv
};

static const ctr_wcomp_visitor_t ctr_sxhtml_conv = {
    ctr_sxhtml_page_conv,
    ctr_xhtml_block_conv,
    ctr_html_form_conv,
    ctr_xhtml_heading_conv,
    ctr_xhtml_list_conv,
    ctr_xhtml_listitem_conv,
    ctr_xhtml_deflist_conv,
    ctr_xhtml_defterm_conv,
    ctr_xhtml_defdesc_conv,
    ctr_html_deco_conv,
    ctr_sxhtml_anchor_conv,
    ctr_html_select_conv,
    ctr_gen_c3if_conv,
    ctr_sxhtml_hrline_conv,
    ctr_shtml_newline_conv,
    ctr_html_image_conv,
    ctr_sxhtml_editbox_conv,
    ctr_sxhtml_passwd_conv,
    ctr_html_radio_conv,
    ctr_html_chkbox_conv,
    ctr_html_hidden_conv,
    ctr_shtml_submit_conv,
    ctr_html_reset_conv,
    ctr_html_option_conv,
    ctr_html_textarea_conv,
    ctr_html_text_conv,
    ctr_shtml_emoji_conv,
    ctr_html_rawtext_conv,
    ctr_html_brank_conv
};

static const ctr_wcomp_visitor_t ctr_hdml_conv = {
    ctr_hdml_page_conv,
    ctr_hdml_block_conv,
    ctr_hdml_form_conv,
    ctr_hdml_heading_conv,
    ctr_hdml_list_conv,
    ctr_hdml_listitem_conv,
    ctr_hdml_deflist_conv,
    ctr_hdml_defterm_conv,
    ctr_hdml_defdesc_conv,
    ctr_hdml_deco_conv,
    ctr_hdml_anchor_conv,
    ctr_hdml_select_conv,
    ctr_gen_c3if_conv,
    ctr_hdml_hrline_conv,
    ctr_hdml_newline_conv,
    ctr_hdml_image_conv,
    ctr_hdml_editbox_conv,
    ctr_hdml_passwd_conv,
    ctr_hdml_radio_conv,
    ctr_hdml_chkbox_conv,
    ctr_hdml_hidden_conv,
    ctr_hdml_submit_conv,
    ctr_hdml_reset_conv,
    ctr_hdml_option_conv,
    ctr_hdml_textarea_conv,
    ctr_hdml_text_conv,
    ctr_hdml_emoji_conv,
    ctr_hdml_rawtext_conv,
    ctr_hdml_brank_conv
};

static const ctr_wcomp_visitor_t ctr_whtml_conv = {
    ctr_html_page_conv,
    ctr_html_block_conv,
    ctr_html_form_conv,
    ctr_html_heading_conv,
    ctr_html_list_conv,
    ctr_html_listitem_conv,
    ctr_html_deflist_conv,
    ctr_html_defterm_conv,
    ctr_html_defdesc_conv,
    ctr_html_deco_conv,
    ctr_html_anchor_conv,
    ctr_html_select_conv,
    ctr_gen_c3if_conv,
    ctr_html_hrline_conv,
    ctr_html_newline_conv,
    ctr_html_image_conv,
    ctr_html_editbox_conv,
    ctr_html_passwd_conv,
    ctr_html_radio_conv,
    ctr_html_chkbox_conv,
    ctr_html_hidden_conv,
    ctr_html_submit_conv,
    ctr_html_reset_conv,
    ctr_html_option_conv,
    ctr_html_textarea_conv,
    ctr_html_text_conv,
    ctr_whtml_emoji_conv,
    ctr_html_rawtext_conv,
    ctr_html_brank_conv
};

static const ctr_wcomp_visitor_t ctr_full_conv = {
    ctr_full_page_conv,
    ctr_xhtml_block_conv,
    ctr_html_form_conv,
    ctr_xhtml_heading_conv,
    ctr_xhtml_list_conv,
    ctr_xhtml_listitem_conv,
    ctr_xhtml_deflist_conv,
    ctr_xhtml_defterm_conv,
    ctr_xhtml_defdesc_conv,
    ctr_html_deco_conv,
    ctr_html_anchor_conv,
    ctr_html_select_conv,
    ctr_gen_c3if_conv,
    ctr_html_hrline_conv,
    ctr_html_newline_conv,
    ctr_html_image_conv,
    ctr_html_editbox_conv,
    ctr_html_passwd_conv,
    ctr_html_radio_conv,
    ctr_html_chkbox_conv,
    ctr_html_hidden_conv,
    ctr_html_submit_conv,
    ctr_html_reset_conv,
    ctr_html_option_conv,
    ctr_html_textarea_conv,
    ctr_html_text_conv,
    ctr_full_emoji_conv,
    ctr_html_rawtext_conv,
    ctr_html_brank_conv
};
