0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

見出しとかの日本語をいい感じの塊で自動改行するCSSのためのHTMLを作るプログラム

0
Posted at

なんなんだこのタイトル。

さて、以前「見出しとかの日本語をいい感じの塊で自動改行するCSS」という記事を投稿しました。

しかし、塊を明記するために結構めんどくさいHTMLを書く必要があり、微妙に使いにくいものでした。

See the Pen 見出しとかの日本語をいい感じの塊で自動改行するCSSのサンプル2 by 松田美文 (@mifumi323) on CodePen.

なんなんだこの例文。

とにかく、ちゃんとやろうとするとHTMLがこのように長く入り組んだものになってしまい、とても手書きでは対応できません(できても書きたくありません)。
もっと簡単な記述でこれ相当のHTMLを出せるプログラムを作りましょう。

目標

塊を記述すること自体はどうしても必要なので、簡単な記述からHTMLを生成できるようにしましょう。

入力例
ウルトラ|||マン||コスモスが|個性的な||お菓子||||作りを|||する|パチンコ||発見

上記のように、パイプ「|」で塊を区切り、以下のようなHTMLを得ることを目標とします。

出力例
<span class="line-chunk"><span class="line-chunk"><span class="line-chunk">ウルトラ</span><span class="line-chunk">マン</span></span><span class="line-chunk">コスモスが</span></span><span class="line-chunk"><span class="line-chunk">個性的な</span><span class="line-chunk"><span class="line-chunk"><span class="line-chunk">お菓子</span><span class="line-chunk">作りを</span></span><span class="line-chunk">する</span></span></span><span class="line-chunk"><span class="line-chunk">パチンコ</span><span class="line-chunk">発見</span></span>

連続した|の数を多くするほど細かい塊の区切りとすることにします。

|    レベル1
||   レベル2
|||  レベル3

できたPHP

なんでPHPかって?いつも使ってるからだよ。

<?php

class InsertLineChunkTags
{
    /**
     * 改行可能なポイントを設定します。
     * 半角パイプ「|」の位置が改行可能な位置となり、連続する「|」の数が多いほど細かい分割単位となります。
     * 「1234」→改行ポイント設定なし。
     * 「12|34」→「1234」が表示しきれない場合は「12」と「34」に分割。
     * 「1||2|3||4」→上記に加え、「12」が表示しきれない場合は「1」と「2」、「34」についても同様。
     */
    public static function convert(string $html): string
    {
        return self::impl($html, 1);
    }

    private static function impl(string $html, int $level): string
    {
        $separator = str_repeat('|', $level);
        if (!str_contains($html, $separator)) {
            return $html;
        }
        $chunks = preg_split('/(?<!\|)\|{'.$level.'}(?!\|)/', $html);
        $next_level = $level + 1;
        $result = '';
        foreach ($chunks as $chunk) {
            $chunk_head = '';
            $chunk_body = $chunk;
            $chunk_tail = '';
            if (preg_match('/\S/u', $chunk)) {
                if (preg_match('/^(\s*)(.*\S)(\s*)$/u', $chunk, $matches)) {
                    $chunk_head = $matches[1];
                    $chunk_body = $matches[2];
                    $chunk_tail = $matches[3];
                }
            }
            $result .= $chunk_head;
            $result .= '<span class="line-chunk">';
            $result .= self::impl($chunk_body, $next_level);
            $result .= '</span>';
            $result .= $chunk_tail;
        }

        return $result;
    }
}

解説いるかな?技術記事だし一応しとくか。

最初の正規表現は、前後に余分な|がついてない$level個連続の|で文字列を分割しています。
(?<!\|)(?!\|)は否定の言明で、文字数を消費せず場所にマッチします。

2つ目の正規表現は、スペースの扱いのケアです。
今回の例文にはスペース含まれないんですけどね。

変数名$htmlって書いてますが、ただspanタグを入れるだけで、ちゃんとHTMLを考慮したものにはなっていません。
XSS対策などはしてないので、あらかじめHTMLエスケープされている想定です。

実用上実害はないと思いますが、「ある||でる」みたいに|のレベルが飛んでいると余分なspanが付きます。

JavaScriptも

ここから先、AI生成物が含まれます。

さて、今回は自分で使っていた都合でPHPを使いましたが、他の言語でやるにしてもだいたいは同じです。
ブラウザ上でリアルタイムに処理できるように、JavaScriptでやってみましょうか。
既にPHPでの正解のコードはあるので、AIに頼っちゃいましょう。

class InsertLineChunkTags {
  static convert(html) {
    return this.impl(html, 1);
  }

  static impl(html, level) {
    const separator = '|'.repeat(level);
    if (!html.includes(separator)) {
      return html;
    }

    const chunks = this.splitBySeparator(html, level);
    const nextLevel = level + 1;
    let result = '';

    for (const chunk of chunks) {
      let chunkHead = '';
      let chunkBody = chunk;
      let chunkTail = '';

      if (/\S/u.test(chunk)) {
        const matches = chunk.match(/^(\s*)(.*\S)(\s*)$/u);
        if (matches) {
          chunkHead = matches[1];
          chunkBody = matches[2];
          chunkTail = matches[3];
        }
      }

      result += chunkHead;
      result += '<span class="line-chunk">';
      result += this.impl(chunkBody, nextLevel);
      result += '</span>';
      result += chunkTail;
    }

    return result;
  }

  static splitBySeparator(html, level) {
    const separator = '|'.repeat(level);
    const parts = [];
    let start = 0;

    for (let i = 0; i <= html.length - level; ) {
      if (html.slice(i, i + level) === separator) {
        const before = i === 0 ? null : html[i - 1];
        const after = i + level >= html.length ? null : html[i + level];

        if (before !== '|' && after !== '|') {
          parts.push(html.slice(start, i));
          i += level;
          start = i;
          continue;
        }
      }
      i += 1;
    }

    parts.push(html.slice(start));
    return parts;
  }
}

GitHub Copilotの無料プランで「このInsertLineChunkTagsのJavaScript版作って。」とだけ指示して作らせました。
最初の正規表現の代わりにsplitBySeparator関数が生えてるとか、impl関数が普通に公開されてるとか、実装に多少違いはありますが、いい感じですね。

Gitリポジトリ

動作確認環境

PHP 8.3.13
Google Chrome 149.0.7827.201

おわりに

一度コードを書いたらAIで他言語版作るんもありですね。
もちろん確認は必要でしょうが。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?