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 )
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 = ' ▲', dnChar = ' ▼'; 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(' ', ' ') ) else: with tag(html, 'td', {'class':'leftBorder noprint'}): pass if rRank: if rPrimePoints: with tag(html, 'td', {'class':'rank noprint'}): write( u'({}) +{}'.format(Utils.ordinal(rRank).replace(' ', ' '), rPrimePoints) ) elif rTimeBonus: with tag(html, 'td', {'class':'rank noprint'}): write( u'({}) -{}'.format( Utils.ordinal(rRank).replace(' ', ' '), Utils.formatTime(rTimeBonus, twoDigitMinutes=False)), ) else: with tag(html, 'td', {'class':'rank noprint'}): write( u'({})'.format(Utils.ordinal(rRank).replace(' ', ' ')) ) 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()
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 = ' ▲', dnChar = ' ▼'; 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()
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)
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 )
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 = ' ▲', dnChar = ' ▼'; 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()
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 = ' ▲', dnChar = ' ▼'; 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( ' ' * 5 ) write( u' Updated: {}'.format(datetime.datetime.now().strftime('%Y-%m-%d %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(' ', ' ') ) else: with tag(html, 'td', {'class':'leftBorder noprint'}): pass if rRank: if rPrimePoints: with tag(html, 'td', {'class':'rank noprint'}): write( u'({}) +{}'.format(Utils.ordinal(rRank).replace(' ', ' '), rPrimePoints) ) elif rTimeBonus: with tag(html, 'td', {'class':'rank noprint'}): write( u'({}) -{}'.format( Utils.ordinal(rRank).replace(' ', ' '), Utils.formatTime(rTimeBonus, twoDigitMinutes=False)), ) else: with tag(html, 'td', {'class':'rank noprint'}): write( u'({})'.format(Utils.ordinal(rRank).replace(' ', ' ')) ) 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()
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 = ' ▲', dnChar = ' ▼'; 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( ' ' + 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()