<commit_before>---
---
$(function() {
    var urlFile = getUrlParameter('f');
    var userFile = getUrlParameter('h');
    if(urlFile) {
        gatherInputXML(urlFile);
    } else if(userFile) {
        if(sessionStorage.getItem(userFile+'_name')) {
            gatherInputXML();
        } else {
            displayError('The file you requested is no longer available. Please select or upload a new file.');
        }
    } else {
        displayError('No file was requested.');
    }
});

//======================================
//
// PAGE SETUP
//
//======================================

// Attempt to load the user-provided XML e-file
function gatherInputXML(inputFile) {
    var templateFile = '{{ site.github.url}}/form_template.xml';
    var templateStr = sessionStorage.getItem('template');
    var inputStr = sessionStorage.getItem(inputStorageId());

    var formsToLoad = [];
    var formKeys = [];
    if(!templateStr) {
        formsToLoad.push(loadXML(templateFile));
        formKeys.push('template');
    }
    if(!inputStr) {
        formsToLoad.push(loadXML(inputFile));
        formKeys.push(inputStorageId());
    }

    // If our files are already cached, skip accessing them again
    if(formsToLoad.length === 0) {
        addXMLToPage(new DOMParser().parseFromString(inputStr, 'text/xml'));
    } else {
        Promise.all(formsToLoad
        ).then(function(responses) {
            var inputDom;
            responses.forEach(function(response, i) {
                cacheForms(response, formKeys[i]);
                if(formKeys[i] !== 'template') {
                    inputDom = response;
                }
            });

            inputDom = inputDom || new DOMParser().parseFromString(inputStr, 'text/xml');
            return Promise.resolve(inputDom).then(addXMLToPage);
        }).catch(function(error) {
            console.log(error);
            displayError('There was a problem accessing ' + inputFilename());
        });
    }
}

// Store the requested XML documents in SessionStorage. This
// allows us to reuse resources on page reloads (such as the user
// going back and forth between forms/schedules).
function cacheForms(xmlDom, key) {
    var ser = new XMLSerializer();
    sessionStorage.setItem(key, ser.serializeToString(xmlDom));
}

// Generic function to display to the user there was an error
function displayError(message) {
    $('#input-filename').text(message);
}

// Confirm to the user their document has been loaded and present
// the user with forms they can select to view
function addXMLToPage(inputDom) {
    $('#input-filename').text('Loaded: ' + inputFilename());

    var formProps = getNameAndTaxYear(inputDom);
    if(formProps) {
        $('#filer-name').append(formProps.name + ' &mdash; ' + formProps.year);
    }

    var forms = getListOfForms(inputDom);
    forms.forEach(function(formName) {
        $('#forms-list').append(
            $('<li>').append(
                $('<a>').attr({href: '#'+formName, id: formName}).append(
                    $('<span>').append(getDisplayName(formName, true)),
                    $('<span>').append(getDisplayName(formName))
                )
            )
        );
    });
    $('#forms-list a').click(displayForm);
}

// Given an XML file representing an IRS e-file, return the name of
// the file and the tax year.
function getNameAndTaxYear(inputDom) {
    // Because this is happening during initial page load, inputDom
    // may be the original value of the input file -- which means
    // namespaces may not be removed. We cannot rely on XPath to get
    // our values.
    // We also shouldn't break page load if these values are not
    // found. The transformation can still occur.
    var filer = inputDom.getElementsByTagName('Filer')[0];
    var name = filer ? filer.getElementsByTagName('BusinessNameLine1Txt')[0] : null;
    var root = inputDom.getElementsByTagName('Return')[0]
    var year = root ? root.getAttribute('returnVersion') : null;
    return name && year ? { 'name': name.textContent, 'year': year.substring(0,4) } : null;
}

// Given an XML file representing an IRS e-file, return an array
// containing the names of each contained form and schedule.
function getListOfForms(inputDom) {
    var returnData = inputDom.getElementsByTagName('ReturnData')[0];
    var children = returnData ? Array.prototype.slice.call(returnData.children) : [];
    return children.map(function(child) {
        return child.nodeName;
    });
}

// Return a version of the formName that should be displayed to the
// user.
function getDisplayName(formName, short) {
    if(short) {
        if(formName.indexOf('Schedule') === -1) {
            if(formName.indexOf('990') === -1) {
                return $('<i>').attr({'class': 'fa fa-file-text-o', 'aria-hidden': true, 'title': 'Form without Schedule letter'});
            } else {
                return '990';
            }
        } else {
            return formName.charAt(formName.length-1);
        }
    } else {
        var name = formName.replace('IRS', '');
        return name.split("Schedule").join(' Schedule ');
    }
}

// A Promise that requests and returns a parsed XML DOM
function loadXML(url) {
    return new Promise (function(resolve, reject) {
        if(!url) {
            reject(Error('No file was requested.'));
            return;
        }

        var request = new XMLHttpRequest();
        request.open('GET', url);
        request.responseType = 'document';
        request.onload = function() {
            if(request.status === 200) {
                resolve(request.responseXML);
            } else {
                reject(Error('XML didn\'t load successfully; error code: ' + request.status + ' (' + request.statusText + ')'));<commit_msg>fix String operations for creating display name<commit_after>---
---
$(function() {
    var urlFile = getUrlParameter('f');
    var userFile = getUrlParameter('h');
    if(urlFile) {
        gatherInputXML(urlFile);
    } else if(userFile) {
        if(sessionStorage.getItem(userFile+'_name')) {
            gatherInputXML();
        } else {
            displayError('The file you requested is no longer available. Please select or upload a new file.');
        }
    } else {
        displayError('No file was requested.');
    }
});

//======================================
//
// PAGE SETUP
//
//======================================

// Attempt to load the user-provided XML e-file
function gatherInputXML(inputFile) {
    var templateFile = '{{ site.github.url}}/form_template.xml';
    var templateStr = sessionStorage.getItem('template');
    var inputStr = sessionStorage.getItem(inputStorageId());

    var formsToLoad = [];
    var formKeys = [];
    if(!templateStr) {
        formsToLoad.push(loadXML(templateFile));
        formKeys.push('template');
    }
    if(!inputStr) {
        formsToLoad.push(loadXML(inputFile));
        formKeys.push(inputStorageId());
    }

    // If our files are already cached, skip accessing them again
    if(formsToLoad.length === 0) {
        addXMLToPage(new DOMParser().parseFromString(inputStr, 'text/xml'));
    } else {
        Promise.all(formsToLoad
        ).then(function(responses) {
            var inputDom;
            responses.forEach(function(response, i) {
                cacheForms(response, formKeys[i]);
                if(formKeys[i] !== 'template') {
                    inputDom = response;
                }
            });

            inputDom = inputDom || new DOMParser().parseFromString(inputStr, 'text/xml');
            return Promise.resolve(inputDom).then(addXMLToPage);
        }).catch(function(error) {
            console.log(error);
            displayError('There was a problem accessing ' + inputFilename());
        });
    }
}

// Store the requested XML documents in SessionStorage. This
// allows us to reuse resources on page reloads (such as the user
// going back and forth between forms/schedules).
function cacheForms(xmlDom, key) {
    var ser = new XMLSerializer();
    sessionStorage.setItem(key, ser.serializeToString(xmlDom));
}

// Generic function to display to the user there was an error
function displayError(message) {
    $('#input-filename').text(message);
}

// Confirm to the user their document has been loaded and present
// the user with forms they can select to view
function addXMLToPage(inputDom) {
    $('#input-filename').text('Loaded: ' + inputFilename());

    var formProps = getNameAndTaxYear(inputDom);
    if(formProps) {
        $('#filer-name').append(formProps.name + ' &mdash; ' + formProps.year);
    }

    var forms = getListOfForms(inputDom);
    forms.forEach(function(formName) {
        $('#forms-list').append(
            $('<li>').append(
                $('<a>').attr({href: '#'+formName, id: formName}).append(
                    $('<span>').append(getDisplayName(formName, true)),
                    $('<span>').append(getDisplayName(formName))
                )
            )
        );
    });
    $('#forms-list a').click(displayForm);
}

// Given an XML file representing an IRS e-file, return the name of
// the file and the tax year.
function getNameAndTaxYear(inputDom) {
    // Because this is happening during initial page load, inputDom
    // may be the original value of the input file -- which means
    // namespaces may not be removed. We cannot rely on XPath to get
    // our values.
    // We also shouldn't break page load if these values are not
    // found. The transformation can still occur.
    var filer = inputDom.getElementsByTagName('Filer')[0];
    var name = filer ? filer.getElementsByTagName('BusinessNameLine1Txt')[0] : null;
    var root = inputDom.getElementsByTagName('Return')[0]
    var year = root ? root.getAttribute('returnVersion') : null;
    return name && year ? { 'name': name.textContent, 'year': year.substring(0,4) } : null;
}

// Given an XML file representing an IRS e-file, return an array
// containing the names of each contained form and schedule.
function getListOfForms(inputDom) {
    var returnData = inputDom.getElementsByTagName('ReturnData')[0];
    var children = returnData ? Array.prototype.slice.call(returnData.children) : [];
    return children.map(function(child) {
        return child.nodeName;
    });
}

// Return a version of the formName that should be displayed to the
// user.
function getDisplayName(formName, short) {
    // This regular expression checks if we are looking at a 990 form
    // (990, 990EZ, 990PF). We specifically do not want to match
    // anything where '990' is surrounded by other digits, such as
    // 8990 or 9902. Additionally, we don't want to match a 990
    // Schedule.
    // Format is influenced by https://stackoverflow.com/a/7376612
    var isPrimary990Form = /^(?:(?!\d990).)*990(?!\d)(?!Schedule)/;

    if(short) {
        if(formName.match(isPrimary990Form)) {
            return '990';
        } else {
            if(formName.match(/Schedule[A-Z]$/)) {
                return formName.charAt(formName.length-1);
            } else {
                return $('<i>').attr({'class': 'fa fa-file-text-o', 'aria-hidden': true, 'title': 'Form without Schedule letter'});
            }
        }
    } else {
        // If it's a primary form, just put a space after 990. Else,
        // put a space between each PascalCase word.
        var name = formName.replace(/^IRS/, '');
        if(name.match(isPrimary990Form)) {
            return name.replace(/(990)/, '$1 ');
        }
        return name.replace(/([A-Z])/g, ' $1');
    }
}

// A Promise that requests and returns a parsed XML DOM
function loadXML(url) {
    return new Promise (function(resolve, reject) {
        if(!url) {
            reject(Error('No file was requested.'));
            return;
        }

        var request = new XMLHttpRequest();
        request.open('GET', url);
        request.responseType = 'document';
        request.onload = function() {
            if(request.status === 200) {
                resolve(request.responseXML);
            } else {
                reject(Error('XML didn\'t load successfully; error code: ' + request.status + ' (' + request.statusText + ')'));
{'fraction_non_alphanumeric': 0.14488240596574614, 'fraction_numerical': 0.007457182660001639, 'mean_word_length': 2.5632116788321166, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 13, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 10}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '179020', 'n_tokens_mistral': 3470, 'n_tokens_neox': 3366, 'n_words': 923}