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;