フレームワーク無しHTMLテーブルをBootstrapで再現する場合

過去に作ったフレームワーク無しのHTMLテーブルを、Bootstrap（5）で再現しようとすると、
過去、手抜き、つまりborder-bottom: noneなどで罫線を消してセル結合っぽくしている場合、
（HTML的にはダメですが、ループを回してDBを表示する場合めんどくさくてつい…）
融通がきかなくて困る場合があります（した）。

##こういうテーブルです
![テーブルサンプル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/458322/95270db3-5f40-70b3-90fe-3c011720d443.png)

##手抜きをBootstrapで再現
###HTML

* 罫線無しのテーブル（table-borderless）にして、個別にborderを設定する
* 同列の前行が違う値だったら、border-topを追加設定する

```html
<div class="table-responsive">
  <table class="table table-borderless">
    <thead class="text-center table-light">
      <tr>
        <th scope="col" class="border-start border-top" nowrap>部名</th>
        <th scope="col" class="border-start border-top" nowrap>部長</th>
        <th scope="col" class="border-start border-top" nowrap>課名</th>
        <th scope="col" class="border-start border-top" nowrap>課長</th>
        <th scope="col" class="border-start border-top" nowrap>ユニット名</th>
        <th scope="col" class="border-start border-top" nowrap>ユニット<br>リーダー</th>
        <th scope="col" class="border-start border-end border-top" nowrap>メンバー</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="border-start border-top" nowrap>何でも部</td>
        <td class="border-start border-top" nowrap>部長 太郎</td>
        <td class="border-start border-top" nowrap>課１</td>
        <td class="border-start border-top" nowrap>課長 太郎</td>
        <td class="border-start border-top" nowrap>ユニット１</td>
        <td class="border-start border-top" nowrap>ユニット 太郎</td>
        <td class="border-start border-end border-top" nowrap>メンバー 太郎</td>
      </tr>
      <tr>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start border-end" nowrap>メンバー 次郎</td>
      </tr>
      <tr>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start border-top" nowrap>ユニット２</td>
        <td class="border-start border-top" nowrap>ユニット 次郎</td>
        <td class="border-start border-end  border-top" nowrap>メンバー 三郎</td>
      </tr>
      <tr>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start border-bottom" nowrap></td>
        <td class="border-start border-bottom" nowrap></td>
        <td class="border-start border-bottom" nowrap></td>
        <td class="border-start border-bottom" nowrap></td>
        <td class="border-start border-end border-bottom" nowrap>メンバー 四郎</td>
      </tr>
      <tr>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start border-top" nowrap>課２</td>
        <td class="border-start border-top" nowrap>課長 次郎</td>
        <td class="border-start border-top" nowrap>ユニット１</td>
        <td class="border-start border-top" nowrap>ユニット 三郎</td>
        <td class="border-start border-end border-top" nowrap>メンバー 五郎</td>
      </tr>
      <tr>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start border-end" nowrap>メンバー 六郎</td>
      </tr>
      <tr>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start" nowrap></td>
        <td class="border-start border-top" nowrap>ユニット２</td>
        <td class="border-start border-top" nowrap>ユニット 四郎</td>
        <td class="border-start border-end  border-top" nowrap>メンバー 七郎</td>
      </tr>
      <tr>
        <td class="border-start border-bottom" nowrap></td>
        <td class="border-start border-bottom" nowrap></td>
        <td class="border-start border-bottom" nowrap></td>
        <td class="border-start border-bottom" nowrap></td>
        <td class="border-start border-bottom" nowrap></td>
        <td class="border-start border-bottom" nowrap></td>
        <td class="border-start border-end border-bottom" nowrap>メンバー 八郎</td>
      </tr>
    </tbody>
  </table>
</div><!-- .table-responsive -->
```

###PHP

```php
<?php
$data[0] = array("何でも部", "部長 太郎", "課１", "課長 太郎", "ユニット１", "ユニット 太郎", "メンバー 太郎");
$data[1] = array("何でも部", "部長 太郎", "課１", "課長 太郎", "ユニット１", "ユニット 太郎", "メンバー 次郎");
$data[2] = array("何でも部", "部長 太郎", "課１", "課長 太郎", "ユニット２", "ユニット 次郎", "メンバー 三郎");
$data[3] = array("何でも部", "部長 太郎", "課１", "課長 太郎", "ユニット２", "ユニット 次郎", "メンバー 四郎");
$data[4] = array("何でも部", "部長 太郎", "課２", "課長 次郎", "ユニット１", "ユニット 三郎", "メンバー 五郎");
$data[5] = array("何でも部", "部長 太郎", "課２", "課長 次郎", "ユニット１", "ユニット 三郎", "メンバー 六郎");
$data[6] = array("何でも部", "部長 太郎", "課２", "課長 次郎", "ユニット２", "ユニット 四郎", "メンバー 七郎");
$data[7] = array("何でも部", "部長 太郎", "課２", "課長 次郎", "ユニット２", "ユニット 四郎", "メンバー 八郎");
?>
<div class="table-responsive">
  <table class="table table-borderless">
    <thead class="text-center table-light">
      <tr>
        <th scope="col" class="border-start border-top" nowrap>部名</th>
        <th scope="col" class="border-start border-top" nowrap>部長</th>
        <th scope="col" class="border-start border-top" nowrap>課名</th>
        <th scope="col" class="border-start border-top" nowrap>課長</th>
        <th scope="col" class="border-start border-top" nowrap>ユニット名</th>
        <th scope="col" class="border-start border-top" nowrap>ユニット<br>リーダー</th>
        <th scope="col" class="border-start border-end border-top" nowrap>メンバー</th>
      </tr>
    </thead>
    <tbody>
<?php
$i = 0;
while($i < count($data)){
    echo "      <tr>\n";
    //先頭行
    if ($i == 0) {
        echo "        <td class=\"border-start border-top\" nowrap>{$data[$i]{0}}</td>\n";
        echo "        <td class=\"border-start border-top\" nowrap>{$data[$i]{1}}</td>\n";
        echo "        <td class=\"border-start border-top\" nowrap>{$data[$i]{2}}</td>\n";
        echo "        <td class=\"border-start border-top\" nowrap>{$data[$i]{3}}</td>\n";
        echo "        <td class=\"border-start border-top\" nowrap>{$data[$i]{4}}</td>\n";
        echo "        <td class=\"border-start border-top\" nowrap>{$data[$i]{5}}</td>\n";
        echo "        <td class=\"border-start border-end border-top\" nowrap>{$data[$i]{6}}</td>\n";
    } else {
        //先頭行後
        $j = 0;
        while($j <= 6){
            //上罫線
            $borderTop = " border-top";
            //表示データ
            $val = $data[$i][$j];
            //同列の前行と値が一致したら上罫線と値は表示しない
            if ($data[$i]{$j} == $data[$i - 1]{$j}) {
                $borderTop = "";
                $val = "";
            }
            //右罫線
            //最終列のみ右罫線表示
            $borderEnd = "";
            if ($j == 6) {
                $borderEnd = " border-end";
            }
            //下罫線
            //最終行のみ下罫線表示
            $borderBottom = "";
            if ($i == count($data) - 1) {
                $borderBottom = " border-bottom";
            }
            echo "        <td class=\"border-start{$borderEnd}{$borderTop}{$borderBottom}\" nowrap>{$val}</td>\n";
            $j++;
        }
    }
    echo "      </tr>\n";
    $i++;
}
?>
    </tbody>
  </table>
</div><!-- .table-responsive -->
```

##HTML的に正しいやり方
###HTML

* 罫線有りのテーブル（table-bordered）にして、同じ値の場合はセルを結合する

```html
<div class="table-responsive">
  <table class="table table-bordered">
    <thead class="text-center table-light">
      <tr>
        <th scope="col" nowrap>部名</th>
        <th scope="col" nowrap>部長</th>
        <th scope="col" nowrap>課名</th>
        <th scope="col" nowrap>課長</th>
        <th scope="col" nowrap>ユニット名</th>
        <th scope="col" nowrap>ユニット<br>リーダー</th>
        <th scope="col" nowrap>メンバー</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td nowrap rowspan="8">何でも部</td>
        <td nowrap rowspan="8">部長 太郎</td>
        <td nowrap rowspan="4">課１</td>
        <td nowrap rowspan="4">課長 太郎</td>
        <td nowrap rowspan="2">ユニット１</td>
        <td nowrap rowspan="2">ユニット 太郎</td>
        <td  nowrap>メンバー 太郎</td>
      </tr>
      <tr>
        <td nowrap>メンバー 次郎</td>
      </tr>
      <tr>
        <td nowrap rowspan="2">ユニット２</td>
        <td nowrap rowspan="2">ユニット 次郎</td>
        <td nowrap>メンバー 三郎</td>
      </tr>
      <tr>
        <td nowrap>メンバー 四郎</td>
      </tr>
      <tr>
        <td nowrap rowspan="7">課２</td>
        <td nowrap rowspan="7">課長 次郎</td>
        <td nowrap rowspan="2">ユニット１</td>
        <td nowrap rowspan="2">ユニット 三郎</td>
        <td nowrap>メンバー 五郎</td>
      </tr>
      <tr>
        <td nowrap>メンバー 六郎</td>
      </tr>
      <tr>
        <td nowrap rowspan="2">ユニット２</td>
        <td nowrap rowspan="2">ユニット 四郎</td>
        <td nowrap>メンバー 七郎</td>
      </tr>
      <tr>
        <td nowrap>メンバー 八郎</td>
      </tr>
    </tbody>
  </table>
</div><!-- .table-responsive -->
```

###PHP

* 同列の前行と違う値だったら（同じ値だったら何も表示しない）
* 内部に別ループを回し、同列の次行と違う値になるまでループを回し、結合数(rowspan)をカウント
* rowspanに結合数を設定

```php
<?php
$data[0] = array("何でも部", "部長 太郎", "課１", "課長 太郎", "ユニット１", "ユニット 太郎", "メンバー 太郎");
$data[1] = array("何でも部", "部長 太郎", "課１", "課長 太郎", "ユニット１", "ユニット 太郎", "メンバー 次郎");
$data[2] = array("何でも部", "部長 太郎", "課１", "課長 太郎", "ユニット２", "ユニット 次郎", "メンバー 三郎");
$data[3] = array("何でも部", "部長 太郎", "課１", "課長 太郎", "ユニット２", "ユニット 次郎", "メンバー 四郎");
$data[4] = array("何でも部", "部長 太郎", "課２", "課長 次郎", "ユニット１", "ユニット 三郎", "メンバー 五郎");
$data[5] = array("何でも部", "部長 太郎", "課２", "課長 次郎", "ユニット１", "ユニット 三郎", "メンバー 六郎");
$data[6] = array("何でも部", "部長 太郎", "課２", "課長 次郎", "ユニット２", "ユニット 四郎", "メンバー 七郎");
$data[7] = array("何でも部", "部長 太郎", "課２", "課長 次郎", "ユニット２", "ユニット 四郎", "メンバー 八郎");
?>
<div class="table-responsive">
  <table class="table table-bordered">
    <thead class="text-center table-light">
      <tr>
        <th scope="col" class="border-start border-top" nowrap>部名</th>
        <th scope="col" class="border-start border-top" nowrap>部長</th>
        <th scope="col" class="border-start border-top" nowrap>課名</th>
        <th scope="col" class="border-start border-top" nowrap>課長</th>
        <th scope="col" class="border-start border-top" nowrap>ユニット名</th>
        <th scope="col" class="border-start border-top" nowrap>ユニット<br>リーダー</th>
        <th scope="col" class="border-start border-end border-top" nowrap>メンバー</th>
      </tr>
    </thead>
    <tbody>
<?php
$i = 0;
while($i < count($data)){
    echo "      <tr>\n";
    //先頭行
    if ($i == 0) {
        $j = 0;
        while($j <= 6){
            //表示データ
            $val = $data[$i][$j];
            //同列の次行と値が一致したら
            if ($data[$i]{$j} == $data[$i + 1]{$j}) {
                //結合数
                $r = 1;
                //現行から開始
                $n = $i;
                while($n < count($data) - 1){
                    if ($data[$i]{$j} == $data[$n + 1]{$j}) {
                        $r++;
                    } else {
                        break;
                    }
                    $n++;
                }
                echo "        <td rowspan=\"{$r}\" nowrap>{$val}</td>\n";
            } else {
                //以外は値のみ表示
                echo "        <td nowrap>{$val}</td>\n";
            }
            $j++;
        }
    } else {
        //先頭行後
        $j = 0;
        while($j <= 6){
            //表示データ
            $val = $data[$i][$j];
            //同列の前行と値が一致しなかったら（一致したら何も表示しない）
            if ($data[$i]{$j} != $data[$i - 1]{$j}) {
                //結合数
                $r = 1;
                //現行から開始
                $n = $i;
                while($n < count($data) - 1){
                    if ($data[$i]{$j} == $data[$n + 1]{$j}) {
                        $r++;
                    } else {
                        break;
                    }
                    $n++;
                }
                echo "        <td rowspan=\"{$r}\" nowrap>{$val}</td>\n";
            }
            $j++;
        }
    }
    echo "      </tr>\n";
    $i++;
}
?>
    </tbody>
  </table>
</div><!-- .table-responsive -->
```