wmlunits: Escape generated URLs and WML-defined output
Some refactoring and a lot of code changes are involved in this. Basically, wmlunits has always fully trusted its WML input. This has several implications: * URLs such as the ones for unit detail views tend to contain unescaped characters which are forbidden in URLs, such as spaces. While browsers generally tolerate this well, the result is still invalid HTML. The same applies to URLs used for inline CSS (e.g. background-image). * Most information read from WML such as unit names, descriptions, campaign names, era names, and so on, was blindly trusted and could allow an attacker to inject malicious HTML into units.wesnoth.org by uploading an add-on that would pass the units.wesnoth.org build process. The resulting code might not be extent of regressions, but hopefully we'll identify them quickly. This commit contains some additional noise in the form of style fixes around offending lines as well. Backporting to 1.12 is, as far as I understand, unnecessary since units.wesnoth.org uses the master branch version of wmlunits, but it might be worth evaluating whether to issue a warning for the 1 person in the world who might be running the 1.12 version of the units.wesnoth.org toolchain on their own facilities (okay, let's face it, nobody would ever do that).
This commit is contained in:
parent
7625b5c5b2
commit
4d5a4fc4c6
2 changed files with 210 additions and 176 deletions
|
@ -1,6 +1,8 @@
|
|||
# encoding: utf-8
|
||||
|
||||
import os, gettext, time, copy, sys, re
|
||||
import html
|
||||
import urllib
|
||||
import traceback
|
||||
import unit_tree.helpers as helpers
|
||||
import wesnoth.wmlparser3 as wmlparser3
|
||||
|
@ -109,6 +111,19 @@ def int_fallback(str_value, int_fallback = 0):
|
|||
except ValueError:
|
||||
return int_fallback
|
||||
|
||||
def cleanurl(url):
|
||||
"Encode the given URL to ensure it only contains valid URL characters "
|
||||
"(also known as percent-encoding)."
|
||||
if url is None:
|
||||
return url
|
||||
return urllib.parse.quote(url, encoding='utf-8')
|
||||
|
||||
def cleantext(text, quote=True):
|
||||
"Escape any HTML special characters in the given string."
|
||||
if text is None:
|
||||
return text
|
||||
return html.escape(text, quote)
|
||||
|
||||
class MyFile:
|
||||
"""
|
||||
Python 2 is a bit weird with encodings, really should switch this to
|
||||
|
@ -367,26 +382,38 @@ class HTMLOutput:
|
|||
abbrev += name[i]
|
||||
return abbrev
|
||||
|
||||
def add_menu(id, name, class2=""):
|
||||
def add_menu(id, name, classes='', is_table_container=False):
|
||||
html_name = cleantext(name)
|
||||
html_classes = cleantext(classes)
|
||||
write("""<li class="popuptrigger" role="menuitem" aria-haspopup="true"
|
||||
onclick="toggle_menu(this, '""" + id + """', 2)"
|
||||
onmouseover="toggle_menu(this, '""" + id + """', 1)"
|
||||
onmouseout="toggle_menu(this, '""" + id + """', 0)">""")
|
||||
write('<a class="' + class2 + '">' + name + "</a>")
|
||||
write('<ul class="popupmenu" id="' + id + '" role="menu" aria-label="' + name + '">')
|
||||
write('<li>' + name + '</li>')
|
||||
onclick="toggle_menu(this, '{0}', 2)"
|
||||
onmouseover="toggle_menu(this, '{0}', 1)"
|
||||
onmouseout="toggle_menu(this, '{0}', 0)">""".format(id))
|
||||
write('<a class="' + html_classes + '">' + html_name + "</a>")
|
||||
if not is_table_container:
|
||||
write('<ul class="popupmenu" id="' + id + '" role="menu" aria-label="' + html_name + '">')
|
||||
write('<li>' + html_name + '</li>')
|
||||
else:
|
||||
write('<div class="popupmenu" id="' + id + '" role="menu" aria-label="' + html_name + '">')
|
||||
write('<div>' + html_name + '</div>')
|
||||
|
||||
# FIXME: This is legacy code needed for the Language menu, since it's
|
||||
# a table and we can't make it otherwise since CSS column
|
||||
# support is still hit-or-miss for some browsers still in use.
|
||||
def add_menu2(id, name, class2=""):
|
||||
write("""<li class="popuptrigger" role="menuitem" aria-haspopup="true"
|
||||
onclick="toggle_menu(this, '""" + id + """', 2)"
|
||||
onmouseover="toggle_menu(this, '""" + id + """', 1)"
|
||||
onmouseout="toggle_menu(this, '""" + id + """', 0)">""")
|
||||
write('<a class="' + class2 + '">' + name + "</a>")
|
||||
write('<div class="popupmenu" id="' + id + '" role="menu" aria-label="' + name + '">')
|
||||
write('<div>' + name + '</div>')
|
||||
def add_menu2(id, name, classes=''):
|
||||
add_menu(id, name, classes, is_table_container=True)
|
||||
|
||||
def add_menuitem(url, label, standalone=False, title=''):
|
||||
if not standalone:
|
||||
write('<li>')
|
||||
extra_attr = (' title="%s"' % cleantext(title)) if title else ''
|
||||
write('<a href="%s" role="menuitem"%s>%s</a>' %
|
||||
(cleantext(url), extra_attr, cleantext(label, quote=False)))
|
||||
if not standalone:
|
||||
write('</li>')
|
||||
|
||||
def add_menuitem_placeholder():
|
||||
write('<li>' + html_entity_horizontal_bar + '</li>')
|
||||
|
||||
def end_menu():
|
||||
write('</ul></li>\n')
|
||||
|
@ -413,12 +440,10 @@ class HTMLOutput:
|
|||
target = "mainline.html"
|
||||
|
||||
if not self.is_era:
|
||||
|
||||
x = self.translate("Race", "wesnoth-lib")
|
||||
add_menu("races_menu", x)
|
||||
|
||||
write('<li><a href="mainline.html" role="menuitem">%s</a></li>\n' % (
|
||||
self.translate("all", "wesnoth-editor")))
|
||||
add_menuitem('mainline.html', self.translate('all', 'wesnoth-editor'))
|
||||
|
||||
r = {}, {}
|
||||
for u in list(self.wesnoth.unit_lookup.values()):
|
||||
|
@ -440,26 +465,22 @@ class HTMLOutput:
|
|||
|
||||
for racename, rid in racenames:
|
||||
if racename == "-":
|
||||
write('<li>%s</li>' % html_entity_horizontal_bar)
|
||||
add_menuitem_placeholder()
|
||||
else:
|
||||
write('<li><a href="%s#%s" role="menuitem">%s</a></li>' % (
|
||||
target, racename, racename))
|
||||
|
||||
url = cleanurl(target) + '#' + cleanurl(racename)
|
||||
add_menuitem(url, racename)
|
||||
end_menu()
|
||||
else:
|
||||
x = self.translate("Factions", "wesnoth-help")
|
||||
add_menu("races_menu", x)
|
||||
|
||||
for row in self.unitgrid:
|
||||
for column in range(6):
|
||||
hspan, vspan, un = row[column]
|
||||
if not un: continue
|
||||
if isinstance(un, helpers.GroupNode):
|
||||
html = "../%s/%s.html" % (
|
||||
self.isocode, self.campaign)
|
||||
write('<li><a href="%s#%s" role="menuitem">%s</a></li>' % (
|
||||
html, un.name, un.name))
|
||||
|
||||
url = cleanurl('../%s/%s.html' % (self.isocode, self.campaign))
|
||||
url += '#' + cleanurl(un.name)
|
||||
add_menuitem(url, un.name)
|
||||
end_menu()
|
||||
|
||||
# Add entries for the races also to the navbar itself.
|
||||
|
@ -490,21 +511,21 @@ class HTMLOutput:
|
|||
menuid += 1
|
||||
got_menu = True
|
||||
c = self.campaign
|
||||
if c == "units": c = "mainline"
|
||||
write('<li><a href="%s#%s" role="menuitem">%s</a></li>' % (
|
||||
target, r, r))
|
||||
if c == "units":
|
||||
c = "mainline"
|
||||
add_menuitem('%s#%s' % (cleanurl(target), cleanurl(r)), r)
|
||||
for uid in races[r]:
|
||||
un = self.wesnoth.unit_lookup[uid]
|
||||
if un.hidden: continue
|
||||
if "mainline" in un.campaigns: addon = "mainline"
|
||||
else: addon = self.addon
|
||||
link = "../../%s/%s/%s.html" % (addon, self.isocode, uid)
|
||||
link = cleanurl("../../%s/%s/%s.html" % (addon, self.isocode, uid))
|
||||
name = self.wesnoth.get_unit_value(un,
|
||||
"name", translation=self.translation.translate)
|
||||
if not name:
|
||||
error_message("Warning: Unit uid=" + uid + " has no name.\n")
|
||||
name = uid
|
||||
write('<li><a href="' + link + '" role="menuitem">' + name + '</a></li>')
|
||||
add_menuitem(link, name)
|
||||
if got_menu:
|
||||
end_menu()
|
||||
|
||||
|
@ -521,17 +542,9 @@ class HTMLOutput:
|
|||
cell += 1
|
||||
col += 1
|
||||
write("<td>")
|
||||
labb = lang
|
||||
#underscore = labb.find("_")
|
||||
#if underscore > 0: labb = labb[:underscore]
|
||||
if self.addon == "mainline":
|
||||
write('<a title="%s" href="../%s/%s" role="menuitem">%s</a>\n' % (
|
||||
languages[lang], lang, self.target,
|
||||
labb))
|
||||
else:
|
||||
write('<a title="%s" href="../%s/%s" role="menuitem">%s</a>\n' % (
|
||||
languages[lang], lang, "mainline.html",
|
||||
labb))
|
||||
url = cleanurl('../%s/%s' % (lang, self.target if self.addon == 'mainline' else 'mainline.html'))
|
||||
# TODO: Maybe use the language name instead of its code for the label?
|
||||
add_menuitem(url, lang, title=languages[lang], standalone=True)
|
||||
write("</td>")
|
||||
if cell % colcount == 0:
|
||||
if cell < lastcell:
|
||||
|
@ -643,7 +656,7 @@ class HTMLOutput:
|
|||
|
||||
pic = image_collector.add_image("general",
|
||||
"../../../images/misc/leader-crown.png", no_tc=True)
|
||||
crownimage = os.path.join(pics_location, pic)
|
||||
crownimage = cleanurl(os.path.join(pics_location, pic))
|
||||
ms = None
|
||||
for row in range(len(rows)):
|
||||
write("<tr>\n")
|
||||
|
@ -673,25 +686,29 @@ class HTMLOutput:
|
|||
racename = un.name
|
||||
# TODO: we need to use a unique race id instead of a potentially duplicate
|
||||
# name for the header id and link target!
|
||||
attributes += " id=\"%s\" class=\"raceheader\"" % racename
|
||||
attributes += " id=\"%s\" class=\"raceheader\"" % cleantext(racename)
|
||||
write("<th" + attributes + ">")
|
||||
write("<a href=\"#%s\">%s</a>" % (racename, racename))
|
||||
write("<a href=\"#%s\">%s</a>" % (cleanurl(racename), cleantext(racename, quote=False)))
|
||||
write("</th>\n")
|
||||
elif un:
|
||||
u = un.unit
|
||||
attributes += " class=\"unitcell\""
|
||||
write("<td%s>" % attributes)
|
||||
|
||||
uid = u.get_text_val("id")
|
||||
def uval(name):
|
||||
return self.wesnoth.get_unit_value(u, name,
|
||||
translation=self.translation.translate)
|
||||
name = uval("name")
|
||||
cost = uval("cost")
|
||||
hp = uval("hitpoints")
|
||||
mp = uval("movement")
|
||||
xp = uval("experience")
|
||||
level = uval("level")
|
||||
|
||||
def clean_uval(name):
|
||||
return cleantext(uval(name))
|
||||
|
||||
uid = cleantext(u.get_text_val("id"))
|
||||
name = clean_uval("name")
|
||||
cost = clean_uval("cost")
|
||||
hp = clean_uval("hitpoints")
|
||||
mp = clean_uval("movement")
|
||||
xp = clean_uval("experience")
|
||||
level = clean_uval("level")
|
||||
|
||||
crown = ""
|
||||
if ms:
|
||||
|
@ -702,14 +719,15 @@ class HTMLOutput:
|
|||
|
||||
uaddon = "mainline"
|
||||
if "mainline" not in u.campaigns: uaddon = self.addon
|
||||
link = "../../%s/%s/%s.html" % (uaddon, self.isocode, uid)
|
||||
write("<div class=\"l\">L%s%s</div>" % (level, crown))
|
||||
write("<a href=\"%s\" title=\"Id: %s\">%s</a><br/>" % (link, uid, name))
|
||||
link = cleanurl("../../%s/%s/%s.html" % (uaddon, self.isocode, uid))
|
||||
write('<div class="l">L%s%s</div>' % (level, crown))
|
||||
write('<a href="%s" title="Id: %s">%s</a><br />' % (link, uid, name))
|
||||
|
||||
write('<div class="pic">')
|
||||
image, portrait = self.pic(u, u)
|
||||
image = cleanurl(image)
|
||||
|
||||
write('<a href=\"%s\" title=\"Id: %s\">' % (link, name))
|
||||
write('<a href="%s" title="Id: %s">' % (link, name))
|
||||
|
||||
if crown == " ♚":
|
||||
write('<div class="spritebg" style="background-image:url(\'%s\')">' % image)
|
||||
|
@ -730,29 +748,28 @@ class HTMLOutput:
|
|||
(_("MP: "), mp),
|
||||
)
|
||||
for attr_label, attr_value in attributes:
|
||||
write('<tr><th>%s</th><td>%s</td></tr>' % (attr_label.strip(), attr_value))
|
||||
write('<tr><th>%s</th><td>%s</td></tr>' % (cleantext(attr_label.strip(), quote=False), attr_value))
|
||||
write('</table>')
|
||||
|
||||
# Write info about abilities.
|
||||
anames = self.get_abilities(u)
|
||||
if anames:
|
||||
write("\n<div class=\"abilities\">")
|
||||
write(", ".join(anames))
|
||||
write('\n<div class="abilities">')
|
||||
write(cleantext(", ".join(anames)))
|
||||
write("</div>")
|
||||
|
||||
# Write info about attacks.
|
||||
write("\n<div class=\"attacks\">")
|
||||
write('\n<div class="attacks">')
|
||||
attacks = self.get_recursive_attacks(u)
|
||||
for attack in attacks:
|
||||
|
||||
n = T(attack, "number")
|
||||
x = T(attack, "damage")
|
||||
x = " ".join((x, html_entity_multiplication_sign, n))
|
||||
write("%s " % x)
|
||||
x = "%s %s %s " % (cleantext(x, quote=False), html_entity_multiplication_sign, cleantext(n, quote=False))
|
||||
write(x)
|
||||
|
||||
r = T(attack, "range")
|
||||
t = T(attack, "type")
|
||||
write("%s (%s)" % (_(r), _(t)))
|
||||
write(cleantext("%s (%s)" % (_(r), _(t)), quote=False))
|
||||
|
||||
s = []
|
||||
specials = attack.get_all(tag="specials")
|
||||
|
@ -762,36 +779,42 @@ class HTMLOutput:
|
|||
if sname:
|
||||
s.append(sname)
|
||||
s = ", ".join(s)
|
||||
if s: write(" (%s)" % s)
|
||||
write("<br />")
|
||||
write("</div>")
|
||||
if s:
|
||||
write(" (%s)" % cleantext(s, quote=False))
|
||||
write('<br />')
|
||||
write('</div>')
|
||||
|
||||
write("</div>")
|
||||
write("</td>\n")
|
||||
write('</div>')
|
||||
write('</td>\n')
|
||||
else:
|
||||
write("<td class=\"empty\"></td>")
|
||||
write("</tr>\n")
|
||||
write("</table>\n")
|
||||
write('<td class="empty"></td>')
|
||||
write('</tr>\n')
|
||||
write('</table>\n')
|
||||
|
||||
def write_units_tree(self, grouper, title, add_parents):
|
||||
self.output.write(html_header % {"path": "../../",
|
||||
"title": title, "classes": "wmlunits-tree"})
|
||||
html_title = cleantext(title)
|
||||
|
||||
self.output.write(html_header % {
|
||||
"path": "../../",
|
||||
"title": html_title,
|
||||
"classes": "wmlunits-tree",
|
||||
})
|
||||
|
||||
n = self.analyze_units(grouper, add_parents)
|
||||
self.write_navbar("units_tree")
|
||||
|
||||
self.output.write("<div class=\"main\">")
|
||||
self.output.write('<div class="main">')
|
||||
|
||||
self.output.write("<h1>%s</h1>" % title)
|
||||
self.output.write('<h1>%s</h1>' % html_title)
|
||||
|
||||
self.write_units()
|
||||
|
||||
self.output.write(html_clear_floats)
|
||||
self.output.write("</div>")
|
||||
self.output.write('</div>')
|
||||
|
||||
self.output.write(html_footer % {
|
||||
"generation_note": "Last updated on " + time.ctime() + "."})
|
||||
|
||||
|
||||
return n
|
||||
|
||||
def write_unit_report(self, output, unit):
|
||||
|
@ -819,23 +842,28 @@ class HTMLOutput:
|
|||
return self.wesnoth.get_unit_value(unit, name,
|
||||
translation=self.translation.translate)
|
||||
|
||||
def clean_uval(name):
|
||||
return cleantext(uval(name))
|
||||
|
||||
# Write unit name, picture and description.
|
||||
uid = unit.get_text_val("id")
|
||||
uname = uval("name")
|
||||
display_name = uname
|
||||
display_name = cleantext(uname)
|
||||
|
||||
self.output = output
|
||||
write(html_header % {"path": "../../",
|
||||
"title": display_name, "classes": "wmlunits-unit"})
|
||||
write(html_header % {
|
||||
"path": "../../",
|
||||
"title": display_name,
|
||||
"classes": "wmlunits-unit"})
|
||||
self.write_navbar("unit_report")
|
||||
|
||||
self.output.write("<div class=\"main\">")
|
||||
self.output.write('<div class="main">')
|
||||
|
||||
female = unit.get_all(tag="female")
|
||||
if female:
|
||||
fname = T(female[0], "name")
|
||||
if fname and fname != uname:
|
||||
display_name += "<br/>" + fname
|
||||
display_name += "<br/>" + cleantext(fname)
|
||||
|
||||
write('<div class="unit-columns">')
|
||||
|
||||
|
@ -847,22 +875,26 @@ class HTMLOutput:
|
|||
if female:
|
||||
mimage, portrait = self.pic(unit, unit)
|
||||
fimage, fportrait = self.pic(unit, female[0])
|
||||
if not fimage: fimage = mimage
|
||||
if not fportrait: fportrait = portrait
|
||||
write('<img src="%s" alt="(image)" />\n' % mimage)
|
||||
write('<img src="%s" alt="(image)" />\n' % fimage)
|
||||
if not fimage:
|
||||
fimage = mimage
|
||||
if not fportrait:
|
||||
fportrait = portrait
|
||||
write('<img src="%s" alt="(image)" />\n' % cleanurl(mimage))
|
||||
write('<img src="%s" alt="(image)" />\n' % cleanurl(fimage))
|
||||
image = mimage
|
||||
else:
|
||||
image, portrait = self.pic(unit, unit)
|
||||
write('<img src="%s" alt="(image)" />\n' % image)
|
||||
write('<img src="%s" alt="(image)" />\n' % cleanurl(image))
|
||||
write('</div>\n')
|
||||
|
||||
description = uval("description")
|
||||
description = clean_uval("description")
|
||||
|
||||
# TODO: what is unit_description?
|
||||
if not description: description = uval("unit_description")
|
||||
if not description: description = "-"
|
||||
write("<p>%s</p>\n" % re.sub("\n", "\n<br />", description))
|
||||
if not description:
|
||||
description = clean_uval("unit_description")
|
||||
if not description:
|
||||
description = "-"
|
||||
write('<p>%s</p>\n' % re.sub('\n', '\n<br />', description))
|
||||
|
||||
# Base info.
|
||||
hp = uval("hitpoints")
|
||||
|
@ -873,39 +905,36 @@ class HTMLOutput:
|
|||
level = uval("level")
|
||||
alignment = uval("alignment")
|
||||
|
||||
write("<h2>Information</h2>\n")
|
||||
write("<table class=\"unitinfo\">\n")
|
||||
write("<tr>\n")
|
||||
write("<th>%s" % _("Advances from: ", "wesnoth-help"))
|
||||
write("</th><td>\n")
|
||||
write('<h2>Information</h2>\n')
|
||||
write('<table class="unitinfo">\n')
|
||||
|
||||
# Advances-from list
|
||||
write('<tr><th>%s</th><td>' % cleantext(_("Advances from: ", "wesnoth-help"), quote=False))
|
||||
have_advances = False
|
||||
for pid in self.forest.get_parents(uid):
|
||||
punit = self.wesnoth.unit_lookup[pid]
|
||||
if "mainline" in unit.campaigns and "mainline" not in punit.campaigns:
|
||||
continue
|
||||
|
||||
if "mainline" in unit.campaigns: addon = "mainline"
|
||||
else: addon = self.addon
|
||||
link = "../../%s/%s/%s.html" % (addon, self.isocode, pid)
|
||||
addon = "mainline" if "mainline" in unit.campaigns else self.addon
|
||||
link = cleanurl("../../%s/%s/%s.html" % (addon, self.isocode, pid))
|
||||
name = self.wesnoth.get_unit_value(punit, "name",
|
||||
translation=self.translation.translate)
|
||||
if have_advances:
|
||||
write(', ')
|
||||
write('<a href="%s">%s</a>' % (link, name))
|
||||
write('<a href="%s">%s</a>' % (link, cleantext(name, quote=False)))
|
||||
have_advances = True
|
||||
if not have_advances:
|
||||
write(html_entity_figure_dash)
|
||||
write("</td>\n")
|
||||
write("</tr><tr>\n")
|
||||
write("<th>%s" % _("Advances to: ", "wesnoth-help"))
|
||||
write("</th><td>\n")
|
||||
write('</td></tr>\n')
|
||||
|
||||
# Advances-to list
|
||||
write('<tr><th>%s</th><td>' % cleantext(_("Advances to: ", "wesnoth-help"), quote=False))
|
||||
have_advances = False
|
||||
for cid in self.forest.get_children(uid):
|
||||
try:
|
||||
cunit = self.wesnoth.unit_lookup[cid]
|
||||
if "mainline" in cunit.campaigns: addon = "mainline"
|
||||
else: addon = self.addon
|
||||
link = "../../%s/%s/%s.html" % (addon, self.isocode, cid)
|
||||
addon = "mainline" if "mainline" in cunit.campaigns else self.addon
|
||||
link = cleanurl("../../%s/%s/%s.html" % (addon, self.isocode, cid))
|
||||
if "mainline" in unit.campaigns and "mainline" not in cunit.campaigns:
|
||||
continue
|
||||
name = self.wesnoth.get_unit_value(cunit, "name",
|
||||
|
@ -913,51 +942,55 @@ class HTMLOutput:
|
|||
except KeyError:
|
||||
error_message("Warning: Unit %s not found.\n" % cid)
|
||||
name = cid
|
||||
if "mainline" in unit.campaigns: continue
|
||||
link = self.target
|
||||
if "mainline" in unit.campaigns:
|
||||
continue
|
||||
link = cleanurl(self.target)
|
||||
if have_advances:
|
||||
write(', ')
|
||||
write('<a href="%s">%s</a>' % (link, name))
|
||||
write('<a href="%s">%s</a>' % (link, cleantext(name, quote=False)))
|
||||
have_advances = True
|
||||
if not have_advances:
|
||||
write(html_entity_figure_dash)
|
||||
write("</td>\n")
|
||||
write("</tr>\n")
|
||||
write('</td></tr>\n')
|
||||
|
||||
for val, text in [
|
||||
("cost", _("Cost: ", "wesnoth-help")),
|
||||
("hitpoints", _("HP: ")),
|
||||
("movement", _("Movement", "wesnoth-help") + ": "),
|
||||
("vision", _("Vision", "wesnoth-help") + ": "),
|
||||
("jamming", _("Jamming", "wesnoth-help") + ":"),
|
||||
attributes = [
|
||||
("cost", _("Cost: ", "wesnoth-help")),
|
||||
("hitpoints", _("HP: ")),
|
||||
("movement", _("Movement", "wesnoth-help") + ": "),
|
||||
("vision", _("Vision", "wesnoth-help") + ": "),
|
||||
("jamming", _("Jamming", "wesnoth-help") + ":"),
|
||||
("experience", _("XP: ")),
|
||||
("level", _("Level") + ": "),
|
||||
("alignment", _("Alignment: ")),
|
||||
("id", "Id: ")]:
|
||||
("level", _("Level") + ": "),
|
||||
("alignment", _("Alignment: ")),
|
||||
("id", "Id: ")
|
||||
]
|
||||
|
||||
for val, text in attributes:
|
||||
x = uval(val)
|
||||
if not x and val in ("jamming", "vision"): continue
|
||||
if val == "alignment": x = _(x)
|
||||
write("<tr>\n")
|
||||
write("<th>%s</th>" % text)
|
||||
write("<td class=\"val\">%s</td>" % x)
|
||||
write("</tr>\n")
|
||||
if not x and val in ("jamming", "vision"):
|
||||
continue
|
||||
if val == "alignment":
|
||||
x = _(x)
|
||||
write('<tr><th>%s</th><td class="val">%s</td></tr>\n' % (cleantext(text, quote=False), cleantext(x, quote=False)))
|
||||
|
||||
# Write info about abilities.
|
||||
anames = self.get_abilities(unit)
|
||||
|
||||
write("<tr>\n")
|
||||
write("<th>%s</th>" % _("Abilities: ", "wesnoth-help"))
|
||||
write('<tr>\n')
|
||||
write('<th>%s</th>' % cleantext(_("Abilities: ", "wesnoth-help"), quote=False))
|
||||
if len(anames):
|
||||
write('<td class="val">' + (', '.join(anames)) + '</td>')
|
||||
write('<td class="val">' + cleantext(', '.join(anames), quote=False) + '</td>')
|
||||
else:
|
||||
write('<td class="val">' + html_entity_figure_dash + '</td>')
|
||||
write("</tr>\n")
|
||||
write('</tr>\n')
|
||||
|
||||
write("</table>\n")
|
||||
write('</table>\n')
|
||||
|
||||
# Write info about attacks.
|
||||
write("<h2>" + _("unit help^Attacks", "wesnoth-help") + " <small>(damage " + html_entity_multiplication_sign + " count)</small></h2> \n")
|
||||
write("<table class=\"unitinfo attacks\">\n")
|
||||
write('<h2>%s <small>(damage %s count)</small></h2>\n' % (
|
||||
cleantext(_("unit help^Attacks", "wesnoth-help"), quote=False),
|
||||
html_entity_multiplication_sign))
|
||||
write('<table class="unitinfo attacks">\n')
|
||||
write('<colgroup><col class="col0" /><col class="col1" /><col class="col2" /><col class="col3" /></colgroup>')
|
||||
attacks = self.get_recursive_attacks(unit)
|
||||
for attack in attacks:
|
||||
|
@ -978,37 +1011,33 @@ class HTMLOutput:
|
|||
icon = os.path.join(pics_location, "unit$elves-wood$shaman.png")
|
||||
else:
|
||||
icon = os.path.join(pics_location, image_add.id_name)
|
||||
write("<td><img src=\"%s\" alt=\"(image)\"/></td>" % icon)
|
||||
write("<td><img src=\"%s\" alt=\"(image)\"/></td>" % cleanurl(icon))
|
||||
|
||||
write("<td><b>%s</b>" % aname)
|
||||
write("<td><b>%s</b>" % cleantext(aname, quote=False))
|
||||
|
||||
r = T(attack, "range")
|
||||
write("<br/>%s</td>" % _(r))
|
||||
write("<br/>%s</td>" % cleantext(_(r), quote=False))
|
||||
|
||||
n = attack.get_text_val("number")
|
||||
x = attack.get_text_val("damage")
|
||||
x = " ".join((x, html_entity_multiplication_sign, n))
|
||||
x = '%s %s %s' % (cleantext(x, quote=False), html_entity_multiplication_sign, cleantext(n, quote=False))
|
||||
write("<td><i>%s</i>" % x)
|
||||
|
||||
t = T(attack, "type")
|
||||
write("<br/>%s</td>" % _(t))
|
||||
write('<br/>%s</td>' % cleantext(_(t), quote=False))
|
||||
|
||||
s = []
|
||||
specials = attack.get_all(tag="specials")
|
||||
|
||||
if specials:
|
||||
for special in specials[0].get_all(tag=""):
|
||||
sname = T(special, "name")
|
||||
if sname:
|
||||
s.append(sname)
|
||||
s.append(cleantext(sname, quote=False))
|
||||
else:
|
||||
error_message(
|
||||
"Warning: Weapon special %s has no name for %s.\n" % (
|
||||
special.name.decode("utf8"), uid))
|
||||
s = "<br/>".join(s)
|
||||
write("<td>%s</td>" % s)
|
||||
|
||||
|
||||
write("<td>%s</td>" % "<br/>".join(s))
|
||||
write("</tr>")
|
||||
write("</table>\n")
|
||||
|
||||
|
@ -1056,7 +1085,7 @@ class HTMLOutput:
|
|||
no_tc = True)
|
||||
icon = os.path.join(pics_location, picname)
|
||||
write('<td><img src="%s" alt="(icon)" /></td>\n' % (icon, ))
|
||||
write('<th>%s</th><td class="%s">%s</td>\n' % (_(rid), ' '.join(resist_classes), resist_str))
|
||||
write('<th>%s</th><td class="%s">%s</td>\n' % (cleantext(_(rid), quote=False), ' '.join(resist_classes), resist_str))
|
||||
if row % 2 == 1:
|
||||
write('</tr>\n')
|
||||
row += 1
|
||||
|
@ -1067,7 +1096,8 @@ class HTMLOutput:
|
|||
write('<div class="unit-column-right">')
|
||||
|
||||
for si in range(2):
|
||||
if si and not female: break
|
||||
if si and not female:
|
||||
break
|
||||
if si:
|
||||
sportrait = fportrait
|
||||
simage = fimage
|
||||
|
@ -1075,22 +1105,24 @@ class HTMLOutput:
|
|||
simage = image
|
||||
sportrait = portrait
|
||||
|
||||
style = "background-image:url('%s');" % simage
|
||||
style = "background-image:url('%s');" % cleanurl(simage)
|
||||
|
||||
write('<div class="portrait">')
|
||||
write('<div style="%s"> </div>' % style)
|
||||
if portrait:
|
||||
write('<img src="%s" alt="(portrait)" />\n' % sportrait)
|
||||
write('<img src="%s" alt="(portrait)" />\n' % cleanurl(sportrait))
|
||||
write('</div>')
|
||||
|
||||
# Write info about movement costs and terrain defense.
|
||||
write("<h2>" + _("Terrain", "wesnoth-help") + "</h2>\n")
|
||||
write("<table class=\"unitinfo terrain\">\n")
|
||||
write('<h2>' + cleantext(_("Terrain", "wesnoth-help"), quote=False) + '</h2>\n')
|
||||
write('<table class="unitinfo terrain">\n')
|
||||
write('<colgroup><col class="col0" /><col class="col1" /><col class="col2" /><col class="col3" /></colgroup>')
|
||||
|
||||
write('<thead>')
|
||||
write('<tr><th colspan="2"><span class="sr-label">%s</span></th><th class="mvtcost">%s</th><th class="numheader">%s</th></tr>\n' % (
|
||||
_("Terrain", "wesnoth-help"), _("Movement Cost", "wesnoth-help"), _("Defense", "wesnoth-help")))
|
||||
cleantext(_("Terrain", "wesnoth-help"), quote=False),
|
||||
cleantext(_("Movement Cost", "wesnoth-help"), quote=False),
|
||||
cleantext(_("Defense", "wesnoth-help"), quote=False)))
|
||||
write('</thead>')
|
||||
|
||||
terrains = self.wesnoth.terrain_lookup
|
||||
|
@ -1189,14 +1221,16 @@ class HTMLOutput:
|
|||
classes_defense.append('rating-' + defense_rating)
|
||||
if move_cost == '-':
|
||||
move_cost = html_entity_figure_dash
|
||||
else:
|
||||
move_cost = cleantext(move_cost, quote=False)
|
||||
|
||||
write("<tr>\n")
|
||||
write('<tr>\n')
|
||||
picname = image_collector.add_image(self.addon,
|
||||
"terrain/" + ticon + ".png", no_tc=True)
|
||||
icon = os.path.join(pics_location, picname)
|
||||
write("<td><img src=\"%s\" alt=\"(icon)\" /></td>\n" % (icon, ))
|
||||
write('<td><img src="%s" alt="(icon)" /></td>\n' % cleanurl(icon))
|
||||
write('<td>%s</td><td class="%s"><i>%s</i></td><td class="%s"><i>%s</i></td>\n' % (
|
||||
tname, ' '.join(classes_cost), move_cost, ' '.join(classes_defense), defense))
|
||||
cleantext(tname, quote=False), ' '.join(classes_cost), move_cost, ' '.join(classes_defense), defense))
|
||||
write("</tr>\n")
|
||||
write("</table>\n")
|
||||
|
||||
|
@ -1316,15 +1350,14 @@ def html_postprocess_file(filename, isocode, batchlist):
|
|||
cids[1].append(c)
|
||||
|
||||
for i in range(2):
|
||||
|
||||
campaigns = cids[i]
|
||||
campaigns.sort(key = lambda x: "A" if x[1] == "mainline" else "B" + x[2])
|
||||
|
||||
for campaign in campaigns:
|
||||
addon, cname, campname, lang = campaign
|
||||
|
||||
chtml += '<li><a title="%s" href="../../%s/%s/%s.html" role="menuitem">%s</a></li>\n' % (
|
||||
campname, addon, lang, cname, campname)
|
||||
url = cleanurl("../../%s/%s/%s.html" % (addon, lang, cname))
|
||||
chtml += '<li><a title="%s" href="%s" role="menuitem">%s</a></li>\n' % (
|
||||
cleantext(campname), url, cleantext(campname, quote=False))
|
||||
if i == 0 and cids[1]:
|
||||
chtml += '<li>%s</li>\n' % html_entity_horizontal_bar
|
||||
|
||||
|
@ -1341,19 +1374,19 @@ def html_postprocess_file(filename, isocode, batchlist):
|
|||
eids[0].append(e)
|
||||
else:
|
||||
eids[1].append(e)
|
||||
|
||||
|
||||
for i in range(2):
|
||||
eras = eids[i]
|
||||
eras.sort(key = lambda x: x[2])
|
||||
|
||||
for era in eras:
|
||||
addon, eid, eraname, lang = era
|
||||
|
||||
ehtml += '<li><a title="%s" href="../../%s/%s/%s.html" role="menuitem">%s</a></li>\n' % (
|
||||
eraname, addon, lang, eid, eraname)
|
||||
url = cleanurl("../../%s/%s/%s.html" % (addon, lang, eid))
|
||||
ehtml += '<li><a title="%s" href="%s" role="menuitem">%s</a></li>\n' % (
|
||||
cleantext(eraname), url, cleantext(eraname, quote=False))
|
||||
if i == 0 and eids[1]:
|
||||
ehtml += '<li>%s</li>\n' % html_entity_horizontal_bar
|
||||
|
||||
|
||||
f = open(filename, "r+b")
|
||||
html = f.read().decode("utf8")
|
||||
html = html.replace("PLACE CAMPAIGNS HERE\n", chtml)
|
||||
|
|
|
@ -5,12 +5,13 @@ from . import html_output
|
|||
|
||||
def write_addon_overview(folder, addon):
|
||||
out = open(os.path.join(folder, "index.html"), "w")
|
||||
def w(x): out.write(x + "\n")
|
||||
def w(x):
|
||||
out.write(x + "\n")
|
||||
|
||||
name = addon["name"]
|
||||
|
||||
path = "../"
|
||||
title = name + " Overview"
|
||||
title = html_output.cleantext(name + " Overview")
|
||||
generation_note = "Last updated on " + time.ctime() + "."
|
||||
|
||||
w(html_output.html_header % locals())
|
||||
|
@ -21,13 +22,13 @@ def write_addon_overview(folder, addon):
|
|||
|
||||
eras = addon.get("eras", [])
|
||||
|
||||
w("<h2>" + name + "</h2>")
|
||||
w("<h2>" + html_output.cleantext(name) + "</h2>")
|
||||
|
||||
if eras:
|
||||
w("<h3>Eras</h3><ul>")
|
||||
for era in eras:
|
||||
epath = os.path.join("en_US", era["id"] + ".html")
|
||||
w('<li><a href="' + epath + '">' + era["name"] + '</a></li>')
|
||||
epath = html_output.cleanurl(os.path.join("en_US", era["id"] + ".html"))
|
||||
w('<li><a href="' + epath + '">' + html_output.cleantext(era["name"], quote=False) + '</a></li>')
|
||||
w("</ul>")
|
||||
|
||||
campaigns = addon.get("campaigns", [])
|
||||
|
@ -35,7 +36,7 @@ def write_addon_overview(folder, addon):
|
|||
w("<h3>Campaigns</h3><ul>")
|
||||
for campaign in campaigns:
|
||||
cpath = os.path.join("en_US", campaign["id"] + ".html")
|
||||
w('<li><a href="' + cpath + '">' + campaign["name"] + '</a></li>')
|
||||
w('<li><a href="' + cpath + '">' + html_output.cleantext(campaign["name"], quote=False) + '</a></li>')
|
||||
w("</ul>")
|
||||
|
||||
w("<div>")
|
||||
|
@ -99,7 +100,7 @@ def main(folder):
|
|||
name = f[len(folder):].lstrip("/")
|
||||
error_name = os.path.join(name, "error.html")
|
||||
w('<tr><td>')
|
||||
w('<a href="' + os.path.join(name, "index.html") + '">' + name + '</a>')
|
||||
w('<a href="' + html_output.cleanurl(os.path.join(name, "index.html")) + '">' + html_output.cleantext(name, quote=False) + '</a>')
|
||||
w('</td><td>')
|
||||
w(str(n))
|
||||
w('</td><td>')
|
||||
|
@ -166,7 +167,7 @@ def main(folder):
|
|||
total_lines += lines_count
|
||||
|
||||
total_error_logs += 1
|
||||
w('<a class="error" href="%s">%s (%d lines)</a>' % (error_name, error_kind, lines_count))
|
||||
w('<a class="error" href="%s">%s (%d lines)</a>' % (html_output.cleanurl(error_name), error_kind, lines_count))
|
||||
w("</td></tr>")
|
||||
|
||||
count += 1
|
||||
|
|
Loading…
Add table
Reference in a new issue