home *** CD-ROM | disk | FTP | other *** search
- Another Wordwrap
-
- BY KENNETH N. GETZ AND KAREN ROBINSON
-
- In earlier issues, TechNotes has presented articles that described
- word-wrapping routines written in the dBASE III PLUS interpretive
- language--effective routines, but somewhat slow. Here we'll present an
- alternative approach: an assembler routine that you can LOAD and CALL to wrap
- long strings.
-
- Wordwrap.ASM is an assembly language program that takes a long string and
- returns a wrapped string. The dBASE III PLUS program that you use to print
- the wrapped strings must define the margin settings, CALL Wordwrap to parse
- the text, and print the string returned by Wordwrap. Wraptest.PRG, a dBASE
- III PLUS sample program, demonstrates how to use Wordwrap.
-
-
- Wordwrap.BIN
-
- Wordwrap accepts up to 254 characters at a time and parses the string
- according to the line width you specify. For example, if the character string
- is 254 characters wide and your line width is 65, Wordwrap returns to the
- calling program 65 characters at a time. If the sixty-fifth character falls
- in the middle of a word, it wraps that word to the next line.
-
- You CALL Wordwrap WITH the character string you want to wrap as the
- parameter. Although you pass only one parameter on the command line, Wordwrap
- uses three: "linewidth" is the ASCII representation of the desired line
- width. "buffer" is a temporary buffer area used by Wordwrap to hold portions
- of the string when parsing. "wrapstring" is the character string to wrap.
-
- In the program that CALLs Wordwrap, you must declare these variables
- consecutively, as shown below. (These variable names are arbitrary.)
-
- linewidth = CHR(x)
- wrapstring = SPACE(254)
- buffer = SPACE(n)
-
- where x is the line width you specify and n is equal to 254 minus the width of
- the field to wrap.
-
- The order in which you declare these variables is important because Wordwrap
- locates them by their position in memory. Once you declare these memory
- variables, do not change the size of "linewidth" or "buffer." Note that you
- initialize "linewidth" using the ASCII representation because the value is
- stored in one byte, making access to it simple at the assembler level.
-
-
- Sending Parameters from dBASE III PLUS
-
- Because Wordwrap.BIN requires the use of three parameters, it is useful to
- understand the concept of passing parameters to and from external procedures.
- When you CALL a procedure WITH a parameter, the address of the parameter is
- passed to the procedure in the DS:BX register pair. Be aware that the DS
- register references the data segment from dBASE III PLUS, not the data segment
- of the CALLed program. One option is to pass multiple parameter strings as
- one string (as done in dBASE TOOLS for C) and then parse the multiple
- parameter string in the CALLed program. The second option is to declare dBASE
- III PLUS variables in strict order so that they are stored contiguously in
- memory. The assembly routine can then access the secondary parameters once it
- has established the location of the parameter passed to it on the command line
- (the DS:BX register pair). Wordwrap uses the second method. Variables are
- stored in strict order and located by their relative positions in memory.
-
- To be sure that you don't make mistakes in external procedures, you need to
- understand how dBASE III PLUS stores character variables in memory. Each
- character variable has two extra bytes, one at the beginning and one at the
- end. The byte at the beginning contains the length of the string and the byte
- at the end is a null (00 hex) byte that terminates the string. The length of
- the string includes the null byte; so, the length value is one greater than
- the number of characters in the string. The diagram below illustrates how the
- following two variables are stored in memory:
-
- linewidth = CHR(65)
- wrapstring = SPACE(254)
- buffer = SPACE(120)
-
- {diagram here}
-
- This method of passing multiple parameters is risky: It is easy to lose track
- of where the variables and null bytes are stored in memory. Remember: Once
- the variables "linewidth" and "buffer" are declared, do not reassign values to
- them.
-
-
- Setup
-
- Before you use Wordwrap.BIN, you must fulfill these basic requirements.
- First, your computer must have at least 320K of RAM. Second, in order to
- assemble Wordwrap.ASM as a .BIN file, you need the IBM or Microsoft Macro
- Assembler (MASM.EXE), version 2.0 or higher. The smaller assembler, ASM.EXE,
- will not suffice because it does not support macros, and Wordwrap.ASM makes
- extensive use of macros. You also need the programs LINK.EXE and EXE2BIN.EXE,
- which are included as supplemental programs with DOS 2.0 and higher.
-
- Follow the steps below to assemble Wordwrap.ASM, LINK, and convert it to a
- LOADable .BIN file.
-
- 1. From the DOS prompt, type:
-
- MASM WORDWRAP.ASM;
-
- This creates an object file (Wordwrap.OBJ).
-
- 2. Then type:
-
- LINK WORDWRAP;
-
- This creates Wordwrap.EXE.
-
- 3. To create a .BIN file, type:
-
- EXE2BIN WORDWRAP.EXE
-
- 4. Last, delete Wordwrap.EXE:
-
- ERASE WORDWRAP.EXE
-
- The semicolons used in the MASM and LINK command lines suppress any
- interactive prompts for additional parameters.
-
-
- ; Program ...: Wordwrap.ASM
- ; Author ....: Kenneth N. Getz
- ; Date ......: February 1, 1987
- ; Version ...: dBASE III PLUS
- ;
- ;
- ;*****************************************************************************
- SAVE MACRO R1,R2,R3,R4,R5,R6,R7,R8
- IRP X,<R1,R2,R3,R4,R5,R6,R7,R8>
- IFNB <X>
- PUSH X
- ENDIF
- ENDM
- ENDM
-
- ;-----------------------------------------------------------------------------
- RESTORE MACRO R1,R2,R3,R4,R5,R6,R7,R8
- IRP X,<R8,R7,R6,R5,R4,R3,R2,R1>
- IFNB <X>
- POP X
- ENDIF
- ENDM
- ENDM
-
- ;*****************************************************************************
- CODESEG SEGMENT BYTE PUBLIC 'CODE'
-
- WRAP PROC FAR
- ASSUME CS:CODESEG
-
- START:
- JMP SHORT BEGIN
- LEN DB ? ; A storage space for the w length.
-
- BEGIN:
- PUSH DS
- POP ES ; Make ES = DS so we can refere DI.
- SAVE AX,DI,SI,CX
- CLD ; Clear direction flag.
- CALL GETBUFFER ; Point SI to first char Wrapstring
- ; and DI to first char in Buffer.
- CALL ZEROSTRING ; Zero out Buffer string.
- CALL FINDSPACE ; Point SI to character after last
- ; space in Wrapstring.
- CMP AL,32 ; See if we searched for a space.
- JNE FINISH ; If not, we're done.
- CALL COPYSTR ; Copy from SI to DI.
- FINISH:
- RESTORE AX,DI,SI,CX
- RET
-
- ;-----------------------------------------------------------------------------
- FINDSPACE PROC NEAR
- ;
- ; Findspace finds the next space at which to wrap.
- ; The SI register ends up pointing to the character after the last space,
- ; if there are any spaces in the section between the beginning of wrapstr
- ; and the linewidth. It doesn't deal at all with the problem
- ; of no spaces in the wrapping region. That's
- ; up to you.
- ;
- SAVE DI,CX
- MOV AL,[LEN] ; AL := wrap length.
- CMP AL,BYTE PTR [BX-1] ; Greater than length WrapString?
- JAE DONE ; If not, Return,
- MOV DI,BX ; Otherwise, point DI WrapString.
- AND AH,0 ; Zero high byte.
- ADD DI,AX ; Point DI to last possi character.
- AND CH,0 ; Zero high byte.
- MOV CL,[LEN] ; Move WrapLength into CX.
- DEC CL ; It's one longer than length word.
- MOV AL,32 ; Look for a space.
- STD ; Set flag to move backwards.
- REPNE SCASB ; Search for space.
- INC DI ; DI points to space.
- INC DI ; DI points to char after space.
- MOV SI,DI ; Need SI to point to gi character.
- DONE:
- CLD ; Clear direction flag.
- RESTORE DI,CX
- RET
- FINDSPACE ENDP
-
- ;-----------------------------------------------------------------------------
- ZEROSTRING PROC NEAR
- ;
- ; Zeroes out strings with nulls. It expects DI to point to beginning string
- ; to be cleared out.
- ;
- SAVE CX,AX,DI
- AND CH,0 ; Clear out top byte.
- MOV CL,[DI-1] ; Get length byte.
- DEC CL ; Don't clear out null at end.
- MOV AL,32 ; Move space to AL for clear out.
- REP STOSB ; Put in CX number of spaces.
- RESTORE CX,AX,DI
- RET
- ZEROSTRING ENDP
-
- ;-----------------------------------------------------------------------------
- GETBUFFER PROC NEAR
- ;
- ; Getbuffer expects BX to point to beginning of main string, returns w DI
- ; pointing to the first character in buffer, and SI pointing to fi character
- ; in main string.
- ;
- SAVE CX,AX
- MOV DI,BX ; Point DI to beginning WrapString.
- STD ; Set direction flag to w backwards.
- AND AL,0 ; Move 0 into AL for REPNE MOVSB.
- MOV CX,0FFFH ; Move a big number into CX.
- REPNE SCASB ; Find first null (end of Buffer).
- REPNE SCASB ; Find real null (end WrapLength).
- MOV AL,BYTE PTR [DI] ; Move length into AL.
- MOV LEN,AL ; Store away wrap length.
- ADD DI,3 ; Point DI to start of buffer.
- MOV SI,BX ; Point SI to beginning WrapString.
- CLD ; Clear direction flag.
- RESTORE CX,AX
- RET
- GETBUFFER ENDP
-
- ;-----------------------------------------------------------------------------
- COPYSTR PROC NEAR
- ;
- ; Expects to find SI pointing to main string somewhere, DI pointing buffer's
- ; first char. Copies out the 'extra' part of the WrapString to the Buffer
- ; and terminates the WrapString at the wrap position.
- ;
- SAVE AX,CX,DI,SI ; Must save SI since MOVSB mo it.
- AND CH,0 ; Clear out top byte.
- MOV CL,BYTE PTR [BX-1] ; Put length of original into CX.
- MOV AX,SI ; Offset of starting byte in AX.
- SUB AX,BX ; Subtract offset of first byte.
- AND AH,0
- SUB CX,AX ; Substract difference from to len
- REPNZ MOVSB ; Move from SI to DI.
- DEC DI ; Move back to NULL byte.
- MOV BYTE PTR [DI],' ' ; Get rid of final null byte.
- POP SI ; Get back to end of WrapString.
- DEC SI ; Back up one space.
- MOV BYTE PTR [SI],0 ; Put a NULL there to indicate end.
- RESTORE AX,CX,DI
- RET
- COPYSTR ENDP
- ;-----------------------------------------------------------------------------
- WRAP ENDP
- CODESEG ENDS
- END START
-
-
- Wraptest.PRG
-
- Wraptest.PRG is a dBASE III PLUS program that demonstrates how to use
- Wordwrap. When you execute Wraptest, it prompts you for the line width and
- page offset (left margin). It then declares the necessary memory variables
- ("linewidth," "buffer," and "wrapstring") and CALLs Wordwrap for each record
- in Wrap.DBF. Wordwrap accepts the text, storing it in a buffer variable. It
- parses the string according to the linewidth and sends back a string for
- Wraptest to print. Wraptest then calls Empty.PRG to clear "buffer" before
- accepting the next record.
-
- To use Wraptest, you need a text file of information that you want to print
- out and a database file named Wrap.DBF with one character field called
- "Line." The width of "Line" can vary, but the sum of "linewidth" and "Line"
- cannot exceed 254 characters. In other words the following expression must
- evaluate to true (.T.).
-
- LEN(Line) = 254 - linewidth
-
- If linewidth = 35, the maximum length of Line is 219. If the sum of
- "linewidth" and LEN(Line) exceeds 254, dBASE III PLUS returns the error
- message "***Execution error on +: Concatenated string too large."
-
- Use APPEND FROM <text file> SDF to read your text file into Wrap.DBF. Execute
- the program with the command line:
-
- DO Wraptest
-
-
- * Program ...: Wraptest.PRG
- * Author ....: Kenneth N. Getz
- * Date ......: February 1, 1987
- * Version ...: dBASE III PLUS
- * Note(s) ...: This program demonstrates how to use Wordwrap.BIN,
- * an assembly language program that wraps text according
- * to the specified line width and offset. It assumes that you
- * have available the database file, Wrap.DBF, which has one
- * field named Line and which already contains the text to wrap
- *
- SET TALK OFF
- LOAD Wordwrap
- SET PROCEDURE TO Empty
- USE Wrap
-
- * ---Define formatting memory variables.
- pagewidth = 80
- linewidth = 0
- offset = 0
-
- * ---Get line width and offset, checking to make sure that
- * ---the text will fit within the page width (pagewidth). If it
- * ---will not fit, adjust the offset to accommodate.
- CLEAR
- INPUT "Enter the line width:" TO linewidth
- INPUT "Enter page offset: " TO offset
- offset = IIF(linewidth + offset > pagewidth, pagewidth - linewidth, offset)
- CLEAR
-
- * ---Initialize the variables that Wrap.BIN will use. They must be
- * ---created in the order shown below.
- width = CHR(linewidth)
- buffer = SPACE(254)
- wrapstring = SPACE(254)
-
- DO WHILE .NOT. EOF()
-
- * ---If the field is empty, print a blank line. This is important for
- * ---separating paragraphs with a blank line.
- IF LEN(TRIM(Line)) = 0
- DO Empty WITH 0
- ?
- SKIP
- LOOP
- ENDIF
- * ---If buffer is empty, store the contents of the next record to
- * ---wrapstring. If not, add to wrapstring a space and the
- * ---contents of the next record.
- * ---Line is the field name.
- wrapstring = IIF(LEN(TRIM(buffer)) <> 0, TRIM(buffer) + ' ' ;
- + TRIM(Line),TRIM(Line))
- CALL Wordwrap WITH wrapstring
- @row(), offset SAY wrapstring
- * ---If there is data left in the buffer, CALL Wordwrap
- * ---again until length of buffer is less than linewidth.
- DO Empty WITH linewidth
- SKIP
- ENDDO
-
- * ---If there is data left in the buffer, CALL Wordwrap again until
- * ---the length of buffer is less than 0.
- DO Empty WITH 0
- CLEAR ALL
- RETURN
-
- * EOP Wraptest.PRG
-
-
- * Program .....: Empty.PRG
- * Author ......: Kenneth N. Getz
- * Date ........: February 1, 1987
- * Version .....: dBASE III PLUS
- * Note(s) .....: This procedure is called by Wraptest.PRG to clear the
- * variable buffer.
- *
- PARAMETERS linewidth
- DO WHILE LEN(TRIM(buffer)) > linewidth
- wrapstring = TRIM(buffer)
- CALL Wordwrap WITH wrapstring
- @ ROW() + 1, offset SAY TRIM(wrapstring)
- ENDDO
- * EOP Empty.PRG
-
-
- Other Uses of Wordwrap and Wraptest
-
- Wordwrap can be useful for many different applications. As the Wraptest.PRG
- program demonstrates, you can APPEND a text file of information into a
- database file and then print that database file as one continuous document.
- You can also use Wordwrap to wrap one record at a time, as you would when
- printing a report such as this one:
-
- In this example, Description is a 220-character field that should wrap within
- itself, instead of all the Description fields printing together and wrapping
- as one continuous paragraph. You can easily modify Wraptest.PRG to
- accommodate this type of report. Within the DO WHILE .NOT. EOF() loop, use
- ROW() or PROW() to place the additional fields in the correct columns.
- Initialize "linewidth" to the length that you want Description to be (in this
- case 33) and "offset" to the column position where you want Description to
- start printing. In addition, in order to empty the buffer completely between
- records, modify the command
-
- DO Empty WITH linewidth
-
- to read
-
- DO Empty WITH 0
-
- This modification separates the records into distinct items to wrap, printing
- the contents of the buffer before SKIPping to the next record. Here is an
- example of the changes you must make to the DO WHILE loop in order to produce
- the example report:
-
- DO WHILE .NOT. EOF()
- @ ROW(), 6 SAY Date
- @ ROW(), 17 SAY Hours
- * ---Line is the field to word wrap.
- wrapstring = IIF(LEN(TRIM(buffer)) <> 0, TRIM(buffer) + ;
- " " + TRIM(Line), TRIM(Line))
- CALL Wordwrap WITH wrapstring
- @ ROW(), offset SAY wrapstring
- * ---Empty the buffer before going to the next record.
- DO Empty WITH 0
- * ---Print a blank line between records.
- @ ROW() + 2, 0
- SKIP
- ENDDO
-
-
- Conclusion
-
- Further Information: See "A Procedure for Wrapping Long Strings" in the April
- 1985 issue; "LISTing a Database File in a Vertical Form" in the April 1986
- issue; and "Mailmerge Application Programming" in the September 1986 issue for
- other algorithms and applications using word wrapping.