3

When I try to write an input given by a user in a token list to a file, it turns # into ##, so if a users wants to write \def\hello#1{Hello #1!}, instead of writing:

\def\hello#1{Hello #1!}

as expected I get:

\def \hello ##1{Hello ##1!}

Any idea how to make it work? Ideally I'd like to avoid asking the user to change their input.

MWE

\documentclass{article}

\newtoks\mytokens \newwrite\myWrite

\NewDocumentCommand{\writeInFile}{m}{% \mytokens={#1}% We put the input in a token list (that token list might actually be populated by another script before) \immediate\openout\myWrite myfile.tex\relax% We open the output file \immediate\write\myWrite{\the\mytokens}% We write to the output file \immediate\closeout\myWrite% We close the file }

\begin{document}

%% I don't want to change the syntax in this part of the document: \writeInFile{\def\hello#1{Hello #1!}} \input{myfile.tex}

\hello{Alice} \hello{Bob}

\end{document}

tobiasBora
  • 8,684

1 Answers1

2

Here I define the command with two arguments, the first being the file name.

You want to change the category code of # before reading.

\documentclass{article}

\newwrite\myWrite \newtoks\mytokens

\newcommand{\writeInFile}{% \begingroup\catcode`#=12 \writeInFileAux } \newcommand{\writeInFileAux}[2]{% #1 = file name, #2 = what to write \immediate\openout\myWrite #1\relax% We open the output file \mytokens={#2}% \immediate\write\myWrite{\the\mytokens}% We write to the output file \immediate\closeout\myWrite% We close the file \endgroup }

\begin{document}

%% I don't want to change the syntax in this part of the document: \writeInFile{\jobname-out}{\def\hello#1{Hello #1!}} \input{\jobname-out}

\hello{Alice} \hello{Bob}

\end{document}

This will be the written out file

\def \hello #1{Hello #1!}

Of course, you cannot have \writeInFile as the argument to another command.

You can remove this limitation by doing a string replacement.

\documentclass{article}

\ExplSyntaxOn

\iow_new:N \g_bora_write_iow \str_new:N \l_bora_write_str

\NewDocumentCommand{\writeInFile}{mm} { % #1 = file name, #2 = what to write \str_set:Nn \l_bora_write_str { #2 } \str_replace_all:NnV \l_bora_write_str { ## } \c_hash_str \iow_open:Nn \g_bora_write_iow { #1 } \iow_now:NV \g_bora_write_iow \l_bora_write_str \iow_close:N \g_bora_write_iow } \cs_generate_variant:Nn \iow_now:Nn { NV } \cs_generate_variant:Nn \str_replace_all:Nnn { NnV }

\ExplSyntaxOff

\begin{document}

%% I don't want to change the syntax in this part of the document: \textit{xx \writeInFile{\jobname-out}{\def\hello#1{Hello #1!}}} \input{\jobname-out}

\hello{Alice} \hello{Bob}

\end{document}

The \textit bit is just for demonstration purpose.

egreg
  • 1,121,712
  • Another solution to the frozen catcode issue would be to use \scantokens (if they're using etex) instead of replacing ##, right? – Slurp Mar 20 '23 at 13:30
  • Thanks a lot! So string can actually contain token lists? I'm still confused however: how does it compare with \scantokens/\tl_rescan? I'm surprised that it seems to handle properly & in align without a need for \tl_rescan as you proposed here https://tex.stackexchange.com/a/619983/116348 – tobiasBora Mar 20 '23 at 14:16
  • @Slurp and egreg: I tried to use \scantokns/tl_rescan to also handle other characters (it might not make a difference for LaTeX documents, but it does in non-latex code) here, without success https://tex.stackexchange.com/questions/680240/put-in-a-latex3-string-the-exact-input-string-to-write-in-a-file – tobiasBora Mar 21 '23 at 09:05