diff options
| -rw-r--r-- | _doc/SPECS.adoc | 2 | ||||
| -rw-r--r-- | document_parser.go | 78 | ||||
| -rw-r--r-- | document_test.go | 47 | ||||
| -rw-r--r-- | parser.go | 46 | ||||
| -rw-r--r-- | testdata/html/header.adoc | 12 | ||||
| -rw-r--r-- | testdata/html/header.exp.html | 34 | ||||
| -rwxr-xr-x | testdata/html/to-html.sh | 5 |
7 files changed, 175 insertions, 49 deletions
diff --git a/_doc/SPECS.adoc b/_doc/SPECS.adoc index 6a5244f..7bfae81 100644 --- a/_doc/SPECS.adoc +++ b/_doc/SPECS.adoc @@ -128,7 +128,7 @@ DOC_REV_DATE = 1*2DIGIT 3*ALPHA 4*DIGIT There are also metadata which affect how the document rendered, ---- -DOC_ATTRIBUTE = ":" DOC_ATTR_KEY ":" *STRING LF +DOC_ATTRIBUTE = ":" DOC_ATTR_KEY ":" *LINE ("\" *LINE) LF DOC_ATTR_KEY = ( "toc" / "sectanchors" / "sectlinks" / "imagesdir" / "data-uri" / *META_KEY ) LF diff --git a/document_parser.go b/document_parser.go index e00dd81..1117b99 100644 --- a/document_parser.go +++ b/document_parser.go @@ -6,7 +6,9 @@ package asciidoctor import ( "bytes" "fmt" + "io" + "github.com/shuLhan/share/lib/ascii" "github.com/shuLhan/share/lib/debug" ) @@ -196,6 +198,78 @@ func (docp *documentParser) line(logp string) (spaces, line []byte, ok bool) { return spaces, line, true } +// parseAttribute parse document attribute and return its key and optional +// value. +func (docp *documentParser) parseAttribute(line []byte, strict bool) (key, value string, ok bool) { + var ( + bb bytes.Buffer + p int + x int + ) + + if !(ascii.IsAlnum(line[1]) || line[1] == '_') { + return ``, ``, false + } + + bb.WriteByte(line[1]) + x = 2 + for ; x < len(line); x++ { + if line[x] == ':' { + break + } + if ascii.IsAlnum(line[x]) || line[x] == '_' || + line[x] == '-' || line[x] == '!' { + bb.WriteByte(line[x]) + continue + } + if strict { + return ``, ``, false + } + } + if x == len(line) { + return ``, ``, false + } + + key = bb.String() + + line = line[x+1:] + p = len(line) + if p > 0 && line[p-1] == '\\' { + bb.Reset() + line = line[:p-1] + bb.Write(line) + docp.parseMultiline(&bb) + line = bb.Bytes() + } + + line = bytes.TrimSpace(line) + value = string(line) + + return key, value, true +} + +// parseMultiline multiline value where each line end with `\`. +func (docp *documentParser) parseMultiline(out io.Writer) { + var ( + isMultiline = true + + line []byte + p int + ) + for isMultiline && docp.lineNum < len(docp.lines) { + line = docp.lines[docp.lineNum] + p = len(line) + + if line[p-1] == '\\' { + line = line[:p-1] + } else { + isMultiline = false + } + _, _ = out.Write(line) + docp.lineNum++ + } +} + func (docp *documentParser) parseBlock(parent *element, term int) { var ( logp = `parseBlock` @@ -297,7 +371,7 @@ func (docp *documentParser) parseBlock(parent *element, term int) { case lineKindAttribute: var ( - key, value, ok = parseAttribute(line, false) + key, value, ok = docp.parseAttribute(line, false) ) if ok { if key == attrNameIcons { @@ -635,7 +709,7 @@ func (docp *documentParser) parseHeader() { continue } if line[0] == ':' { - key, value, ok = parseAttribute(line, false) + key, value, ok = docp.parseAttribute(line, false) if ok { docp.doc.Attributes.apply(key, value) } diff --git a/document_test.go b/document_test.go index a6f60e7..42e9b03 100644 --- a/document_test.go +++ b/document_test.go @@ -4,6 +4,7 @@ package asciidoctor import ( + "bytes" "os" "testing" @@ -94,3 +95,49 @@ func TestParse_document_title(t *testing.T) { test.Assert(t, `String`, c.expString, got.Title.String()) } } + +func TestDocument_ToHTML(t *testing.T) { + type testCase struct { + name string + fileAdoc string + fileExpHtml string + } + + var ( + cases = []testCase{{ + name: `header`, + fileAdoc: `testdata/html/header.adoc`, + fileExpHtml: `testdata/html/header.exp.html`, + }} + + c testCase + doc *Document + err error + got bytes.Buffer + exp []byte + ) + + for _, c = range cases { + doc, err = Open(c.fileAdoc) + if err != nil { + t.Fatal(err) + } + + // Since we cannot overwrite the asciidoctor output for + // generator, we override ourself. + doc.Attributes[MetaNameGenerator] = `Asciidoctor 2.0.18` + + got.Reset() + + err = doc.ToHTML(&got) + if err != nil { + t.Fatal(err) + } + + exp, err = os.ReadFile(c.fileExpHtml) + if err != nil { + t.Fatal(err) + } + test.Assert(t, c.name, string(exp), got.String()) + } +} @@ -553,52 +553,6 @@ func isValidID(id []byte) bool { return true } -// parseAttribute parse document attribute and return its key and optional -// value. -// -// DOC_ATTRIBUTE = ":" DOC_ATTR_KEY ":" *STRING LF -// -// DOC_ATTR_KEY = ( "toc" / "sectanchors" / "sectlinks" -// / "imagesdir" / "data-uri" / *META_KEY ) LF -// -// META_KEY_CHAR = (A..Z | a..z | 0..9 | '_') -// -// META_KEY = 1META_KEY_CHAR *(META_KEY_CHAR | '-') -func parseAttribute(line []byte, strict bool) (key, value string, ok bool) { - var ( - sb strings.Builder - valb []byte - x int - ) - - if !(ascii.IsAlnum(line[1]) || line[1] == '_') { - return ``, ``, false - } - - sb.WriteByte(line[1]) - x = 2 - for ; x < len(line); x++ { - if line[x] == ':' { - break - } - if ascii.IsAlnum(line[x]) || line[x] == '_' || - line[x] == '-' || line[x] == '!' { - sb.WriteByte(line[x]) - continue - } - if strict { - return ``, ``, false - } - } - if x == len(line) { - return ``, ``, false - } - - valb = bytes.TrimSpace(line[x+1:]) - - return sb.String(), string(valb), true -} - // parseAttrRef parse the attribute reference, an attribute key wrapped by // "{" "}". If the attribute reference exist, replace the content with the // attribute value and reset the parser state to zero. diff --git a/testdata/html/header.adoc b/testdata/html/header.adoc new file mode 100644 index 0000000..d807033 --- /dev/null +++ b/testdata/html/header.adoc @@ -0,0 +1,12 @@ += Title +John Doe <john@doe.tld> +v1.0, 15 Dec 2022 +:description: Multiline \ +description \ +with backslash +:keywords: multiline, \ +key, \ +words +:last-update-label!: + +Document body. diff --git a/testdata/html/header.exp.html b/testdata/html/header.exp.html new file mode 100644 index 0000000..54ebd62 --- /dev/null +++ b/testdata/html/header.exp.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="UTF-8"> +<meta http-equiv="X-UA-Compatible" content="IE=edge"> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<meta name="generator" content="Asciidoctor 2.0.18"> +<meta name="description" content="Multiline description with backslash"> +<meta name="keywords" content="multiline, key, words"> +<meta name="author" content="John Doe"> +<title>Title</title> +</head> +<body class="article"> +<div id="header"> +<h1>Title</h1> +<div class="details"> +<span id="author" class="author">John Doe</span><br> +<span id="email" class="email"><a href="mailto:john@doe.tld">john@doe.tld</a></span><br> +<span id="revnumber">version 1.0,</span> +<span id="revdate">15 Dec 2022</span> +</div> +</div> +<div id="content"> +<div class="paragraph"> +<p>Document body.</p> +</div> +</div> +<div id="footer"> +<div id="footer-text"> +Version 1.0<br> +</div> +</div> +</body> +</html>
\ No newline at end of file diff --git a/testdata/html/to-html.sh b/testdata/html/to-html.sh new file mode 100755 index 0000000..7d45b15 --- /dev/null +++ b/testdata/html/to-html.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +## Script to convert all adoc files to HTML without stylesheet. + +asciidoctor -a stylesheet! -o header.exp.html header.adoc |
