【JS】テキストを一定の文字数で切り捨てる

JavaScript

こんにちは、しきゆらです。
今回は、SNSなどで時々見るような「一定数を超えたテキストを切り捨てる」処理を作ったのでメモしておきます。

結論: 以下のコードでいい感じに端折れる

const truncate = (str, max) => {
  if (str.length < max) return str;

  return `${str.slice(0, max - 1)}…`;
};

const truncateStringExceptURL = (str, max) => {
  if (!str) return "";

  // URLの正規表現
  const urlRegexp = /https?:\\/\\/\\S+/g;
  // マッチしたURLリスト
  const urls = [];
  // 文字列からURLにマークして除外する
  const markedString = str.replace(urlRegexp, (matchStr) => {
    urls.push(matchStr);

    return `>${urls.length - 1}<`;
  });

  // テキストを切り捨てる
  const truncatedString = truncate(markedString, max);

  // マークしたURL箇所の正規表現
  const replaceUrlRegexp = />\\d+</;
  // マークをURLに置換
  return truncatedString.replace(replaceUrlRegexp, (matchStr) => {
    const urlIndex = parseInt(matchStr.replace(/>|</, ""), 10);

    return urls[urlIndex];
  });
};

細かく見ていきます。

単純に文字数で切り捨てる

単純に一定の文字数で切り捨てるだけであれば、以下だけで実現できます。
なお、ここでは文字数をString.lengthで簡易的に取得していますが、文字コード等によっては意図しない数になりそうです。
正確な文字数に関しては、以下が参考になると思います。

const truncate = (str, max) => {
  if (str.length < max) return str;

  return `${str.slice(0, max - 1)}…`;
};

ただし、上記処理だとURLが入っている場合はURLの途中で切り捨てられてしまいます。
それが問題にならなければよいのですが、私の場合はURLは残した方が便利だったのでURL以外の部分で切り捨てるようにしてみた結果が、最初のコードでした。

処理の流れとしては、以下の通り。

  • 文字列に含まれるURLを特定のマーカに置換
  • マーカに置換した文字列を指定された文字数に切り捨て
  • 切り捨てられた文字列にあるURLマーカをURLに戻す

それぞれ見てみます。

文字列に含まれるURLを特定のマーカに置換

該当部分を抜き出します。

const truncateStringExceptURL = (str, max) => {
 ...
  // URLの正規表現
  const urlRegexp = /https?:\\/\\/\\S+/g;
  // マッチしたURLリスト
  const urls = [];
  // 文字列からURLにマークして除外する
  const markedString = str.replace(urlRegexp, (matchStr) => {
    urls.push(matchStr);

    return `>${urls.length - 1}<`;
  });
  ...
}

URLを表現する正規表現を定義し、文字列内にあれば配列に格納しつつマーカに保管しています。

String.prototype.replace()は第1引数に検知するパターン、第2引数に置換するものを渡しますが、第2引数は文字列以外にも関数も渡すことができます。

なお、この処理自体は過去記載した「YAMLに環境変数を埋め込む」や「YAMLにローカル変数を埋め込む」と同じ発想です。

マーカに置換した文字列を指定された文字数に切り捨て

該当部分を抜き出します。

const truncate = (str, max) => {
  if (str.length < max) return str;

  return `${str.slice(0, max - 1)}…`;
};

const truncateStringExceptURL = (str, max) => {
  ...
  // テキストを切り捨てる
  const truncatedString = truncate(markedString, max);
  ...
}

URLをマーカに置換した文字列に対して、指定数以上の文字列を切り捨てる処理をかませます。

単純にString.prototype.slice()で先頭から最大数-1までの文字列を作り、末尾に…を入れてます。

切り捨てられた文字列にあるURLマーカをURLに戻す

該当箇所を抜き出します。

const truncateStringExceptURL = (str, max) => {
 ...
 // マークしたURL箇所の正規表現
  const replaceUrlRegexp = />\\d+</;
  // マークをURLに置換
  return truncatedString.replace(replaceUrlRegexp, (matchStr) => {
    const urlIndex = parseInt(matchStr.replace(/>|</, ""), 10);

    return urls[urlIndex];
  });
  ...
}

ここでは、マーカを正規表現で表現し、切り捨てた文字列からマーカ部分をURLに置換しています。
処理的にはマーカに置き換える処理の反対なので特に複雑なことはないかと。

マーカ部分をもう少し綺麗に書けないかな、と思っていますがいい方法思いついてません。
URLをマーカに置換する部分と、マーカからURLに置換するが分かれているので何とかしたい気持ち。

まとめ

今回は、SNSなどでよく見るような一定数を超えた文字列を端折る処理を書いたのでメモしました。

改めて全体を記載しておきます。

const truncate = (str, max) => {
  if (str.length < max) return str;

  return `${str.slice(0, max - 1)}…`;
};

const truncateStringExceptURL = (str, max) => {
  if (!str) return "";

  // URLの正規表現
  const urlRegexp = /https?:\\/\\/\\S+/g;
  // マッチしたURLリスト
  const urls = [];
  // 文字列からURLにマークして除外する
  const markedString = str.replace(urlRegexp, (matchStr) => {
    urls.push(matchStr);

    return `>${urls.length - 1}<`;
  });

  // テキストを切り捨てる
  const truncatedString = truncate(markedString, max);

  // マークしたURL箇所の正規表現
  const replaceUrlRegexp = />\\d+</;
  // マークをURLに置換
  return truncatedString.replace(replaceUrlRegexp, (matchStr) => {
    const urlIndex = parseInt(matchStr.replace(/>|</, ""), 10);

    return urls[urlIndex];
  });
};

1冊ですべて身につくJavaScript入門講座

Mana
2,738円(05/18 20:21時点)
発売日: 2023/02/28
Amazonの情報を掲載しています
確かな力が身につくJavaScript「超」入門 第2版

確かな力が身につくJavaScript「超」入門 第2版

狩野 祐東
2,673円(05/18 22:16時点)
発売日: 2019/09/20
Amazonの情報を掲載しています

今回は、ここまで。

おわり

Posted by しきゆら