fulltext.h (9713B)
1 /* 2 This file is part of libextractor. 3 Copyright (C) 2026 Christian Grothoff 4 5 libextractor is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 libextractor is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with libextractor; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 Boston, MA 02110-1301, USA. 19 */ 20 /** 21 * @file plugins/fulltext.h 22 * @brief helpers shared by the plugins implementing the "fulltext" option 23 * @author Christian Grothoff 24 * 25 * The full text is reported as a single #EXTRACTOR_METATYPE_FULL_TEXT 26 * item in UTF-8 with MIME type "text/plain". Within a paragraph, all 27 * whitespace is collapsed into a single space; paragraphs are separated 28 * by exactly "\n\n"; there is no leading or trailing whitespace. 29 */ 30 #ifndef FULLTEXT_H 31 #define FULLTEXT_H 32 33 #include "extractor.h" 34 #include <stdint.h> 35 36 #ifdef __cplusplus 37 extern "C" { 38 #endif 39 40 /** 41 * Largest full text we are willing to emit, in bytes (including the 42 * terminating 0). Must stay below MAX_META_DATA in 43 * src/main/extractor_ipc.h; longer text is truncated at a character 44 * boundary. 45 */ 46 #define EXTRACTOR_FULLTEXT_MAX (16 * 1024 * 1024) 47 48 49 /** 50 * Check whether the comma-separated plugin configuration @a config 51 * contains the token @a key (exactly, or followed by '=' or ':'). 52 * 53 * @param config plugin configuration, may be NULL 54 * @param key token to look for, e.g. "fulltext" 55 * @return NULL if @a key is absent; otherwise a pointer to the 56 * character after @a key in @a config (one of '\0', ',', '=' 57 * or ':') 58 */ 59 const char * 60 EXTRACTOR_config_find_ (const char *config, 61 const char *key); 62 63 64 /** 65 * Check whether the "fulltext" option is set in @a config. 66 * 67 * @param config plugin configuration, may be NULL 68 * @return 1 if full-text extraction was requested, 0 if not 69 */ 70 int 71 EXTRACTOR_fulltext_requested_ (const char *config); 72 73 74 /** 75 * A growing UTF-8 text buffer that normalizes whitespace and paragraph 76 * breaks as text is appended. 77 */ 78 struct EXTRACTOR_FullText 79 { 80 /** 81 * The text so far, 0-terminated if @e len > 0. 82 */ 83 char *buf; 84 85 /** 86 * Number of bytes in @e buf, excluding the terminating 0. 87 */ 88 size_t len; 89 90 /** 91 * Allocated size of @e buf. 92 */ 93 size_t cap; 94 95 /** 96 * 1 if a space must precede the next non-space text. 97 */ 98 int pending_space; 99 100 /** 101 * 1 if a paragraph break must precede the next non-space text. 102 */ 103 int pending_par; 104 105 /** 106 * 1 if we ran into #EXTRACTOR_FULLTEXT_MAX or out of memory; 107 * all further text is discarded. 108 */ 109 int full; 110 111 /** 112 * Extraction context to keep alive while we work, may be NULL; set 113 * with #EXTRACTOR_fulltext_set_context_(). 114 */ 115 struct EXTRACTOR_ExtractContext *ec; 116 117 /** 118 * Units of work done since the last keep-alive. 119 */ 120 unsigned int ticks; 121 }; 122 123 124 /** 125 * Let @a ft keep the host informed that the plugin is still working 126 * (out of process, a plugin that is silent for too long is presumed 127 * hung): every few thousand characters appended, or calls to 128 * #EXTRACTOR_fulltext_tick_(), `seek (cls, 0, SEEK_CUR)` is called 129 * on @a ec. 130 * 131 * @param ft buffer to modify 132 * @param ec extraction context of the plugin 133 */ 134 void 135 EXTRACTOR_fulltext_set_context_ (struct EXTRACTOR_FullText *ft, 136 struct EXTRACTOR_ExtractContext *ec); 137 138 139 /** 140 * Account for one unit of work that did not append text (e.g. a tag 141 * that was skipped), for the keep-alive. 142 * 143 * @param ft buffer being filled 144 */ 145 void 146 EXTRACTOR_fulltext_tick_ (struct EXTRACTOR_FullText *ft); 147 148 149 /** 150 * Initialize @a ft to the empty text. 151 * 152 * @param[out] ft buffer to initialize 153 */ 154 void 155 EXTRACTOR_fulltext_init_ (struct EXTRACTOR_FullText *ft); 156 157 158 /** 159 * Append @a len bytes of @a text. Invalid UTF-8 is replaced by U+FFFD, 160 * control characters are dropped, and any run of whitespace (including 161 * line breaks and U+00A0) becomes a single space. 162 * 163 * @param ft buffer to append to 164 * @param text text to append, need not be 0-terminated 165 * @param len number of bytes in @a text 166 */ 167 void 168 EXTRACTOR_fulltext_append_ (struct EXTRACTOR_FullText *ft, 169 const char *text, 170 size_t len); 171 172 173 /** 174 * Append a 0-terminated string. 175 * 176 * @param ft buffer to append to 177 * @param text 0-terminated text to append 178 */ 179 void 180 EXTRACTOR_fulltext_append_str_ (struct EXTRACTOR_FullText *ft, 181 const char *text); 182 183 184 /** 185 * Append a single Unicode code point, subject to the same 186 * normalization as #EXTRACTOR_fulltext_append_(). 187 * 188 * @param ft buffer to append to 189 * @param cp code point to append 190 */ 191 void 192 EXTRACTOR_fulltext_append_cp_ (struct EXTRACTOR_FullText *ft, 193 uint32_t cp); 194 195 196 /** 197 * Request a word break: the next text is separated from what came 198 * before by a single space (unless a paragraph break is pending). 199 * 200 * @param ft buffer to modify 201 */ 202 void 203 EXTRACTOR_fulltext_space_ (struct EXTRACTOR_FullText *ft); 204 205 206 /** 207 * End the current paragraph: the next text is preceded by "\n\n". 208 * Multiple calls without text in between have the same effect as one. 209 * 210 * @param ft buffer to modify 211 */ 212 void 213 EXTRACTOR_fulltext_paragraph_ (struct EXTRACTOR_FullText *ft); 214 215 216 /** 217 * Emit the text (if non-empty) as #EXTRACTOR_METATYPE_FULL_TEXT. 218 * 219 * @param ft buffer to emit 220 * @param ec extraction context 221 * @param plugin_name short name of the plugin 222 * @return return value of the `proc` callback (0 if nothing was emitted) 223 */ 224 int 225 EXTRACTOR_fulltext_emit_ (const struct EXTRACTOR_FullText *ft, 226 struct EXTRACTOR_ExtractContext *ec, 227 const char *plugin_name); 228 229 230 /** 231 * Release the memory held by @a ft and reset it to the empty text. 232 * 233 * @param ft buffer to clean up 234 */ 235 void 236 EXTRACTOR_fulltext_clear_ (struct EXTRACTOR_FullText *ft); 237 238 239 /** 240 * Check whether @a line (already whitespace-trimmed or not) looks like 241 * a page number or page label, e.g. "12", "- 12 -", "xiv", "Page 3", 242 * "3 of 17", "[4]", "Seite 7". 243 * 244 * @param line text to check 245 * @param len number of bytes in @a line 246 * @return 1 if @a line looks like a page number, 0 if not 247 */ 248 int 249 EXTRACTOR_fulltext_is_page_number_ (const char *line, 250 size_t len); 251 252 253 /** 254 * One line of text on a page, as collected by a plugin for paged 255 * formats (PDF, DVI) before margin filtering. 256 */ 257 struct EXTRACTOR_FullTextLine 258 { 259 /** 260 * UTF-8 text of the line, 0-terminated, owned by the page. 261 */ 262 char *text; 263 264 /** 265 * Vertical position of the line's baseline, relative to the page 266 * height, in [0,1] with 0 at the top. Negative if unknown. 267 */ 268 double y; 269 270 /** 271 * 1 if this line ends a paragraph (the plugin decided that from the 272 * layout, e.g. vertical gap, indentation, short last line). 273 */ 274 int par_end; 275 276 /** 277 * Set by #EXTRACTOR_fulltext_filter_margins_() if the line is page 278 * furniture (page number, running header/footer) and must be skipped. 279 */ 280 int skip; 281 }; 282 283 284 /** 285 * All lines of one page, in reading order. 286 */ 287 struct EXTRACTOR_FullTextPage 288 { 289 /** 290 * Lines of the page. 291 */ 292 struct EXTRACTOR_FullTextLine *lines; 293 294 /** 295 * Number of entries in @e lines. 296 */ 297 unsigned int num_lines; 298 299 /** 300 * Allocated size of @e lines. 301 */ 302 unsigned int cap_lines; 303 }; 304 305 306 /** 307 * Append a line to @a page. @a text is copied. 308 * 309 * @param page page to append to 310 * @param text UTF-8 text of the line 311 * @param len number of bytes in @a text 312 * @param y relative vertical position in [0,1], or negative if unknown 313 * @param par_end 1 if the line ends a paragraph 314 * @return 0 on success, -1 on allocation failure 315 */ 316 int 317 EXTRACTOR_fulltext_page_add_ (struct EXTRACTOR_FullTextPage *page, 318 const char *text, 319 size_t len, 320 double y, 321 int par_end); 322 323 324 /** 325 * Mark page furniture in @a pages as `skip`: the first/last lines of 326 * a page within the top/bottom margin band that look like page numbers, 327 * or whose text (with digits ignored) recurs in the same band on at 328 * least half of the pages (when there are at least 3 pages). 329 * 330 * @param pages array of pages 331 * @param num_pages number of entries in @a pages 332 */ 333 void 334 EXTRACTOR_fulltext_filter_margins_ (struct EXTRACTOR_FullTextPage *pages, 335 unsigned int num_pages); 336 337 338 /** 339 * Append all non-skipped lines of @a pages to @a ft. Lines are joined 340 * with a space (words hyphenated across a line break are re-joined); 341 * `par_end` lines end a paragraph. A paragraph continues across page 342 * boundaries unless the last line of the page has `par_end`. 343 * 344 * @param ft buffer to append to 345 * @param pages array of pages 346 * @param num_pages number of entries in @a pages 347 */ 348 void 349 EXTRACTOR_fulltext_append_pages_ (struct EXTRACTOR_FullText *ft, 350 const struct EXTRACTOR_FullTextPage *pages, 351 unsigned int num_pages); 352 353 354 /** 355 * Release the lines of @a page. 356 * 357 * @param page page to clean up 358 */ 359 void 360 EXTRACTOR_fulltext_page_clear_ (struct EXTRACTOR_FullTextPage *page); 361 362 #ifdef __cplusplus 363 } 364 #endif 365 366 #endif