2024-04-27に更新

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

こんにちは、しきゆらです。
今回は、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で簡易的に取得していますが、文字コード等によっては意図しない数になりそうです。
正確な文字数に関しては、以下が参考になると思います。
JavaScript: 文字数を正確にカウントするには? #JavaScript - Qiita https://qiita.com/suin/items/3da4fb016728c024eaca

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

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

String.prototype.slice() - JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/slice

この中で、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引数は文字列以外にも関数も渡すことができます。

String.prototype.replace() - JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/replace

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

【Ruby】YAMLに環境変数を埋め込む | しきゆらの備忘録 https://shikiyura.com/2021/03/embed_env_value_in_yaml/

【Ruby】YAMLにローカル変数を埋め込む | しきゆらの備忘録 https://shikiyura.com/2021/10/embed_local_variable_in_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までの文字列を作り、末尾に…を入れてます。

String.prototype.slice() - JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/slice

切り捨てられた文字列にある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];
  });
};

今回は、ここまで。

おわり

Originally published at shikiyura.com
ツイッターでシェア
みんなに共有、忘れないようにメモ

しきゆら

勉強したり手を動かした記録を「しきゆらの備忘録」(http://shikiyura.com)へ投稿している人。 Ruby/JavaScriptをよく書いている。いろんな言語に触れてみたい。新しい物・辛いもの好き。バグは愛すべきもの。一応社会人。

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント