home *** CD-ROM | disk | FTP | other *** search
/ Shareware Overload / ShartewareOverload.cdr / database / wordwrap.zip / ARTICLE.TXT next >
Text File  |  1987-02-24  |  18KB  |  438 lines

  1. Another Wordwrap
  2.  
  3. BY KENNETH N. GETZ AND KAREN ROBINSON
  4.  
  5. In earlier issues, TechNotes has presented articles that described
  6. word-wrapping routines written in the dBASE III PLUS interpretive
  7. language--effective routines, but somewhat slow.  Here we'll present an
  8. alternative approach: an assembler routine that you can LOAD and CALL to wrap
  9. long strings.
  10.  
  11. Wordwrap.ASM is an assembly language program that takes a long string and
  12. returns a wrapped string.  The dBASE III PLUS program that you use to print
  13. the wrapped strings must define the margin settings, CALL Wordwrap to parse
  14. the text, and print the string returned by Wordwrap.  Wraptest.PRG, a dBASE
  15. III PLUS sample program, demonstrates how to use Wordwrap.
  16.  
  17.  
  18. Wordwrap.BIN
  19.  
  20. Wordwrap accepts up to 254 characters at a time and parses the string
  21. according to the line width you specify.  For example, if the character string
  22. is 254 characters wide and your line width is 65, Wordwrap returns to the
  23. calling program 65 characters at a time.  If the sixty-fifth character falls
  24. in the middle of a word, it wraps that word to the next line.
  25.  
  26. You CALL Wordwrap WITH the character string you want to wrap as the
  27. parameter.  Although you pass only one parameter on the command line, Wordwrap
  28. uses three: "linewidth" is the ASCII representation of the desired line
  29. width.  "buffer" is a temporary buffer area used by Wordwrap to hold portions
  30. of the string when parsing.  "wrapstring" is the character string to wrap.
  31.  
  32. In the program that CALLs Wordwrap, you must declare these variables
  33. consecutively, as shown below.  (These variable names are arbitrary.)  
  34.  
  35. linewidth  = CHR(x)
  36. wrapstring = SPACE(254)
  37. buffer     = SPACE(n)
  38.  
  39. where x is the line width you specify and n is equal to 254 minus the width of
  40. the field to wrap.
  41.  
  42. The order in which you declare these variables is important because Wordwrap
  43. locates them by their position in memory.  Once you declare these memory
  44. variables, do not change the size of "linewidth" or "buffer."  Note that you
  45. initialize "linewidth" using the ASCII representation because the value is
  46. stored in one byte, making access to it simple at the assembler level.
  47.  
  48.  
  49. Sending Parameters from dBASE III  PLUS
  50.  
  51. Because Wordwrap.BIN requires the use of three parameters, it is useful to
  52. understand the concept of passing parameters to and from external procedures. 
  53. When you CALL a procedure WITH a parameter, the address of the parameter is
  54. passed to the procedure in the DS:BX register pair.  Be aware that the DS
  55. register references the data segment from dBASE III PLUS, not the data segment
  56. of the CALLed program.  One option is to pass multiple parameter strings as
  57. one string (as done in dBASE TOOLS for C) and then parse the multiple
  58. parameter string in the CALLed program.  The second option is to declare dBASE
  59. III PLUS variables in strict order so that they are stored contiguously in
  60. memory.  The assembly routine can then access the secondary parameters once it
  61. has established the location of the parameter passed to it on the command line
  62. (the DS:BX register pair).  Wordwrap uses the second method.  Variables are
  63. stored in strict order and located by their relative positions in memory.  
  64.  
  65. To be sure that you don't make mistakes in external procedures, you need to
  66. understand how dBASE III PLUS stores character variables in memory.  Each
  67. character variable has two extra bytes, one at the beginning and one at the
  68. end.  The byte at the beginning contains the length of the string and the byte
  69. at the end is a null (00 hex) byte that terminates the string.  The length of
  70. the string includes the null byte; so, the length value is one greater than
  71. the number of characters in the string.  The diagram below illustrates how the
  72. following two variables are stored in memory: 
  73.  
  74. linewidth  = CHR(65)
  75. wrapstring = SPACE(254)
  76. buffer     = SPACE(120)
  77.  
  78.       {diagram here}
  79.  
  80. This method of passing multiple parameters is risky: It is easy to lose track
  81. of where the variables and null bytes are stored in memory.  Remember: Once
  82. the variables "linewidth" and "buffer" are declared, do not reassign values to
  83. them.
  84.  
  85.  
  86. Setup
  87.  
  88. Before you use Wordwrap.BIN, you must fulfill these basic requirements. 
  89. First, your computer must have at least 320K of RAM.  Second, in order to
  90. assemble Wordwrap.ASM as a .BIN file, you need the IBM or Microsoft Macro
  91. Assembler (MASM.EXE), version 2.0 or higher.  The smaller assembler, ASM.EXE,
  92. will not suffice because it does not support macros, and Wordwrap.ASM makes
  93. extensive use of macros.  You also need the programs LINK.EXE and EXE2BIN.EXE,
  94. which are included as supplemental programs with DOS 2.0 and higher.  
  95.  
  96. Follow the steps below to assemble Wordwrap.ASM, LINK, and convert it to a
  97. LOADable .BIN file.  
  98.  
  99. 1. From the DOS prompt, type: 
  100.  
  101.       MASM WORDWRAP.ASM;
  102.    
  103.    This creates an object file (Wordwrap.OBJ).
  104.  
  105. 2. Then type: 
  106.  
  107.    LINK WORDWRAP;
  108.    
  109.    This creates Wordwrap.EXE.
  110.  
  111. 3. To create a .BIN file, type:
  112.  
  113.       EXE2BIN WORDWRAP.EXE
  114.  
  115. 4. Last, delete Wordwrap.EXE:
  116.  
  117.       ERASE WORDWRAP.EXE
  118.  
  119. The semicolons used in the MASM and LINK command lines suppress any
  120. interactive prompts for additional parameters.
  121.  
  122.  
  123. ; Program ...: Wordwrap.ASM
  124. ; Author ....: Kenneth N. Getz
  125. ; Date ......: February 1, 1987
  126. ; Version ...: dBASE III PLUS 
  127. ;
  128. ;
  129. ;*****************************************************************************
  130. SAVE    MACRO   R1,R2,R3,R4,R5,R6,R7,R8
  131.         IRP     X,<R1,R2,R3,R4,R5,R6,R7,R8>
  132.                 IFNB    <X>
  133.                         PUSH X  
  134.                 ENDIF
  135.         ENDM
  136.         ENDM    
  137.  
  138. ;-----------------------------------------------------------------------------
  139. RESTORE MACRO   R1,R2,R3,R4,R5,R6,R7,R8
  140.         IRP     X,<R8,R7,R6,R5,R4,R3,R2,R1>
  141.                 IFNB    <X>
  142.                         POP X
  143.                 ENDIF
  144.         ENDM
  145.         ENDM    
  146.  
  147. ;*****************************************************************************
  148. CODESEG SEGMENT BYTE    PUBLIC  'CODE'
  149.  
  150. WRAP    PROC    FAR
  151.         ASSUME  CS:CODESEG
  152.         
  153. START:
  154.         JMP     SHORT   BEGIN
  155. LEN     DB      ?                       ; A storage space for the w length.
  156.  
  157. BEGIN:  
  158.         PUSH    DS
  159.         POP     ES                      ; Make ES = DS so we can refere DI.
  160.         SAVE    AX,DI,SI,CX
  161.         CLD                             ; Clear direction flag.  
  162.         CALL    GETBUFFER               ; Point SI to first char Wrapstring
  163.                                         ; and DI to first char in Buffer.
  164.         CALL    ZEROSTRING              ; Zero out Buffer string.
  165.         CALL    FINDSPACE               ; Point SI to character after last 
  166.                                         ; space in Wrapstring.
  167.         CMP     AL,32                   ; See if we searched for a space.
  168.         JNE     FINISH                  ; If not, we're done.
  169.         CALL    COPYSTR                 ; Copy from SI to DI.
  170. FINISH:
  171.         RESTORE AX,DI,SI,CX
  172.         RET                     
  173.  
  174. ;-----------------------------------------------------------------------------
  175. FINDSPACE       PROC    NEAR
  176. ;
  177. ; Findspace finds the next space at which to wrap.  
  178. ; The SI register ends up pointing to the character after the last space,
  179. ; if there are any spaces in the section between the beginning of wrapstr 
  180. ; and the linewidth. It doesn't deal at all with the problem 
  181. ; of no spaces in the wrapping region.  That's
  182. ; up to you.
  183. ;
  184.         SAVE    DI,CX
  185.         MOV     AL,[LEN]                ; AL := wrap length.
  186.         CMP     AL,BYTE PTR [BX-1]      ; Greater than length WrapString?
  187.         JAE     DONE                    ; If not, Return,
  188.         MOV     DI,BX                   ; Otherwise, point DI WrapString.
  189.         AND     AH,0                    ; Zero high byte.
  190.         ADD     DI,AX                   ; Point DI to last possi character.
  191.         AND     CH,0                    ; Zero high byte.
  192.         MOV     CL,[LEN]                ; Move WrapLength into CX.
  193.         DEC     CL                      ; It's one longer than length word.
  194.         MOV     AL,32                   ; Look for a space.
  195.         STD                             ; Set flag to move backwards.
  196.         REPNE   SCASB                   ; Search for space.
  197.         INC     DI                      ; DI points to space.
  198.         INC     DI                      ; DI points to char after space.
  199.         MOV     SI,DI                   ; Need SI to point to gi character.
  200. DONE:
  201.         CLD                             ; Clear direction flag.
  202.         RESTORE DI,CX
  203.         RET
  204. FINDSPACE       ENDP
  205.  
  206. ;-----------------------------------------------------------------------------
  207. ZEROSTRING      PROC    NEAR
  208. ; Zeroes out strings with nulls.  It expects DI to point to beginning string
  209. ; to be cleared out.
  210. ;
  211.         SAVE    CX,AX,DI
  212.         AND     CH,0                    ; Clear out top byte.
  213.         MOV     CL,[DI-1]               ; Get length byte.
  214.         DEC     CL                      ; Don't clear out null at end.
  215.         MOV     AL,32                   ; Move space to AL for clear out.
  216.         REP     STOSB                   ; Put in CX number of spaces.
  217.         RESTORE CX,AX,DI
  218.         RET
  219. ZEROSTRING      ENDP    
  220.  
  221. ;-----------------------------------------------------------------------------
  222. GETBUFFER       PROC    NEAR
  223. ;
  224. ; Getbuffer expects BX to point to beginning of main string, returns w DI
  225. ; pointing to the first character in buffer, and SI pointing to fi character
  226. ; in main string.
  227. ;
  228.         SAVE    CX,AX
  229.         MOV     DI,BX                   ; Point DI to beginning WrapString.
  230.         STD                             ; Set direction flag to w backwards.
  231.         AND     AL,0                    ; Move 0 into AL for REPNE MOVSB.
  232.         MOV     CX,0FFFH                ; Move a big number into CX.
  233.         REPNE   SCASB                   ; Find first null (end of Buffer).
  234.         REPNE   SCASB                   ; Find real null (end WrapLength).
  235.         MOV     AL,BYTE PTR [DI]        ; Move length into AL.
  236.         MOV     LEN,AL                  ; Store away wrap length.
  237.         ADD     DI,3                    ; Point DI to start of buffer.
  238.         MOV     SI,BX                   ; Point SI to beginning WrapString.
  239.         CLD                             ; Clear direction flag. 
  240.         RESTORE CX,AX
  241.         RET
  242. GETBUFFER       ENDP
  243.  
  244. ;-----------------------------------------------------------------------------
  245. COPYSTR PROC    NEAR
  246. ;
  247. ; Expects to find SI pointing to main string somewhere, DI pointing buffer's
  248. ; first char.  Copies out the 'extra' part of the WrapString to the Buffer
  249. ; and terminates the WrapString at the wrap position.
  250. ;
  251.         SAVE    AX,CX,DI,SI             ; Must save SI since MOVSB mo it.
  252.         AND     CH,0                    ; Clear out top byte.
  253.         MOV     CL,BYTE PTR [BX-1]      ; Put length of original into CX.
  254.         MOV     AX,SI                   ; Offset of starting byte in AX.
  255.         SUB     AX,BX                   ; Subtract offset of first byte.
  256.         AND     AH,0
  257.         SUB     CX,AX                   ; Substract difference from to len
  258.         REPNZ   MOVSB                   ; Move from SI to DI.
  259.         DEC     DI                      ; Move back to NULL byte.
  260.         MOV     BYTE PTR [DI],' '       ; Get rid of final null byte.
  261.         POP     SI                      ; Get back to end of WrapString.
  262.         DEC     SI                      ; Back up one space.
  263.         MOV     BYTE PTR [SI],0         ; Put a NULL there to indicate  end.
  264.         RESTORE AX,CX,DI
  265.         RET
  266. COPYSTR ENDP
  267. ;-----------------------------------------------------------------------------
  268. WRAP    ENDP
  269. CODESEG ENDS
  270.         END     START
  271.  
  272.  
  273. Wraptest.PRG
  274.  
  275. Wraptest.PRG is a dBASE III PLUS program that demonstrates how to use
  276. Wordwrap.  When you execute Wraptest, it prompts you for the line width and
  277. page offset (left margin).  It then declares the necessary memory variables
  278. ("linewidth," "buffer," and "wrapstring") and CALLs Wordwrap for each record
  279. in Wrap.DBF.  Wordwrap accepts the text, storing it in a buffer variable. It
  280. parses the string according to the linewidth and sends back a string for
  281. Wraptest to print.  Wraptest then calls Empty.PRG to clear "buffer" before
  282. accepting the next record.    
  283.  
  284. To use Wraptest, you need a text file of information that you want to print
  285. out and a database file named Wrap.DBF with one character field called
  286. "Line."  The width of "Line" can vary, but the sum of "linewidth" and "Line"
  287. cannot exceed 254 characters.  In other words the following expression must
  288. evaluate to true (.T.).
  289.  
  290.    LEN(Line) = 254 - linewidth 
  291.  
  292. If linewidth = 35, the maximum length of Line is 219.  If the sum of
  293. "linewidth" and LEN(Line) exceeds 254, dBASE III PLUS returns the error
  294. message "***Execution error on +: Concatenated string too large."    
  295.  
  296. Use APPEND FROM <text file> SDF to read your text file into Wrap.DBF.  Execute
  297. the program with the command line:
  298.  
  299.    DO Wraptest
  300.  
  301.  
  302. * Program ...: Wraptest.PRG
  303. * Author ....: Kenneth N. Getz
  304. * Date ......: February 1, 1987
  305. * Version ...: dBASE III PLUS
  306. * Note(s) ...: This program demonstrates how to use Wordwrap.BIN,
  307. *              an assembly language program that wraps text according
  308. *              to the specified line width and offset.  It assumes that you
  309. *              have available the database file, Wrap.DBF, which has one 
  310. *              field named Line and which already contains the text to wrap
  311. *
  312. SET TALK OFF
  313. LOAD Wordwrap           
  314. SET PROCEDURE TO Empty  
  315. USE Wrap
  316.  
  317. * ---Define formatting memory variables.
  318. pagewidth  = 80 
  319. linewidth  = 0 
  320. offset     = 0 
  321.  
  322. * ---Get line width and offset, checking to make sure that
  323. * ---the text will fit within the page width (pagewidth).  If it
  324. * ---will not fit, adjust the offset to accommodate.
  325. CLEAR
  326. INPUT "Enter the line width:" TO linewidth
  327. INPUT "Enter page offset: "   TO offset
  328. offset = IIF(linewidth + offset > pagewidth, pagewidth - linewidth, offset)
  329. CLEAR
  330.  
  331. * ---Initialize the variables that Wrap.BIN will use.  They must be
  332. * ---created in the order shown below. 
  333. width      = CHR(linewidth)
  334. buffer     = SPACE(254)
  335. wrapstring = SPACE(254)
  336.  
  337. DO WHILE .NOT. EOF()
  338.  
  339.    * ---If the field is empty, print a blank line.  This is important for 
  340.    * ---separating paragraphs with a blank line.
  341.    IF LEN(TRIM(Line)) = 0
  342.       DO Empty WITH 0
  343.       ?
  344.       SKIP
  345.       LOOP
  346.    ENDIF
  347.    * ---If buffer is empty, store the contents of the next record to 
  348.    * ---wrapstring.  If not, add to wrapstring a space and the 
  349.    * ---contents of the next record.  
  350.    * ---Line is the field name.
  351.    wrapstring = IIF(LEN(TRIM(buffer)) <> 0, TRIM(buffer) + ' ' ;
  352.                 + TRIM(Line),TRIM(Line))          
  353.    CALL Wordwrap WITH wrapstring
  354.    @row(), offset SAY wrapstring
  355.    * ---If there is data left in the buffer, CALL Wordwrap
  356.    * ---again until length of buffer is less than linewidth.
  357.    DO Empty WITH linewidth
  358.    SKIP
  359. ENDDO
  360.  
  361. * ---If there is data left in the buffer, CALL Wordwrap again until
  362. * ---the length of buffer is less than 0.
  363. DO Empty WITH 0
  364. CLEAR ALL
  365. RETURN
  366.  
  367. * EOP Wraptest.PRG
  368.  
  369.  
  370. * Program .....: Empty.PRG
  371. * Author ......: Kenneth N. Getz
  372. * Date ........: February 1, 1987
  373. * Version .....: dBASE III PLUS
  374. * Note(s) .....: This procedure is called by Wraptest.PRG to clear the 
  375. *                variable buffer.
  376. *
  377. PARAMETERS linewidth
  378. DO WHILE LEN(TRIM(buffer)) > linewidth 
  379.    wrapstring = TRIM(buffer)    
  380.    CALL Wordwrap WITH wrapstring           
  381.    @ ROW() + 1, offset SAY TRIM(wrapstring)
  382. ENDDO
  383. * EOP Empty.PRG 
  384.  
  385.  
  386. Other Uses of Wordwrap and Wraptest
  387.  
  388. Wordwrap can be useful for many different applications.  As the Wraptest.PRG
  389. program demonstrates, you can APPEND a text file of information into a
  390. database file and then print that database file as one continuous document. 
  391. You can also use Wordwrap to wrap one record at a time, as you would when
  392. printing a report such as this one: 
  393.  
  394. In this example, Description is a 220-character field that should wrap within
  395. itself, instead of all the Description fields printing together and wrapping
  396. as one continuous paragraph.  You can easily modify Wraptest.PRG to
  397. accommodate this type of report.  Within the DO WHILE .NOT. EOF() loop, use
  398. ROW() or PROW() to place the additional fields in the correct columns. 
  399. Initialize "linewidth" to the length that you want Description to be (in this
  400. case 33) and "offset" to the column position where you want Description to
  401. start printing.  In addition, in order to empty the buffer completely between
  402. records, modify the command
  403.  
  404.    DO Empty WITH linewidth 
  405.  
  406. to read
  407.  
  408.    DO Empty WITH 0  
  409.  
  410. This modification separates the records into distinct items to wrap, printing
  411. the contents of the buffer before SKIPping to the next record.  Here is an
  412. example of the changes you must make to the DO WHILE loop in order to produce
  413. the example report:
  414.  
  415.   DO WHILE .NOT. EOF()
  416.      @ ROW(),  6 SAY Date
  417.      @ ROW(), 17 SAY Hours
  418.      * ---Line is the field to word wrap.
  419.      wrapstring = IIF(LEN(TRIM(buffer)) <> 0, TRIM(buffer) + ;
  420.                   " " + TRIM(Line), TRIM(Line))       
  421.      CALL Wordwrap WITH wrapstring
  422.      @ ROW(), offset SAY wrapstring
  423.      * ---Empty the buffer before going to the next record.
  424.      DO Empty WITH 0
  425.      * ---Print a blank line between records.
  426.      @ ROW() + 2, 0
  427.      SKIP
  428.   ENDDO
  429.  
  430.  
  431. Conclusion
  432.  
  433. Further Information: See "A Procedure for Wrapping Long Strings" in the April
  434. 1985 issue; "LISTing a Database File in a Vertical Form" in the April 1986
  435. issue; and "Mailmerge Application Programming" in the September 1986 issue for
  436. other algorithms and applications using word wrapping.  
  437.