Add support for headline CUSTOM_ID property & linking

this introduces the PropertyDrawer node to make it easier to access the
properties associated to a headline - normal drawers don't parse their content
into kv pairs
This commit is contained in:
Niklas Fasching 2018-12-19 13:25:12 +01:00
parent ec895cbe83
commit aa42998dbc
8 changed files with 79 additions and 14 deletions

View file

@ -10,8 +10,13 @@ type Drawer struct {
Children []Node
}
type PropertyDrawer struct {
Properties [][]string
}
var beginDrawerRegexp = regexp.MustCompile(`^(\s*):(\S+):\s*$`)
var endDrawerRegexp = regexp.MustCompile(`^(\s*):END:\s*$`)
var propertyRegexp = regexp.MustCompile(`^(\s*):(\S+):(\s+(.*)$|\s*$)`)
func lexDrawer(line string) (token, bool) {
if m := endDrawerRegexp.FindStringSubmatch(line); m != nil {
@ -23,7 +28,11 @@ func lexDrawer(line string) (token, bool) {
}
func (d *Document) parseDrawer(i int, parentStop stopFn) (int, Node) {
drawer, start := Drawer{Name: strings.ToUpper(d.tokens[i].content)}, i
name := strings.ToUpper(d.tokens[i].content)
if name == "PROPERTIES" {
return d.parsePropertyDrawer(i, parentStop)
}
drawer, start := Drawer{Name: name}, i
i++
stop := func(d *Document, i int) bool {
if parentStop(d, i) {
@ -49,3 +58,34 @@ func (d *Document) parseDrawer(i int, parentStop stopFn) (int, Node) {
}
return i - start, drawer
}
func (d *Document) parsePropertyDrawer(i int, parentStop stopFn) (int, Node) {
drawer, start := PropertyDrawer{}, i
i++
stop := func(d *Document, i int) bool {
return parentStop(d, i) || (d.tokens[i].kind != "text" && d.tokens[i].kind != "beginDrawer")
}
for ; !stop(d, i); i++ {
m := propertyRegexp.FindStringSubmatch(d.tokens[i].matches[0])
k, v := strings.ToUpper(m[2]), strings.TrimSpace(m[4])
drawer.Properties = append(drawer.Properties, []string{k, v})
}
if i < len(d.tokens) && d.tokens[i].kind == "endDrawer" {
i++
} else {
return 0, nil
}
return i - start, drawer
}
func (d *PropertyDrawer) Get(key string) (string, bool) {
if d == nil {
return "", false
}
for _, kvPair := range d.Properties {
if kvPair[0] == key {
return kvPair[1], true
}
}
return "", false
}

View file

@ -10,7 +10,7 @@ type Headline struct {
Lvl int
Status string
Priority string
Properties Node
Properties *PropertyDrawer
Title []Node
Tags []string
Children []Node
@ -62,8 +62,8 @@ func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) {
}
consumed, nodes := d.parseMany(i+1, stop)
if len(nodes) > 0 {
if d, ok := nodes[0].(Drawer); ok && d.Name == "PROPERTIES" {
headline.Properties = d
if d, ok := nodes[0].(PropertyDrawer); ok {
headline.Properties = &d
nodes = nodes[1:]
}
}

View file

@ -89,6 +89,8 @@ func (w *HTMLWriter) writeNodes(ns ...Node) {
w.writeBlock(n)
case Drawer:
w.writeDrawer(n)
case PropertyDrawer:
continue
case FootnoteDefinition:
w.writeFootnoteDefinition(n)
@ -207,7 +209,13 @@ func (w *HTMLWriter) writeHeadline(h Headline) {
if h.Lvl == 1 && title == w.FootnotesHeadingTitle {
return
}
w.WriteString(fmt.Sprintf("<h%d>\n", h.Lvl))
if id, ok := h.Properties.Get("CUSTOM_ID"); ok {
w.WriteString(fmt.Sprintf(`<h%d id="%s">`, h.Lvl, id) + "\n")
} else {
w.WriteString(fmt.Sprintf("<h%d>\n", h.Lvl))
}
if h.Status != "" {
w.WriteString(fmt.Sprintf(`<span class="todo">%s</span>`, h.Status) + "\n")
}

View file

@ -63,6 +63,8 @@ func (w *OrgWriter) writeNodes(ns ...Node) {
w.writeBlock(n)
case Drawer:
w.writeDrawer(n)
case PropertyDrawer:
w.writePropertyDrawer(n)
case FootnoteDefinition:
w.writeFootnoteDefinition(n)
@ -132,7 +134,7 @@ func (w *OrgWriter) writeHeadline(h Headline) {
w.WriteString(w.indent)
}
if h.Properties != nil {
w.writeNodes(h.Properties)
w.writeNodes(*h.Properties)
}
w.writeNodes(h.Children...)
}
@ -159,6 +161,18 @@ func (w *OrgWriter) writeDrawer(d Drawer) {
w.WriteString(w.indent + ":END:\n")
}
func (w *OrgWriter) writePropertyDrawer(d PropertyDrawer) {
w.WriteString(":PROPERTIES:\n")
for _, kvPair := range d.Properties {
k, v := kvPair[0], kvPair[1]
if v != "" {
v = " " + v
}
w.WriteString(fmt.Sprintf(":%s:%s\n", k, v))
}
w.WriteString(":END:\n")
}
func (w *OrgWriter) writeFootnoteDefinition(f FootnoteDefinition) {
w.WriteString(fmt.Sprintf("[fn:%s]", f.Name))
if !(len(f.Children) >= 1 && isEmptyLineParagraph(f.Children[0])) {

View file

@ -25,12 +25,12 @@ not just where they are actually meant to be - even here &gt; <code class="stati
<span class="priority">[B]</span>
Headline with todo status &amp; priority
</h1>
<h1>
<h1 id="this-will-be-the-id-of-the-headline">
<span class="todo">DONE</span>
Headline with TODO status
</h1>
<p>
the <strong>content</strong>
we can link to headlines that define a custom_id: <a href="#this-will-be-the-id-of-the-headline">#this-will-be-the-id-of-the-headline</a>
</p>
<h1>
<span class="priority">[A]</span>

View file

@ -7,10 +7,11 @@
* TODO [#B] Headline with todo status & priority
* DONE Headline with TODO status
:PROPERTIES:
:Note: property drawers are not exported as html like other drawers
:custom_id: this-will-be-the-id-of-the-headline
:note: property drawers are not exported as html like other drawers
:END:
the *content*
we can link to headlines that define a custom_id: [[#this-will-be-the-id-of-the-headline]]
* [#A] Headline with tags & priority :foo:bar:
Still outside the drawer
:DRAWERNAME:

View file

@ -7,10 +7,11 @@
* TODO [#B] Headline with todo status & priority
* DONE Headline with TODO status
:PROPERTIES:
:Note: property drawers are not exported as html like other drawers
:CUSTOM_ID: this-will-be-the-id-of-the-headline
:NOTE: property drawers are not exported as html like other drawers
:END:
the *content*
we can link to headlines that define a custom_id: [[#this-will-be-the-id-of-the-headline]]
* [#A] Headline with tags & priority :foo:bar:
Still outside the drawer
:DRAWERNAME:

View file

@ -52,10 +52,11 @@ src block
* TODO [#B] Headline with todo status &amp; priority
* DONE Headline with TODO status
:PROPERTIES:
:Note: property drawers are not exported as html like other drawers
:custom_id: this-will-be-the-id-of-the-headline
:note: property drawers are not exported as html like other drawers
:END:
the *content*
we can link to headlines that define a custom_id: [[#this-will-be-the-id-of-the-headline]]
* [#A] Headline with tags &amp; priority :foo:bar:
Still outside the drawer
:DRAWERNAME: