Example #1
0
	def menuExportToHtml( self, event ):
		self.commit()
		iSelection = self.notebook.GetSelection()
		page = self.pages[iSelection]
		try:
			grid = page.getGrid()
		except:
			return
		
		try:
			pageTitle = self.pages[iSelection].getTitle()
		except:
			pageTitle = self.attrClassName[iSelection][2]
		
		if not self.fileName or len(self.fileName) < 4:
			Utils.MessageOK(self, 'You must Save before you can Export to Html', 'Excel Export')
			return

		pageTitle = Utils.RemoveDisallowedFilenameChars( pageTitle.replace('/', '_') )
		htmlfileName = self.fileName[:-4] + '-' + pageTitle + '.html'
		dlg = wx.DirDialog( self, 'Folder to write "%s"' % os.path.basename(htmlfileName),
						style=wx.DD_DEFAULT_STYLE, defaultPath=os.path.dirname(htmlfileName) )
		ret = dlg.ShowModal()
		dName = dlg.GetPath()
		dlg.Destroy()
		if ret != wx.ID_OK:
			return

		htmlfileName = os.path.join( dName, os.path.basename(htmlfileName) )

		title = self.getTitle()
		
		html = StringIO.StringIO()
		
		with tag(html, 'html'):
			with tag(html, 'head'):
				with tag(html, 'title'):
					html.write( title.replace('\n', ' ') )
				with tag(html, 'meta', dict(charset="UTF-8", author="Edward Sitarski", copyright="Edward Sitarski, 2013", generator="SeriesMgr")):
					pass
				with tag(html, 'style', dict( type="text/css")):
					html.write( '''
body{ font-family: sans-serif; }

#idRaceName {
	font-size: 200%;
	font-weight: bold;
}
#idImgHeader { box-shadow: 4px 4px 4px #888888; }
.smallfont { font-size: 80%; }
.bigfont { font-size: 120%; }
.hidden { display: none; }

table.results
{
	font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
	border-collapse:collapse;
}
table.results td, table.results th 
{
	font-size:1em;
	padding:3px 7px 2px 7px;
	text-align: left;
}
table.results th 
{
	font-size:1.1em;
	text-align:left;
	padding-top:5px;
	padding-bottom:4px;
	background-color:#7FE57F;
	color:#000000;
}
table.results tr.odd
{
	color:#000000;
	background-color:#EAF2D3;
}
table.results tr:hover
{
	color:#000000;
	background-color:#FFFFCC;
}
table.results tr.odd:hover
{
	color:#000000;
	background-color:#FFFFCC;
}

table.results td {
	border-top:1px solid #98bf21;
}

table.results td.noborder {
	border-top:0px solid #98bf21;
}

table.results td.rAlign, table.results th.rAlign {
	text-align:right;
}

table.results tr td.fastest{
	color:#000000;
	background-color:#80FF80;
}

@media print { .noprint { display: none; } }''' )

			with tag(html, 'body'):
				ExportGrid( title, grid ).toHtml(html)
		
		html = html.getvalue()
		
		try:
			with open(htmlfileName, 'wb') as fp:
				fp.write( html )
			webbrowser.open( htmlfileName, new = 2, autoraise = True )
			Utils.MessageOK(self, 'Html file written to:\n\n   %s' % htmlfileName, 'Html Write')
		except IOError:
			Utils.MessageOK(self,
						'Cannot write "%s".\n\nCheck if this spreadsheet is open.\nIf so, close it, and try again.' % htmlfileName,
						'Html File Error', iconMask=wx.ICON_ERROR )
Example #2
0
def getHtml( htmlfileName=None, seriesFileName=None ):
	model = SeriesModel.model
	scoreByTime = model.scoreByTime
	scoreByPercent = model.scoreByPercent
	scoreByTrueSkill = model.scoreByTrueSkill
	bestResultsToConsider = model.bestResultsToConsider
	mustHaveCompleted = model.mustHaveCompleted
	hasUpgrades = model.upgradePaths
	considerPrimePointsOrTimeBonus = model.considerPrimePointsOrTimeBonus
	raceResults = model.extractAllRaceResults()
	
	categoryNames = model.getCategoryNamesSortedPublish()
	if not categoryNames:
		return '<html><body>SeriesMgr: No Categories.</body></html>'
	
	HeaderNames = getHeaderNames()
	pointsForRank = { r.getFileName(): r.pointStructure for r in model.races }

	if not seriesFileName:
		seriesFileName = (os.path.splitext(Utils.mainWin.fileName)[0] if Utils.mainWin and Utils.mainWin.fileName else 'Series Results')
	title = os.path.basename( seriesFileName )
	
	licenseLinkTemplate = model.licenseLinkTemplate
	
	pointsStructures = {}
	pointsStructuresList = []
	for race in model.races:
		if race.pointStructure not in pointsStructures:
			pointsStructures[race.pointStructure] = []
			pointsStructuresList.append( race.pointStructure )
		pointsStructures[race.pointStructure].append( race )
	
	html = io.open( htmlfileName, 'w', encoding='utf-8', newline='' )
	
	def write( s ):
		html.write( unicode(s) )
	
	with tag(html, 'html'):
		with tag(html, 'head'):
			with tag(html, 'title'):
				write( title.replace('\n', ' ') )
			with tag(html, 'meta', dict(charset="UTF-8",
										author="Edward Sitarski",
										copyright="Edward Sitarski, 2013-{}".format(datetime.datetime.now().strftime('%Y')),
										generator="SeriesMgr")):
				pass
			with tag(html, 'style', dict( type="text/css")):
				write( u'''
body{ font-family: sans-serif; }

h1{ font-size: 250%; }
h2{ font-size: 200%; }

#idRaceName {
	font-size: 200%;
	font-weight: bold;
}
#idImgHeader { box-shadow: 4px 4px 4px #888888; }
.smallfont { font-size: 80%; }
.bigfont { font-size: 120%; }
.hidden { display: none; }

#buttongroup {
	margin:4px;   
	float:left;
}

#buttongroup label {
	float:left;
	margin:4px;
	background-color:#EFEFEF;
	border-radius:4px;
	border:1px solid #D0D0D0;
	overflow:auto;
	cursor: pointer;
}

#buttongroup label span {
	text-align:center;
	padding:8px 8px;
	display:block;
}

#buttongroup label input {
	position:absolute;
	top:-20px;
}

#buttongroup input:checked + span {
	background-color:#404040;
	color:#F7F7F7;
}

#buttongroup .yellow {
	background-color:#FFCC00;
	color:#333;
}

#buttongroup .blue {
	background-color:#00BFFF;
	color:#333;
}

#buttongroup .pink {
	background-color:#FF99FF;
	color:#333;
}

#buttongroup .green {
	background-color:#7FE57F;
	color:#333;
}
#buttongroup .purple {
	background-color:#B399FF;
	color:#333;
}

table.results {
	font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
	border-collapse:collapse;
}
table.results td, table.results th {
	font-size:1em;
	padding:3px 7px 2px 7px;
	text-align: left;
}
table.results th {
	font-size:1.1em;
	text-align:left;
	padding-top:5px;
	padding-bottom:4px;
	background-color:#7FE57F;
	color:#000000;
	vertical-align:bottom;
}
table.results tr.odd {
	color:#000000;
	background-color:#EAF2D3;
}

.smallFont {
	font-size: 75%;
}

table.results td.leftBorder, table.results th.leftBorder
{
	border-left:1px solid #98bf21;
}

table.results tr:hover
{
	color:#000000;
	background-color:#FFFFCC;
}
table.results tr.odd:hover
{
	color:#000000;
	background-color:#FFFFCC;
}

table.results td.colSelect
{
	color:#000000;
	background-color:#FFFFCC;
}}

table.results td {
	border-top:1px solid #98bf21;
}

table.results td.noborder {
	border-top:0px solid #98bf21;
}

table.results td.rightAlign, table.results th.rightAlign {
	text-align:right;
}

table.results td.leftAlign, table.results th.leftAlign {
	text-align:left;
}

.topAlign {
	vertical-align:top;
}

table.results th.centerAlign, table.results td.centerAlign {
	text-align:center;
}

.ignored {
	color: #999;
	font-style: italic;
}

table.points tr.odd {
	color:#000000;
	background-color:#EAF2D3;
}

.rank {
	color: #999;
	font-style: italic;
}

.points-cell {
	text-align: right;
	padding:3px 7px 2px 7px;
}

hr { clear: both; }

@media print {
	.noprint { display: none; }
	.title { page-break-after: avoid; }
}
''')

			with tag(html, 'script', dict( type="text/javascript")):
				write( u'\nvar catMax={};\n'.format( len(categoryNames) ) )
				write( u'''
function removeClass( classStr, oldClass ) {
	var classes = classStr.split( ' ' );
	var ret = [];
	for( var i = 0; i < classes.length; ++i ) {
		if( classes[i] != oldClass )
			ret.push( classes[i] );
	}
	return ret.join(' ');
}

function addClass( classStr, newClass ) {
	return removeClass( classStr, newClass ) + ' ' + newClass;
}

function selectCategory( iCat ) {
	for( var i = 0; i < catMax; ++i ) {
		var e = document.getElementById('catContent' + i);
		if( i == iCat || iCat < 0 )
			e.className = removeClass(e.className, 'hidden');
		else
			e.className = addClass(e.className, 'hidden');
	}
}

function sortTable( table, col, reverse ) {
	var tb = table.tBodies[0];
	var tr = Array.prototype.slice.call(tb.rows, 0);
	
	var parseRank = function( s ) {
		if( !s )
			return 999999;
		var fields = s.split( '(' );
		return parseInt( fields[1] );
	}
	
	var cmpPos = function( a, b ) {
		return parseInt( a.cells[0].textContent.trim() ) - parseInt( b.cells[0].textContent.trim() );
	};
	
	var MakeCmpStable = function( a, b, res ) {
		if( res != 0 )
			return res;
		return cmpPos( a, b );
	};
	
	var cmpFunc;
	if( col == 0 || col == 4 || col == 5 ) {		// Pos, Points or Gap
		cmpFunc = cmpPos;
	}
	else if( col >= 6 ) {				// Race Points/Time and Rank
		cmpFunc = function( a, b ) {
			var x = parseRank( a.cells[6+(col-6)*2+1].textContent.trim() );
			var y = parseRank( b.cells[6+(col-6)*2+1].textContent.trim() );
			return MakeCmpStable( a, b, x - y );
		};
	}
	else {								// Rider data field.
		cmpFunc = function( a, b ) {
		   return MakeCmpStable( a, b, a.cells[col].textContent.trim().localeCompare(b.cells[col].textContent.trim()) );
		};
	}
	tr = tr.sort( function (a, b) { return reverse * cmpFunc(a, b); } );
	
	for( var i = 0; i < tr.length; ++i) {
		tr[i].className = (i % 2 == 1) ? addClass(tr[i].className,'odd') : removeClass(tr[i].className,'odd');
		tb.appendChild( tr[i] );
	}
}

var ssPersist = {};
function sortTableId( iTable, iCol ) {
	var upChar = '&nbsp;&nbsp;&#x25b2;', dnChar = '&nbsp;&nbsp;&#x25bc;';
	var isNone = 0, isDn = 1, isUp = 2;
	var id = 'idUpDn' + iTable + '_' + iCol;
	var upDn = document.getElementById(id);
	var sortState = ssPersist[id] ? ssPersist[id] : isNone;
	var table = document.getElementById('idTable' + iTable);
	
	// Clear all sort states.
	var row0Len = table.tBodies[0].rows[0].cells.length;
	for( var i = 0; i < row0Len; ++i ) {
		var idCur = 'idUpDn' + iTable + '_' + i;
		var ele = document.getElementById(idCur);
		if( ele ) {
			ele.innerHTML = '';
			ssPersist[idCur] = isNone;
		}
	}

	if( iCol == 0 ) {
		sortTable( table, 0, 1 );
		return;
	}
	
	++sortState;
	switch( sortState ) {
	case isDn:
		upDn.innerHTML = dnChar;
		sortTable( table, iCol, 1 );
		break;
	case isUp:
		upDn.innerHTML = upChar;
		sortTable( table, iCol, -1 );
		break;
	default:
		sortState = isNone;
		sortTable( table, 0, 1 );
		break;
	}
	ssPersist[id] = sortState;
}
''' )

		with tag(html, 'body'):
			with tag(html, 'table'):
				with tag(html, 'tr'):
					with tag(html, 'td', dict(valign='top')):
						write( u'<img id="idImgHeader" src="{}" />'.format(getHeaderGraphicBase64()) )
					with tag(html, 'td'):
						with tag(html, 'h1', {'style': 'margin-left: 1cm;'}):
							write( cgi.escape(model.name) )
						if model.organizer:
							with tag(html, 'h2', {'style': 'margin-left: 1cm;'}):
								write( u'by {}'.format(cgi.escape(model.organizer)) )
			with tag(html, 'div', {'id':'buttongroup', 'class':'noprint'} ):
				with tag(html, 'label', {'class':'green'} ):
					with tag(html, 'input', {
							'type':"radio",
							'name':"categorySelect",
							'checked':"true",
							'onclick':"selectCategory(-1);"} ):
						with tag(html, 'span'):
							write( u'All' )
				for iTable, categoryName in enumerate(categoryNames):
					with tag(html, 'label', {'class':'green'} ):
						with tag(html, 'input', {
								'type':"radio",
								'name':"categorySelect",
								'onclick':"selectCategory({});".format(iTable)} ):
							with tag(html, 'span'):
								write( unicode(cgi.escape(categoryName)) )
			for iTable, categoryName in enumerate(categoryNames):
				results, races, potentialDuplicates = GetModelInfo.GetCategoryResults(
					categoryName,
					raceResults,
					pointsForRank,
					useMostEventsCompleted=model.useMostEventsCompleted,
					numPlacesTieBreaker=model.numPlacesTieBreaker )
				results = [rr for rr in results if rr[3] > 0]
				
				headerNames = HeaderNames + [u'{}'.format(r[1]) for r in races]
				
				with tag(html, 'div', {'id':'catContent{}'.format(iTable)} ):
					write( u'<p/>')
					write( u'<hr/>')
					
					with tag(html, 'h2', {'class':'title'}):
						write( cgi.escape(categoryName) )
					with tag(html, 'table', {'class': 'results', 'id': 'idTable{}'.format(iTable)} ):
						with tag(html, 'thead'):
							with tag(html, 'tr'):
								for iHeader, col in enumerate(HeaderNames):
									colAttr = { 'onclick': 'sortTableId({}, {})'.format(iTable, iHeader) }
									if col in ('License', 'Gap'):
										colAttr['class'] = 'noprint'
									with tag(html, 'th', colAttr):
										with tag(html, 'span', dict(id='idUpDn{}_{}'.format(iTable,iHeader)) ):
											pass
										write( unicode(cgi.escape(col).replace('\n', '<br/>\n')) )
								for iRace, r in enumerate(races):
									# r[0] = RaceData, r[1] = RaceName, r[2] = RaceURL, r[3] = Race
									with tag(html, 'th', {
											'class':'leftBorder centerAlign noprint',
											'colspan': 2,
											'onclick': 'sortTableId({}, {})'.format(iTable, len(HeaderNames) + iRace),
										} ):
										with tag(html, 'span', dict(id='idUpDn{}_{}'.format(iTable,len(HeaderNames) + iRace)) ):
											pass
										if r[2]:
											with tag(html,'a',dict(href=u'{}?raceCat={}'.format(r[2], urllib.quote(categoryName.encode('utf8')))) ):
												write( unicode(cgi.escape(r[1]).replace('\n', '<br/>\n')) )
										else:
											write( unicode(cgi.escape(r[1]).replace('\n', '<br/>\n')) )
										if r[0]:
											write( u'<br/>' )
											with tag(html, 'span', {'class': 'smallFont'}):
												write( unicode(r[0].strftime('%b %d, %Y')) )
										if not scoreByTime and not scoreByPercent and not scoreByTrueSkill:
											write( u'<br/>' )
											with tag(html, 'span', {'class': 'smallFont'}):
												write( u'Top {}'.format(len(r[3].pointStructure)) )
						with tag(html, 'tbody'):
							for pos, (name, license, team, points, gap, racePoints) in enumerate(results):
								with tag(html, 'tr', {'class':'odd'} if pos % 2 == 1 else {} ):
									with tag(html, 'td', {'class':'rightAlign'}):
										write( unicode(pos+1) )
									with tag(html, 'td'):
										write( unicode(name or u'') )
									with tag(html, 'td', {'class':'noprint'}):
										if licenseLinkTemplate and license:
											with tag(html, 'a', {'href':u'{}{}'.format(licenseLinkTemplate, license), 'target':'_blank'}):
												write( unicode(license or u'') )
										else:
											write( unicode(license or u'') )
									with tag(html, 'td'):
										write( unicode(team or '') )
									with tag(html, 'td', {'class':'rightAlign'}):
										write( unicode(points or '') )
									with tag(html, 'td', {'class':'rightAlign noprint'}):
										write( unicode(gap or '') )
									for rPoints, rRank, rPrimePoints, rTimeBonus in racePoints:
										if rPoints:
											with tag(html, 'td', {'class':'leftBorder rightAlign noprint' + (' ignored' if u'**' in u'{}'.format(rPoints) else '')}):
												write( u'{}'.format(rPoints).replace(u'[',u'').replace(u']',u'').replace(' ', '&nbsp;') )
										else:
											with tag(html, 'td', {'class':'leftBorder noprint'}):
												pass
										
										if rRank:
											if rPrimePoints:
												with tag(html, 'td', {'class':'rank noprint'}):
													write( u'({})&nbsp;+{}'.format(Utils.ordinal(rRank).replace(' ', '&nbsp;'), rPrimePoints) )
											elif rTimeBonus:
												with tag(html, 'td', {'class':'rank noprint'}):
													write( u'({})&nbsp;-{}'.format(
														Utils.ordinal(rRank).replace(' ', '&nbsp;'),
														Utils.formatTime(rTimeBonus, twoDigitMinutes=False)),
													)
											else:
												with tag(html, 'td', {'class':'rank noprint'}):
													write( u'({})'.format(Utils.ordinal(rRank).replace(' ', '&nbsp;')) )
										else:
											with tag(html, 'td', {'class':'noprint'}):
												pass
										
			#-----------------------------------------------------------------------------
			if considerPrimePointsOrTimeBonus:
				with tag(html, 'p', {'class':'noprint'}):
					if scoreByTime:
						with tag(html, 'strong'):
							with tag(html, 'span', {'style':'font-style: italic;'}):
								write( u'-MM:SS' )
						write( u' - {}'.format( u'Time Bonus subtracted from Finish Time.') )
					elif not scoreByTime and not scoreByPercent and not scoreByTrueSkill:
						with tag(html, 'strong'):
							with tag(html, 'span', {'style':'font-style: italic;'}):
								write( u'+N' )
						write( u' - {}'.format( u'Bonus Points added to Points for Place.') )
					
			if bestResultsToConsider > 0 and not scoreByTrueSkill:
				with tag(html, 'p', {'class':'noprint'}):
					with tag(html, 'strong'):
						write( u'**' )
					write( u' - {}'.format( u'Result not considered.  Not in best of {} scores.'.format(bestResultsToConsider) ) )
					
			if hasUpgrades:
				with tag(html, 'p', {'class':'noprint'}):
					with tag(html, 'strong'):
						write( u'pre-upg' )
					write( u' - {}'.format( u'Points carried forward from pre-upgrade category results (see Upgrades Progression below).' ) )
			
			if mustHaveCompleted > 0:
				with tag(html, 'p', {'class':'noprint'}):
					write( u'Participants completing fewer than {} events are not shown.'.format(mustHaveCompleted) )
			
			#-----------------------------------------------------------------------------
			if scoreByTrueSkill:
				with tag(html, 'div', {'class':'noprint'} ):
					with tag(html, 'p'):
						pass
					with tag(html, 'hr'):
						pass
					
					with tag(html, 'p'):
						with tag(html, 'h2'):
							write( u'TrueSkill' )
						with tag(html, 'p'):
							write( u"TrueSkill is a ranking method developed by Microsoft Research for the XBox.  ")
							write( u"TrueSkill maintains an estimation of the skill of each competitor.  Every time a competitor races, the system accordingly changes the perceived skill of the competitor and acquires more confidence about this perception.  This is unlike a regular points system where a points can be accumulated through regular participation: not necessarily representing  overall racing ability.  ")
						with tag(html, 'p'):
							write( u"Results are shown above in the form RR (MM,VV).  Competitor skill is represented by a normally distributed random variable with estimated mean (MM) and variance (VV).  The mean is an estimation of the skill of the competitor and the variance represents how unsure the system is about it (bigger variance = more unsure).  Competitors all start with mean = 25 and variance = 25/3 which corresponds to a zero ranking (see below).  ")
						with tag(html, 'p'):
							write( u"The parameters of each distribution are updated based on the results from each race using a Bayesian approach.  The extent of updates depends on each player's variance and on how 'surprising' the outcome is to the system. Changes to scores are negligible when outcomes are expected, but can be large when favorites surprisingly do poorly or underdogs surprisingly do well.  ")
						with tag(html, 'p'):
							write( u"RR is the skill ranking defined by RR = MM - 3 * VV.  This is a conservative estimate of the 'actual skill', which is expected to be higher than the estimate 99.7% of the time.  " )
							write( u"There is no meaning to positive or negative skill levels which are a result of the underlying mathematics.  The numbers are only meaningful relative to each other.  ")
						with tag(html, 'p'):
							write( u"The TrueSkill score can be improved by 'consistently' (say, 2-3 times in a row) finishing ahead of higher ranked competitors.  ")
							write( u"Repeatedly finishing with similarly ranked competitors will not change the score much as it isn't evidence of improvement.  ")
						with tag(html, 'p'):
							write("Full details ")
							with tag(html, 'a', {'href': 'https://www.microsoft.com/en-us/research/publication/trueskilltm-a-bayesian-skill-rating-system/'} ):
								write(u'here.')
				
			if not scoreByTime and not scoreByPercent and not scoreByTrueSkill:
				with tag(html, 'div', {'class':'noprint'} ):
					with tag(html, 'p'):
						pass
					with tag(html, 'hr'):
						pass
					
					with tag(html, 'h2'):
						write( 'Point Structures' )
					with tag(html, 'table' ):
						for ps in pointsStructuresList:
							with tag(html, 'tr'):
								for header in [ps.name, u'Races Scored with {}'.format(ps.name)]:
									with tag(html, 'th'):
										write( header )
							
							with tag(html, 'tr'):
								with tag(html, 'td', {'class': 'topAlign'}):
									write( ps.getHtml() )
								with tag(html, 'td', {'class': 'topAlign'}):
									with tag(html, 'ul'):
										for r in pointsStructures[ps]:
											with tag(html, 'li'):
												write( r.getRaceName() )
						
						with tag(html, 'tr'):
							with tag(html, 'td'):
								pass
							with tag(html, 'td'):
								pass
						
					#-----------------------------------------------------------------------------
					
					with tag(html, 'p'):
						pass
					with tag(html, 'hr'):
						pass
						
					with tag(html, 'h2'):
						write( u'Tie Breaking Rules' )
						
					with tag(html, 'p'):
						write( u"If two or more riders are tied on points, the following rules are applied in sequence until the tie is broken:" )
					isFirst = True
					tieLink = u"if still a tie, use "
					with tag(html, 'ol'):
						if model.useMostEventsCompleted:
							with tag(html, 'li'):
								write( u"{}number of events completed".format( tieLink if not isFirst else "" ) )
								isFirst = False
						if model.numPlacesTieBreaker != 0:
							finishOrdinals = [Utils.ordinal(p+1) for p in xrange(model.numPlacesTieBreaker)]
							if model.numPlacesTieBreaker == 1:
								finishStr = finishOrdinals[0]
							else:
								finishStr = u', '.join(finishOrdinals[:-1]) + u' then ' + finishOrdinals[-1]
							with tag(html, 'li'):
								write( u"{}number of {} place finishes".format( tieLink if not isFirst else "",
									finishStr,
								) )
								isFirst = False
						with tag(html, 'li'):
							write( u"{}finish position in most recent event".format(tieLink if not isFirst else "") )
							isFirst = False
					
					if hasUpgrades:
						with tag(html, 'p'):
							pass
						with tag(html, 'hr'):
							pass
						with tag(html, 'h2'):
							write( u"Upgrades Progression" )
						with tag(html, 'ol'):
							for i in xrange(len(model.upgradePaths)):
								with tag(html, 'li'):
									write( u"{}: {:.2f} points in pre-upgrade category carried forward".format(model.upgradePaths[i], model.upgradeFactors[i]) )
			#-----------------------------------------------------------------------------
			with tag(html, 'p'):
				with tag(html, 'a', dict(href='http://sites.google.com/site/crossmgrsoftware')):
					write( u'Powered by CrossMgr' )
	
	html.close()
Example #3
0
def getHtml( htmlfileName=None, seriesFileName=None ):
	model = SeriesModel.model
	scoreByPoints = model.scoreByPoints
	scoreByTime = model.scoreByTime
	bestResultsToConsider = model.bestResultsToConsider
	mustHaveCompleted = model.mustHaveCompleted
	considerPrimePointsOrTimeBonus = model.considerPrimePointsOrTimeBonus
	raceResults = model.extractAllRaceResults( adjustForUpgrades=False, isIndividual=False )
	
	categoryNames = model.getCategoryNamesSortedTeamPublish()
	if not categoryNames:
		return '<html><body>SeriesMgr: No Categories.</body></html>'
	
	HeaderNames = getHeaderNames()
	pointsForRank = { r.getFileName(): r.pointStructure for r in model.races }
	teamPointsForRank = { r.getFileName(): r.teamPointStructure for r in model.races }

	if not seriesFileName:
		seriesFileName = (os.path.splitext(Utils.mainWin.fileName)[0] if Utils.mainWin and Utils.mainWin.fileName else 'Series Results')
	title = os.path.basename( seriesFileName ) + ' Team Results'
	
	pointsStructures = {}
	pointsStructuresList = []
	for race in model.races:
		if race.pointStructure not in pointsStructures:
			pointsStructures[race.pointStructure] = []
			pointsStructuresList.append( race.pointStructure )
		pointsStructures[race.pointStructure].append( race )
	
	html = io.open( htmlfileName, 'w', encoding='utf-8', newline='' )
	
	def write( s ):
		html.write( six.text_type(s) )
	
	with tag(html, 'html'):
		with tag(html, 'head'):
			with tag(html, 'title'):
				write( title.replace('\n', ' ') )
			with tag(html, 'meta', dict(charset="UTF-8",
										author="Edward Sitarski",
										copyright="Edward Sitarski, 2013-{}".format(datetime.datetime.now().strftime('%Y')),
										generator="SeriesMgr")):
				pass
			with tag(html, 'style', dict( type="text/css")):
				write( u'''
body{ font-family: sans-serif; }

h1{ font-size: 250%; }
h2{ font-size: 200%; }

#idRaceName {
	font-size: 200%;
	font-weight: bold;
}
#idImgHeader { box-shadow: 4px 4px 4px #888888; }
.smallfont { font-size: 80%; }
.bigfont { font-size: 120%; }
.hidden { display: none; }

#buttongroup {
	margin:4px;   
	float:left;
}

#buttongroup label {
	float:left;
	margin:4px;
	background-color:#EFEFEF;
	border-radius:4px;
	border:1px solid #D0D0D0;
	overflow:auto;
	cursor: pointer;
}

#buttongroup label span {
	text-align:center;
	padding:8px 8px;
	display:block;
}

#buttongroup label input {
	position:absolute;
	top:-20px;
}

#buttongroup input:checked + span {
	background-color:#404040;
	color:#F7F7F7;
}

#buttongroup .yellow {
	background-color:#FFCC00;
	color:#333;
}

#buttongroup .blue {
	background-color:#00BFFF;
	color:#333;
}

#buttongroup .pink {
	background-color:#FF99FF;
	color:#333;
}

#buttongroup .green {
	background-color:#7FE57F;
	color:#333;
}
#buttongroup .purple {
	background-color:#B399FF;
	color:#333;
}

table.results {
	font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
	border-collapse:collapse;
}
table.results td, table.results th {
	font-size:1em;
	padding:3px 7px 2px 7px;
	text-align: left;
}
table.results th {
	font-size:1.1em;
	text-align:left;
	padding-top:5px;
	padding-bottom:4px;
	background-color:#7FE57F;
	color:#000000;
	vertical-align:bottom;
}
table.results tr.odd {
	color:#000000;
	background-color:#EAF2D3;
}

.smallFont {
	font-size: 75%;
}

table.results td.leftBorder, table.results th.leftBorder
{
	border-left:1px solid #98bf21;
}

table.results tr:hover
{
	color:#000000;
	background-color:#FFFFCC;
}
table.results tr.odd:hover
{
	color:#000000;
	background-color:#FFFFCC;
}

table.results td.colSelect
{
	color:#000000;
	background-color:#FFFFCC;
}}

table.results td {
	border-top:1px solid #98bf21;
}

table.results td.noborder {
	border-top:0px solid #98bf21;
}

table.results td.rightAlign, table.results th.rightAlign {
	text-align:right;
}

table.results td.leftAlign, table.results th.leftAlign {
	text-align:left;
}

.topAlign {
	vertical-align:top;
}

table.results th.centerAlign, table.results td.centerAlign {
	text-align:center;
}

.ignored {
	color: #999;
	font-style: italic;
}

table.points tr.odd {
	color:#000000;
	background-color:#EAF2D3;
}

.rank {
	color: #999;
	font-style: italic;
}

.points-cell {
	text-align: right;
	padding:3px 7px 2px 7px;
}

hr { clear: both; }

@media print {
	.noprint { display: none; }
	.title { page-break-after: avoid; }
}
''')

			with tag(html, 'script', dict( type="text/javascript")):
				write( u'\nvar catMax={};\n'.format( len(categoryNames) ) )
				write( u'''
function removeClass( classStr, oldClass ) {
	var classes = classStr.split( ' ' );
	var ret = [];
	for( var i = 0; i < classes.length; ++i ) {
		if( classes[i] != oldClass )
			ret.push( classes[i] );
	}
	return ret.join(' ');
}

function addClass( classStr, newClass ) {
	return removeClass( classStr, newClass ) + ' ' + newClass;
}

function selectCategory( iCat ) {
	for( var i = 0; i < catMax; ++i ) {
		var e = document.getElementById('catContent' + i);
		if( i == iCat || iCat < 0 )
			e.className = removeClass(e.className, 'hidden');
		else
			e.className = addClass(e.className, 'hidden');
	}
}

function sortTable( table, col, reverse ) {
	var tb = table.tBodies[0];
	var tr = Array.prototype.slice.call(tb.rows, 0);
	
	var parseRank = function( s ) {
		if( !s )
			return 999999;
		var fields = s.split( '(' );
		return parseInt( fields[1] );
	}
	
	var cmpPos = function( a, b ) {
		return parseInt( a.cells[0].textContent.trim() ) - parseInt( b.cells[0].textContent.trim() );
	};
	
	var MakeCmpStable = function( a, b, res ) {
		if( res != 0 )
			return res;
		return cmpPos( a, b );
	};
	
	var cmpFunc;
	if( col == 0 || col == 4 || col == 5 ) {		// Pos, Points or Gap
		cmpFunc = cmpPos;
	}
	else if( col >= 6 ) {				// Race Points/Time and Rank
		cmpFunc = function( a, b ) {
			var x = parseRank( a.cells[6+(col-6)*2+1].textContent.trim() );
			var y = parseRank( b.cells[6+(col-6)*2+1].textContent.trim() );
			return MakeCmpStable( a, b, x - y );
		};
	}
	else {								// Rider data field.
		cmpFunc = function( a, b ) {
		   return MakeCmpStable( a, b, a.cells[col].textContent.trim().localeCompare(b.cells[col].textContent.trim()) );
		};
	}
	tr = tr.sort( function (a, b) { return reverse * cmpFunc(a, b); } );
	
	for( var i = 0; i < tr.length; ++i) {
		tr[i].className = (i % 2 == 1) ? addClass(tr[i].className,'odd') : removeClass(tr[i].className,'odd');
		tb.appendChild( tr[i] );
	}
}

var ssPersist = {};
function sortTableId( iTable, iCol ) {
	var upChar = '&nbsp;&nbsp;&#x25b2;', dnChar = '&nbsp;&nbsp;&#x25bc;';
	var isNone = 0, isDn = 1, isUp = 2;
	var id = 'idUpDn' + iTable + '_' + iCol;
	var upDn = document.getElementById(id);
	var sortState = ssPersist[id] ? ssPersist[id] : isNone;
	var table = document.getElementById('idTable' + iTable);
	
	// Clear all sort states.
	var row0Len = table.tBodies[0].rows[0].cells.length;
	for( var i = 0; i < row0Len; ++i ) {
		var idCur = 'idUpDn' + iTable + '_' + i;
		var ele = document.getElementById(idCur);
		if( ele ) {
			ele.innerHTML = '';
			ssPersist[idCur] = isNone;
		}
	}

	if( iCol == 0 ) {
		sortTable( table, 0, 1 );
		return;
	}
	
	++sortState;
	switch( sortState ) {
	case isDn:
		upDn.innerHTML = dnChar;
		sortTable( table, iCol, 1 );
		break;
	case isUp:
		upDn.innerHTML = upChar;
		sortTable( table, iCol, -1 );
		break;
	default:
		sortState = isNone;
		sortTable( table, 0, 1 );
		break;
	}
	ssPersist[id] = sortState;
}
''' )

		with tag(html, 'body'):
			with tag(html, 'table'):
				with tag(html, 'tr'):
					with tag(html, 'td', dict(valign='top')):
						write( u'<img id="idImgHeader" src="{}" />'.format(getHeaderGraphicBase64()) )
					with tag(html, 'td'):
						with tag(html, 'h1', {'style': 'margin-left: 1cm;'}):
							write( cgi.escape(model.name + ' Team Results') )
						if model.organizer:
							with tag(html, 'h2', {'style': 'margin-left: 1cm;'}):
								write( u'by {}'.format(cgi.escape(model.organizer)) )
			with tag(html, 'div', {'id':'buttongroup', 'class':'noprint'} ):
				with tag(html, 'label', {'class':'green'} ):
					with tag(html, 'input', {
							'type':"radio",
							'name':"categorySelect",
							'checked':"true",
							'onclick':"selectCategory(-1);"} ):
						with tag(html, 'span'):
							write( u'All' )
				for iTable, categoryName in enumerate(categoryNames):
					with tag(html, 'label', {'class':'green'} ):
						with tag(html, 'input', {
								'type':"radio",
								'name':"categorySelect",
								'onclick':"selectCategory({});".format(iTable)} ):
							with tag(html, 'span'):
								write( six.text_type(cgi.escape(categoryName)) )
			for iTable, categoryName in enumerate(categoryNames):
				results, races = GetModelInfo.GetCategoryResultsTeam(
					categoryName,
					raceResults,
					pointsForRank,
					teamPointsForRank,
					useMostEventsCompleted=model.useMostEventsCompleted,
					numPlacesTieBreaker=model.numPlacesTieBreaker )
				results = [rr for rr in results if rr[1] > 0]
				
				headerNames = HeaderNames + [u'{}'.format(r[1]) for r in races]
				
				with tag(html, 'div', {'id':'catContent{}'.format(iTable)} ):
					write( u'<p/>')
					write( u'<hr/>')
					
					with tag(html, 'h2', {'class':'title'}):
						write( cgi.escape(categoryName) )
					with tag(html, 'table', {'class': 'results', 'id': 'idTable{}'.format(iTable)} ):
						with tag(html, 'thead'):
							with tag(html, 'tr'):
								for iHeader, col in enumerate(HeaderNames):
									colAttr = { 'onclick': 'sortTableId({}, {})'.format(iTable, iHeader) }
									if col in ('Gap',):
										colAttr['class'] = 'noprint'
									with tag(html, 'th', colAttr):
										with tag(html, 'span', dict(id='idUpDn{}_{}'.format(iTable,iHeader)) ):
											pass
										write( six.text_type(cgi.escape(col).replace('\n', '<br/>\n')) )
								for iRace, r in enumerate(races):
									# r[0] = RaceData, r[1] = RaceName, r[2] = RaceURL, r[3] = Race
									with tag(html, 'th', {
											'class':'centerAlign noprint',
											#'onclick': 'sortTableId({}, {})'.format(iTable, len(HeaderNames) + iRace),
										} ):
										with tag(html, 'span', dict(id='idUpDn{}_{}'.format(iTable,len(HeaderNames) + iRace)) ):
											pass
										if r[2]:
											with tag(html,'a',dict(href=u'{}?raceCat={}'.format(r[2], quote(categoryName.encode('utf8')))) ):
												write( six.text_type(cgi.escape(r[1]).replace('\n', '<br/>\n')) )
										else:
											write( six.text_type(cgi.escape(r[1]).replace('\n', '<br/>\n')) )
										if r[0]:
											write( u'<br/>' )
											with tag(html, 'span', {'class': 'smallFont'}):
												write( six.text_type(r[0].strftime('%b %d, %Y')) )
						with tag(html, 'tbody'):
							for pos, (team, result, gap, rrs) in enumerate(results):
								with tag(html, 'tr', {'class':'odd'} if pos % 2 == 1 else {} ):
									with tag(html, 'td', {'class':'rightAlign'}):
										write( six.text_type(pos+1) )
									with tag(html, 'td'):
										write( six.text_type(team or u'') )
									with tag(html, 'td', {'class':'rightAlign'}):
										write( six.text_type(result or '') )
									with tag(html, 'td', {'class':'rightAlign noprint'}):
										write( six.text_type(gap or '') )
									for rt in rrs:
										with tag(html, 'td', {'class': 'centerAlign noprint'}):
											write( formatTeamResults(scoreByPoints, rt) )
										
			#-----------------------------------------------------------------------------
			if considerPrimePointsOrTimeBonus:
				with tag(html, 'p', {'class':'noprint'}):
					write( u'Bonus Points added to Points for Place.' )
					
			#-----------------------------------------------------------------------------
			if scoreByPoints:
				with tag(html, 'div', {'class':'noprint'} ):
					with tag(html, 'p'):
						pass
					with tag(html, 'hr'):
						pass
					
					with tag(html, 'h2'):
						write( 'Point Structures' )
					with tag(html, 'table' ):
						for ps in pointsStructuresList:
							with tag(html, 'tr'):
								for header in [ps.name, u'Races Scored with {}'.format(ps.name)]:
									with tag(html, 'th'):
										write( header )
							
							with tag(html, 'tr'):
								with tag(html, 'td', {'class': 'topAlign'}):
									write( ps.getHtml() )
								with tag(html, 'td', {'class': 'topAlign'}):
									with tag(html, 'ul'):
										for r in pointsStructures[ps]:
											with tag(html, 'li'):
												write( r.getRaceName() )
						
						with tag(html, 'tr'):
							with tag(html, 'td'):
								pass
							with tag(html, 'td'):
								pass
						
					#-----------------------------------------------------------------------------
					
					with tag(html, 'p'):
						pass
					with tag(html, 'hr'):
						pass
						
					with tag(html, 'h2'):
						write( u'Tie Breaking Rules' )
						
					with tag(html, 'p'):
						write( u"If two or more teams are tied on points, the following rules are applied in sequence until the tie is broken:" )
					isFirst = True
					tieLink = u"if still a tie, use "
					with tag(html, 'ol'):
						if model.useMostEventsCompleted:
							with tag(html, 'li'):
								write( u"{}number of events completed".format( tieLink if not isFirst else "" ) )
								isFirst = False
						if model.numPlacesTieBreaker != 0:
							finishOrdinals = [Utils.ordinal(p+1) for p in range(model.numPlacesTieBreaker)]
							if model.numPlacesTieBreaker == 1:
								finishStr = finishOrdinals[0]
							else:
								finishStr = u', '.join(finishOrdinals[:-1]) + u' then ' + finishOrdinals[-1]
							with tag(html, 'li'):
								write( u"{}number of {} place finishes".format( tieLink if not isFirst else "",
									finishStr,
								) )
								isFirst = False
						with tag(html, 'li'):
							write( u"{}best finish position in most recent event".format(tieLink if not isFirst else "") )
							isFirst = False
					
			#-----------------------------------------------------------------------------
			with tag(html, 'p'):
				with tag(html, 'a', dict(href='http://sites.google.com/site/crossmgrsoftware')):
					write( u'Powered by CrossMgr' )
	
	html.close()
Example #4
0
    def menuExportToHtml(self, event):
        self.commit()
        iSelection = self.notebook.GetSelection()
        page = self.pages[iSelection]
        try:
            grid = page.getGrid()
        except:
            return

        try:
            pageTitle = self.pages[iSelection].getTitle()
        except:
            pageTitle = self.attrClassName[iSelection][2]

        if not self.fileName or len(self.fileName) < 4:
            Utils.MessageOK(self,
                            'You must Save before you can Export to Html',
                            'Html Export')
            return

        pageTitle = Utils.RemoveDisallowedFilenameChars(
            pageTitle.replace('/', '_'))
        htmlfileName = self.fileName[:-4] + '-' + pageTitle + '.html'
        dlg = wx.DirDialog(self,
                           'Folder to write "{}"'.format(
                               os.path.basename(htmlfileName)),
                           style=wx.DD_DEFAULT_STYLE,
                           defaultPath=os.path.dirname(htmlfileName))
        ret = dlg.ShowModal()
        dName = dlg.GetPath()
        dlg.Destroy()
        if ret != wx.ID_OK:
            return

        htmlfileName = os.path.join(dName, os.path.basename(htmlfileName))

        title = self.getTitle()

        html = StringIO.StringIO()

        with tag(html, 'html'):
            with tag(html, 'head'):
                with tag(html, 'title'):
                    html.write(title.replace('\n', ' '))
                with tag(html, 'meta', {'charset': 'UTF-8'}):
                    pass
                for k, v in SeriesModel.model.getMetaTags():
                    with tag(html, 'meta', {'name': k, 'content': v}):
                        pass
                with tag(html, 'style', dict(type="text/css")):
                    html.write('''
body{ font-family: sans-serif; }

#idRaceName {
	font-size: 200%;
	font-weight: bold;
}
#idImgHeader { box-shadow: 4px 4px 4px #888888; }
.smallfont { font-size: 80%; }
.bigfont { font-size: 120%; }
.hidden { display: none; }

table.results
{
	font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
	border-collapse:collapse;
}
table.results td, table.results th 
{
	font-size:1em;
	padding:3px 7px 2px 7px;
	text-align: left;
}
table.results th 
{
	font-size:1.1em;
	text-align:left;
	padding-top:5px;
	padding-bottom:4px;
	background-color:#7FE57F;
	color:#000000;
}
table.results tr.odd
{
	color:#000000;
	background-color:#EAF2D3;
}
table.results tr:hover
{
	color:#000000;
	background-color:#FFFFCC;
}
table.results tr.odd:hover
{
	color:#000000;
	background-color:#FFFFCC;
}

table.results td {
	border-top:1px solid #98bf21;
}

table.results td.noborder {
	border-top:0px solid #98bf21;
}

table.results td.rAlign, table.results th.rAlign {
	text-align:right;
}

table.results tr td.fastest{
	color:#000000;
	background-color:#80FF80;
}

@media print { .noprint { display: none; } }''')

            with tag(html, 'body'):
                ExportGrid(title, grid).toHtml(html)

        html = html.getvalue()

        try:
            with open(htmlfileName, 'wb') as fp:
                fp.write(html)
            webbrowser.open(htmlfileName, new=2, autoraise=True)
            Utils.MessageOK(self,
                            'Html file written to:\n\n   %s' % htmlfileName,
                            'Html Write')
        except IOError:
            Utils.MessageOK(
                self,
                'Cannot write "%s".\n\nCheck if this file is open.\nIf so, close it, and try again.'
                % htmlfileName,
                'Html File Error',
                iconMask=wx.ICON_ERROR)
Example #5
0
	def menuExportToHtml( self, event ):
		self.commit()
		iSelection = self.notebook.GetSelection()
		page = self.pages[iSelection]
		
		grid = None
		image = None
		try:
			grid = page.getGrid()
		except Exception as e:
			pass
		
		if not grid:
			try:
				image = page.getImage()
			except Exception as e:
				pass
				
		if not (grid or image):
			return
		
		try:
			pageTitle = self.pages[iSelection].getTitle()
		except:
			pageTitle = self.attrClassName[iSelection][2]
		
		if not self.fileName or len(self.fileName) < 4:
			Utils.MessageOK(self, u'You must Save before you can Export to Html', u'Excel Export')
			return

		pageTitle = Utils.RemoveDisallowedFilenameChars( pageTitle.replace(u'/', u'_') )
		htmlFName = self.fileName[:-4] + '-' + pageTitle + '.html'
		dlg = wx.DirDialog( self, u'Folder to write "%s"' % os.path.basename(htmlFName),
						style=wx.DD_DEFAULT_STYLE, defaultPath=os.path.dirname(htmlFName) )
		ret = dlg.ShowModal()
		dName = dlg.GetPath()
		dlg.Destroy()
		if ret != wx.ID_OK:
			return

		htmlFName = os.path.join( dName, os.path.basename(htmlFName) )

		title = self.getTitle()
		
		htmlStream = StringIO()
		html = codecs.getwriter('utf8')( htmlStream )
		
		with tag(html, 'html'):
			with tag(html, 'head'):
				with tag(html, 'title'):
					html.write( title.replace(u'\n', u' ') )
				with tag(html, 'meta', dict(charset="UTF-8", author="Edward Sitarski", copyright="Edward Sitarski, 2013", generator="SprintMgr")):
					pass
				with tag(html, 'style', dict( type="text/css")):
					html.write( '''
body{ font-family: sans-serif; }

#idRaceName {
	font-size: 200%;
	font-weight: bold;
}
#idImgHeader { box-shadow: 4px 4px 4px #888888; }
.smallfont { font-size: 80%; }
.bigfont { font-size: 120%; }
.hidden { display: none; }

table.results
{
	font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
	border-collapse:collapse;
}
table.results td, table.results th 
{
	font-size:1em;
	padding:3px 7px 2px 7px;
	text-align: left;
}
table.results th 
{
	font-size:1.1em;
	text-align:left;
	padding-top:5px;
	padding-bottom:4px;
	background-color:#7FE57F;
	color:#000000;
}
table.results tr.odd
{
	color:#000000;
	background-color:#EAF2D3;
}
table.results tr:hover
{
	color:#000000;
	background-color:#FFFFCC;
}
table.results tr.odd:hover
{
	color:#000000;
	background-color:#FFFFCC;
}

table.results td {
	border-top:1px solid #98bf21;
}

table.results td.noborder {
	border-top:0px solid #98bf21;
}

table.results td.rAlign, table.results th.rAlign {
	text-align:right;
}

table.results tr td.fastest{
	color:#000000;
	background-color:#80FF80;
}

@media print { .noprint { display: none; } }''' )

			with tag(html, 'body'):
				if grid:
					ExportGrid( title, grid ).toHtml(html)
				elif image:
					pngFName = os.path.join( dName, '{}.png'.format(uuid.uuid4()) )
					image.SaveFile( pngFName, wx.BITMAP_TYPE_PNG )
					with open(pngFName, 'rb') as fp:
						data = base64.b64encode( fp.read() )
					os.remove( pngFName )
					writeHtmlHeader( html, title )
					html.write( '<img id="idResultsSummary" src="data:image/png;base64,%s" />' % data )
		
		html = htmlStream.getvalue()
		
		try:
			with open(htmlFName, 'wb') as fp:
				fp.write( html )
			webbrowser.open( htmlFName, new = 2, autoraise = True )
			Utils.MessageOK(self, u'Html file written to:\n\n   %s' % htmlFName, 'Html Write')
		except IOError:
			Utils.MessageOK(self,
						u'Cannot write "%s".\n\nCheck if this file is open.\nIf so, close it, and try again.' % htmlFName,
						u'Html File Error', iconMask=wx.ICON_ERROR )
Example #6
0
def getHtml(htmlfileName=None, seriesFileName=None):
    model = SeriesModel.model
    scoreByPoints = model.scoreByPoints
    scoreByTime = model.scoreByTime
    bestResultsToConsider = model.bestResultsToConsider
    mustHaveCompleted = model.mustHaveCompleted
    considerPrimePointsOrTimeBonus = model.considerPrimePointsOrTimeBonus
    raceResults = model.extractAllRaceResults(False)

    categoryNames = model.getCategoryNamesSortedTeamPublish()
    if not categoryNames:
        return '<html><body>SeriesMgr: No Categories.</body></html>'

    HeaderNames = getHeaderNames()
    pointsForRank = {r.getFileName(): r.pointStructure for r in model.races}

    if not seriesFileName:
        seriesFileName = (os.path.splitext(Utils.mainWin.fileName)[0]
                          if Utils.mainWin and Utils.mainWin.fileName else
                          'Series Results')
    title = os.path.basename(seriesFileName) + ' Team Results'

    pointsStructures = {}
    pointsStructuresList = []
    for race in model.races:
        if race.pointStructure not in pointsStructures:
            pointsStructures[race.pointStructure] = []
            pointsStructuresList.append(race.pointStructure)
        pointsStructures[race.pointStructure].append(race)

    html = io.open(htmlfileName, 'w', encoding='utf-8', newline='')

    def write(s):
        html.write(unicode(s))

    with tag(html, 'html'):
        with tag(html, 'head'):
            with tag(html, 'title'):
                write(title.replace('\n', ' '))
            with tag(
                    html, 'meta',
                    dict(charset="UTF-8",
                         author="Edward Sitarski",
                         copyright="Edward Sitarski, 2013-{}".format(
                             datetime.datetime.now().strftime('%Y')),
                         generator="SeriesMgr")):
                pass
            with tag(html, 'style', dict(type="text/css")):
                write(u'''
body{ font-family: sans-serif; }

h1{ font-size: 250%; }
h2{ font-size: 200%; }

#idRaceName {
	font-size: 200%;
	font-weight: bold;
}
#idImgHeader { box-shadow: 4px 4px 4px #888888; }
.smallfont { font-size: 80%; }
.bigfont { font-size: 120%; }
.hidden { display: none; }

#buttongroup {
	margin:4px;   
	float:left;
}

#buttongroup label {
	float:left;
	margin:4px;
	background-color:#EFEFEF;
	border-radius:4px;
	border:1px solid #D0D0D0;
	overflow:auto;
	cursor: pointer;
}

#buttongroup label span {
	text-align:center;
	padding:8px 8px;
	display:block;
}

#buttongroup label input {
	position:absolute;
	top:-20px;
}

#buttongroup input:checked + span {
	background-color:#404040;
	color:#F7F7F7;
}

#buttongroup .yellow {
	background-color:#FFCC00;
	color:#333;
}

#buttongroup .blue {
	background-color:#00BFFF;
	color:#333;
}

#buttongroup .pink {
	background-color:#FF99FF;
	color:#333;
}

#buttongroup .green {
	background-color:#7FE57F;
	color:#333;
}
#buttongroup .purple {
	background-color:#B399FF;
	color:#333;
}

table.results {
	font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
	border-collapse:collapse;
}
table.results td, table.results th {
	font-size:1em;
	padding:3px 7px 2px 7px;
	text-align: left;
}
table.results th {
	font-size:1.1em;
	text-align:left;
	padding-top:5px;
	padding-bottom:4px;
	background-color:#7FE57F;
	color:#000000;
	vertical-align:bottom;
}
table.results tr.odd {
	color:#000000;
	background-color:#EAF2D3;
}

.smallFont {
	font-size: 75%;
}

table.results td.leftBorder, table.results th.leftBorder
{
	border-left:1px solid #98bf21;
}

table.results tr:hover
{
	color:#000000;
	background-color:#FFFFCC;
}
table.results tr.odd:hover
{
	color:#000000;
	background-color:#FFFFCC;
}

table.results td.colSelect
{
	color:#000000;
	background-color:#FFFFCC;
}}

table.results td {
	border-top:1px solid #98bf21;
}

table.results td.noborder {
	border-top:0px solid #98bf21;
}

table.results td.rightAlign, table.results th.rightAlign {
	text-align:right;
}

table.results td.leftAlign, table.results th.leftAlign {
	text-align:left;
}

.topAlign {
	vertical-align:top;
}

table.results th.centerAlign, table.results td.centerAlign {
	text-align:center;
}

.ignored {
	color: #999;
	font-style: italic;
}

table.points tr.odd {
	color:#000000;
	background-color:#EAF2D3;
}

.rank {
	color: #999;
	font-style: italic;
}

.points-cell {
	text-align: right;
	padding:3px 7px 2px 7px;
}

hr { clear: both; }

@media print {
	.noprint { display: none; }
	.title { page-break-after: avoid; }
}
''')

            with tag(html, 'script', dict(type="text/javascript")):
                write(u'\nvar catMax={};\n'.format(len(categoryNames)))
                write(u'''
function removeClass( classStr, oldClass ) {
	var classes = classStr.split( ' ' );
	var ret = [];
	for( var i = 0; i < classes.length; ++i ) {
		if( classes[i] != oldClass )
			ret.push( classes[i] );
	}
	return ret.join(' ');
}

function addClass( classStr, newClass ) {
	return removeClass( classStr, newClass ) + ' ' + newClass;
}

function selectCategory( iCat ) {
	for( var i = 0; i < catMax; ++i ) {
		var e = document.getElementById('catContent' + i);
		if( i == iCat || iCat < 0 )
			e.className = removeClass(e.className, 'hidden');
		else
			e.className = addClass(e.className, 'hidden');
	}
}

function sortTable( table, col, reverse ) {
	var tb = table.tBodies[0];
	var tr = Array.prototype.slice.call(tb.rows, 0);
	
	var parseRank = function( s ) {
		if( !s )
			return 999999;
		var fields = s.split( '(' );
		return parseInt( fields[1] );
	}
	
	var cmpPos = function( a, b ) {
		return parseInt( a.cells[0].textContent.trim() ) - parseInt( b.cells[0].textContent.trim() );
	};
	
	var MakeCmpStable = function( a, b, res ) {
		if( res != 0 )
			return res;
		return cmpPos( a, b );
	};
	
	var cmpFunc;
	if( col == 0 || col == 4 || col == 5 ) {		// Pos, Points or Gap
		cmpFunc = cmpPos;
	}
	else if( col >= 6 ) {				// Race Points/Time and Rank
		cmpFunc = function( a, b ) {
			var x = parseRank( a.cells[6+(col-6)*2+1].textContent.trim() );
			var y = parseRank( b.cells[6+(col-6)*2+1].textContent.trim() );
			return MakeCmpStable( a, b, x - y );
		};
	}
	else {								// Rider data field.
		cmpFunc = function( a, b ) {
		   return MakeCmpStable( a, b, a.cells[col].textContent.trim().localeCompare(b.cells[col].textContent.trim()) );
		};
	}
	tr = tr.sort( function (a, b) { return reverse * cmpFunc(a, b); } );
	
	for( var i = 0; i < tr.length; ++i) {
		tr[i].className = (i % 2 == 1) ? addClass(tr[i].className,'odd') : removeClass(tr[i].className,'odd');
		tb.appendChild( tr[i] );
	}
}

var ssPersist = {};
function sortTableId( iTable, iCol ) {
	var upChar = '&nbsp;&nbsp;&#x25b2;', dnChar = '&nbsp;&nbsp;&#x25bc;';
	var isNone = 0, isDn = 1, isUp = 2;
	var id = 'idUpDn' + iTable + '_' + iCol;
	var upDn = document.getElementById(id);
	var sortState = ssPersist[id] ? ssPersist[id] : isNone;
	var table = document.getElementById('idTable' + iTable);
	
	// Clear all sort states.
	var row0Len = table.tBodies[0].rows[0].cells.length;
	for( var i = 0; i < row0Len; ++i ) {
		var idCur = 'idUpDn' + iTable + '_' + i;
		var ele = document.getElementById(idCur);
		if( ele ) {
			ele.innerHTML = '';
			ssPersist[idCur] = isNone;
		}
	}

	if( iCol == 0 ) {
		sortTable( table, 0, 1 );
		return;
	}
	
	++sortState;
	switch( sortState ) {
	case isDn:
		upDn.innerHTML = dnChar;
		sortTable( table, iCol, 1 );
		break;
	case isUp:
		upDn.innerHTML = upChar;
		sortTable( table, iCol, -1 );
		break;
	default:
		sortState = isNone;
		sortTable( table, 0, 1 );
		break;
	}
	ssPersist[id] = sortState;
}
''')

        with tag(html, 'body'):
            with tag(html, 'table'):
                with tag(html, 'tr'):
                    with tag(html, 'td', dict(valign='top')):
                        write(u'<img id="idImgHeader" src="{}" />'.format(
                            getHeaderGraphicBase64()))
                    with tag(html, 'td'):
                        with tag(html, 'h1', {'style': 'margin-left: 1cm;'}):
                            write(cgi.escape(model.name + ' Team Results'))
                        if model.organizer:
                            with tag(html, 'h2',
                                     {'style': 'margin-left: 1cm;'}):
                                write(u'by {}'.format(
                                    cgi.escape(model.organizer)))
            with tag(html, 'div', {'id': 'buttongroup', 'class': 'noprint'}):
                with tag(html, 'label', {'class': 'green'}):
                    with tag(
                            html, 'input', {
                                'type': "radio",
                                'name': "categorySelect",
                                'checked': "true",
                                'onclick': "selectCategory(-1);"
                            }):
                        with tag(html, 'span'):
                            write(u'All')
                for iTable, categoryName in enumerate(categoryNames):
                    with tag(html, 'label', {'class': 'green'}):
                        with tag(
                                html, 'input',
                            {
                                'type': "radio",
                                'name': "categorySelect",
                                'onclick': "selectCategory({});".format(iTable)
                            }):
                            with tag(html, 'span'):
                                write(unicode(cgi.escape(categoryName)))
            for iTable, categoryName in enumerate(categoryNames):
                results, races = GetModelInfo.GetCategoryResultsTeam(
                    categoryName,
                    raceResults,
                    pointsForRank,
                    useMostEventsCompleted=model.useMostEventsCompleted,
                    numPlacesTieBreaker=model.numPlacesTieBreaker)
                results = [rr for rr in results if rr[1] > 0]

                headerNames = HeaderNames + [u'{}'.format(r[1]) for r in races]

                with tag(html, 'div', {'id': 'catContent{}'.format(iTable)}):
                    write(u'<p/>')
                    write(u'<hr/>')

                    with tag(html, 'h2', {'class': 'title'}):
                        write(cgi.escape(categoryName))
                    with tag(html, 'table', {
                            'class': 'results',
                            'id': 'idTable{}'.format(iTable)
                    }):
                        with tag(html, 'thead'):
                            with tag(html, 'tr'):
                                for iHeader, col in enumerate(HeaderNames):
                                    colAttr = {
                                        'onclick':
                                        'sortTableId({}, {})'.format(
                                            iTable, iHeader)
                                    }
                                    if col in ('Gap', ):
                                        colAttr['class'] = 'noprint'
                                    with tag(html, 'th', colAttr):
                                        with tag(
                                                html, 'span',
                                                dict(id='idUpDn{}_{}'.format(
                                                    iTable, iHeader))):
                                            pass
                                        write(
                                            unicode(
                                                cgi.escape(col).replace(
                                                    '\n', '<br/>\n')))
                                for iRace, r in enumerate(races):
                                    # r[0] = RaceData, r[1] = RaceName, r[2] = RaceURL, r[3] = Race
                                    with tag(
                                            html,
                                            'th',
                                        {
                                            'class': 'centerAlign noprint',
                                            #'onclick': 'sortTableId({}, {})'.format(iTable, len(HeaderNames) + iRace),
                                        }):
                                        with tag(
                                                html, 'span',
                                                dict(id='idUpDn{}_{}'.format(
                                                    iTable,
                                                    len(HeaderNames) +
                                                    iRace))):
                                            pass
                                        if r[2]:
                                            with tag(
                                                    html, 'a',
                                                    dict(href=u'{}?raceCat={}'.
                                                         format(
                                                             r[2],
                                                             urllib.quote(
                                                                 categoryName.
                                                                 encode('utf8'
                                                                        ))))):
                                                write(
                                                    unicode(
                                                        cgi.escape(
                                                            r[1]).replace(
                                                                '\n',
                                                                '<br/>\n')))
                                        else:
                                            write(
                                                unicode(
                                                    cgi.escape(r[1]).replace(
                                                        '\n', '<br/>\n')))
                                        if r[0]:
                                            write(u'<br/>')
                                            with tag(html, 'span',
                                                     {'class': 'smallFont'}):
                                                write(
                                                    unicode(r[0].strftime(
                                                        '%b %d, %Y')))
                        with tag(html, 'tbody'):
                            for pos, (team, result, gap,
                                      rrs) in enumerate(results):
                                with tag(html, 'tr', {'class': 'odd'} if pos %
                                         2 == 1 else {}):
                                    with tag(html, 'td',
                                             {'class': 'rightAlign'}):
                                        write(unicode(pos + 1))
                                    with tag(html, 'td'):
                                        write(unicode(team or u''))
                                    with tag(html, 'td',
                                             {'class': 'rightAlign'}):
                                        write(unicode(result or ''))
                                    with tag(html, 'td',
                                             {'class': 'rightAlign noprint'}):
                                        write(unicode(gap or ''))
                                    for rt in rrs:
                                        with tag(
                                                html, 'td',
                                            {'class': 'centerAlign noprint'}):
                                            write(
                                                formatTeamResults(
                                                    scoreByPoints, rt))

            #-----------------------------------------------------------------------------
            if considerPrimePointsOrTimeBonus:
                with tag(html, 'p', {'class': 'noprint'}):
                    write(u'Bonus Points added to Points for Place.')

            #-----------------------------------------------------------------------------
            if scoreByPoints:
                with tag(html, 'div', {'class': 'noprint'}):
                    with tag(html, 'p'):
                        pass
                    with tag(html, 'hr'):
                        pass

                    with tag(html, 'h2'):
                        write('Point Structures')
                    with tag(html, 'table'):
                        for ps in pointsStructuresList:
                            with tag(html, 'tr'):
                                for header in [
                                        ps.name,
                                        u'Races Scored with {}'.format(ps.name)
                                ]:
                                    with tag(html, 'th'):
                                        write(header)

                            with tag(html, 'tr'):
                                with tag(html, 'td', {'class': 'topAlign'}):
                                    write(ps.getHtml())
                                with tag(html, 'td', {'class': 'topAlign'}):
                                    with tag(html, 'ul'):
                                        for r in pointsStructures[ps]:
                                            with tag(html, 'li'):
                                                write(r.getRaceName())

                        with tag(html, 'tr'):
                            with tag(html, 'td'):
                                pass
                            with tag(html, 'td'):
                                pass

                    #-----------------------------------------------------------------------------

                    with tag(html, 'p'):
                        pass
                    with tag(html, 'hr'):
                        pass

                    with tag(html, 'h2'):
                        write(u'Tie Breaking Rules')

                    with tag(html, 'p'):
                        write(
                            u"If two or more teams are tied on points, the following rules are applied in sequence until the tie is broken:"
                        )
                    isFirst = True
                    tieLink = u"if still a tie, use "
                    with tag(html, 'ol'):
                        if model.useMostEventsCompleted:
                            with tag(html, 'li'):
                                write(u"{}number of events completed".format(
                                    tieLink if not isFirst else ""))
                                isFirst = False
                        if model.numPlacesTieBreaker != 0:
                            finishOrdinals = [
                                Utils.ordinal(p + 1)
                                for p in xrange(model.numPlacesTieBreaker)
                            ]
                            if model.numPlacesTieBreaker == 1:
                                finishStr = finishOrdinals[0]
                            else:
                                finishStr = u', '.join(
                                    finishOrdinals[:-1]
                                ) + u' then ' + finishOrdinals[-1]
                            with tag(html, 'li'):
                                write(u"{}number of {} place finishes".format(
                                    tieLink if not isFirst else "",
                                    finishStr,
                                ))
                                isFirst = False
                        with tag(html, 'li'):
                            write(
                                u"{}best finish position in most recent event".
                                format(tieLink if not isFirst else ""))
                            isFirst = False

            #-----------------------------------------------------------------------------
            with tag(html, 'p'):
                with tag(
                        html, 'a',
                        dict(
                            href='http://sites.google.com/site/crossmgrsoftware'
                        )):
                    write(u'Powered by CrossMgr')

    html.close()
Example #7
0
def getHtml( htmlfileName=None, seriesFileName=None ):
	model = SeriesModel.model
	scoreByTime = model.scoreByTime
	scoreByPercent = model.scoreByPercent
	scoreByTrueSkill = model.scoreByTrueSkill
	bestResultsToConsider = model.bestResultsToConsider
	mustHaveCompleted = model.mustHaveCompleted
	hasUpgrades = model.upgradePaths
	considerPrimePointsOrTimeBonus = model.considerPrimePointsOrTimeBonus
	raceResults = model.extractAllRaceResults()
	
	categoryNames = model.getCategoryNamesSortedPublish()
	if not categoryNames:
		return '<html><body>SeriesMgr: No Categories.</body></html>'
	
	HeaderNames = getHeaderNames()
	pointsForRank = { r.getFileName(): r.pointStructure for r in model.races }

	if not seriesFileName:
		seriesFileName = (os.path.splitext(Utils.mainWin.fileName)[0] if Utils.mainWin and Utils.mainWin.fileName else 'Series Results')
	title = os.path.basename( seriesFileName )
	
	licenseLinkTemplate = model.licenseLinkTemplate
	
	pointsStructures = {}
	pointsStructuresList = []
	for race in model.races:
		if race.pointStructure not in pointsStructures:
			pointsStructures[race.pointStructure] = []
			pointsStructuresList.append( race.pointStructure )
		pointsStructures[race.pointStructure].append( race )
	
	html = io.open( htmlfileName, 'w', encoding='utf-8', newline='' )
	
	def write( s ):
		html.write( six.text_type(s) )
	
	with tag(html, 'html'):
		with tag(html, 'head'):
			with tag(html, 'title'):
				write( title.replace('\n', ' ') )
			with tag(html, 'meta', {'charset':'UTF-8'}):
				pass
			for k, v in model.getMetaTags():
				with tag(html, 'meta', {'name':k, 'content':v}):
					pass
			with tag(html, 'style', dict( type="text/css")):
				write( u'''
body{ font-family: sans-serif; }

h1{ font-size: 250%; }
h2{ font-size: 200%; }

#idRaceName {
	font-size: 200%;
	font-weight: bold;
}
#idImgHeader { box-shadow: 4px 4px 4px #888888; }
.smallfont { font-size: 80%; }
.bigfont { font-size: 120%; }
.hidden { display: none; }

#buttongroup {
	margin:4px;   
	float:left;
}

#buttongroup label {
	float:left;
	margin:4px;
	background-color:#EFEFEF;
	border-radius:4px;
	border:1px solid #D0D0D0;
	overflow:auto;
	cursor: pointer;
}

#buttongroup label span {
	text-align:center;
	padding:8px 8px;
	display:block;
}

#buttongroup label input {
	position:absolute;
	top:-20px;
}

#buttongroup input:checked + span {
	background-color:#404040;
	color:#F7F7F7;
}

#buttongroup .yellow {
	background-color:#FFCC00;
	color:#333;
}

#buttongroup .blue {
	background-color:#00BFFF;
	color:#333;
}

#buttongroup .pink {
	background-color:#FF99FF;
	color:#333;
}

#buttongroup .green {
	background-color:#7FE57F;
	color:#333;
}
#buttongroup .purple {
	background-color:#B399FF;
	color:#333;
}

table.results {
	font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
	border-collapse:collapse;
}
table.results td, table.results th {
	font-size:1em;
	padding:3px 7px 2px 7px;
	text-align: left;
}
table.results th {
	font-size:1.1em;
	text-align:left;
	padding-top:5px;
	padding-bottom:4px;
	background-color:#7FE57F;
	color:#000000;
	vertical-align:bottom;
}
table.results tr.odd {
	color:#000000;
	background-color:#EAF2D3;
}

.smallFont {
	font-size: 75%;
}

table.results td.leftBorder, table.results th.leftBorder
{
	border-left:1px solid #98bf21;
}

table.results tr:hover
{
	color:#000000;
	background-color:#FFFFCC;
}
table.results tr.odd:hover
{
	color:#000000;
	background-color:#FFFFCC;
}

table.results td.colSelect
{
	color:#000000;
	background-color:#FFFFCC;
}}

table.results td {
	border-top:1px solid #98bf21;
}

table.results td.noborder {
	border-top:0px solid #98bf21;
}

table.results td.rightAlign, table.results th.rightAlign {
	text-align:right;
}

table.results td.leftAlign, table.results th.leftAlign {
	text-align:left;
}

.topAlign {
	vertical-align:top;
}

table.results th.centerAlign, table.results td.centerAlign {
	text-align:center;
}

.ignored {
	color: #999;
	font-style: italic;
}

table.points tr.odd {
	color:#000000;
	background-color:#EAF2D3;
}

.rank {
	color: #999;
	font-style: italic;
}

.points-cell {
	text-align: right;
	padding:3px 7px 2px 7px;
}

hr { clear: both; }

@media print {
	.noprint { display: none; }
	.title { page-break-after: avoid; }
}
''')

			with tag(html, 'script', dict( type="text/javascript")):
				write( u'\nvar catMax={};\n'.format( len(categoryNames) ) )
				write( u'''
function removeClass( classStr, oldClass ) {
	var classes = classStr.split( ' ' );
	var ret = [];
	for( var i = 0; i < classes.length; ++i ) {
		if( classes[i] != oldClass )
			ret.push( classes[i] );
	}
	return ret.join(' ');
}

function addClass( classStr, newClass ) {
	return removeClass( classStr, newClass ) + ' ' + newClass;
}

function selectCategory( iCat ) {
	for( var i = 0; i < catMax; ++i ) {
		var e = document.getElementById('catContent' + i);
		if( i == iCat || iCat < 0 )
			e.className = removeClass(e.className, 'hidden');
		else
			e.className = addClass(e.className, 'hidden');
	}
}

function sortTable( table, col, reverse ) {
	var tb = table.tBodies[0];
	var tr = Array.prototype.slice.call(tb.rows, 0);
	
	var parseRank = function( s ) {
		if( !s )
			return 999999;
		var fields = s.split( '(' );
		return parseInt( fields[1] );
	}
	
	var cmpPos = function( a, b ) {
		return parseInt( a.cells[0].textContent.trim() ) - parseInt( b.cells[0].textContent.trim() );
	};
	
	var MakeCmpStable = function( a, b, res ) {
		if( res != 0 )
			return res;
		return cmpPos( a, b );
	};
	
	var cmpFunc;
	if( col == 0 || col == 4 || col == 5 ) {		// Pos, Points or Gap
		cmpFunc = cmpPos;
	}
	else if( col >= 6 ) {				// Race Points/Time and Rank
		cmpFunc = function( a, b ) {
			var x = parseRank( a.cells[6+(col-6)*2+1].textContent.trim() );
			var y = parseRank( b.cells[6+(col-6)*2+1].textContent.trim() );
			return MakeCmpStable( a, b, x - y );
		};
	}
	else {								// Rider data field.
		cmpFunc = function( a, b ) {
		   return MakeCmpStable( a, b, a.cells[col].textContent.trim().localeCompare(b.cells[col].textContent.trim()) );
		};
	}
	tr = tr.sort( function (a, b) { return reverse * cmpFunc(a, b); } );
	
	for( var i = 0; i < tr.length; ++i) {
		tr[i].className = (i % 2 == 1) ? addClass(tr[i].className,'odd') : removeClass(tr[i].className,'odd');
		tb.appendChild( tr[i] );
	}
}

var ssPersist = {};
function sortTableId( iTable, iCol ) {
	var upChar = '&nbsp;&nbsp;&#x25b2;', dnChar = '&nbsp;&nbsp;&#x25bc;';
	var isNone = 0, isDn = 1, isUp = 2;
	var id = 'idUpDn' + iTable + '_' + iCol;
	var upDn = document.getElementById(id);
	var sortState = ssPersist[id] ? ssPersist[id] : isNone;
	var table = document.getElementById('idTable' + iTable);
	
	// Clear all sort states.
	var row0Len = table.tBodies[0].rows[0].cells.length;
	for( var i = 0; i < row0Len; ++i ) {
		var idCur = 'idUpDn' + iTable + '_' + i;
		var ele = document.getElementById(idCur);
		if( ele ) {
			ele.innerHTML = '';
			ssPersist[idCur] = isNone;
		}
	}

	if( iCol == 0 ) {
		sortTable( table, 0, 1 );
		return;
	}
	
	++sortState;
	switch( sortState ) {
	case isDn:
		upDn.innerHTML = dnChar;
		sortTable( table, iCol, 1 );
		break;
	case isUp:
		upDn.innerHTML = upChar;
		sortTable( table, iCol, -1 );
		break;
	default:
		sortState = isNone;
		sortTable( table, 0, 1 );
		break;
	}
	ssPersist[id] = sortState;
}
''' )

		with tag(html, 'body'):
			with tag(html, 'table'):
				with tag(html, 'tr'):
					with tag(html, 'td', dict(valign='top')):
						write( u'<img id="idImgHeader" src="{}" />'.format(getHeaderGraphicBase64()) )
					with tag(html, 'td'):
						with tag(html, 'h1', {'style': 'margin-left: 1cm;'}):
							write( cgi.escape(model.name) )
						with tag(html, 'h2', {'style': 'margin-left: 1cm;'}):
							if model.organizer:
								write( u'by {}'.format(cgi.escape(model.organizer)) )
							with tag(html, 'span', {'style': 'font-size: 60%'}):
								write( '&nbsp;' * 5 )
								write( u' Updated:&nbsp;{}'.format(datetime.datetime.now().strftime('%Y-%m-%d&nbsp;%H:%M:%S')) )
			with tag(html, 'div', {'id':'buttongroup', 'class':'noprint'} ):
				with tag(html, 'label', {'class':'green'} ):
					with tag(html, 'input', {
							'type':"radio",
							'name':"categorySelect",
							'checked':"true",
							'onclick':"selectCategory(-1);"} ):
						with tag(html, 'span'):
							write( u'All' )
				for iTable, categoryName in enumerate(categoryNames):
					with tag(html, 'label', {'class':'green'} ):
						with tag(html, 'input', {
								'type':"radio",
								'name':"categorySelect",
								'onclick':"selectCategory({});".format(iTable)} ):
							with tag(html, 'span'):
								write( six.text_type(cgi.escape(categoryName)) )
			for iTable, categoryName in enumerate(categoryNames):
				results, races, potentialDuplicates = GetModelInfo.GetCategoryResults(
					categoryName,
					raceResults,
					pointsForRank,
					useMostEventsCompleted=model.useMostEventsCompleted,
					numPlacesTieBreaker=model.numPlacesTieBreaker )
				results = [rr for rr in results if toFloat(rr[3]) > 0]
				
				headerNames = HeaderNames + [u'{}'.format(r[1]) for r in races]
				
				with tag(html, 'div', {'id':'catContent{}'.format(iTable)} ):
					write( u'<p/>')
					write( u'<hr/>')
					
					with tag(html, 'h2', {'class':'title'}):
						write( cgi.escape(categoryName) )
					with tag(html, 'table', {'class': 'results', 'id': 'idTable{}'.format(iTable)} ):
						with tag(html, 'thead'):
							with tag(html, 'tr'):
								for iHeader, col in enumerate(HeaderNames):
									colAttr = { 'onclick': 'sortTableId({}, {})'.format(iTable, iHeader) }
									if col in ('License', 'Gap'):
										colAttr['class'] = 'noprint'
									with tag(html, 'th', colAttr):
										with tag(html, 'span', dict(id='idUpDn{}_{}'.format(iTable,iHeader)) ):
											pass
										write( six.text_type(cgi.escape(col).replace('\n', '<br/>\n')) )
								for iRace, r in enumerate(races):
									# r[0] = RaceData, r[1] = RaceName, r[2] = RaceURL, r[3] = Race
									with tag(html, 'th', {
											'class':'leftBorder centerAlign noprint',
											'colspan': 2,
											'onclick': 'sortTableId({}, {})'.format(iTable, len(HeaderNames) + iRace),
										} ):
										with tag(html, 'span', dict(id='idUpDn{}_{}'.format(iTable,len(HeaderNames) + iRace)) ):
											pass
										if r[2]:
											with tag(html,'a',dict(href=u'{}?raceCat={}'.format(r[2], quote(categoryName.encode('utf8')))) ):
												write( six.text_type(cgi.escape(r[1]).replace('\n', '<br/>\n')) )
										else:
											write( six.text_type(cgi.escape(r[1]).replace('\n', '<br/>\n')) )
										if r[0]:
											write( u'<br/>' )
											with tag(html, 'span', {'class': 'smallFont'}):
												write( six.text_type(r[0].strftime('%b %d, %Y')) )
										if not scoreByTime and not scoreByPercent and not scoreByTrueSkill:
											write( u'<br/>' )
											with tag(html, 'span', {'class': 'smallFont'}):
												write( u'Top {}'.format(len(r[3].pointStructure)) )
						with tag(html, 'tbody'):
							for pos, (name, license, team, points, gap, racePoints) in enumerate(results):
								with tag(html, 'tr', {'class':'odd'} if pos % 2 == 1 else {} ):
									with tag(html, 'td', {'class':'rightAlign'}):
										write( six.text_type(pos+1) )
									with tag(html, 'td'):
										write( six.text_type(name or u'') )
									with tag(html, 'td', {'class':'noprint'}):
										if licenseLinkTemplate and license:
											with tag(html, 'a', {'href':u'{}{}'.format(licenseLinkTemplate, license), 'target':'_blank'}):
												write( six.text_type(license or u'') )
										else:
											write( six.text_type(license or u'') )
									with tag(html, 'td'):
										write( six.text_type(team or '') )
									with tag(html, 'td', {'class':'rightAlign'}):
										write( six.text_type(points or '') )
									with tag(html, 'td', {'class':'rightAlign noprint'}):
										write( six.text_type(gap or '') )
									for rPoints, rRank, rPrimePoints, rTimeBonus in racePoints:
										if rPoints:
											with tag(html, 'td', {'class':'leftBorder rightAlign noprint' + (' ignored' if u'**' in u'{}'.format(rPoints) else '')}):
												write( u'{}'.format(rPoints).replace(u'[',u'').replace(u']',u'').replace(' ', '&nbsp;') )
										else:
											with tag(html, 'td', {'class':'leftBorder noprint'}):
												pass
										
										if rRank:
											if rPrimePoints:
												with tag(html, 'td', {'class':'rank noprint'}):
													write( u'({})&nbsp;+{}'.format(Utils.ordinal(rRank).replace(' ', '&nbsp;'), rPrimePoints) )
											elif rTimeBonus:
												with tag(html, 'td', {'class':'rank noprint'}):
													write( u'({})&nbsp;-{}'.format(
														Utils.ordinal(rRank).replace(' ', '&nbsp;'),
														Utils.formatTime(rTimeBonus, twoDigitMinutes=False)),
													)
											else:
												with tag(html, 'td', {'class':'rank noprint'}):
													write( u'({})'.format(Utils.ordinal(rRank).replace(' ', '&nbsp;')) )
										else:
											with tag(html, 'td', {'class':'noprint'}):
												pass
										
			#-----------------------------------------------------------------------------
			if considerPrimePointsOrTimeBonus:
				with tag(html, 'p', {'class':'noprint'}):
					if scoreByTime:
						with tag(html, 'strong'):
							with tag(html, 'span', {'style':'font-style: italic;'}):
								write( u'-MM:SS' )
						write( u' - {}'.format( u'Time Bonus subtracted from Finish Time.') )
					elif not scoreByTime and not scoreByPercent and not scoreByTrueSkill:
						with tag(html, 'strong'):
							with tag(html, 'span', {'style':'font-style: italic;'}):
								write( u'+N' )
						write( u' - {}'.format( u'Bonus Points added to Points for Place.') )
					
			if bestResultsToConsider > 0 and not scoreByTrueSkill:
				with tag(html, 'p', {'class':'noprint'}):
					with tag(html, 'strong'):
						write( u'**' )
					write( u' - {}'.format( u'Result not considered.  Not in best of {} scores.'.format(bestResultsToConsider) ) )
					
			if hasUpgrades:
				with tag(html, 'p', {'class':'noprint'}):
					with tag(html, 'strong'):
						write( u'pre-upg' )
					write( u' - {}'.format( u'Points carried forward from pre-upgrade category results (see Upgrades Progression below).' ) )
			
			if mustHaveCompleted > 0:
				with tag(html, 'p', {'class':'noprint'}):
					write( u'Participants completing fewer than {} events are not shown.'.format(mustHaveCompleted) )
			
			#-----------------------------------------------------------------------------
			if scoreByTrueSkill:
				with tag(html, 'div', {'class':'noprint'} ):
					with tag(html, 'p'):
						pass
					with tag(html, 'hr'):
						pass
					
					with tag(html, 'p'):
						with tag(html, 'h2'):
							write( u'TrueSkill' )
						with tag(html, 'p'):
							write( u"TrueSkill is a ranking method developed by Microsoft Research for the XBox.  ")
							write( u"TrueSkill maintains an estimation of the skill of each competitor.  Every time a competitor races, the system accordingly changes the perceived skill of the competitor and acquires more confidence about this perception.  This is unlike a regular points system where a points can be accumulated through regular participation: not necessarily representing  overall racing ability.  ")
						with tag(html, 'p'):
							write( u"Results are shown above in the form RR (MM,VV).  Competitor skill is represented by a normally distributed random variable with estimated mean (MM) and variance (VV).  The mean is an estimation of the skill of the competitor and the variance represents how unsure the system is about it (bigger variance = more unsure).  Competitors all start with mean = 25 and variance = 25/3 which corresponds to a zero ranking (see below).  ")
						with tag(html, 'p'):
							write( u"The parameters of each distribution are updated based on the results from each race using a Bayesian approach.  The extent of updates depends on each player's variance and on how 'surprising' the outcome is to the system. Changes to scores are negligible when outcomes are expected, but can be large when favorites surprisingly do poorly or underdogs surprisingly do well.  ")
						with tag(html, 'p'):
							write( u"RR is the skill ranking defined by RR = MM - 3 * VV.  This is a conservative estimate of the 'actual skill', which is expected to be higher than the estimate 99.7% of the time.  " )
							write( u"There is no meaning to positive or negative skill levels which are a result of the underlying mathematics.  The numbers are only meaningful relative to each other.  ")
						with tag(html, 'p'):
							write( u"The TrueSkill score can be improved by 'consistently' (say, 2-3 times in a row) finishing ahead of higher ranked competitors.  ")
							write( u"Repeatedly finishing with similarly ranked competitors will not change the score much as it isn't evidence of improvement.  ")
						with tag(html, 'p'):
							write("Full details ")
							with tag(html, 'a', {'href': 'https://www.microsoft.com/en-us/research/publication/trueskilltm-a-bayesian-skill-rating-system/'} ):
								write(u'here.')
				
			if not scoreByTime and not scoreByPercent and not scoreByTrueSkill:
				with tag(html, 'div', {'class':'noprint'} ):
					with tag(html, 'p'):
						pass
					with tag(html, 'hr'):
						pass
					
					with tag(html, 'h2'):
						write( 'Point Structures' )
					with tag(html, 'table' ):
						for ps in pointsStructuresList:
							with tag(html, 'tr'):
								for header in [ps.name, u'Races Scored with {}'.format(ps.name)]:
									with tag(html, 'th'):
										write( header )
							
							with tag(html, 'tr'):
								with tag(html, 'td', {'class': 'topAlign'}):
									write( ps.getHtml() )
								with tag(html, 'td', {'class': 'topAlign'}):
									with tag(html, 'ul'):
										for r in pointsStructures[ps]:
											with tag(html, 'li'):
												write( r.getRaceName() )
						
						with tag(html, 'tr'):
							with tag(html, 'td'):
								pass
							with tag(html, 'td'):
								pass
						
					#-----------------------------------------------------------------------------
					
					with tag(html, 'p'):
						pass
					with tag(html, 'hr'):
						pass
						
					with tag(html, 'h2'):
						write( u'Tie Breaking Rules' )
						
					with tag(html, 'p'):
						write( u"If two or more riders are tied on points, the following rules are applied in sequence until the tie is broken:" )
					isFirst = True
					tieLink = u"if still a tie, use "
					with tag(html, 'ol'):
						if model.useMostEventsCompleted:
							with tag(html, 'li'):
								write( u"{}number of events completed".format( tieLink if not isFirst else "" ) )
								isFirst = False
						if model.numPlacesTieBreaker != 0:
							finishOrdinals = [Utils.ordinal(p+1) for p in six.moves.range(model.numPlacesTieBreaker)]
							if model.numPlacesTieBreaker == 1:
								finishStr = finishOrdinals[0]
							else:
								finishStr = u', '.join(finishOrdinals[:-1]) + u' then ' + finishOrdinals[-1]
							with tag(html, 'li'):
								write( u"{}number of {} place finishes".format( tieLink if not isFirst else "",
									finishStr,
								) )
								isFirst = False
						with tag(html, 'li'):
							write( u"{}finish position in most recent event".format(tieLink if not isFirst else "") )
							isFirst = False
					
					if hasUpgrades:
						with tag(html, 'p'):
							pass
						with tag(html, 'hr'):
							pass
						with tag(html, 'h2'):
							write( u"Upgrades Progression" )
						with tag(html, 'ol'):
							for i in six.moves.range(len(model.upgradePaths)):
								with tag(html, 'li'):
									write( u"{}: {:.2f} points in pre-upgrade category carried forward".format(model.upgradePaths[i], model.upgradeFactors[i]) )
			#-----------------------------------------------------------------------------
			with tag(html, 'p'):
				with tag(html, 'a', dict(href='http://sites.google.com/site/crossmgrsoftware')):
					write( u'Powered by CrossMgr' )
	
	html.close()
Example #8
0
def getHtml():
	model = SeriesModel.model
	scoreByTime = model.scoreByTime
	raceResults = model.extractAllRaceResults()
	
	categoryNames = sorted( set(rr.categoryName for rr in raceResults) )
	if not categoryNames:
		return ''
	
	HeaderNames = getHeaderNames()
	pointsForRank = { r.getFileName(): r.pointStructure for r in model.races }
	
	title = os.path.basename( os.path.splitext(Utils.mainWin.fileName)[0] ) if Utils.mainWin and Utils.mainWin.fileName else 'Series Results'
	
	pointsStructures = {}
	pointsStructuresList = []
	for race in model.races:
		if race.pointStructure not in pointsStructures:
			pointsStructures[race.pointStructure] = []
			pointsStructuresList.append( race.pointStructure )
		pointsStructures[race.pointStructure].append( race )
	
	html = StringIO.StringIO()
	
	with tag(html, 'html'):
		with tag(html, 'head'):
			with tag(html, 'title'):
				html.write( title.replace('\n', ' ') )
			with tag(html, 'meta', dict(charset="UTF-8",
										author="Edward Sitarski",
										copyright="Edward Sitarski, 2013-{}".format(datetime.datetime.now().strftime('%Y')),
										generator="SeriesMgr")):
				pass
			with tag(html, 'style', dict( type="text/css")):
				html.write( '''
body{ font-family: sans-serif; }

#idRaceName {
	font-size: 200%;
	font-weight: bold;
}
#idImgHeader { box-shadow: 4px 4px 4px #888888; }
.smallfont { font-size: 80%; }
.bigfont { font-size: 120%; }
.hidden { display: none; }

table.results
{
	font-family:"Trebuchet MS", Arial, Helvetica, sans-serif;
	border-collapse:collapse;
}
table.results td, table.results th 
{
	font-size:1em;
	padding:3px 7px 2px 7px;
	text-align: left;
}
table.results th 
{
	font-size:1.1em;
	text-align:left;
	padding-top:5px;
	padding-bottom:4px;
	background-color:#7FE57F;
	color:#000000;
}
table.results tr.odd
{
	color:#000000;
	background-color:#EAF2D3;
}

smallFont {
	font-size: 75%;
}

table.results td.leftBorder, table.results th.leftBorder
{
	border-left:1px solid #98bf21;
}

table.results tr:hover
{
	color:#000000;
	background-color:#FFFFCC;
}
table.results tr.odd:hover
{
	color:#000000;
	background-color:#FFFFCC;
}

table.results td.colSelect
{
	color:#000000;
	background-color:#FFFFCC;
}}

table.results td {
	border-top:1px solid #98bf21;
}

table.results td.noborder {
	border-top:0px solid #98bf21;
}

table.results td.rightAlign, table.results th.rightAlign {
	text-align:right;
}

table.results td.leftAlign, table.results th.leftAlign {
	text-align:left;
}

.topAlign {
	vertical-align:top;
}

table.results th.centerAlign, table.results td.centerAlign {
	text-align:center;
}

@media print { .noprint { display: none; } }
''')

			with tag(html, 'script', dict( type="text/javascript")):
				html.write( '''
function removeClass( classStr, oldClass ) {
	var classes = classStr.split( ' ' );
	var ret = [];
	for( var i = 0; i < classes.length; ++i ) {
		if( classes[i] != oldClass )
			ret.push( classes[i] );
	}
	return ret.join(' ');
}

function addClass( classStr, newClass ) {
	return removeClass( classStr, newClass ) + ' ' + newClass;
}

function sortTable( table, col, reverse ) {
	var tb = table.tBodies[0];
	var tr = Array.prototype.slice.call(tb.rows, 0);
	
	var parseRank = function( s ) {
		if( !s )
			return 999999;
		var fields = s.split( '(' );
		return parseInt( fields[1] );
	}
	
	var cmpPos = function( a, b ) {
		return parseInt( a.cells[0].textContent.trim() ) - parseInt( b.cells[0].textContent.trim() );
	};
	
	var MakeCmpStable = function( a, b, res ) {
		if( res != 0 )
			return res;
		return cmpPos( a, b );
	};
	
	var cmpFunc;
	if( col == 0 || col == 4 || col == 5 ) {		// Pos, Points or Gap
		cmpFunc = cmpPos;
	}
	else if( col > 4 ) {				// Race Points/Time and Rank
		cmpFunc = function( a, b ) {
			var x = parseRank( a.cells[col].textContent.trim() );
			var y = parseRank( b.cells[col].textContent.trim() );
			return MakeCmpStable( a, b, x - y );
		};
	}
	else {								// Rider data field.
		cmpFunc = function( a, b ) {
		   return MakeCmpStable( a, b, a.cells[col].textContent.trim().localeCompare(b.cells[col].textContent.trim()) );
		};
	}
	tr = tr.sort( function (a, b) { return reverse * cmpFunc(a, b); } );
	
	for( var i = 0; i < tr.length; ++i) {
		tr[i].className = (i % 2 == 1) ? addClass(tr[i].className,'odd') : removeClass(tr[i].className,'odd');
		tb.appendChild( tr[i] );
	}
}

var ssPersist = {};
function sortTableId( iTable, iCol ) {
	var upChar = '&nbsp;&nbsp;&#x25b2;', dnChar = '&nbsp;&nbsp;&#x25bc;';
	var isNone = 0, isDn = 1, isUp = 2;
	var id = 'idUpDn' + iTable + '_' + iCol;
	var upDn = document.getElementById(id);
	var sortState = ssPersist[id] ? ssPersist[id] : isNone;
	var table = document.getElementById('idTable' + iTable);
	
	// Clear all sort states.
	var row0Len = table.tBodies[0].rows[0].cells.length;
	for( var i = 0; i < row0Len; ++i ) {
		var idCur = 'idUpDn' + iTable + '_' + i;
		document.getElementById(idCur).innerHTML = '';
		ssPersist[idCur] = isNone;
	}

	if( iCol == 0 ) {
		sortTable( table, 0, 1 );
		return;
	}
	
	++sortState;
	switch( sortState ) {
	case isDn:
		upDn.innerHTML = dnChar;
		sortTable( table, iCol, 1 );
		break;
	case isUp:
		upDn.innerHTML = upChar;
		sortTable( table, iCol, -1 );
		break;
	default:
		sortState = isNone;
		sortTable( table, 0, 1 );
		break;
	}
	ssPersist[id] = sortState;
}
''' )

		with tag(html, 'body'):
			with tag(html, 'table'):
				with tag(html, 'tr'):
					with tag(html, 'td', dict(valign='top')):
						data = base64.b64encode(io.open(getHeaderGraphic(),'rb').read())
						html.write( '<img id="idImgHeader" src="data:image/png;base64,%s" />' % data )
					with tag(html, 'td'):
						with tag(html, 'h1'):
							html.write( '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + cgi.escape(title) )
			for iTable, categoryName in enumerate(categoryNames):
				results, races = GetModelInfo.GetCategoryResults(
					categoryName,
					raceResults,
					pointsForRank,
					useMostEventsCompleted=model.useMostEventsCompleted,
					numPlacesTieBreaker=model.numPlacesTieBreaker )
				results = [rr for rr in results if rr[3] > 0]
				
				headerNames = HeaderNames + [u'{}'.format(r[1]) for r in races]
				
				with tag(html, 'p'):
					pass
				with tag(html, 'hr'):
					pass
				
				with tag(html, 'h2'):
					html.write( cgi.escape(categoryName) )
				with tag(html, 'table', {'class': 'results', 'id': 'idTable{}'.format(iTable)} ):
					with tag(html, 'thead'):
						with tag(html, 'tr'):
							for iHeader, col in enumerate(HeaderNames):
								with tag(html, 'th', {
										'onclick': 'sortTableId({}, {})'.format(iTable, iHeader),
										} ):
									with tag(html, 'span', dict(id='idUpDn{}_{}'.format(iTable,iHeader)) ):
										pass
									html.write( cgi.escape(col).replace('\n', '<br/>\n') )
							for iRace, r in enumerate(races):
								# r[0] = RaceData, r[1] = RaceName, r[2] = RaceURL, r[3] = Race
								with tag(html, 'th', {
										'class':'leftBorder centerAlign',
										'onclick': 'sortTableId({}, {})'.format(iTable, len(HeaderNames) + iRace),
									} ):
									with tag(html, 'span', dict(id='idUpDn{}_{}'.format(iTable,len(HeaderNames) + iRace)) ):
										pass
									if r[2]:
										with tag(html,'a',dict(href=u'{}?raceCat={}'.format(r[2], urllib.quote(categoryName.encode('utf8')))) ):
											html.write( cgi.escape(r[1]).replace('\n', '<br/>\n') )
									else:
										html.write( cgi.escape(r[1]).replace('\n', '<br/>\n') )
									if r[0]:
										html.write( '<br/>' )
										with tag(html, 'span', {'class': 'smallFont'}):
											html.write( r[0].strftime('%b %d, %Y') )
									if not scoreByTime:
										html.write( '<br/>' )
										with tag(html, 'span', {'class': 'smallFont'}):
											html.write( u'Top {}'.format(len(r[3].pointStructure)) )
					with tag(html, 'tbody'):
						for pos, (name, license, team, points, gap, racePoints) in enumerate(results):
							with tag(html, 'tr', {'class':'odd'} if pos % 2 == 1 else {} ):
								with tag(html, 'td', {'class':'rightAlign'}):
									html.write( unicode(pos+1) )
								with tag(html, 'td'):
									html.write( unicode(name or u'') )
								with tag(html, 'td'):
									html.write( unicode(license or u'') )
								with tag(html, 'td'):
									html.write( unicode(team or '') )
								with tag(html, 'td', {'class':'rightAlign'}):
									html.write( unicode(points or '') )
								with tag(html, 'td', {'class':'rightAlign'}):
									html.write( unicode(gap or '') )
								for rPoints, rRank in racePoints:
									with tag(html, 'td', {'class':'leftBorder centerAlign'}):
										html.write( u'{} ({})'.format(rPoints, Utils.ordinal(rRank)) if rPoints else '' )
										
			#-----------------------------------------------------------------------------
			if not scoreByTime:
				with tag(html, 'p'):
					pass
				with tag(html, 'hr'):
					pass
					
				with tag(html, 'h2'):
					html.write( 'Point Structures' )
				with tag(html, 'table' ):
					for ps in pointsStructuresList:
						with tag(html, 'tr'):
							for header in [ps.name, u'Races Scored with "{}"'.format(ps.name)]:
								with tag(html, 'th'):
									html.write( header )
						
						with tag(html, 'tr'):
							with tag(html, 'td', {'class': 'topAlign'}):
								html.write( ps.getHtml() )
							with tag(html, 'td', {'class': 'topAlign'}):
								with tag(html, 'ul'):
									for r in pointsStructures[ps]:
										with tag(html, 'li'):
											html.write( r.getRaceName() )
					
					with tag(html, 'tr'):
						with tag(html, 'td'):
							pass
						with tag(html, 'td'):
							pass
						
				#-----------------------------------------------------------------------------
				
				with tag(html, 'p'):
					pass
				with tag(html, 'hr'):
					pass
					
				with tag(html, 'h2'):
					html.write( 'Tie Breaking Rules' )
					
				with tag(html, 'p'):
					html.write( "If two or more riders are tied on points, the following rules will be applied in sequence until the tie is broken:" )
				isFirst = True
				tieLink = "if still a tie, use "
				with tag(html, 'ol'):
					if model.useMostEventsCompleted:
						with tag(html, 'li'):
							html.write( "{}number of events completed".format( tieLink if not isFirst else "" ) )
							isFirst = False
					if model.numPlacesTieBreaker != 0:
						finishOrdinals = [Utils.ordinal(p+1) for p in xrange(model.numPlacesTieBreaker)]
						if model.numPlacesTieBreaker == 1:
							finishStr = finishOrdinals[0]
						else:
							finishStr = u', '.join(finishOrdinals[:-1]) + u' then ' + finishOrdinals[-1]
						with tag(html, 'li'):
							html.write( "{}number of {} place finishes".format( tieLink if not isFirst else "",
								finishStr,
							) )
							isFirst = False
					with tag(html, 'li'):
						html.write( "{}finish position in most recent event".format(tieLink if not isFirst else "") )
						isFirst = False
				
			#-----------------------------------------------------------------------------
			with tag(html, 'p'):
				with tag(html, 'a', dict(href='http://sites.google.com/site/crossmgrsoftware')):
					html.write( 'Powered by CrossMgr' )
	
	return html.getvalue()