連載記事です。前回、談話ごとの発話総覧を作ったので、今回は話者ごとの発話総覧を作ります。やることはあまり変わりません。完全に自分用の作業メモで、説明もいろいろ足りていないと思いますが、ご容赦ください。
- 第1回: 日本語諸方言コーパスをDB化して遊ぶ (1) 構成を考える
- 第2回: 日本語諸方言コーパスをDB化して遊ぶ (2) SQLite3 で DB 化
- 第3回: 日本語諸方言コーパスをDB化して遊ぶ (3) PHP Laravel で操作する
- 第4回: 日本語諸方言コーパスをDB化して遊ぶ (4) サービスの全体像を決める
- 第5回: 日本語諸方言コーパスをDB化して遊ぶ (5) データベースの移行とモデルの作成
- 第6回: 日本語諸方言コーパスをDB化して遊ぶ (6) 談話ごとの発話総覧を作る
- 第7回: 日本語諸方言コーパスをDB化して遊ぶ (7) 話者ごとの発話総覧を作る ←今ここ
- 第8回: 日本語諸方言コーパスをDB化して遊ぶ (8) ファイル形式変換機能をつける
- 第9回: 日本語諸方言コーパスをDB化して遊ぶ (9) Heroku でデプロイする
画面遷移図
画面遷移図を再掲します。前回よりずっとシンプルなつくりですが、発話単位で分割されているテキストを結合して、文単位に切りなおす操作を挟みます。
コンポーネントのルーティング
画面遷移図のとおりにルーティングします。今回作りこむ2画面を登録しましょう。
+ import SpeakerIndexComponent from "./components/SpeakerIndexComponent";
+ import SpeakerShowComponent from "./components/SpeakerShowComponent";
+ {
+ path: "/speaker",
+ name: "speaker.index",
+ component: SpeakerIndexComponent,
+ props: true
+ },
+ {
+ path: "/speaker/:speakerid",
+ name: "speaker.show",
+ component: SpeakerShowComponent,
+ props: true
+ },
各コンポーネントの作成
話者一覧
特筆すべき箇所はありません。全話者の情報を取得して(getSpeakers
関数)、「話者ID」「話者生年」「話者性別」を表示するだけです。今回はページ幅に余裕があるので[閲覧]ボタンを別途用意しても大丈夫でしょう。
<template>
<table class="table table-sm table-hover">
<thead class="thead-dark">
<tr>
<th>話者 ID</th>
<th>話者生年</th>
<th>話者性別</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="s in speakers" v-bind:key="s.speakerid">
<td>{{ s.speakerid }}</td>
<td>{{ s.speakerbirthyear }}</td>
<td>{{ s.speakersex }}</td>
<td>
<div class="text-right">
<router-link
v-bind:to="{ name: 'speaker.show', params: { speakerid: s.speakerid } }"
>
<button class="btn btn-success btn-sm text-nowrap">
閲覧
</button>
</router-link>
</div>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
data: function() {
return {
speakers: []
};
},
methods: {
getSpeakers() {
spinner.style.opacity = 1;
axios.get("/api/speaker").then(res => {
this.speakers = res.data;
spinner.style.opacity = 0;
});
}
},
mounted() {
this.getSpeakers();
}
};
</script>
話者の発話総覧
話者情報を表示する部分と発話総覧の部分に分かれています。処理もそれぞれに用意します。
前者では、パスで話者IDを渡して(props
の部分)、データベースから当該話者の情報だけ取ってきて表示します(getSpeakerInfo
関数)。
後者では、データベースから発話の全レコードを取得してきて、それをフロントエンドで加工して文単位に切りなおしています(getUtterances
関数)。この処理はサーバでやってもいいかもしれません。
<template>
<div>
<table class="table table-sm table-hover">
<tbody>
<tr class="table-success">
<td nowrap>話者ID</td>
<td>{{ speakerInfo.speakerid }}</td>
</tr>
<tr class="table-success">
<td nowrap>話者生年</td>
<td>{{ speakerInfo.speakerbirthyear }}</td>
</tr>
<tr class="table-success">
<td nowrap>話者性別</td>
<td>{{ speakerInfo.speakersex }}</td>
</tr>
</tbody>
</table>
<div class="pt-3">
<table class="table table-sm table-striped table-hover">
<thead>
<tr class="thead-dark">
<th><div class="text-center">発話</div></th>
</tr>
</thead>
<tbody>
<tr v-for="sentence in sentences" v-bind:key="sentence.id">
<td>
<div class="px-4">{{ sentence.d }}。</div>
<div class="px-4">{{ sentence.s }}。</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
props: {
speakerid: String
},
data: () => {
return {
sentences: [],
speakerInfo: {}
};
},
methods: {
getUtterances() {
axios
.get("/api/speaker/" + this.speakerid + "/utterances")
.then(res => {
// 取得した発話をいったん全て結合する
let dTextStr = "";
let sTextStr = "";
for (let i = 0, i_len = res.data.length; i < i_len; i++) {
// 文節区切りをつぶさないよう適宜スペースを入れる
dTextStr += " " + res.data[i].dialecttext;
sTextStr += " " + res.data[i].standardtext;
}
// 句点で切って this.sentences に入れていく
const dSentences = dTextStr.split("。");
const sSentences = sTextStr.split("。");
for (let i = 0, i_len = dSentences.length; i < i_len; i++) {
// sentences は id, d(ialect), s(tandard) の連想配列にする
if (dSentences[i] && sSentences[i]) {
this.sentences.push({
id: i,
d: dSentences[i].trim(),
s: sSentences[i].trim()
});
}
}
});
},
getSpeakerInfo() {
axios.get("/api/speaker/" + this.speakerid).then(res => {
// correction で返ってくるので [0] で取得する
this.speakerInfo = {
speakerid: res.data[0]["speakerid"],
speakerbirthyear: res.data[0]["speakerbirthyear"],
speakersex: res.data[0]["speakersex"]
};
});
}
},
mounted() {
this.getUtterances();
this.getSpeakerInfo();
}
};
</script>
コントローラへのルーティング
使用するコントローラはひとつ(SpeakerController
)でよさそうなので、API のルーティングも悩むことはありません。関数の名前も適当に考えます。
Route::get('/speaker', 'SpeakerController@index');
Route::get('/speaker/{speakerid}', 'SpeakerController@show');
Route::get('/speaker/{speakerid}/utterances', 'SpeakerController@getUtterances');
コントローラの作成
先ほど使うことにした3つの関数を実装していきます。前回と同様に、Controller で ->first()
するのではなく、->get()
してコレクションを返してフロントエンドで絞る感じにしています。
<?php
namespace App\Http\Controllers;
use App\Models\Speaker;
use Illuminate\Support\Facades\DB;
class SpeakerController extends Controller{
// 全レコード取得
public function index(){
$md = new Speaker();
$data = $md->getData();
return $data;
}
// 指定した話者 ID に一致する話者レコードを返す
public function show(String $speakerid){
$data = DB::table('speaker')
->select('speaker.*')
->where('speaker.speakerid', '=', $speakerid)
->get();
return $data;
}
// 指定した話者 ID をもつ発話をすべて返す
public function getAllUtterances(String $speakerid){
$data = DB::table('utterance')
->select('utterance.*')
->where('utterance.speakerid', '=', $speakerid)
->get();
return $data;
}
}
完成形
うまくいけばこんな具合に表示されるはずです。
次回
第3の機能「ファイルの変換」を作りこんでいきます。