javascriptで、正規表現にマッチした箇所に置換しつつ連番を振ることができるにするコードを書きました。 本ブログの右側にある目次一覧のリンクをクリックした時にジャンプする機能を実装するにあたり、どうしても必要だったので。 せっかくなので、本記事でそのライブラリと使い方をまとめました。

例えば、このような構成のHTMLの構成があったとして、 <h2>と<h3>に正規表現で置き換えをしつつ連番を振りたいとする。

<h2>このブログについて</h2>
~~記事中略~~
<h2>技術ブログです。</h2>
<h3>javascript</h3>
~~記事中略~~
<h3>vue.js</h3>
~~記事中略~~
<h2>wordpress</h2>
<h3>レンタルサーバ</h3>
~~記事中略~~
<h4>さくらインターネット</h4>
~~記事中略~~
<h2>運用方法</h2>
~~記事中略~~

正規表現のみだと連番を振ることができないので、 このように設定したら正規表現で置換をしつつ、{count}の部分が連番になり、

{
  "<h2(.*)>" : `<h2 id="section_{count}"$1>`,
  "<h3(.*)>" : `<h3 id="section_{count}"$1>`
}

このように置換がされるコードです。

<h2 id="section_1">このブログについて</h2>
~~記事中略~~
<h2 id="section_2">技術ブログです。</h2>
<h3 id="section_1">javascript</h3>
~~記事中略~~
<h3 id="section_2">vue.js</h3>
~~記事中略~~
<h2 id="section_3">wordpress</h2>
<h3 id="section_3">レンタルサーバ</h3>
~~記事中略~~
<h4>さくらインターネット</h4>
~~記事中略~~
<h2 id="section_4">運用方法</h2>
~~記事中略~~

h2とh3の{count}の部分が、1から連番で番号が振られています。 それ以外の部分は正規表現による置換が適用されています。 これを実現するソースコードが以下になります。

str_regex.js

export default {
  /**
   * 正規表現により文字列の置き換えを行う
   * @param {string} text 置き換え対象の文字列
   * @param {Object} replace_arr 置き換え条件
   *        example {
   *            "<h2(.*)>" : "<h2 id='section_h2_{count}'$1>",
   *            "<h3>" : "<h3 id='section_h3_{count}'>",
   *            "<h4>" : "<h4 id='section_h4_{count}'>"
   *        }
   *        {count}の部分は連番に置き換わる。
   * @return {string} 置き換え後の文字列
   */
  str_replace_regex : function(text, replace_arr) {
    var result = text;
    var main_count = 1;

    // 置き換え対象数分ループ
    for (var val in replace_arr) {
      var pattern = new RegExp(val, "g");
      var reg_after_str = this.stg_replace_regex_one(result, pattern, replace_arr[val]);

      result = reg_after_str;
      main_count++;
    };
    return result;
  },

  /**
   * 正規表現により文字列の置き換えを行う (一つの置き換えルールについて実施)
   * @param {string} text 置き換え対象の文字列
   * @param {RegExp} pattern 正規表現パターン
   * @param {string} replace 置き換え文字列
   *                           {count}の部分は連番に置き換わる。
   * @return {string} 置き換え後の文字列                    
   */
  stg_replace_regex_one : function(text, pattern, replace) {
    var result = text;

    // マッチした行番号などの情報を取得
    var match_result = result.search(pattern);
    var matches = [];
    var match_arr;

    while (match_arr = pattern.exec(text)) {
      matches.push(match_arr);
    }

    // マッチした行がなかった場合はコンティニュー
    if (matches.length == 0) {
      return result;
    }

    // マッチした行数分ループ
    var count = 0;
    var reg_after_str = "";
    var index = 0;

    for (var match_i = 0; match_i <= matches.length; match_i++) {
      var match = matches[match_i];
      var next_index = match_i < matches.length ? matches[match_i]['index'] : result.length;
      var sub_str = result.substring(index, next_index);
      var one_replace = this.str_replace(replace, {
        "{count}" : count
      });

      var add_pattern = pattern;
      var add_value = one_replace;

      reg_after_str += sub_str.replace(add_pattern, add_value);
      index = next_index;
      count++;
    };
    result = reg_after_str;
    return result;
  },

  /**
   * 実際の文字列の置き換えを行う
   * @param {string} text 返還対象の文字列
   * @param {Object} replace ルール
   * @return {string} 置き換え後の文字列
   */
  str_replace : function(text, replace) {
    var result = text;

    for (var val in replace) {
      var pattern = new RegExp(val, "g");
      var value = String(replace[val]);

      result = result.replace(pattern, value);
    }
    return result;
  }
}

使い方

本ブログではこのように使いました。

import str_regex from '~/commons/str_regex';
~~中略~~
var setArticle = function(content) {
  // h2, h3タグなどにIDを割り振る
  return str_regex.str_replace_regex(content, {
    "<h2(.*)>" : `<h2 id="section_h2_{count}"$1>`,
    "<h3(.*)>" : `<h3 id="section_h3_{count}"$1>`,
    "<h4(.*)>" : `<h4 id="section_h4_{count}"$1>`,
    "<h5(.*)>" : `<h5 id="section_h5_{count}"$1>`,
    "<h6(.*)>" : `<h6 id="section_h6_{count}"$1>`
  });
}