import algorithm
import unicode
import sets
import tables
import sugar
import strutils
type IDNATableStatus* = enum
IDNA_VALID, IDNA_IGNORED, IDNA_MAPPED, IDNA_DEVIATION, IDNA_DISALLOWED
const IdnaMappingTable = staticRead"res/IdnaMappingTable.txt"
func loadStuff(s: string): (seq[(uint16, cstring)], seq[(int, cstring)],
seq[(uint16, uint16)], seq[(int, int)],
set[uint16], HashSet[int],
set[uint16], HashSet[int],
seq[(uint16, cstring)]) =
template add_map(i: int, str: string) =
if cast[uint](i) <= high(uint16):
result[0].add((cast[uint16](i), cstring(str)))
else:
result[1].add((i, cstring(str)))
template add_disallow(i, j: int) =
if cast[uint](i) <= high(uint16):
result[2].add((cast[uint16](i), cast[uint16](j)))
else:
result[3].add((i, j))
template add_disallow(i: int) =
if cast[uint](i) <= high(uint16):
result[4].incl(cast[uint16](i))
else:
result[5].incl(i)
template add_ignore(i: int) =
if cast[uint](i) <= high(uint16):
result[6].incl(cast[uint16](i))
else:
result[7].incl(i)
template add_deviation(i: int, str: string) =
if cast[uint](i) <= high(uint16):
result[8].add((cast[uint16](i), cstring(str)))
else:
assert false
for line in s.split('\n'):
if line.len == 0 or line[0] == '#':
continue
var i = 0
var firstcol = ""
var status = ""
var thirdcol: seq[string]
var fourthcol = ""
while i < line.len and line[i] notin {'#', ';'}:
if line[i] != ' ':
firstcol &= line[i]
inc i
if line[i] != '#':
inc i
while i < line.len and line[i] notin {'#', ';'}:
if line[i] != ' ':
status &= line[i]
inc i
if line[i] != '#':
inc i
var nw = true
while i < line.len and line[i] notin {'#', ';'}:
if line[i] == ' ':
nw = true
else:
if nw:
thirdcol.add("")
nw = false
thirdcol[^1] &= line[i]
inc i
if line[i] != '#':
inc i
while i < line.len and line[i] notin {'#', ';'}:
if line[i] != ' ':
fourthcol &= line[i]
inc i
case status
of "mapped", "disallowed_STD3_mapped":
let codepoints = thirdcol
var str = ""
for code in codepoints:
str &= Rune(parseHexInt(code))
if firstcol.contains(".."):
let fcs = firstcol.split("..")
let rstart = parseHexInt(fcs[0])
let rend = parseHexInt(fcs[1])
for i in rstart..rend:
add_map(i, str)
else:
add_map(parseHexInt(firstcol), str)
of "deviation":
let codepoints = thirdcol
var str = ""
for code in codepoints:
str &= Rune(parseHexInt(code))
if firstcol.contains(".."):
let fcs = firstcol.split("..")
let rstart = parseHexInt(fcs[0])
let rend = parseHexInt(fcs[1])
for i in rstart..rend:
add_deviation(i, str)
else:
add_deviation(parseHexInt(firstcol), str)
of "valid":
if fourthcol == "NV8" or fourthcol == "XV8":
if firstcol.contains(".."):
let fcs = firstcol.split("..")
let rstart = parseHexInt(fcs[0])
let rend = parseHexInt(fcs[1])
add_disallow(rstart, rend)
else:
add_disallow(parseHexInt(firstcol))
of "disallowed":
if firstcol.contains(".."):
let fcs = firstcol.split("..")
let rstart = parseHexInt(fcs[0])
let rend = parseHexInt(fcs[1])
add_disallow(rstart, rend)
else:
add_disallow(parseHexInt(firstcol))
of "ignored":
if firstcol.contains(".."):
let fcs = firstcol.split("..")
let rstart = parseHexInt(fcs[0])
let rend = parseHexInt(fcs[1])
for i in rstart..rend:
add_ignore(i)
else:
add_ignore(parseHexInt(firstcol))
when defined(release):
const (MappedMap1,
MappedMap2,
DisallowedRanges1,
DisallowedRanges2,
Disallowed1,
Disallowed2,
Ignored1,
Ignored2,
Deviation) = loadStuff(IdnaMappingTable)
else:
let (MappedMap1,
MappedMap2,
DisallowedRanges1,
DisallowedRanges2,
Disallowed1,
Disallowed2,
Ignored1,
Ignored2,
Deviation) = loadStuff(IdnaMappingTable)
func searchInMap[U, T](a: openarray[(U, T)], u: U): int =
binarySearch(a, u, (x, y) => cmp(x[0], y))
func isInMap[U, T](a: openarray[(U, T)], u: U): bool =
a.searchInMap(u) != -1
func isInRange[U](a: openarray[(U, U)], u: U): bool =
binarySearch(a, u, (x, y) => (if x[0] < y: -1 elif x[1] > y: 1 else: 0)) != -1
func getIdnaTableStatus*(r: Rune): IDNATableStatus =
let i = int(r)
{.cast(noSideEffect).}:
if cast[uint](i) <= high(uint16):
let u = cast[uint16](i)
if u in Ignored1:
return IDNA_IGNORED
if u in Disallowed1:
return IDNA_DISALLOWED
for item in Deviation:
if item[0] == u:
return IDNA_DEVIATION
if DisallowedRanges1.isInRange(u):
return IDNA_DISALLOWED
if MappedMap1.isInMap(u):
return IDNA_MAPPED
else:
if i in Ignored2:
return IDNA_IGNORED
if i in Disallowed2:
return IDNA_DISALLOWED
if DisallowedRanges2.isInRange(i):
return IDNA_DISALLOWED
if MappedMap2.isInMap(i):
return IDNA_MAPPED
return IDNA_VALID
func getIdnaMapped*(r: Rune): string =
{.cast(noSideEffect).}:
let i = int(r)
if cast[uint](i) <= high(uint16):
let u = cast[uint16](i)
let n = MappedMap1.searchInMap(u)
if n != -1:
return $MappedMap1[n][1]
let n = MappedMap2.searchInMap(i)
return $MappedMap2[n][1]
func getDeviationMapped*(r: Rune): string =
{.cast(noSideEffect).}:
for item in Deviation:
if item[0] == cast[uint16](r):
return $item[1]