use crate::ParseOptions;
use crate::inline::{InlineBuffers, LinkRefMap, parse_inline_pass};
use super::AnsiOptions;
use super::constants::*;
pub(super) fn parse_inline_ansi(
out: &mut String,
raw: &str,
refs: &LinkRefMap,
opts: &ParseOptions,
aopts: &AnsiOptions,
bufs: &mut InlineBuffers,
) {
let mut html = String::with_capacity(raw.len() + 16);
parse_inline_pass(&mut html, raw, refs, opts, bufs);
html_to_ansi(&html, out, aopts.color);
}
pub(super) fn parse_inline_ansi_heading(
out: &mut String,
raw: &str,
refs: &LinkRefMap,
opts: &ParseOptions,
aopts: &AnsiOptions,
bufs: &mut InlineBuffers,
heading_fg: &str,
) {
let mut html = String::with_capacity(raw.len() + 16);
parse_inline_pass(&mut html, raw, refs, opts, bufs);
html_to_ansi_inner(&html, out, aopts.color, false, Some(heading_fg));
}
pub(super) fn html_to_ansi(html: &str, out: &mut String, color: bool) {
html_to_ansi_inner(html, out, color, false, None);
}
pub(super) fn html_to_ansi_inner(
html: &str,
out: &mut String,
color: bool,
in_code: bool,
base_fg: Option<&str>,
) {
let bytes = html.as_bytes();
let len = bytes.len();
let mut i = 0;
let mut inside_code = in_code;
while i < len {
if bytes[i] == b'&' {
if html[i..].starts_with("&") {
out.push('&');
i += 5;
} else if html[i..].starts_with("<") {
out.push('<');
i += 4;
} else if html[i..].starts_with(">") {
out.push('>');
i += 4;
} else if html[i..].starts_with(""") {
out.push('"');
i += 6;
} else {
out.push('&');
i += 1;
}
continue;
}
if bytes[i] != b'<' {
if inside_code && bytes[i] == b' ' {
out.push('\u{00A0}');
i += 1;
} else {
let char_len = crate::utf8_char_len(bytes[i]);
out.push_str(&html[i..i + char_len]);
i += char_len;
}
continue;
}
let tag_end = match memchr::memchr(b'>', &bytes[i..]) {
Some(off) => i + off + 1,
None => {
out.push('<');
i += 1;
continue;
}
};
let tag = &html[i..tag_end];
macro_rules! close_tag {
() => {
if let Some(fg) = base_fg {
out.push_str(RESET);
out.push_str(BOLD);
out.push_str(fg);
} else {
out.push_str(RESET);
}
};
}
if color {
match tag {
"<strong>" => {
out.push_str(BOLD);
if base_fg.is_none() {
out.push_str(FG_STRONG);
}
}
"</strong>" => close_tag!(),
"<em>" => {
out.push_str(ITALIC);
if base_fg.is_none() {
out.push_str(FG_ITALIC);
}
}
"</em>" => close_tag!(),
"<del>" => {
out.push_str(STRIKETHROUGH);
if base_fg.is_none() {
out.push_str(FG_DEL);
}
}
"</del>" => close_tag!(),
"<mark>" => {
out.push_str(BG_MARK);
out.push_str(FG_MARK);
}
"</mark>" => close_tag!(),
"<u>" => {
out.push_str(UNDERLINE);
if base_fg.is_none() {
out.push_str(FG_UNDERLINE);
}
}
"</u>" => close_tag!(),
"<code>" => {
out.push_str(BG_INLINE_CODE);
out.push_str(FG_INLINE_CODE);
inside_code = true;
}
"</code>" => {
inside_code = false;
out.push_str(RESET);
if let Some(fg) = base_fg {
out.push_str(BOLD);
out.push_str(fg);
}
}
"<br />" | "<br />\n" => out.push('\n'),
_ if tag.starts_with("<a href=") => {
if let Some(href_start) = tag.find("href=\"") {
let href_start = href_start + 6;
if let Some(href_end) = tag[href_start..].find('"') {
if let Some(close_off) = html[tag_end..].find("</a>") {
let link_text = &html[tag_end..tag_end + close_off];
let href = &tag[href_start..href_start + href_end];
out.push_str("\x1b]8;;");
out.push_str(href);
out.push_str("\x1b\\");
out.push_str(FG_LINK);
out.push_str(UNDERLINE);
html_to_ansi_inner(link_text, out, color, false, None);
out.push_str(RESET);
out.push_str("\x1b]8;;\x1b\\");
if !href.starts_with('#') {
out.push_str(FG_LINK_URL);
out.push_str(" (");
let mut decoded = String::with_capacity(href.len());
html_to_ansi_inner(href, &mut decoded, false, false, None);
out.push_str(&decoded);
out.push(')');
out.push_str(RESET);
}
i = tag_end + close_off + 4;
continue;
}
}
}
}
"</a>" => out.push_str(RESET),
_ if tag.starts_with("<img ") => {
let alt = tag.find("alt=\"").map(|p| {
let start = p + 5;
tag[start..]
.find('"')
.map(|e| &tag[start..start + e])
.unwrap_or("")
});
let src = tag.find("src=\"").map(|p| {
let start = p + 5;
tag[start..]
.find('"')
.map(|e| &tag[start..start + e])
.unwrap_or("")
});
out.push_str(FG_IMAGE);
out.push_str("◈");
if let Some(alt_text) = alt {
if !alt_text.is_empty() {
out.push(' ');
out.push_str(alt_text);
}
}
out.push_str(RESET);
if let Some(src_url) = src {
if !src_url.is_empty() {
out.push_str(FG_LINK_URL);
out.push_str(" (");
out.push_str(src_url);
out.push(')');
out.push_str(RESET);
}
}
}
_ if tag.starts_with("<input ") => {
if tag.contains("checked") {
out.push_str(FG_CHECKED);
out.push('✓');
out.push_str(RESET);
} else {
out.push_str(FG_UNCHECKED);
out.push('☐');
out.push_str(RESET);
}
}
_ if tag.starts_with("<span class=\"math") => out.push_str(FG_MATH),
"</span>" => out.push_str(RESET),
_ => {}
}
} else {
match tag {
"<br />" | "<br />\n" => out.push('\n'),
_ if tag.starts_with("<a href=") => {
if let Some(close_off) = html[tag_end..].find("</a>") {
html_to_ansi_inner(
&html[tag_end..tag_end + close_off],
out,
false,
false,
None,
);
i = tag_end + close_off + 4;
continue;
}
}
_ if tag.starts_with("<img ") => {
let alt = tag.find("alt=\"").map(|p| {
let start = p + 5;
tag[start..]
.find('"')
.map(|e| &tag[start..start + e])
.unwrap_or("")
});
if let Some(alt_text) = alt {
out.push_str("[image: ");
out.push_str(alt_text);
out.push(']');
}
}
_ if tag.starts_with("<input ") => {
if tag.contains("checked") {
out.push_str("[x] ");
} else {
out.push_str("[ ] ");
}
}
_ => {}
}
}
i = tag_end;
}
}