前回よりWebブラウザで使える文字列整形ツールを作っています。Webブラウザで動くなら、スマートフォンでも、PCやタブレットでも同じように動かすことができるのがメリットです。今回は前回よりも複雑な規則でテキスト整形を行うプログラムを紹介します。

  • 今回作る文字列整形ツール - メルマガ向けに番号を振りインデントし、さらにタグを付与する

    今回作る文字列整形ツール - メルマガ向けに番号を振りインデントし、さらにタグを付与する

(続)編集部の仕事をJSで自動化しよう

さて、前回から編集部で定期的に発行しているメールマガジンを自動整形するプログラムを作成しています。実は前回のプログラムは、実際の整形仕様をより簡単にしたものを解いてみました。今回は、処理がもう少し難しくなります。さっそく、お題を確認しましょう。

【お題】

Webシステムから記事タイトルとURLの一覧が出力されます。記事のそれぞれに対して、連番をつけURLに応じたタグをタイトルに付与してください。入力データである記事タイトルとURLの一覧は以下のようなものです。

オンライン結婚式場見学を開始したノバレーゼの成約に向けた取組
https://news.mynavi.jp/article/20200619-1056453/
Windowsユーザーに贈るLinux超入門 第51回
Windows 10にUbuntu 20.04 LTSをインストールする
https://news.mynavi.jp/article/liunx_win-51/
ノートPCと周辺機器の連係が一気に楽になる「ドッキングステーション」
https://news.mynavi.jp/kikaku/beginner_pc-3/
...

これを以下のように出力します。

[ 1] オンライン結婚式場見学を開始したノバレーゼの成約に向けた取組
     https://news.mynavi.jp/article/20200619-1056453/
[ 2] 【連載】Windowsユーザーに贈るLinux超入門 第51回
     Windows 10にUbuntu 20.04 LTSをインストールする
     https://news.mynavi.jp/article/liunx_win-51/
[ 3] 【広告】ノートPCと周辺機器の連係が一気に楽になる「ドッキングステーション」
     https://news.mynavi.jp/kikaku/beginner_pc-3/

このとき、入力と出力を見比べると分かりますが、タイトルに【連載】とか【広告】のようなタグが付与されています。URLの規則を元にこうしたタグを付与します。URLの規則は以下のような規則です。

| 規則     | 付与するタグ
| /article/数字か"-" | タグなし
| /article/アルファベットか数字か"_" | 連載
| /kikaku/... | 広告 

それでは、このルールに沿ったプログラムを作っていきましょう。

データを読むときはデータの区切りを考察しよう

ちなみに、もう一度、入力データに注目してみてください。前回利用した入力データは必ず2行1組となっていましたが、今回のデータは、2行1組のデータだけでなく、3行1組のものもあります。前回は一行ずつ読んでいく時に、偶数行と奇数行でどのようにデータを処理するかを変えていたのですが、今回は、別の手法が必要になります。実際に処理したいデータには、このように明らかに揃っていないものも多くあります。

データを区切ることができるかに注目して考察すると良いでしょう。今回のデータでは、「タイトル、副タイトル、URL」あるいは「タイトル、URL」のどちらかになっています。つまり、データの末尾には必ずURLがあるということです。URLは必ず「http://...」か「https://...」となっているので、これを利用できます。

  • データの末尾行に注目

    データの末尾行に注目

それでは、プログラムを作っていきましょう。画面設計は前回と同じで入力テキストボックスと出力テキストボックスの二つを配置し、画面上部の入力ボックスを編集すると、画面下部の出力ボックスに整形後のテキストが表示されるようにします。

それでは、入力テキストボックスのテキストを読んで、一つずつのデータに分割し配列形式でデータを返すプログラムを紹介します。それは、以下のようになります。

// 入力データを配列形式で得る
function getInputArray(text) {
  // 前後の空白をトリム --- (*1)
  text = text.replace(/^\s+/, '').replace(/\s+$/, '')
  // 改行コードを揃えて一行ずつに分解 --- (*2)
  const lines = text.replace(/\r\n/g, "\n").split("\n")
  // タイトルとURLの組に分ける --- (*3)
  const result = []
  let title = "", subtitle = ""
  for (let i in lines) {
    let line = lines[i]
    // URLかどうか判定 --- (*4)
    if (line.substr(0, 4) == "http") {
      result.push([title, subtitle, line])
      // タイトルとサブタイトルを初期化
      title = ""
      subtitle = ""
      continue
    }
    if (title == "") { // --- (*5)
      title = line
    } else {
      subtitle = line
    }
  }
  return result
}

プログラムの(*1)の部分では、前後の空白を除去します。(*2)の部分では改行をLF(\n)に統一し、LF(\n)を用いて一行ずつのテキストに分割します。(*3)以降の部分でfor構文を利用して一行ずつデータを読みながら、データを結果を表す配列resultに追加していきます。

(*4)の部分でデータの末尾かどうかを判定します。URL行であればそれは各データの末尾を表します。ここでは、行頭がhttpの四文字であればURL行と判断しています。もう少し厳密に判定するには、7文字切り出して「http://」か「https:/」であればURLとしても良いかなと思います。それで、もし、URL行であればそこがデータの区切りなので、それ以前に取得したタイトルデータ、サブタイトルデータを含めて、配列resultに追加します。

そして、(*5)以降の部分はURL行でない場合の処理です。URLでなければ、タイトルかサブタイトルとなります。(*4)でresultにデータを追加してすぐ変数titleとsubtitleを空にします。そのため、変数titleが空であればそれはデータ一行目にあるタイトル、titleが空でなければ、データに行目にあるサブタイトルであることが分かります。それで、それぞれの変数に操作行を設定して、次の行を読むという具合です。

ちょっと複雑でしょうか。箇条書きすると、以下のようになります。

・(1)入力データを一行ずつに区切って、以下の処理を繰り返す
・(2)URL行かどうか判定
・・(3)URL行なら、それ以前で調べたタイトルとサブタイトルを結果配列に追加。
・・(4)タイトルとサブタイトルを空にして、次の行を調べるため(2)に戻る。
・(5)URL行でない場合、それはタイトルかサブタイトル。
・・(6)タイトルが空なら、その行はタイトル。(2)に戻る。
・・(7)タイトルが空でなければ、その行はサブタイトル。(2)に戻る。

URLをもとにタイトルにタグ付けを施そう

次に入力データを元に整形したテキストを出力する処理を作っていきましょう。ただし、出力に際して、URLを元にしてタグを付与する処理が必要になります。そこで、URLを調べてタグを付与するかを調べる関数を作ってみましょう。

// URLに応じてタグを付与する
function getTag(url) {
  let tag = ''
  if (url.match(/article\/[0-9\-]+\//)) { // --- (*1)
// タグなし
  }
  else if (url.match(/article\/[a-zA-Z0-9\-\_]+\//)) { // --- (*2)
    tag = '【連載】'
  }
  else if (url.match(/kikaku\//)) { // --- (*3)
    tag = '【広告】'
  }
  return tag
}

ここでは、引数として与えたURLを正規表現で次々と調べて、タグを付与するかどうかを決定するという処理になっています。正規表現を使うと文字列が完全一致するかどうかを調べるだけでなく、アルファベットか数字か記号かなどパターンを用いて一致するかどうかを調べることができます。

プログラムの(*1)の部分では、URLがarticle/に続いて数字かハイフンかどうかを調べます。(*2)の部分ではarticle/に続いてアルファベットか数字かハイフンかアンダーバーかを調べます。(*3)ではkikaku/という文字列があるかどうかを調べます。このように正規表現を使うと手軽に文字列の一致を調べることができるので便利です。

データを整形して出力しよう

ここまでの部分でデータ整形するための準備が整いました。それでは、入力データをタグを含めて出力しましょう。

// テキストに変換
function convertText(input) {
  const result = []
  for (let i in input) { // 繰り返し出力
    const line = input[i] // 対象となるデータ
    // 分かりやすく意味のある定数に代入
    const title = line[0]
    const subtitle = line[1]
    const url = line[2]
    const tag = getTag(url) // タグを調べる
    const no = pad2(parseInt(i) + 1) // 番号を二桁右寄せ
    // 出力文字列を作る --- (*1)
    let s = `[${no}] ${tag}${title}\n`
    if (subtitle != '') {
      s += "     " + subtitle + "\n"
    }
    s += "     " + url
    result.push(s)
  }
  return result.join("\n")
}

それなりに複雑な処理が必要な部分を関数として定義してしまったので、すっきりとしています。ここで一点だけ、(*1)に注目しましょう。JavaScriptの文字列`...`を使うと変数を手軽に文字列の中に埋め込むことができます。これはテンプレート文字列と呼ばれる機能で、JavaScriptの規格ES6以降で導入された新しい機能です。

`...`の文字列の中に、${変数名} と書くと、そこに変数の値を埋め込んでくれます。今回のようにそれなりに複雑なデータを出力したい場合には、このテンプレート文字列が役立ちます。

実行してみよう

それでは、ここまで作成した完全なプログラムを確認してみましょう。ちょっと長くなったので、こちらに置いてあります。

ソースコードをコピーして「convert-text2.html」という名前で保存したら、Webブラウザにドラッグ&ドロップして実行してみましょう。

  • 今回作った整形プログラム。画面上部にデータを入力すると整形済みのテキストを出力する

    今回作った整形プログラム。画面上部にデータを入力すると整形済みのテキストを出力する

文字列整形処理のまとめ

今回は、編集部でメルマガ発行のために行っている定型処理を処理するプログラムを紹介しました。しかし、実際、これに類する処理は、いろいろなところで行われています。

以前、別の連載でこれに似た定型処理を行うプログラムを「なでしこ」を使って解いたことがありました。こちらです。これは、4行1組のデータを整形するというプログラムで、さらに日付順に並び替えるというものでした。こちらも腕試しにはぴったりのお題でしたが、処理の方法もちょっと似ているので参考になると思います。

実際のところ、こうした小さなプログラムは、自分でササッと作れたら楽しいものです。これまで、何時間もかけて行っていた作業を、自作プログラムで数秒で片付けることができたときは、なんとも言えない達成感があります。読者の皆さん、何かしらのテキスト整形が必要でしたら挑戦してみると良いでしょう。

自由型プログラマー。くじらはんどにて、プログラミングの楽しさを伝える活動をしている。代表作に、日本語プログラミング言語「なでしこ」 、テキスト音楽「サクラ」など。2001年オンラインソフト大賞入賞、2004年度未踏ユース スーパークリエータ認定、2010年 OSS貢献者章受賞。技術書も多く執筆している。