/* tag.c -- Functions to handle Info tags (that is, the special construct for images, not the "tag table" of starting position.) Copyright 2012-2023 Free Software Foundation, Inc. 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 . */ #include "info.h" #include "tag.h" #include "scan.h" #include "util.h" struct tag_handler { const char *name; size_t len; int (*handler) (char *, struct text_buffer *); }; struct info_tag { struct info_tag *next; char *kw; char *val; }; static void info_tag_free (struct info_tag *tag) { while (tag) { struct info_tag *next = tag->next; free (tag->kw); free (tag->val); free (tag); tag = next; } } /* See if KW is one of the tags in the list starting at TAG. */ static struct info_tag * info_tag_find (struct info_tag *tag, const char *kw) { for (; tag; tag = tag->next) if (strcmp (tag->kw, kw) == 0) return tag; return NULL; } /* Found a keyword when parsing the full tag string: alt, text, etc. Return the new tag, update *TMPBUF_PTR and set *KW. */ static struct info_tag * tag_found_keyword (struct text_buffer *tmpbuf_ptr, char **kw) { struct info_tag *tag = xmalloc (sizeof (*tag)); tag->next = NULL; /* have to update in caller */ text_buffer_add_char (tmpbuf_ptr, 0); if (*kw != tmpbuf_ptr->base) { /* in case tmpbuf got realloc-ed */ *kw = tmpbuf_ptr->base; /* ick */ } tag->kw = xstrdup (*kw); tag->val = xstrdup (*kw + strlen(*kw) + 1); text_buffer_reset (tmpbuf_ptr); return tag; } /* Handle the image tag. */ static int tag_image (char *text, struct text_buffer *outbuf) { mbi_iterator_t iter; enum { state_kw, state_val, state_qstr, state_delim } state = state_kw; struct text_buffer tmpbuf; char *kw; struct info_tag *tag_head = NULL, *tag; int escaped = 0; text_buffer_init (&tmpbuf); for (mbi_init (iter, text, strlen (text)); mbi_avail (iter); mbi_advance (iter)) { const char *cur_ptr; size_t cur_len; if (mb_isspace (mbi_cur (iter))) { if (state == state_val) { struct info_tag *new_kw = tag_found_keyword (&tmpbuf, &kw); new_kw->next = tag_head; tag_head = new_kw; state = state_delim; continue; } if (state == state_delim) continue; } else if (state == state_delim) state = state_kw; cur_len = mb_len (mbi_cur (iter)); cur_ptr = mbi_cur_ptr (iter); if (state == state_qstr && escaped) { escaped = 0; } else if (cur_len == 1) { switch (*cur_ptr) { case '=': if (state != state_kw) break; text_buffer_add_char (&tmpbuf, 0); kw = tmpbuf.base; if (!mbi_avail (iter)) break; mbi_advance (iter); state = state_val; cur_len = mb_len (mbi_cur (iter)); cur_ptr = mbi_cur_ptr (iter); if (!(cur_len == 1 && *cur_ptr == '"')) break; /* fall through */ case '"': if (state == state_val) { state = state_qstr; continue; } if (state == state_qstr) { struct info_tag *new_kw = tag_found_keyword (&tmpbuf, &kw); new_kw->next = tag_head; tag_head = new_kw; state = state_delim; continue; } break; case '\\': if (state == state_qstr) { escaped = 1; continue; } } } text_buffer_add_string (&tmpbuf, cur_ptr, cur_len); } tag = info_tag_find (tag_head, "text"); if (!tag) tag = info_tag_find (tag_head, "alt"); if (tag) { text_buffer_add_string (outbuf, tag->val, strlen (tag->val)); } text_buffer_free (&tmpbuf); info_tag_free (tag_head); return 0; } /* We don't do anything with the index tag; it'll just be ignored. */ static struct tag_handler tagtab[] = { { "image", 5, tag_image }, { "index", 5, NULL }, { NULL } }; static struct tag_handler * find_tag_handler (char *tag, size_t taglen) { struct tag_handler *tp; for (tp = tagtab; tp->name; tp++) if (taglen >= tp->len && strncmp (tp->name, tag, tp->len) == 0) return tp; return NULL; } /* Expand \b[...\b] construct at *INPUT. If encountered, append the expanded text to OUTBUF, advance *INPUT past the tag, and return 1. Otherwise, return 0. If it is an index tag, set IS_INDEX to 1. *INPUT points into a null-terminated area which may however contain other null characters. INPUT_END points to the end of this area. */ int tag_expand (char **input, char *input_end, struct text_buffer *outbuf, int *is_index) { char *p = *input; char *q; size_t len; struct tag_handler *tp; if (p >= input_end - 3 || memcmp(p, "\0\b[", 3) != 0) /* opening magic? */ return 0; p += 3; q = p + strlen (p); if (q >= input_end - 3 || memcmp (q + 1, "\b]", 2)) /* closing magic? */ return 0; /* Not a proper tag. */ /* Output is different for index nodes */ if (!strncmp ("index", p, strlen ("index"))) *is_index = 1; len = strcspn (p, " \t"); /* tag name */ tp = find_tag_handler (p, len); if (tp && tp->handler) { while (p[len] == ' ' || p[len] == '\t') ++len; /* move past whitespace */ tp->handler (p + len, outbuf); } *input = q + 3; return 1; }