您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
allow orienteering splits visualisation
// ==UserScript== // @name Visualise Orienteering Splits // @namespace http://watsons.id.au // @version 2.7.000 // @description allow orienteering splits visualisation // @include http://obasen.orientering.se/winsplits/online/*/default.asp* // @include http://wmoc2016.ee/2016/wmoc2016/longf/split* // @include http://act.orienteering.asn.au/eventor/results/splits/* // @author Arthur Watson // @copyright 2017+ GPL // @grant none // @run-at-document-start // ==/UserScript== /* @require http://http://members.iinet.net.au/[email protected]/visualiser/visualise_splits.user.js @require file:///home/watsar/public_html/tampermonkey/visualise_splits.user.js @include http://act.orienteering.asn.au/gfolder/results/* */ //"use strict"; /*jslint browser: true, continue: true, indent: 2, regexp: true, white: true, sloppy: true */ var visLiterals = { }, visConstants = { secondsPerMinute: 60, secondsPerHour: 3600, minutesPerHour: 60 }, htmlHead = function() {/* <meta charset="utf-8"> <meta name="description" content="Visualise Orienteering Split Times"> <meta name="author" content="Arthur Watson"> <meta name="version" content="2.1"> <meta name="last modified" content="04 Sep 2017"> <meta name="robots" content="noindex, nofollow"> <title>Visualise Orienteering Split Times</title> */}.toString().slice( 15, -3), htmlBody = function() {/* <div class="container"> <div class="headers"> <h1 class="aardvark">Orienteering Splits Time Visualiser</h1> <h2 class="aardvark" id="description"></h2> </div> <!-- <div class="box"> <button class="splits" type="submit" onclick="drawSplits();">1. Begin Here!</button> </div> --> <div class="box"> <div id="courseclasslistcell"></div> <div id="showrunnerbuttoncell"></div> </div> <div class="box"> <div id="runnerlistcell"></div> <div id="visualisebuttoncell"></div> </div> <div class="box"> <button id="helpButton" class="splits" type="submit" onclick="window.open( 'http://members.iinet.net.au/[email protected]/visualiser/documentation.html',target='_blank','height=500,width=400');">Show Help.</button> <!-- <button id="helpButton" class="splits" type="submit" onclick="interact.displayHelp();">Show Help.</button> --> <!-- <div id="showhelp" class="hide box"></div> --> </div> <div class="courses" id="tableSplits"></div> <div class="graph" id="visualSplits"></div> <div class="declaration" id="source"><p>Produced from published data by SOURCE. [Aardvark Systems 2017]</p></div> <input type="hidden" id="eventdata" onchange="drawSplits();" value=""> */}.toString().slice( 15, -3); function fixTime( t) { var items = [], seconds = 0; // remove the leg place [ n] for WMOC t = t.replace( /\[.*?\]/, ''); items = t.split( /[.:]/); switch ( items.length) { case 2: // m:s, so will be OK but change . to : t = items[ 0] + ':' + items[ 1]; seconds = parseInt( items[ 0], 10) * 60 + parseInt( items[ 1], 10); break; case 3: // h:m:s t = parseInt( items[ 0], 10) * 60 + parseInt( items[ 1], 10); t = t.toString() + ':' + items[ 2]; seconds = parseInt( items[ 0], 10) * 3600 + parseInt( items[ 1], 10) * 60 + parseInt( items[ 2], 10); break; default: if ( !isNaN( parseInt( t, 10))) { t = '0:' + t; seconds = parseInt( t, 10); } } return [ t, seconds]; } function timeToSeconds( t) { // transform the [h:]mm.ss on the WinSplits page --> seconds var items = [], seconds = 0; items = t.split( /[.:]/); switch ( items.length) { case 2: // m.s seconds = parseInt( items[ 0], 10) * visConstants.secondsPerMinute + parseInt( items[ 1], 10); break; case 3: // h:m.s seconds = parseInt( items[ 0], 10) * visConstants.secondsPerHour + parseInt( items[ 1], 10) * visConstants.secondsPerMinute + parseInt( items[ 2], 10); break; default: if ( !isNaN( parseInt( t, 10))) { seconds = parseInt( t, 10); } } return seconds; } function timeFormat( t) { // transform the [h]:mm.ss on the WinSplits page --> mmm:ss var items = []; items = t.split( /[.:]/); switch ( items.length) { case 2: // m:s, so will be OK but change . to : t = items[ 0] + ':' + items[ 1]; break; case 3: // h:m:s --> m:s t = parseInt( items[ 0], 10) * visConstants.minutesPerHour + parseInt( items[ 1], 10); t = t.toString() + ':' + items[ 2]; break; default: if ( !isNaN( parseInt( t, 10))) { t = '0:' + t; } } return t; } function pad( str, max) { return str.length < max ? pad( "0" + str, max) : str; } function secondsToTime( s) { // convert seconds to mmm:ss var min, sec; min = ( Math.floor( s / 60)).toString(); sec = pad( (s - ( min* 60)).toString(), 2); return min + ':' + sec; } function stripHeader( header) { // change the header from S-1 (135) to 1, & 11-F to 11 var regex = /^[\w\W]*\-\s*(\d+)[\w\W]*$/; if ( header.indexOf( 'F') > -1) { header = 'F'; } else { header = header.replace( regex, '$1'); } return header; } function stripWhitespace( text) { // remove leading and trailing whitespace var regex = /\s*(.*?)\s*$/; // non greedy match of non-whitespace text = text.replace( regex, '$1'); return text; } function processMessage ( message, ageClass, eventTitle) { var i, j, runnerIndex, results = {}; // fix ageclass ageClass = ageClass.replace( /\s+\([\w\s]*\)/, ''); results.description = eventTitle; results.courses = []; results.courses[ 0] = {}; results.courses[ 0].name = 'Course: - Not Stated'; results.courses[ 0].ageclasses = []; results.courses[ 0].ageclasses[ 0] = ageClass; results.courses[ 0].controlcodes = []; for ( j = 1; j < message[ 1].length - 1; j += 1) { // just the control sequence really results.courses[ 0].controlcodes[ j - 1] = stripHeader( message[ 1][ j]); } results.courses[ 0].runners = []; runnerIndex = 0; for ( i = 2; i < message.length; i += 2) { // process every second line if ( fixTime( message[ i][ 2])[ 1] < 1 || isNaN( fixTime( message[ i][ 2])[ 1])) { // ignore dnf's etc continue; } results.courses[ 0].runners[ runnerIndex] = {}; results.courses[ 0].runners[ runnerIndex].name = message[ i][ 1]; results.courses[ 0].runners[ runnerIndex].ageclass = ageClass; results.courses[ 0].runners[ runnerIndex].time = timeFormat( message[ i][ 2]); results.courses[ 0].runners[ runnerIndex].splits = {}; results.courses[ 0].runners[ runnerIndex].splits.raw = []; for ( j = 4; j < message[ i].length - 1; j += 2) { // and only every 2nd is a split results.courses[ 0].runners[ runnerIndex].splits.raw.push( fixTime( message[ i][ j])[ 0]); } runnerIndex += 1; } return results; } function processWMOCMessage ( message, ageClass, eventTitle) { var i, j, runnerIndex, results = {}; results.description = eventTitle; results.courses = []; results.courses[ 0] = {}; results.courses[ 0].name = 'Course: - Not Stated'; results.courses[ 0].ageclasses = []; results.courses[ 0].ageclasses[ 0] = ageClass; results.courses[ 0].controlcodes = []; var code = 1; for ( j = 7; j < message[ 1].length - 1; j += 1) { // wmoc doesn't give codes so just the numbers //results.courses[ 0].controlcodes[ j - 7] = message[ 1][ j]; results.courses[ 0].controlcodes[ j - 7] = code++; } results.courses[ 0].controlcodes[ message[ 1].length - 7] = 'F'; results.courses[ 0].runners = []; runnerIndex = 0; for ( i = 2; i < message.length; i += 2) { // process every two line pair if ( fixTime( message[ i][ 5])[ 1] < 1 || isNaN( fixTime( message[ i][ 5])[ 1])) { // ignore dnf's etc continue; } results.courses[ 0].runners[ runnerIndex] = {}; results.courses[ 0].runners[ runnerIndex].name = stripWhitespace( message[ i][ 2]); results.courses[ 0].runners[ runnerIndex].ageclass = ageClass; results.courses[ 0].runners[ runnerIndex].time = timeFormat( message[ i][ 5]); results.courses[ 0].runners[ runnerIndex].splits = {}; results.courses[ 0].runners[ runnerIndex].splits.raw = []; for ( j = 7; j < message[ i].length; j += 1) { // the second line has the splits results.courses[ 0].runners[ runnerIndex].splits.raw.push( fixTime( message[ i + 1][ j])[ 0]); } //console.log( results.courses[0].runners[runnerIndex].name + ", " + // results.courses[0].runners[runnerIndex].time + " --> " + // results.courses[ 0].runners[ runnerIndex].splits.raw); runnerIndex += 1; } return results; } function setupNewWindow( oEvent) { var scriptSource = 'http://members.iinet.net.au/[email protected]/visualiser/', //scriptSource = 'http://localhost/~watsar/tampermonkey/', splitsElements, splitsVisualiser, scriptElement, i, j; splitsElements = [ { 'element':'script', 'attributes': [[ 'src', scriptSource + 'draw_splits.js'], [ 'type', 'text/javascript']] }, { 'element':'script', 'attributes': [[ 'src', 'http://d3js.org/d3.v3.min.js'], [ 'charset', 'utf-8']] }, { 'element':'link', 'attributes': [[ 'href', scriptSource + 'draw_splits.css'], [ 'type', 'text/css'], [ 'rel', 'stylesheet']] }]; splitsVisualiser = window.open( '', 'Splits Visualiser', 'left=100,top=100,width=1200,height=800,menubar=no,titlebar=no,location=no,scrollbars=yes', 'POS'); splitsVisualiser.document.head.innerHTML = htmlHead; for ( i = 0; i < splitsElements.length; i += 1) { scriptElement = splitsVisualiser.document.createElement( splitsElements[ i].element); for ( j = 0; j < splitsElements[ i].attributes.length; j += 1) { scriptElement.setAttribute( splitsElements[ i].attributes[ j][ 0], splitsElements[ i].attributes[ j][ 1] ); splitsVisualiser.document.head.appendChild( scriptElement); } } splitsVisualiser.document.body.innerHTML = htmlBody; //splitsVisualiser.document.body.setAttribute( 'onload', "drawSplits();"); splitsVisualiser.document.title = 'Orienteering Event - Splits Visualiser --> ' + oEvent.description; splitsVisualiser.document.getElementById( 'eventdata').setAttribute( 'value', JSON.stringify( oEvent)); return 0; } function validateMessage( message) { var headerLine; headerLine = message[ 0].join( ','); headerLine = headerLine.replace( /\s/g, ''); //console.log( 'header: ' + headerLine); if ( headerLine.match( /^Pos,Name,Finishtime,Diff,legtot/) && headerLine.match( /legtot,Name$/)) { return true;} return false; } // The following are for extracting from OACT web pages // check that this is a splits result from Stephan Kramer SportSoftware function getResultHeaders( headerRow) { var i = 0, count = 0, headers = {}, t = '', cells = headerRow.querySelectorAll( 'th'); for ( i = 0; i < cells.length; i += 1) { t = cells[ i].textContent; if ( t.length > 0) { headers[ t] = i; count += 1; } else { break; } } headers.length = count; return headers; } function getEventData() { // preliminary scan through the results page to get: // 1. the name and date of the event // 2. the names of the course // 3. how many controls in each // 4. how many runners in each var event = {}, eventDescription, courseNameCells, courseLengthCells, courseNumberOfControlCells, courseName, courseLength, courseNumberOfControls, courseNumberOfRunners, courseNamePattern = /\s+\(\d+\)$/, // to get the number of runners in the parenthised bit at the end runners, i; eventDescription = document.querySelector( 'div#reporttop table tr td nobr').textContent; event.description = eventDescription ; courseNameCells = document.querySelectorAll( 'td#c00'); courseLengthCells = document.querySelectorAll( 'td#c01'); courseNumberOfControlCells = document.querySelectorAll( 'td#c02'); event.courses = []; for ( i = 0; i < courseNameCells.length; i += 1) { event.courses[ i] = {}; // course name field is <course_name> <(no_of_runners)> courseName = courseNameCells[ i].textContent; runners = courseNamePattern.exec( courseName); courseName = courseName.replace( runners[ 0], ''); courseName = courseName.replace( /\s+/, '_'); runners = /\d+/.exec( runners[ 0]); courseNumberOfRunners = runners[ 0]; event.courses[ i].name = courseName; event.courses[ i].numberofrunners = courseNumberOfRunners; event.courses[ i].ageclasses = []; // take the Km off the end of the length courseLength = courseLengthCells[ i].textContent; courseLength = courseLength.replace( /\s+km/i, ''); event.courses[ i].length = courseLength; // and take off the 'C' and the end of this field courseNumberOfControls= courseNumberOfControlCells[ i].textContent; courseNumberOfControls = courseNumberOfControls.replace( /\s+c/i, ''); event.courses[ i].controls = courseNumberOfControls; } return event; } function getRunnerData( runnersRowIndex, rowCells, resultHeaders, runnersData) { // grab name, ageclass and time from the first runner row // the club from the second // the indices are in the result headers in the event object if ( runnersRowIndex === 0) { // get name, ageclass and time runnersData.Pl = rowCells[ resultHeaders.Pl].textContent; runnersData.name = rowCells[ resultHeaders.Name].textContent; runnersData.ageclass = rowCells[ resultHeaders[ 'Cl.']].textContent; runnersData.time = rowCells[ resultHeaders.Time].textContent; } else if ( runnersRowIndex === 1) { if ( typeof runnersData === "undefined"){ runnersData = {}; runnersData.time = 'undef'; } else { // get the club, in the same position as name in row 0 runnersData.club = rowCells[ resultHeaders.Name].textContent; // club is in same position as name in the previous row } } return runnersData; } function getRunnerSplits( rowCells, resultHeadersLength, runnersSplits) { // get the splits from odd numbered rows var i; for ( i = resultHeadersLength; i < rowCells.length; i += 1) { if ( rowCells[ i].textContent.length > 0) { runnersSplits.push( rowCells[ i].textContent); } else { break; } } return runnersSplits; } function extractResults( event, rows, courseIndex, columnLength) { // the first row tuple has the control numbers // then we have tuples of double the length for the results which have // split and cumulative times on alternating rows // also there are short rows which we will ignore // also stop scanning when we get to non-finishers, ie the time is not in the correct format var cellLength, controlsPerRow, rowsForControls, rowsForRunner, rowCells, controlCodes, thisCode, controlCodePattern, runnersSplits, runnersData, splitsIndex, runnersIndex, runnersRowIndex, resultHeaders, rowContent, i, j; // check that the column length and number of cells tally cellLength = rows[ 0].querySelectorAll( 'td').length; if ( columnLength === cellLength) { event.checkCellLength[ courseIndex] = true; } else { event.checkCellLength[ courseIndex] = false; return event; } controlsPerRow = columnLength - event.courses[ courseIndex].resultHeaders.length; rowsForControls = Math.ceil(( parseInt( event.courses[ courseIndex].controls, 10) + 1) / controlsPerRow); // need an extra cell for last control to finish rowsForRunner = rowsForControls * 2; //alert( 'course ' + event.courses[ courseIndex].name + ': columns ==> ' + columnLength + ', rowsForControls ==> ' + rowsForControls + ', rowsForRunners ==> ' + rowsForRunner); controlCodes = []; controlCodePattern = /\(\d+/; for ( i = 0; i < rowsForControls; i += 1) { rowCells = rows[ i].querySelectorAll( 'td'); for ( j = event.courses[ courseIndex].resultHeaders.length; j < rowCells.length; j += 1) { thisCode = rowCells[ j].textContent; if ( controlCodePattern.test( thisCode)) { thisCode = controlCodePattern.exec( thisCode); thisCode = thisCode[ 0].replace( '(', ''); } controlCodes.push( thisCode); if( /F/.test( thisCode)) { break;} // no more controls after the finish } if( /F/.test( thisCode)) { break;} // and break from the outer loop } event.courses[ courseIndex].controlcodes = controlCodes; // now get the runners' results splitsIndex = 0; runnersIndex = 0; runnersRowIndex = 0; resultHeaders = event.courses[ courseIndex].resultHeaders; event.courses[ courseIndex].runners = []; for ( i = rowsForControls; i < rows.length; i += 1) { rowCells = rows[ i].querySelectorAll( 'td'); if( rowCells.length < columnLength) { continue;} // there are short rows used as spacers that can be ignored // also some rows that indicate controls visited that weren't on the course // these have an attribute of style="font-style: italic;" rowContent = ''; for ( j = resultHeaders.length; j < columnLength; j += 1) { if ( rowCells[ j].getAttribute( 'style') === 'font-style: italic;') { // set the content to empty string rowCells[ j].textContent = ''; } if ( /\-+/.test( rowCells[ j].textContent)) { // make unknowns ( '-----' ) zero time rowCells[ j].textContent = '0:00'; } if ( !/\d+:\d+/.test( rowCells[ j].textContent)) { // set non-times to empty string rowCells[ j].textContent = ''; } rowContent += rowCells[ j].textContent; } if ( rowContent.length < 1) { continue; } if( /\d+/.test( rowCells[ 0].textContent)) { // this should be the runners place // start a new runner runnersRowIndex = 0; runnersData = {}; runnersSplits = []; runnersData = getRunnerData( runnersRowIndex, rowCells, resultHeaders, runnersData); } if ( runnersRowIndex === 1) { runnersData = getRunnerData( runnersRowIndex, rowCells, resultHeaders, runnersData); // do this to prevent funnies for undefined splits and messy data if ( !( /\d{1,4}:\d{1,}/.test( runnersData.time))) { // if not a valid time mm:ss then skip this runner runnersRowIndex = rowsForRunner + 1; // this will force a skip below runnersData = {}; // and the data and splits runnersSplits = []; } } if( runnersRowIndex % 2 !== 0) { // for odd rows just get the splits runnersSplits = getRunnerSplits( rowCells, resultHeaders.length, runnersSplits); } // increment the runner row index runnersRowIndex += 1; if ( runnersRowIndex === rowsForRunner) { if ( runnersSplits.length === ( parseInt( event.courses[ courseIndex].controls, 10) + 1)) { runnersData.splits = {}; runnersData.splits.raw = runnersSplits; // add the splits to the runner // add the ageclass to the course if not there already if ( event.courses[ courseIndex].ageclasses.indexOf( runnersData.ageclass) < 0) { // add the ageclass to the course event.courses[ courseIndex].ageclasses.push( runnersData.ageclass); } event.courses[ courseIndex].runners.push( runnersData); // add the runner to the course } runnersRowIndex = 0; // reset the runner row index runnersData = {}; // and the data and splits runnersSplits = []; } else if ( runnersRowIndex > rowsForRunner) { continue; } } return event; } function getResults( event) { // now go through the page and grab the runner sin each course by ageclass // so we get: // ageClass[ i] = [ runner[ 0], runner[ 1], ... runner[ max]], // runner[ i] = { name: 'name', 'time', splits: []} // splits = [ split[ 0], split[ 1], ... split[ F]] // how the page is organised: // there are groups of three tables for each course: // 1. table with one row of course info, we've already got this but it is useful as a place marker. // 2. a table with one row of headers for the results, this will tell us how many headers there // are since sometimes some fields are optional. Note that these are 'th' not 'td'. // 3. a table with the results, the first rows in this are the control numbers so the number of controls // in this course is useful since we can work out how many rows we need for the headers plus the controls. // we can get the width of the table from the number of cells but there is also a 'col' group that is // convenient. // Some of the rows here are short and must just be spacers so we need to drop them so that we can // run through the runner data without hiccuping. // Some results of team members don't have split times just '---' so we can ignore them too. // Once we reach runners with a time not in a [hh:]mm:ss format we can stop since we can't compare // non finishers. // method: // get all relevant tables by querySelector( table[width]:not([width=""]"), the first is the reporttop which // we've already looked at. so start at the second. There shoudl be three per course. // check that this is a course description by it having a 'td#c00'. if not abort and report an error. // do a couple of nextSibling to get the results headers table. // work out how many headers there are. // do a nextSibling to get the results. // count the columns, either by the count of querySelectorAll( 'col') and / or count the 'td's in a row. // do both and check that we get the same answer, abort if we don't with an error message. var tables, rows, //cells, courseIndex, //resultHeaders, columnLength, i; tables = document.querySelectorAll( 'table[width]:not([width=""])'); // check that there are three times number of courses plus one if (( event.courses.length * 3 + 1) === tables.length) { event.checkCourseNumber = true; } else { //console.log( 'There are ' + tables.length + ' tables, and ' + event.courses.length + ' courses, there should be ' + ( event.courses.length * 3 + 1) + ' tables!.'); event.checkCourseNumber = false; return event; } // preset the courseIndex courseIndex = -1; // initialise some event properties event.checkCellLength = []; // run through the tables for ( i = 1; i < tables.length; i += 1) { // check which table we are processing rows = tables[ i].querySelectorAll( 'tr'); if ( rows[ 0].querySelector( 'td#c00') !== null) { // this is the course info header, set the index and skip courseIndex += 1; } else if ( rows[ 0].querySelector( 'th') !== null && rows[ 0].querySelector( 'th').textContent === 'Pl') { // this is the results header, so count the header cells and // evaluate their indices event.courses[ courseIndex].resultHeaders = getResultHeaders( rows[ 0]); //alert( 'result headers for course ' + event.courses[ courseIndex].name + ': ' + JSON.stringify( event.resultHeaders[ courseIndex])); } else if ( rows[ 0].querySelector( 'td#c11') !== null) { // now we have the results table columnLength = tables[ i].querySelectorAll( 'col').length; event = extractResults( event, rows, courseIndex, columnLength); } } return event; } function getEventData() { // preliminary scan through the results page to get: // 1. the name and date of the event // 2. the names of the course // 3. how many controls in each // 4. how many runners in each var event, //headerTable, eventDescription, courseNameCells, courseLengthCells, courseNumberOfControlCells, courseName, courseLength, courseNumberOfControls, courseNumberOfRunners, courseNamePattern = /\s+\(\d+\)$/, // to get the number of runners in the parenthised bit at the end runners, i; event = {}; // note the source of this data if ( document.URL.indexOf( 'act.orienteering') !== -1) { event.source = 'Orienteering ACT'; } else if ( document.URL.indexOf( 'websplits') !== -1) { event.source = 'WebSplits'; } else { event.source = 'unknown'; } eventDescription = document.querySelector( 'div#reporttop table tr td nobr').textContent; event.description = eventDescription ; courseNameCells = document.querySelectorAll( 'td#c00'); courseLengthCells = document.querySelectorAll( 'td#c01'); courseNumberOfControlCells = document.querySelectorAll( 'td#c02'); event.courses = []; for ( i = 0; i < courseNameCells.length; i += 1) { event.courses[ i] = {}; // course name field is <course_name> <(no_of_runners)> courseName = courseNameCells[ i].textContent; runners = courseNamePattern.exec( courseName); courseName = courseName.replace( runners[ 0], ''); courseName = courseName.replace( /\s+/, '_'); runners = /\d+/.exec( runners[ 0]); courseNumberOfRunners = runners[ 0]; event.courses[ i].name = courseName; event.courses[ i].numberofrunners = courseNumberOfRunners; event.courses[ i].ageclasses = []; // take the Km off the end of the length courseLength = courseLengthCells[ i].textContent; courseLength = courseLength.replace( /\s+km/i, ''); event.courses[ i].length = courseLength; // and take off the 'C' and the end of this field courseNumberOfControls= courseNumberOfControlCells[ i].textContent; courseNumberOfControls = courseNumberOfControls.replace( /\s+c/i, ''); event.courses[ i].controls = courseNumberOfControls; } return event; } function getOACTResults( results) { results = getEventData(); results = getResults( results); return results; } function getNewOACTCourse( ageclass, controls, allResults){ /* see if this is the same as any other course */ /* if not then give it a new one */ /* return the index of the course in allResults*/ var thisSeq = controls.join( ''), otherSeq = '', thisCourse = '', nextCourse = 'course' + pad(( allResults.courses.length + 1).toString(), 2); for( let i = 0; i < allResults.courses.length; i++){ otherSeq = allResults.courses[ i].controlcodes.join( ''); if( thisSeq === otherSeq){ allResults.courses[ i].ageclasses.push( ageclass); return [ i, allResults]; } } if( thisCourse === ''){ /* add a new course to results */ let newCourse = {}; newCourse.name = nextCourse; newCourse.ageclasses = [ ageclass]; newCourse.controlcodes = controls; allResults.courses.push( newCourse); } return [ allResults.courses.length - 1, allResults]; } function getNewOACTResults( allResults){ /* get split results from new OACT web pages */ var ageClasses = []; let eventTitle = document.getElementsByTagName( 'h1')[0].textContent; let classHeaders = document.getElementsByTagName( 'h3'); let splitsTables = document.getElementsByClassName( 'evt-results'); /* set event title */ allResults.description = eventTitle; /* initialise other results structures */ allResults.courses = []; /* data structure allResults = { description: '', courses = [ { name: string, controlcodes:[], ageclasses: [], runners:[ { name, club, time, splits:{ raw:[]} } ] } ] }; */ /* get the list of age classes from the h3 elements */ for ( let i = 0; i < classHeaders.length; i++) { ageClasses[ i] = classHeaders[ i].textContent; } /* check whether we have the same number of age classes and result tables */ if( ageClasses.length !== splitsTables.length) { alert( 'class and results length mismatch! ' + ageClasses.length + ' ' + results.length); return 0; /* exit here, no point in going further */ } /* now work our way through the results which are shown by age class */ /* but we can get them into course by comparing control sequences */ for ( let i = 0; i < splitsTables.length; i ++) { /* set the age class since the two collections are in sync */ thisAgeClass = ageClasses[ i]; /* get the control sequence */ let th = splitsTables[ i].getElementsByTagName( 'tr')[0].getElementsByTagName( 'th'); let controls = []; for ( let j = 1; j < th.length; j++) { items = th[j].innerHTML.split( '<br>'); if( items.length > 1){ controls[ j - 1] = items[ 1]; } else { controls[ j -1] = items[ 0]; } } /* either add ageclass to an exisitng course or set up a new course */ var returnValues = getNewOACTCourse( thisAgeClass, controls, allResults); var courseIndex = returnValues[ 0]; // which course to add runners to allResults = returnValues[ 1]; // preserve changes made in the function /* now get the runners */ if( allResults.courses[ courseIndex].runners === undefined){ allResults.courses[ courseIndex].runners = []; } let runnerRows = splitsTables[ i].getElementsByClassName( 'classResult OK'); for( let j = 0; j < runnerRows.length; j++){ /* set up the runner */ let thisRowInfo = runnerRows[ j].getElementsByTagName( 'th'); let runner = {}; runner.name = thisRowInfo[ 0].textContent; runner.club = thisRowInfo[ 1].textContent; runner.time = thisRowInfo[ 2].textContent; runner.ageclass = thisAgeClass; /* add the split times */ runner.splits = {}; let thisRowTimes = runnerRows[ j].getElementsByTagName( 'td'); thisSplits = []; for( let k = 0; k < thisRowTimes.length; k++){ let elementContents = thisRowTimes[ k].getElementsByClassName( 's')[0].textContent; sTime = elementContents.split( ' ')[ 0]; thisSplits.push( sTime); } runner.splits.raw = thisSplits; allResults.courses[ courseIndex].runners.push( runner); } } //console.log( allResults); return allResults; } function getWMOCresults( results) { var message = []; var courseData = window.document.querySelectorAll( 'div.classinfo'); var ageClass = stripWhitespace( courseData[ 0].querySelector( 'span.classheader').textContent); var eventTitle = courseData[ 0].textContent; var re = new RegExp( ageClass); eventTitle = stripWhitespace( eventTitle.replace( re, '')); // extract the splits data from the web page trs = window.document.querySelectorAll( 'tr'); for ( let i = 0; i < trs.length; i += 1) { tds = trs[ i].querySelectorAll( 'td'); messageRow = []; for ( j = 0; j < tds.length; j += 1) { messageRow.push( stripWhitespace( tds[ j].textContent)); } message.push( messageRow); } results = processWMOCMessage( message, ageClass, eventTitle); return results; } // this is where we start //var window = {}; window.onload = function() { var i, j, results = {}, message = [], messageRow = [], courseData = [], eventTitle, ageClass, tds, trs, source = 'unknown', splitsButton, splitsButtonPlace, splitsStyleText = function() {/* font-family: Arial, Helvetica, sans-serif; font-size: 12px; color: #ffffff; padding: 10px 20px; background: -moz-linear-gradient( top, #42aaff 0%, #003366); background: -webkit-gradient( linear, left top, left bottom, from(#42aaff), to(#003366)); -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; border: 1px solid #003366; -moz-box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 1px rgba(255,255,255,0.5); -webkit-box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 1px rgba(255,255,255,0.5); box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 1px rgba(255,255,255,0.5); text-shadow: 0px -1px 0px rgba(000,000,000,0.7), 0px 1px 0px rgba(255,255,255,0.3); margin-bottom: 10px; */}.toString().slice( 15, -3); // determine where we are getting this data from // get source if ( document.URL.indexOf( 'winsplits') > 0) { source = 'Winsplits'; // get the event and course / age class data courseData = window.frames[ 1].document.querySelectorAll( 'table.border table a.menubar'); eventTitle = courseData[ 0].textContent; ageClass = courseData[ 1].textContent; // extract the splits data from the web page // these are in frame name 'main' trs = window.frames[ 2].document.querySelectorAll( 'tr'); for ( i = 0; i < trs.length; i += 1) { tds = trs[ i].querySelectorAll( 'td'); messageRow = []; for ( j = 0; j < tds.length; j += 1) { messageRow.push( tds[ j].textContent); } message.push( messageRow); } if ( validateMessage( message)) { results = processMessage( message, ageClass, eventTitle); } else { window.alert( 'Unusual Winsplit options selected. Only tick "position, finish times and extended information" at the foot of the page'); return 0; } results.source = source; splitsButtonPlace = window.frames[ 2].document; // make the button } //else if ( document.URL.indexOf( 'act.orienteering.asn.au/gfolder/results') > 0) { // results.source = 'Old Orienteering ACT'; // if ( document.querySelector( 'body div#reporttop tr:nth-child( 2) td nobr').textContent !== 'Split time results') { // return 0; // } // results = getOACTResults( results); // splitsButtonPlace = document; // make the button //} else if ( document.URL.indexOf( 'act.orienteering.asn.au/eventor/results/splits') > 0) { results.source = 'Orienteering ACT'; results = getNewOACTResults( results); splitsButtonPlace = document; // make the button } else if ( document.URL.indexOf( 'wmoc') > 0) { results.source = 'WMOC'; results = getWMOCresults( results); splitsButtonPlace = document; // make the button } else { window.alert( 'Fell: ' + document.URL); return 0; } // do the popup splitsButton = splitsButtonPlace.createElement( 'button'); splitsButton.setAttribute( 'class', 'splits'); splitsButton.setAttribute( 'style', splitsStyleText); splitsButton.id = 'splitsButton'; splitsButton.textContent = 'Show Visualisation!'; splitsButton.onclick = function(){ setupNewWindow( results);}; splitsButtonPlace.body.insertBefore( splitsButton, splitsButtonPlace.body.firstChild); };