Tuesday, January 9, 2018

JavaScript Report 2


Continuing from the previous JavaScript post, I wanted to do some different user interfaces. 

At first I wanted to do a form with the button that allowed the user to navigate and selected the file to be parsed.  And since I found a method to do a popup I thought I would use a popup to report the results.  This post had html that displayed a form with three label, edit box combinations for the entry of a name, address and phone number plus a button to be clicked to cause the entries to be displayed in a popup.

So I wanted to do something similar by just putting the button of the first solution in the form and then when the file had been parsed, displaying the results in a popup.

However, since the reference to the button used in the first solution was somehow hidden – that is, never directly referenced in the solution with no code for it visible in the html.  As mentioned, this hidden button allowed the user to navigate Windows folders to find the wanted file and select it as the text file to be treated.  So the question was "how to select the file to be parsed?"

So I attempted to just have the user enter the path to the file in an edit box.  But the method to open the file from the first post was using a file passed as the first element in an array structure where the selection was somehow more than just the path to file.  So just supplying the path didn't work to open the file.

Continuing the searches I did find another post by Microsoft with the code to select a file via Windows Explorer and drag and drop it into a drop box.  Capturing the code provided it worked to select a file that way.  And in used the files in an array where each element of the array was part of a structure; that is,
            f = files[i]; // If anything goes awry, the error would occur here.
            output.push('<li><strong>',
                        f.name, '</strong> (',
                        f.type || 'unknown file type',
                        ') - ',
                        f.size, ' bytes, last modified: ',
                        f.lastModifiedDate,
                        '</li>');
            document.getElementById('list').innerHTML = output.join('');
to display data about the selected file.  So I figured that perhaps the filePath.files[0] of the first post that was used to open the file would be of the same structure as the files[i] of the Microsoft post.

Therefore, I set about adding to the Microsoft drag and drop box to open the selected file and having the learntocode.js code of my previous post parse the file and determine the file contents key and value with the maximum number of references.

Before lunch it was working and was time to add the popup to display the results.

However the popup refused to pop up.  Even thou the internet samples did.  Then found out that the window.open was returning null when used in my code.

It took quite a while to get a working application.  I would make a change and the html wouldn't work again and I would have to start over since I couldn't detect what part of my change caused it to stop working.  Therefore, I began to retain working samples and make a copy from which to continue so I could go back to the working sample when changes to the copy ceased to work.

Eventually, via alerts, I found some strange behaviour.  The display() function that did a window.open of the popup was invoked following the return from the parse() function of the learntocode.js file.  The parse() function was getting called twice and hence the display() function.  The first time parse was called the buffer from reading the selected file had a 0 length so parse didn't have anything to work with and would return results of 0,0,0.  It would pass this to display.  Then somehow parse would get invoked again with the actual data (as if there was a hidden callback in the display function).  Then, upon return from parse, display would get entered again (although the alert at its beginning wouldn't occur) but now display would have the actual parse results and they would be displayed twice.

Therefore, I added code to only display the results when entered for the second time.

I don't understand this behaviour.  Especially since JavaScript is supposed to be single threaded from what I find on the internet.  I had thought that the window read of the file was being done in another thread so that the regular thread was continuing while the read thread was taking place.  Thus resulting in a buffer length of 0 in the html thread.  Then when the assumed window read thread finished, the buffer contained the data.  Even if so, I don't understand how the parse function was re-entered with the now filled buffer.

But, incrementing the counter in display() and only calling DispWin.document.write (where DispWin is the result of the window.open) upon the second entry resulted in the expected results.

A second problem is that an attempt to display a title for the popup failed.

The browser window, the same one after the file dropped into the drag and drop box, and the popup with the results follow.





This new solution with a drag and drop box for supplying the file to be parsed and the popup to display the results is as follows.

LearntoCode-popup.html

<!DOCTYPE html>
<html>
  <head>
    <title>Drag &amp; Learn To Code - popup</title>
    <meta http-equiv="X-UA-Compatible" content="IE=10">
    <style>
      #fileDropBox {
        width: 20em;
        line-height: 10em;
        border: 1px dashed gray;
        text-align: center;
        color: gray;
        border-radius: 7px;
    </style>

    <script type="text/javascript" src="learntocode.js"></script>

    <script LANGUAGE="JavaScript" type="text/javascript">
    function display(results) {
      var DispWin = window.open('','NewWin', 'toolbar=no,status=no,width=300,height=200');

      message = "<ul><b>Results from Parse of File</b> \n";
      message += "<li><b>NAME: </b>" + results[0];
      message += "<li><b>KEY: </b>" + results[1];
      message += "<li><b>INSTANCES: </b>" + results[2] + "</ul>";

      /* NOTE: For some reason display is entered a second time although after the window.open
               so without the test below the results are displayed twice.  Also, the call to
               parse is done twice that is followed by the call to this function.  The first
               time parse is called, the data buffer is empty whereas the second time it is
               full.  So the first time this function is entered the results passed in are
               0,0,0 while the second time they are the actual results. */
      if (displayEntryCount==1)
      {
          DispWin.document.title = "Learn to Code Popup"; // this doesn't work
          DispWin.document.write(message);
      } // end if
      displayEntryCount++;
    } // end display

    var reader; /*GLOBAL File Reader object*/
    var displayEntryCount = 0; // count of the number of times display is entered

    function checkFileAPI() {
      if (window.File && window.FileReader && window.FileList && window.Blob) {
        reader = new FileReader();
        return true;
      } else {
        alert('The File APIs are not fully supported by your browser. Fallback required.');
        return false;
      }
    } // end checkFileAPI

    function readText(files) {
      var result = checkFileAPI(); // create the FileReader
      var buffer = ""; /* to contain the file text */

      if (files && files[0]) {          
        reader.onload = function (e) {
          buffer = e.target.result;
          treatFile(buffer);
        } // end reader/onload function
        reader.readAsText(files[0]);
        treatFile(buffer);
      } // end if html5 filelist support
      else if (ActiveXObject && files[0]) { // fallback to IE 6-8 support via ActiveX
        try {  //<<< won't work since need filePath that no longer have >>>
          reader = new ActiveXObject("Scripting.FileSystemObject");
          var file = reader.OpenTextFile(files, 1); // ActiveX File Object
          buffer = file.ReadAll(); // text contents of file
          file.Close(); // close file "input stream"
          treatFile(buffer);
        } catch (e) {
          if (e.number == -2146827859) {
            alert('Unable to access local files due to browser security ' +
                  'settings. ' + 'To overcome this, go to ' +
                  'Tools->Internet Options->Security->Custom Level. ' +
                  'Find the setting for "Initialize and script ActiveX ' +
                  'controls not marked as safe" and change it ' +
                  'to "Enable" or "Prompt"');
          }
        }      
      }
      else { // this is where you could fallback to Java Applet, Flash or similar
        return false;
      }      
      return true;

    } // end readText

    function treatFile(buffer)
    {
      var results = [];
      results = parse(buffer);
      display(results);
    } // end treatFile

    </script>
  </head>
  <body>
    <h1>Drag and Drop File Selection</h1>
    <h3>Learn to Code Example 2</h3>
    <p>Using Windows Explorer (or similar), select one or more files (directories are
       not allowed), and then drag them to the below drop box:</p>
    <div id="fileDropBox">Drop files here.</div>
    <ul id="list"></ul>
    <script>
      var message = [];

      if (!window.FileReader) {
        message = '<p>The ' +
                  '<a href="http://dev.w3.org/2006/webapi/FileAPI/" target="_blank">File API</a>s ' +
                  'are not fully supported by this browser.</p>' +
                  '<p>Upgrade your browser to the latest version.</p>';
     
        document.querySelector('body').innerHTML = message;
      }
      else {
        // Set up the file drag and drop listeners:        
        document.getElementById('fileDropBox').addEventListener('dragover', handleDragOver, false);
        document.getElementById('fileDropBox').addEventListener('drop', handleFileSelection, false);
      }

      function handleDragOver(evt) {
        evt.stopPropagation();  // Do not allow the dragover event to bubble.
        evt.preventDefault(); // Prevent default dragover event behavior.
      } // handleDragOver()

      function handleFileSelection(evt) {
        evt.stopPropagation(); // Do not allow the drop event to bubble.
        evt.preventDefault(); // Prevent default drop event behavior.

        var files = evt.dataTransfer.files; // Grab the list of files dragged to the drop box.

        if (!files) {
          msa.alert("<p>At least one selected file is invalid - do not select any folders.</p><p>Please reselect and try again.</p>");
          return;
        }

        // "files" is a FileList of file objects. List a few file object properties:   
        var output = [];
        for (var i = 0, f; i < files.length; i++) {
          try {
            f = files[i]; // If anything goes awry, the error would occur here.
            output.push('<li><strong>',
                        f.name, '</strong> (',
                        f.type || 'unknown file type',
                        ') - ',
                        f.size, ' bytes, last modified: ',
                        f.lastModifiedDate,
                        '</li>');
            document.getElementById('list').innerHTML = output.join('');
          } // try
          catch (fileError) {
            msa.alert("<p>An unspecified file error occurred.</p><p>Selecting one or more folders will cause a file error.</p>");
            console.log("The following error occurred at i = " + i + ": " + fileError); // Send the error object to the browser's debugger console window, if active.
            return;
          } // catch
        } // for
        readText(files);
       
      } // handleFileSelection()     
    </script>   
    <script src="../utilities.js" type="text/javascript"></script> <!-- Provides the msa.alert() method. -->       
  </body>
</html>

The learntocode.js file is unchanged from the initial solution.

learntocode.js

// Global data

// The following would be the KeyData and KeyTable structures if there were
// something like struct or class in JavaScript.
var keyCount = 0;
var valueKey = [];
var valueCount = [];
var valueValues = [,]; // different values of the key
var valueSums = [,];   // sum of values of particular key

// Data to be retained as the key and its associated value with the maximum
// number of references.
var maxKey = 0;
var maxValue = 0;
var maxSum = 0;

// Saved data as parse each line.
var savedData = [];
savedData[0] = 0;
savedData[1] = 0;
savedData[2] = 0;

// Functions

// Parse data lines of the text file.
function parse(data) {

    var CR = "\r";
    var HT = "\t";
    var LF = "\n";

    /* Parse each line of data in the buffer to obtain the three fields of
     * interest, converting those fields to integers into an array, and
     * then updating a data structure to retain the data for evaluation
     * when the complete buffer has been parsed.
     */

    var nextField = 0;   // index of the beginning of next field
    var startField;      // range of indexes of
    var numFields = 0;   // index into dataFields array
    var dataFields = []; // Integer values of the three fields of interest
    dataFields[0] = 0;
    dataFields[1] = 0;
    dataFields[2] = 0;

    var bufSize = data.length;
    var index = 0;

    for (index = 0; index < bufSize; index++ )
    {
        // Parse the buffer line
        if (data[index] == HT) // beginning of a field
        {   startField = nextField; // save starting index
            nextField = index + 1;  // the next byte will contain part of next field
            if (numFields > 0)
            { //convert and store in dataFields
                dataFields[numFields-1] = toInt(data, startField, index - 1);
            } // end if numFields > 0
            numFields++;
        } // end if HT

        // Do the update when find CR or LF of reach the end of the buffer.
        // The last option since the file has no terminators for the last line.
        if ((data[index] == CR) || (data[index] == LF) || (index == (bufSize-1)))
        {
            if (numFields < 4) // only do the update once for each line
            { //convert and store in dataFields
                dataFields[numFields-1] = toInt(data, nextField, index - 1);
                // Save the data to determine the key, value combination with
                // maximum number of references
                update(dataFields);

                // Finished with the line in the data array, initialize
                // for next line.
                nextField = 0;
                startField;
                numFields = 0;
                dataFields[0] = 0;
                dataFields[1] = 0;
                dataFields[2] = 0;
                if ((data[index] == CR) && (data[index+1] == LF))
                { index++ } // bypass the extra char at end of line
            } // end if
        } // end if

    } // end for loop

    // Report the number of references with a particular Key and Value.
    var results = [];
    results = report();
    return results;

}; // end parse
   
// Convert character array to integer
function toInt(data, iS, iE)
{
    var NINE = "9" //57 // ASCII character for digit 9
    var ZERO = "0" //48 // ASCII character for digit 0

    var index = iE; // loop in reverse
    var digit = 0;
    var m = 1;      // multiplier for shift
    var number = 0; // Numeric result

    while (index >= iS)
    {
        if ((data[index] >= ZERO) && (data[index] <= NINE))
        {
            digit = data[index] - ZERO; // convert ASCII to digit
            number = number + (m * digit);
            m = m * 10;
            index--;
        }
    }

    return number;

} // end toInt

// Update the tables for a particular file line.
function update(data)
{
    // This function first checks if the key is new and, if so, adds it to the
    // keys array.  It then does similar for the values associated with it.
    // Note: data[0] is the key while data[1] and data[2] are the two values
    //       associated with the key.

    // Save data to be updated for use by updateTotals since needed by
    // multiple functions
    savedData[0] = data[0];
    savedData[1] = data[1];
    savedData[2] = data[2];

    // Check whether the key is already in the key table
    var keyIndex = -1;
    for (var k = 0; k < keyCount; k++)
    {
        if (valueKey[k] == data[0])
        {
            keyIndex = k; // key already in the table
            break; // exit loop   
        }
    } // end for

    if (keyIndex < 0) // key not in the table
    { // add the key
        keyIndex = keyCount;
        valueKey[keyIndex] = data[0];
        valueCount[keyIndex] = 0;
        keyCount++;
    } // end if

    // Add the values for the key
    addValues(keyIndex, savedData[1], savedData[2]);

} // end update

// Add the values or increment their sums
function addValues(keyIndex, value1, value2)
{
    // Check whether the first value is already in the table
    var valueIndex = -1;
    for (var v = 0; v < valueCount[keyIndex]; v++)
    {
        if (valueValues[keyIndex,v] == value1)
        { // value already in the table
            valueIndex = v;
            valueSums[keyIndex,v]++; // increment its number of references
            if (value1 == value2) // 2nd value the same
            {
                valueSums[keyIndex,v]++; // increment again
            }
            // check if new max
            updateTotals(savedData[0],value1,valueSums[keyIndex,v]);
            break; // exit loop
        }
    } // end loop
 
    if (valueIndex < 0) // value not yet in table
    { // add value to the table - index points to last value checked
        var index = valueCount[keyIndex];
        valueValues[keyIndex,index] = value1;
        valueSums[keyIndex,index] = 1;
        valueCount[keyIndex]++;
        if (value1 == value2) // dupicated value
        {
            valueSums[keyIndex,index]++; // increment to 2
            // check if new max
            updateTotals(savedData[0],value1,valueSums[keyIndex,index]);
        }
        else
        {
            add2ndValue(keyIndex, value2);
        }
    } // end outer if

} // end addValues

function add2ndValue(keyIndex, value2)
{
    // Check whether the second value is already in the table
    var valueIndex = -1;
    for (var v = 0; v < valueCount[keyIndex]; v++)
    {
        if (valueValues[keyIndex,v] == value2)
        { // value already in the table
            valueIndex = v;
            valueSums[keyIndex,valueIndex]++; // increment number of references
            // check if new max
            updateTotals(savedData[0],value2,valueSums[keyIndex,valueIndex]);
            break; // exit loop
        }
    } // end loop
    if (valueIndex < 0) // value not yet in table
    { // add value to the table
        var index = valueCount[keyIndex];
        valueValues[keyIndex,index] = value2;
        valueSums[keyIndex,index] = 1;
        valueCount[keyIndex]++;
        updateTotals(savedData[0],value2,valueSums[keyIndex,index]);
    }

} // end add2ndValue

function updateTotals(key, value, sum)
{
    if (sum > maxSum)
    {
        maxKey = key;
        maxValue = value;
        maxSum = sum;
    }

} // end updateTotals

// Report the key and value with the most references
function report()
{
    var results = [];
    results[0] = maxKey;
    results[1] = maxValue;
    results[2] = maxSum;
    return results;

} // end report

Wednesday, January 3, 2018

Max Column Sum by Key (part 5)

JavaScript

Since JavaScript is being reported as one of the current popular languages, I thought it time to try the Learn to Code GR application that I previously wrote in Ada, C#, Python, Java, C# again, C, Kotlin, and Pascal using JavaScript.

Getting started I had to use the internet once again as my classroom.  Took most a day to get started since the advice I found didn't work.  Finally I found someone who knew what they were talking about so was able to do a simple demo of html invoking an external function.  Then, a similar simple demo of html invoking an internal function.  The working samples are:

External function – script.index.js
function go(){
  document.write("javascript is working");
}
– script.html
<html>
  <head>
    <script type="text/javascript" src="script.index.js"></script>
  </head>
  <body>
    <input type="button" onclick="go()" value="Display JS"/>
  </body>
</html>
which displayed a browser page with a button with a label of Display JS.  When clicked javascript is working was displayed.

Internal function – script-internal.html
<html>
  <head>
    <script>
      function go(){
        document.write("internal javascript is working");
      }
    </script>
  </head>
  <body>
    <input type="button" onclick="go()" value="Display Internal"/>
  </body>
</html>
with similar results when script-internal.html was run.

Since reading the max-col-sum-by-key.tsv text file is a necessary first step in reworking the Learn to Code GR application I next tried various html files in an attempt to open and read the file.  I found various results with internet searches but was unable to get them to work until I found one via a Bing search for "javascript open named file" that provided an option from stackoverflow of htm15 - How to read text file in JavaScript - Stack Overflow.

This solution worked out-of-the-box where the html is
<!DOCTYPE html>
<html>
  <head>
    <title>Read File (via User Input selection)</title>
    <script type="text/javascript">
    var reader; //GLOBAL File Reader object for demo purpose only

    /**
     * Check for the various File API support.
     */
    function checkFileAPI() {
        if (window.File && window.FileReader && window.FileList && window.Blob) {
            reader = new FileReader();
            return true;
        } else {
            alert('The File APIs are not fully supported by your browser. Fallback required.');
            return false;
        }
    }

    /**
     * read text input
     */
    function readText(filePath) {
        var output = ""; //placeholder for text output
        if(filePath.files && filePath.files[0]) {          
            reader.onload = function (e) {
                output = e.target.result;
                displayContents(output);
            };//end onload()
            reader.readAsText(filePath.files[0]);
        }//end if html5 filelist support
        else if(ActiveXObject && filePath) { //fallback to IE 6-8 support via ActiveX
            try {
                reader = new ActiveXObject("Scripting.FileSystemObject");
                var file = reader.OpenTextFile(filePath, 1); //ActiveX File Object
                output = file.ReadAll(); //text contents of file
                file.Close(); //close file "input stream"
                displayContents(output);
            } catch (e) {
                if (e.number == -2146827859) {
                    alert('Unable to access local files due to browser security settings. ' +
                     'To overcome this, go to Tools->Internet Options->Security->Custom Level. ' +
                     'Find the setting for "Initialize and script ActiveX controls not marked as safe" and change it to "Enable" or "Prompt"');
                }
            }      
        }
        else { //this is where you could fallback to Java Applet, Flash or similar
            return false;
        }      
        return true;
    }  

    /**
     * display content using a basic HTML replacement
     */
    function displayContents(txt) {
        var el = document.getElementById('main');
        el.innerHTML = txt; //display output in DOM
    }  
</script>
</head>
<body onload="checkFileAPI();">
    <div id="container">   
        <input type="file" onchange='readText(this)' />
        <br/>
        <hr/>  
        <h3>Contents of the Text file:</h3>
        <div id="main">
            ...
        </div>
    </div>
</body>
</html>

This html opens a browser window with a Choose File button with "No file chosen" to the right of it and then a dividing line across the screen followed by

Contents of the Text file:

below the line.  When the Choose File button is clicked I was able to select the max-col-sum-by-key.tsv that I had copied to the folder with the various html files.  This resulted in all the records of the file being displayed in the browser window as
0,912_NUM 1000 1 1 0,912_NUM 1000 2 1 0,912_NUM 1000 1 1 0,912_NUM 1000 2 2 0,912_NUM 1000 1 1 0,912_NUM 1000 2 2 0,912_NUM 1000 1 1 0,912_NUM 1000 1 1 0,912_NUM 1000 2 2 0,912_NUM 1000 1 1 0,912_NUM 2000 1 1 0,912_NUM 2000 1 1
etc.

I expect that this will be the final difficult problem standing in the way of producing another version of the Learn to Code GR problem.  (This proved to be true.  I started on 12/29 and finished the morning of Jan 3 with bowl games and NFL reducing the available time.)

After opening and reading the tsv file I moved the parse function to a .js file and did the functions that it calls in the .js file so I could use node to catch some of the errors in advance.  That is, I didn't see a way to do so for the .html file.  Even so, there were errors that running node on the .js file didn't catch.  For instance, references to an undefined variable.

Also, I reverted to how I kept track of the data in the original C# solution since everything I found says that there isn't a class in JavaScript.  Although I modified the update function from the original C# to use multiple functions as I had done in the C solution.

I couldn't find anything to output to the browser window from the .js file so I used the alert function for debugging.

Also, because I couldn't do output from the .js file, I passed the results back to the .html file and displayed the results there. 

This resulted in a browser window of

Before the file was chosen the display had the Choose File button with the horizontal line and No file chosen to the right of the button.  In both cases "Learn to Code GR" is the text shown on the browser tab.

The two files to do the JavaScript version of the solution are as follows.

LearntoCode.html

<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript" src="learntocode.js"></script>

        <title>Learn to Code GR</title>

        <script type="text/javascript">
        var reader; /*GLOBAL File Reader object*/

        /*
         * Code to select, open, and read the text file.
         */

        /* Check for the various File API support. */
        function checkFileAPI() {
            if (window.File && window.FileReader && window.FileList && window.Blob) {
                reader = new FileReader();
                return true;
            } else {
                alert('The File APIs are not fully supported by your browser. Fallback required.');
                return false;
            }
        } // end checkFileAPI


        /* Read text input */
        function readText(filePath) {
            var buffer = ""; /* to contain the file text */
            if(filePath.files && filePath.files[0]) {          
                reader.onload = function (e) {
                    buffer = e.target.result;
                    treatFile(buffer);
                }; // end reader/onload function
                reader.readAsText(filePath.files[0]);
            } // end if html5 filelist support
            else if(ActiveXObject && filePath) { // fallback to IE 6-8 support via ActiveX
                try {
                    reader = new ActiveXObject("Scripting.FileSystemObject");
                    var file = reader.OpenTextFile(filePath, 1); // ActiveX File Object
                    buffer = file.ReadAll(); // text contents of file
                    file.Close(); // close file "input stream"
                    treatFile(buffer);
                } catch (e) {
                    if (e.number == -2146827859) {
                        alert('Unable to access local files due to browser security ' +
                               'settings. ' + 'To overcome this, go to ' +
                               'Tools->Internet Options->Security->Custom Level. ' +
                               'Find the setting for "Initialize and script ActiveX ' +
                               'controls not marked as safe" and change it ' +
                               'to "Enable" or "Prompt"');
                    }
                }      
            }
            else { // this is where you could fallback to Java Applet, Flash or similar
                return false;
            }      
            return true;
        } // end readText

        function display(results) {
            var myDiv = document.getElementById("main");
            var message = "<br><b><u>Results</u></b>";
            message += "<ul><li><b>KEY: </b>" + results[0];
            message += "<li><b>VALUE: </b>" + results[1];
            message += "<li><b>SUM: </b>" + results[2];
            myDiv.innerHTML = message;
        } // end display

        function treatFile(buffer)
        {
            var results = [];
            results = parse(buffer);
            display(results);
        } // end treatFile

        </script>
    </head>

    <body onload="checkFileAPI();">
        <input type="file" onchange='readText(this)' />
        <hr/>
        <div id="main">
        </div>
    </body>

</html>

learntocode.js

// Global data

// The following would be the KeyData and KeyTable structures if there were
// something like struct or class in JavaScript.
var keyCount = 0;
var valueKey = [];
var valueCount = [];
var valueValues = [,]; // different values of the key
var valueSums = [,];   // sum of values of particular key

// Data to be retained as the key and its associated value with the maximum
// number of references.
var maxKey = 0;
var maxValue = 0;
var maxSum = 0;

// Saved data as parse each line.
var savedData = [];
savedData[0] = 0;
savedData[1] = 0;
savedData[2] = 0;

// Functions

// Parse data lines of the text file.
function parse(data) {

    var CR = "\r";
    var HT = "\t";
    var LF = "\n";

    /* Parse each line of data in the buffer to obtain the three fields of
     * interest, converting those fields to integers into an array, and
     * then updating a data structure to retain the data for evaluation
     * when the complete buffer has been parsed.
     */

    var nextField = 0;   // index of the beginning of next field
    var startField;      // range of indexes of
    var numFields = 0;   // index into dataFields array
    var dataFields = []; // Integer values of the three fields of interest
    dataFields[0] = 0;
    dataFields[1] = 0;
    dataFields[2] = 0;

    var bufSize = data.length;
    var index = 0;

    for (index = 0; index < bufSize; index++ )
    {
        // Parse the buffer line
        if (data[index] == HT) // beginning of a field
        {   startField = nextField; // save starting index
            nextField = index + 1;  // the next byte will contain part of next field
            if (numFields > 0)
            { //convert and store in dataFields
                dataFields[numFields-1] = toInt(data, startField, index - 1);
            } // end if numFields > 0
            numFields++;
        } // end if HT

        // Do the update when find CR or LF of reach the end of the buffer.
        // The last option since the file has no terminators for the last line.
        if ((data[index] == CR) || (data[index] == LF) || (index == (bufSize-1)))
        {
            if (numFields < 4) // only do the update once for each line
            { //convert and store in dataFields
                dataFields[numFields-1] = toInt(data, nextField, index - 1);
                // Save the data to determine the key, value combination with
                // maximum number of references
                update(dataFields);

                // Finished with the line in the data array, initialize
                // for next line.
                nextField = 0;
                startField;
                numFields = 0;
                dataFields[0] = 0;
                dataFields[1] = 0;
                dataFields[2] = 0;
                if ((data[index] == CR) && (data[index+1] == LF))
                { index++ } // bypass the extra char at end of line
            } // end if
        } // end if

    } // end for loop

    // Report the number of references with a particular Key and Value.
    var results = [];
    results = report();
    return results;

}; // end parse
   
// Convert character array to integer
function toInt(data, iS, iE)
{
    var NINE = "9" //57 // ASCII character for digit 9
    var ZERO = "0" //48 // ASCII character for digit 0

    var index = iE; // loop in reverse
    var digit = 0;
    var m = 1;      // multiplier for shift
    var number = 0; // Numeric result

    while (index >= iS)
    {
        if ((data[index] >= ZERO) && (data[index] <= NINE))
        {
            digit = data[index] - ZERO; // convert ASCII to digit
            number = number + (m * digit);
            m = m * 10;
            index--;
        }
    }

    return number;

} // end toInt

// Update the tables for a particular file line.
function update(data)
{
    // This function first checks if the key is new and, if so, adds it to the
    // keys array.  It then does similar for the values associated with it.
    // Note: data[0] is the key while data[1] and data[2] are the two values
    //       associated with the key.

    // Save data to be updated for use by updateTotals since needed by
    // multiple functions
    savedData[0] = data[0];
    savedData[1] = data[1];
    savedData[2] = data[2];

    // Check whether the key is already in the key table
    var keyIndex = -1;
    for (var k = 0; k < keyCount; k++)
    {
        if (valueKey[k] == data[0])
        {
            keyIndex = k; // key already in the table
            break; // exit loop      
        }
    } // end for

    if (keyIndex < 0) // key not in the table
    { // add the key
        keyIndex = keyCount;
        valueKey[keyIndex] = data[0];
        valueCount[keyIndex] = 0;
        keyCount++;
    } // end if

    // Add the values for the key
    addValues(keyIndex, savedData[1], savedData[2]);

} // end update

// Add the values or increment their sums
function addValues(keyIndex, value1, value2)
{
    // Check whether the first value is already in the table
    var valueIndex = -1;
    for (var v = 0; v < valueCount[keyIndex]; v++)
    {
        if (valueValues[keyIndex,v] == value1)
        { // value already in the table
            valueIndex = v;
            valueSums[keyIndex,v]++; // increment its number of references
            if (value1 == value2) // 2nd value the same
            {
                valueSums[keyIndex,v]++; // increment again
            }
            // check if new max
            updateTotals(savedData[0],value1,valueSums[keyIndex,v]);
            break; // exit loop
        }
    } // end loop
 
    if (valueIndex < 0) // value not yet in table
    { // add value to the table - index points to last value checked
        var index = valueCount[keyIndex];
        valueValues[keyIndex,index] = value1;
        valueSums[keyIndex,index] = 1;
        valueCount[keyIndex]++;
        if (value1 == value2) // dupicated value
        {
            valueSums[keyIndex,index]++; // increment to 2
            // check if new max
            updateTotals(savedData[0],value1,valueSums[keyIndex,index]);
        }
        else
        {
            add2ndValue(keyIndex, value2);
        }
    } // end outer if

} // end addValues

function add2ndValue(keyIndex, value2)
{
    // Check whether the second value is already in the table
    var valueIndex = -1;
    for (var v = 0; v < valueCount[keyIndex]; v++)
    {
        if (valueValues[keyIndex,v] == value2)
        { // value already in the table
            valueIndex = v;
            valueSums[keyIndex,valueIndex]++; // increment number of references
            // check if new max
            updateTotals(savedData[0],value2,valueSums[keyIndex,valueIndex]);
            break; // exit loop
        }
    } // end loop
    if (valueIndex < 0) // value not yet in table
    { // add value to the table
        var index = valueCount[keyIndex];
        valueValues[keyIndex,index] = value2;
        valueSums[keyIndex,index] = 1;
        valueCount[keyIndex]++;
        updateTotals(savedData[0],value2,valueSums[keyIndex,index]);
    }

} // end add2ndValue

function updateTotals(key, value, sum)
{
    if (sum > maxSum)
    {
        maxKey = key;
        maxValue = value;
        maxSum = sum;
    }

} // end updateTotals

// Report the key and value with the most references
function report()
{
    var results = [];
    results[0] = maxKey;
    results[1] = maxValue;
    results[2] = maxSum;
    return results;
} // end report