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 }