Need help with some text functions

Tags: #<Tag:0x00007fc0def274d8>

Hi all
I am struggling with two simple text functions on a form. I have 4 fields:

FullName
FirstName
LastName
SortField

Fullname and Sortfield are calculated from the other two, and are marked as ReadOnly on the form. Whenever FirstName and/or LastName are updated, I want to update the other fields as follows:

FullName = FirstName + space + LastName

SortField = first 3 letters of LastName in caps, followed by first 3 letters of FirstName in lower case. No spaces, no punctuation. Spaces can be used only if there are no more letters in the field.

For example: First name is “St. John”, Last name is “O’Brien”, SortField becomes “OBRstj” and FullName becomes “St. John O’Brien”.

Simple enough, but I can’t figure out which text functions to use to strip out the unwanted characters, and I have no idea which insertion points to use on the form.

Any help or suggestions would be most welcome. I can use StringTheory functions if that makes it any easier. I know exactly how to do this in Visual Basic, but not in Clarion. An example would help me greatly.

Donn

Try StringTheory.KeepChars

1 Like

Thanks. What insertion point would I use, after the user has changed a field contents?

If the Sort name is visible then make a Routine and DO it on Accepted for both fields. If not visible just DO in the OK button.

There is noting tricky about string code, just write a simple loop to take first 3 chars. … Why SIZE() and not LEN(CLIP()) because the Latter pushes the entire string on the stack, and hunts backwards. That’s a lot of effort when we just want to look at the first 3 to 6.

NameCalcs ROUTINE
   FullName=LEFT(CLIP(FirstName)&' ') & LastName   !Left() incase First Name Blank
   SortField=SortName3x3(FIrstName,LastName)


SortName3x3 PROCEDURE(STRING pFirstName, STRING pLastName)!,STRING
    CODE
    RETURN SortPart4Name(pLastName,1,3) & SortPart4Name(pFirstName,0,3) 

SortPart4Name PROCEDURE(STRING pName, BOOL pIsLastName=1, BYTE pLenPart=3)!,STRING
P   BYTE
X   USHORT,AUTO
    CODE
    pName=UPPER(pName)
    LOOP X=1 TO SIZE(pName)
        CASE pName[X]
        OF 'A' to 'Z'
           P += 1
           pName[P]=pName[X]
           IF P=pLenPart THEN BREAK.
        END
    END
    IF ~pIsLastName THEN pName=lower(pName).
    RETURN CHOOSE(P=0,'',pName[1: P])
1 Like

In addition to what Carl has said. You should take note of the TakeCompleted Embed in Forms.
TakeCompleted is called after ok and after the form has done an Accepted for all fields for Validation.

Before parent can be used for additional validation, a return Level:Cancel will stop and return to the form before updates.
Parent Updates the record

2 Likes

as Carl said, for fullname use

FullName = left(clip(FirstName)&’ ') & LastName

you mentioned having ST and that can simplify the code for your sort field…

and Jeff mentioned keepChars…

st stringTheory
sortField stringTheory

(snip)

st.setValue(upper(LastName))
st.keepChars(‘ABCDEFGHIJKLMNOPQRSTUVWXYZ’)
sortField.setvalue(st.slice(1,3))
sortField.setLength(3) ! in case last name < 3 chars

st.setValue(lower(FirstName))
st.keepChars(‘abcdefghijklmnopqrstuvwxyz’)
sortField.append(st.slice(1,3))

mySortField = sortField.getValue()

that should do the job although there is one slight optimization you could do if you were worried about speed… if you know beforehand what length a field is going to be then you can set that at the beginning. So in this case you could put

sortField.setLength(6)

at the start before your sortField.setValue() - but really this is a minor tweak and only would be noticed if doing this in a loop maybe hundreds of thousands of times…

1 Like

Hi Carl

nice code as always - a couple of comments if I may…

>RETURN CHOOSE(P=0,'',pName[1: P])

I think this probably needs to be something like:

return sub(pname,1,pLenPart) & all(' ',pLenPart - P)

in order to force (pad) the correct length on the LastName

but to be honest without thinking too hard (or, for that matter, testing…) I am not sure if that is correct so I reckon if you have ST then just keep it simple and use it :grinning:

but if “doing it by hand” I might also make P and X into LONGs as that should be faster in 32 bit (even though you don’t need their range) and also on your case statement:

   CASE pName[X]
   OF 'A' to 'Z'

make that

   CASE val(pName[X])
   OF val('A') to val('Z')

for a bit more speed.

cheers

1 Like

Thank you to everyone who has contributed. I have learnt a whole lot more than I was expecting. I will post my working code later today.

Using tps file or an sql backend?

TPS for now (It’s a modified version of the LearningClarion lessons) but will move to PostgreSQL in my actual app.

I was aware of that but usually don’t bother for small bits of code. IIRC (verified below) the original is string compare and VAL() way is numeric. The VAL(‘A’) numbers provided by compiler.

Debug shows your way is simple “cmp b1,41H” i.e. compare to 41H VAL(‘A’) versus using ComareStr and doing a bunch more MOV.

If I change the code to compare A-Z and a-z the the VAL code is equally very tight, nice! I should have lowercase ‘a’-‘z’ first because lower case should be more common

At a DAB talk answering a question he said the USHORT would be the most efficient LOOP variable if you can live with the 65535 limit. If was off the cuff and I have never tested.

I missed Spaces are OK if the name is short e.g The A team’s Mr T changed his legal name to Mr T.

This code will always return 3 bytes of that’s what you request

NameCalcs ROUTINE
   FullName=LEFT(CLIP(FirstName)&' ') & LastName   !Left() incase First Name Blank
   SortField=SortName3x3(FIrstName,LastName)

SortName3x3 PROCEDURE(STRING pFirstName, STRING pLastName)!,STRING
    CODE
    RETURN SortPart4Name(pLastName, 1,3) & |
           SortPart4Name(pFirstName,0,3) 

SortPart4Name PROCEDURE(*STRING pName, BOOL pIsLastName=1, BYTE pLenPart=3)!,STRING
Part STRING(pLenPart)  !<-- Sized as specified
P   BYTE
X   LONG,AUTO
    CODE
    LOOP X=1 TO SIZE(pName)
        CASE VAL(pName[X])
        OF   VAL('a') to VAL('z')  !First as more common                                             
        OROF VAL('A') to VAL('Z')
           P += 1
           Part[P]=pName[X]
           IF P=pLenPart THEN BREAK.
        END
    END
    Part=CHOOSE(~pIsLastName,lower(Part),UPPER(Part))
    RETURN Part

If just learning Clarion is the goal, that is one thing. If really planning to move to PostgreSQL, my suggestion would be to learn how to do it in pgSql and simply display it in Clarion.

I’m missing something obvious here: I selected the “UpdateCustomer” form and used the Embeditor. I went right to the bottom of the form code and pasted the code there, making sure that “NameCalcs ROUTINE” started in the first column so that NameCalcs shows in red.

But when I compile, I got
Unknown identifier FULLNAME
Unknown identifier FIRSTNAME
Unknown identifier LASTNAME

So I assume I’m pasting it in the wrong place, or something else is wrong.

I changed the code to read:

and commented out the rest of the routines for now. That compiles but when I added
DO NameCalcs
in the ACCPTED loop, as shown here:
Capture
I get
Routine not defined: NAMECALCS
from the compiler.

So for now I’m stuck.

I need to do it on the form because the form data gets encrypted by MyTable before it is written to the disk.

I have to do it on the Update form, because when the data is saved, MyTable encrypts it, and creates a RowHash to ensure the data isn’t modified by TopScan or anything else.

I know too little about the table classes to know whether that will work.

I think you made a boo boo. In the embed tree, look for the line Local Routines. It places the routines after the procedure code, But BEFORE any class code. I Think you added your routines into the last method of some class. That would explain the scope problems.

1 Like

As Sean said your Routine must be in the proper embed which would be named “Procedure Routines”. In the Embeditor that would be not far after CODE and before first ThisWindow.Xxxx PROCEDURE().

You do NOT need any PROP:xxx just:

NAMECALCS ROUTINE
  Cus:FullName=LEFT(CLIP(Cus:FirstName)&' ') & Cus:LastName
  DISPLAY(?Cus:FullName)

or you can use Change(?feq)

NAMECALCS ROUTIN 
  CHANGE(?Cus:FullName,LEFT(CLIP(Cus:FirstName)&' ') & Cus:LastName)

Once you get that working you can consider if your Customer Name changes in other forms then read the post by @Richard_R

1 Like

Sean was right. I made a boo boo. Probably several of them! Once I got the code into the right place, I was able to update the fullname field perfectly. Now I am having problems with the calculated sortField.

I’m getting “No Matching Prototype available” errors with the new procedures. I commented out the second one to help with narrowing down the problem.

Again, I’m too new to Clarion programming to have a clue what to do next. But I’m learning and I’m really grateful for all the help so willingly offered.

I would recommend against this change. Firstly because it will have no impact on performance in this context, and secondly because it actually changes the context of what you are doing.

CASE pName[X]
 OF 'A' to 'Z'

Technically this line is already wrong. It should read

 CASE Sub(pName,x,1)
 OF 'A' to 'Z'

The point being that you are comparing “characters in a string”. Using pName[ x] implies that each character takes 1 byte, and using VAL implies the encoding is in ASCII.

In other words changing to

  CASE val(pName [X])
  OF val('A') to val('Z')

is making 2 explicit “equivalences” in the code, and these equivalences may not be true in the future. In the future characters in the string may take more than 1 byte in the string, and they may not be ANSI encoded.

While there is a place for optimizing for performance in code, this is NOT one of those places. And prematurely optimizing code like this will ultimately make programs un-upgradeable.