From 97d5ac593374b1c81ebdecbcaec092abb6b0d622 Mon Sep 17 00:00:00 2001 From: glacials Date: Sat, 18 Feb 2023 15:08:18 -0800 Subject: [PATCH] Allow customizing header level offset Allows consumers to specify `TopLevelHLevel` to `HTMLWriter`, which works identically to Org's official [`:html-toplevel-hlevel` / `org-html-toplevel-hlevel`](https://orgmode.org/manual/Publishing-options.html) property. Fixes #94. --- org/html_writer.go | 26 +++++++++++++++++++---- org/html_writer_test.go | 47 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/org/html_writer.go b/org/html_writer.go index 5918af6..e763aa7 100644 --- a/org/html_writer.go +++ b/org/html_writer.go @@ -21,6 +21,21 @@ type HTMLWriter struct { ExtendingWriter Writer HighlightCodeBlock func(source, lang string, inline bool, params map[string]string) string PrettyRelativeLinks bool + // TopLevelHLevel determines what HTML heading to use for a + // level-1 Org headline, and by extension further headings. + // + // For example, a value of 1 means a top-level Org headline will be + // rendered as an

element, a level-2 Org headline will be + // rendered as an

element, and so on. + // + // A value of 2 (default) means a top-level Org headline will be + // rendered as an

element, a level-2 Org headline will be + // rendered as an

element, and so on. + // + // This setting and its default behavior match Org's + // :html-toplevel-hlevel export property and the associated + // org-html-toplevel-hlevel variable. + TopLevelHLevel int strings.Builder document *Document @@ -72,6 +87,7 @@ func NewHTMLWriter() *HTMLWriter { } return fmt.Sprintf("
\n
\n%s\n
\n
", html.EscapeString(source)) }, + TopLevelHLevel: 2, footnotes: &footnotes{ mapping: map[string]int{}, }, @@ -272,8 +288,10 @@ func (w *HTMLWriter) WriteHeadline(h Headline) { return } - w.WriteString(fmt.Sprintf(`
`, h.ID(), h.Lvl+1) + "\n") - w.WriteString(fmt.Sprintf(``, h.Lvl+1, h.ID()) + "\n") + level := (h.Lvl - 1) + w.TopLevelHLevel + + w.WriteString(fmt.Sprintf(`
`, h.ID(), level) + "\n") + w.WriteString(fmt.Sprintf(``, level, h.ID()) + "\n") if w.document.GetOption("todo") != "nil" && h.Status != "" { w.WriteString(fmt.Sprintf(`%s`, h.Status) + "\n") } @@ -290,9 +308,9 @@ func (w *HTMLWriter) WriteHeadline(h Headline) { w.WriteString("   ") w.WriteString(fmt.Sprintf(`%s`, strings.Join(tags, " "))) } - w.WriteString(fmt.Sprintf("\n\n", h.Lvl+1)) + w.WriteString(fmt.Sprintf("\n\n", level)) if content := w.WriteNodesAsString(h.Children...); content != "" { - w.WriteString(fmt.Sprintf(`
`, h.ID(), h.Lvl+1) + "\n" + content + "
\n") + w.WriteString(fmt.Sprintf(`
`, h.ID(), level) + "\n" + content + "
\n") } w.WriteString("
\n") } diff --git a/org/html_writer_test.go b/org/html_writer_test.go index bccb464..a150845 100644 --- a/org/html_writer_test.go +++ b/org/html_writer_test.go @@ -56,3 +56,50 @@ func TestPrettyRelativeLinks(t *testing.T) { }) } } + +var topLevelHLevelTests = map[struct { + TopLevelHLevel int + input string +}]string{ + {1, "* Top-level headline"}: "

\nTop-level headline\n

", + {1, "** Second-level headline"}: "

\nSecond-level headline\n

", + {1, "*** Third-level headline"}: "

\nThird-level headline\n

", + {1, "**** Fourth-level headline"}: "

\nFourth-level headline\n

", + {1, "***** Fifth-level headline"}: "
\nFifth-level headline\n
", + {1, "****** Sixth-level headline"}: "
\nSixth-level headline\n
", + + {2, "* Top-level headline"}: "

\nTop-level headline\n

", + {2, "** Second-level headline"}: "

\nSecond-level headline\n

", + {2, "*** Third-level headline"}: "

\nThird-level headline\n

", + {2, "**** Fourth-level headline"}: "
\nFourth-level headline\n
", + {2, "***** Fifth-level headline"}: "
\nFifth-level headline\n
", + + {3, "* Top-level headline"}: "

\nTop-level headline\n

", + {3, "** Second-level headline"}: "

\nSecond-level headline\n

", + {3, "*** Third-level headline"}: "
\nThird-level headline\n
", + {3, "**** Fourth-level headline"}: "
\nFourth-level headline\n
", + + {4, "* Top-level headline"}: "

\nTop-level headline\n

", + {4, "** Second-level headline"}: "
\nSecond-level headline\n
", + {4, "*** Third-level headline"}: "
\nThird-level headline\n
", + + {5, "* Top-level headline"}: "
\nTop-level headline\n
", + {5, "** Second-level headline"}: "
\nSecond-level headline\n
", + + {6, "* Top-level headline"}: "
\nTop-level headline\n
", +} + +func TestTopLevelHLevel(t *testing.T) { + for org, expected := range topLevelHLevelTests { + t.Run(org.input, func(t *testing.T) { + writer := NewHTMLWriter() + writer.TopLevelHLevel = org.TopLevelHLevel + actual, err := New().Silent().Parse(strings.NewReader(org.input), "./topLevelHLevelTests.org").Write(writer) + if err != nil { + t.Errorf("TopLevelHLevel=%d %s\n got error: %s", org.TopLevelHLevel, org.input, err) + } else if actual := strings.TrimSpace(actual); !strings.Contains(actual, expected) { + t.Errorf("TopLevelHLevel=%d %s:\n%s'", org.TopLevelHLevel, org.input, diff(actual, expected)) + } + }) + } +}