aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <m.shulhan@gmail.com>2020-10-31 04:01:41 +0700
committerShulhan <m.shulhan@gmail.com>2020-10-31 04:01:41 +0700
commit45e1e54c75bda5591cf02ea8bc899a8150b3dcfc (patch)
tree2119c2076af4dc4d738064946a3e7995538218bc
parentee78188ea91964725f58cc3d8c472cc9ab2f5c36 (diff)
downloadasciidoctor-go-45e1e54c75bda5591cf02ea8bc899a8150b3dcfc.tar.xz
all: implement parser for block anchor and inline anchor
-rw-r--r--SPECS.adoc25
-rw-r--r--adoc_node.go164
-rw-r--r--adoc_node_test.go4
-rw-r--r--document.go135
-rw-r--r--html_backend.go27
-rw-r--r--html_template.go112
-rw-r--r--parser.go85
-rw-r--r--parser_inline.go102
-rw-r--r--parser_inline_test.go131
-rw-r--r--parser_test.go17
-rw-r--r--testdata/got.test.html48
-rw-r--r--testdata/test.adoc24
-rw-r--r--testdata/test.html51
13 files changed, 786 insertions, 139 deletions
diff --git a/SPECS.adoc b/SPECS.adoc
index 1ee6cc9..46002c4 100644
--- a/SPECS.adoc
+++ b/SPECS.adoc
@@ -50,6 +50,8 @@ STRING = WORD *(WSP WORD) ; Sequence of word with spaces between them.
LINE = STRING LF ; STRING that end with new line.
TEXT = 1*LINE ; One or more LINE.
+
+REF_ID = 1*ALPHA *("-" / "_" / ALPHA / DIGIT)
----
@@ -397,6 +399,20 @@ URL_ATTR_TARGET = "window" "=" "_blank"
URL_ATTR_RILE = "role=" WORD *("," WORD)
----
+
+== Anchor
+
+----
+ANCHOR_LINE = "[[" REF_ID "]]" LF
+
+ANCHOR_LINE_SHORT = "[#" REF_ID "]" LF
+
+ANCHOR_INLINE = "[[" REF_ID "]]" TEXT
+
+ANCHOR_INLINE_SHORT = "[#" REF_ID "]#" TEXT "#" FORMAT_END.
+----
+
+
== Inconsistencies and bugs on asciidoctor
Listing style "[listing]" followed by "...." is become listing block.
@@ -413,3 +429,12 @@ Example,
----
image::sunset.jpg[Text,a,b]
----
+
+Link with "https" end with '.' works, but "mailto" end with '.' is not
+working.
+Example,
+----
+https://asciidoctor.org.
+
+mailto:me@example.com.
+----
diff --git a/adoc_node.go b/adoc_node.go
index d8bc8ac..2118bcf 100644
--- a/adoc_node.go
+++ b/adoc_node.go
@@ -50,7 +50,7 @@ func (node *adocNode) Classes() string {
if len(node.classes) == 0 {
return ""
}
- return " " + strings.Join(node.classes, " ")
+ return strings.Join(node.classes, " ")
}
func (node *adocNode) Content() string {
@@ -273,6 +273,15 @@ func (node *adocNode) addChild(child *adocNode) {
}
}
+func (node *adocNode) addNext(next *adocNode) {
+ if next == nil {
+ return
+ }
+ next.parent = node.parent
+ next.prev = node
+ node.next = next
+}
+
func (node *adocNode) applySubstitutions() {
if len(node.rawTitle) > 0 {
node.rawTitle = htmlSubstituteSpecialChars(node.rawTitle)
@@ -303,6 +312,20 @@ func (node *adocNode) debug(n int) {
}
}
+func (node *adocNode) lastSuccessor() (last *adocNode) {
+ if node.child == nil {
+ return nil
+ }
+ last = node
+ for last.child != nil {
+ last = last.child
+ for last.next != nil {
+ last = last.next
+ }
+ }
+ return last
+}
+
func (node *adocNode) parseBlockAudio(line string) bool {
attrBegin := strings.IndexByte(line, '[')
if attrBegin < 0 {
@@ -416,8 +439,15 @@ func (node *adocNode) parseImage(line string) bool {
return true
}
-func (node *adocNode) parseInlineMarkup() {
- container := parseInlineMarkup(node.raw)
+func (node *adocNode) parseInlineMarkup(doc *Document, kind int) {
+ if len(node.raw) == 0 {
+ return
+ }
+
+ container := parseInlineMarkup(doc, node.raw)
+ if kind != 0 {
+ container.kind = kind
+ }
container.parent = node
container.next = node.child
if node.child != nil {
@@ -438,7 +468,7 @@ func (node *adocNode) parseLineAdmonition(line string) {
node.WriteByte('\n')
}
-func (node *adocNode) parseListDescription(line string) {
+func (node *adocNode) parseListDescriptionItem(line string) {
var (
x int
c rune
@@ -475,7 +505,7 @@ func (node *adocNode) parseListDescription(line string) {
}
}
-func (node *adocNode) parseListOrdered(line string) {
+func (node *adocNode) parseListOrderedItem(line string) {
x := 0
for ; x < len(line); x++ {
if line[x] == '.' {
@@ -496,7 +526,7 @@ func (node *adocNode) parseListOrdered(line string) {
node.WriteByte('\n')
}
-func (node *adocNode) parseListUnordered(line string) {
+func (node *adocNode) parseListUnorderedItem(line string) {
x := 0
for ; x < len(line); x++ {
if line[x] == '*' {
@@ -517,11 +547,31 @@ func (node *adocNode) parseListUnordered(line string) {
node.WriteByte('\n')
}
-func (node *adocNode) parseSection() {
- node.ID = generateID(string(node.raw))
+func (node *adocNode) parseSection(doc *Document) {
node.level = (node.kind - nodeKindSectionL1) + 1
- container := parseInlineMarkup(node.raw)
+ container := parseInlineMarkup(doc, node.raw)
+
+ container.debug(0)
+
+ if len(node.ID) == 0 {
+ lastChild := container.lastSuccessor()
+ if lastChild != nil && lastChild.kind == nodeKindInlineID {
+ node.ID = lastChild.ID
+
+ // Delete last child
+ if lastChild.prev != nil {
+ p := lastChild.prev
+ p.next = nil
+ } else if lastChild.parent != nil {
+ p := lastChild.parent
+ p.child = nil
+ }
+ lastChild.prev = nil
+ lastChild.parent = nil
+ }
+ }
+
container.parent = node
node.title = container
node.raw = nil
@@ -532,6 +582,11 @@ func (node *adocNode) parseSection() {
log.Fatalf("parseSection: " + err.Error())
}
node.Text = text.String()
+
+ if len(node.ID) == 0 {
+ node.ID = generateID(node.Text)
+ doc.registerAnchor(node.ID, node.Text)
+ }
}
func (node *adocNode) parseStyleClass(line string) {
@@ -595,6 +650,16 @@ func (node *adocNode) parseVideo(line string) bool {
return true
}
+func (node *adocNode) postParseList(doc *Document, kind int) {
+ item := node.child
+ for item != nil {
+ if item.kind == kind {
+ item.parseInlineMarkup(doc, nodeKindInlineParagraph)
+ }
+ item = item.next
+ }
+}
+
//
// postParseParagraph check if paragraph is a blockquote based on the first
// character of the first line ('"'), the last character of last second line
@@ -669,6 +734,32 @@ func (node *adocNode) postParseParagraphAsQuote(lines [][]byte) bool {
return true
}
+func (node *adocNode) removeLastIfEmpty() {
+ if node.child == nil {
+ return
+ }
+ c := node
+ for c.child != nil {
+ c = c.child
+ for c.next != nil {
+ c = c.next
+ }
+ }
+ if c.kind != nodeKindText || len(c.raw) > 0 {
+ return
+ }
+ if c.prev != nil {
+ c.prev.next = nil
+ if c.prev.kind == nodeKindText {
+ node.raw = bytes.TrimRight(node.raw, " \t")
+ }
+ } else if c.parent != nil {
+ c.parent.child = nil
+ }
+ c.prev = nil
+ c.parent = nil
+}
+
func (node *adocNode) setQuoteOpts(opts []string) {
if len(opts) >= 1 {
node.key = strings.TrimSpace(opts[0])
@@ -684,7 +775,7 @@ func (node *adocNode) setStyleAdmonition(admName string) {
node.rawLabel.WriteString(strings.Title(admName))
}
-func (node *adocNode) toHTML(doc *Document, tmpl *template.Template, w io.Writer) (err error) {
+func (node *adocNode) toHTML(doc *Document, tmpl *template.Template, w io.Writer, isForToC bool) (err error) {
switch node.kind {
case lineKindAttribute:
doc.attributes[node.key] = node.value
@@ -702,7 +793,7 @@ func (node *adocNode) toHTML(doc *Document, tmpl *template.Template, w io.Writer
if err != nil {
return err
}
- err = node.title.toHTML(doc, tmpl, w)
+ err = node.title.toHTML(doc, tmpl, w, isForToC)
if err != nil {
return err
}
@@ -718,7 +809,7 @@ func (node *adocNode) toHTML(doc *Document, tmpl *template.Template, w io.Writer
if err != nil {
return err
}
- err = node.title.toHTML(doc, tmpl, w)
+ err = node.title.toHTML(doc, tmpl, w, isForToC)
if err != nil {
return err
}
@@ -729,7 +820,7 @@ func (node *adocNode) toHTML(doc *Document, tmpl *template.Template, w io.Writer
return err
}
if node.title != nil {
- err = node.title.toHTML(doc, tmpl, w)
+ err = node.title.toHTML(doc, tmpl, w, isForToC)
}
_, err = w.Write([]byte("</h4>"))
case nodeKindSectionL4:
@@ -738,7 +829,7 @@ func (node *adocNode) toHTML(doc *Document, tmpl *template.Template, w io.Writer
return err
}
if node.title != nil {
- err = node.title.toHTML(doc, tmpl, w)
+ err = node.title.toHTML(doc, tmpl, w, isForToC)
}
_, err = w.Write([]byte("</h5>"))
case nodeKindSectionL5:
@@ -747,7 +838,7 @@ func (node *adocNode) toHTML(doc *Document, tmpl *template.Template, w io.Writer
return err
}
if node.title != nil {
- err = node.title.toHTML(doc, tmpl, w)
+ err = node.title.toHTML(doc, tmpl, w, isForToC)
}
_, err = w.Write([]byte("</h6>"))
case nodeKindParagraph:
@@ -774,8 +865,10 @@ func (node *adocNode) toHTML(doc *Document, tmpl *template.Template, w io.Writer
err = tmpl.ExecuteTemplate(w, "BEGIN_LIST_UNORDERED", node)
case nodeKindListDescription:
err = tmpl.ExecuteTemplate(w, "BEGIN_LIST_DESCRIPTION", node)
+
case nodeKindListOrderedItem, nodeKindListUnorderedItem:
- err = tmpl.ExecuteTemplate(w, "BEGIN_LIST_ITEM", node)
+ _, err = w.Write([]byte("\n<li>"))
+
case nodeKindListDescriptionItem:
err = tmpl.ExecuteTemplate(w, "BEGIN_LIST_DESCRIPTION_ITEM", node)
case lineKindHorizontalRule:
@@ -827,6 +920,28 @@ func (node *adocNode) toHTML(doc *Document, tmpl *template.Template, w io.Writer
err = tmpl.ExecuteTemplate(w, "BLOCK_VIDEO", node)
case nodeKindBlockAudio:
err = tmpl.ExecuteTemplate(w, "BLOCK_AUDIO", node)
+
+ case nodeKindInlineID:
+ if !isForToC {
+ err = tmpl.ExecuteTemplate(w, "INLINE_ID", node)
+ }
+
+ case nodeKindInlineIDShort:
+ if !isForToC {
+ err = tmpl.ExecuteTemplate(w, "BEGIN_INLINE_ID_SHORT", node)
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(node.raw)
+ }
+
+ case nodeKindInlineParagraph:
+ _, err = w.Write([]byte("\n<p>"))
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(node.raw)
+
case nodeKindPassthrough:
_, err = w.Write(node.raw)
case nodeKindPassthroughDouble:
@@ -938,7 +1053,7 @@ func (node *adocNode) toHTML(doc *Document, tmpl *template.Template, w io.Writer
}
if node.child != nil {
- err = node.child.toHTML(doc, tmpl, w)
+ err = node.child.toHTML(doc, tmpl, w, isForToC)
if err != nil {
return err
}
@@ -974,8 +1089,10 @@ func (node *adocNode) toHTML(doc *Document, tmpl *template.Template, w io.Writer
} else {
err = tmpl.ExecuteTemplate(w, "END_PARAGRAPH", node)
}
+
case nodeKindListOrderedItem, nodeKindListUnorderedItem:
- err = tmpl.ExecuteTemplate(w, "END_LIST_ITEM", nil)
+ _, err = w.Write([]byte("\n</li>"))
+
case nodeKindListDescriptionItem:
err = tmpl.ExecuteTemplate(w, "END_LIST_DESCRIPTION_ITEM", node)
case nodeKindListOrdered:
@@ -1008,6 +1125,15 @@ func (node *adocNode) toHTML(doc *Document, tmpl *template.Template, w io.Writer
}
case nodeKindBlockSidebar:
err = tmpl.ExecuteTemplate(w, "END_SIDEBAR", node)
+
+ case nodeKindInlineIDShort:
+ if !isForToC {
+ err = tmpl.ExecuteTemplate(w, "END_INLINE_ID_SHORT", node)
+ }
+
+ case nodeKindInlineParagraph:
+ _, err = w.Write([]byte("</p>"))
+
case nodeKindTextBold, nodeKindUnconstrainedBold:
if node.HasStyle(styleTextBold) {
_, err = fmt.Fprintf(w, "</strong>")
@@ -1028,7 +1154,7 @@ func (node *adocNode) toHTML(doc *Document, tmpl *template.Template, w io.Writer
}
if node.next != nil {
- err = node.next.toHTML(doc, tmpl, w)
+ err = node.next.toHTML(doc, tmpl, w, isForToC)
if err != nil {
return err
}
diff --git a/adoc_node_test.go b/adoc_node_test.go
index a0311a6..174be94 100644
--- a/adoc_node_test.go
+++ b/adoc_node_test.go
@@ -10,7 +10,7 @@ import (
"github.com/shuLhan/share/lib/test"
)
-func TestAdocNode_parseListDescription(t *testing.T) {
+func TestAdocNode_parseListDescriptionItem(t *testing.T) {
cases := []struct {
line string
expLevel int
@@ -24,7 +24,7 @@ func TestAdocNode_parseListDescription(t *testing.T) {
for _, c := range cases {
node := &adocNode{}
- node.parseListDescription(c.line)
+ node.parseListDescriptionItem(c.line)
test.Assert(t, "adocNode.Level", c.expLevel, node.level, true)
test.Assert(t, "adocNode.rawLabel", c.expRawTerm, node.rawLabel.String(), true)
diff --git a/document.go b/document.go
index b3df45f..66bde0a 100644
--- a/document.go
+++ b/document.go
@@ -50,6 +50,10 @@ type Document struct {
tocIsEnabled bool
tocClasses []string
+ // anchors contains mapping between reference ID and its label and
+ // vice versa.
+ anchors map[string]string
+
title *adocNode
header *adocNode
content *adocNode
@@ -78,6 +82,7 @@ func Open(file string) (doc *Document, err error) {
TOCLevel: defTOCLevel,
TOCTitle: defTOCTitle,
attributes: make(map[string]string),
+ anchors: make(map[string]string),
content: &adocNode{
kind: nodeKindDocContent,
},
@@ -103,7 +108,7 @@ func (doc *Document) Parse(content []byte) {
_, _, _ = doc.parseHeader()
- doc.title = parseInlineMarkup([]byte(doc.Title))
+ doc.title = parseInlineMarkup(doc, []byte(doc.Title))
var text bytes.Buffer
err := doc.title.toText(&text)
@@ -157,7 +162,7 @@ func (doc *Document) ToHTML(w io.Writer) (err error) {
if err != nil {
return err
}
- err = doc.title.toHTML(doc, tmpl, w)
+ err = doc.title.toHTML(doc, tmpl, w, false)
if err != nil {
return err
}
@@ -192,13 +197,13 @@ func (doc *Document) ToHTML(w io.Writer) (err error) {
}
if doc.content.child != nil {
- err = doc.content.child.toHTML(doc, tmpl, w)
+ err = doc.content.child.toHTML(doc, tmpl, w, false)
if err != nil {
return err
}
}
if doc.content.next != nil {
- err = doc.content.next.toHTML(doc, tmpl, w)
+ err = doc.content.next.toHTML(doc, tmpl, w, false)
if err != nil {
return err
}
@@ -339,6 +344,63 @@ func (doc *Document) parseBlock(parent *adocNode, term int) {
node = &adocNode{}
line = ""
continue
+
+ case lineKindID:
+ idLabel := line[2 : len(line)-2]
+ id, label := parseIDLabel(idLabel)
+ if len(id) > 0 {
+ node.ID = id
+ doc.registerAnchor(id, label)
+ line = ""
+ continue
+ }
+ node.kind = nodeKindParagraph
+ node.WriteString(line)
+ node.WriteByte('\n')
+ line, _ = doc.consumeLinesUntil(
+ node,
+ lineKindEmpty,
+ []int{
+ term,
+ nodeKindBlockListing,
+ nodeKindBlockListingNamed,
+ nodeKindBlockLiteral,
+ nodeKindBlockLiteralNamed,
+ lineKindListContinue,
+ })
+ node.parseInlineMarkup(doc, nodeKindText)
+ parent.addChild(node)
+ node = &adocNode{}
+ continue
+
+ case lineKindIDShort:
+ id := line[2 : len(line)-1]
+ id, label := parseIDLabel(id)
+ if len(id) > 0 {
+ node.ID = id
+ doc.registerAnchor(id, label)
+ line = ""
+ continue
+ }
+ node.kind = nodeKindParagraph
+ node.WriteString(line)
+ node.WriteByte('\n')
+ line, _ = doc.consumeLinesUntil(
+ node,
+ lineKindEmpty,
+ []int{
+ term,
+ nodeKindBlockListing,
+ nodeKindBlockListingNamed,
+ nodeKindBlockLiteral,
+ nodeKindBlockLiteralNamed,
+ lineKindListContinue,
+ })
+ node.parseInlineMarkup(doc, nodeKindText)
+ parent.addChild(node)
+ node = &adocNode{}
+ continue
+
case lineKindPageBreak:
node.kind = doc.kind
parent.addChild(node)
@@ -370,7 +432,7 @@ func (doc *Document) parseBlock(parent *adocNode, term int) {
nodeKindBlockLiteralNamed,
lineKindListContinue,
})
- node.parseInlineMarkup()
+ node.parseInlineMarkup(doc, nodeKindText)
parent.addChild(node)
node = &adocNode{}
continue
@@ -410,7 +472,7 @@ func (doc *Document) parseBlock(parent *adocNode, term int) {
lineKindListContinue,
})
node.postParseParagraph(parent)
- node.parseInlineMarkup()
+ node.parseInlineMarkup(doc, nodeKindText)
parent.addChild(node)
node = &adocNode{}
continue
@@ -435,7 +497,7 @@ func (doc *Document) parseBlock(parent *adocNode, term int) {
nodeKindBlockLiteralNamed,
lineKindListContinue,
})
- node.parseInlineMarkup()
+ node.parseInlineMarkup(doc, nodeKindText)
parent.addChild(node)
node = new(adocNode)
continue
@@ -458,7 +520,7 @@ func (doc *Document) parseBlock(parent *adocNode, term int) {
nodeKindBlockLiteralNamed,
lineKindListContinue,
})
- node.parseInlineMarkup()
+ node.parseInlineMarkup(doc, nodeKindText)
parent.addChild(node)
node = new(adocNode)
continue
@@ -478,7 +540,7 @@ func (doc *Document) parseBlock(parent *adocNode, term int) {
break
}
}
- node.parseSection()
+ node.parseSection(doc)
parent.addChild(node)
parent = node
node = new(adocNode)
@@ -501,7 +563,7 @@ func (doc *Document) parseBlock(parent *adocNode, term int) {
nodeKindBlockLiteralNamed,
lineKindListContinue,
})
- node.parseInlineMarkup()
+ node.parseInlineMarkup(doc, nodeKindText)
} else {
node.kind = doc.kind
node.classes = append(node.classes, classNameLiteralBlock)
@@ -608,7 +670,7 @@ func (doc *Document) parseBlock(parent *adocNode, term int) {
nodeKindBlockLiteralNamed,
lineKindListContinue,
})
- node.parseInlineMarkup()
+ node.parseInlineMarkup(doc, nodeKindText)
}
parent.addChild(node)
node = &adocNode{}
@@ -664,7 +726,7 @@ func (doc *Document) parseBlock(parent *adocNode, term int) {
nodeKindBlockLiteralNamed,
lineKindListContinue,
})
- node.parseInlineMarkup()
+ node.parseInlineMarkup(doc, nodeKindText)
}
parent.addChild(node)
node = new(adocNode)
@@ -689,7 +751,7 @@ func (doc *Document) parseBlock(parent *adocNode, term int) {
nodeKindBlockLiteralNamed,
lineKindListContinue,
})
- node.parseInlineMarkup()
+ node.parseInlineMarkup(doc, nodeKindText)
}
parent.addChild(node)
node = new(adocNode)
@@ -846,7 +908,7 @@ func (doc *Document) parseListBlock() (node *adocNode, line string, c rune) {
nodeKindBlockLiteralNamed,
lineKindListContinue,
})
- node.parseInlineMarkup()
+ node.parseInlineMarkup(doc, nodeKindText)
break
}
if doc.kind == lineKindBlockComment {
@@ -894,7 +956,7 @@ func (doc *Document) parseListBlock() (node *adocNode, line string, c rune) {
nodeKindListDescriptionItem,
})
node.postParseParagraph(nil)
- node.parseInlineMarkup()
+ node.parseInlineMarkup(doc, nodeKindText)
break
}
if doc.kind == nodeKindBlockListing {
@@ -931,7 +993,7 @@ func (doc *Document) parseListDescription(parent, node *adocNode, line string) (
kind: nodeKindListDescriptionItem,
style: list.style,
}
- listItem.parseListDescription(line)
+ listItem.parseListDescriptionItem(line)
list.level = listItem.level
list.addChild(listItem)
parent.addChild(list)
@@ -984,7 +1046,7 @@ func (doc *Document) parseListDescription(parent, node *adocNode, line string) (
kind: nodeKindListDescriptionItem,
style: list.style,
}
- node.parseListDescription(line)
+ node.parseListDescriptionItem(line)
if listItem.level == node.level {
list.addChild(node)
listItem = node
@@ -994,6 +1056,7 @@ func (doc *Document) parseListDescription(parent, node *adocNode, line string) (
parentListItem := parent
for parentListItem != nil {
if parentListItem.kind == doc.kind && parentListItem.level == node.level {
+ list.postParseList(doc, nodeKindListDescriptionItem)
return line, c
}
parentListItem = parentListItem.parent
@@ -1061,6 +1124,7 @@ func (doc *Document) parseListDescription(parent, node *adocNode, line string) (
listItem.WriteByte('\n')
line = ""
}
+ list.postParseList(doc, nodeKindListDescriptionItem)
return line, c
}
@@ -1079,7 +1143,7 @@ func (doc *Document) parseListOrdered(parent *adocNode, title, line string) (
listItem := &adocNode{
kind: nodeKindListOrderedItem,
}
- listItem.parseListOrdered(line)
+ listItem.parseListOrderedItem(line)
list.level = listItem.level
list.addChild(listItem)
parent.addChild(list)
@@ -1124,7 +1188,7 @@ func (doc *Document) parseListOrdered(parent *adocNode, title, line string) (
node := &adocNode{
kind: nodeKindListOrderedItem,
}
- node.parseListOrdered(line)
+ node.parseListOrderedItem(line)
if listItem.level == node.level {
list.addChild(node)
listItem = node
@@ -1139,6 +1203,7 @@ func (doc *Document) parseListOrdered(parent *adocNode, title, line string) (
parentListItem := parent
for parentListItem != nil {
if parentListItem.kind == doc.kind && parentListItem.level == node.level {
+ list.postParseList(doc, nodeKindListOrderedItem)
return line, c
}
parentListItem = parentListItem.parent
@@ -1151,7 +1216,7 @@ func (doc *Document) parseListOrdered(parent *adocNode, title, line string) (
node := &adocNode{
kind: nodeKindListUnorderedItem,
}
- node.parseListUnordered(line)
+ node.parseListUnorderedItem(line)
// Case:
// . Parent
@@ -1160,6 +1225,7 @@ func (doc *Document) parseListOrdered(parent *adocNode, title, line string) (
parentListItem := parent
for parentListItem != nil {
if parentListItem.kind == doc.kind && parentListItem.level == node.level {
+ list.postParseList(doc, nodeKindListOrderedItem)
return line, c
}
parentListItem = parentListItem.parent
@@ -1172,11 +1238,12 @@ func (doc *Document) parseListOrdered(parent *adocNode, title, line string) (
node := &adocNode{
kind: doc.kind,
}
- node.parseListDescription(line)
+ node.parseListDescriptionItem(line)
parentListItem := parent
for parentListItem != nil {
if parentListItem.kind == doc.kind && parentListItem.level == node.level {
+ list.postParseList(doc, nodeKindListOrderedItem)
return line, c
}
parentListItem = parentListItem.parent
@@ -1249,7 +1316,7 @@ func (doc *Document) parseListOrdered(parent *adocNode, title, line string) (
listItem.WriteByte('\n')
line = ""
}
-
+ list.postParseList(doc, nodeKindListOrderedItem)
return line, c
}
@@ -1264,7 +1331,7 @@ func (doc *Document) parseListUnordered(parent, node *adocNode, line string) (
listItem := &adocNode{
kind: nodeKindListUnorderedItem,
}
- listItem.parseListUnordered(line)
+ listItem.parseListUnorderedItem(line)
list.level = listItem.level
list.addChild(listItem)
parent.addChild(list)
@@ -1310,7 +1377,7 @@ func (doc *Document) parseListUnordered(parent, node *adocNode, line string) (
node := &adocNode{
kind: nodeKindListOrderedItem,
}
- node.parseListOrdered(line)
+ node.parseListOrderedItem(line)
// Case:
// . Parent
@@ -1319,6 +1386,7 @@ func (doc *Document) parseListUnordered(parent, node *adocNode, line string) (
parentListItem := parent
for parentListItem != nil {
if parentListItem.kind == doc.kind && parentListItem.level == node.level {
+ list.postParseList(doc, nodeKindListUnorderedItem)
return line, c
}
parentListItem = parentListItem.parent
@@ -1332,7 +1400,7 @@ func (doc *Document) parseListUnordered(parent, node *adocNode, line string) (
node := &adocNode{
kind: nodeKindListUnorderedItem,
}
- node.parseListUnordered(line)
+ node.parseListUnorderedItem(line)
if listItem.level == node.level {
list.addChild(node)
listItem = node
@@ -1347,6 +1415,7 @@ func (doc *Document) parseListUnordered(parent, node *adocNode, line string) (
parentListItem := parent
for parentListItem != nil {
if parentListItem.kind == doc.kind && parentListItem.level == node.level {
+ list.postParseList(doc, nodeKindListUnorderedItem)
return line, c
}
parentListItem = parentListItem.parent
@@ -1359,11 +1428,12 @@ func (doc *Document) parseListUnordered(parent, node *adocNode, line string) (
node := &adocNode{
kind: doc.kind,
}
- node.parseListDescription(line)
+ node.parseListDescriptionItem(line)
parentListItem := parent
for parentListItem != nil {
if parentListItem.kind == doc.kind && parentListItem.level == node.level {
+ list.postParseList(doc, nodeKindListUnorderedItem)
return line, c
}
parentListItem = parentListItem.parent
@@ -1436,10 +1506,21 @@ func (doc *Document) parseListUnordered(parent, node *adocNode, line string) (
listItem.WriteByte('\n')
line = ""
}
-
+ list.postParseList(doc, nodeKindListUnorderedItem)
return line, c
}
+func (doc *Document) registerAnchor(id, label string) {
+ if doc.anchors == nil {
+ doc.anchors = make(map[string]string)
+ }
+
+ doc.anchors[id] = label
+ if len(label) > 0 {
+ doc.anchors[label] = id
+ }
+}
+
//
// tocHTML write table of contents with HTML template into out.
//
diff --git a/html_backend.go b/html_backend.go
index d6b126e..a43ee99 100644
--- a/html_backend.go
+++ b/html_backend.go
@@ -86,29 +86,26 @@ func (doc *Document) htmlGenerateTOC(
if len(sectClass) > 0 {
if level < node.level {
- _, err = fmt.Fprintf(out, `
-<ul class="%s">
-`, sectClass)
+ _, err = fmt.Fprintf(out, "\n<ul class=\"%s\">", sectClass)
} else if level > node.level {
n := level
for n > node.level {
- _, err = out.Write([]byte(`</ul>
-`))
+ _, err = out.Write([]byte("\n</ul>"))
n--
}
}
- _, err = fmt.Fprintf(out, `<li><a href="#%s">`, node.ID)
+ _, err = fmt.Fprintf(out, "\n<li><a href=\"#%s\">", node.ID)
if err != nil {
return fmt.Errorf("htmlGenerateTOC: %w", err)
}
- err = node.title.toHTML(doc, tmpl, out)
+ err = node.title.toHTML(doc, tmpl, out, true)
if err != nil {
return fmt.Errorf("htmlGenerateTOC: %w", err)
}
- _, err = out.Write([]byte(`</a>`))
+ _, err = out.Write([]byte("</a>"))
if err != nil {
return fmt.Errorf("htmlGenerateTOC: %w", err)
}
@@ -119,15 +116,13 @@ func (doc *Document) htmlGenerateTOC(
if err != nil {
return err
}
-
- if len(sectClass) > 0 {
- _, err = out.Write([]byte("</li>\n"))
- if err != nil {
- return fmt.Errorf("htmlGenerateTOC: %w", err)
- }
+ }
+ if len(sectClass) > 0 {
+ _, err = out.Write([]byte("</li>"))
+ if err != nil {
+ return fmt.Errorf("htmlGenerateTOC: %w", err)
}
}
-
if node.next != nil {
err = doc.htmlGenerateTOC(node.next, tmpl, out, node.level)
if err != nil {
@@ -136,7 +131,7 @@ func (doc *Document) htmlGenerateTOC(
}
if len(sectClass) > 0 && level < node.level {
- _, err = out.Write([]byte("</ul>\n"))
+ _, err = out.Write([]byte("\n</ul>\n"))
if err != nil {
return fmt.Errorf("htmlGenerateTOC: %w", err)
}
diff --git a/html_template.go b/html_template.go
index 747e2ab..5fa58be 100644
--- a/html_template.go
+++ b/html_template.go
@@ -150,7 +150,10 @@ Last updated {{.LastUpdated}}
{{- end}}
{{/*----------------------------------------------------------------------*/}}
{{- define "BEGIN_PARAGRAPH"}}
-<div class="paragraph {{- .Classes}}">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := printf "paragraph %s" .Classes | trimSpace}} class="{{$c}}"{{end -}}
+>
{{- template "BLOCK_TITLE" .}}
<p>
{{- end}}
@@ -159,7 +162,10 @@ Last updated {{.LastUpdated}}
{{- end}}
{{/*----------------------------------------------------------------------*/}}
{{- define "BLOCK_LITERAL"}}
-<div class="{{trimSpace .Classes}}">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := .Classes}} class="{{$c -}}"{{end -}}
+>
<div class="content">
<pre>{{.Content -}}</pre>
</div>
@@ -167,7 +173,10 @@ Last updated {{.LastUpdated}}
{{- end}}
{{/*----------------------------------------------------------------------*/}}
{{- define "BLOCK_LISTING"}}
-<div class="{{trimSpace .Classes}}">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := .Classes}} class="{{$c -}}"{{end -}}
+>
<div class="content">
<pre>{{.Content -}}</pre>
</div>
@@ -177,7 +186,10 @@ Last updated {{.LastUpdated}}
{{- define "BEGIN_LIST_ORDERED"}}
{{- $class := .GetListOrderedClass}}
{{- $type := .GetListOrderedType}}
-<div class="olist {{$class}} {{- .Classes}}">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := printf "olist %s %s" $class .Classes | trimSpace}} class="{{$c}}"{{end -}}
+>
{{- template "BLOCK_TITLE" .}}
<ol class="{{$class}}"{{- if $type}} type="{{$type}}"{{end}}>
{{- end}}
@@ -187,7 +199,10 @@ Last updated {{.LastUpdated}}
{{- end}}
{{/*----------------------------------------------------------------------*/}}
{{- define "BEGIN_LIST_UNORDERED"}}
-<div class="{{trimSpace .Classes}}">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := .Classes}} class="{{$c -}}"{{end -}}
+>
{{- template "BLOCK_TITLE" .}}
<ul>
{{- end}}
@@ -195,22 +210,28 @@ Last updated {{.LastUpdated}}
</ul>
</div>
{{- end}}
-{{/*----------------------------------------------------------------------*/}}
+
+
{{- define "BEGIN_LIST_DESCRIPTION"}}
{{- if .IsStyleQandA}}
-<div class="qlist qanda {{- .Classes}}">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := printf "qlist qanda %s" .Classes | trimSpace}} class="{{$c}}"{{end -}}
+>
{{- template "BLOCK_TITLE" .}}
<ol>
{{- else if .IsStyleHorizontal}}
-<div class="hdlist {{- .Classes}}">
+<div class="hdlist {{- .Classes -}}">
{{- template "BLOCK_TITLE" .}}
<table>
{{- else}}
-<div class="dlist {{- .Classes}}">
+<div class="dlist {{- .Classes -}}">
{{- template "BLOCK_TITLE" .}}
<dl>
{{- end}}
{{- end}}
+
+
{{- define "END_LIST_DESCRIPTION"}}
{{- if .IsStyleQandA}}
</ol>
@@ -221,15 +242,8 @@ Last updated {{.LastUpdated}}
{{- end}}
</div>
{{- end}}
-{{/*----------------------------------------------------------------------*/}}
-{{- define "BEGIN_LIST_ITEM"}}
-<li>
-<p>{{- .Content -}}</p>
-{{- end}}
-{{- define "END_LIST_ITEM"}}
-</li>
-{{- end}}
-{{/*----------------------------------------------------------------------*/}}
+
+
{{- define "BEGIN_LIST_DESCRIPTION_ITEM"}}
{{- if .IsStyleQandA}}
<li>
@@ -244,10 +258,9 @@ Last updated {{.LastUpdated}}
<dt class="hdlist1">{{- .Label -}}</dt>
<dd>
{{- end}}
- {{- with $content := .Content}}
-<p>{{- $content -}}</p>
- {{- end}}
{{- end}}
+
+
{{- define "END_LIST_DESCRIPTION_ITEM"}}
{{- if .IsStyleQandA}}
</li>
@@ -258,7 +271,8 @@ Last updated {{.LastUpdated}}
</dd>
{{- end}}
{{- end}}
-{{/*----------------------------------------------------------------------*/}}
+
+
{{- define "HORIZONTAL_RULE"}}
<hr>
{{- end}}
@@ -268,7 +282,10 @@ Last updated {{.LastUpdated}}
{{- end}}
{{/*----------------------------------------------------------------------*/}}
{{- define "BLOCK_IMAGE"}}
-<div class="imageblock {{- .Classes}}">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := printf "imageblock %s" .Classes | trimSpace}} class="{{$c}}"{{end -}}
+>
<div class="content">
<img src="{{.Attrs.src}}" alt="{{.Attrs.alt}}"
{{- with $w := .Attrs.width}} width="{{$w}}"{{end}}
@@ -279,17 +296,27 @@ Last updated {{.LastUpdated}}
{{- end}}
</div>
{{- end}}
-{{/*----------------------------------------------------------------------*/}}
+
+{{define "INLINE_ID"}}<a id="{{.ID}}"></a>{{end}}
+
+{{define "BEGIN_INLINE_ID_SHORT"}}<span id="{{.ID}}">{{end}}
+{{define "END_INLINE_ID_SHORT"}}</span>{{end}}
+
{{- define "INLINE_IMAGE" -}}
{{- $content := .Content -}}
-<span class="image {{- .Classes}}"><img src="{{.Attrs.src}}" alt="{{.Attrs.alt}}"
+<span
+ {{- with $c := printf "image %s" .Classes | trimSpace}} class="{{$c}}"{{end -}}
+><img src="{{.Attrs.src}}" alt="{{.Attrs.alt}}"
{{- with $w := .Attrs.width}} width="{{$w}}"{{end}}
{{- with $h := .Attrs.height}} height="{{$h}}"{{end}}></span>
{{- end}}
{{/*----------------------------------------------------------------------*/}}
{{- define "BEGIN_BLOCK_OPEN"}}
-<div class="openblock {{- .Classes}}">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := printf "openblock %s" .Classes | trimSpace}} class="{{$c}}"{{end -}}
+>
{{- template "BLOCK_TITLE" .}}
<div class="content">
{{- end}}
@@ -300,7 +327,8 @@ Last updated {{.LastUpdated}}
{{- end}}
{{/*----------------------------------------------------------------------*/}}
{{- define "BLOCK_VIDEO"}}
-<div class="videoblock">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}} class="videoblock">
{{- template "BLOCK_TITLE" .}}
<div class="content">
{{- if .Attrs.youtube}}
@@ -329,7 +357,10 @@ Your browser does not support the video tag.
{{- end}}
{{/*----------------------------------------------------------------------*/}}
{{- define "BLOCK_AUDIO"}}
-<div class="audioblock">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := printf "audioblock %s" .Classes | trimSpace}} class="{{$c}}"{{end -}}
+>
{{- template "BLOCK_TITLE" .}}
<div class="content">
<audio src="{{.Attrs.src}}"
@@ -343,7 +374,10 @@ Your browser does not support the audio tag.
{{- end}}
{{/*----------------------------------------------------------------------*/}}
{{- define "BEGIN_ADMONITION"}}
-<div class="admonitionblock {{- .Classes}}">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := printf "admonitionblock %s" .Classes | trimSpace}} class="{{$c}}"{{end -}}
+>
<table>
<tr>
<td class="icon">
@@ -366,7 +400,10 @@ Your browser does not support the audio tag.
{{- end}}
{{/*----------------------------------------------------------------------*/}}
{{- define "BEGIN_SIDEBAR"}}
-<div class="sidebarblock {{- .Classes}}">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := printf "sidebarblock %s" .Classes | trimSpace}} class="{{$c}}"{{end -}}
+>
<div class="content">
{{- template "BLOCK_TITLE" .}}
{{- end}}
@@ -376,7 +413,10 @@ Your browser does not support the audio tag.
{{- end}}
{{/*----------------------------------------------------------------------*/}}
{{- define "BEGIN_EXAMPLE"}}
-<div class="exampleblock {{- .Classes}}">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := printf "exampleblock %s" .Classes | trimSpace}} class="{{$c}}"{{end -}}
+>
{{- with $caption := .Title}}
<div class="title">Example {{exampleCounter}}. {{$caption}}</div>
{{- end}}
@@ -388,7 +428,10 @@ Your browser does not support the audio tag.
{{- end}}
{{/*----------------------------------------------------------------------*/}}
{{- define "BEGIN_QUOTE"}}
-<div class="quoteblock {{- .Classes}}">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := printf "quoteblock %s" .Classes | trimSpace}} class="{{$c}}"{{end -}}
+>
{{- with $caption := .Title}}
<div class="title">{{$caption}}</div>
{{- end}}
@@ -412,7 +455,10 @@ Your browser does not support the audio tag.
{{- end}}
{{/*----------------------------------------------------------------------*/}}
{{- define "BEGIN_VERSE"}}
-<div class="verseblock {{- .Classes}}">
+<div
+ {{- if .ID}} id="{{.ID}}"{{end}}
+ {{- with $c := printf "verseblock %s" .Classes | trimSpace}} class="{{$c}}"{{end -}}
+>
{{- with $caption := .Title}}
<div class="title">{{$caption}}</div>
{{- end}}
diff --git a/parser.go b/parser.go
index b1b853d..f4220fb 100644
--- a/parser.go
+++ b/parser.go
@@ -43,42 +43,47 @@ const (
nodeKindBlockLiteral // "...."
nodeKindBlockLiteralNamed // "[literal]"
nodeKindBlockOpen // Block wrapped with "--"
- nodeKindBlockPassthrough // Block wrapped with "++++"
- nodeKindBlockSidebar // 20: "****"
+ nodeKindBlockPassthrough // 20: Block wrapped with "++++"
+ nodeKindBlockSidebar // "****"
nodeKindBlockVideo // "video::"
+ nodeKindInlineID // "[[" REF_ID "]]" TEXT
+ nodeKindInlineIDShort // "[#" REF_ID "]#" TEXT "#"
nodeKindInlineImage // Inline macro for "image:"
+ nodeKindInlineParagraph //
nodeKindListOrdered // Wrapper.
nodeKindListOrderedItem // Line start with ". "
nodeKindListUnordered // Wrapper.
- nodeKindListUnorderedItem // Line start with "* "
+ nodeKindListUnorderedItem // 30: Line start with "* "
nodeKindListDescription // Wrapper.
nodeKindListDescriptionItem // Line that has "::" + WSP
nodeKindMacroTOC // "toc::[]"
nodeKindPassthrough // Text wrapped inside "+"
- nodeKindPassthroughDouble // 30: Text wrapped inside "++"
+ nodeKindPassthroughDouble // Text wrapped inside "++"
nodeKindPassthroughTriple // Text wrapped inside "+++"
nodeKindSymbolQuoteDoubleBegin // The ("`)
nodeKindSymbolQuoteDoubleEnd // The (`")
nodeKindSymbolQuoteSingleBegin // The ('`)
- nodeKindSymbolQuoteSingleEnd // The (`')
+ nodeKindSymbolQuoteSingleEnd // 40: The (`')
nodeKindText //
nodeKindTextBold // Text wrapped by "*"
nodeKindTextItalic // Text wrapped by "_"
nodeKindTextMono // Text wrapped by "`"
- nodeKindTextSubscript // 40: Word wrapped by '~'
+ nodeKindTextSubscript // Word wrapped by '~'
nodeKindTextSuperscript // Word wrapped by '^'
nodeKindUnconstrainedBold // Text wrapped by "**"
nodeKindUnconstrainedItalic // Text wrapped by "__"
nodeKindUnconstrainedMono // Text wrapped by "``"
- nodeKindURL // Anchor text.
+ nodeKindURL // 50: Anchor text.
lineKindAdmonition // "LABEL: WSP"
lineKindAttribute // Line start with ":"
lineKindBlockComment // Block start and end with "////"
lineKindBlockTitle // Line start with ".<alnum>"
- lineKindComment // 50: Line start with "//"
+ lineKindComment // Line start with "//"
lineKindEmpty // LF
lineKindHorizontalRule // "'''", "---", "- - -", "***", "* * *"
- lineKindListContinue // A single "+" line
+ lineKindID // "[[" REF_ID "]]"
+ lineKindIDShort // "[#" REF_ID "]#" TEXT "#"
+ lineKindListContinue // 60: A single "+" line
lineKindPageBreak // "<<<"
lineKindStyle // Line start with "["
lineKindStyleClass // Custom style "[.x.y]"
@@ -92,6 +97,7 @@ const (
attrNameFloat = "float"
attrNameHeight = "height"
attrNameHref = "href"
+ attrNameID = "id"
attrNameLang = "lang"
attrNameOptions = "options"
attrNamePoster = "poster"
@@ -298,6 +304,27 @@ func isTitle(line string) bool {
}
//
+// isValidID will return true if id is valid XML ID, where the first character
+// is letter and the rest is either '-', '_', letter or digits.
+//
+func isValidID(id string) bool {
+ for x, r := range id {
+ if x == 0 {
+ if !unicode.IsLetter(r) {
+ return false
+ }
+ continue
+ }
+ if r == '-' || r == '_' || unicode.IsLetter(r) ||
+ unicode.IsDigit(r) {
+ continue
+ }
+ return false
+ }
+ return true
+}
+
+//
// parseBlockAttribute parse list of attributes in between "[" "]".
//
// BLOCK_ATTRS = BLOCK_ATTR *("," BLOCK_ATTR)
@@ -369,8 +396,24 @@ func parseBlockAttribute(in string) (out []string) {
return out
}
-func parseInlineMarkup(content []byte) (container *adocNode) {
- pi := newParserInline(content)
+//
+// parseIDLabel parse the s "ID (,LABEL)" into ID and label.
+// It will return empty id and label if ID is not valid.
+//
+func parseIDLabel(s string) (id, label string) {
+ idLabel := strings.Split(s, ",")
+ id = idLabel[0]
+ if len(idLabel) >= 2 {
+ label = idLabel[1]
+ }
+ if isValidID(idLabel[0]) {
+ return id, label
+ }
+ return "", ""
+}
+
+func parseInlineMarkup(doc *Document, content []byte) (container *adocNode) {
+ pi := newParserInline(doc, content)
pi.do()
return pi.container
}
@@ -489,14 +532,24 @@ func whatKindOfLine(line string) (kind int, spaces, got string) {
kind = lineKindAttribute
} else if line[0] == '[' {
newline := strings.TrimRight(line, " \t")
- if newline[len(newline)-1] == ']' {
+ l := len(newline)
+ if newline[l-1] != ']' {
+ return lineKindText, "", line
+ }
+ if l >= 5 {
+ if newline[1] == '[' && newline[l-2] == ']' {
+ return lineKindID, "", line
+ }
+ }
+ if l >= 4 {
+ if line[1] == '#' {
+ return lineKindIDShort, "", line
+ }
if line[1] == '.' {
- kind = lineKindStyleClass
- } else {
- kind = lineKindStyle
+ return lineKindStyleClass, "", line
}
- return kind, spaces, line
}
+ return lineKindStyle, spaces, line
} else if line[0] == '=' {
if line == "====" {
return nodeKindBlockExample, spaces, line
diff --git a/parser_inline.go b/parser_inline.go
index abdc18b..197482e 100644
--- a/parser_inline.go
+++ b/parser_inline.go
@@ -19,18 +19,20 @@ type parserInline struct {
container *adocNode
current *adocNode
content []byte
+ doc *Document
x int
state *parserInlineState
prev, c, nextc byte
isEscaped bool
}
-func newParserInline(content []byte) (pi *parserInline) {
+func newParserInline(doc *Document, content []byte) (pi *parserInline) {
pi = &parserInline{
container: &adocNode{
kind: nodeKindText,
},
content: bytes.TrimRight(content, "\n"),
+ doc: doc,
state: &parserInlineState{},
}
pi.current = pi.container
@@ -193,11 +195,39 @@ func (pi *parserInline) do() {
if pi.parseFormat(nodeKindTextMono, styleTextMono) {
continue
}
+ } else if pi.c == '[' {
+ if pi.isEscaped {
+ pi.escape()
+ continue
+ }
+ if pi.nextc == '[' {
+ if pi.parseInlineID() {
+ continue
+ }
+ } else if pi.nextc == '#' {
+ if pi.parseInlineIDShort() {
+ continue
+ }
+ }
+ } else if pi.c == '#' {
+ if pi.isEscaped {
+ pi.escape()
+ continue
+ }
+ // Do we have the beginning?
+ if pi.state.has(nodeKindInlineIDShort) {
+ pi.terminate(nodeKindInlineIDShort, 0)
+ pi.x++
+ pi.prev = 0
+ continue
+ }
}
pi.current.WriteByte(pi.c)
pi.x++
pi.prev = pi.c
}
+
+ pi.container.removeLastIfEmpty()
}
func (pi *parserInline) escape() {
@@ -220,6 +250,74 @@ func (pi *parserInline) getBackMacroName() (macroName string, lastc byte) {
}
//
+// parseInlineID parse the ID and optional label between "[[" "]]".
+//
+func (pi *parserInline) parseInlineID() bool {
+ // Check if we have term at the end.
+ raw := pi.content[pi.x+2:]
+ raw, idx := indexUnescape(raw, []byte("]]"))
+ if idx < 0 {
+ return false
+ }
+ id, label := parseIDLabel(string(raw))
+ if len(id) == 0 {
+ return false
+ }
+
+ pi.doc.registerAnchor(id, label)
+
+ node := &adocNode{
+ ID: id,
+ kind: nodeKindInlineID,
+ }
+ pi.current.addChild(node)
+ node = &adocNode{
+ kind: nodeKindText,
+ }
+ pi.current.addChild(node)
+ pi.current = node
+ pi.x += 2 + len(raw) + 2
+ pi.prev = 0
+ return true
+}
+
+//
+// parseInlineIDShort parse the ID and optional label between "[#", "]#", and
+// "#".
+//
+func (pi *parserInline) parseInlineIDShort() bool {
+ // Check if we have term at the end.
+ raw := pi.content[pi.x+2:]
+ id, idx := indexUnescape(raw, []byte("]#"))
+ if idx < 0 {
+ return false
+ }
+
+ stringID := string(id)
+ if !isValidID(stringID) {
+ return false
+ }
+
+ // Check if we have "#"
+ _, idx = indexByteUnescape(raw[idx+2:], '#')
+ if idx < 0 {
+ return false
+ }
+
+ pi.doc.registerAnchor(stringID, "")
+
+ node := &adocNode{
+ ID: stringID,
+ kind: nodeKindInlineIDShort,
+ }
+ pi.state.push(nodeKindInlineIDShort)
+ pi.current.addChild(node)
+ pi.current = node
+ pi.x += 2 + len(id) + 2
+ return true
+}
+
+//
// parseQuoteBegin check if the double quote curve ("`) is valid (does not
// followed by space) and has an end (`").
//
@@ -649,7 +747,7 @@ func (pi *parserInline) parseURL(scheme string) (node *adocNode) {
text = strings.TrimRight(text, "^")
node.Attrs[attrNameRel] = attrValueNoopener
}
- child := parseInlineMarkup([]byte(text))
+ child := parseInlineMarkup(pi.doc, []byte(text))
node.addChild(child)
}
if len(attrs) >= 2 {
diff --git a/parser_inline_test.go b/parser_inline_test.go
index 30a88da..5a7228d 100644
--- a/parser_inline_test.go
+++ b/parser_inline_test.go
@@ -30,8 +30,8 @@ func TestParserInline_do(t *testing.T) {
for _, c := range cases {
buf.Reset()
- container := parseInlineMarkup([]byte(c.content))
- err := container.toHTML(_testDoc, _testTmpl, &buf)
+ container := parseInlineMarkup(_testDoc, []byte(c.content))
+ err := container.toHTML(_testDoc, _testTmpl, &buf, false)
if err != nil {
t.Fatal(err)
}
@@ -80,8 +80,8 @@ func TestParserInline_parseFormat(t *testing.T) {
for _, c := range cases {
buf.Reset()
- container := parseInlineMarkup([]byte(c.content))
- err := container.toHTML(_testDoc, _testTmpl, &buf)
+ container := parseInlineMarkup(_testDoc, []byte(c.content))
+ err := container.toHTML(_testDoc, _testTmpl, &buf, false)
if err != nil {
t.Fatal(err)
}
@@ -122,8 +122,95 @@ func TestParserInline_parseFormatUnconstrained(t *testing.T) {
for _, c := range cases {
buf.Reset()
- container := parseInlineMarkup([]byte(c.content))
- err := container.toHTML(_testDoc, _testTmpl, &buf)
+ container := parseInlineMarkup(_testDoc, []byte(c.content))
+ err := container.toHTML(_testDoc, _testTmpl, &buf, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // container.debug(0)
+
+ got := buf.String()
+ test.Assert(t, c.content, c.exp, got, true)
+ }
+}
+
+func TestParserInline_parseInlineID(t *testing.T) {
+ cases := []struct {
+ content string
+ exp string
+ isForToC bool
+ }{{
+ content: `[[A]] B`,
+ exp: `<a id="A"></a> B`,
+ }, {
+ content: `[[A]] B`,
+ exp: ` B`,
+ isForToC: true,
+ }, {
+ content: `[[A] B`,
+ exp: `[[A] B`,
+ }, {
+ content: `[A]] B`,
+ exp: `[A]] B`,
+ }, {
+ content: `[[A ]] B`,
+ exp: `[[A ]] B`,
+ }, {
+ content: `[[ A]] B`,
+ exp: `[[ A]] B`,
+ }, {
+ content: `[[A B]] C`,
+ exp: `[[A B]] C`,
+ }}
+
+ var buf bytes.Buffer
+ for _, c := range cases {
+ buf.Reset()
+
+ container := parseInlineMarkup(_testDoc, []byte(c.content))
+ err := container.toHTML(_testDoc, _testTmpl, &buf, c.isForToC)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // container.debug(0)
+
+ got := buf.String()
+ test.Assert(t, c.content, c.exp, got, true)
+ }
+}
+
+func TestParserInline_parseInlineIDShort(t *testing.T) {
+ cases := []struct {
+ content string
+ exp string
+ }{{
+ content: `[#A]#B#`,
+ exp: `<span id="A">B</span>`,
+ }, {
+ content: `[#A]#B`,
+ exp: `[#A]#B`,
+ }, {
+ content: `[#A]B#`,
+ exp: `[#A]B#`,
+ }, {
+ content: `[#A ]#B#`,
+ exp: `[#A ]#B#`,
+ }, {
+ content: `[# A]# B#`,
+ exp: `[# A]# B#`,
+ }, {
+ content: `[#A B]# C#`,
+ exp: `[#A B]# C#`,
+ }}
+
+ var buf bytes.Buffer
+ for _, c := range cases {
+ buf.Reset()
+
+ container := parseInlineMarkup(_testDoc, []byte(c.content))
+ err := container.toHTML(_testDoc, _testTmpl, &buf, false)
if err != nil {
t.Fatal(err)
}
@@ -156,8 +243,8 @@ You can find Linux everywhere these days!`,
for _, c := range cases {
buf.Reset()
- container := parseInlineMarkup([]byte(c.content))
- err := container.toHTML(_testDoc, _testTmpl, &buf)
+ container := parseInlineMarkup(_testDoc, []byte(c.content))
+ err := container.toHTML(_testDoc, _testTmpl, &buf, false)
if err != nil {
t.Fatal(err)
}
@@ -194,8 +281,8 @@ func TestParserInline_parsePassthrough(t *testing.T) {
for _, c := range cases {
buf.Reset()
- container := parseInlineMarkup([]byte(c.content))
- err := container.toHTML(_testDoc, _testTmpl, &buf)
+ container := parseInlineMarkup(_testDoc, []byte(c.content))
+ err := container.toHTML(_testDoc, _testTmpl, &buf, false)
if err != nil {
t.Fatal(err)
}
@@ -238,8 +325,8 @@ func TestParserInline_parsePassthroughDouble(t *testing.T) {
for _, c := range cases {
buf.Reset()
- container := parseInlineMarkup([]byte(c.content))
- err := container.toHTML(_testDoc, _testTmpl, &buf)
+ container := parseInlineMarkup(_testDoc, []byte(c.content))
+ err := container.toHTML(_testDoc, _testTmpl, &buf, false)
if err != nil {
t.Fatal(err)
}
@@ -288,8 +375,8 @@ func TestParserInline_parsePassthroughTriple(t *testing.T) {
for _, c := range cases {
buf.Reset()
- container := parseInlineMarkup([]byte(c.content))
- err := container.toHTML(_testDoc, _testTmpl, &buf)
+ container := parseInlineMarkup(_testDoc, []byte(c.content))
+ err := container.toHTML(_testDoc, _testTmpl, &buf, false)
if err != nil {
t.Fatal(err)
}
@@ -335,8 +422,8 @@ func TestParserInline_parseQuote(t *testing.T) {
for _, c := range cases {
buf.Reset()
- container := parseInlineMarkup([]byte(c.content))
- err := container.toHTML(_testDoc, _testTmpl, &buf)
+ container := parseInlineMarkup(_testDoc, []byte(c.content))
+ err := container.toHTML(_testDoc, _testTmpl, &buf, false)
if err != nil {
t.Fatal(err)
}
@@ -373,8 +460,8 @@ func TestParserInline_parseSubscsript(t *testing.T) {
for _, c := range cases {
buf.Reset()
- container := parseInlineMarkup([]byte(c.content))
- err := container.toHTML(_testDoc, _testTmpl, &buf)
+ container := parseInlineMarkup(_testDoc, []byte(c.content))
+ err := container.toHTML(_testDoc, _testTmpl, &buf, false)
if err != nil {
t.Fatal(err)
}
@@ -411,8 +498,8 @@ func TestParserInline_parseSuperscript(t *testing.T) {
for _, c := range cases {
buf.Reset()
- container := parseInlineMarkup([]byte(c.content))
- err := container.toHTML(_testDoc, _testTmpl, &buf)
+ container := parseInlineMarkup(_testDoc, []byte(c.content))
+ err := container.toHTML(_testDoc, _testTmpl, &buf, false)
if err != nil {
t.Fatal(err)
}
@@ -458,8 +545,8 @@ func TestParserInline_parseURL(t *testing.T) {
for _, c := range cases {
buf.Reset()
- container := parseInlineMarkup([]byte(c.content))
- err := container.toHTML(_testDoc, _testTmpl, &buf)
+ container := parseInlineMarkup(_testDoc, []byte(c.content))
+ err := container.toHTML(_testDoc, _testTmpl, &buf, false)
if err != nil {
t.Fatal(err)
}
diff --git a/parser_test.go b/parser_test.go
index ddc7500..beb5fe8 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -10,6 +10,23 @@ import (
"github.com/shuLhan/share/lib/test"
)
+func TestIsValidID(t *testing.T) {
+ cases := []struct {
+ id string
+ exp bool
+ }{{
+ id: "a",
+ exp: true,
+ }, {
+ id: "1",
+ }}
+
+ for _, c := range cases {
+ got := isValidID(c.id)
+ test.Assert(t, c.id, c.exp, got, true)
+ }
+}
+
func TestParseBlockAttribute(t *testing.T) {
cases := []struct {
in string
diff --git a/testdata/got.test.html b/testdata/got.test.html
index b286298..e58cb54 100644
--- a/testdata/got.test.html
+++ b/testdata/got.test.html
@@ -64,6 +64,13 @@
<li><a href="#_horizontal_rules">Horizontal rules</a></li>
<li><a href="#_page_break">Page break</a></li>
<li><a href="#_urls">URLs</a></li>
+<li><a href="#_anchors">Anchors</a>
+<ul class="sectlevel2">
+<li><a href="#version-4_9">Version 4.9 </a></li>
+<li><a href="#version-4_10">Version 4.10 </a></li>
+<li><a href="#which-one">Version 4.11 </a></li>
+</ul>
+</li>
<li><a href="#_block_images">Block images</a>
<ul class="sectlevel2">
<li><a href="#_float_group">Float group</a></li>
@@ -1249,6 +1256,45 @@ Hard drive
</div>
</div>
<div class="sect1">
+<h2 id="_anchors">Anchors</h2>
+<div class="sectionbody">
+<div id="notice" class="paragraph">
+<p>This paragraph gets a lot of attention.</p>
+</div>
+<div id="notice_2" class="paragraph">
+<p>This paragraph gets a lot of attention.</p>
+</div>
+<div class="paragraph">
+<p><a id="bookmark-a"></a>Inline anchors make arbitrary content referenceable.</p>
+</div>
+<div class="paragraph">
+<p><span id="bookmark-b">Inline <em>anchors</em> can be applied to a phrase like this one.</span></p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item</p>
+</li>
+<li>
+<p><a id="step2"></a>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+</li>
+</ul>
+</div>
+<div class="sect2">
+<h3 id="version-4_9">Version 4.9 </h3>
+</div>
+<div class="sect2">
+<h3 id="version-4_10"><a id="current"></a>Version 4.10 </h3>
+</div>
+<div class="sect2">
+<h3 id="which-one">Version 4.11 <a id="version-4_11"></a></h3>
+</div>
+</div>
+</div>
+<div class="sect1">
<h2 id="_block_images">Block images</h2>
<div class="sectionbody">
<div class="imageblock">
@@ -1829,7 +1875,7 @@ and then moves on.</pre>
<div id="footer">
<div id="footer-text">
Version 1.1.1<br>
-Last updated 2020-10-30 03:55:01 +0700
+Last updated 2020-10-30 21:31:38 +0700
</div>
</div>
</body>
diff --git a/testdata/test.adoc b/testdata/test.adoc
index 33c43eb..4cf0dca 100644
--- a/testdata/test.adoc
+++ b/testdata/test.adoc
@@ -506,6 +506,30 @@ mailto:ms@kilabit.info[Mail to me].
Relative file link:test.html[test.html].
+
+== Anchors
+
+[[notice]]
+This paragraph gets a lot of attention.
+
+[#notice_2]
+This paragraph gets a lot of attention.
+
+[[bookmark-a]]Inline anchors make arbitrary content referenceable.
+
+[#bookmark-b]#Inline _anchors_ can be applied to a phrase like this one.#
+
+* First item
+* [[step2]]Second item
+* Third item
+
+=== Version 4.9 [[version-4_9]]
+
+=== [[current]]Version 4.10 [[version-4_10]]
+
+[#which-one]
+=== Version 4.11 [[version-4_11]]
+
== Block images
image::sunset.jpg[]
diff --git a/testdata/test.html b/testdata/test.html
index 0d35c48..9858551 100644
--- a/testdata/test.html
+++ b/testdata/test.html
@@ -63,6 +63,13 @@
<li><a href="#_horizontal_rules">Horizontal rules</a></li>
<li><a href="#_page_break">Page break</a></li>
<li><a href="#_urls">URLs</a></li>
+<li><a href="#_anchors">Anchors</a>
+<ul class="sectlevel2">
+<li><a href="#version-4_9">Version 4.9</a></li>
+<li><a href="#version-4_10">Version 4.10</a></li>
+<li><a href="#which-one">Version 4.11 </a></li>
+</ul>
+</li>
<li><a href="#_block_images">Block images</a>
<ul class="sectlevel2">
<li><a href="#_float_group">Float group</a></li>
@@ -1250,6 +1257,48 @@ Hard drive
</div>
</div>
<div class="sect1">
+<h2 id="_anchors">Anchors</h2>
+<div class="sectionbody">
+<div id="notice" class="paragraph">
+<p>This paragraph gets a lot of attention.</p>
+</div>
+<div id="notice_2" class="paragraph">
+<p>This paragraph gets a lot of attention.</p>
+</div>
+<div class="paragraph">
+<p><a id="bookmark-a"></a>Inline anchors make arbitrary content referenceable.</p>
+</div>
+<div class="paragraph">
+<p><span id="bookmark-b">Inline <em>anchors</em> can be applied to a phrase like this one.</span></p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First item</p>
+</li>
+<li>
+<p><a id="step2"></a>Second item</p>
+</li>
+<li>
+<p>Third item</p>
+</li>
+</ul>
+</div>
+<div class="sect2">
+<h3 id="version-4_9">Version 4.9</h3>
+
+</div>
+<div class="sect2">
+<h3 id="version-4_10"><a id="current"></a>Version 4.10</h3>
+
+</div>
+<div class="sect2">
+<h3 id="which-one">Version 4.11 <a id="version-4_11"></a></h3>
+
+</div>
+</div>
+</div>
+<div class="sect1">
<h2 id="_block_images">Block images</h2>
<div class="sectionbody">
<div class="imageblock">
@@ -1826,7 +1875,7 @@ and then moves on.</pre>
<div id="footer">
<div id="footer-text">
Version 1.1.1<br>
-Last updated 2020-10-30 03:55:00 +0700
+Last updated 2020-10-30 21:31:37 +0700
</div>
</div>
</body>