fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 山下 山下 の投稿
返信数: 15
こんにちは.

以下のような新環境を作りたいと思います.
(1)実体は fancyvrb の Verbatim にカウンター付きのラベルをつけたもの.
(2)ラベルに参照ラベルを付ける場合と付けない場合を,ifnextchar[ で分岐させる.

envmath.sty を参考に以下のようなマクロを作ってみたのですが,うまくいきません.エラーメッセージを見ると,\newenvironment{Verba} の \@ifnextchar[{}{} が私の意図通りに機能していないように思えます.

\documentclass{jsarticle}
\usepackage{fancyvrb}
\makeatletter
\newcounter{cntr}[section]
\setcounter{cntr}{0}
\renewcommand{\thecntr}{\thesection.\arabic{cntr}}
\newcommand{\labcntr}{no~\thecntr}
% 本体
\newenvironment{Verba}{%
\@ifnextchar[{\LabeledVerba}{\NoLabelVerba}%
}{\end{Verbatim}}
% 参照ラベルあり
\def\LabeledVerba[#1]{\VerbatimEnvironment
\refstepcounter{cntr}%
\begin{Verbatim}[label=\labcntr\label{#1}, frame=lines, labelposition=topline]}
% 参照ラベルなし
\def\NoLabelVerba{\VerbatimEnvironment
\refstepcounter{cntr}%
\begin{Verbatim}[label=\labcntr, frame=lines, labelposition=topline]}
\makeatother
\begin{document}

\section{最初の節}

\begin{Verba}[aa]
a_b^c
\end{Verba}

\begin{Verba}
d_e^f
\end{Verba}
%↑ NoLabelVerba で処理してくれるのでは? LabeledVerba で処理されているように思える.\@ifnextchar[{}{} が正しく動いてない?

\ref{aa}

\end{document}


一方,LabeledVerba と NoLabelVerba を
\def\LabeledVerba[#1]{\begin{equation}\label{#1}}
\def\NoLabelVerba{\begin{equation}}
に差し替えると正しく動きますので,\newenvironment{Verba} には問題がないようにも思えます.

どこをどう直せばよいのか,お分かりの方はご教示いただきたく存じます.

山下
山下 山下 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 山下 山下 の投稿
自己レスです.後半の「一方」以下にミスがありましたので,訂正致します.

一方,LabeledVerba と NoLabelVerba を
\def\LabeledVerba[#1]{\begin{equation}\label{#1}}
\def\NoLabelVerba{\begin{equation}}
に差し替え,本体を
\newenvironment{Verba}{%
\@ifnextchar[{\LabeledVerba}{\NoLabelVerba}%
}{\end{equation}}
と修正すると正しく動きますので,\newenvironment{Verba} には問題がないようにも思えます.

以上,ご教示よろしくお願い致します.山下
山下 山下 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- はやて (h20y6m) の投稿

\@ifnextchar は正しく動いていて \NoLabelVerba が選択されています。

おそらく、\@ifnextchar\futurelet)による先読みのときに \begin{Verba} 直後の改行文字のカテゴリーコードが空白トークンに確定されてしまうのではないかと思います。 Verbatim は空白文字や改行文字等のカテゴリーコードをアクティブ文字に変更して特別な処理をしているようですが、最初の改行文字が既に空白トークンになっており改行の処理が行われないためにエラーになるのではないかと思います。

(どのように修正すればよいのか私にはわかりません……)

山下 山下 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 本田 知亮 の投稿
原因ははやてさんの仰る通りですよね.

verbatimの関係するマクロ類は
マクロの中にいれたりすると
ほとんどの場合,期待通りの動作はしません.
TeX言語業界(笑)ではこれを
「verbatimの呪い」(curse of verbatim)といいいます
#英語は今でっちあげました.
#海外のTeXユーザがなんと表現してるかは知りません

帯田さんのように*付を作るのがシンプルでベストだと思いますが
別解的なものを.

fancyvrbの中に手をいれているので
よろしくないのですが,
インタフェースを保って
同じ名前の環境名で進むるとなると
こんな感じかなと思います.
参照用ラベルの指定のキー「reflabel」を新設しています.

\labelと\refstepcounterのタイミングがトリッキーです.
タイミングを細工しないと
\labelの発行がVerba環境の下のアキに影響を及ぼすようで
fancyvrbのlabelの直後がそういう副作用のないところです.
加えて,\refstepcounterもうまいところに置かないと
複数回実行されてしまうようです・・・・


\documentclass{jsarticle}
\usepackage{fancyvrb}

\makeatletter

\newcounter{cntr}[section]
\setcounter{cntr}{0}
\renewcommand{\thecntr}{\thesection.\arabic{cntr}}
\newcommand{\labcntr}{no~\thecntr}


\define@key{FV}{label}{%
\def\@tempa{#1}%
\ifx\@tempa\FV@None
\let\FV@LabelBegin\relax
\let\FV@LabelEnd\relax
\else
\refstepcounter{cntr}%
\FV@Label@i#1\@nil
\fi}


\define@key{FV}{reflabel}{%
\def\@tempa{#1}%
\ifx\@tempa\FV@None
\else
\let\org@FV@LabelBegin\FV@LabelBegin
\def\FV@LabelBegin{\org@FV@LabelBegin\label{#1}}
\fi
}
\fvset{reflabel=none}


\DefineVerbatimEnvironment%
{Verba}{Verbatim}
{label=\labcntr, frame=lines, labelposition=topline}


\makeatother

\begin{document}

\section{最初の節}
a
\section{AAA}

\begin{Verba}[reflabel=AAaa]
1a_b^c
\end{Verba}

\begin{Verba}
2a_b^c
\end{Verba}

\begin{Verba}[reflabel=XX]
3a_b^c
\end{Verba}


\begin{Verba}
4a_b^c
\end{Verba}


\begin{Verba}[reflabel=YY]
5a_b^c
\end{Verba}

\ref{AAaa}

\ref{XX}

\end{document}



本田 知亮 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 本田 知亮 の投稿
ちょっとだけ改良

\define@key{FV}{label}{%
\def\@tempa{#1}%
\ifx\@tempa\FV@None
\let\FV@LabelBegin\relax
\let\FV@LabelEnd\relax
\else
\refstepcounter{cntr}%
\FV@Label@i#1\@nil
\fi}




\DefineVerbatimEnvironment%
{Verba}{Verbatim}
{label=\labcntr, frame=lines, labelposition=topline}



を削除して,以下にすると,
キーを新設して対処してるので
fancyvrbの中を直接いじることは排除できます.


\define@key{FV}{title}{%
\def\@tempa{#1}%
\ifx\@tempa\FV@None
\let\FV@LabelBegin\relax
\let\FV@LabelEnd\relax
\else
\refstepcounter{cntr}%
\FV@Label@i#1\@nil
\fi}

\DefineVerbatimEnvironment%
{Verba}{Verbatim}
{title=\labcntr, frame=lines, labelposition=topline}

山下 山下 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 帯田 木偶太 の投稿
》 LabeledVerba で処理されているように思える.
というのというのがどういうことなのか、分かりかねるのですが…
※  中途半端な推測を書くより、エラー・メッセージそのものを提示するのが
    よかろうと思います。

ご提示の入力例を当方でタイプセットしてみたところ、
    ! FancyVerb Error:
      Extraneous input `d_e^f' between \begin{Verba}[<key=value>] and line end
    .
    \FV@Error ...ncyVerb Error:^^J\space \space #1^^J}
   
    l.33 d_e^f
というエラーになりました。
原因については、はやてさんのご指摘のとおりなんだろうと思います。

で、対策ですが、参照ラベルを環境のオプション引数として指定するという
アプローチにこだわるなら、かなり困難な道になりそう(少なくとも
私の手には余る)ので、代替案として、Verba 環境は参照ラベルなしのものに
してしまって、それとは別に、参照ラベルありのバージョンを別名で
用意すればどうだろうかと思います。


\documentclass{jsarticle}
\usepackage{fancyvrb}
\makeatletter
\newcounter{cntr}[section]
\setcounter{cntr}{0}
\renewcommand{\thecntr}{\thesection.\arabic{cntr}}
\newcommand{\labcntr}{no~\thecntr}
\newenvironment{Verba*}[1]{%        参照ラベルありのバージョン
    \VerbatimEnvironment
    \refstepcounter{cntr}%
    \begin{Verbatim}[label=\labcntr\label{#1}, frame=lines, labelposition=topline]%
}{%
    \end{Verbatim}%
}
\newenvironment{Verba}{%            参照ラベルなしのバージョン
    \VerbatimEnvironment
    \refstepcounter{cntr}%
    \begin{Verbatim}[label=\labcntr, frame=lines, labelposition=topline]%
}{%
    \end{Verbatim}%
}
\makeatother
\begin{document}

\section{最初の節}

\begin{Verba*}{aa}
a_b^c
\end{Verba*}

\begin{Verba}
d_e^f
\end{Verba}

\ref{aa}

\end{document}
帯田 木偶太 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 山下 山下 の投稿
はやて様,帯田様

早速のご回答ありがとうございました.

エラーの原因について,推測とのことですが,承知しました.Verbatim 環境独特の面倒臭さがあるようですね.全く思いもしませんでした.今後も Verbarim 関係のコマンドを扱う際は注意しようと思います.


》中途半端な推測を書くより、エラー・メッセージそのものを提示するのがよかろうと思います。

失礼しました.以後気をつけます.なお,エラーメッセージは私も同じものが出ました.

》参照ラベルを環境のオプション引数として指定するというアプローチにこだわるなら、かなり困難な道になりそう

私なら不可能ということになりそうですから,諦めます.ご提案いただいた代替案で十分です.本当に助かりました.

お二方には改めて御礼申し上げます.山下
山下 山下 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 帯田 木偶太 の投稿
蛇足ながら、本質的な解決にならない代替案をもう一つ。

参照ラベル文字列を必須引数にしておき、参照ラベルが必要ない場合は、
予め決めておいたダミーの文字列を必須引数として指定するようにするという
手もあるかもしれません。

下記の例では、ダミーの文字列を“no_label_is_required_here”としていますが、
実際のラベルとして使用される懸念のないものなら、どんな文字列でも
構いません。
使い勝手を考えると、空文字列にしてしまうのがよさそう。

※  本田さんのように、fancyvrb.sty の中身をちゃんと読み解いて対処する御仁には
    太刀打ちできないです。

\documentclass{jsarticle}
\usepackage{fancyvrb}
\newcounter{cntr}[section]
\setcounter{cntr}{0}
\renewcommand{\thecntr}{\thesection.\arabic{cntr}}
\newcommand{\labcntr}{no~\thecntr}
\makeatletter
\def\Verba@dummy{%
    no_label_is_required_here% <-- これがダミーの文字列
}
\newenvironment{Verba}[1]{%
    \VerbatimEnvironment
    \refstepcounter{cntr}%
    \def\Verba@Temp{#1}%
    \begin{Verbatim}[%
        label=\labcntr
        \ifx\Verba@Temp\Verba@dummy  \else
            \label{#1}%
        \fi
        ,
        frame=lines, labelposition=topline
    ]%
}{%
    \end{Verbatim}%
}
\makeatother
\begin{document}

\section{最初の節}

\begin{Verba}{aa}
a_b^c
\end{Verba}

\begin{Verba}{no_label_is_required_here}
d_e^f
\end{Verba}

\begin{Verba}{no_label_is_required_here}
g_h^i
\end{Verba}

\ref{aa}

\end{document}


帯田 木偶太 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 山下 山下 の投稿
本田様,帯田様

アドバイスありがとうございました.

実は昨晩,カウンタ以外のものもラベルにできた方が便利じゃない?と思い,Verba 環境やカウンタを以下のものに書き換えてみたのですが,カウンタが一度に2つずつ進んでしまい,困っていたところでした.

今朝改めてフォーラムを見たら,質問する前に本田様から答えを頂いておりました.恐らくキー title を新設するという方法で対処できると思います.

帯田様のダミーのラベルを入れるというアイデアも,特にその判定方法が大変参考になりました.メモして後日活かしたいと思います.

改めて御礼申し上げます.山下

\documentclass{jsarticle}
\usepackage{fancyvrb}
\makeatletter
\newcounter{cntr}[section]
\setcounter{cntr}{0}
\renewcommand{\thecntr}{\thesection.\arabic{cntr}}
\newcommand{\labcntr}{\refstepcounter{cntr}no~\thecntr}
\newenvironment{Verba}[1]{\VerbatimEnvironment
\begin{Verbatim}[label=#1, frame=lines, labelposition=topline]}
{\end{Verbatim}}
\makeatother
\begin{document}

\section{最初の節}

\begin{Verba}{\labcntr\label{aa}}
aaaa
\end{Verba}

\begin{Verba}{\labcntr}
bbbb
\end{Verba}

\begin{Verba}{kauntajanai}
cccc
\end{Verba}

\ref{aa}

\end{document}
山下 山下 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 山下 山下 の投稿
こんにちは

先日教えていただいた環境 Verba について,カウンタ以外のものもラベルにできるよう,下記のように改造しました.ほぼ丸パクリですが.

うまく行ったと思っていたのですが,ラベルが \labcntr でないものを挟んだ場合にラベルの数字が飛ぶことに気づきました.Verba 環境を呼ぶと,ラベルとは無関係にカウンタ cntr が1つ増えるためですね.

そこで,\refstepcounter{cntr} を「#1 の先頭から8文字が『\labcntr』なら \refstepcounter{cntr} を実行し,違うなら何もしない」とすればよかろうと考えました.(\labcntr\label{bb} のような引数も考えているため,先頭から8文字の一致具合を見たいと思います.)

要するに

\ifx\csname#1\endcsname\labcntr
\refstepcounter{cntr}%
\fi

のような差し替えをすればよかろうと考えたのですが,#1の先頭からX文字を取り出す方法,引数が文字列Sと等しいか否かを判定する方法などが,分かりません.

何度も恐縮ですが,ヒントなど頂戴できれば幸いです.山下

\documentclass{jsarticle}
\usepackage{fancyvrb}
\makeatletter
\newcounter{cntr}[section]
\setcounter{cntr}{0}
\renewcommand{\thecntr}{\thesection.\arabic{cntr}}
\newcommand{\labcntr}{no~\thecntr}
\define@key{FV}{title}{%
\def\@tempa{#1}%
\ifx\@tempa\FV@None
 \let\FV@LabelBegin\relax
 \let\FV@LabelEnd\relax
\else
 \refstepcounter{cntr}% ここを直したい
 \FV@Label@i#1\@nil
\fi}
\newenvironment{Verba}[1]{\VerbatimEnvironment%
\begin{Verbatim}[title=#1, frame=lines, labelposition=topline]}
{\end{Verbatim}}
\makeatother
\begin{document}

\section{最初の節}

\begin{Verba}{\labcntr}
aaaa
\end{Verba}

\begin{Verba}{\labcntr\label{bb}}
bbbb
\end{Verba}

\begin{Verba}{kauntajanai}
cccc
\end{Verba}

\begin{Verba}{\labcntr}
dddd
\end{Verba}

\ref{bb}

\end{document}
山下 山下 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 和田 勇 の投稿
やりたいことがあるのでヒントです。

> #1の先頭からX文字を取り出す方法

藤田眞作 個人ページの著者にある「LaTeXまくろの八衢」を参考にされてはどうでしょうか

その pdf 版が online で参照できます。121 ページにある「23.1 文字列の抽出」を利用工夫してみてください

#マクロを作成するならこの本はとても参考になると思います。

> 引数が文字列Sと等しいか否かを判定する方法

\def\tempa{#1}%
\def\tempb{ターゲット文字列}%
\ifx\tempa\tempb
同じ
\else
違ってる
\fu
和田 勇 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 山下 山下 の投稿
和田様

ヒントありがとうございました.

そうですね.「文字列の抽出」ですね.思い込むと頭が働かず,「変数」「引数」「部分」「一部」「照合」などばかりで調べておりました.

藤田先生のPDFも見つけ,該当箇所を試してみました.例題は確かに動きましたが,

\ShortenChar{\labcntr\label{aa}}{1}{8}

だとエラーが出て動きませんでした.これから色々調べてみます.山下
山下 山下 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 本田 知亮 の投稿
>だとエラーが出て動きませんでした.これから色々調べてみます

あのマクロだと,すくなくとも
今の話題のような用途では使えません.

藤田さんの\ShortenCharは
#1が完全に文字列になるようなものでないと,
期待の動きになりません.
\xdef/\edefがどういう挙動をするかがカギです.


ちなみに,p.121の\edefの説明は間違いです
texliveをお使いなら
texdoc texbytopicで出てくる文書の
12章・13章が理解に重要な説明ではあります.


さて,本当に「文字列の検査」で作るなら,
生半可な手ではうまくいきません.

真面目に実装するなら,
\futureletを再帰的に使って,
文字ごと(正確にはトークンごと)に検査するかです.

既存の部品を組み合わせて処理するなら
expl3のl3token moduleか
l3regex moduleあたりを使えばできるでしょうが,
expl3自体が敷居がちょっと高いかもしれません.


本題.
こういうは帯田さんのように
環境を二つ作って,
それぞれでtitleキーの動きを変えれば
いいと思います.

Verba環境とVerba*環境をつくって
それぞれの環境の定義の内部で
\define@key{FV}{title}{....}
を専用に作れば行けるように思います.

コンパイルしてないので
エラーがのこってるかもしれないけども
↓みたいな感じです.

\newenvironment{Verba}[1]{%
\define@key{FV}{title}{%
\def\@tempa{##1}%
\ifx\@tempa\FV@None
\let\FV@LabelBegin\relax
\let\FV@LabelEnd\relax
\else
\refstepcounter{cntr}% ここを直したい
\FV@Label@i#1\@nil
\fi}
\VerbatimEnvironment%
\begin{Verbatim}[title=#1, frame=lines, labelposition=topline]}
{\end{Verbatim}}

\newenvironment{Verba*}[1]{%
\define@key{FV}{title}{%
\def\@tempa{##1}%
\ifx\@tempa\FV@None
\let\FV@LabelBegin\relax
\let\FV@LabelEnd\relax
\else
%%%%\refstepcounter{cntr}%%%!!
\FV@Label@i#1\@nil
\fi}
\VerbatimEnvironment%
\begin{Verbatim}[title=#1, frame=lines, labelposition=topline]}
{\end{Verbatim}}


本田 知亮 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 和田 勇 の投稿
本田さん、フォローありがとうございます。


できないというので、調べはじめてみましたが、expandafter や edef を考慮しないと面倒そうだなと感じ、本田さんのように別のマクロにするか、引数増やして、1番目が空だったらカウンター無しでとかの対応されたらどうかなと思い、提案しようかと思っていました。

本田さんの改修案一応、通りました。
和田 勇 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 本田 知亮 の投稿
>本田さんの改修案一応、通りました。

ありがとうございます.
ただ,コンパイルは通りますが,
動作がまずいですね,たぶん.

これがあるから,
マクロの中でのマクロの定義は避けたいわけで,
やらかしの事例としてご笑覧ください
(とくに,\defin@keyみたいな
引数#nが定義部分に現れないものは
間違いやすい・・・)

\newenvironment{Verba}[1]{%
\define@key{FV}{title}{%
\def\@tempa{##1}%
\ifx\@tempa\FV@None
\let\FV@LabelBegin\relax
\let\FV@LabelEnd\relax
\else
\refstepcounter{cntr}% ここを直したい
\FV@Label@i##1\@nil%%!!! #が足りないから追加!
\fi}
\VerbatimEnvironment%
\begin{Verbatim}[title=#1, frame=lines, labelposition=topline]}
{\end{Verbatim}}

\newenvironment{Verba*}[1]{%
\define@key{FV}{title}{%
\def\@tempa{##1}%
\ifx\@tempa\FV@None
\let\FV@LabelBegin\relax
\let\FV@LabelEnd\relax
\else
%%%%\refstepcounter{cntr}%%%!!
\FV@Label@i##1\@nil%%!!! #が足りないから追加!
\fi}
\VerbatimEnvironment%
\begin{Verbatim}[title=#1, frame=lines, labelposition=topline]}
{\end{Verbatim}}
本田 知亮 への返信

Re: fancyvrb の Verbatim を ifnextchar で条件分岐させる新環境について

- 山下 山下 の投稿
和田様,本田様

色々とアドバイスありがとうございました.

》真面目に実装するなら,
\futureletを再帰的に使って,
文字ごと(正確にはトークンごと)に検査するかです.

》既存の部品を組み合わせて処理するなら
expl3のl3token moduleか
l3regex moduleあたりを使えばできるでしょうが,
expl3自体が敷居がちょっと高いかもしれません.

これはもうお手上げだなというのが正直なところです.

どうやら帯田様のおっしゃったようにラベルごとに環境を別けるのが無難とのことですから,そのようにしたいと思います.

ありがとうございました.山下