structure SafeName:
sig
val escapeJobname: string -> string
val safeInput: {name: string, isPdfTeX: bool} -> string
end =
struct
local
fun escapeChar #" " = "_"
| escapeChar c =
if
Char.isSpace c orelse c = #"\"" orelse c = #"$" orelse c = #"%"
orelse c = #"&" orelse c = #"'" orelse c = #"(" orelse c = #")"
orelse c = #";" orelse c = #"<" orelse c = #">" orelse c = #"\\"
orelse c = #"^" orelse c = #"`" orelse c = #"|"
then
let
val x = Char.ord c
val s = Int.fmt StringCvt.HEX x
in
if x <= 0xf then "_0" ^ s else "_" ^ s
end
else
String.str c
in
fun escapeJobname name = String.translate escapeChar name
end
local
fun handleSpecialChar #"\\" = "~\\\\"
| handleSpecialChar #"%" = "~\\%"
| handleSpecialChar #"^" = "~\\^"
| handleSpecialChar #"{" = "~\\{"
| handleSpecialChar #"}" = "~\\}"
| handleSpecialChar #"~" = "~\\~"
| handleSpecialChar #"#" = "~\\#"
| handleSpecialChar c = String.str c
fun handleSpaces s =
let
fun go (s, acc) =
if Substring.isEmpty s then
Substring.concat (List.rev acc)
else
let
val (a, b) = Substring.splitl (fn c => c <> #" ") s
val (c, d) = Substring.splitl (fn c => c = #" ") b
val c' = Substring.full (String.concatWith "~"
(List.map String.str (Substring.explode c)))
in
go (d, c' :: a :: acc)
end
in
go (Substring.full s, [])
end
fun handleNonAscii s =
let
fun go (s, acc) =
if Substring.isEmpty s then
Substring.concat (List.rev acc)
else
let
val (a, b) = Substring.splitl Char.isAscii s
val (c, d) = Substring.splitl (Bool.not o Char.isAscii) b
val c' =
if Substring.isEmpty c then c
else Substring.full ("\\detokenize{" ^ Substring.string c ^ "}")
in
go (d, c' :: a :: acc)
end
in
go (Substring.full s, [])
end
in
fun safeInput {name, isPdfTeX} =
let
val escaped = handleSpaces (String.translate handleSpecialChar name)
val escaped = if isPdfTeX then handleNonAscii escaped else escaped
in
if name = escaped then
"\\input\"" ^ name ^ "\""
else
"\\begingroup\\escapechar-1\\let~\\string\\edef\\x{\"" ^ escaped
^ "\" }\\expandafter\\endgroup\\expandafter\\input\\x"
end
end
end;