1/* $OpenBSD: tbl_data.c,v 1.46 2021/09/10 13:23:44 schwarze Exp $ */ 2/* 3 * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2011,2015,2017-2019,2021 Ingo Schwarze <schwarze@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18#include <sys/types.h> 19 20#include <assert.h> 21#include <ctype.h> 22#include <stdint.h> 23#include <stdio.h> 24#include <stdlib.h> 25#include <string.h> 26#include <time.h> 27 28#include "mandoc_aux.h" 29#include "mandoc.h" 30#include "tbl.h" 31#include "libmandoc.h" 32#include "tbl_int.h" 33 34static void getdata(struct tbl_node *, struct tbl_span *, 35 int, const char *, int *); 36static struct tbl_span *newspan(struct tbl_node *, int, 37 struct tbl_row *); 38 39 40static void 41getdata(struct tbl_node *tbl, struct tbl_span *dp, 42 int ln, const char *p, int *pos) 43{ 44 struct tbl_dat *dat, *pdat; 45 struct tbl_cell *cp; 46 struct tbl_span *pdp; 47 const char *ccp; 48 int startpos, endpos; 49 50 /* 51 * Determine the length of the string in the cell 52 * and advance the parse point to the end of the cell. 53 */ 54 55 startpos = *pos; 56 ccp = p + startpos; 57 while (*ccp != '\0' && *ccp != tbl->opts.tab) 58 if (*ccp++ == '\\') 59 mandoc_escape(&ccp, NULL, NULL); 60 *pos = ccp - p; 61 62 /* Advance to the next layout cell, skipping spanners. */ 63 64 cp = dp->last == NULL ? dp->layout->first : dp->last->layout->next; 65 while (cp != NULL && cp->pos == TBL_CELL_SPAN) 66 cp = cp->next; 67 68 /* 69 * If the current layout row is out of cells, allocate 70 * a new cell if another row of the table has at least 71 * this number of columns, or discard the input if we 72 * are beyond the last column of the table as a whole. 73 */ 74 75 if (cp == NULL) { 76 if (dp->layout->last->col + 1 < dp->opts->cols) { 77 cp = mandoc_calloc(1, sizeof(*cp)); 78 cp->pos = TBL_CELL_LEFT; 79 cp->font = ESCAPE_FONTROMAN; 80 cp->spacing = SIZE_MAX; 81 dp->layout->last->next = cp; 82 cp->col = dp->layout->last->col + 1; 83 dp->layout->last = cp; 84 } else { 85 mandoc_msg(MANDOCERR_TBLDATA_EXTRA, 86 ln, startpos, "%s", p + startpos); 87 while (p[*pos] != '\0') 88 (*pos)++; 89 return; 90 } 91 } 92 93 dat = mandoc_malloc(sizeof(*dat)); 94 dat->layout = cp; 95 dat->next = NULL; 96 dat->string = NULL; 97 dat->hspans = 0; 98 dat->vspans = 0; 99 dat->block = 0; 100 dat->pos = TBL_DATA_NONE; 101 102 /* 103 * Increment the number of vertical spans in a data cell above, 104 * if this cell vertically extends one or more cells above. 105 * The iteration must be done over data rows, 106 * not over layout rows, because one layout row 107 * can be reused for more than one data row. 108 */ 109 110 if (cp->pos == TBL_CELL_DOWN || 111 (*pos - startpos == 2 && 112 p[startpos] == '\\' && p[startpos + 1] == '^')) { 113 pdp = dp; 114 while ((pdp = pdp->prev) != NULL) { 115 pdat = pdp->first; 116 while (pdat != NULL && 117 pdat->layout->col < dat->layout->col) 118 pdat = pdat->next; 119 if (pdat == NULL) 120 break; 121 if (pdat->layout->pos != TBL_CELL_DOWN && 122 strcmp(pdat->string, "\\^") != 0) { 123 pdat->vspans++; 124 break; 125 } 126 } 127 } 128 129 /* 130 * Count the number of horizontal spans to the right of this cell. 131 * This is purely a matter of the layout, independent of the data. 132 */ 133 134 for (cp = cp->next; cp != NULL; cp = cp->next) 135 if (cp->pos == TBL_CELL_SPAN) 136 dat->hspans++; 137 else 138 break; 139 140 /* Append the new data cell to the data row. */ 141 142 if (dp->last == NULL) 143 dp->first = dat; 144 else 145 dp->last->next = dat; 146 dp->last = dat; 147 148 /* Strip leading and trailing spaces, if requested. */ 149 150 endpos = *pos; 151 if (dp->opts->opts & TBL_OPT_NOSPACE) { 152 while (p[startpos] == ' ') 153 startpos++; 154 while (endpos > startpos && p[endpos - 1] == ' ') 155 endpos--; 156 } 157 158 /* 159 * Check for a continued-data scope opening. This consists of a 160 * trailing `T{' at the end of the line. Subsequent lines, 161 * until a standalone `T}', are included in our cell. 162 */ 163 164 if (endpos - startpos == 2 && 165 p[startpos] == 'T' && p[startpos + 1] == '{') { 166 tbl->part = TBL_PART_CDATA; 167 return; 168 } 169 170 dat->string = mandoc_strndup(p + startpos, endpos - startpos); 171 172 if (p[*pos] != '\0') 173 (*pos)++; 174 175 if ( ! strcmp(dat->string, "_")) 176 dat->pos = TBL_DATA_HORIZ; 177 else if ( ! strcmp(dat->string, "=")) 178 dat->pos = TBL_DATA_DHORIZ; 179 else if ( ! strcmp(dat->string, "\\_")) 180 dat->pos = TBL_DATA_NHORIZ; 181 else if ( ! strcmp(dat->string, "\\=")) 182 dat->pos = TBL_DATA_NDHORIZ; 183 else 184 dat->pos = TBL_DATA_DATA; 185 186 if ((dat->layout->pos == TBL_CELL_HORIZ || 187 dat->layout->pos == TBL_CELL_DHORIZ || 188 dat->layout->pos == TBL_CELL_DOWN) && 189 dat->pos == TBL_DATA_DATA && *dat->string != '\0') 190 mandoc_msg(MANDOCERR_TBLDATA_SPAN, 191 ln, startpos, "%s", dat->string); 192} 193 194void 195tbl_cdata(struct tbl_node *tbl, int ln, const char *p, int pos) 196{ 197 struct tbl_dat *dat; 198 size_t sz; 199 200 dat = tbl->last_span->last; 201 202 if (p[pos] == 'T' && p[pos + 1] == '}') { 203 pos += 2; 204 if (tbl->opts.opts & TBL_OPT_NOSPACE) 205 while (p[pos] == ' ') 206 pos++; 207 if (p[pos] == tbl->opts.tab) { 208 tbl->part = TBL_PART_DATA; 209 pos++; 210 while (p[pos] != '\0') 211 getdata(tbl, tbl->last_span, ln, p, &pos); 212 return; 213 } else if (p[pos] == '\0') { 214 tbl->part = TBL_PART_DATA; 215 return; 216 } 217 218 /* Fallthrough: T} is part of a word. */ 219 } 220 221 dat->pos = TBL_DATA_DATA; 222 dat->block = 1; 223 224 if (dat->string != NULL) { 225 sz = strlen(p + pos) + strlen(dat->string) + 2; 226 dat->string = mandoc_realloc(dat->string, sz); 227 (void)strlcat(dat->string, " ", sz); 228 (void)strlcat(dat->string, p + pos, sz); 229 } else 230 dat->string = mandoc_strdup(p + pos); 231 232 if (dat->layout->pos == TBL_CELL_DOWN) 233 mandoc_msg(MANDOCERR_TBLDATA_SPAN, 234 ln, pos, "%s", dat->string); 235} 236 237static struct tbl_span * 238newspan(struct tbl_node *tbl, int line, struct tbl_row *rp) 239{ 240 struct tbl_span *dp; 241 242 dp = mandoc_calloc(1, sizeof(*dp)); 243 dp->line = line; 244 dp->opts = &tbl->opts; 245 dp->layout = rp; 246 dp->prev = tbl->last_span; 247 248 if (dp->prev == NULL) { 249 tbl->first_span = dp; 250 tbl->current_span = NULL; 251 } else 252 dp->prev->next = dp; 253 tbl->last_span = dp; 254 255 return dp; 256} 257 258void 259tbl_data(struct tbl_node *tbl, int ln, const char *p, int pos) 260{ 261 struct tbl_row *rp; 262 struct tbl_cell *cp; 263 struct tbl_span *sp; 264 265 for (sp = tbl->last_span; sp != NULL; sp = sp->prev) 266 if (sp->pos == TBL_SPAN_DATA) 267 break; 268 rp = sp == NULL ? tbl->first_row : 269 sp->layout->next == NULL ? sp->layout : sp->layout->next; 270 assert(rp != NULL); 271 272 if (p[1] == '\0') { 273 switch (p[0]) { 274 case '.': 275 /* 276 * Empty request lines must be handled here 277 * and cannot be discarded in roff_parseln() 278 * because in the layout section, they 279 * are significant and end the layout. 280 */ 281 return; 282 case '_': 283 sp = newspan(tbl, ln, rp); 284 sp->pos = TBL_SPAN_HORIZ; 285 return; 286 case '=': 287 sp = newspan(tbl, ln, rp); 288 sp->pos = TBL_SPAN_DHORIZ; 289 return; 290 default: 291 break; 292 } 293 } 294 295 /* 296 * If the layout row contains nothing but horizontal lines, 297 * allocate an empty span for it and assign the current span 298 * to the next layout row accepting data. 299 */ 300 301 while (rp->next != NULL) { 302 if (rp->last->col + 1 < tbl->opts.cols) 303 break; 304 for (cp = rp->first; cp != NULL; cp = cp->next) 305 if (cp->pos != TBL_CELL_HORIZ && 306 cp->pos != TBL_CELL_DHORIZ) 307 break; 308 if (cp != NULL) 309 break; 310 sp = newspan(tbl, ln, rp); 311 sp->pos = TBL_SPAN_DATA; 312 rp = rp->next; 313 } 314 315 /* Process a real data row. */ 316 317 sp = newspan(tbl, ln, rp); 318 sp->pos = TBL_SPAN_DATA; 319 while (p[pos] != '\0') 320 getdata(tbl, sp, ln, p, &pos); 321} 322