Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,6 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
around it, so `[x]{#k .key kbd}` is `<kbd id="k" class="key">x</kbd>`, and
where nothing is left over there is no wrapper at all. A derived `title` or
`datetime` yields to an authored one of the same name.
- **A caption on a block quote is now that quote's attribution** (PART 9 §4a,
markup-carve/carve#1159). `> To be` followed by `^ Hamlet` no longer parses as
a `figure` wrapping a `block_quote`; it is a `block_quote` carrying an
`attribution`, and HTML renders `<footer>Hamlet</footer>` inside the
`<blockquote>` rather than a `<figure>` / `<figcaption>` pair. A quote is not
a figure, takes no number, and no longer turns up in a walk for figures. The
Markdown, plain-text, ANSI and Carve writers all carry the attribution, and
the HTML importer reads a trailing `<footer>` in a `<blockquote>` back as the
attribution so the renderer's own output round-trips.
- **Every `<th>` carries a `scope`** (PART 10 §T9, markup-carve/carve#1159).
`col` for a header cell in the head-row run, `row` for one below it, on pipe
tables and list tables alike. An authored `scope` replaces the emitted one
Expand Down Expand Up @@ -518,18 +509,6 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
welding the words onto the last data cell (`| a |Fruit prices` on Markdown,
`aFruit prices` on plain text).

- **A quote attribution stays attached to its quote on every target**
(markup-carve/carve#1179, PART 11 §10c). It used to follow the quote as a
sibling separated by a blank line, which kept the words but not what they
mean - read back the attribution was attached to nothing, and a round trip
produced a blockquote with no attribution at all. Markdown now emits a
`<footer>` element inside the quote (that target already writes `<u>`,
`<mark>` and `<ins>` where Markdown has no spelling, and through a CommonMark
reader `<footer>` opens an HTML block rather than being wrapped in a
paragraph, so the rendered HTML matches the HTML target's); the terminal
carries its quote bar onto the attribution line; plain text attaches by
adjacency, dropping the blank line. A quote with no attribution is unchanged.

- **An authored `abbr` wins on the Markdown, ANSI and plain targets**
(markup-carve/carve#1176). markup-carve/carve#1127 ruled that an explicit
`abbr` outranks automatic expansion; the HTML renderer honoured it while
Expand Down
6 changes: 0 additions & 6 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,6 @@ pub struct ListItem {
pub struct BlockQuote {
pub attrs: Option<Attrs>,
pub children: Vec<BlockNode>,
/// The source of the quotation (PART 9 §4a).
///
/// A `^` caption on a quote is its ATTRIBUTION, not a figure caption: the
/// quote is not a figure, takes no number, and nothing walking the tree for
/// figures finds it (carve#1159).
pub attribution: Option<Vec<InlineNode>>,
/// Span in the original source, when the parser could determine it.
pub pos: Option<Pos>,
}
Expand Down
4 changes: 0 additions & 4 deletions src/ast_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -839,9 +839,6 @@ fn write_block_quote(out: &mut String, n: &BlockQuote) {
let mut w = typed(out, "block_quote");
w.field("children", |out| write_blocks(out, &n.children));
// PART 9 §4a: the source of the quotation, absent when there is none.
if let Some(attribution) = &n.attribution {
w.field("attribution", |out| write_inlines(out, attribution));
}
write_attrs_field(&mut w, &n.attrs);
write_pos_field(&mut w, &n.pos);
w.finish();
Expand Down Expand Up @@ -1513,7 +1510,6 @@ fn decode_block(value: &Json) -> Result<BlockNode, AstJsonError> {
"block_quote" => Ok(BlockNode::BlockQuote(BlockQuote {
attrs: optional_attrs(obj)?,
children: decode_blocks(required_array(obj, "block_quote", "children")?)?,
attribution: optional_inlines(obj, "attribution")?,
pos: optional_pos(obj, "block_quote")?,
})),
"list" => Ok(BlockNode::List(List {
Expand Down
3 changes: 0 additions & 3 deletions src/citations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -764,9 +764,6 @@ fn annotate_citations_block(
}
}
BlockNode::BlockQuote(b) => {
if let Some(attribution) = &mut b.attribution {
annotate_citations_inline(attribution, defs, mode, has_bib, seen, order, uses);
}
for child in &mut b.children {
annotate_citations_block(child, defs, mode, has_bib, seen, order, uses);
}
Expand Down
3 changes: 0 additions & 3 deletions src/document_ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,9 +382,6 @@ impl Seeder {

fn walk_blockquote(&mut self, b: &BlockQuote) {
self.reserve_attrs(&b.attrs);
if let Some(attribution) = &b.attribution {
self.walk_inlines(attribution);
}
self.walk_blocks(&b.children);
}

Expand Down
3 changes: 0 additions & 3 deletions src/extensions/external_links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,6 @@ impl ExternalLinks {
}
}
BlockNode::BlockQuote(b) => {
if let Some(attribution) = &mut b.attribution {
self.visit_inlines(attribution);
}
for child in &mut b.children {
self.visit_block(child);
}
Expand Down
3 changes: 0 additions & 3 deletions src/extensions/heading_numbers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,6 @@ fn rewrite_links_blocks(
BlockNode::Heading(h) => rewrite_links_inlines(&mut h.children, by_id, opts),
BlockNode::Paragraph(p) => rewrite_links_inlines(&mut p.children, by_id, opts),
BlockNode::BlockQuote(b) => {
if let Some(attribution) = &mut b.attribution {
rewrite_links_inlines(attribution, by_id, opts);
}
rewrite_links_blocks(&mut b.children, by_id, opts);
}
BlockNode::Div(d) => rewrite_links_blocks(&mut d.children, by_id, opts),
Expand Down
3 changes: 0 additions & 3 deletions src/extensions/index_terms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,6 @@ fn rewrite_markers_block(
}
}
BlockNode::BlockQuote(b) => {
if let Some(attribution) = &mut b.attribution {
rewrite_markers_inline(attribution, counts, display);
}
for child in &mut b.children {
rewrite_markers_block(child, counts, display);
}
Expand Down
3 changes: 0 additions & 3 deletions src/extensions/smart_quotes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,6 @@ impl SmartQuotes {
}
}
BlockNode::BlockQuote(b) => {
if let Some(attribution) = &mut b.attribution {
self.visit_inlines(attribution);
}
self.visit_blocks(&mut b.children);
}
BlockNode::Table(t) => {
Expand Down
3 changes: 0 additions & 3 deletions src/extensions/tab_normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,6 @@ impl TabNormalize {
}
}
BlockNode::BlockQuote(b) => {
if let Some(attribution) = &mut b.attribution {
self.visit_inlines(attribution);
}
for child in &mut b.children {
self.visit_block(child);
}
Expand Down
39 changes: 1 addition & 38 deletions src/html_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,46 +375,9 @@ impl<'a> Importer<'a> {
})]);
}
if tag == "blockquote" {
// A trailing `<footer>` is the quote's ATTRIBUTION (PART 9 §4a).
// This renderer emits the source of a quotation that way, so
// reading it back as an ordinary second paragraph meant the
// engine's own HTML did not survive a round trip - the `^ ` line
// was gone from the Carve it wrote (carve#1159).
//
// The LAST footer, because that is the one the renderer emits and
// the one an author puts after the quoted text; an earlier one
// stays an ordinary block.
// The slot holds INLINE content, so a footer carrying blocks does
// not fit it. Flattening one would run its paragraphs together
// with no separator; leaving it an ordinary block inside the quote
// keeps every word, which is the better answer when the shape
// cannot be represented.
let footer = children.iter().rposition(|n| {
Self::tag(n).as_deref() == Some("footer")
&& !n.children.borrow().iter().any(|c| {
Self::tag(c)
.as_deref()
.map(Self::is_block_tag)
.unwrap_or(false)
})
});
let attribution = match footer {
Some(index) => {
let inner = children[index].children.borrow().clone();
Some(self.inlines(&inner, path, depth + 1)?)
}
None => None,
};
let body: Vec<_> = children
.iter()
.enumerate()
.filter(|(index, _)| Some(*index) != footer)
.map(|(_, node)| node.clone())
.collect();
return Ok(vec![BlockNode::BlockQuote(BlockQuote {
attrs,
children: self.blocks(&body, path, depth + 1)?,
attribution,
children: self.blocks(&children, path, depth + 1)?,
pos: None,
})]);
}
Expand Down
3 changes: 0 additions & 3 deletions src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,6 @@ fn walk_block(node: &BlockNode, visit: &mut Visit<'_>) {
fn walk_block_quote(n: &BlockQuote, visit: &mut Visit<'_>) {
report("block_quote", &n.attrs, n.pos, visit);
walk_blocks(&n.children, visit);
if let Some(attribution) = &n.attribution {
walk_inlines(attribution, visit);
}
}

fn walk_table(n: &Table, visit: &mut Visit<'_>) {
Expand Down
1 change: 0 additions & 1 deletion src/markdown_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,6 @@ impl Builder {
Frame::BlockQuote(children) => self.block(BlockNode::BlockQuote(BlockQuote {
attrs: None,
children,
attribution: None,
pos: None,
})),
Frame::List {
Expand Down
43 changes: 11 additions & 32 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,9 +416,6 @@ fn fill_crossref_hrefs(doc: &mut Document, lowercase_ids: bool) {
BlockNode::Paragraph(p) => inlines(&mut p.children, index),
BlockNode::Heading(h) => inlines(&mut h.children, index),
BlockNode::BlockQuote(b) => {
if let Some(attribution) = &mut b.attribution {
inlines(attribution, index);
}
blocks(&mut b.children, index);
}
BlockNode::Div(d) => blocks(&mut d.children, index),
Expand Down Expand Up @@ -2810,9 +2807,6 @@ fn fill_offsets(blocks: &mut [BlockNode], line_starts: &[usize]) {
}
}
BlockNode::BlockQuote(b) => {
if let Some(attribution) = &mut b.attribution {
apply_inline_offsets(attribution, line_starts);
}
fill_offsets(&mut b.children, line_starts);
}
BlockNode::Div(d) => fill_offsets(&mut d.children, line_starts),
Expand Down Expand Up @@ -5077,21 +5071,22 @@ fn parse_blockquote(cur: &mut LineCursor, options: &Options<'_>) -> BlockNode {
}
let inner = inner.into_source();
let children = parse_mapped_source(&inner, options);
let mut quote = BlockQuote {
let quote = BlockQuote {
pos: span_of(cur, span_start, cur.pos, options),
attrs: None,
attribution: None,
children,
};
// PART 9 §4a: the caption on a quote is its ATTRIBUTION, so no figure wraps
// it - it takes no number, and nothing walking the tree for figures finds
// it (carve#1159). The span still runs from the quote's first line through
// the caption the cursor just consumed.
if let Some(attribution) = consume_attribution(cur, options) {
quote.attribution = Some(attribution);
quote.pos = span_of(cur, span_start, cur.pos, options);
if let Some(caption) = consume_caption(cur, options) {
BlockNode::Figure(Figure {
attrs: None,
target: FigureTarget::BlockQuote(quote),
caption,
short_caption: None,
pos: span_of(cur, span_start, cur.pos, options),
})
} else {
BlockNode::BlockQuote(quote)
}
BlockNode::BlockQuote(quote)
}

fn is_list_marker(line: &str) -> bool {
Expand Down Expand Up @@ -8678,13 +8673,6 @@ fn image_is_block(cur: &mut LineCursor) -> bool {
interrupts
}

/// A quote's attribution (PART 9 §4a): the same slot as a caption, parsed
/// without caption context so a bare `#` stays literal - an attribution has no
/// number to place, and §4a says the placeholder does not resolve there.
fn consume_attribution(cur: &mut LineCursor, options: &Options<'_>) -> Option<Vec<InlineNode>> {
consume_caption_slot(cur, options, false)
}

fn consume_caption(cur: &mut LineCursor, options: &Options<'_>) -> Option<Vec<InlineNode>> {
consume_caption_slot(cur, options, true)
}
Expand Down Expand Up @@ -13744,9 +13732,6 @@ fn apply_abbreviations_block(block: &mut BlockNode, index: &AbbreviationIndex<'_
}
}
BlockNode::BlockQuote(b) => {
if let Some(attribution) = &mut b.attribution {
apply_abbreviations_inline(attribution, index);
}
for child in &mut b.children {
apply_abbreviations_block(child, index);
}
Expand Down Expand Up @@ -14432,9 +14417,6 @@ fn resolve_reference_links_block(
}
}
BlockNode::BlockQuote(b) => {
if let Some(attribution) = &mut b.attribution {
resolve_reference_links_inline(attribution, defs, heading_index);
}
for child in &mut b.children {
resolve_reference_links_block(child, defs, heading_index);
}
Expand Down Expand Up @@ -15340,9 +15322,6 @@ fn coalesce_block(block: &mut BlockNode) {
}
}
BlockNode::BlockQuote(b) => {
if let Some(attribution) = &mut b.attribution {
coalesce_inlines(attribution);
}
for child in &mut b.children {
coalesce_block(child);
}
Expand Down
15 changes: 0 additions & 15 deletions src/profile_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,6 @@ impl ProfileFilter<'_> {
}
}
BlockNode::BlockQuote(bq) => {
if let Some(attribution) = &mut bq.attribution {
self.filter_inlines(attribution, depth + 1)?;
}
self.filter_blocks(&mut bq.children, depth)?;
}
BlockNode::Table(table) => {
Expand Down Expand Up @@ -860,15 +857,6 @@ fn extract_block_text(node: &BlockNode, smart: SmartTypographyMode) -> String {
paras.push(format!("> {text}"));
}
}
if let Some(attribution) = &bq.attribution {
let text: String = attribution
.iter()
.map(|n| extract_inline_text(n, smart))
.collect();
if !text.is_empty() {
paras.push(format!("^ {text}"));
}
}
paras.join("\n")
}
BlockNode::DefinitionList(dl) => {
Expand Down Expand Up @@ -1090,9 +1078,6 @@ fn cleanup_block_children(block: &mut BlockNode) {
}
}
BlockNode::BlockQuote(bq) => {
if let Some(attribution) = &mut bq.attribution {
cleanup_inlines(attribution);
}
cleanup_blocks(&mut bq.children);
}
BlockNode::Table(t) => {
Expand Down
22 changes: 1 addition & 21 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,16 +370,6 @@ fn collect_footnotes_block(
}
}
BlockNode::BlockQuote(b) => {
if let Some(attribution) = &mut b.attribution {
collect_footnotes_inline(
assign_ref_ids,
attribution,
def_labels,
label_indices,
seen,
order,
);
}
for child in &mut b.children {
collect_footnotes_block(
assign_ref_ids,
Expand Down Expand Up @@ -1490,10 +1480,7 @@ fn render_blockquote(
state: &mut RenderState,
) {
indent(out, level);
// PART 9 §4a: an attribution renders INSIDE the quote, where a quotation's
// source belongs, and forces the expanded form - the compact one has
// nowhere to put a second element (carve#1159).
if b.attribution.is_none() && b.children.len() == 1 {
if b.children.len() == 1 {
if let BlockNode::Paragraph(p) = &b.children[0] {
out.push_str("<blockquote");
write_attrs(out, &b.attrs);
Expand All @@ -1516,13 +1503,6 @@ fn render_blockquote(
out.push_str(&child);
first = false;
}
if let Some(attribution) = &b.attribution {
out.push('\n');
indent(out, level + 1);
out.push_str("<footer>");
render_inlines(out, attribution, options, state);
out.push_str("</footer>");
}
out.push('\n');
indent(out, level);
out.push_str("</blockquote>");
Expand Down
Loading