// ==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);
};