///|
fn is_sentence_boundary(c : Char) -> Bool {
c == '。' ||
c == '!' ||
c == '?' ||
c == '.' ||
c == '!' ||
c == '?' ||
c == ';' ||
c == ';'
}
///|
fn is_soft_space(c : Char) -> Bool {
c.is_whitespace() || c == '\u{3000}'
}
///|
fn is_wordish(c : Char) -> Bool {
c.is_ascii_alphabetic() ||
c.is_ascii_digit() ||
(
!c.is_ascii() &&
!c.is_whitespace() &&
!is_sentence_boundary(c) &&
c != ',' &&
c != ',' &&
c != '、' &&
c != ':' &&
c != ':'
)
}
///|
fn char_weight(c : Char) -> Double {
if c.is_whitespace() {
0.0
} else if c.is_ascii_alphabetic() || c.is_ascii_digit() {
0.55
} else if c.is_ascii_punctuation() {
0.2
} else {
1.0
}
}
///|
pub fn normalize_text(text : String) -> String {
let replaced = text
.replace_all(old="\r\n", new="\n")
.replace_all(old="\r", new="\n")
let out = StringBuilder()
let mut just_wrote_line = false
for raw_line in replaced.split("\n").to_array() {
let line = raw_line.to_owned()
let line_out = StringBuilder()
let mut prev_space = false
for c in line {
if is_soft_space(c) {
if !prev_space {
line_out.write_char(' ')
}
prev_space = true
} else {
line_out.write_char(c)
prev_space = false
}
}
let normalized_line = line_out.to_string().trim().to_owned()
if !normalized_line.is_empty() {
if just_wrote_line {
out.write_char('\n')
}
out.write_string(normalized_line)
just_wrote_line = true
}
}
out.to_string().trim().to_owned()
}
///|
fn append_unit(
units : Array[TextUnit],
text : String,
paragraph_index : Int,
sentence_index : Int,
) -> Unit {
let normalized = normalize_text(text)
if normalized.is_empty() {
()
} else {
let mut weight = 0.0
let mut in_token = false
let mut token_count = 0
for c in normalized {
weight += char_weight(c)
let wordish = is_wordish(c)
if wordish && !in_token {
token_count += 1
}
in_token = wordish
}
units.push({
id: units.length(),
paragraph_index,
sentence_index,
text: text.trim().to_owned(),
normalized,
char_weight: weight,
token_count,
})
}
}
///|
fn split_paragraph_text(text : String) -> Array[String] {
let lines = text
.replace_all(old="\r\n", new="\n")
.replace_all(old="\r", new="\n")
.split("\n")
.to_array()
let paragraphs = []
let mut current = ""
for line in lines {
let trimmed = line.to_owned().trim().to_owned()
if trimmed.is_empty() {
if !current.trim().is_empty() {
paragraphs.push(current.trim().to_owned())
current = ""
}
} else {
let normalized_line = normalize_text(trimmed)
if !current.is_empty() {
current = current + "\n"
}
current = current + normalized_line
}
}
if !current.trim().is_empty() {
paragraphs.push(current.trim().to_owned())
}
paragraphs
}
///|
pub fn segment_text(
text : String,
options? : AlignOptions = default_options(),
) -> Array[TextUnit] {
let paragraphs = split_paragraph_text(text)
let units = []
match options.segment_mode {
Paragraph =>
for paragraph_index, paragraph in paragraphs {
append_unit(units, paragraph, paragraph_index, 0)
}
Sentence =>
for paragraph_index, paragraph in paragraphs {
let mut current = ""
let mut sentence_index = 0
for c in paragraph {
current = current + "\{c}"
if is_sentence_boundary(c) || c == '\n' {
let candidate = current.trim().to_owned()
if candidate.char_length() >= options.min_sentence_chars {
append_unit(units, candidate, paragraph_index, sentence_index)
sentence_index += 1
current = ""
}
}
}
let tail = current.trim().to_owned()
if !tail.is_empty() {
append_unit(units, tail, paragraph_index, sentence_index)
}
}
}
units
}