# StrSubstitute Missing Number Treated as 0 when Inputing \_

I'm having trouble tracking down an error that looks like a missing input error, but is occurring in a strange place. I'm trying to take a detokenized string (actually a macro in my actual doc) with an underscore in it and replace the detokenized underscore with a "cancelled" underscore (ie: \_). I pass the result to \Result and then attempt to print it to check if it worked. It doesn't. In fact the error is for line 13 rather than line 9 where the initial macro is.

If, however, I replace the \_ with any other non-control character then I receive no errors. Can anyone explain why I am having this error and how to fix it?

Error:

  1 Tutorials/Example1/Example1.tex|13 error| Missing number, treated as zero.
2 Tutorials/Example1/Example1.tex|13 error| Missing number, treated as zero.


Example:

\documentclass{minimal}   % EDIT: From answer, minimal shouldn't be used.

\usepackage[abspath]{currfile}
\usepackage{xstring}

%%% Set a fake return.
\def\theabsdir{\detokenize{/Users/Me/Example_with_underscore}}

\StrSubstitute[0]{\theabsdir}{\detokenize{_}}{\_}[\Result]      % LINE 9

\begin{document}
Before: \theabsdir\\
After: \Result                                                 % LINE 13
\end{document}


This is the intended usage, but it doesn't work for a variety of reasons. Essentially pseudocode.

\documentclass{article}

\usepackage[abspath]{currfile}
\usepackage{xstring}

% Determine path of current file
\getabspath{Example.tex} % this provides \theabsdir, the file's directory.

% Delete folders up to the project's root directory.
\StrDel{\theabsdir}{path/to/project_root/}[\ProjectRoot]

% Add the location of custom packages
\def\StylePath{\ProjectRoot style_name}

\usepackage[options]{\StylePath}

%%% End Preamble


Welcome to TeX.SE!

First, you shouldn't use the minimal class; it isn't intended for end-users, only for specific tests by LaTeX developers.

Second, your problem goes away if you add \usepackage[T1]{fontenc} to your document. T1 being more modern than OT1, this may be what you want to do.

The problem when using OT1 here is due to the fact that \_ doesn't stay unchanged1 when you use it inside \edef, and according to the xstring documentation, this happens under the hood when you call \StrSubstitute in the default régime of \fullexpandarg (see Expansion of arguments in the xstring documentation).

I suspect this documentation doesn't tell the whole truth, though, otherwise the following ought to work even in OT1:

\StrSubstitute[0]{\theabsdir}{\detokenize{_}}{\noexpand\_}[\Result]


as well as

\StrSubstitute[0]{\theabsdir}{\detokenize{_}}{\unexpanded{\_}}[\Result]


You can convince yourself that \_ in OT1 encoding doesn't survive an \edef with the following test document:

\documentclass{article}
\begin{document}
\edef\x{\_}
\x
\end{document}


which prints:

./test.tex:5: Missing number, treated as zero.
\let
l.5 \end{document}


With \usepackage[T1]{fontenc}, it works well.

One way to avoid the error in OT1 while keeping using xstring is to wrap \_ inside a \protected macro—such macros never ever get expanded by \edef, \write, etc.:

\documentclass{article}
\usepackage{xstring}

\def\theabsdir{\detokenize{/Users/Me/Example_with_underscore}}

\protected\def\zzz{\_}
\StrSubstitute[0]{\theabsdir}{\detokenize{_}}{\zzz}[\Result]

\begin{document}
Before: \theabsdir\par
After: \Result
\end{document}


But this is probably not what you were hoping for. A reliable way to solve this problem is to use expl3 functions. I propose to use slightly different input, I think it will be more useful for your real-world use cases. The input with the following code can be either:

% Note: no \detokenize here
\def\theabsdir{/Users/Me/Example_with_underscore}
...
\sanitizeMacro{\theabsdir}{\Result}


or

\sanitizeString{/Users/Me/Example_with_underscore}{\Result}


(but I can handle your \def\theabsdir{\detokenize{/Users/Me/Example_with_underscore}} if you really prefer).

Here is the code:

\documentclass{article}
% Probably a good idea to load this, even if it works without.
%\usepackage[T1]{fontenc}
\usepackage{xparse}

\ExplSyntaxOn
\cs_new_protected:Npn \lagix_sanitize:nN #1#2
{
\tl_set:Nx \l_tmpa_tl { \tl_to_str:n {#1} } % \tl_to_str:n is \detokenize
\tl_replace_all:Nxn \l_tmpa_tl { \char_generate:nn { _ } { 12 } } { \_ }
% Alternative to the previous line:
% \regex_replace_all:nnN { \cD\_ } { \c{_} } \l_tmpa_tl
\tl_set_eq:NN #2 \l_tmpa_tl
}

\cs_generate_variant:Nn \lagix_sanitize:nN { V }
% Not needed if you use the \regex_replace_all:nnN way above:
\cs_generate_variant:Nn \tl_replace_all:Nnn { Nx }

\NewDocumentCommand \sanitizeString { m m }
{
\lagix_sanitize:nN {#1} #2
}

\NewDocumentCommand \sanitizeMacro { m m }
{
\lagix_sanitize:VN #1 #2
}

\ExplSyntaxOff

% Note: no \detokenize here
\def\theabsdir{/Users/Me/Example_with_underscore}

\begin{document}

\sanitizeMacro{\theabsdir}{\Resulti}
\sanitizeString{/Users/Me/Example_with_underscore}{\Resultii}

Before: \detokenize{/Users/Me/Example_with_underscore}\par
After: \Resulti, \Resultii.

\end{document}


Footnote

1. Various tests are performed at the time of the \edef and the results of these tests become hardcoded in the expansion recorded by \edef, since one of the branches is discarded for each test (assuming the test itself can be performed, which is not necessarily the case when expanding too early!).
• Thanks for the in depth answer! Both the T1 and the sanitizeMacro are giving me good results printing, but they aren't giving me good results for utilization in other macros - specifically in StrSubstitute and usepackage. I use the currfile to get theabsdir and so I can get around the StrSubstitute by subbing before sanitizing, but usepackage gives me a Missing \endcsname inserted error. Any hints as to how I might get around this issue? This may be one of those "post a new question" scenarios. – Lagix Feb 11 '20 at 23:29
• I modified the initial question in case it provides clarity about how I am using the final string. – Lagix Feb 11 '20 at 23:45
• Sorry, but I'm afraid it can't work this way. \usepackage expects a package *name*—providing a path is an error. Moreover, you aren't even passing a path in your new pseudo-code, you are passing a macro (there is no rule saying that macros have to expand all their arguments—rule which would need to be precised, BTW). And yes, this is a different question. – frougon Feb 12 '20 at 0:01
• Ok, so this is not the right way to go about it. Thanks. – Lagix Feb 13 '20 at 18:44
• It appears the cs method doesn't work for input either. Are these methods only usable for actual text and can't be used in other macros? – Lagix Feb 13 '20 at 20:11

The problem is that xstring will perform “all the way expansion” to the arguments to \StrSubstitute and \_ doesn't survive this process.

\documentclass{article}

\usepackage[abspath]{currfile}
\usepackage{xstring}

%%% Set a fake return.
\def\theabsdir{\detokenize{/Users/Me/Example_with_underscore}}

\StrSubstitute[0]{\theabsdir}{\detokenize{_}}{\noexpand\noexpand\noexpand\_}[\Result]

\begin{document}

Before: \theabsdir

After: \Result

\end{document}

• How do you know to use exactly three \noexpand` commands? I would think that only one would be needed. – Lagix Feb 11 '20 at 23:33
• @Lagix The argument is subject to full expansion twice. – egreg Feb 12 '20 at 8:58