User:Dudemanfellabra/UpdateNRHPProgress.js

/*  The following script places a button at the top of the NRHP Progress page WP:NRHPPROGRESS. When the button is clicked, the script begins to load each county list linked from the Progress page in the background, extract statistics about sites in each list, and updates the Progress page with the fetched data.

var wikitext = 'error' var ProgressStructure=[]; // ProgressStructure[table][row].StatName var TotalToQuery=0; var TotalQueried=0; 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

function ProgressButton { if (mw.config.get('wgPageName')!="Wikipedia:WikiProject_National_Register_of_Historic_Places/Progress"||location.href.indexOf('action')!=-1) return; var button=document.createElement("input") button.setAttribute("type", "button"); button.setAttribute("value", "Update Statistics"); button.setAttribute("id", "button2"); $(button).click( Click ); var content=document.getElementById('mw-content-text')

content.parentNode.insertBefore(button, content) }

function Click {  // after button is clicked, disable it and fetch wikitext of Progress page var button2 = document.getElementById('button2') button2.disabled = true var ProgressDiv = document.createElement("div") ProgressDiv.setAttribute("id", "ProgressDiv") ProgressDiv.setAttribute("style", "width:500px; border:1px solid black; padding:5px; background:#ffffff") button2.parentNode.insertBefore(ProgressDiv, button2) ProgressDiv.innerHTML = "Initializing..."

mw.loader.using( ['mediawiki.util', 'user.options'] ).then( function {		getWikitext(mw.config.get('wgPageName')) // after wikitext fetched, SetupTables is called	} ); }

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

// set up national totals var tr=table[0].getElementsByTagName("tr") ProgressStructure[0]=[]; for (var j=1; j<tr.length-3; j++) { var td=tr[j].getElementsByTagName("td") ProgressStructure[0][j-1]={}; ProgressStructure[0][j-1].ID=td[0].innerHTML // state name ProgressStructure[0][j-1].Total=0 ProgressStructure[0][j-1].Illustrated=0 ProgressStructure[0][j-1].Articled=0 ProgressStructure[0][j-1].Stubs=0 ProgressStructure[0][j-1].NRISonly=0 ProgressStructure[0][j-1].StartPlus=0 ProgressStructure[0][j-1].Unassessed=0 ProgressStructure[0][j-1].Untagged=0 }

// special row for Tangier, Morocco var td=tr[tr.length-3].getElementsByTagName("td") ProgressStructure[0][tr.length-4]={}; ProgressStructure[0][tr.length-4].ID="Tangier, Morocco" ProgressStructure[0][tr.length-4].Total=parseFloat(td[1].innerHTML.replace(",","")) ProgressStructure[0][tr.length-4].Illustrated=parseFloat(td[2].innerHTML.replace(",","")) ProgressStructure[0][tr.length-4].Articled=parseFloat(td[4].innerHTML.replace(",","")) ProgressStructure[0][tr.length-4].Stubs=parseFloat(td[6].innerHTML.replace(",","")) ProgressStructure[0][tr.length-4].NRISonly=parseFloat(td[7].innerHTML.replace(",","")) ProgressStructure[0][tr.length-4].StartPlus=parseFloat(td[8].innerHTML.replace(",","")) ProgressStructure[0][tr.length-4].Unassessed=parseFloat(td[10].innerHTML.replace(",","")) ProgressStructure[0][tr.length-4].Untagged=parseFloat(td[11].innerHTML.replace(",",""))

// duplicates row var td=tr[tr.length-2].getElementsByTagName("td") ProgressStructure[0][tr.length-3]={}; ProgressStructure[0][tr.length-3].ID="National Duplicates" ProgressStructure[0][tr.length-3].Total=parseFloat(td[0].innerHTML.replace(",","")) ProgressStructure[0][tr.length-3].Illustrated=parseFloat(td[1].innerHTML.replace(",","")) ProgressStructure[0][tr.length-3].Articled=parseFloat(td[3].innerHTML.replace(",","")) ProgressStructure[0][tr.length-3].Stubs=parseFloat(td[5].innerHTML.replace(",","")) ProgressStructure[0][tr.length-3].NRISonly=parseFloat(td[6].innerHTML.replace(",","")) ProgressStructure[0][tr.length-3].StartPlus=parseFloat(td[7].innerHTML.replace(",","")) ProgressStructure[0][tr.length-3].Unassessed=parseFloat(td[9].innerHTML.replace(",","")) ProgressStructure[0][tr.length-3].Untagged=parseFloat(td[10].innerHTML.replace(",",""))

// national totals ProgressStructure[0][tr.length-2]={}; ProgressStructure[0][tr.length-2].ID="National Totals" ProgressStructure[0][tr.length-2].Total=0 ProgressStructure[0][tr.length-2].Illustrated=0 ProgressStructure[0][tr.length-2].Articled=0 ProgressStructure[0][tr.length-2].Stubs=0 ProgressStructure[0][tr.length-2].NRISonly=0 ProgressStructure[0][tr.length-2].StartPlus=0 ProgressStructure[0][tr.length-2].Unassessed=0 ProgressStructure[0][tr.length-2].Untagged=0

// now data for each state for (var i=1; i<table.length; i++) { var tr=table[i].getElementsByTagName("tr") ProgressStructure[i]=[]; 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 ProgressStructure[i][j-1]={}; ProgressStructure[i][j-1].ID=td[0].innerHTML.substr(0,5) ProgressStructure[i][j-1].Total=parseFloat(td[3].innerHTML.replace(",","")) ProgressStructure[i][j-1].Illustrated=parseFloat(td[4].innerHTML.replace(",","")) ProgressStructure[i][j-1].Articled=parseFloat(td[6].innerHTML.replace(",","")) ProgressStructure[i][j-1].Stubs=parseFloat(td[8].innerHTML.replace(",","")) ProgressStructure[i][j-1].NRISonly=parseFloat(td[9].innerHTML.replace(",","")) ProgressStructure[i][j-1].StartPlus=parseFloat(td[10].innerHTML.replace(",","")) ProgressStructure[i][j-1].Unassessed=parseFloat(td[12].innerHTML.replace(",","")) ProgressStructure[i][j-1].Untagged=parseFloat(td[13].innerHTML.replace(",","")) var link=td[1].getElementsByTagName("a") if (link.length!=0 && link[0].href.search("#")==-1) { link=decodeURI(link[0].href).split("/") link=link[link.length-1].replace(/_/g," ") ProgressStructure[i][j-1].Link=link

ProgressStructure[i][j-1].ArticleQueried=0 // for querying later ProgressStructure[i][j-1].TalkQueried=0 } else { if (ProgressStructure[i][j-1].ID!="ddddd") { // if no link and not duplicate, must be totals row, so we can zero it                   ProgressStructure[i][j-1].Total=0 ProgressStructure[i][j-1].Illustrated=0 ProgressStructure[i][j-1].Articled=0 ProgressStructure[i][j-1].Stubs=0 ProgressStructure[i][j-1].NRISonly=0 ProgressStructure[i][j-1].StartPlus=0 ProgressStructure[i][j-1].Unassessed=0 ProgressStructure[i][j-1].Untagged=0 }           }        }

// duplicates row var td=tr[tr.length-2].getElementsByTagName("td") ProgressStructure[i][tr.length-3]={}; ProgressStructure[i][tr.length-3].ID=ProgressStructure[0][i-1].ID+" Duplicates" ProgressStructure[i][tr.length-3].Total=parseFloat(td[0].innerHTML.replace(",","")) ProgressStructure[i][tr.length-3].Illustrated=parseFloat(td[1].innerHTML.replace(",","")) ProgressStructure[i][tr.length-3].Articled=parseFloat(td[3].innerHTML.replace(",","")) ProgressStructure[i][tr.length-3].Stubs=parseFloat(td[5].innerHTML.replace(",","")) ProgressStructure[i][tr.length-3].NRISonly=parseFloat(td[6].innerHTML.replace(",","")) ProgressStructure[i][tr.length-3].StartPlus=parseFloat(td[7].innerHTML.replace(",","")) ProgressStructure[i][tr.length-3].Unassessed=parseFloat(td[9].innerHTML.replace(",","")) ProgressStructure[i][tr.length-3].Untagged=parseFloat(td[10].innerHTML.replace(",",""))

// state totals ProgressStructure[i][tr.length-2]={}; ProgressStructure[i][tr.length-2].ID=ProgressStructure[0][i-1].ID+" Totals" ProgressStructure[i][tr.length-2].Total=0 ProgressStructure[i][tr.length-2].Illustrated=0 ProgressStructure[i][tr.length-2].Articled=0 ProgressStructure[i][tr.length-2].Stubs=0 ProgressStructure[i][tr.length-2].NRISonly=0 ProgressStructure[i][tr.length-2].StartPlus=0 ProgressStructure[i][tr.length-2].Unassessed=0 ProgressStructure[i][tr.length-2].Untagged=0 }   for (var i=1; i<ProgressStructure.length; i++) { // count total number of rows to check for (var j=0; j<ProgressStructure[i].length-2; j++) { if (typeof ProgressStructure[i][j].Link!="undefined") TotalToQuery++ // don't count duplicates and total rows }   }    TotalQueried=0;

var ProgressDiv=document.getElementById("ProgressDiv") ProgressDiv.innerHTML+=" Done! "

var ProgressSpan=document.createElement("span") ProgressSpan.setAttribute("id", "ProgressSpan") ProgressDiv.appendChild(ProgressSpan) ProgressSpan.innerHTML = "Querying county data... 0 (0%) of "+TotalToQuery+" lists checked."

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

InitialTime=new Date // record starting time UpdateProgressDiv; LoadList(1,0); // begin querying first page }

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

if (typeof ProgressStructure[currentTable][currentRow].Link=="undefined") { // skip duplicate and total rows LoadList(currentTable,currentRow+1) return; }

var title=ProgressStructure[currentTable][currentRow].Link

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

function WikitextFetched(ajaxResponse,status,title,currentTable,currentRow) { if (status!="success") { NewWarning("Wikitext "+ajaxResponse.errorThrown) setTimeout(function{ // try again after delay if rate limit reached           getProgressListWikitext(title,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]["*"] 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,"'") }

var regex = new RegExp("=[ ]*(\\[\\[(.*?\\|)?[ ]*)?"+SectionName+"([ ]*\\]\\])?[ ]*=", "g") var sectionheader=pagetext.match(regex) if (sectionheader == null) { // if no section found, check if one of known empty counties var EmptyCounties=["02270", "12067", "42023", "48017", "48023", "48033", "48069", "48075", "48079", "48083", "48103", "48107", "48119", "48131", "48155", "48165", "48195", "48207", "48219", "48247", "48269", "48279", "48341", "48369", "48389", "48415", "48421", "48431", "48433", "48437", "48445", "48461", "48475", "48501", "51735"]

var ID = ProgressStructure[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 ProgressStructure[currentTable][currentRow].Total=0 ProgressStructure[currentTable][currentRow].Illustrated=0 ProgressStructure[currentTable][currentRow].Articled=0 ProgressStructure[currentTable][currentRow].Stubs=0 ProgressStructure[currentTable][currentRow].NRISonly=0 ProgressStructure[currentTable][currentRow].StartPlus=0 ProgressStructure[currentTable][currentRow].Unassessed=0 ProgressStructure[currentTable][currentRow].Untagged=0 ProgressStructure[currentTable][currentRow].Link=title

TotalQueried++ if (TotalQueried==TotalToQuery) CalculateProgressTotals 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 ProgressFatalError(0,title,currentTable,currentRow) }       }        var 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) { ProgressFatalError(2,title,currentTable,currentRow) // incorrectly formatted table } else { // must be an empty county ProgressStructure[currentTable][currentRow].Total=0 ProgressStructure[currentTable][currentRow].Illustrated=0 ProgressStructure[currentTable][currentRow].Articled=0 ProgressStructure[currentTable][currentRow].Stubs=0 ProgressStructure[currentTable][currentRow].NRISonly=0 ProgressStructure[currentTable][currentRow].StartPlus=0 ProgressStructure[currentTable][currentRow].Unassessed=0 ProgressStructure[currentTable][currentRow].Untagged=0 ProgressStructure[currentTable][currentRow].Link=title

TotalQueried++ if (TotalQueried==TotalToQuery) CalculateProgressTotals return; }       }        var tabletext=sectiontext.substr(StartIndex,sectiontext.indexOf("\n|}",StartIndex)-StartIndex) } else { // if not a redirect, default to first table on page var StartIndex=pagetext.indexOf("{{NRHP header") if (StartIndex==-1) { ProgressFatalError(1,title,currentTable,currentRow) // no list found return; }       var 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("",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) var 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 (var i=0; i((?!<[ ]*?/[ ]*?(nowiki|pre)[ ]*?>)(.|\\n))*?"+Rows[i].replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&")+"(.|\\n)*?<[ ]*?/[ ]*?(nowiki|pre)[ ]*?>", "g") if (tabletext.match(regex)!=null) {Rows.splice(i,1); i--} }

// now begin querying statistics var Stats={"ID":ProgressStructure[currentTable][currentRow].ID, "Total":Rows.length, "Illustrated":0, "Articled":0, "Stubs":0, "NRISonly":0, "StartPlus":0, "Unassessed":0, "Untagged":0, "Link":title}

var Titles=[]; for (var i=0; i/g,"").trim if (test!="") { Stats.Illustrated++ // only true if image param there and non-blank }       }

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 Titles.push(article) }

var StartIndex=0 LoadNextProgressQuery(Rows,Stats,Titles,StartIndex,title,currentTable,currentRow) return; }

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

setTimeout(function{ // short delay to prevent API overload       QueryProgressStats(Rows,Stats,Titles,TempTitles,StartIndex,title,currentTable,currentRow)        LoadNextProgressQuery(Rows,Stats,Titles,StartIndex,title,currentTable,currentRow)    }, DefaultQueryPause); return; }

// query next batch of articles function QueryProgressStats(Rows,Stats,Titles,TempTitles,StartIndex,title,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) {                ProgressArticleChecked(ArticlejsonObject,status,Rows,Stats,Titles,TempTitles,StartIndex,title,currentTable,currentRow)            }    }); return; }

// parse API response for article query function ProgressArticleChecked(ArticlejsonObject,status,Rows,Stats,Titles,TempTitles,StartIndex,title,currentTable,currentRow) { if (status!="success") { NewWarning("Articles "+ArticlejsonObject.errorThrown)

setTimeout(function{ // try again after delay if rate limit reached           QueryProgressStats(Rows,Stats,Titles,TempTitles,StartIndex,title,currentTable,currentRow)        }, 250); return; }   // won't get here unless successful ProgressStructure[currentTable][currentRow].ArticleQueried+=TempTitles.length

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

// now determine the number of bluelinks and NRIS-only articles for (var page in responseText.query.pages) { var articled=true  // default to articled, not NRIS-only var NRISonly=false var pagetitle=responseText.query.pages[page].title if (typeof responseText.query.pages[page].missing!="undefined") { // redlink=unarticled articled=false }       if (responseText.query.pages[page].categories) { for (var category in responseText.query.pages[page].categories) { if (responseText.query.pages[page].categories[category].title=="Category:All disambiguation pages") { // dab=unarticled articled=false }               if (responseText.query.pages[page].categories[category].title.indexOf("sourced only to NRIS")!=-1) { // mark as NRIS-only NRISonly=true }           }        }        for (var i=0; i<TempTitles.length; i++) { // if page is duplicated, count it multiple times if (TempTitles[i]==pagetitle) { if (articled) Stats.Articled++ if (NRISonly) Stats.NRISonly++ }       }    }

if (ProgressStructure[currentTable][currentRow].ArticleQueried==Stats.Total) { // after querying all articles, query talk pages for (var i=0; i50) { var TempTitles=Titles.slice(StartIndex,StartIndex+50) } else { var TempTitles=Titles.slice(StartIndex) }   StartIndex+=TempTitles.length

setTimeout(function{ // short delay to prevent API overload       QueryProgressTalkStats(Rows,Stats,Titles,TempTitles,StartIndex,title,currentTable,currentRow)        LoadNextProgressTalkQuery(Rows,Stats,Titles,StartIndex,title,currentTable,currentRow)    }, DefaultQueryPause); return; }

// query the next batch of talk pages function QueryProgressTalkStats(Rows,Stats,Titles,TempTitles,StartIndex,title,currentTable,currentRow) { var catlist='Category:FA-Class National Register of Historic Places articles‎|Category:A-Class National Register of Historic Places ' catlist+='articles‎|Category:GA-Class National Register of Historic Places articles‎|Category:B-Class National Register of Historic ' catlist+='Places articles‎|Category:C-Class National Register of Historic Places articles‎|Category:Start-Class National Register of ' catlist+='Historic Places articles‎|Category:Stub-Class National Register of Historic Places articles‎|Category:Unassessed National ' catlist+='Register of Historic Places articles‎|Category:List-Class National Register of Historic Places articles|Category:Redirect-' catlist+='Class National Register of Historic Places articles'

var TitleList=TempTitles.join("|") $.ajax({       dataType: "json",        url: mw.util.wikiScript('api'),        data: {            format: 'json',            action: 'query',            prop: 'categories',            clcategories: catlist,            cllimit: 'max',            titles: TitleList,            redirects: 'true'        },        error: function(ArticlejsonObject,status,errorThrown) {ArticlejsonObject.errorThrown=errorThrown},        complete: function(ArticlejsonObject,status) {                ProgressTalkChecked(ArticlejsonObject,status,Rows,Stats,Titles,TempTitles,StartIndex,title,currentTable,currentRow)            }    }); return; }

// parse API response for talk page query function ProgressTalkChecked(ArticlejsonObject,status,Rows,Stats,Titles,TempTitles,StartIndex,title,currentTable,currentRow) { if (status!="success") { NewWarning("Talk "+ArticlejsonObject.errorThrown)

setTimeout(function{ // try again after delay if rate limit reached           QueryProgressTalkStats(Rows,Stats,Titles,TempTitles,StartIndex,title,currentTable,currentRow)        }, 250); return; }   // won't get here unless successful ProgressStructure[currentTable][currentRow].TalkQueried+=TempTitles.length

// now determine quality statistics var responseText=JSON.parse(ArticlejsonObject.responseText) for (var page in responseText.query.pages) { if (typeof responseText.query.pages[page].missing!="undefined") continue; // skip talk page if not articled

var untagged=true // default to untagged var articled=true // assume articled to check for link to other county/MPS lists var stub=false var unassessed=false var startPlus=false var pagetitle=responseText.query.pages[page].title if (responseText.query.pages[page].categories) { untagged=false // if cat hit, mark as tagged for (var category in responseText.query.pages[page].categories) { var CatTitle=responseText.query.pages[page].categories[category].title if (CatTitle.indexOf("Stub")!=-1) { stub=true // mark as stub }               if (CatTitle.indexOf("Unassessed")!=-1||CatTitle.indexOf("Redirect")!=-1) { unassessed=true // mark as unassessed }               if  (CatTitle.indexOf("List")!=-1) { // count links to other county/MPS lists as unarticled; other list-class as stubs if (responseText.query.pages[page].title.indexOf("National Register of Historic Places")!=-1){ articled=false } else { stub=true }               }            }        }        if (articled&&!untagged&&!unassessed&&!stub) { // if articled, tagged, and assessed, but not stub, must be Start+ startPlus=true }

for (var i=0; i<TempTitles.length; i++) { if (TempTitles[i]==pagetitle) { if (!articled) Stats.Articled-- // reduce the count of articled if links to lists if (untagged) Stats.Untagged++ if (stub) Stats.Stubs++ if (unassessed) Stats.Unassessed++ if (startPlus) Stats.StartPlus++ }       }    }

if (ProgressStructure[currentTable][currentRow].TalkQueried==Stats.Total) { ProgressStructure[currentTable][currentRow].Total=Stats.Total ProgressStructure[currentTable][currentRow].Illustrated=Stats.Illustrated ProgressStructure[currentTable][currentRow].Articled=Stats.Articled ProgressStructure[currentTable][currentRow].Stubs=Stats.Stubs ProgressStructure[currentTable][currentRow].NRISonly=Stats.NRISonly ProgressStructure[currentTable][currentRow].StartPlus=Stats.StartPlus ProgressStructure[currentTable][currentRow].Unassessed=Stats.Unassessed ProgressStructure[currentTable][currentRow].Untagged=Stats.Untagged ProgressStructure[currentTable][currentRow].Link=title

TotalQueried++ if (TotalQueried==TotalToQuery) CalculateProgressTotals } }

// keep track of warnings encountered while querying API function NewWarning(warning) { var NewWarning=true for (var 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 (var 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 ProgressFatalError(code,title,currentTable,currentRow) { var errorArray = ['No county section found for ','No list found for ','Incorrectly formatted list for '] var retry=confirm(errorArray[code]+title+"!\n\nCancel=Skip                  OK=Retry") if (retry) { getProgressListWikitext(title,currentTable,currentRow); } else { // if chose to skip, add one to error count TotalQueried++ ErrorCount++ if (TotalQueried==TotalToQuery) CalculateProgressTotals }   return; }

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

var PercentQueried=Math.round(TotalQueried/TotalToQuery*1000)/10 ProgressSpan.innerHTML = "Querying county data... "+TotalQueried+" ("+PercentQueried+"%) of "+TotalToQuery+" lists checked."

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++ }

var 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 { var TimeRemainingStr="Calculating..." }   TimeSpan.innerHTML=" Estimated time remaining: "+TimeRemainingStr

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

// after all querying complete, calculate totals for states and counties with multiple sublists function CalculateProgressTotals { clearTimeout(ProgressDivTimer) var ProgressSpan=document.getElementById("ProgressSpan") var TimeSpan=document.getElementById("TimeSpan")

ProgressSpan.innerHTML = "Querying county data... Done! "+TotalToQuery+" lists checked."

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

for (var i=1; i<ProgressStructure.length; i++) { // i=table number; skip national table until end for (var j=0; j<ProgressStructure[i].length-2; j++) { var TotalsIndex=ProgressStructure[i].length-1 if (!isNaN(parseFloat(ProgressStructure[i][j].ID))) { // if regular county without sublists, add to totals ProgressStructure[i][TotalsIndex].Total+=ProgressStructure[i][j].Total ProgressStructure[i][TotalsIndex].Illustrated+=ProgressStructure[i][j].Illustrated ProgressStructure[i][TotalsIndex].Articled+=ProgressStructure[i][j].Articled ProgressStructure[i][TotalsIndex].Stubs+=ProgressStructure[i][j].Stubs ProgressStructure[i][TotalsIndex].NRISonly+=ProgressStructure[i][j].NRISonly ProgressStructure[i][TotalsIndex].StartPlus+=ProgressStructure[i][j].StartPlus ProgressStructure[i][TotalsIndex].Unassessed+=ProgressStructure[i][j].Unassessed ProgressStructure[i][TotalsIndex].Untagged+=ProgressStructure[i][j].Untagged } else if (ProgressStructure[i][j].ID=="-") { // if county sublist, find total county row and add there var CountyTotalsIndex=j+1 while (CountyTotalsIndex<ProgressStructure[i].length-2) { if (!isNaN(parseFloat(ProgressStructure[i][CountyTotalsIndex].ID))) break; CountyTotalsIndex++ }               ProgressStructure[i][CountyTotalsIndex].Total+=ProgressStructure[i][j].Total ProgressStructure[i][CountyTotalsIndex].Illustrated+=ProgressStructure[i][j].Illustrated ProgressStructure[i][CountyTotalsIndex].Articled+=ProgressStructure[i][j].Articled ProgressStructure[i][CountyTotalsIndex].Stubs+=ProgressStructure[i][j].Stubs ProgressStructure[i][CountyTotalsIndex].NRISonly+=ProgressStructure[i][j].NRISonly ProgressStructure[i][CountyTotalsIndex].StartPlus+=ProgressStructure[i][j].StartPlus ProgressStructure[i][CountyTotalsIndex].Unassessed+=ProgressStructure[i][j].Unassessed ProgressStructure[i][CountyTotalsIndex].Untagged+=ProgressStructure[i][j].Untagged } else if (ProgressStructure[i][j].ID=="ddddd") { // if county duplicate row, subtract from county total ProgressStructure[i][j+1].Total-=ProgressStructure[i][j].Total ProgressStructure[i][j+1].Illustrated-=ProgressStructure[i][j].Illustrated ProgressStructure[i][j+1].Articled-=ProgressStructure[i][j].Articled ProgressStructure[i][j+1].Stubs-=ProgressStructure[i][j].Stubs ProgressStructure[i][j+1].NRISonly-=ProgressStructure[i][j].NRISonly ProgressStructure[i][j+1].StartPlus-=ProgressStructure[i][j].StartPlus ProgressStructure[i][j+1].Unassessed-=ProgressStructure[i][j].Unassessed ProgressStructure[i][j+1].Untagged-=ProgressStructure[i][j].Untagged } else { // unknown ID; skip it               alert("Error! Unknown ID="+ProgressStructure[i][j].ID+" in Table "+i+", Row "+j+". Skipping this list in totals.") }       }        // subtract state duplicates ProgressStructure[i][TotalsIndex].Total-=ProgressStructure[i][TotalsIndex-1].Total ProgressStructure[i][TotalsIndex].Illustrated-=ProgressStructure[i][TotalsIndex-1].Illustrated ProgressStructure[i][TotalsIndex].Articled-=ProgressStructure[i][TotalsIndex-1].Articled ProgressStructure[i][TotalsIndex].Stubs-=ProgressStructure[i][TotalsIndex-1].Stubs ProgressStructure[i][TotalsIndex].NRISonly-=ProgressStructure[i][TotalsIndex-1].NRISonly ProgressStructure[i][TotalsIndex].StartPlus-=ProgressStructure[i][TotalsIndex-1].StartPlus ProgressStructure[i][TotalsIndex].Unassessed-=ProgressStructure[i][TotalsIndex-1].Unassessed ProgressStructure[i][TotalsIndex].Untagged-=ProgressStructure[i][TotalsIndex-1].Untagged

// record state totals in national table ProgressStructure[0][i-1].Total=ProgressStructure[i][TotalsIndex].Total ProgressStructure[0][i-1].Illustrated=ProgressStructure[i][TotalsIndex].Illustrated ProgressStructure[0][i-1].Articled=ProgressStructure[i][TotalsIndex].Articled ProgressStructure[0][i-1].Stubs=ProgressStructure[i][TotalsIndex].Stubs ProgressStructure[0][i-1].NRISonly=ProgressStructure[i][TotalsIndex].NRISonly ProgressStructure[0][i-1].StartPlus=ProgressStructure[i][TotalsIndex].StartPlus ProgressStructure[0][i-1].Unassessed=ProgressStructure[i][TotalsIndex].Unassessed ProgressStructure[0][i-1].Untagged=ProgressStructure[i][TotalsIndex].Untagged

// add state totals to national totals var NationalTotalsIndex=ProgressStructure[0].length-1 ProgressStructure[0][NationalTotalsIndex].Total+=ProgressStructure[0][i-1].Total ProgressStructure[0][NationalTotalsIndex].Illustrated+=ProgressStructure[0][i-1].Illustrated ProgressStructure[0][NationalTotalsIndex].Articled+=ProgressStructure[0][i-1].Articled ProgressStructure[0][NationalTotalsIndex].Stubs+=ProgressStructure[0][i-1].Stubs ProgressStructure[0][NationalTotalsIndex].NRISonly+=ProgressStructure[0][i-1].NRISonly ProgressStructure[0][NationalTotalsIndex].StartPlus+=ProgressStructure[0][i-1].StartPlus ProgressStructure[0][NationalTotalsIndex].Unassessed+=ProgressStructure[0][i-1].Unassessed ProgressStructure[0][NationalTotalsIndex].Untagged+=ProgressStructure[0][i-1].Untagged }   // special row for Tangier, Morocco ProgressStructure[0][NationalTotalsIndex].Total+=ProgressStructure[0][NationalTotalsIndex-2].Total ProgressStructure[0][NationalTotalsIndex].Illustrated+=ProgressStructure[0][NationalTotalsIndex-2].Illustrated ProgressStructure[0][NationalTotalsIndex].Articled+=ProgressStructure[0][NationalTotalsIndex-2].Articled ProgressStructure[0][NationalTotalsIndex].Stubs+=ProgressStructure[0][NationalTotalsIndex-2].Stubs ProgressStructure[0][NationalTotalsIndex].NRISonly+=ProgressStructure[0][NationalTotalsIndex-2].NRISonly ProgressStructure[0][NationalTotalsIndex].StartPlus+=ProgressStructure[0][NationalTotalsIndex-2].StartPlus ProgressStructure[0][NationalTotalsIndex].Unassessed+=ProgressStructure[0][NationalTotalsIndex-2].Unassessed ProgressStructure[0][NationalTotalsIndex].Untagged+=ProgressStructure[0][NationalTotalsIndex-2].Untagged

// subtract national duplicates ProgressStructure[0][NationalTotalsIndex].Total-=ProgressStructure[0][NationalTotalsIndex-1].Total ProgressStructure[0][NationalTotalsIndex].Illustrated-=ProgressStructure[0][NationalTotalsIndex-1].Illustrated ProgressStructure[0][NationalTotalsIndex].Articled-=ProgressStructure[0][NationalTotalsIndex-1].Articled ProgressStructure[0][NationalTotalsIndex].Stubs-=ProgressStructure[0][NationalTotalsIndex-1].Stubs ProgressStructure[0][NationalTotalsIndex].NRISonly-=ProgressStructure[0][NationalTotalsIndex-1].NRISonly ProgressStructure[0][NationalTotalsIndex].StartPlus-=ProgressStructure[0][NationalTotalsIndex-1].StartPlus ProgressStructure[0][NationalTotalsIndex].Unassessed-=ProgressStructure[0][NationalTotalsIndex-1].Unassessed ProgressStructure[0][NationalTotalsIndex].Untagged-=ProgressStructure[0][NationalTotalsIndex-1].Untagged

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

// update wikitext with queried totals function ParseProgressWikitext { var newwikitext=wikitext var TableStartIndex=wikitext.indexOf("==State totals") for (var i=0; i<ProgressStructure.length; i++) { var TableStartIndex=wikitext.indexOf("{|",TableStartIndex+1)   // find next table in old wikitext var TableEndIndex=wikitext.indexOf("|}",TableStartIndex)+2 var oldTable=wikitext.substr(TableStartIndex,TableEndIndex-TableStartIndex) var newTable=oldTable

var RowStartIndex=0 for (var j=0; j<ProgressStructure[i].length-2; j++) { RowStartIndex=oldTable.indexOf("\n|-",RowStartIndex+1) // find next row in old table if (ProgressStructure[i][j].ID=="ddddd") continue;  // skip duplicate rows var RowEndIndex=oldTable.indexOf("\n|-",RowStartIndex+1) var oldRow=oldTable.substr(RowStartIndex,RowEndIndex-RowStartIndex) var firstColumn=oldRow.indexOf("\n|") var stop=4       // skip one cell for national table, three for states if (i==0) stop=2 for (var inc=0; inc999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); // add thousands separators newRow+="\n| "+str // illustrated str=temp.Illustrated.toString if (temp.Illustrated>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n| "+str // illustrated percent if (temp.Total==0) { str="-" } else { str = Math.round(temp.Illustrated/temp.Total*1000)/10 var test = str.toString.indexOf(".") if (test==-1 && str!=100 && str!=0) str+=".0" // force decimal str+="%" }           newRow+="\n| "+str // articled str=temp.Articled.toString if (temp.Articled>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n| "+str // articled percent if (temp.Total==0) { str="-" } else { str = Math.round(temp.Articled/temp.Total*1000)/10 var test = str.toString.indexOf(".") if (test==-1 && str!=100 && str!=0) str+=".0" str+="%" }           newRow+="\n| "+str // stubs str=temp.Stubs.toString if (temp.Stubs>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n| "+str // NRIS-only str=temp.NRISonly.toString if (temp.NRISonly>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n| "+str // Start+ str=temp.StartPlus.toString if (temp.StartPlus>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n| "+str // Start+ percent if (temp.Total==0) { str="-" } else { str = Math.round(temp.StartPlus/temp.Total*1000)/10 var test = str.toString.indexOf(".") if (test==-1 && str!=100 && str!=0) str+=".0" // force decimal str+="%" }           newRow+="\n| "+str // unassessed str=temp.Unassessed.toString if (temp.Unassessed>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n| "+str // untagged str=temp.Untagged.toString if (temp.Untagged>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n| "+str // net quality if (temp.Total==0) { str="-" } else { str=temp.StartPlus+0.5*temp.Stubs+0.5*temp.Unassessed-0.5*temp.Untagged-0.75*temp.NRISonly str=Math.round((0.75*str/temp.Total+0.25*temp.Illustrated/temp.Total)*1000)/10 if (str<0) str=0 var test=str.toString.indexOf(".") if (test==-1 && str!=100 && str!=0) str+=".0" str+="%" }           newRow+="\n| "+str

// update new table with new row newTable=newTable.replace(oldRow,newRow) }       RowStartIndex=oldTable.indexOf("\n|-",RowStartIndex+1) // skip duplicate row RowStartIndex=oldTable.indexOf("\n|-",RowStartIndex+1) RowEndIndex=oldTable.indexOf("\n|}",RowStartIndex+1) oldRow=oldTable.substr(RowStartIndex,RowEndIndex-RowStartIndex) firstColumn=oldRow.indexOf("\n!") // totals row uses ! instead of | firstColumn=oldRow.indexOf("\n!",firstColumn+1) // skip first cell

// build up new totals row var newRow=oldRow.substr(0,firstColumn) var temp=ProgressStructure[i][ProgressStructure[i].length-1] // total var str=temp.Total.toString if (temp.Total>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); // add thousands separators newRow+="\n! "+str // illustrated str=temp.Illustrated.toString if (temp.Illustrated>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n! "+str // illustrated percent if (temp.Total==0) { str="-" } else { str = Math.round(temp.Illustrated/temp.Total*1000)/10 var test = str.toString.indexOf(".") if (test==-1 && str!=100 && str!=0) str+=".0" // force decimal str+="%" }       newRow+="\n! "+str // articled str=temp.Articled.toString if (temp.Articled>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n! "+str // articled percent if (temp.Total==0) { str="-" } else { str = Math.round(temp.Articled/temp.Total*1000)/10 var test = str.toString.indexOf(".") if (test==-1 && str!=100 && str!=0) str+=".0" // force decimal str+="%" }       newRow+="\n! "+str // stubs str=temp.Stubs.toString if (temp.Stubs>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n! "+str // NRIS-only str=temp.NRISonly.toString if (temp.NRISonly>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n! "+str // Start+ str=temp.StartPlus.toString if (temp.StartPlus>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n! "+str // Start+ percent if (temp.Total==0) { str="-" } else { str = Math.round(temp.StartPlus/temp.Total*1000)/10 var test = str.toString.indexOf(".") if (test==-1 && str!=100 && str!=0) str+=".0" // force decimal str+="%" }       newRow+="\n! "+str // unassessed str=temp.Unassessed.toString if (temp.Unassessed>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n! "+str // untagged str=temp.Untagged.toString if (temp.Untagged>999) str=str.replace(/\B(?=(\d{3})+(?!\d))/g, ","); newRow+="\n! "+str // net quality if (temp.Total==0) { str="-" } else { str=temp.StartPlus+0.5*temp.Stubs+0.5*temp.Unassessed-0.5*temp.Untagged-0.75*temp.NRISonly str=Math.round((0.75*str/temp.Total+0.25*temp.Illustrated/temp.Total)*1000)/10 if (str<0) str=0 var test=str.toString.indexOf(".") if (test==-1 && str!=100 && str!=0) str+=".0" str+="%" }       newRow+="\n! "+str

// update new wikitext with new table newTable=newTable.replace(oldRow,newRow) newwikitext=newwikitext.replace(oldTable,newTable) }

// now edit page with new wikitext var ProgressDiv=document.getElementById("ProgressDiv") ProgressDiv.innerHTML+=" Editing page (this might take up to one minute)... " InitializeEdit(newwikitext) }

// initialize edit function InitializeEdit(newwikitext) { 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

regex=/(January|February|March|April|May|June|July|August|September|October|November|December) [0-9]{1,2}, [0-9]{4}/g var tempstring=newwikitext.split('==County totals==') // ignore dates in lead (e.g. date of last map update) newwikitext=tempstring[0]+'==County totals=='+tempstring[1].replace(regex,DateStr) // update date strings above tables

var ErrorStr = '' if (ErrorCount>0) ErrorStr = " Errors encountered for "+ErrorCount+" counties, which were skipped. Human attention needed." var summary='Updating county data as of '+DateStr+' using script.'+ErrorStr editPage(newwikitext,mw.config.get('wgPageName'),summary) }

function PageEdited(ajaxResponse,status,newwikitext) { var ProgressDiv=document.getElementById("ProgressDiv") if (status!="success") { var retry=confirm("Error: "+ajaxResponse.errorThrown+" while editing page!\n\nCancel=Abort                  OK=Retry") if (retry) { ProgressDiv.innerHTML+="Retrying... " InitializeEdit(newwikitext) // try again } else { ProgressDiv.innerHTML+="Edit failure! Script aborted!" }       return; }   var responseText=JSON.parse(ajaxResponse.responseText) var diff=responseText.edit.newrevid var linkStr="//en.wikipedia.org/w/index.php?diff="+diff ProgressDiv.innerHTML+="Page edited! Click here for diff."

// 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" }   console.log(WarningText) }

function editPage(text,title,summary) { // edit page when done $.ajax({       dataType: 'json',        url: mw.util.wikiScript( 'api' ),        type: 'POST',        data: {            format: 'json',            action: 'edit',            title: title,            text: text,            summary: summary,            token: mw.user.tokens.get( 'csrfToken' )        },        error: function(ajaxResponse,status,errorThrown) {ajaxResponse.errorThrown=errorThrown},        complete: function(ajaxResponse,status) {PageEdited(ajaxResponse,status,text)}    }) }

function getWikitext(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 {wikitext="error"},        success: function(output) {            for (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 {                SetupTables            }        }    }) }

function getProgressListWikitext(title,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: title,            indexpageids: true,            redirects: 'true'        },        error: function(ajaxResponse,status,errorThrown) {ajaxResponse.errorThrown=errorThrown},        complete: function(ajaxResponse,status) {WikitextFetched(ajaxResponse,status,title,currentTable,currentRow)}    }) }

$(window).on('load', ProgressButton);