Update to go-org 1.3.2 (#12728)

* Update to go-org 1.3.2

Fix #12727

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Fix unit test

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
mj-v1.14.3
zeripath 4 years ago committed by GitHub
parent e80eda7d01
commit 9fdb4f887b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -27,6 +27,7 @@ require (
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dlclark/regexp2 v1.2.1 // indirect
github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize v1.0.0
github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 github.com/editorconfig/editorconfig-core-go/v2 v2.1.1
github.com/emirpasic/gods v1.12.0 github.com/emirpasic/gods v1.12.0
@ -73,7 +74,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5
github.com/niklasfasching/go-org v0.1.9 github.com/niklasfasching/go-org v1.3.2
github.com/oliamb/cutter v0.2.2 github.com/oliamb/cutter v0.2.2
github.com/olivere/elastic/v7 v7.0.9 github.com/olivere/elastic/v7 v7.0.9
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
@ -99,7 +100,7 @@ require (
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
golang.org/x/net v0.0.0-20200707034311-ab3426394381 golang.org/x/net v0.0.0-20200904194848-62affa334b73
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae
golang.org/x/text v0.3.3 golang.org/x/text v0.3.3

@ -194,6 +194,8 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.2.1 h1:Ff/S0snjr1oZHUNOkvA/gP6KUaMg5vDDl3Qnhjnwgm8=
github.com/dlclark/regexp2 v1.2.1/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
@ -705,8 +707,8 @@ github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/R
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/niklasfasching/go-org v0.1.9 h1:Toz8WMIt+qJb52uYEk1YD/muLuOOmRt1CfkV+bKVMkI= github.com/niklasfasching/go-org v1.3.2 h1:ZKTSd+GdJYkoZl1pBXLR/k7DRiRXnmB96TRiHmHdzwI=
github.com/niklasfasching/go-org v0.1.9/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU= github.com/niklasfasching/go-org v1.3.2/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
@ -1010,6 +1012,8 @@ golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=

@ -27,12 +27,12 @@ func TestRender_StandardLinks(t *testing.T) {
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
} }
googleRendered := "<p>\n<a href=\"https://google.com/\" title=\"https://google.com/\">https://google.com/</a>\n</p>" googleRendered := "<p><a href=\"https://google.com/\" title=\"https://google.com/\">https://google.com/</a></p>"
test("[[https://google.com/]]", googleRendered) test("[[https://google.com/]]", googleRendered)
lnk := util.URLJoin(AppSubURL, "WikiPage") lnk := util.URLJoin(AppSubURL, "WikiPage")
test("[[WikiPage][WikiPage]]", test("[[WikiPage][WikiPage]]",
"<p>\n<a href=\""+lnk+"\" title=\"WikiPage\">WikiPage</a>\n</p>") "<p><a href=\""+lnk+"\" title=\"WikiPage\">WikiPage</a></p>")
} }
func TestRender_Images(t *testing.T) { func TestRender_Images(t *testing.T) {
@ -48,5 +48,5 @@ func TestRender_Images(t *testing.T) {
result := util.URLJoin(AppSubURL, url) result := util.URLJoin(AppSubURL, url)
test("[[file:"+url+"]]", test("[[file:"+url+"]]",
"<p>\n<img src=\""+result+"\" alt=\""+result+"\" title=\""+result+"\" />\n</p>") "<p><img src=\""+result+"\" alt=\""+result+"\" title=\""+result+"\" /></p>")
} }

@ -1,5 +1,5 @@
language: go language: go
go: go:
- 1.5 - 1.9
- tip - tip

@ -235,17 +235,14 @@ func (re *Regexp) getRunesAndStart(s string, startAt int) ([]rune, int) {
ret[i] = r ret[i] = r
i++ i++
} }
if startAt == len(s) {
runeIdx = i
}
return ret[:i], runeIdx return ret[:i], runeIdx
} }
func getRunes(s string) []rune { func getRunes(s string) []rune {
ret := make([]rune, len(s)) return []rune(s)
i := 0
for _, r := range s {
ret[i] = r
i++
}
return ret[:i]
} }
// MatchRunes return true if the runes matches the regex // MatchRunes return true if the runes matches the regex

@ -1250,10 +1250,10 @@ func (p *parser) scanBasicBackslash(scanOnly bool) (*regexNode, error) {
return nil, nil return nil, nil
} }
if p.useOptionE() || p.isCaptureSlot(capnum) { if p.isCaptureSlot(capnum) {
return newRegexNodeM(ntRef, p.options, capnum), nil return newRegexNodeM(ntRef, p.options, capnum), nil
} }
if capnum <= 9 { if capnum <= 9 && !p.useOptionE() {
return nil, p.getErr(ErrUndefinedBackRef, capnum) return nil, p.getErr(ErrUndefinedBackRef, capnum)
} }
@ -1808,11 +1808,11 @@ func (p *parser) scanOctal() rune {
i := 0 i := 0
d := int(p.rightChar(0) - '0') d := int(p.rightChar(0) - '0')
for c > 0 && d <= 7 { for c > 0 && d <= 7 {
i *= 8 if i >= 0x20 && p.useOptionE() {
i += d
if p.useOptionE() && i >= 0x20 {
break break
} }
i *= 8
i += d
c-- c--
p.moveRight(1) p.moveRight(1)

@ -10,6 +10,11 @@ type Block struct {
Name string Name string
Parameters []string Parameters []string
Children []Node Children []Node
Result Node
}
type Result struct {
Node Node
} }
type Example struct { type Example struct {
@ -19,6 +24,8 @@ type Example struct {
var exampleLineRegexp = regexp.MustCompile(`^(\s*):(\s(.*)|\s*$)`) var exampleLineRegexp = regexp.MustCompile(`^(\s*):(\s(.*)|\s*$)`)
var beginBlockRegexp = regexp.MustCompile(`(?i)^(\s*)#\+BEGIN_(\w+)(.*)`) var beginBlockRegexp = regexp.MustCompile(`(?i)^(\s*)#\+BEGIN_(\w+)(.*)`)
var endBlockRegexp = regexp.MustCompile(`(?i)^(\s*)#\+END_(\w+)`) var endBlockRegexp = regexp.MustCompile(`(?i)^(\s*)#\+END_(\w+)`)
var resultRegexp = regexp.MustCompile(`(?i)^(\s*)#\+RESULTS:`)
var exampleBlockEscapeRegexp = regexp.MustCompile(`(^|\n)([ \t]*),([ \t]*)(\*|,\*|#\+|,#\+)`)
func lexBlock(line string) (token, bool) { func lexBlock(line string) (token, bool) {
if m := beginBlockRegexp.FindStringSubmatch(line); m != nil { if m := beginBlockRegexp.FindStringSubmatch(line); m != nil {
@ -29,6 +36,13 @@ func lexBlock(line string) (token, bool) {
return nilToken, false return nilToken, false
} }
func lexResult(line string) (token, bool) {
if m := resultRegexp.FindStringSubmatch(line); m != nil {
return token{"result", len(m[1]), "", m}, true
}
return nilToken, false
}
func lexExample(line string) (token, bool) { func lexExample(line string) (token, bool) {
if m := exampleLineRegexp.FindStringSubmatch(line); m != nil { if m := exampleLineRegexp.FindStringSubmatch(line); m != nil {
return token{"example", len(m[1]), m[3], m}, true return token{"example", len(m[1]), m[3], m}, true
@ -45,22 +59,41 @@ func (d *Document) parseBlock(i int, parentStop stopFn) (int, Node) {
stop := func(d *Document, i int) bool { stop := func(d *Document, i int) bool {
return i >= len(d.tokens) || (d.tokens[i].kind == "endBlock" && d.tokens[i].content == name) return i >= len(d.tokens) || (d.tokens[i].kind == "endBlock" && d.tokens[i].content == name)
} }
block, i := Block{name, parameters, nil}, i+1 block, i := Block{name, parameters, nil, nil}, i+1
if isRawTextBlock(name) { if isRawTextBlock(name) {
rawText := "" rawText := ""
for ; !stop(d, i); i++ { for ; !stop(d, i); i++ {
rawText += trim(d.tokens[i].matches[0]) + "\n" rawText += trim(d.tokens[i].matches[0]) + "\n"
} }
if name == "EXAMPLE" || (name == "SRC" && len(parameters) >= 1 && parameters[0] == "org") {
rawText = exampleBlockEscapeRegexp.ReplaceAllString(rawText, "$1$2$3$4")
}
block.Children = d.parseRawInline(rawText) block.Children = d.parseRawInline(rawText)
} else { } else {
consumed, nodes := d.parseMany(i, stop) consumed, nodes := d.parseMany(i, stop)
block.Children = nodes block.Children = nodes
i += consumed i += consumed
} }
if i < len(d.tokens) && d.tokens[i].kind == "endBlock" && d.tokens[i].content == name { if i >= len(d.tokens) || d.tokens[i].kind != "endBlock" || d.tokens[i].content != name {
return i + 1 - start, block return 0, nil
}
if name == "SRC" {
consumed, result := d.parseSrcBlockResult(i+1, parentStop)
block.Result = result
i += consumed
}
return i + 1 - start, block
}
func (d *Document) parseSrcBlockResult(i int, parentStop stopFn) (int, Node) {
start := i
for ; !parentStop(d, i) && d.tokens[i].kind == "text" && d.tokens[i].content == ""; i++ {
}
if parentStop(d, i) || d.tokens[i].kind != "result" {
return 0, nil
} }
return 0, nil consumed, result := d.parseResult(i, parentStop)
return (i - start) + consumed, result
} }
func (d *Document) parseExample(i int, parentStop stopFn) (int, Node) { func (d *Document) parseExample(i int, parentStop stopFn) (int, Node) {
@ -71,6 +104,14 @@ func (d *Document) parseExample(i int, parentStop stopFn) (int, Node) {
return i - start, example return i - start, example
} }
func (d *Document) parseResult(i int, parentStop stopFn) (int, Node) {
if i+1 >= len(d.tokens) {
return 0, nil
}
consumed, node := d.parseOne(i+1, parentStop)
return consumed + 1, Result{node}
}
func trimIndentUpTo(max int) func(string) string { func trimIndentUpTo(max int) func(string) string {
return func(line string) string { return func(line string) string {
i := 0 i := 0
@ -80,5 +121,17 @@ func trimIndentUpTo(max int) func(string) string {
} }
} }
func (b Block) ParameterMap() map[string]string {
if len(b.Parameters) == 0 {
return nil
}
m := map[string]string{":lang": b.Parameters[0]}
for i := 1; i+1 < len(b.Parameters); i += 2 {
m[b.Parameters[i]] = b.Parameters[i+1]
}
return m
}
func (n Example) String() string { return orgWriter.WriteNodesAsString(n) } func (n Example) String() string { return orgWriter.WriteNodesAsString(n) }
func (n Block) String() string { return orgWriter.WriteNodesAsString(n) } func (n Block) String() string { return orgWriter.WriteNodesAsString(n) }
func (n Result) String() string { return orgWriter.WriteNodesAsString(n) }

@ -36,6 +36,8 @@ type Document struct {
Path string // Path of the file containing the parse input - used to resolve relative paths during parsing (e.g. INCLUDE). Path string // Path of the file containing the parse input - used to resolve relative paths during parsing (e.g. INCLUDE).
tokens []token tokens []token
baseLvl int baseLvl int
Macros map[string]string
Links map[string]string
Nodes []Node Nodes []Node
NamedNodes map[string]Node NamedNodes map[string]Node
Outline Outline // Outline is a Table Of Contents for the document and contains all sections (headline + content). Outline Outline // Outline is a Table Of Contents for the document and contains all sections (headline + content).
@ -63,6 +65,7 @@ var lexFns = []lexFn{
lexHeadline, lexHeadline,
lexDrawer, lexDrawer,
lexBlock, lexBlock,
lexResult,
lexList, lexList,
lexTable, lexTable,
lexHorizontalRule, lexHorizontalRule,
@ -83,7 +86,7 @@ func New() *Configuration {
DefaultSettings: map[string]string{ DefaultSettings: map[string]string{
"TODO": "TODO | DONE", "TODO": "TODO | DONE",
"EXCLUDE_TAGS": "noexport", "EXCLUDE_TAGS": "noexport",
"OPTIONS": "toc:t <:t e:t f:t pri:t todo:t tags:t", "OPTIONS": "toc:t <:t e:t f:t pri:t todo:t tags:t title:t",
}, },
Log: log.New(os.Stderr, "go-org: ", 0), Log: log.New(os.Stderr, "go-org: ", 0),
ReadFile: ioutil.ReadFile, ReadFile: ioutil.ReadFile,
@ -120,6 +123,8 @@ func (c *Configuration) Parse(input io.Reader, path string) (d *Document) {
Outline: Outline{outlineSection, outlineSection, 0}, Outline: Outline{outlineSection, outlineSection, 0},
BufferSettings: map[string]string{}, BufferSettings: map[string]string{},
NamedNodes: map[string]Node{}, NamedNodes: map[string]Node{},
Links: map[string]string{},
Macros: map[string]string{},
Path: path, Path: path,
} }
defer func() { defer func() {
@ -169,12 +174,13 @@ func (d *Document) Get(key string) string {
// - < (export timestamps) // - < (export timestamps)
// - e (export org entities) // - e (export org entities)
// - f (export footnotes) // - f (export footnotes)
// - toc (export table of content) // - title (export title)
// - toc (export table of content. an int limits the included org headline lvl)
// - todo (export headline todo status) // - todo (export headline todo status)
// - pri (export headline priority) // - pri (export headline priority)
// - tags (export headline tags) // - tags (export headline tags)
// see https://orgmode.org/manual/Export-settings.html for more information // see https://orgmode.org/manual/Export-settings.html for more information
func (d *Document) GetOption(key string) bool { func (d *Document) GetOption(key string) string {
get := func(settings map[string]string) string { get := func(settings map[string]string) string {
for _, field := range strings.Fields(settings["OPTIONS"]) { for _, field := range strings.Fields(settings["OPTIONS"]) {
if strings.HasPrefix(field, key+":") { if strings.HasPrefix(field, key+":") {
@ -187,15 +193,11 @@ func (d *Document) GetOption(key string) bool {
if value == "" { if value == "" {
value = get(d.DefaultSettings) value = get(d.DefaultSettings)
} }
switch value { if value == "" {
case "t": value = "nil"
return true d.Log.Printf("Missing value for export option %s", key)
case "nil":
return false
default:
d.Log.Printf("Bad value for export option %s (%s)", key, value)
return false
} }
return value
} }
func (d *Document) parseOne(i int, stop stopFn) (consumed int, node Node) { func (d *Document) parseOne(i int, stop stopFn) (consumed int, node Node) {
@ -206,6 +208,8 @@ func (d *Document) parseOne(i int, stop stopFn) (consumed int, node Node) {
consumed, node = d.parseTable(i, stop) consumed, node = d.parseTable(i, stop)
case "beginBlock": case "beginBlock":
consumed, node = d.parseBlock(i, stop) consumed, node = d.parseBlock(i, stop)
case "result":
consumed, node = d.parseResult(i, stop)
case "beginDrawer": case "beginDrawer":
consumed, node = d.parseDrawer(i, stop) consumed, node = d.parseDrawer(i, stop)
case "text": case "text":

@ -35,14 +35,14 @@ var tagRegexp = regexp.MustCompile(`(.*?)\s+(:[A-Za-z0-9_@#%:]+:\s*$)`)
func lexHeadline(line string) (token, bool) { func lexHeadline(line string) (token, bool) {
if m := headlineRegexp.FindStringSubmatch(line); m != nil { if m := headlineRegexp.FindStringSubmatch(line); m != nil {
return token{"headline", len(m[1]), m[2], m}, true return token{"headline", 0, m[2], m}, true
} }
return nilToken, false return nilToken, false
} }
func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) { func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) {
t, headline := d.tokens[i], Headline{} t, headline := d.tokens[i], Headline{}
headline.Lvl = t.lvl headline.Lvl = len(t.matches[1])
headline.Index = d.addHeadline(&headline) headline.Index = d.addHeadline(&headline)
@ -69,7 +69,7 @@ func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) {
headline.Title = d.parseInline(text) headline.Title = d.parseInline(text)
stop := func(d *Document, i int) bool { stop := func(d *Document, i int) bool {
return parentStop(d, i) || d.tokens[i].kind == "headline" && d.tokens[i].lvl <= headline.Lvl return parentStop(d, i) || d.tokens[i].kind == "headline" && len(d.tokens[i].matches[1]) <= headline.Lvl
} }
consumed, nodes := d.parseMany(i+1, stop) consumed, nodes := d.parseMany(i+1, stop)
if len(nodes) > 0 { if len(nodes) > 0 {

@ -5,6 +5,7 @@ import (
"html" "html"
"log" "log"
"regexp" "regexp"
"strconv"
"strings" "strings"
"unicode" "unicode"
@ -15,7 +16,7 @@ import (
// HTMLWriter exports an org document into a html document. // HTMLWriter exports an org document into a html document.
type HTMLWriter struct { type HTMLWriter struct {
ExtendingWriter Writer ExtendingWriter Writer
HighlightCodeBlock func(source, lang string) string HighlightCodeBlock func(source, lang string, inline bool) string
strings.Builder strings.Builder
document *Document document *Document
@ -60,7 +61,10 @@ func NewHTMLWriter() *HTMLWriter {
document: &Document{Configuration: defaultConfig}, document: &Document{Configuration: defaultConfig},
log: defaultConfig.Log, log: defaultConfig.Log,
htmlEscape: true, htmlEscape: true,
HighlightCodeBlock: func(source, lang string) string { HighlightCodeBlock: func(source, lang string, inline bool) string {
if inline {
return fmt.Sprintf("<div class=\"highlight-inline\">\n<pre>\n%s\n</pre>\n</div>", html.EscapeString(source))
}
return fmt.Sprintf("<div class=\"highlight\">\n<pre>\n%s\n</pre>\n</div>", html.EscapeString(source)) return fmt.Sprintf("<div class=\"highlight\">\n<pre>\n%s\n</pre>\n</div>", html.EscapeString(source))
}, },
footnotes: &footnotes{ footnotes: &footnotes{
@ -88,7 +92,11 @@ func (w *HTMLWriter) WriterWithExtensions() Writer {
func (w *HTMLWriter) Before(d *Document) { func (w *HTMLWriter) Before(d *Document) {
w.document = d w.document = d
w.log = d.Log w.log = d.Log
if title := d.Get("TITLE"); title != "" { if title := d.Get("TITLE"); title != "" && w.document.GetOption("title") != "nil" {
titleDocument := d.Parse(strings.NewReader(title), d.Path)
if titleDocument.Error == nil {
title = w.WriteNodesAsString(titleDocument.Nodes...)
}
w.WriteString(fmt.Sprintf(`<h1 class="title">%s</h1>`+"\n", title)) w.WriteString(fmt.Sprintf(`<h1 class="title">%s</h1>`+"\n", title))
} }
w.WriteOutline(d) w.WriteOutline(d)
@ -102,38 +110,54 @@ func (w *HTMLWriter) WriteComment(Comment) {}
func (w *HTMLWriter) WritePropertyDrawer(PropertyDrawer) {} func (w *HTMLWriter) WritePropertyDrawer(PropertyDrawer) {}
func (w *HTMLWriter) WriteBlock(b Block) { func (w *HTMLWriter) WriteBlock(b Block) {
content := "" content, params := w.blockContent(b.Name, b.Children), b.ParameterMap()
if isRawTextBlock(b.Name) {
builder, htmlEscape := w.Builder, w.htmlEscape switch b.Name {
w.Builder, w.htmlEscape = strings.Builder{}, false case "SRC":
WriteNodes(w, b.Children...) if params[":exports"] == "results" || params[":exports"] == "none" {
out := w.String() break
w.Builder, w.htmlEscape = builder, htmlEscape }
content = strings.TrimRightFunc(out, unicode.IsSpace)
} else {
content = w.WriteNodesAsString(b.Children...)
}
switch name := b.Name; {
case name == "SRC":
lang := "text" lang := "text"
if len(b.Parameters) >= 1 { if len(b.Parameters) >= 1 {
lang = strings.ToLower(b.Parameters[0]) lang = strings.ToLower(b.Parameters[0])
} }
content = w.HighlightCodeBlock(content, lang) content = w.HighlightCodeBlock(content, lang, false)
w.WriteString(fmt.Sprintf("<div class=\"src src-%s\">\n%s\n</div>\n", lang, content)) w.WriteString(fmt.Sprintf("<div class=\"src src-%s\">\n%s\n</div>\n", lang, content))
case name == "EXAMPLE": case "EXAMPLE":
w.WriteString(`<pre class="example">` + "\n" + content + "\n</pre>\n") w.WriteString(`<pre class="example">` + "\n" + html.EscapeString(content) + "\n</pre>\n")
case name == "EXPORT" && len(b.Parameters) >= 1 && strings.ToLower(b.Parameters[0]) == "html": case "EXPORT":
w.WriteString(content + "\n") if len(b.Parameters) >= 1 && strings.ToLower(b.Parameters[0]) == "html" {
case name == "QUOTE": w.WriteString(content + "\n")
}
case "QUOTE":
w.WriteString("<blockquote>\n" + content + "</blockquote>\n") w.WriteString("<blockquote>\n" + content + "</blockquote>\n")
case name == "CENTER": case "CENTER":
w.WriteString(`<div class="center-block" style="text-align: center; margin-left: auto; margin-right: auto;">` + "\n") w.WriteString(`<div class="center-block" style="text-align: center; margin-left: auto; margin-right: auto;">` + "\n")
w.WriteString(content + "</div>\n") w.WriteString(content + "</div>\n")
default: default:
w.WriteString(fmt.Sprintf(`<div class="%s-block">`, strings.ToLower(b.Name)) + "\n") w.WriteString(fmt.Sprintf(`<div class="%s-block">`, strings.ToLower(b.Name)) + "\n")
w.WriteString(content + "</div>\n") w.WriteString(content + "</div>\n")
} }
if b.Result != nil && params[":exports"] != "code" && params[":exports"] != "none" {
WriteNodes(w, b.Result)
}
}
func (w *HTMLWriter) WriteResult(r Result) { WriteNodes(w, r.Node) }
func (w *HTMLWriter) WriteInlineBlock(b InlineBlock) {
content := w.blockContent(strings.ToUpper(b.Name), b.Children)
switch b.Name {
case "src":
lang := strings.ToLower(b.Parameters[0])
content = w.HighlightCodeBlock(content, lang, true)
w.WriteString(fmt.Sprintf("<div class=\"src src-inline src-%s\">\n%s\n</div>", lang, content))
case "export":
if strings.ToLower(b.Parameters[0]) == "html" {
w.WriteString(content)
}
}
} }
func (w *HTMLWriter) WriteDrawer(d Drawer) { func (w *HTMLWriter) WriteDrawer(d Drawer) {
@ -155,7 +179,7 @@ func (w *HTMLWriter) WriteFootnoteDefinition(f FootnoteDefinition) {
} }
func (w *HTMLWriter) WriteFootnotes(d *Document) { func (w *HTMLWriter) WriteFootnotes(d *Document) {
if !w.document.GetOption("f") || len(w.footnotes.list) == 0 { if w.document.GetOption("f") == "nil" || len(w.footnotes.list) == 0 {
return return
} }
w.WriteString(`<div class="footnotes">` + "\n") w.WriteString(`<div class="footnotes">` + "\n")
@ -183,25 +207,33 @@ func (w *HTMLWriter) WriteFootnotes(d *Document) {
} }
func (w *HTMLWriter) WriteOutline(d *Document) { func (w *HTMLWriter) WriteOutline(d *Document) {
if w.document.GetOption("toc") && len(d.Outline.Children) != 0 { if w.document.GetOption("toc") != "nil" && len(d.Outline.Children) != 0 {
maxLvl, _ := strconv.Atoi(w.document.GetOption("toc"))
w.WriteString("<nav>\n<ul>\n") w.WriteString("<nav>\n<ul>\n")
for _, section := range d.Outline.Children { for _, section := range d.Outline.Children {
w.writeSection(section) w.writeSection(section, maxLvl)
} }
w.WriteString("</ul>\n</nav>\n") w.WriteString("</ul>\n</nav>\n")
} }
} }
func (w *HTMLWriter) writeSection(section *Section) { func (w *HTMLWriter) writeSection(section *Section, maxLvl int) {
if maxLvl != 0 && section.Headline.Lvl > maxLvl {
return
}
// NOTE: To satisfy hugo ExtractTOC() check we cannot use `<li>\n` here. Doesn't really matter, just a note. // NOTE: To satisfy hugo ExtractTOC() check we cannot use `<li>\n` here. Doesn't really matter, just a note.
w.WriteString("<li>") w.WriteString("<li>")
h := section.Headline h := section.Headline
title := cleanHeadlineTitleForHTMLAnchorRegexp.ReplaceAllString(w.WriteNodesAsString(h.Title...), "") title := cleanHeadlineTitleForHTMLAnchorRegexp.ReplaceAllString(w.WriteNodesAsString(h.Title...), "")
w.WriteString(fmt.Sprintf("<a href=\"#%s\">%s</a>\n", h.ID(), title)) w.WriteString(fmt.Sprintf("<a href=\"#%s\">%s</a>\n", h.ID(), title))
if len(section.Children) != 0 { hasChildren := false
for _, section := range section.Children {
hasChildren = hasChildren || maxLvl == 0 || section.Headline.Lvl <= maxLvl
}
if hasChildren {
w.WriteString("<ul>\n") w.WriteString("<ul>\n")
for _, section := range section.Children { for _, section := range section.Children {
w.writeSection(section) w.writeSection(section, maxLvl)
} }
w.WriteString("</ul>\n") w.WriteString("</ul>\n")
} }
@ -217,16 +249,17 @@ func (w *HTMLWriter) WriteHeadline(h Headline) {
} }
} }
w.WriteString(fmt.Sprintf(`<div id="outline-container-%s" class="outline-%d">`, h.ID(), h.Lvl+1) + "\n")
w.WriteString(fmt.Sprintf(`<h%d id="%s">`, h.Lvl+1, h.ID()) + "\n") w.WriteString(fmt.Sprintf(`<h%d id="%s">`, h.Lvl+1, h.ID()) + "\n")
if w.document.GetOption("todo") && h.Status != "" { if w.document.GetOption("todo") != "nil" && h.Status != "" {
w.WriteString(fmt.Sprintf(`<span class="todo">%s</span>`, h.Status) + "\n") w.WriteString(fmt.Sprintf(`<span class="todo">%s</span>`, h.Status) + "\n")
} }
if w.document.GetOption("pri") && h.Priority != "" { if w.document.GetOption("pri") != "nil" && h.Priority != "" {
w.WriteString(fmt.Sprintf(`<span class="priority">[%s]</span>`, h.Priority) + "\n") w.WriteString(fmt.Sprintf(`<span class="priority">[%s]</span>`, h.Priority) + "\n")
} }
WriteNodes(w, h.Title...) WriteNodes(w, h.Title...)
if w.document.GetOption("tags") && len(h.Tags) != 0 { if w.document.GetOption("tags") != "nil" && len(h.Tags) != 0 {
tags := make([]string, len(h.Tags)) tags := make([]string, len(h.Tags))
for i, tag := range h.Tags { for i, tag := range h.Tags {
tags[i] = fmt.Sprintf(`<span>%s</span>`, tag) tags[i] = fmt.Sprintf(`<span>%s</span>`, tag)
@ -235,13 +268,16 @@ func (w *HTMLWriter) WriteHeadline(h Headline) {
w.WriteString(fmt.Sprintf(`<span class="tags">%s</span>`, strings.Join(tags, "&#xa0;"))) w.WriteString(fmt.Sprintf(`<span class="tags">%s</span>`, strings.Join(tags, "&#xa0;")))
} }
w.WriteString(fmt.Sprintf("\n</h%d>\n", h.Lvl+1)) w.WriteString(fmt.Sprintf("\n</h%d>\n", h.Lvl+1))
WriteNodes(w, h.Children...) if content := w.WriteNodesAsString(h.Children...); content != "" {
w.WriteString(fmt.Sprintf(`<div id="outline-text-%s" class="outline-text-%d">`, h.ID(), h.Lvl+1) + "\n" + content + "</div>\n")
}
w.WriteString("</div>\n")
} }
func (w *HTMLWriter) WriteText(t Text) { func (w *HTMLWriter) WriteText(t Text) {
if !w.htmlEscape { if !w.htmlEscape {
w.WriteString(t.Content) w.WriteString(t.Content)
} else if !w.document.GetOption("e") || t.IsRaw { } else if w.document.GetOption("e") == "nil" || t.IsRaw {
w.WriteString(html.EscapeString(t.Content)) w.WriteString(html.EscapeString(t.Content))
} else { } else {
w.WriteString(html.EscapeString(htmlEntityReplacer.Replace(t.Content))) w.WriteString(html.EscapeString(htmlEntityReplacer.Replace(t.Content)))
@ -277,7 +313,7 @@ func (w *HTMLWriter) WriteExplicitLineBreak(l ExplicitLineBreak) {
} }
func (w *HTMLWriter) WriteFootnoteLink(l FootnoteLink) { func (w *HTMLWriter) WriteFootnoteLink(l FootnoteLink) {
if !w.document.GetOption("f") { if w.document.GetOption("f") == "nil" {
return return
} }
i := w.footnotes.add(l) i := w.footnotes.add(l)
@ -286,7 +322,7 @@ func (w *HTMLWriter) WriteFootnoteLink(l FootnoteLink) {
} }
func (w *HTMLWriter) WriteTimestamp(t Timestamp) { func (w *HTMLWriter) WriteTimestamp(t Timestamp) {
if !w.document.GetOption("<") { if w.document.GetOption("<") == "nil" {
return return
} }
w.WriteString(`<span class="timestamp">&lt;`) w.WriteString(`<span class="timestamp">&lt;`)
@ -306,20 +342,46 @@ func (w *HTMLWriter) WriteRegularLink(l RegularLink) {
if l.Protocol == "file" { if l.Protocol == "file" {
url = url[len("file:"):] url = url[len("file:"):]
} }
description := url if prefix := w.document.Links[l.Protocol]; prefix != "" {
if l.Description != nil { url = html.EscapeString(prefix) + strings.TrimPrefix(url, l.Protocol+":")
description = w.WriteNodesAsString(l.Description...)
} }
switch l.Kind() { switch l.Kind() {
case "image": case "image":
w.WriteString(fmt.Sprintf(`<img src="%s" alt="%s" title="%s" />`, url, description, description)) if l.Description == nil {
w.WriteString(fmt.Sprintf(`<img src="%s" alt="%s" title="%s" />`, url, url, url))
} else {
description := strings.TrimPrefix(String(l.Description), "file:")
w.WriteString(fmt.Sprintf(`<a href="%s"><img src="%s" alt="%s" /></a>`, url, description, description))
}
case "video": case "video":
w.WriteString(fmt.Sprintf(`<video src="%s" title="%s">%s</video>`, url, description, description)) if l.Description == nil {
w.WriteString(fmt.Sprintf(`<video src="%s" title="%s">%s</video>`, url, url, url))
} else {
description := strings.TrimPrefix(String(l.Description), "file:")
w.WriteString(fmt.Sprintf(`<a href="%s"><video src="%s" title="%s"></video></a>`, url, description, description))
}
default: default:
description := url
if l.Description != nil {
description = w.WriteNodesAsString(l.Description...)
}
w.WriteString(fmt.Sprintf(`<a href="%s">%s</a>`, url, description)) w.WriteString(fmt.Sprintf(`<a href="%s">%s</a>`, url, description))
} }
} }
func (w *HTMLWriter) WriteMacro(m Macro) {
if macro := w.document.Macros[m.Name]; macro != "" {
for i, param := range m.Parameters {
macro = strings.Replace(macro, fmt.Sprintf("$%d", i+1), param, -1)
}
macroDocument := w.document.Parse(strings.NewReader(macro), w.document.Path)
if macroDocument.Error != nil {
w.log.Printf("bad macro: %s -> %s: %v", m.Name, macro, macroDocument.Error)
}
WriteNodes(w, macroDocument.Nodes...)
}
}
func (w *HTMLWriter) WriteList(l List) { func (w *HTMLWriter) WriteList(l List) {
tags, ok := listTags[l.Kind] tags, ok := listTags[l.Kind]
if !ok { if !ok {
@ -363,11 +425,8 @@ func (w *HTMLWriter) WriteParagraph(p Paragraph) {
return return
} }
w.WriteString("<p>") w.WriteString("<p>")
if _, ok := p.Children[0].(LineBreak); !ok {
w.WriteString("\n")
}
WriteNodes(w, p.Children...) WriteNodes(w, p.Children...)
w.WriteString("\n</p>\n") w.WriteString("</p>\n")
} }
func (w *HTMLWriter) WriteExample(e Example) { func (w *HTMLWriter) WriteExample(e Example) {
@ -414,23 +473,31 @@ func (w *HTMLWriter) WriteNodeWithName(n NodeWithName) {
func (w *HTMLWriter) WriteTable(t Table) { func (w *HTMLWriter) WriteTable(t Table) {
w.WriteString("<table>\n") w.WriteString("<table>\n")
beforeFirstContentRow := true inHead := len(t.SeparatorIndices) > 0 &&
t.SeparatorIndices[0] != len(t.Rows)-1 &&
(t.SeparatorIndices[0] != 0 || len(t.SeparatorIndices) > 1 && t.SeparatorIndices[len(t.SeparatorIndices)-1] != len(t.Rows)-1)
if inHead {
w.WriteString("<thead>\n")
} else {
w.WriteString("<tbody>\n")
}
for i, row := range t.Rows { for i, row := range t.Rows {
if row.IsSpecial || len(row.Columns) == 0 { if len(row.Columns) == 0 && i != 0 && i != len(t.Rows)-1 {
continue if inHead {
}
if beforeFirstContentRow {
beforeFirstContentRow = false
if i+1 < len(t.Rows) && len(t.Rows[i+1].Columns) == 0 {
w.WriteString("<thead>\n")
w.writeTableColumns(row.Columns, "th")
w.WriteString("</thead>\n<tbody>\n") w.WriteString("</thead>\n<tbody>\n")
continue inHead = false
} else { } else {
w.WriteString("<tbody>\n") w.WriteString("</tbody>\n<tbody>\n")
} }
} }
w.writeTableColumns(row.Columns, "td") if row.IsSpecial {
continue
}
if inHead {
w.writeTableColumns(row.Columns, "th")
} else {
w.writeTableColumns(row.Columns, "td")
}
} }
w.WriteString("</tbody>\n</table>\n") w.WriteString("</tbody>\n</table>\n")
} }
@ -472,6 +539,19 @@ func (w *HTMLWriter) withHTMLAttributes(input string, kvs ...string) string {
return out.String() return out.String()
} }
func (w *HTMLWriter) blockContent(name string, children []Node) string {
if isRawTextBlock(name) {
builder, htmlEscape := w.Builder, w.htmlEscape
w.Builder, w.htmlEscape = strings.Builder{}, false
WriteNodes(w, children...)
out := w.String()
w.Builder, w.htmlEscape = builder, htmlEscape
return strings.TrimRightFunc(out, unicode.IsSpace)
} else {
return w.WriteNodesAsString(children...)
}
}
func setHTMLAttribute(attributes []h.Attribute, k, v string) []h.Attribute { func setHTMLAttribute(attributes []h.Attribute, k, v string) []h.Attribute {
for i, a := range attributes { for i, a := range attributes {
if strings.ToLower(a.Key) == strings.ToLower(k) { if strings.ToLower(a.Key) == strings.ToLower(k) {

@ -30,6 +30,12 @@ type Emphasis struct {
Content []Node Content []Node
} }
type InlineBlock struct {
Name string
Parameters []string
Children []Node
}
type LatexFragment struct { type LatexFragment struct {
OpeningPair string OpeningPair string
ClosingPair string ClosingPair string
@ -48,6 +54,11 @@ type RegularLink struct {
AutoLink bool AutoLink bool
} }
type Macro struct {
Name string
Parameters []string
}
var validURLCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=" var validURLCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;="
var autolinkProtocols = regexp.MustCompile(`^(https?|ftp|file)$`) var autolinkProtocols = regexp.MustCompile(`^(https?|ftp|file)$`)
var imageExtensionRegexp = regexp.MustCompile(`^[.](png|gif|jpe?g|svg|tiff?)$`) var imageExtensionRegexp = regexp.MustCompile(`^[.](png|gif|jpe?g|svg|tiff?)$`)
@ -58,6 +69,9 @@ var timestampRegexp = regexp.MustCompile(`^<(\d{4}-\d{2}-\d{2})( [A-Za-z]+)?( \d
var footnoteRegexp = regexp.MustCompile(`^\[fn:([\w-]*?)(:(.*?))?\]`) var footnoteRegexp = regexp.MustCompile(`^\[fn:([\w-]*?)(:(.*?))?\]`)
var statisticsTokenRegexp = regexp.MustCompile(`^\[(\d+/\d+|\d+%)\]`) var statisticsTokenRegexp = regexp.MustCompile(`^\[(\d+/\d+|\d+%)\]`)
var latexFragmentRegexp = regexp.MustCompile(`(?s)^\\begin{(\w+)}(.*)\\end{(\w+)}`) var latexFragmentRegexp = regexp.MustCompile(`(?s)^\\begin{(\w+)}(.*)\\end{(\w+)}`)
var inlineBlockRegexp = regexp.MustCompile(`src_(\w+)(\[(.*)\])?{(.*)}`)
var inlineExportBlockRegexp = regexp.MustCompile(`@@(\w+):(.*?)@@`)
var macroRegexp = regexp.MustCompile(`{{{(.*)\((.*)\)}}}`)
var timestampFormat = "2006-01-02 Mon 15:04" var timestampFormat = "2006-01-02 Mon 15:04"
var datestampFormat = "2006-01-02 Mon" var datestampFormat = "2006-01-02 Mon"
@ -66,6 +80,7 @@ var latexFragmentPairs = map[string]string{
`\(`: `\)`, `\(`: `\)`,
`\[`: `\]`, `\[`: `\]`,
`$$`: `$$`, `$$`: `$$`,
`$`: `$`,
} }
func (d *Document) parseInline(input string) (nodes []Node) { func (d *Document) parseInline(input string) (nodes []Node) {
@ -76,25 +91,29 @@ func (d *Document) parseInline(input string) (nodes []Node) {
case '^': case '^':
consumed, node = d.parseSubOrSuperScript(input, current) consumed, node = d.parseSubOrSuperScript(input, current)
case '_': case '_':
consumed, node = d.parseSubScriptOrEmphasis(input, current) rewind, consumed, node = d.parseSubScriptOrEmphasisOrInlineBlock(input, current)
case '@':
consumed, node = d.parseInlineExportBlock(input, current)
case '*', '/', '+': case '*', '/', '+':
consumed, node = d.parseEmphasis(input, current, false) consumed, node = d.parseEmphasis(input, current, false)
case '=', '~': case '=', '~':
consumed, node = d.parseEmphasis(input, current, true) consumed, node = d.parseEmphasis(input, current, true)
case '[': case '[':
consumed, node = d.parseOpeningBracket(input, current) consumed, node = d.parseOpeningBracket(input, current)
case '{':
consumed, node = d.parseMacro(input, current)
case '<': case '<':
consumed, node = d.parseTimestamp(input, current) consumed, node = d.parseTimestamp(input, current)
case '\\': case '\\':
consumed, node = d.parseExplicitLineBreakOrLatexFragment(input, current) consumed, node = d.parseExplicitLineBreakOrLatexFragment(input, current)
case '$': case '$':
consumed, node = d.parseLatexFragment(input, current) consumed, node = d.parseLatexFragment(input, current, 1)
case '\n': case '\n':
consumed, node = d.parseLineBreak(input, current) consumed, node = d.parseLineBreak(input, current)
case ':': case ':':
rewind, consumed, node = d.parseAutoLink(input, current) rewind, consumed, node = d.parseAutoLink(input, current)
current -= rewind
} }
current -= rewind
if consumed != 0 { if consumed != 0 {
if current > previous { if current > previous {
nodes = append(nodes, Text{input[previous:current], false}) nodes = append(nodes, Text{input[previous:current], false})
@ -143,6 +162,23 @@ func (d *Document) parseLineBreak(input string, start int) (int, Node) {
return i - start, LineBreak{i - start} return i - start, LineBreak{i - start}
} }
func (d *Document) parseInlineBlock(input string, start int) (int, int, Node) {
if !(strings.HasSuffix(input[:start], "src") && (start-4 < 0 || unicode.IsSpace(rune(input[start-4])))) {
return 0, 0, nil
}
if m := inlineBlockRegexp.FindStringSubmatch(input[start-3:]); m != nil {
return 3, len(m[0]), InlineBlock{"src", strings.Fields(m[1] + " " + m[3]), d.parseRawInline(m[4])}
}
return 0, 0, nil
}
func (d *Document) parseInlineExportBlock(input string, start int) (int, Node) {
if m := inlineExportBlockRegexp.FindStringSubmatch(input[start:]); m != nil {
return len(m[0]), InlineBlock{"export", m[1:2], d.parseRawInline(m[2])}
}
return 0, nil
}
func (d *Document) parseExplicitLineBreakOrLatexFragment(input string, start int) (int, Node) { func (d *Document) parseExplicitLineBreakOrLatexFragment(input string, start int) (int, Node) {
switch { switch {
case start+2 >= len(input): case start+2 >= len(input):
@ -153,7 +189,7 @@ func (d *Document) parseExplicitLineBreakOrLatexFragment(input string, start int
} }
} }
case input[start+1] == '(' || input[start+1] == '[': case input[start+1] == '(' || input[start+1] == '[':
return d.parseLatexFragment(input, start) return d.parseLatexFragment(input, start, 2)
case strings.Index(input[start:], `\begin{`) == 0: case strings.Index(input[start:], `\begin{`) == 0:
if m := latexFragmentRegexp.FindStringSubmatch(input[start:]); m != nil { if m := latexFragmentRegexp.FindStringSubmatch(input[start:]); m != nil {
if open, content, close := m[1], m[2], m[3]; open == close { if open, content, close := m[1], m[2], m[3]; open == close {
@ -166,15 +202,18 @@ func (d *Document) parseExplicitLineBreakOrLatexFragment(input string, start int
return 0, nil return 0, nil
} }
func (d *Document) parseLatexFragment(input string, start int) (int, Node) { func (d *Document) parseLatexFragment(input string, start int, pairLength int) (int, Node) {
if start+2 >= len(input) { if start+2 >= len(input) {
return 0, nil return 0, nil
} }
openingPair := input[start : start+2] if pairLength == 1 && input[start:start+2] == "$$" {
pairLength = 2
}
openingPair := input[start : start+pairLength]
closingPair := latexFragmentPairs[openingPair] closingPair := latexFragmentPairs[openingPair]
if i := strings.Index(input[start+2:], closingPair); i != -1 { if i := strings.Index(input[start+pairLength:], closingPair); i != -1 {
content := d.parseRawInline(input[start+2 : start+2+i]) content := d.parseRawInline(input[start+pairLength : start+pairLength+i])
return i + 2 + 2, LatexFragment{openingPair, closingPair, content} return i + pairLength + pairLength, LatexFragment{openingPair, closingPair, content}
} }
return 0, nil return 0, nil
} }
@ -186,11 +225,14 @@ func (d *Document) parseSubOrSuperScript(input string, start int) (int, Node) {
return 0, nil return 0, nil
} }
func (d *Document) parseSubScriptOrEmphasis(input string, start int) (int, Node) { func (d *Document) parseSubScriptOrEmphasisOrInlineBlock(input string, start int) (int, int, Node) {
if consumed, node := d.parseSubOrSuperScript(input, start); consumed != 0 { if rewind, consumed, node := d.parseInlineBlock(input, start); consumed != 0 {
return consumed, node return rewind, consumed, node
} else if consumed, node := d.parseSubOrSuperScript(input, start); consumed != 0 {
return 0, consumed, node
} }
return d.parseEmphasis(input, start, false) consumed, node := d.parseEmphasis(input, start, false)
return 0, consumed, node
} }
func (d *Document) parseOpeningBracket(input string, start int) (int, Node) { func (d *Document) parseOpeningBracket(input string, start int) (int, Node) {
@ -204,6 +246,13 @@ func (d *Document) parseOpeningBracket(input string, start int) (int, Node) {
return 0, nil return 0, nil
} }
func (d *Document) parseMacro(input string, start int) (int, Node) {
if m := macroRegexp.FindStringSubmatch(input[start:]); m != nil {
return len(m[0]), Macro{m[1], strings.Split(m[2], ",")}
}
return 0, nil
}
func (d *Document) parseFootnoteReference(input string, start int) (int, Node) { func (d *Document) parseFootnoteReference(input string, start int) (int, Node) {
if m := footnoteRegexp.FindStringSubmatch(input[start:]); m != nil { if m := footnoteRegexp.FindStringSubmatch(input[start:]); m != nil {
name, definition := m[1], m[3] name, definition := m[1], m[3]
@ -334,6 +383,14 @@ func isValidPostChar(r rune) bool {
func isValidBorderChar(r rune) bool { return !unicode.IsSpace(r) } func isValidBorderChar(r rune) bool { return !unicode.IsSpace(r) }
func (l RegularLink) Kind() string { func (l RegularLink) Kind() string {
description := String(l.Description)
descProtocol, descExt := strings.SplitN(description, ":", 2)[0], path.Ext(description)
if ok := descProtocol == "file" || descProtocol == "http" || descProtocol == "https"; ok && imageExtensionRegexp.MatchString(descExt) {
return "image"
} else if ok && videoExtensionRegexp.MatchString(descExt) {
return "video"
}
if p := l.Protocol; l.Description != nil || (p != "" && p != "file" && p != "http" && p != "https") { if p := l.Protocol; l.Description != nil || (p != "" && p != "file" && p != "http" && p != "https") {
return "regular" return "regular"
} }
@ -351,7 +408,9 @@ func (n LineBreak) String() string { return orgWriter.WriteNodesAsString
func (n ExplicitLineBreak) String() string { return orgWriter.WriteNodesAsString(n) } func (n ExplicitLineBreak) String() string { return orgWriter.WriteNodesAsString(n) }
func (n StatisticToken) String() string { return orgWriter.WriteNodesAsString(n) } func (n StatisticToken) String() string { return orgWriter.WriteNodesAsString(n) }
func (n Emphasis) String() string { return orgWriter.WriteNodesAsString(n) } func (n Emphasis) String() string { return orgWriter.WriteNodesAsString(n) }
func (n InlineBlock) String() string { return orgWriter.WriteNodesAsString(n) }
func (n LatexFragment) String() string { return orgWriter.WriteNodesAsString(n) } func (n LatexFragment) String() string { return orgWriter.WriteNodesAsString(n) }
func (n FootnoteLink) String() string { return orgWriter.WriteNodesAsString(n) } func (n FootnoteLink) String() string { return orgWriter.WriteNodesAsString(n) }
func (n RegularLink) String() string { return orgWriter.WriteNodesAsString(n) } func (n RegularLink) String() string { return orgWriter.WriteNodesAsString(n) }
func (n Macro) String() string { return orgWriter.WriteNodesAsString(n) }
func (n Timestamp) String() string { return orgWriter.WriteNodesAsString(n) } func (n Timestamp) String() string { return orgWriter.WriteNodesAsString(n) }

@ -35,7 +35,7 @@ type Include struct {
} }
var keywordRegexp = regexp.MustCompile(`^(\s*)#\+([^:]+):(\s+(.*)|$)`) var keywordRegexp = regexp.MustCompile(`^(\s*)#\+([^:]+):(\s+(.*)|$)`)
var commentRegexp = regexp.MustCompile(`^(\s*)#(.*)`) var commentRegexp = regexp.MustCompile(`^(\s*)#\s(.*)`)
var includeFileRegexp = regexp.MustCompile(`(?i)^"([^"]+)" (src|example|export) (\w+)$`) var includeFileRegexp = regexp.MustCompile(`(?i)^"([^"]+)" (src|example|export) (\w+)$`)
var attributeRegexp = regexp.MustCompile(`(?:^|\s+)(:[-\w]+)\s+(.*)$`) var attributeRegexp = regexp.MustCompile(`(?:^|\s+)(:[-\w]+)\s+(.*)$`)
@ -62,6 +62,16 @@ func (d *Document) parseKeyword(i int, stop stopFn) (int, Node) {
return d.loadSetupFile(k) return d.loadSetupFile(k)
case "INCLUDE": case "INCLUDE":
return d.parseInclude(k) return d.parseInclude(k)
case "LINK":
if parts := strings.Split(k.Value, " "); len(parts) >= 2 {
d.Links[parts[0]] = parts[1]
}
return 1, k
case "MACRO":
if parts := strings.Split(k.Value, " "); len(parts) >= 2 {
d.Macros[parts[0]] = parts[1]
}
return 1, k
case "CAPTION", "ATTR_HTML": case "CAPTION", "ATTR_HTML":
consumed, node := d.parseAffiliated(i, stop) consumed, node := d.parseAffiliated(i, stop)
if consumed != 0 { if consumed != 0 {
@ -150,7 +160,7 @@ func (d *Document) parseInclude(k Keyword) (int, Node) {
d.Log.Printf("Bad include %#v: %s", k, err) d.Log.Printf("Bad include %#v: %s", k, err)
return k return k
} }
return Block{strings.ToUpper(kind), []string{lang}, d.parseRawInline(string(bs))} return Block{strings.ToUpper(kind), []string{lang}, d.parseRawInline(string(bs)), nil}
} }
} }
return 1, Include{k, resolve} return 1, Include{k, resolve}

@ -2,6 +2,7 @@ package org
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
@ -16,6 +17,8 @@ type OrgWriter struct {
indent string indent string
} }
var exampleBlockUnescapeRegexp = regexp.MustCompile(`(^|\n)([ \t]*)(\*|,\*|#\+|,#\+)`)
var emphasisOrgBorders = map[string][]string{ var emphasisOrgBorders = map[string][]string{
"_": []string{"_", "_"}, "_": []string{"_", "_"},
"*": []string{"*", "*"}, "*": []string{"*", "*"},
@ -90,11 +93,42 @@ func (w *OrgWriter) WriteBlock(b Block) {
if isRawTextBlock(b.Name) { if isRawTextBlock(b.Name) {
w.WriteString(w.indent) w.WriteString(w.indent)
} }
WriteNodes(w, b.Children...) content := w.WriteNodesAsString(b.Children...)
if b.Name == "EXAMPLE" || (b.Name == "SRC" && len(b.Parameters) >= 1 && b.Parameters[0] == "org") {
content = exampleBlockUnescapeRegexp.ReplaceAllString(content, "$1$2,$3")
}
w.WriteString(content)
if !isRawTextBlock(b.Name) { if !isRawTextBlock(b.Name) {
w.WriteString(w.indent) w.WriteString(w.indent)
} }
w.WriteString("#+END_" + b.Name + "\n") w.WriteString("#+END_" + b.Name + "\n")
if b.Result != nil {
w.WriteString("\n")
WriteNodes(w, b.Result)
}
}
func (w *OrgWriter) WriteResult(r Result) {
w.WriteString("#+RESULTS:\n")
WriteNodes(w, r.Node)
}
func (w *OrgWriter) WriteInlineBlock(b InlineBlock) {
switch b.Name {
case "src":
w.WriteString(b.Name + "_" + b.Parameters[0])
if len(b.Parameters) > 1 {
w.WriteString("[" + strings.Join(b.Parameters[1:], " ") + "]")
}
w.WriteString("{")
WriteNodes(w, b.Children...)
w.WriteString("}")
case "export":
w.WriteString("@@" + b.Parameters[0] + ":")
WriteNodes(w, b.Children...)
w.WriteString("@@")
}
} }
func (w *OrgWriter) WriteDrawer(d Drawer) { func (w *OrgWriter) WriteDrawer(d Drawer) {
@ -173,7 +207,7 @@ func (w *OrgWriter) WriteNodeWithName(n NodeWithName) {
} }
func (w *OrgWriter) WriteComment(c Comment) { func (w *OrgWriter) WriteComment(c Comment) {
w.WriteString(w.indent + "#" + c.Content + "\n") w.WriteString(w.indent + "# " + c.Content + "\n")
} }
func (w *OrgWriter) WriteList(l List) { WriteNodes(w, l.Items...) } func (w *OrgWriter) WriteList(l List) { WriteNodes(w, l.Items...) }
@ -326,3 +360,7 @@ func (w *OrgWriter) WriteRegularLink(l RegularLink) {
w.WriteString(fmt.Sprintf("[[%s][%s]]", l.URL, w.WriteNodesAsString(l.Description...))) w.WriteString(fmt.Sprintf("[[%s][%s]]", l.URL, w.WriteNodesAsString(l.Description...)))
} }
} }
func (w *OrgWriter) WriteMacro(m Macro) {
w.WriteString(fmt.Sprintf("{{{%s(%s)}}}", m.Name, strings.Join(m.Parameters, ",")))
}

@ -8,8 +8,9 @@ import (
) )
type Table struct { type Table struct {
Rows []Row Rows []Row
ColumnInfos []ColumnInfo ColumnInfos []ColumnInfo
SeparatorIndices []int
} }
type Row struct { type Row struct {
@ -23,14 +24,15 @@ type Column struct {
} }
type ColumnInfo struct { type ColumnInfo struct {
Align string Align string
Len int Len int
DisplayLen int
} }
var tableSeparatorRegexp = regexp.MustCompile(`^(\s*)(\|[+-|]*)\s*$`) var tableSeparatorRegexp = regexp.MustCompile(`^(\s*)(\|[+-|]*)\s*$`)
var tableRowRegexp = regexp.MustCompile(`^(\s*)(\|.*)`) var tableRowRegexp = regexp.MustCompile(`^(\s*)(\|.*)`)
var columnAlignRegexp = regexp.MustCompile(`^<(l|c|r)>$`) var columnAlignAndLengthRegexp = regexp.MustCompile(`^<(l|c|r)?(\d+)?>$`)
func lexTable(line string) (token, bool) { func lexTable(line string) (token, bool) {
if m := tableSeparatorRegexp.FindStringSubmatch(line); m != nil { if m := tableSeparatorRegexp.FindStringSubmatch(line); m != nil {
@ -42,7 +44,7 @@ func lexTable(line string) (token, bool) {
} }
func (d *Document) parseTable(i int, parentStop stopFn) (int, Node) { func (d *Document) parseTable(i int, parentStop stopFn) (int, Node) {
rawRows, start := [][]string{}, i rawRows, separatorIndices, start := [][]string{}, []int{}, i
for ; !parentStop(d, i); i++ { for ; !parentStop(d, i); i++ {
if t := d.tokens[i]; t.kind == "tableRow" { if t := d.tokens[i]; t.kind == "tableRow" {
rawRow := strings.FieldsFunc(d.tokens[i].content, func(r rune) bool { return r == '|' }) rawRow := strings.FieldsFunc(d.tokens[i].content, func(r rune) bool { return r == '|' })
@ -51,13 +53,14 @@ func (d *Document) parseTable(i int, parentStop stopFn) (int, Node) {
} }
rawRows = append(rawRows, rawRow) rawRows = append(rawRows, rawRow)
} else if t.kind == "tableSeparator" { } else if t.kind == "tableSeparator" {
separatorIndices = append(separatorIndices, i-start)
rawRows = append(rawRows, nil) rawRows = append(rawRows, nil)
} else { } else {
break break
} }
} }
table := Table{nil, getColumnInfos(rawRows)} table := Table{nil, getColumnInfos(rawRows), separatorIndices}
for _, rawColumns := range rawRows { for _, rawColumns := range rawRows {
row := Row{nil, isSpecialRow(rawColumns)} row := Row{nil, isSpecialRow(rawColumns)}
if len(rawColumns) != 0 { if len(rawColumns) != 0 {
@ -94,7 +97,7 @@ func getColumnInfos(rows [][]string) []ColumnInfo {
columnInfos[i].Len = n columnInfos[i].Len = n
} }
if m := columnAlignRegexp.FindStringSubmatch(columns[i]); m != nil && isSpecialRow(columns) { if m := columnAlignAndLengthRegexp.FindStringSubmatch(columns[i]); m != nil && isSpecialRow(columns) {
switch m[1] { switch m[1] {
case "l": case "l":
columnInfos[i].Align = "left" columnInfos[i].Align = "left"
@ -103,6 +106,10 @@ func getColumnInfos(rows [][]string) []ColumnInfo {
case "r": case "r":
columnInfos[i].Align = "right" columnInfos[i].Align = "right"
} }
if m[2] != "" {
l, _ := strconv.Atoi(m[2])
columnInfos[i].DisplayLen = l
}
} else if _, err := strconv.ParseFloat(columns[i], 32); err == nil { } else if _, err := strconv.ParseFloat(columns[i], 32); err == nil {
countNumeric++ countNumeric++
} else if strings.TrimSpace(columns[i]) != "" { } else if strings.TrimSpace(columns[i]) != "" {
@ -120,7 +127,7 @@ func getColumnInfos(rows [][]string) []ColumnInfo {
func isSpecialRow(rawColumns []string) bool { func isSpecialRow(rawColumns []string) bool {
isAlignRow := true isAlignRow := true
for _, rawColumn := range rawColumns { for _, rawColumn := range rawColumns {
if !columnAlignRegexp.MatchString(rawColumn) && rawColumn != "" { if !columnAlignAndLengthRegexp.MatchString(rawColumn) && rawColumn != "" {
isAlignRow = false isAlignRow = false
} }
} }

@ -18,6 +18,8 @@ type Writer interface {
WriteNodeWithName(NodeWithName) WriteNodeWithName(NodeWithName)
WriteHeadline(Headline) WriteHeadline(Headline)
WriteBlock(Block) WriteBlock(Block)
WriteResult(Result)
WriteInlineBlock(InlineBlock)
WriteExample(Example) WriteExample(Example)
WriteDrawer(Drawer) WriteDrawer(Drawer)
WritePropertyDrawer(PropertyDrawer) WritePropertyDrawer(PropertyDrawer)
@ -34,6 +36,7 @@ type Writer interface {
WriteExplicitLineBreak(ExplicitLineBreak) WriteExplicitLineBreak(ExplicitLineBreak)
WriteLineBreak(LineBreak) WriteLineBreak(LineBreak)
WriteRegularLink(RegularLink) WriteRegularLink(RegularLink)
WriteMacro(Macro)
WriteTimestamp(Timestamp) WriteTimestamp(Timestamp)
WriteFootnoteLink(FootnoteLink) WriteFootnoteLink(FootnoteLink)
WriteFootnoteDefinition(FootnoteDefinition) WriteFootnoteDefinition(FootnoteDefinition)
@ -57,6 +60,10 @@ func WriteNodes(w Writer, nodes ...Node) {
w.WriteHeadline(n) w.WriteHeadline(n)
case Block: case Block:
w.WriteBlock(n) w.WriteBlock(n)
case Result:
w.WriteResult(n)
case InlineBlock:
w.WriteInlineBlock(n)
case Example: case Example:
w.WriteExample(n) w.WriteExample(n)
case Drawer: case Drawer:
@ -89,6 +96,8 @@ func WriteNodes(w Writer, nodes ...Node) {
w.WriteLineBreak(n) w.WriteLineBreak(n)
case RegularLink: case RegularLink:
w.WriteRegularLink(n) w.WriteRegularLink(n)
case Macro:
w.WriteMacro(n)
case Timestamp: case Timestamp:
w.WriteTimestamp(n) w.WriteTimestamp(n)
case FootnoteLink: case FootnoteLink:

File diff suppressed because it is too large Load Diff

@ -201,7 +201,8 @@ github.com/denisenkom/go-mssqldb/internal/querytext
# github.com/dgrijalva/jwt-go v3.2.0+incompatible # github.com/dgrijalva/jwt-go v3.2.0+incompatible
## explicit ## explicit
github.com/dgrijalva/jwt-go github.com/dgrijalva/jwt-go
# github.com/dlclark/regexp2 v1.2.0 # github.com/dlclark/regexp2 v1.2.1
## explicit
github.com/dlclark/regexp2 github.com/dlclark/regexp2
github.com/dlclark/regexp2/syntax github.com/dlclark/regexp2/syntax
# github.com/dsnet/compress v0.0.1 # github.com/dsnet/compress v0.0.1
@ -599,7 +600,7 @@ github.com/msteinert/pam
# github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 # github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5
## explicit ## explicit
github.com/nfnt/resize github.com/nfnt/resize
# github.com/niklasfasching/go-org v0.1.9 # github.com/niklasfasching/go-org v1.3.2
## explicit ## explicit
github.com/niklasfasching/go-org/org github.com/niklasfasching/go-org/org
# github.com/nwaples/rardecode v1.0.0 # github.com/nwaples/rardecode v1.0.0
@ -795,7 +796,7 @@ golang.org/x/crypto/ssh/knownhosts
# golang.org/x/mod v0.3.0 # golang.org/x/mod v0.3.0
golang.org/x/mod/module golang.org/x/mod/module
golang.org/x/mod/semver golang.org/x/mod/semver
# golang.org/x/net v0.0.0-20200707034311-ab3426394381 # golang.org/x/net v0.0.0-20200904194848-62affa334b73
## explicit ## explicit
golang.org/x/net/context golang.org/x/net/context
golang.org/x/net/context/ctxhttp golang.org/x/net/context/ctxhttp

Loading…
Cancel
Save