///|
pub(all) struct Comment {
cell : String
author : String
author_id : Int?
text : String
paragraph : Array[RichTextRun]
width : Int?
height : Int?
} derive(Debug)
///|
fn clone_comment(comment : Comment) -> Comment {
let paragraph : Array[RichTextRun] = []
for run in comment.paragraph {
paragraph.push(run)
}
{
cell: comment.cell,
author: comment.author,
author_id: comment.author_id,
text: comment.text,
paragraph,
width: comment.width,
height: comment.height,
}
}
///|
let max_comment_author_chars = 255
///|
let max_comment_content_chars = 32767
///|
fn validate_comment_xml_text(
value : StringView,
field : StringView,
) -> Unit raise XlsxError {
for character in value {
if !is_valid_xml_output_character(character) {
raise InvalidComment(
msg="\{field} contains forbidden XML code point \{character.to_int()}",
)
}
}
}
///|
fn validate_comment_rich_text_font(font : RichTextFont) -> Unit raise XlsxError {
match font.underline {
Some(value) => validate_comment_xml_text(value, "comment font underline")
None => ()
}
match font.color {
Some(value) => validate_comment_xml_text(value, "comment font color")
None => ()
}
match font.scheme {
Some(value) => validate_comment_xml_text(value, "comment font scheme")
None => ()
}
match font.vert_align {
Some(value) => validate_comment_xml_text(value, "comment font vert_align")
None => ()
}
match font.family {
Some(value) => validate_comment_xml_text(value, "comment font family")
None => ()
}
}
///|
fn validate_comment_content_units(
value : StringView,
field : StringView,
initial_units : Int,
) -> Int raise XlsxError {
let mut units = initial_units
for character in value {
if !is_valid_xml_output_character(character) {
raise InvalidComment(
msg="\{field} contains forbidden XML code point \{character.to_int()}",
)
}
let character_units = if character.to_int() > 0xffff { 2 } else { 1 }
if character_units > max_comment_content_chars - units {
raise InvalidComment(
msg="comment content exceeds \{max_comment_content_chars} UTF-16 units " +
"(at least \{units + character_units})",
)
}
units = units + character_units
}
units
}
///|
fn normalize_comment(comment : Comment) -> Comment raise XlsxError {
let (row, col) = cell_ref_to_rc(comment.cell)
let cell = cell_ref_from(row, col)
let author = if comment.author == "" { "Author" } else { comment.author }
validate_comment_xml_text(author, "comment author")
let author_units = count_utf16_units(author)
if author_units > max_comment_author_chars {
raise InvalidComment(
msg="comment author exceeds \{max_comment_author_chars} UTF-16 units " +
"(actual \{author_units})",
)
}
ignore(validate_comment_content_units(comment.text, "comment text", 0))
let paragraph : Array[RichTextRun] = []
let rich_text_builder = StringBuilder::new()
let mut rich_text_units = 0
for run in comment.paragraph {
rich_text_units = validate_comment_content_units(
run.text,
"comment rich-text run",
rich_text_units,
)
match run.font {
Some(font) => validate_comment_rich_text_font(font)
None => ()
}
paragraph.push(run)
rich_text_builder.write_view(run.text)
}
let rich_text = rich_text_builder.to_string()
let text = if paragraph.length() == 0 {
comment.text
} else {
if comment.text != "" && comment.text != rich_text {
raise InvalidComment(
msg="comment text must be empty or exactly match the concatenated " +
"rich-text runs",
)
}
rich_text
}
{
cell,
author,
author_id: comment.author_id,
text,
paragraph,
width: comment.width,
height: comment.height,
}
}