
次のようなリストを含むファイルがあります:
apple
orange
pear
pineapple
アイテムがそのリストにあるかどうかを確認するマクロを作成するにはどうすればよいでしょうか。例:
\isfruit{pear}
「1」を返します。\isfruit{carrot}
「0」を返します。
答え1
ファイルがそれほど長くない場合は、
\documentclass{article}
\makeatletter
\newread\@readisfruit
\newcommand\isfruit[3][]{%
\begingroup\endlinechar=\m@ne
\openin\@readisfruit=#2
\def\@tempa{#3}%
\def\@result{0}%
\loop\unless\ifeof\@readisfruit
\read\@readisfruit to \@tempb
\ifx\@tempa\@tempb\def\@result{1}\fi
\repeat
\closein\@readisfruit
\edef\x{\endgroup\if!\noexpand#1!\@result\else\edef\noexpand#1{\@result}\fi}\x}
\makeatother
\begin{document}
\isfruit{village.dat}{pear}
\isfruit[\result]{village.dat}{carrot}\show\result
\end{document}
ただし、これは拡張可能ではないため、結果を保存する制御シーケンス名というオプションの引数を用意しました。
代替定義
アハメド・ムサが指摘しているように、ファイルが長い場合、特にテストを何度も実行する場合、それを読むのに非常に時間がかかります。キャッチファイル:
\usepackage{catchfile}
\CatchFileDef{\village}{village.dat}{\endlinechar=`| }
\makeatletter
\newcommand{\isfruitA}[3][]{%
\ifcsname loaded@#2\endcsname\else
\expandafter\CatchFileDef\csname loaded@#2\endcsname{#2}{\endlinechar=`| }%
\fi
\begingroup\expandafter\let\expandafter\@tempa\csname loaded@#2\endcsname
\edef\x{\endgroup\noexpand\in@{\unexpanded{#3}|}{\unexpanded\expandafter{\@tempa}}}\x
\ifin@ \def\@result{1}\else \def\@result{0}\fi
\if!\noexpand#1!\@result\else\edef#1{\@result}\fi}
\makeatother
は\isfruitA{village.dat}{pear}
1を出力します( の内容を展開するマクロを定義した後village.dat
、行は で区切られますが|
、これは文字列には現れないと仮定します)。
\isfruitA[\result]{village.dat}{orange}
テストの結果(0または1)がマクロに入れられます\result
。ファイルは一度だけ読み込まれます。xstrings面倒なテストを回避するために使用できます\ifin@
。
答え2
この質問に答えてみます。誰かが私の答えを改善できるかもしれません。
マクロは、が に設定されている\isfruit
間にファイルを読み取るように作成しました。すべての行が読み取られ、リストに保存されます。その後、リストを引数と比較します。enlinechar
-1
\documentclass{article}
\usepackage{filecontents}
\begin{filecontents*}{fruit.tex}
apple
orange
pear
pineapple
\end{filecontents*}
\usepackage{etoolbox}
\newread\InputFruit
\newcommand*\isfruit[2]{%
\begingroup%
\def\MyList{}
\openin\InputFruit=#1
\endlinechar=-1%
\loop\unless\ifeof\InputFruit
\read\InputFruit to \reserveda
\listxadd\MyList{\reserveda}
\repeat
\closein\InputFruit
\xifinlist{#2}{\MyList}{in list}{not in list}
\endgroup%
}%
\begin{document}
\isfruit{fruit.tex}{apple}
\isfruit{fruit.tex}{foo}
\end{document}
答え3
@Marco Daniel: マクロには、内部の によって無効化されないスペースがあります\endlinechar=-1
。それらは、水平モードでドキュメントに表示されます。さらに、割り当ては\endlinechar=-1%
であるべきでした。また、コマンドの外側で\endlinechar=-1 %
の分岐を取り、 のユーザーに2 つのコールバックを変更する機会を提供します。また、リスト セパレータ ( でデフォルトで使用される) は、テストする元のリストに存在する可能性があります。\xifinlist
\isfruit
\isfruit
\xifinlist
\documentclass{article}
\usepackage{filecontents}
\begin{filecontents*}{fruit.tex}
apple
orange
pear
pineapple
\end{filecontents*}
\begingroup
\catcode`\|=3
\endlinechar=-1
\makeatletter
\gdef\ifinfruitlist#1#2{%
\begingroup
\endlinechar=-1
\def\MyList{}
\openin\@inputcheck=#1 %
\loop\unless\ifeof\@inputcheck
\read\@inputcheck to \reserveda
\edef\MyList{\ifx\MyList\@empty\else
\unexpanded\expandafter{\MyList}|
\fi\unexpanded\expandafter{\reserveda}}
\repeat
\closein\@inputcheck
\@expandtwoargs\in@{|#2|}{|\unexpanded\expandafter{\MyList}|}
\expandafter\endgroup
\ifin@\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
}
\endgroup
\begin{document}
\ifinfruitlist{fruit.tex}{apple}{in list}{not in list}
\ifinfruitlist{fruit.tex}{foo}{in list}{not in list}
\end{document
上記のソリューションは、許容できないほど非効率的です。リストが 10 ページあり、対象のトークンがリストの最初のものである場合、テスト文字列の存在を確認する前に、まずドキュメント全体を読み取る必要があります。以下は、より効率的な実装です。
\documentclass{article}
\begin{filecontents*}{fruit.tex}
apple
orange
pear
pineapple
\end{filecontents*}
\makeatletter
\gdef\ifinfruitlist#1#2{%
\begingroup
\def\reserved@b{#2}%
\endlinechar=-1 %
\openin\@inputcheck=#1 %
\@tempswafalse\@testfalse
\def\do{%
\if@tempswa
\closein\@inputcheck
\else
\ifeof\@inputcheck
\@tempswatrue
\else
\read\@inputcheck to\reserved@a
\ifx\reserved@a\reserved@b
\@testtrue\@tempswatrue
\fi
\fi
\expandafter\do
\fi
}%
\do
\expandafter\endgroup
\if@test\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
}
\makeatother
\begin{document}
\ifinfruitlist{fruit.tex}{pear}{in list}{not in list}
\ifinfruitlist{fruit.tex}{foo}{in list}{not in list}
\end{document}
答え4
LuaTeX はこのようなタスクに最適です。以下は ConTeXt の luatex ベースのソリューションです。
\startbuffer[fruits]
apple
orange
pear
pineapple
\stopbuffer
%% Save the contents of fruits buffer in \jobname-fruits.tmp
\savebuffer[fruits][fruits]
\startluacode
local data = {}
local find = string.find
function commands.doiffruitelse(file, fruit)
if not data[file] then
data[file] = io.loaddata(file) or ""
end
return commands.testcase(find(data[file], "%f[%a]"..fruit.."%f[%A]") ~= nil)
end
\stopluacode
\def\doiffruitelse#1%
{\ctxcommand{doiffruitelse("\jobname-fruits.tmp", "#1")}}
\def\isFruit#1%
{\doiffruitelse{#1}{1}{0}}
\starttext
\startlines
\isFruit{pear}
\isFruit{carrot}
\stoplines
\stoptext
これは、ファイルの内容を読み込むためのio:loaddata()
関数from 、 luaコマンドの名前空間分離のためのテーブル、 do-if-else機能を提供するための関数を使用します。実際のマッチングは関数を使用して行われます。l-io.lua
commands
commands.testcase
string.find
フロンティアパターン単語の境界を一致させます。