Personal C Sharp                                                         By  famsoft.org
HomeHow To StartExamples-DesktopExamples-WebPC# MethodsReference-DesktopReference-Web


DEMONSTRATIVE EXAMPLES ====================== EXAMPLES ON FILING =================== For many years, filing has been easy and simple. There has been two types of files: (1) Sequential Access Files (SAF)'s: In this type records are read sequentially, so you can't read a record unless you read all the records located before it in the file. (2) Random Access Files (RAF)'s: In which you can read a record located at any place in the file instantly by supplying its record number. Sequential Access Files have been good for short files, like "letters" and "memos". Random Access Files have been good for longer files like "Inventories" and "Personnel" files. Things have become more complicated recently, SAF's have become not as easy to create and RAF's have been replaced with databases. This is fine for a large business but not as good for someone who likes to use his programming ability to put his personal data into a file made to his/her specs. Personal C# is specialized in simplification, so we have developed a software which returns you to the good old days. Both file types are now available and their creation and use have become even simpler than ever. SELECTING THE FILE KEYNAME: If you have already looked at the examples on "Controls", you know what keynames mean. You know that the keyname "bt0" represents a button since the first two characters "bt" are for a Button type. You also know that you should supply (cs="bt0") when you call method cm() at any mode to perform an operation on that button. In Filing, we have only two types of files SAF and RAF. The two char's representing each of them are "sf" and "rf" respectively. So, you can name your first SAF "sf0" and you should supply (fs="sf0") when you call method fm() at any mode to perform an operation on that file. Filing keynames can also be either 3 or 4 characters long, just like Control's keynames. So "sf0", "rf08"and "sf89" are all legal keynames. you cannot use numbers which start with 9 since they are reserved for internal use. So "sf9" and "rf90" are illegal keynames. REMARK: ======= If you are using Windows 10, you may need to run your filing programs "as administrator" in order to allow them to save files. Here is how to do it (We'll assume that the file name is "a.exe"): Run the file, right click its icon on taskbar, right click the item "a.exe" and select "Properties". Click on [Advanced], check the item "Run as administrator" then click on [OK][Apply][OK] =========================================================================================== SEQUENTIAL ACCESS FLES ====================== The easiest and shortest way to access them is to do the job using the modes of method fm() which reads from or writes into the file in one step. These modes are identified with upper case letters. They are as follows: R: USE: Read all file as both text and binary IN: fls OUT:os,OY[] RL: USE: Read all text lines of the file IN: fls OUT: OS[] W: USE: Write all file with either the text in (os) or the binary data in OY[] If OY[] is to be used, supply os="@". IN: fls, os, OY[] WL: USE: Write all rows of OS[] into file as lines. IN: fls, OS[] WA: USE: Append text to text file. IN: fls, os REMARK CONCERNING MODES R, RL, W, WL: The arrays which you load with data and supply to the method when you write must not contain unused rows at their bottoms. Also, the arrays the method returns to you when you read are dimensioned so that they contain no empty rows. The file read modes of this kind allow you to read the entire file into the string (os), the string array OS[] or the byte array OY[]. The file write modes write the data in (os), OS[] or OY[] into the file. Reading and writing data one piece at a time: --------------------------------------------- We also have the modes which can read and write one byte at a time, one string at a time or one line at a time. Let us see some examples: =========================================================================================== EXAMPLE 1: Make a short text file using Notepad and save it under the name "test.txt". We are going to Read the file and display its contents on the "Text Screen". We are going to use 2 file read modes "read all" to read it all at once and "read line" to read it line by line. =========================================================================================== public class a:pcs { public override void init() { tia=toa="t"; // Select "text screen" display bli=1; // Start execution at block 1 base.init(); // Initialize PC# classes } public override void run() { if (blp==1) { // Starting block os="Using Read All mode: ";tm(); // First mode fls="test.txt";fs="sf0";fm("or"); // Open for read, use keyname "sf0" fm("ra");tm(); // Read it all & display it fm("c"); // Close file os="\nUsing Read Line mode: ";tm();// Second mode. fls="test.txt";fs="sf0";fm("or"); // Open for read, use keyname "sf0" dnb=false; // Initialize End Of File flag while (!dnb) { fm("rl");if (!dnb) tm(); // Keep reading, displaying lines until EOF } fm("c"); // Close file } } } =========================================================================================== HOW TO WRITE, COMPILE AND RUN THE PROGRAM? See Example 1 of the "General Examples". =========================================================================================== TUTORIAL: Reading a SAF is very easy. At first, we select a keyname for it. Since it is a SAF the keyname must start with "sf". We called it "sf0" since it is the first file. The second step is to open the file. There are 3 modes to open a SAF at: or Open for read, which is the mode we wanted. ow Open for write oa Open for append There are also 3 modes for reading a SAF: ra Read the entire file and assign its text content to (os) rl Read one line rb Read one byte The boolean var (dnb) which is the "done" flag is a general flag used to indicate whether something we are working on has been completed or not. In filing, dnb changes its state to true when an end of file (EOF) has been encountered. So, before we start reading, we make sure that it is (false) then we keep reading until it changes to (true) You may have noticed that we has specified the file keyname only when we opened it, we didn't specify it when we read the file text or when we closed it. Why?. The answer is that method fm() does not reset (fs) at its end like it resets the GUV's. So we have been able to get away with doing that. However, this is not a good practice. If we have been working with more than one file, we should have specified the file keyname at each operation. ===========================================================================================


EXAMPLE 2: This time, we are not going to write a full class for each example, we are going to add one block for each new example to the class of example 1. so, this example will be in block 2. After adding block two to the last class, change the startup block request statement in method init() to (bli=2;) In this example, we are going to erase "test.txt" file contents, open it in "write" mode, write the line "Text Line Number 1" into it, close it then open it again in "append" mode, write the line "Text Line Number 2" into it, close it then open it in "read" mode, read and display all its data. if (blp==2) { // ----------------------- CREATING THE FILE AND WRITING DATA --------------------- ks="f";fls="test.txt";fm("D"); // delete file. fls="test.txt";fs="sf0";fm("ow"); // open for write os="Text Line Number 1";fm("wl"); // write line fm("c"); // close os="Data written to file";tm(); // Inform user fls="test.txt";fs="sf0";fm("oa"); // open for append os="Text Line Number 2";fm("wl"); // write line fm("c"); // close os="Data appended to fle";tm(); // Inform user // --------------------------------- READING DATA BACK ---------------------------- os="Reading data back:";tm(); fls="test.txt";fs="sf0";fm("or"); // Open for read fm("ra");tm(); // Read all file & display on text screen fm("c"); // close } =========================================================================================


EXAMPLE 3: Now, we need to add a new block to show how to obtain current directory name, how to check if a file or dir exists, how to create files and dir's and delete them, how to get a list of all files & sub folders in a folder and how to copy one file to another. So. add block 3 to your program and change "Startup block request" statement in method init() to (bli=3;) IMPORTANT REMARK: In this example, we have assumed that your current directory name is "c:\pcs" and that there is a file with the path "c:\windows.WindowsUpdate.log" into your computer. If this was not correct, modify the code before trying to execute it. ========================================================================================= if (blp==3) { //-------------- OBTAINING CURRENT DIRECTORY AND CHECKING ATTRIBUTES -------------- fm(".");os="Current Directory: "+os;tm(); // Get currnt dir name, dsplay it. fls="x.txt";fm("A");os="Code: "+os;tm(); // Get attributes & dsplys returned fls="xxxx.cs";fm("A");os="Code: "+os;tm(); // code for 3 items. Should display "f" fls="c:\\health";fm("A");os="Code: "+os;tm();// if file, "d" if directory, " " if // non-existing. //------------------ CREATING AND DELETING FILES AND DIRECTORIES ------------------ ks="f";fls="testFl";fm("M");os="File created";tm();// Create file, inform user. ks="f";fls="testFl";fm("D");os="File deleted";tm();// Delete same file ks="d";fls="testDir";fm("M");os="Dir created";tm();// Create dir, inform user ks="d";fls="testDir";fm("D");os="Dir deleted";tm();// Delete same dir //----------- DISPLAYING ALL SUB-FOLDERS AND FILE CONTENTS OF A FOLDER ----------- os="Reading file list:";tm(); ks="d";fls="c:\\pcs";fm("L"); // Get all sub-dir's in OS[] om("fa"); // Convert OS[] to (os) ns=os; // save (os) temporarely in (ns) ks="f";fls="c:\\pcs";fm("L"); // Get all files in OS[] om("fa"); // Convert to (os) os=ns+os;tm(); // Add the 2 lists & display //-------------------------- COPYING FILES AND DIRECTORIES ------------------------ fls="c:\\windows\\WindowsUpdate.log";os="newfile.txt";fm("C");// copy extrnl file to local file os="File copied";tm(); // Inform user with dialog box ks="f";fls="newfile.txt";fm("D"); // Delete new file os="File deleted";tm(); // Inform user } ========================================================================================= TUTORIAL: So far everything seems to be too easy to require additional explanation. Let us now get into the more important filing type "The Random Access Filing".


RANDOM ACCESS FILES =================== ============================================================================================== We are always searching for new ways to simplify software development. Random Access files have been considered to be the simplest means of accessing data randomly for some time. This has been changed lately. The new "Table Files" are considerably simpler and easier to use. For this reason we do not expect to support Random Access Files for a long time. So please read about Table Files and convert any Random Access files which you already have to this new type. We'll show you an example on how to do the conversion. =============================================================================================== The difference between a SAF and RAF is in the record length. In a text SAF, the strings you write into the file are not of equal lengths, the only way we can seperate them from each other is by adding a "new line" code at the end of each string. The new line code which we use is made of two special characters, a carriage return character (code 13 in decimal form) followed with a line feed character (code 10, decimal) In a RAF, you specify a record length when you open the file. The record length you choose should be at least equal to the length of the longest string which you may like to add to the file. When you call method fm() with a string which you want to write into the file, the method adds at the end of your string as many null characters as it takes to make its length equal to the record length. Since all records of a RAF are of same length, it is possible to know where the start and end of any record in the file are, so we can retrieve the record instantly without having to read all the records which preceed it into the file. We have two types of RAF's: --------------------------- (1) Manually read RAF's. (2) Automatically read RAF's In the first type, you need to supply the record length each time you open the file and you are on your own concerning how you place your data into your record. In the second type, at the time you create the file, you supply the filing method fm() with the record length and data concerning the fields each record should contain. After the file is created, the method writes a header record at the beginning of the file which contains all data necessary to read its records and to retrieve the field data within them. So anytime you open the file for read or write thereafter, you don't have to tell the method how to read it or where fields are located. Next example will be on the first type of Random Access Files. ========================================================================================= EXAMPLE 4: We are going to add a new block to last program to show how to write 2 lines into a no header Random Access File (RAF) then read them back. We need to erase file contents before writing into it. ========================================================================================= // ----------------------- CREATING THE FILE AND WRITING DATA --------------------- if (blp==4) { fls="test.raf";fm("A"); // check file attributes if(os != " ") {ks="f";fm("D");} // If exists, delete it fls="test.raf";fs="rf0";rcl=24;ib=true;fm("o"); // open RAF, record len=24,no header rcs="Line number one.";rci=0;fm("w");// Write first record rcs="Line #2.";rci=1;fm("w"); // Write second record fm("c"); // Close the file. // --------------------------------- READING DATA BACK ---------------------------- fls="test.raf";fs="rf0";rcl=24;ib=true;fm("o"); // open RAF again n=0;dnb=false; // Initialize record number counter, eof flag. while (!dnb) { // Start record reading loop. rci=n;fm("r"); // Read one record. os=rcs;om("c"); // Clean record (remove trailing null chars) tm(); // Then display record. n++; // increment record number. } fm("c"); // Close the file. } ========================================================================================= TUTORIAL: As you can see, the records supplied to method fm() to be written into the file have not been equal in length, but the method made them equal by adding enough null char's to each of them to make the length of each one of them equal to the record length. When the records were read back, they came with the null char's attached to them, so we had to remove the null chars before displaying them. HOW TO CREATE AN AUTOMATICALLY READ RAF: The first step is to create the header. To do so, you open the file as a no header file, write the header then close it. To create the header, you need to determine the fields necessary. To simplify things, we use only two types of fields, string and numeric fields. A numeric field always occupies 5 bytes. The number of bytes a string field requires is the number of characters of the logest string to be writen into it. Setting the record length: -------------------------- To calculate the record length, add the bytes which all fields require together, then add some more spare bytes for future expansion. Setting the data type: ---------------------- The double type var (od) is used to indicate the data type for a field. Here is how to detrmine data types: (1) For a string field, od=No. of characters occupied by the field. (2) For a numeric field, (od) is always in the range (0.0 to 0.9) The number to the right of the decimal point is the number of decimal digits you like the field to have. If the field will be used to represent money, you would be expected to choose (od=0.2) so your numbers come formatted as Dollars and Cents. If the field is going to be used to store an integer, you would use (od=0.0) since there will be no decimal digits. So, (od) can tell exactly what a field contains. If it was an integer like (35), we know that the field is a string, upto 35 characters long. If it was zero (0.0), the field is an integer and if it was a fraction like (0.4) we know that the field is a number of 4 decimal digits (in this case) How the header data is supplied: -------------------------------- Practically, we don't supply field data one at a time, we supply them all at once. We supply the array OS[] containing all field names, and array OD[] containing all field types in the same order at which we like the fields to be placed into the record. =========================================================================================


EXAMPLE 5: Let us add a new block to last program to show how to make a RAF with a header containing all information necessary for reading it. We are also going to Enter two records of data into the file then read them back and display them on the screen. ========================================================================================= if (blp==5) { // ------------------- CREATING THE FILE AND WRITING THE HEADER ------------------ fls="test.raf";fm("A"); // check file attributes if(os != " ") {ks="f";fm("D");} // If exists, delete it fs="rf0";rcl=100;ib=true;fm("o"); // open the file as no header file OS[0]="name";OD[0]=35; // 35 character string field OS[1]="age";OD[1]=0.0; // Integer field OS[2]="position";OD[2]=25; // 25 character string field OS[3]="salary";OD[3]=0.2; // numeric field with 2-decimal digits fm("wh"); // Write header. fm("c"); // Close the file. // -------------------------- ENTERING DATA INTO THE FILE ------------------------- fls="test.raf";fs="rf0";fm("o"); // open file and read header information // Notice that we did not supply rec length OS[0]="John Smith"; // First field data, a string in OS[] OD[1]=25; // Second field data, a number in OD[] OS[2]="Machine operator"; // Third field data, a string in OS[] OD[3]=35000.50; // Fourth field data, a number in OD[] rci=1;fm("w"); // Write record #1 OS[0]="Mary Jones"; // OD[1]=20; // OS[2]="Clerk"; // Field data for second record OD[3]=25000.66; // rci=2;fm("w"); // Write record #2 fm("c"); // Close the file. // ------------------------------ READING DATA BACK ------------------------------ fls="test.raf";fs="rf0";fm("o"); // open file and read header information // Notice that we did not supply rec len or any // field information. The file reads itself. n=1; // Start by record # 1 while (!dnb) { // Do until eof encountered. rci=n;fm("r"); // Read one record. (See REMARK) os=OS[0];tm(); // Read and display 1st field os=OS[1];tm(); // Read & display 2nd field os=OS[2];tm(); // Read and display 3rd field os=OS[3];tm(); // Read & display 4th field os="";tm(); // Add blank line to seperate records n++; // increment record number. } fm("c"); // Close the file. } ========================================================================================= TUTORIAL: As you can see, at the block of code titeled "CREATING THE FILE AND WRITING THE HEADER", the file did not have a header, so we opened it as a no header RAF. We wrote the header and closed the file. From this point up, the file is a self read file, reading it or writing into it does not require supplying any information other than the file name. At the block of code titled "ENTERING DATA INTO THE FILE", we opened the file as a RAF with a header (since ib=false), so we did not need to supply rec length. Immediately after the open statement, we have all field names in OS[] and their type codes in OD[] If you have forgot what was in the file you can remind yourself by displaying the two arrays with a code like this: fls="test.raf";fs="rf0";fm("o"); // open file and read header info; fm("rh"); // Read header for (c=0;c<20;c++) { // Scan field arrays if (OS[c].Length<1) break; // Stop at end of data os="Name: "+OS[c]+" Type: "+OD[c];tm();// Display name and type of each field } This is what you get: Name: name Type: 35 Name: age Type: 0 Name: position Type: 25 Name: salary Type: 0.2 You may be wondering why we included the read header statement [fm("rh")] if the open statement have already read the header. The answer is that the open statement reads the header, copies OS[] and OD[] contents to internal PC# archives then resets OS[] and OD[] so they can be used in the next record read or write operations. READING AND WRITING DATA: You must have noticed that we leave empty gaps into arrays OS[] and OD[] when we read or write. For example, during writing of the first 3 fields, we skipped OS[0]="John Smith"; // First field data, a string in OS[] OD[1]=25; // Second field data, a number in OD[] OS[2]="Machine operator"; // Third field data, a string in OS[] OD[0],OS[1] and OD[2]. The rule is that when the field is string, we keep its place in OD[] empty and when the field is numeric we keep its place in OS[] empty. Why do we need these gaps? In addition to simplicity, After each read operation, numeric field data are formatted according to their type specs and their string type format is stored into its place in OS[] in addition to their numeric value which is stored in OD[]. =========================================================================================


============================================================================================== The Table File ============== (For version 1.80 and higher) If you try to find the easiest and simplest way to store field data into a file, you can hardly find anything better than the Table File. Here is a sample: HISTORICAL STOCK PRICES OF MICROSOFT CORPORATION ------------------------------------------------ Date Open High Low Close Volume ========== ======== ======== ======== ======== ============ 02/07/2008 28.34 28.78 27.90 28.12 164,964,900 02/06/2008 29.28 29.35 28.29 28.52 138,021,700 03/05/2008 29.91 29.94 28.89 29.07 137,522,600 02/04/2008 30.49 30.72 30.11 30.19 119,987,300 02/01/2008 31.06 33.25 30.25 30.45 291,095,400 01/31/2008 31.91 32.74 31.72 32.60 103,363,200 01/30/2008 32.56 32.80 32.05 32.20 106,343,700 01/29/2008 32.85 32.89 32.35 32.60 68,007,200 01/28/2008 33.02 33.10 32.42 32.72 81,007,400 As you can see, you can include a title on the top which can occupy any number of lines and you can insert empty lines within data records to improve visibility. You can read from and write to the Table File programmatically as well as with a simple text editor like Notepad. The only two items which you should pay attention to when you write to a Table File are: (1) The Template record: ------------------------ The row which is made of short lines under column titles is the template record. It's the most important record in the file. Its length sets the file's record length. The short lines made of equal signs set the start and end of each field's data (we like to call them column data here) The numbers of spaces which seperate each two lines are optional, however there should be at least one space between each two lines. (2) The Data records: --------------------- There are two rules to abide with when you write data records: a) The first character of each field must lineup with the first character of the template line at the top of its column. b) The field data must not exceed that template line in length. This requires that when you create a template for a new file, you must make each line it contains at least equal in length to the longest expected data to be written into its column. That's it. There are no more rules for this file type other than making sure not to include any line which could be confused for the template record. The template record is defined to be a record which is made of nothing but equal signs and space char's. Columns and rows: ----------------- Being a table and a file at the same time has made some terms exchangeable. A row, a line and a record mean the same item in a table file. Also, when we are talking about one record, field and column mean the same item. How to make a Table File using NotePad: --------------------------------------- This should be obvious. After reading the rules above and looking at the sample table, you should have no problem with writing data into a Table File using Notepad. However, you need to make sure to press [ENTER] following each row you enter including the last one. The extension to use for this file type can be "txt" since it's actually no more than a simple text file. This is not a rule. Sometimes we may need to use different extensions to diffrentiate between different table files. How is the Table File read and edited programmatically: ------------------------------------------------------- When you request opening a file of this type, method fm("o") reads records line by line looking for the template row. It looks for a row which is made of nothing but equal signs and spaces. This is why we recommend that you avoid including (=)'s within the lines which are above the template line. When the template row is found, it's analyzed and the start and end positions of each data field are written into a table. Additionally, the record length is set as the template row's length. The entire file is then copied to an array and the file is closed. The array is used for all data reading and writing until you request closing the file. When you call method fm("c") to close the file, the array is copied to the file overwriting its original content, then all data stored into PC# archives concerning this file are deleted. The keynames to use when you call method fm() to do an operation on a Table File must start with "tf". So, "tf0", "tf01", "tf89" are valid Table File keynames. Your file must contain a template before you can call it a Table File or assign such keynames to it. If you like to create the file and write the title and template into it programmatically, open it as a SAF file using a keyname like "sf0". Writing the table title and template record: -------------------------------------------- To start a new Table File, the simplest way is to use Notepad to write at least upto the template record. If you must do this job programmatically, open it for write as "SAF" file using keyname like "sf0". What is the greatest advantage in using Table Files: ---------------------------------------------------- The ease and simplicity in writing and reading data and the flexiblity in allowing you to insert blank lines and to write a title on the top of the file are some advantages. However what we consider to be the greatest advantage is that what is in the file is exactly what we like to see on the screen when we display it or on paper when we print it. How about the RAF files: ------------------------ Although RAF's are considered to be simpler than most other data storage means, they cannot beat the Table File in simplicity and ease of use. So, we recommend that you replace any RAF file which you have with a Table File. We are going to show you an example on how to transfer data from RAF to a Table File. How about Databases: -------------------- The Table File is good for simple data storage jobs when security is not an important issue and amount of data is not very large. Databases must be used for larger data storage jobs or tighter security rules. Table files can be used for simple jobs like filing your personal data or filing non-critical data in a business. How to set the maximum data size of a Table File: ------------------------------------------------- Call method dm("hd") within method init() to set the highst number of records which you expect for your Table File. The initial default amount is 1000 records. EXAMPLES: ========= Let us see some examples before we get further. (1) Writing file with Notepad and displaying its content on the console: ------------------------------------------------------------------------ Copy the table above including its title and template and paste it into Notepad window. Save it into the file "test.txt". You can display file content from command mode by just typing: type test.txt [ENTER] If you like to do it programmatically: -------------------------------------- public class a : pcs { public override void run() { cm("fe"); // Eliminate form. fls="test.txt";fm("R");tm(); // Read all data and display. } } --------------------------------------------------------------------------------------------- (2) Creating a new Table File and writing template: --------------------------------------------------- public class a : pcs { public override void run() { cm("fe"); // Eliminate Form. fls="test1.txt";ks="f";fm("D"); // Delete file if exists. fs="sf0";fm("ow"); // Create and open new file. // Write next 5 lines into file. os=" HISTORICAL STOCK PRICES OF MICROSOFT CORPORATION";fm("wl"); os=" ------------------------------------------";fm("wl"); os="";fm("wl"); os=" Date Open High Low Close Volume";fm("wl"); os="========== ======== ======== ======== ======== ============";fm("wl"); fm("c"); // close os="Table File has been created and data written into it successfully.";tm(); } } --------------------------------------------------------------------------------------------- When we created the file and wrote the title and template lines, we treated the file as a regular SAF file. Now, after the template record has been written, we can open it as a table file, read and write records randomly at great speed and work on column data independantly. We can search the file for specific strings at specific columns, obtain the record numbers of the records where matches have been found then read the wanted records and work on them. We can also sort the file based on data of a specific column or columns. How are records accessed randomly? ---------------------------------- If the file is SAF and records are not exactly equal in length, how could we retrieve records randomly? The answer is that the entire file is dumped into an array when it's opened. Any reading or writing thereafter is done from/to the array. When we request closing the file, the array is written back into the file before it's closed. How much data could be dumped into memory without causing a crash? ------------------------------------------------------------------ Let's make some calculations. If your file contains names and addresses of people, it will require about 128 bytes per record. This means that each 8 records require 1 kilobytes. So: 8 records require 1 Kilobytes. 8,000 records require 1 Megabytes. 800,000 records require 100 Megabytes. 1 Million records require 125 Megabytes. Years ago, 125 megabytes of RAM has been larger than anything imaginable. Now, you can buy a cheap computer with no less than 4 Gigabytes of RAM. With putting this into consideration, there is nothing wrong with dumping your file data into arrays. It makes reading and writing data extremely fast and also allows you to access the records randomly. Modes of method fm() which are used to read from and write into Table Files: ---------------------------------------------------------------------------- o: USE: Opens a file for Read/Write. It copies file data to archives and also stores field data into archives. File must exist and contain template record or it will return an error. IN : fls=file name fs=keyname (like "tf0") OUT: o=Start data record number. ol=Total number of records. dnb=true:File is empty. of=Record length. r: USE: Reads data for one record. IN: rci=Record number. OUT: rcs=record data in a string form. dnb=true: Record at or beyond end of file OS[]=Field data. oi=Number of occupied rows in OS[] w: USE: Write a record. IN : rci=record number, OS[]=field data. wn: USE: Write a new record at end of data IN : OS[]=Field data c: USE: Copies data back to file then closes it and rest archives. When you open the file, all data in the file is copied to an array. Additionally, the template is analyzed and record length is determined (which is the length of the template record) Data record may not exactly match the template in length but this causes no problem. The start character and number of characters of each column are determined and also placed into arrays. All arrays are public, so you can work on them directly if you choose. How to access our archives directly: ------------------------------------ You can always access our data archives directly. If your file keyname is tf3, here is where to find data: rcs : Last record read. This value is returned to you at mode "r" assigned to (os) rcl : Record Length. This value is returned to you at mode 'o' assigned to (of) fll : Total number of records. Also returned to you at mode 'o' assugned to (ol) TBS[3][] : Stores a copy of the entire file. CNS[3][] : Stores column titles. CSI[3][] : Stores the start character order of each column. CCI[3][] : Stores the number of characters each column contains CNI[3] : Stores the number of columns in the file. DSI[3] : Stores the record number of first data record. This is the record which immediately follows the template record. Method fm("o") returns this value to you assigned to (o) Note that for the table at the top of this page, DSI[]=5 which means that it points to the blank line following the template. Column Title Rules: ------------------- Normally, we like to identify columns by their order numbers. So we neglect the titles' row. But since some people like to use column titles to identify columns with, we like to enforce the following rules: (1) Do not include spaces into titles. You can seperate words of the title with underscores like "Color_code" or by starting each word with upper case letter, like "ColorCode". (2) No title name should go beyond its column borders and extend to adjacent columns. This may require you to add extra seperating spaces between column template lines when the title length exceeds the data length. For example, consider the 3 neighbouring columns "Item", "ColorCode" and "FontCode". The color codes are always made of 2 char's, so the column title's length exceeds the template's length for that column. Here is how to create a legal template in this case: Item ColorCode FontCode or Item ColorCode FontCode ==== == ======== ==== == ======== As you can see, we have solved the problem by seperating the template lines with extra spaces. This should also tell you that it's always better to make the titles as short as they can be. Method fm() archives column titles into array CNS[][] which is public. You can also obtain the order of any column by assigning its title to (ks) and calling fm("fo") The order is returned to you assigned to (o) The "Form Documents" menu which will come later contains a selection which displays for you a list of all columns in a Table File, their order numbers and their templates. You can also get this list with: fls="Table File Path";um("fa"); Resetting Vaiables: ------------------- When you work on Table Files, it's important to know which var's are reset after calling method fm() to perform an operation and make sense of the logic involved. The general rules are: (1) All j,k & i based single value var's (GUV)'s must be reset under all condtions. (2) ALL J,K & I based arrays are reset only when they are used as input parameters. (3) All o based single value and array var's are reset only when used as input parameters and kept unchanged when not, with exceptions for (os) and OS[]. MODE RESET VARIABLES REASON o OS[] To prepare for a future read/write operation. fo None None involved in operations fs KS[],OS[],J[],K[] KS[],J[],K[] are used for input, OS[] is prepared for coming rd/wr op. fS JS[],KS[] Used for input. fp None None involved in operations. r None os & OS[] are output values. w OS[],rci,rcs,os To erase values supplied at prior read operation. d None None involved in operations. c OS[],All archives To make sure no old values associated with file are still there. ============================================================================================== Example 6: Open file "test.txt" which contains the table at the top as a Table File. Read the 3rd data record and display it. Modify the low price to "25.00" and overwrite that record. Read the record again and change the date to "02/08/2008", then write it as a new record at the end of the file following a blank line. Display the resulting file showing the modified records in red. ============================================================================================== public class a : pcs { public override void init() { toa="t"; // Use text screen for text output base.init(); } public override void run() { fns="crb10"; // Use courier font since alignment is necessary. fs="tf0";fls="test.txt";fm("o"); // Open as a Table File. rci=o+3-1; // Record number of the 3rd record n=rci; // Save this number temporarely. fm("r"); // Read that record (OUT=OS[]) OS[3]="25.00";fm("w"); // Modify column #3 (Fourth column where "low" is) fm("wn"); // Write a blank line following last record. rci=n;fm("r"); // Read the record again. OS[0]="02/08/2008";fm("wn"); // Modify date then write it as a new record. fm("c"); // Close file. fls="test.txt";fm("RL"); // Read file as lines into OS[] for (c=0;c< OS.Length;c++) { // Scan all rows. os=OS[c]; // Assign each row to (os) if (c==n || c==OS.Length-1) { // If was a modified row: cls="r0";tm(); // Display it in red. cls="S9"; // then restore black color. } else tm(); // Display all other rows in black. } } } ---------------------------------------------------------------------------------------------- REMARK: ======= In order to protect file, method fm() assigns zero to (rci) after each write operation. This is to guard against writing next record at the same location of the file. Note that if you try to write at record zero, you'll get an error message. When you read a record, method fm() keeps (rci) unchanged. This is because you normally like to read a record, modify it then overwrite it with new data. Array OS[] is also reset after each write operation. This is why we could write a blank line at the end of data by calling fm("wn") with no parameters. ==============================================================================================


============================================================================================== Searching for a specific data at one column: -------------------------------------------- In the previous example, we modified the third data record of the file. In the real world, we don't identify records by their numbers. When we modify records we like to modify a record of specific criteria, for example we may like to modify the stock data for the date "02/01/2008". This means that we must search the file in order to obtain the record number of the record with date ="02/01/2008" before we can read it, modify it and write it back. Sometimes we search for all the records which satisfy a specific criteria, for example, if the two days "01/31/2008" and "02/01/2008" contain data which need to be modified, we can search for the records with char number 4 of the Date column = "1" and expect to receive two record numbers as the search result. When we search, we search for a string which starts at a specific char of a specific column. So, we may search for the string "1", "1/" or "1/2008" and expect the same results. In order to do things faster, we are allowed to supply more than one criteria to be searched in one step and expect to receive the numbers of all records which satisfy all the criterias specified. If we search for the string "1/2008" at char number 4 of the "Date" column and for the string "25.00" in the "Low" column, we should receive 4 record numbers, 2 for records with dates "01/31/2008" and "02/01/2008" and two for the two records which we have written into file in the previous example. Since we supply more than one item to be searched and also receive more than one record number, arrays are used for both jobs. Assign the order numbers of all the columns to be searched to K[] and the strings to look for into each column to the rows of array KS[] of matching orders. If the search string was not expected to start at the first character of a column, assign to the same row in array I[] the order of the start character within the column to search at. The numbers of all records which satisfy all criteria come assigned to array O[]. In order to clarify that, when we search the date column at char number 4 for the string "1/2008" we supply: k[0] = 0; // (Date)'s column number is zero. I[0] = 4; // Search starts at char number 4 of the column. KS[0]= "1/2008"; // String to search for. fm("fs"); // Call fm() at "field search" mode. And we expect to receive array O[] containing 4 record numbers. Resetting used arrays: ---------------------- The most critical operation in PC# software is resetting general use variables. General use single value variables are guaranteed to be reset at the end of every method call. General use arrays are reset only when used as input parameters and not used for output. So, after each search operation you expect arrays K[], I[] and KS[] to be reset. We mean by "reset" that all numerics are assigned zeros, all strings are assigned "" and all boolean values are assigned (false) When arrays are reset, their sizes are made to be equal to the default size for general use arrays which is 100. This default amount can be changed by calling dm("gh") from method init() However, if you keep their sizes unchanged and assign values to few rows at the top leaving the rest unassigned, PC# methods will have no problem. This is actually what we prefer. The output array O[] which method fm("fs") returns to you is trimmed to the size of the data it contains before it's returned to you. However, method fm("r") does not trim array OS[] before it's returned to you loaded with field data. It returns (oi) assigned the number of occupied rows instead. The reason is that you may need to modify OS[] then write it back into the file. Such modification may involve assigning values to fields which have not been assigned values initially. =============================================================================================== Example 7: Search the file for the two records with dates "01/31/2008" and "02/01/2008" together with the records which contain "25.00" at their "Low" column. Modify the "Low" column of all records received to contain "26.00". Display the file, showing modified records in red. =============================================================================================== public class a : pcs { public override void init() { toa="t"; // Use text screen for text output base.init(); } public override void run() { fns="crb10"; // Use courier font since alignment is necessary. fs="tf0";fls="test.txt";fm("o"); // Open as a Table File. K[0]=0;I[0]=4;KS[0]="1/2008"; // First search criteria. K[1]=3;KS[1]="25.00"; // Second Search criteria. fm("fs"); // Execute search. int[] O1=O; // Save O[] temporarely for (n=0;n< O.Length;n++) { // Scan all returned array rci=O[n];fm("r"); // Assign each record number to (rci) OS[3]="26.00";fm("w"); // Change column #3 data then write record. } fm("c"); // Close file. fls="test.txt";fm("RL"); // Read file as lines into OS[] for (c=0;c< OS.Length;c++) { // Scan received data. os=OS[c]; // Assign each row to (os) for (n=0;n< O1.Length;n++) { // Compare with modified records ob=false; // Initialize "already displayed" flag. if (c==O1[n]) { // If row to be displayed was a modified row: cls="r0";tm(); // Display it in red. cls="S9"; // Restore black color. ob=true;break; // Set "already displayed" flag and exit. } } if (!ob) tm(); // Display all other rows in black. } } } ============================================================================================== REMARK: ======= You may like to say that searching for a string at a specific location in a specific column is not the only kind of search which I like to do. This is true, but don't forget that you are not limited to using method fm("fs") for your search. You can read the records one by one in a loop and search them by yourself and if you like to do the job faster, you can search array TBS[][] instead. This is one advantage in using a file instead of using a database; there is no limit to what you can do. ==============================================================================================


============================================================================================== Performing Table File sort which is based on one column's data: --------------------------------------------------------------- Sorting can be necessary for many reasons. One of them is making it easy for you to locate a record manually. Sorting a name and address file based on the zip code before sending mail to the persons who are listed in the file can reduce your mailing cost since it reduces the postage necessary. Finally sorting at a field which you regularly search can allow you to use binary search which is much faster than the ordinary. Sorting numeric data is not the same as sorting string data. To understand this point, let us assume that you have two data items, "9" and "10" and you like to sort them from low to high. If you sort them as strings, the sort method will consider each character to be more important than the next one. The 1st character is the most important and since the char '1' is smaller than the char '9', it will consider "10" to be the first item and "9" to be the second which is not what you like to get. So, in this case you need to inform the sort method that you want to sort the data as numerics not as strings. To do so, supply (ib=true) to the method. Sorting a table file is very easy. All you need to do is to assign column number to (k) and and if you like to do numeric sort, make the assignment (ib=true) before calling fm("fS") Notice that "S" in the mode string is an upper case one. Search uses the lower case "s". Binary Search: ============== Binary Search is very useful to know about and apply to many of the tasks which you do in your life. Its value to you far exceeds searching files. Let us consider here one simple application. Let us assume that there is a connection problem somewhere in the telephone line in your house and that there are 8 plugs which your telephone line goes through between the point it enters your house and the point where your phone set is. Entry O------X------X------X------X------X------X------X------O TELEPHONE POINT 1 2 3 4 5 6 7 8 SET You like to know at which piece of the cable the line is broken. One way to find that is to disconnect your phone set and try connecting it at point 1. If you find that it works, you repeat at point 2 and so on until you find the defective part. This can take upto 8 steps. Mathematically you can locate the defective part in the shortest steps possible if you do it in the following manner: (1) You test at the middle plug which is #4. If it works, you know that there is nothing wrong upto plug #4. (2) You test at the plug which is located at the middle between plug #4 and the end of the line which is plug #6. (3) If it works, you then test at plug #7. If it did not work, you test at plug #5. With the idea of binary search you have reduced the steps from 8 to 3. You can calculate the number of steps as follows: Number of steps = Log (of base 2) 8 = 3. The advantage may not look too dramatic in this case, but if the number of plugs has been one million, you could have located the defective piece of the cable in only 20 steps. Searching for a bug in your program which is causing a confusing compiling error like when one brace is missing can also be simplified with the use of the same binary search idea. This can be done by setting the comment symbols "/*" and "*/" around half of your code then compiling your program again. if the problem stays, you place the comment symbols around half of the other half and so on. This should save you plenty of developing time. And here is a problem in math where the binary search idea can be of great help. Suppose that you have the equation: y=x^4 + 2x^3 + 4x^2 + 5x +1 (^ means exponent here) and you like to find one positive value of (x) when (y=10.55) Trying to get the roots of this equation algebrically can be very hard. One solution is to use a loop to find the value of (y) for all values of (x) within expected range and compare the calculated (y) with the actual one. When a match is found, you'll be at the right (x) value. If the expected range of (x) was (0:10,000) and you like it to be accurate to two decimal digits, you may have to do the calculation one million times. Using the binary search idea, you'll need to do the calculations only 20 times. Using binary search to locate data in a large file is great for speed. This is because it takes inspecting only 20 records to locate data inside a million record file. This means that whatever mode "fs" can do with upto 1 million checks, mode "fsb" can do with no more than 20. However, you can't use binary search to locate data in one column of a Table File unless the column data has been sorted in advance. ============================================================================================== Example 8: Sort the file at the "High" column (which is column #2) then do a binary search to locate the records where the "High" prices of "30.72" and "33.10" are located. Display the sorted file showing the records which contain the two prices in red. ============================================================================================== public class a : pcs { public override void init() { toa="t"; // Use text screen for text output base.init(); } public override void run() { fns="crb10"; // Use courier font since alignment is necessary. fs="tf0";fls="test.txt";fm("o"); // Open as a Table File. k=2;ib=true;fm("fS"); // Do numeric Sort on the file based on column #2 K[0]=2;KS[0]="30.72"; // Search for "30.72" at sorted column K[1]=2;KS[1]="33.10"; // Search for "33.10" at sorted column fm("fsb"); // Execute binary search. int[] O1=O; // Save O[] temporarely fm("c"); // Close file. fls="test.txt";fm("RL"); // Read file as lines into OS[] for (c=0;c< OS.Length;c++) { // Scan received data. os=OS[c]; // Assign each row to (os) for (n=0;n< O1.Length;n++) { // Compare with selected records ob=false; // Initialize "already displayed" flag. if (c==O1[n]) { // If row to be displayed was a selected row: cls="r0";tm(); // Display it in red. cls="S9"; // Restore black color. ob=true;break; // Set "already displayed" flag and exit. } } if (!ob) tm(); // Display all other rows in black. } } } ==============================================================================================


============================================================================================== Purging the Table File: ======================= (1) Record length irregularity: ------------------------------- When someone uses Notepad to enter data into the file, you can't ask him to count characters of each line and not to Press [ENTER] unless the count is equal to the record length. Fortunately, having records of different lengths in the file causes no problem. However, whenever you call method fm("w") or fm("wn") to write a record, the record is made to be equal in length to the record length. The record length of the file is the length of the template record. Mode "fp", the "File Purge" mode readjusts the length of every record in the file making them all equal in length to the record length. As we have just mentioned this is nice but not a must to do. (2) Blank lines within data records: ------------------------------------ It is nice to insert a blank line each 3-5 lines. It improves visibility and look of the data. Unfortunately, whenever we sort the file, all blank lines get eliminated. Additional to readjusting record lengths, mode "fp" can insert blank lines into the file if you assign a value to (i) If your assignment was (i=4), each 4th line will be a blank line. (3) Deleting unwanted records: ------------------------------ This job can be done easily by calling fm("d") The file must be open before you do it and the delete effect will not take place until you close the file. ============================================================================================== Example 9: Delete the record with date "02/08/2008" which we have added to the file earlier, then purge the file and add a blank line after each 3 data lines. ============================================================================================== public class a : pcs { public override void init() { toa="t"; // Use text screen for text output base.init(); } public override void run() { fns="crb10"; // Use courier font since alignment is necessary. fs="tf0";fls="test.txt";fm("o"); // Open as a Table File. K[0]=0;KS[0]="02/08";fm("fs"); // Search for rec with Date starting with "02/08" rci=O[0];fm("d"); // Delete that record. i=4;fm("fp"); // Purge remaining records, inserting blank line fm("c"); // each 4th line. Close file. fls="test.txt";fm("R");tm(); // Read all data and display. } } =========================================================================================


============================================================================================== Performing an operation which requires reading all records in the file: ----------------------------------------------------------------------- Just like any other file type, Table Files set the "job done" flag (dnb) when the last record is read. So, you can use a "While" loop conditioned with (dnb) being not set to read all records, same as you do with all other file types. ============================================================================================== Example 10: Read all records of file "test.txt", add all the stock prices in each of the columns "Open", "High" "Low" and "Close" and calculate the average of each column. Display the average amounts at the bottom of their columns. ============================================================================================== public class a : pcs { // Var's used: t= Total number of records not counting blank ones. // ts=String used to display price averages. double[] TD=new double[4]; // Define an array to store totals of the 4 col's public override void init() { toa="t"; // Use text screen for text output base.init(); } public override void run() { fns="crb10"; // Use courier font since alignment is necessary. fs="tf0";fls="test.txt";fm("o"); // Open as a Table File. rci=o; // Assign returned starting data rec# to (rci) dnb=false;t=0; // Initialize eof flag and record counter. while (!dnb) { // Repeat until eof encountered: fm("r"); // Read a record (returns os, OS[]) om("c"); // Remove spaces from (os) to check record. if (os.Length>0) { // If was not a blank line: t++; // Increment number of records. for (n=1;n<5;n++) { // Scan columns 1:4 os=OS[n];om("td"); // Convert data of each column to double. TD[n-1]+=od; // and add it to each column's total. } } rci++; // Increment record # } // Then repeat loop. fm("c"); // Close File. fls="test.txt";fm("R");tm(); // Read all data and display. os=" -------- -------- -------- --------";tm(); // Display lines at bottom of the 4 columns. //--------------------- calculating and displaying price averages ---------------------- ts=" "; // Space before first column. for (n=0;n<4;n++) { // Scan the 4 columns od=TD[n]/t; // Get average price for each column. ib=true;om("fd"); // Convert to formatted string of 2 decimal digits ts+=os+" "; // Add price to display string (ts) } cls="r0";os=ts;tm(); // Display (ts) } } =========================================================================================


============================================================================================== Identification Key: =================== You already know that there is a considerable difference in speed between standard search and binary search. Binary search requires data to be sorted at the column which is searched. We can only sort at one column (if we like to keep things simple), so which column should we choose for the sort? Most of the time there is a column which can identify the record more than any other column. This column should be the best to choose for the sort. Here is a list of variety of data types and the fields which can best identify them: (1) For Membership data, that column would be the User ID. (2) For Sales and purchases that column would be the invoice number. (3) For parts and other items Inventories that column would be the part number (4) For Tax and financial data that column would be the Social Security Number. Complex Identification Keys: ---------------------------- If your file contains peoples' names and addresses, you may like your Identification Key to be the last name of the person followed with his first name. If you have a large mailing list, you may like your primary field to sort at, to be the zipcode since this can qualify your mailing for a discounted postage. You may then add the last name to the end of the zipcode then follow the two with the first initial of the person like this: "45321SmithJ" The Identification Key is required to be unique for each person in the list. This is why in large lists, you may need to add more data to the Identification Key, like the street name, the house number, .. etc. How to make a sort within another: ---------------------------------- Sometimes, you like to sort your file by the zipcode as the primary field, however when more than one person have the same zipcode, you like to sort their records additionally by the name within the sorted zipcode. To do that, you sort the file by the secondary sort field first, then you sort the file again by the primary field. This means that you sort by the name, then by the zipcpde. Can we sort more than one column independantly? ----------------------------------------------- Yes. We can have subfiles which contain sorted single columns. This can allow us to use binary search with any column except that it requires organization and maintenance which we'll have time to discuss in the future. For now, we need to get into one important application on the Table File which is entering data into standard form documents. Data submission documents: ========================== Let us assume that you like to write a Tax Preperation software. You like to enter the data, save it, modify it from time to time, then when ready you want to print it into a standard tax form and mail the form to the IRS. The first step is to enter the data into file. You can enter the data item by item using the Console or the Text Screen or you can use text fields to do more than one field at a time. All those methods are slow, bulky and never feel you the same as when you look at the Tax Form and fill it as it is. This is what we like to fix here. We like you to be able to do the following: (1) Download the Tax Form from the IRS website and save it as an image file. (2) Display the image on the screen, find the start and end locations of each required data entry and save the (x,y) locations into a Table File. This should be done automatically. All you should be doing is clicking on those locations. (3) After the locations Table File is made, We need to create a Data Table File for the form document. The template of this data file will also be made automatically for you based on the field locations in the locations file. (4) After the two files are made, it will be easy to write a program which asks the data entry person to enter the identification key for the record he or she likes to submit data for. The data file will then be searched for a record with a matching key. If found, all field data will be read from the file and displayed at their correct locations in the form. If no match found, the form will stay empty and the record will be considered a new record to be added at the end of the file. The data entry person will then enter or modify field data then save the data into file. Do you need to write the software to do this job? ------------------------------------------------- Yes you can easily do it, but we also have it all done for you. The Utilities method um() is the method which handles Form Documents. Here is how to access the 5 required operations: um("fl") Generates the program which creates "field locations" file as explained in item (2) above. um("ft") Creates the column titles and template for the "data file" based on the "field locations" file as explained in item (3) above. um("fa") File Analysis. Displays a list of all column names, their order no's and templates. um("fd") Generates the data entry program which is explained in item (4) above. um("fp") Print the form containing the data of one record. IN: kys = Key of the record to be displayed into form before print. jf,kf= Location of Form's center relative to paper's center. lf,of= Desired Width and Height of printed Form. REMARK: jf,kf,lf,of are not in pixels. They are in 1/100th of an inch unit. Naming Files: ------------- The file name which you supply to all 4 methods is the Form's image file name. Nmaes of all files created will be based on that name. For example, If your Form's image file path was: tax\\F1040EZ.bmp Your Location file path will be : tax\\F1040EZ.loc and your Data file path will be : tax\\F1040EZ.dta You supply the image file to all 4 methods assigned to (fls) Method um("ft") requires one more parameter which is the length (in char's) of the identification key assigned to (k) Also method um("fp") requires one more parameter which is the key of the record to be printed assigned to (kys) Using the 4 methods: -------------------- The easiest way to use those 4 methods would be to put them as items of a menu. Here is a menu class (m) which can do it: ---------------------------------------------------------------------------------------------- public class m : pcs { string fl1s="tax\\F1040EZ.bmp"; // Stores image file name. int k1=9; // Default Key Length. public override void run() { cm("fe"); // Eliminate form j=78;k=20;dm("cs"); // Recize Console cls="s0";dm("ccb"); // Paint background with light gray. while(true) { // Start of endless loop. tm("c"); // Clear Screen cls="r0"; // Set color to "Pure red". fns="trb14"; // Set font to "times roman",bold,size 14. os=" FORM DOCUMENTS MENU"; // and display this text. tm(); // Calling method tm() to execute. os="";tm(); // Skip one line cls="b0";fns="trb10"; os=" Select (F) to supply image file name before any other selection is made."; tm();os="";tm();cls="S9"; // Change color, font os=" (F) Enter Form's Image File Name and Key length.";tm(); os=" (L) Create / Modify Field Locations File.";tm(); os=" (T) Create / Modify Data File template.";tm(); os=" (A) Analyze File's Columns and template.";tm(); os=" (D) Enter / Edit Data into Data File.";tm(); os=" (P) Print Form with one record data.";tm(); os=" (E) Exit.";tm(); os="";tm(); // skip one line cls="b0"; os="Selection :";tm("i"); // Get user selection. om("u"); int index="EFLTADP".IndexOf(os); // Check selection if (index<0){ // If unexpected char entered continue; // return to start of endless loop. } else if (index==0) break; // If selection was "E or e" exit loop else if (index==1) { // Enter image file name. os="Enter image file's name :";tm("i"); // Get file name, Assign it to (fl1s) fl1s=os; os="Enter Identification Key length (in char's):";tm("i"); om("ti");k=o; // Get Key length, assign it to (k) } else if (index==2) {fls=fl1s;um("fl");} // Create/Edit Locations File. else if (index==3) {fls=fl1s;k=k1;um("ft");}// Create/Edit template of data file. else if (index==4) {fls=fl1s;um("fa");} // Analyze file columns & templates. else if (index==5) {fls=fl1s;um("fd");} // Enter/Edit data into data file. else if (index==6) { // Print Form with one key data. os="Enter Identification Key:";tm("i"); kys=os; // Get Key, assign it to (kys) tm();os="";tm();cls="r0"; // Display instructions in red. os=" X = Wanted horizontal location of Form's center relative to paper's center.";tm(); os=" Y = Wanted vertical location of Form's center relative to paper's center.";tm(); os=" W = Wanted Width of printed Form. H = Wanted Height of printed Form.";tm(); os="";tm();cls="b0"; // Get print locations from user os="Enter X,Y,W,I (all in 1/100th of an inch unit) seperated with commas:";tm(); os="";tm();cls="S9";os="";tm("i"); tm();oc=',';om("s"); // Seperate 4 numbers into array OS[] float j1f,k1f,l1f,o1f; // Convert each number to float and os=OS[0];om("tf");j1f=of;os=OS[1];om("tf");k1f=of;os=OS[2];om("tf");l1f=of; os=OS[3];om("tf");o1f=of; // assign to temp var's. jf=j1f;kf=k1f;lf=l1f;of=o1f; fls=fl1s;um("fp"); // Print Form with wanted rec data. } } } } ---------------------------------------------------------------------------------------------- Compile the menu with: pcp m [ENTER] Notice that the the first item on the menu receives the two necessary parameters for other selections. The defaults for image file name and key length are the values we are going to be using in the next examples. Writing Software for Tax Preparation Business: ============================================== In order to illustrate the use of our "Form Documents" software, we are going to assume that you need to create a "Tax Preparation software". We like the person who uses it to be able to prepare taxes for several customers and store their tax data in a way which allows him to edit the data at any time he chooses and to calculate their tax due. The 3 methods above can help you with entering data into forms and file the raw data as they are. You should then write programs to read those files and do their arithmatic in addition to all the necessary software for the business. Preparation for the project: ---------------------------- (1) Creating Subfolders: You need to organize your data by creating different subfolders for each related items. For our sample, we are going to create one subfolder for the project named "tax". (2) Obtaining Tax Form Images: You need to connect to the IRS website at: http://www.irs.gov/pub/irs-pdf/ they may require you to register before you can get to this page. You'll then see a list of all "pdf" files of the forms. For the purpose of this demonstration, click on the tax form "f1040ez.pdf" since it's the only form we'll be using. The forms come with instruction pages attached which you don't need. So, click on the "Graphics Select Tool" icon on the top. Click on the top left corner of the usable part of the form and drag the mouse diagonally to the bottom right corner. You should see a frame around the part which you are selecting. Right click anywhere inside selected rectangle and select "Copy". The simplist way to create an image file is by using the "Paint" program which is available in all versions of windows. So, start "Paint" and paste the form there. Click on [File][Save as] and save it as a 24-bit Bitmap file. Call the file "F1040EZ.bmp" and place it into the "tax" subfolder. (3) Selecting an Identification Key: The best to use is the "Social Security Number" as a 9 digits with no hyphens or any other characters added. Creating the Field Locations File: ---------------------------------- Compile and run the menu class (class m) listed above. You don't need to enter file name or key length since the defaults are what you have. Select (L) You'll be instructed to type a field name, after you type it and press [ENTER], you'll be instructed to click at the start position then on the last position of the field. The process will repeat until all fields of the Form are done. At this point, press [ENTER] without typing a field name, the locations file will be created and you'll return back to the menu. Get into Notepad and read the file. You should see a table similar to the one below except that all colors, fonts and justifications for all fields will be at their default values which are: Color: b0 Font: crb14 Justification: c You'll need to edit the file using the following guidelines: (1) The order of the fields should be the most comfortable to you since when you enter data and use [TAB] to move forward, you'll be moving accress fields at the same order. When you enter data you can either use [TAB] and [BACKSPACE] to move forward and backward accross fields or you can jump to any field by clicking inside it. (2) Field names are good to be short in general, however this becomes more important when the expected field data is short. We used l/c for all field names of the tax form although this is not a must. The IRS uses line numbers to identify lines within their forms. This is good to use as field names. So, as you can see, we gave the field "Line 1" the name "ln1" and so on. When there is a seperator line in the form where one field is, we count it as two fields and suffex them with "#1" and "#2". See the two fields "ln1#1" and "ln1#2". This helps with alignment. Some data like social security number are seperated into 3 fields. (3) Clicking the mouse can't be a very precisive process. So, you should expect to see some inaccuracy in your start and end point coordinates of fields. Here are examples: a) The Y-position of the start point and the Y-position of the end point may not be equal although they should be since they are on the same line. You need to edit data to make them equal. b) The X-positions of the start and end points which you have clicked on may not allow enough field width to enter the data into. for example, after you go to next step and make the data file template, you may discover that some of the money amount fields allow only one char for the pennies. In this case you must edit the X-values to allow for 2-digits data. The most serious problems can happen with one char data fields like check boxes. You may not give a check box enough width for one char, so there will be no template made for it. This may cause field alignment errors when you start entering data. (4) The data of each field can be displayed and printed in any color you want. However, blue is used as the default for all. You may edit the color code of any field if you like. (5) Font codes are important since they determine the number of char's which you can insert into a field. Courrier font is important to use since width is unified for all its char's and this makes it possible to compute the templates. Font code "crb14" is used as a default, however we have changed the "date field" fonts to "crb11". (6) JustificatiOn: This is important for alignment. The justification code can be (c/l/r) meaning (Center/Left/right) For monies, you need to right justify dollar amounts and left justify cent amounts in order to get them closer to each others. Try to enter and edit field locations for the form by yourself and compare the result with ours. FD NAME START X START Y LAST X LAST Y Color Font J Regular Expression ============== ====== ====== ====== ====== ===== ======= = ============================== first 171 103 334 103 b0 crb14 l last 361 103 577 103 b0 crb14 l s_first 170 132 332 132 b0 crb14 l s_last 359 132 582 132 b0 crb14 l address 170 166 525 166 b0 crb14 l aptno 543 166 586 166 b0 crb14 c csaz 182 198 574 198 b0 crb14 l ss#1 613 104 642 104 b0 crb14 r ss#2 656 104 673 104 b0 crb14 c ss#3 685 104 737 104 b0 crb14 l s_ss#1 617 135 643 135 b0 crb14 r s_ss#2 655 135 673 135 b0 crb14 c s_ss#3 684 135 739 135 b0 crb14 l ln1#1 644 266 714 266 b0 crb14 r ln1#2 730 264 749 264 b0 crb14 l ln2#1 629 296 716 296 b0 crb14 r ln2#2 728 295 749 295 b0 crb14 l ln3#1 629 325 716 325 b0 crb14 r ln3#2 729 324 747 324 b0 crb14 l ln4#1 629 354 716 354 b0 crb14 r ln4#2 728 356 746 356 b0 crb14 l ln5cby 180 410 190 410 b0 crb14 c ln5cbs 275 410 284 410 b0 crb14 c ln5#1 640 433 714 433 b0 crb14 r ln5#2 730 434 746 434 b0 crb14 l ln6#1 643 470 715 470 b0 crb14 r ln6#2 729 470 746 470 b0 crb14 l ln7#1 639 486 715 486 b0 crb14 r ln7#2 728 486 747 486 b0 crb14 l ln8a#1 642 501 718 501 b0 crb14 r ln8a#2 729 502 747 502 b0 crb14 l ln9#1 639 545 715 545 b0 crb14 r ln9#2 730 544 745 544 b0 crb14 l ln10#1 637 591 713 591 b0 crb14 r ln10#2 729 592 747 592 b0 crb14 l ln11acb 374 623 387 623 b0 crb14 c ln11a#1 642 621 717 621 b0 crb14 r ln11a#2 728 623 746 623 b0 crb14 l ln12#1 638 711 714 711 b0 crb14 r ln12#2 729 712 744 712 b0 crb14 l date 391 820 446 820 b0 crb11 c occupation 457 818 617 818 b0 crb14 c s_date 391 851 446 851 b0 crb11 c s_occupation 455 851 618 851 b0 crb14 c ----------------------------------------------------------------------------------------- Creating the Data File Template: -------------------------------- Your "locations" file must be complete as the one above and should be named "F1040EZ.loc" and placed into the "tax" folder before selecting (t) from the menu to create the template. The template creation is automatic and should be done in few seconds. Look in the "tax" folder you should find a new file named "F1040EZ.dta". Open this file using notepad and inspect the template. Return back to the guidelines above, especially (3b) and see if something needs to be corrected. If you are sure that templates of all fields are OK, proceed to next section. Entering data into data file: ----------------------------- When you select (d) from the menu, the form will be loaded and displayed and you'll be asked to enter the identification key of the record which you like to work on. Enter the key into the text field at bottom and hit [ENTER]. The file will be searched for a record which contains that key. If found all data of that record will be read and displayed at their proper locations in the form. Otherwise it will be considered to be a new record and will be saved at the end of all data in the file when you are ready. To modify a field click where field data should be typed in, the field will be highlighted with a yellow rectangle of same size as all the data which you are expected to insert into that field. After typing your new data, you can do one of three: a) Press [TAB] to edit the next field forward. b) Press [BACKSPACE] to edit the next field backward. c) Click into another field anywhere to move to it and edit it. When you are ready to save the record, press [ENTER]. The record will be saved, all data on the screen will be erased and you'll be asked to enter a new key for a record to edit. If you don't like to save the edited data, press [ESCAPE] instead of [ENTER]. If you have no more records to edit, close the Form's window. =========================================================================================


============================================================================================== Who can make use of the method explained so far to enter data into Tax Forms? ============================================================================= Accountants who like to prepare their companies' taxes could appreciate being able to enter data in a real Tax Form, save the data and retrieve it back any time they want then print the form with the data in whenever they'are ready. Those accountants will be using their calculators to do the arithmatic required by the form as they enter the data. However, a Tax preparation business who serves larger number of customers cannot be satisfied of using this software in its manual form. They need to do the calculations programmatically and they like to store data which is common to more than one form into seperate files with software available to transfer the data to any form whenever necessary. So, let us see how we can extend this software to these new requirements. Creating customer information file: ----------------------------------- The first thing a business needs is a file which contains basic information about its customers. It includes their names, addresses, phone numbers and any other necessary information. They should be able to transfer data from this file to tax forms in addition to many other uses like sending letters to their customers or calling them. We need to make a table file which contains customer information. This should be easy, but how are we going to enter the data into the file? The easiest way is "Form Documents" since as we have already seen, they work automatically upto creating the data files and populating them. They can also be printed as they are with no modifications. This is a personal file, we have no image to download this time for the form, but we have all the tools to make one. It should contain the business name and logo on the top and all data exactly as the business needs it and should also look nice when displayed on the screen or printed. =============================================================================================== Example 12: Create a form document to be used for entering data into customer information file. The data should include all customer information which a tax business needs. After creating the form image, use the "Form Documents Menu" to create the locations file, the data file and the data file template. Use the menu also to populate the data file. =============================================================================================== public class a : pcs { public override void init() { base.init(); } public override void run() { j=725;k=355;cm("fs"); cls="r0";i=5;gm("sps"); // Create red pen with 5-pixels width. cls="s9";gm("ec"); // Paint form with white color. lf=720;of=350;gm("crd"); // Display red frame. // Create rect & draw. cls="g0";gm("sps"); // Change color to green. kf=160;fns="trb20";os="TAX PRO";gm("ctf");// Paint company name in green, cls="S9";gm("sps"); // then frame around it in black. kf=160;fns="trb20";os="TAX PRO";gm("ctd");gm("grd"); kf=140;fns="crb14";os="Tax Preparation Professionals";gm("ctf");gm("grf"); cls="c7";gm("sps"); // Background color of customer's section. kf=40;lf=680;of=160;gm("crf"); cls="c7";gm("sps"); // Background color of spouse's section. kf=-105;lf=680;of=90;gm("crf"); cls="r0";gm("sps"); // Display titles of both sections in red kf=110;os="Customer Information";gm("ctf");gm("grf"); kf=-70;os="Spouse Information";gm("ctf");gm("grf"); cls="S9";gm("sps"); // Change color to black //--------------------------------- Customer's Section ------------------------------- os="First Name: __________________ MI: _ Last Name: _______________________"; kf=88;gm("ctf");gm("grf"); os="Address: _____________________________________________________ Apt No: ____"; kf=66;gm("ctf");gm("grf"); os="City, State and Zipcode: ___________________________________________________"; kf=44;gm("ctf");gm("grf"); os="Telephone Number, H: ______________ M: ______________ B: ______________"; kf=22;gm("ctf");gm("grf"); // Double-Strike Text os="Social Security Number: ___-__-____ Occupation: ___________________________"; kf=0;gm("ctf");gm("grf"); //----------------------------------- Spouse's Section ------------------------------- os="First Name: __________________ MI: _ Last Name: _______________________"; kf=-92;gm("ctf");gm("grf"); os="Social Security Number: ___-__-____ Occupation: ___________________________"; kf=-114;gm("ctf");gm("grf"); //---------------------------- Save form into an image file -------------------------- fls="tax\\customers.bmp";bip=bio;gm("bsb"); // Save as "bmp" file. } } =============================================================================================== COMMENTS: ========= Ther are few items concerning "Text Graphics" which we have not discussed in the graphics section and will discuss here. (1) When you draw the graphics object of text (gpp), using gm("ctd"), you draw the text outlines and when you use method gm("ctf") you draw it filled. This feature has been used in this example to draw the business name "TAX PRO" in green color with black borders. (2) For some reason when you use a small font to draw text on the screen graphically, it comes not as sharp and clear as when you display the text textually. You can't increase the drawing pen's width to make it clearer. So we have used an old trick to solve this problem which is called "double-striking". Double-striking means displaying the item twice at the same spot on the screen or on paper. We have used this method with mostly all text in the example. Calling gm("ctf");gm("grf"); double-strikes the text on the screen. Compile the code of this example and run it. It should create the image file "customers.bmp" and store it into the "tax" folder. Run the "Form Documents Menu", select (F), supply the form image file name "tax\\customers.bmp" and a key length of 9 char's. Then select (L) to create the locations file, select (T) to create the data file, its column titles and its template. Finally select (D) to enter customers data into the file. Here is the location file which we have created: FD NAME START X START Y LAST X LAST Y Color Font J Regular Expression ============== ====== ====== ====== ====== ===== ======= = ============================== first 177 101 331 101 b0 crb14 l mi 390 101 402 101 b0 crb14 c last 532 101 730 101 b0 crb14 l address 150 122 608 122 b0 crb14 l aptno 695 122 729 122 b0 crb14 l csaz 289 144 728 144 b0 crb14 l phone_h 263 167 383 167 b0 crb14 c phone_m 436 167 556 167 b0 crb14 c phone_b 609 167 730 167 b0 crb14 c ss#1 280 189 305 189 b0 crb14 c ss#2 314 189 331 189 b0 crb14 c ss#3 341 189 375 189 b0 crb14 c occupation 496 189 730 189 b0 crb14 l s_first 177 281 331 281 b0 crb14 l s_mi 390 281 402 281 b0 crb14 c s_last 532 281 730 281 b0 crb14 l s_ss#1 280 303 305 303 b0 crb14 c s_ss#2 315 303 331 303 b0 crb14 c s_ss#3 341 303 375 303 b0 crb14 c s_occupation 496 303 730 303 b0 crb14 l ===============================================================================================


============================================================================================== Creating data entry file: ------------------------- Most of the time, the income amounts which we enter into a Tax Form make only about half of the data to be entered into the form. The rest are calculated amounts which are derived from those income amounts. This tells us that we can simplify the tax preparation job and reduce the possibility of errors if we file the income amounts only using a small file, then write a program which transfers those amounts to the Tax Form together with the calculated amounts. In the real world, you'll be dealing with many tax forms, so you'll need a file which can store enough data for them all. You may also consider making more than one file and creating a memu which allows the tax preparer to select the file or files which store applicable data to each customer. The program you write should read all those "income" files, make all necessary calculations then write data into all applicable tax forms. This is what you'll be doing. We are not going to be sharing all this fun with you. We are going to assume that form "1040EZ" is the only tax form to be used and will create the file "income.dta" which stores the income data of that form. We are also going to show you an example on how to use the two files "customers.dta" and "income.dta" to fill up form 1040EZ programmatically and print it when complete. =============================================================================================== Example 13: Create a form document to be used for entering data into income information file. The data should include all income data of form 1040EZ not including calculated data. After creating the form image, use the "Form Documents Menu" to create the locations file, the data file and the data file template. Use the menu also to populate the data file. =============================================================================================== public class a : pcs { public override void init() { base.init(); } public override void run() { j=430;k=355;cm("fs"); cls="r0";i=5;gm("sps"); // Create red pen with 5-pixels width. cls="s9";gm("ec"); // Paint form with white color. lf=425;of=350;gm("crd"); // Display red frame. // Create rect & draw. cls="g0";gm("sps"); // Change color to green. kf=160;fns="trb20";os="TAX PRO";gm("ctf");// Paint company name in green, cls="S9";gm("sps"); // then frame around it in black. kf=160;fns="trb20";os="TAX PRO";gm("ctd");gm("grd"); kf=140;fns="crb14";os="Tax Preparation Professionals";gm("ctf");gm("grf"); cls="c7";gm("sps"); // Background color of upper section. kf=40;lf=400;of=135;gm("crf"); cls="c7";gm("sps"); // Background color of bottom section. kf=-100;lf=400;of=125;gm("crf"); cls="r0";gm("sps"); // Display title in red kf=120;os="Income Information";gm("ctf");gm("grf"); cls="S9";gm("sps"); // Change color to black //------------------------------------ Form body ---------------------------------------- os="Tax Year: ____ "; kf=88;gm("ctf");gm("grf"); // Double-strike all the text to be displayed. os="Check if married filing joint return ... [ ]"; kf=66;gm("ctf");gm("grf"); os="Check if someone claims you as dependant [ ]"; kf=44;gm("ctf");gm("grf"); os="Check if someone claim your spouse"; jf=-43;kf=22;gm("ctf");gm("grf"); os="as dependant and you are filing jointly [ ]"; kf=0;gm("ctf");gm("grf"); os="Wages, Salaries and Tips: ...... ________.__"; kf=-54;gm("ctf");gm("grf"); os="Taxable Interest: .............. ________.__"; kf=-78;gm("ctf");gm("grf"); os="Unemployment Compensation: ..... ________.__"; kf=-98;gm("ctf");gm("grf"); os="Federal income tax withheld: ... ________.__"; kf=-121;gm("ctf");gm("grf"); os="Earned income credit (EIC): .... ________.__"; kf=-143;gm("ctf");gm("grf"); //---------------------------- Save form into an image file -------------------------- fls="tax\\income.bmp";bip=bio;gm("bsb"); // Save as "bmp" file. } } =============================================================================================== Compile the code of this example and run it. It should create the image file "income.bmp" and store it into the "tax" folder. Run the "Form Documents Menu", select (F), supply the form image file name "tax\\income.bmp" and a key length of 9 char's. Then select (L) to create the locations file, select (T) to create the data file, its column titles and its template. Finally select (D) to enter customers data into the file. Here is the location file which we have created: FD NAME START X START Y LAST X LAST Y Color Font J Regular Expression ============== ====== ====== ====== ====== ===== ======= = ============================== tax-year 426 101 461 101 b0 crb14 c joint 572 121 583 121 b0 crb14 c dependant 572 143 583 143 b0 crb14 c s_dependant 572 188 583 188 b0 crb14 c wages#1 494 243 563 243 b0 crb14 r wages#2 574 243 590 243 b0 crb14 l t_interest#1 494 267 564 267 b0 crb14 r t_interest#2 573 267 590 267 b0 crb14 l unemp#1 494 287 563 287 b0 crb14 r unemp#2 573 287 589 287 b0 crb14 l t_withheld#1 494 310 562 310 b0 crb14 r t_withheld#2 573 310 590 310 b0 crb14 l eic#1 494 332 564 332 b0 crb14 r eic#2 573 332 590 332 b0 crb14 l ===============================================================================================


============================================================================================== Automated Tax Preparation: ========================== Now it's time for the program which will read the "customers.dta" and the "income.dta" files, and use their data to fill the "1040EZ" form, in addition to doing all arithmatic required by the form. Filling the form with data means writing the data into file "F1040EZ.dta". Before we write one record's data into a file, we always attempt to read the record first, so that if it exists, we'll be overwriting its old data not creating a duplicate record. Whenever we read a record we get 3 items which are: (1) OS[] which contains all field data in order. (2) (rcs) which is the archived copy of the last read or written record in a string form just like it is in the file. You may use it but not overwrite it. (3) (os) which is your copy of (rcs) Normally, you use it to know whether the record was a data record or a blank line. You trim it using om("c") then check its length to determine which type of record it is. The record number (rci) which you supply to fm("r") when you read a record is kept unchanged for you so that when you write the record, you'll be overwriting the existing one. After the record is written, (rci) is reset to zero. You can't use (rci) at this point to read or write unless you assign it a new value since (rci=0) generates an error. In the following example, we'll be getting the identification key of the record to work on from the user, searching for its record number into file "customer.dta" using method fm("fs") then reading the record. The field data array OS[] returned will be persisted by assigning its value to the local array O1S[]. Next, we'll repeat the same procedures with file "income.dta" and assign OS[] returned to the local array O2S[]. Finally we'll do the same with file "F1040EZ.dta". Array OS[] returned from that file will be overwritten by data stored into O1S[] and O2S[] and by calculated values. The array will then be written back into that last file and the Tax Form data will be ready to display or print. Tax Form Arithmatic: -------------------- Doing tax form's arithmatic requires some repeated jobs. So we are going to create 3 methods, add(), subtract() and SetResult() When we store money amounts into a Form Document file, we use two adjacent fields to store each amount, one for the dollars and one for the cents. When we like a method to operate on a specific amount, we supply it with the index of OS[] where the amount's dollar field is. The methods know that the cents amount of that field will be found at the next row of OS[]. Let us start with method SetResult() since it's the simplest. void SetResult(int n) { // Store amount in (od) into data array. IN: n=index of OS[] to store dollar part at. ib=true;om("fd"); // Convert (od) to string with 2 decimal digits. int d=os.IndexOf("."); // Find index of decimal point OS[n]=os.Substring(0,d); // Store dollar part at (n) OS[n+1]=os.Substring(d+1); // and cents part at (n+1) } This method recieves a money amount stored into the global variable (od) and splits it into two parts, "dollars" part and "cents" part. It also receives an additional parameter which is the index of OS[] where the "dollars" part should be stored. The "cents" part will be stored into the next field. Now, let us see method subtract(): void subtract(int x,int y) { // Calculate (First amount) - (Second amount) and store result into (od) // IN: x,y=index of OS[] where dollar parts of the two amounts are. OUT: od od=0; // Start with od=0 os=OS[x];om("ti");od+=o; // add dollar part of first amount to (od) os=OS[x+1];om("ti");od+=0.01d*o; // add cents part of first amount to (od) os=OS[y];om("ti");od-=o; // subtract dollar part of second amount from (od) os=OS[y+1];om("ti");od-=0.01d*o; // subtract cents part of second amount from (od) } and finally here is method add(): void add() {//IN:I[]=Indices of Dollar fields to be added. OUT:od // Add all amounts whose dollar parts are to be found in OS[] at indices contained into array // I[] and return result assigned to (od) od=0; // Start with od=0 for (int i=0;i< I.Length;i++) { // Scan all indices in the supplied array if (I[i]==0) break; // When first unassigned row is reached,exit os=OS[I[i]];om("ti");od+=o; // Add dollar part of each amount to (od) os=OS[I[i]+1];om("ti");od+=0.01d*o; // Add cents part of each amount to (od) } } Finding Tax Due: ---------------- The correct way is to store the Tax Tables into an additional Table File and search the file to get the tax due using the "Taxable Income" amount on line 6 of the form. We don't see a reason to do that here since it'll not add much to your learning. So, we used this simple formula: Tax rate for singles: 30% Tax rate for couples filing jointly: 20% Finding Column orders for all 3 files: -------------------------------------- You are going to need to know where each field is located into each file before writing this program. So select (A) from the "Form Documents" menu and print the displayed list (using text screen print option) Use Courrier font size 10 for printing. Do that for all 3 files. ------------------------------------------------------------------------------------------------ public class a : pcs { // var's used: O1S[] Persists field array OS[] of file "customers.dta" // O2S[] Persists field array OS[] of file "incone.dta" // FileLength: a type "long" value to persist file length of "F1040EZ,dta" // JointReturn: a flag which indicates that "Joint Return" filing is to be used string[] O1S,O2S; // Define var's. See above. long FileLength; bool JointReturn=false; public override void init() { tia=toa="t"; bli=1; base.init(); } public override void run() { if (blp==1) { // Starting block cls="r0";os=" TAX PROCESSING"; tm();os="";tm();cls="b0"; // Display title then change color. os="Enter key number of the tax record you like to process: ";bli=2;tm("i");return; } // Get key from user then move to block 2. if (blp==2) { // Return after key has been received kys=os; // Assign key to (kys) //---------------------------- Get all field data for key ------------------------------ // Open customers file, get data for the wanted key and store it into O1S[] fs="tf0";fls="tax\\customers.dta";fm("o"); K[0]=0;KS[0]=kys;fs="tf0";fm("fs"); // Search for the record which contains (kys) if (O[0]>0) { // If a match found: rci=O[0];fs="tf0";fm("r"); // Read matching record } O1S=OS; // and store its field data array into O1S[] // Open income file, get data for the wanted key and store it into O2S[] fs="tf1";fls="tax\\income.dta";fm("o"); K[0]=0;KS[0]=kys;fs="tf1";fm("fs"); // Repeat with the income file if (O[0]>0) { rci=O[0];fs="tf1";fm("r"); } O2S=OS; // Open Tax Form file, get data for the wanted key (if available), store (ol) into // (FileLength) fs="tf2";fls="tax\\F1040EZ.dta";fm("o"); FileLength=ol; K[0]=0;KS[0]=kys;fs="tf2";fm("fs"); // Repeat with the Tax Form file if (O[0]>0) { rci=O[0];fs="tf2";fm("r"); } else rci=(int)FileLength; // If no match found, make (rci) point to end of file //------------------------- Copy values from "customers" file --------------------------- OS[0]=kys; // Key OS[1]=O1S[1]+" "+O1S[2]; // First Name and Initial OS[2]=O1S[3]; // Last Name OS[3]=O1S[14]+" "+O1S[15]; // Spouse 1st name and initial OS[4]=O1S[16]; // Spouse last name OS[5]=O1S[4]; // Address OS[6]=O1S[5]; // Appartment number OS[7]=O1S[6]; // City, State and Zipcode OS[8]=O1S[10];OS[9]=O1S[11];OS[10]=O1S[12]; OS[11]=O1S[17];OS[12]=O1S[18];OS[13]=O1S[19]; // Social Sec Number for customer and his spouse OS[42]=O1S[13]; // Occupation OS[44]=O1S[20]; // Spouse Occupation //--------------------------- Copy values from "income" file ---------------------------- // If "Joint Return" checked set flag. // Standard Deductions: Start with od=0. add $8,750 for each of customer and his spouse // unless someone claims them as dependant. Set results and mark proper checkboxes in form if (O2S[2].Length>0) JointReturn=true; od=0; if (O2S[3]=="") od+=8750.00;else OS[22]="x"; if (O2S[4]=="" && JointReturn) od+=8750.00;else OS[23]="x"; SetResult(24); // Set standard deduction amount at line 5. OS[14]=O2S[5];OS[15]=O2S[6]; // Set Wages at line 1 OS[16]=O2S[7];OS[17]=O2S[8]; // Set Interest at line 2 OS[18]=O2S[9];OS[19]=O2S[10]; // Set unemployment compensation at line 3. I=new int[]{14,16,18};add(); // Add lines 1,2 and 3 SetResult(20); // and set them at line 4. subtract(20,24);SetResult(26); // Subtract line 5 from line 4 and set result at ln 6 OS[28]=O2S[11];OS[29]=O2S[12]; // Copy "Tax Withheld" to form OS[30]=O2S[13];OS[31]=O2S[14]; // Copy EIC to form I=new int[]{28,30};add(); // Add the two together and SetResult(32); // Set result at line 9. // Finding tax due: We are going to simplify the process by making it 30% for singles // and 20% for couples od=0; // Initialize (od) os=OS[26];om("ti");od+=o; // Add dollar part of "Taxable income" (line 6) os=OS[27];om("ti");od+=0.01d*o; // and also add cents part to it. if (!JointReturn) od=0.30*od; // Assuming 30% tax on singles else od=0.20*od; // and 20% tax on couples SetResult(34); // Set tax due at line 10. subtract(32,34); // Subtract taxes due (ln 10) from paid taxes (ln 9) if (od>0) SetResult(37); // If positive set as amount to be refunded (ln 11a) else if (od<0) { // If negative: od=-od;SetResult(39); // Set as amount owed (ln 12) } display(); // Display data fs="tf2";fm("w"); // Write record into file. fm("c"); // Close file. } } //------------------------------------ Local Methods --------------------------------------- void add() {//IN:I[]=Indices of Dollar fields to be added. OUT:od // Add all amounts whose dollar parts are to be found in OS[] at indices contained into array // I[] and return result assigned to (od) od=0; // Start with od=0 for (int i=0;i< I.Length;i++) { // Scan all indices in the supplied array if (I[i]==0) break; // When first unassigned row is reached,exit os=OS[I[i]];om("ti");od+=o; // Add dollar part of each amount to (od) os=OS[I[i]+1];om("ti");od+=0.01d*o; // Add cents part of each amount to (od) } } void subtract(int x,int y) { // Calculate (First amount) - (Second amount) and store result into (od) // IN: x,y=index of OS[] where dollar parts of the two amounts are. OUT: od od=0; // Start with od=0 os=OS[x];om("ti");od+=o; // add dollar part of first amount to (od) os=OS[x+1];om("ti");od+=0.01d*o; // add cents part of first amount to (od) os=OS[y];om("ti");od-=o; // subtract dollar part of second amount from (od) os=OS[y+1];om("ti");od-=0.01d*o; // subtract cents part of second amount from (od) } void SetResult(int n) { // Store amount in (od) into data array. IN: n=index of OS[] to store dollar part at. ib=true;om("fd"); // Convert (od) to string with 2 decimal digits. int d=os.IndexOf("."); // Find index of decimal point OS[n]=os.Substring(0,d); // Store dollar part at (n) OS[n+1]=os.Substring(d+1); // and cents part at (n+1) } void display() { fns="crb12";cls="S9";os="";tm(); // Change color and font os="No. NAME VALUE";tm(); // Display Title for (n=0;n< CNI[2];n++) { // Scan all columns. Start with spaces in (os) os=" "; js=""+n;j=0;om("m"); // Insert column no js=CNS[2][n];j=4;om("m"); // Insert Column Title js=OS[n];j=20;om("m"); // Insert Column data tm(); // Display row } } } =============================================================================================== Printing the Form: ------------------ You can print the form after being filled-up with one record either manually by clicking the "Print" button which is available when you select (D) from the menu or programmatically by calling um("fp") When you use the print button, the Form will be printed at a default size which is computed using the Form image's size in pixels and its resolution. Method um("fp") allows you to select the location of the printed Form's center relative to paper's center and the wanted width and Height of the Form. These values are not in Pixels. They are in 1/100th of an inch unit. We don't recommend supplying both width and height (lf,of) at the same time. Supply one of them and assign zero to the other one. The method will reassign the one which is assigned zero a calculated value which makes the width to height ratio of the printed image equal to the original's. If you like to print at the default size like you do when you use the Print button, assign zeros to both (lf & of) ===============================================================================================


===================================================================================================== Exporting and Importing the Table Files' Data using the Comma Seperated values files ==================================== For PC# version 4.39 and later Most databases have the ability to export their data to others and to import other's data using the "Comma Seperated Values Files" as intermediary. Therefore, we are going to add this file type to the files which PC# can use for reading and writing data. Additionally, we like to be able to export and import data between this file type and our Table Files. There are two major types of Comma Seperated values files (csv) One type encloses data items into double quotes and the other one does not. The second type is the simpler except that it will fail if the data items contain commas internally. Let us return to the table which we started the table files study with and save it again into the table file "test.txt". HISTORICAL STOCK PRICES OF MICROSOFT CORPORATION ------------------------------------------------ Date Open High Low Close Volume ========== ======== ======== ======== ======== ============ 02/07/2008 28.34 28.78 27.90 28.12 164,964,900 02/06/2008 29.28 29.35 28.29 28.52 138,021,700 03/05/2008 29.91 29.94 28.89 29.07 137,522,600 02/04/2008 30.49 30.72 30.11 30.19 119,987,300 02/01/2008 31.06 33.25 30.25 30.45 291,095,400 01/31/2008 31.91 32.74 31.72 32.60 103,363,200 01/30/2008 32.56 32.80 32.05 32.20 106,343,700 01/29/2008 32.85 32.89 32.35 32.60 68,007,200 01/28/2008 33.02 33.10 32.42 32.72 81,007,400 Exporting data of the table file: ================================= Method fm("X") exports the table file's data. This means that it converts it into a (csv) file which other data storage softwares can convert to theirs. If we supply the method with (kb=true), the file will be converted to the (csv) file type which encloses data items into double quotes. The method expects the additional parameters (fls=table file name) and (os=csv file name) Here is a simple class which can do this job: --------------------------------------------------------------------------------------------------- public class a:pcs { public override void run() { cm("fe"); // Eliminate form fls="test.txt";os="test.csv";kb=true;fm("X"); // Export "test.txt" data to "test.csv" file } } --------------------------------------------------------------------------------------------------- After compiling and running this class, file "test.csv" will be generated and here is what it contains: ----------------------------------------------------------- "Date","Open","High","Low","Close","Volume" "02/07/2008","28.34","28.78","27.90","28.12","164,964,900" "02/06/2008","29.28","29.35","28.29","28.52","138,021,700" "03/05/2008","29.91","29.94","28.89","29.07","137,522,600" "02/04/2008","30.49","30.72","30.11","30.19","119,987,300" "02/01/2008","31.06","33.25","30.25","30.45","291,095,400" "01/31/2008","31.91","32.74","31.72","32.60","103,363,200" "01/30/2008","32.56","32.80","32.05","32.20","106,343,700" "01/29/2008","32.85","32.89","32.35","32.60","68,007,200" "01/28/2008","33.02","33.10","32.42","32.72","81,007,400" ------------------------------------------------------------ As you can see, our method has eliminated all the text above the titles and the empty lines within the data. The (csv) file specs are not fully standardized. If we have not eliminated the top text, some softwares will not be able to make use of this file. Now let us see how we can convert our table file into the (csv) file type which does not enclose data items into double quotes. Before we do so, we must correct one problem. All data at the "Volume" column contain commas. These commas must be removed since they would be thought of as data seperators if we dont. So modify "test.txt" file by removing all commas from data items then remove the statement (kb=true;) in class (a) and run it again. Here is what you get: --------------------------------------------- Date,Open,High,Low,Close,Volume 02/07/2008,28.34,28.78,27.90,28.12,164964900 02/06/2008,29.28,29.35,28.29,28.52,138021700 03/05/2008,29.91,29.94,28.89,29.07,137522600 02/04/2008,30.49,30.72,30.11,30.19,119987300 02/01/2008,31.06,33.25,30.25,30.45,291095400 01/31/2008,31.91,32.74,31.72,32.60,103363200 01/30/2008,32.56,32.80,32.05,32.20,106343700 01/29/2008,32.85,32.89,32.35,32.60,68007200 01/28/2008,33.02,33.10,32.42,32.72,81007400 --------------------------------------------- REMARK: You may find a (csv) file with some data items enclosed in double quotes and some are not. Supply (kb=true) for this type also. Importing data to the table file: ================================= Method fm("I") imports data to the table file. This means that it converts the (csv) file into a table file. It expects the same parameters (fls,os,kb) which method fm("X") expects in addition to (j,k,i) which we'll discuss shortly. Exporting the table file data has been simple since we started with the table file which is made to our specs and contains clean data. Importing is not as simple since we start with the (csv) file which is made by others. Some (csv) files may contain some text lines which are not part of the data at the top, some may contain text lines at the bottom and some may contain text lines at both locations. When data items contain commas, some remove them or replace them with other characters and some keep the commas and enclose data items in double quotes. Our export and import methods can handle both types. Enclosing data items in double quotes has created another problem. What can they do when data items contain double quotes? They have solved this problem by doubling them, which means that two double quotes mean one double quote which is part of the data. The hardest of all problems come when some data contain embedded line feeds. So, When we read the file, the record which contains such data will be read as two or more records. A manual operation will be required to solve this problem. What to do to help method fm("I") read the (csv) file data correctly: --------------------------------------------------------------------- Before you call the method, you need to display the file with Notepad and do the following: 1) Find how many lines at the top of the file and how many lines at the bottom which are not part of the data and assign them to (j & k) respectively. These lines will be skipped by PC# software. The rest of the file should be the titles record and data records only which contain equal number of data items seperated with commas. You may also delete those unwanted lines at the top and bottom with Notepad. In this case (j,k) values to be supplied to the method will be zeros. 2) Count the number of data items at the titles record, assign the number to (i) and supply it to the method. If you do so, PC# software will be counting the data items at each data record and comparing their total with (i) If found to be not the same, will give an error message and exits to allow you to correct the problem with Notepad. Most likely the reason will be a linefeed char embedded into a data item which can be deleted manually. If you don't like the method to verify the data items count at each record, supply (i=0) 3) As you know, PC# archives are initialized with default sizes. However, you can modify the sizes with method dm() Therefore, if you were importing a large size (csv) data file, you need to request changing the max size of data records by assigning a larger number to (o) and calling method dm("hd") This is good to be done within method init() Preparing the Table File to Receive (csv) File's data: ------------------------------------------------------ The table file must be available and contain upto the template record before it can receive the (csv) data. The titles must be ordered the same as in the (csv) file (although their names don't have to be identical to the (csv) file's title names) So, you should either create the new file "test1.txt" and write upto the template record into it using Notepad or do the whole job programmatically as we'll do in the following class. ------------------------------------------------------------------------------------------------------ public class a:pcs { public override void run() { cm("fe"); // Eliminate form fls="test1.txt";ks="f";fm("D"); // Delete file if exists. fs="sf0";fm("ow"); // Create and open new file. // Write next 5 lines into file. os=" HISTORICAL STOCK PRICES OF MICROSOFT CORPORATION";fm("wl"); os=" ------------------------------------------------";fm("wl"); os="";fm("wl"); os=" Date Open High Low Close Volume";fm("wl"); os="========== ======== ======== ======== ======== ============";fm("wl"); fm("c"); // close fls="test1.txt";os="test.csv";kb=false;fm("I"); // Import data from "test.csv" file to "test1.txt" } } ------------------------------------------------------------------------------------------------------ After compiling and running this class, file "test1.txt" will be generated and here is what it contains: --------------------------------------------------------------- HISTORICAL STOCK PRICES OF MICROSOFT CORPORATION ------------------------------------------------ Date Open High Low Close Volume ========== ======== ======== ======== ======== ============ 02/07/2008 28.34 28.78 27.90 28.12 164964900 02/06/2008 29.28 29.35 28.29 28.52 138021700 03/05/2008 29.91 29.94 28.89 29.07 137522600 02/04/2008 30.49 30.72 30.11 30.19 119987300 02/01/2008 31.06 33.25 30.25 30.45 291095400 01/31/2008 31.91 32.74 31.72 32.60 103363200 01/30/2008 32.56 32.80 32.05 32.20 106343700 01/29/2008 32.85 32.89 32.35 32.60 68007200 01/28/2008 33.02 33.10 32.42 32.72 81007400 --------------------------------------------------------------- Notice that when we called method fm("I") we supplied it with (kb=false) That was because the last (csv) file we used did not contain double quotes around data items. If it did, we could have supplied the method with (kb=true) and ended with the same table file. Notice also that we have not assigned values in (j,k,i) to the method. This was because we know that the source (csv) file which was made by us contains nothing but the titles record followed with the data records. We did not care to verify the number of data items at each record for the same reason. If we wanted to do the verification, we should have supplied (i=6) Importing specific data items from the (csv) file and neglecting the rest: -------------------------------------------------------------------------- You may import to a table file which contains less number of data items than in the (csv) file. Notice also that the title names of the table file may not be the same as in the (csv) file. This is because we identify titles by their order numbers, not by their names. The title order numbers start by (0) If you are not importing all (csv) file data, you need to add to your parameters the string (js) which contains a dictionary that tells the method the order number at the csv record of each item you want to import and the order number at the table file record where it should be. Here is an example: If you supply (js="0:0,8:1,5:2";) The method will understand that the table file contains 3 data items while the (csv) file contains more. Item number (0) of the (csv) goes to item (0) of the (tf), item number (8) of the (csv) goes to item (1) of the (tf) and item (5) of the (csv) goes to item (2) of the (tf) All other items in the (csv) file will be ignored. Here is a modification of the example above which imports the same (csv) data to a table file which contains only two items, "Date" and "Close": --------------------------------------------------------------------------------------------------- public class a:pcs { public override void run() { cm("fe"); // Eliminate form fls="test1.txt";ks="f";fm("D"); // Delete file if exists. fs="sf0";fm("ow"); // Create and open new file. // Write next 5 lines into file. os=" HISTORICAL STOCK PRICES OF MICROSOFT CORPORATION";fm("wl"); os=" ------------------------------------------------";fm("wl"); os="";fm("wl"); os=" Date Close";fm("wl"); os="========== ========";fm("wl"); fm("c"); // close js="0:0,4:1"; // Wanted items and their order no's at (source:destination) fls="test1.txt";os="test.csv";kb=false;fm("I"); // Import data from "test.csv" file to "test1.txt" } } ------------------------------------------------------------------------------------------------------ And here is the resulting table file ---------------------------------------------------------------- HISTORICAL STOCK PRICES OF MICROSOFT CORPORATION ------------------------------------------------ Date Close ========== ======== 02/07/2008 28.12 02/06/2008 28.52 03/05/2008 29.07 02/04/2008 30.19 02/01/2008 30.45 01/31/2008 32.60 01/30/2008 32.20 01/29/2008 32.60 01/28/2008 32.72 ---------------------------------------------------------------- Accessing Comma Seperated Values Files Directly: ================================================ Many of the statistical data available on the internet which we could like to download and study are available in a (csv) file format. We can always convert them to table file format before doing any software analysis or modification, but it should be easier if we can read them as they are without the conversion. Therefore, we have developed a software which allows a simple access to this file type. It allows reading a record from a (csv) file, modifying it and writing it back if necessary, but it does not allow other features which table files allow, like searching, sorting or adding new records. To make it easy for you, you access the (csv) files using exactly the same methods which you access table files with. The only difference is that you assign them keynames in the range (cf0:cf89) instead of the table file key names (tf0:tf89) CSV FILES' ACCESS MODES: ======================== o: USE: Opens a file for Read/Write. It copies file data to archives after removing any non-data record found at the top or the bottom of the file. IN : fls=file name fs=keyname (like "cf0") j=Number of non-data records found at top, k=Number of non-data records foud at bottom i=Number of data items at each record (for verification purpose) Def:i=0:Don't verify. OUT: o=Start data record number (always =1), ol=Total number of records after removing any non-data record. dnb=true: File is empty. r: USE: Reads data for one record. IN: rci=Record number. i=Number of data items at each record (for verification purpose) Def:i=0:Don't verify. OUT: os=rcs=record data in a string form. dnb=true: Record at or beyond end of file OS[]=Field data. oi=Number of occupied rows in OS[]. w: USE: Write a record. Data will not be written into file until you call fm("c") IN : rci=record number, OS[]=field data. c: USE: Copies data back to file then closes it and resets archives. You need to know the following: =============================== (1) When you call method fm("o") to open the file, the non-data records whose numbers are in (j,k) are neglected. Only the titles record followed with all data records are read and stored in the (csv) data array for your file. The file type (whether it encloses data between double quotes or not) is stored into its type indicator array. (2) To know the names of those two arrays, check the keyname you assigned to this file. If it was "cf5", the data array names will be CFS[5][] and the type indicator array will be CFB[5]. The two arrays are declared public so you can access them too if necessary. (3) If you have assigned a number to (i) before calling fm("o"), the method will check the number of data items at each record. If was not found to be equal to (i) at one record, you will get an error message which will show all the data items found on that record. You should repair the record using Notepad before trying to run your class again. (4) Starting after the end of fm("o"), all data access will be from or to the data array. The file will be neglected. So, (j,k) are meaningless to methods fm("r"), fm("w") and fm("c") (5) Identically, as with table files, method fm("o") returns: o = Starting data record number (It returns o=1 always) ol = Total number of records which is the length of the data array. dnb=true if file was found to be empty. Additionally, fll,rcs will be available in the archives (6) Also, identically as with table files, the following items are reset at each mode in addition to the (i,j,k) based GUV's: MODE RESET VARIABLES REASON o OS[] To prepare for a future read/write operation. r None os & OS[] are output values. w OS[],rci,rcs,os To erase values supplied at prior read operation. c OS[],All archives To make sure no old values associated with file are still there. (7) When you call fm("c") to close the file, all data in the data array will overwrite the file content. This means that if there were non data records in the file, they will be lost. Next time you access the file you must assign zeros to (j,k) since the file now contains nothing but the titles record and data records. EXAMPLES on (CSV) FILES: ======================== In order to point the fact that accessing (csv) files requires almost the same code as accessing table files, we're going to duplicate the same examples which we used before to access table files. They are examples 6:10. Examples 7 and 8 cannot be duplicated since search and sort operations are not available for (csv) files at present time. Example 9 will not be duplicated also since purging is unnecessary. (csv) files are purged by default. When you open a file you let method fm("o") knows how many text lines at the top and at the bottom which are not part of the data. The method skips them and reads the rest into an array. When you call method fm("c") to close the file, only the array is written into the file replacing all its original contents. Example 6 in which we have read one record, modified one of its data items then written it back into the file can be duplicated. However in this example, we added a new record to the table file. That part cannot be duplicated at present time. Example 10 in which we read a complete table file added its data together at each of 4 columns and computed the average can be duplicated. So, to start, let us see how we can do what we have done in examples 6 using "test.csv" file and "cf0" keyname in place of "test.txt" file and "tf0" keyname. ====================================================================================================== Example 15: (csv) file duplication of Example 6: Open file "test.csv". Read the 2nd data record and display it. Modify the low price to "25.00" and overwrite that record. Display the resulting file showing the modified record in red. ====================================================================================================== public class a : pcs { public override void init() { j=825;k=425;dm("s"); // Resize Form toa="t"; // Use text screen for text output base.init(); } public override void run() { fns="crb10"; // Use courier font. fs="cf0";fls="test.csv";kb=true;fm("o"); // ** Open as a (csv) File. rci=o+2-1; // Record number of the 2nd record n=rci; // Save this number temporarely. fm("r"); // Read that record (OUT=OS[]) OS[3]="25.00"; // Modify data item #3 (where "low" is) fm("w"); // and write record. fm("c"); // Close file. This writes all data into file fls="test.csv";os.Trim();fm("RL"); // ** Read file as lines into OS[] for (c=0;c< OS.Length;c++) { // Scan all rows. os=OS[c]; // Assign each row to (os) if (c==n) { // If was a modified row: cls="r0";tm(); // Display it in red. cls="S9"; // then restore black color. } else tm(); // Display all other rows in black. } } } ====================================================================================================== REMARKS: ======== (1) The lines which needed to be changed are marked with "**". (2) In the "Table File" example, we modified the 3rd data record. Here we modified the 2nd data record That was because the table file contained an empty record as data record number 1, but (csv) files contain no empty records. ======================================================================================================


====================================================================================================== And here is a duplication of example 10. It needed no change except in the way column averages are displayed. ====================================================================================================== Example 16: (csv) file duplication of Example 10: Read all records of file "test.csv", add all the stock prices in each of the columns "Open", "High" "Low" and "Close" and calculate the average of each column. Display the average amounts at the bottom of their columns. ====================================================================================================== public class a : pcs { // Var's used: t= Total number of records not counting blank ones. // ts=String used to display price averages. double[] TD=new double[4]; // Define an array to store totals of the 4 col's public override void init() { j=825;k=425;dm("s"); // Resize Form toa="t"; // Use text screen for text output base.init(); } public override void run() { fns="crb10"; // Use courier font fs="cf0";fls="test.csv";kb=true;fm("o"); // ** Open (csv) File. rci=o; // Assign returned starting data rec# to (rci) dnb=false;t=0; // Initialize eof flag and record counter. while (!dnb) { // Repeat until eof encountered: fm("r"); // Read a record (returns os, OS[]) os=rcs;om("c"); // Remove spaces from (os) to check record. if (os.Length>0) { // If was not a blank line: t++; // Increment number of records. for (n=1;n<5;n++) { // Scan columns 1:4 os=OS[n];om("td"); // Convert data of each column to double. TD[n-1]+=od; // and add it to each column's total. } } rci++; // Increment record # } // Then repeat loop. fm("c"); // Close File. fls="test.csv";fm("R");tm(); // Read all data and display. //--------------------- calculating and displaying price averages ---------------------- // The wanted averages are for data items number (1,2,3,4). No averages for (0,5) Therefore we // are going to put "" at their places in the display string ts="\"\","; // ** Starting the display string with "", for (n=0;n<4;n++) { // Scan the 4 columns od=TD[n]/t; // Get average price for each column. ib=true;om("fd"); // Convert to formatted string of 2 decimal digits ts+="\""+os+"\","; // ** Add price to (ts) surrounded with (")'s } ts+="\"\""; // End the display string with "". cls="r0";os=ts;tm(); // Display (ts) } } =====================================================================================================