MeCab+python+JavaScriptでサイトにルビを振る

標題の通り。意外と簡単にできたので共有します。


※正規表現で先読みと後読みのアサーションを使っているため、最新のEcmaScriptでないと動作しません…PC版Chromeでしか動作しません…なんとかしたい…。


ではさっそく。

python:


#coding: utf-8

from mod_python import apache, util
import re
import json
import MeCab

def handler(req):
    # テキスト取得
    fs  = util.FieldStorage(req, True)
    q   = fs.getfirst("q", "")

    if q:
        tagger  = MeCab.Tagger("-Oyomi")
        node    = tagger.parseToNode(q)

        wordTable   = []
        a2n_regexp  = re.compile("^[あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわゐゑをんがぎぐげござじずぜぞだぢづでどばびぶべぼぱぴぷぺぽぁぃぅぇぉっゃゅょアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヰヱヲンガギグゲゴザジズゼゾダヂズデドバビブベボパピプペポァィゥェォッャュョヴ]+$")
        while node:
            wordTypes   = node.feature.split(',')
            if node.surface != "" and \
                len(wordTypes) >= 8 and \
                not a2n_regexp.match(node.surface) and \
                wordTypes[7] != node.surface:
                wordTable.append({
                    "y": wordTypes[7] or "*",
                    "k": node.surface or ""
                })
            node    = node.next
        sendJson(req, True, wordTable)

    else:
        sendJson(req, False)
    
    return apache.OK

# JSON形式で返答を返す。
def sendJson(req, result = False, returnValue = []):
    req.content_type    = "application/json; charset=utf-8"
    req.write(json.dumps({"result": result, "words": returnValue}, False, False))



JavaScript:

"use esversion:6";

(function($, d, w, undef) {
    "use strict";

    $(d).on("click", ".furigana-toggler", function(e) {
        if (!$(this).hasClass("on")) {
            $(this).remove();
            furiganaOn();
        }
        e.preventDefault()
    })

    function furiganaOn() {
        $("body").find("*:not([nodeType=1])").each(function() {
            let node = $(this);
            if (node.text() !== "") {
                $.post(
                    "/py/mecab.py/getFurigana",
                    {
                        q : node.text()
                    },
                    function(json) {
                        if (json.result === true && json.words !== undef) {
                            let text  = node.html();
                            let max   = json.words.length;
                            for (let i = 0; i < max; i++) {
                                let regex = new RegExp("(?<!<ruby>[^<>]*)" + json.words[i].k + "(?![^<]*<rt>" + json.words[i].y + "</rt></ruby>)");
                                text = text.replace(
                                    regex,
                                    "<ruby>" + json.words[i].k + "<rp>[</rp><rt>" + json.words[i].y + "</rt><rp>]</rp></ruby>"
                                );
                            }
                            node.html(text);
                        }
                    },
                    "json"
                );
            }
        });
    }
    
    function furiganaOff() {
        $("ruby.furigana, rp.furigana, rt.furigana").unwrap();
    }
})(jQuery, document, window);

pythonの方には何らかの認証機構をつけなければいけませんが、ひとまずこんな感じです。

Google Map API: KML/KMZファイルを使ってマイマップを表示する

作ったのは以下のページ。ページが残っている間はソースコードを参考にしてください。
http://www.gowest-comewest.net/map/

下準備として、KMLもしくはKMZファイルのURLを取得します。いったんダウンロードしてURLがわかる状態にします。

マイマップのプレビューページの「︙」をクリックすると「KMLをダウンロード」という項目が出てきます。それをクリックすると2つの選択肢が出てきます。上の選択肢がKMZ、下がKMLです。KMZのほうがリッチな表示ができます。

その後、Chromeだったら、Ctrl+Jでダウンロード項目を表示して、各ファイルのURLを右クリックして「リンクをコピー」でOKです。これでKML/KMZファイルのURLが取得できます。

ここからコーディングです。
わたしは以下のようなJSONを作りました。付記したコメントごと貼りつけるので、読んで参考にしてください。

// キャッシュ回避のためのパラメータを作成する。一日おきに更新されるようになる。
let dateObj = new Date();
let param = "" + dateObj.getFullYear() + dateObj.getMonth() + dateObj.getDate();

// 新しいレイヤーを作ったらここに追記する
// KmlLayerというクラス名だけどKMZもOK。KMLじゃなしにKMZ使ったほうが表示がリッチ。
let layerSetting = {
All: {
name: "すべて表示する",
url: "https://www.google.com/maps/d/u/0/kml?mid=1_2yJu1dr6XKvzy2kn7mYTsUwMX0&nl=1&" + param,
controlId: "MapControlAll"
},
Osaka: {
name: "大阪のおすすめスポット",
url: "https://www.google.com/maps/d/u/0/kml?mid=1_2yJu1dr6XKvzy2kn7mYTsUwMX0&lid=1uWfMgRUbJE&nl=1&" + param,
controlId: "MapControlOsaka"
},
Kyoto: {
name: "京都のおすすめスポット",
url: "https://www.google.com/maps/d/u/0/kml?mid=1_2yJu1dr6XKvzy2kn7mYTsUwMX0&lid=PKzfdnvz1AM&nl=1&" + param,
controlId: "MapControlKyoto"
},
Hyogo: {
name: "兵庫のおすすめスポット",
url: "https://www.google.com/maps/d/u/0/kml?mid=1_2yJu1dr6XKvzy2kn7mYTsUwMX0&lid=0d8OkxEeeIw&nl=1&" + param,
controlId: "MapControlHyogo"
},
Moyori: {
name: "移住したひとの最寄り駅",
url: "https://www.google.com/maps/d/u/0/kml?mid=1_2yJu1dr6XKvzy2kn7mYTsUwMX0&lid=zM_vTKi76EM&nl=1&" + param,
controlId: "MapControlMoyori"
}
};

※URLの読み込みはGoogle側でキャッシュされるので、URLパラメータを一日ごとに更新して毎日一度は更新されるようにしています。

次は、表示する部分です。

// マップを初期化する
function initMap() {
let map = new google.maps.Map(document.getElementById("GoWestMap"), {
scrollwheel: false
});

let layers = new Array();
$.each(layerSetting, function(id, setting) {

// レイヤーを作成する
layers[id] = new google.maps.KmlLayer(setting.url);

// レイヤー切り替えボタンを設置する
let controlTag = `<a id="${setting.controlId}" href="#">${setting.name}</a>`;
$("#GoWestMapControl").append(controlTag);

$("#" + setting.controlId).on("click touchend", function(e) {
e.preventDefault();
displayLayer(map, layers, id);
$(this).siblings(".display").removeClass("display").end().addClass("display");
});
});

// 最初は全部表示する
displayLayer(map, layers, "All");
$("#" + layerSetting.All.controlId).addClass("display");
}

// すべてのレイヤーをクリアしてから指定されたレイヤーを表示する
function displayLayer (map, layers, id) {
$.each(layerSetting, function(layerId, setting) {
layers[layerId].setMap(null);
});
layers[id].setMap(map);
}

<div id="GoWestMap"></div>
というHTMLが書いてあることが前提です。そこに地図が描画されます。CSSで大きさの指定も忘れずに。

最後に、地図の描画を実行するJavaScriptを書きます。
<script src="https://maps.googleapis.com/maps/api/js?key=<YOUR API KEY>&callback=initMap" async defer></script>

※ YOUR API KEYのところは、ご自身でAPI KEYを取得して、書き込んでください。

読ませて理解させるレポートでごめんなさい。こんなことにトライする方にとってはそんなに難しくないはず・・・。
以上です。