///|
fn hex_value(c : Char) -> Int? {
match c {
'0'..='9' => Some(c.to_int() - '0'.to_int())
'a'..='f' => Some(c.to_int() - 'a'.to_int() + 10)
'A'..='F' => Some(c.to_int() - 'A'.to_int() + 10)
_ => None
}
}
///|
fn octal_value(c : Char) -> Int? {
match c {
'0'..='7' => Some(c.to_int() - '0'.to_int())
_ => None
}
}
///|
/// Escape a string for the body of a double-quoted PO string.
///
/// The returned value does not include the surrounding quote characters.
pub fn escape_po_string(input : String) -> String {
let output = StringBuilder()
for c in input {
match c {
'\\' => output.write_string("\\\\")
'"' => output.write_string("\\\"")
'\n' => output.write_string("\\n")
'\r' => output.write_string("\\r")
'\t' => output.write_string("\\t")
'\u{0008}' => output.write_string("\\b")
'\u{000c}' => output.write_string("\\f")
'\u{000b}' => output.write_string("\\v")
'\u{0007}' => output.write_string("\\a")
_ => output.write_char(c)
}
}
output.to_string()
}
///|
/// Decode C-style escapes used inside a PO quoted string.
///
/// Common named escapes, octal escapes of up to three digits, and hexadecimal
/// escapes are supported. Unknown escapes follow gettext's permissive reader
/// behavior and evaluate to the escaped character itself.
pub fn unescape_po_string(input : String) -> String raise GettextError {
let chars = input.to_array()
let output = StringBuilder()
let mut i = 0
while i < chars.length() {
let c = chars[i]
if c != '\\' {
output.write_char(c)
i += 1
continue
}
if i + 1 >= chars.length() {
raise PoSyntax(
line=0,
column=i + 1,
message="unterminated escape sequence",
)
}
let escaped = chars[i + 1]
match escaped {
'n' => {
output.write_char('\n')
i += 2
}
'r' => {
output.write_char('\r')
i += 2
}
't' => {
output.write_char('\t')
i += 2
}
'b' => {
output.write_char('\u{0008}')
i += 2
}
'f' => {
output.write_char('\u{000c}')
i += 2
}
'v' => {
output.write_char('\u{000b}')
i += 2
}
'a' => {
output.write_char('\u{0007}')
i += 2
}
'\\' => {
output.write_char('\\')
i += 2
}
'"' => {
output.write_char('"')
i += 2
}
'?' => {
output.write_char('?')
i += 2
}
'x' | 'X' => {
let mut j = i + 2
let mut value = 0
let mut digits = 0
while j < chars.length() {
match hex_value(chars[j]) {
Some(digit) => {
value = value * 16 + digit
digits += 1
j += 1
}
None => break
}
}
if digits == 0 {
raise PoSyntax(
line=0,
column=i + 1,
message="hexadecimal escape requires at least one digit",
)
}
match value.to_char() {
Some(decoded) => output.write_char(decoded)
None =>
raise PoSyntax(
line=0,
column=i + 1,
message="escape is not a valid Unicode scalar value",
)
}
i = j
}
'0'..='7' => {
let mut j = i + 1
let mut value = 0
let mut digits = 0
while j < chars.length() && digits < 3 {
match octal_value(chars[j]) {
Some(digit) => {
value = value * 8 + digit
digits += 1
j += 1
}
None => break
}
}
match value.to_char() {
Some(decoded) => output.write_char(decoded)
None =>
raise PoSyntax(
line=0,
column=i + 1,
message="escape is not a valid Unicode scalar value",
)
}
i = j
}
_ => {
output.write_char(escaped)
i += 2
}
}
}
output.to_string()
}