Documents: code protection closes and reopens tags as the reader does
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2 files changed
+56
-12
No files matched your search
@@ -72,7 +72,7 @@ pub(super) fn swapped<'a>(text: &'a str, options: &ParseOptions) -> Swapped<'a>
|
|||||||
fn keep(container: &mdast::Node, text: &str, kept: &mut [bool]) {
|
fn keep(container: &mdast::Node, text: &str, kept: &mut [bool]) {
|
||||||
let mut nodes = Vec::new();
|
let mut nodes = Vec::new();
|
||||||
walk(container, &mut nodes);
|
walk(container, &mut nodes);
|
||||||
// Open code tags, and where the outermost one's text starts.
|
// Open styled tags, and where code text last started.
|
||||||
let (mut open, mut from) = (Vec::new(), 0);
|
let (mut open, mut from) = (Vec::new(), 0);
|
||||||
for node in &nodes[1..] {
|
for node in &nodes[1..] {
|
||||||
use mdast::Node::*;
|
use mdast::Node::*;
|
||||||
@@ -97,30 +97,53 @@ fn keep(container: &mdast::Node, text: &str, kept: &mut [bool]) {
|
|||||||
let Some(tag) = html::tag(&text[whole.clone()]) else {
|
let Some(tag) = html::tag(&text[whole.clone()]) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if html::inline_style(&tag.name) != Some(Style::Code) {
|
if html::inline_style(&tag.name).is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if !tag.closing {
|
let was = coded(&open);
|
||||||
if open.is_empty() {
|
match tag.closing {
|
||||||
from = whole.end;
|
true => end(&mut open, &tag.name),
|
||||||
}
|
false => {
|
||||||
open.push(tag.name);
|
if tag.name == "a" {
|
||||||
} else if let Some(ix) = open.iter().rposition(|name| *name == tag.name) {
|
end(&mut open, "a");
|
||||||
open.remove(ix);
|
}
|
||||||
if open.is_empty() {
|
open.push(tag.name);
|
||||||
kept[from..whole.start].fill(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
match (was, coded(&open)) {
|
||||||
|
(false, true) => from = whole.end,
|
||||||
|
(true, false) => kept[from..whole.start].fill(true),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// An unclosed `<code>` runs to its block's end.
|
// An unclosed `<code>` runs to its block's end.
|
||||||
if !open.is_empty() {
|
if coded(&open) {
|
||||||
kept[from..span(container).end].fill(true);
|
kept[from..span(container).end].fill(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether an open tag shows its text as code.
|
||||||
|
fn coded(open: &[String]) -> bool {
|
||||||
|
(open.iter()).any(|name| html::inline_style(name) == Some(Style::Code))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ends the innermost `name` as the reader does: inner tags close.
|
||||||
|
fn end(open: &mut Vec<String>, name: &str) {
|
||||||
|
let Some(at) = open.iter().rposition(|opened| opened == name) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let closed = open.split_off(at);
|
||||||
|
open.extend(
|
||||||
|
closed
|
||||||
|
.into_iter()
|
||||||
|
.skip(1)
|
||||||
|
.filter(|name| html::reopens(name)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Pairs and bare dollars in `within`; pairs cross nothing kept.
|
/// Pairs and bare dollars in `within`; pairs cross nothing kept.
|
||||||
fn paired(
|
fn paired(
|
||||||
text: &str,
|
text: &str,
|
||||||
|
|||||||
@@ -86,6 +86,27 @@ fn code_and_tags_stay_literal() {
|
|||||||
untouched(r"a <code><code>\(x\)</code> \(y\)</code> b");
|
untouched(r"a <code><code>\(x\)</code> \(y\)</code> b");
|
||||||
untouched(r"a <kbd><code>\(x\)</code> \(y\)</kbd> b");
|
untouched(r"a <kbd><code>\(x\)</code> \(y\)</kbd> b");
|
||||||
assert_eq!(tex(r"<code>a</code> \(x\)"), ["x"], "after code, prose");
|
assert_eq!(tex(r"<code>a</code> \(x\)"), ["x"], "after code, prose");
|
||||||
|
for crossed in [
|
||||||
|
r"a <code><kbd>x</code> \(y\) b",
|
||||||
|
r"a <b><kbd>x</b> \(y\) b",
|
||||||
|
r"a <code><b>x</code> \(y\)</b> b",
|
||||||
|
] {
|
||||||
|
assert_eq!(
|
||||||
|
tex(crossed),
|
||||||
|
["y"],
|
||||||
|
"a crossed close ends code: {crossed:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
untouched(r"a <b><code>x</b> \(y\) b");
|
||||||
|
let reopened = crate::documents::tree::parse(r"a <b><code>x</b> \(y\) b");
|
||||||
|
let crate::documents::tree::Node::Paragraph(inline) = &reopened[0] else {
|
||||||
|
panic!("a paragraph: {reopened:?}");
|
||||||
|
};
|
||||||
|
let code = crate::documents::tree::Style::Code;
|
||||||
|
assert!(
|
||||||
|
(inline.styles.iter()).any(|(range, style)| *style == code && range.contains(&6)),
|
||||||
|
"the reader reopens code over (y): {inline:?}"
|
||||||
|
);
|
||||||
untouched(r"`\(x\)`");
|
untouched(r"`\(x\)`");
|
||||||
untouched(r"\(a `b` c\)");
|
untouched(r"\(a `b` c\)");
|
||||||
assert_eq!(tex(r"<b>\(x\)</b>"), ["x"], "other tags hold prose");
|
assert_eq!(tex(r"<b>\(x\)</b>"), ["x"], "other tags hold prose");
|
||||||
|
|||||||
Reference in new issue
Block a user