diff --git a/README.org b/README.org index 7fea250..33bad8f 100644 --- a/README.org +++ b/README.org @@ -12,9 +12,6 @@ in general, have a look at the Makefile - it's short enough. * not yet implemented ** deadlines and scheduling see https://orgmode.org/manual/Deadlines-and-scheduling.html -** latex fragments -see https://orgmode.org/manual/LaTeX-fragments.html -+ mathjax for html_writer ? ** more types of links see https://orgmode.org/manual/External-links.html & https://orgmode.org/manual/Internal-links.html - radio target <<>> diff --git a/org/html_writer.go b/org/html_writer.go index 20a0a42..e8bc1e4 100644 --- a/org/html_writer.go +++ b/org/html_writer.go @@ -247,6 +247,12 @@ func (w *HTMLWriter) WriteEmphasis(e Emphasis) { w.WriteString(tags[1]) } +func (w *HTMLWriter) WriteLatexFragment(l LatexFragment) { + w.WriteString(l.OpeningPair) + WriteNodes(w, l.Content...) + w.WriteString(l.ClosingPair) +} + func (w *HTMLWriter) WriteStatisticToken(s StatisticToken) { w.WriteString(fmt.Sprintf(`[%s]`, s.Content)) } diff --git a/org/inline.go b/org/inline.go index 21400d0..02d5a15 100644 --- a/org/inline.go +++ b/org/inline.go @@ -30,6 +30,12 @@ type Emphasis struct { Content []Node } +type LatexFragment struct { + OpeningPair string + ClosingPair string + Content []Node +} + type FootnoteLink struct { Name string Definition *FootnoteDefinition @@ -51,10 +57,17 @@ var subScriptSuperScriptRegexp = regexp.MustCompile(`^([_^]){([^{}]+?)}`) var timestampRegexp = regexp.MustCompile(`^<(\d{4}-\d{2}-\d{2})( [A-Za-z]+)?( \d{2}:\d{2})?( \+\d+[dwmy])?>`) var footnoteRegexp = regexp.MustCompile(`^\[fn:([\w-]*?)(:(.*?))?\]`) var statisticsTokenRegexp = regexp.MustCompile(`^\[(\d+/\d+|\d+%)\]`) +var latexFragmentRegexp = regexp.MustCompile(`(?s)^\\begin{(\w+)}(.*)\\end{(\w+)}`) var timestampFormat = "2006-01-02 Mon 15:04" var datestampFormat = "2006-01-02 Mon" +var latexFragmentPairs = map[string]string{ + `\(`: `\)`, + `\[`: `\]`, + `$$`: `$$`, +} + func (d *Document) parseInline(input string) (nodes []Node) { previous, current := 0, 0 for current < len(input) { @@ -73,7 +86,9 @@ func (d *Document) parseInline(input string) (nodes []Node) { case '<': consumed, node = d.parseTimestamp(input, current) case '\\': - consumed, node = d.parseExplicitLineBreak(input, current) + consumed, node = d.parseExplicitLineBreakOrLatexFragment(input, current) + case '$': + consumed, node = d.parseLatexFragment(input, current) case '\n': consumed, node = d.parseLineBreak(input, current) case ':': @@ -128,14 +143,38 @@ func (d *Document) parseLineBreak(input string, start int) (int, Node) { return i - start, LineBreak{i - start} } -func (d *Document) parseExplicitLineBreak(input string, start int) (int, Node) { - if start == 0 || input[start-1] == '\n' || start+2 >= len(input) || input[start+1] != '\\' { +func (d *Document) parseExplicitLineBreakOrLatexFragment(input string, start int) (int, Node) { + switch { + case start+2 >= len(input): + case input[start+1] == '\\' && start != 0 && input[start-1] != '\n': + for i := start + 2; unicode.IsSpace(rune(input[i])); i++ { + if i >= len(input) || input[i] == '\n' { + return i + 1 - start, ExplicitLineBreak{} + } + } + case input[start+1] == '(' || input[start+1] == '[': + return d.parseLatexFragment(input, start) + case strings.Index(input[start:], `\begin{`) == 0: + if m := latexFragmentRegexp.FindStringSubmatch(input[start:]); m != nil { + if open, content, close := m[1], m[2], m[3]; open == close { + openingPair, closingPair := `\begin{`+open+`}`, `\end{`+close+`}` + i := strings.Index(input[start:], closingPair) + return i + len(closingPair), LatexFragment{openingPair, closingPair, d.parseRawInline(content)} + } + } + } + return 0, nil +} + +func (d *Document) parseLatexFragment(input string, start int) (int, Node) { + if start+2 >= len(input) { return 0, nil } - for i := start + 2; unicode.IsSpace(rune(input[i])); i++ { - if i >= len(input) || input[i] == '\n' { - return i + 1 - start, ExplicitLineBreak{} - } + openingPair := input[start : start+2] + closingPair := latexFragmentPairs[openingPair] + if i := strings.Index(input[start+2:], closingPair); i != -1 { + content := d.parseRawInline(input[start+2 : start+2+i]) + return i + 2 + 2, LatexFragment{openingPair, closingPair, content} } return 0, nil } @@ -312,6 +351,7 @@ func (n LineBreak) String() string { return orgWriter.nodesAsString(n) } func (n ExplicitLineBreak) String() string { return orgWriter.nodesAsString(n) } func (n StatisticToken) String() string { return orgWriter.nodesAsString(n) } func (n Emphasis) String() string { return orgWriter.nodesAsString(n) } +func (n LatexFragment) String() string { return orgWriter.nodesAsString(n) } func (n FootnoteLink) String() string { return orgWriter.nodesAsString(n) } func (n RegularLink) String() string { return orgWriter.nodesAsString(n) } func (n Timestamp) String() string { return orgWriter.nodesAsString(n) } diff --git a/org/org_writer.go b/org/org_writer.go index a894482..9312fea 100644 --- a/org/org_writer.go +++ b/org/org_writer.go @@ -266,6 +266,12 @@ func (w *OrgWriter) WriteEmphasis(e Emphasis) { w.WriteString(borders[1]) } +func (w *OrgWriter) WriteLatexFragment(l LatexFragment) { + w.WriteString(l.OpeningPair) + WriteNodes(w, l.Content...) + w.WriteString(l.ClosingPair) +} + func (w *OrgWriter) WriteStatisticToken(s StatisticToken) { w.WriteString(fmt.Sprintf("[%s]", s.Content)) } diff --git a/org/testdata/latex.html b/org/testdata/latex.html new file mode 100644 index 0000000..08385e3 --- /dev/null +++ b/org/testdata/latex.html @@ -0,0 +1,45 @@ +

+without latex delimiters the _{i=1} and _n in \sum_{i=1}^n a_n are interpreted as subscripts. +we support \(...\), \[...\], $$...$$ and \begin{$env}...\end{$env} as latex fragment delimiters. +

+ diff --git a/org/testdata/latex.org b/org/testdata/latex.org new file mode 100644 index 0000000..4d3f874 --- /dev/null +++ b/org/testdata/latex.org @@ -0,0 +1,15 @@ +without latex delimiters the =_{i=1}= and =_n= in =\sum_{i=1}^n a_n= are interpreted as subscripts. +we support =\(...\)=, =\[...\]=, =$$...$$= and =\begin{$env}...\end{$env}= as latex fragment delimiters. + +- \sum_{i=1}^n a_n (without latex delimiter) +- \(\sum_{i=1}^n a_n\) +- \[\sum_{i=1}^n a_n\] +- $$\sum_{i=1}^n a_n$$ +- \begin{xyz}\sum_{i=1}^n a_n\end{xyz} +- \begin{xyz} + \sum_{i=1}^n a_n + \end{xyz} + +- \begin{xyz} + foobar + \end{xyz} diff --git a/org/testdata/latex.pretty_org b/org/testdata/latex.pretty_org new file mode 100644 index 0000000..4d3f874 --- /dev/null +++ b/org/testdata/latex.pretty_org @@ -0,0 +1,15 @@ +without latex delimiters the =_{i=1}= and =_n= in =\sum_{i=1}^n a_n= are interpreted as subscripts. +we support =\(...\)=, =\[...\]=, =$$...$$= and =\begin{$env}...\end{$env}= as latex fragment delimiters. + +- \sum_{i=1}^n a_n (without latex delimiter) +- \(\sum_{i=1}^n a_n\) +- \[\sum_{i=1}^n a_n\] +- $$\sum_{i=1}^n a_n$$ +- \begin{xyz}\sum_{i=1}^n a_n\end{xyz} +- \begin{xyz} + \sum_{i=1}^n a_n + \end{xyz} + +- \begin{xyz} + foobar + \end{xyz} diff --git a/org/writer.go b/org/writer.go index 03d901e..005b27f 100644 --- a/org/writer.go +++ b/org/writer.go @@ -25,6 +25,7 @@ type Writer interface { WriteParagraph(Paragraph) WriteText(Text) WriteEmphasis(Emphasis) + WriteLatexFragment(LatexFragment) WriteStatisticToken(StatisticToken) WriteExplicitLineBreak(ExplicitLineBreak) WriteLineBreak(LineBreak) @@ -71,6 +72,8 @@ func WriteNodes(w Writer, nodes ...Node) { w.WriteText(n) case Emphasis: w.WriteEmphasis(n) + case LatexFragment: + w.WriteLatexFragment(n) case StatisticToken: w.WriteStatisticToken(n) case ExplicitLineBreak: