github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/markup/goldmark/convert.go (about)

     1  // Copyright 2019 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  // Package goldmark converts Markdown to HTML using Goldmark.
    15  package goldmark
    16  
    17  import (
    18  	"bytes"
    19  
    20  	"github.com/gohugoio/hugo/identity"
    21  
    22  	"github.com/gohugoio/hugo/markup/goldmark/codeblocks"
    23  	"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
    24  	"github.com/gohugoio/hugo/markup/goldmark/images"
    25  	"github.com/gohugoio/hugo/markup/goldmark/internal/extensions/attributes"
    26  	"github.com/gohugoio/hugo/markup/goldmark/internal/render"
    27  
    28  	"github.com/gohugoio/hugo/markup/converter"
    29  	"github.com/gohugoio/hugo/markup/tableofcontents"
    30  	"github.com/yuin/goldmark"
    31  	"github.com/yuin/goldmark/ast"
    32  	"github.com/yuin/goldmark/extension"
    33  	"github.com/yuin/goldmark/parser"
    34  	"github.com/yuin/goldmark/renderer"
    35  	"github.com/yuin/goldmark/renderer/html"
    36  	"github.com/yuin/goldmark/text"
    37  
    38  	"github.com/graemephi/goldmark-qjs-katex"
    39  )
    40  
    41  const (
    42  	internalAttrPrefix = "_h__"
    43  )
    44  
    45  // Provider is the package entry point.
    46  var Provider converter.ProviderProvider = provide{}
    47  
    48  type provide struct{}
    49  
    50  func (p provide) New(cfg converter.ProviderConfig) (converter.Provider, error) {
    51  	md := newMarkdown(cfg)
    52  
    53  	return converter.NewProvider("goldmark", func(ctx converter.DocumentContext) (converter.Converter, error) {
    54  		return &goldmarkConverter{
    55  			ctx: ctx,
    56  			cfg: cfg,
    57  			md:  md,
    58  			sanitizeAnchorName: func(s string) string {
    59  				return sanitizeAnchorNameString(s, cfg.MarkupConfig().Goldmark.Parser.AutoHeadingIDType)
    60  			},
    61  		}, nil
    62  	}), nil
    63  }
    64  
    65  var _ converter.AnchorNameSanitizer = (*goldmarkConverter)(nil)
    66  
    67  type goldmarkConverter struct {
    68  	md  goldmark.Markdown
    69  	ctx converter.DocumentContext
    70  	cfg converter.ProviderConfig
    71  
    72  	sanitizeAnchorName func(s string) string
    73  }
    74  
    75  func (c *goldmarkConverter) SanitizeAnchorName(s string) string {
    76  	return c.sanitizeAnchorName(s)
    77  }
    78  
    79  func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
    80  	mcfg := pcfg.MarkupConfig()
    81  	cfg := mcfg.Goldmark
    82  	var rendererOptions []renderer.Option
    83  
    84  	if cfg.Renderer.HardWraps {
    85  		rendererOptions = append(rendererOptions, html.WithHardWraps())
    86  	}
    87  
    88  	if cfg.Renderer.XHTML {
    89  		rendererOptions = append(rendererOptions, html.WithXHTML())
    90  	}
    91  
    92  	if cfg.Renderer.Unsafe {
    93  		rendererOptions = append(rendererOptions, html.WithUnsafe())
    94  	}
    95  
    96  	var (
    97  		extensions = []goldmark.Extender{
    98  			newLinks(cfg),
    99  			newTocExtension(rendererOptions),
   100  		}
   101  		parserOptions []parser.Option
   102  	)
   103  
   104  	extensions = append(extensions, images.New(cfg.Parser.WrapStandAloneImageWithinParagraph))
   105  
   106  	if mcfg.Highlight.CodeFences {
   107  		extensions = append(extensions, codeblocks.New())
   108  	}
   109  
   110  	if cfg.Extensions.Table {
   111  		extensions = append(extensions, extension.Table)
   112  	}
   113  
   114  	if cfg.Extensions.Strikethrough {
   115  		extensions = append(extensions, extension.Strikethrough)
   116  	}
   117  
   118  	if cfg.Extensions.Linkify {
   119  		extensions = append(extensions, extension.Linkify)
   120  	}
   121  
   122  	if cfg.Extensions.TaskList {
   123  		extensions = append(extensions, extension.TaskList)
   124  	}
   125  
   126  	if !cfg.Extensions.Typographer.Disable {
   127  		t := extension.NewTypographer(
   128  			extension.WithTypographicSubstitutions(toTypographicPunctuationMap(cfg.Extensions.Typographer)),
   129  		)
   130  		extensions = append(extensions, t)
   131  	}
   132  
   133  	if cfg.Extensions.DefinitionList {
   134  		extensions = append(extensions, extension.DefinitionList)
   135  	}
   136  
   137  	if cfg.Extensions.Footnote {
   138  		extensions = append(extensions, extension.Footnote)
   139  	}
   140  
   141  	if cfg.Extensions.CJK.Enable {
   142  		opts := []extension.CJKOption{}
   143  		if cfg.Extensions.CJK.EastAsianLineBreaks {
   144  			opts = append(opts, extension.WithEastAsianLineBreaks())
   145  		}
   146  
   147  		if cfg.Extensions.CJK.EscapedSpace {
   148  			opts = append(opts, extension.WithEscapedSpace())
   149  		}
   150  		c := extension.NewCJK(opts...)
   151  		extensions = append(extensions, c)
   152  	}
   153  
   154  	if cfg.Parser.AutoHeadingID {
   155  		parserOptions = append(parserOptions, parser.WithAutoHeadingID())
   156  	}
   157  
   158  	if cfg.Parser.Attribute.Title {
   159  		parserOptions = append(parserOptions, parser.WithAttribute())
   160  	}
   161  	if cfg.Parser.Attribute.Block {
   162  		extensions = append(extensions, attributes.New())
   163  	}
   164  	
   165  	 if cfg.Katex.Enable {
   166        		extensions = append(extensions, &qjskatex.Extension{EnableWarnings: cfg.Katex.Warnings})
   167   	}
   168  
   169  	md := goldmark.New(
   170  		goldmark.WithExtensions(
   171  			extensions...,
   172  		),
   173  		goldmark.WithParserOptions(
   174  			parserOptions...,
   175  		),
   176  		goldmark.WithRendererOptions(
   177  			rendererOptions...,
   178  		),
   179  	)
   180  
   181  	return md
   182  }
   183  
   184  var _ identity.IdentitiesProvider = (*converterResult)(nil)
   185  
   186  type parserResult struct {
   187  	doc any
   188  	toc *tableofcontents.Fragments
   189  }
   190  
   191  func (p parserResult) Doc() any {
   192  	return p.doc
   193  }
   194  
   195  func (p parserResult) TableOfContents() *tableofcontents.Fragments {
   196  	return p.toc
   197  }
   198  
   199  type renderResult struct {
   200  	converter.ResultRender
   201  	ids identity.Identities
   202  }
   203  
   204  func (r renderResult) GetIdentities() identity.Identities {
   205  	return r.ids
   206  }
   207  
   208  type converterResult struct {
   209  	converter.ResultRender
   210  	tableOfContentsProvider
   211  	identity.IdentitiesProvider
   212  }
   213  
   214  type tableOfContentsProvider interface {
   215  	TableOfContents() *tableofcontents.Fragments
   216  }
   217  
   218  var converterIdentity = identity.KeyValueIdentity{Key: "goldmark", Value: "converter"}
   219  
   220  func (c *goldmarkConverter) Parse(ctx converter.RenderContext) (converter.ResultParse, error) {
   221  	pctx := c.newParserContext(ctx)
   222  	reader := text.NewReader(ctx.Src)
   223  
   224  	doc := c.md.Parser().Parse(
   225  		reader,
   226  		parser.WithContext(pctx),
   227  	)
   228  
   229  	return parserResult{
   230  		doc: doc,
   231  		toc: pctx.TableOfContents(),
   232  	}, nil
   233  
   234  }
   235  func (c *goldmarkConverter) Render(ctx converter.RenderContext, doc any) (converter.ResultRender, error) {
   236  	n := doc.(ast.Node)
   237  	buf := &render.BufWriter{Buffer: &bytes.Buffer{}}
   238  
   239  	rcx := &render.RenderContextDataHolder{
   240  		Rctx: ctx,
   241  		Dctx: c.ctx,
   242  		IDs:  identity.NewManager(converterIdentity),
   243  	}
   244  
   245  	w := &render.Context{
   246  		BufWriter:   buf,
   247  		ContextData: rcx,
   248  	}
   249  
   250  	if err := c.md.Renderer().Render(w, ctx.Src, n); err != nil {
   251  		return nil, err
   252  	}
   253  
   254  	return renderResult{
   255  		ResultRender: buf,
   256  		ids:          rcx.IDs.GetIdentities(),
   257  	}, nil
   258  
   259  }
   260  
   261  func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (converter.ResultRender, error) {
   262  	parseResult, err := c.Parse(ctx)
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  	renderResult, err := c.Render(ctx, parseResult.Doc())
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  	return converterResult{
   271  		ResultRender:            renderResult,
   272  		tableOfContentsProvider: parseResult,
   273  		IdentitiesProvider:      renderResult.(identity.IdentitiesProvider),
   274  	}, nil
   275  
   276  }
   277  
   278  var featureSet = map[identity.Identity]bool{
   279  	converter.FeatureRenderHooks: true,
   280  }
   281  
   282  func (c *goldmarkConverter) Supports(feature identity.Identity) bool {
   283  	return featureSet[feature.GetIdentity()]
   284  }
   285  
   286  func (c *goldmarkConverter) newParserContext(rctx converter.RenderContext) *parserContext {
   287  	ctx := parser.NewContext(parser.WithIDs(newIDFactory(c.cfg.MarkupConfig().Goldmark.Parser.AutoHeadingIDType)))
   288  	ctx.Set(tocEnableKey, rctx.RenderTOC)
   289  	return &parserContext{
   290  		Context: ctx,
   291  	}
   292  }
   293  
   294  type parserContext struct {
   295  	parser.Context
   296  }
   297  
   298  func (p *parserContext) TableOfContents() *tableofcontents.Fragments {
   299  	if v := p.Get(tocResultKey); v != nil {
   300  		return v.(*tableofcontents.Fragments)
   301  	}
   302  	return nil
   303  }
   304  
   305  // Note: It's tempting to put this in the config package, but that doesn't work.
   306  // TODO(bep) create upstream issue.
   307  func toTypographicPunctuationMap(t goldmark_config.Typographer) map[extension.TypographicPunctuation][]byte {
   308  	return map[extension.TypographicPunctuation][]byte{
   309  		extension.LeftSingleQuote:  []byte(t.LeftSingleQuote),
   310  		extension.RightSingleQuote: []byte(t.RightSingleQuote),
   311  		extension.LeftDoubleQuote:  []byte(t.LeftDoubleQuote),
   312  		extension.RightDoubleQuote: []byte(t.RightDoubleQuote),
   313  		extension.EnDash:           []byte(t.EnDash),
   314  		extension.EmDash:           []byte(t.EmDash),
   315  		extension.Ellipsis:         []byte(t.Ellipsis),
   316  		extension.LeftAngleQuote:   []byte(t.LeftAngleQuote),
   317  		extension.RightAngleQuote:  []byte(t.RightAngleQuote),
   318  		extension.Apostrophe:       []byte(t.Apostrophe),
   319  	}
   320  
   321  }