home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / misc / volume2 / brwsr < prev    next >
Internet Message Format  |  1991-08-07  |  50KB

  1. From: science@nems.ARPA (Mark Zimmermann)
  2. Newsgroups: comp.sources.misc
  3. Subject: v02i042: brwsr - browse text files using inverted index
  4. Message-ID: <7177@ncoast.UUCP>
  5. Date: 3 Feb 88 02:12:45 GMT
  6. Approved: allbery@ncoast.UUCP
  7.  
  8. Comp.sources.misc: Volume 2, Issue 42
  9. Submitted-By: "Mark Zimmerman" <science@nems.ARPA>
  10. Archive-Name: brwsr
  11.  
  12. Following C code uses the index files built by qndxr.c and lets you move
  13. around in them ... view occurrences of words in context, retrieve full
  14. text, do simple proximity searching, etc. ... seems to work on Suns,
  15. VAXen, and Macintoshes ... heavily commented ... ^z
  16.  
  17. /* transportable version of the browser program -- 871008 ^z
  18.  * version 0.11 -- with fixes to allow for non-ASCII characters
  19.  * in the document/database file....
  20.  *
  21.  * This program lets a user browse through indices created by
  22.  * the qndxr program....
  23.  *
  24.  * Contact Mark Zimmermann, 9511 Gwyndale Drive, Silver Spring, MD  20910
  25.  * ("science (at) nems.arpa, [75066,2044] on CompuServe, 301-565-2166
  26.  * for further information....
  27.  */
  28.  
  29.  
  30. /* commands:
  31.  *
  32.  *    ?            print out helpful info
  33.  *    :            quit program
  34.  *    :fnord       open document file 'fnord' for browsing
  35.  *    >grok        append copy of future output to notes file 'grok'
  36.  *    >            end copying to notes file
  37.  *    'DON.MAC     append comment string 'DON.MAC' to notes file
  38.  *    xyzzy        jump to INDEX item 'XYZZY'
  39.  *    ".012345     take string '.012345' as target for jump
  40.  *    -17          back up 17 lines from here
  41.  *    -            back up one line; same as '-1' command
  42.  *    +23          jump down 23 lines from here
  43.  *    + or <ret>   move down one line; same as '+1' command
  44.  *    .29          print 29 lines from here
  45.  *    =            descend:  INDEX -> CONTEXT -> TEXT display
  46.  *    ^            ascend:  TEXT -> CONTEXT -> INDEX display
  47.  *    *            empty 'working subset' proximity filter (nothing valid)
  48.  *    **           fill 'working subset' (everything valid)
  49.  *    &            add word-neighborhood of current index item to subset
  50.  *    &&           add sentence-neighborhood to subset
  51.  *    &&&          add paragraph-neighborhood to subset
  52.  *    ~            invert (logical NOT) entire working subset
  53.  *    ;            repeat previous command
  54.  *    
  55.  */
  56.  
  57.  
  58. #include <stdio.h>            /* for FILE, printf(), etc. */
  59. #include <strings.h>
  60. #include <ctype.h>
  61.  
  62. /* -----------------header stuff---------------- */
  63.  
  64. /* some definitions for the brwsr program...
  65.  * 870805-07-... ^z
  66.  */
  67.  
  68. /* tell the compiler that we are using Lightspeed C ... mostly so that
  69.  * certain sections of non-transportable code will be activated to
  70.  * compensate for the use of 16-bit int variables in LSC.... */
  71. /* #define LIGHTSPEED */
  72.  
  73. /* KEY_LENGTH is the number of letters we have in each record.... */
  74. #define KEY_LENGTH    28
  75.  
  76. /* CONTEXT_LINE_LENGTH is the total length of a key-word-in-context
  77.  * display line.... */
  78. #define CONTEXT_LINE_LENGTH  75
  79.  
  80. /* CONTEXT_OFFSET is the distance from the start of the context line
  81.  * to where the key word itself begins.... */
  82. #define CONTEXT_OFFSET  30
  83.  
  84. /* STRLEN is the most characters to allow in a line from the
  85.  * file at one time, and the maximum length of a command line.... */
  86. #define STRLEN  256
  87.  
  88. /* CMASK masks off characters to avoid negativity problems with funny
  89.  * non-ASCII symbols... */
  90. #define CMASK  0xFF
  91.  
  92. /* NEIGHBORHOOD is the distance in bytes that defines the proximity
  93.  * neighborhood of a word in the index ... used when defining a
  94.  * subset for proximity searching.... NEIGHBORHOOD * 8 is the
  95.  * compression factor for squeezing the document file down into the
  96.  * array subset[] ...
  97.  * Thus, NEIGHBORHOOD = 32, a good choice, defines a chunkiness of 32
  98.  * characters in making comparisons for proximity determination purposes....
  99.  */
  100. #define NEIGHBORHOOD  32
  101.  
  102. /* WORD_RANGE, SENTENCE_RANGE, and PARAGRAPH_RANGE define how many chunks
  103.  * of size NEIGHBORHOOD on each side of an item that the neighborhood of
  104.  * each type extends.  Values 1, 10, and 100 work pretty well....
  105.  */
  106. #define WORD_RANGE         1
  107. #define SENTENCE_RANGE    10
  108. #define PARAGRAPH_RANGE  100
  109.  
  110. /* define a value to help recognize long operations, for which
  111.  * the user should be warned of a likely delay in response ...
  112.  */
  113. #define GIVE_WARNING  200
  114.  
  115. /* structure of the records in the index key file:
  116.  *    a fixed-length character string kkey, filled out with blanks and
  117.  *        containing the unique alphanumeric 'words' in the document file,
  118.  *        changed to all-capital letters;
  119.  *    a cumulative count of how many total occurrences of words, including
  120.  *        the current one, have happened up to this point in the alphabetized
  121.  *        index.
  122.  *
  123.  *    Obviously, the number of occurrences of the nth word is just the
  124.  *        difference between the nth cumulative count and the (n-1)th
  125.  *        cumulative count.  Also, the (n-1)th cumulative count is a
  126.  *        pointer to the first occurrence of the nth word in the ptr_file
  127.  *        (which holds all the index offset pointers for the document file).
  128.  *
  129.  *    See the index building program "ndxr.c" for further details of the
  130.  *        index structure....
  131.  */
  132. typedef struct
  133.   {
  134.     char kkey[KEY_LENGTH];
  135.     long ccount;
  136.   }  KEY_REC;
  137.  
  138. /* the pointer records are simply long integers, offsets from the start
  139.  * of the document to the indexed words....*/
  140. #define PTR_REC  long
  141.  
  142. /* define the values for the three levels that the user may be browsing
  143.  * on:  INDEX is the display of unique words and their occurrence counts;
  144.  * CONTEXT is the key-word-in-context (KWIC) display; TEXT is the full
  145.  * text of the document.  */
  146. #define INDEX    0
  147. #define CONTEXT  1
  148. #define TEXT     2
  149.  
  150. /* symbolic values for yes/no answers... */
  151. #define TRUE  1
  152. #define FALSE 0
  153.  
  154. /* ---------------prototypes------------------- */
  155.  
  156. /* prototypes for my brwsr functions ...
  157.  * 870805...  ^z
  158.  */
  159.  
  160. #ifdef LIGHTSPEED
  161.  
  162. /* in file_utils.c */
  163. void open_files (char cmd[]);
  164. void close_files (void);
  165. void init_items (void);
  166.  
  167. /* in brwsr.c */
  168. void main (void);
  169.  
  170. /* in show_items.c */
  171. void show_current_item (void);
  172. void show_current_index_item (void);
  173. void show_current_context_item (void);
  174. void show_current_text_item (void);
  175.  
  176. /* in brws_utils.c */
  177. void get_key_rec (KEY_REC *recp, long n);
  178. PTR_REC get_ptr_rec (long n);
  179. long start_of_line (long p);
  180. long next_line (long p);
  181. void beep (void);
  182.  
  183. /* in do_help/files.c */
  184. void do_help (void);
  185. void do_open (char cmd[]);
  186. void do_redirection (char cmd[]);
  187. void do_comments (char cmd[]);
  188.  
  189. /* in do_moves_etc.c */
  190. void do_move (char cmd[]);
  191. int make_move (long n);
  192. int move_in_text (long move);
  193. int make_subindex_move (long move);
  194.  
  195. /* in do_targets.c */
  196. void do_descend (void);
  197. void do_ascend (void);
  198. void do_target_jump (char cmd[]);
  199. int zstrcmp (char *s1, char *s2);
  200. void init_target_item (KEY_REC *rp, char cmd[]);
  201. void do_multiprint (char cmd[]);
  202.  
  203. /* in do_subset.c */
  204. void do_make_subset (char cmd[]);
  205. void create_subset (void);
  206. void destroy_subset (void);
  207. void do_invert_subset (void);
  208. void do_add_neighborhood (char cmd[]);
  209. void set_neighborhood_bits (int n);
  210. void set_subset_bit (long offset);
  211. int get_subset_bit (long offset);
  212. long count_subset_recs (KEY_REC *this_rec, KEY_REC *prev_rec);
  213.  
  214. #endif
  215.  
  216. /* ------------------------main program-------------- */
  217.  
  218.  
  219. /* First, define a few external variables to hold:
  220.  *    - pointers to the document, key, pointer, and notes files;
  221.  *    - numbers indicating the current and the minimum/maximum values for:
  222.  *        INDEX item (one line for each unique word in the document);
  223.  *        CONTEXT item (one line for each occurrence of chosen INDEX word);
  224.  *        TEXT item (byte offset from start of file to current line).
  225.  *    - the actual KEY_RECs corresponding to the current INDEX item and
  226.  *        the INDEX item just before that one
  227.  *    - the subset array pointer used for proximity searching, the flag
  228.  *        empty_subset, and the count of how many current records are in
  229.  *        the working subset subset_rec_count
  230.  *    - the level of user operations:
  231.  *        0     means browsing the INDEX
  232.  *        1    means browsing the CONTEXT (key-word-in-context = KWIC)
  233.  *        2    means browsing the TEXT
  234.  *
  235.  * These items are referred to by various routines at scattered
  236.  * locations, and it seems easiest to let them reside in external
  237.  * variables....
  238.  */
  239.  
  240. FILE *doc_file = NULL, *key_file = NULL, *ptr_file = NULL,
  241.         *notes_file = NULL;
  242. long current_item[3] = {0, 0, 0};
  243. long min_item[3] = {0, 0, 0};
  244. long max_item[3] = {0, 0, 0};
  245. KEY_REC this_rec, prev_rec;
  246. char *subset = NULL;
  247. int empty_subset = TRUE;
  248. long subset_rec_count = 0;
  249. int level = INDEX;
  250.  
  251. void main()
  252.   {
  253.     char cmd[STRLEN], prevcmd[STRLEN], *gets(), *strcpy();
  254.     void do_help(), do_open(), do_redirection(), do_comments(), do_move(),
  255.         do_make_subset(), do_invert_subset(), do_add_neighborhood(),
  256.         do_descend(), do_ascend(), do_target_jump(), do_multiprint(),
  257.         close_files();
  258.     
  259.     prevcmd[0] = '\0';
  260.     printf ("Greetings, program! ... type '?' for help.\n");
  261.     printf ("Browser version 0.11 by ^z - 871007\n");
  262.     
  263.     while (gets (cmd))
  264.       {
  265.          do_cmd:
  266.          switch (cmd[0])
  267.           {
  268.             case '?':
  269.                   do_help ();
  270.                   break;
  271.             case '\0':
  272.                 strcpy (cmd, "+");
  273.                 /* <return> is just + */
  274.             case '+':
  275.                 /* + or - commands are both moves */
  276.             case '-':
  277.                 do_move (cmd);
  278.                 break;
  279.             case ':':
  280.                 do_open (cmd);
  281.                 break;
  282.             case '>':
  283.                 do_redirection (cmd);
  284.                 break;
  285.             case '\'':
  286.                 do_comments (cmd);
  287.                 break;
  288.             case '=':
  289.                 do_descend ();
  290.                 break;
  291.             case '^':
  292.                 do_ascend ();
  293.                 break;
  294.             case '.':
  295.                 do_multiprint (cmd);
  296.                 break;
  297.             case '*':
  298.                 do_make_subset (cmd);
  299.                 break;
  300.             case '&':
  301.                 do_add_neighborhood (cmd);
  302.                 break;
  303.             case '~':
  304.                 do_invert_subset ();
  305.                 break;
  306.             case ';':
  307.                 strcpy (cmd, prevcmd);
  308.                 goto do_cmd;
  309.                 break;
  310.             case '"':
  311.                 do_target_jump (cmd + 1);
  312.                 break;
  313.             default:
  314.                 do_target_jump (cmd);
  315.                 break;
  316.           }
  317.         strcpy (prevcmd, cmd);
  318.       }
  319.   }
  320.  
  321.  
  322. /* ---------------------brws_utils.c----------------- */
  323.  
  324. /* miscellaneous utility functions for helping the higher-level routines
  325.  * browse around in indexed files....
  326.  * 870805-13-...  ^z
  327.  */
  328.  
  329. /* function to get the 'n'th KEY_REC from key_file and store it in the
  330.  * KEY_REC space pointed to by recp ... note that if an illegal value
  331.  * of 'n' is requested, a 'null' KEY_REC is produced....
  332.  */
  333.  
  334. void get_key_rec (recp, n)
  335.   KEY_REC *recp;
  336.   register long n;
  337.   {
  338.     extern FILE *key_file;
  339.     extern long min_item[], max_item[];
  340.     char *strncpy();
  341.     void beep();
  342.     
  343.     if (n < min_item[INDEX] || n > max_item[INDEX])
  344.       {
  345.         strncpy ((char *)recp->kkey, "                    ",
  346.                     KEY_LENGTH);
  347.         recp->ccount = 0;
  348.         return;
  349.       }
  350.     
  351.     if (fseek (key_file, sizeof (KEY_REC) * n, 0) != 0)
  352.       {
  353.         beep ();
  354.         printf ("Fatal error in fseek() getting key record #%ld!\n", n);
  355.         exit();
  356.       }
  357.     
  358.     if (fread ((char *)recp, sizeof (KEY_REC), 1, key_file) == 0)
  359.       {
  360.         beep ();
  361.         printf ("Fatal error in fread() getting key record #%ld!\n", n);
  362.         exit();
  363.       }
  364.   }
  365.  
  366.  
  367. /* routine to get the nth pointer record (a long integer) from ptr_file;
  368.  * the pointer record gives the actual offset in the document file of
  369.  * the nth word in the expanded index....
  370.  */
  371.  
  372. PTR_REC get_ptr_rec (n)
  373.   register long n;
  374.   {
  375.     PTR_REC p;
  376.     extern FILE *ptr_file;
  377.     void beep();
  378.     
  379.     if (fseek (ptr_file, sizeof (PTR_REC) * n, 0) != 0)
  380.       {
  381.         beep ();
  382.         printf ("Fatal error in fseek() getting pointer record #%ld!\n", n);
  383.         exit();
  384.       }
  385.     
  386.     if (fread ((char *)&p, sizeof (PTR_REC), 1, ptr_file) == 0)
  387.       {
  388.         beep ();
  389.         printf ("Fatal error in fread() getting pointer record #%ld!\n", n);
  390.         exit();
  391.       }
  392.     
  393.     return (p);
  394.   }
  395.  
  396.  
  397. /* Move backwards in document file to find the start of the line of text
  398.  * which has offset p (from start of file) somewhere in that line.
  399.  * However, don't go back beyond STRLEN characters at a time... that
  400.  * is, put a ceiling of STRLEN on the maximum 'length of a line', to
  401.  * avoid pathological input files with no \n's....
  402.  * Do the work by grabbing a buffer full and then scanning back in that,
  403.  * rather than by laboriously lseek() and fgetc() back one character at
  404.  * a time....
  405.  */
  406.  
  407. long start_of_line (p)
  408.   register long p;
  409.   {
  410.     extern FILE *doc_file;
  411.     char buf[STRLEN];
  412.     register long i;
  413.     register int n;
  414.     void beep();
  415.  
  416.     if (p <= 0)
  417.         return (0L);
  418.  
  419.     i = p - STRLEN;
  420.     i = ((i < 0) ? 0 : i);
  421.     if (fseek (doc_file, i, 0) != 0)
  422.       {
  423.         beep ();
  424.         printf ("Fatal error in fseek() backing up in document!\n");
  425.         exit();
  426.       }
  427.     if ((n = fread (buf, sizeof (char), p - i, doc_file)) == 0)
  428.       {
  429.         beep ();
  430.         printf ("Fatal error in fread() backing up in document!\n");
  431.         exit();
  432.       }
  433.  
  434.     while (--n >= 0 && buf[n] != '\n');
  435.     
  436.     return (i + n + 1);
  437.   }
  438.  
  439.  
  440. /* scan forward until reaching the beginning of the next line of text;
  441.  * if at or beyond the end of the file, return a pointer to the last
  442.  * character in the file ...
  443.  * Don't go beyond STRLEN characters, however, in scanning forward....
  444.  */
  445.  
  446. long next_line (p)
  447.   long p;
  448.   {
  449.     register int c, n;
  450.     extern long max_item[];
  451.     void beep();
  452.     
  453.     if (p >= max_item[TEXT])
  454.         return (max_item[TEXT]);
  455.     
  456.     if (fseek (doc_file, p, 0) != 0)
  457.       {
  458.         beep ();
  459.         printf ("Fatal error in fseek() scanning forward in document!\n");
  460.         exit();
  461.       }
  462.     
  463.     for (n = 0; n < STRLEN; ++n)
  464.         if ((c = fgetc (doc_file)) == EOF || c == '\n')
  465.             break;
  466.     
  467.     return (ftell (doc_file));
  468.   }
  469.  
  470.  
  471. /* paging James Bond ....
  472.  */
  473.  
  474. void beep ()
  475.   {
  476.     putchar ('\007');
  477.   }
  478.  
  479.  
  480. /* ---------------------do_help/files.c---------------- */
  481.  
  482.  
  483.  
  484. /* function to print out helpful information -- a command summary
  485.  * for the user....
  486.  */
  487.  
  488. void do_help ()
  489.   {
  490.     printf ("  ?        print this help\n");
  491.     printf ("  :        quit the program\n");
  492.     printf ("  :fnord   open document file 'fnord' for browsing\n");
  493.     printf ("  >grok    open notes file 'grok' and append output\n");
  494.     printf ("  >        close notes file\n");
  495.     printf ("  'DON.MAC append comment 'DON.MAC' to notes file\n");
  496.     printf ("  xyzzy    jump to 'XYZZY' in INDEX\n");
  497.     printf ("  \".123    jump to '.123' in INDEX\n");
  498.     printf ("  -17      back up 17 lines\n");
  499.     printf ("  -        back up one line\n");
  500.     printf ("  +0       print current line\n");
  501.     printf (" <return>  move down one line\n");
  502.     printf ("  +23      move down 23 lines\n");
  503.     printf ("  =        INDEX --> CONTEXT --> TEXT\n");
  504.     printf ("  ^        INDEX <-- CONTEXT <-- TEXT\n");
  505.     printf ("  .9       move down and print out 9 lines\n");
  506.     printf ("  *        create an empty working subset\n");
  507.     printf ("  **       fill the working subset (complete database)\n");
  508.     printf ("  &        add word-neighborhood to subset\n");
  509.     printf ("  &&       add sentence-neighborhood to subset\n");
  510.     printf ("  &&&      add paragraph-neighborhood to subset\n");
  511.     printf ("  ~        invert (logical NOT) entire working subset\n");
  512.     printf ("  ;        repeat previous command\n");
  513.   }
  514.  
  515.  
  516. /* function to handle a ":" command, to either quit the program or to
  517.  * open a new database for browsing.... display some amusing data
  518.  * about the file that is opened, if successful (total length in bytes,
  519.  * total number of words, and number of unique words in the file)...
  520.  */
  521.  
  522. void do_open (cmd)
  523.   char cmd[];
  524.   {
  525.     char qcmd[STRLEN];
  526.     extern FILE *doc_file;
  527.     extern long max_item[];
  528.     void destroy_subset (), open_files (), init_items ();
  529.     
  530.     if (cmd[1] == '\0')
  531.       {
  532.         printf ("Quit?\n");
  533.         gets (qcmd);
  534.         if (qcmd[0] == 'y' || qcmd[0] == 'Y')
  535.           {
  536.             printf ("Good-bye ... ^z\n");
  537.             exit ();
  538.           }
  539.         else
  540.           {
  541.             printf ("Cancelled!\n");
  542.             return;
  543.           }
  544.       }
  545.     
  546.     destroy_subset ();
  547.     open_files (cmd);
  548.     if (doc_file == NULL)
  549.         return;
  550.     init_items ();
  551.     printf ("File \"%s\" contains:\n", cmd + 1);
  552.     printf ("%9ld characters\n%9ld total words\n%9ld unique words\n",
  553.         max_item[TEXT] + 1, max_item[CONTEXT] + 1, max_item[INDEX] + 1);
  554.   }
  555.  
  556.  
  557. /* function to open or close a notes file ... copies of browser
  558.  * program output gets appended to that notes file as long as it
  559.  * is open, and the user may add comment strings with the '"' command ....
  560.  */
  561.  
  562. void do_redirection (cmd)
  563.   char cmd[];
  564.   {
  565.      extern FILE *notes_file;
  566.      FILE *fopen ();
  567.      void beep();
  568.      
  569.     if (notes_file != NULL)
  570.          fclose (notes_file);
  571.      
  572.      if (cmd[1] == '\0')
  573.          return;
  574.  
  575.      if ((notes_file = fopen (cmd + 1, "a")) == NULL)
  576.        {
  577.         beep ();
  578.         printf ("Error opening notes file \"%s\"!\n", cmd + 1);
  579.         return;
  580.        }
  581.   }
  582.  
  583.  
  584. /* function to append a comment line to the notes_file... does the job
  585.  * for the '"' command....
  586.  */
  587.  
  588. void do_comments (cmd)
  589.   char cmd[];
  590.   {
  591.      extern FILE *notes_file;
  592.      void beep();
  593.      
  594.      if (notes_file == NULL)
  595.        {
  596.         beep ();
  597.         printf ("No notes file open!\n");
  598.         return;
  599.        }
  600.      else
  601.         fprintf (notes_file, "%s\n", cmd + 1);
  602.   }
  603.   
  604.  /* -------------------do_moves.c---------------------- */
  605.  
  606.  
  607.  
  608. /* function to interpret a command to move around in the current level
  609.  * of the browser display ... handles "+", "-", and "<return>" ... calls
  610.  * routine make_move() to do the real work....
  611.  */
  612.  
  613. void do_move (cmd)
  614.   char cmd[];
  615.   {
  616.     long move;
  617.     extern FILE *doc_file;
  618.     char *strcat();
  619.     void beep(), show_current_item();
  620.     
  621.     if (doc_file == NULL)
  622.       {
  623.         beep ();
  624.         printf ("No file open!\n");
  625.         return;
  626.       }
  627.     
  628.     if (cmd[1] == '\0')
  629.         strcat (cmd, "1");
  630.     move = atol (cmd);
  631.     make_move (move);
  632.     show_current_item ();
  633.   }
  634.  
  635.  
  636. /* function to do the actual moving around in the INDEX or CONTEXT or
  637.  * TEXT displays .... Safety features prevent user from going
  638.  * off the end of the file in either direction....
  639.  * make_move() returns FALSE if it fails to completely execute the move
  640.  * (i.e., if the move would have run off either end of the file) and
  641.  * beeps at the user ... it returns TRUE if the move succeeds....
  642.  */
  643.  
  644. int make_move (n)
  645.   long n;
  646.   {
  647.     int r;
  648.     PTR_REC get_ptr_rec ();
  649.     extern long current_item[], min_item[], max_item[];
  650.     extern int level;
  651.     extern char *subset;
  652.     void beep();
  653.     
  654.     r = TRUE;
  655.     switch (level)
  656.       {
  657.         case INDEX:
  658.             current_item[INDEX] += n;
  659.             if (current_item[INDEX] < min_item[INDEX])
  660.               {
  661.                 current_item[INDEX] = min_item[INDEX];
  662.                 r = FALSE;
  663.               }
  664.             if (current_item[INDEX] > max_item[INDEX])
  665.               {
  666.                 current_item[INDEX] = max_item[INDEX];
  667.                 r = FALSE;
  668.               }
  669.             break;
  670.  
  671.         case CONTEXT:
  672.             if (subset == NULL)
  673.               {
  674.                 current_item[CONTEXT] += n;
  675.                 if (current_item[CONTEXT] < min_item[CONTEXT])
  676.                   {
  677.                     current_item[CONTEXT] = min_item[CONTEXT];
  678.                     r = FALSE;
  679.                   }
  680.                 if (current_item[CONTEXT] > max_item[CONTEXT])
  681.                   {
  682.                     current_item[CONTEXT] = max_item[CONTEXT];
  683.                     r = FALSE;
  684.                   }
  685.               }
  686.             else
  687.                 r = make_subindex_move (n);
  688.  
  689.             current_item[TEXT] = get_ptr_rec (current_item[CONTEXT]);
  690.             break;
  691.  
  692.         case TEXT:
  693.             r = move_in_text (n);
  694.             break;
  695.       }
  696.     
  697.     if (r == FALSE)
  698.         beep ();
  699.     return (r);
  700.   }
  701.  
  702.  
  703. /* move up or down the chosen number of lines of text, and put the result
  704.  * into current_item[TEXT] ... return FALSE if attempted move would have
  705.  * run off the end of the file, non-zero if the move was completed without
  706.  * error....
  707.  */
  708.  
  709. int move_in_text (move)
  710.   register long move;
  711.   {
  712.     int result;
  713.     register long loc;
  714.     extern long current_item[];
  715.     
  716.     result = TRUE;
  717.     loc = current_item[TEXT];
  718.     
  719.     if (move < 0)
  720.         for ( ; move < 0; ++move)
  721.           {
  722.             if (loc <= 0)
  723.               {
  724.                 result = FALSE;
  725.                 break;
  726.               }
  727.             loc = start_of_line (loc - 1);
  728.           }
  729.             
  730.     else if (move > 0)
  731.         for ( ; move > 0; --move)
  732.           {
  733.             loc = next_line (loc);
  734.             if (loc >= max_item[TEXT])
  735.               {
  736.                 result = FALSE;
  737.                 loc = start_of_line (max_item[TEXT]);
  738.                 break;
  739.               }
  740.           }
  741.     
  742.     current_item[TEXT] = loc;
  743.     return (result);
  744.   }
  745.  
  746.  
  747. /* make a move in the working subindex ... must just step along in
  748.  * whichever direction is desired until either hitting the end (in
  749.  * which case the last valid item found in the subindex should be
  750.  * returned as the current one) or finishing the requested move.
  751.  * Return FALSE if hit a wall, TRUE otherwise....
  752.  *
  753.  * This routine is only called when level == CONTEXT ... since
  754.  * in TEXT level we are just moving around in the full text of
  755.  * the document file, and in the INDEX level we count and display
  756.  * even items with zero occurrences in the subindex...
  757.  *
  758.  * This routine is only called when subset != NULL, since if there
  759.  * is no working subset the move becomes trivial arithmetic.
  760.  *
  761.  * This routine is only called when either current_item[CONTEXT]
  762.  * is a good item in the subset already, or when there is a certainty
  763.  * that a good item will be found in the subsequent scan (specifically,
  764.  * when scanning down to find the first good item when called from
  765.  * do_descend () ....).
  766.  */
  767.  
  768. int make_subindex_move (move)
  769.   long move;
  770.   {
  771.     register long i, last_good_item;
  772.     int result;
  773.     PTR_REC get_ptr_rec ();
  774.     extern long current_item[], min_item[], max_item[];
  775.     extern char *subset;
  776.  
  777.     result = TRUE;
  778.     last_good_item = current_item[CONTEXT];
  779.  
  780.     if (move >= 0)
  781.       {
  782.         for (i = 0; i < move; ++i)
  783.           {
  784.             while (++current_item[CONTEXT] <= max_item[CONTEXT] &&
  785.                 ! get_subset_bit ((long)
  786.                     get_ptr_rec (current_item[CONTEXT])));
  787.             if (current_item[CONTEXT] > max_item[CONTEXT])
  788.                 break;
  789.             last_good_item = current_item[CONTEXT];
  790.           }
  791.         if (current_item[CONTEXT] > max_item[CONTEXT])
  792.           {
  793.             result = FALSE;
  794.             current_item[CONTEXT] = last_good_item;
  795.           }
  796.       }
  797.     else
  798.       {
  799.         for (i = 0; i > move; --i)
  800.           {
  801.             while (--current_item[CONTEXT] >= min_item[CONTEXT] &&
  802.                 ! get_subset_bit ((long)
  803.                     get_ptr_rec (current_item[CONTEXT])));
  804.             if (current_item[CONTEXT] < min_item[CONTEXT])
  805.                 break;
  806.             last_good_item = current_item[CONTEXT];
  807.           }
  808.         if (current_item[CONTEXT] < min_item[CONTEXT])
  809.           {
  810.             result = FALSE;
  811.             current_item[CONTEXT] = last_good_item;
  812.           }
  813.       }
  814.     return (result);
  815.   }
  816.   
  817.  /* -------------------do_subset.c------------------- */
  818.  
  819. /* words to handle subset (proximity filter) tasks....
  820.  * ^z -- 870810-13-....
  821.  *
  822.  * Subset (or subindex) restrictions are useful in doing searches for
  823.  * combinations of terms, phrases, etc., where the terms are themselves
  824.  * common words.  Subsets also become indispensible when working in
  825.  * very large databases, where even slightly-exotic terms occur too many
  826.  * times for convenient browsing through their CONTEXT displays.
  827.  *
  828.  * When a subset has been defined, then instead of showing an INDEX
  829.  * item as something like:
  830.  *      3141 UNIX
  831.  * that item is displayed with a count of how many occurrences are
  832.  * 'valid' in addition to the total number of occurrences:
  833.  *   27/3141 UNIX
  834.  * Thus, in this case only 27 out of the 3141 appearances of the term
  835.  * 'UNIX' happened to be in the neighborhood of the set of words chosen
  836.  * by the user to define the current working subset.
  837.  * 
  838.  * The 'neighborhood' (a half dozen words or so on each side) of a chosen
  839.  * index term is added (logical OR) into the working subset by giving
  840.  * the '&' command when that term's INDEX display is the current item.
  841.  * If a larger neighborhood (half a dozen sentences or so each way) is
  842.  * preferable, use the '&&' command; for a still larger neighborhood
  843.  * (a few paragraphs on each side), use the '&&&' command.
  844.  *
  845.  *
  846.  * IMPLEMENTATION NOTES:
  847.  *
  848.  * Subest searching is handled by an array of flag bits -- one bit
  849.  * for each chunk of NEIGHBORHOOD bytes in the original text.  Bits are
  850.  * held in the array subset[]; they are zero for 'invalid' regions
  851.  * of the original document, and one for 'valid' regions that have
  852.  * been defined by proximity to index terms selected by the user.
  853.  *
  854.  * A value of 32 for NEIGHBORHOOD seems to work very well for defining
  855.  * proximity.  The compression factor from the original document to the
  856.  * array subset[] is NEIGHBORHOOD*8 = 256 in that case.  So, even for a
  857.  * document that is tens of megabytes long, the subset[] array only
  858.  * requires a few hundred kB and it is quite feasible to hold it in
  859.  * memory.
  860.  *
  861.  * The presence of a subset is signalled when the global array pointer
  862.  * 'subset' is non-NULL ... in which case show_current_index_item,
  863.  * make_move, and other words modify their behavior in order to
  864.  * reflect the chosen subset.  If the subset is completely
  865.  * empty, after having just been created or flushed, then the
  866.  * global variable empty_subset is nonzero (true) and short-cuts
  867.  * are taken in counting and displaying words....
  868.  *
  869.  * Consider a single occurrence of a word.  Suppose we want to add the
  870.  * immediate neighborhood of that word to our working subset.  To avoid
  871.  * edge-effects due to the chunkiness of the definition of neighborhood,
  872.  * the routine to set bits in subset[] actually sets 2*WORD_RANGE+1 bits:
  873.  * the bit corresponding to the chunk wherein the chosen word falls,
  874.  * and WORD_RANGE extra bits on each side.
  875.  *
  876.  * Thus, for the choice WORD_RANGE = 1, any index item which is
  877.  * within NEIGHBORHOOD (=32) bytes of our chosen word is certain
  878.  * to be included in the proximity subset; any item which occurs
  879.  * at least 2*NEIGHBORHOOD (=64) bytes away is certain NOT to be
  880.  * included, and items in the intermediate range have a
  881.  * linearly-decreasing probability of false inclusion.  The choice
  882.  * of 32 for NEIGHBORHOOD and 1 for WORD_RANGE therefore gives
  883.  * essentially all items within half a dozen or so words
  884.  * (average word length is 5-6 letters) of the term used to
  885.  * define the subset....
  886.  *
  887.  * In extensive experimentation with the MacForth-based Browser v.244+
  888.  * program on the Macintosh, the above definition of proximity seemed
  889.  * to be by far the most useful one in real life.  It is also quite
  890.  * straightforward to implement with good efficiency.  In addition
  891.  * to the 'word'-neighborhood proximity (within WORD_RANGE chunks),
  892.  * it is occasionally convenient to add a somewhat larger
  893.  * neighborhood to the working subset -- corresponding to proximity
  894.  * within a few sentences or even a few paragraphs.  To do that,
  895.  * the && (sentence-proximity) and &&& (paragraph-proximity) commands
  896.  * simply set more bits on either side of the word's location in
  897.  * subset[].  Specifically, && sets SENTENCE_RANGE bits on each side,
  898.  * and &&& sets PARAGRAPH_RANGE bits on each side.  Values of 10 and 100
  899.  * respectively for those parameters seem to work quite well.
  900.  */
  901.  
  902.  
  903.  
  904. /* Handle the * command to empty out the working subset before
  905.  * beginning to do proximity-filtered searching (that is, to create
  906.  * the array subset[] in empty form). 
  907.  * Also handle the ** command to destroy subset[] (which makes it
  908.  * seem as if it is "full" from the user's viewpoint)
  909.  *
  910.  * Force the user back to INDEX level when the working subindex
  911.  * is emptied or filled, to avoid inconsistencies in moving around
  912.  * the CONTEXT level....
  913.  */
  914.  
  915. void do_make_subset (cmd)
  916.   char cmd[];
  917.   {
  918.     void beep (), create_subset (), destroy_subset (),
  919.             show_current_item ();
  920.     extern int level;
  921.     
  922.     if (cmd[1] == '\0')
  923.         create_subset ();
  924.     else if (cmd[1] == '*')
  925.         destroy_subset ();
  926.     else
  927.         beep ();
  928.     
  929.     level = INDEX;
  930.     show_current_item ();
  931.   }
  932.  
  933.  
  934. /* Do the job of creating the empty array subset[] ... note that if
  935.  * running on a machine with 16-bit int variables such as Lightspeed C
  936.  * on the Mac, the calloc() function won't work for files bigger
  937.  * than 16 MB or so (for NEIGHBORHOOD = 32) ....must modify this
  938.  * to use clalloc(), the non-ANSI standard function for allocating
  939.  * and clearing bigger memory chunks.  Hence, the #ifdef LIGHTSPEED
  940.  * lines below....
  941.  *
  942.  * Note that there is no need to provide a "No file open!" error msg
  943.  * since show_current_item() in calling function will do that for
  944.  * us....
  945.  */
  946.  
  947. void create_subset ()
  948.   {
  949.     extern char* subset;
  950.     extern FILE *doc_file;
  951.     extern int empty_subset;
  952.     extern long max_item[];
  953. #ifdef LIGHTSPEED
  954.     char *clalloc ();
  955. #else
  956.     char *calloc ();
  957. #endif
  958.     void beep (), destroy_subset ();
  959.     
  960.     if (doc_file == NULL)
  961.       return;
  962.  
  963.     destroy_subset ();
  964. #ifdef LIGHTSPEED
  965.     if ((subset = clalloc ((unsigned long)(1 +
  966.             max_item[TEXT]/(NEIGHBORHOOD*8)), (long)sizeof(char))) == NULL)
  967. #else    
  968.     if ((subset = calloc ((unsigned int)(1 +
  969.             max_item[TEXT]/(NEIGHBORHOOD*8)), sizeof(char))) == NULL)
  970. #endif
  971.       {
  972.         beep ();
  973.         printf ("Not enough memory for subset!\n");
  974.         return;
  975.       }
  976.     
  977.     empty_subset = TRUE;
  978.   }
  979.  
  980.  
  981. /* routine to destroy a subset (used in response to a ** command,
  982.  * or when changing over to a new file with a : command, or when about
  983.  * to create a new empty subset inside the * command)
  984.  */
  985.  
  986. void destroy_subset ()
  987.   {
  988.     extern char *subset;
  989.     
  990.     if (subset != NULL)
  991.         free (subset);
  992.     subset = NULL;
  993.   }
  994.  
  995.  
  996. /* routine to invert the working subindex (response to a ~ command) --
  997.  * perform a logical NOT on the entire set, so that what was once a
  998.  * member of the set becomes a non-member, and vice versa....
  999.  *
  1000.  * Must set empty_subset FALSE since we don't a priori know that the
  1001.  * resulting set is empty or full or what....
  1002.  *
  1003.  * As with * and ** commands, kick the user back to the INDEX level
  1004.  * when this command is executed, for safety's sake....
  1005.  */
  1006.  
  1007. void do_invert_subset ()
  1008.   {
  1009.     register long i, lim;
  1010.     extern char *subset;
  1011.     extern long max_item[];
  1012.     extern int empty_subset;
  1013.     void beep();
  1014.  
  1015.     if (doc_file == NULL)
  1016.       {
  1017.         beep ();
  1018.         printf ("No file open!\n");
  1019.         return;
  1020.       }
  1021.     if (subset == NULL)
  1022.       {
  1023.         beep ();
  1024.         printf ("No subset!\n");
  1025.         return;
  1026.       }
  1027.  
  1028.     lim = max_item[TEXT]/(NEIGHBORHOOD*8);
  1029.     for (i = 0; i <= lim; ++i)
  1030.         subset[i] = ~subset[i];
  1031.  
  1032.     empty_subset = FALSE;
  1033.     level = INDEX;
  1034.     show_current_item ();
  1035.   }
  1036.   
  1037.  
  1038. /* handle the '&' command to add the current word's neighborhood to
  1039.  * the working subset:
  1040.  *    &    adds a few-words neighborhood
  1041.  *    &&    adds a few-sentences neighborhood
  1042.  *    &&&    adds a few-paragraphs neighborhood
  1043.  *
  1044.  * Note that instead of calling show_current_item() to finish up
  1045.  * this function, since we know that every occurrence of the
  1046.  * current item will be in the working subset we can save time
  1047.  * and avoid re-counting them all ... hence, the final lines
  1048.  * of this routine
  1049.  */
  1050.  
  1051. void do_add_neighborhood (cmd)
  1052.   char cmd[];
  1053.   {
  1054.     int nsize;
  1055.     extern int level;
  1056.     extern char *subset;
  1057.     extern FILE *doc_file, *notes_file;
  1058.     void beep (), set_neighborhood_bits (), get_key_rec ();
  1059.     extern KEY_REC this_rec, prev_rec;
  1060.     extern long current_item[], subset_rec_count;
  1061.  
  1062.     if (doc_file == NULL)
  1063.       {
  1064.         beep ();
  1065.         printf ("No file open!\n");
  1066.         return;
  1067.       }
  1068.     if (subset == NULL)
  1069.       {
  1070.         beep ();
  1071.         printf ("No subset!\n");
  1072.         return;
  1073.       }
  1074.  
  1075.     nsize = WORD_RANGE;
  1076.     if (cmd[1] == '&')
  1077.       {
  1078.         nsize = SENTENCE_RANGE;
  1079.         if (cmd[2] == '&')
  1080.             nsize = PARAGRAPH_RANGE;
  1081.       }
  1082.     
  1083.     level = INDEX;
  1084.     set_neighborhood_bits (nsize);
  1085.     empty_subset = FALSE;
  1086.     get_key_rec (&prev_rec, current_item[INDEX] - 1);
  1087.     get_key_rec (&this_rec, current_item[INDEX]);
  1088.     subset_rec_count = this_rec.ccount - prev_rec.ccount;
  1089.     printf ("%6ld/%-6ld %.28s\n", subset_rec_count, subset_rec_count,
  1090.                 this_rec.kkey);
  1091.     if (notes_file != NULL)
  1092.         fprintf (notes_file, "%6ld/%-6ld %.28s\n", subset_rec_count,
  1093.                     subset_rec_count, this_rec.kkey);
  1094.   }
  1095.  
  1096.  
  1097. /* function to set n bits on each side of each current index term in the
  1098.  * subset[] array ... note that it is important to set min_item[CONTEXT]
  1099.  * and max_item[CONTEXT] values before using them, since they are only
  1100.  * set ordinarily when descending ('=' command) into the CONTEXT level...
  1101.  * values of prev_rec and this_rec are set every time an index item
  1102.  * is displayed, so they will be all right for us already....
  1103.  */
  1104.  
  1105. void set_neighborhood_bits (n)
  1106.   register int n;
  1107.   {
  1108.     register long i;
  1109.     register PTR_REC j, j0, j1;
  1110.     extern long min_item[], max_item[];
  1111.     extern KEY_REC prev_rec, this_rec;
  1112.     PTR_REC get_ptr_rec ();
  1113.     void set_subset_bit ();
  1114.     
  1115.     min_item[CONTEXT] = prev_rec.ccount;
  1116.     max_item[CONTEXT] = this_rec.ccount - 1;
  1117.     
  1118.     if ((this_rec.ccount-prev_rec.ccount) * n > GIVE_WARNING)
  1119.         printf ("Please wait (%ld bits to set)...\n",
  1120.             (this_rec.ccount - prev_rec.ccount) * n * 2 + 1);
  1121.  
  1122.     for (i = min_item[CONTEXT]; i <= max_item[CONTEXT]; ++i)
  1123.       {
  1124.         j = get_ptr_rec (i);
  1125.         j0 = j - n * NEIGHBORHOOD;
  1126.         if (j0 < min_item[TEXT])
  1127.             j0 = min_item[TEXT];
  1128.         j1 = j + n * NEIGHBORHOOD;
  1129.         if (j1 > max_item[TEXT])
  1130.             j1 = max_item[TEXT];
  1131.         for (j = j0; j <= j1; j += NEIGHBORHOOD)
  1132.             set_subset_bit (j);
  1133.       }
  1134.   }
  1135.  
  1136.  
  1137. /* function to set a bit in the array subset[] for a chosen character
  1138.  * offset from the start of the file ... just convert the offset into
  1139.  * byte and bit numbers and then "OR" the 1 into that slot....
  1140.  *
  1141.  * (An attempt to optimize for the specific value NEIGHBORHOOD = 32,
  1142.  * using & and >> in place of % and /, did not yield a significant
  1143.  * improvement in speed... ^z 870813)
  1144.  */
  1145.  
  1146. void set_subset_bit (offset)
  1147.   long offset;
  1148.   {
  1149.     int bit_no;
  1150.     long byte_no;
  1151.     extern char *subset;
  1152.  
  1153.     bit_no = (offset % (8 * NEIGHBORHOOD)) / NEIGHBORHOOD;
  1154.     byte_no = offset / (8 * NEIGHBORHOOD);
  1155.     
  1156.     subset[byte_no] |= 1 << bit_no;
  1157.   }
  1158.  
  1159.  
  1160. /* function to return the value of a bit in the array subset[] for a
  1161.  * chosen character offset from the start of the document file ...
  1162.  * as in set_subset_bit() above, just convert offset into byte and
  1163.  * bit numbers, extract the relevant bit, shift it down, and return
  1164.  * it....
  1165.  *
  1166.  * (An attempt to optimize for the specific value NEIGHBORHOOD = 32,
  1167.  * using & and >> in place of % and /, did not yield a significant
  1168.  * improvement in speed... ^z 870813)
  1169.  */
  1170.  
  1171. int get_subset_bit (offset)
  1172.   long offset;
  1173.   {
  1174.     int bit_no;
  1175.     long byte_no;
  1176.     extern char *subset;
  1177.  
  1178.     bit_no = (offset % (8 * NEIGHBORHOOD)) / NEIGHBORHOOD;
  1179.     byte_no = offset / (8 * NEIGHBORHOOD);
  1180.  
  1181.     return ((subset[byte_no] >> bit_no) & 1);
  1182.   }
  1183.  
  1184.  
  1185. /* function to count and return the number of occurrences of this_rec
  1186.  * in the working subindex (i.e., the number of times the word comes
  1187.  * up with its subset[] bit turned ON) ....
  1188.  */
  1189.  
  1190. long count_subset_recs (this_recp, prev_recp)
  1191.   KEY_REC *this_recp, *prev_recp;
  1192.   {
  1193.     register long i, n;
  1194.     extern int empty_subset;
  1195.     PTR_REC get_ptr_rec ();
  1196.     int get_subset_bit ();
  1197.     
  1198.     if (empty_subset)
  1199.         return (0L);
  1200.  
  1201.     if (this_recp->ccount - prev_recp->ccount > GIVE_WARNING)
  1202.         printf ("Please wait (%ld words to check)...\n",
  1203.                     this_recp->ccount - prev_recp->ccount);
  1204.     
  1205.     for (n = 0, i = prev_recp->ccount; i < this_recp->ccount; ++i)
  1206.         n += get_subset_bit ((long)get_ptr_rec (i));
  1207.     
  1208.     return (n);
  1209.   }
  1210.  
  1211.  
  1212. /* ----------------------do_targets.c----------------- */
  1213.  
  1214. /* some functions to respond to various user inputs...
  1215.  *  870806-13-... ^z
  1216.  */
  1217.  
  1218.  
  1219. /* function to move down a level in the browser hierarchy:
  1220.  *    INDEX --> CONTEXT --> TEXT
  1221.  * includes various safety features against errors, and takes
  1222.  * care of initializations for the level being entered ... also
  1223.  * displays the current line when entering the new level, for the user's
  1224.  * convenience....
  1225.  *
  1226.  * Modified for subset operations, ^z - 870813.  Note that we must check
  1227.  * here to be sure that we are descending into a non-empty subset when
  1228.  * a subset is active and we are descending from INDEX --> CONTEXT;
  1229.  * we also must make sure in that case that we initialize the CONTEXT
  1230.  * display to start with the first good item, not just min_item[CONTEXT].
  1231.  */
  1232.  
  1233. void do_descend ()
  1234.   {
  1235.     extern int level, empty_subset;
  1236.     extern long current_item[], min_item[], max_item[], subset_rec_count;
  1237.     extern KEY_REC this_rec, prev_rec;
  1238.     extern FILE *doc_file;
  1239.     extern char *subset;
  1240.     void beep();
  1241.     
  1242.     if (doc_file == NULL)
  1243.       {
  1244.         beep ();
  1245.         printf ("No file open!\n");
  1246.         return;
  1247.       }
  1248.  
  1249.     if (current_item[INDEX] < 0)
  1250.       {
  1251.         beep ();
  1252.         printf ("No current index item!\n");
  1253.         return;
  1254.       }
  1255.     
  1256.     if (subset != NULL && (empty_subset || subset_rec_count == 0))
  1257.       {
  1258.         beep ();
  1259.         printf ("No items in subset!\n");
  1260.         return;
  1261.       }
  1262.  
  1263.     ++level;
  1264.  
  1265.     if (level == CONTEXT)
  1266.       {
  1267.         min_item[CONTEXT] = prev_rec.ccount;
  1268.         max_item[CONTEXT] = this_rec.ccount - 1;
  1269.         if (subset == NULL)
  1270.             current_item[CONTEXT] = min_item[CONTEXT];
  1271.         else
  1272.           {
  1273.             current_item[CONTEXT] = min_item[CONTEXT] - 1;
  1274.             make_subindex_move (1L);
  1275.           }
  1276.         current_item[TEXT] = get_ptr_rec (current_item[CONTEXT]);
  1277.       }
  1278.  
  1279.     if (level == TEXT)
  1280.         current_item[TEXT] =
  1281.                 start_of_line (current_item[TEXT]);
  1282.  
  1283.     if (level > TEXT)
  1284.       {
  1285.         beep ();
  1286.         printf ("Can't descend past full text display!\n");
  1287.         level = TEXT;
  1288.       }
  1289.  
  1290.     show_current_item ();
  1291.   }
  1292.  
  1293. /* function to move up a level in the browser hierarchy:
  1294.  *       TEXT --> CONTEXT --> INDEX
  1295.  * includes a safety net against going past INDEX, and
  1296.  * displays the current line when entering the new level...
  1297.  * note that when coming up from TEXT, must reset the current_item[TEXT]
  1298.  * before displaying CONTEXT line, as TEXT browsing changes it....
  1299.  */
  1300.  
  1301.  
  1302. void do_ascend ()
  1303.   {
  1304.     extern int level;
  1305.     void beep();
  1306.     
  1307.     --level;
  1308.     
  1309.     if (level < INDEX)
  1310.       {
  1311.         beep ();
  1312.         printf ("Can't ascend past index display!\n");
  1313.         level = INDEX;
  1314.       }
  1315.  
  1316.     if (level == CONTEXT)
  1317.         current_item[TEXT] = get_ptr_rec (current_item[CONTEXT]);
  1318.  
  1319.     show_current_item ();
  1320.   }
  1321.  
  1322.  
  1323. /* function to jump the INDEX display to a target entered by the
  1324.  * user ... does a simple binary search (see K&R p.54) to get there
  1325.  * and then shows that line of the INDEX ... and if the
  1326.  * target word is not found, it beeps and shows the preceeding
  1327.  * word available in the index ... if called from another level, it
  1328.  * jumps to the INDEX level ...
  1329.  */
  1330.  
  1331. void do_target_jump (cmd)
  1332.   char cmd[];
  1333.   {
  1334.     register int c, diff;
  1335.     register long low, high, mid;
  1336.     KEY_REC target_item, this_item;
  1337.     extern long current_item[], max_item[];
  1338.     extern FILE *key_file;
  1339.     extern int level;
  1340.     void beep(), init_target_item();
  1341.     
  1342.     if (key_file == NULL)
  1343.       {
  1344.         beep ();
  1345.         printf ("No file open!\n");
  1346.         return;
  1347.       }
  1348.     
  1349.     level = INDEX;
  1350.     init_target_item (&target_item, cmd);
  1351.     low = min_item[INDEX];
  1352.     high = max_item[INDEX];
  1353.  
  1354.     while (low <= high)
  1355.       {
  1356.         mid = (low + high) / 2;
  1357.         get_key_rec (&this_item, mid);
  1358.         diff = zstrcmp (target_item.kkey, this_item.kkey);
  1359.         if (diff < 0)
  1360.             high = mid - 1;
  1361.         else if (diff > 0)
  1362.             low = mid + 1;
  1363.         else
  1364.           break;
  1365.       }
  1366.  
  1367.     current_item[INDEX] = mid;
  1368.     if (diff < 0)
  1369.         --current_item[INDEX];
  1370.     if (current_item[INDEX] < 0)
  1371.         current_item[INDEX] = 0;
  1372.     if (diff != 0)
  1373.         beep ();
  1374.     show_current_item ();
  1375.   }
  1376.  
  1377.  
  1378.  
  1379. /* my function to compare two strings and give a result as to who is
  1380.  * alphabetically earlier.  Note that this is almost the same as strncmp()
  1381.  * with the fixed value of KEY_LENGTH as the maximum comparison distance,
  1382.  * except that I must be sure to mask the characters to make them positive
  1383.  * (since we want to be able to handle the non-ASCII funny letters in
  1384.  * the Apple character set properly/consistently).  If the masking isn't
  1385.  * done, then inconsistent results can occur with those non-ASCII chars!
  1386.  */
  1387.  
  1388. int zstrcmp (s1, s2)
  1389.   register char *s1, *s2;
  1390.   {
  1391.     register int n = KEY_LENGTH;
  1392.     
  1393.     for (; --n && ((*s1 & 0xFF) == (*s2 & 0xFF)); s1++, s2++)
  1394.         if (!*s1) break;
  1395.         
  1396.     return ((*s1 & 0xFF) - (*s2 & 0xFF));
  1397.   }
  1398.  
  1399.  
  1400.  
  1401. /* initialize the target KEY_REC kkey string to the value typed in by
  1402.  * the user ... convert the user's string to all capital letters, and
  1403.  * pad it out to KEY_LENGTH with trailing blanks as needed ... use the
  1404.  * toupper() function warily (assume that it may be nonstandard and
  1405.  * mess up the value of non-lowercase characters, as it seems to on
  1406.  * the VAX and Sun C compilers I've tried -- so check and don't apply
  1407.  * toupper() except to lower-case characters!)....
  1408.  */
  1409.  
  1410. void init_target_item (rp, cmd)
  1411.   KEY_REC *rp;
  1412.   char cmd[];
  1413.   {
  1414.     register int c, n;
  1415.  
  1416.     strncpy (rp->kkey, "                                    ", KEY_LENGTH);
  1417.     for (n = 0; n < KEY_LENGTH && (c = cmd[n]) != 0; ++n)
  1418.         rp->kkey[n] = (islower (c) ? toupper (c) : c);
  1419.   }
  1420.  
  1421.  
  1422. /* handle the printing out of N lines due to a ".N" user command ... do
  1423.  * the job simply by marching down a step and displaying that line N times
  1424.  * ... precisely equivalent to hitting the <return> key N times.
  1425.  */
  1426.  
  1427. void do_multiprint (cmd)
  1428.   char cmd[];
  1429.   {
  1430.     register long i, n;
  1431.     extern FILE *doc_file;
  1432.     void beep();
  1433.     
  1434.     if (doc_file == NULL)
  1435.       {
  1436.         beep ();
  1437.         printf ("No file open!\n");
  1438.         return;
  1439.       }
  1440.       
  1441.     if (cmd[1] == '\0')
  1442.         strcat (cmd, "1");
  1443.     n = atol (cmd + 1);
  1444.     for (i = 0; i < n; ++i)
  1445.       {
  1446.         if (! make_move (1L))
  1447.             break;
  1448.         show_current_item ();
  1449.       }
  1450.   }
  1451.  
  1452. /* ---------------------file_utils.c--------------------- */
  1453.  
  1454. /* initialize, open, etc. various things for brwsr program...
  1455.  * 870805-13... ^z
  1456.  */
  1457.  
  1458.  
  1459. /* open the document, key, and pointer files ...
  1460.  * for now, demand that if the document file is named * then the
  1461.  * key file must be named *.k and the pointer file must be *.p
  1462.  * ... just the way that the ndxr program builds the index.....
  1463.  */
  1464.  
  1465. void open_files (cmd)
  1466.   char cmd[];
  1467.   {
  1468.     char ocmd[STRLEN], *strcat(), *strcpy();
  1469.     FILE *fopen();
  1470.     extern FILE *doc_file, *key_file, *ptr_file;
  1471.     void beep();
  1472.  
  1473.     close_files ();
  1474.     strcpy (ocmd, cmd + 1);
  1475.     if ((doc_file = fopen (ocmd, "r")) == NULL)
  1476.       {
  1477.         beep ();
  1478.         printf ("Error opening document file \"%s\"!\n", ocmd);
  1479.         close_files ();
  1480.         return;
  1481.       }
  1482.  
  1483.     strcat (ocmd, ".k");
  1484.     if ((key_file = fopen (ocmd, "rb")) == NULL)
  1485.       {
  1486.         beep ();
  1487.         printf ("Error opening key file \"%s\"!\n", ocmd);
  1488.         close_files ();
  1489.         return;
  1490.       }
  1491.  
  1492.     ocmd[strlen (ocmd) - 2] = '\0';
  1493.     strcat (ocmd, ".p");
  1494.     if ((ptr_file = fopen (ocmd, "rb")) == NULL)
  1495.       {
  1496.         beep ();
  1497.         printf ("Error opening ptr file \"%s\"!\n", ocmd);
  1498.         close_files ();
  1499.         return;
  1500.       }
  1501.   }
  1502.  
  1503.  
  1504. /* close off the document, key, and pointer files
  1505.  */
  1506.  
  1507. void close_files ()
  1508.   {
  1509.     extern FILE *doc_file, *key_file, *ptr_file;
  1510.     
  1511.     if (doc_file != NULL)
  1512.       {
  1513.         fclose (doc_file);
  1514.         doc_file = NULL;
  1515.       }
  1516.     if (key_file != NULL)
  1517.       {
  1518.         fclose (key_file);
  1519.         key_file = NULL;
  1520.       }
  1521.     if (ptr_file != NULL)
  1522.       {
  1523.         fclose (ptr_file);
  1524.         ptr_file = NULL;
  1525.       }
  1526.   }
  1527.  
  1528.  
  1529. /* set up initial values for current_item and max_item arrays ...
  1530.  * initially, current_item is set to an illegal value, -1, and
  1531.  * max_item[CONTEXT] and max_item[TEXT] convey information about
  1532.  * the sizes of the pointer and document files respectively...
  1533.  */
  1534.  
  1535. void init_items ()
  1536.   {
  1537.     extern long current_item[], max_item[];
  1538.     extern FILE *doc_file, *key_file, *ptr_file;
  1539.     extern int level;
  1540.     
  1541.     level = INDEX;
  1542.     
  1543.     current_item[INDEX] = -1;
  1544.     current_item[CONTEXT] = -1;
  1545.     current_item[TEXT] = -1;
  1546.  
  1547.     fseek (key_file, 0L, 2);
  1548.     max_item[INDEX] = ftell (key_file) / sizeof (KEY_REC) - 1;
  1549.     
  1550.     fseek (ptr_file, 0L, 2);
  1551.     max_item[CONTEXT] = ftell (ptr_file) / sizeof (PTR_REC) - 1;
  1552.  
  1553.     fseek (doc_file, 0L, 2);
  1554.     max_item[TEXT] = ftell (doc_file) - 1;
  1555.   }
  1556.  
  1557.  
  1558. /* --------------------show_items.c------------------ */
  1559.  
  1560. /* functions to show a chosen item from an index key file,etc.
  1561.  * 870805-13...  ^z
  1562.  */
  1563.  
  1564.  
  1565. /* function to show whatever the current item is, in whatever level
  1566.  * the user is at the moment.... basically, it just routes the
  1567.  * request to whichever is the appropriate agent to take care of
  1568.  * it, for INDEX, CONTEXT, or TEXT levels....
  1569.  */
  1570.  
  1571. void show_current_item ()
  1572.   {
  1573.     extern int level;
  1574.     extern FILE *doc_file;
  1575.     void beep(), show_current_index_item(), show_current_context_item(),
  1576.         show_current_text_item();
  1577.     
  1578.     if (doc_file == NULL)
  1579.       {
  1580.         beep ();
  1581.         printf ("No file open!\n");
  1582.         return;
  1583.       }
  1584.     
  1585.     if (level == INDEX)
  1586.         show_current_index_item ();
  1587.     else if (level == CONTEXT)
  1588.         show_current_context_item ();
  1589.     else
  1590.         show_current_text_item ();
  1591.   }
  1592.  
  1593.  
  1594. /* function to show the current INDEX item (count of occurrences followed
  1595.  * by the indexed word itself) ... send copy to notes file, if open....
  1596.  */
  1597.  
  1598. void show_current_index_item ()
  1599.   {
  1600.     extern KEY_REC this_rec, prev_rec;
  1601.     extern long current_item[], subset_rec_count;
  1602.     extern FILE *notes_file;
  1603.     extern char *subset;
  1604.     long count_subset_recs ();
  1605.     void get_key_rec ();
  1606.  
  1607.     get_key_rec (&prev_rec, current_item[INDEX] - 1);
  1608.     get_key_rec (&this_rec, current_item[INDEX]);
  1609.     
  1610.     if (subset == NULL)
  1611.       {
  1612.         printf ("%6ld %.28s\n", this_rec.ccount - prev_rec.ccount,
  1613.                     this_rec.kkey);
  1614.         if (notes_file != NULL)
  1615.             fprintf (notes_file, "%6ld %.28s\n",
  1616.                     this_rec.ccount - prev_rec.ccount, this_rec.kkey);
  1617.       }
  1618.     else
  1619.       {
  1620.           subset_rec_count = count_subset_recs (&this_rec, &prev_rec);
  1621.         printf ("%6ld/%-6ld %.28s\n", subset_rec_count,
  1622.                     this_rec.ccount - prev_rec.ccount, this_rec.kkey);
  1623.         if (notes_file != NULL)
  1624.         fprintf (notes_file, "%6ld/%-6ld %.28s\n", subset_rec_count,
  1625.                     this_rec.ccount - prev_rec.ccount, this_rec.kkey);
  1626.       }
  1627.   }
  1628.  
  1629.  
  1630. /* function to show the current CONTEXT item, a line in the form of a
  1631.  * key-word-in-context (KWIC) display with the indexed term in the'
  1632.  * middle ... the line is filtered so that the presence of tabs, <cr>'s,
  1633.  * or other nasty control characters won't cause any problems... 
  1634.  * a little testing is needed to take care of end-effects, so that 
  1635.  * nothing happens for context lines near either end of the document
  1636.  * file ... finally, send a copy to the notes file, if open....
  1637.  */
  1638.  
  1639. void show_current_context_item ()
  1640.   {
  1641.     register int n, m;
  1642.     register long p;
  1643.     char buf[CONTEXT_LINE_LENGTH + 1];
  1644.     extern long current_item[];
  1645.     extern FILE *doc_file;
  1646.     void beep();
  1647.     
  1648.     p = current_item[TEXT] - CONTEXT_OFFSET;
  1649.     n = 0;
  1650.     if (p < 0)
  1651.       {
  1652.         for ( ; n < -p; ++n)
  1653.             buf[n] = ' ';
  1654.         p = 0;
  1655.       }
  1656.     if (fseek (doc_file, p, 0) != 0)
  1657.           {
  1658.             beep ();
  1659.             printf ("Error in fseek() getting a context line to show!\n");
  1660.             return;
  1661.           }
  1662.     if ((m = fread (buf + n, sizeof (char), CONTEXT_LINE_LENGTH - n,
  1663.                         doc_file)) == 0)
  1664.           {
  1665.             beep ();
  1666.             printf ("Error in fread() getting a context line to show!\n");
  1667.             return;
  1668.           }
  1669.     n += m;
  1670.     for ( ; n < CONTEXT_LINE_LENGTH; ++n)
  1671.         buf[n] = ' ';
  1672.     for (n = 0; n < CONTEXT_LINE_LENGTH; ++n)
  1673.         if (! isprint (buf[n] & 0xFF))
  1674.             buf[n] = ' ';
  1675.     buf[CONTEXT_LINE_LENGTH] = '\0';
  1676.     printf ("%s\n", buf);
  1677.     if (notes_file != NULL)
  1678.         fprintf (notes_file, "%s\n", buf);
  1679.   }
  1680.  
  1681.  
  1682. /* function to show the current TEXT item, a line from the document file
  1683.  * itself ... copy to notes file also, if so desired....
  1684.  */
  1685.  
  1686. void show_current_text_item ()
  1687.   {
  1688.     char buf[STRLEN + 1];
  1689.     extern long current_item[];
  1690.     extern FILE *doc_file;
  1691.     void beep();
  1692.     
  1693.     if (fseek (doc_file, current_item[TEXT], 0) != 0)
  1694.           {
  1695.             beep ();
  1696.             printf ("Error in fseek() getting a text line to show!\n");
  1697.             return;
  1698.           }
  1699.     if (fgets (buf, STRLEN, doc_file) == NULL)
  1700.           {
  1701.             beep ();
  1702.             printf ("Error in fgets() getting a text line to show!\n");
  1703.             return;
  1704.           }
  1705.     printf ("%s", buf);
  1706.     if (notes_file != NULL)
  1707.             fprintf (notes_file, "%s", buf);
  1708.   }
  1709.  
  1710.  
  1711.  
  1712. -------
  1713.