diff --git a/markdown/markdown.go b/markdown/markdown.go index 2ae7edc5..821d19ad 100644 --- a/markdown/markdown.go +++ b/markdown/markdown.go @@ -56,6 +56,7 @@ func (c *ConfluenceExtension) Extend(m goldmark.Markdown) { util.Prioritized(crenderer.NewConfluenceImageRenderer(c.Stdlib, c, c.Path), 100), util.Prioritized(crenderer.NewConfluenceParagraphRenderer(), 100), util.Prioritized(crenderer.NewConfluenceLinkRenderer(), 100), + util.Prioritized(crenderer.NewConfluenceMathLatexRenderer(), 100), )) if slices.Contains(c.MarkConfig.Features, "mkdocsadmonitions") { @@ -74,6 +75,7 @@ func (c *ConfluenceExtension) Extend(m goldmark.Markdown) { // Must be registered with a higher priority than goldmark's linkParser to make sure goldmark doesn't parse // the tags. util.Prioritized(cparser.NewConfluenceTagParser(), 199), + util.Prioritized(cparser.NewMathLatexParser(), 200), )) } diff --git a/parser/mathlatex.go b/parser/mathlatex.go new file mode 100644 index 00000000..2c96d010 --- /dev/null +++ b/parser/mathlatex.go @@ -0,0 +1,160 @@ +package parser + +import ( + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" + "github.com/yuin/goldmark/util" +) + +type MathLatexInline struct { + ast.BaseInline + + Equation []byte +} + +func (n *MathLatexInline) Inline() {} + +func (n *MathLatexInline) IsBlank(source []byte) bool { + for c := n.FirstChild(); c != nil; c = c.NextSibling() { + text := c.(*ast.Text).Segment + if !util.IsBlank(text.Value(source)) { + return false + } + } + return true +} + +func (n *MathLatexInline) Dump(source []byte, level int) { + ast.DumpHelper(n, source, level, nil, nil) +} + +var KindMathLatexInline = ast.NewNodeKind("MathLatexInline") + +func (n *MathLatexInline) Kind() ast.NodeKind { + return KindMathLatexInline +} + +type MathLatexBlock struct { + ast.BaseInline + + Equation []byte +} + +func (n *MathLatexBlock) IsBlank(source []byte) bool { + for c := n.FirstChild(); c != nil; c = c.NextSibling() { + text := c.(*ast.Text).Segment + if !util.IsBlank(text.Value(source)) { + return false + } + } + return true +} + +func (n *MathLatexBlock) Dump(source []byte, level int) { + ast.DumpHelper(n, source, level, nil, nil) +} + +var KindMathLatexBlock = ast.NewNodeKind("MathLatexBlock") + +func (n *MathLatexBlock) Kind() ast.NodeKind { + return KindMathLatexBlock +} + +type MathLatexParser struct { +} + +func NewMathLatexParser() parser.InlineParser { + return &MathLatexParser{} +} + +func (s *MathLatexParser) Trigger() []byte { + return []byte{'$'} +} + +func (s *MathLatexParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node { + buf := block.Source() + ln, pos := block.Position() + + lstart := pos.Start + lend := pos.Stop + line := buf[lstart:lend] + + var start, end, advance int + + trigger := line[0] + + display := len(line) > 1 && line[1] == trigger + + if display { // Display + start = lstart + 2 + + offset := 2 + + L: + for x := 0; x < 20; x++ { + for j := offset; j < len(line); j++ { + if len(line) > j+1 && line[j] == trigger && line[j+1] == trigger { + end = lstart + j + advance = 2 + break L + } + } + if lend == len(buf) { + break + } + if end == 0 { + rest := buf[lend:] + j := 1 + for j < len(rest) && rest[j] != '\n' { + j++ + } + lstart = lend + lend += j + line = buf[lstart:lend] + ln++ + offset = 0 + } + } + + } else { // Inline + start = lstart + 1 + + for i := 1; i < len(line); i++ { + c := line[i] + if c == '\\' { + i++ + continue + } + if c == trigger { + end = lstart + i + advance = 1 + break + } + } + if end >= len(buf) || buf[end] != trigger { + return nil + } + } + + if start >= end { + return nil + } + + newpos := end + advance + if newpos < lend { + block.SetPosition(ln, text.NewSegment(newpos, lend)) + } else { + block.Advance(newpos) + } + + if display { + return &MathLatexBlock{ + Equation: buf[start:end], + } + } else { + return &MathLatexInline{ + Equation: buf[start:end], + } + } +} diff --git a/renderer/mathlatex.go b/renderer/mathlatex.go new file mode 100644 index 00000000..ce5e2cb9 --- /dev/null +++ b/renderer/mathlatex.go @@ -0,0 +1,58 @@ +package renderer + +import ( + "fmt" + + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/renderer" + "github.com/yuin/goldmark/renderer/html" + "github.com/yuin/goldmark/util" + + cparser "github.com/kovetskiy/mark/parser" +) + +type ConfluenceMathLatexRenderer struct { + html.Config +} + +// NewConfluenceRenderer creates a new instance of the ConfluenceRenderer +func NewConfluenceMathLatexRenderer(opts ...html.Option) renderer.NodeRenderer { + return &ConfluenceMathLatexRenderer{ + Config: html.NewConfig(), + } +} + +// RegisterFuncs implements NodeRenderer.RegisterFuncs . +func (r *ConfluenceMathLatexRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { + reg.Register(cparser.KindMathLatexInline, r.renderInline) + reg.Register(cparser.KindMathLatexBlock, r.renderBlock) +} + +func (r *ConfluenceMathLatexRenderer) renderInline(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { + if entering { + node := n.(*cparser.MathLatexInline) + quoteType := "ppl mathjax inline macro" + + w.WriteString(fmt.Sprintf("", quoteType)) + w.WriteString("") + w.Write(node.Equation) + w.WriteString("") + w.WriteString("") + } + return ast.WalkContinue, nil +} + +func (r *ConfluenceMathLatexRenderer) renderBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { + if entering { + node := n.(*cparser.MathLatexBlock) + quoteType := "ppl mathjax block macro" + + w.WriteString(fmt.Sprintf("", quoteType)) + w.WriteString("") + w.WriteString("") + } + + return ast.WalkContinue, nil +}