diff --git a/html.go b/html.go index cb4f26e3..5a5a39cf 100644 --- a/html.go +++ b/html.go @@ -465,6 +465,8 @@ var ( h5CloseTag = []byte("") h6Tag = []byte("") + mathTag = []byte("") + mathCloseTag = []byte("") footnotesDivBytes = []byte("\n
\n\n") footnotesCloseDivBytes = []byte("\n
\n") @@ -827,6 +829,10 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt r.out(w, trCloseTag) r.cr(w) } + case Math: + r.out(w, mathTag) + escapeAllHTML(w, node.Literal) + r.out(w, mathCloseTag) default: panic("Unknown node type " + node.Type.String()) } diff --git a/inline.go b/inline.go index d45bd941..9a742a66 100644 --- a/inline.go +++ b/inline.go @@ -735,7 +735,9 @@ func linkEndsWithEntity(data []byte, linkEnd int) bool { } // hasPrefixCaseInsensitive is a custom implementation of -// strings.HasPrefix(strings.ToLower(s), prefix) +// +// strings.HasPrefix(strings.ToLower(s), prefix) +// // we rolled our own because ToLower pulls in a huge machinery of lowercasing // anything from Unicode and that's very slow. Since this func will only be // used on ASCII protocol prefixes, we can take shortcuts. @@ -1226,3 +1228,25 @@ func text(s []byte) *Node { func normalizeURI(s []byte) []byte { return s // TODO: implement } + +func math(p *Markdown, data []byte, offset int) (int, *Node) { + if offset > 0 && data[offset-1] == '\\' { + return 0, nil // Dollar sign has been escaped. + } + data = data[offset:] + isInline := len(data) > 2 && data[1] != '$' + if isInline { + data = data[1:] + nl := bytes.IndexByte(data, '\n') + dolla := bytes.IndexByte(data, '$') + if dolla < 0 || (nl >= 0 && nl < dolla) { + return 0, nil // No end to math node or newline before dollar. + } + math := NewNode(Math) + math.Literal = data[:dolla] + return dolla + 2, math + } + // Block math unsupported. + // isBlock := len(data) > 4 && data[1] == '$' && data[2] != '$' + return 0, nil +} diff --git a/inline_test.go b/inline_test.go index 5a91d8aa..02e56672 100644 --- a/inline_test.go +++ b/inline_test.go @@ -1214,3 +1214,15 @@ func BenchmarkSmartDoubleQuotes(b *testing.B) { runMarkdown("this should be normal \"quoted\" text.\n", params) } } + +func TestMath(t *testing.T) { + doTestsParam(t, []string{ + "In class we saw $a$ is a length.", + "

In class we saw a is a length.

\n", + + "What we see is that following $a+b four$. Right?", + "

What we see is that following a+b <three> four. Right?

\n", + }, TestParams{ + extensions: DollarMath, + }) +} diff --git a/markdown.go b/markdown.go index 58d2e453..aff091c3 100644 --- a/markdown.go +++ b/markdown.go @@ -47,6 +47,7 @@ const ( AutoHeadingIDs // Create the heading ID from the text BackslashLineBreak // Translate trailing backslashes into line breaks DefinitionLists // Render definition lists + DollarMath // Parse inline and block math inside '$' as Math nodes. CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants | SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes @@ -308,6 +309,9 @@ func New(opts ...Option) *Markdown { if p.extensions&Footnotes != 0 { p.notes = make([]*reference, 0) } + if p.extensions&DollarMath != 0 { + p.inlineCallback['$'] = math + } return &p } @@ -345,8 +349,8 @@ func WithNoExtensions() Option { // In Markdown, the link reference syntax can be made to resolve a link to // a reference instead of an inline URL, in one of the following ways: // -// * [link text][refid] -// * [refid][] +// - [link text][refid] +// - [refid][] // // Usually, the refid is defined at the bottom of the Markdown document. If // this override function is provided, the refid is passed to the override @@ -363,7 +367,9 @@ func WithRefOverride(o ReferenceOverrideFunc) Option { // block of markdown-encoded text. // // The simplest invocation of Run takes one argument, input: -// output := Run(input) +// +// output := Run(input) +// // This will parse the input with CommonExtensions enabled and render it with // the default HTMLRenderer (with CommonHTMLFlags). // @@ -371,13 +377,15 @@ func WithRefOverride(o ReferenceOverrideFunc) Option { // type does not contain exported fields, you can not use it directly. Instead, // use the With* functions. For example, this will call the most basic // functionality, with no extensions: -// output := Run(input, WithNoExtensions()) +// +// output := Run(input, WithNoExtensions()) // // You can use any number of With* arguments, even contradicting ones. They // will be applied in order of appearance and the latter will override the // former: -// output := Run(input, WithNoExtensions(), WithExtensions(exts), -// WithRenderer(yourRenderer)) +// +// output := Run(input, WithNoExtensions(), WithExtensions(exts), +// WithRenderer(yourRenderer)) func Run(input []byte, opts ...Option) []byte { r := NewHTMLRenderer(HTMLRendererParameters{ Flags: CommonHTMLFlags, @@ -491,35 +499,35 @@ func (p *Markdown) parseRefsToAST() { // // Consider this markdown with reference-style links: // -// [link][ref] +// [link][ref] // -// [ref]: /url/ "tooltip title" +// [ref]: /url/ "tooltip title" // // It will be ultimately converted to this HTML: // -//

link

+//

link

// // And a reference structure will be populated as follows: // -// p.refs["ref"] = &reference{ -// link: "/url/", -// title: "tooltip title", -// } +// p.refs["ref"] = &reference{ +// link: "/url/", +// title: "tooltip title", +// } // // Alternatively, reference can contain information about a footnote. Consider // this markdown: // -// Text needing a footnote.[^a] +// Text needing a footnote.[^a] // -// [^a]: This is the note +// [^a]: This is the note // // A reference structure will be populated as follows: // -// p.refs["a"] = &reference{ -// link: "a", -// title: "This is the note", -// noteID: , -// } +// p.refs["a"] = &reference{ +// link: "a", +// title: "This is the note", +// noteID: , +// } // // TODO: As you can see, it begs for splitting into two dedicated structures // for refs and for footnotes. diff --git a/node.go b/node.go index 04e6050c..7b612885 100644 --- a/node.go +++ b/node.go @@ -36,6 +36,7 @@ const ( TableHead TableBody TableRow + Math ) var nodeTypeNames = []string{ @@ -63,6 +64,7 @@ var nodeTypeNames = []string{ TableHead: "TableHead", TableBody: "TableBody", TableRow: "TableRow", + Math: "Math", } func (t NodeType) String() string {