Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/gohugoio/hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'markup/asciidocext')
-rw-r--r--markup/asciidocext/convert.go129
-rw-r--r--markup/asciidocext/convert_test.go35
2 files changed, 162 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() != ""
diff --git a/markup/asciidocext/convert_test.go b/markup/asciidocext/convert_test.go
index 22ac8e557..1c4b77bb9 100644
--- a/markup/asciidocext/convert_test.go
+++ b/markup/asciidocext/convert_test.go
@@ -270,3 +270,38 @@ func TestConvert(t *testing.T) {
c.Assert(err, qt.IsNil)
c.Assert(string(b.Bytes()), qt.Equals, "<div class=\"paragraph\">\n<p>testContent</p>\n</div>\n")
}
+
+func TestTableOfContents(t *testing.T) {
+ if !Supports() {
+ t.Skip("asciidoc/asciidoctor not installed")
+ }
+ c := qt.New(t)
+ p, err := Provider.New(converter.ProviderConfig{Logger: loggers.NewErrorLogger()})
+ c.Assert(err, qt.IsNil)
+ conv, err := p.New(converter.DocumentContext{})
+ c.Assert(err, qt.IsNil)
+ b, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: macro
+:toclevels: 4
+toc::[]
+
+=== Introduction
+
+== Section 1
+
+=== Section 1.1
+
+==== Section 1.1.1
+
+=== Section 1.2
+
+testContent
+
+== Section 2
+`)})
+ c.Assert(err, qt.IsNil)
+ toc, ok := b.(converter.TableOfContentsProvider)
+ c.Assert(ok, qt.Equals, true)
+ root := toc.TableOfContents()
+ c.Assert(root.ToHTML(2, 4, false), qt.Equals, "<nav id=\"TableOfContents\">\n <ul>\n <li><a href=\"#_introduction\">Introduction</a></li>\n <li><a href=\"#_section_1\">Section 1</a>\n <ul>\n <li><a href=\"#_section_1_1\">Section 1.1</a>\n <ul>\n <li><a href=\"#_section_1_1_1\">Section 1.1.1</a></li>\n </ul>\n </li>\n <li><a href=\"#_section_1_2\">Section 1.2</a></li>\n </ul>\n </li>\n <li><a href=\"#_section_2\">Section 2</a></li>\n </ul>\n</nav>")
+ c.Assert(root.ToHTML(2, 3, false), qt.Equals, "<nav id=\"TableOfContents\">\n <ul>\n <li><a href=\"#_introduction\">Introduction</a></li>\n <li><a href=\"#_section_1\">Section 1</a>\n <ul>\n <li><a href=\"#_section_1_1\">Section 1.1</a></li>\n <li><a href=\"#_section_1_2\">Section 1.2</a></li>\n </ul>\n </li>\n <li><a href=\"#_section_2\">Section 2</a></li>\n </ul>\n</nav>")
+}