diff --git a/org/drawer.go b/org/drawer.go index 7010ce8..239d524 100644 --- a/org/drawer.go +++ b/org/drawer.go @@ -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 +} diff --git a/org/headline.go b/org/headline.go index 998fa84..16b1bc1 100644 --- a/org/headline.go +++ b/org/headline.go @@ -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:] } } diff --git a/org/html.go b/org/html.go index 2d83770..1d64823 100644 --- a/org/html.go +++ b/org/html.go @@ -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("\n", h.Lvl)) + + if id, ok := h.Properties.Get("CUSTOM_ID"); ok { + w.WriteString(fmt.Sprintf(``, h.Lvl, id) + "\n") + } else { + w.WriteString(fmt.Sprintf("\n", h.Lvl)) + } + if h.Status != "" { w.WriteString(fmt.Sprintf(`%s`, h.Status) + "\n") } diff --git a/org/org.go b/org/org.go index f9564e0..2c49c9e 100644 --- a/org/org.go +++ b/org/org.go @@ -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])) { diff --git a/org/testdata/headlines.html b/org/testdata/headlines.html index 171c223..bbec0a5 100644 --- a/org/testdata/headlines.html +++ b/org/testdata/headlines.html @@ -25,12 +25,12 @@ not just where they are actually meant to be - even here > [B] Headline with todo status & priority -

+

DONE Headline with TODO status

-the content +we can link to headlines that define a custom_id: #this-will-be-the-id-of-the-headline

[A] diff --git a/org/testdata/headlines.org b/org/testdata/headlines.org index 876f8e8..cc98965 100644 --- a/org/testdata/headlines.org +++ b/org/testdata/headlines.org @@ -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: diff --git a/org/testdata/headlines.pretty_org b/org/testdata/headlines.pretty_org index 876f8e8..69e24da 100644 --- a/org/testdata/headlines.pretty_org +++ b/org/testdata/headlines.pretty_org @@ -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: diff --git a/org/testdata/misc.html b/org/testdata/misc.html index 5b8e66b..f10d0ef 100644 --- a/org/testdata/misc.html +++ b/org/testdata/misc.html @@ -52,10 +52,11 @@ src block * 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: