/* */
This source file includes following definitions.
- fputs_nl
- setup_xhtml
- save_current_path
- get_current_dir
- get_current_file
- upperdir
- sed
- gen_insert_header
- gen_insert_footer
- gen_page_generic_begin
- gen_page_begin
- gen_page_index_begin
- gen_page_frameset_begin
- gen_page_end
- gen_image
- gen_name_number
- gen_name_string
- gen_href_begin_with_title_target
- gen_href_begin_simple
- gen_href_begin
- gen_href_begin_with_title
- gen_href_end
- gen_list_begin
- gen_list_body
- gen_list_end
- gen_form_begin
- gen_form_end
- gen_input
- gen_input_radio
- gen_input_checkbox
- gen_input_with_title_checked
- gen_frameset_begin
- gen_frameset_end
- gen_frame
- fix_attr_value
1 /*
2 * Copyright (c) 2004, 2005, 2008, 2010, 2011 Tama Communications Corporation
3 *
4 * This file is part of GNU GLOBAL.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 #include <stdio.h>
23 #ifdef STDC_HEADERS
24 #include <stdlib.h>
25 #endif
26 #ifdef HAVE_STRING_H
27 #include <string.h>
28 #else
29 #include <strings.h>
30 #endif
31 #include <ctype.h>
32 #include <sys/types.h>
33 #ifdef HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36 #ifdef HAVE_FCNTL_H
37 #include <fcntl.h>
38 #else
39 #include <sys/file.h>
40 #endif
41
42 #include "global.h"
43 #include "anchor.h"
44 #include "common.h"
45 #include "htags.h"
46 #include "path2url.h"
47
48 /**
49 * @name Tag definitions
50 */
51 /** @{ */
52 const char *html_begin = "<html xmlns='http://www.w3.org/1999/xhtml'>";
53 const char *html_end = "</html>";
54 const char *html_head_begin = "<head>";
55 const char *html_head_end = "</head>";
56 const char *html_title_begin = "<title>";
57 const char *html_title_end = "</title>";
58 const char *body_begin = "<body>";
59 const char *body_end = "</body>";
60 const char *title_begin = "<h1 class='title'>";
61 const char *title_end = "</h1>";
62 const char *header_begin = "<h2 class='header'>";
63 const char *header_end = "</h2>";
64 const char *poweredby_begin = "<div class='poweredby'>";
65 const char *poweredby_end = "</div>";
66 const char *cvslink_begin = "<span class='cvs'>";
67 const char *cvslink_end = "</span>";
68 const char *caution_begin = "<div class='caution'>";
69 const char *caution_end = "</div>";
70 const char *list_begin = "<ol>";
71 const char *list_end = "</ol>";
72 const char *item_begin = "<li>";
73 const char *item_end = "</li>";
74 const char *flist_begin = "<table class='flist'>";
75 const char *flist_end = "</table>";
76 const char *fline_begin = "<tr class='flist'>";
77 const char *fline_end = "</tr>";
78 const char *fitem_begin = "<td class='flist'>";
79 const char *fitem_end = "</td>";
80 const char *define_list_begin = "<dl>";
81 const char *define_list_end = "</dl>";
82 const char *define_term_begin = "<dt>";
83 const char *define_term_end = "</dt>";
84 const char *define_desc_begin = "<dd>";
85 const char *define_desc_end = "</dd>";
86 const char *table_begin = "<table>";
87 const char *table_end = "</table>";
88 const char *comment_begin = "<em class='comment'>";
89 const char *comment_end = "</em>";
90 const char *sharp_begin = "<em class='sharp'>";
91 const char *sharp_end = "</em>";
92 const char *brace_begin = "<em class='brace'>";
93 const char *brace_end = "</em>";
94 const char *verbatim_begin = "<pre>";
95 const char *verbatim_end = "</pre>";
96 const char *reserved_begin = "<strong class='reserved'>";
97 const char *reserved_end = "</strong>";
98 const char *position_begin = "<em class='position'>";
99 const char *position_end = "</em>";
100 const char *warned_line_begin = "<em class='warned'>";
101 const char *warned_line_end = "</em>";
102 const char *current_line_begin = "<span class='curline'>";
103 const char *current_line_end = "</span>";
104 const char *current_row_begin = "<tr class='curline'>";
105 const char *current_row_end = "</tr>";
106 const char *error_begin = "<h2 class='error'>";
107 const char *error_end = "</h2>";
108 const char *message_begin = "<h3 class='message'>";
109 const char *message_end = "</h3>";
110 const char *string_begin = "<em class='string'>";
111 const char *string_end = "</em>";
112 const char *quote_great = ">";
113 const char *quote_little = "<";
114 const char *quote_amp = "&";
115 const char *quote_space = " ";
116 const char *hr = "<hr />";
117 const char *br = "<br />";
118 const char *empty_element = " /";
119 const char *noframes_begin = "<noframes>";
120 const char *noframes_end = "</noframes>";
121 /** @} */
122
123 /** @name tree view tag (--tree-view) */
124 /** @{ */
125 const char *tree_control = "<div id='control'>All <a href='#'>close</a> | <a href='#'>open</a></div>";
126 const char *tree_begin = "<ul id='tree'>";
127 const char *tree_begin_using = "<ul id='tree' class='%s'>";
128 const char *tree_end = "</ul>";
129 const char *dir_begin = "<li><span class='folder'></span>";
130 const char *dir_end = "";
131 const char *file_begin = "<li><span class='file'>";
132 const char *file_end = "</span></li>";
133 /** @} */
134
135 /** @name fixed guide tag (--fixed-guide) */
136 /** @{ */
137 const char *guide_begin = "<div id='guide'><ul>";
138 const char *guide_end = "</ul></div>";
139 const char *guide_unit_begin = "<li>";
140 const char *guide_unit_end = "</li>";
141 const char *guide_path_begin = "<li class='standout'><span>";
142 const char *guide_path_end = "</span></li>";
143 /** @} */
144
145 /**
146 * 1: Enforce @NAME{XHTML1.0 strict} or @NAME{XHTML1.1}.
147 */
148 static int strict_xhtml = 0;
149
150 static const char *fix_attr_value(const char *);
151
152 /**
153 * print string and new line.
154 *
155 * This function is a replacement of @CODE{fprintf(op, \"\%s\\n\", s)} in @NAME{htags}.
156 */
157 int
158 fputs_nl(const char *s, FILE *op)
159 {
160 fputs(s, op);
161 putc('\n', op);
162 return 0;
163 }
164 /**
165 * @NAME{XHTML} support.
166 *
167 * If the @OPTION{--xhtml} option is specified, @XREF{htags,1} generates @NAME{XHTML} output.
168 * We define each style for the tags in @FILE{style.css} in this directory.
169 */
170 void
171 setup_xhtml(void)
172 {
173 if (!strcmp(xhtml_version, "1.1") && !Fflag)
174 strict_xhtml = 1;
175 }
176 /**
177 * @name These methods is used to tell lex() the current path infomation.
178 */
179 /** @{ */
180 static char current_path[MAXPATHLEN];
181 static char current_dir[MAXPATHLEN];
182 static char current_file[MAXPATHLEN];
183 /** @} */
184
185 /**
186 * save path infomation
187 */
188 void
189 save_current_path(const char *path)
190 {
191 char *startp, *p;
192
193 strlimcpy(current_path, path, sizeof(current_path));
194 /* Extract directory name and file name from path */
195 strlimcpy(current_dir, path, sizeof(current_path));
196 startp = current_dir;
197 p = startp + strlen(current_dir);
198 while (p > startp) {
199 if (*--p == '/') {
200 *p = '\0';
201 strlimcpy(current_file, p + 1, sizeof(current_file));
202 return;
203 }
204 }
205 /* It seems that the path doesn't include '/' */
206 strlimcpy(current_file, path, sizeof(current_file));
207 }
208 char *
209 get_current_dir(void)
210 {
211 return current_dir;
212 }
213 char *
214 get_current_file(void)
215 {
216 return current_file;
217 }
218
219 /**
220 * Generate upper directory.
221 *
222 * Just returns the parent path of @a dir. (Adds @FILE{../} to it).
223 */
224 const char *
225 upperdir(const char *dir)
226 {
227 STATIC_STRBUF(sb);
228
229 strbuf_clear(sb);
230 strbuf_sprintf(sb, "../%s", dir);
231 return strbuf_value(sb);
232 }
233 /**
234 * Load text from file with replacing @CODE{\@PARENT_DIR\@} macro.
235 * Macro @CODE{\@PARENT_DIR\@} is replaced with the parent directory
236 * of the @FILE{HTML} directory.
237 */
238 static const char *
239 sed(FILE *ip, int place)
240 {
241 STATIC_STRBUF(sb);
242 const char *parent_dir = (place == SUBDIR) ? "../.." : "..";
243 int c, start_position = -1;
244
245 strbuf_clear(sb);
246 while ((c = fgetc(ip)) != EOF) {
247 strbuf_putc(sb, c);
248 if (c == '@') {
249 int curpos = strbuf_getlen(sb);
250 if (start_position == -1) {
251 start_position = curpos - 1;
252 } else {
253 if (!strncmp("@PARENT_DIR@",
254 strbuf_value(sb) + start_position,
255 curpos - start_position))
256 {
257 strbuf_setlen(sb, start_position);
258 strbuf_puts(sb, parent_dir);
259 start_position = -1;
260 } else {
261 start_position = curpos - 1;
262 }
263 }
264 } else if (!isalpha(c) && c != '_') {
265 if (start_position != -1)
266 start_position = -1;
267 }
268 }
269 return strbuf_value(sb);
270 }
271 /**
272 * Generate custom header.
273 */
274 const char *
275 gen_insert_header(int place)
276 {
277 static FILE *ip;
278
279 if (ip != NULL) {
280 rewind(ip);
281 } else {
282 ip = fopen(insert_header, "r");
283 if (ip == NULL)
284 die("cannot open include header file '%s'.", insert_header);
285 }
286 return sed(ip, place);
287 }
288 /**
289 * Generate custom footer.
290 */
291 const char *
292 gen_insert_footer(int place)
293 {
294 static FILE *ip;
295
296 if (ip != NULL) {
297 rewind(ip);
298 } else {
299 ip = fopen(insert_footer, "r");
300 if (ip == NULL)
301 die("cannot open include footer file '%s'.", insert_footer);
302 }
303 return sed(ip, place);
304 }
305 /**
306 * Generate beginning of generic page
307 *
308 * @param[in] title title of this page
309 * @param[in] place #SUBDIR: this page is in sub directory <br>
310 * #TOPDIR: this page is in the top directory
311 * @param[in] use_frameset
312 * use frameset document type or not
313 * @param[in] header_item
314 * item which should be inserted into the header
315 */
316 static const char *
317 gen_page_generic_begin(const char *title, int place, int use_frameset, const char *header_item)
318 {
319 STATIC_STRBUF(sb);
320 const char *dir = NULL;
321
322 switch (place) {
323 case TOPDIR:
324 dir = "";
325 break;
326 case SUBDIR:
327 dir = "../";
328 break;
329 case CGIDIR:
330 dir = "$basedir/"; /* decided by the CGI script */
331 break;
332 }
333 strbuf_clear(sb);
334 if (enable_xhtml) {
335 /*
336 * Since some browser cannot treat "<?xml...>", we don't
337 * write the declaration as long as XHTML1.1 is not required.
338 */
339 if (strict_xhtml) {
340 strbuf_puts_nl(sb, "<?xml version='1.0' encoding='ISO-8859-1'?>");
341 strbuf_sprintf(sb, "<?xml-stylesheet type='text/css' href='%sstyle.css'?>\n", dir);
342 }
343 /*
344 * If the --frame option are specified then we take
345 * 'XHTML 1.0 Frameset' for index.html
346 * and 'XHTML 1.0 Transitional' for other files,
347 * else if the config variable 'xhtml_version' is
348 * set to '1.1' then we take 'XHTML 1.1',
349 * else 'XHTML 1.0 Transitional'.
350 */
351 if (use_frameset)
352 strbuf_puts_nl(sb, "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Frameset//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd'>");
353 else if (!Fflag && strict_xhtml)
354 strbuf_puts_nl(sb, "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.1//EN' 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'>");
355 else
356 strbuf_puts_nl(sb, "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>");
357 }
358 strbuf_puts_nl(sb, html_begin);
359 strbuf_puts_nl(sb, html_head_begin);
360 strbuf_puts(sb, html_title_begin);
361 strbuf_puts(sb, title);
362 strbuf_puts_nl(sb, html_title_end);
363 strbuf_sprintf(sb, "<meta name='robots' content='noindex,nofollow'%s>\n", empty_element);
364 strbuf_sprintf(sb, "<meta name='generator' content='GLOBAL-%s'%s>\n", get_version(), empty_element);
365 if (enable_xhtml) {
366 strbuf_sprintf(sb, "<meta http-equiv='Content-Style-Type' content='text/css'%s>\n", empty_element);
367 strbuf_sprintf(sb, "<link rel='stylesheet' type='text/css' href='%sstyle.css'%s>\n", dir, empty_element);
368 }
369 if (header_item)
370 strbuf_puts(sb, header_item); /* internal use */
371 if (html_header)
372 strbuf_puts(sb, html_header); /* --html-header=file */
373 strbuf_puts(sb, html_head_end);
374 return strbuf_value(sb);
375 }
376 /**
377 * Generate beginning of normal page
378 *
379 * @param[in] title title of this page
380 * @param[in] place #SUBDIR: this page is in sub directory <br>
381 * #TOPDIR: this page is in the top directory
382 */
383 const char *
384 gen_page_begin(const char *title, int place)
385 {
386 return gen_page_generic_begin(title, place, 0, NULL);
387 }
388 /**
389 * beginning of normal page for index page
390 *
391 * @param[in] title title of this page
392 * @param[in] header_item an item which should be inserted into the header
393 */
394 const char *
395 gen_page_index_begin(const char *title, const char *header_item)
396 {
397 return gen_page_generic_begin(title, TOPDIR, 0, header_item);
398 }
399 /**
400 * Generate beginning of frameset page (@CODE{\<frameset\>})
401 *
402 * @param[in] title title of this page
403 */
404 const char *
405 gen_page_frameset_begin(const char *title)
406 {
407 return gen_page_generic_begin(title, TOPDIR, 1, NULL);
408 }
409 /**
410 * Generate end of page (@CODE{\</html\>})
411 */
412 const char *
413 gen_page_end(void)
414 {
415 return html_end;
416 }
417
418 /**
419 * Generate image tag (@CODE{\<img\>})
420 *
421 * @param[in] where Where is the icon directory? <br>
422 * #CURRENT: current directory <br>
423 * #PARENT: parent directory
424 * @param[in] file icon file without suffix.
425 * @param[in] alt alt string (the @CODE{alt} attribute is always added)
426 *
427 * @note Images are assumed to be in the @FILE{icons} or @FILE{../icons} directory, only.
428 */
429 const char *
430 gen_image(int where, const char *file, const char *alt)
431 {
432 STATIC_STRBUF(sb);
433 const char *dir = (where == PARENT) ? "../icons" : "icons";
434
435 strbuf_clear(sb);
436 if (enable_xhtml)
437 strbuf_sprintf(sb, "<img class='icon' src='%s/%s.%s' alt='[%s]'%s>",
438 dir, file, icon_suffix, fix_attr_value(alt), empty_element);
439 else
440 strbuf_sprintf(sb, "<img src='%s/%s.%s' alt='[%s]' %s%s>",
441 dir, file, icon_suffix, fix_attr_value(alt), icon_spec, empty_element);
442 return strbuf_value(sb);
443 }
444 /**
445 * Generate name tag.
446 */
447 const char *
448 gen_name_number(int number)
449 {
450 static char buf[32];
451
452 snprintf(buf, sizeof(buf), "L%d", number);
453 return gen_name_string(buf);
454 }
455 /**
456 * Generate name tag (@CODE{\<a name='xxx'\>}).
457 *
458 * Uses attribute @CODE{'id'}, if is @NAME{XHTML}.
459 */
460 const char *
461 gen_name_string(const char *name)
462 {
463 STATIC_STRBUF(sb);
464
465 strbuf_clear(sb);
466 if (enable_xhtml) {
467 /*
468 * Since some browser cannot understand "<a id='xxx' />",
469 * we put both of 'id=' and 'name=' as long as XHTML1.1
470 * is not required. XHTML1.1 prohibit 'name='.
471 */
472 if (strict_xhtml)
473 strbuf_sprintf(sb, "<a id='%s'></a>", name);
474 else
475 strbuf_sprintf(sb, "<a id='%s' name='%s'></a>", name, name);
476 } else {
477 strbuf_sprintf(sb, "<a name='%s'></a>", name);
478 }
479 return strbuf_value(sb);
480 }
481 /**
482 * Generate anchor begin tag (@CODE{\<a href='dir/file.suffix\#key'\>}).
483 * (complete format)
484 *
485 * @param[in] dir directory
486 * @param[in] file file
487 * @param[in] suffix suffix (file extension e.g. @CODE{'.txt'}). A @CODE{'.'} (dot) will be added.
488 * @param[in] key key
489 * @param[in] title @CODE{title='xxx'} attribute; if @VAR{NULL}, doesn't add it.
490 * @param[in] target @CODE{target='xxx'} attribute; if @VAR{NULL}, doesn't add it.
491 * @return generated anchor tag
492 *
493 * @note @a dir, @a file, @a suffix, @a key, @a target and @a title may be @VAR{NULL}.
494 * @note Single quote (@CODE{'}) characters are used with the attribute values.
495 */
496 const char *
497 gen_href_begin_with_title_target(const char *dir, const char *file, const char *suffix, const char *key, const char *title, const char *target)
498 {
499 STATIC_STRBUF(sb);
500
501 strbuf_clear(sb);
502 /*
503 * Construct URL.
504 * href='dir/file.suffix#key'
505 */
506 strbuf_puts(sb, "<a href='");
507 if (file) {
508 if (dir) {
509 strbuf_puts(sb, dir);
510 strbuf_putc(sb, '/');
511 }
512 strbuf_puts(sb, file);
513 if (suffix) {
514 strbuf_putc(sb, '.');
515 strbuf_puts(sb, suffix);
516 }
517 }
518 if (key) {
519 strbuf_putc(sb, '#');
520 /*
521 * If the key starts with a digit, it assumed line number.
522 * XHTML 1.1 profibits number as an anchor.
523 */
524 if (isdigit(*key))
525 strbuf_putc(sb, 'L');
526 strbuf_puts(sb, key);
527 }
528 strbuf_putc(sb, '\'');
529 if (Fflag && target)
530 strbuf_sprintf(sb, " target='%s'", fix_attr_value(target));
531 if (title)
532 strbuf_sprintf(sb, " title='%s'", fix_attr_value(title));
533 strbuf_putc(sb, '>');
534 return strbuf_value(sb);
535 }
536 /**
537 * Generate simple anchor begin tag.
538 *
539 * @par Uses:
540 * gen_href_begin_with_title_target()
541 */
542 const char *
543 gen_href_begin_simple(const char *file)
544 {
545 return gen_href_begin_with_title_target(NULL, file, NULL, NULL, NULL, NULL);
546 }
547 /**
548 * Generate anchor begin tag without title and target.
549 *
550 * @par Uses:
551 * gen_href_begin_with_title_target()
552 */
553 const char *
554 gen_href_begin(const char *dir, const char *file, const char *suffix, const char *key)
555 {
556 return gen_href_begin_with_title_target(dir, file, suffix, key, NULL, NULL);
557 }
558 /**
559 * Generate anchor begin tag without target.
560 *
561 * @par Uses:
562 * gen_href_begin_with_title_target()
563 */
564 const char *
565 gen_href_begin_with_title(const char *dir, const char *file, const char *suffix, const char *key, const char *title)
566 {
567 return gen_href_begin_with_title_target(dir, file, suffix, key, title, NULL);
568 }
569 /**
570 * Generate anchor end tag (@CODE{\</a\>}).
571 */
572 const char *
573 gen_href_end(void)
574 {
575 return "</a>";
576 }
577 /**
578 * Generate list begin tag.
579 */
580 const char *
581 gen_list_begin(void)
582 {
583 STATIC_STRBUF(sb);
584
585 if (strbuf_empty(sb)) {
586 strbuf_clear(sb);
587 if (table_list) {
588 if (enable_xhtml) {
589 strbuf_sprintf(sb, "%s\n%s%s%s%s",
590 table_begin,
591 "<tr><th class='tag'>tag</th>",
592 "<th class='line'>line</th>",
593 "<th class='file'>file</th>",
594 "<th class='code'>source code</th></tr>");
595 } else {
596 strbuf_sprintf(sb, "%s\n%s%s%s%s",
597 table_begin,
598 "<tr><th nowrap='nowrap' align='left'>tag</th>",
599 "<th nowrap='nowrap' align='right'>line</th>",
600 "<th nowrap='nowrap' align='center'>file</th>",
601 "<th nowrap='nowrap' align='left'>source code</th></tr>");
602 }
603 } else {
604 strbuf_puts(sb, verbatim_begin);
605 }
606 }
607 return strbuf_value(sb);
608 }
609 /**
610 * Generate list body.
611 *
612 * @NAME{ctags_x} with the @CODE{--encode-path=\" \\t\"}
613 */
614 const char *
615 gen_list_body(const char *srcdir, const char *ctags_x, const char *fid) /* virtually const */
616 {
617 STATIC_STRBUF(sb);
618 char path[MAXPATHLEN];
619 const char *p;
620 SPLIT ptable;
621
622 strbuf_clear(sb);
623 if (split((char *)ctags_x, 4, &ptable) < 4) {
624 recover(&ptable);
625 die("too small number of parts in list_body().\n'%s'", ctags_x);
626 }
627 strlimcpy(path, decode_path(ptable.part[PART_PATH].start + 2), sizeof(path));
628 if (fid == NULL)
629 fid = path2fid(path);
630 if (table_list) {
631 strbuf_puts(sb, current_row_begin);
632 if (enable_xhtml) {
633 strbuf_puts(sb, "<td class='tag'>");
634 strbuf_puts(sb, gen_href_begin(srcdir, fid, HTML, ptable.part[PART_LNO].start));
635 strbuf_puts(sb, ptable.part[PART_TAG].start);
636 strbuf_puts(sb, gen_href_end());
637 strbuf_sprintf(sb, "</td><td class='line'>%s</td><td class='file'>%s</td><td class='code'>",
638 ptable.part[PART_LNO].start, path);
639 } else {
640 strbuf_puts(sb, "<td nowrap='nowrap'>");
641 strbuf_puts(sb, gen_href_begin(srcdir, fid, HTML, ptable.part[PART_LNO].start));
642 strbuf_puts(sb, ptable.part[PART_TAG].start);
643 strbuf_puts(sb, gen_href_end());
644 strbuf_sprintf(sb, "</td><td nowrap='nowrap' align='right'>%s</td>"
645 "<td nowrap='nowrap' align='left'>%s</td><td nowrap='nowrap'>",
646 ptable.part[PART_LNO].start, path);
647 }
648 for (p = ptable.part[PART_LINE].start; *p; p++) {
649 unsigned char c = *p;
650
651 if (c == '&')
652 strbuf_puts(sb, quote_amp);
653 else if (c == '<')
654 strbuf_puts(sb, quote_little);
655 else if (c == '>')
656 strbuf_puts(sb, quote_great);
657 else if (c == ' ')
658 strbuf_puts(sb, quote_space);
659 else if (c == '\t') {
660 strbuf_puts(sb, quote_space);
661 strbuf_puts(sb, quote_space);
662 } else
663 strbuf_putc(sb, c);
664 }
665 strbuf_puts(sb, "</td>");
666 strbuf_puts(sb, current_row_end);
667 recover(&ptable);
668 } else {
669 /* print tag name with anchor */
670 strbuf_puts(sb, current_line_begin);
671 strbuf_puts(sb, gen_href_begin(srcdir, fid, HTML, ptable.part[PART_LNO].start));
672 strbuf_puts(sb, ptable.part[PART_TAG].start);
673 strbuf_puts(sb, gen_href_end());
674 recover(&ptable);
675
676 /* print line number */
677 for (p = ptable.part[PART_TAG].end; p < ptable.part[PART_PATH].start; p++)
678 strbuf_putc(sb, *p);
679 /* print file name */
680 strbuf_puts(sb, path);
681 /* print the rest */
682 for (p = ptable.part[PART_PATH].end; *p; p++) {
683 unsigned char c = *p;
684
685 if (c == '&')
686 strbuf_puts(sb, quote_amp);
687 else if (c == '<')
688 strbuf_puts(sb, quote_little);
689 else if (c == '>')
690 strbuf_puts(sb, quote_great);
691 else
692 strbuf_putc(sb, c);
693 }
694 strbuf_puts(sb, current_line_end);
695 }
696 return strbuf_value(sb);
697 }
698 /**
699 * Generate list end tag.
700 */
701 const char *
702 gen_list_end(void)
703 {
704 return table_list ? table_end : verbatim_end;
705 }
706 /**
707 * Generate beginning of form (@CODE{\<form\>})
708 *
709 * @param[in] target target attribute or @VAR{NULL} for no target.
710 */
711 const char *
712 gen_form_begin(const char *target)
713 {
714 STATIC_STRBUF(sb);
715
716 strbuf_clear(sb);
717 strbuf_sprintf(sb, "<form method='get' action='%s'", fix_attr_value(action));
718 if (Fflag && target)
719 strbuf_sprintf(sb, " target='%s'", fix_attr_value(target));
720 strbuf_puts(sb, ">");
721 return strbuf_value(sb);
722 }
723 /**
724 * Generate end of form (@CODE{\</form\>})
725 */
726 const char *
727 gen_form_end(void)
728 {
729 return "</form>";
730 }
731 /**
732 * Generate input tag (@CODE{\<input\>})
733 * @par Uses:
734 * gen_input_with_title_checked()
735 */
736 const char *
737 gen_input(const char *name, const char *value, const char *type)
738 {
739 return gen_input_with_title_checked(name, value, type, 0, NULL);
740 }
741 /**
742 * Generate input radiobox tag (@CODE{\<input type='radio'\>})
743 * @par Uses:
744 * gen_input_with_title_checked()
745 */
746 const char *
747 gen_input_radio(const char *name, const char *value, int checked, const char *title)
748 {
749 return gen_input_with_title_checked(name, value, "radio", checked, title);
750 }
751 /**
752 * Generate input checkbox tag (@CODE{\<input type='checkbox'\>})
753 * @par Uses:
754 * gen_input_with_title_checked()
755 */
756 const char *
757 gen_input_checkbox(const char *name, const char *value, const char *title)
758 {
759 return gen_input_with_title_checked(name, value, "checkbox", 0, title);
760 }
761 /**
762 * Generate input radio tag (@CODE{\<input\>})
763 *
764 * @note @a name, @a value, @a type and @a title may be @VAR{NULL}, thus only those
765 * with a non-@VAR{NULL} value will have there attribute added. <br>
766 * The argument names are the same as the corresponding HTML attribute names.
767 * @note Single quote (@CODE{'}) characters are used with the attribute values.
768 */
769 const char *
770 gen_input_with_title_checked(const char *name, const char *value, const char *type, int checked, const char *title)
771 {
772 STATIC_STRBUF(sb);
773
774 strbuf_clear(sb);
775 strbuf_puts(sb, "<input");
776 if (type)
777 strbuf_sprintf(sb, " type='%s'", type);
778 if (name)
779 strbuf_sprintf(sb, " name='%s' id='%s'", name, name);
780 if (value)
781 strbuf_sprintf(sb, " value='%s'", fix_attr_value(value));
782 if (checked) {
783 if (enable_xhtml)
784 strbuf_puts(sb, " checked='checked'");
785 else
786 strbuf_puts(sb, " checked");
787 }
788 if (title)
789 strbuf_sprintf(sb, " title='%s'", fix_attr_value(title));
790 strbuf_sprintf(sb, "%s>", empty_element);
791 return strbuf_value(sb);
792 }
793 /**
794 * Generate beginning of frameset (@CODE{\<frameset\>})
795 *
796 * @param[in] contents target
797 */
798 const char *
799 gen_frameset_begin(const char *contents)
800 {
801 STATIC_STRBUF(sb);
802
803 strbuf_clear(sb);
804 strbuf_sprintf(sb, "<frameset %s>", contents);
805 return strbuf_value(sb);
806 }
807 /**
808 * Generate end of frameset (@CODE{\</frameset\>})
809 */
810 const char *
811 gen_frameset_end(void)
812 {
813 return "</frameset>";
814 }
815 /**
816 * Generate beginning of frame (@CODE{\<frame\>})
817 *
818 * @param[in] name target (value for @CODE{name} and @CODE{id} attributes)
819 * @param[in] src value for @CODE{src} attribute
820 */
821 const char *
822 gen_frame(const char *name, const char *src)
823 {
824 STATIC_STRBUF(sb);
825
826 strbuf_clear(sb);
827 strbuf_sprintf(sb, "<frame name='%s' id='%s' src='%s'%s>", name, name, src, empty_element);
828 return strbuf_value(sb);
829 }
830
831
832 /** HTML attribute delimiter character ( ' or " only) */
833 #define ATTR_DELIM '\''
834
835 /**
836 * Check and fix an attribute's value; convert all @c ' (single quote) characters
837 * into @CODE{\&\#39;} within it.
838 */
839 static const char *
840 fix_attr_value(const char *value)
841 {
842 STATIC_STRBUF(sb);
843 char c;
844 const char *cptr;
845
846 strbuf_clear(sb);
847 cptr = value;
848
849 while((c = *cptr) != '\0') {
850 if(c == ATTR_DELIM)
851 strbuf_puts(sb, "'");
852 else
853 strbuf_putc(sb, c);
854 ++cptr;
855 }
856 return strbuf_value(sb);
857 }
/* */