Skip to content
Closed
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
4 changes: 2 additions & 2 deletions python/xhtmlmd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
__all__ = ["to_xhtml", "render", "blocks", "rewrite"]


def to_xhtml(markdown: str, *, math: str = "brackets", tagfilter: bool = False, balance: bool = False, callbacks: dict | None = None,
def to_xhtml(markdown: str, *, math: str = "brackets", mustache: bool = True, tagfilter: bool = False, balance: bool = False, callbacks: dict | None = None,
max_inline_depth: int | None = None, max_block_depth: int | None = None, max_link_paren_depth: int | None = None) -> str:
"Render Markdown to an XHTML fragment."
return _to_xhtml(markdown, math=math, tagfilter=tagfilter, balance=balance, callbacks=callbacks, max_inline_depth=max_inline_depth,
return _to_xhtml(markdown, math=math, mustache=mustache, tagfilter=tagfilter, balance=balance, callbacks=callbacks, max_inline_depth=max_inline_depth,
max_block_depth=max_block_depth, max_link_paren_depth=max_link_paren_depth)


Expand Down
29 changes: 29 additions & 0 deletions src/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ fn parse_inner(src: &str, ctx: &InlineContext<'_>, depth: usize) -> Vec<Inline>
let mut failed = FailedScans::default();
let mut i = 0;
while i < src.len() {
if ctx.options.mustache && starts(src, i, "{{") {
if let Some((item, next)) = mustache(src, i) {
scanner.flush_text();
scanner.push_inline(item);
i = next;
continue;
}
}
if starts(src, i, "\\[")
&& matches!(ctx.options.math, MathMode::Brackets | MathMode::Dollars)
{
Expand Down Expand Up @@ -420,6 +428,9 @@ fn plain_text_fast_path(src: &str, ctx: &InlineContext<'_>) -> bool {
if src.contains("==") {
return false;
}
if ctx.options.mustache && src.contains("{{") {
return false;
}
if ctx.options.math == MathMode::Dollars && src.contains('$') {
return false;
}
Expand All @@ -437,6 +448,24 @@ fn plain_text_fast_path(src: &str, ctx: &InlineContext<'_>) -> bool {
!can_link_or_span
}

fn mustache(src: &str, i: usize) -> Option<(Inline, usize)> {
let end = src[i + 2..].find("}}")? + i + 4;
let body = src[i + 2..end - 2].trim_start();
let class = match body.chars().next() {
Some('#' | '^' | '/') => "mustache.section",
Some('!') => "mustache.comment",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mustache.comment is very unlikely class due to the . which minimize hitting existing styles.

Some('>') => "mustache.partial",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{{> partial}} is something we could use in fast tract to include other docx or pdf's at rendering time.

_ => "mustache.placeholder",
};
Some((
Inline::Span {
attrs: Attr::with_class(class),
children: vec![Inline::Html(src[i..end].to_string())],
},
end,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We reuse existing span for simplicity instead of exposing a mustache markup in ast.

))
}

struct InlineScanner<'a, 'b> {
src: &'a str,
ctx: &'b InlineContext<'b>,
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub enum MathMode {
#[derive(Clone, Debug)]
pub struct Options {
pub math: MathMode,
pub mustache: bool,
pub tagfilter: bool,
pub balance: bool,
pub max_inline_depth: usize,
Expand All @@ -45,6 +46,7 @@ impl Default for Options {
fn default() -> Self {
Self {
math: MathMode::Brackets,
mustache: true,
tagfilter: false,
balance: false,
max_inline_depth: 64,
Expand Down
3 changes: 3 additions & 0 deletions src/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{MathMode, Options};
markdown,
*,
math = "brackets",
mustache = true,
tagfilter = false,
balance = false,
callbacks = None,
Expand All @@ -24,6 +25,7 @@ use crate::{MathMode, Options};
fn to_xhtml(
markdown: &str,
math: &str,
mustache: bool,
tagfilter: bool,
balance: bool,
callbacks: Option<Bound<'_, PyDict>>,
Expand All @@ -33,6 +35,7 @@ fn to_xhtml(
) -> PyResult<String> {
let mut options = Options {
math: parse_math_mode(math)?,
mustache,
tagfilter,
balance,
..Options::default()
Expand Down
15 changes: 15 additions & 0 deletions tests/test_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ def test_invalid_math_mode_raises():
with pytest.raises(ValueError, match="math must be"): to_xhtml("x", math="inline")


def test_mustache_placeholders_are_opaque_and_can_be_disabled():
src = "{{name.name}} {{#items}} {{/items}} {{^empty}} {{/empty}} {{! comment}} {{> partial}} {{*markdown*}}"
html = to_xhtml(src)
assert html.count('class="mustache.placeholder"') == 2
assert html.count('class="mustache.section"') == 4
assert html.count('class="mustache.comment"') == 1
assert html.count('class="mustache.partial"') == 1
assert '<span class="mustache.placeholder">{{name.name}}</span>' in html
assert '<span class="mustache.section">{{#items}}</span>' in html
assert '<span class="mustache.comment">{{! comment}}</span>' in html
assert '<span class="mustache.partial">{{> partial}}</span>' in html
assert "{{*markdown*}}" in html and "<em>markdown</em>" not in html
assert_html(to_xhtml("{{*markdown*}}", mustache=False), "<p>{{<em>markdown</em>}}</p>")


def test_node_callback_can_override_heading():
calls = []

Expand Down
Loading