///|
fn escape_lexicon_field(value : String) -> String {
let mut output = ""
for code in text_code_points(value) {
let escaped = match code {
92 => "\\\\"
9 => "\\t"
10 => "\\n"
13 => "\\r"
_ => code_points_text([code])
}
output = output + escaped
}
output
}
///|
fn unescape_lexicon_field(value : String) -> Result[String, PinyinError] {
let output_codes : Array[Int] = []
let mut index = 0
while index < value.length() {
let code = value[index].to_int()
if code != 92 {
output_codes.push(code)
index = index + 1
continue
}
if index + 1 >= value.length() {
return Err(InvalidLexiconText("trailing_escape"))
}
let escaped = value[index + 1].to_int()
match escaped {
92 => output_codes.push(92)
116 => output_codes.push(9)
110 => output_codes.push(10)
114 => output_codes.push(13)
_ => return Err(InvalidLexiconText("unknown_escape"))
}
index = index + 2
}
Ok(code_points_text(output_codes))
}
///|
fn separator_positions(line : String) -> Array[Int] {
let positions : Array[Int] = []
let mut escaped = false
for index = 0; index < line.length(); index = index + 1 {
let code = line[index].to_int()
if escaped {
escaped = false
} else if code == 92 {
escaped = true
} else if code == 9 {
positions.push(index)
}
}
positions
}
///|
fn parse_lexicon_readings(field : String) -> Result[Array[String], PinyinError] {
let readings : Array[String] = []
for value in field.split(",") {
let reading = value.to_owned()
if reading.length() == 0 {
return Err(InvalidLexiconText("empty_reading"))
}
readings.push(reading)
}
Ok(readings)
}
///|
/// Exports entries in stable insertion order for reviewable configuration.
pub fn lexicon_to_text(lexicon : Lexicon) -> String {
let mut output = ""
for entry in lexicon.values {
output = output + escape_lexicon_field(entry.phrase_value) + "\t"
for index = 0; index < entry.reading_values.length(); index = index + 1 {
if index > 0 {
output = output + ","
}
output = output +
entry.reading_values[index].format(ToneNumbers, Lowercase)
}
output = output + "\n"
}
output
}
///|
/// Imports the stable text format through the same LexiconBuilder validation.
pub fn lexicon_from_text(text : String) -> Result[Lexicon, PinyinError] {
let builder = LexiconBuilder::new()
let mut line_number = 0
for line_view in text.split("\n") {
let line = line_view.to_owned()
line_number = line_number + 1
if line.length() == 0 {
continue
}
let positions = separator_positions(line)
if positions.length() == 0 {
return Err(
InvalidLexiconText("missing_separator:" + line_number.to_string()),
)
}
if positions.length() > 1 {
return Err(
InvalidLexiconText("multiple_separators:" + line_number.to_string()),
)
}
let separator = positions[0]
let phrase_field = syllable_ascii_slice(line, 0, separator)
let readings_field = syllable_ascii_slice(
line,
separator + 1,
line.length(),
)
let phrase = match unescape_lexicon_field(phrase_field) {
Err(error) => return Err(error)
Ok(value) => value
}
let readings = match parse_lexicon_readings(readings_field) {
Err(error) => return Err(error)
Ok(value) => value
}
match builder.add(phrase, readings) {
Err(error) => return Err(error)
Ok(_) => ()
}
}
Ok(builder.build())
}