TeXの表を自動で作成しよう。

# TeXとは？
TeXとは**活版印刷の技法をコンピューター上で実現するための組版システム**です。
いわゆるWordのような文書ソフトなのですが、扱うにはとても癖があります。
TeX独特の書き方があります。
そして、表を作るのもすごく面倒くさい！！
Wordなら、**挿入タブ**から表を選択するだけで挿入できますよね？
でもTeXなら以下のようなことをしなければなりません。
```
\begin{tabular}{|c|c|} \hline
品名 & 単価 \\ \hline
みかん & 50 \\ \hline
```

あー、、、非常に面倒くさいですよね
でも、TeXにはいいところもあります。
構造的に書けるので、Wordのように番号が途中からわけわからなくなるということがなくなります。
例
1. セクション１
1.1 サブセクション1.1
1.2 サブセクション1.2
2. セクション2
3. サブセクション2.2

あれ、セクション2.2にしたかったのに3になってしまった
これが長い文書なら非常に修正に時間がかかります。
一方で、TeXなら以下のように書けます。
```
\section{セクション1}
    \subsection{サブセクション1.1}
    \subsection{サブセクション1.2}
\section{セクション2}
    \subsection{サブセクション2.1}
```

このように書くだけで自動で番号が振られます。

# 表を自動で作る
**仕様**
- 実際にデータがあるセルを見つけ、表が何列何行になるか数える。
- 結合セルに対応している。
- 一番上の行をヘッダーとして処理する。

でも表を書くのが面倒くさいということで、GoogleSpreadsheetで自動化しました。
**コードはこちら**
```
function convertCleanSheetToLatexLongtable() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const range = sheet.getDataRange();
  const values = range.getValues();
  const mergedRanges = range.getMergedRanges();

  // 実際にデータがある範囲を見つける
  let minRow = values.length, maxRow = 0;
  let minCol = values[0].length, maxCol = 0;
  for (let r = 0; r < values.length; r++) {
    for (let c = 0; c < values[r].length; c++) {
      if (values[r][c] !== "") {
        minRow = Math.min(minRow, r);
        maxRow = Math.max(maxRow, r);
        minCol = Math.min(minCol, c);
        maxCol = Math.max(maxCol, c);
      }
    }
  }

  const numRows = maxRow - minRow + 1;
  const numCols = maxCol - minCol + 1;

  const data = [];
  for (let r = minRow; r <= maxRow; r++) {
    data.push(values[r].slice(minCol, maxCol + 1));
  }

  // 結合セル対応
  const mergeMap = {};
  const skipMap = {};
  mergedRanges.forEach(range => {
    const row = range.getRow() - 1;
    const col = range.getColumn() - 1;
    const rowspan = range.getNumRows();
    const colspan = range.getNumColumns();

    if (
      row >= minRow && row <= maxRow &&
      col >= minCol && col <= maxCol
    ) {
      const localRow = row - minRow;
      const localCol = col - minCol;
      mergeMap[`${localRow}-${localCol}`] = { rowspan, colspan };

      for (let r = localRow; r < localRow + rowspan; r++) {
        for (let c = localCol; c < localCol + colspan; c++) {
          if (r !== localRow || c !== localCol) {
            skipMap[`${r}-${c}`] = true;
          }
        }
      }
    }
  });

  function escapeLatex(str) {
    if (str instanceof Date) {
      return Utilities.formatDate(str, Session.getScriptTimeZone(), 'yyyy/MM/dd');
    }
    if (typeof str !== 'string') str = String(str);
    return str.replace(/([&%$#_{}~^\\])/g, '\\$1').replace(/\n/g, ' ');
  }

  let latex = `\\begin{longtable}{|${'c|'.repeat(numCols)}}\n\\hline\n`;

  // ヘッダー（1行目）処理
  const headerRow = 0;
  function makeRowLatex(r) {
    let rowStr = '';
    for (let c = 0; c < numCols; c++) {
      if (skipMap[`${r}-${c}`]) continue;

      const value = escapeLatex(data[r][c]);
      const mergeInfo = mergeMap[`${r}-${c}`];

      let cell = '';
      if (mergeInfo) {
        const rowspan = mergeInfo.rowspan;
        const colspan = mergeInfo.colspan;
        if (rowspan > 1 && colspan > 1) {
          cell = `\\multirow{${rowspan}}{*}{\\multicolumn{${colspan}}{|c|}{${value}}}`;
        } else if (rowspan > 1) {
          cell = `\\multirow{${rowspan}}{*}{${value}}`;
        } else if (colspan > 1) {
          cell = `\\multicolumn{${colspan}}{|c|}{${value}}`;
        } else {
          cell = value;
        }
      } else {
        cell = value;
      }

      rowStr += cell;
      if (c < numCols - 1) rowStr += ' & ';
    }
    return rowStr + ' \\\\\n\\hline\n';
  }

  const headStr = makeRowLatex(headerRow);
  latex += headStr;
  latex += '\\endfirsthead\n';
  latex += headStr;
  latex += '\\endhead\n';

  // データ行
  for (let r = 1; r < numRows; r++) {
    latex += makeRowLatex(r);
  }

  latex += '\\end{longtable}';
  Logger.log(latex);
}


function myFunction() {
  convertSheetToLatexLongtableWithHeader();
}
```