1# Copyright (C) 2015 Wildfire Games.
2#
3# Permission is hereby granted, free of charge, to any person obtaining a copy
4# of this software and associated documentation files (the "Software"), to deal
5# in the Software without restriction, including without limitation the rights
6# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7# copies of the Software, and to permit persons to whom the Software is
8# furnished to do so, subject to the following conditions:
9#
10# The above copyright notice and this permission notice shall be included in
11# all copies or substantial portions of the Software.
12#
13# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
16# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19# THE SOFTWARE.
20
21import xml.etree.ElementTree as ET
22import os
23import glob
24
25AttackTypes = ["Hack","Pierce","Crush"]
26Resources = ["food", "wood", "stone", "metal"]
27
28# Generic templates to load
29# The way this works is it tries all generic templates
30# But only loads those who have one of the following parents
31# EG adding "template_unit.xml" will load all units.
32LoadTemplatesIfParent = ["template_unit_infantry.xml", "template_unit_cavalry.xml", "template_unit_champion.xml", "template_unit_hero.xml"]
33
34# Those describe Civs to analyze.
35# The script will load all entities that derive (to the nth degree) from one of the above templates.
36Civs = ["athen", "brit", "cart", "gaul", "iber", "kush", "mace", "maur", "pers", "ptol", "rome", "sele", "spart"]
37
38# Remote Civ templates with those strings in their name.
39FilterOut = ["marian", "thureophoros", "thorakites", "kardakes"]
40
41# Sorting parameters for the "roster variety" table
42ComparativeSortByCav = True
43ComparativeSortByChamp = True
44SortTypes = ["Support", "Pike", "Spear", "Sword", "Archer", "Javelin", "Sling", "Elephant"]	# Classes
45
46# Disable if you want the more compact basic data. Enable to allow filtering and sorting in-place.
47AddSortingOverlay = True
48
49# This is the path to the /templates/ folder to consider. Change this for mod support.
50basePath = os.path.realpath(__file__).replace("unitTables.py","") + "../../../binaries/data/mods/public/simulation/templates/"
51
52# For performance purposes, cache opened templates files.
53globalTemplatesList = {}
54
55def htbout(file, balise, value):
56	file.write("<" + balise + ">" + value + "</" + balise + ">\n" )
57def htout(file, value):
58	file.write("<p>" + value + "</p>\n" )
59
60def fastParse(templateName):
61	if templateName in globalTemplatesList:
62		return globalTemplatesList[templateName]
63	globalTemplatesList[templateName] = ET.parse(templateName)
64	return globalTemplatesList[templateName]
65
66# This function checks that a template has the given parent.
67def hasParentTemplate(UnitName, parentName):
68	Template = fastParse(UnitName)
69
70	found = False
71	Name = UnitName
72	while found != True and Template.getroot().get("parent") != None:
73		Name = Template.getroot().get("parent") + ".xml"
74		if Name == parentName:
75			return True
76		Template = ET.parse(Name)
77
78	return False
79
80def NumericStatProcess(unitValue, templateValue):
81	if not "op" in templateValue.attrib:
82		return float(templateValue.text)
83	if (templateValue.attrib["op"] == "add"):
84		unitValue += float(templateValue.text)
85	elif (templateValue.attrib["op"] == "sub"):
86		unitValue -= float(templateValue.text)
87	elif (templateValue.attrib["op"] == "mul"):
88		unitValue *= float(templateValue.text)
89	elif (templateValue.attrib["op"] == "div"):
90		unitValue /= float(templateValue.text)
91	return unitValue
92
93
94# This function parses the entity values manually.
95def CalcUnit(UnitName, existingUnit = None):
96	unit = { 'HP' : "0", "BuildTime" : "0", "Cost" : { 'food' : "0", "wood" : "0", "stone" : "0", "metal" : "0", "population" : "0"},
97	'Attack' : { "Melee" : { "Hack" : 0, "Pierce" : 0, "Crush" : 0 }, "Ranged" : { "Hack" : 0, "Pierce" : 0, "Crush" : 0 } },
98	'RepeatRate' : {"Melee" : "0", "Ranged" : "0"},'PrepRate' : {"Melee" : "0", "Ranged" : "0"}, "Armour" : { "Hack" : 0, "Pierce" : 0, "Crush" : 0},
99	"Ranged" : False, "Classes" : [], "AttackBonuses" : {}, "Restricted" : [], "WalkSpeed" : 0, "Range" : 0, "Spread" : 0,
100	"Civ" : None }
101
102	if (existingUnit != None):
103		unit = existingUnit
104
105	Template = fastParse(UnitName)
106
107	# Recursively get data from our parent which we'll override.
108	if (Template.getroot().get("parent") != None):
109		unit = CalcUnit(Template.getroot().get("parent") + ".xml", unit)
110		unit["Parent"] = Template.getroot().get("parent") + ".xml"
111
112	if (Template.find("./Identity/Civ") != None):
113		unit['Civ'] = Template.find("./Identity/Civ").text
114
115	if (Template.find("./Health/Max") != None):
116		unit['HP'] = NumericStatProcess(unit['HP'], Template.find("./Health/Max"))
117
118	if (Template.find("./Cost/BuildTime") != None):
119		unit['BuildTime'] = NumericStatProcess(unit['BuildTime'], Template.find("./Cost/BuildTime"))
120
121	if (Template.find("./Cost/Resources") != None):
122		for type in list(Template.find("./Cost/Resources")):
123			unit['Cost'][type.tag] = NumericStatProcess(unit['Cost'][type.tag], type)
124
125	if (Template.find("./Cost/Population") != None):
126		unit['Cost']["population"] = NumericStatProcess(unit['Cost']["population"], Template.find("./Cost/Population"))
127
128	if (Template.find("./Attack/Melee") != None):
129		if (Template.find("./Attack/Melee/RepeatTime") != None):
130			unit['RepeatRate']["Melee"] = NumericStatProcess(unit['RepeatRate']["Melee"], Template.find("./Attack/Melee/RepeatTime"))
131		if (Template.find("./Attack/Melee/PrepareTime") != None):
132			unit['PrepRate']["Melee"] = NumericStatProcess(unit['PrepRate']["Melee"], Template.find("./Attack/Melee/PrepareTime"))
133		for atttype in AttackTypes:
134			if (Template.find("./Attack/Melee/"+atttype) != None):
135				unit['Attack']['Melee'][atttype] = NumericStatProcess(unit['Attack']['Melee'][atttype], Template.find("./Attack/Melee/"+atttype))
136		if (Template.find("./Attack/Melee/Bonuses") != None):
137			for Bonus in Template.find("./Attack/Melee/Bonuses"):
138				Against = []
139				CivAg = []
140				if (Bonus.find("Classes") != None and Bonus.find("Classes").text != None):
141					Against = Bonus.find("Classes").text.split(" ")
142				if (Bonus.find("Civ") != None and Bonus.find("Civ").text != None):
143					CivAg = Bonus.find("Civ").text.split(" ")
144				Val = float(Bonus.find("Multiplier").text)
145				unit["AttackBonuses"][Bonus.tag] = {"Classes" : Against, "Civs" : CivAg, "Multiplier" : Val}
146		if (Template.find("./Attack/Melee/RestrictedClasses") != None):
147			newClasses = Template.find("./Attack/Melee/RestrictedClasses").text.split(" ")
148			for elem in newClasses:
149				if (elem.find("-") != -1):
150					newClasses.pop(newClasses.index(elem))
151					if elem in unit["Restricted"]:
152						unit["Restricted"].pop(newClasses.index(elem))
153			unit["Restricted"] += newClasses
154
155
156	if (Template.find("./Attack/Ranged") != None):
157		unit['Ranged'] = True
158		if (Template.find("./Attack/Ranged/MaxRange") != None):
159			unit['Range'] = NumericStatProcess(unit['Range'], Template.find("./Attack/Ranged/MaxRange"))
160		if (Template.find("./Attack/Ranged/Spread") != None):
161			unit['Spread'] = NumericStatProcess(unit['Spread'], Template.find("./Attack/Ranged/Spread"))
162		if (Template.find("./Attack/Ranged/RepeatTime") != None):
163			unit['RepeatRate']["Ranged"] = NumericStatProcess(unit['RepeatRate']["Ranged"], Template.find("./Attack/Ranged/RepeatTime"))
164		if (Template.find("./Attack/Ranged/PrepareTime") != None):
165			unit['PrepRate']["Ranged"] = NumericStatProcess(unit['PrepRate']["Ranged"], Template.find("./Attack/Ranged/PrepareTime"))
166		for atttype in AttackTypes:
167			if (Template.find("./Attack/Ranged/"+atttype) != None):
168				unit['Attack']['Ranged'][atttype] = NumericStatProcess(unit['Attack']['Ranged'][atttype], Template.find("./Attack/Ranged/"+atttype))
169		if (Template.find("./Attack/Ranged/Bonuses") != None):
170			for Bonus in Template.find("./Attack/Ranged/Bonuses"):
171				Against = []
172				CivAg = []
173				if (Bonus.find("Classes") != None and Bonus.find("Classes").text != None):
174					Against = Bonus.find("Classes").text.split(" ")
175				if (Bonus.find("Civ") != None and Bonus.find("Civ").text != None):
176					CivAg = Bonus.find("Civ").text.split(" ")
177				Val = float(Bonus.find("Multiplier").text)
178				unit["AttackBonuses"][Bonus.tag] = {"Classes" : Against, "Civs" : CivAg, "Multiplier" : Val}
179		if (Template.find("./Attack/Melee/RestrictedClasses") != None):
180			newClasses = Template.find("./Attack/Melee/RestrictedClasses").text.split(" ")
181			for elem in newClasses:
182				if (elem.find("-") != -1):
183					newClasses.pop(newClasses.index(elem))
184					if elem in unit["Restricted"]:
185						unit["Restricted"].pop(newClasses.index(elem))
186			unit["Restricted"] += newClasses
187
188	if (Template.find("./Armour") != None):
189		for atttype in AttackTypes:
190			if (Template.find("./Armour/"+atttype) != None):
191				unit['Armour'][atttype] = NumericStatProcess(unit['Armour'][atttype], Template.find("./Armour/"+atttype))
192
193	if (Template.find("./UnitMotion") != None):
194		if (Template.find("./UnitMotion/WalkSpeed") != None):
195				unit['WalkSpeed'] = NumericStatProcess(unit['WalkSpeed'], Template.find("./UnitMotion/WalkSpeed"))
196
197	if (Template.find("./Identity/VisibleClasses") != None):
198		newClasses = Template.find("./Identity/VisibleClasses").text.split(" ")
199		for elem in newClasses:
200			if (elem.find("-") != -1):
201				newClasses.pop(newClasses.index(elem))
202				if elem in unit["Classes"]:
203					unit["Classes"].pop(newClasses.index(elem))
204		unit["Classes"] += newClasses
205
206	if (Template.find("./Identity/Classes") != None):
207		newClasses = Template.find("./Identity/Classes").text.split(" ")
208		for elem in newClasses:
209			if (elem.find("-") != -1):
210				newClasses.pop(newClasses.index(elem))
211				if elem in unit["Classes"]:
212					unit["Classes"].pop(newClasses.index(elem))
213		unit["Classes"] += newClasses
214
215
216	return unit
217
218def WriteUnit(Name, UnitDict):
219	ret = "<tr>"
220
221	ret += "<td class=\"Sub\">" + Name + "</td>"
222
223	ret += "<td>" + str(int(UnitDict["HP"])) + "</td>"
224
225	ret += "<td>" +str("%.0f" % float(UnitDict["BuildTime"])) + "</td>"
226
227	ret += "<td>" + str("%.1f" % float(UnitDict["WalkSpeed"])) + "</td>"
228
229	for atype in AttackTypes:
230		PercentValue = 1.0 - (0.9 ** float(UnitDict["Armour"][atype]))
231		ret += "<td>" + str("%.0f" % float(UnitDict["Armour"][atype])) + " / " + str("%.0f" % (PercentValue*100.0)) + "%</td>"
232
233	attType = ("Ranged" if UnitDict["Ranged"] == True else "Melee")
234	if UnitDict["RepeatRate"][attType] != "0":
235		for atype in AttackTypes:
236			repeatTime = float(UnitDict["RepeatRate"][attType])/1000.0
237			ret += "<td>" + str("%.1f" % (float(UnitDict["Attack"][attType][atype])/repeatTime)) + "</td>"
238
239		ret += "<td>" + str("%.1f" % (float(UnitDict["RepeatRate"][attType])/1000.0)) + "</td>"
240	else:
241		for atype in AttackTypes:
242			ret += "<td> - </td>"
243		ret += "<td> - </td>"
244
245	if UnitDict["Ranged"] == True and UnitDict["Range"] > 0:
246		ret += "<td>" + str("%.1f" % float(UnitDict["Range"])) + "</td>"
247		spread = float(UnitDict["Spread"])
248		ret += "<td>" + str("%.1f" % spread) + "</td>"
249	else:
250		ret += "<td> - </td><td> - </td>"
251
252	for rtype in Resources:
253		ret += "<td>" + str("%.0f" % float(UnitDict["Cost"][rtype])) + "</td>"
254
255	ret += "<td>" + str("%.0f" % float(UnitDict["Cost"]["population"])) + "</td>"
256
257	ret += "<td style=\"text-align:left;\">"
258	for Bonus in UnitDict["AttackBonuses"]:
259		ret += "["
260		for classe in UnitDict["AttackBonuses"][Bonus]["Classes"]:
261			ret += classe + " "
262		ret += ': ' + str(UnitDict["AttackBonuses"][Bonus]["Multiplier"]) + "]  "
263	ret += "</td>"
264
265	ret += "</tr>\n"
266	return ret
267
268# Sort the templates dictionary.
269def SortFn(A):
270	sortVal = 0
271	for classe in SortTypes:
272		sortVal += 1
273		if classe in A[1]["Classes"]:
274			break
275	if ComparativeSortByChamp == True and A[0].find("champion") == -1:
276		sortVal -= 20
277	if ComparativeSortByCav == True and A[0].find("cavalry") == -1:
278		sortVal -= 10
279	if A[1]["Civ"] != None and A[1]["Civ"] in Civs:
280		sortVal += 100 * Civs.index(A[1]["Civ"])
281	return sortVal
282
283# helper to write coloured text.
284def WriteColouredDiff(file, diff, PositOrNegat):
285
286	def cleverParse(diff):
287		if float(diff) - int(diff) < 0.001:
288			return str(int(diff))
289		else:
290			return str("%.1f" % float(diff))
291
292	if (PositOrNegat == "positive"):
293		file.write("<td><span style=\"color:rgb(" +("200,200,200" if diff == 0 else ("180,0,0" if diff > 0 else "0,150,0")) + ");\">" + cleverParse(diff) + "</span></td>")
294	elif (PositOrNegat == "negative"):
295		file.write("<td><span style=\"color:rgb(" +("200,200,200" if diff == 0 else ("180,0,0" if diff < 0 else "0,150,0")) + ");\">" + cleverParse(diff) + "</span></td>")
296	else:
297		complain
298
299
300############################################################
301############################################################
302# Create the HTML file
303
304f = open(os.path.realpath(__file__).replace("unitTables.py","") + 'unit_summary_table.html', 'w')
305
306f.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n<html>\n<head>\n	<title>Unit Tables</title>\n	<link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>")
307htbout(f,"h1","Unit Summary Table")
308f.write("\n")
309
310os.chdir(basePath)
311
312############################################################
313# Load generic templates
314
315templates = {}
316
317htbout(f,"h2", "Units")
318
319f.write("<table id=\"genericTemplates\">\n")
320f.write("<thead><tr>")
321f.write("<th></th><th>HP</th>	<th>BuildTime</th>	<th>Speed(walk)</th>	<th colspan=\"3\">Armour</th>	<th colspan=\"6\">Attack (DPS)</th>													<th colspan=\"5\">Costs</th>						<th>Efficient Against</th> 	</tr>\n")
322f.write("<tr class=\"Label\" style=\"border-bottom:1px black solid;\">")
323f.write("<th></th><th></th>		<th></th>			<th></th>				<th>H</th><th>P</th><th>C</th>	<th>H</th><th>P</th><th>C</th><th>Rate</th><th>Range</th><th>Spread\n(/100m)</th>	<th>F</th><th>W</th><th>S</th><th>M</th><th>P</th>	<th></th>		</tr>\n</thead>\n")
324
325for template in list(glob.glob('template_*.xml')):
326	if os.path.isfile(template):
327		found = False
328		for possParent in LoadTemplatesIfParent:
329			if hasParentTemplate(template, possParent):
330				found = True
331				break
332		if found == True:
333			templates[template] = CalcUnit(template)
334			f.write(WriteUnit(template, templates[template]))
335
336f.write("</table>")
337
338############################################################
339# Load Civ specific templates
340
341CivTemplates = {}
342
343for Civ in Civs:
344	CivTemplates[Civ] = {}
345	# Load all templates that start with that civ indicator
346	for template in list(glob.glob('units/' + Civ + '_*.xml')):
347		if os.path.isfile(template):
348
349			# filter based on FilterOut
350			breakIt = False
351			for filter in FilterOut:
352				if template.find(filter) != -1: breakIt = True
353			if breakIt: continue
354
355			# filter based on loaded generic templates
356			breakIt = True
357			for possParent in LoadTemplatesIfParent:
358				if hasParentTemplate(template, possParent):
359					breakIt = False
360					break
361			if breakIt: continue
362
363			unit = CalcUnit(template)
364
365			# Remove variants for now
366			if unit["Parent"].find("template_") == -1:
367				continue
368
369			# load template
370			CivTemplates[Civ][template] = unit
371
372############################################################
373f.write("\n\n<h2>Units Specializations</h2>\n")
374f.write("<p class=\"desc\">This table compares each template to its parent, showing the differences between the two.<br/>Note that like any table, you can copy/paste this in Excel (or Numbers or ...) and sort it.</p>")
375
376TemplatesByParent = {}
377
378#Get them in the array
379for Civ in Civs:
380	for CivUnitTemplate in CivTemplates[Civ]:
381		parent = CivTemplates[Civ][CivUnitTemplate]["Parent"]
382		if parent in templates and templates[parent]["Civ"] == None:
383			if parent not in TemplatesByParent:
384				TemplatesByParent[parent] = []
385			TemplatesByParent[parent].append( (CivUnitTemplate,CivTemplates[Civ][CivUnitTemplate]))
386
387#Sort them by civ and write them in a table.
388f.write("<table id=\"TemplateParentComp\">\n")
389f.write("<thead><tr>")
390f.write("<th></th><th></th><th>HP</th>	<th>BuildTime</th>	<th>Speed</th>	<th colspan=\"3\">Armour</th>	<th colspan=\"6\">Attack</th>												<th colspan=\"5\">Costs</th>						<th>Civ</th>	</tr>\n")
391f.write("<tr class=\"Label\" style=\"border-bottom:1px black solid;\">")
392f.write("<th></th><th></th><th></th>	<th></th>			<th></th>		<th>H</th><th>P</th><th>C</th>	<th>H</th><th>P</th><th>C</th><th>Rate</th><th>Range</th><th>Spread</th>	<th>F</th><th>W</th><th>S</th><th>M</th><th>P</th>	<th></th>		</tr>\n<tr></thead>")
393for parent in TemplatesByParent:
394	TemplatesByParent[parent].sort(key=lambda x : Civs.index(x[1]["Civ"]))
395	for tp in TemplatesByParent[parent]:
396		f.write("<th style='font-size:10px'>" + parent.replace(".xml","").replace("template_","") + "</th>")
397
398		f.write("<td class=\"Sub\">" + tp[0].replace(".xml","").replace("units/","") + "</td>")
399
400		# HP
401		diff = int(tp[1]["HP"]) - int(templates[parent]["HP"])
402		WriteColouredDiff(f, diff, "negative")
403
404		# Build Time
405		diff = int(tp[1]["BuildTime"]) - int(templates[parent]["BuildTime"])
406		WriteColouredDiff(f, diff, "positive")
407
408		# walk speed
409		diff = float(tp[1]["WalkSpeed"]) - float(templates[parent]["WalkSpeed"])
410		WriteColouredDiff(f, diff, "negative")
411
412		# Armor
413		for atype in AttackTypes:
414			diff = float(tp[1]["Armour"][atype]) - float(templates[parent]["Armour"][atype])
415			WriteColouredDiff(f, diff, "negative")
416
417		# Attack types (DPS) and rate.
418		attType = ("Ranged" if tp[1]["Ranged"] == True else "Melee")
419		if tp[1]["RepeatRate"][attType] != "0":
420			for atype in AttackTypes:
421				myDPS = float(tp[1]["Attack"][attType][atype]) / (float(tp[1]["RepeatRate"][attType])/1000.0)
422				parentDPS = float(templates[parent]["Attack"][attType][atype]) / (float(templates[parent]["RepeatRate"][attType])/1000.0)
423				WriteColouredDiff(f, myDPS - parentDPS, "negative")
424			WriteColouredDiff(f, float(tp[1]["RepeatRate"][attType])/1000.0 - float(templates[parent]["RepeatRate"][attType])/1000.0, "negative")
425			# range and spread
426			if tp[1]["Ranged"] == True:
427				WriteColouredDiff(f, float(tp[1]["Range"]) - float(templates[parent]["Range"]), "negative")
428				mySpread = float(tp[1]["Spread"])
429				parentSpread = float(templates[parent]["Spread"])
430				WriteColouredDiff(f,  mySpread - parentSpread, "positive")
431			else:
432				f.write("<td></td><td></td>")
433		else:
434				f.write("<td></td><td></td><td></td><td></td><td></td><td></td>")
435
436		for rtype in Resources:
437			WriteColouredDiff(f, float(tp[1]["Cost"][rtype]) - float(templates[parent]["Cost"][rtype]), "positive")
438
439		WriteColouredDiff(f, float(tp[1]["Cost"]["population"]) - float(templates[parent]["Cost"]["population"]), "positive")
440
441		f.write("<td>" + tp[1]["Civ"] + "</td>")
442
443		f.write("</tr>\n<tr>")
444f.write("<table/>")
445
446# Table of unit having or not having some units.
447f.write("\n\n<h2>Roster Variety</h2>\n")
448f.write("<p class=\"desc\">This table show which civilizations have units who derive from each loaded generic template.<br/>Green means 1 deriving unit, blue means 2, black means 3 or more.<br/>The total is the total number of loaded units for this civ, which may be more than the total of units inheriting from loaded templates.</p>")
449f.write("<table class=\"CivRosterVariety\">\n")
450f.write("<tr><th>Template</th>\n")
451for civ in Civs:
452	f.write("<td class=\"vertical-text\">" + civ + "</td>\n")
453f.write("</tr>\n")
454
455sortedDict = sorted(templates.items(), key=SortFn)
456
457for tp in sortedDict:
458	if tp[0] not in TemplatesByParent:
459		continue
460	f.write("<tr><td>" + tp[0] +"</td>\n")
461	for civ in Civs:
462		found = 0
463		for temp in TemplatesByParent[tp[0]]:
464			if temp[1]["Civ"] == civ:
465				found += 1
466		if found == 1:
467			f.write("<td style=\"background-color:rgb(0,230,0);\"></td>")
468		elif found == 2:
469			f.write("<td style=\"background-color:rgb(0,0,200);\"></td>")
470		elif found >= 3:
471			f.write("<td style=\"background-color:rgb(0,0,0);\"></td>")
472		else:
473			f.write("<td style=\"background-color:rgb(235,0,0);\"></td>")
474	f.write("</tr>\n")
475f.write("<tr style=\"margin-top:2px;border-top:2px #aaa solid;\"><th style=\"text-align:right; padding-right:10px;\">Total:</th>\n")
476for civ in Civs:
477	count = 0
478	for units in CivTemplates[civ]: count += 1
479	f.write("<td style=\"text-align:center;\">" + str(count) + "</td>\n")
480
481f.write("</tr>\n")
482
483f.write("<table/>")
484
485# Add a simple script to allow filtering on sorting directly in the HTML page.
486if AddSortingOverlay:
487	f.write("<script src=\"tablefilter/tablefilter.js\"></script>\n\
488	\n\
489	<script data-config>\n\
490	\
491		var cast = function (val) {\n\
492		console.log(val);\
493			if (+val != val)\n\
494				return -999999999999;\n\
495			return +val;\n\
496		}\n\
497	\n\n\
498		var filtersConfig = {\n\
499			base_path: 'tablefilter/',\n\
500			col_0: 'checklist',\n\
501			alternate_rows: true,\n\
502			rows_counter: true,\n\
503			btn_reset: true,\n\
504			loader: false,\n\
505			status_bar: false,\n\
506			mark_active_columns: true,\n\
507			highlight_keywords: true,\n\
508			col_number_format: [\n\
509				'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US'\n\
510			],\n\
511			filters_row_index: 2,\n\
512			headers_row_index: 1,\n\
513			extensions:[{\
514				name: 'sort',\
515				types: [\
516					'string', 'us', 'us', 'us', 'us', 'us', 'us', 'mytype', 'mytype', 'mytype', 'mytype', 'mytype', 'mytype', 'us', 'us', 'us', 'us', 'us', 'string'\
517				],\
518				on_sort_loaded: function(o, sort) {\
519					sort.addSortType('mytype',cast);\
520				},\
521			}],\n\
522			col_widths: [\n\
523				null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,'120px'\n\
524			],\n\
525		};\n\
526		var tf = new TableFilter('genericTemplates', filtersConfig,2);\n\
527		tf.init();\n\
528	\n\
529	   \
530		var secondFiltersConfig = {\n\
531			base_path: 'tablefilter/',\n\
532			col_0: 'checklist',\n\
533			col_19: 'checklist',\n\
534			alternate_rows: true,\n\
535			rows_counter: true,\n\
536			btn_reset: true,\n\
537			loader: false,\n\
538			status_bar: false,\n\
539			mark_active_columns: true,\n\
540			highlight_keywords: true,\n\
541			col_number_format: [\n\
542				null, null, 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', null\n\
543			],\n\
544			filters_row_index: 2,\n\
545			headers_row_index: 1,\n\
546			extensions:[{\
547				name: 'sort',\
548				types: [\
549					'string', 'string', 'us', 'us', 'us', 'us', 'us', 'us', 'typetwo', 'typetwo', 'typetwo', 'typetwo', 'typetwo', 'typetwo', 'us', 'us', 'us', 'us', 'us', 'string'\
550				],\
551				on_sort_loaded: function(o, sort) {\
552					sort.addSortType('typetwo',cast);\
553				},\
554			}],\n\
555			col_widths: [\n\
556				null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null\n\
557			],\n\
558		};\n\
559		\
560		var tf2 = new TableFilter('TemplateParentComp', secondFiltersConfig,2);\n\
561		tf2.init();\n\
562	\n\
563	</script>\n")
564
565
566f.write("</body>\n</html>")
567