てくてく

2023年9月2件]

小説をサイトに載せるとき気付いたんです。
「中身ほとんど一緒なんだからぱっと置き換えたら楽なんじゃない?」
最近PHPでパーツの共通化してとても便利ではあったのですが、小説本文をペッと貼ったらパッとhtml形式で書き出してくれたら楽できるなって気付いたんです。
ということでそんな小説をhtmlに書き出してくれる置き換えスクリプトを作りました!
今回は配布ありです。

配布
ダウンロードはこちら⇨novel 2 template
免責事項をご確認の上、ご利用くださいませ。

デモはこちらより確認できます。

内容物
  • index.html
  • readme.txt
  • script.js
  • style.css


確認環境はGoogle Chromeのみです。またローカルでの使用を想定しております。

使い方
あらかじめ、以下のものを用意してください。
  • 小説本文
  • 小説を載せたいテンプレート(html、phpどちらでも可)以降テンプレート表記


novel 2 template
index.htmlを開く(ダブルクリック)と規定ブラウザでページが開きます。
各項目を設定します。

◯タイトルタグ
<title>に書きたい内容。
置き換えの必要がない場合は空欄で大丈夫です。

◯cssのURL
ファイルを配置する場所からcssまでのパス。
文字を置き換えるだけなので、相対パス・絶対パスどちらでもOKです。
置き換えの必要がない場合は空欄で大丈夫です。

◯作品タイトル
小説のタイトル。
置き換えの必要がない場合は空欄で大丈夫です。

◯本文
小説本文。通常だとタグに囲まれずそのまま置き換えられます。
置き換えの必要がない場合は空欄で大丈夫です。
下記の機能があります。
・改行タグを入れる
 - 改行済みテキストの場合、改行タグを挿入します。デフォルトでチェックが入っています。
・空行が◯行以上ならpタグで囲む
 - 空行の行数を指定すると、その間のテキストをpタグで囲みます。
・ルビ表記をルビタグに差し替える
 - 特定のルビ表記をルビタグに変更します。対応しているルビ表記は後述です。
・pタグとpタグの間に水平タグを挟む
 - pタグとpタグの間に水平タグを入れます。これは管理人が欲しくて追加したオマケ機能です。

対応しているルビ表記は以下のとおりです。
| 漢字 《かんじ》
#漢字__かんじ__#
[RB:漢字,かんじ]
[[rb: 漢字 > かんじ ]]
[漢字"かんじ"]

◯JavaScriptのURL
JavaScriptのファイルまでのパス。
cssのURL同様、ファイルを配置する場所からJavaScriptファイルまでのパスをいれてください。
置き換えの必要がない場合は空欄で大丈夫です。

◯項目追加
項目名を入れ追加ボタンを押すと、項目を追加できます。
項目名はわかりやすくするためのものなので、未定義のままでも動きます。
追加した項目は削除可能です。追加・削除した項目はナンバリングが再設定されます。

◯ファイルから読み込み
テンプレートファイルがあればこちらから読み込むのが早いです。
読み込むとテンプレート欄に内容が表示されます。

◯テンプレート
ファイルから読み込まない場合、テンプレート欄にテンプレートを記入してください。

◯置き換え
設定した項目をテンプレートに反映させます。
テンプレート欄になにもないとアラートが表示されます。

◯置き換え結果
置き換えた内容をこのエリアに表示します。
内容をコピーしてご利用ください。
整形されていないので、整形したい場合は外部ツールやソフトをご利用くださいませ。

◯ダウンロード
ファイル名(デフォルトはoutput)を記入しhtmlかphpかを選びダウンロードボタンを押すと、置き換え結果の内容を選択した拡張子でダウンロードできます。
なお、サーバーに送信はされませんのでご安心ください。
置き換え結果欄になにもないと、アラートが表示されます。

テンプレートファイルの書き方
novel 2 templateの各項目には[item数字]が書かれています。
この[item数字]を書いた場所が、設定した内容と置き換わる仕組みです。
例1:
タイトルタグフォーム:小説をhtmlに置き換えるスクリプト作ってみたっ
テンプレート:<title>[item0]</title>
書き出されるもの:<title>小説をhtmlに置き換えるスクリプト作ってみたっ</title>

例2:
cssのURLフォーム:../css/style.css
テンプレート:<link rel="stylesheet" href="[item1]" type="text/css" id="style">
書き出されるもの:<link rel="stylesheet" href="../css/style.css" type="text/css" id="style">

例3:
追加した項目[item5]:明日は明日の風が吹く
テンプレート:<p>[item5]</p>
書き出されるもの:<p>明日は明日の風が吹く</p>

カスタマイズ
MITライセンスで配布しておりますので、ご自身のテンプレートに合うように好きに改変していただいて構いません!
カスタマイズ向けの説明は同梱のreadme.txtに記載されています。

最後に
自分用に試しに作ってみたものですが、割といい感じにできたと思います。
カスタマイズ次第では項目追加も不要になると思いますので、自分だけの変換ツールを作ってみてください!

関連記事
簡易ログイン作ってみた!
【JavaScript】名前変換機能を作ってみた!
今は便利な名前変換機能(単語変換機能)スクリプトを配布してくださっている方がいらっしゃいます。
私が自分のサイトに実装した当時、求めている機能が微妙になかったため「作るっきゃねえ!」の精神で自作しました。

デモ
こちらのデモページから動作確認ができます。

仕様
①名字・名前・名字ひらがな・名前ひらがなを変換
②名字と名前はユニークなものを前提としている
③ひらがなは[RB:吃 > ども]りなどで利用
④小説ページごとにデフォルト名が異なっていても対応可能
⑤Cookieを利用
⑥変換フォームのプレースホルダーには変換した名前を入れておく。

ユニークなものとは、重複しない独特なものを意味しています。本文で使われない単語のことです。
例えば、名前が「空」だと「青空の下に広がる海。」など本文に「空」の文字が入っていると仕様上その文字も変換されてしまいます。

基本私が小説を書くときは名前変換を前提としておらず、名前を決めて執筆しています。
htmlに持っていくときに名前の部分にタグ付けがいちいち面倒だな……と思い、JavaScriptの文字列変換を利用して書き換える方向にしました。
ただひらがなはなかなか難しかったため、ひらがなだけタグをつけています。
(私の小説ではそこまで多用しなかったので妥協しました)

ファイル構成
index.html
contents.html
css/style.css
js.name-change.js
今回はjQueryを使いません。
Cookie処理はかわりにjs-cookieを利用しました。

JavaScriptの読み込み
名前変換のフォームがあるページと、名前変換処理をするページで読み込みます。
<!-- js-cookie -->
<script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js"></script>
<!-- script -->
<script src="js/name-change.js"></script>

js-cookieのCDNはこちらからコピペしています。
Cookie操作をしやすいように利用しています。

名前変換フォーム
今回index.htmlに書いてる名前変換のフォームです。
なお、デザインに関するクラスは省いています。
<form name="d_name">
    <input type="text" placeholder="名字" name="lastname">
    <input type="text" placeholder="名前" name="firstname"><br>
    <input type="text" placeholder="みょうじ" name="lastname_hira">
    <input type="text" placeholder="なまえ" name="firstname_hira"><br>
    <button type="button" onclick="setName()">変換</button>
    <button type="button" onclick="resetName()">リセット</button>
</form>
<div id="setName"></div>

onclickの処理はjs/name-change.jsファイルに記述します。
<div id="setName"></div>は変換ボタンやリセットボタンを押した後の結果を表示するためのものです。

style.css
上記<div id="setName"></div>の設定です。
#setName {
    display: none;
    /* 下記はデザイン用 */
    margin: 15px 0;
    padding: 15px;
    border: 1px solid transparent;
    border-radius: 3px;
    margin: 0 0 10px;
    padding: 8px;
}

#setName.change,#setName.reset {
    display: block;
}

#setName.change::after {
    content: "名前を変換しました。";
}

#setName.reset::after {
    content: "名前をリセットしました。";
}

/* 下記はデザイン用 */
#setName.change,#setName.reset {
    border-color: #9ad4de;
    background-color: rgba(154, 212, 222, 0.25);
}


名前変換ありの本文
記述はこのようにします。
<div id="d-name-default"  data-last="デフォルト名字" data-first="デフォルト名前" data-last-hira="でふぉるとみょうじ" data-first-hira="でふぉるとなまえ"></div>
<div id="d-name-nvl">
    <!-- 本文 -- >
</div>

一つずつ解説していきます。

<div id="d-name-default" data-last="デフォルト名字" data-first="デフォルト名前" data-last-hira="でふぉるとみょうじ" data-first-hira="でふぉるとなまえ"></div>
タグはdivにしていますがなんでもいいです。こちらは
<script src="js/name-change.js"></script>
より上に記述してあればどこに記述しても構いません。今回は本文の前に記述しています。
id="d-name-default"でJavaScriptから取得できるようにします。
data-last=""はデフォルトの名字を入れます。省略可能です。
data-first=""はデフォルトの名前を入れます。省略可能です。
data-last-hira=""はデフォルトの名字のひらがな(読み方)を入れます。省略可能です。
data-first-hira=""はデフォルトの名前のひらがな(読み方)を入れます。省略可能です。
この省略可能は、本文で使用しない場合に記述しなくていいようにするものです。

id="d-name-nvl"の中に小説本文を流し込みます。これは上記で設定したデフォルト名と変換フォームで設定した名前を置き換えるためのものです。このidがないと変換されないので注意です。

ひらがなのタグ
名字や名前のひらがな部分にはタグを追加します。
デモ版の場合はこのようにしてます。
「俺は<em>愛上陵</em>!”<em><span class="last-hira">あいうえ</span></em><em><span class="first-hira">おか</span></em>”って読むんだぜ!珍しいだろ?」<br>
 ──<em><span class="last-hira">あ……いう……</span></em>──<br>
「ん?いまなにか聞こえたか?」<br>
 ──<em><span class="last-hira">あい……うえ……</span></em><em><span class="first-hira">お……か</span></em>──<br>
「やっぱ誰かに呼ばれてる!」

名字のひらがなを
<span class="last-hira"></span>
名前のひらがなを
<span class="first-hira"></span>
で囲みます。
このとき間に三点リーダ(…)や句読点が入っても大丈夫です。
もしひらがなの部分に別のタグ(強調タグやルビタグなど)を使いたい場合は、spanタグの外側に設置します。

name-change.js
処理はこちらです。
/* 名前設定 */
function setName() {
    const formEl = document.forms.d_name;
    let change = false;
    change = setItem(formEl.lastname,'lastname');
    change = setItem(formEl.firstname,'firstname');
    change = setItem(formEl.lastname_hira,'lastname_hira');
    change = setItem(formEl.firstname_hira,'firstname_hira');
    if (!change) { return; }
    document.getElementById("setName").classList.remove("reset");
    document.getElementById("setName").classList.add("change");
    /* 同じページ内に変換箇所がある場合 */
    changeName();
}

/* 項目別 */
function setItem(elName,cookieId){
    const name = elName ? elName.value : '';
    if(name === null || name === '') return false;
    Cookies.set(cookieId, name);
    return true;
}

/* 名前変換時にテキストエリアを変換した名前に保持 */
function keepName() {
    const formEl = document.forms.d_name;
    if (!formEl) { return; }
    keepItem(formEl.lastname,'lastname');
    keepItem(formEl.firstname,'firstname');
    keepItem(formEl.lastname_hira,'lastname_hira');
    keepItem(formEl.firstname_hira,'firstname_hira');
    document.getElementById("setName").classList.remove("reset");
    document.getElementById("setName").classList.remove("change");
}

/* 項目別 */
function keepItem(elName,cookieId){
    let input = elName ?? null;
    if(input === null) return;
    const name = Cookies.get(cookieId) ?? input.placeholder;
    if(input.placeholder !== name){
        input.value = name;
    }
}

/* 名前リセット */
function resetName() {
    const formEl = document.forms.d_name;
    let reset = resetItem(formEl.lastname,'lastname');
    reset = resetItem(formEl.firstname,'firstname');
    reset = resetItem(formEl.lastname_hira,'lastname_hira');
    reset = resetItem(formEl.firstname_hira,'firstname_hira');
    if(reset){
        ajaxReload(); // 同一ページに変換箇所がある場合
        document.getElementById("setName").classList.remove("change");
        document.getElementById("setName").classList.add("reset");
    }
}

/* 項目別 */
function resetItem(elName,coolieId){
    Cookies.remove(coolieId);
    if (!elName) return false;
    else elName.value = '';
    return true;
}

/* 名前変換 */
function changeName() {
    let d_name_nvl = document.getElementById('d-name-nvl');
    if (d_name_nvl === null) { return; }
    let str = changeItem('lastname', 'data-last', d_name_nvl.innerHTML);
    str = changeItem('firstname', 'data-first', str);
    d_name_nvl.innerHTML = str;
    changeHiragana('lastname_hira', 'data-last-hira', 'last-hira');
    changeHiragana('firstname_hira', 'data-first-hira', 'first-hira');
}

/* 項目別 */
function changeItem(cookieId,attrName,str){
    const data_d = document.getElementById('d-name-default').getAttribute(attrName);
    const name = Cookies.get(cookieId);
    if(name && data_d) return str.replace(new RegExp(data_d,'g'),name);
    return str;
}

/* ひらがな対応 */
function changeHiragana(cookieId,attrName,className){
    const data_d = document.getElementById('d-name-default').getAttribute(attrName);
    const name = Cookies.get(cookieId);
    if(!(name && data_d)) return;
    let arr = document.getElementsByClassName(className);
    for(i = 0;i<arr.length;i++){
        let el = arr[i];

        let defHira = data_d.split('');
        let nameHira = name.split('');
        let str = el.innerText.split('');
        let txt ='';
        let nameCount = 0;
        for (k = 0; k < str.length; k++) {
            if(!str[k].match(/^[ぁ-んー ]+$/) || defHira.length <= nameCount){
                txt += str[k];
            }else{
                let replace;
                if(nameCount < nameHira.length){
                    replace = nameHira[nameCount];
                    txt += str[k].replace(defHira[nameCount], replace);
                    nameCount++;
                }else if(nameHira.length < defHira.length){
                    // 設定された名前のほうが短ければ以降はつけない
                    break;
                }
            }
        }
       
        // 不足分追加
        if(defHira.length < nameHira.length){
            let substr = name.substr(nameCount);
            txt += substr;
        }

        el.innerText = txt;
    }
}

/* 同じページに変換がある場合 */
function ajaxReload(){
    let d_name_nvl = document.getElementById('d-name-nvl');
    if (d_name_nvl) {
        let url = location.href + "?date="+new Date().getTime();
        let ajax = new XMLHttpRequest;
        ajax.open("GET",url,true);
        ajax.onload = function(){
            var res = ajax.responseText;
            var parse = new DOMParser().parseFromString(res,"text/html");
            d_name_nvl.innerHTML = parse.getElementById('d-name-nvl').innerHTML;
        };
        ajax.send(null);
    }
}

/* 読み込み直後 */
window.onload = function () {
    /* 入力フォームの設定 */
    keepName();
    /* 名前変換 */
    changeName();
};

同一ページに変換箇所がある場合の処理も追記しておきました。
もしない場合はその部分は削除して大丈夫です。
名前を変換する場合は文字を置き換え、リセットする場合は名前変換のあるタグ(d_name_nvl)の中だけをajaxをつかってリロードしています。
一部のみリロードについてはjavascriptでページを一部だけ更新する方法を参考にしました。
ajaxでもどってくる内容はテキストデータなので、htmlにパースして利用しています。テキストのパースは【JavaScript】DOMParserで文字列をHTMLElement・Nodeに変換するを参考にしました。

最後に
実装したのは割と前で記事にしようとしてなかなかできていなかったので、ようやく記事にできて嬉しいです。
「もっと汎用性がほしい!」「もっと使いやすい方がいい!」「全ページ同じにしたい!」と言った場合は、すでに配布されている素晴らしいスクリプトがありますのでそちらを使っていただくといいかもしれないです。
私の場合「名前にいちいちタグしたくない」「デフォルト名が小説によって違う」と自分で用意したほうが良さそうだったので用意してみました。
同じように自作しようとしている方の参考になれば幸いですっ

それと記事に関係ないですが、Twitter(現X)アカウントを作成しました!
Twitter(現X)アカウント てくてく@個人サイト向け情報共有サイト
よければフォローして下さいませ♪

参考
js-cookie
CDN js-cookie
javascriptでページを一部だけ更新する方法
JavaScript】DOMParserで文字列をHTMLElement・Nodeに変換する
DOMParser の parseFromString() メソッドの使い方

DASHBOARD

つぶやき

アカウント作成しました!
Twitter(現X)

全文検索

カレンダー

日付一覧を表示
2023年9月
12
3456789
10111213141516
17181920212223
24252627282930

Thanks !

Link

Contact

目次