Misc tips/reminders
ILE
There are three types of arrays:
This will Z-ADD 15 to every element of ARRAY
C Z-ADD15 ARRAY
Each element of ARRAY1 will be added to the corresponding element of ARRAY2
C ADD ARRAY1 ARRAY2
%Abs
%Char
%Check(look_for: string_to_search)
%Date(optional_date: optional_date_format)
%Days
%Dec
%Dech
%DecPos
%Diff
%Div
DIV = %DIV(A:B); (divide A by B)
%Editc(Number: 'X': *CURSYM)
Edit Code Description | No Sign | CR Sign | -Sign (R) | -Sign (L) |
Commas and zero balances | 1 | A | J | N |
Commas | 2 | B | K | O |
Zero balances | 3 | C | L | P |
No commas or zero balances | 4 | D | M | Q |
User-defined edit codes | 5-9 |
Date edit (4 digits) | W |
Date edit | Y |
Suppress leading zeros | Z |
%Editw
%Elem
%EOF(filename)
%Equal
%Error
%Fields
%Float
%Found(optional_filename)
%Int %Inth
%Len %Lookupxx(argument: array{:start index {:number of elements}}
%Occur(DS)
%Open(filename)
%Parms
%Rem
REM = %REM(A:B);
%Replace(string: into_string: optional_start: optional_length) %Scan(look_for: look_in: optional_start)
%Size
DIM(%SIZE(OTHER_FIELD)) // Use %size and %substr to extract 3 concatenated fields WITHOUT hard-coding the field sizes eval #cono = %subst($$id:1:%size(#cono)) eval #cusn = %subst($$id:%size(#cono)+1:%size(#cusn)) eval #dseq = %subst($$id:%size(#cono)+%size(#cusn)+1:%size(#dseq))
%Subdt(date: unit)
%SUBDT(value:*MSECONDS|*SECONDS|*MINUTES|*HOURS|*DAYS|*MONTHS|*YEARS) %SUBDT(value:*MS|*S|*MN|*H|*D|*M|*Y)
%Subst
// After the EVAL the original value of A contains ’ab****ghijklmno’ %SUBST(A(3:4))= ’****’;
%Time %Timestamp %TLookupxx(argument: search table {: alternate table}
%Trim %Triml %Trimr %Uns
%XFoot(array)
%Xlate(Lower:Upper:String)
CAT(P)
CHECK CHECKR CLEAR
Eval
Eval(R)
Evalr
FEOD
FOR
GOTO
Max() Min() SCAN SUBST TIME
TRIM TRIML TRIMR XFOOT
Continuation Lines for C-Specs
Continuation for field names use elipses
C eval MyLongF... C ieldName = A + B
That even works on D-Spec names, constants, and literals
D QuiteLongFieldNameThatCannotAlwaysFitInOneLine... D S 10A * DNUMERIC C 12345 D 67
C CALL 'PGM' BBERLR C VAR1 PARM VAR2 PASS1 10
CALLB(D/E)
CALLP(E M/R)
CHAIN(EN)
CL0N01Factor1+++++++Opcode(E)+Factor2+++++++Result++++++++Len++D+HiLoEq.. C KEY CHAIN FILE NRERBB
DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++ D custRecKeys ds likerec(custRec : *key)
/free // custRecKeys is a qualified data structure custRecKeys.name =; custRecKeys.zip =; // the *KEY data structure is used as the search argument for CHAIN chain %kds(custRecKeys) custRec; /end-free // Read the record directly into the data structure chain (’abc’ : ’AB’) custRec custRecDs;
Verifies that all characters in Factor 2 are in Factor 1
If VALID is a constant containing the valid characters, then the following line verifies that FIELD contains only characters from VALID.
C VALID CHECKFIELD X
This does the same validation, but it starts checking at position 2.
C VALID CHECKFIELD:2 X
This finds the first non-blank character in FIELD.
C ' ' CHECKFIELD X
This finds the LAST non-blank character in FIELD.
C ' ' CHEKRFIELD X
CLEAR
RESET
For Record Formats, ONLY Output or Both fields are initialized - NOT Input Only fields. Field conditioning indicators are also initialized.
If you need to build a CL command (to use with QCMDEXC) use F13 to change from RPG to CLP. Now you can enter the command, prompt it to get the format, press enter, and select F13 to change back to RPG. Now you have the format for the command, without manually typing each parm.
CL0N01Factor1+++++++Opcode(E)+Factor2+++++++Result++++++++Len++D+HiLoEq.. C HOURS COMP 40 HiLoEq
/COPY libraryname/filename,membername /COPY filename,membername /COPY membername
This defines TOTAL as a Data Area
C *NAMVAR DEFN TOTAL
This retrieves the Data Area, but also puts an exclusive lock on it
C *LOCK IN TOTAL
*LOCK is optional
This Updates the Data Area, (the optional *LOCK makes it Not Release the Lock)
C *LOCK OUT TOTAL
*LOCK is optional
* Define Data areas
D TotAmt s 8p 2 dtaara D TotGrs s 10p 2 dtaara D TotNet s 10p 2 dtaara /free // Retrieval all 3 data areas in *lock *dtaara; /end-free
To define a D.S. to match an external file:
FCUSTMASTIF E K DISK ICUSTDS E DSCUSTMAST
The DS CUSTDS is now defined with every field in the file CUSTMAST. You could even define CUSTDS to be multiple ocurring!
Typical DS:
I DS I 1 6 DATE I 7 12 DATE2
DS Keywords
To view the occurrences of a mult-occur DS, you need to have the Object Definition Table (ODT) number of the occurrence - there are 2 ways to get this:
Once you have the ODT, use one of these:
ADDBKP 123.00 PGMVAR('/1234') - the '1234' is the ODT DSPPGMVAR PGMVAR('/1234')
Date math uses normal math symbols (date1 + %days(5))
STRDBG
If you have a file w/ lots of decimal data errors (especially if you migrated from a S/36), compile the following pgm w/ IGNDECERR(YES):
Ffilename UP E DISK C UPDATRcdFmt
C MOVE *ZEROS CUSTNO C MOVEL CUSTNO STR 256 P (256 Chars, padded w/ blanks) * STR now contains a '0' for each position in CUSTNO C '' CHECKR STR LEN 30 (3,0 Decimal)
LEN now contains the length of CUSTNO
C Z-ADD .999999999 BALNC C Z-ADD BALNC DPTST 99 (9,9 Decimal) C MOVEL DPTST DPTST@ 10 P (10 Char, padded w/ blanks) * DPTST@ now contains a '9' for each decimal position in BALNC C '9' CHECK DPTST@ DP 30 (3,0 Decimal) * DP contains the 1st position that is not '9' (3, in this case) C SUB 1 DP
Response Indicators (Rollup, Rolldown, CAxx, CFxx, etc.) can be renamed as follows:
IRECFMT1 I *IN03 EXIT I *IN04 PROMPT
Compile-time array
E EXAM 1 5 3
For above line…
1 = # of fields per record
5 = total # of fields in the array
3 = length of each entry
CT Arrays get their data from lines @ end of pgm
** Line 1 Line 2 ** << 2 asterisks separate each set of array data (comments like this are allowed) Data for 2nd table/array
E EXAM 5 3
For above line…
5 = total # of fields in the array
3 = length of each entry
Run-time Arrays get their data from code in the C-Specs
RPG (or CL) doesn't bomb when you pass fewer parms than than in the *ENTRY list, only when you reference the un-passed parms - to avoid this, get the # of passed parms from the SDS:
I SDS
I *PARMS PARMS
V7 adds %ParmNum() - no longer need to hard-code # of parms (i.e., If %Parms > 1;)
D OptionalTest PI D Parm1 20A D Optional2 5P 0 Options(*NoPass) D Parm2 S 5P 0 Inz(99999) // Check for optional parameter and use value if present If %ParmNum(Optional2) <= %Parms; // do something with Optional2 Endif;
/free TotalCost = 0; for i = 1 to SalesTransation.Numproducts; TotalCost = TotalCost + SalesTransaction.Products(i).Cost; dsply SalesTransaction.Products(i).Cost; endfor; dsply (’Total cost is ’ + %char(TotalCost)); /end-free
/free #exit =; #error =; setll k#plp45 plp45l01; reade k#plp45 plp45l01; dow not %eof(plp45l01); if pmth45 = 'CCD'; exsr $detail; endif; reade k#plp45 plp45l01; enddo; out lda; *inlr =*; /end-free
/free setll k#plp50 plpyitms; reade k#plp50 plpyitms; dow NOT %eof(plpyitms); chain k#plp15 plunitms; if %found(plunitms); exsr $date; exsr $datep; eval supn =; eval sref =; eval usert1 = 'Payment Run id#'; eval usert2 = 'Payment Reference #'; eval usert3 = 'Invoice #'; eval user1 =; eval user2 =; eval user3 =; eval btmt = %editc(bpmt50:'L'); eval usert4 = 'Payment Run Date'; eval user4 =; eval usert5 = 'System Identifier'; eval user5 = 'JBA'; write apccoutr; endif; reade k#plp50 plpyitms; enddo; /end-free
/free monitor; p#cono = 'D1'; today_date = %editc(%dec(%date():*cymd) : 'X'); p#fdat = %dec(today_date:7:0); /end-free
EXEC SQL SELECT * INTO :Pmp02Rec FROM PMP02 WHERE ORDN02=SUBSTRING(:Lda,349,7) AND DTLC02=9999999; IF SqlCod=0; // go to next step when po's found ELSEIF SqlCod=100; // if po's not found this is (probably) interactive - use LDA values Vndr02=%SUBST(Lda:257:6); Whse02=%SUBST(Lda:368:2); ELSE; RETURN; ENDIF;
/free Chain (cono:cusn:dseq:catn) OEP70M17; dow %Found(OEP70M17); if lqty70 > 0; eval(h) price3 =/; eval lprc =; leave; else; reade (cono:cusn:dseq:catn) OEP70M17; iter; endif; enddo; /end-free
D Msg PR EXTPGM('MSGWDWR') D Line1 50A CONST D Line1 50A CONST D Qcmdexc PR EXTPGM('QCMDEXC') D Cmd 500A OPTIONS(*VARSIZE) CONST D CmdLen 15P 5 CONST /free Cmd='ADDLIBLE EDIUK *LAST'; CALLP(E) QCMDEXC(%TRIMR(Cmd):%LEN(%TRIMR(Cmd))); /end-free
ACTGRP(*NEW | *CALLER | 'activation-group-name') ALWNULL(*NO | *INPUTONLY | *USRCTL) AUT(*LIBRCRTAUT | *ALL | *CHANGE | *USE | *EXCLUDE | 'authorization-list-name') BNDDIR('binding-directory-name' {:'binding-directory-name'…}) CURSYM('sym') DATEDIT(fmt{separator})
DATFMT(fmt{separator})
DEBUG{(*NO | *YES)}
DFTACTGRP(*YES | *NO)
EXPROPTS(*MAXDIGITS | *RESDECPOS)
FORMSALIGN{(*NO | *YES)}
GENLVL(number)
NOMAIN
OPTION(*{NO}XREF *{NO}SECLVL *{NO}SHOWCPY *{NO}EXPDDS *{NO}EXT *{NO}SHOWSKP) *{NO}SRCSTMT) *{NO}DEBUGIO)
PRFDTA(*NOCOL | *COL) - for profiling (not performance) data TRUNCNBR(*YES | *NO) USRPRF(*USER | *OWNER)
Keywords (valid for both the return value and parms)
Keywords valid for parms
* Prototype for procedure FUNCTION D FUNCTION PR 10I 0 D TERM1 5I 0 VALUE D TERM2 5I 0 VALUE D TERM3 5I 0 VALUE P Function B *------------------------------------------------------------- * This procedure performs a function on the 3 numeric values * passed to it as value parameters. * * This illustrates how a procedure interface is specified for a * procedure and how values are returned from a procedure. *------------------------------------------------------------- D FUNCTION PI 10I 0 D TERM1 5I 0 VALUE D TERM2 5I 0 VALUE D TERM3 5I 0 VALUE D Result S 10I 0 /free Result = Term1 ** 2 * 17 + Term2 * 7 ?5% + Term3; return Result * 45 + 23; /end-free P E
Indicators
* saving indicators
C MOVEA *IN,61 SAV8 8 C EXSR SUB1 C MOVEA SAV8 *IN,61
This can also be used for nested subroutines, IF you use different save field names for each subroutine
CL1 * a calc on this line would only run when L1 is on
CLR * a calc on this line would only run when LR is on
Left Justify
* Find the first non-blank character in FIELD. C ' ' CHECKFIELD X * BEFORE, FIELD= " MY TEXT " * AFTER, FIELD= "MY TEXT " * Extract into itself, starting at that position C X IFNE *ZERO C SUBSTFIELD:X FIELD P C ENDIF
Right Justify
* Find the LAST non-blank character in FIELD. C ' ' CHEKRFIELD X * BEFORE, FIELD= "MY TEXT " * AFTER, FIELD= " MY TEXT" C X IFNE *ZERO * Determine number of Trailing Blanks. C 30 SUB X X Concatenate FIELD into itself, which moves the trailing blanks to the front C ' ' CAT FIELD:X FIELD P C ENDIF
Center
* BEFORE, FIELD= "XXXXXXXXXX " * AFTER, FIELD= " XXXXXXXXXX " * Find the LAST non-blank character in FIELD. C ' ' CHEKRFIELD X X=10 * Subtract position of last non-blank character from the length of the output field, which gives the total number of blanks in the output field. C 50 SUB X X X=40 * Get the number of blanks that will be on each side of the data. C X DIV 2 X X=20 * Concatenate FIELD into CENTER, putting X number of blanks in front of the data C ' ' CAT FIELD:X CENTER P
If L5 is turned on, then all lower levels (L1 - L4) are also turned on. The LR indicator automatically turns on all L1 - L9 indicators. Printing and calculations will occur from low to high (L1 > L9)
IQSYSPRT ... ... ... I 2 3 DEPT L1 I 4 15 SLMN
H DATFMT(*ISO) * Examples of literals used to initialize fields DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++++++ D DateField S D INZ(D'1988-09-03') D NumField S 5P 1 INZ(5.2) D CharField S 10A INZ('abcdefghij') D UCS2Field S 2C INZ(U'00610062') * Even though the date field is defined with a 2-digit year, the * initialization value must be defined with a 4-digit year, since * all literals must be specified in date format specified * on the control specification. D YmdDate S D INZ(D'2001-01-13') D DATFMT(*YMD) * Examples of literals used to define named constants D DateConst C CONST(D'1988-09-03') D NumConst C CONST(5.2) D CharConst C CONST('abcdefghij') * Note that the CONST keyword is not required. D Upper C 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' * Note that the literal may be continued on the next line D Lower C 'abcdefghijklmn- D opqrstuvwxyz'
The following lists some examples of valid float literals:
C VALUES LOKUP LOOK_INTO HiLoEq
Lookup for Related Tables
C VALUES LOKUP LOOK_INTO RELATED HiLoEq
If a match is found, the “field” called related contains the matching value
Sending Messages Directly from the RPG Program
C MSG,1 DSPLYUSERMQ
C *MUSR0001 DSPLYUSERMQ
/EJECT
/SPACE #
SCR01
SFL01
CTL01
CMD01
LDA - to make updates to it available to called pgms, must do OUT first
Optional way to define an array as a field:
E FLD 12 20 I$FLD DS I 1 240 FLD
PGMA calls PGMB, which uses RETRN - when PGMA ends w/ LR on, it doesn't close PGMB's files! You could call PGMB w/ a parm, which tells it to end w/ LR on, or call QCMDEXC from PGMA, executing RCLRSC
Record formats - reading more than 1 from the same screen
C WRITE ABOVE C WRITE BELOW C EXFMT SFLCTL C READ ABOVE 90 C READ BELOW 91
Record Locks - use an error indicator for CHAIN or READ to know if you have a record lock
READE w/ a key - it locks the next record, then compares the key values - this means the first record after the key value will get locked by this process - it also means that this “wrong” record could create a wait state
External PRTFs (instead of O-Specs) allow you to use the same pgm to create 2 or more different (but similar) reports - create the 2nd PRTF (using some of the original fields, or extra fields, different column or report headings, etc.) using LVLCHK(*NO) - use OVRPRTF to direct the output to the new PRTF - you just created a new report w/o doing any RPG coding!
KLIST - you can define fields sizes on KFLD lines
Using CAT, you can “MOVEL” up to 16 characters in one operation:
C '12345678'CAT '90ABCDEF' RESULT
c monitor * start monitor c eval ivinue = ivinue + movc95 * eval equation c on-error 0103 * field too small c eval ivinue = *hival * reset c endmon * end monitor
WHERE CONO55 = &CONO
STRQMQRY QMQRY(*LIBL/DONOFRTQ) SETVAR((CONO &CONOW) (LOCD &LOCDW))
You can easily rename INPUT fields in RPG using the I specs, but this doesn't work for Output-only files. To accomplish this, define the file as Input, Full Procedural w/ Add, and add a “dummy” read - now you can re-define your “output” field.
4 is placed in RESULT, and *IN66 is turned on
HiLoEq C 'ABC' SCAN 'XYZABCII' RESULT 5566
The Scan starts at position 3
C 'ABC' SCAN FLDA:3 RESULT 5566
I SDS I 1 10 $$PGM I 244 253 $$WS I 254 263 $$USER I 264 269 $$JOB
Multiple programs can write to the same printer file - start w/ OVRPRTF FILE(QSYSPRT) SHARE(*YES)
The 1st RPG program can be a simple little thing that only opens the file:
FQSYSPRT O F 132 OF PRINTER C RETRN C EXCPTDUMMY C MOVE *ON *INLR OQSYSPRT E DUMMY
You can end the process by using RCLRSC in the CL. As an alternative, you can pass a parm to the first & last programs to control opening & closing the file.
DECLARE ... OPEN ... FETCH NEXT ... Loop, CHECK FOR Error Code CLOSE...
alias
CREATE ALIAS MYLIB.MYMBR2_ALIAS FOR MYLIB.MYFILE(MBR2)
Alter Table
Comment On
Constraint
Create commands create 2 names - an SQL name (128 char max) and a system name (10 char max) Create Table (PF)
CREATE TABLE xxx AS SELECT...
CREATE TABLE xxx LIKE MyTable...
Create View
Create Index
data dictionary
DECLARE cname CURSOR
DECLARE cname SCROLL CURSOR
Drop
Fetch Next From … For 10 Rows Into :DS Functions (UDFs)
Index
Insert Into MyFile 10 Rows Values :DS Label On
Rename
schema
sequence
Stored Procedure
System Name
trigger
UDT
CREATE DISTINCT TYPE US_DOLLAR AS DECIMAL (9,2)
view
CREATE FUNCTION CUSPRC (IN VARCHAR(8), VARCHAR(3), VARCHAR(15)) RETURNS VARCHAR(30) EXTERNAL NAME '&LIB/GETCUSPRCF(CUSPRC)' LANGUAGE RPGLE PARAMETER STYLE SQL NOT DETERMINISTIC
CREATE FUNCTION PMGTPOOQ (CONO VARCHAR(2), ORDN VARCHAR(7) , ITEM VARCHAR(15)) RETURNS DECIMAL(15,3) LANGUAGE SQL NOT DETERMINISTIC BEGIN DECLARE ORDQTY DECIMAL(15,3); SELECT SUM(OQTY03) INTO ORDQTY FROM PMP03 WHERE CONO03=CONO AND ORDN03=ORDN AND ITEM03=ITEM AND DTLC03 = 9999999 AND BOSN03=0 AND ACRC03=' ' AND DLTF03=' ' AND SUBSTR(ORDN03,1,1) IN('P','S'); RETURN ORDQTY; END
C/EXEC SQL C+ declare cGLC1 cursor for C+ select max(prdt) C+ into :w_pdate C+ from C+ where C/END-EXEC c/exec sql c+ open cGLC1 c/end-exec c dow all_cglc1 = *off c/exec sql c+ fetch next from cGLC1 c+ into :#pstamt, :#sesno, :#prlssn c+ c/end-exec c if sqlcod = 0 * process data c else * EOF (or an error) c/exec sql c+ close cGLC1 c/end-exec c endif c enddo
C/EXEC SQL C+ insert into cusprcp C+ C+ Select C+ 'C', C+ CONO70 CONO, C+ LOCD70 LOCD, C+ CUSN70 CUSN, C+ DSEQ70 DSEQ, C+ CATN70 CATN, C+ :pdate PRDT C+ from C+ inp20 C+ Join oep70 on cono20 = cono70 and strc20 = locd70 C+ C+ Where C+ CONO20 = 'D1' C+ and DEPC20 = 'MFG' C+ and pern70 > LEFT(DEC(DEC(Replace(CHAR(CURRENT DATE - 13 Months, C+ ISO), '-', '')) - 19000000,7,0) ,5) C+ and lqty70 > 0 C+ GROUP BY C+ CONO70, C+ LOCD70, C+ CUSN70, C+ DSEQ70, C+ CATN70 C+ C/END-EXEC
D Pmp02Rec E DS EXTNAME(Pmp02) EXEC SQL SELECT * INTO :Pmp02Rec FROM PMP02 WHERE ORDN02=SUBSTRING(:Lda,349,7) AND DTLC02=9999999;
EXEC SQL SELECT DSCUSCO||DSCUSN||DSDSEQ INTO :LocationValue FROM OEIDSPF WHERE DSPO=:Po; IF SqlCod<>0; LocationValue=*BLANK; ENDIF;
SELECT System_Table_Name, System_Table_Schema, TABLE_NAME FROM SysTables WHERE TABLE_NAME = "Customer_Master"
C/exec sql C+ set option commit=*none,closqlcsr=*endmod C/end-exec C/EXEC SQL C+ select max(prdt) C+ into :w_pdate C+ from cusprcp C+ where prtyp = 'C' C/END-EXEC C/EXEC SQL C+ delete from cusprcp C+ where prtyp = 'C' C/END-EXEC C/EXEC SQL C+ update cusprcp C+ set prtyp = 'H' C+ where prtyp = 'C' C/END-EXEC
V7 can process the result set from a proc
RPG stored procedures have always been able to return a result set
This new support may feel a bit “clunky” - But it works - You need to …
D CustResultSet S SQLType(RESULT_SET_LOCATOR) Exec SQL Call CustomersByState( :InputState ); Exec SQL Associate Result Set Locator (:CustResultSet) with Procedure CustomersByState; Exec SQL Allocate C1 Cursor for Result Set :CustResultSet; Exec SQL Fetch next from C1 into :CustData;
Traditional Syntax | Free-Form Syntax |
---|---|
CAT | + operator |
CHECK | %CHECK |
CHECKR | %CHECKR |
%STR | |
%REPLACE | |
SCAN | %SCAN |
SUBST | %SUBST |
XLATE | %XLATE |
TRIM | %TRIM |
TRIML | %TRIML |
TRIMR | %TRIMR |
Find the first non-zero non-blank character - Note that Factor 1 contains a space, followed by a Zero
C ' 0' CHECK FIELD X
* BEFORE, FIELD= “000048006” * If any non-zero non-blank character was found. AFTER, FIELD= “ 48006”
C X IFNE *ZERO
Translate all zeros to blanks
C '0':' ' XLATE FIELD FIELD
Translate all blanks back to zeros, starting at the position of the first non-zero non-blank character
C ' ':'0' XLATE FIELD:X FIELD C ENDIF
CSRLOC(ROW COLUMN)
Error Handling
Page Size
: read ahead to see if we're @ EOF, then readp, then show More or Bottom ROLLDOWN (same as PAGEUP) - use same indicator as SFLDSP ROLLUP (same as PAGEDOWN) - use same indicator as SFLDSP RTNCSRLOC(&CSRRCD &CSRFLD &CSRFLP)
RTNCSRLOC(*WINDOW &CSRROW &CSRCOL &WDWROW &WDWCOL)
SFLCLR
SFLDSP
SFLDSPCTL
SFLNXTCHG
The test operations are: TEST
TESTB
TESTN
TESTZ
Translate characters in Factor 2 based on Factor 1
Translate all lower case characters to upper case (LWR & UPR need to be constants or tables)
C LWR:UPR XLATE FIELD FIELD
Old-School Column Markers
*...1....+....2....+....3....+....4....+....5....+....6....+....7... IFilename++SqNORiPos1+NCCPos2+NCCPos3+NCC................................ I........................Fmt+SPFrom+To+++DcField+++++++++L1M1FrPlMnZr.... DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++++++ CL0N01Factor1+++++++Opcode(E)+Factor2+++++++Result++++++++Len++D+HiLoEq.. OFilename++DF..N01N02N03Excnam++++B++A++Sb+Sa+........................... O..............N01N02N03Field+++++++++YB.End++PConstant/editword/DTformat