github.com/neohugo/neohugo@v0.123.8/hugolib/page__per_output.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 hugolib
    15  
    16  import (
    17  	"bytes"
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"html/template"
    22  	"strings"
    23  	"sync"
    24  
    25  	"github.com/neohugo/neohugo/common/text"
    26  	"github.com/neohugo/neohugo/common/types/hstring"
    27  	"github.com/neohugo/neohugo/identity"
    28  	"github.com/neohugo/neohugo/parser/pageparser"
    29  
    30  	"github.com/mitchellh/mapstructure"
    31  	"github.com/spf13/cast"
    32  
    33  	"github.com/neohugo/neohugo/markup/converter/hooks"
    34  	"github.com/neohugo/neohugo/markup/highlight/chromalexers"
    35  	"github.com/neohugo/neohugo/markup/tableofcontents"
    36  
    37  	"github.com/neohugo/neohugo/markup/converter"
    38  
    39  	bp "github.com/neohugo/neohugo/bufferpool"
    40  	"github.com/neohugo/neohugo/tpl"
    41  
    42  	"github.com/neohugo/neohugo/helpers"
    43  	"github.com/neohugo/neohugo/output"
    44  	"github.com/neohugo/neohugo/resources/page"
    45  	"github.com/neohugo/neohugo/resources/resource"
    46  )
    47  
    48  var (
    49  	nopTargetPath    = targetPathsHolder{}
    50  	nopPagePerOutput = struct {
    51  		resource.ResourceLinksProvider
    52  		page.ContentProvider
    53  		page.PageRenderProvider
    54  		page.PaginatorProvider
    55  		page.TableOfContentsProvider
    56  		page.AlternativeOutputFormatsProvider
    57  
    58  		targetPather
    59  	}{
    60  		page.NopPage,
    61  		page.NopPage,
    62  		page.NopPage,
    63  		page.NopPage,
    64  		page.NopPage,
    65  		page.NopPage,
    66  		nopTargetPath,
    67  	}
    68  )
    69  
    70  func newPageContentOutput(po *pageOutput) (*pageContentOutput, error) {
    71  	cp := &pageContentOutput{
    72  		po:          po,
    73  		renderHooks: &renderHooks{},
    74  	}
    75  	return cp, nil
    76  }
    77  
    78  type renderHooks struct {
    79  	getRenderer hooks.GetRendererFunc
    80  	init        sync.Once
    81  }
    82  
    83  // pageContentOutput represents the Page content for a given output format.
    84  type pageContentOutput struct {
    85  	po *pageOutput
    86  
    87  	contentRenderedVersion int  // Incremented on reset.
    88  	contentRendered        bool // Set on content render.
    89  
    90  	// Renders Markdown hooks.
    91  	renderHooks *renderHooks
    92  }
    93  
    94  func (pco *pageContentOutput) trackDependency(idp identity.IdentityProvider) {
    95  	pco.po.p.dependencyManagerOutput.AddIdentity(idp.GetIdentity())
    96  }
    97  
    98  func (pco *pageContentOutput) Reset() {
    99  	if pco == nil {
   100  		return
   101  	}
   102  	pco.contentRenderedVersion++
   103  	pco.contentRendered = false
   104  	pco.renderHooks = &renderHooks{}
   105  }
   106  
   107  func (pco *pageContentOutput) Fragments(ctx context.Context) *tableofcontents.Fragments {
   108  	return pco.po.p.m.content.mustContentToC(ctx, pco).tableOfContents
   109  }
   110  
   111  func (pco *pageContentOutput) RenderShortcodes(ctx context.Context) (template.HTML, error) {
   112  	content := pco.po.p.m.content
   113  	source, err := content.pi.contentSource(content)
   114  	if err != nil {
   115  		return "", err
   116  	}
   117  	ct, err := content.contentToC(ctx, pco)
   118  	if err != nil {
   119  		return "", err
   120  	}
   121  
   122  	var insertPlaceholders bool
   123  	var hasVariants bool
   124  	cb := setGetContentCallbackInContext.Get(ctx)
   125  	if cb != nil {
   126  		insertPlaceholders = true
   127  	}
   128  	c := make([]byte, 0, len(source)+(len(source)/10))
   129  	for _, it := range content.pi.itemsStep2 {
   130  		switch v := it.(type) {
   131  		case pageparser.Item:
   132  			c = append(c, source[v.Pos():v.Pos()+len(v.Val(source))]...)
   133  		case pageContentReplacement:
   134  			// Ignore.
   135  		case *shortcode:
   136  			if !insertPlaceholders || !v.insertPlaceholder() {
   137  				// Insert the rendered shortcode.
   138  				renderedShortcode, found := ct.contentPlaceholders[v.placeholder]
   139  				if !found {
   140  					// This should never happen.
   141  					panic(fmt.Sprintf("rendered shortcode %q not found", v.placeholder))
   142  				}
   143  
   144  				b, more, err := renderedShortcode.renderShortcode(ctx)
   145  				if err != nil {
   146  					return "", fmt.Errorf("failed to render shortcode: %w", err)
   147  				}
   148  				hasVariants = hasVariants || more
   149  				c = append(c, []byte(b)...)
   150  
   151  			} else {
   152  				// Insert the placeholder so we can insert the content after
   153  				// markdown processing.
   154  				c = append(c, []byte(v.placeholder)...)
   155  			}
   156  		default:
   157  			panic(fmt.Sprintf("unknown item type %T", it))
   158  		}
   159  	}
   160  
   161  	if hasVariants {
   162  		pco.po.p.pageOutputTemplateVariationsState.Add(1)
   163  	}
   164  
   165  	if cb != nil {
   166  		cb(pco, ct)
   167  	}
   168  
   169  	return helpers.BytesToHTML(c), nil
   170  }
   171  
   172  func (pco *pageContentOutput) Content(ctx context.Context) (any, error) {
   173  	r, err := pco.po.p.m.content.contentRendered(ctx, pco)
   174  	return r.content, err
   175  }
   176  
   177  func (pco *pageContentOutput) TableOfContents(ctx context.Context) template.HTML {
   178  	return pco.po.p.m.content.mustContentToC(ctx, pco).tableOfContentsHTML
   179  }
   180  
   181  func (p *pageContentOutput) Len(ctx context.Context) int {
   182  	return len(p.mustContentRendered(ctx).content)
   183  }
   184  
   185  func (pco *pageContentOutput) mustContentRendered(ctx context.Context) contentSummary {
   186  	r, err := pco.po.p.m.content.contentRendered(ctx, pco)
   187  	if err != nil {
   188  		pco.fail(err)
   189  	}
   190  	return r
   191  }
   192  
   193  func (pco *pageContentOutput) mustContentPlain(ctx context.Context) contentPlainPlainWords {
   194  	r, err := pco.po.p.m.content.contentPlain(ctx, pco)
   195  	if err != nil {
   196  		pco.fail(err)
   197  	}
   198  	return r
   199  }
   200  
   201  func (pco *pageContentOutput) fail(err error) {
   202  	pco.po.p.s.h.FatalError(pco.po.p.wrapError(err))
   203  }
   204  
   205  func (pco *pageContentOutput) Plain(ctx context.Context) string {
   206  	return pco.mustContentPlain(ctx).plain
   207  }
   208  
   209  func (pco *pageContentOutput) PlainWords(ctx context.Context) []string {
   210  	return pco.mustContentPlain(ctx).plainWords
   211  }
   212  
   213  func (pco *pageContentOutput) ReadingTime(ctx context.Context) int {
   214  	return pco.mustContentPlain(ctx).readingTime
   215  }
   216  
   217  func (pco *pageContentOutput) WordCount(ctx context.Context) int {
   218  	return pco.mustContentPlain(ctx).wordCount
   219  }
   220  
   221  func (pco *pageContentOutput) FuzzyWordCount(ctx context.Context) int {
   222  	return pco.mustContentPlain(ctx).fuzzyWordCount
   223  }
   224  
   225  func (pco *pageContentOutput) Summary(ctx context.Context) template.HTML {
   226  	return pco.mustContentPlain(ctx).summary
   227  }
   228  
   229  func (pco *pageContentOutput) Truncated(ctx context.Context) bool {
   230  	return pco.mustContentPlain(ctx).summaryTruncated
   231  }
   232  
   233  func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (template.HTML, error) {
   234  	if len(args) < 1 || len(args) > 2 {
   235  		return "", errors.New("want 1 or 2 arguments")
   236  	}
   237  
   238  	var contentToRender string
   239  	opts := defaultRenderStringOpts
   240  	sidx := 1
   241  
   242  	if len(args) == 1 {
   243  		sidx = 0
   244  	} else {
   245  		m, ok := args[0].(map[string]any)
   246  		if !ok {
   247  			return "", errors.New("first argument must be a map")
   248  		}
   249  
   250  		if err := mapstructure.WeakDecode(m, &opts); err != nil {
   251  			return "", fmt.Errorf("failed to decode options: %w", err)
   252  		}
   253  	}
   254  
   255  	contentToRenderv := args[sidx]
   256  
   257  	if _, ok := contentToRenderv.(hstring.RenderedString); ok {
   258  		// This content is already rendered, this is potentially
   259  		// a infinite recursion.
   260  		return "", errors.New("text is already rendered, repeating it may cause infinite recursion")
   261  	}
   262  
   263  	var err error
   264  	contentToRender, err = cast.ToStringE(contentToRenderv)
   265  	if err != nil {
   266  		return "", err
   267  	}
   268  
   269  	if err = pco.initRenderHooks(); err != nil {
   270  		return "", err
   271  	}
   272  
   273  	conv := pco.po.p.getContentConverter()
   274  	if opts.Markup != "" && opts.Markup != pco.po.p.m.pageConfig.Markup {
   275  		var err error
   276  		conv, err = pco.po.p.m.newContentConverter(pco.po.p, opts.Markup)
   277  		if err != nil {
   278  			return "", pco.po.p.wrapError(err)
   279  		}
   280  	}
   281  
   282  	var rendered []byte
   283  
   284  	parseInfo := &contentParseInfo{
   285  		h:   pco.po.p.s.h,
   286  		pid: pco.po.p.pid,
   287  	}
   288  
   289  	if pageparser.HasShortcode(contentToRender) {
   290  		contentToRenderb := []byte(contentToRender)
   291  		// String contains a shortcode.
   292  		parseInfo.itemsStep1, err = pageparser.ParseBytesMain(contentToRenderb, pageparser.Config{})
   293  		if err != nil {
   294  			return "", err
   295  		}
   296  
   297  		s := newShortcodeHandler(pco.po.p.pathOrTitle(), pco.po.p.s)
   298  		if err := parseInfo.mapItemsAfterFrontMatter(contentToRenderb, s); err != nil {
   299  			return "", err
   300  		}
   301  
   302  		placeholders, err := s.prepareShortcodesForPage(ctx, pco.po.p, pco.po.f, true)
   303  		if err != nil {
   304  			return "", err
   305  		}
   306  
   307  		contentToRender, hasVariants, err := parseInfo.contentToRender(ctx, contentToRenderb, placeholders)
   308  		if err != nil {
   309  			return "", err
   310  		}
   311  		if hasVariants {
   312  			pco.po.p.pageOutputTemplateVariationsState.Add(1)
   313  		}
   314  		b, err := pco.renderContentWithConverter(ctx, conv, contentToRender, false)
   315  		if err != nil {
   316  			return "", pco.po.p.wrapError(err)
   317  		}
   318  		rendered = b.Bytes()
   319  
   320  		if parseInfo.hasNonMarkdownShortcode {
   321  			var hasShortcodeVariants bool
   322  
   323  			tokenHandler := func(ctx context.Context, token string) ([]byte, error) {
   324  				if token == tocShortcodePlaceholder {
   325  					toc, err := pco.po.p.m.content.contentToC(ctx, pco)
   326  					if err != nil {
   327  						return nil, err
   328  					}
   329  					// The Page's TableOfContents was accessed in a shortcode.
   330  					return []byte(toc.tableOfContentsHTML), nil
   331  				}
   332  				renderer, found := placeholders[token]
   333  				if found {
   334  					repl, more, err := renderer.renderShortcode(ctx)
   335  					if err != nil {
   336  						return nil, err
   337  					}
   338  					hasShortcodeVariants = hasShortcodeVariants || more
   339  					return repl, nil
   340  				}
   341  				// This should not happen.
   342  				return nil, fmt.Errorf("unknown shortcode token %q", token)
   343  			}
   344  
   345  			rendered, err = expandShortcodeTokens(ctx, rendered, tokenHandler)
   346  			if err != nil {
   347  				return "", err
   348  			}
   349  			if hasShortcodeVariants {
   350  				pco.po.p.pageOutputTemplateVariationsState.Add(1)
   351  			}
   352  		}
   353  
   354  		// We need a consolidated view in $page.HasShortcode
   355  		pco.po.p.m.content.shortcodeState.transferNames(s)
   356  
   357  	} else {
   358  		c, err := pco.renderContentWithConverter(ctx, conv, []byte(contentToRender), false)
   359  		if err != nil {
   360  			return "", pco.po.p.wrapError(err)
   361  		}
   362  
   363  		rendered = c.Bytes()
   364  	}
   365  
   366  	if opts.Display == "inline" {
   367  		// We may have to rethink this in the future when we get other
   368  		// renderers.
   369  		rendered = pco.po.p.s.ContentSpec.TrimShortHTML(rendered)
   370  	}
   371  
   372  	return template.HTML(string(rendered)), nil
   373  }
   374  
   375  func (pco *pageContentOutput) Render(ctx context.Context, layout ...string) (template.HTML, error) {
   376  	if len(layout) == 0 {
   377  		return "", errors.New("no layout given")
   378  	}
   379  	templ, found, err := pco.po.p.resolveTemplate(layout...)
   380  	if err != nil {
   381  		return "", pco.po.p.wrapError(err)
   382  	}
   383  
   384  	if !found {
   385  		return "", nil
   386  	}
   387  
   388  	// Make sure to send the *pageState and not the *pageContentOutput to the template.
   389  	res, err := executeToString(ctx, pco.po.p.s.Tmpl(), templ, pco.po.p)
   390  	if err != nil {
   391  		return "", pco.po.p.wrapError(fmt.Errorf("failed to execute template %s: %w", templ.Name(), err))
   392  	}
   393  	return template.HTML(res), nil
   394  }
   395  
   396  func (pco *pageContentOutput) initRenderHooks() error {
   397  	if pco == nil {
   398  		return nil
   399  	}
   400  
   401  	pco.renderHooks.init.Do(func() {
   402  		if pco.po.p.pageOutputTemplateVariationsState.Load() == 0 {
   403  			pco.po.p.pageOutputTemplateVariationsState.Store(1)
   404  		}
   405  
   406  		type cacheKey struct {
   407  			tp hooks.RendererType
   408  			id any
   409  			f  output.Format
   410  		}
   411  
   412  		renderCache := make(map[cacheKey]any)
   413  		var renderCacheMu sync.Mutex
   414  
   415  		resolvePosition := func(ctx any) text.Position {
   416  			source := pco.po.p.m.content.mustSource()
   417  			var offset int
   418  
   419  			switch v := ctx.(type) {
   420  			case hooks.CodeblockContext:
   421  				offset = bytes.Index(source, []byte(v.Inner()))
   422  			}
   423  
   424  			pos := pco.po.p.posFromInput(source, offset)
   425  
   426  			if pos.LineNumber > 0 {
   427  				// Move up to the code fence delimiter.
   428  				// This is in line with how we report on shortcodes.
   429  				pos.LineNumber = pos.LineNumber - 1
   430  			}
   431  
   432  			return pos
   433  		}
   434  
   435  		pco.renderHooks.getRenderer = func(tp hooks.RendererType, id any) any {
   436  			renderCacheMu.Lock()
   437  			defer renderCacheMu.Unlock()
   438  
   439  			key := cacheKey{tp: tp, id: id, f: pco.po.f}
   440  			if r, ok := renderCache[key]; ok {
   441  				return r
   442  			}
   443  
   444  			layoutDescriptor := pco.po.p.getLayoutDescriptor()
   445  			layoutDescriptor.RenderingHook = true
   446  			layoutDescriptor.LayoutOverride = false
   447  			layoutDescriptor.Layout = ""
   448  
   449  			switch tp {
   450  			case hooks.LinkRendererType:
   451  				layoutDescriptor.Kind = "render-link"
   452  			case hooks.ImageRendererType:
   453  				layoutDescriptor.Kind = "render-image"
   454  			case hooks.HeadingRendererType:
   455  				layoutDescriptor.Kind = "render-heading"
   456  			case hooks.CodeBlockRendererType:
   457  				layoutDescriptor.Kind = "render-codeblock"
   458  				if id != nil {
   459  					lang := id.(string)
   460  					lexer := chromalexers.Get(lang)
   461  					if lexer != nil {
   462  						layoutDescriptor.KindVariants = strings.Join(lexer.Config().Aliases, ",")
   463  					} else {
   464  						layoutDescriptor.KindVariants = lang
   465  					}
   466  				}
   467  			}
   468  
   469  			getHookTemplate := func(f output.Format) (tpl.Template, bool) {
   470  				templ, found, err := pco.po.p.s.Tmpl().LookupLayout(layoutDescriptor, f)
   471  				if err != nil {
   472  					panic(err)
   473  				}
   474  				if found {
   475  					if isitp, ok := templ.(tpl.IsInternalTemplateProvider); ok && isitp.IsInternalTemplate() {
   476  						renderHookConfig := pco.po.p.s.conf.Markup.Goldmark.RenderHooks
   477  						switch templ.Name() {
   478  						case "_default/_markup/render-link.html":
   479  							if !renderHookConfig.Link.IsEnableDefault() {
   480  								return nil, false
   481  							}
   482  						case "_default/_markup/render-image.html":
   483  							if !renderHookConfig.Image.IsEnableDefault() {
   484  								return nil, false
   485  							}
   486  						}
   487  					}
   488  				}
   489  				return templ, found
   490  			}
   491  
   492  			templ, found1 := getHookTemplate(pco.po.f)
   493  
   494  			if pco.po.p.reusePageOutputContent() {
   495  				// Check if some of the other output formats would give a different template.
   496  				for _, f := range pco.po.p.s.renderFormats {
   497  					if f.Name == pco.po.f.Name {
   498  						continue
   499  					}
   500  					templ2, found2 := getHookTemplate(f)
   501  					if found2 {
   502  						if !found1 {
   503  							templ = templ2
   504  							found1 = true
   505  							break
   506  						}
   507  
   508  						if templ != templ2 {
   509  							pco.po.p.pageOutputTemplateVariationsState.Add(1)
   510  							break
   511  						}
   512  					}
   513  				}
   514  			}
   515  			if !found1 {
   516  				if tp == hooks.CodeBlockRendererType {
   517  					// No user provided template for code blocks, so we use the native Go version -- which is also faster.
   518  					r := pco.po.p.s.ContentSpec.Converters.GetHighlighter()
   519  					renderCache[key] = r
   520  					return r
   521  				}
   522  				return nil
   523  			}
   524  
   525  			r := hookRendererTemplate{
   526  				templateHandler: pco.po.p.s.Tmpl(),
   527  				templ:           templ,
   528  				resolvePosition: resolvePosition,
   529  			}
   530  			renderCache[key] = r
   531  			return r
   532  		}
   533  	})
   534  
   535  	return nil
   536  }
   537  
   538  func (pco *pageContentOutput) getContentConverter() (converter.Converter, error) {
   539  	if err := pco.initRenderHooks(); err != nil {
   540  		return nil, err
   541  	}
   542  	return pco.po.p.getContentConverter(), nil
   543  }
   544  
   545  func (cp *pageContentOutput) ParseAndRenderContent(ctx context.Context, content []byte, renderTOC bool) (converter.ResultRender, error) {
   546  	c, err := cp.getContentConverter()
   547  	if err != nil {
   548  		return nil, err
   549  	}
   550  	return cp.renderContentWithConverter(ctx, c, content, renderTOC)
   551  }
   552  
   553  func (pco *pageContentOutput) ParseContent(ctx context.Context, content []byte) (converter.ResultParse, bool, error) {
   554  	c, err := pco.getContentConverter()
   555  	if err != nil {
   556  		return nil, false, err
   557  	}
   558  	p, ok := c.(converter.ParseRenderer)
   559  	if !ok {
   560  		return nil, ok, nil
   561  	}
   562  	rctx := converter.RenderContext{
   563  		Ctx:         ctx,
   564  		Src:         content,
   565  		RenderTOC:   true,
   566  		GetRenderer: pco.renderHooks.getRenderer,
   567  	}
   568  	r, err := p.Parse(rctx)
   569  	return r, ok, err
   570  }
   571  
   572  func (pco *pageContentOutput) RenderContent(ctx context.Context, content []byte, doc any) (converter.ResultRender, bool, error) {
   573  	c, err := pco.getContentConverter()
   574  	if err != nil {
   575  		return nil, false, err
   576  	}
   577  	p, ok := c.(converter.ParseRenderer)
   578  	if !ok {
   579  		return nil, ok, nil
   580  	}
   581  	rctx := converter.RenderContext{
   582  		Ctx:         ctx,
   583  		Src:         content,
   584  		RenderTOC:   true,
   585  		GetRenderer: pco.renderHooks.getRenderer,
   586  	}
   587  	r, err := p.Render(rctx, doc)
   588  	return r, ok, err
   589  }
   590  
   591  func (pco *pageContentOutput) renderContentWithConverter(ctx context.Context, c converter.Converter, content []byte, renderTOC bool) (converter.ResultRender, error) {
   592  	r, err := c.Convert(
   593  		converter.RenderContext{
   594  			Ctx:         ctx,
   595  			Src:         content,
   596  			RenderTOC:   renderTOC,
   597  			GetRenderer: pco.renderHooks.getRenderer,
   598  		})
   599  	return r, err
   600  }
   601  
   602  // these will be shifted out when rendering a given output format.
   603  type pagePerOutputProviders interface {
   604  	targetPather
   605  	page.PaginatorProvider
   606  	resource.ResourceLinksProvider
   607  }
   608  
   609  type targetPather interface {
   610  	targetPaths() page.TargetPaths
   611  }
   612  
   613  type targetPathsHolder struct {
   614  	paths page.TargetPaths
   615  	page.OutputFormat
   616  }
   617  
   618  func (t targetPathsHolder) targetPaths() page.TargetPaths {
   619  	return t.paths
   620  }
   621  
   622  func executeToString(ctx context.Context, h tpl.TemplateHandler, templ tpl.Template, data any) (string, error) {
   623  	b := bp.GetBuffer()
   624  	defer bp.PutBuffer(b)
   625  	if err := h.ExecuteWithContext(ctx, templ, b, data); err != nil {
   626  		return "", err
   627  	}
   628  	return b.String(), nil
   629  }
   630  
   631  func splitUserDefinedSummaryAndContent(markup string, c []byte) (summary []byte, content []byte, err error) {
   632  	defer func() {
   633  		if r := recover(); r != nil {
   634  			err = fmt.Errorf("summary split failed: %s", r)
   635  		}
   636  	}()
   637  
   638  	startDivider := bytes.Index(c, internalSummaryDividerBaseBytes)
   639  
   640  	if startDivider == -1 {
   641  		return
   642  	}
   643  
   644  	startTag := "p"
   645  	switch markup {
   646  	case "asciidocext":
   647  		startTag = "div"
   648  	}
   649  
   650  	// Walk back and forward to the surrounding tags.
   651  	start := bytes.LastIndex(c[:startDivider], []byte("<"+startTag))
   652  	end := bytes.Index(c[startDivider:], []byte("</"+startTag))
   653  
   654  	if start == -1 {
   655  		start = startDivider
   656  	} else {
   657  		start = startDivider - (startDivider - start)
   658  	}
   659  
   660  	if end == -1 {
   661  		end = startDivider + len(internalSummaryDividerBase)
   662  	} else {
   663  		end = startDivider + end + len(startTag) + 3
   664  	}
   665  
   666  	var addDiv bool
   667  
   668  	switch markup {
   669  	case "rst":
   670  		addDiv = true
   671  	}
   672  
   673  	withoutDivider := append(c[:start], bytes.Trim(c[end:], "\n")...)
   674  
   675  	if len(withoutDivider) > 0 {
   676  		summary = bytes.TrimSpace(withoutDivider[:start])
   677  	}
   678  
   679  	if addDiv {
   680  		// For the rst
   681  		summary = append(append([]byte(nil), summary...), []byte("</div>")...)
   682  	}
   683  
   684  	if err != nil {
   685  		return
   686  	}
   687  
   688  	content = bytes.TrimSpace(withoutDivider)
   689  
   690  	return
   691  }