use super::TitleKind;
pub(super) fn title_kind_str(kind: &TitleKind) -> &'static str {
match kind {
TitleKind::Text => "text",
TitleKind::String => "string",
TitleKind::Markdown => "markdown",
}
}
pub(super) fn unquote(s: &str) -> String {
let bytes = s.as_bytes();
if bytes.len() >= 2 && bytes[0] == b'"' && bytes[bytes.len() - 1] == b'"' {
return s[1..s.len() - 1].to_string();
}
s.to_string()
}
pub(super) fn is_ecmascript_trim_char(ch: char) -> bool {
matches!(
ch,
'\u{0009}'
| '\u{000a}'
| '\u{000b}'
| '\u{000c}'
| '\u{000d}'
| '\u{0020}'
| '\u{00a0}'
| '\u{1680}'
| '\u{2000}'
..='\u{200a}'
| '\u{2028}'
| '\u{2029}'
| '\u{202f}'
| '\u{205f}'
| '\u{3000}'
| '\u{feff}'
)
}
pub(super) fn trim_flowdb_label_text(text: &str) -> &str {
text.trim_matches(is_ecmascript_trim_char)
}
pub(super) fn parse_label_text(raw: &str) -> (String, TitleKind) {
let quoted = raw.starts_with('"') && raw.ends_with('"');
let unquoted = if quoted {
unquote(raw)
} else {
raw.to_string()
};
if quoted {
let (no_backticks, is_markdown) = strip_wrapping_backticks(&unquoted);
if is_markdown {
return (
trim_flowdb_label_text(&no_backticks).to_string(),
TitleKind::Markdown,
);
}
return (
trim_flowdb_label_text(&unquoted).to_string(),
TitleKind::String,
);
}
(
trim_flowdb_label_text(&unquoted).to_string(),
TitleKind::Text,
)
}
pub(super) fn parse_edge_label_text(raw: &str) -> (String, TitleKind) {
let quoted = raw.starts_with('"') && raw.ends_with('"');
if quoted {
return parse_label_text(raw);
}
(trim_flowdb_label_text(raw).to_string(), TitleKind::Text)
}
pub(super) fn strip_wrapping_backticks(s: &str) -> (String, bool) {
if s.len() >= 2 && s.starts_with('`') && s.ends_with('`') {
return (s[1..s.len() - 1].to_string(), true);
}
(s.to_string(), false)
}
#[cfg(test)]
mod tests {
use super::{parse_edge_label_text, parse_label_text};
use crate::diagrams::flowchart::TitleKind;
#[test]
fn parse_label_text_keeps_backslashes_in_string_labels() {
let (text, kind) = parse_label_text(r#""Path: C:\\Temp\\merman\\out.svg (Windows-style)""#);
assert_eq!(kind, TitleKind::String);
assert_eq!(text, r#"Path: C:\\Temp\\merman\\out.svg (Windows-style)"#);
}
#[test]
fn parse_label_text_does_not_treat_tex_commands_as_escapes() {
let (text, kind) = parse_label_text(r#""$$\nabla\therefore\alpha$$""#);
assert_eq!(kind, TitleKind::String);
assert_eq!(text, r#"$$\nabla\therefore\alpha$$"#);
}
#[test]
fn parse_label_text_keeps_single_quotes_as_text() {
let (text, kind) = parse_label_text("'Literal quotes'");
assert_eq!(kind, TitleKind::Text);
assert_eq!(text, "'Literal quotes'");
}
#[test]
fn parse_label_text_keeps_bare_backticks_as_text() {
let (text, kind) = parse_label_text("bare `tick` text");
assert_eq!(kind, TitleKind::Text);
assert_eq!(text, "bare `tick` text");
}
#[test]
fn parse_edge_label_text_keeps_unquoted_backticks_literal() {
let (text, kind) =
parse_edge_label_text(r#"`This is **bold** </br>and <strong>strong</strong>`"#);
assert_eq!(kind, TitleKind::Text);
assert_eq!(
text,
r#"`This is **bold** </br>and <strong>strong</strong>`"#
);
}
#[test]
fn parse_edge_label_text_keeps_unquoted_partial_markdown_literal() {
let (text, kind) = parse_edge_label_text(r#"`**bold*`"#);
assert_eq!(kind, TitleKind::Text);
assert_eq!(text, r#"`**bold*`"#);
}
#[test]
fn parse_edge_label_text_supports_quoted_markdown_strings() {
let (text, kind) = parse_edge_label_text(r#""`Bold **edge label**`""#);
assert_eq!(kind, TitleKind::Markdown);
assert_eq!(text, r#"Bold **edge label**"#);
}
#[test]
fn flowdb_label_trim_preserves_entity_provenance_for_nodes_and_edges() {
let cases = [
("\u{00a0}A\u{00a0}", "A", TitleKind::Text),
("\"\u{00a0}A\u{00a0}\"", "A", TitleKind::String),
("\"`\u{00a0}A\u{00a0}`\"", "A", TitleKind::Markdown),
(
"\"\u{00a0} A \u{00a0}\"",
" A ",
TitleKind::String,
),
(
"\"`\u{00a0} A \u{00a0}`\"",
" A ",
TitleKind::Markdown,
),
("\"\u{feff}A\u{feff}\"", "A", TitleKind::String),
(
"\"\u{0085}A\u{0085}\"",
"\u{0085}A\u{0085}",
TitleKind::String,
),
("\" `A` \"", "`A`", TitleKind::String),
("\"\u{00a0}`A`\u{00a0}\"", "`A`", TitleKind::String),
(
"\"\u{0085}`A`\u{0085}\"",
"\u{0085}`A`\u{0085}",
TitleKind::String,
),
("\"` A `\"", "A", TitleKind::Markdown),
];
for (raw, expected, expected_kind) in cases {
let (node_text, node_kind) = parse_label_text(raw);
assert_eq!(node_text, expected, "node label {raw:?}");
assert_eq!(node_kind, expected_kind, "node label {raw:?}");
let (edge_text, edge_kind) = parse_edge_label_text(raw);
assert_eq!(edge_text, expected, "edge label {raw:?}");
assert_eq!(edge_kind, expected_kind, "edge label {raw:?}");
}
}
}