Easy to Create, Easy to Change - Easy to use!


Syntax of enter a record function


Started by Bill Nicholson
Search
You will need to Sign In to be able to add or comment on the forum!

Syntax of enter a record function

Could someone please explain the construct of the following coding from the CRM application:

For Customers with Selected= yes; -- This selection only print the customers that are checked in the list.
     For DocumentEditor with DocumentID = data-entry field1
         enter a record in Documents
             copy all from DocumentEditor ;
             customerNr := Customers CustomerNr ;
             DocumentGroupID := data-entry Field1 ;
             Dummy := MemoReplace(Body ,concat("[{CustomerName}]",""), Customers CustomerName ) ; -- here we dynamically replace the tags in the matrix
             Dummy := MemoReplace(Body ,concat("[{Contact}]",""),Customers Contact ) ;
             Dummy := MemoReplace(Body ,concat("[{Address}]",""),Customers Address ) ;
             Dummy := MemoReplace(Body ,concat("[{Town}]",""),Customers Town ) ;
             Dummy := MemoReplace(Body ,concat("[{PostCode}]",""),Customers PostCode ) ;
             Dummy := MemoReplace(Body ,concat("[{Heading}]",""),DocumentEditor Heading ) ;
             Dummy := MemoReplace(Body ,concat("[{Country}]",""),Customers Country ) .
     end
     telle := telle + 1 . -- lazy again, it never need to be more than 1 to make the trigger mechanise safe.
end

The use of the MemoReplace function within the span of the 'enter a record' statement isn't in accordance with the usual syntax however it seems to work.

Using the above as a model I have tried the following coding which creates an error at compile time:

for _PrintInterim ;
     for rGenDocs_PrintInt -- (this is the name of the relationship between _PrintInterim and _GeneratedDocuments)
     modify records
         retval := MemoReplace(MemoBody, "[[Recipient]]", _PrintInterim Memo1) ;
         retval := MemoReplace(MemoBody, "[[Documents]]", _PrintInterim Memo2) .
end

If there was a memo data type one could define a temporary DQL variable, perform the data substitutions in memory and then have a conventional 'modify records' statement to save it.

Bill NIcholson

Written by Bill Nicholson 19/11/14 at 07:28:52 Dataease [{8}]FIVE

Re:Syntax of enter a record function

Download Sample!

Hi again Bill.

Well spotted! There is something wrong with MemoMemoReplace(). It is such a powerful function when it works, but it looks like it doesn't work as it should now. I think the problem is that it hasn't been used much yet and some change in the Memo Structure lately must have broken it.

MemoReplace() we use extensively and work without fail. 

MemoMemoReplace is a big function as it replaces a Tag in a Memo with the content of a full memo so a little "special". it has slipped through our net because even though it has great potential, it is not that often you want to replace that much information in the middle of a document. Mostly what you want to replace fit nicely and snugly within 255 which is covered by the working one above.

MemoMemoCopy() works without fail to, which is the way we build documents.

We Copy all the bits of the document together with MemoMemoCopy() and the we replace the tags in one go with MemoReplace().
define "retval" text .

for MemoEditor with DocID=data-entry field1 ;

retval := MemoMemoCopy(Result,Heading,1) .

retval := MemoMemoCopy(Result,Body,0) .

retval := MemoMemoCopy(Result,Footer,0) .

retval := MemoReplace(Result,[{Name}],any Customers with CustomerID=Data-Entry field2 Name) .
retval := MemoReplace(Result,[{Address}} any Customers Address) .

enter a  record in NewDcuments 

Result := MemoEditor Result .

end


if the document is formatted in HTMLEdit it will be properly closed (i.e <p></p> or <div></div> etc, so you should not add extra CR etc. 

The functions is designed to work on pure text so one need to look after ones html tags oneself.

HTMLedit and WebField is just GUI controls and have no effect on the memo. The Memo field is the same whatever and you can change the control around in the GUI to see the the code, webpage, or edit etc.

Obviously a Text written in a Editobox won't look correct if presented in a MemoField or a HTMLeditor, and if you prsent the result of a HTMLedit in an Editobox it will show the html.

We have learnt a lot from this thread and hopefully so have you. A lot of this is virgin matter for everyone involved so we need to iron out the creases. The problem from your angle is that you try to do to much before in one go ;-)

With new functionality the best approach is baby steps. Get something small to work so you get the underlying idea and then up it a notch. Debugging is way easier if one only have one bug to find, when it is a matrix it gets close to impossible. You were unlucky in your last example because you used MemoMemoCopy() rather than MemoCopy() which kind of threw the entire thing off its rails.

Memos should be used only when one really need a big field because they are slow and cumbersome compared to normal fields.

They are great for what they are great at, but crap as fields in normal DE terms.

define "retval" text .

for MemoEditor with DocID=data-entry field1 ;

retval := MemoMemoCopy(Result,Heading,1) .

retval := MemoMemoCopy(Result,Body,0) .

retval := MemoMemoCopy(Result,Footer,0) .

modify records in MemoEditor with DocID=data-entry field1

Result := MemoEditor Result .

end

retval := SelectionFilter("DocID=",Data-entry field1) .

PS! The sample is there so you can test the fix when we publish it ;-)

Written by DataEase 26/11/14 at 21:56:35 Dataease [{8}]FIVE

Re:Re:Syntax of enter a record function

Even in the best of families...

You will love this. There has been some harmonization of code, and this has meant that the MemoMemoReplace() function takes Memo as argument for all three arguments... (Only programmers...).

This meant that it would have worked if you put the tag in a memo field and referenced it but that is a little "far out".

It has now been fixed and uploaded, you find the link to it here. 

Again, sorry for the trouble and thanks for the help in identifying this bug.

Written by DataEase 27/11/14 at 11:29:55 Dataease [{8}]FIVE

Re:Syntax of enter a record function

"The use of the MemoReplace function within the span of the 'enter a record' statement isn't in accordance with the usual syntax however it seems to work."

You are right, this is not in accordance with the usual syntax but is that necessarily a bad thing? 

I must admit that it was a little trial and error and based on some far out theories that lay beyond this code, but then again it was basically made just days after we implemented the functions and without anyone really understanding what and how it would work....

The original problem was of course that we tried to do something that we didn't really thought would work, but it did. The problem was that we didn't have a complete function library and we didn't really have the complete understanding of how PRISM and GUI was "glued" together.

You have to remember that we didn't make this product, we simply "Inherited" it...

But what it show us is fantastic.

Thinking back now we the understanding we have now acquired it is almost humoristic, but you have to realise that when we did this we simply "tried" to get it to work.

We had already realised that Memo doesn't work like everything else, so how can we change it (without your Memo DataType) and still get what we want....

The way we saw it is.

The For read the Record into memory (Right hand), when we enter a record we create a new Memory Record that match a the Document TDF which we then populate (Left Hand). 

According to the "linear" thinking of traditonal DataEase we needed to find a way of manipulating the left hand side. Normally that would be a temp variable but as you have pointed out, we don't have a Memo Temp Type...

So what we did was to create a "dummy" field in the table and use the derivation we would have done in the form as "derivation" in the DQL.

And VOILA it worked! Simply because this is how it does work. DataEase keep both the LeftHand and the RightHand memory structure and read from the right and write into the left and when it is happy, it write the left one to file.

So the Memo named Body is in the references in the right hand structure before the Enter a Record in and in the RightHand structure outside the Enter a record....

When one realise this and one then realise how MemoManipulation works you realise that you don't have to manipulate the LeftHand one, you simply need to Manipulate the Right hand one and then write it to the LeftHand one...

define "retval" text .

For Customers with Selected= yes; -- This selection only print the customers that are checked in the list.

For DocumentEditor with DocumentID = data-entry field1 ;
Retval := MemoReplace(Body ,concat("[{CustomerName}]",""), Customers CustomerName ) .
Retval := MemoReplace(Body ,concat("[{Contact}]",""),Customers Contact ) .
Retval := MemoReplace(Body ,concat("[{Address}]",""),Customers Address ) .

Retval := MemoReplace(Body ,concat("[{Town}]",""),Customers Town ) .
Retval := MemoReplace(Body ,concat("[{PostCode}]",""),Customers PostCode ) .
Retval := MemoReplace(Body ,concat("[{Heading}]",""),DocumentEditor Heading ) .
Retval  := MemoReplace(Body ,concat("[{Country}]",""),Customers Country ) .

enter a record in Documents
copy all from DocumentEditor ;
customerNr := Customers CustomerNr ;
DocumentGroupID := data-entry Field1 .
end
telle := telle + 1 . -- lazy again, it never need to be more than 1 to make the trigger mechanise safe.
end

Now we simply modify the Memo in the Original Memory record and it gets copied over with the Copy All From... We could also have done.
Body := DocumentEditor Body ;

Sorry for the long introduction but this is all for "posterity" so need some time to knock the concept into the public...

Now onto your code.

We take it on the chin but I think that if you guys start at least mistaking that we know what we are doing, then it would be easier for all of us...he,he.

We obviously started with wanting to build a Memo Data Type but that was and is a showstopper. Simply because that would mean that we would have to change the entire PRISM structure into being a Memo DataType. 

DataEase "coding" is one big mess. Even if we had a Memo DataType non of the functions would be able to use it as they can't handle more than 255...

So either we redesigned PRISM, which would have taken years and would have led to another Migration nightmare....(all your CFDS would no longer work for starters...) As good old Richard Zboray pointed out to me some time back: "DataEase has been in decline for a while now...."., so that level of investment would basically make it a non-starter. 

We would basically completely redefine DataEase with no hope of carrying any existing apps with us. 

So our policy is to let dead dogs lie and add functionality that plug the holes and lift it off the ground.

We know it is early days for this, but you have to clearly separate DataEase pre 8.x and and 8.0 onwards and you also have to completely separate Memo from the rest of the DataTypes.

1. When working with Memo you MANIPULATE the existing Memo. When you work with a field you re-allocate the value for that field.
2. There is no connection between Memo and other field types that doesn't go via a Specialised Memo Function.
3. No Function can work on a Memo and the other Data Types, so to do the same on a Memo as you do on a Normal Data Type you need a Memo Function that do it.

retval := MemoReplace(MemoBody, "[[Recipient]]", _PrintInterim Memo1) ;

1. this would have worked if Retval had been a field in _GeneratedDocuments. You can't allocated anything to a Variable inside the Modify Records or Enter a Record part. That is why we made the hack with Dummy that was a Field in the table.

2. MemoReplace() replace the Search(TAG) with the content of a NORMAL data type . This can be a string(255) or a any field EXCEPT a Memo. (It can be a memo too but then you only get the first 128 bytes of the memo....don't ask).

3. We do of course have a function that can insert a Memo into another Memo it is like all Memo2Memo functions called MemoMemo i.e MemoMemoReplace.

4. On top of everything, you haven't defined the temporary variable...atlest not in the snippet you put on here.

define "retval" text .
for _PrintInterim ;

for rGenDocs_PrintInt -- (this is the name of the relationship between _PrintInterim and _GeneratedDocuments) 
retval := MemoReplace(MemoBody, "[[Recipient]]", _PrintInterim Memo1) .

retval := MemoReplace(MemoBody, "[[Documents]]", _PrintInterim Memo2) .

modify records 

MemoBody := MemoBody . -- should work, as DE writes the entire record to file whatever you do, so if you had a field called Dummy (virtual) like me you could simply write: Dummy := "Anything" .
end

end

The truth is that the way MemoClass work is much better than using Temp variables or the traditonal MyName := Concat(FirstName,SurName). 

We simply modify and manipulate the memory structure and when happy we copy stuff back or write it to file or do whatever.

it might be a little to get ones head around, but when one do one realise that the sky is the limit.

You can build documents from snippets of code, replace part of it with other documents etc...

Written by DataEase 19/11/14 at 17:35:38 Dataease [{8}]FIVE

Re:Re:Syntax of enter a record function

It seems I got a little ahead of myself there... This is what happens when you write things from the top of your head and don't have the code to work on.

There is a SIGNIFICANT difference between Enter A Record and Modify Records and that is when you modify the Record which is currently holding the cursor.

DataEase is not "stupid" so when you do a Modify records of the current record it take a short-cut and simply save the LeftHand structure to file without you being able to "touch" the RightHand data. 

So the work around is either to do the manipulation like we did in the CRM sample via a Dummy field in the Table or simply do the Modify Records fully with a relation to itself so you can reference the RightHand Structure.

So your DQL would look like this:


define "retval" text .for _PrintInterim ;

for rGenDocs_PrintInt -- (this is the name of the relationship between _PrintInterim and _GeneratedDocuments)
retval := MemoReplace(MemoBody, "[[Recipient]]", _PrintInterim Memo1) .

retval := MemoReplace(MemoBody, "[[Documents]]", _PrintInterim Memo2) .

modify records in _GeneratedDocuments with DocId = rGenDocs_PrintInt DocId -- (This is just to show as I don't know if ther is a doc id...)

MemoBody := rGenDocs_PrintInt MemoBody . -- now you can set your new MemBody to be the manipulated one.
end

end

Written by DataEase 20/11/14 at 00:18:07 Dataease [{8}]FIVE

Re:Re:Re:Syntax of enter a record function

I have spent a lot of time on this and cannot make it work. For one final time, the code we now have is:

define "retval" text 5 . -- temp variable at head of DQL procedure

for _PrintInterim ;
     for rGenDocs_PrintInt -- this is the relationship between _PrinterInterim and _GeneratedDocuments
          Message concat( "Flag 1 ",DocumentID) window .

     retval := MemoMemoReplace(MemoBody, "[[Recipient]]" , _PrintInterim Memo1) .
           Message concat( "Flag 2 ",retval) window .
     retval := MemoMemoReplace(MemoBody, "[[Documents]]" , _PrintInterim Memo2) .
             Message concat( "Flag 3 ",retval) window .
     modify records in _GeneratedDocuments with DocumentID = TransNo
         MemoBody := rGenDocs_PrintInt MemoBody .
     end
end

 My comments are:

1. "retval" is a temporary variable rather than a field in _GeneratedDocuments which would create a compile time error.

2. The Flag 1 message confirms that we have found the right record in _GeneratedDocuments.

3. The Flag 2 / 3 messages confirm that execution has passed through this code. The return value is "1" - what is the significance of this?

4. We are now using the MemoMemoReplace function as advised.

Everything seems to run fine but the placeholder text isn't replaced as expected. There is probably an idiot error there but I can't see it. Any advice would be welcome.

Written by Bill Nicholson 20/11/14 at 10:11:46 Dataease [{8}]FIVE

Re:Re:Re:Re:Syntax of enter a record function

This is another attempt to change the placeholder text in _GeneratedDocuments with the text held in _PrinterInterim Memo1/2. It follows the construction used in routine DQL0003 in DQLStore of the CRM example.

define "retval" text 5 .

for _DummyForm ;
for _GeneratedDocuments with DocumentID = TransNo ;
retval := MemoMemoCopy(_DummyForm DummyMemo, MemoBody, 1) .
Message concat( "Flag 1 ",retval) window .
-- we now have the draft document in memory for manipulation
for _PrintInterim with ID = TransNo ;
retval := MemoMemoReplace(_DummyForm DummyMemo, "[[Recipient]]", Memo1) .
Message concat( "Flag 2 ",retval) window .
retval := MemoMemoReplace(_DummyForm DummyMemo, "[[Documents]]", Memo2) .
Message concat( "Flag 3 ",retval) window .
end
modify records in _GeneratedDocuments with DocumentID = TransNo
MemoBody := _DummyForm DummyMemo .
end

The first flag message has return value 0 and flags 2 and 3 have value 1. Is this a clue?

Any ideas will be gratefully welcomed.

Written by Bill Nicholson 20/11/14 at 14:20:01 Dataease [{8}]FIVE

Re:Re:Re:Re:Re:Syntax of enter a record function

The MemoMemoCopy() function returns the number of replacements it has done, so that one works.

Firstly you don't need the _Dummyform.

define "retval" text 5 .

for _GeneratedDocuments with DocumentID = TransNo ;
for _PrintInterim with ID = TransNo ;
retval := MemoMemoReplace(_GeneratedDocuments MemoBody, "[[Recipient]]", Memo1) .
Message concat( "Flag 2 ",retval) window .
retval := MemoMemoReplace(_GeneratedDocuments MemoBody, "[[Documents]]", Memo2) .

Message concat( "Flag 3 ",retval) window .
end
modify records in _GeneratedDocuments with DocumentID = TransNo
MemoBody := _GeneratedDocuments MemoBody,.

end

Written by DataEase 20/11/14 at 15:39:10 Dataease [{8}]FIVE

Re:Re:Re:Re:Re:Re:Syntax of enter a record function

I am sorry to keep coming back to the Forum but I simply cannot get the MemoReplace / MemoCopy functions to work no matter how I approach it. I have stripped the whole procedure down to the bare minimum and it now looks like this:

-- This pocedure extracts relevant data from Transmittal Notes and writes to file for printing later

define "retval" text 5 .
define "TransNo" text 40 .

TransNo := GetVar("TransmittalNo") .
delete records in _GeneratedDocuments with DocumentID = TransNo .

enter a record in _GeneratedDocuments
DocumentID := TransNo ;
TemplateRef := "0005" .
modify records in _GeneratedDocuments with DocumentID = TransNo
MemoBody := any rTemplate_GenDocs MemoBody .

for _GeneratedDocuments with DocumentID = TransNo ;
message concat("Flag1 ", DocumentID) window .
for _Trans Orgs with TN Ref = TransNo ;
retval := MemoCopy(_GeneratedDocuments MemoBody,Organisation,4) .
message concat("Flag2 retval = ",retval) window .
message concat("Flag3 ",Organisation) window .
end
modify records in _GeneratedDocuments with DocumentID = TransNo
MemoBody := _GeneratedDocuments MemoBody .

end

retval := DocumentOpen("GeneratedDocuments") .

Notes:

1. Flag1 shows that we have found the right record in _GeneratedDocuments.

2. Flag2 shows that retval has value 0. Does this mean that the MemoCopy has failed and if so what can cause this?

3. Flag3 shows that we have found the right Organisation.

4. The field MemoBody isn't virtual, doesn't have Prevent Entry set, it doesn't have a derivation nor is there any OML to mess things up. In other words it should be possible to write to it without problem.

Generated document should say "COSCO Marine" at the bottom but it doesn't as one can see from the attached.

Any advice to get this to work will be gratefull received. If I can't get it to work the whole application will have to be scrapped since its purpose is to generated nicely formatted documents like this.

Written by Bill Nicholson 24/11/14 at 14:24:53 Dataease [{8}]FIVE

Re:Re:Re:Re:Re:Re:Re:Syntax of enter a record function

Download Sample!

Hi again Bill.

Your main problem is that you do things very difficult for yourself.

1. DataEase is not C, Basic of Pascal or any such thing. The scripting language basically create an advanced view/structure so if  you "jump around" it will be a) Ineffective b) Don't work.

It looks like nothing in the DQL but what you do is "crazy" in DE terms.

Do delete, enter and modify the same "record" in the same DQL won't simply work. (Yes it will sometimes, but in general it won't!).

You need to think batch. Do one thing in one DQL and then call another DQL to do the next etc. Each one of these will then have their own multiview. The way you do it you basically nest DataEase into an exclusive loop.

Anyway, the problem comes from your testing setup with the Delete and Enter stuff.

I won't start fixing your DQL, but I have included a sample that do the same thing.

And one last ting. Try to nest as few FOR loops as possible. if you want to allocate data from a remote table to a variable of a field simply use any directly in the Modify/Enter a record.


define "retval" text .

modify records in MyMailmerge with LetterID = data-entry field1

Letter := any TemplateGenerator with TemplateName = data-entry field2 Template .

for MyMailMerge with LetterID = data-entry field1 ;

retval := MemoReplace(Letter,"[{CompanyName}]",CompanyName ) .

retval := MemoReplace(Letter,"[{Address}]",Address ) .

retval := MemoReplace(Letter,"[{Town}]",Town ) .

retval := MemoReplace(Letter,"[{PostCode}]",PostCode ) .

retval := MemoReplace(Letter,"[{Att}]",Att) .

retval := MemoReplace(Letter,"[{CurrentDate}]",CurrentDate ) .

retval := MemoReplace(Letter,"[{SpellDate}]",SpellDate ) .

retval := MemoReplace(Letter,"[{Heading}]",Heading ) .

retval := MemoCopy(Letter,"(C) This Document is Autogenerated with DE8.2",4) .

modify records in MyMailMerge with LetterID=data-entry field1

Letter := MyMailMerge Letter .

end .

retval := SelectionFilter("LetterID=",data-entry field1) .

This was my original DQL in my sample, just to prove the point. This one didn't work...

Then I simply unwound it into two DQLs.

Label Named DQL1:

define "retval" text .

modify records in MyMailmerge with LetterID = data-entry field1

Letter := any TemplateGenerator with TemplateName = data-entry field2 Template .

Retval := LabelExecDQL("DQL2",data-entry field1 ) .

Label Named DQL2:

define "retval" text .

for MyMailMerge with LetterID = data-entry field1 ;

retval := MemoReplace(Letter,"[{CompanyName}]",CompanyName ) .

retval := MemoReplace(Letter,"[{Address}]",Address ) .

retval := MemoReplace(Letter,"[{Town}]",Town ) .

retval := MemoReplace(Letter,"[{PostCode}]",PostCode ) .

retval := MemoReplace(Letter,"[{Att}]",Att) .

retval := MemoReplace(Letter,"[{CurrentDate}]",CurrentDate ) .

retval := MemoReplace(Letter,"[{SpellDate}]",SpellDate ) .

retval := MemoReplace(Letter,"[{Heading}]",Heading ) .

retval := MemoCopy(Letter,"(C) This Document is Autogenerated with DE8.2",4) .

modify records in MyMailMerge with LetterID=data-entry field1

Letter := MyMailMerge Letter .

end .

retval := SelectionFilter("LetterID=",data-entry field1) .

Now it works suddenly, simply because I get two different Multiview so no conflict.

DataEase is DataEase and nothing else, if you work with it you get wonderful results but if you treat it like a normal development tool you are in for a rough ride. The general rule is to combine many small DQL's rather than long and intricate ones as they aren't scripts but really instances like a form etc.

Written by DataEase 25/11/14 at 13:46:26 Dataease [{8}]FIVE

Re:Re:Re:Re:Re:Re:Re:Re:Syntax of enter a record function

Well there's a lot of comments in the last post about programming style which aren't contained elsewhere (e.g. the existing DQL manual) and users can be forgiven for simply treating DQL as another language if not told otherwise. I would also point out that some of the deprecated constructs are included in the sample applications (e.g. nested 'for' loops in CRM procedure ref 400 in ExecDQL Store). I would suggest that the sample applications should only contain best programming practice because users will naturally use these as a model.

I have followed the advice and split my DQL into two scripts, the second of which is given below. I cannot get MemoReplace or MemoMemoReplace to work in any shape or form. MemoCopy works (just) although the formatting (i.e. the line breaks) gets lost. See the attached exhibits. Where can I go now?

-- This pocedure performs the second stage of extracting data and merging into a Transmittal Note.

define "retval" text 5 .
define "TransNo" text 40 .

TransNo := GetVar("TransmittalNo") .

-- we now substitute the placeholders in the generated transmittal note

for _GeneratedDocuments with DocumentID = TransNo ;
Message any rPrintInt_GenDocs ID window . -- this shows that we have found the right record in _PrinterInterim (relationship name rPrintInt_GenDocs)
retval := MemoReplace(MemoBody, "[[TN No]] ", TransNo ) .
retval := MemoReplace(MemoBody ,"[{Date}]",spelldate(Current Date) ) .
retval := MemoMemoReplace(MemoBody, "[[Recipient]]", any rPrintInt_GenDocs Memo1 ) .
retval := MemoMemoReplace(MemoBody, "[[Documents]]", any rPrintInt_GenDocs Memo2 ) .

retval := MemoMemoCopy(MemoBody, any rPrintInt_GenDocs Memo1,4 ) . -- if we can't replace text then let's try to append instead
retval := MemoMemoCopy(MemoBody, any rPrintInt_GenDocs Memo2,4 ) . -- if we can't replace text then let's try to append instead

modify records in _GeneratedDocuments with DocumentID = TransNo
MemoBody := _GeneratedDocuments MemoBody .

end

Written by Bill Nicholson 26/11/14 at 11:41:56 Dataease [{8}]FIVE

Re:Re:Re:Re:Re:Re:Re:Re:Re:Syntax of enter a record function

This will be my final post on this topic which has gone on for too long.

The MemoReplace and MemoMemoReplace functions appear to be fragile and may need further testing.

The desired document can be constructed from a series of MemoCopy statements albeit this will be tedious to create. To keep the desired formatting one needs to insert the proper HTML tags of course (e.g <br> rather than CR). The behaviour of the memo field depends whether it's defined as EditBox or HTML.  If one uses a MemoMemoCopy statement the two memos should be of the same type.

Written by Bill Nicholson 26/11/14 at 20:27:21 Dataease [{8}]FIVE