home *** CD-ROM | disk | FTP | other *** search
- EDITORS NOTE:
- NPN member Denny Dias has supplied us with a handy Clipper UDF
- (User Defined Function). As the our first publication of an
- outside article, I would like to encourage all potential NPN
- authors to check out Denny's formating and composition. This is
- the type of 'copy' that editors dream of. Thanks Denny!
-
- Because of the length of this article, and because I am
- presuming that most people will not be reading it online, there
- will not be any page breaks from this page on. If you do intend
- on reading the article online, use the CTRL S and CTRL Q keys to
- stop and start the text. If you wish to leave the article type
- CTRL P.
-
- So here comes FIRSTCAP. Fire it up...its fast...you'll love it!
-
- -ROGER
-
- FIRSTCAP . . . THE FUNCTION
- ---------------------------
- by Dennis L. Dias (NAN449)
-
- Sometimes it is necessary to capitalize the first character of
- each word in a character string. This is true for titles,
- headings, and proper names. There is a procedure listed on the
- Ashton-Tate network that has as similar purpose; however that
- routine has shortcomings that inspired me to write my own.
-
- For one thing, the Ashton-Tate program is a procedure not a
- function; therefore it can only return a result by altering its
- input. Consequently, it can only recieve a memory variable as
- input.
-
- Another problem is the use of the AT() function to find the next
- space between words. That algorithm will return "Ashton-tate"
- (the 't' is left uncapitalized) and "C&h Sugar".
-
- Admittedly, the routines presented here are not perfect. There
- is no test for context or grammer. However, most of the
- problems have been resolved and the function may be used in many
- ways. For example:
-
- LIST FIRSTCAP(TRIM(last) + ", " + TRIM(first) + " " + TRIM(middle))
-
- The main problem with the first version of FIRSTCAP() is speed.
- The routine is much too slow..so slow that I would hesitate to use
- it for anything more than a heading. Repeated calls are out of
- the question. It is possible to gain a little speed by
- capitalizing the first word before entering the main loop and
- eliminating the ( LEN() > 0 ) test in the first CASE statement.
- However, that is not enough to permit the repeated calls of a LIST
- command.
-
- This led me to the second version. In the second version I make
- use of assembly language to access the full speed of the CPU.
- This version is so fast that it is completely invisible during
- program execution. It also demonstrates one method of using
- assembly language within a user defined function.
-
- Here, then, are both versions of FIRSTCAP() along with the
- subfunction for version 1 and assembler routine necessary to
- utilize version 2.
-
-
- *********************************************
- * V E R S I O N 1 *
- *********************************************
- *
- * Function.....: Firstcap() . . . Version 1
- * Author.......: Dennis L. Dias
- * Source ID....: NAN449
- * Date.........: 12/14/85
- *
- * Syntax: FIRSTCAP( <expC> )
- * Return: Character string with first letter of each word
- * capitalized and the rest lower case. The special
- * words "and", "but", "for", "the", "by", "an", "of",
- * "on", "in", "if", "to", "or", "at" and "a" will remain
- * lower case unless: (A) The word is the first word in
- * the string with no leading word separators; (B) The
- * word is followed by a non-space character or (C) The
- * word is the last word in the string.
- *
- * Notes: This first version is written entirely in dBASE. It
- * calls upon a second function (ISCHAR()..also written
- * in dBASE) to determine which characters are "in-word"
- * characters and which are not. The execution speed is
- * slow..unacceptable for repeated calls.
- *
- FUNCTION FIRSTCAP
- *
- PARAMETERS fl_strg
- PRIVATE fl_begn,fl_len,fl_pos,fl_outstr,fl_part,fl_true
- *
- fl_begn = 0
- fl_len = 0
- fl_pos = 1
- IF LEN(fl_strg) = 1
- fl_outstr = UPPER(fl_strg)
- ELSE
- fl_outstr = ""
- ENDIF
- fl_part = ""
- fl_true = LEN(fl_strg) > 1
- DO WHILE fl_true
- fl_begn = fl_pos
- *
- * Scan past in-word characters
- *
- DO WHILE ISCHAR(SUBSTR(fl_strg,fl_pos,1)) .AND. fl_pos < LEN(fl_strg)
- fl_pos = fl_pos + 1
- ENDDO
- *
- * Scan past word separators
- *
- DO WHILE .NOT. ISCHAR(SUBSTR(fl_strg,fl_pos,1)) .AND.;
- fl_pos < LEN(fl_strg)
- fl_pos = fl_pos + 1
- ENDDO
- *
- * Determine if this is the last word
- *
- IF fl_pos = LEN(fl_strg) .AND. (ISCHAR(SUBSTR(fl_strg,fl_pos - 1,1));
- .OR. fl_begn = fl_pos)
- fl_len = fl_pos - fl_begn + 1
- *
- * Set false to exit main loop
- *
- fl_true = .F.
- ELSE
- fl_len = fl_pos - fl_begn
- ENDIF
- *
- * Isolate one word and convert it to lower case
- *
- fl_part = LOWER(SUBSTR(fl_strg,fl_begn,fl_len))
- *
- * Test for special word or a one character word at the end of
- * the line. The use of (" " + TRIM() + " ") protects against
- * an unwanted substring match such as ("he " $ "the ").
- *
- DO CASE
- CASE (" " + TRIM(fl_part) + " ") $ " and the for but to or by an" +;
- " in of on at if a " .AND. LEN(fl_outstr) > 0 .AND.;
- fl_pos < LEN(fl_strg)
- *
- * Special word found in mid-string position where it should
- * not be capitalized..concatenate the word as is
- *
- fl_outstr = fl_outstr + fl_part
- CASE LEN(fl_part) = 1
- *
- * LEN() = 1 only occurs when a one character word appears
- * at the end of the string..capitalize and concatenate
- *
- fl_outstr = fl_outstr + UPPER(fl_part)
- OTHERWISE
- *
- * Capitalize the first character and concatenate
- * the word to the output string
- *
- fl_outstr = fl_outstr + UPPER(SUBSTR(fl_part,1,1)) +;
- SUBSTR(fl_part,2)
- ENDCASE
- ENDDO
- RETURN(fl_outstr)
- *
- **********************************
-
- **********************************
- *
- * Function.....: Ischar()
- * Author.......: Dennis L. Dias
- * Source ID....: NAN449
- * Date.........: 12/14/85
- *
- * Syntax: ISCHAR( <expC> )
- * Return: Logical true if the first character in <expC> is alpha
- * or STR() or an apostrophe (')
- *
- FUNCTION ISCHAR
- *
- PARAMETERS fl_string
- *
- RETURN(UPPER(SUBSTR(fl_string,1,1)) $;
- "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'")
- *
-
- *********************************************
- * V E R S I O N 2 *
- *********************************************
- *
- * Function.....: Firstcap() . . . Version 2
- * Author.......: Dennis L. Dias
- * Source ID....: NAN449
- * Date.........: 12/14/85
- *
- * Syntax: FIRSTCAP( <expC> )
- * Same syntax and output as version 1 except that the first word
- * will always be capitalized..even if a special word is preceded
- * by one or more leading word separators such as "...A Fine
- * Madness...".
- *
- * Notes: This version calls an assembly language routine to do
- * most of the work. The character string passed to the
- * assembler module must first be converted to lower case.
- * Execution speed is very fast..completely invisible.
- *
- FUNCTION FIRSTCAP
- *
- PARAMETERS fl_strg
- PRIVATE fl_outstr
- *
- fl_outstr = LOWER(fl_strg)
- CALL fcap with fl_outstr
- RETURN(fl_outstr)
- *
- ***************************
-
- ;-------------------------------:
- ; Program File.: FCAP.ASM :
- ; Author.......: Dennis L. Dias :
- ; Source ID....: NAN449 :
- ; Date.........: 12/14/85 :
- ;-------------------------------:
- ;
- ;-----------------------------------------------------------------:
- ;This assembly language routine is to be called by Clipper to :
- ; convert the first letter of each word of the input string to :
- ; caps. The special words listed as "special" below will remain :
- ; lower case unless they appear as the first or last word or if :
- ; they are followed by a non- space character. The input string :
- ; must be passed as lower case. The apostrophe (') is :
- ; considered a character. :
- ;-----------------------------------------------------------------:
- ;
- public fcap ;Public procedure
- ;
- _prog segment byte ;Clipper segment
- assume cs:_prog,ds:_prog,es:nothing,ss:nothing
- ;
- ;----------------:
- ;Local data area :
- ;----------------:
- ;
- ;Special words are grouped according to length. Each group is
- ; preceeded by the length plus one space, and the number of words
- ; of that length. This greatly reduces the number of comparisons
- ; required to search the entire list. These numbers MUST be
- ; changed when adding or removing wods from the list.
- ;
- special db 4,4,"and the for but "
- db 3,9,"to or by an in of on if at "
- db 2,1,"a ",0 ;Special words are terminated with null
- ;
- ;List of characters to be considered "in-word" characters
- ;
- char db "0123456789abcdefghijklmnopqrstuvwxyz'"
- ;
- ;------------------------------------------------------:
- ;Primary routine..far procedure as required by Clipper :
- ;------------------------------------------------------:
- ;
- fcap proc far
- ;
- push bp ;Required by Clipper
- mov bp,sp ;To address variable at SP + 6
- push ds ;Must save seg regs
- push es
- cld ;Forward direction flag
- ;
- lds si,dword ptr[bp+6] ;Point to input string with DS:SI
- push cs
- pop es ;Address local data via ES register
- ;
- lea di,char ;ES:DI points to in-word characters
- mov cx,37 ;Length of in-word characters
- ;
- ;Capitalize the first word no matter what it is
- ;
- fc1: lodsb ;Fetch char
- or al,al ;Check for null terminator
- jz fc_ret ;Quit if null..no characters at all
- ;
- call fl_scan ;Test for in-word character
- jnz fc1 ;Scan off word separators
- mov bx,si
- dec bx ;DS:BX points to first character
- call fl_cap ;Cap first letter
- ;
- ;Return here for each new word
- ;
- fc2: mov bx,si
- dec bx ;DS:BX points to first character
- ;
- fc3: lodsb ;Fetch character
- or al,al ;Check for null terminator
- jz fc5 ;Cap last word
- ;
- call fl_scan ;Test for in-word character
- jz fc3 ;Scan past in-word characters
- ;
- fc4: mov dx,si
- sub dx,bx ;DX has size of word plus 1
- cmp dl,4 ;Special words are 3 chars
- ja fc5 ; plus 1 space max
- ;
- call fl_com ;Test for special word
- jc fc6 ;Found..don't cap
- ;
- fc5: call fl_cap ;Cap first letter
- ;
- fc6: dec si ;Point to end of prev word + 1
- lea di,char ;Point to in-word characters
- mov cx,37 ;Length of in-word characters
- ;
- fc7: lodsb ;Fetch char
- or al,al ;Check for null terminator
- jz fc_ret ;Quit if null
- ;
- call fl_scan
- jnz fc7 ;Scan off word separators
- jmp fc2 ;Go process next word
- ;
- ;Done
- ;
- fc_ret: pop es ;Restore registers for Clipper
- pop ds
- pop bp
- ret ;Far return to Clipper
- ;
- fcap endp
- ;
- ;------------------:
- ;Local subroutines :
- ;------------------:
- ;
- ;The following procedure tests the character in AL as being in-word or
- ; not. The zero flag returns set if the character is in-word.
- ;
- fl_scan proc near
- ;
- push di ;Save pointer to in-word characters
- push cx ;Save length of in-word characters
- repne scasb ;Look for match
- pop cx ;Restore saved values and return
- pop di ; result in Zero flag
- ret ;Near return
- ;
- fl_scan endp
- ;
- ;Convert lower case to upper case..DS:BX points to the character
- ;
- fl_cap proc near
- ;
- cmp byte ptr[bx],"a"
- jb fl_cap_ret ;Less than "a" not lower
- cmp byte ptr[bx],"z"
- ja fl_cap_ret ;Above "z" not lower
- sub byte ptr[bx],20h ;Convert to upper case
- ;
- fl_cap_ret: ret ;Near return
- ;
- fl_cap endp
- ;
- ;Look for special words..return carry flag set if found..on entry
- ; DS:BX points to first character of the word to test. Begin by
- ; exchanging the contents of ES with DS to facilitate the use of
- ; the string instructions. AX, CX, DX, and DI destroyed.
- ;
- fl_com proc near
- ;
- push si ;Save current pointer
- push ds
- pop es ;Clipper segment to ES
- push cs
- pop ds ;Local segment to DS
- mov di,bx ;ES:DI points to current word
- lea si,special ;DS:SI points to local list of words
- ;
- fl_c1: lodsb ;Fetch word size
- or al,al ;Check for null terminator
- jz fl_clc ;Exit routine..special word not found
- ;
- mov cl,al ;Size of next group of words to count reg
- xor ch,ch ;Zero high byte
- lodsb ;Fetch word count
- mov dh,al ;To DH for outer loop
- mov ax,cx ;Save count in AX
- ;
- fl_c2: push di ;Save pointer to test word
- mov cx,ax ;Word size to counter
- repe cmpsb ;Compare bytes
- je fl_stc ;Jump if special word found
- add si,cx ;Point to next word or next group
- pop di ;Recover pointer to test word
- dec dh ;Reduce outer loop counter
- jnz fl_c2 ;Anoter word..same length
- jmp fl_c1 ;Go for next group of words
- ;
- fl_clc: clc ;Clear carry..special word not found
- ;
- fl_com_ret: pop si ;Restore registers and return
- push es
- pop ds ;Clipper segment to DS
- push cs
- pop es ;Local segment to ES
- ret ;Near return
- ;
- ;Special word found..return result in carry flag
- ;
- fl_stc: pop di ;Clear stack
- stc ;Set carry flag
- jmp fl_com_ret ;Restore and return
- ;
- fl_com endp
- _prog ends
- end