Jump to content

User:Magicpiano/NRBot/NRISOnly.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/* jshint maxerr: 3000 , esversion: 6 */
var wikitext = 'error';
var NRISOnlyStructure=[]; // NRISOnlyStructure[table][row]["Titles"][item].StatName to get info
var TotalToQuery=0;
var TotalQueried=0;
var BotName= "NationalRegisterBot";
var BotUser = "User:"+BotName;
var OutputBase = BotUser+"/AllNRHPPages";
var NRISOnlyFile = BotUser+"/NRISOnly";
var SubstubsFile = BotUser+"/Substubs";
var StatusBase = BotUser;
var ErrorCount=0;
var WarningCount=[["",0]]; // 0=status, 1=count
var InitialTime=0;
var ProgressDivTimer=0;// timer for updating ProgressDiv
var DefaultQueryPause=1; // number of milliseconds to wait between each API query; increased by code if rate limit reached

// Users authorized to run this script
var AuthorizedUsers = [BotName, "Dudemanfellabra", "Magicpiano", "TheCatalyst31"];
var CurrentMaintainer = "Magicpiano"; // User who is currently maintaining the script
var CurrentOperator = ""; // User who is running this instance (will be set from dropdown list)
var CurrentOperatorEditSummary = ""; // "(operated by $CurrentOperator )"

function CheckPermission() {
	var username = mw.user.getName();
	for (var i=0; i< AuthorizedUsers.length; i++) {
		if (username == AuthorizedUsers[i]) {
	        NRISOnlyButtons();
			return;					
		}
	}
    alert("You are not authorized to run the NRISOnly script. Contact "+CurrentMaintainer+" for permission if you think you should have it.");
}

function NRISOnlyButtons() {
    var pageName=mw.config.get('wgPageName');
    
    //look to see if button is already here
    var button = document.getElementById('nrisonlybutton');
    var needsButton = false;
    if (button === null) {
    	// no such element
    	needsButton = true;
    } else if (!button.hasAttributes || !button.hasAttribute("class")) {
    	needsButton = true;
    } else if (button.getAttribute("id") !== "nrisonlybutton") {
    	needsButton = true;
    }
    
    if (!needsButton) return;
    
    button=document.createElement("input");
    button.setAttribute("type", "button");
    button.setAttribute("id", "nrisonlybutton");
    button.id = 'nrisonlybutton';

    var operatorfield=document.createElement("SELECT");
    operatorfield.setAttribute("id","nrisonlyoperator");
    operatorfield.setAttribute("type","select");
	var nochoice=document.createElement("OPTION");
	nochoice.setAttribute("type","option");
	nochoice.text="Please select your username";
	operatorfield.add(nochoice);
	
	var username = mw.user.getName();
	var initial_selection = 0; //default to "Please select"
	var count=0;
	for (var i=1; i< AuthorizedUsers.length; i++) {
		if (AuthorizedUsers[i] != BotName) {
			count++;
			var opchoice=document.createElement("OPTION");
			opchoice.setAttribute("type","option");
			opchoice.label=AuthorizedUsers[i];
			opchoice.text=AuthorizedUsers[i];
			if (username != BotName) {
				opchoice.disabled = (username != AuthorizedUsers[i]);
				if (!opchoice.disabled) {
					initial_selection = count; // forcing to current user
				}
			} else {
				opchoice.disabled=false;
			}
			operatorfield.add(opchoice);
		}
	}
	operatorfield.selectedIndex=initial_selection;
	
    var content=document.getElementById('mw-content-text');

    if (location.href.indexOf('action')==-1) {
        if (pageName=="Wikipedia:WikiProject_National_Register_of_Historic_Places/Progress") {
            button.setAttribute("value", "Update list for "+BotName);
            button.setAttribute("onclick", "NRISOnlyClicked('update')");
        } else if (pageName==OutputBase+"/Duplications") {
            button.setAttribute("value", "Gather duplicate stats");
            button.setAttribute("onclick", "NRISOnlyClicked('duplicate')");
        } else if (pageName==NRISOnlyFile) {
            button.setAttribute("value", "Tag these articles");
            button.setAttribute("onclick", "NRISOnlyClicked('tag')");
        } else {
            return;
        }
        content.parentNode.insertBefore(button, content);
        content.parentNode.insertBefore(operatorfield, content);
    }
}

function NRISOnlyClicked(action) { // after button is clicked, disable it and perform action
   var operatorfield = document.getElementById('nrisonlyoperator');
   
   if (operatorfield.selectedIndex===0) {
	   alert("Please select your username");
	   return;
   }
   CurrentOperator = operatorfield.value;
   CurrentOperatorEditSummary = " (operated by [[User:"+CurrentOperator+"|"+CurrentOperator+"]])";

    var nrisonlybutton = document.getElementById('nrisonlybutton');
    nrisonlybutton.disabled = true;
    var ProgressDiv = document.createElement("div");
    ProgressDiv.setAttribute("id", "ProgressDiv");
    ProgressDiv.setAttribute("style", "width:600px; border:1px solid black; padding:5px; background:#ffffff");
    nrisonlybutton.parentNode.insertBefore(ProgressDiv, nrisonlybutton);

    if (action=="update") {
        ProgressDiv.innerHTML = "Initializing...";
        getNRISOnlyProgressPageWikitext(mw.config.get('wgPageName')); // after wikitext fetched, SetupNRISOnlyTables() is called
    } else if (action=="duplicate") {
        CheckDuplicates();
    } else if (action=="tag") {
        TagNRISOnly();
    }
}

// create array of table structure to be populated later
function SetupNRISOnlyTables() {
    var table=document.getElementsByClassName('wikitable sortable');

    // extract list names from Progress page
    for (var i=1; i<table.length; i++) { // skip national table
        var tr=table[i].getElementsByTagName("tr");
        NRISOnlyStructure[i-1]=[];
        var skipOffset=1; // number of duplicate/skippable rows encountered so far
        for (var j=1; j<tr.length-2; j++) { // skip title row, statewide duplicates, and totals row
            var td=tr[j].getElementsByTagName("td"); // fill in existing data in case error
            var link=td[1].getElementsByTagName("a");
            if (link.length!==0 && link[0].href.search("#")==-1) {
                NRISOnlyStructure[i-1][j-skipOffset]={};
                NRISOnlyStructure[i-1][j-skipOffset].Titles=[]; // Placeholder for article names

                link=decodeURI(link[0].href).split("/");
                link=link[link.length-1].replace(/_/g," ");
                NRISOnlyStructure[i-1][j-skipOffset].Link=link;

                NRISOnlyStructure[i-1][j-skipOffset].ID=td[0].innerHTML.substr(0,5); // for finding empty counties

                NRISOnlyStructure[i-1][j-skipOffset].CatsQueried=0; // for querying later
                NRISOnlyStructure[i-1][j-skipOffset].TotalToCheck=0;
                NRISOnlyStructure[i-1][j-skipOffset].TotalChecked=0;
            } else { // must be a duplicate row
                skipOffset++;
            }
        }
    }
    for (i=0; i<NRISOnlyStructure.length; i++) { // count total number of lists to check
        TotalToQuery+=NRISOnlyStructure[i].length;
    }

    var ProgressDiv=document.getElementById("ProgressDiv");
    ProgressDiv.innerHTML+=" Done!<br>";

    var ProgressSpan=document.createElement("span");
    ProgressSpan.setAttribute("id", "ProgressSpan");
    ProgressDiv.appendChild(ProgressSpan);
    ProgressSpan.innerHTML = "Querying articles in each list... 0 (0%) of "+TotalToQuery+" lists complete.";

    var TimeSpan=document.createElement("span");
    TimeSpan.setAttribute("id", "TimeSpan");
    ProgressDiv.appendChild(TimeSpan);
    TimeSpan.innerHTML = "";

    var EditSpan=document.createElement("span");
    EditSpan.setAttribute("id", "EditSpan");
    ProgressDiv.appendChild(EditSpan);
    EditSpan.innerHTML = "";

    InitialTime=new Date(); // record starting time
    UpdateNRISOnlyProgressDiv();
    LoadNRISOnlyList(0,0); // begin querying first page
}

// load next list to query
function LoadNRISOnlyList(currentTable,currentRow) {
    // check if we need to go to the next table
    if (currentRow>NRISOnlyStructure[currentTable].length-1) {
        currentRow=0;
        currentTable++;
    }
    // check if there are no more tables
    if (currentTable>NRISOnlyStructure.length-1) return;

    setTimeout(function(){ // short delay to prevent API overload
        getNRISOnlyListWikitext(currentTable,currentRow);
        LoadNRISOnlyList(currentTable,currentRow+1);
    }, DefaultQueryPause);
    return;
}

function NRISOnlyWikitextFetched(ajaxResponse,status,currentTable,currentRow) {
    if (status!="success") {
        NewNRISOnlyWarning("Wikitext "+ajaxResponse.errorThrown);
        setTimeout(function(){ // try again after delay if rate limit reached
            getNRISOnlyListWikitext(currentTable,currentRow);
        }, 250);
        return;
    }
    // won't get here unless successful
    var responseText=JSON.parse(ajaxResponse.responseText);
    var pagetext=responseText.query.pages[responseText.query.pageids[0]].revisions[0]["*"];
    var regex;
    var StartIndex;
    var tabletext;
    if (responseText.query.redirects) { // if redirect, find section
        var SectionName="Undefined";
        for (var r in responseText.query.redirects) {
            if (typeof responseText.query.redirects[r].tofragment!="undefined") SectionName=responseText.query.redirects[r].tofragment.replace(/.27/g,"'");
        }

        regex = new RegExp("=[ ]*(\\[\\[(.*?\\|)?[ ]*)?"+SectionName+"([ ]*\\]\\])?[ ]*=", "g");
        var sectionheader=pagetext.match(regex);
        if (sectionheader === null || sectionheader === undefined) { // if no section found, check if one of known empty counties
        	   // last checked: 2023-04-03
	            var EmptyCounties=["01061", // Geneva County AL
	            	"02270", // Kusilvak Census Area AK
	            	"08014", // Broomfield County CO
	            	"12067", // Lafayette County FL
	            	"20081", // Haskell County KS
	            	"20175", // Seward County KS
	            	"20187", // Stanton County KS
	            	"20189", // Stevens County KS
	            	"26051", // Gladwin County MI
	            	"26079", // Kalkaska County MI
	            	"26119", // Montmorency County MI
	            	"26129", // Ogemaw County MI
	            	"26133", // Osceola County MI
	            	"31009", // Blaine County NE
	            	"31113", // Logan County NE
	            	"31117", // McPherson County NE
	            	"38085", // Sioux County ND
	            	"48017", // Bailey County TX
	            	"48023", // Baylor County TX
	            	"48033", // Borden County TX
	            	"48069", // Castro County TX
	            	"48079", // Cochran County TX
	            	"48103", // Crane County TX
	            	"48107", // Crosby County TX
	            	"48119", // Delta County TX
	            	"48131", // Duval County TX
	            	"48155", // Foard County TX
	            	"48165", // Gaines County TX
	            	"48207", // Haskell County TX
	            	"48219", // Hockley County TX
	            	"48247", // Jim Hogg County TX
	            	"48269", // King County TX
	            	"48279", // Lamb County TX
	            	"48341", // Moore County TX
	            	"48389", // Reeves County TX
	            	"48415", // Scurry County TX
	            	"48421", // Sherman County TX
	            	"48433", // Stonewall County TX
	            	"48437", // Swisher County TX
	            	"48445", // Terry County TX
	            	"48461", // Upton County TX
	            	"48475", // Ward County TX
	            	"48501", // Yoakum County TX
	            	"51735"  // Poquoson VA
	            ];
        
            var ID = NRISOnlyStructure[currentTable][currentRow].ID;
            var errorcode = 0;
            for (var k=0; k<EmptyCounties.length; k++) {
                if (ID==EmptyCounties[k]) {errorcode=-1}
            }
            if (errorcode!==0) { // must be an empty county
                TotalQueried++;
                if (TotalQueried==TotalToQuery) DoneQueryingNRIS();
                return;
            }
            // if we're here, must have been a redirect with no section, and not a known empty county
            sectionheader=pagetext.match(/{{NRHP header/g); // then look for tables without a section
            if (sectionheader===null||sectionheader.length>1) { // if still can't find a table or find multiple tables, fatal error
                NRISOnlyFatalError(0,currentTable,currentRow);
                return;
            }
        }
        StartIndex=pagetext.indexOf(sectionheader[0]);
        var sectiontext=pagetext.substr(StartIndex,pagetext.indexOf("\n==",StartIndex)-StartIndex); // only look at relevant section

        StartIndex=sectiontext.indexOf("{{NRHP header");
        if (StartIndex==-1) {
            if (sectiontext.indexOf("{{NRHP row")!=-1) {
                NRISOnlyFatalError(2,currentTable,currentRow); // incorrectly formatted table
                return;
            } else { // must be an empty county
                TotalQueried++;
                if (TotalQueried==TotalToQuery) DoneQueryingNRIS();
                return;
            }
        }
        tabletext=sectiontext.substr(StartIndex,sectiontext.indexOf("\n|}",StartIndex)-StartIndex);
    } else { // if not a redirect, default to first table on page
        StartIndex=pagetext.indexOf("{{NRHP header");
        if (StartIndex==-1) {
            NRISOnlyFatalError(1,currentTable,currentRow); // no list found
            return;
        }
        tabletext=pagetext.substr(StartIndex,pagetext.indexOf("\n|}",StartIndex)-StartIndex);
    }

    // now that tabletext has only relevant table, extract rows
    var Rows=[];
    var str = "{{";
    var start=0;
    var commentstart=0;
    while (true) {
        commentstart=tabletext.indexOf("<!--",start);
        start=tabletext.indexOf(str,start);
        if (start==-1) break;
        while (commentstart<start&&commentstart!=-1) { // skip any commented out rows
            start=tabletext.indexOf("-->",commentstart);
            commentstart=tabletext.indexOf("<!--",start);
            start=tabletext.indexOf(str,start);
        }
        if (start==-1) break;
        var open=1;
        var index=start+str.length;
        while (open!==0 && index<tabletext.length) { // make sure to find correct matching close brackets for row template
            if (tabletext.substr(index,2)=="}}") {
                open--;
                index++;
            } else if (tabletext.substr(index,2)=="{{") {
                open++;
                index++;
            }
            index++;
        }
        var template=tabletext.substr(start,index-start);
        regex = new RegExp("{{[\\s]*NRHP row(\\s)*\\|", "g");
        if (template.match(regex)!==null) Rows.push(template); // make sure it's the row template and not some other one
        start++;
    }
    for (i=0; i<Rows.length; i++) { // get rid of false positives inside nowiki or pre tags
        regex=new RegExp("<[ ]*?(nowiki|pre)[ ]*?>((?!<[ ]*?/[ ]*?(nowiki|pre)[ ]*?>)(.|\\n))*?"+Rows[i].replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&")+"(.|\\n)*?<[ ]*?/[ ]*?(nowiki|pre)[ ]*?>", "g");
        if (tabletext.match(regex)!==null) {Rows.splice(i,1); i--;}
    }

    for (var i=0; i<Rows.length; i++) { // extract titles for querying
        var ThisRow=Rows[i];

        var article=ThisRow.match(/\|[ ]*?article[ ]*?=[ ]*?.*?[\n|\|]/g);
        var blank=ThisRow.match(/\|[ ]*?article[ ]*?=[ ]*?[\n|\|]/g) ;                              // default to name param if article
        if (article===null||blank!==null) article=ThisRow.match(/\|[ ]*?name[ ]*?=[ ]*?.*?[\n|\|]/g); // blank or missing
        // strip param name, final line break
        article=article[0].replace(/\|[ ]*?(article|name)[ ]*?=[ ]*?/g,"").replace(/[\n|\|]/g,"").replace(/<\!\-\-(.|[\r\n])*?\-\-\>/g,"").trim();
        article=decodeURIComponent(article.split("#")[0].trim());     // corrections for weird titles

        var refnum=ThisRow.match(/[0-9]{8,9}/g); // also extract refnums
        if (refnum!==null) {
            if (refnum[0]=="98000562"||refnum[0]=="98000563") refnum[0]="98000562,98000563";   // hard-code troublesome
            if (refnum[0]=="01000363"||refnum[0]=="01000364") refnum[0]="01000363,01000364";   // multistate duplicates
            if (refnum[0]=="02000529"||refnum[0]=="02000530") refnum[0]="02000529,02000530";   // ...
            refnum=refnum[0];
        } else {
            refnum="missing";
        }

        var temp=NRISOnlyStructure[currentTable][currentRow].Titles;
        temp[temp.length]={"name":article, "refnum":refnum, "exists":true,
                           "isDab":false, "isTaggedNRISOnly":false, "isNRISOnly":false, "proseSize":0, "numberRefs":0};
        NRISOnlyStructure[currentTable][currentRow].Titles=temp;
    }

    StartIndex=0;
    LoadNextNRISOnlyListQuery(StartIndex,currentTable,currentRow);
    return;
}

// ready next batch of articles to query
function LoadNextNRISOnlyListQuery(StartIndex,currentTable,currentRow) {
    if (StartIndex==NRISOnlyStructure[currentTable][currentRow].Titles.length) { // all queries begun for this list
        return;
    }
    // must have some more rows to query
    var TempTitles;
    if (NRISOnlyStructure[currentTable][currentRow].Titles.length-StartIndex>50) {
        TempTitles=NRISOnlyStructure[currentTable][currentRow].Titles.slice(StartIndex,StartIndex+50);
    } else {
        TempTitles=NRISOnlyStructure[currentTable][currentRow].Titles.slice(StartIndex);
    }

    for (var i=0; i<TempTitles.length; i++) { // only extract article names
        TempTitles[i]=TempTitles[i].name;
    }

    StartIndex+=TempTitles.length;
    setTimeout(function(){ // short delay to prevent API overload
        QueryNRISOnlyCats(TempTitles,StartIndex,currentTable,currentRow);
        LoadNextNRISOnlyListQuery(StartIndex,currentTable,currentRow);
    }, DefaultQueryPause);
    return;
}

// query next batch of articles
function QueryNRISOnlyCats(TempTitles,StartIndex,currentTable,currentRow) {
    var TitleList=TempTitles.join("|");
    $.ajax({
        dataType: "json",
        url: mw.util.wikiScript('api'),
        data: {
            format: 'json',
            action: 'query',
            prop: 'categories',
            clcategories: 'Category:All disambiguation pages|Category:All articles sourced only to NRIS',
            cllimit: 'max',
            titles: TitleList,
            redirects: 'true'
        },
        error: function(ArticlejsonObject,status,errorThrown) {ArticlejsonObject.errorThrown=errorThrown;},
        complete: function(ArticlejsonObject,status) {
                NRISOnlyCatsChecked(ArticlejsonObject,status,TempTitles,StartIndex,currentTable,currentRow);
            }
    });
    return;
}

// parse API response for article query
function NRISOnlyCatsChecked(ArticlejsonObject,status,TempTitles,StartIndex,currentTable,currentRow) {
    if (status!="success") {
        NewNRISOnlyWarning("Articles "+ArticlejsonObject.errorThrown);

        setTimeout(function(){ // try again after delay if rate limit reached
            QueryNRISOnlyCats(TempTitles,StartIndex,currentTable,currentRow);
        }, 250);
        return;
    }
    // won't get here unless successful
    NRISOnlyStructure[currentTable][currentRow].CatsQueried+=TempTitles.length;

    var responseText=JSON.parse(ArticlejsonObject.responseText);
    var i;
    if (responseText.query.normalized) { // normalize any weird titles
        for (var n in responseText.query.normalized) {
            for (i=0; i<TempTitles.length; i++) {
                if (TempTitles[i]==responseText.query.normalized[n].from) TempTitles[i]=responseText.query.normalized[n].to;
            }
            for (i=0; i<NRISOnlyStructure[currentTable][currentRow].Titles.length; i++) { // also update in main data array
                if (NRISOnlyStructure[currentTable][currentRow].Titles[i].name==responseText.query.normalized[n].from) {
                    NRISOnlyStructure[currentTable][currentRow].Titles[i].name=responseText.query.normalized[n].to;
                }
            }
        }
    }
    if (responseText.query.redirects) { // resolve any redirects also
        for (var r in responseText.query.redirects) {
            for (i=0; i<TempTitles.length; i++) {
                if (TempTitles[i]==responseText.query.redirects[r].from) TempTitles[i]=responseText.query.redirects[r].to;
            }
            for (i=0; i<NRISOnlyStructure[currentTable][currentRow].Titles.length; i++) { // also update in main data array
                if (NRISOnlyStructure[currentTable][currentRow].Titles[i].name==responseText.query.redirects[r].from) {
                    NRISOnlyStructure[currentTable][currentRow].Titles[i].name=responseText.query.redirects[r].to;
                }
            }
        }
    }

    // now determine if dab/NRIS-only
    for (var page in responseText.query.pages) {
        var pagetitle=responseText.query.pages[page].title;
        // mark as redlink
        if (typeof responseText.query.pages[page].missing!="undefined") {
            for (i=0; i<NRISOnlyStructure[currentTable][currentRow].Titles.length; i++) {
                if (NRISOnlyStructure[currentTable][currentRow].Titles[i].name==pagetitle) {
                    NRISOnlyStructure[currentTable][currentRow].Titles[i].exists=false;
                }
            }
        }
        if (responseText.query.pages[page].categories) {
            for (var category in responseText.query.pages[page].categories) {
                // mark as link to dab
                if (responseText.query.pages[page].categories[category].title=="Category:All disambiguation pages") {
                    for (i=0; i<NRISOnlyStructure[currentTable][currentRow].Titles.length; i++) {
                        if (NRISOnlyStructure[currentTable][currentRow].Titles[i].name==pagetitle) {
                            NRISOnlyStructure[currentTable][currentRow].Titles[i].exists=false; // dab=redlink
                            NRISOnlyStructure[currentTable][currentRow].Titles[i].isDab=true;
                        }
                    }
                }
                // mark already tagged NRIS-only
                if (responseText.query.pages[page].categories[category].title.indexOf("sourced only to NRIS")!=-1) {
                    for (i=0; i<NRISOnlyStructure[currentTable][currentRow].Titles.length; i++) {
                        if (NRISOnlyStructure[currentTable][currentRow].Titles[i].name==pagetitle) {
                            NRISOnlyStructure[currentTable][currentRow].Titles[i].isTaggedNRISOnly=true;
                        }
                    }
                }
            }
        }
    }

    if (NRISOnlyStructure[currentTable][currentRow].CatsQueried==NRISOnlyStructure[currentTable][currentRow].Titles.length) {
        var Titles=[];
        for (i=0; i<NRISOnlyStructure[currentTable][currentRow].Titles.length; i++) { // tally up articles to query
            if (NRISOnlyStructure[currentTable][currentRow].Titles[i].exists) {
                Titles.push(NRISOnlyStructure[currentTable][currentRow].Titles[i].name);
                NRISOnlyStructure[currentTable][currentRow].TotalToCheck++;
            }
        }
        // now begin querying article content and checking if NRIS-only
        StartIndex=0;
        LoadNextNRISOnlyPageQuery(Titles,StartIndex,currentTable,currentRow);
    }
}

// load each existing page to check if it should be tagged NRIS-only
function LoadNextNRISOnlyPageQuery(Titles,StartIndex,currentTable,currentRow) {
    if (StartIndex==Titles.length) { // all queries begun for this list
        if (Titles.length===0) { // some lists have no bluelinks
            TotalQueried++;
            if (TotalQueried==TotalToQuery) DoneQueryingNRIS();
        }
        return;
    }
    // must have some more rows to query
    setTimeout(function(){ // short delay to prevent API overload
        QueryNextNRISOnlyPage(Titles,StartIndex,currentTable,currentRow);
        StartIndex++;
        LoadNextNRISOnlyPageQuery(Titles,StartIndex,currentTable,currentRow);
    }, DefaultQueryPause);
    return;
}

// query the next page
function QueryNextNRISOnlyPage(Titles,StartIndex,currentTable,currentRow) {
    $.ajax({
        dataType: "json",
        url: mw.util.wikiScript('api'),
        data: {
            format: 'json',
            action: 'query',
            prop: 'revisions',
            rvprop: 'content',
            titles: Titles[StartIndex],
            indexpageids: true,
            redirects: 'true'
        },
		error: function(ajaxResponse,status,errorThrown) {
			ajaxResponse.errorThrown=errorThrown;
			console.log("QueryNextNRISOnlyPage failed title="+Titles[StartIndex],status, errorThrown);
		},
        complete: function(ajaxResponse,status) {
            NRISOnlyPageWikitextFetched(ajaxResponse,status,Titles,StartIndex,currentTable,currentRow);
        }
    });
    return;
}

// parse API response for page wikitext
function NRISOnlyPageWikitextFetched(ajaxResponse,status,Titles,StartIndex,currentTable,currentRow) {
    if (status!="success") {
        NewNRISOnlyWarning("Articles wikitext "+ajaxResponse.errorThrown);

        setTimeout(function(){ // try again after delay if rate limit reached
            QueryNextNRISOnlyPage(Titles,StartIndex,currentTable,currentRow);
        }, 250);
        return;
    }
    // won't get here unless successful
    NRISOnlyStructure[currentTable][currentRow].TotalChecked++;

    // now determine prose size, number of references, etc.
    var responseText=JSON.parse(ajaxResponse.responseText);
    for (var page in responseText.query.pages) {
        var pagetext;
        try {
        	pagetext = responseText.query.pages[page].revisions[0]['*'];
        } catch (e) {
        	console.log('NRISOnlyPageWikitextFetched error reading responseText for page '+NRISOnlyStructure[currentTable][currentRow].Link,responseText.query.pages[page],e);
        	console.log('Current row',NRISOnlyStructure[currentTable][currentRow]);
        	throw e;
        }

        var prose=pagetext.replace(/<\!\-\-(.|[\r\n])*?\-\-\>/g,"");                        // strip comments
        prose=prose.replace(/<ref[^e](.|[\r\n])*?([ ]*?\/|<\/ref[ ]*?)\>/gi,"");           // strip refs
        prose=prose.replace(/==[ ]*?External links[ ]*?==(.|\n)*?(?=(==.*?==|$))/gi,"");     // strip external links section
        prose=prose.replace(/==[ ]*?See also[ ]*?==(.|\n)*?(?=(==.*?==|$))/gi,"");           // strip see also section
        prose=prose.replace(/==[ ]*?(References|Notes)[ ]*?==(.|\n)*?(?=(==.*?==|$))/gi,""); // strip references section
        // strip further reading section
        prose=prose.replace(/==[ ]*?(Further|Additional) reading[ ]*?==(.|\n)*?(?=(==.*?==|$))/gi,"");
        prose=prose.replace(/={2,5}.*?={2,5}/g,"");                                          // strip section titles
        // replace wikilinks with displayed text
        prose=prose.replace(/\[\[(?![ ]*?Category:|[ ]*?Image:|[ ]*?File:)([^\]]*?\|)?(.*?)\]\]/gi,"$2");
        prose=prose.replace(/\[[ ]*?http.*? (.*?)\]/g,"$1");                 // replace inline external links with displayed text
        prose=prose.replace(/'{2,5}(.*?)'{2,5}/g,"$1");                      // replace bold/italic with displayed text
        prose=prose.replace(/\[\[[ ]*?Category:.*?\]\]/g,"");                                // strip categories
        prose=prose.replace(/\[\[[ ]*?(Image|File):.*?\]\]/g,"");                            // strip images
        prose=prose.replace(/<[ ]*?gallery(.|\n)*?<[ ]*?\/[ ]*?gallery[ ]*?\>/gi,"");      // strip galleries
        while(true) {                                                                       // strip templates
            var str="{{";
            var start=prose.indexOf(str);
            if (start==-1) break;
            var open=1;
            var index=start+str.length;
            while (open!==0 && index<prose.length) {
                if (prose.substr(index,2)=="}}") {
                    open--;
                    index++;
                } else if (prose.substr(index,2)=="{{") {
                    open++;
                    index++;
                }
                index++;
            }
            prose=prose.replace(prose.substr(start,index-start),"");
        }
        prose=prose.replace(/{\|(.|\n)*?\|}/g,"");                   // strip tables
        prose=prose.replace(/&nbsp;/g," ");                          // replace nbsp with regular space
        prose=prose.replace(/<[ ]*?br.*?>/g, "\n");                  // replace HTML line break with string line break
        prose=prose.replace(/[ \n]+/g," ");                          // replace multiple spaces/linebreaks with single space
        prose=prose.trim();

		var i;
        for (i=0; i<NRISOnlyStructure[currentTable][currentRow].Titles.length; i++) { // record prose size
            if (NRISOnlyStructure[currentTable][currentRow].Titles[i].name==Titles[StartIndex]) {
                NRISOnlyStructure[currentTable][currentRow].Titles[i].proseSize=prose.length;
            }
        }

        var hasSpecialRefs=0;
        if (pagetext.indexOf("{{GR")!=-1||pagetext.indexOf("{{sfn")!=-1||pagetext.indexOf("{{Sfn")!=-1) { // these count as refs
            hasSpecialRefs=pagetext.match(/{{[ ]*(GR|sfn|Sfn)/g).length;
        }

        var Refs=pagetext.match(/<ref[^e](.|[\r\n])*?([ ]*?\/|<\/ref[ ]*?)\>/gi);
        var comments=pagetext.match(/<\!\-\-(.|[\r\n])*?\-\-\>/g);

        if (Refs===null) { // if no refs, skip page (default numberRefs=0)
            if (hasSpecialRefs===0) continue;
            Refs=[];
        }

		var l, m;
        if (comments!==null) {
            for (l=0; l<comments.length; l++) {
                var CommentedRefs=comments[l].match(/<ref[^e](.|[\r\n])*?([ ]*?\/|<\/ref[ ]*?)\>/gi);
                if (CommentedRefs===null) continue;
                for (m=0; m<CommentedRefs.length; m++) {
                    for (var n=0; n<Refs.length; n++) {
                        if (Refs[n]==CommentedRefs[m]) {Refs.splice(n,1); n--;}
                    }
                }
            }
        }

        if (Refs.length===0) { // if all refs commented out, skip page (default numberRefs=0)
            if (hasSpecialRefs===0) continue;
        }

        var citesNRIS=false;
        for (l=0; l<Refs.length; l++) {
            if (Refs[l].indexOf("{{NRISref")!=-1) citesNRIS=true; // check if NRISref is one of the refs
        }

        var namedRefs=[];
        for (l=0; l<Refs.length; l++) { // extract names of refs
            var nameOfRef=Refs[l].match(/name[ ]*?=.*?(\/|\>)/gi);
            if (nameOfRef===null) {
                continue;
            } else {
                nameOfRef = nameOfRef[0].replace(/("| )/g,'');
                nameOfRef = nameOfRef.substr(5,nameOfRef.length-6);
            }
            namedRefs.push(nameOfRef);
        }

        var Duplicates=0;
        for (l=0; l<namedRefs.length; l++) { // remove duplicate named refs
            for (m=l+1; m<namedRefs.length; m++) {
                if (namedRefs[m]==namedRefs[l]) {
                    Duplicates++;
                    namedRefs.splice(m,1);
                    m--;
                }
            }
        }

        var DistinctRefs = Refs.length-Duplicates;
        if (hasSpecialRefs>0) DistinctRefs+=hasSpecialRefs;

        for (i=0; i<NRISOnlyStructure[currentTable][currentRow].Titles.length; i++) { // record number of refs
            if (NRISOnlyStructure[currentTable][currentRow].Titles[i].name==Titles[StartIndex]) {
                NRISOnlyStructure[currentTable][currentRow].Titles[i].numberRefs=DistinctRefs;
            }
        }

        if (DistinctRefs>1) continue; // not NRIS-only if more than one ref

        if (citesNRIS&&DistinctRefs==1) { // if one ref and is NRIS-only, then tag
            for (i=0; i<NRISOnlyStructure[currentTable][currentRow].Titles.length; i++) { // record number of refs
                if (NRISOnlyStructure[currentTable][currentRow].Titles[i].name==Titles[StartIndex]) {
                    NRISOnlyStructure[currentTable][currentRow].Titles[i].isNRISOnly=true;
                }
            }
        }
    }

    if (NRISOnlyStructure[currentTable][currentRow].TotalChecked==NRISOnlyStructure[currentTable][currentRow].TotalToCheck) {
        TotalQueried++;
        if (TotalQueried==TotalToQuery) DoneQueryingNRIS();
        return;
    }
}

// keep track of warnings encountered while querying API
function NewNRISOnlyWarning(warning) {
    var NewWarning=true;
    var i;
    for (i=0; i<WarningCount.length; i++) { // check if already encountered error
        if (warning==WarningCount[i][0]||WarningCount[i][0]==="") {WarningCount[i][0]=warning; WarningCount[i][1]++; NewWarning=false;}
    }
    if (NewWarning) WarningCount[WarningCount.length]=[warning,1]; // if new warning, make new entry

    var test=0;
    for (i=0; i<WarningCount.length; i++) {
        test+=WarningCount[i][1];
    }
    if (test%50===0) DefaultQueryPause++; // for every 50 errors encountered, increase time between each query to throttle speed
}

// these errors require user input; can't just be ignored
function NRISOnlyFatalError(code,currentTable,currentRow) {
    var errorArray = ['No county section found for ','No list found for ','Incorrectly formatted list for '];
    var retry=confirm(errorArray[code]+NRISOnlyStructure[currentTable][currentRow].Link+"!\n\nCancel=Skip                   OK=Retry");
    if (retry) {
        getNRISOnlyListWikitext(currentTable,currentRow);
    } else { // if chose to skip, add one to error count
        TotalQueried++;
        ErrorCount++;
        if (TotalQueried==TotalToQuery) DoneQueryingNRIS();
    }
    return;
}

// update ProgressDiv to let user know what's going on
function UpdateNRISOnlyProgressDiv() {
    var ProgressSpan=document.getElementById("ProgressSpan");
    var TimeSpan=document.getElementById("TimeSpan");
    var TimeRemainingStr;

    var PercentQueried=Math.round(TotalQueried/TotalToQuery*1000)/10;
    ProgressSpan.innerHTML = "Querying articles in each list... "+TotalQueried+" ("+PercentQueried+"%) of "+TotalToQuery+" lists complete.";

    if (TotalQueried>100) {
        var CurrentTime=new Date();
        var SecondsElapsed = (CurrentTime-InitialTime)/1000;
        var Average = SecondsElapsed/TotalQueried;
        SecondsElapsed=Math.round(SecondsElapsed);
        var MinutesElapsed = 0;
        while (SecondsElapsed>=60) {
            SecondsElapsed-=60;
            MinutesElapsed++;
        }
        var SecondsRemaining = Math.round(Average*(TotalToQuery-TotalQueried));
        var MinutesRemaining = 0;
        while (SecondsRemaining>=60) {
            SecondsRemaining-=60;
            MinutesRemaining++;
        }

        TimeRemainingStr = "";
        if (MinutesRemaining!==0) TimeRemainingStr=MinutesRemaining+" min ";
        TimeRemainingStr+=SecondsRemaining+" sec";
        var TimeElapsedStr = "";
        if (MinutesElapsed!==0) TimeElapsedStr=MinutesElapsed+" min ";
        TimeElapsedStr+=SecondsElapsed+" sec";
        TimeRemainingStr+=" ("+TimeElapsedStr+" elapsed)";
    } else {
        TimeRemainingStr="Calculating...";
    }
    TimeSpan.innerHTML="<br>Estimated time remaining: "+TimeRemainingStr;

    if (TotalQueried!=TotalToQuery) { // update ProgressDiv only at regular intervals to prevent CPU overload; stop once done
        ProgressDivTimer=setTimeout(function(){
            UpdateNRISOnlyProgressDiv();
        }, 500);
    }
    return;
}

// get here once done querying everything
function DoneQueryingNRIS() {
    clearTimeout(ProgressDivTimer);
    var ProgressSpan=document.getElementById("ProgressSpan");
    var TimeSpan=document.getElementById("TimeSpan");

    var CurrentTime=new Date();
    var SecondsElapsed = (CurrentTime-InitialTime)/1000;
    SecondsElapsed=Math.round(SecondsElapsed);
    var MinutesElapsed = 0;
    while (SecondsElapsed>=60) {
        SecondsElapsed-=60;
        MinutesElapsed++;
    }
    var TimeStr=" Time elapsed: ";
    if (MinutesElapsed!==0) TimeStr+=MinutesElapsed+" min ";
    TimeStr+=SecondsElapsed+" sec";

    ProgressSpan.innerHTML="Querying articles in each list... Done!"+TimeStr;

    TimeSpan.innerHTML="<br>Compiling data (this could take a while)... ";

    setTimeout(function() {CalculateNRISOnlyTotals()},100); // small delay for non-Firefox browsers to update screen
}

// now compile all the data
function CalculateNRISOnlyTotals() {
    var StartTime=new Date(); // for calculating computation time

    var AllTitles = [];             // [name,refnum,list] - includes redlinks; used to find duplicates later

    var Dabs = [];                  // [name,list]
    var MissingRefnum = [];         // [name,list]
    var ExistingTitles = [];        // name               - only bluelinks

    var ToBeTagged = [];            // name
    var ToBeUntagged = [];          // name

    var AllNRISOnly = [];           // name               - includes manually tagged articles
    var OneRefNotNRIS = [];         // name
    var Unreferenced = [];          // name

    var ShortNRIS = [];             // [name,length]      - NRIS-only with <325 bytes prose
    var ShortNotNRIS = [];          // [name,length]      - not NRIS-only with <325 bytes prose
    var LongNRIS = [];              // [name,length]      - NRIS-only with >=325 bytes prose

    var WorstNotNRIS = [];          // [name,length]      - 100 smallest non-NRIS-only
    var WorstNRIS = [];             // [name,length]      - 100 smallest NRIS-only

    var Duplications = [];          // [name,refnum,[list1,..listn]]

    for (var table=0; table<NRISOnlyStructure.length; table++) {
        for (var row=0; row<NRISOnlyStructure[table].length; row++) {
            for (var item=0; item<NRISOnlyStructure[table][row].Titles.length; item++) {
                var thisItem=NRISOnlyStructure[table][row].Titles[item];
                if (thisItem.refnum=="missing") {
                    MissingRefnum[MissingRefnum.length]=[thisItem.name,NRISOnlyStructure[table][row].Link];
                } else {
                    AllTitles[AllTitles.length]=[thisItem.name,thisItem.refnum,NRISOnlyStructure[table][row].Link];
                }
                if (thisItem.isDab) Dabs[Dabs.length]=[thisItem.name,NRISOnlyStructure[table][row].Link];
                if (!thisItem.exists) continue; // if redlink, no longer care about it

                // now no longer have to check for existence
                ExistingTitles.push(thisItem.name);

                // tagging
                if (thisItem.isNRISOnly&&!thisItem.isTaggedNRISOnly) ToBeTagged.push(thisItem.name);
                // only untag if more than one ref; sometimes tag is added manually to NRIS mirrors
                if (!thisItem.isNRISOnly&&thisItem.isTaggedNRISOnly&&thisItem.numberRefs>1) ToBeUntagged.push(thisItem.name);

                // referencing
                if (thisItem.isNRISOnly||(!thisItem.isNRISOnly&&thisItem.isTaggedNRISOnly&&thisItem.numberRefs<=1)) {
                    // also include manually tagged in output
                    AllNRISOnly.push(thisItem.name);
                }
                if (!thisItem.isNRISOnly&&thisItem.numberRefs==1) OneRefNotNRIS.push(thisItem.name);
                if (thisItem.numberRefs===0) Unreferenced.push(thisItem.name);

                // prose length
                if (thisItem.proseSize<325&thisItem.isNRISOnly) ShortNRIS[ShortNRIS.length]=[thisItem.name,thisItem.proseSize];
                if (thisItem.proseSize<325&!thisItem.isNRISOnly) ShortNotNRIS[ShortNotNRIS.length]=[thisItem.name,thisItem.proseSize];
                if (thisItem.proseSize>=325&thisItem.isNRISOnly) LongNRIS[LongNRIS.length]=[thisItem.name,thisItem.proseSize];
            }
        }
    }

    // remove duplicates
    var i, j;
    for (i=0;i<ExistingTitles.length; i++) {
        for (j=i+1;j<ExistingTitles.length; j++) {
            if (ExistingTitles[i]==ExistingTitles[j]) {ExistingTitles.splice(j,1);j--;}
        }
    }
    for (i=0;i<ToBeTagged.length; i++) {
        for (j=i+1;j<ToBeTagged.length; j++) {
            if (ToBeTagged[i]==ToBeTagged[j]) {ToBeTagged.splice(j,1);j--;}
        }
    }
    for (i=0;i<ToBeUntagged.length; i++) {
        for (j=i+1;j<ToBeUntagged.length; j++) {
            if (ToBeUntagged[i]==ToBeUntagged[j]) {ToBeUntagged.splice(j,1);j--;}
        }
    }
    for (i=0;i<AllNRISOnly.length; i++) {
        for (j=i+1;j<AllNRISOnly.length; j++) {
            if (AllNRISOnly[i]==AllNRISOnly[j]) {AllNRISOnly.splice(j,1);j--;}
        }
    }
    for (i=0;i<OneRefNotNRIS.length; i++) {
        for (j=i+1;j<OneRefNotNRIS.length; j++) {
            if (OneRefNotNRIS[i]==OneRefNotNRIS[j]) {OneRefNotNRIS.splice(j,1);j--;}
        }
    }
    for (i=0;i<Unreferenced.length; i++) {
        for (j=i+1;j<Unreferenced.length; j++) {
            if (Unreferenced[i]==Unreferenced[j]) {Unreferenced.splice(j,1);j--;}
        }
    }
    for (i=0;i<ShortNRIS.length; i++) {
        for (j=i+1;j<ShortNRIS.length; j++) {
            if (ShortNRIS[i][0]==ShortNRIS[j][0]) {ShortNRIS.splice(j,1);j--;}
        }
    }
    for (i=0;i<ShortNotNRIS.length; i++) {
        for (j=i+1;j<ShortNotNRIS.length; j++) {
            if (ShortNotNRIS[i][0]==ShortNotNRIS[j][0]) {ShortNotNRIS.splice(j,1);j--;}
        }
    }
    for (i=0;i<LongNRIS.length; i++) {
        for (j=i+1;j<LongNRIS.length; j++) {
            if (LongNRIS[i][0]==LongNRIS[j][0]) {LongNRIS.splice(j,1);j--;}
        }
    }

    // sort by length
    ShortNRIS.sort(function(a,b){if (a[1] < b[1]) return -1; if (a[1] > b[1]) return 1; return 0;});
    ShortNotNRIS.sort(function(a,b){if (a[1] < b[1]) return -1; if (a[1] > b[1]) return 1; return 0;});
    // extract worst
    WorstNRIS=ShortNRIS.slice(0,100);
    WorstNotNRIS=ShortNotNRIS.slice(0,100);
    // now sort by name
    ShortNRIS.sort(function(a,b){if (a[0] < b[0]) return -1; if (a[0] > b[0]) return 1; return 0;});
    ShortNotNRIS.sort(function(a,b){if (a[0] < b[0]) return -1; if (a[0] > b[0]) return 1; return 0;});

    // sort the other arrays by name
    LongNRIS.sort(function(a,b){if (a[0] < b[0]) return -1; if (a[0] > b[0]) return 1; return 0;});
    Dabs.sort(function(a,b){if (a[0] < b[0]) return -1; if (a[0] > b[0]) return 1; return 0;});
    MissingRefnum.sort(function(a,b){if (a[0] < b[0]) return -1; if (a[0] > b[0]) return 1; return 0;});
    // custom sort not needed for these
    ExistingTitles.sort();
    ToBeTagged.sort();
    ToBeUntagged.sort();
    AllNRISOnly.sort();
    OneRefNotNRIS.sort();
    Unreferenced.sort();

    // now find duplicates across county lines
    for (i=0;i<AllTitles.length; i++) {
        Duplications[Duplications.length]=[AllTitles[i][0],AllTitles[i][1],[AllTitles[i][2]]]; // [name,refnum,[list]]
        for (j=i+1;j<AllTitles.length; j++) {
            if (AllTitles[i][1]==AllTitles[j][1]) {
                Duplications[Duplications.length-1][2].push(AllTitles[j][2]);
                AllTitles.splice(j,1);
                j--;
            }
        }
        if (Duplications[Duplications.length-1][2].length==1) Duplications.pop(); // if no other lists added, not duplicate
    }

    // now dump data to subpages
    var TimeSpan=document.getElementById("TimeSpan");
    var EditSpan=document.getElementById("EditSpan");
    var CurrentTime=new Date();
    var SecondsElapsed = (CurrentTime-StartTime)/1000;
    SecondsElapsed=Math.round(SecondsElapsed);
    var MinutesElapsed = 0;
    while (SecondsElapsed>=60) {
        SecondsElapsed-=60;
        MinutesElapsed++;
    }
    var TimeStr=" Time elapsed: ";
    if (MinutesElapsed!==0) TimeStr+=MinutesElapsed+" min ";
    TimeStr+=SecondsElapsed+" sec";

    TimeSpan.innerHTML+="Done!"+TimeStr;
    EditSpan.innerHTML="<br>Dumping data to subpages of ";
    EditSpan.innerHTML+="<a href='http://wiki.riteme.site/wiki/"+OutputBase+"'>"+OutputBase+"</a>... 0 edits made.";

    var d=new Date();
    var months = ['January','February','March','April','May','June','July','August','September','October','November','December'];
    var year=d.getYear();
    if (year < 1000) year += 1900;
    var DateStr = months[d.getMonth()]+" "+d.getDate()+", "+year;

    var subpages=0;
    var firstletter=ExistingTitles[0].substr(0,1);
    var oldfirstletter;
    var basewikitext = "";
    var partialwikitext = "=="+firstletter+"==\n{{refbegin|3}}\n";
    var k;
    for (k=0; k<ExistingTitles.length; k++) {
        oldfirstletter = firstletter;
        firstletter = ExistingTitles[k].substr(0,1);
        if (firstletter!=oldfirstletter) { // must be starting a new subpage, so edit the previous one
            partialwikitext+='{{refend}}';
            subpages++;

            NRISOnlyDump({
                title: OutputBase+'/'+oldfirstletter,
                text: partialwikitext,
                summary:'Generate list of all NRHP articles beginning with '+oldfirstletter+' linked from county lists as of '+DateStr+CurrentOperatorEditSummary
            },EditSpan,subpages,"no");

            basewikitext+='*[[/'+oldfirstletter+']]\n';
            partialwikitext='=='+firstletter+'==\n{{refbegin|3}}\n# <li value="'+parseFloat(k+1)+'">[['+ExistingTitles[k]+']]\n';
        } else {
            partialwikitext+='# [['+ExistingTitles[k]+']]\n';
        }
    }
    partialwikitext+="{{refend}}";
    subpages++;
    NRISOnlyDump({ // manually edit final subpage
        title: OutputBase+'/'+firstletter,
        text: partialwikitext,
        summary:'Generate list of all NRHP articles beginning with '+firstletter+' linked from county lists as of '+DateStr
    },EditSpan,subpages,"no");

    basewikitext+='*[[/'+firstletter+']]\n<hr />*[[/Duplications]]';
    subpages++;

    NRISOnlyDump({
        title: OutputBase,
        text: basewikitext,
        summary: 'Generate list of all NRHP articles linked from county lists as of '+DateStr+CurrentOperatorEditSummary
    },EditSpan,subpages,"no");

    // now dump list of duplicates
    basewikitext = "{{TOC right}}\n<div id=\"duplicates-div\">\n";
    if (Duplications.length>0) {
        var multistate="==Multi-state==\n<div id=\"multistate\">\n{{refbegin|2}}\n";
        var unknown="==Unknown==\n<div id=\"unknown\">\n{{refbegin|2}}\n";
        var oldstate="";
        var state="";
        var prevstate="";
        var multiflag=false;
        var unknownflag=false;
        var firststateflag=true;
        partialwikitext="";
        console.log('Duplications', Duplications);
        for (k=0; k<Duplications.length; k++) {
            partialwikitext="# [["+Duplications[k][0]+"]]&nbsp;(#"+Duplications[k][1]+") <small>(";
            multiflag=false;
            unknownflag=false;
            for (var l=0; l<Duplications[k][2].length; l++) {
                state=getState(Duplications[k][2][l]);
                if (l>0) {
                    if (oldstate===""||state==="") {
                    	console.log("Found entry withunknown state: oldstate='"+oldstate+"' state='"+state+"'", Duplications[k]);
                    	console.log("getState failed on: "+Duplications[k][2][l]);
                        unknownflag=true;
                    } else if (state!=oldstate) {
                        multiflag=true;
                    }
                }
                oldstate=state;
                partialwikitext+="[["+Duplications[k][2][l]+"|";
                partialwikitext+=Duplications[k][2][l].replace(/National Register of Historic Places listings (i|o)n /g,"")+"]]; ";
            }
            partialwikitext=partialwikitext.substr(0,partialwikitext.length-2);
            partialwikitext+=")</small>\n";
            if (unknownflag) {
                unknown+=partialwikitext;
            } else if (multiflag) {
                multistate+=partialwikitext;
            } else {
                if (state!=prevstate) {
                    if (firststateflag) {
                        basewikitext+="=="+state+"==\n{{refbegin|2}}\n";
                        firststateflag=false;
                    } else {
                        basewikitext+="{{refend}}\n\n=="+state+"==\n{{refbegin|2}}\n";
                    }
                }
                var smalltext=partialwikitext.match(/<small>(.)*?<\/small>/g)[0];
                var newsmalltext=smalltext.replace(/,[a-zA-Z .,]*?]]/g,"]]"); // remove state names from dupes inside single state
                partialwikitext=partialwikitext.replace(smalltext,newsmalltext);
                basewikitext+=partialwikitext;
                prevstate=state;
            }
        }
        basewikitext+="{{refend}}\n</div>";
        if (multistate!="==Multi-state==\n<div id=\"multistate\">\n{{refbegin|2}}\n") {
            basewikitext+="\n\n"+multistate+"\n{{refend}}\n</div>";
        }
        if (unknown!="==Unknown==\n<div id=\"unknown\">\n{{refbegin|2}}\n") {
            basewikitext+="\n\n"+unknown+"\n{{refend}}\n</div>";
        }
    }
    NRISOnlyDump({
        title: OutputBase+'/Duplications',
        text: basewikitext,
        summary:'Generate list of all NRHP articles duplicated across county lines as of '+DateStr+CurrentOperatorEditSummary
    },EditSpan,subpages,"yes");

    // now dump NRIS-only data
    var OutputPage=NRISOnlyFile;
    EditSpan.innerHTML+="<br>Now outputting data to <a href='http://wiki.riteme.site/wiki/"+OutputPage+"'>"+OutputPage+"</a>... ";

    basewikitext="{{TOC right}}\n";
    if (ToBeTagged.length>0) {
        basewikitext+="==Articles that need to be tagged==\n<div id=\"tobetagged-div\">\n{{refbegin|3}}\n";
        for (k=0; k<ToBeTagged.length; k++) {
            basewikitext+='# [['+ToBeTagged[k]+']]\n';
        }
        basewikitext+="{{refend}}</div>\n\n";
    }
    if (ToBeUntagged.length>0) {
        basewikitext+="==Articles that need to be untagged==\n<div id=\"tobeuntagged-div\">\n{{refbegin|3}}\n";
        for (k=0; k<ToBeUntagged.length; k++) {
            basewikitext+='# [['+ToBeUntagged[k]+']]\n';
        }
        basewikitext+="{{refend}}</div>\n\n";
    }
    if (Dabs.length>0) {
        basewikitext+="==Links to disambiguation pages==\n{{refbegin|3}}\n";
        for (k=0; k<Dabs.length; k++) {
            basewikitext+='# [['+Dabs[k][0]+']]&nbsp;([['+Dabs[k][1]+'|';
            basewikitext+=Dabs[k][1].replace(/National Register of Historic Places listings (i|o)n /g,"")+']])\n';
        }
        basewikitext+="{{refend}}\n\n";
    }
    if (MissingRefnum.length>0) {
        basewikitext+="==Missing refnum in county list==\n{{refbegin|3}}\n";
        for (k=0; k<MissingRefnum.length; k++) {
            basewikitext+='# [['+MissingRefnum[k][0]+']]&nbsp;([['+MissingRefnum[k][1]+'|';
            basewikitext+=MissingRefnum[k][1].replace(/National Register of Historic Places listings (i|o)n /g,"")+']])\n';
        }
        basewikitext+="{{refend}}\n\n";
    }
    if (Unreferenced.length>0) {
        basewikitext+="==Articles with no references==\n<div id=\"unreferenced-div\">\n{{refbegin|3}}\n";
        for (k=0; k<Unreferenced.length; k++) {
            basewikitext+='# [['+Unreferenced[k]+']]\n';
        }
        basewikitext+="{{refend}}</div>\n\n";
    }
    if (OneRefNotNRIS.length>0) {
        basewikitext+="==Articles with only one source that is not NRIS==\n{{refbegin|3}}\n";
        for (k=0; k<OneRefNotNRIS.length; k++) {
            basewikitext+='# [['+OneRefNotNRIS[k]+']]\n';
        }
        basewikitext+="{{refend}}\n\n";
    }
    NRISOnlyDump({
        title: OutputPage,
        text: basewikitext,
        summary:'Generate list of all NRHP articles to be tagged/untagged with [[Template:NRIS-only]] as of '+DateStr+CurrentOperatorEditSummary
    },EditSpan,subpages,"");

    // now substubs
    OutputPage=SubstubsFile;
    basewikitext="";
    if (WorstNRIS.length>0) {
        basewikitext+="==Smallest 100 NRIS-only articles by prose size==\n{{refbegin|3}}\n";
        for (k=0; k<WorstNRIS.length; k++) {
            basewikitext+='# [['+WorstNRIS[k][0]+']] ('+WorstNRIS[k][1]+'&nbsp;bytes)\n';
        }
        basewikitext+="{{refend}}\n\n";
    }
    if (WorstNotNRIS.length>0) {
        basewikitext+="==Smallest 100 articles by prose size NOT tagged NRIS-only==\n{{refbegin|3}}\n";
        for (k=0; k<WorstNotNRIS.length; k++) {
            basewikitext+='# [['+WorstNotNRIS[k][0]+']] ('+WorstNotNRIS[k][1]+'&nbsp;bytes)\n';
        }
        basewikitext+="{{refend}}\n\n";
    }

    if (ShortNRIS.length>0||ShortNotNRIS.length>0) {
        basewikitext+="==Articles with less than 325 bytes of prose==\n";
        if (ShortNotNRIS.length>0) {
            basewikitext+="===Not tagged NRIS-only===\n{{refbegin|3}}\n";
            for (k=0; k<ShortNotNRIS.length; k++) {
                basewikitext+='# [['+ShortNotNRIS[k][0]+']] ('+ShortNotNRIS[k][1]+'&nbsp;bytes)\n';
            }
            basewikitext+="{{refend}}\n\n";
            if (ShortNRIS.length>0) basewikitext+="===Tagged NRIS-only===\n";
        }
        if (ShortNRIS.length>0) {
            basewikitext+="{{refbegin|3}}\n";
            for (k=0; k<ShortNRIS.length; k++) {
                basewikitext+='# [['+ShortNRIS[k][0]+']] ('+ShortNRIS[k][1]+'&nbsp;bytes)\n';
            }
            basewikitext+="{{refend}}\n\n";
        }
    }
    if (LongNRIS.length>0) {
        basewikitext+="==Articles tagged NRIS-only with more than 325 bytes of prose==\n{{refbegin|3}}\n";
        for (k=0; k<LongNRIS.length; k++) {
            basewikitext+='# [['+LongNRIS[k][0]+']] ('+LongNRIS[k][1]+'&nbsp;bytes)\n';
        }
        basewikitext+="{{refend}}\n\n";
    }
    NRISOnlyDump({
        title: OutputPage,
        text: basewikitext,
        summary:'Generate list of all minimal (<325 bytes) NRHP stubs as of '+DateStr+CurrentOperatorEditSummary
    },EditSpan,subpages,"");

    // now all NRIS-only
    OutputPage=NRISOnlyFile+"/All";
    basewikitext = "{{TOC right}}\n<div id=\"NRISOnly-div\">\n";
    if (AllNRISOnly.length>0) {
        firstletter=AllNRISOnly[0].substr(0,1);
        basewikitext += "=="+firstletter+"==\n{{refbegin|3}}\n";
        for (k=0; k<AllNRISOnly.length; k++) {
            oldfirstletter = firstletter;
            firstletter = AllNRISOnly[k].substr(0,1);
            if (firstletter!=oldfirstletter) {
                basewikitext+='{{refend}}\n=='+firstletter+'==\n{{refbegin|3}}\n# <li value="';
                basewikitext+=parseFloat(k+1)+'">[['+AllNRISOnly[k]+']]\n';
            } else {
                basewikitext+='# [['+AllNRISOnly[k]+']]\n';
            }
        }
        basewikitext+="{{refend}}</div>";
    }
    NRISOnlyDump({
        title: OutputPage,
        text: basewikitext,
        summary:'Generate list of all NRHP articles sourced only to NRIS as of '+DateStr+CurrentOperatorEditSummary
    },EditSpan,subpages,"NRIS");
}

function CheckDuplicates2() {
    var ProgressDiv=document.getElementById("ProgressDiv");
    ProgressDiv.innerHTML="Checking multi-state duplicates...";
    var MultiSpan = document.createElement("span");
    ProgressDiv.appendChild(MultiSpan);

	var temp;
    var multistate={
    	entries: [],
    	wikitext: "",
    	switchtext: ""
    };
    var links, refnum,titles;

    var li=document.getElementById("multistate").getElementsByClassName("refbegin")[0].getElementsByTagName("li");
    var i, j, k, l;
	
    for (k=0; k<li.length; k++) {
        links=li[k].getElementsByTagName("a");
        refnum=li[k].innerHTML.match(/[0-9]{8,9}/)[0];
        titles = {
        	'refnum': refnum,
        	'title': null,
        	'states': {},
        	'counties': [],
        	'wikitext': "",
        	'switchtext': ""
        };
        for (l=0; l<links.length; l++) {
            temp = links[l].href.replace(/https:\/\/en\.wikipedia\.org\/w(iki)?\/(index\.php\?title=)?/,"").split("&")[0];
            temp = decodeURIComponent(temp).replace(/_/g," ");
            if (l == 0) {
            	titles.title = temp;
            } else {
				titles.counties.push(temp);            	
            }
        }
        multistate.entries[multistate.entries.length]=titles;
    }
    
    multistate.wikitext="==National==\nThe following listings are included in two or more lists which are located in different states.";
    multistate.wikitext+="\n{| class=\"wikitable sortable\" width=100%\n! {{NRHP color}} width=30% | Site\n! {{NRHP color}} width=30% | ";
    multistate.wikitext+="Lists\n! {{NRHP color}} width=10% | Number of Duplicates\n! {{NRHP color}} width=30% colspan=7 | Stats\n";

    multistate.switchtext="<includeonly>{{#tag:ref|[[WP:NRHPPROGRESS/Duplicates#{{{1}}}{{#if:{{{statewide|}}}|&nbsp;Statewide}}";
    multistate.switchtext+="{{!}}Click here]] for {{{1}}} duplicate information.}}{{#ifeq:{{{1}}}|National|</th>|{{#ifeq:{{{1}}}|";
    multistate.switchtext+="{{First word|{{{1}}}|sep=,}}|</th>|</td><td>[[{{Remove first word|{{{1}}}|sep=,}}]]</td>}}}}<td>{{#switch:";
    multistate.switchtext+="{{{1}}}\n";

    var totals = {
		total: 0,
		illustrated: 0,
		articled: 0,
		stub: 0,
		startplus: 0,
		untagged: 0,
		unassessed: 0,
		nrisonly: 0
    };

    for (i=0; i<multistate.entries.length; i++) {
    	var entry = multistate.entries[i];

        MultiSpan.innerHTML = " "+i+" of "+multistate.entries.length+" complete...";
        entry.wikitext+="|-\n| [["+entry.title+"]] (#"+entry.refnum+")\n|\n";
        var oldstate="";
        var state="";
        var totalstates=0;

        for (j=0; j<entry.counties.length; j++) {
        	var county = entry.counties[j];
        	entry.wikitext+="*[["+county+"|"+county.replace(/National Register of Historic Places listings (i|o)n /g,"");
            entry.wikitext+="]]\n";
            state=getState(county);
            if (state!=oldstate) {
                totalstates++;
                if (!entry.states.hasOwnProperty(state)) {
                	entry.states[state] = 1;
                } else {
                	entry.states[state]++;
                }
            } else {
            	// if state property doesn't exist it's an error
            	entry.states[state]++;
            }
            oldstate=state;
        }
        var duplications=totalstates-1;
        totals.total+=duplications;
        entry.wikitext+="| align=center | "+duplications+"\n| colspan=7 | ";

		var ctx = {
			'entry': entry,
			'totals': totals,
			'duplications': duplications,
			'StatsStr': ""
		};
        getDuplicateStats2(ctx).then((ctx) => {
        	console.log('getDuplicateStats2 result',ctx);
	        if (ctx.StatsStr.indexOf("unarticled")==-1) ctx.totals.articled+=ctx.duplications;
	        if (ctx.StatsStr.indexOf(", illustrated")!=-1) ctx.totals.illustrated+=ctx.duplications;
	        if (ctx.StatsStr.indexOf("Stub-class")!=-1) ctx.totals.stub+=ctx.duplications;
	        if (ctx.StatsStr.indexOf("NRIS-only")!=-1) ctx.totals.nrisonly+=ctx.duplications;
	        if (ctx.StatsStr.indexOf("Start+")!=-1) ctx.totals.startplus+=ctx.duplications;
	        if (ctx.StatsStr.indexOf("unassessed")!=-1) ctx.totals.unassessed+=ctx.duplications;
	        if (ctx.StatsStr.indexOf("untagged")!=-1) ctx.totals.untagged+=ctx.duplications;
	
	        ctx.entry.wikitext+=ctx.StatsStr+"\n";
        	
        });
    }

    console.log('CheckDuplicates2 multistate',multistate);
    console.log('CheckDuplicates2 totals',totals);
    return;

}
// gather information about duplicates from output
function CheckDuplicates() {
    var ProgressDiv=document.getElementById("ProgressDiv");
    ProgressDiv.innerHTML="Checking multi-state duplicates...";
    var MultiSpan = document.createElement("span");
    ProgressDiv.appendChild(MultiSpan);

	var temp;
    var multistate=[];
    var links, refnum,titles;

    var li=document.getElementById("multistate").getElementsByClassName("refbegin")[0].getElementsByTagName("li");
    var i, j, k, l;
    for (k=0; k<li.length; k++) {
        links=li[k].getElementsByTagName("a");
        refnum=li[k].innerHTML.match(/[0-9]{8,9}/)[0];
        titles=[refnum];                                   // make refnum element 0; title=1; county lists=2,3,4,...
        for (l=0; l<links.length; l++) {
            temp=links[l].href.replace(/https:\/\/en\.wikipedia\.org\/w(iki)?\/(index\.php\?title=)?/,"").split("&")[0];
            titles.push(decodeURIComponent(temp).replace(/_/g," "));
        }
        multistate[multistate.length]=titles;
    }
    var wikitext="==National==\nThe following listings are included in two or more lists which are located in different states.";
    wikitext+="\n{| class=\"wikitable sortable\" width=100%\n! {{NRHP color}} width=30% | Site\n! {{NRHP color}} width=30% | ";
    wikitext+="Lists\n! {{NRHP color}} width=10% | Number of Duplicates\n! {{NRHP color}} width=30% colspan=7 | Stats\n";

    var switchtabletext="<includeonly>{{#tag:ref|[[WP:NRHPPROGRESS/Duplicates#{{{1}}}{{#if:{{{statewide|}}}|&nbsp;Statewide}}";
    switchtabletext+="{{!}}Click here]] for {{{1}}} duplicate information.}}{{#ifeq:{{{1}}}|National|</th>|{{#ifeq:{{{1}}}|";
    switchtabletext+="{{First word|{{{1}}}|sep=,}}|</th>|</td><td>[[{{Remove first word|{{{1}}}|sep=,}}]]</td>}}}}<td>{{#switch:";
    switchtabletext+="{{{1}}}\n";

    var Total=0;
    var TotalIllustrated=0;
    var TotalArticled=0;
    var TotalStub=0;
    var TotalStartPlus=0;
    var TotalUntagged=0;
    var TotalUnassessed=0;
    var TotalNRISOnly=0;
    for (i=0; i<multistate.length; i++) {
        MultiSpan.innerHTML = " "+i+" of "+multistate.length+" complete...";
        wikitext+="|-\n| [["+multistate[i][1]+"]] (#"+multistate[i][0]+")\n|\n";
        temp=[multistate[i][1],0];  // 0=not duplicated inside single state by default
        multistate[i][1]=temp;
        var oldstate="";
        var state="";
        var totalstates=0;

        for (j=2; j<multistate[i].length; j++) {
        wikitext+="*[["+multistate[i][j]+"|"+multistate[i][j].replace(/National Register of Historic Places listings (i|o)n /g,"");
            wikitext+="]]\n";
            state=getState(multistate[i][j]);
            if (state!=oldstate) {
                totalstates++;
            } else {
                if (multistate[i][1][1]===0) {  // tag as being duplicated inside one state (and record which state) for later use
                    multistate[i][1][1]=state;
                } else {
                    multistate[i][1][multistate[i][1].length]=state;
                }
            }
            oldstate=state;
        }
        var duplications=totalstates-1;
        Total+=duplications;
        wikitext+="| align=center | "+duplications+"\n| colspan=7 | ";

        var StatsStr=getDuplicateStats(multistate[i]);
        if (StatsStr.indexOf("unarticled")==-1) TotalArticled+=duplications;
        if (StatsStr.indexOf(", illustrated")!=-1) TotalIllustrated+=duplications;
        if (StatsStr.indexOf("Stub-class")!=-1) TotalStub+=duplications;
        if (StatsStr.indexOf("NRIS-only")!=-1) TotalNRISOnly+=duplications;
        if (StatsStr.indexOf("Start+")!=-1) TotalStartPlus+=duplications;
        if (StatsStr.indexOf("unassessed")!=-1) TotalUnassessed+=duplications;
        if (StatsStr.indexOf("untagged")!=-1) TotalUntagged+=duplications;

        wikitext+=StatsStr+"\n";
    }
    wikitext+="|-\n! colspan=2 | Total\n! "+Total+"\n! "+TotalIllustrated+"\n! "+TotalArticled+"\n! "+TotalStub+"\n! ";
    wikitext+=TotalNRISOnly+"\n! "+TotalStartPlus+"\n! "+TotalUnassessed+"\n! "+TotalUntagged+"\n|}\n\n";

    switchtabletext+="|National="+Total+"</td><td>"+TotalIllustrated+"</td><td>-</td><td>"+TotalArticled+"</td><td>-</td><td>";
    switchtabletext+=TotalStub+"</td><td>"+TotalNRISOnly+"</td><td>"+TotalStartPlus+"</td><td>-</td><td>"+TotalUnassessed;
    switchtabletext+="</td><td>"+TotalUntagged+"</td><td>-</td>\n";

    MultiSpan.innerHTML = " Complete!";

    for (i=0; i<multistate.length; i++) {
        if (multistate[i][1][1]===0) {multistate.splice(i,1); i--;}   // get rid of those not duplicated inside single states
    }

    var StateSpan = document.createElement("span");
    ProgressDiv.appendChild(StateSpan);
    StateSpan.inerHTML="test";
    var CountySpan = document.createElement("span");
    ProgressDiv.appendChild(CountySpan);
    CountySpan.inerHTML="test";
    var ThisStateSpan = document.createElement("span");
    ProgressDiv.appendChild(ThisStateSpan);
    ThisStateSpan.inerHTML="test";

    var StateStructure=[
        ["Alabama",
            ["Jefferson County",["Jefferson County", "Birmingham"]],
            ["Mobile County",["Mobile County", "Mobile"]]],
        ["Alaska"],
        ["Arizona",
            ["Maricopa County",["Maricopa County", "Phoenix"]],
            ["Yavapai County",["Yavapai County", "Prescott"]]],
        ["Arkansas",
            ["Pulaski County",["Pulaski County", "Little Rock"]]],
        ["California",
            ["Los Angeles County",["Los Angeles County", "Los Angeles", "Pasadena"]]],
        ["Colorado",
            ["Denver County",["downtown Denver","northeast Denver","southeast Denver","west Denver"]]],
        ["Connecticut",
            ["Fairfield County",["Fairfield County","Bridgeport","Greenwich","Stamford"]],
            ["Hartford County",["Hartford County","Hartford","Southington","West Hartford","Windsor"]],
            ["Middlesex County",["Middlesex County","Middletown"]],
            ["New Haven County",["New Haven County","New Haven"]]],
        ["Delaware",
            ["New Castle County",["northern New Castle County","southern New Castle County","Wilmington"]]],
        ["D.C."],
        ["Florida",
            ["Miami-Dade County",["Miami-Dade County","Miami"]],
            ["Hillsborough County",["Hillsborough County","Tampa"]]],
        ["Georgia"],
        ["Hawaii",
            ["Honolulu County",["Oahu","Northwestern Hawaiian Islands"]],
            ["Maui County",["Maui","Kahoolawe","Lanai","Molokai"]]],
        ["Idaho"],
        ["Illinois",
            ["Cook County",["Cook County","Central Chicago","North Side Chicago","South Side Chicago","West Side Chicago","Evanston"]]],
        ["Indiana",
            ["Marion County",["Marion County","Center Township, Marion County"]]],
        ["Iowa",
            ["Polk County",["Polk County","Des Moines"]],
            ["Scott County",["Scott County","Downtown Davenport","east Davenport","west Davenport"]]],
        ["Kansas"],
        ["Kentucky",
            ["Jefferson County",["Jefferson County","Anchorage","Downtown Louisville","The Highlands, Louisville","Old Louisville","Portland, Louisville","Louisville's West End"]]],
        ["Louisiana"],
        ["Maine",
            ["Cumberland County",["Cumberland County","Portland"]]],
        ["Maryland",
            ["City of Baltimore",["Central Baltimore","East and Northeast Baltimore","North and Northwest Baltimore","South and Southeast Baltimore","West and Southwest Baltimore"]]],
        ["Massachusetts",
            ["Barnstable County",["Barnstable County","Barnstable"]],
            ["Bristol County",["Bristol County","Fall River","New Bedford","Taunton"]],
            ["Essex County",["Essex County","Andover","Gloucester","Ipswich","Lawrence","Lynn","Methuen","Salem"]],
            ["Hampden County",["Hampden County","Springfield"]],
            ["Middlesex County",["Middlesex County","Arlington","Cambridge","Concord","Framingham","Lexington","Lowell","Marlborough","Medford","Newton","Reading","Sherborn","Somerville","Stoneham","Wakefield","Waltham","Weston","Winchester"]],
            ["Norfolk County",["Norfolk County","Brookline","Milton","Quincy"]],
            ["Suffolk County",["Suffolk County","northern Boston","southern Boston"]],
            ["Worcester County",["Worcester County","northern Worcester County","Southbridge","Uxbridge","eastern Worcester","northwestern Worcester","southwestern Worcester"]]],
        ["Michigan",
            ["Wayne County",["Wayne County","Downtown and Midtown Detroit","Detroit"]]],
        ["Minnesota"],
        ["Mississippi"],
        ["Missouri",
            ["Jackson County",["Jackson County: Downtown Kansas City","Jackson County: Kansas City other"]],
            ["St. Louis",["Downtown and Downtown West St. Louis","St. Louis north and west of downtown","St. Louis south and west of downtown"]]],
        ["Montana"],
        ["Nebraska"],
        ["Nevada"],
        ["New Hampshire"],
        ["New Jersey",
            ["Bergen County",["Bergen County","Closter","Franklin Lakes","Ridgewood","Saddle River","Wyckoff"]]],
        ["New Mexico"],
        ["New York",
            ["Albany County",["Albany County","Albany"]],
            ["Dutchess County",["Dutchess County","Poughkeepsie","Rhinebeck"]],
            ["Erie County",["Erie County","Buffalo"]],
            ["Monroe County",["Monroe County","Rochester"]],
            ["Nassau County",["Hempstead (town)","North Hempstead (town)","Oyster Bay (town)"]],
            ["New York County",["Manhattan below 14th Street","Manhattan from 14th to 59th Streets","Manhattan above 59th to 110th Streets","Manhattan above 110th Street","Manhattan on islands"]],
            ["Niagara County",["Niagara County","Niagara Falls"]],
            ["Onondaga County",["Onondaga County","Syracuse"]],
            ["Suffolk County",["Babylon (town)","Brookhaven (town)","East Hampton (town)","Huntington (town)","Islip (town)","Riverhead (town)","Shelter Island (town)","Smithtown (town)","Southampton (town)","Southold (town)"]],
            ["Westchester County",["northern Westchester County","southern Westchester County","New Rochelle","Peekskill","Yonkers"]]],
        ["North Carolina"],
        ["North Dakota"],
        ["Ohio",
            ["Cuyahoga County",["Cuyahoga County","Cleveland"]],
            ["Erie County",["Erie County","Sandusky"]],
            ["Franklin County",["Franklin County","Columbus"]],
            ["Hamilton County",["Hamilton County","downtown Cincinnati","eastern Cincinnati","western Cincinnati"]],
            ["Montgomery County",["Montgomery County","Dayton"]],
            ["Summit County",["Summit County","Akron"]]],
        ["Oklahoma"],
        ["Oregon",
            ["Multnomah County",["Multnomah County","North Portland","Northeast Portland","Northwest Portland","Southeast Portland","Southwest Portland"]]],
        ["Pennsylvania",
            ["Allegheny County",["Allegheny County","Pittsburgh"]],
            ["Chester County",["eastern Chester County","northern Chester County","southern Chester County"]],
            ["Lancaster County",["Lancaster County","Lancaster"]],
            ["Philadelphia",["Center City, Philadelphia","North Philadelphia","Northeast Philadelphia","Northwest Philadelphia","South Philadelphia","Southwest Philadelphia","West Philadelphia"]]],
        ["Rhode Island",
            ["Providence County",["Providence County","Pawtucket","Providence", "Woonsocket"]]],
        ["South Carolina",
            ["Charleston County",["Charleston County","Charleston"]],
            ["Greenville County",["Greenville County","Greenville"]],
            ["Richland County",["Richland County","Columbia"]],
            ["York County",["York County","Rock Hill"]]],
        ["South Dakota"],
        ["Tennessee"],
        ["Texas",
        	["Harris County", ["downtown Houston, Texas", "Houston Heights, Houston, Texas", "inner Harris County, Texas", "outer Harris County, Texas"]]
        ],
        ["Utah",
            ["Salt Lake County",["Salt Lake County","Salt Lake City"]],
            ["Washington County",["Washington County","Zion National Park"]]],
        ["Vermont"],
        ["Virginia"],
        ["Washington",
            ["King County",["King County","Seattle"]],
            ["Pierce County",["Pierce County","Tacoma"]],
            ["Spokane County",["Spokane County","Spokane"]]],
        ["West Virginia"],
        ["Wisconsin",
            ["Dane County",["Dane County","Madison"]],
            ["Milwaukee County",["Milwaukee County","Milwaukee"]]],
        ["Wyoming"],
        ["Puerto Rico"],
        ["Guam"],
        ["Virgin Islands"],
        ["Northern Mariana Islands"],
        ["American Samoa"],
        ["Federated States of Micronesia"],
        ["Palau"],
        ["Marshall Islands"],
        ["U.S. Minor Outlying Islands"]
    ];

    var states=document.getElementById("duplicates-div").getElementsByClassName("refbegin");
    var currentState=0;
    var result, inThisState;
    for (i=0; i<StateStructure.length; i++) {
        StateSpan.innerHTML = "<br />Now working on individual states... "+i+" of "+StateStructure.length+" complete...";
        var thisstate=[];
        li=states[currentState].getElementsByTagName("li");
        for (j=0; j<li.length; j++) {
            links=li[j].getElementsByTagName("a");
            refnum=li[j].innerHTML.match(/[0-9]{8,9}/)[0];
            titles=[refnum];                                   // make refnum element 0; title=1; county lists=2,3,4,...
            for (l=0; l<links.length; l++) {
                temp=links[l].href.replace(/https:\/\/en\.wikipedia\.org\/w(iki)?\/(index\.php\?title=)?/,"").split("&")[0];
                titles.push(decodeURIComponent(temp).replace(/_/g," "));
            }
            thisstate[thisstate.length]=titles;
        }
        var stateName=getState(thisstate[0][2]);
        wikitext+="=="+StateStructure[i][0]+"==\n";
        while (i<StateStructure.length&&stateName!=StateStructure[i][0]) {
            // if the state we're looking at isn't the next one in the structure array
            var tempstate=[];
            for (j=0; j<multistate.length; j++) {
                inThisState=0;
                for (k=1; k<multistate[j][1].length; k++) {                    // check if any multistate duplicates are
                    if (multistate[j][1][k]==StateStructure[i][0]) inThisState=1;   // duplicated in this state
                }
                if (inThisState==1) {
                    temp=[];
                    for (k=0; k<multistate[j].length; k++) { // make temp static
                        temp[k]=multistate[j][k];
                    }
                    for (k=2; k<temp.length; k++) {
                        if (getState(temp[k])!=StateStructure[i][0]) {temp.splice(k,1); k--} // only pick out those in this state
                    }
                    tempstate[tempstate.length] = [temp[0],temp[1][0],temp[2],temp[3]]; // refnum, title, list1, list2
                    for (k=4; k<temp.length; k++) {
                        tempstate[tempstate.length-1].push(temp[k]); // list3, list4,...
                    }
                }
            }
            result=GenerateDuplicateTable(StateStructure,i,tempstate,stateName,CountySpan,ThisStateSpan);
            wikitext+=result[0];
            switchtabletext+=result[1];

            i++;  // look at next state in StateStructure and repeat until we find the one we're looking at
            StateSpan.innerHTML = "<br />Now working on individual states... "+i+" of "+StateStructure.length+" complete...";
            if (i!=StateStructure.length) wikitext+="=="+StateStructure[i][0]+"==\n";
        }
        if (i!=StateStructure.length) {
            // now we know the state we're looking at has the duplications in thisstate array
            for (j=0; j<multistate.length; j++) {
                inThisState=0;
                for (k=1; k<multistate[j][1].length; k++) {         // check if any multistate duplicates are also
                    if (multistate[j][1][k]==stateName) inThisState=1;   // duplicated in the current state
                }
                if (inThisState==1) {
                    temp=[];
                    for (k=0; k<multistate[j].length; k++) { // make temp static
                        temp[k]=multistate[j][k];
                    }
                    for (k=2; k<temp.length; k++) {
                        if (getState(temp[k])!=stateName) {temp.splice(k,1); k--} // only pick out those in this state
                    }
                    thisstate[thisstate.length] = [temp[0],temp[1][0],temp[2],temp[3]]; // refnum, title, list1, list2
                    for (k=4; k<temp.length; k++) {
                        thisstate[thisstate.length-1].push(temp[k]); // list3, list4,...
                    }
                }
            }
            result=GenerateDuplicateTable(StateStructure,i,thisstate,stateName,CountySpan,ThisStateSpan);
            wikitext+=result[0];
            switchtabletext+=result[1];

            CountySpan.innerHTML="<br />&nbsp;";
            ThisStateSpan.innerHTML="<br />&nbsp;";
            if (currentState<states.length-1) currentState++;  // next state on the page
        }
    }
    CountySpan.innerHTML="";
    ThisStateSpan.innerHTML="";
    StateSpan.innerHTML = "<br />Now working on individual states... Complete!";
    ProgressDiv.innerHTML+="<br />Now outputting data to ";
    ProgressDiv.innerHTML+="<a href='http://wiki.riteme.site/wiki/WP:NRHPPROGRESS/Duplicates'>WP:NRHPPROGRESS/Duplicates</a>...";

    switchtabletext+="|error</td><td>error</td><td>-</td><td>error</td><td>-</td><td>error</td><td>error</td><td>error</td><td>";
    switchtabletext+="-</td><td>error</td><td>error</td><td>-</td>\n}}</includeonly><noinclude>\n";

    var TOCtext="__NOTOC__\n{{anchor|top}}\n{| style=\"margin:.5em; border:1px solid #000; padding:0;";
    TOCtext+=" text-align:center\"\n| '''[[#National|National]]'''\n<hr />\n";

    for (i=0; i<StateStructure.length; i++) {
        TOCtext+="[[#"+StateStructure[i][0];
        if (StateStructure[i].length>1) TOCtext+=" Statewide";
        TOCtext+="|"+StateStructure[i][0]+"]] ";
        for (j=1; j<StateStructure[i].length; j++) {
            if (j==1) TOCtext+="(";
            TOCtext+="[[#"+StateStructure[i][j][0]+", "+StateStructure[i][0]+"|";
            TOCtext+=StateStructure[i][j][0].replace(", "+StateStructure[i][0],"").replace(" County","").replace("City of ","");
            TOCtext+="]], ";
        }
        if (StateStructure[i].length>1) {
            TOCtext=TOCtext.substr(0,TOCtext.length-2);  // remove final comma
            TOCtext+=")";
        }
        if (StateStructure[i][0]=="Wyoming") TOCtext+="\n<hr />\n";
        else if (i!=StateStructure.length-1) TOCtext+=" – ";
    }
    TOCtext+="\n|}\n\n";

    wikitext=switchtabletext+TOCtext+wikitext+"</noinclude>";

    var d=new Date();
    var months = ['January','February','March','April','May','June','July','August','September','October','November','December'];
    var year=d.getYear();
    if (year < 1000) year += 1900;
    var DateStr = months[d.getMonth()]+" "+d.getDate()+", "+year;

    // edit page with total wikitext
    NRISOnlyDump({
        title: 'Wikipedia:WikiProject National Register of Historic Places/Progress/Duplicates',
        text: wikitext,
    summary:'Generate statistics about duplicates in NRHP lists as of '+DateStr+CurrentOperatorEditSummary
    },ProgressDiv,0,"duplicates");
}

var TaggingState=null;
// tag/untag pages with the bot
function TagNRISOnly() {
    var tobetagged=document.getElementById('tobetagged-div');
    var tobeuntagged=document.getElementById('tobeuntagged-div');
    var ProgressDiv = document.getElementById('ProgressDiv');

    var HiddenSpan=document.createElement("span");
    HiddenSpan.setAttribute("id", "HiddenSpan");
    HiddenSpan.setAttribute("style", "display:none");
    ProgressDiv.appendChild(HiddenSpan);

    var d=new Date();
    var months = ['January','February','March','April','May','June','July','August','September','October','November','December'];
    var year=d.getYear();
    if (year < 1000) year += 1900;
    var DateStr = months[d.getMonth()]+" "+year;
    
    TaggingState = {
    	TagProgress: null,
    	TagLinks: null,
    	TagSections: null,
    	TagTotal: 0,
    	TagSkipped: 0,
    	TagTagged: 0,
    	TagDone: 0,
    	UntagProgress: null,
    	UntagLinks: null,
    	UntagSections: null,
    	UntagTotal: 0,
    	UntagSkipped: 0,
    	UntagUntagged: 0,
    	UntagDone: 0
    };

    var i, j, total;

    if (tobetagged!==null) {
        ProgressDiv.innerHTML = "Tagging articles that need to be tagged...";
        TaggingState.TagProgress = document.createElement("span");
        TaggingState.TagProgress.setAttribute("id", "ProgressSpan");
        ProgressDiv.appendChild(TaggingState.TagProgress);
        TaggingState.TagProgress.innerHTML="<br />";

        TaggingState.TagSections=tobetagged.getElementsByClassName('refbegin');
        TaggingState.TagTotal=0;
        for (i=0; i<TaggingState.TagSections.length; i++) {
            TaggingState.TagLinks=TaggingState.TagSections[i].getElementsByTagName('a');
            TaggingState.TagTotal+=TaggingState.TagLinks.length;
        }
        TaggingState.TagProgress.innerHTML="<br />0 of "+TaggingState.TagTotal+" articles examined: 0 tagged, 0 skipped...";
        for (i=0; i<TaggingState.TagSections.length; i++) {
            links=TaggingState.TagSections[i].getElementsByTagName('a');
            for (j=0; j<links.length; j++) {
                var wikitext=getNRISOnlyWikitext(links[j].title);
                if (wikitext!="error") {
                    if (wikitext.match(/{{( |[\r\n])*?NRIS[\ -]only(.|[\r\n])*?}}/g)!==null) {  // don't tag if already tagged
                        TaggingState.TagDone++;
                        TaggingState.TagSkipped++;
                        links[j].style.backgroundColor = "yellow";
                        TaggingState.TagProgress.innerHTML="<br />"+TaggingState.TagDone+" of "+TaggingState.TagTotal+" articles examined: "+TaggingState.TagTagged+" tagged, ";
                        TaggingState.TagProgress.innerHTML+=TaggingState.TagSkipped+" skipped...";
                        continue;
                    }
                    var result=NRISOnlyQuery([links[j].title],HiddenSpan);
                    if (result[0][0]!=links[j].title) {     // check again to make sure list isn't outdated
                        TaggingState.TagDone++;
                        TaggingState.TagSkipped++;
                        links[j].style.backgroundColor = "yellow";
                        TaggingState.TagProgress.innerHTML="<br />"+TaggingState.TagDone+" of "+TaggingState.TagTotal+" articles examined: "+TaggingState.TagTagged+" tagged, ";
                        TaggingState.TagProgress.innerHTML+=TaggingState.TagSkipped+" skipped...";
                        continue;
                    }

                    var multipleIssues=wikitext.match(/{{( )*?(multiple|many|mi|article|issues)( )?(issues)?( |[\r\n])*?\|/gi);
                    var NRISstr="{{NRIS-only|date="+DateStr+"}}";

                    if (multipleIssues!==null) {           // if multiple issues tag, add NRIS-only to bottom of list
                        var open=1;
                        var start=wikitext.indexOf(multipleIssues[0]);
                        var index=start+multipleIssues[0].length;
                        while (open!==0 && index<wikitext.length) {
                            if (wikitext.substr(index,2)=="}}") {
                                open--;
                                index++;
                            } else if (wikitext.substr(index,2)=="{{") {
                                open++;
                                index++;
                            }
                            index++;
                        }
                        index-=2;
                        var full=wikitext.substr(start,index-start);
                        if (wikitext.substr(index-1,1)!="\n") full+="\n";
                        var oldsyntax=full.substr(2,full.length-2).match(/{{/g);
                        if (oldsyntax===null) {    // if using old syntax
                            full = full.replace(/\|( )*?one( )?source( )*?=(.|[\r\n])*?(\||$)/g, "|"); // replace "|one source"
                            if (full.substr(full.length-1,1)=="|") {
                                full+="\n";
                            } else {
                                NRISstr="|"+NRISstr;
                            }
                        } else {                  // replace "{{one source}}"
                            full = full.replace(/{{( )*?(one|single|1)( |-)?(source|ref)( |[\r\n])*?\|(.|[\r\n])*?}}[\r\n]?/gi,"");
                        }
                        full+=NRISstr+"\n";
                        wikitext = wikitext.substr(0,start)+full+wikitext.substr(index,wikitext.length-index);
                    } else {
                      wikitext=wikitext.replace(/{{( )*?(one|single|1)( |-)?(source|ref)( |[\r\n])*?\|(.|[\r\n])*?}}[\r\n]?/gi,"");
                        wikitext = NRISstr+"\n"+wikitext;
                    }
                    NRISOnlyDump({
                        title: links[j].title,
                        text: wikitext,
                        summary:'Tag as only sourced to the [[National Register Information System]]'+CurrentOperatorEditSummary
                    },TaggingState.TagProgress,TaggingState.TagDone,"tag");
                    TaggingState.TagDone++;
                    TaggingState.TagTagged++;
                    TaggingState.TagProgress.innerHTML="<br />"+TaggingState.TagDone+" of "+TaggingState.TagTotal+" articles examined: "+TaggingState.TagTagged+" tagged, ";
                    TaggingState.TagProgress.innerHTML+=TaggingState.TagSkipped+" skipped...";
                } else {
                    TaggingState.TagDone++;
                    TaggingState.TagSkipped++;
                    links[j].style.backgroundColor = "orange";
                    TaggingState.TagProgress.innerHTML="<br />"+TaggingState.TagDone+" of "+TaggingState.TagTotal+" articles examined: "+TaggingState.TagTagged+" tagged, ";
                    TaggingState.TagProgress.innerHTML+=TaggingState.TagSkipped+" skipped...";
                }
            }
        }
        TaggingState.TagProgress.innerHTML="<br />Tagging complete! "+TaggingState.TagTagged+" articles tagged successfully! "+TaggingState.TagSkipped;
        TaggingState.TagProgress.innerHTML+=" skipped as unnecessary.";
    }
    if (tobeuntagged!==null) {
        var str="";
        if (tobetagged!==null) {
            str="<br />Now u";
        } else {
            str="U";
        }
        ProgressDiv.innerHTML+=str+"ntagging articles that need to be untagged...";
        TaggingState.UntagProgress = document.createElement("span");
        TaggingState.UntagProgress.setAttribute("id", "ProgressSpan2");
        ProgressDiv.appendChild(TaggingState.UntagProgress);
        TaggingState.UntagProgress.innerHTML="<br />";

        TotalDone=0;

        TaggingState.UntagSections=tobeuntagged.getElementsByClassName('refbegin');
        total=0;
        for (i=0; i<TaggingState.UntagSections.length; i++) {
            links=TaggingState.UntagSections[i].getElementsByTagName('a');
            TaggingState.UntagTotal+=links.length;
        }
        TaggingState.UntagUntagged=0;
        TaggingState.UntagSkipped=0;
        TaggingState.UntagProgress.innerHTML="<br />0 of "+TaggingState.UntagTotal+" articles examined: 0 untagged, 0 skipped...";

        for (i=0; i<TaggingState.UntagSections.length; i++) {
            links=TaggingState.UntagSections[i].getElementsByTagName('a');
            for (j=0; j<links.length; j++) {
                var wikitext=getNRISOnlyWikitext(links[j].title);
                if (wikitext!="error") {
                    if (wikitext.match(/{{( |[\r\n])*?NRIS[\ -]only(.|[\r\n])*?}}[\r\n]?/g)===null) { //don't untag if already untagged
                        TaggingState.UntagDone++;
                        TaggingState.UntagSkipped++;
                        links[j].style.backgroundColor = "yellow";
                        TaggingState.UntagProgress.innerHTML="<br />"+TaggingState.UntagDone+" of "+TaggingState.UntagTotal+" articles examined: "+TaggingState.UntagUntagged+" untagged, ";
                        TaggingState.UntagProgress.innerHTML+=TaggingState.UntagSkipped+" skipped...";
                        continue;
                    }
                    var result=NRISOnlyQuery([links[j].title],HiddenSpan);
                    if (result[1][0]!=links[j].title) {     // check again to make sure list isn't outdated
                        TaggingState.UntagDone++;
                        TaggingState.UntagSkipped++;
                        links[j].style.backgroundColor = "yellow";
                        TaggingState.UntagProgress.innerHTML="<br />"+TaggingState.UntagDone+" of "+TaggingState.UntagTotal+" articles examined: "+TaggingState.UntagUntagged+" untagged, ";
                        TaggingState.UntagProgress.innerHTML+=TaggingState.UntagSkipped+" skipped...";
                        continue;
                    }

                    var multipleIssues=wikitext.match(/{{( )*?(multiple|many|mi|article|issues)( )?(issues)?( |[\r\n])*?\|/gi);

                    if (multipleIssues!==null) {     // if multiple issues tag, check to see we're not leaving only one tag behind
                        var open=1;
                        var start=wikitext.indexOf(multipleIssues[0]);
                        var index=start+multipleIssues[0].length;
                        while (open!==0 && index<wikitext.length) {
                            if (wikitext.substr(index,2)=="}}") {
                                open--;
                                index++;
                            } else if (wikitext.substr(index,2)=="{{") {
                                open++;
                                index++;
                            }
                            index++;
                        }
                        index-=2;
                        var full=wikitext.substr(start,index-start);
                        var oldsyntax=full.substr(2,full.length-2).match(/{{( )*?[^(NRIS)](.|[\r\n])*?}}/g); //match non-NRIS temp

						var numberTemplates;
                        if (oldsyntax===null) {
                            numberTemplates=full.substr(2,full.length-2).match(/\|/g).length - 2;
                        } else {
                            numberTemplates=oldsyntax.length;
                        }

                        if (numberTemplates===0) {      // if NRIS-only is only template in multiple issues, remove entire block
                            wikitext=wikitext.replace(full+"}}\n","");
                        } else if (numberTemplates===1 && oldsyntax!==null) {       // if one template left, remove multiple issues
                            wikitext = wikitext.replace(full+"}}", oldsyntax[0]);  // and replace with other template
                        } else {      // just remove if more than one other or if using old syntax
                            wikitext = wikitext.replace(/\|?{{( |[\r\n])*?NRIS[\ -]only(.|[\r\n])*?}}[\r\n]?/g, '');
                        }
                    } else {
                        wikitext = wikitext.replace(/{{( |[\r\n])*?NRIS[\ -]only(.|[\r\n])*?}}[\r\n]?/g, '');
                    }

                    NRISOnlyDump({
                        title: links[j].title,
                        text: wikitext,
                        summary:'Remove [[Template:NRIS-only]]; article has more than one reference'+CurrentOperatorEditSummary
                    },TaggingState.UntagProgress,TotalDone,"tag");
                    TaggingState.UntagDone++;
                    TaggingState.UntagUntagged++;
                    TaggingState.UntagProgress.innerHTML="<br />"+TaggingState.UntagDone+" of "+TaggingState.UntagTotal+" articles examined: "+TaggingState.UntagUntagged+" untagged, ";
                    TaggingState.UntagProgress.innerHTML+=TaggingState.UntagSkipped+" skipped...";
                } else {
                    TaggingState.UntagDone++;      // skip if wikitext error
                    TaggingState.UntagSkipped++;
                    links[j].style.backgroundColor = "orange";
                    TaggingState.UntagProgress.innerHTML="<br />"+TaggingState.UntagDone+" of "+TaggingState.UntagTotal+" articles examined: "+TaggingState.UntagUntagged+" untagged, ";
                    TaggingState.UntagProgress.innerHTML+=TaggingState.UntagSkipped+" skipped...";
                }
            }
        }
        TaggingState.UntagProgress.innerHTML="<br />Untagging complete! "+TaggingState.UntagUntagged+" articles untagged successfully! "+TaggingState.UntagSkipped;
        TaggingState.UntagProgress.innerHTML+=" skipped as unnecessary.";
    }
}

function getDuplicateStats2(ctx) {

    var IllustratedStr = "illustrated";
    var countytext="";
    var Illustrated=0;
    var j, page;
    var item = ctx.entry;

	ctx.errors = [];

	ctx.StatsStr = "articled, unillustrated, Stub-class, NRIS-only";
	
	var countyPromises = [];
	
	for (j=0; j<item.counties.length; j++) {
    	var county = item.counties[j];

// asyncNRISOnlyWikitext(title,ctx,successfn,errorfn,completefn)
	}
	//console.log('getDuplicateStats2', ctx);
	return Promise.resolve(ctx);
	
}

function getDuplicateStats(item) {
        var IllustratedStr = "illustrated";
        var countytext="";
        var Illustrated=0;
        var j, page;
        for (j=2; j<item.length; j++) {
            countytext=getNRISOnlyWikitext(item[j]);
            if (countytext!="error") {
                var StartIndex = 0;
                var str = "{{NRHP row";
                var skip = str.length;
                var index, RowLocations = [];
                while ((index = countytext.indexOf(str, StartIndex)) > -1) {
                    RowLocations.push(index);
                    StartIndex = index + skip;
                }
                RowLocations.push(countytext.length);    // if duplicated entry happens to be last in table

                var k=0;
                while (RowLocations[k]<countytext.indexOf(item[0])) {
                    k++;
                }
                var CountyRow=countytext.substr(RowLocations[k-1],RowLocations[k]-RowLocations[k-1]);
                CountyRow=CountyRow.replace(/<\!\-\-(.|[\r\n])*?\-\-\>/g, "");      // get rid of commented out pictures
                if (CountyRow.match(/\|[ ]*?image[ ]*?=[ ]*?[a-zA-Z0-9]/g)!==null) Illustrated++;
            } else {
                alert("Error checking duplicate stats! Script aborted!");
                return;
            }
        }

        if (Illustrated!==item.length-2&&Illustrated!==0) IllustratedStr="partially-"+IllustratedStr;
        if (Illustrated===0) IllustratedStr="un"+IllustratedStr;

        var ArticledStr="articled";
        var AssessmentStr="";
        var NRISStr="";
        var title="";

        if(Object.prototype.toString.call(item[1]) === '[object Array]') { // if in multistate, pick out first item in array
            title=item[1][0];
        } else {
            title = item[1];
        }

        var nrisonlyquery=JSON.parse(  // check if NRIS-only
            $.ajax({
                dataType: "json",
                url: mw.util.wikiScript('api'),
                data: {
                    format: 'json',
                    action: 'query',
                    prop: 'categories',
                    clcategories: 'Category:All articles sourced only to NRIS',
                    cllimit: 'max',
                    titles: title,
                    redirects: 'true'
                },
                async:false
            })
            .responseText
        );

        if (nrisonlyquery.query.redirects) { // resolve any redirects
            for (var r in nrisonlyquery.query.redirects) {
                title=nrisonlyquery.query.redirects[r].to;
            }
        }

        for (page in nrisonlyquery.query.pages) {
            if (typeof nrisonlyquery.query.pages[page].missing!="undefined") {
                ArticledStr="un"+ArticledStr;
                continue;
            }
            if (nrisonlyquery.query.pages[page].categories) {
                NRISStr="NRIS-only";
            }
        }

        var catlist='Category:FA-Class National Register of Historic Places articles';
        catlist+='|Category:A-Class National Register of Historic Places articles';
        catlist+='|Category:GA-Class National Register of Historic Places articles';
        catlist+='|Category:B-Class National Register of Historic Places articles';
        catlist+='|Category:C-Class National Register of Historic Places articles';
        catlist+='|Category:Start-Class National Register of Historic Places articles';
        catlist+='|Category:Stub-Class National Register of Historic Places articles';
        catlist+='|Category:Unassessed National Register of Historic Places articles';
        catlist+='|Category:FL-Class National Register of Historic Places articles';
        catlist+='|Category:List-Class National Register of Historic Places articles';
        catlist+='|Category:Redirect-Class National Register of Historic Places articles';

        if (ArticledStr!="unarticled") {
            var statsquery=JSON.parse(  // look at quality stats
                $.ajax({
                    dataType: "json",
                    url: mw.util.wikiScript('api'),
                    data: {
                        format: 'json',
                        action: 'query',
                        prop: 'categories',
                        clcategories: catlist,
                        cllimit: 'max',
                        titles: 'Talk:'+title,
                        redirects: 'true'
                    },
                    async:false
                })
                .responseText
            );

            for (page in statsquery.query.pages) {
                var tagged = "no";
                if (statsquery.query.pages[page].categories) {
                    tagged = "yes";
                    for (var category in statsquery.query.pages[page].categories) {
                        var CatTitle=statsquery.query.pages[page].categories[category].title;
                        if (CatTitle.indexOf("Stub")!=-1) {
                            AssessmentStr="Stub-class";
                            continue;
                        }
                        if (CatTitle.indexOf("Unassessed")!=-1 || CatTitle.indexOf("Redirect")!=-1) { // also count rdr unassessed
                            AssessmentStr="unassessed";
                            continue;
                        }
                        if  (CatTitle.indexOf("List") != -1 || CatTitle.indexOf("FL-Class") != -1) {
                            if (statsquery.query.pages[page].title.indexOf("National Register of Historic Places")!=-1){
                            	// references into NRHP list considered unarticled
                                ArticledStr="un"+ArticledStr;
                                continue;
                            } else {
                            	// references into other lists considered stub class
                                AssessmentStr="Stub-class";
                                continue;
                            }
                        }
                        AssessmentStr="Start+";
                    }
                }
                if (tagged=="no") {
                    AssessmentStr="untagged";
                }
            }
        }

        var StatsStr = ArticledStr+", "+IllustratedStr;
        if (AssessmentStr!=="") StatsStr+=", "+AssessmentStr;
        if (NRISStr!=="") StatsStr+=", "+NRISStr;

        return StatsStr;
}

function GenerateDuplicateTable(StateStructure,i,list,stateName,CountySpan,ThisStateSpan) {
    var wikitext="";
    var switchtabletext="";
    // check county-level duplicates
    CountySpan.innerHTML="<br />First checking county-level duplications in "+stateName+"...";
    var NumberFound=0;
    var j, k, l, m;

    for (j=1; j<StateStructure[i].length; j++) {
        wikitext+="==="+StateStructure[i][j][0]+", "+StateStructure[i][0]+"===\n";
        var TotalCountyDuplicates=0;
        var TotalCountyArticled=0;
        var TotalCountyIllustrated=0;
        var TotalCountyStub=0;
        var TotalCountyNRISOnly=0;
        var TotalCountyStartPlus=0;
        var TotalCountyUnassessed=0;
        var TotalCountyUntagged=0;

        for (k=0; k<list.length; k++) {
            var numberlists=list[k].length-2;
            var NumberInThisCounty=0;
            var temp=[];
            for (l=0; l<list[k].length; l++) { // make temp static
                temp[l]=list[k][l];
            }
            for (l=2; l<temp.length; l++) {
                var ListName=temp[l].replace(/National Register of Historic Places listings (i|o)n /g,"");
                ListName=ListName.replace(", "+stateName,"");
                var oldnumber=NumberInThisCounty;
                for (m=0; m<StateStructure[i][j][1].length; m++) {
                    if (ListName==StateStructure[i][j][1][m]) NumberInThisCounty++;
                }
                if (NumberInThisCounty==oldnumber) {temp.splice(l,1); l--;}  // get rid of sublists not in this county
            }
            if (NumberInThisCounty>1) {
                NumberFound++;
                CountySpan.innerHTML="<br />First checking county-level duplications in "+stateName+"... ";
                CountySpan.innerHTML+=NumberFound+" found so far...";
                TotalCountyDuplicates++;
                if (TotalCountyDuplicates==1) {
                    wikitext+="{| class=\"wikitable sortable\" width=100%\n! {{NRHP color}} width=30% | Site\n! {{NRHP ";
                    wikitext+="color}} width=30% | Lists\n! {{NRHP color}} width=10% | Number of Duplicates\n! {{NRHP ";
                    wikitext+="color}} width=30% colspan=7 | Stats\n";
                }
                wikitext+="|-\n| [["+temp[1]+"]] (#"+temp[0]+")\n|\n";
                for (m=2; m<temp.length; m++) {
                    wikitext+="*[["+temp[m]+"|"+temp[m].replace(/National Register of Historic Places listings (i|o)n /g,"");
                    wikitext+="]]\n";
                }
                var duplications=NumberInThisCounty-1;
                TotalCountyDuplicates+=duplications-1;
                wikitext+="| align=center | "+duplications+"\n| colspan=7 | ";

                var StatsStr=getDuplicateStats(temp);
                if (StatsStr.indexOf("unarticled")==-1) TotalCountyArticled+=duplications;
                if (StatsStr.indexOf(", illustrated")!=-1) TotalCountyIllustrated+=duplications;
                if (StatsStr.indexOf("Stub-class")!=-1) TotalCountyStub+=duplications;
                if (StatsStr.indexOf("NRIS-only")!=-1) TotalCountyNRISOnly+=duplications;
                if (StatsStr.indexOf("Start+")!=-1) TotalCountyStartPlus+=duplications;
                if (StatsStr.indexOf("unassessed")!=-1) TotalCountyUnassessed+=duplications;
                if (StatsStr.indexOf("untagged")!=-1) TotalCountyUntagged+=duplications;

                wikitext+=StatsStr+"\n";

                if (NumberInThisCounty==numberlists) {  // if only duplicated inside this county, get rid of it
                    list.splice(k,1);
                    k--;
                }
            }
        }
        if (TotalCountyDuplicates===0) {
            wikitext+="There are no duplications across sublists of "+StateStructure[i][j][0];
            wikitext+=", "+StateStructure[i][0]+".\n\n";

            switchtabletext+="    |"+StateStructure[i][j][0]+", "+StateStructure[i][0]+"=0</td><td>0</td><td>-</td><td>";
            switchtabletext+="0</td><td>-</td><td>0</td><td>0</td><td>0</td><td>-</td><td>0</td><td>0</td><td>-</td>\n";

            CountySpan.innerHTML="<br />First checking county-level duplications in "+stateName+"... None found.";
        } else {
            wikitext+="|-\n! colspan=2 | Total\n! "+TotalCountyDuplicates+"\n! "+TotalCountyIllustrated+"\n! ";
            wikitext+=TotalCountyArticled+"\n! "+TotalCountyStub+"\n! "+TotalCountyNRISOnly+"\n! "+TotalCountyStartPlus;
            wikitext+="\n! "+TotalCountyUnassessed+"\n! "+TotalCountyUntagged+"\n|}\n\n";

            switchtabletext+="    |"+StateStructure[i][j][0]+", "+StateStructure[i][0]+"="+TotalCountyDuplicates+"</td><td>";
            switchtabletext+=TotalCountyIllustrated+"</td><td>-</td><td>"+TotalCountyArticled+"</td><td>-</td><td>";
            switchtabletext+=TotalCountyStub+"</td><td>"+TotalCountyNRISOnly+"</td><td>"+TotalCountyStartPlus;
            switchtabletext+="</td><td>-</td><td>"+TotalCountyUnassessed+"</td><td>"+TotalCountyUntagged+"</td><td>-</td>\n";

            CountySpan.innerHTML="<br />First checking county-level duplications in "+stateName+"... Complete! "+TotalCountyDuplicates+" found.";
        }
    }
    if (StateStructure[i].length==1) {
        CountySpan.innerHTML="<br />First checking county-level duplications in "+stateName+"... None found.";
    }
    ThisStateSpan.innerHTML="<br />Now checking statewide duplicates...";
    if (StateStructure[i].length>1) wikitext+="==="+StateStructure[i][0]+" Statewide===\n";
    if (list.length===0) {
        wikitext+="There are no duplications across sublists of "+StateStructure[i][0]+".\n\n";

        switchtabletext+="|"+StateStructure[i][0]+"=0</td><td>0</td><td>-</td><td>0</td><td>-</td><td>0</td><td>0</td><td>0";
        switchtabletext+="</td><td>-</td><td>0</td><td>0</td><td>-</td>\n";
    } else {
        var TotalStateDuplicates=0;
        var TotalStateArticled=0;
        var TotalStateIllustrated=0;
        var TotalStateStub=0;
        var TotalStateNRISOnly=0;
        var TotalStateStartPlus=0;
        var TotalStateUnassessed=0;
        var TotalStateUntagged=0;

        wikitext+="{| class=\"wikitable sortable\" width=100%\n! {{NRHP color}} width=30% | Site\n! {{NRHP ";
        wikitext+="color}} width=30% | Lists\n! {{NRHP color}} width=10% | Number of Duplicates\n! {{NRHP ";
        wikitext+="color}} width=30% colspan=7 | Stats\n";

        for (j=0; j<list.length; j++) {
            ThisStateSpan.innerHTML="<br />Now checking statewide duplicates... ";
            ThisStateSpan.innerHTML+=j+" of "+list.length+" examined so far...";

            wikitext+="|-\n| [["+list[j][1]+"]] (#"+list[j][0]+")\n|\n";
            for (m=2; m<list[j].length; m++) {
                wikitext+="*[["+list[j][m]+"|";
                wikitext+=list[j][m].replace(/National Register of Historic Places listings (i|o)n /g,"")+"]]\n";
            }

            var thisDuplicateStates=[];
            for (l=2; l<list[j].length; l++) {
                var ListName=list[j][l].replace(/National Register of Historic Places listings (i|o)n /g,"");
                ListName=ListName.replace(", "+stateName,"");
                thisDuplicateStates.push(ListName);
                for (k=1; k<StateStructure[i].length; k++) {
                    for (m=0; m<StateStructure[i][k][1].length; m++) {
                        if (ListName==StateStructure[i][k][1][m]) {
                            thisDuplicateStates[thisDuplicateStates.length-1]=StateStructure[i][k][0];
                        }
                    }
                }
            }

            for (l=0;l<thisDuplicateStates.length; l++) {    // shrink list if duplicated inside single county
                for (m=l+1;m<thisDuplicateStates.length; m++) {
                    if (thisDuplicateStates[l]==thisDuplicateStates[m]) {thisDuplicateStates.splice(m,1);m--;}
                }
            }
            var duplications=thisDuplicateStates.length-1;

            TotalStateDuplicates+=duplications;
            wikitext+="| align=center | "+duplications+"\n| colspan=7 | ";

            var StatsStr=getDuplicateStats(list[j]);
            if (StatsStr.indexOf("unarticled")==-1) TotalStateArticled+=duplications;
            if (StatsStr.indexOf(", illustrated")!=-1) TotalStateIllustrated+=duplications;
            if (StatsStr.indexOf("Stub-class")!=-1) TotalStateStub+=duplications;
            if (StatsStr.indexOf("NRIS-only")!=-1) TotalStateNRISOnly+=duplications;
            if (StatsStr.indexOf("Start+")!=-1) TotalStateStartPlus+=duplications;
            if (StatsStr.indexOf("unassessed")!=-1) TotalStateUnassessed+=duplications;
            if (StatsStr.indexOf("untagged")!=-1) TotalStateUntagged+=duplications;

            wikitext+=StatsStr+"\n";
        }
        wikitext+="|-\n! colspan=2 | Total\n! "+TotalStateDuplicates+"\n! "+TotalStateIllustrated+"\n! ";
        wikitext+=TotalStateArticled+"\n! "+TotalStateStub+"\n! "+TotalStateNRISOnly+"\n! "+TotalStateStartPlus;
        wikitext+="\n! "+TotalStateUnassessed+"\n! "+TotalStateUntagged+"\n|}\n\n";

        switchtabletext+="|"+StateStructure[i][0]+"="+TotalStateDuplicates+"</td><td>"+TotalStateIllustrated+"</td><td>-</td><td>";
        switchtabletext+=TotalStateArticled+"</td><td>-</td><td>"+TotalStateStub+"</td><td>"+TotalStateNRISOnly+"</td><td>";
        switchtabletext+=TotalStateStartPlus+"</td><td>-</td><td>"+TotalStateUnassessed+"</td><td>"+TotalStateUntagged;
        switchtabletext+="</td><td>-</td>\n";
    }
    return [wikitext,switchtabletext];
}

// subroutine to determine state from list name
function getState(title) {
    var temp=title.split(", ");
    var state=temp[temp.length-1];
    if (state=="Philadelphia") state="Pennsylvania";
    if (temp[0]==state||state.indexOf(':')!=-1) {
        if (title.indexOf("Chicago")!=-1) state="Illinois";
        else if (title.indexOf("Baltimore")!=-1) state="Maryland";
        else if (title.indexOf("Boston")!=-1) state="Massachusetts";
        else if (title.indexOf("Cincinnati")!=-1) state="Ohio";
        else if (title.indexOf("Cleveland")!=-1) state="Ohio";
        else if (title.indexOf("Denver")!=-1) state="Colorado";
        else if (title.indexOf("Detroit")!=-1) state="Michigan";
        else if (title.indexOf("Kansas City")!=-1) state="Missouri";
        else if (title.indexOf("Los Angeles")!=-1) state="California";
        else if (title.indexOf("Manhattan")!=-1) state="New York";
        else if (title.indexOf("Miami")!=-1) state="Florida";
        else if (title.indexOf("Milwaukee")!=-1) state="Wisconsin";
        else if (title.indexOf("Philadelphia")!=-1) state="Pennsylvania";
        else if (title.indexOf("Phoenix")!=-1) state="Arizona";
        else if (title.indexOf("St. Louis")!=-1) state="Missouri";
        else if (title.indexOf("San Francisco")!=-1) state="California";
        else if (title.indexOf("Salt Lake City")!=-1) state="Utah";
        else if (title.indexOf("Zion")!=-1) state="Utah";
        else if (title.indexOf("Guam")!=-1) state="Guam";
        else state=""; // TODO perhaps a better default is to strip "National Register of Historic Places listings in " from title and return that
    }
    return state;
}

function AsyncNRISDump(info,Span,subpages,doneEditing) {
	var thisTitle = title;
    return $.ajax({
        dataType: "json",
        url: mw.util.wikiScript('api'),
        data: {
            format: 'json',
            action: 'query',
            prop: 'revisions',
            rvprop: 'content',
            titles: thisTitle,
            indexpageids: true,
            redirects: 'true'
        },
        async:true,
        success: function (data, textStatus,jqXHR) {
	        if (data && data.edit && data.edit.result && data.edit.result=="Success") {
	            if (doneEditing=="no") {
	                Span.innerHTML="<br>Dumping data to subpages of ";
	                Span.innerHTML+="<a href='http://wiki.riteme.site/wiki/"+OutputBase+"'>"+OutputBase+"</a>... "+subpages+" edits made.";
	            } else if (doneEditing=="yes") {
	                Span.innerHTML="<br>Dumping data to subpages of ";
	                Span.innerHTML+="<a href='http://wiki.riteme.site/wiki/"+OutputBase+"'>"+OutputBase+"</a>... Done! "+subpages;
	                Span.innerHTML+=" subpages edited successfully!";
	            } else if (doneEditing=="NRIS") {
	                Span.innerHTML+="Done! Click link to see output!";
	
	                // output technical information to console
	                var WarningText="NRHP Progress Warnings: ";
	                for (var i=0; i<WarningCount.length; i++) {
	                    WarningText+=WarningCount[i][0]+" ("+WarningCount[i][1]+"), ";
	                }
	                if (WarningCount[0][0]!=="") {
	                    WarningText=WarningText.substr(0,WarningText.length-2);
	                } else {
	                    WarningText="NRHP Progress Warnings: none";
	                }
	                throw(WarningText);
	            } else if (doneEditing=="duplicates") {
	                Span.innerHTML+="<br>Output saved! Click link to see it.";
	            }
	        } else {
	            Span.innerHTML += " Edit of "+thisTitle+" returned an error! Aborting script.";
	            alert('Edit query error:\n'+data.error.code+': '+data.error.info+'\nClick OK to view raw wikitext for latest subpage.');
	            var popup = open("");
	            var div = popup.document.createElement("div");
	            div.innerHTML=thisTitle+"<br><hr><br>"+info.text;
	            popup.document.body.appendChild(div);
	        }
        },
        error: function (jqXHR,textStatus, errorThrown) {
            Span.innerHTML += " The edit query returned an error! Aborting script.";
            alert('Ajax failure. Click OK to view raw wikitext for latest subpage.');
            var popup = open("");
            var div = popup.document.createElement("div");
            div.innerHTML=info.title+"<br><hr><br>"+info.text;
            popup.document.body.appendChild(div);
        },
        complete: function (jqXHR,textStatus) {
        	if (completefn !== null) {
        		completefn(title,ctx,jqXHR,textStatus);
        	}
        }
    });
}

// dump lists to subpages
function NRISOnlyDump(info,Span,subpages,doneEditing) {
    var api = new mw.Api();

    api.postWithToken( "csrf", {
        action: "edit",
        title: info.title,
        summary: info.summary,
        text: info.text,
        bot: 'true'
        },
        {async:false})
    .done( function(data) {
        if (data && data.edit && data.edit.result && data.edit.result=="Success") {
            if (doneEditing=="no") {
                Span.innerHTML="<br>Dumping data to subpages of ";
                Span.innerHTML+="<a href='http://wiki.riteme.site/wiki/"+OutputBase+"'>"+OutputBase+"</a>... "+subpages+" edits made.";
            } else if (doneEditing=="yes") {
                Span.innerHTML="<br>Dumping data to subpages of ";
                Span.innerHTML+="<a href='http://wiki.riteme.site/wiki/"+OutputBase+"'>"+OutputBase+"</a>... Done! "+subpages;
                Span.innerHTML+=" subpages edited successfully!";
            } else if (doneEditing=="NRIS") {
                Span.innerHTML+="Done! Click link to see output!";

                // output technical information to console
                var WarningText="NRHP Progress Warnings: ";
                for (var i=0; i<WarningCount.length; i++) {
                    WarningText+=WarningCount[i][0]+" ("+WarningCount[i][1]+"), ";
                }
                if (WarningCount[0][0]!=="") {
                    WarningText=WarningText.substr(0,WarningText.length-2);
                } else {
                    WarningText="NRHP Progress Warnings: none";
                }
                throw(WarningText);
            } else if (doneEditing=="duplicates") {
                Span.innerHTML+="<br>Output saved! Click link to see it.";
            }
        } else {
            Span.innerHTML += " Edit of "+info.title+" returned an error! Aborting script.";
            alert('Edit query error:\n'+data.error.code+': '+data.error.info+'\nClick OK to view raw wikitext for latest subpage.');
            var popup = open("");
            var div = popup.document.createElement("div");
            div.innerHTML=info.title+"<br><hr><br>"+info.text;
            popup.document.body.appendChild(div);
        }
    })
    .fail( function() {
            Span.innerHTML += " Edit of "+info.title+" returned an error! Aborting script.";
            alert('Ajax failure. Click OK to view raw wikitext for latest subpage.');
            var popup = open("");
            var div = popup.document.createElement("div");
            div.innerHTML=info.title+"<br><hr><br>"+info.text;
            popup.document.body.appendChild(div);
    });
}

function getNRISOnlyProgressPageWikitext(title) {    // asynchronous fetch of Progress page wikitext
    $.ajax({
        dataType: "json",
        url: mw.util.wikiScript('api'),
        data: {
            format: 'json',
            action: 'query',
            prop: 'revisions',
            rvprop: 'content',
            titles: title,
            indexpageids: true,
            redirects: 'true'
        },
		error: function(ajaxResponse,status,errorThrown) {
			wikitext="error";
			console.log("getNRISOnlyProgressPageWikitext failed title="+title,status, errorThrown);
		},
        success: function(output) {
            for (var page in output.query.pages) {
                wikitext=output.query.pages[page].revisions[0]['*'];
            }
        },
        complete: function() {
            if (wikitext=="error") {
                var ProgressDiv=document.getElementById("ProgressDiv");
                ProgressDiv.innerHTML+=" Unable to fetch wikitext! Script aborted.";
            } else {
                SetupNRISOnlyTables();
            }
        }
    });
}

function getNRISOnlyListWikitext(currentTable,currentRow) {   // asynchronous fetch of each list's wikitext
    $.ajax({
        dataType: "json",
        url: mw.util.wikiScript('api'),
        data: {
            format: 'json',
            action: 'query',
            prop: 'revisions',
            rvprop: 'content',
            titles: NRISOnlyStructure[currentTable][currentRow].Link,
            indexpageids: true,
            redirects: 'true'
        },
		error: function(ajaxResponse,status,errorThrown) {
			ajaxResponse.errorThrown=errorThrown;
			console.log("getNRISOnlyListWikitext failed title="+Titles[StartIndex],status, errorThrown);
		},
        complete: function(ajaxResponse,status) {NRISOnlyWikitextFetched(ajaxResponse,status,currentTable,currentRow);}
    });
}

// async version of getNRISOnlyWikitext
// 
// Returns: a Promise object
// Parameters:
//  title: String title of page to fetch
//  ctx: Context object passed unaltered to callbacks
//  successfn: Function to call on success (required)
//   Parameters: title, ctx, data, textStatus, jqXHR
//  errorfn: Function to call on error (optional)
//   Parameters: title,ctx,jqXHR,textStatus,errorThrown
//  completefn: Function to call on request completion (after successfn or errorfn is called) (optional)
//   Parameters: title,ctx,jqXHR,textStatus
//
//  The first two parameters to the callback functions are always the title and ctx passed to this function.  The
//  remaining parameters are those that were passed to the callback in the Ajax call with one exception.
//  The data passed to successfn, instead of being the data blob passed to the Ajax success callback, is first processed
//  by JSON.parse.
function asyncNRISOnlyWikitext(title,ctx,successfn,errorfn,completefn) {
        return $.ajax({
            dataType: "json",
            url: mw.util.wikiScript('api'),
            data: {
                format: 'json',
                action: 'query',
                prop: 'revisions',
                rvprop: 'content',
                titles: title,
                indexpageids: true,
                redirects: 'true'
            },
            async:true,
	        success: function (data, textStatus,jqXHR) {
	        	if (successfn !== null) {
	        		successfn(title,ctx,JSON.parse(data),textStatus,jqXHR);
	        	}
	        },
	        error: function (jqXHR,textStatus, errorThrown) {
	        	if (errorfn !== null) {
	        		errorfn(title,ctx,jqXHR,textStatus,errorThrown);
	        	}
	        	// else we should probably log something
	        },
	        complete: function (jqXHR,textStatus) {
	        	if (completefn !== null) {
	        		completefn(title,ctx,jqXHR,textStatus);
	        	}
	        }
        });
}

function getNRISOnlyWikitext(title) {   // legacy synchronous fetch Wikitext of each page for tagging
    try {
        var output=JSON.parse(
            $.ajax({
                dataType: "json",
                url: mw.util.wikiScript('api'),
                data: {
                    format: 'json',
                    action: 'query',
                    prop: 'revisions',
                    rvprop: 'content',
                    titles: title,
                    indexpageids: true,
                    redirects: 'true'
                },
                async:false
            })
            .responseText
        );
        for (var page in output.query.pages) {
            wikitext = output.query.pages[page].revisions[0]['*'];
        }
        return wikitext;
    }
    catch(err) {
        return "error";
    }
}

// legacy check to see if article is NRIS-only used during tagging
function NRISOnlyQuery(toQuery,SubpageSpan) { // look through wikitext of each article to find NRIS-only ones
    var ToBeTagged=[];
    var ToBeUntagged=[];
    var Unreferenced=[];
    var OneRefNotNRIS=[];
    var AllNRISOnly=[];
    var Substubs=[];
    var Errors=[];
    var isTagged="no";
    var k, l, m;
    for (k=1;k<toQuery.length+1; k++) {
        SubpageSpan.innerHTML = "<br />Finding NRIS-only articles... Querying page "+k+" of "+toQuery.length+" in this county...";
        var wikitext=getNRISOnlyWikitext(toQuery[k-1]);
        isTagged="no";
        if (wikitext!="error") {
            // calculate prose size
            var prose=wikitext.replace(/<\!\-\-(.|[\r\n])*?\-\-\>/g,"");                        // strip comments
            prose=prose.replace(/<ref[^e](.|[\r\n])*?([ ]*?\/|<\/ref[ ]*?)\>/gi,"");           // strip refs
            prose=prose.replace(/==[ ]*?External links[ ]*?==(.|\n)*?(?=(==.*?==|$))/gi,"");     // strip external links section
            prose=prose.replace(/==[ ]*?See also[ ]*?==(.|\n)*?(?=(==.*?==|$))/gi,"");           // strip see also section
            prose=prose.replace(/==[ ]*?(References|Notes)[ ]*?==(.|\n)*?(?=(==.*?==|$))/gi,""); // strip references section
            // strip further reading section
            prose=prose.replace(/==[ ]*?(Further|Additional) reading[ ]*?==(.|\n)*?(?=(==.*?==|$))/gi,"");
            prose=prose.replace(/={2,5}.*?={2,5}/g,"");                                          // strip section titles
            // replace wikilinks with displayed text
            prose=prose.replace(/\[\[(?![ ]*?Category:|[ ]*?Image:|[ ]*?File:)([^\]]*?\|)?(.*?)\]\]/gi,"$2");
            prose=prose.replace(/\[[ ]*?http.*? (.*?)\]/g,"$1");                 // replace inline external links with displayed text
            prose=prose.replace(/'{2,5}(.*?)'{2,5}/g,"$1");                      // replace bold/italic with displayed text
            prose=prose.replace(/\[\[[ ]*?Category:.*?\]\]/g,"");                                // strip categories
            prose=prose.replace(/\[\[[ ]*?(Image|File):.*?\]\]/g,"");                            // strip images
            prose=prose.replace(/<[ ]*?gallery(.|\n)*?<[ ]*?\/[ ]*?gallery[ ]*?\>/gi,"");      // strip galleries
            while(true) {                                                                       // strip templates
                var str="{{";
                var start=prose.indexOf(str);
                if (start==-1) break;
                var open=1;
                var index=start+str.length;
                while (open!==0 && index<prose.length) {
                    if (prose.substr(index,2)=="}}") {
                        open--;
                        index++;
                    } else if (prose.substr(index,2)=="{{") {
                        open++;
                        index++;
                    }
                    index++;
                }
                prose=prose.replace(prose.substr(start,index-start),"");
            }
            prose=prose.replace(/{\|(.|\n)*?\|}/g,"");                   // strip tables
            prose=prose.replace(/&nbsp;/g," ");                          // replace nbsp with regular space
            prose=prose.replace(/<[ ]*?br.*?>/g, "\n");                  // replace HTML line break with string line break
            prose=prose.replace(/[ \n]+/g," ");                          // replace multiple spaces/linebreaks with single space
            prose=prose.trim();

            if (prose.length<325) Substubs[Substubs.length]=[toQuery[k-1],prose.length];

            if (wikitext.match(/{{( |[\r\n])*?NRIS[\ -]only(.|[\r\n])*?}}/g)!==null) isTagged="yes";
            if (wikitext.indexOf("{{GR")!=-1||wikitext.indexOf("{{sfn")!=-1||wikitext.indexOf("{{Sfn")!=-1) {
                if (isTagged=="yes") ToBeUntagged.push(toQuery[k-1]);
                continue;
            }
            var Refs=wikitext.match(/<ref[^e](.|[\r\n])*?([ ]*?\/|<\/ref[ ]*?)\>/gi);
            var comments=wikitext.match(/<\!\-\-(.|[\r\n])*?\-\-\>/g);

            if (Refs===null) {     // if no refs, count as unreferenced
                Unreferenced.push(toQuery[k-1]);
                continue;
            }

            if (comments!==null) {
                for (l=0; l<comments.length; l++) {
                    var CommentedRefs=comments[l].match(/<ref[^e](.|[\r\n])*?([ ]*?\/|<\/ref[ ]*?)\>/gi);
                    if (CommentedRefs===null) continue;
                    for (m=0; m<CommentedRefs.length; m++) {
                        for (var n=0; n<Refs.length; n++) {
                            if (Refs[n]==CommentedRefs[m]) {Refs.splice(n,1); n--;}
                        }
                    }
                }
            }

            if (Refs.length===0) {    // if all refs commented out, count as unreferenced
                Unreferenced.push(toQuery[k-1]);
                continue;
            }

            var citesNRIS="no";
            for (l=0; l<Refs.length; l++) {
                if (Refs[l].indexOf("{{NRISref")!=-1) citesNRIS="yes";
            }

            var namedRefs=[];
            var Duplications=[];
            for (l=0; l<Refs.length; l++) {
                var nameOfRef=Refs[l].match(/name[ ]*?=.*?(\/|\>)/gi);
                if (nameOfRef===null) {
                    continue;
                } else {
                    nameOfRef = nameOfRef[0].replace(/("| )/g,'');
                    nameOfRef = nameOfRef.substr(5,nameOfRef.length-6);
                }
                namedRefs.push(nameOfRef);
                Duplications.push(1);
                for (m=0; m<namedRefs.length-1; m++) {   // if title is duplicated, count how many times
                    if (nameOfRef==namedRefs[m]) {
                        namedRefs.splice(m,1);
                        Duplications[Duplications.length-1] = Duplications[m] + 1;
                        Duplications.splice(m,1);
                        m--;
                    }
                }
            }
            if (namedRefs.length==2&&namedRefs[0]==namedRefs[1]) Duplications[0]++; // fix for if all refs are same
            var toSubtract=0;
            for (l=0; l<Duplications.length; l++) {
                toSubtract = toSubtract + Duplications[l] - 1;
            }

            var DistinctRefs = Refs.length-toSubtract;

            if (DistinctRefs>1) {
                if (isTagged=="yes") ToBeUntagged.push(toQuery[k-1]); // untag if ref has been added since tag placed
                continue;
            }
            if (citesNRIS=="no") {OneRefNotNRIS.push(toQuery[k-1]);continue;} // if only one ref and not NRIS, count that
            if (isTagged=="no") ToBeTagged.push(toQuery[k-1]); // only push if the only ref is NRISref and it's not already tagged
            AllNRISOnly.push(toQuery[k-1]);
        } else {
            Errors.push(toQuery[k-1]);
            continue;
        }
    }
    return [ToBeTagged,ToBeUntagged,Unreferenced,OneRefNotNRIS,AllNRISOnly,Substubs,Errors];
}

//$(window).on('load',CheckPermission);
$.when($.ready).then(CheckPermission);