# canonicalize file path strings in TeX

I have a package which recursively loads many little modules (essentially tex files) from relative file paths. As modules may load other modules, I have to be careful not to load modules twice, so I keep a record of the file paths I have loaded. BUT, for this to work I have to canonicalize file names to avoid double loading. So what I really want is a macro \mod@simpl that canonicalizes the respective strings by eliminating any occurrences of foo/... I have been able to do that for one level (the code is so ugly that I do not want to include it here; there must be a better way) but not for two. I also looked at the xstring package, but that does not help either. The table below is my testing table.

\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}


Michael

-

Because LaTeX packages are mentioned and used, I assume the package is a LaTeX package. If you want to install the package in a TDS compliant tree, the file names of the modules must be unique, see TDS specification, "2.4 Duplicate filenames":

Names of TEX input files must be unique within each first-level subdirectory of texmf/tex and texmf/tex/generic, [...]

Also modern TeX distributions support the search in subdirectories since many years. There is no need to specify the directories for finding the modules.

Then, with unique file names, you can use LaTeX for the module management. Modules in LaTeX are packages. LaTeX does not load a package file twice. At the second loading request it only checks the options. It triggers errors/warnings in the following cases:

• The requested file name (\RequirePackage or \usepackage) differs from the provided file name (\ProvidesPackage inside the file).
• Later requests come with options that are not present at the first package loading.
• Version management: The date provided in the package (\ProvidesPackage) is older than the date in the final optional argument of \RequirePackage or \usepackage.

Example for module moda and package pkg. The file name of the module would be pkg-moda.sty. It contains the line, e.g.:

\ProvidesPackage{pkg-moda}[2012/10/06 v2.4 Module moda for package pkg]


This module is then loaded in other modules or the package via:

\RequirePackage{pkg-moda}


Then LaTeX will load the module the first time only.

If the module should require a certain version, the date can be added:

\RequirePackage{pkg-moda}[2012/10/06]


or

\RequirePackage{pkg-moda}[2010/06/01]


if the versions of pkg-moda since 2010/06/01 implements the needed features.

## plain TeX

LaTeX's package management is not provided by plain TeX. There each module can define a marker that is checked at the beginning of module loading:

% File: pkg-moda.sty
\expandafter\ifx\csname ver@pkg-moda.sty\endcsname\relax
\else
\expandafter\endinput
\fi
\expandafter\def\csname ver@pkg-moda.sty\endcsname{2012/10/06}

• The extension of the file name does not matter. I have used .sty because many of my LaTeX packages can also be loaded by plain TeX (or even iniTeX), the extension comes from the LaTeX requirements for a package.
• The marker can be anything, a command that is exclusively defined by the module, a register that is exclusively allocated by the module, …

In the example above I have used the LaTeX convention: \ver@<filename> is either the empty macro:

\expandafter\def\csname ver@<filename>\endcsname{}


or it contains the date in form of YYYY/MM/DD, optionally followed by a space and a version number and a description.

The first check at the beginning of the file stops the further loading of the module, if the marker is already defined.

\expandafter\ifx\csname ver@pkg-moda.sty\endcsname\relax
\input pkg-moda.sty\relax
\fi


Then the module file is not even opened, when it is already loaded.

-
Thanks a lot for the explanation (which I actually was aware of). I had indeed thougth about using (or adapting) the \usepackage code from the LaTeX kernel, but decided that it would not use it for my problem. I should probably have made it more clear that my modules are not LaTeX packages, in particular there can be multiple modules in one file, thus one of the basic assumptions fails. I am pretty sure that I have to remember (and compare) canonicalized files paths. But Bruno's answer helps me with this. –  Michael Kohlhase Oct 8 '12 at 5:36
I have a section about plain TeX in my answer. –  Heiko Oberdiek Oct 8 '12 at 9:59

Heiko has answered on how to manage a situation such as the one you describe. Indeed, letting each small file define one macro to indicate that it has been loaded is the best solution. I will answer on how to remove aaa/.. from a string to make it canonical.

EDIT: egreg mentions that my solution was wrong: the regex I used did not do what I claimed. This should now be fixed. Also, I had done a half-hearted attempt at supporting Windows, I now decided to leave this as an exercise to the reader.

(Explanations below.)

\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}


The main ingredient is to use LaTeX3's regex module to turn /aaa/../ to /, recursively (see the call to \kohlhase_aux:N if the patern has matched: True argument of \regex_replace_all:nnNT). What is the correct pattern for aaa? The pattern [^/]+ would almost do, but we do not want to replace /../../ to /, so we work a bit harder. Doing this, we soon realize that a leading or trailing pattern of aaa/.. would be missed, so we first prepend and append / before doing the replacement. As an added bonus, I change /./ to / since . stands for the current directory in Unix. At the end, remove the leading and trailing / (the string may contain a single / before that step) by keeping a substring only.

-
How wonderful, there is a regexp package in LaTeX, this will help me tremendously. I am sure that I can extend the actual regexp. –  Michael Kohlhase Oct 8 '12 at 5:31
It's more complicated than that: the last example should resolve to the empty string, for instance. –  egreg Oct 8 '12 at 9:45
you are right, the problem is context-sensitive, since you need to balance pairs of directory names and "go up" statements /... I guess I will need to have some kind of loop that makes replacements until nothing changes any more. I will post the answer when I have one. –  Michael Kohlhase Oct 9 '12 at 10:46
@egreg I included a loop: if the pattern was found, call the auxiliary \kohlhase_aux:N again. –  Bruno Le Floch Oct 13 '12 at 20:52
@BrunoLeFloch Sorry, but it doesn't produce the required result: just check the table. Leading ../ should be stripped in advance and stored away; in what remains, consecutive ../ must remove the same number of directory names above them, but recursively until no ../ remains. –  egreg Oct 13 '12 at 21:00