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?

LaTeX 文書 (Beamer スライド) の「今回の内容」を空にして「前回の内容」へスライドする Python スクリプト (定期的な報告資料に便利)

0
Last updated at Posted at 2026-05-15

定期的な報告資料作成の便利用に、LaTeX で予め定義した環境の中身を空にする / スライドさせる Python スクリプトを記します (標準ライブラリのみで動きます)。

背景・方針

定期的に報告資料を作成することはあると思います。が、

  • 前回資料をコピーして編集する運用にすると、編集漏れが発生しがちです。また、どこが前回からの差分なのかもわかりにくくなりがちです。
  • テンプレートに記入する運用にすると、テンプレートが陳腐化してもメンテナンスがなおざりになりがちです。また、引き続き掲載すべき情報はコピーしなければなりません。

そこで、LaTeX で「この環境の中身は毎回空にする (または他の環境の中身をスライドさせる)」というフロー情報用環境を定義し、フロー情報とストック情報を区別して資料を作成し、次回資料はスクリプトで作成するようにすることで、上記の課題を解決します。

具体例

下図の左の資料 (Beamer スライド) から、右の次回資料を自動生成します。
なお、「年間日程表」は、引き続き掲載すべきストック情報の例です (遠い未来の予定とか、これまでの成果のまとめとかが相当すると思います)。

image.png

以下を守って運用する必要があります。ただし、2 回目以降の資料作成ではスクリプトの自動記入が機能するので、毎回記入すべきなのは太字の箇所だけです。

  • \newcommand{\zenkai}{2026/04/30} の定義箇所に、前回報告日付を記入します。
    • 2 回目以降は前回資料の「今回報告日付」がスライドされてきます。
  • \newcommand{\konkai}{2026/05/12} の定義箇所に、今回報告日付を定義します。
    • 2 回目以降は前回資料の「次回打合せ:」の箇所のパースが自動で記入されます。
  • myEnvZenkaiItemize 環境 (itemize 環境のラップ) で「前回のトピック」を書きます。
    • 2 回目以降は前回資料の「今回のトピック」の中身がスライドされてきます。
  • myEnvKonkaiItemize 環境 (itemize 環境のラップ) で「今回のトピック」を書きます。 なお、この環境は末尾に「次回打合せ:」が自動で挿入されます。
  • myEnvHoukoku 環境で報告内容を書きます。
  • 引き続き掲載すべきストック情報は、環境の外に書きます。
  • myEnvKonkaiItemize環境の定義箇所の「次回打合せ:」に次回報告日付を記入します。

上図の左側の資料の LaTeX ファイルは以下です (xelatex slide.tex でビルドできます)。

slide.tex
\documentclass[t,unicode,10pt]{beamer}
\usepackage{zxjatype}
\setsansfont[BoldFont={BIZ UDゴシック Bold}]{BIZ UDゴシック}
\setCJKsansfont[BoldFont={BIZ UDゴシック Bold}]{BIZ UDゴシック}
\usepackage{relsize}
\usepackage{booktabs}
\newcommand{\structfg}{\usebeamercolor[fg]{structure}}
\newcommand{\myheader}[1]{\textbf{\relsize{+0.5}\structfg #1}\par\smallskip}

% =============== 変数・環境定義 ===============
\newcommand{\konkai}{2026/05/12}
\newcommand{\zenkai}{2026/04/30}
\newenvironment{myEnvZenkaiItemize}{%
  \myheader{前回報告 (\zenkai) のトピック}
  \begin{itemize}
}{\end{itemize}}
\newenvironment{myEnvKonkaiItemize}{%
  \myheader{今回報告 (\konkai) のトピック}
  \begin{itemize}
}{%
  {\structfg \item 次回打合せ: 2026/05/20 (水) 15時}
  \end{itemize}
}
\newenvironment{myEnvHoukoku}{%
  \myheader{報告内容}
}{}

% ########################################
\begin{document}

\begin{frame}[allowframebreaks]

\begin{myEnvZenkaiItemize}
\item ほげほげについて
\end{myEnvZenkaiItemize}

\bigskip
\begin{myEnvKonkaiItemize}
\item ふがふがについて
\end{myEnvKonkaiItemize}

\bigskip
\begin{myEnvHoukoku}
ふがふがふがふがふがふが\par
ふがふがふがふがふがふが\par
\end{myEnvHoukoku}

\bigskip
\myheader{年間日程表}
\begin{tabular}{ll}
\toprule 2026/07/20 & 海の日 \\
\midrule 2026/08/11 & 山の日 \\
\bottomrule
\end{tabular}

\end{frame}

\end{document}

上記の LaTeX ファイルを元に新しい LaTeX ファイルを生成するスクリプトは以下です。
python new_mtg_deck.py slide.tex slide_new.tex のように実行します。

new_mtg_deck.py
import re
from pathlib import Path
import argparse


def parse_and_reset_next_meeting(text):
    pattern = r'(\\item\s+次回打合せ:\s*)(\d{4}/\d{1,2}/\d{1,2})(\s*)\([^)]*\)(\s*)\d+時'
    m = re.search(pattern, text)
    if m is None:
        raise ValueError('次回打合せが見つかりません')
    next_date = m.group(2)
    text = text[:m.start(2)] + 'Y/m/d' + m.group(3) + '(DOW)' + m.group(4) + 'H時' + text[m.end():]
    return next_date, text


def replace_command(text, name, value):
    pat = rf'^(\\newcommand\{{\\{name}\}}\{{)([^}}]*)(\}})'
    return re.sub(pat, rf'\g<1>{value}\3', text, count=1, flags=re.MULTILINE)


def find_env_content_range(text, env_name, i_char_from=0):
    pattern_begin = rf'^\\begin\{{{re.escape(env_name)}\}}'
    pattern_end = rf'^\\end\{{{re.escape(env_name)}\}}'
    m_begin = re.search(pattern_begin, text[i_char_from:], re.MULTILINE)
    if m_begin is None:
        return None, None
    i_char_start = i_char_from + m_begin.end()
    m_end = re.search(pattern_end, text[i_char_start:], re.MULTILINE)
    if m_end is None:
        raise ValueError(f'\\end{{{env_name}}} が見つかりません')
    i_char_end = i_char_start + m_end.start()
    return i_char_start, i_char_end


def get_env_content(text, env_name):
    i_char_start, i_char_end = find_env_content_range(text, env_name)
    if i_char_start is None:
        return None
    return text[i_char_start:i_char_end]


def replace_env_content(text, env_name, content):
    ranges = []
    i_char_from = 0
    while True:
        i_char_start, i_char_end = find_env_content_range(text, env_name, i_char_from)
        if i_char_start is None:
            break
        ranges.append((i_char_start, i_char_end))
        i_char_from = i_char_end
    for i_char_start, i_char_end in reversed(ranges):
        text = text[:i_char_start] + '\n' + content + '\n' + text[i_char_end:]
    return text


def update_tex(current_slide, new_slide):
    text = Path(current_slide).read_text(encoding='utf-8')
    next_date, text = parse_and_reset_next_meeting(text)
    m = re.search(r'^\\newcommand\{\\konkai\}\{([^}]*)\}', text, re.MULTILINE)
    if not m:
        raise ValueError(r'\konkai が見つかりません')
    old_konkai = m.group(1)
    text = replace_command(text, 'zenkai', old_konkai)
    text = replace_command(text, 'konkai', next_date)
    tbu = r'\alert{To be updated}'
    konkai_content = get_env_content(text, 'myEnvKonkaiItemize').strip()
    if not konkai_content.startswith(r'\item '):
        konkai_content = r'\item ' + tbu
    text = replace_env_content(text, 'myEnvZenkaiItemize', konkai_content)
    text = replace_env_content(text, 'myEnvKonkaiItemize', r'\item ' + tbu)
    text = replace_env_content(text, 'myEnvHoukoku', tbu)
    Path(new_slide).write_text(text, encoding='utf-8', newline='\n')


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('current_slide')
    parser.add_argument('new_slide')
    args = parser.parse_args()
    update_tex(args.current_slide, args.new_slide)

補足

  • 上記の slide.tex ではフレームに allowframebreaks を指定しているので、どんどん内容を増やしても自動的にフレームが区切られます。
  • 上記の Python スクリプトは LaTeX ファイル内に myEnvZenkaiItemize myEnvKonkaiItemize myEnvHoukoku 環境が複数あってもすべて中身を刷新します (myEnvZenkaiItemize myEnvKonkaiItemize 環境が複数あることはあまり想定しないが)。
    • ただし、同名環境の入れ子には対応していません。
    • また、対象環境の \begin{...} と \end{...} はどちらも行頭にあることを想定しています (意図せぬマッチを防ぐためであり、修正したい場合はパターンから行頭記号を除去ください)。
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?