diff options
Diffstat (limited to 'markup/asciidocext/convert.go')
-rw-r--r-- | markup/asciidocext/convert.go | 129 |
1 files changed, 127 insertions, 2 deletions
diff --git a/markup/asciidocext/convert.go b/markup/asciidocext/convert.go index 7dd3e3533..73ed85daa 100644 --- a/markup/asciidocext/convert.go +++ b/markup/asciidocext/convert.go @@ -17,6 +17,7 @@ package asciidocext import ( + "bytes" "os/exec" "path/filepath" @@ -24,6 +25,8 @@ import ( "github.com/gohugoio/hugo/markup/asciidocext/asciidocext_config" "github.com/gohugoio/hugo/markup/converter" "github.com/gohugoio/hugo/markup/internal" + "github.com/gohugoio/hugo/markup/tableofcontents" + "golang.org/x/net/html" ) /* ToDo: RelPermalink patch for svg posts not working*/ @@ -45,16 +48,32 @@ func (p provider) New(cfg converter.ProviderConfig) (converter.Provider, error) }), nil } +type asciidocResult struct { + converter.Result + toc tableofcontents.Root +} + +func (r asciidocResult) TableOfContents() tableofcontents.Root { + return r.toc +} + type asciidocConverter struct { ctx converter.DocumentContext cfg converter.ProviderConfig } func (a *asciidocConverter) Convert(ctx converter.RenderContext) (converter.Result, error) { - return converter.Bytes(a.getAsciidocContent(ctx.Src, a.ctx)), nil + content, toc, err := extractTOC(a.getAsciidocContent(ctx.Src, a.ctx)) + if err != nil { + return nil, err + } + return asciidocResult{ + Result: converter.Bytes(content), + toc: toc, + }, nil } -func (c *asciidocConverter) Supports(feature identity.Identity) bool { +func (a *asciidocConverter) Supports(_ identity.Identity) bool { return false } @@ -182,6 +201,112 @@ func getAsciidoctorExecPath() string { return path } +// extractTOC extracts the toc from the given src html. +// It returns the html without the TOC, and the TOC data +func extractTOC(src []byte) ([]byte, tableofcontents.Root, error) { + var buf bytes.Buffer + buf.Write(src) + node, err := html.Parse(&buf) + if err != nil { + return nil, tableofcontents.Root{}, err + } + var ( + f func(*html.Node) bool + toc tableofcontents.Root + toVisit []*html.Node + ) + f = func(n *html.Node) bool { + if n.Type == html.ElementNode && n.Data == "div" { + for _, a := range n.Attr { + if a.Key == "id" && a.Val == "toc" { + toc, err = parseTOC(n) + if err != nil { + return false + } + n.Parent.RemoveChild(n) + return true + } + } + } + if n.FirstChild != nil { + toVisit = append(toVisit, n.FirstChild) + } + if n.NextSibling != nil { + if ok := f(n.NextSibling); ok { + return true + } + } + for len(toVisit) > 0 { + nv := toVisit[0] + toVisit = toVisit[1:] + if ok := f(nv); ok { + return true + } + } + return false + } + f(node) + if err != nil { + return nil, tableofcontents.Root{}, err + } + buf.Reset() + err = html.Render(&buf, node) + if err != nil { + return nil, tableofcontents.Root{}, err + } + // ltrim <html><head></head><body> and rtrim </body></html> which are added by html.Render + res := buf.Bytes()[25:] + res = res[:len(res)-14] + return res, toc, nil +} + +// parseTOC returns a TOC root from the given toc Node +func parseTOC(doc *html.Node) (tableofcontents.Root, error) { + var ( + toc tableofcontents.Root + f func(*html.Node, int, int) + ) + f = func(n *html.Node, parent, level int) { + if n.Type == html.ElementNode { + switch n.Data { + case "ul": + if level == 0 { + parent += 1 + } + level += 1 + f(n.FirstChild, parent, level) + case "li": + for c := n.FirstChild; c != nil; c = c.NextSibling { + if c.Type != html.ElementNode || c.Data != "a" { + continue + } + var href string + for _, a := range c.Attr { + if a.Key == "href" { + href = a.Val[1:] + break + } + } + for d := c.FirstChild; d != nil; d = d.NextSibling { + if d.Type == html.TextNode { + toc.AddAt(tableofcontents.Header{ + Text: d.Data, + ID: href, + }, parent, level) + } + } + } + f(n.FirstChild, parent, level) + } + } + if n.NextSibling != nil { + f(n.NextSibling, parent, level) + } + } + f(doc.FirstChild, 0, 0) + return toc, nil +} + // Supports returns whether Asciidoctor is installed on this computer. func Supports() bool { return getAsciidoctorExecPath() != "" |