1 /* $OpenBSD: tbl_data.c,v 1.43 2021/08/10 12:36:42 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 34 static void getdata(struct tbl_node *, struct tbl_span *, 35 int, const char *, int *); 36 static struct tbl_span *newspan(struct tbl_node *, int, 37 struct tbl_row *); 38 39 40 static void 41 getdata(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 sv; 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 sv = *pos; 56 ccp = p + sv; 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, sv, "%s", p + sv); 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 - sv == 2 && p[sv] == '\\' && p[sv + 1] == '^')) { 112 pdp = dp; 113 while ((pdp = pdp->prev) != NULL) { 114 pdat = pdp->first; 115 while (pdat != NULL && 116 pdat->layout->col < dat->layout->col) 117 pdat = pdat->next; 118 if (pdat == NULL) 119 break; 120 if (pdat->layout->pos != TBL_CELL_DOWN && 121 strcmp(pdat->string, "\\^") != 0) { 122 pdat->vspans++; 123 break; 124 } 125 } 126 } 127 128 /* 129 * Count the number of horizontal spans to the right of this cell. 130 * This is purely a matter of the layout, independent of the data. 131 */ 132 133 for (cp = cp->next; cp != NULL; cp = cp->next) 134 if (cp->pos == TBL_CELL_SPAN) 135 dat->hspans++; 136 else 137 break; 138 139 /* Append the new data cell to the data row. */ 140 141 if (dp->last == NULL) 142 dp->first = dat; 143 else 144 dp->last->next = dat; 145 dp->last = dat; 146 147 /* 148 * Check for a continued-data scope opening. This consists of a 149 * trailing `T{' at the end of the line. Subsequent lines, 150 * until a standalone `T}', are included in our cell. 151 */ 152 153 if (*pos - sv == 2 && p[sv] == 'T' && p[sv + 1] == '{') { 154 tbl->part = TBL_PART_CDATA; 155 return; 156 } 157 158 dat->string = mandoc_strndup(p + sv, *pos - sv); 159 160 if (p[*pos] != '\0') 161 (*pos)++; 162 163 if ( ! strcmp(dat->string, "_")) 164 dat->pos = TBL_DATA_HORIZ; 165 else if ( ! strcmp(dat->string, "=")) 166 dat->pos = TBL_DATA_DHORIZ; 167 else if ( ! strcmp(dat->string, "\\_")) 168 dat->pos = TBL_DATA_NHORIZ; 169 else if ( ! strcmp(dat->string, "\\=")) 170 dat->pos = TBL_DATA_NDHORIZ; 171 else 172 dat->pos = TBL_DATA_DATA; 173 174 if ((dat->layout->pos == TBL_CELL_HORIZ || 175 dat->layout->pos == TBL_CELL_DHORIZ || 176 dat->layout->pos == TBL_CELL_DOWN) && 177 dat->pos == TBL_DATA_DATA && *dat->string != '\0') 178 mandoc_msg(MANDOCERR_TBLDATA_SPAN, 179 ln, sv, "%s", dat->string); 180 } 181 182 void 183 tbl_cdata(struct tbl_node *tbl, int ln, const char *p, int pos) 184 { 185 struct tbl_dat *dat; 186 size_t sz; 187 188 dat = tbl->last_span->last; 189 190 if (p[pos] == 'T' && p[pos + 1] == '}') { 191 pos += 2; 192 if (p[pos] == tbl->opts.tab) { 193 tbl->part = TBL_PART_DATA; 194 pos++; 195 while (p[pos] != '\0') 196 getdata(tbl, tbl->last_span, ln, p, &pos); 197 return; 198 } else if (p[pos] == '\0') { 199 tbl->part = TBL_PART_DATA; 200 return; 201 } 202 203 /* Fallthrough: T} is part of a word. */ 204 } 205 206 dat->pos = TBL_DATA_DATA; 207 dat->block = 1; 208 209 if (dat->string != NULL) { 210 sz = strlen(p + pos) + strlen(dat->string) + 2; 211 dat->string = mandoc_realloc(dat->string, sz); 212 (void)strlcat(dat->string, " ", sz); 213 (void)strlcat(dat->string, p + pos, sz); 214 } else 215 dat->string = mandoc_strdup(p + pos); 216 217 if (dat->layout->pos == TBL_CELL_DOWN) 218 mandoc_msg(MANDOCERR_TBLDATA_SPAN, 219 ln, pos, "%s", dat->string); 220 } 221 222 static struct tbl_span * 223 newspan(struct tbl_node *tbl, int line, struct tbl_row *rp) 224 { 225 struct tbl_span *dp; 226 227 dp = mandoc_calloc(1, sizeof(*dp)); 228 dp->line = line; 229 dp->opts = &tbl->opts; 230 dp->layout = rp; 231 dp->prev = tbl->last_span; 232 233 if (dp->prev == NULL) { 234 tbl->first_span = dp; 235 tbl->current_span = NULL; 236 } else 237 dp->prev->next = dp; 238 tbl->last_span = dp; 239 240 return dp; 241 } 242 243 void 244 tbl_data(struct tbl_node *tbl, int ln, const char *p, int pos) 245 { 246 struct tbl_row *rp; 247 struct tbl_cell *cp; 248 struct tbl_span *sp; 249 250 for (sp = tbl->last_span; sp != NULL; sp = sp->prev) 251 if (sp->pos == TBL_SPAN_DATA) 252 break; 253 rp = sp == NULL ? tbl->first_row : 254 sp->layout->next == NULL ? sp->layout : sp->layout->next; 255 assert(rp != NULL); 256 257 if (p[1] == '\0') { 258 switch (p[0]) { 259 case '.': 260 /* 261 * Empty request lines must be handled here 262 * and cannot be discarded in roff_parseln() 263 * because in the layout section, they 264 * are significant and end the layout. 265 */ 266 return; 267 case '_': 268 sp = newspan(tbl, ln, rp); 269 sp->pos = TBL_SPAN_HORIZ; 270 return; 271 case '=': 272 sp = newspan(tbl, ln, rp); 273 sp->pos = TBL_SPAN_DHORIZ; 274 return; 275 default: 276 break; 277 } 278 } 279 280 /* 281 * If the layout row contains nothing but horizontal lines, 282 * allocate an empty span for it and assign the current span 283 * to the next layout row accepting data. 284 */ 285 286 while (rp->next != NULL) { 287 if (rp->last->col + 1 < tbl->opts.cols) 288 break; 289 for (cp = rp->first; cp != NULL; cp = cp->next) 290 if (cp->pos != TBL_CELL_HORIZ && 291 cp->pos != TBL_CELL_DHORIZ) 292 break; 293 if (cp != NULL) 294 break; 295 sp = newspan(tbl, ln, rp); 296 sp->pos = TBL_SPAN_DATA; 297 rp = rp->next; 298 } 299 300 /* Process a real data row. */ 301 302 sp = newspan(tbl, ln, rp); 303 sp->pos = TBL_SPAN_DATA; 304 while (p[pos] != '\0') 305 getdata(tbl, sp, ln, p, &pos); 306 } 307