apropos-utils.c revision 1.5 1 /* $NetBSD: apropos-utils.c,v 1.5 2012/05/07 11:18:16 wiz Exp $ */
2 /*-
3 * Copyright (c) 2011 Abhinav Upadhyay <er.abhinav.upadhyay (at) gmail.com>
4 * All rights reserved.
5 *
6 * This code was developed as part of Google's Summer of Code 2011 program.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
17 * distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
25 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33 #include <sys/cdefs.h>
34 __RCSID("$NetBSD: apropos-utils.c,v 1.5 2012/05/07 11:18:16 wiz Exp $");
35
36 #include <sys/stat.h>
37
38 #include <assert.h>
39 #include <ctype.h>
40 #include <err.h>
41 #include <math.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <util.h>
46 #include <zlib.h>
47
48 #include "apropos-utils.h"
49 #include "mandoc.h"
50 #include "sqlite3.h"
51
52 typedef struct orig_callback_data {
53 void *data;
54 int (*callback) (void *, const char *, const char *, const char *,
55 const char *, size_t);
56 } orig_callback_data;
57
58 typedef struct inverse_document_frequency {
59 double value;
60 int status;
61 } inverse_document_frequency;
62
63 /* weights for individual columns */
64 static const double col_weights[] = {
65 2.0, // NAME
66 2.00, // Name-description
67 0.55, // DESCRIPTION
68 0.10, // LIBRARY
69 0.001, //RETURN VALUES
70 0.20, //ENVIRONMENT
71 0.01, //FILES
72 0.001, //EXIT STATUS
73 2.00, //DIAGNOSTICS
74 0.05, //ERRORS
75 0.00, //md5_hash
76 1.00 //machine
77 };
78
79 /*
80 * lower --
81 * Converts the string str to lower case
82 */
83 char *
84 lower(char *str)
85 {
86 assert(str);
87 int i = 0;
88 char c;
89 while (str[i] != '\0') {
90 c = tolower((unsigned char) str[i]);
91 str[i++] = c;
92 }
93 return str;
94 }
95
96 /*
97 * concat--
98 * Utility function. Concatenates together: dst, a space character and src.
99 * dst + " " + src
100 */
101 void
102 concat(char **dst, const char *src)
103 {
104 concat2(dst, src, strlen(src));
105 }
106
107 void
108 concat2(char **dst, const char *src, size_t srclen)
109 {
110 size_t total_len, dst_len;
111 assert(src != NULL);
112
113 /* If destination buffer dst is NULL, then simply strdup the source buffer */
114 if (*dst == NULL) {
115 *dst = estrdup(src);
116 return;
117 }
118
119 dst_len = strlen(*dst);
120 /*
121 * NUL Byte and separator space
122 */
123 total_len = dst_len + srclen + 2;
124
125 *dst = erealloc(*dst, total_len);
126
127 /* Append a space at the end of dst */
128 (*dst)[dst_len++] = ' ';
129
130 /* Now, copy src at the end of dst */
131 memcpy(*dst + dst_len, src, srclen + 1);
132 }
133
134 void
135 close_db(sqlite3 *db)
136 {
137 sqlite3_close(db);
138 sqlite3_shutdown();
139 }
140
141 /*
142 * create_db --
143 * Creates the database schema.
144 */
145 static int
146 create_db(sqlite3 *db)
147 {
148 const char *sqlstr = NULL;
149 char *schemasql;
150 char *errmsg = NULL;
151
152 /*------------------------ Create the tables------------------------------*/
153
154 #if NOTYET
155 sqlite3_exec(db, "PRAGMA journal_mode = WAL", NULL, NULL, NULL);
156 #else
157 sqlite3_exec(db, "PRAGMA journal_mode = DELETE", NULL, NULL, NULL);
158 #endif
159
160 schemasql = sqlite3_mprintf("PRAGMA user_version = %d",
161 APROPOS_SCHEMA_VERSION);
162 sqlite3_exec(db, schemasql, NULL, NULL, &errmsg);
163 if (errmsg != NULL)
164 goto out;
165 sqlite3_free(schemasql);
166
167 sqlstr = "CREATE VIRTUAL TABLE mandb USING fts4(section, name, "
168 "name_desc, desc, lib, return_vals, env, files, "
169 "exit_status, diagnostics, errors, md5_hash UNIQUE, machine, "
170 "compress=zip, uncompress=unzip, tokenize=porter); " //mandb
171 "CREATE TABLE IF NOT EXISTS mandb_meta(device, inode, mtime, "
172 "file UNIQUE, md5_hash UNIQUE, id INTEGER PRIMARY KEY); "
173 //mandb_meta
174 "CREATE TABLE IF NOT EXISTS mandb_links(link, target, section, "
175 "machine, md5_hash); "; //mandb_links
176
177 sqlite3_exec(db, sqlstr, NULL, NULL, &errmsg);
178 if (errmsg != NULL)
179 goto out;
180
181 sqlstr = "CREATE INDEX IF NOT EXISTS index_mandb_links ON mandb_links "
182 "(link); "
183 "CREATE INDEX IF NOT EXISTS index_mandb_meta_dev ON mandb_meta "
184 "(device, inode); "
185 "CREATE INDEX IF NOT EXISTS index_mandb_links_md5 ON mandb_links "
186 "(md5_hash);";
187 sqlite3_exec(db, sqlstr, NULL, NULL, &errmsg);
188 if (errmsg != NULL)
189 goto out;
190 return 0;
191
192 out:
193 warnx("%s", errmsg);
194 free(errmsg);
195 sqlite3_close(db);
196 sqlite3_shutdown();
197 return -1;
198 }
199
200 /*
201 * zip --
202 * User defined Sqlite function to compress the FTS table
203 */
204 static void
205 zip(sqlite3_context *pctx, int nval, sqlite3_value **apval)
206 {
207 int nin;
208 long int nout;
209 const unsigned char * inbuf;
210 unsigned char *outbuf;
211
212 assert(nval == 1);
213 nin = sqlite3_value_bytes(apval[0]);
214 inbuf = (const unsigned char *) sqlite3_value_blob(apval[0]);
215 nout = nin + 13 + (nin + 999) / 1000;
216 outbuf = emalloc(nout);
217 compress(outbuf, (unsigned long *) &nout, inbuf, nin);
218 sqlite3_result_blob(pctx, outbuf, nout, free);
219 }
220
221 /*
222 * unzip --
223 * User defined Sqlite function to uncompress the FTS table.
224 */
225 static void
226 unzip(sqlite3_context *pctx, int nval, sqlite3_value **apval)
227 {
228 unsigned int rc;
229 unsigned char *outbuf;
230 z_stream stream;
231
232 assert(nval == 1);
233 stream.next_in = __UNCONST(sqlite3_value_blob(apval[0]));
234 stream.avail_in = sqlite3_value_bytes(apval[0]);
235 stream.avail_out = stream.avail_in * 2 + 100;
236 stream.next_out = outbuf = emalloc(stream.avail_out);
237 stream.zalloc = NULL;
238 stream.zfree = NULL;
239
240 if (inflateInit(&stream) != Z_OK) {
241 free(outbuf);
242 return;
243 }
244
245 while ((rc = inflate(&stream, Z_SYNC_FLUSH)) != Z_STREAM_END) {
246 if (rc != Z_OK ||
247 (stream.avail_out != 0 && stream.avail_in == 0)) {
248 free(outbuf);
249 return;
250 }
251 outbuf = erealloc(outbuf, stream.total_out * 2);
252 stream.next_out = outbuf + stream.total_out;
253 stream.avail_out = stream.total_out;
254 }
255 if (inflateEnd(&stream) != Z_OK) {
256 free(outbuf);
257 return;
258 }
259 outbuf = erealloc(outbuf, stream.total_out);
260 sqlite3_result_text(pctx, (const char *) outbuf, stream.total_out, free);
261 }
262
263 /* init_db --
264 * Prepare the database. Register the compress/uncompress functions and the
265 * stopword tokenizer.
266 * db_flag specifies the mode in which to open the database. 3 options are
267 * available:
268 * 1. DB_READONLY: Open in READONLY mode. An error if db does not exist.
269 * 2. DB_READWRITE: Open in read-write mode. An error if db does not exist.
270 * 3. DB_CREATE: Open in read-write mode. It will try to create the db if
271 * it does not exist already.
272 * RETURN VALUES:
273 * The function will return NULL in case the db does not exist and DB_CREATE
274 * was not specified. And in case DB_CREATE was specified and yet NULL is
275 * returned, then there was some other error.
276 * In normal cases the function should return a handle to the db.
277 */
278 sqlite3 *
279 init_db(int db_flag)
280 {
281 sqlite3 *db = NULL;
282 sqlite3_stmt *stmt;
283 struct stat sb;
284 int rc;
285 int create_db_flag = 0;
286
287 /* Check if the database exists or not */
288 if (!(stat(DBPATH, &sb) == 0 && S_ISREG(sb.st_mode))) {
289 /* Database does not exist, check if DB_CREATE was specified, and set
290 * flag to create the database schema
291 */
292 if (db_flag != (MANDB_CREATE)) {
293 warnx("Missing apropos database. "
294 "Please run makemandb to create it.");
295 return NULL;
296 }
297 create_db_flag = 1;
298 }
299
300 /* Now initialize the database connection */
301 sqlite3_initialize();
302 rc = sqlite3_open_v2(DBPATH, &db, db_flag, NULL);
303
304 if (rc != SQLITE_OK) {
305 warnx("%s", sqlite3_errmsg(db));
306 sqlite3_shutdown();
307 return NULL;
308 }
309
310 if (create_db_flag && create_db(db) < 0) {
311 warnx("%s", "Unable to create database schema");
312 goto error;
313 }
314
315 rc = sqlite3_prepare_v2(db, "PRAGMA user_version", -1, &stmt, NULL);
316 if (rc != SQLITE_OK) {
317 warnx("Unable to query schema version: %s",
318 sqlite3_errmsg(db));
319 goto error;
320 }
321 if (sqlite3_step(stmt) != SQLITE_ROW) {
322 sqlite3_finalize(stmt);
323 warnx("Unable to query schema version: %s",
324 sqlite3_errmsg(db));
325 goto error;
326 }
327 if (sqlite3_column_int(stmt, 0) != APROPOS_SCHEMA_VERSION) {
328 sqlite3_finalize(stmt);
329 warnx("Incorrect schema version found. "
330 "Please run makemandb -f.");
331 goto error;
332 }
333 sqlite3_finalize(stmt);
334
335 sqlite3_extended_result_codes(db, 1);
336
337 /* Register the zip and unzip functions for FTS compression */
338 rc = sqlite3_create_function(db, "zip", 1, SQLITE_ANY, NULL, zip, NULL, NULL);
339 if (rc != SQLITE_OK) {
340 warnx("Unable to register function: compress: %s",
341 sqlite3_errmsg(db));
342 goto error;
343 }
344
345 rc = sqlite3_create_function(db, "unzip", 1, SQLITE_ANY, NULL,
346 unzip, NULL, NULL);
347 if (rc != SQLITE_OK) {
348 warnx("Unable to register function: uncompress: %s",
349 sqlite3_errmsg(db));
350 goto error;
351 }
352 return db;
353 error:
354 sqlite3_close(db);
355 sqlite3_shutdown();
356 return NULL;
357 }
358
359 /*
360 * rank_func --
361 * Sqlite user defined function for ranking the documents.
362 * For each phrase of the query, it computes the tf and idf and adds them over.
363 * It computes the final rank, by multiplying tf and idf together.
364 * Weight of term t for document d = (term frequency of t in d *
365 * inverse document frequency of t)
366 *
367 * Term Frequency of term t in document d = Number of times t occurs in d /
368 * Number of times t appears in all
369 * documents
370 *
371 * Inverse document frequency of t = log(Total number of documents /
372 * Number of documents in which t occurs)
373 */
374 static void
375 rank_func(sqlite3_context *pctx, int nval, sqlite3_value **apval)
376 {
377 inverse_document_frequency *idf = sqlite3_user_data(pctx);
378 double tf = 0.0;
379 const unsigned int *matchinfo;
380 int ncol;
381 int nphrase;
382 int iphrase;
383 int ndoc;
384 int doclen = 0;
385 const double k = 3.75;
386 /* Check that the number of arguments passed to this function is correct. */
387 assert(nval == 1);
388
389 matchinfo = (const unsigned int *) sqlite3_value_blob(apval[0]);
390 nphrase = matchinfo[0];
391 ncol = matchinfo[1];
392 ndoc = matchinfo[2 + 3 * ncol * nphrase + ncol];
393 for (iphrase = 0; iphrase < nphrase; iphrase++) {
394 int icol;
395 const unsigned int *phraseinfo = &matchinfo[2 + ncol+ iphrase * ncol * 3];
396 for(icol = 1; icol < ncol; icol++) {
397
398 /* nhitcount: number of times the current phrase occurs in the current
399 * column in the current document.
400 * nglobalhitcount: number of times current phrase occurs in the current
401 * column in all documents.
402 * ndocshitcount: number of documents in which the current phrase
403 * occurs in the current column at least once.
404 */
405 int nhitcount = phraseinfo[3 * icol];
406 int nglobalhitcount = phraseinfo[3 * icol + 1];
407 int ndocshitcount = phraseinfo[3 * icol + 2];
408 doclen = matchinfo[2 + icol ];
409 double weight = col_weights[icol - 1];
410 if (idf->status == 0 && ndocshitcount)
411 idf->value += log(((double)ndoc / ndocshitcount))* weight;
412
413 /* Dividing the tf by document length to normalize the effect of
414 * longer documents.
415 */
416 if (nglobalhitcount > 0 && nhitcount)
417 tf += (((double)nhitcount * weight) / (nglobalhitcount * doclen));
418 }
419 }
420 idf->status = 1;
421
422 /* Final score = (tf * idf)/ ( k + tf)
423 * Dividing by k+ tf further normalizes the weight leading to better
424 * results.
425 * The value of k is experimental
426 */
427 double score = (tf * idf->value/ ( k + tf)) ;
428 sqlite3_result_double(pctx, score);
429 return;
430 }
431
432 /*
433 * run_query --
434 * Performs the searches for the keywords entered by the user.
435 * The 2nd param: snippet_args is an array of strings providing values for the
436 * last three parameters to the snippet function of sqlite. (Look at the docs).
437 * The 3rd param: args contains rest of the search parameters. Look at
438 * arpopos-utils.h for the description of individual fields.
439 *
440 */
441 int
442 run_query(sqlite3 *db, const char *snippet_args[3], query_args *args)
443 {
444 const char *default_snippet_args[3];
445 char *section_clause = NULL;
446 char *limit_clause = NULL;
447 char *machine_clause = NULL;
448 char *query;
449 const char *section;
450 char *name;
451 const char *name_desc;
452 const char *machine;
453 const char *snippet;
454 const char *name_temp;
455 char *slash_ptr;
456 char *m = NULL;
457 int rc;
458 inverse_document_frequency idf = {0, 0};
459 sqlite3_stmt *stmt;
460
461 if (args->machine)
462 easprintf(&machine_clause, "AND machine = \'%s\' ", args->machine);
463
464 /* Register the rank function */
465 rc = sqlite3_create_function(db, "rank_func", 1, SQLITE_ANY, (void *)&idf,
466 rank_func, NULL, NULL);
467 if (rc != SQLITE_OK) {
468 warnx("Unable to register the ranking function: %s",
469 sqlite3_errmsg(db));
470 sqlite3_close(db);
471 sqlite3_shutdown();
472 exit(EXIT_FAILURE);
473 }
474
475 /* We want to build a query of the form: "select x,y,z from mandb where
476 * mandb match :query [AND (section LIKE '1' OR section LIKE '2' OR...)]
477 * ORDER BY rank DESC..."
478 * NOTES: 1. The portion in square brackets is optional, it will be there
479 * only if the user has specified an option on the command line to search in
480 * one or more specific sections.
481 * 2. I am using LIKE operator because '=' or IN operators do not seem to be
482 * working with the compression option enabled.
483 */
484
485 if (args->sec_nums) {
486 char *temp;
487 int i;
488
489 for (i = 0; i < SECMAX; i++) {
490 if (args->sec_nums[i] == 0)
491 continue;
492 easprintf(&temp, " OR section = \'%d\'", i + 1);
493 if (section_clause) {
494 concat(§ion_clause, temp);
495 free(temp);
496 } else {
497 section_clause = temp;
498 }
499 }
500 if (section_clause) {
501 /*
502 * At least one section requested, add glue for query.
503 */
504 temp = section_clause;
505 /* Skip " OR " before first term. */
506 easprintf(§ion_clause, " AND (%s)", temp + 4);
507 free(temp);
508 }
509 }
510 if (args->nrec >= 0) {
511 /* Use the provided number of records and offset */
512 easprintf(&limit_clause, " LIMIT %d OFFSET %d",
513 args->nrec, args->offset);
514 }
515
516 if (snippet_args == NULL) {
517 default_snippet_args[0] = "";
518 default_snippet_args[1] = "";
519 default_snippet_args[2] = "...";
520 snippet_args = default_snippet_args;
521 }
522 query = sqlite3_mprintf("SELECT section, name, name_desc, machine,"
523 " snippet(mandb, %Q, %Q, %Q, -1, 40 ),"
524 " rank_func(matchinfo(mandb, \"pclxn\")) AS rank"
525 " FROM mandb"
526 " WHERE mandb MATCH %Q %s "
527 "%s"
528 " ORDER BY rank DESC"
529 "%s",
530 snippet_args[0], snippet_args[1], snippet_args[2], args->search_str,
531 machine_clause ? machine_clause : "",
532 section_clause ? section_clause : "",
533 limit_clause ? limit_clause : "");
534
535 free(machine_clause);
536 free(section_clause);
537 free(limit_clause);
538
539 if (query == NULL) {
540 *args->errmsg = estrdup("malloc failed");
541 return -1;
542 }
543 rc = sqlite3_prepare_v2(db, query, -1, &stmt, NULL);
544 if (rc == SQLITE_IOERR) {
545 warnx("Corrupt database. Please rerun makemandb");
546 sqlite3_free(query);
547 return -1;
548 } else if (rc != SQLITE_OK) {
549 warnx("%s", sqlite3_errmsg(db));
550 sqlite3_free(query);
551 return -1;
552 }
553
554 while (sqlite3_step(stmt) == SQLITE_ROW) {
555 section = (const char *) sqlite3_column_text(stmt, 0);
556 name_temp = (const char *) sqlite3_column_text(stmt, 1);
557 name_desc = (const char *) sqlite3_column_text(stmt, 2);
558 machine = (const char *) sqlite3_column_text(stmt, 3);
559 snippet = (const char *) sqlite3_column_text(stmt, 4);
560 if ((slash_ptr = strrchr(name_temp, '/')) != NULL)
561 name_temp = slash_ptr + 1;
562 if (machine && machine[0]) {
563 m = estrdup(machine);
564 easprintf(&name, "%s/%s", lower(m),
565 name_temp);
566 free(m);
567 } else {
568 name = estrdup((const char *) sqlite3_column_text(stmt, 1));
569 }
570
571 (args->callback)(args->callback_data, section, name, name_desc, snippet,
572 strlen(snippet));
573
574 free(name);
575 }
576
577 sqlite3_finalize(stmt);
578 sqlite3_free(query);
579 return *(args->errmsg) == NULL ? 0 : -1;
580 }
581
582 /*
583 * callback_html --
584 * Callback function for run_query_html. It builds the html output and then
585 * calls the actual user supplied callback function.
586 */
587 static int
588 callback_html(void *data, const char *section, const char *name,
589 const char *name_desc, const char *snippet, size_t snippet_length)
590 {
591 const char *temp = snippet;
592 int i = 0;
593 size_t sz = 0;
594 int count = 0;
595 struct orig_callback_data *orig_data = (struct orig_callback_data *) data;
596 int (*callback) (void *, const char *, const char *, const char *,
597 const char *, size_t) = orig_data->callback;
598
599 /* First scan the snippet to find out the number of occurrences of {'>', '<'
600 * '"', '&'}.
601 * Then allocate a new buffer with sufficient space to be able to store the
602 * quoted versions of the special characters {>, <, ", &}.
603 * Copy over the characters from the original snippet to this buffer while
604 * replacing the special characters with their quoted versions.
605 */
606
607 while (*temp) {
608 sz = strcspn(temp, "<>\"&\002\003");
609 temp += sz + 1;
610 count++;
611 }
612 size_t qsnippet_length = snippet_length + count * 5;
613 char *qsnippet = emalloc(qsnippet_length + 1);
614 sz = 0;
615 while (*snippet) {
616 sz = strcspn(snippet, "<>\"&\002\003");
617 if (sz) {
618 memcpy(&qsnippet[i], snippet, sz);
619 snippet += sz;
620 i += sz;
621 }
622
623 switch (*snippet++) {
624 case '<':
625 memcpy(&qsnippet[i], "<", 4);
626 i += 4;
627 break;
628 case '>':
629 memcpy(&qsnippet[i], ">", 4);
630 i += 4;
631 break;
632 case '\"':
633 memcpy(&qsnippet[i], """, 6);
634 i += 6;
635 break;
636 case '&':
637 /* Don't perform the quoting if this & is part of an mdoc escape
638 * sequence, e.g. \&
639 */
640 if (i && *(snippet - 2) != '\\') {
641 memcpy(&qsnippet[i], "&", 5);
642 i += 5;
643 } else {
644 qsnippet[i++] = '&';
645 }
646 break;
647 case '\002':
648 memcpy(&qsnippet[i], "<b>", 3);
649 i += 3;
650 break;
651 case '\003':
652 memcpy(&qsnippet[i], "</b>", 4);
653 i += 4;
654 break;
655 default:
656 break;
657 }
658 }
659 qsnippet[++i] = 0;
660 (*callback)(orig_data->data, section, name, name_desc,
661 (const char *)qsnippet, qsnippet_length);
662 free(qsnippet);
663 return 0;
664 }
665
666 /*
667 * run_query_html --
668 * Utility function to output query result in HTML format.
669 * It internally calls run_query only, but it first passes the output to it's
670 * own custom callback function, which preprocess the snippet for quoting
671 * inline HTML fragments.
672 * After that it delegates the call the actual user supplied callback function.
673 */
674 int
675 run_query_html(sqlite3 *db, query_args *args)
676 {
677 struct orig_callback_data orig_data;
678 orig_data.callback = args->callback;
679 orig_data.data = args->callback_data;
680 const char *snippet_args[] = {"\002", "\003", "..."};
681 args->callback = &callback_html;
682 args->callback_data = (void *) &orig_data;
683 return run_query(db, snippet_args, args);
684 }
685
686 /*
687 * callback_pager --
688 * A callback similar to callback_html. It overstrikes the matching text in
689 * the snippet so that it appears emboldened when viewed using a pager like
690 * more or less.
691 */
692 static int
693 callback_pager(void *data, const char *section, const char *name,
694 const char *name_desc, const char *snippet, size_t snippet_length)
695 {
696 struct orig_callback_data *orig_data = (struct orig_callback_data *) data;
697 char *psnippet;
698 const char *temp = snippet;
699 int count = 0;
700 int i = 0;
701 size_t sz = 0;
702 size_t psnippet_length;
703
704 /* Count the number of bytes of matching text. For each of these bytes we
705 * will use 2 extra bytes to overstrike it so that it appears bold when
706 * viewed using a pager.
707 */
708 while (*temp) {
709 sz = strcspn(temp, "\002\003");
710 temp += sz;
711 if (*temp == '\003') {
712 count += 2 * (sz);
713 }
714 temp++;
715 }
716
717 psnippet_length = snippet_length + count;
718 psnippet = emalloc(psnippet_length + 1);
719
720 /* Copy the bytes from snippet to psnippet:
721 * 1. Copy the bytes before \002 as it is.
722 * 2. The bytes after \002 need to be overstriked till we encounter \003.
723 * 3. To overstrike a byte 'A' we need to write 'A\bA'
724 */
725 while (*snippet) {
726 sz = strcspn(snippet, "\002");
727 memcpy(&psnippet[i], snippet, sz);
728 snippet += sz;
729 i += sz;
730
731 /* Don't change this. Advancing the pointer without reading the byte
732 * is causing strange behavior.
733 */
734 if (*snippet == '\002')
735 snippet++;
736 while (*snippet && *snippet != '\003') {
737 psnippet[i++] = *snippet;
738 psnippet[i++] = '\b';
739 psnippet[i++] = *snippet++;
740 }
741 if (*snippet)
742 snippet++;
743 }
744
745 psnippet[i] = 0;
746 (orig_data->callback)(orig_data->data, section, name, name_desc, psnippet,
747 psnippet_length);
748 free(psnippet);
749 return 0;
750 }
751
752 /*
753 * run_query_pager --
754 * Utility function similar to run_query_html. This function tries to
755 * pre-process the result assuming it will be piped to a pager.
756 * For this purpose it first calls it's own callback function callback_pager
757 * which then delegates the call to the user supplied callback.
758 */
759 int run_query_pager(sqlite3 *db, query_args *args)
760 {
761 struct orig_callback_data orig_data;
762 orig_data.callback = args->callback;
763 orig_data.data = args->callback_data;
764 const char *snippet_args[] = {"\002", "\003", "..."};
765 args->callback = &callback_pager;
766 args->callback_data = (void *) &orig_data;
767 return run_query(db, snippet_args, args);
768 }
769