静的サイトジェネレーターであるHugoはページの元になるファイルとしてOrg mode形式のファイルを使用できる。Org modeはEmacsで用いられる軽量マーク アップ言語と、それに関連するシステム全体のことを指す。Emacs上ではEmacs LispによってOrg mode形式の解析などが行われる。一方HugoはGo言語で実装さ れているため、Org modeのコードをEmacs Lispのまま流用することはできない。 HugoではどのようにしてOrg modeを利用しているのかと疑問に思った。そのた めHugoの内部でOrg modeをHTMLに変換する方法を調べることにした。

TL;DR

  • HugoでOrg-modeからHTMLを生成する方法を調べた。

  • github.com/niklasfasching/go-org を用いてHTMLの生成を行ってることがわかった。

  • Hugoのorg-modeのconvertで行っている。

目的

静的サイトジェネレーターHugoではページの元になるファイルとしてOrg-mode のファイルを使用できる。内部ではどのようにOrg-modeをHTMLに変換している のか気になったので調べた。

変換にはgo-orgを使っている

変換処理はhugo/markup/org/convert.go で実装しており、 github.com/niklasfasching/go-org を利用してOrg-modeからHTMLに変換して いる。

変換処理

Org-modeからHTMLへの変換処理のみを抽出すると次のような処理となる。

main.go

org.New, org.NewHTMLWriterを用いて変換を行う。

package main

import (
	"bytes"
	"fmt"
	"github.com/niklasfasching/go-org/org"
	"os"
)

func main() {

	config := org.New()
	writer := org.NewHTMLWriter()

	content, err := os.ReadFile("./foo.org")
	reader := bytes.NewReader(content)
	parser := config.Parse(reader, "foo")
	html, err := parser.Write(writer)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(html))
}

go.mod

module github.com/TakesxiSximada/symdon/pages/posts/1626346909

go 1.16

require (
	github.com/niklasfasching/go-org v1.5.0 // indirect
	golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
)

[WIP] go-orgの改行処理の挙動を修正する

go-orgでレンダリングした文章の改行位置に微妙な空白が挿入される。文章中 の改行文字が空白文字として処理されるため、文章の中途半端なところに空白 文字が含まれてしまう。これはウェブブラウザの仕様ではあるのだが、日本語 の文章を記述する立場からすると、すこぶる都合が悪い。そのため、この空白 を挿入されないようになんとかできないかを考えてみたい。

ファイル名を引数に取る簡単な変換処理の実装

orgファイルがどのようなHTMLになるのかを素早く(可能な限りインタラクティ ブに)確認できると、作業効率が向上する。今回は手間をあまりかけずにそれ を実現するために、引数にファイルへのパスを渡すとそのファイルをhtmlに変 換し標準出力に表示するプログラムを実装した。

convert_html_simple.go::

package main

import (
	"bytes"
	"fmt"
	"os"

	"github.com/niklasfasching/go-org/org"
)

func main() {
	fileName := os.Args[1]
	fp, err := os.ReadFile(fileName)
	if err != nil {
		panic(err)
	}
	r := bytes.NewReader(fp)

	c := org.New()
	p := c.Parse(r, "foo")

	w := org.NewHTMLWriter()
	html, err := p.Write(w)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(html))
}

通常の状態で実行する

テスト用に2つの文章のブロックがあるファイルを作成した。

simple.org::

テストテストテストテストテスト
テストテストテストテストテスト
テストテストテストテストテスト
テストテストテストテストテスト


テストテストテストテストテスト
テストテストテストテストテスト
テストテストテストテストテスト
テストテストテストテストテスト


#+begin_src golang
func main () {
  return nil
}
#+end_src

このファイルを先程の変換プログラムに渡す。

go run convert_html_simple.go simple.org
<p>テストテストテストテストテスト
テストテストテストテストテスト
テストテストテストテストテスト
テストテストテストテストテスト</p>
<p>
テストテストテストテストテスト
テストテストテストテストテスト
テストテストテストテストテスト
テストテストテストテストテスト</p>
<div class="src src-golang">
<div class="highlight">
<pre>
func main () {
  return nil
}
</pre>
</div>
</div>

pタグで括られた文章のブロックが2つ出力された。この挙動は通常正しいもの とも考えられる。例えば以下の英語の文章を考える。

<p>This is
a pen.</p>

isの直後に改行を挿入している。この改行を何も存在しないように処理してし まうと、isとaが繋がり以下のような文章ができあがってしまう。

This isa pen.

これでは明らかに不便だ。そのため改行は空白として処理されるようになって いる。ただし日本語の文章の場合は事情が異なる。日本語の場合には単語を空 白で区切らないため、改行を空白で処理してしまうと、不要なところに空白が 入った文章になる。日本語の文章を改行を混ぜすに記述すれば済む話ではある が、本来は文を処理するプログラムのどこかで、空白の要不要を適切に判断し 挿入が必要な場合のみ空白が挿入されるべきだ。文中にマルチバイト文字が含 まれているかどうかを判断基準に出来るだろうか。マルチバイト文字が含まれ ているが改行の空白変換は必要になるケースとしては、次のような文章が考え られる。

Hi, しむどん san. How
are you?

文中にマルチバイト文字が含まれる英文は普通に存在する。またエモーティコ ンのような顔文字を使用している場合にも同様だろう。そのためマルチバイト 文字が含まれるかどうかは、改行文字の空白変換の必要性の判断基準にはなら ない。改行の前後の文字が特定の文字であれば改行を除去し、そうでなければ 空白を挿入するという提案がbugzillaにはあるが作成時期が13年前、最終更新 が10年前だった。この処理はある意味では正しいように思える。提案はブラウ ザの挙動として変更を検討するものだが、現状では変換処理で対応するほうが 良いだろう。単純に改行文字をそのまま処理するのではなく、生成される要素 自体を変更するほうが筋が良いようにも思える。先程の出力例を考えると、次 のように出力を明示的に切り替えることができると、CSSにより挙動を変更し やすくて都合がよさそうだ。

<p><span>テストテストテストテストテスト</span><span>テストテストテストテストテスト</span><span>テストテストテストテストテスト</span><span>テストテストテストテストテスト</span></p>
<p><span>テストテストテストテストテスト</span><span>テストテストテストテストテスト</span><span>テストテストテストテストテスト</span><span>テストテストテストテストテスト</span></p>

とりあえずパラグラフ内の改行を挿入しないように修正してみる

go-orgを以下のように変更することでパラグラフ内の改行を挿入しないように 修正できることがわかった。

diff -u go/pkg/mod/github.com/niklasfasching/go-org\@v1.5.0/org/paragraph.go.old /Users/sximada/go/pkg/mod/github.com/niklasfasching/go-org\@v1.5.0/org/paragraph.go
--- go/pkg/mod/github.com/niklasfasching/[email protected]/org/paragraph.go.old	2022-05-30 09:56:15.000000000 +0900
+++ go/pkg/mod/github.com/niklasfasching/[email protected]/org/paragraph.go	2022-05-30 09:57:51.000000000 +0900
@@ -36,7 +36,7 @@
 		lines = append(lines, strings.Repeat(" ", int(lvl))+d.tokens[i].content)
 	}
 	consumed := i - start
-	return consumed, Paragraph{d.parseInline(strings.Join(lines, "\n"))}
+	return consumed, Paragraph{d.parseInline(strings.Join(lines, ""))}
 }

 func (d *Document) parseHorizontalRule(i int, parentStop stopFn) (int, Node) {

Diff finished.  Mon May 30 09:58:27 2022

実行すると次のようになる。

go run convert_html_simple.go simple.org
<p>テストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテスト</p>
<p>テストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテスト</p>
<div class="src src-golang">
<div class="highlight">
<pre>
func main () {
  return nil
}
</pre>
</div>
</div>

挙動としては期待値どおりだ。しかしこれでは英文を記述する際に行末の単語 と行頭の単語が繋ってしまう。そのためこれらをうまく切り替えられればなら ない。

参考