home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / compsrcs / misc / volume02 / brwsr < prev    next >
Encoding:
Internet Message Format  |  1991-08-27  |  49.5 KB

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