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