root/htags/common.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. fputs_nl
  2. setup_xhtml
  3. upperdir
  4. sed
  5. gen_insert_header
  6. gen_insert_footer
  7. gen_page_generic_begin
  8. gen_page_begin
  9. gen_page_frameset_begin
  10. gen_page_end
  11. gen_image
  12. gen_name_number
  13. gen_name_string
  14. gen_href_begin_with_title_target
  15. gen_href_begin_simple
  16. gen_href_begin
  17. gen_href_begin_with_title
  18. gen_href_end
  19. gen_list_begin
  20. gen_list_body
  21. gen_list_end
  22. gen_div_begin
  23. gen_div_end
  24. gen_form_begin
  25. gen_form_end
  26. gen_input
  27. gen_input_radio
  28. gen_input_checkbox
  29. gen_input_with_title_checked
  30. gen_frameset_begin
  31. gen_frameset_end
  32. gen_frame

/*
 * Copyright (c) 2004, 2005 Tama Communications Corporation
 *
 * This file is part of GNU GLOBAL.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#ifdef STDC_HEADERS
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#else
#include <sys/file.h>
#endif

#include "global.h"
#include "common.h"
#include "htags.h"
#include "path2url.h"

/*
 * Tag definitions
 *
 * Htags generates HTML tag by default.
 */
const char *html_begin          = "<html>";
const char *html_end            = "</html>";
const char *html_head_begin     = "<head>";
const char *html_head_end       = "</head>";
const char *html_title_begin    = "<title>";
const char *html_title_end      = "</title>";
const char *body_begin          = "<body text='#191970' bgcolor='#f5f5dc' vlink='gray'>";
const char *body_end            = "</body>";
const char *title_begin         = "<h1><font color='#cc0000'>";
const char *title_end           = "</font></h1>";
const char *header_begin        = "<h2>";
const char *header_end          = "</h2>";
const char *cvslink_begin       = "<font size='-1'>";
const char *cvslink_end         = "</font>";
const char *caution_begin       = "<center>\n<blockquote>";
const char *caution_end         = "</blockquote>\n</center>";
const char *list_begin          = "<ol>";
const char *list_end            = "</ol>";
const char *item_begin          = "<li>";
const char *item_end            = "";
const char *flist_begin         = "<table cellpadding='2' width='100%'>";
const char *flist_end           = "</table>";
const char *fline_begin         = "<tr>";
const char *fline_end           = "</tr>";
const char *fitem_begin         = "<td nowrap>";
const char *fitem_end           = "</td>";
const char *define_list_begin   = "<dl>";
const char *define_list_end     = "</dl>";
const char *define_term_begin   = "<dt>";
const char *define_term_end     = "";
const char *define_desc_begin   = "<dd>";
const char *define_desc_end     = "";
const char *table_begin         = "<table>";
const char *table_end           = "</table>";
const char *comment_begin       = "<i><font color='green'>";
const char *comment_end         = "</font></i>";
const char *sharp_begin         = "<font color='darkred'>";
const char *sharp_end           = "</font>";
const char *brace_begin         = "<font color='red'>";
const char *brace_end           = "</font>";
const char *verbatim_begin      = "<pre>";
const char *verbatim_end        = "</pre>";
const char *reserved_begin      = "<b>";
const char *reserved_end        = "</b>";
const char *position_begin      = "<font color='gray'>";
const char *position_end        = "</font>";
const char *warned_line_begin   = "<span style='background-color:yellow'>";
const char *warned_line_end     = "</span>";
const char *error_begin         = "<h1><font color='#cc0000'>";
const char *error_end           = "</font></h1>";
const char *message_begin       = "<h3>";
const char *message_end         = "</h3>";
const char *string_begin        = "<u>";
const char *string_end          = "</u>";
const char *quote_great         = "&gt;";
const char *quote_little        = "&lt;";
const char *quote_amp           = "&amp;";
const char *quote_space         = "&nbsp;";
const char *hr                  = "<hr>";
const char *br                  = "<br>";
const char *empty_element       = "";
const char *noframes_begin      = "<noframes>";
const char *noframes_end        = "</noframes>";

/*
 * 1: Enforce XHTML1.0 strict or XHTML1.1.
 */
static int strict_xhtml = 0;

/*
 * print string and new line.
 *
 * This function is a replacement of fprintf(op, "%s\n", s) in htags.
 */
int
fputs_nl(const char *s, FILE *op)
{
        fputs(s, op);
        putc('\n', op);
        return 0;
}
/*
 * XHTML support.
 *
 * If the --xhtml option is specified, htags(1) generates XHTML output.
 * We define each style for the tags in 'style.css' in this directory.
 */
void
setup_xhtml(void)
{
        if (!strcmp(xhtml_version, "1.1") && !Fflag)
                strict_xhtml = 1;
        html_begin              = "<html xmlns='http://www.w3.org/1999/xhtml'>";
        html_end                = "</html>";
        html_head_begin         = "<head>";
        html_head_end           = "</head>";
        html_title_begin        = "<title>";
        html_title_end          = "</title>";
        body_begin              = "<body>";
        body_end                = "</body>";
        title_begin             = "<h1 class='title'>";
        title_end               = "</h1>";
        header_begin            = "<h2 class='header'>";
        header_end              = "</h2>";
        cvslink_begin           = "<span class='cvs'>";
        cvslink_end             = "</span>";
        caution_begin           = "<div class='caution'>";
        caution_end             = "</div>";
        list_begin              = "<ol>";
        list_end                = "</ol>";
        item_begin              = "<li>";
        item_end                = "</li>";
        flist_begin             = "<table class='flist'>";
        flist_end               = "</table>";
        fline_begin             = "<tr class='flist'>";
        fline_end               = "</tr>";
        fitem_begin             = "<td class='flist'>";
        fitem_end               = "</td>";
        define_list_begin       = "<dl>";
        define_list_end         = "</dl>";
        define_term_begin       = "<dt>";
        define_term_end         = "</dt>";
        define_desc_begin       = "<dd>";
        define_desc_end         = "</dd>";
        table_begin             = "<table>";
        table_end               = "</table>";
        comment_begin           = "<em class='comment'>";
        comment_end             = "</em>";
        sharp_begin             = "<em class='sharp'>";
        sharp_end               = "</em>";
        brace_begin             = "<em class='brace'>";
        brace_end               = "</em>";
        verbatim_begin          = "<pre>";
        verbatim_end            = "</pre>";
        reserved_begin          = "<strong class='reserved'>";
        reserved_end            = "</strong>";
        position_begin          = "<em class='position'>";
        position_end            = "</em>";
        warned_line_begin       = "<em class='warned'>";
        warned_line_end         = "</em>";
        error_begin             = "<h2 class='error'>";
        error_end               = "</h2>";
        message_begin           = "<h3 class='message'>";
        message_end             = "</h3>";
        string_begin            = "<em class='string'>";
        string_end              = "</em>";
        quote_great             = "&gt;";
        quote_little            = "&lt;";
        quote_amp               = "&amp;";
        quote_space             = "&nbsp;";
        hr                      = "<hr />";
        br                      = "<br />";
        empty_element           = " /";
        noframes_begin          = "<noframes>";
        noframes_end            = "</noframes>";
}
/*
 * Generate upper directory.
 */
const char *
upperdir(const char *dir)
{
        STATIC_STRBUF(sb);

        strbuf_clear(sb);
        strbuf_sprintf(sb, "../%s", dir);
        return strbuf_value(sb);
}
/*
 * Load text from file with replacing @PARENT_DIR@ macro.
 * Macro @PARENT_DIR@ is replaced with the parent directory
 * of the 'HTML' directory.
 */
static const char *
sed(FILE *ip, int place)
{
        STATIC_STRBUF(sb);
        const char *parent_dir = (place == SUBDIR) ? "../.." : "..";
        int c, start_position = -1;

        strbuf_clear(sb);
        while ((c = fgetc(ip)) != EOF) {
                strbuf_putc(sb, c);
                if (c == '@') {
                        int curpos = strbuf_getlen(sb);
                        if (start_position == -1) {
                                start_position = curpos - 1;
                        } else {
                                if (!strncmp("@PARENT_DIR@",
                                        strbuf_value(sb) + start_position,
                                        curpos - start_position))
                                {
                                        strbuf_setlen(sb, start_position);
                                        strbuf_puts(sb, parent_dir);
                                        start_position = -1;
                                } else {
                                        start_position = curpos - 1;
                                }
                        }
                } else if (!isalpha(c) && c != '_') {
                        if (start_position != -1)
                                start_position = -1;
                }
        }
        return strbuf_value(sb);
}
/*
 * Generate custom header.
 */
const char *
gen_insert_header(int place)
{
        static FILE *ip;

        if (ip != NULL) {
                rewind(ip);
        } else {
                ip = fopen(insert_header, "r");
                if (ip == NULL)
                        die("cannot open include header file '%s'.", insert_header);
        }
        return sed(ip, place);
}
/*
 * Generate custom footer.
 */
const char *
gen_insert_footer(int place)
{
        static FILE *ip;

        if (ip != NULL) {
                rewind(ip);
        } else {
                ip = fopen(insert_footer, "r");
                if (ip == NULL)
                        die("cannot open include footer file '%s'.", insert_footer);
        }
        return sed(ip, place);
}
/*
 * Generate beginning of generic page
 *
 *      i)      title   title of this page
 *      i)      place   SUBDIR: this page is in sub directory
 *                      TOPDIR: this page is in the top directory
 *      i)      use_frameset
 *                      use frameset document type or not
 */
static const char *
gen_page_generic_begin(const char *title, int place, int use_frameset)
{
        STATIC_STRBUF(sb);
        const char *dir = (place == SUBDIR) ? "../" : "";

        strbuf_clear(sb);
        if (enable_xhtml) {
                /*
                 * Since some browser cannot treat "<?xml...>", we don't
                 * write the declaration as long as XHTML1.1 is not required.
                 */
                if (strict_xhtml) {
                        strbuf_puts_nl(sb, "<?xml version='1.0' encoding='ISO-8859-1'?>");
                        strbuf_sprintf(sb, "<?xml-stylesheet type='text/css' href='%sstyle.css'?>\n", dir);
                }
                /*
                 * If the --frame option are specified then we take
                 * 'XHTML 1.0 Frameset' for index.html
                 * and 'XHTML 1.0 Transitional' for other files,
                 * else if the config variable 'xhtml_version' is
                 * set to '1.1' then we take 'XHTML 1.1',
                 * else 'XHTML 1.0 Transitional'.
                 */
                if (use_frameset)
                        strbuf_puts_nl(sb, "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Frameset//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd'>");
                else if (!Fflag && strict_xhtml)
                        strbuf_puts_nl(sb, "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.1//EN' 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'>");
                else
                        strbuf_puts_nl(sb, "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>");
        }
        strbuf_puts_nl(sb, html_begin);
        strbuf_puts_nl(sb, html_head_begin);
        strbuf_puts(sb, html_title_begin);
        strbuf_puts(sb, title);
        strbuf_puts_nl(sb, html_title_end);
        strbuf_sprintf(sb, "<meta name='robots' content='noindex,nofollow'%s>\n", empty_element);
        strbuf_sprintf(sb, "<meta name='generator' content='GLOBAL-%s'%s>\n", get_version(), empty_element);
        if (enable_xhtml) {
                strbuf_sprintf(sb, "<meta http-equiv='Content-Style-Type' content='text/css'%s>\n", empty_element);
                strbuf_sprintf(sb, "<link rel='stylesheet' type='text/css' href='%sstyle.css'%s>\n", dir, empty_element);
        }
        strbuf_puts(sb, html_head_end);
        return strbuf_value(sb);
}
/*
 * Generate beginning of normal page
 *
 *      i)      title   title of this page
 *      i)      place   SUBDIR: this page is in sub directory
 *                      TOPDIR: this page is in the top directory
 */
const char *
gen_page_begin(const char *title, int place)
{
        return gen_page_generic_begin(title, place, 0);
}
/*
 * Generate beginning of frameset page
 *
 *      i)      title   title of this page
 */
const char *
gen_page_frameset_begin(const char *title)
{
        return gen_page_generic_begin(title, TOPDIR, 1);
}
/*
 * Generate end of page
 */
const char *
gen_page_end(void)
{
        return html_end;
}

/*
 * Generate image tag.
 *
 *      i)      where   Where is the icon directory?
 *                      CURRENT: current directory
 *                      PARENT: parent directory
 *      i)      file    icon file without suffix.
 *      i)      alt     alt string
 */
const char *
gen_image(int where, const char *file, const char *alt)
{
        STATIC_STRBUF(sb);
        const char *dir = (where == PARENT) ? "../icons" : "icons";

        strbuf_clear(sb);
        if (enable_xhtml)
                strbuf_sprintf(sb, "<img class='icon' src='%s/%s.%s' alt='[%s]'%s>",
                        dir, file, icon_suffix, alt, empty_element);
        else
                strbuf_sprintf(sb, "<img src='%s/%s.%s' alt='[%s]' %s%s>",
                        dir, file, icon_suffix, alt, icon_spec, empty_element);
        return strbuf_value(sb);
}
/*
 * Generate name tag.
 */
const char *
gen_name_number(int number)
{
        static char buf[32];

        snprintf(buf, sizeof(buf), "L%d", number);
        return gen_name_string(buf);
}
/*
 * Generate name tag.
 */
const char *
gen_name_string(const char *name)
{
        STATIC_STRBUF(sb);

        strbuf_clear(sb);
        if (enable_xhtml) {
                /*
                 * Since some browser cannot understand "<a id='xxx' />",
                 * we put both of 'id=' and 'name=' as long as XHTML1.1
                 * is not required. XHTML1.1 prohibit 'name='.
                 */
                if (strict_xhtml)
                        strbuf_sprintf(sb, "<a id='%s' />", name);
                else
                        strbuf_sprintf(sb, "<a id='%s' name='%s' />", name, name);
        } else {
                strbuf_sprintf(sb, "<a name='%s'>", name);
        }
        return strbuf_value(sb);
}
/*
 * Generate anchor begin tag.
 * (complete format)
 *
 *      i)      dir     directory
 *      i)      file    file
 *      i)      suffix  suffix
 *      i)      key     key
 *      i)      title   title='xxx'
 *      i)      target  target='xxx'
 *      r)              generated anchor tag
 */
const char *
gen_href_begin_with_title_target(const char *dir, const char *file, const char *suffix, const char *key, const char *title, const char *target)
{
        STATIC_STRBUF(sb);

        strbuf_clear(sb);
        /*
         * Construct URL.
         * href='dir/file.suffix#key'
         */
        strbuf_puts(sb, "<a href='");
        if (file) {
                if (dir) {
                        strbuf_puts(sb, dir);
                        strbuf_putc(sb, '/');
                }
                strbuf_puts(sb, file);
                if (suffix) {
                        strbuf_putc(sb, '.');
                        strbuf_puts(sb, suffix);
                }
        }
        if (key) {
                strbuf_putc(sb, '#');
                /*
                 * If the key starts with a digit, it assumed line number.
                 * XHTML 1.1 profibits number as an anchor.
                 */
                if (isdigit(*key))
                        strbuf_putc(sb, 'L');
                strbuf_puts(sb, key);
        }
        strbuf_putc(sb, '\'');
        if (target)
                strbuf_sprintf(sb, " target='%s'", target);
        if (title)
                strbuf_sprintf(sb, " title='%s'", title);
        strbuf_putc(sb, '>');
        return strbuf_value(sb);
}
/*
 * Generate simple anchor begin tag.
 */
const char *
gen_href_begin_simple(const char *file)
{
        return gen_href_begin_with_title_target(NULL, file, NULL, NULL, NULL, NULL);
}
/*
 * Generate anchor begin tag without title and target.
 */
const char *
gen_href_begin(const char *dir, const char *file, const char *suffix, const char *key)
{
        return gen_href_begin_with_title_target(dir, file, suffix, key, NULL, NULL);
}
/*
 * Generate anchor begin tag without target.
 */
const char *
gen_href_begin_with_title(const char *dir, const char *file, const char *suffix, const char *key, const char *title)
{
        return gen_href_begin_with_title_target(dir, file, suffix, key, title, NULL);
}
/*
 * Generate anchor end tag.
 */
const char *
gen_href_end(void)
{
        return "</a>";
}
/*
 * Generate list begin tag.
 */
const char *
gen_list_begin(void)
{
        STATIC_STRBUF(sb);

        if (strbuf_empty(sb)) {
                strbuf_clear(sb);
                if (table_list) {
                        if (enable_xhtml) {
                                strbuf_sprintf(sb, "%s\n%s%s%s%s",
                                        table_begin, 
                                        "<tr><th class='tag'>tag</th>",
                                        "<th class='line'>line</th>",
                                        "<th class='file'>file</th>",
                                        "<th class='code'>source code</th></tr>");
                        } else {
                                strbuf_sprintf(sb, "%s\n%s%s%s%s",
                                        table_begin, 
                                        "<tr><th nowrap align='left'>tag</th>",
                                        "<th nowrap align='right'>line</th>",
                                        "<th nowrap align='center'>file</th>",
                                        "<th nowrap align='left'>source code</th></tr>");
                        }
                } else {
                        strbuf_puts(sb, verbatim_begin);
                }
        }
        return strbuf_value(sb);
}
/*
 * Generate list body.
 *
 * s must be choped.
 */
const char *
gen_list_body(const char *srcdir, const char *ctags_x)          /* virtually const */
{
        STATIC_STRBUF(sb);
        const char *p, *filename, *fid;
        SPLIT ptable;

        strbuf_clear(sb);
        if (split((char *)ctags_x, 4, &ptable) < 4) {
                recover(&ptable);
                die("too small number of parts in list_body().\n'%s'", ctags_x);
        }
        filename = ptable.part[PART_PATH].start + 2;    /* remove './' */
        fid = path2fid(filename);
        if (table_list) {
                if (enable_xhtml) {
                        strbuf_puts(sb, "<tr><td class='tag'>");
                        strbuf_puts(sb, gen_href_begin(srcdir, fid, HTML, ptable.part[PART_LNO].start));
                        strbuf_puts(sb, ptable.part[PART_TAG].start);
                        strbuf_puts(sb, gen_href_end());
                        strbuf_sprintf(sb, "</td><td class='line'>%s</td><td class='file'>%s</td><td class='code'>",
                                ptable.part[PART_LNO].start, filename);
                } else {
                        strbuf_puts(sb, "<tr><td nowrap>");
                        strbuf_puts(sb, gen_href_begin(srcdir, fid, HTML, ptable.part[PART_LNO].start));
                        strbuf_puts(sb, ptable.part[PART_TAG].start);
                        strbuf_puts(sb, gen_href_end());
                        strbuf_sprintf(sb, "</td><td nowrap align='right'>%s</td><td nowrap align='left'>%s</td><td nowrap>",
                                ptable.part[PART_LNO].start, filename);
                }

                for (p = ptable.part[PART_LINE].start; *p; p++) {
                        unsigned char c = *p;

                        if (c == '&')
                                strbuf_puts(sb, quote_amp);
                        else if (c == '<')
                                strbuf_puts(sb, quote_little);
                        else if (c == '>')
                                strbuf_puts(sb, quote_great);
                        else if (c == ' ')
                                strbuf_puts(sb, quote_space);
                        else if (c == '\t') {
                                strbuf_puts(sb, quote_space);
                                strbuf_puts(sb, quote_space);
                        } else
                                strbuf_putc(sb, c);
                }
                strbuf_puts(sb, "</td></tr>");
                recover(&ptable);
        } else {
                int done = 0;

                strbuf_puts(sb, gen_href_begin(srcdir, fid, HTML, ptable.part[PART_LNO].start));
                strbuf_puts(sb, ptable.part[PART_TAG].start);
                strbuf_puts(sb, gen_href_end());
                p = ctags_x + strlen(ptable.part[PART_TAG].start);
                recover(&ptable);

                for (; *p; p++) {
                        unsigned char c = *p;

                        /* ignore "./" in path name */
                        if (!done && c == '.' && *(p + 1) == '/') {
                                p++;
                                done = 1;
                        } else if (c == '&')
                                strbuf_puts(sb, quote_amp);
                        else if (c == '<')
                                strbuf_puts(sb, quote_little);
                        else if (c == '>')
                                strbuf_puts(sb, quote_great);
                        else
                                strbuf_putc(sb, c);
                }
        }
        return strbuf_value(sb);
}
/*
 * Generate list end tag.
 */
const char *
gen_list_end(void)
{
        return table_list ? table_end : verbatim_end;
}
/*
 * Generate div begin tag.
 *
 *      i)      align   right,left,center
 */
const char *
gen_div_begin(const char *align)
{
        STATIC_STRBUF(sb);

        if (strbuf_empty(sb)) {
                strbuf_clear(sb);
                if (align) {
                        /*
                         * In XHTML, alignment is defined in the file 'style.css'.
                         */
                        if (enable_xhtml)
                                strbuf_sprintf(sb, "<div class='%s'>", align);
                        else
                                strbuf_sprintf(sb, "<div align='%s'>", align);
                } else {
                        strbuf_puts(sb, "<div>");
                }
        }
        return strbuf_value(sb);
}
/*
 * Generate div end tag.
 */
const char *
gen_div_end(void)
{
        return "</div>";
}
/*
 * Generate beginning of form
 *
 *      i)      target  target
 */
const char *
gen_form_begin(const char *target)
{
        STATIC_STRBUF(sb);

        strbuf_clear(sb);
        strbuf_sprintf(sb, "<form method='get' action='%s'", action);
        if (target)
                strbuf_sprintf(sb, " target='%s'", target);
        strbuf_puts(sb, ">");
        return strbuf_value(sb);
}
/*
 * Generate end of form
 */
const char *
gen_form_end(void)
{
        return "</form>";
}
/*
 * Generate input tag
 */
const char *
gen_input(const char *name, const char *value, const char *type)
{
        return gen_input_with_title_checked(name, value, type, 0, NULL);
}
/*
 * Generate input radiobox tag
 */
const char *
gen_input_radio(const char *name, const char *value, int checked, const char *title)
{
        return gen_input_with_title_checked(name, value, "radio", checked, title);
}
/*
 * Generate input checkbox tag
 */
const char *
gen_input_checkbox(const char *name, const char *value, const char *title)
{
        return gen_input_with_title_checked(name, value, "checkbox", 0, title);
}
/*
 * Generate input radio tag
 */
const char *
gen_input_with_title_checked(const char *name, const char *value, const char *type, int checked, const char *title)
{
        STATIC_STRBUF(sb);

        strbuf_clear(sb);
        strbuf_puts(sb, "<input");
        if (type)
                strbuf_sprintf(sb, " type='%s'", type);
        if (name)
                strbuf_sprintf(sb, " name='%s'", name);
        if (value)
                strbuf_sprintf(sb, " value='%s'", value);
        if (checked) {
                if (enable_xhtml)
                        strbuf_puts(sb, " checked='checked'");
                else
                        strbuf_puts(sb, " checked");
        }
        if (title)
                strbuf_sprintf(sb, " title='%s'", title);
        strbuf_sprintf(sb, "%s>", empty_element);
        return strbuf_value(sb);
}
/*
 * Generate beginning of frameset
 *
 *      i)      target  target
 */
const char *
gen_frameset_begin(const char *contents)
{
        STATIC_STRBUF(sb);

        strbuf_clear(sb);
        strbuf_sprintf(sb, "<frameset %s>", contents);
        return strbuf_value(sb);
}
/*
 * Generate end of frameset
 */
const char *
gen_frameset_end(void)
{
        return "</frameset>";
}
/*
 * Generate beginning of frame
 *
 *      i)      target  target
 */
const char *
gen_frame(const char *name, const char *src)
{
        STATIC_STRBUF(sb);

        strbuf_clear(sb);
        strbuf_sprintf(sb, "<frame name='%s' id='%s' src='%s'%s>", name, name, src, empty_element);
        return strbuf_value(sb);
}

/* [<][>][^][v][top][bottom][index][help] */