############################################################################ # # File: gettext.icn # # Subject: Procedures for gettext (simple text-base routines) # # Author: Richard L. Goerwitz # # Date: May 2, 2001 # ############################################################################ # # This file is in the public domain. # ############################################################################ # # History: # Version 1.19: December 28, 1993 (plt) # Tested with DOS, DOS-386, OS/2, ProIcon, UNIX # Modified link and OS statements. # Open index file in untranslated mode for # MS-DOS and OS/2 -- ignored by UNIX and Amiga # Handle multiple, indexed citations. # Change delimiter from to char(255). # Simplified binary search. # Version 1.20: August 5, 1995 (plt) # Replace link statement with preprocessor include. # Retrieve text for multiple keys on the same line. # Correct debug printout of indexed and sequential # search values. # ############################################################################ # # Version: 1.19 December 28, 1993 - Phillip Lee Thomas # Version: 1.20 August 5, 1995 - plt # ############################################################################ # # Gettext() and associated routines allow the user to maintain a file # of KEY/value combinations such that a call to gettext(KEY, FNAME) # will produce value. Gettext() fails if no such KEY exists. # Returns an empty string if the key exists, but has no associated # value in the file, FNAME. # # The file format is simple. Keys belong on separate lines, marked # as such by an initial colon+colon (::). Values begin on the line # following their respective keys, and extend up to the next # colon+colon-initial line or EOF. E.g. # # ::sample.1 # or: # ::sample.1 ::sample.2 # # Notice how the key above, sample.1, has :: prepended to mark it # out as a key. The text you are now reading represents that key's # value. To retrieve this text, you would call gettext() with the # name of the key passed as its first argument, and the name of the # file in which this text is stored as its second argument (as in # gettext("sample.1","tmp.idx")). # ::next.key # etc... # # For faster access, an indexing utility is included, idxtext. Idxtext # creates a separate index for a given text-base file. If an index file # exists in the same directory as FNAME, gettext() will make use of it. # The index becomes worthwhile (at least on my system) after the text- # base file becomes longer than 5 kilobytes. # # Donts: # 1) Don't nest gettext text-base files. # 2) In searches, surround phrases with spaces or tabs in # key names with quotation marks: "an example" # 3) Don't modify indexed files in any way other than to append # additional keys/values (unless you want to re-index). # # This program is intended for situations where keys tend to have # very large values, and use of an Icon table structure would be # unwieldy. # # BUGS: Gettext() relies on the Icon runtime system and the OS to # make sure the last text/index file it opens gets closed. # ############################################################################ # # Links: adjuncts # ############################################################################ # # Invoke set_OS() before first call to gettext() or # sequential_search() # # Tested with UNIX, OS/2, DOS, DOS-386, ProIcon # ############################################################################ link adjuncts global _slash, _baselen, _delimiter procedure gettext(KEY,FNAME) #: search database by indexed term local line, value static last_FNAME, intext, inidx, off_set, off_sets (/KEY | /FNAME) & stop("error (gettext): null argument") if FNAME == \last_FNAME then { seek(intext, 1) seek(\inidx, 1) } else { # We've got a new text-base file. Close the old one. every close(\intext | \inidx) # Try to open named text-base file. intext := open(FNAME) | stop("gettext: file \"",FNAME,"\" not found") # Try to open index file. inidx := open(Pathname(FNAME) || getidxname(FNAME),"ru") | &null } last_FNAME := FNAME # Find offsets, if any, for key KEY in index file. # Then seek to the end and do a sequential search # for any key/value entries that have been added # since the last time idxtext was run. if off_sets := get_offsets(KEY, inidx) then { off_sets ? { while off_set := (move(1),tab(many(&digits))) do { seek(intext, off_set) # Find key. Should be right there, unless the user has appended # key/value pairs to the end without re-indexing, or else has not # bothered to index in the first place. In this case we're # supposed to start a sequential search for KEY upto EOF. while line := (read(intext) | fail) do { line ? { if (="::",KEY) then break } } # Collect all text upto the next colon+colon line (::) # or EOF. value := "" while line := read(intext) do { find("::",line) & break value ||:= line || "\n" } # Note that a key with an empty value returns an empty string. suspend trim(value, '\n') || " (" || off_set || "-i)" } } } # Find additional values appended to file since last indexing. seek(intext, \firstline - _OS_offset) while value := sequential_search(KEY, intext) do suspend trim(value,'\n') #|| " (" || off_set || "-s)" end procedure get_offsets(KEY, inidx) #: binary search of index local incr, bottom, top, loc, firstpart, offset, line # Use these to store values likely to be reused. static old_inidx, SOF, EOF # If there's no index file, then fail. if /inidx then fail # First line contains offset of last indexed byte in the main # text file. We need this later. Save it. Start the binary # search routine at the next byte after this line. seek(inidx, 1) if not (inidx === \old_inidx) then { # Get first line. firstline := !inidx # Set "bottom." SOF := 1 # How big is this file? seek(inidx, 0) EOF := where(inidx) old_inidx := inidx } # SOF, EOF constant for a given inidx file. bottom := SOF ; top := EOF # If bottom gets bigger than top, there's no such key. until bottom >= top do { loc := (top+bottom) / 2 seek(inidx, loc) # Move past next newline. If at EOF, break. read(inidx) if (where(inidx) > EOF) | (loc = bottom) | (loc = top) then { break } # Check to see if the current line contains KEY. if line := read(inidx) then { line ? { # .IDX file line format is KEYoffset firstpart := tab(upto(_delimiter)) if KEY == firstpart then { # return offset and addresses for any added material return tab(1 - _OS_offset) } # Ah, this is what all binary searches do. else { if KEY >> firstpart then bottom := loc else top := loc } } } else top := loc # Too far, move back } end # Perform sequential search of intext for all instances of KEY. procedure sequential_search(KEY, intext) #: brute-force database search local line, value, off_set # Collect all text upto the next colon+colon line (::) # or EOF. off_set := where(intext) while (line := read(intext)) | fail do { line ? { if =("::" || KEY) & (match(" " | "\t") | pos(0)) then break else off_set := where(intext) } } value := "" while line := read(intext) do { find("::", line) & break value ||:= line || "\n" } # Debug information for sequential searching: value := value[1:-1] || " (" || off_set || "-s)\n" # Back up to allow for consecutive instances of KEY. seek(intext, where(intext) - *line - 2) suspend trim(value || "\n") end