import json
import sys
SOURCE = "https://html.spec.whatwg.org/entities.json"
def rust_str(s):
out = []
for ch in s:
if ch in '"\\':
out.append("\\" + ch)
elif " " <= ch <= "~":
out.append(ch)
else:
out.append("\\u{%x}" % ord(ch))
return '"' + "".join(out) + '"'
def main():
if len(sys.argv) != 2:
sys.exit("usage: python3 -I scripts/gen_entities.py <entities.json>")
with open(sys.argv[1], encoding="utf-8") as f:
table = json.load(f)
entries = {}
for key, value in table.items():
if not (key.startswith("&") and key.endswith(";")):
continue name = key[1:-1]
if not (name.isascii() and name.isalnum()):
sys.exit("unexpected entity name: %r" % key)
chars = "".join(chr(c) for c in value["codepoints"])
if chars != value["characters"]:
sys.exit("codepoints and characters disagree for %r" % key)
entries[name] = chars
names = sorted(entries, key=lambda n: n.encode("ascii"))
longest = max(len(n) for n in names)
if longest + 1 > 32:
sys.exit("a name no longer fits entities::MAX_ENTITY_LEN (32 bytes)")
w = sys.stdout.write
w("//! The complete WHATWG named character reference table, for the\n")
w("//! `entities-full` feature.\n")
w("//!\n")
w("//! GENERATED by `scripts/gen_entities.py` from\n")
w("//! <%s> -- do not edit by hand.\n" % SOURCE)
w("//! Only `;`-terminated names: the legacy no-semicolon spellings are\n")
w("//! left verbatim, as with the curated table.\n")
w("\n")
w("/// `(name, replacement)` pairs, sorted by name in byte order.\n")
w("#[rustfmt::skip]\n")
w("pub(crate) static ENTITIES: [(&str, &str); %d] = [\n" % len(names))
for n in names:
w(" (%s, %s),\n" % (rust_str(n), rust_str(entries[n])))
w("];\n")
if __name__ == "__main__":
main()