我有一个包,它可以从相对文件路径递归加载许多小模块(本质上是 tex 文件)。由于模块可能会加载其他模块,我必须小心不要两次加载模块,因此我会记录已加载的文件路径。但是,为了使其工作,我必须规范化文件名以避免重复加载。所以我真正想要的是一个宏\mod@simpl
,它通过消除任何出现的 来规范化相应的字符串foo/..
。我已经能够对一个级别做到这一点(代码太丑了,我不想在这里包括它;一定有更好的方法),但不能对两个级别做到这一点。我也查看了包xstring
,但这也无济于事。下表是我的测试表。
\begin{tabular}{|l|l|l|}\hline
source & result & should be \\\hline\hline
aaa & \mod@simpl{aaa} & aaa\\\hline
../../aaa & \mod@simpl{../../aaa} & ../../aaa\\\hline
aaa/bbb & \mod@simpl{aaa/bbb} & aaa/bbb\\\hline
aaa/.. & \mod@simpl{aaa/..} & \\\hline
../../aaa/bbb & \mod@simpl{../../aaa/bbb} & ../../aaa/bbb\\\hline
../aaa/../bbb & \mod@simpl{../aaa/../bbb} & ../bbb\\\hline
../aaa/bbb & \mod@simpl{../aaa/bbb} & ../aaa/bbb\\\hline
aaa/bbb/../ddd & \mod@simpl{aaa/bbb/../ddd} & aaa/ddd\\\hline
aaa/bbb/../.. & \mod@simpl{aaa/bbb/../..} & \\\hline
\end{tabular}
请帮忙。
迈克尔
答案1
因为提到并使用了 LaTeX 包,所以我假设该包是 LaTeX 包。如果要在符合 TDS 的树中安装包,模块的文件名必须是唯一的,请参阅TDS 规范,“2.4 重复文件名”:
texmf/tex
TEX 输入文件的名称在和的每个一级子目录中必须是唯一的texmf/tex/generic
,[...]
此外,现代 TeX 发行版多年来一直支持在子目录中搜索。无需指定查找模块的目录。
然后,使用唯一的文件名,您可以使用 LaTeX 进行模块管理。LaTeX 中的模块是包。LaTeX 不会两次加载包文件。在第二次加载请求时,它只会检查选项。它会在以下情况下触发错误/警告:
- 请求的文件名(
\RequirePackage
或\usepackage
)与提供的文件名(\ProvidesPackage
文件内)不同。 - 后续请求附带第一次包加载时不存在的选项。
- 版本管理:包中提供的日期( )比或
\ProvidesPackage
的最后一个可选参数中的日期旧。\RequirePackage
\usepackage
moda
模块和包的示例pkg
。模块的文件名将是
pkg-moda.sty
。它包含以下行,例如:
\ProvidesPackage{pkg-moda}[2012/10/06 v2.4 Module moda for package pkg]
然后通过以下方式将该模块加载到其他模块或包中:
\RequirePackage{pkg-moda}
然后 LaTeX 将仅在第一次加载该模块。
如果模块需要某个版本,可以添加日期:
\RequirePackage{pkg-moda}[2012/10/06]
或者
\RequirePackage{pkg-moda}[2010/06/01]
如果自 2010/06/01 以来的版本pkg-moda
实现了所需的功能。
纯 TeX
LaTeX 的包管理不是由纯 TeX 提供的。每个模块都可以定义一个在模块加载开始时检查的标记:
% File: pkg-moda.sty
\expandafter\ifx\csname [email protected]\endcsname\relax
\else
\expandafter\endinput
\fi
\expandafter\def\csname [email protected]\endcsname{2012/10/06}
- 文件名的扩展名无关紧要。我之所以使用,是
.sty
因为我的许多 LaTeX 包也可以通过纯 TeX(甚至是 iniTeX)加载,扩展名来自包的 LaTeX 要求。 标记可以是任何东西,一个由模块专门定义的命令,一个由模块专门分配的寄存器,...
在上面的例子中,我使用了 LaTeX 约定:
\ver@<filename>
是空宏:\expandafter\def\csname ver@<filename>\endcsname{}
或者它包含 形式的日期
YYYY/MM/DD
,后面可选择跟一个空格、一个版本号和一个说明。
如果标记已定义,则文件开头的第一次检查将停止模块的进一步加载。
还可以在加载模块之前添加检查,例如:
\expandafter\ifx\csname [email protected]\endcsname\relax
\input pkg-moda.sty\relax
\fi
那么模块文件甚至不会被打开,即使它已经被加载。
答案2
Heiko 已经回答了如何处理您描述的情况。事实上,让每个小文件定义一个宏来指示它已被加载是最好的解决方案。我将回答如何从aaa/..
字符串中删除以使其规范化。
编辑:egreg 提到我的解决方案是错误的:我使用的正则表达式没有按照我声称的那样工作。现在应该修复这个问题了。另外,我曾半心半意地尝试支持 Windows,现在我决定把这个留给读者作为练习。
(解释如下。)
\documentclass{article}
\usepackage{expl3,l3regex}
\ExplSyntaxOn
\str_new:N \l__kohlhase_str
\cs_new_protected:Npn \mod@simpl #1
{
\str_set:Nn \l__kohlhase_str {/#1/}
\regex_replace_all:nnN { /\./ } { / } \l__kohlhase_str
\kohlhase_aux:N \l__kohlhase_str
\str_substr:Nnn \l__kohlhase_str { 2 } { -2 }
}
\cs_new_protected:Npn \kohlhase_aux:N #1
{
\regex_replace_all:nnNT
{
/
( [^/.]
| [^/.][^/]
| [^/][^/.]
| [^/]{3,}
)
/ \.\. /
}
{ / }
#1
{ \kohlhase_aux:N #1 }
}
\ExplSyntaxOff
\begin{document}
\begin{tabular}{|l|l|l|}\hline
source & result & should be \\\hline\hline
aaa & \mod@simpl{aaa} & aaa\\\hline
../../aaa & \mod@simpl{../../aaa} & ../../aaa\\\hline
aaa/bbb & \mod@simpl{aaa/bbb} & aaa/bbb\\\hline
aaa/.. & \mod@simpl{aaa/..} & \\\hline
../../aaa/bbb & \mod@simpl{../../aaa/bbb} & ../../aaa/bbb\\\hline
../aaa/../bbb & \mod@simpl{../aaa/../bbb} & ../bbb\\\hline
../aaa/bbb & \mod@simpl{../aaa/bbb} & ../aaa/bbb\\\hline
aaa/bbb/../ddd & \mod@simpl{aaa/bbb/../ddd} & aaa/ddd\\\hline
aaa/bbb/../.. & \mod@simpl{aaa/bbb/../..} & \\\hline
\end{tabular}
\end{document}
主要内容是使用 LaTeX3 的正则表达式模块递归地转向/aaa/../
(如果模式匹配,/
请参见对的调用:的 rue 参数)。的正确模式是什么?该模式几乎可以满足要求,但我们不想将其替换为,因此我们需要付出更多努力。这样做,我们很快意识到会遗漏的前导或尾随模式,因此我们首先在进行替换之前添加和添加。作为额外的奖励,我将更改为,因为在Unix 中代表当前目录。最后,删除前导和尾随(该步骤之前的字符串可能包含单个字符),仅保留一个子字符串。\kohlhase_aux:N
T
\regex_replace_all:nnNT
aaa
[^/]+
/../../
/
aaa/..
/
/./
/
.
/
/