diff options
Diffstat (limited to 'tests/test_utils/mdast_util_to_hast.rs')
-rw-r--r-- | tests/test_utils/mdast_util_to_hast.rs | 1275 |
1 files changed, 0 insertions, 1275 deletions
diff --git a/tests/test_utils/mdast_util_to_hast.rs b/tests/test_utils/mdast_util_to_hast.rs deleted file mode 100644 index 29d9489..0000000 --- a/tests/test_utils/mdast_util_to_hast.rs +++ /dev/null @@ -1,1275 +0,0 @@ -//! Turn a markdown AST into an HTML AST. -//! -//! Port of <https://github.com/syntax-tree/mdast-util-to-hast>, by the same -//! author: -//! -//! (The MIT License) -//! -//! Copyright (c) 2016 Titus Wormer <tituswormer@gmail.com> -//! -//! Permission is hereby granted, free of charge, to any person obtaining -//! a copy of this software and associated documentation files (the -//! 'Software'), to deal in the Software without restriction, including -//! without limitation the rights to use, copy, modify, merge, publish, -//! distribute, sublicense, and/or sell copies of the Software, and to -//! permit persons to whom the Software is furnished to do so, subject to -//! the following conditions: -//! -//! The above copyright notice and this permission notice shall be -//! included in all copies or substantial portions of the Software. -//! -//! THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -//! EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -//! MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -//! IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -//! CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -//! TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -//! SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -use crate::test_utils::hast; -use markdown::{mdast, sanitize, unist::Position}; - -// To do: support these compile options: -// ``` -// pub gfm_footnote_label: Option<String>, -// pub gfm_footnote_label_tag_name: Option<String>, -// pub gfm_footnote_label_attributes: Option<String>, -// pub gfm_footnote_back_label: Option<String>, -// pub gfm_footnote_clobber_prefix: Option<String>, -// ``` -// -// Maybe also: -// * option to persist `meta`? -// * option to generate a `style` attribute instead of `align`? -// * support `Raw` nodes for HTML? -// -// To do: -// * revert references when undefined? -// <https://github.com/syntax-tree/mdast-util-to-hast/blob/c393d0a/lib/revert.js> - -#[derive(Debug)] -struct State { - definitions: Vec<(String, String, Option<String>)>, - footnote_definitions: Vec<(String, Vec<hast::Node>)>, - footnote_calls: Vec<(String, usize)>, -} - -#[derive(Debug)] -enum Result { - Fragment(Vec<hast::Node>), - Node(hast::Node), - None, -} - -#[allow(dead_code)] -pub fn mdast_util_to_hast(mdast: &mdast::Node) -> hast::Node { - let mut definitions = vec![]; - - // Collect definitions. - // Calls take info from their definition. - // Calls can come come before definitions. - // Footnote calls can also come before footnote definitions, but those - // calls *do not* take info from their definitions, so we don’t care - // about footnotes here. - visit(mdast, |node| { - if let mdast::Node::Definition(definition) = node { - definitions.push(( - definition.identifier.clone(), - definition.url.clone(), - definition.title.clone(), - )); - } - }); - - let mut state = State { - definitions, - footnote_definitions: vec![], - footnote_calls: vec![], - }; - - let result = one(&mut state, mdast, None); - - if state.footnote_calls.is_empty() { - if let Result::Node(node) = result { - return node; - } - } - - // We either have to generate a footer, or we don’t have a single node. - // So we need a root. - let mut root = hast::Root { - children: vec![], - position: None, - }; - - match result { - Result::Fragment(children) => root.children = children, - Result::Node(node) => { - if let hast::Node::Root(existing) = node { - root = existing; - } else { - root.children.push(node); - } - } - Result::None => {} - } - - if !state.footnote_calls.is_empty() { - let mut items = vec![]; - - let mut index = 0; - while index < state.footnote_calls.len() { - let (id, count) = &state.footnote_calls[index]; - let safe_id = sanitize(&id.to_lowercase()); - - // Find definition: we’ll always find it. - let mut definition_index = 0; - while definition_index < state.footnote_definitions.len() { - if &state.footnote_definitions[definition_index].0 == id { - break; - } - definition_index += 1; - } - debug_assert_ne!( - definition_index, - state.footnote_definitions.len(), - "expected definition" - ); - - // We’ll find each used definition once, so we can split off to take the content. - let mut content = state.footnote_definitions[definition_index].1.split_off(0); - - let mut reference_index = 0; - let mut backreferences = vec![]; - while reference_index < *count { - let mut backref_children = vec![hast::Node::Text(hast::Text { - value: "↩".into(), - position: None, - })]; - - if reference_index != 0 { - backreferences.push(hast::Node::Text(hast::Text { - value: " ".into(), - position: None, - })); - - backref_children.push(hast::Node::Element(hast::Element { - tag_name: "sup".into(), - properties: vec![], - children: vec![hast::Node::Text(hast::Text { - value: (reference_index + 1).to_string(), - position: None, - })], - position: None, - })); - } - - backreferences.push(hast::Node::Element(hast::Element { - tag_name: "a".into(), - properties: vec![ - ( - "href".into(), - hast::PropertyValue::String(format!( - "#fnref-{}{}", - safe_id, - if reference_index == 0 { - "".into() - } else { - format!("-{}", &(reference_index + 1).to_string()) - } - )), - ), - ( - "dataFootnoteBackref".into(), - hast::PropertyValue::Boolean(true), - ), - ( - "ariaLabel".into(), - hast::PropertyValue::String("Back to content".into()), - ), - ( - "className".into(), - hast::PropertyValue::SpaceSeparated(vec![ - "data-footnote-backref".into() - ]), - ), - ], - children: backref_children, - position: None, - })); - - reference_index += 1; - } - - let mut backreference_opt = Some(backreferences); - - if let Some(hast::Node::Element(tail_element)) = content.last_mut() { - if tail_element.tag_name == "p" { - if let Some(hast::Node::Text(text)) = tail_element.children.last_mut() { - text.value.push(' '); - } else { - tail_element.children.push(hast::Node::Text(hast::Text { - value: " ".into(), - position: None, - })); - } - - tail_element - .children - .append(&mut backreference_opt.take().unwrap()); - } - } - - // No paragraph, just push them. - if let Some(mut backreference) = backreference_opt { - content.append(&mut backreference); - } - - items.push(hast::Node::Element(hast::Element { - tag_name: "li".into(), - properties: vec![( - "id".into(), - hast::PropertyValue::String(format!("#fn-{}", safe_id)), - )], - children: wrap(content, true), - position: None, - })); - index += 1; - } - - root.children.push(hast::Node::Text(hast::Text { - value: "\n".into(), - position: None, - })); - root.children.push(hast::Node::Element(hast::Element { - tag_name: "section".into(), - properties: vec![ - ("dataFootnotes".into(), hast::PropertyValue::Boolean(true)), - ( - "className".into(), - hast::PropertyValue::SpaceSeparated(vec!["footnotes".into()]), - ), - ], - children: vec![ - hast::Node::Element(hast::Element { - tag_name: "h2".into(), - properties: vec![ - ( - "id".into(), - hast::PropertyValue::String("footnote-label".into()), - ), - ( - "className".into(), - hast::PropertyValue::SpaceSeparated(vec!["sr-only".into()]), - ), - ], - children: vec![hast::Node::Text(hast::Text { - value: "Footnotes".into(), - position: None, - })], - position: None, - }), - hast::Node::Text(hast::Text { - value: "\n".into(), - position: None, - }), - hast::Node::Element(hast::Element { - tag_name: "ol".into(), - properties: vec![], - children: wrap(items, true), - position: None, - }), - hast::Node::Text(hast::Text { - value: "\n".into(), - position: None, - }), - ], - position: None, - })); - root.children.push(hast::Node::Text(hast::Text { - value: "\n".into(), - position: None, - })); - } - - hast::Node::Root(root) -} - -fn one(state: &mut State, node: &mdast::Node, parent: Option<&mdast::Node>) -> Result { - match node { - mdast::Node::BlockQuote(d) => transform_block_quote(state, node, d), - mdast::Node::Break(d) => transform_break(state, node, d), - mdast::Node::Code(d) => transform_code(state, node, d), - mdast::Node::Delete(d) => transform_delete(state, node, d), - mdast::Node::Emphasis(d) => transform_emphasis(state, node, d), - mdast::Node::FootnoteDefinition(d) => transform_footnote_definition(state, node, d), - mdast::Node::FootnoteReference(d) => transform_footnote_reference(state, node, d), - mdast::Node::Heading(d) => transform_heading(state, node, d), - mdast::Node::Image(d) => transform_image(state, node, d), - mdast::Node::ImageReference(d) => transform_image_reference(state, node, d), - mdast::Node::InlineCode(d) => transform_inline_code(state, node, d), - mdast::Node::InlineMath(d) => transform_inline_math(state, node, d), - mdast::Node::Link(d) => transform_link(state, node, d), - mdast::Node::LinkReference(d) => transform_link_reference(state, node, d), - mdast::Node::ListItem(d) => transform_list_item(state, node, parent, d), - mdast::Node::List(d) => transform_list(state, node, d), - mdast::Node::Math(d) => transform_math(state, node, d), - mdast::Node::MdxFlowExpression(_) | mdast::Node::MdxTextExpression(_) => { - transform_mdx_expression(state, node) - } - mdast::Node::MdxJsxFlowElement(_) | mdast::Node::MdxJsxTextElement(_) => { - transform_mdx_jsx_element(state, node) - } - mdast::Node::MdxjsEsm(d) => transform_mdxjs_esm(state, node, d), - mdast::Node::Paragraph(d) => transform_paragraph(state, node, d), - mdast::Node::Root(d) => transform_root(state, node, d), - mdast::Node::Strong(d) => transform_strong(state, node, d), - // Note: this is only called here if there is a single cell passed, not when one is found in a table. - mdast::Node::TableCell(d) => { - transform_table_cell(state, node, false, mdast::AlignKind::None, d) - } - // Note: this is only called here if there is a single row passed, not when one is found in a table. - mdast::Node::TableRow(d) => transform_table_row(state, node, false, None, d), - mdast::Node::Table(d) => transform_table(state, node, d), - mdast::Node::Text(d) => transform_text(state, node, d), - mdast::Node::ThematicBreak(d) => transform_thematic_break(state, node, d), - // Ignore. - mdast::Node::Definition(_) - | mdast::Node::Html(_) - | mdast::Node::Yaml(_) - | mdast::Node::Toml(_) => Result::None, - } -} - -/// [`BlockQuote`][mdast::BlockQuote]. -fn transform_block_quote( - state: &mut State, - node: &mdast::Node, - block_quote: &mdast::BlockQuote, -) -> Result { - Result::Node(hast::Node::Element(hast::Element { - tag_name: "blockquote".into(), - properties: vec![], - children: wrap(all(state, node), true), - position: block_quote.position.clone(), - })) -} - -/// [`Break`][mdast::Break]. -fn transform_break(_state: &mut State, _node: &mdast::Node, break_: &mdast::Break) -> Result { - Result::Fragment(vec![ - hast::Node::Element(hast::Element { - tag_name: "br".into(), - properties: vec![], - children: vec![], - position: break_.position.clone(), - }), - hast::Node::Text(hast::Text { - value: "\n".into(), - position: None, - }), - ]) -} - -/// [`Code`][mdast::Code]. -fn transform_code(_state: &mut State, _node: &mdast::Node, code: &mdast::Code) -> Result { - let mut value = code.value.clone(); - value.push('\n'); - let mut properties = vec![]; - - if let Some(lang) = code.lang.as_ref() { - properties.push(( - "className".into(), - hast::PropertyValue::SpaceSeparated(vec![format!("language-{}", lang)]), - )); - } - - Result::Node(hast::Node::Element(hast::Element { - tag_name: "pre".into(), - properties: vec![], - children: vec![hast::Node::Element(hast::Element { - tag_name: "code".into(), - properties, - children: vec![hast::Node::Text(hast::Text { - value, - position: None, - })], - position: code.position.clone(), - })], - position: code.position.clone(), - })) -} - -/// [`Delete`][mdast::Delete]. -fn transform_delete(state: &mut State, node: &mdast::Node, delete: &mdast::Delete) -> Result { - Result::Node(hast::Node::Element(hast::Element { - tag_name: "del".into(), - properties: vec![], - children: all(state, node), - position: delete.position.clone(), - })) -} - -/// [`Emphasis`][mdast::Emphasis]. -fn transform_emphasis(state: &mut State, node: &mdast::Node, emphasis: &mdast::Emphasis) -> Result { - Result::Node(hast::Node::Element(hast::Element { - tag_name: "em".into(), - properties: vec![], - children: all(state, node), - position: emphasis.position.clone(), - })) -} - -/// [`FootnoteDefinition`][mdast::FootnoteDefinition]. -fn transform_footnote_definition( - state: &mut State, - node: &mdast::Node, - footnote_definition: &mdast::FootnoteDefinition, -) -> Result { - let children = all(state, node); - // Set aside. - state - .footnote_definitions - .push((footnote_definition.identifier.clone(), children)); - Result::None -} - -/// [`FootnoteReference`][mdast::FootnoteReference]. -fn transform_footnote_reference( - state: &mut State, - _node: &mdast::Node, - footnote_reference: &mdast::FootnoteReference, -) -> Result { - let safe_id = sanitize(&footnote_reference.identifier.to_lowercase()); - let mut call_index = 0; - - // See if this has been called before. - while call_index < state.footnote_calls.len() { - if state.footnote_calls[call_index].0 == footnote_reference.identifier { - break; - } - call_index += 1; - } - - // New. - if call_index == state.footnote_calls.len() { - state - .footnote_calls - .push((footnote_reference.identifier.clone(), 0)); - } - - // Increment. - state.footnote_calls[call_index].1 += 1; - - let reuse_counter = state.footnote_calls[call_index].1; - - Result::Node(hast::Node::Element(hast::Element { - tag_name: "sup".into(), - properties: vec![], - children: vec![hast::Node::Element(hast::Element { - tag_name: "a".into(), - properties: vec![ - ( - "href".into(), - hast::PropertyValue::String(format!("#fn-{}", safe_id)), - ), - ( - "id".into(), - hast::PropertyValue::String(format!( - "fnref-{}{}", - safe_id, - if reuse_counter > 1 { - format!("-{}", reuse_counter) - } else { - "".into() - } - )), - ), - ("dataFootnoteRef".into(), hast::PropertyValue::Boolean(true)), - ( - "ariaDescribedBy".into(), - hast::PropertyValue::String("footnote-label".into()), - ), - ], - children: vec![hast::Node::Text(hast::Text { - value: (call_index + 1).to_string(), - position: None, - })], - position: None, - })], - position: footnote_reference.position.clone(), - })) -} - -/// [`Heading`][mdast::Heading]. -fn transform_heading(state: &mut State, node: &mdast::Node, heading: &mdast::Heading) -> Result { - Result::Node(hast::Node::Element(hast::Element { - tag_name: format!("h{}", heading.depth), - properties: vec![], - children: all(state, node), - position: heading.position.clone(), - })) -} - -/// [`Image`][mdast::Image]. -fn transform_image(_state: &mut State, _node: &mdast::Node, image: &mdast::Image) -> Result { - let mut properties = vec![]; - - properties.push(( - "src".into(), - hast::PropertyValue::String(sanitize(&image.url)), - )); - - properties.push(("alt".into(), hast::PropertyValue::String(image.alt.clone()))); - - if let Some(value) = image.title.as_ref() { - properties.push(("title".into(), hast::PropertyValue::String(value.into()))); - } - - Result::Node(hast::Node::Element(hast::Element { - tag_name: "img".into(), - properties, - children: vec![], - position: image.position.clone(), - })) -} - -/// [`ImageReference`][mdast::ImageReference]. -fn transform_image_reference( - state: &mut State, - _node: &mdast::Node, - image_reference: &mdast::ImageReference, -) -> Result { - let mut properties = vec![]; - - let definition = state - .definitions - .iter() - .find(|d| d.0 == image_reference.identifier); - - let (_, url, title) = - definition.expect("expected reference to have a corresponding definition"); - - properties.push(("src".into(), hast::PropertyValue::String(sanitize(url)))); - - properties.push(( - "alt".into(), - hast::PropertyValue::String(image_reference.alt.clone()), - )); - - if let Some(value) = title { - properties.push(("title".into(), hast::PropertyValue::String(value.into()))); - } - - Result::Node(hast::Node::Element(hast::Element { - tag_name: "img".into(), - properties, - children: vec![], - position: image_reference.position.clone(), - })) -} - -/// [`InlineCode`][mdast::InlineCode]. -fn transform_inline_code( - _state: &mut State, - _node: &mdast::Node, - inline_code: &mdast::InlineCode, -) -> Result { - Result::Node(hast::Node::Element(hast::Element { - tag_name: "code".into(), - properties: vec![], - children: vec![hast::Node::Text(hast::Text { - value: replace_eols_with_spaces(&inline_code.value), - position: None, - })], - position: inline_code.position.clone(), - })) -} - -/// [`InlineMath`][mdast::InlineMath]. -fn transform_inline_math( - _state: &mut State, - _node: &mdast::Node, - inline_math: &mdast::InlineMath, -) -> Result { - Result::Node(hast::Node::Element(hast::Element { - tag_name: "code".into(), - properties: vec![( - "className".into(), - hast::PropertyValue::SpaceSeparated(vec!["language-math".into(), "math-inline".into()]), - )], - children: vec![hast::Node::Text(hast::Text { - value: replace_eols_with_spaces(&inline_math.value), - position: None, - })], - position: inline_math.position.clone(), - })) -} - -/// [`Link`][mdast::Link]. -fn transform_link(state: &mut State, node: &mdast::Node, link: &mdast::Link) -> Result { - let mut properties = vec![]; - - properties.push(( - "href".into(), - hast::PropertyValue::String(sanitize(&link.url)), - )); - - if let Some(value) = link.title.as_ref() { - properties.push(("title".into(), hast::PropertyValue::String(value.into()))); - } - - Result::Node(hast::Node::Element(hast::Element { - tag_name: "a".into(), - properties, - children: all(state, node), - position: link.position.clone(), - })) -} - -/// [`LinkReference`][mdast::LinkReference]. -fn transform_link_reference( - state: &mut State, - node: &mdast::Node, - link_reference: &mdast::LinkReference, -) -> Result { - let mut properties = vec![]; - - let definition = state - .definitions - .iter() - .find(|d| d.0 == link_reference.identifier); - - let (_, url, title) = - definition.expect("expected reference to have a corresponding definition"); - - properties.push(("href".into(), hast::PropertyValue::String(sanitize(url)))); - - if let Some(value) = title { - properties.push(("title".into(), hast::PropertyValue::String(value.into()))); - } - - Result::Node(hast::Node::Element(hast::Element { - tag_name: "a".into(), - properties, - children: all(state, node), - position: link_reference.position.clone(), - })) -} - -/// [`ListItem`][mdast::ListItem]. -fn transform_list_item( - state: &mut State, - node: &mdast::Node, - parent: Option<&mdast::Node>, - list_item: &mdast::ListItem, -) -> Result { - let mut children = all(state, node); - let mut loose = list_item_loose(node); - - if let Some(parent) = parent { - if matches!(parent, mdast::Node::List(_)) { - loose = list_loose(parent); - } - }; - - let mut properties = vec![]; - - // Inject a checkbox. - if let Some(checked) = list_item.checked { - // According to github-markdown-css, this class hides bullet. - // See: <https://github.com/sindresorhus/github-markdown-css>. - properties.push(( - "className".into(), - hast::PropertyValue::SpaceSeparated(vec!["task-list-item".into()]), - )); - - let mut input = Some(hast::Node::Element(hast::Element { - tag_name: "input".into(), - properties: vec![ - ( - "type".into(), - hast::PropertyValue::String("checkbox".into()), - ), - ("checked".into(), hast::PropertyValue::Boolean(checked)), - ("disabled".into(), hast::PropertyValue::Boolean(true)), - ], - children: vec![], - position: None, - })); - - if let Some(hast::Node::Element(x)) = children.first_mut() { - if x.tag_name == "p" { - if !x.children.is_empty() { - x.children.insert( - 0, - hast::Node::Text(hast::Text { - value: " ".into(), - position: None, - }), - ); - } - - x.children.insert(0, input.take().unwrap()); - } - } - - // If the input wasn‘t injected yet, inject a paragraph. - if let Some(input) = input { - children.insert( - 0, - hast::Node::Element(hast::Element { - tag_name: "p".into(), - properties: vec![], - children: vec![input], - position: None, - }), - ); - } - } - - children.reverse(); - let mut result = vec![]; - let mut head = true; - let empty = children.is_empty(); - let mut tail_p = false; - - while let Some(child) = children.pop() { - let mut is_p = false; - if let hast::Node::Element(el) = &child { - if el.tag_name == "p" { - is_p = true; - } - } - - // Add eols before nodes, except if this is a tight, first paragraph. - if loose || !head || !is_p { - result.push(hast::Node::Text(hast::Text { - value: "\n".into(), - position: None, - })); - } - - if is_p && !loose { - // Unwrap the paragraph. - if let hast::Node::Element(mut el) = child { - result.append(&mut el.children); - } - } else { - result.push(child); - } - - head = false; - tail_p = is_p; - } - - // Add eol after last node, except if it is tight or a paragraph. - if !empty && (loose || !tail_p) { - result.push(hast::Node::Text(hast::Text { - value: "\n".into(), - position: None, - })); - } - - Result::Node(hast::Node::Element(hast::Element { - tag_name: "li".into(), - properties, - children: result, - position: list_item.position.clone(), - })) -} - -/// [`List`][mdast::List]. -fn transform_list(state: &mut State, node: &mdast::Node, list: &mdast::List) -> Result { - let mut contains_task_list = false; - let mut index = 0; - - while index < list.children.len() { - if let mdast::Node::ListItem(item) = &list.children[index] { - if item.checked.is_some() { - contains_task_list = true; - } - } - - index += 1; - } - - let mut properties = vec![]; - - // Add start. - if let Some(start) = list.start { - if list.ordered && start != 1 { - properties.push(( - "start".into(), - hast::PropertyValue::String(start.to_string()), - )); - } - } - - // Like GitHub, add a class for custom styling. - if contains_task_list { - properties.push(( - "className".into(), - hast::PropertyValue::SpaceSeparated(vec!["contains-task-list".into()]), - )); - } - - Result::Node(hast::Node::Element(hast::Element { - tag_name: if list.ordered { - "ol".into() - } else { - "ul".into() - }, - properties, - children: wrap(all(state, node), true), - position: list.position.clone(), - })) -} - -/// [`Math`][mdast::Math]. -fn transform_math(_state: &mut State, _node: &mdast::Node, math: &mdast::Math) -> Result { - let mut value = math.value.clone(); - value.push('\n'); - - Result::Node(hast::Node::Element(hast::Element { - tag_name: "pre".into(), - properties: vec![], - children: vec![hast::Node::Element(hast::Element { - tag_name: "code".into(), - properties: vec![( - "className".into(), - hast::PropertyValue::SpaceSeparated(vec![ - "language-math".into(), - "math-display".into(), - ]), - )], - children: vec![hast::Node::Text(hast::Text { - value, - position: None, - })], - position: math.position.clone(), - })], - position: math.position.clone(), - })) -} - -/// [`MdxFlowExpression`][mdast::MdxFlowExpression],[`MdxTextExpression`][mdast::MdxTextExpression]. -fn transform_mdx_expression(_state: &mut State, node: &mdast::Node) -> Result { - match node { - mdast::Node::MdxFlowExpression(node) => { - Result::Node(hast::Node::MdxExpression(hast::MdxExpression { - value: node.value.clone(), - position: node.position.clone(), - stops: node.stops.clone(), - })) - } - mdast::Node::MdxTextExpression(node) => { - Result::Node(hast::Node::MdxExpression(hast::MdxExpression { - value: node.value.clone(), - position: node.position.clone(), - stops: node.stops.clone(), - })) - } - _ => unreachable!("expected expression"), - } -} - -/// [`MdxJsxFlowElement`][mdast::MdxJsxFlowElement],[`MdxJsxTextElement`][mdast::MdxJsxTextElement]. -fn transform_mdx_jsx_element(state: &mut State, node: &mdast::Node) -> Result { - let (name, attributes) = match node { - mdast::Node::MdxJsxFlowElement(n) => (&n.name, &n.attributes), - mdast::Node::MdxJsxTextElement(n) => (&n.name, &n.attributes), - _ => unreachable!("expected mdx jsx element"), - }; - - Result::Node(hast::Node::MdxJsxElement(hast::MdxJsxElement { - name: name.clone(), - attributes: attributes.clone(), - children: all(state, node), - position: node.position().cloned(), - })) -} - -/// [`MdxjsEsm`][mdast::MdxjsEsm]. -fn transform_mdxjs_esm( - _state: &mut State, - _node: &mdast::Node, - mdxjs_esm: &mdast::MdxjsEsm, -) -> Result { - Result::Node(hast::Node::MdxjsEsm(hast::MdxjsEsm { - value: mdxjs_esm.value.clone(), - position: mdxjs_esm.position.clone(), - stops: mdxjs_esm.stops.clone(), - })) -} - -/// [`Paragraph`][mdast::Paragraph]. -fn transform_paragraph( - state: &mut State, - node: &mdast::Node, - paragraph: &mdast::Paragraph, -) -> Result { - Result::Node(hast::Node::Element(hast::Element { - tag_name: "p".into(), - properties: vec![], - children: all(state, node), - position: paragraph.position.clone(), - })) -} - -/// [`Root`][mdast::Root]. -fn transform_root(state: &mut State, node: &mdast::Node, root: &mdast::Root) -> Result { - Result::Node(hast::Node::Root(hast::Root { - children: wrap(all(state, node), false), - position: root.position.clone(), - })) -} - -/// [`Strong`][mdast::Strong]. -fn transform_strong(state: &mut State, node: &mdast::Node, strong: &mdast::Strong) -> Result { - Result::Node(hast::Node::Element(hast::Element { - tag_name: "strong".into(), - properties: vec![], - children: all(state, node), - position: strong.position.clone(), - })) -} - -/// [`TableCell`][mdast::TableCell]. -fn transform_table_cell( - state: &mut State, - node: &mdast::Node, - head: bool, - align: mdast::AlignKind, - table_cell: &mdast::TableCell, -) -> Result { - let align_value = match align { - mdast::AlignKind::None => None, - mdast::AlignKind::Left => Some("left"), - mdast::AlignKind::Right => Some("right"), - mdast::AlignKind::Center => Some("center"), - }; - - let mut properties = vec![]; - - if let Some(value) = align_value { - properties.push(("align".into(), hast::PropertyValue::String(value.into()))); - } - - Result::Node(hast::Node::Element(hast::Element { - tag_name: if head { "th".into() } else { "td".into() }, - properties, - children: all(state, node), - position: table_cell.position.clone(), - })) -} - -/// [`TableRow`][mdast::TableRow]. -fn transform_table_row( - state: &mut State, - _node: &mdast::Node, - head: bool, - align: Option<&[mdast::AlignKind]>, - table_row: &mdast::TableRow, -) -> Result { - let mut children = vec![]; - let mut index = 0; - #[allow(clippy::redundant_closure_for_method_calls)] - let len = align.map_or(table_row.children.len(), |d| d.len()); - let empty_cell = mdast::Node::TableCell(mdast::TableCell { - children: vec![], - position: None, - }); - - while index < len { - let align_value = align - .and_then(|d| d.get(index)) - .unwrap_or(&mdast::AlignKind::None); - - let child = table_row.children.get(index).unwrap_or(&empty_cell); - - let result = if let mdast::Node::TableCell(table_cell) = child { - transform_table_cell(state, child, head, *align_value, table_cell) - } else { - unreachable!("expected tale cell in table row") - }; - - append_result(&mut children, result); - index += 1; - } - - Result::Node(hast::Node::Element(hast::Element { - tag_name: "tr".into(), - properties: vec![], - children: wrap(children, true), - position: table_row.position.clone(), - })) -} - -/// [`Table`][mdast::Table]. -fn transform_table(state: &mut State, _node: &mdast::Node, table: &mdast::Table) -> Result { - let mut rows = vec![]; - let mut index = 0; - - while index < table.children.len() { - let child = &table.children[index]; - let result = if let mdast::Node::TableRow(table_row) = child { - transform_table_row( - state, - &table.children[index], - index == 0, - Some(&table.align), - table_row, - ) - } else { - unreachable!("expected table row as child of table") - }; - - append_result(&mut rows, result); - index += 1; - } - - let body_rows = rows.split_off(1); - let head_row = rows.pop(); - let mut children = vec![]; - - if let Some(row) = head_row { - let position = row.position().cloned(); - children.push(hast::Node::Element(hast::Element { - tag_name: "thead".into(), - properties: vec![], - children: wrap(vec![row], true), - position, - })); - } - - if !body_rows.is_empty() { - let mut position = None; - - if let Some(position_start) = body_rows.first().and_then(hast::Node::position) { - if let Some(position_end) = body_rows.last().and_then(hast::Node::position) { - position = Some(Position { - start: position_start.start.clone(), - end: position_end.end.clone(), - }); - } - } - - children.push(hast::Node::Element(hast::Element { - tag_name: "tbody".into(), - properties: vec![], - children: wrap(body_rows, true), - position, - })); - } - - Result::Node(hast::Node::Element(hast::Element { - tag_name: "table".into(), - properties: vec![], - children: wrap(children, true), - position: table.position.clone(), - })) -} - -/// [`Text`][mdast::Text]. -fn transform_text(_state: &mut State, _node: &mdast::Node, text: &mdast::Text) -> Result { - Result::Node(hast::Node::Text(hast::Text { - value: text.value.clone(), - position: text.position.clone(), - })) -} - -/// [`ThematicBreak`][mdast::ThematicBreak]. -fn transform_thematic_break( - _state: &mut State, - _node: &mdast::Node, - thematic_break: &mdast::ThematicBreak, -) -> Result { - Result::Node(hast::Node::Element(hast::Element { - tag_name: "hr".into(), - properties: vec![], - children: vec![], - position: thematic_break.position.clone(), - })) -} - -// Transform children of `parent`. -fn all(state: &mut State, parent: &mdast::Node) -> Vec<hast::Node> { - let mut nodes = vec![]; - if let Some(children) = parent.children() { - let mut index = 0; - while index < children.len() { - let child = &children[index]; - let result = one(state, child, Some(parent)); - append_result(&mut nodes, result); - index += 1; - } - } - - nodes -} - -/// Wrap `nodes` with line feeds between each entry. -/// Optionally adds line feeds at the start and end. -fn wrap(mut nodes: Vec<hast::Node>, loose: bool) -> Vec<hast::Node> { - let mut result = vec![]; - let was_empty = nodes.is_empty(); - let mut head = true; - - nodes.reverse(); - - if loose { - result.push(hast::Node::Text(hast::Text { - value: "\n".into(), - position: None, - })); - } - - while let Some(item) = nodes.pop() { - // Inject when there’s more: - if !head { - result.push(hast::Node::Text(hast::Text { - value: "\n".into(), - position: None, - })); - } - head = false; - result.push(item); - } - - if loose && !was_empty { - result.push(hast::Node::Text(hast::Text { - value: "\n".into(), - position: None, - })); - } - - result -} - -/// Visit. -fn visit<Visitor>(node: &mdast::Node, visitor: Visitor) -where - Visitor: FnMut(&mdast::Node), -{ - visit_impl(node, visitor); -} - -/// Visit, mutably. -// Probably useful later: -#[allow(dead_code)] -fn visit_mut<Visitor>(node: &mut mdast::Node, visitor: Visitor) -where - Visitor: FnMut(&mut mdast::Node), -{ - visit_mut_impl(node, visitor); -} - -/// Internal implementation to visit. -fn visit_impl<Visitor>(node: &mdast::Node, mut visitor: Visitor) -> Visitor -where - Visitor: FnMut(&mdast::Node), -{ - visitor(node); - - if let Some(children) = node.children() { - let mut index = 0; - while index < children.len() { - let child = &children[index]; - visitor = visit_impl(child, visitor); - index += 1; - } - } - - visitor -} - -/// Internal implementation to visit, mutably. -fn visit_mut_impl<Visitor>(node: &mut mdast::Node, mut visitor: Visitor) -> Visitor -where - Visitor: FnMut(&mut mdast::Node), -{ - visitor(node); - - if let Some(children) = node.children_mut() { - let mut index = 0; - while let Some(child) = children.get_mut(index) { - visitor = visit_mut_impl(child, visitor); - index += 1; - } - } - - visitor -} - -// To do: trim arounds breaks: <https://github.com/syntax-tree/mdast-util-to-hast/blob/c393d0a/lib/traverse.js>. -/// Append an (optional, variadic) result. -fn append_result(list: &mut Vec<hast::Node>, result: Result) { - match result { - Result::Fragment(mut fragment) => list.append(&mut fragment), - Result::Node(node) => list.push(node), - Result::None => {} - }; -} - -/// Replace line endings (CR, LF, CRLF) with spaces. -/// -/// Used for inline code and inline math. -fn replace_eols_with_spaces(value: &str) -> String { - // It’ll grow a bit small for each CR+LF. - let mut result = String::with_capacity(value.len()); - let bytes = value.as_bytes(); - let mut index = 0; - let mut start = 0; - - while index < bytes.len() { - let byte = bytes[index]; - - if byte == b'\r' || byte == b'\n' { - result.push_str(&value[start..index]); - result.push(' '); - - if index + 1 < bytes.len() && byte == b'\r' && bytes[index + 1] == b'\n' { - index += 1; - } - - start = index + 1; - } - - index += 1; - } - - result.push_str(&value[start..]); - - result -} - -/// Check if a list is loose. -fn list_loose(node: &mdast::Node) -> bool { - if let mdast::Node::List(list) = node { - if list.spread { - return true; - } - - if let Some(children) = node.children() { - let mut index = 0; - while index < children.len() { - if list_item_loose(&children[index]) { - return true; - } - index += 1; - } - } - } - - false -} - -/// Check if a list item is loose. -fn list_item_loose(node: &mdast::Node) -> bool { - if let mdast::Node::ListItem(item) = node { - item.spread - } else { - false - } -} |