SICP教本をよりReadableにするためのScriptAutoRunnerスクリプト

概要

この記事について

かんたんな概要と結論

CDNからライブラリを読み込むことでハイライトされていないコードに対して
シンタックスハイライティングを行うことが可能。
コードの実行環境にはScriptAutoRunnerを使用。

こんにちは、dedeです。

この記事では、
Webで一般公開されているSICP教本について
スクリプトを用いてより見やすくする方法について紹介します。

SICP教本とは

ABOUT

SICPは、コンピュータ科学における古典的な名著の一つです。
再帰的手続きやモジュール化の利益など、コンピュータプログラミングの頻出パターンについて説明する本です。

公開サイト

こちらで書籍版と同等の内容のテキストが公開されています。

問題点

上記サイトですが、個人的見解として二点見にくい点がありました。

  • 各内容の説明のために、 Scheme (プログラミング言語Lispのバリエーションの一つ)を用いているが、
    それらのコードブロックにはシンタックスハイライトが施されていないため、少々見にくい。
  • 脚注部分の文字サイズがやや小さい。

これらを改善するために、ブラウザで動作する、デザインを改善するスクリプトを書きました。

通常、ブラウザで自作Javascriptコードを動作させるには
「開発者コンソールで対話的に実行する」あるいは「ブックマークレットとして登録し実行する」のですが、
両者ともページを更新するたびに再度実行しなければならない欠点がありました。

しかし、Chromeブラウザの場合、便利な拡張機能があります。

ScriptAutoRunnerとは

[ScriptAutoRunner

あらかじめ特定ドメインと流したいスクリプトコードのペアを登録し、
該当するページを開く/更新するたびにコードが実行されるようにできる便利なChrome拡張です。

ScriptAutoRunner(公式より)

作成環境・利用ツール

  • Google Chrome バージョン: 101.0.4951.67
  • highlight.js v11.5.1

スクリプトのコード

全体

全体のコードです。 続くセクションで個々の意味と動作について記します。

 1// if document is not fully loaded, load event take the place
 2if (document.readyState === "complete") {
 3    restyleSICP();
 4} else {
 5    window.addEventListener("load", restyleSICP);
 6}
 7
 8function restyleSICP() {
 9    // only in specific paths this script runs
10    if (!window.location.pathname.startsWith("/sites/default/files/sicp/full-text/book")) return;
11    highlightSchemeCode();
12    expandFootnote();
13}
14
15function highlightSchemeCode() {
16    // exclude elements which contain some img elements
17    const targetTts = Array.from(document.querySelectorAll("p > tt:first-child:last-child")).filter(e => !e.querySelector("img"));
18
19    for (const tt of targetTts) {
20        const pre = document.createElement("pre");
21        const code = document.createElement("code");
22
23        code.classList.add("language-scheme");
24        code.innerHTML = tt.innerHTML;
25
26        // replace tt element with pre + code elements
27        pre.append(code);
28        tt.before(pre);
29        tt.remove();
30    }
31
32    const link = document.createElement("link");
33    link.rel = "stylesheet";
34    link.href = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/a11y-dark.min.css";
35
36    const hlScr = document.createElement("script");
37    hlScr.src = "//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js";
38    hlScr.onload = () => {
39        hljs.configure({
40            ignoreUnescapedHTML: true
41        });
42        const schemeScr = document.createElement("script");
43        schemeScr.src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/languages/scheme.min.js";
44        schemeScr.onload = () => hljs.highlightAll();
45        document.head.append(schemeScr);
46
47    }
48    document.head.append(link, hlScr);
49
50
51}
52
53function expandFootnote() {
54    document.querySelector(".footnote").style.fontSize = "1rem";
55}

始動

1// if document is not fully loaded, load event take the place
2if (document.readyState === "complete") {
3    restyleSICP();
4} else {
5    window.addEventListener("load", restyleSICP);
6}

DOM読み込みが完了していない場合と既に完了している場合とで
メイン処理の実行タイミングが異なります。

実行ページのチェック

1function restyleSICP() {
2    // only in specific paths this script runs
3    if (!window.location.pathname.startsWith("/sites/default/files/sicp/full-text/book")) return;
4    // ...
5}

ScriptAutoRunnerではドメインの指定は可能でもパスの指定はできないため、
SICPテキストに関係したページでのみ実行継続するようにします。

ハイライティング

 1function highlightSchemeCode() {
 2    // exclude elements which contain some img elements
 3    const targetTts = Array.from(document.querySelectorAll("p > tt:first-child:last-child")).filter(e => !e.querySelector("img"));
 4
 5    for (const tt of targetTts) {
 6        const pre = document.createElement("pre");
 7        const code = document.createElement("code");
 8
 9        code.classList.add("language-scheme");
10        code.innerHTML = tt.innerHTML;
11
12        // replace tt element with pre + code elements
13        pre.append(code);
14        tt.before(pre);
15        tt.remove();
16    }
17
18    

各ページで Scheme のコードに相当する部分はtt要素なので、
それらを探し出し、pre要素+code要素に置き換えます。
pre要素+code要素は、 highlight.js の検知対象です。

img要素が含まれるコードブロックもあり、ハイライトするとかえって見づらくなるので除外します。

 1const link = document.createElement("link");
 2    link.rel = "stylesheet";
 3    link.href = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/a11y-dark.min.css";
 4
 5    const hlScr = document.createElement("script");
 6    hlScr.src = "//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js";
 7    hlScr.onload = () => {
 8        hljs.configure({
 9            ignoreUnescapedHTML: true
10        });
11        const schemeScr = document.createElement("script");
12        schemeScr.src = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/languages/scheme.min.js";
13        schemeScr.onload = () => hljs.highlightAll();
14        document.head.append(schemeScr);
15
16    }
17    document.head.append(link, hlScr);
18
19
20}

シンタックスハイライト実施には、 highlight.js を利用します。

CDNから highlight.js のメインスクリプトおよび Scheme のための付属スクリプトを順番に読み込み、
すべて読み込み完了したあとにhighlightAllを実行します。

脚注スタイル

1function expandFootnote() {
2    document.querySelector(".footnote").style.fontSize = "1rem";
3}

脚注の文字サイズを大きくします。

デモ

before

after

終わりに

ScriptAutoRunnerは最近知りました。
ブックマークレットを実行するよりも手軽かつメンテナンス容易にJavascriptコードを実行できるので、
サイトを見やすくする際に、様々に役に立つ拡張かと思います。

comments powered by Disqus

Translations: