diff options
| author | Shulhan <m.shulhan@gmail.com> | 2020-10-31 04:01:41 +0700 |
|---|---|---|
| committer | Shulhan <m.shulhan@gmail.com> | 2020-10-31 04:01:41 +0700 |
| commit | 45e1e54c75bda5591cf02ea8bc899a8150b3dcfc (patch) | |
| tree | 2119c2076af4dc4d738064946a3e7995538218bc | |
| parent | ee78188ea91964725f58cc3d8c472cc9ab2f5c36 (diff) | |
| download | asciidoctor-go-45e1e54c75bda5591cf02ea8bc899a8150b3dcfc.tar.xz | |
all: implement parser for block anchor and inline anchor
| -rw-r--r-- | SPECS.adoc | 25 | ||||
| -rw-r--r-- | adoc_node.go | 164 | ||||
| -rw-r--r-- | adoc_node_test.go | 4 | ||||
| -rw-r--r-- | document.go | 135 | ||||
| -rw-r--r-- | html_backend.go | 27 | ||||
| -rw-r--r-- | html_template.go | 112 | ||||
| -rw-r--r-- | parser.go | 85 | ||||
| -rw-r--r-- | parser_inline.go | 102 | ||||
| -rw-r--r-- | parser_inline_test.go | 131 | ||||
| -rw-r--r-- | parser_test.go | 17 | ||||
| -rw-r--r-- | testdata/got.test.html | 48 | ||||
| -rw-r--r-- | testdata/test.adoc | 24 | ||||
| -rw-r--r-- | testdata/test.html | 51 |
13 files changed, 786 insertions, 139 deletions
@@ -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}} @@ -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> |
