M _doc/SPECS.adoc => _doc/SPECS.adoc +34 -0
@@ 476,6 476,8 @@ exist, otherwise it would be considered as normal text.
== Passthrough
+{url_ref}/pass/[Reference^]
+
----
PASSTHROUGH_SINGLE = FORMAT_BEGIN "+" TEXT "+" FORMAT_END
@@ 484,8 486,40 @@ PASSTHROUGH_DOUBLE = "++" TEXT "++"
PASSTHROUGH_TRIPLE = "+++" TEXT "+++"
PASSTHROUGH_BLOCK = "++++" LF 1*LINE "++++" LF
+
+PASSTHROUGH_MACRO = "pass:" *(PASSMACRO_SUB) "[" TEXT "]"
+
+PASSMACRO_SUB = PASSMACRO_CHAR *("," PASSMACRO_CHAR)
+
+PASSMACRO_CHAR = "c" / "q" / "a" / "r" / "m" / "p"
+ / PASSMACRO_GROUP_NORMAL
+ / PASSMACRO_GROUP_VERBATIM
+
+PASSMACRO_GROUP_NORMAL = "n" ; equal to "c,q,r,m,p"
+
+PASSMACRO_GROUP_VERBATIM = "v" ; equal to "c"
----
+The "c" allow
+{url_ref}/subs/special-characters/[special character substitutions].
+
+The "q" allow
+{url_ref}/subs/quotes/[quotes substitutions].
+
+The "a" allow
+{url_ref}/subs/attributes/[attributes references substitutions].
+
+The "r" allow
+{url_ref}/subs/replacements/[character replacement substitutions].
+
+The "m" allow
+{url_ref}/subs/macros/[macro substitutions].
+
+The "p" allow
+{url_ref}/subs/post-replacements/[post-replacement substitutions].
+
+The substitutions are applied in above order.
+
== URLs
A const.go => const.go +14 -0
@@ 0,0 1,14 @@
+package asciidoctor
+
+// List of passthrough substitutions.
+const (
+ passSubNone int = 0
+ passSubChar = 1 // 'c'
+ passSubQuote = 2 // 'q'
+ passSubAttr = 4 // 'a'
+ passSubRepl = 8 // 'r'
+ passSubMacro = 16 // 'm'
+ passSubPostRepl = 32 // 'p'
+ passSubNormal = passSubChar | passSubQuote | passSubAttr | passSubRepl | passSubMacro | passSubPostRepl
+ passSubVerbatim = passSubChar
+)
M element.go => element.go +6 -0
@@ 45,6 45,9 @@ type element struct {
rawLabel bytes.Buffer
level int // The number of dot for ordered list, or '*' for unordered list.
kind int
+
+ // List of substitutions to be applied on raw.
+ applySubs int
}
func (el *element) getListOrderedClass() string {
@@ 870,6 873,9 @@ func (el *element) toHTML(doc *Document, w io.Writer) {
case elKindInlineImage:
htmlWriteInlineImage(el, w)
+ case elKindInlinePass:
+ htmlWriteInlinePass(doc, el, w)
+
case elKindListDescription:
htmlWriteListDescription(el, w)
case elKindListOrdered:
M html_backend.go => html_backend.go +521 -0
@@ 9,6 9,7 @@ import (
"io"
"strings"
+ libascii "github.com/shuLhan/share/lib/ascii"
libstrings "github.com/shuLhan/share/lib/strings"
)
@@ 60,6 61,519 @@ const (
htmlSymbolZeroWidthSpace = `​`
)
+// htmlSubs apply the text substitutions to element.raw based on applySubs in
+// the following order: c, q, a, r, m, p.
+// If applySubs is 0, it will return element.raw as is.
+func htmlSubs(doc *Document, el *element) []byte {
+ var (
+ input = el.raw
+ )
+ if el.applySubs == 0 {
+ return input
+ }
+ if el.applySubs&passSubChar != 0 {
+ input = htmlSubsChar(input)
+ }
+ if el.applySubs&passSubQuote != 0 {
+ input = htmlSubsQuote(input)
+ }
+ if el.applySubs&passSubAttr != 0 {
+ input = htmlSubsAttr(doc, input)
+ }
+ if el.applySubs&passSubRepl != 0 {
+ input = htmlSubsRepl(input)
+ }
+ if el.applySubs&passSubMacro != 0 {
+ input = htmlSubsMacro(doc, input, el.kind == elKindInlinePass)
+ }
+ return input
+}
+
+// htmlSubsChar replace character '<', '>', and '&' with "<", ">", and
+// "&".
+//
+// Ref: https://docs.asciidoctor.org/asciidoc/latest/subs/special-characters/
+func htmlSubsChar(input []byte) []byte {
+ var (
+ bb bytes.Buffer
+ c byte
+ )
+ for _, c = range input {
+ if c == '<' {
+ bb.WriteString(`<`)
+ continue
+ }
+ if c == '>' {
+ bb.WriteString(`>`)
+ continue
+ }
+ if c == '&' {
+ bb.WriteString(`&`)
+ continue
+ }
+ bb.WriteByte(c)
+ }
+ return bb.Bytes()
+}
+
+// htmlSubsQuote replace inline markup with its HTML markup.
+// The following inline markup ara parsed and substitutes,
+//
+// - emphasis: _word_ with "<em>word</em>".
+// - strong: *word* with "<strong>word</strong>".
+// - monospace: `word` with "<code>word</code>".
+// - superscript: ^word^ with "<sup>word</sup>".
+// - subscript: ~word~ with "<sub>word</sub>".
+// - double curved quotes: "`word`" with "“word”"
+// - single curved quotes: '`word`' with "‘word’"
+//
+// Ref: https://docs.asciidoctor.org/asciidoc/latest/subs/quotes/
+func htmlSubsQuote(input []byte) []byte {
+ var (
+ bb bytes.Buffer
+ x int
+ idx int
+ text []byte
+ c1 byte
+ nextc byte
+ )
+ for x < len(input) {
+ c1 = input[x]
+
+ x++
+ if x == len(input) {
+ // Nothing left to parsed.
+ bb.WriteByte(c1)
+ break
+ }
+ nextc = input[x]
+
+ if c1 == '_' {
+ text, idx = indexByteUnescape(input[x:], c1)
+ if text == nil {
+ bb.WriteByte(c1)
+ continue
+ }
+ bb.WriteString(`<em>`)
+ bb.Write(text)
+ bb.WriteString(`</em>`)
+ x = x + idx + 1
+ continue
+ }
+ if c1 == '*' {
+ text, idx = indexByteUnescape(input[x:], c1)
+ if text == nil {
+ bb.WriteByte(c1)
+ continue
+ }
+ bb.WriteString(`<strong>`)
+ bb.Write(text)
+ bb.WriteString(`</strong>`)
+ x = x + idx + 1
+ continue
+ }
+ if c1 == '`' {
+ text, idx = indexByteUnescape(input[x:], c1)
+ if text == nil {
+ bb.WriteByte(c1)
+ continue
+ }
+ bb.WriteString(`<code>`)
+ bb.Write(text)
+ bb.WriteString(`</code>`)
+ x = x + idx + 1
+ continue
+ }
+ if c1 == '^' {
+ text, idx = indexByteUnescape(input[x:], c1)
+ if text == nil {
+ bb.WriteByte(c1)
+ continue
+ }
+ bb.WriteString(`<sup>`)
+ bb.Write(text)
+ bb.WriteString(`</sup>`)
+ x = x + idx + 1
+ continue
+ }
+ if c1 == '~' {
+ text, idx = indexByteUnescape(input[x:], c1)
+ if text == nil {
+ bb.WriteByte(c1)
+ continue
+ }
+ bb.WriteString(`<sub>`)
+ bb.Write(text)
+ bb.WriteString(`</sub>`)
+ x = x + idx + 1
+ continue
+ }
+ if c1 == '"' {
+ if nextc != '`' {
+ bb.WriteByte(c1)
+ continue
+ }
+ if x+1 == len(input) {
+ bb.WriteByte(c1)
+ continue
+ }
+
+ text, idx = indexUnescape(input[x+1:], []byte("`\""))
+ if text == nil {
+ bb.WriteByte(c1)
+ continue
+ }
+ bb.WriteString(htmlSymbolLeftDoubleQuote)
+ bb.Write(text)
+ bb.WriteString(htmlSymbolRightDoubleQuote)
+ x = x + idx + 3
+ continue
+ }
+ if c1 == '\'' {
+ if nextc != '`' {
+ bb.WriteByte(c1)
+ continue
+ }
+ if x+1 == len(input) {
+ bb.WriteByte(c1)
+ continue
+ }
+
+ text, idx = indexUnescape(input[x+1:], []byte("`'"))
+ if text == nil {
+ bb.WriteByte(c1)
+ continue
+ }
+ bb.WriteString(htmlSymbolLeftSingleQuote)
+ bb.Write(text)
+ bb.WriteString(htmlSymbolRightSingleQuote)
+ x = x + idx + 3
+ continue
+ }
+ bb.WriteByte(c1)
+ }
+ return bb.Bytes()
+}
+
+// htmlSubsAttr replace attribute (the `{...}`) with its values.
+//
+// Ref: https://docs.asciidoctor.org/asciidoc/latest/subs/attributes/
+func htmlSubsAttr(doc *Document, input []byte) []byte {
+ var (
+ bb bytes.Buffer
+ key string
+ val string
+ vbytes []byte
+ idx int
+ x int
+ c byte
+ ok bool
+ )
+
+ for x < len(input) {
+ c = input[x]
+ x++
+ if c != '{' {
+ bb.WriteByte(c)
+ continue
+ }
+
+ vbytes, idx = indexByteUnescape(input[x:], '}')
+ if vbytes == nil {
+ bb.WriteByte(c)
+ continue
+ }
+ vbytes = bytes.TrimSpace(vbytes)
+ vbytes = bytes.ToLower(vbytes)
+
+ key = string(vbytes)
+ val, ok = _attrRef[key]
+ if ok {
+ bb.WriteString(val)
+ x = x + idx + 1
+ continue
+ }
+
+ val, ok = doc.Attributes[key]
+ if !ok {
+ bb.WriteByte(c)
+ continue
+ }
+
+ // Add prefix "mailto:" if the ref name start with email, so
+ // it can be parsed by caller as macro link.
+ if key == `email` || strings.HasPrefix(key, `email_`) {
+ val = `mailto:` + val + `[` + val + `]`
+ }
+
+ bb.WriteString(val)
+ x = x + idx + 1
+ }
+
+ return bb.Bytes()
+}
+
+// htmlSubsRepl substitutes special characters with HTML unicode.
+//
+// The special characters are,
+//
+// - (C) replaced with ©
+// - (R) : ®
+// - (TM) : ™
+// - -- : — Only replaced if between two word characters, between a
+// word character and a line boundary, or flanked by spaces.
+// When flanked by space characters (e.g., a -- b), the normal spaces are
+// replaced by thin spaces ( ).
+// - ... : …
+// - -> : →
+// - => : ⇒
+// - <- : ←
+// - <= : ⇐
+// - ' : ’
+//
+// According to [the documentation], this substitution step also recognizes
+// HTML and XML character references as well as decimal and hexadecimal
+// Unicode code points, but we only cover the above right now.
+//
+// [the documentation]: https://docs.asciidoctor.org/asciidoc/latest/subs/replacements/
+func htmlSubsRepl(input []byte) (out []byte) {
+ var (
+ text []byte
+ x int
+ idx int
+ c1 byte
+ nextc byte
+ prevc byte
+ )
+
+ out = make([]byte, 0, len(input))
+
+ for x < len(input) {
+ prevc = c1
+ c1 = input[x]
+
+ x++
+ if x == len(input) {
+ out = append(out, c1)
+ break
+ }
+ nextc = input[x]
+
+ if c1 == '(' {
+ text, idx = indexByteUnescape(input[x:], ')')
+ if len(text) == 1 {
+ if text[0] == 'C' {
+ out = append(out, []byte(htmlSymbolCopyright)...)
+ x = x + idx + 1
+ c1 = ')'
+ continue
+ }
+ if text[0] == 'R' {
+ out = append(out, []byte(htmlSymbolRegistered)...)
+ x = x + idx + 1
+ c1 = ')'
+ continue
+ }
+ } else if len(text) == 2 {
+ if text[0] == 'T' && text[1] == 'M' {
+ out = append(out, []byte(htmlSymbolTrademark)...)
+ x = x + idx + 1
+ c1 = ')'
+ continue
+ }
+ }
+
+ out = append(out, c1)
+ continue
+ }
+ if c1 == '-' {
+ if nextc == '>' {
+ out = append(out, []byte(htmlSymbolSingleRightArrow)...)
+ x++
+ c1 = nextc
+ continue
+ }
+ if nextc == '-' {
+ if x+1 >= len(input) {
+ out = append(out, c1)
+ continue
+ }
+ // set c1 to the third character after '--'.
+ c1 = input[x+1]
+ if libascii.IsSpace(prevc) && libascii.IsSpace(c1) {
+ out = out[:len(out)-1]
+ out = append(out, []byte(htmlSymbolThinSpace)...)
+ out = append(out, []byte(htmlSymbolEmdash)...)
+ out = append(out, []byte(htmlSymbolThinSpace)...)
+ x += 2
+ continue
+ }
+ if libascii.IsAlpha(prevc) && libascii.IsAlpha(c1) {
+ out = append(out, []byte(htmlSymbolEmdash)...)
+ x++
+ continue
+ }
+ }
+ out = append(out, c1)
+ continue
+ }
+ if c1 == '=' {
+ if nextc == '>' {
+ out = append(out, []byte(htmlSymbolDoubleRightArrow)...)
+ x++
+ c1 = nextc
+ continue
+ }
+ out = append(out, c1)
+ continue
+ }
+ if c1 == '<' {
+ if nextc == '-' {
+ out = append(out, []byte(htmlSymbolSingleLeftArrow)...)
+ x++
+ continue
+ }
+ if nextc == '=' {
+ out = append(out, []byte(htmlSymbolDoubleLeftArrow)...)
+ x++
+ continue
+ }
+ out = append(out, c1)
+ continue
+ }
+ if c1 == '.' {
+ if nextc != '.' {
+ out = append(out, c1)
+ continue
+ }
+ if x+1 >= len(input) {
+ out = append(out, c1)
+ continue
+ }
+ // Set c1 to the third character.
+ c1 = input[x+1]
+ if c1 == '.' {
+ out = append(out, []byte(htmlSymbolEllipsis)...)
+ x += 2
+ continue
+ }
+ out = append(out, c1)
+ continue
+ }
+ if c1 == '\'' {
+ if libascii.IsAlpha(prevc) {
+ out = append(out, []byte(htmlSymbolApostrophe)...)
+ continue
+ }
+ out = append(out, c1)
+ continue
+ }
+ out = append(out, c1)
+ }
+ return out
+}
+
+// htmlSubsMacro substitutes macro with its HTML markup.
+func htmlSubsMacro(doc *Document, input []byte, isInlinePass bool) (out []byte) {
+ var (
+ el *element
+ bb bytes.Buffer
+ macroName string
+ x int
+ n int
+ c byte
+ )
+
+ for x < len(input) {
+ c = input[x]
+ if c != ':' {
+ out = append(out, c)
+ x++
+ continue
+ }
+
+ macroName = parseMacroName(input[:x])
+ if len(macroName) == 0 {
+ out = append(out, c)
+ x++
+ continue
+ }
+
+ switch macroName {
+ case macroFootnote:
+ el, n = parseMacroFootnote(doc, input[x+1:])
+ if el == nil {
+ out = append(out, c)
+ x++
+ continue
+ }
+ x += n
+ n = len(out)
+ out = out[:n-len(macroName)] // Undo the macro name
+ bb.Reset()
+ htmlWriteFootnote(el, &bb)
+ out = append(out, bb.Bytes()...)
+
+ case macroFTP, macroHTTPS, macroHTTP, macroIRC, macroLink, macroMailto:
+ el, n = parseURL(doc, macroName, input[x+1:])
+ if el == nil {
+ out = append(out, c)
+ x++
+ continue
+ }
+ x += n
+ n = len(out)
+ out = out[:n-len(macroName)]
+ bb.Reset()
+ htmlWriteURLBegin(el, &bb)
+ if el.child != nil {
+ el.child.toHTML(doc, &bb)
+ }
+ htmlWriteURLEnd(&bb)
+ out = append(out, bb.Bytes()...)
+
+ case macroImage:
+ el, n = parseInlineImage(doc, input[x+1:])
+ if el == nil {
+ out = append(out, c)
+ x++
+ continue
+ }
+ x += n
+ n = len(out)
+ out = out[:n-len(macroName)]
+ bb.Reset()
+ htmlWriteInlineImage(el, &bb)
+ out = append(out, bb.Bytes()...)
+
+ case macroPass:
+ if isInlinePass {
+ // Prevent recursive substitutions.
+ out = append(out, c)
+ x++
+ continue
+ }
+ el, n = parseMacroPass(input[x+1:])
+ if el == nil {
+ out = append(out, c)
+ x++
+ continue
+ }
+ x += n
+ n = len(out)
+ out = out[:n-len(macroName)]
+ bb.Reset()
+ htmlWriteInlinePass(doc, el, &bb)
+ out = append(out, bb.Bytes()...)
+
+ default:
+ out = append(out, c)
+ x++
+ }
+ }
+ return out
+}
+
func htmlWriteBlockBegin(el *element, out io.Writer, addClass string) {
fmt.Fprint(out, "\n<div")
@@ 560,6 1074,13 @@ func htmlWriteInlineImage(el *element, out io.Writer) {
fmt.Fprint(out, `</span>`)
}
+func htmlWriteInlinePass(doc *Document, el *element, out io.Writer) {
+ var (
+ text []byte = htmlSubs(doc, el)
+ )
+ fmt.Fprint(out, string(text))
+}
+
func htmlWriteListDescription(el *element, out io.Writer) {
var openTag string
if el.isStyleQandA() {
M inline_parser.go => inline_parser.go +9 -3
@@ 743,6 743,14 @@ func (pi *inlineParser) parseMacro() bool {
}
pi.x += n
pi.prev = 0
+
+ case macroPass:
+ el, n = parseMacroPass(pi.content[pi.x+1:])
+ if el == nil {
+ return false
+ }
+ pi.x += n
+ pi.prev = 0
}
pi.current.raw = pi.current.raw[:len(pi.current.raw)-len(name)]
@@ 934,8 942,6 @@ func (pi *inlineParser) parseSuperscript() bool {
// parseURL parser the URL, an optional text, optional attribute for target,
// and optional role.
-//
-// The current state of p.x is equal to ":".
func parseURL(doc *Document, scheme string, content []byte) (el *element, n int) {
var (
x int
@@ 1066,7 1072,7 @@ func (pi *inlineParser) terminate(kind int, style int64) {
// indexByteUnescape find the index of the first unescaped byte `c` on
// slice of byte `in`.
-// It will return nil and -1 if no unescape byte `c` found.
+// It will return nil and -1 if no unescaped byte `c` found.
func indexByteUnescape(in []byte, c byte) (out []byte, idx int) {
var (
x int
M inline_parser_test.go => inline_parser_test.go +48 -0
@@ 6,6 6,7 @@ package asciidoctor
import (
"bytes"
"fmt"
+ "strings"
"testing"
"github.com/shuLhan/share/lib/test"
@@ 132,3 133,50 @@ func TestInlineParser_macro_footnote(t *testing.T) {
got.Reset()
}
}
+
+func TestInlineParser_macro_pass(t *testing.T) {
+ var (
+ testFiles = []string{
+ `testdata/inline_parser/macro_pass_none_test.txt`,
+ `testdata/inline_parser/macro_pass_c_test.txt`,
+ `testdata/inline_parser/macro_pass_q_test.txt`,
+ `testdata/inline_parser/macro_pass_a_test.txt`,
+ `testdata/inline_parser/macro_pass_r_test.txt`,
+ `testdata/inline_parser/macro_pass_m_test.txt`,
+ }
+
+ testFile string
+ inputName string
+ outputName string
+ got bytes.Buffer
+ tdata *test.Data
+ doc *Document
+ exp []byte
+ err error
+ )
+
+ for _, testFile = range testFiles {
+ tdata, err = test.LoadData(testFile)
+ if err != nil {
+ t.Fatalf(`%s: %s`, testFile, err)
+ }
+
+ for inputName = range tdata.Input {
+ t.Logf(`%s: %s`, testFile, inputName)
+
+ outputName = strings.Replace(inputName, `.adoc`, `.html`, 1)
+
+ doc = Parse(tdata.Input[inputName])
+
+ got.Reset()
+ err = doc.ToHTMLEmbedded(&got)
+ if err != nil {
+ t.Fatalf(`%s: %s`, inputName, err)
+ }
+
+ exp = tdata.Output[outputName]
+
+ test.Assert(t, inputName, string(exp), got.String())
+ }
+ }
+}
M macro.go => macro.go +70 -1
@@ 9,6 9,7 @@ import (
"github.com/shuLhan/share/lib/ascii"
)
+// List of macro names.
const (
macroFTP = `ftp`
macroFootnote = `footnote`
@@ 18,6 19,7 @@ const (
macroImage = `image`
macroLink = `link`
macroMailto = `mailto`
+ macroPass = `pass`
)
var (
@@ 30,6 32,7 @@ var (
macroImage: elKindInlineImage,
macroLink: elKindURL,
macroMailto: elKindURL,
+ macroPass: elKindText,
}
)
@@ 44,7 47,7 @@ type macro struct {
// val represent the text for URL or image and footnote.
rawContent []byte
- // level represent footnoted index number.
+ // level represent footnote index number.
level int
}
@@ 158,3 161,69 @@ func parseMacroFootnote(doc *Document, text []byte) (el *element, n int) {
return el, n
}
+
+// parseMacroPass parse the macro for passthrough.
+//
+// "pass:" *(SUB) "[" TEXT "]"
+//
+// SUB = SUB_KIND *("," SUB_KIND)
+//
+// SUB_KIND = "c" / "q" / "a" / "r" / "m" / "p" / "n" / "v"
+func parseMacroPass(text []byte) (el *element, n int) {
+ var (
+ x int
+ c byte
+ )
+
+ el = &element{
+ kind: elKindInlinePass,
+ }
+
+ // Consume the substitutions until "[" or spaces.
+ // Spaces automatically stop the process.
+ // Other characters except the sub kinds are ignored.
+ for ; x < len(text); x++ {
+ c = text[x]
+ if c == '[' {
+ break
+ }
+ if c == ',' {
+ continue
+ }
+ if ascii.IsSpace(c) {
+ return nil, 0
+ }
+ switch c {
+ case 'c':
+ el.applySubs |= passSubChar
+ case 'q':
+ el.applySubs |= passSubQuote
+ case 'a':
+ el.applySubs |= passSubAttr
+ case 'r':
+ el.applySubs |= passSubRepl
+ case 'm':
+ el.applySubs |= passSubMacro
+ case 'p':
+ el.applySubs |= passSubPostRepl
+ case 'n':
+ el.applySubs |= passSubNormal
+ case 'v':
+ el.applySubs |= passSubChar
+ }
+ }
+ if c != '[' {
+ return nil, 0
+ }
+ x++
+ n = x
+
+ el.raw, x = parseClosedBracket(text[x:], '[', ']')
+ if x < 0 {
+ return nil, 0
+ }
+
+ n += x + 2
+
+ return el, n
+}
M parser.go => parser.go +62 -0
@@ 40,6 40,7 @@ const (
elKindInlineID // "[[" REF_ID "]]" TEXT
elKindInlineIDShort // "[#" REF_ID "]#" TEXT "#"
elKindInlineImage // Inline macro for "image:"
+ elKindInlinePass // Inline macro for passthrough "pass:"
elKindInlineParagraph //
elKindListOrdered // Wrapper.
elKindListOrderedItem // 30: Line start with ". "
@@ 637,6 638,67 @@ func parseAttrRef(doc *Document, content []byte, x int) (newContent []byte, ok b
return newContent, true
}
+// parseClosedBracket parse the text in input until we found the last close
+// bracket.
+// It will skip any open-close brackets inside input.
+// For example, parsing ("test:[]]", '[', ']') will return ("test:[]", 7).
+//
+// If no closed bracket found it will return (nil, -1).
+func parseClosedBracket(input []byte, openb, closedb byte) (out []byte, idx int) {
+ var (
+ openCount int
+ c byte
+ isEsc bool
+ )
+
+ out = make([]byte, 0, len(input))
+
+ for idx, c = range input {
+ if c == '\\' {
+ if isEsc {
+ out = append(out, '\\')
+ isEsc = false
+ } else {
+ isEsc = true
+ }
+ continue
+ }
+
+ if c == closedb {
+ if isEsc {
+ out = append(out, c)
+ isEsc = false
+ continue
+ }
+ if openCount == 0 {
+ return out, idx
+ }
+ openCount--
+ out = append(out, c)
+ continue
+ }
+
+ if c == openb {
+ out = append(out, c)
+ if isEsc {
+ isEsc = false
+ } else {
+ openCount++
+ }
+ continue
+ }
+
+ if isEsc {
+ out = append(out, '\\')
+ isEsc = false
+ }
+ out = append(out, c)
+ }
+
+ // No closed bracket found.
+ return nil, -1
+}
+
// parseIDLabel parse the string "ID (,LABEL)" into ID and label.
// It will return empty id and label if ID is not valid.
func parseIDLabel(s []byte) (id, label []byte) {
M parser_test.go => parser_test.go +45 -0
@@ 87,6 87,51 @@ func TestGenerateID(t *testing.T) {
}
}
+func TestParseClosedBracket(t *testing.T) {
+ type testCase struct {
+ input string
+ expOut string
+ expIdx int
+ }
+
+ var cases = []testCase{{
+ input: `test:[]] input`,
+ expOut: `test:[]`,
+ expIdx: 7,
+ }, {
+ input: `[test:[]]] input`,
+ expOut: `[test:[]]`,
+ expIdx: 9,
+ }, {
+ input: `[test:[]] input`,
+ expOut: ``,
+ expIdx: -1,
+ }, {
+ input: `test:\[\]] input`,
+ expOut: `test:[]`,
+ expIdx: 9,
+ }, {
+ input: `test:\x\]] input`,
+ expOut: `test:\x]`,
+ expIdx: 9,
+ }}
+
+ var (
+ c testCase
+ got []byte
+ gotIdx int
+ )
+
+ for _, c = range cases {
+ t.Logf(`input: %s`, c.input)
+
+ got, gotIdx = parseClosedBracket([]byte(c.input), '[', ']')
+
+ test.Assert(t, `got`, c.expOut, string(got))
+ test.Assert(t, `got index`, c.expIdx, gotIdx)
+ }
+}
+
func TestIsValidID(t *testing.T) {
type testCase struct {
id string
A testdata/inline_parser/macro_pass_a_test.txt => testdata/inline_parser/macro_pass_a_test.txt +13 -0
@@ 0,0 1,13 @@
+Test macro pass with attribute substitutions only.
+
+>>> pass_a.adoc
+:meta-a: meta A
+:meta-b: meta B
+
+pass:a[attributes: {meta-A}, {meta-b}, and {meta-not_exist}].
+
+<<< pass_a.html
+
+<div class="paragraph">
+<p>attributes: meta A, meta B, and {meta-not_exist}.</p>
+</div>
A testdata/inline_parser/macro_pass_c_test.txt => testdata/inline_parser/macro_pass_c_test.txt +16 -0
@@ 0,0 1,16 @@
+Test macro pass with special character substitutions only.
+
+>>> pass_c.adoc
+
+pass:c[char: < > &].
+
+pass:c[replacement: (C) (R) (TM) -- ... -> => <- <= user's input].
+
+<<< pass_c.html
+
+<div class="paragraph">
+<p>char: < > &.</p>
+</div>
+<div class="paragraph">
+<p>replacement: (C) (R) (TM) -- ... -> => <- <= user's input.</p>
+</div>
A testdata/inline_parser/macro_pass_m_test.txt => testdata/inline_parser/macro_pass_m_test.txt +32 -0
@@ 0,0 1,32 @@
+Test macro pass with macro only.
+
+>>> pass_m.adoc
+
+pass:m[Text with footnote:id[footnote]].
+
+pass:m[Text with http://127.0.0.1[HTTP URL]].
+
+pass:m[Text with image:test.jpg[image]].
+
+pass:m[Text with pass:[_none_] and pass:c[<_char_>]].
+
+<<< pass_m.html
+
+<div class="paragraph">
+<p>Text with <sup class="footnote" id="_footnote_id">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>.</p>
+</div>
+<div class="paragraph">
+<p>Text with <a href="http://127.0.0.1">HTTP URL</a>.</p>
+</div>
+<div class="paragraph">
+<p>Text with <span class="image"><img src="test.jpg" alt="image"></span>.</p>
+</div>
+<div class="paragraph">
+<p>Text with pass:[_none_] and pass:c[<_char_>].</p>
+</div>
+<div id="footnotes">
+<hr>
+<div class="footnote" id="_footnotedef_1">
+<a href="#_footnoteref_1">1</a>. footnote
+</div>
+</div>
A testdata/inline_parser/macro_pass_none_test.txt => testdata/inline_parser/macro_pass_none_test.txt +29 -0
@@ 0,0 1,29 @@
+
+>>> pass_none.adoc
+
+pass:[char: < > &].
+
+pass:[quote: _emphasis_, *strong*],
+pass:[`monospace`, ^superscript^, ~subscript~],
+pass:["`double curved quotes`", and '`single curved quotes`'].
+
+pass:[attributes: {meta-A}, {meta-b}, and {meta-not_exist}].
+
+pass:[replacement: (C) (R) (TM) -- ... -> => <- <= user's input].
+
+<<< pass_none.html
+
+<div class="paragraph">
+<p>char: < > &.</p>
+</div>
+<div class="paragraph">
+<p>quote: _emphasis_, *strong*,
+`monospace`, ^superscript^, ~subscript~,
+"`double curved quotes`", and '`single curved quotes`'.</p>
+</div>
+<div class="paragraph">
+<p>attributes: {meta-A}, {meta-b}, and {meta-not_exist}.</p>
+</div>
+<div class="paragraph">
+<p>replacement: (C) (R) (TM) -- ... -> => <- <= user's input.</p>
+</div>
A testdata/inline_parser/macro_pass_q_test.txt => testdata/inline_parser/macro_pass_q_test.txt +15 -0
@@ 0,0 1,15 @@
+Test macro pass with inline markup substitutions only.
+
+>>> pass_q.adoc
+
+pass:q[quote: _emphasis_, *strong*],
+pass:q[`monospace`, ^superscript^, ~subscript~],
+pass:q["`double curved quotes`", and '`single curved quotes`'].
+
+<<< pass_q.html
+
+<div class="paragraph">
+<p>quote: <em>emphasis</em>, <strong>strong</strong>,
+<code>monospace</code>, <sup>superscript</sup>, <sub>subscript</sub>,
+“double curved quotes”, and ‘single curved quotes’.</p>
+</div>
A testdata/inline_parser/macro_pass_r_test.txt => testdata/inline_parser/macro_pass_r_test.txt +18 -0
@@ 0,0 1,18 @@
+Test macro pass with special replacements only.
+
+>>> pass_r.adoc
+
+pass:r[char: < > &].
+
+pass:r[replacement: (C) (R) (TM) -- ...]
+pass:r[-> => <- <= user's input].
+
+<<< pass_r.html
+
+<div class="paragraph">
+<p>char: < > &.</p>
+</div>
+<div class="paragraph">
+<p>replacement: © ® ™ — …
+→ ⇒ ← ⇐ user’s input.</p>
+</div>