ejabberd のウェブ管理画面の不具合

Debian GNU/Linux 上で ejabberd を用いて XMPP サーバーを運用している。開発元で公開される最新版ではなく、Debian 公式のパッケージ を用いているため、そのパッケージがいつ作成されるかに依存して更新が遅くなったりバージョンが跳ぶことがある。

先ごろ、その公式パッケージに従ってバージョンを 23.01 から 24.12 に更新したところ、ejabberd のウェブ管理画面に情報が表示されなくなってしまった。アクセスはできるのだがほぼすべての情報部分が空になってしまう。バージョンアップのどこかで仕様の変更があり設定方法が変わったのだと思うが、公式ドキュメントだけではそれをすぐに見つけることができなかった。

ログには

[debug] Command 'registered_vhosts' execution denied
(CallerInfo=#{caller_host => <<"example.com">>,
caller_module => ejabberd_web_admin,
ip => {{9221,25991,62144,0,12521,33279,65131,31585},59544},
usr => {<<"admin">>,<<"example.com">>,<<>>}})
[debug] Blocked access to command registered_vhosts for
CallerInfo: #{caller_host => <<"example.com">>,
caller_module => ejabberd_web_admin,
ip => {{9221,25991,62144,0,12521,33279,65131,31585},59544},
usr => {<<"admin">>,<<"example.com">>,<<>>}}
[info] Access to WebAdmin page vhosts/ for account admin@example.com was denied

のようなエラーが記録される。

ようやく解決できたので記録しておく。常に最新版を使い、その度にリリース情報をチェックしていればたやすく気づいたのだろうが、Debian パッケージのように大きく跳ばすと見落としていた。

探し回って見つけて、参照したのは

具体的には、設定ファイル ejabberd.yml の api_permissions セクションに以下を追加する。

api_permissions:
  "webadmin access":
    from:
      - ejabberd_web_admin
    who:
      - acl: admin
    what:
      - "*"

なお、acl セクションや access_rules セクションでは次のような設定が行われていること。

acl:
  admin:
    user:
      - "admin@example.com"  # 管理者アカウント
access_rules:
  configure:
    allow: admin

重要なポイントは from: ejabberd_web_admin である。ログに表示される caller_module => ejabberd_web_admin が示すように、WebAdmin からのリクエストは ejabberd_web_admin モジュールから発信される。そのため、from フィールドに ejabberd_web_admin を指定することが必要であった。

emacs-mozc の設定

ちょっと不便だなあと思っても、その場は何とかなるので長いこと放っておいたのをようやく見直した。Emacs 以外では fcitx で、Emacs では emacs-mozc で mozc を使っているのだが、つい両方が同時に ON になっていたりして煩わしく思っていた。

emacs-mozcを快適に使う

の記事にあるようにして、やっと「Emacs利用時にはXIMを無効」にできた。

! ~/.Xresources
! Emacs XIMを無効化
Emacs*useXIM: false

ON/OFFキーを割り当てる書き方も適当にやっていたが、

emacs でのキー入力の表現方法

を読んで、(やっぱり何となくだが)理解した。

デフォルトのON/OFFキーは C-\ で両手を使うので何だかなあと思っていた。片手で操作できて、しかし誤操作しにくいように、(HHKBなので左下隅の2つである) M-<muhenkan> にした(fcitx では以前からそうしていた)。あまり一般的ではないだろうが。

;; ~/.emacs
(global-set-key  (kbd "M-<muhenkan>") 'toggle-input-method)

ついでに、最初の記事を読んで mozc-temp が便利そうなので入れてみることにした。こちらの呼び出しはスペースバーの右側のキーに割り当てることにした。

(load "mozc-temp")
(global-set-key (kbd "M-<henkan>") #'mozc-temp-convert)

やれやれ、何年遅れだろうか。

ウェブ会議でホワイトボード/PDF に手書き/Debian で使うタブレット

しばらく前(2023年に入ったころ)から、いわゆるウェブ会議を使って、リモートで家庭教師のようなこと(本当は違うのだが少しフェイクを入れるのでここではそういうことにしておく)をやっている。

こちらの環境は Debian で、そもそもプロプライエタリな道具はほぼ存在しないので、いろいろと探し回ることになった。

必要な道具は、まずウェブ会議の仕組みにホワイトボード機能となる。はじめは Jitsi を使っていたのだが、そのホワイトボード機能にバグのようなもの(数十分ほど書いたり消したりしているとハングアップしたようにそれ以上の書いたり消したりができなくなる)が出現して、Whereby を使うようになった。この切り替えはもう3か月ほど前なので、現在(2023年9月末)ではもう解決しているかもしれない。

Whereby は2人(1対1)で使う分には時間無制限なので、ここでの目的では困らない。Whereby ではホワイトボードとして外部サービスの Miro の画面を共有するようになっており、この使い方では Miro にアカウントを作る必要もない。

また、ホワイトボードに PDF を背景画像のように表示させてその上に書き込むように表示させる(家庭教師的に言えば、練習問題のプリントを表示させておいて解説を書き込む)ような使い方は、Jitsi のホワイトボードではできず、バグの有無に関わらず Whereby (Miro) のほうが優れている。

やや話はずれるが、ウェブ会議とは無関係に、既存のPDFに手書きで書き込むようなアプリケーションを探してみたら、Xournal++ というものが見つかった。これはこれでたいへん優れた道具だ。

さて、そのように手書きで書き込む際、マウス(と言っても自分はトラックボールだが)ではなかなか上手く書くことができないので、ペンタブレットを用意することにした。価格が手頃で Linux での実績がありそうな XPPen の Deco Fun XS を購入。お絵描きには小さすぎるかもしれないが、ここでの目的にはちょうどいい。

表面は思いの外ツルツルで、ペンが滑りすぎると感じるので、紙(たとえば便箋)をタブレットの作業エリアに貼り付けた。これで書き味がまさに紙に書いている感じになり、すこぶるよくなった。

XPPen には Linux 用のドライバが用意されている。これがなくても機器を接続するだけで十分使えるのだが、これによってさらに便利に使うことができる。たとえば、タブレットでの作業エリアを画面上の一部領域に設定できたり、ペンのボタンに機能を割り当てたりできるようになる。

ここでは「180度回転」と設定し、タブレットそのものを上下逆さまに置くことにした。このタブレットには作業エリア以外に幅 2cm ほどの部分があり、それを手前に持ってきてちょうどパームレスト(文字を書くので実際には小指の脇か)のように使うことで、より安定して字を書けるようになった。

【以下は、2023年9月末現在の自分の環境(Debian testing)での問題である。】

ところが、ここでは Debian の testing バージョン(安定バージョンではなく、ソフトウェア(パッケージ)が少しずつ新しいものに置き換わっていく)を使用しているため、ある時の Debian の更新以降この XP-Pen 提供のドライバ(この時点で XPPen-pentablet-3.2.3.230215-1.x86_64.deb)が動かなくなってしまった。提供されているドライバは当然にも 対応は Debian 安定版としか謳っていないので文句をいう筋合いではない。

XP-Pen 提供のドライバは実際には /usr/lib/pentablet/pentablet.sh というシェルスクリプト

#!/bin/sh
appname=`basename $0 | sed s,\.sh$,,`
dirname=`dirname $0`
tmp="${dirname#?}"
if [ "${dirname%$tmp}" != "/" ]; then
dirname=$PWD/$dirname
fi
LD_LIBRARY_PATH=$dirname/lib
export LD_LIBRARY_PATH
$dirname/$appname "$@"

を通じて、同じディレクトリの lib/ 以下のライブラリを使いつつ、同じディレクトリの pentablet というバイナリが起動されている。OSバージョンで困らないようにわざわざ同梱されている lib/ 以下のライブラリのどれかが逆に悪さして、このドライバが動かなくなっていた。このシェルスクリプトを飛ばして(LD_LIBRARY_PATHを設定しないようにして)直接 /usr/lib/pentablet/pentablet を起動すると、問題なく使えるようになった。

$ ldd /usr/lib/pentablet/pentablet

	linux-vdso.so.1 (0x00007fff87dc2000)
	libusb-1.0.so.0 => /lib/x86_64-linux-gnu/libusb-1.0.so.0 (0x00007f4e0b29d000)
	libX11.so.6 => /lib/x86_64-linux-gnu/libX11.so.6 (0x00007f4e0b15b000)
	libXtst.so.6 => /lib/x86_64-linux-gnu/libXtst.so.6 (0x00007f4e0b153000)
	libXi.so.6 => /lib/x86_64-linux-gnu/libXi.so.6 (0x00007f4e0b13f000)
	libXrandr.so.2 => /lib/x86_64-linux-gnu/libXrandr.so.2 (0x00007f4e0b132000)
	libXinerama.so.1 => /lib/x86_64-linux-gnu/libXinerama.so.1 (0x00007f4e0b12b000)
	libQt5X11Extras.so.5 => /lib/x86_64-linux-gnu/libQt5X11Extras.so.5 (0x00007f4e0b124000)
	libQt5Widgets.so.5 => /lib/x86_64-linux-gnu/libQt5Widgets.so.5 (0x00007f4e0aa00000)
	libQt5Gui.so.5 => /lib/x86_64-linux-gnu/libQt5Gui.so.5 (0x00007f4e0a200000)
	libQt5Xml.so.5 => /lib/x86_64-linux-gnu/libQt5Xml.so.5 (0x00007f4e0b0e0000)
	libQt5Network.so.5 => /lib/x86_64-linux-gnu/libQt5Network.so.5 (0x00007f4e0a055000)
	libQt5Core.so.5 => /lib/x86_64-linux-gnu/libQt5Core.so.5 (0x00007f4e09a00000)
	libGL.so.1 => /lib/x86_64-linux-gnu/libGL.so.1 (0x00007f4e0a979000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f4e0b0d9000)
	libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f4e09600000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f4e09f76000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f4e0b0b5000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4e0941e000)
	libudev.so.1 => /lib/x86_64-linux-gnu/libudev.so.1 (0x00007f4e0a947000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f4e0b2d2000)
	libxcb.so.1 => /lib/x86_64-linux-gnu/libxcb.so.1 (0x00007f4e0a91d000)
	libXext.so.6 => /lib/x86_64-linux-gnu/libXext.so.6 (0x00007f4e0a908000)
	libXrender.so.1 => /lib/x86_64-linux-gnu/libXrender.so.1 (0x00007f4e0a8fb000)
	libpng16.so.16 => /lib/x86_64-linux-gnu/libpng16.so.16 (0x00007f4e099ca000)
	libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f4e099ab000)
	libharfbuzz.so.0 => /lib/x86_64-linux-gnu/libharfbuzz.so.0 (0x00007f4e09884000)
	libmd4c.so.0 => /lib/x86_64-linux-gnu/libmd4c.so.0 (0x00007f4e0a8e9000)
	libgssapi_krb5.so.2 => /lib/x86_64-linux-gnu/libgssapi_krb5.so.2 (0x00007f4e093cb000)
	libdouble-conversion.so.3 => /lib/x86_64-linux-gnu/libdouble-conversion.so.3 (0x00007f4e0986f000)
	libicui18n.so.72 => /lib/x86_64-linux-gnu/libicui18n.so.72 (0x00007f4e09000000)
	libicuuc.so.72 => /lib/x86_64-linux-gnu/libicuuc.so.72 (0x00007f4e08e02000)
	libpcre2-16.so.0 => /lib/x86_64-linux-gnu/libpcre2-16.so.0 (0x00007f4e0933d000)
	libzstd.so.1 => /lib/x86_64-linux-gnu/libzstd.so.1 (0x00007f4e08d41000)
	libglib-2.0.so.0 => /lib/x86_64-linux-gnu/libglib-2.0.so.0 (0x00007f4e08bfb000)
	libGLdispatch.so.0 => /lib/x86_64-linux-gnu/libGLdispatch.so.0 (0x00007f4e08b42000)
	libGLX.so.0 => /lib/x86_64-linux-gnu/libGLX.so.0 (0x00007f4e08b0e000)
	libcap.so.2 => /lib/x86_64-linux-gnu/libcap.so.2 (0x00007f4e09f6a000)
	libXau.so.6 => /lib/x86_64-linux-gnu/libXau.so.6 (0x00007f4e0b0a8000)
	libXdmcp.so.6 => /lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007f4e08800000)
	libfreetype.so.6 => /lib/x86_64-linux-gnu/libfreetype.so.6 (0x00007f4e08a42000)
	libgraphite2.so.3 => /lib/x86_64-linux-gnu/libgraphite2.so.3 (0x00007f4e08a16000)
	libkrb5.so.3 => /lib/x86_64-linux-gnu/libkrb5.so.3 (0x00007f4e08724000)
	libk5crypto.so.3 => /lib/x86_64-linux-gnu/libk5crypto.so.3 (0x00007f4e086f7000)
	libcom_err.so.2 => /lib/x86_64-linux-gnu/libcom_err.so.2 (0x00007f4e09869000)
	libkrb5support.so.0 => /lib/x86_64-linux-gnu/libkrb5support.so.0 (0x00007f4e0985b000)
	libicudata.so.72 => /lib/x86_64-linux-gnu/libicudata.so.72 (0x00007f4e06800000)
	libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007f4e0865c000)
	libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007f4e08647000)
	libbz2.so.1.0 => /lib/x86_64-linux-gnu/libbz2.so.1.0 (0x00007f4e0932a000)
	libbrotlidec.so.1 => /lib/x86_64-linux-gnu/libbrotlidec.so.1 (0x00007f4e08a09000)
	libkeyutils.so.1 => /lib/x86_64-linux-gnu/libkeyutils.so.1 (0x00007f4e08640000)
	libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007f4e0862f000)
	libmd.so.0 => /lib/x86_64-linux-gnu/libmd.so.0 (0x00007f4e08620000)
	libbrotlicommon.so.1 => /lib/x86_64-linux-gnu/libbrotlicommon.so.1 (0x00007f4e085fd000)

Markdown でビジネス文書を作成する

まとめ

  • Pandoc’s Markdown という一方言には::: という汎用Divに対応する記法がある。
  • {markdown, latex} → html は pandoc のみでできる。別途 css を用意するといい。
  • {html, latex} → markdown は pandoc のみでできる。
  • {html, markdown} → latex はフィルタが必要。別途 sty を用意するといい。

ビジネス文書

しばらく前に「公文書を Markdown で」という話題があった。図入りの面倒くさいものや白書のような普通の書籍ほどの長大なものや法律の条文のような特殊なものは別として、圧倒的に多いであろう1枚ペラ程度の通達の類の文書には、 Markdown はとても向いていると思う。簡単なものほど Word のような高機能文書作成ソフトを使わずに Markdown で済ませたい。

この様式 ((英文レターの形式を調べると、アメリカ式「フル・ブロック・スタイル」とイギリス式「フル・インデント・スタイル」が見つかる。日本のいわゆるビジネス文書はこのイギリス式「フル・インデント・ス タイル」にとてもよく似ている。特に電磁的文書ならアメリカ式がとても楽だし合理的だと思うのだが、なんと言っても理屈ではない習慣だからそう簡単に廃れることはないだろう。))は、役所が作成する公文書 ((検索すると多くの自治体の規程が見つかる。たとえば墨田区佐伯市など。))から会社、さらには小学校や PTA、町内会までよく浸透している。

しかし Markdown では、この類の文書の様式に欠かせない右寄せ(右揃え)ができないのが最大のネックだ。意味見た目の分離という観点から言えば右寄せ(右揃え)は見た目なのだが、ここではもうそう呼ぶ ことにする。

Markdown

機能を絞り込んで簡潔であることが Markdown の特長なのだから、あれもこれも盛り込んでいけば最初から HTML を使えばいいとなって元も子もない。とは言え不満はなんとかしたい。Markdown で書きながらも最終的には HTML にすることを想定して直接 HTMLタグ (<div style="text-align:right">)を書いてしまうという手段もあるのだが、せっかくの Markdown なのだし他の形式への変換も考えるとなるべくそういうことはやりたくない。

ここは Markdown 記法に HTML の汎用タグ <div><span> を表せるものがあれば万能なのになあ……と思っていたら、Pandoc’s Markdown という一方言にはこれが存在していることに今さらながら気がついた。

Pandoc の Markdown 拡張

Pandoc’s Markdown という一方言には::: という汎用の Div に対応する記法がある。ここでは触れないが、汎用の Span に対応する []{} 記法もある。

この記法は Pandoc で考案されたようだ ((Syntax for divs))。他の方言ではあまりサポートされていないようだ。HackMD (CodiMD, HedgeDoc) では ::: はやや違った意味にされ、記法も違い限定された語だけしか使えないようだ。

汎用タグを使いすぎれば意味見た目の分離が台なしになることは解っているのだが、最終手段として便利であることは間違いない。

たとえばファイル名 input.md に、Pandoc’s Markdown で書く。

::: {.myaddress}
○△□町会\
会長 ▼▲ ■◆
:::

Markdown から HTML へ

pandoc の標準機能で、特に何もしなくてもいい。コマンドラインで

pandoc --from markdown input.md -to html -o output.html

これにより作成される output.html は次のようになる。

<div class="myaddress">
<p>○△□町会<br />
会長 ▼▲ ■◆</p>
</div>

つまり、Markdown で書いた {.myaddress} がクラス名になる。このクラス名に対応する CSS は別途用意しておく。たとえばファイル名 myaddress.css に

.myaddress {
        text-align: right;
        text-indent: 0pt;
}

と書いておき、コマンドラインで

pandoc --from markdown input.md --to html --output output.html --css myaddress.css --standalone

として利用する。スタイルシートへのリンクがドキュメントヘッダに含まれるため、–standalone オプションも同時に付ける必要がある。

Markdown から LaTeX へ

出力形式を LaTeX とすると、::: は除去されてしまう。つまりコマンドラインで

pandoc --from markdown input.md --to latex --output output.tex

とすると、作成されるファイル output.tex は次のようになる。

○△□町会\\
会長 ▼▲ ■◆

pandoc の作者によるフィルタの例 latexdivs.py を使う ((サンプルのフィルタ latexdivs.py をそのまま使うには、入力ファイルのクラス名を書くところに .latex を加えておく必要がある。つまり input.md は ::: {.latex .myaddress} のように書く。))と、出力を

\begin{myaddress}

○△□町会\\
会長 ▼▲ ■◆

\end{myaddress}

のようにできる。これを実際に LaTeX で処理するには、 myaddress 環境を別途定義しておく必要がある。たとえば myaddress.sty に

% myaddress
\newenvironment{myaddress}
{\begin{flushright}}
  {\end{flushright}}

と書いておき、コマンドラインで --include-in-header で読み込ませる。

pandoc --from markdown input.md --to latex --output output.tex --include-in-header myaddress.sty --standalone

これにより作成される出力ファイルの内容は

\documentclass (略)
  (略)
% myaddress
\newenvironment{myaddress}
{\begin{flushright}}
  {\end{flushright}}
  (略)
\begin{document}

\begin{myaddress}

○△□町会\\
会長 ▼▲ ■◆

\end{myaddress}

\end{document}

のようになる。

形式の相互変換

以上、markdown → {html, latex} の例を示した。

自分のよく使う markdown, html, latex の相互変換についてまとめると、

  • {markdown, latex} → html は pandoc のみでできる。別途 css を用意するといい。
  • {html, latex} → markdown は pandoc のみでできる。
  • {html, markdown} → latex はフィルタが必要。別途 sty を用意するといい。

となる。

具体的な例

変換された html で利用する CSS ファイル
business.css
latex 出力のための pandoc フィルタ
business_md.py
latex 出力が利用する sty ファイル
business.sty
入力ファイル (markdown)
business-sample.md
pandoc --from markdown --to html --css business.css --standalone --output business-sample.html business-sample.md での出力ファイル (html)
business-sample.html
pandoc --from markdown --to latex --filter=./business_md.py --include-in-header business.sty --standalone --output business-sample.tex business-sample.md ((PDF に変換させるには、オプションはこれだけでは実は足りず、ここの例では --pdf-engine=lualatex -V documentclass=bxjsarticle -V classoption=pandoc -V classoption=jafont=auto -V indent=1zw -V pagestyle=empty を加えている。))での出力ファイル (latex)
business-sample.tex
pandoc --from markdown --to pdf --filter=./business_md.py --include-in-header=./business.sty --output business-sample.pdf business-sample.md ((前註に同じ。))での出力ファイル (pdf)
business-sample.pdf

2001–2010年ころの日本語システムフォント

これまでにも何度か書いているように、私自身は個人的には「日本語のある程度の長さのまとまった文章には、ゴシック体より明朝体のほうが向いている」と考えています。Web ページにおいても、です。

一方で、「Web ページはゴシック体」という意見が数多く見られます。おそらく、単純に「明朝派」か「ゴシック派」かで言えば、「ゴシック派」のほうがかなり多数のような印象があります。

システムフォントの歴史

いろいろ思い出すために、2000年頃以降のシステムフォント—大きなシェアを占めていた Windows と Mac にデフォルトで装備されているフォント—について、ざっと調べてみました。

Windows

2001XPMS明朝ゴシック 2.30 MS明朝 2.31
2006Vistaメイリオ 5.00 MS明朝ゴシック 5.00 MS明朝 5.00 (JIS X 0213:2004)
20138.1游ゴシック 游明朝

Mac

MacOS 9.2.2までOsaka 平成明朝 リュウミンライト-KL
2001OS X 10.0ヒラギノ角ゴ Pro W4・ヒラギノ明朝 Pro W3
200710.5ヒラギノ角ゴ ProN W4・ヒラギノ明朝 ProN W3
201310.9游ゴシック体 游明朝体 M

フリーフォント

私自身はこの時代より前から今日に至るまでずっと Linux (Debian) を常用していて、それにはデフォルトとか標準という考えがなく好みのフォントを使います。もう記憶が確かでない部分もあるのですが、主流だったと思えるものを拾い出してみました。

1998-1999渡邊フォント
2000-2003Kochi
2003-2004Sazanami
2007IPAゴシック・IPA明朝 (単体配布)
2010IPAexゴシック・IPAex明朝
2010Takaoフォント
2014源ノ角ゴシック / Noto Sans CJK JP
2017源ノ明朝 / Noto Serif CJK JP

参考:ブラウザーの歴史

1992mozaic
1994Netscape Navigator
1996NN3, IE3
1997NN4
2001IE6
2003Safari (10.3から。それ以前(10.2)の標準ブラウザはIEforMac)
2004Firefox 0.8
2006IE7
2008Chrome

漠然とですが、

  • 2001–2010年ころ、Web ページにとって明朝体は、技術的に「使い物にならな」かった
  • そのため、その頃とそれ以降、日本語の Web ページは圧倒的にゴシック体を主体としたものが多い
  • その環境で育った人たちは、もう「Web ページはゴシック体」が当たり前であり、技術的な問題が既に解決されても、むしろ明朝体だと違和感がある

のようなことではなかろうかと考えています ((翻って考えると、私の「日本語のある程度の長さのまとまった文章には、ゴシック体より明朝体のほうが向いている」という考えも、WWW 以前の、印刷物に接する時間が長かった(印刷物は言うまでもなく、本文は明朝系であることが圧倒的に多い)影響が強いのかもしれません。))。

2001年のMacOS Xにヒラギノというのは本当に画期的だったとは思うのですが、何しろシェアが違いすぎ、それにあぐらをかいた Windows のために「暗黒の10年」だったと言っても過言ではありません。ブラウザーの IE6 天下と軌を一にしています。

話はややずれますが、ハードウェアとしてのディスプレイが CRT から LCD になっていったのものこの頃でした。私が切り換えたのはだいぶ遅めの2005年ころでしたが、CRT ではいい具合にボケていた文字の輪郭が LCD だとくっきりしすぎて、文字として美しくなくなったのを覚えています。それからアンチエイリアスとかヒンティングなどを意識することになりました。

Emacs 風キーバインド

先日、久しぶりに勉強会のようなところへ出かけてみました。そこで会の主要なテーマとは直接関係のない Mac のキーバインドの話を聞きました。私は Mac にはほとんど触れたことがないので知りませんでしたが、おやこれは「Emacs風キーバインド」ではないかと思いました。起源はもう忘れられているかもしれません。

さて、そこで Debian をデスクトップで使っているという自分の環境を思い返してみました。エディターは主に Emacs なので、いろいろなところを「Emacs風キーバインド」にしたいと思いながら、ウィンドウマネージャーを変更したりブラウザーがバージョンアップして拡張機能が効かなくなっていたり、と、いつの間にか不便な状態になっていて、まあそのうち、と思いながら過ごしていたのです。

これを機会に、ちょっと探してみました。ほとんど自分用メモですが、Mac から Linux に乗り換えたい人への情報になるかもしれません。

Xfce4

以前 Gnome を使っていた頃はどこかで設定していたのですが、数年前に Xfce4 に切り替えてから、どうすればいいのか調べてもおらず、そのままになっていました。

「設定」→「設定エディター」で、「チャネル」の一番下の xsettings を開き、Gtk のグループの中のプロパティ KeyThemeName の値に Emacs と入力します。

これで、Xfce4 上で開く多くのアプリケーションで、C-aC-eC-k などが有効になりました。ただし C-y で取り出せるバッファが C-k と連動していないところが Emacs や次の bash と違う挙動で、ときどき混乱してしまいます。

Bash

ターミナル内のシェル は bash です。これは元々「Emacs風キーバインド」のようで、入力行で C-aC-eC-k が使えて、C-k したものを C-y で貼り付けることができます。C-pC-n で履歴をたどれるなど、かなり多くのキーバインドが使えます

Firefox

以前は、拡張機能でそういうのがあったのですが、ブラウザー本体のバージョンアップで使えなくなっていました。

上述の Xfce4 の設定で、いくらかのキーが使えるようになりました。しかし、上下に動かしたいときについ C-pC-n と打って「印刷」「新しいウィンドウを開く」になってしまったり、ページ内検索のつもりでつい C-s と打って「ファイルに保存」になってしまうことがしょっちゅうなので、この辺りを補ってくれるものはないかなあと思っています。

GhostText

Firefox の入力欄 (textarea) で、好きな外部エディターを使えます。以前は別の拡張機能を使っていたのですが、やはりブラウザー本体のバージョンアップで使えなくなってしまい、また別の拡張機能も WordPress の新しい入力画面 Gutenberg との相性が悪そうなので、現在は GhostText を使っています ((GhostText という拡張機能は Firefox にも Chrome にもあり、元々 Emacs 以外のエディターを使うものだったので、それらのどの組み合わせでも使えます。))。

Emacs 側でも準備が必要で、atomic-chrome パッケージをインストールして、設定しておきます。

ニュースのスクレイピングでタイピング練習

ひらがな数文字を打ち返すだけのタイピング練習は案の定すぐに飽きてしまったので、何か別のネタを考えなくてはならなくなりました。飽きないためには膨大かまたは頻繁に更新される元データがあればいい、青空文庫かな、でも小学生に向いているものがどれほどあるかしらん、頻繁に更新されるといえばニュース、でもこれまた小学生向きではなさそう……と思ったら実にぴったりのものがありました。NHK NENS WEB EASY です。ひとつの記事で50字ほどの文が10ほど。意味もわかりやすくて量もちょうどいい。かなり手間をかけて作られているようです。

さて、これをなんとか持ってきてタイピング練習の材料にしようと思ったのですが、何しろ本業でも何でもないので情報を集めるところからスタートでした。今回やっていることは実は「スクレイピング」という程のこともないのですが、そのとっかかりということで、せっかくなのでここに記録しておきます。

環境の準備

NHK NENS WEB EASY のページの肝心な部分は JavaScript で生成されているようで、Python で単純に requests.get(url) とやっても、ブラウザで見ている HTML ソースとは別のものしか得ることができません。そこでまず、ブラウザが実際に表示するページを取得できるようにします。

Debian パッケージ chromium-driver をインストールします。

sudo apt-get install chromium-driver

これを Python から使うためにライブラリ Selenium をインストールします。

pip install selenium

取得した HTML から必要な箇所を切り出すのには BeautifulSoup4 を使います ((Python にはじめからある html.parser でもある程度できます。また Selenium にも同様の機能があるようです。))。

pip install beautifulsoup4

でインストールします。

ニュースサイトの構造

ニュースサイトによくあることですが、各記事の URL は数字の羅列のような名前で、先頭ページではそれが日々更新されます。

ブラウザのデベロッパーツールで NHK NENS WEB EASY の先頭ページ https://www3.nhk.or.jp/news/easy/ の構造を見てみます。

<div class="top-news-list__pickup news-list-item" id="js-news-pickup">
  ...
  <h1 class="news-list-item__title is-pickup">
  <a href="./k10011463631000/k10011463631000.html"><em class="title"><ruby>日本<rt>にっぽん</rt></ruby>の<ruby>二酸化炭素<rt>にさんかたんそ</rt></ruby>の<ruby>濃度<rt>のうど</rt></ruby>が<ruby>今<rt>いま</rt></ruby>まででいちばん<ruby>高<rt>たか</rt></ruby>くなる</em><time class="time">6月5日 11時30分</time></a>
  </h1>
</div>

最初に大きく取り上げられている記事は <div id="js-news-pickup"> で、その中の <h1> の中の <a> から記事個別ページの URL が得られます。

その記事個別ページを同様にブラウザのデベロッパーツールで見てみると、記事本文は <div id="#js-article-body"> にあることがわかります。これを切り出してくればいい訳です。

スクレイピング

参考にした (というか、ほぼそのままコピーさせてもらった) コードは「Python Webスクレイピング テクニック集」の「JavaScriptによる描画に対応する」です。

ソース中のコメント「ブラウザを起動する」の箇所で、ブラウザのパスを指定する必要がありました。Debian のパッケージを使っている場合

driver = webdriver.Chrome(executable_path='/usr/bin/chromedriver', chrome_options=options)

です。

切り出し

1回めの

        # ブラウザでアクセスする
        siteurl = "https://www3.nhk.or.jp/news/easy/"
        driver.get(siteurl)

        ...

        # BeautifulSoupで扱えるようにパースします
        soup = BeautifulSoup(html, "html.parser")

        # id で特定の要素を切り出す
        href = soup.select_one("#js-news-pickup h1 a").get('href')

で記事個別ニュースの URL が得られるので、2回めは

        driver.get(newsurl)
        html = driver.page_source.encode('utf-8')
        soup = BeautifulSoup(html, "html.parser")
        # ルビを削除
        for s in soup(['rt']):
            s.decompose()

        ...

        # ニュースの本文
        text = soup.select_one("#js-article-body").text

で、記事本文を切り出します。

ルビを削除

ニュースの本文は

...<p><span class="colorC"><ruby>気象庁<rt>きしょうちょう</rt></ruby></span>は「もっと<a href="javascript:void(0)" class="dicWin" id="id-0000"><ruby><span class="under">二酸化炭素</span><rt>にさんかたんそ</rt></ruby></a>を<ruby>出<rt>だ</rt></ruby>さないようにしなければなりません」と<ruby>話<rt>はな</rt></ruby>しています。</p>...

のようになっています。BeautifulSoup の .text で単純にタグを削除するだけでは

気象庁きしょうちょうは「もっと二酸化炭素にさんかたんそを出ださないようにしなければなりません」と話はなしています。

と、ルビが本文に混じってしまい、まともな文になりません。事前に rt 要素を削除しておく必要があります。

この作業をやってみて、rt がほかとは異質なタグ(要素)であることを実感しました。これについてはまた別の記事に書こうと思います。

1文ずつに分解

記事を「。」で区切り、リストにします。「。」自身も含めたいので split が使えません。NHK のニュース記事で全体の最後に「。」がないことはまさかないだろうと仮定して、

        lines = re.findall(".*?。", text)

とします。あとは Errnot がこれを1文ずつ表示するようにするだけです。これでこの bot を相手に XMPP のチャットでオウム返しにタイピングの練習をすることができるようになりました。

それにしても、いろいろ寄せ集めるだけでこれだけできるのですから、便利な世の中になったものだとつくづく思いました。