
我有一個包含列表的文件,如下所示:
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}
但它不可擴展,因此我提供了一個可選參數:用於儲存結果的控制序列名稱。
替代定義
正如 Ahmed Musa 所觀察到的,如果文件很長,讀取它可能會非常耗時,尤其是在多次執行測試的情況下。我們可以利用捕獲文件:
\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
。該文件將僅被讀取一次。一些包例如x字串可以用來避免繁瑣的測試\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 %
。另外,我將在命令\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
上述解決方案的效率低得令人無法接受。如果清單有十頁長,且感興趣的標記是清單中的第一個標記,則我們必須先讀取整個文檔,然後再檢查測試字串是否存在。這是一個更有效的實作:
\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 函數來l-io.lua
載入檔案內容,commands
使用 lua 指令的命名空間分隔表,以及commands.testcase
提供 do-if-else 功能的函數。實際的匹配是使用string.find
函數完成的。我用前沿格局匹配單字邊界。