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