7

I want to create an environment, that generates a new command. There are some similar examples on google, but nothing fits exactly.

The minimal (not) working example shows best, what I want:

\documentclass[12pt,a4paper]{article}
\usepackage[utf8]{inputenc}

\setlength{\parindent}{0pt}

%Thats my environment:
\newenvironment{LHZZ}[1]{
\newcommand{\csname LH#1\endcsname }[1]\bgroup
}{\egroup}

%Thats the use of it. It is very important, that I can use arguments:  
\begin{LHZZ}{XY}
write stuff here 

refer to #1
\end{LHZZ}

The environment above should (but doesnt) equal the following:

% not part of the code !!!!!
\newcommand{\LHXY}[1]{
write stuff here

refer to #1
}

The following is the call in the document.

\begin{document}
\LHXY{parameter} %the command \LHXY has been created indirectly by \begin{LHZZ}
\end{document}

The output should be:

write stuff here

refer to parameter

It doesnt work. It says \csname is already defined. But I dont want to define \csname. It is just the first command inside the argument. What do I have to change, to make this work?

Edit: There is a purpose for this, what seems to be code golfing. This should lead into a package which can be used by people who are not Tex-safe. The package is for making brochures for marriages, church service, etc. easily.

Edit 2: I sticked to one of the given solutions as far as possible and far as I understood it. Unfortunately, I need one optional parameter. To understand, what is the meaning of this quirky code, have a look at the example songbook and the call of it. The almost final version as a still not working MWE:

%Business as usual
\documentclass[12pt,a4paper]{book}
\usepackage[utf8]{inputenc}
\usepackage[german]{babel}
\usepackage{environ}

%This sets a default value
\newcommand{\LHliederStandard}{n}

%This calls the environment(s)
\newcommand{\LHsong}[3][\LHliederStandard]{
\csname LH#2\endcsname[#1]#3 %indirect call of what is defined in Liederbuch environment
}

\makeatletter
%This is the Liederbuch environment (Liederbuch = songbook)
\NewEnviron{Liederbuch}[1]{
\xdef\LB@my@temp{
\noexpand\newcommand{\csname LH#1\endcsname}[2][\relax]{
\unexpanded\expandafter{\BODY}
}
}
\aftergroup\LB@my@temp
}
\makeatother

%This is the Lied environment (Lied = song)
\NewEnviron{Lied}[2]{
\ifnum\numexpr#2=\numexpr##3 %the double # refers to parameter of the level above, or doesnt it.
\ifnum\pdfstrcmp{#1}{##2}=0
\BODY
\fi
\fi
}

%example for a songbook
\begin{Liederbuch}{songbook}

\begin{Lied}{n}{1}
song 1 mode n
\end{Lied}

\begin{Lied}{nt}{2}
song 2 mode nt
\end{Lied}

\end{Liederbuch}

%call of the songbook
\begin{document}

\LHsong{songbook}{1}     %calls song 1 mode n
\LHsong[nt]{songbook}{2} %calls song 2 mode nt
%hypothetical other songbook
\LHsong[n]{childrensongs}{35}

\end{document}
  • You need to \expandafter the \newcommand relative to the stuff which comes next. Not sure whether this works with \newcommand, though. But probably you want to look at the environ package. – cfr Apr 9 '16 at 12:49
  • This can't work this way -- you can't grab the environment content like this. – user31729 Apr 9 '16 at 12:49
  • I used \expandafter in some tries, but it didn't work. – MaestroGlanz Apr 9 '16 at 12:51
  • That's why I said you should look at environ. – cfr Apr 9 '16 at 23:02
4

The command form is easier:

\documentclass{article}

\newcommand{\LHZZ}[2]{%
  \expandafter\newcommand\csname LH#1\endcsname[1]{#2}}%
}

\begin{document}

\LHZZ{XY}{%
  write stuff here

  refer to #1%
}

\LHXY{parameter}

\end{document}

If you really insist in using an environment, use environ:

\documentclass{article}
\usepackage{environ}

\makeatletter
\NewEnviron{LHZZ}[1]{%
  \edef\maestroglanz@temp{%
    \unexpanded{\expandafter\gdef\csname LH#1\endcsname}####1{\unexpanded\expandafter{\BODY}}%
  }%
  \maestroglanz@temp
}
\makeatother

\begin{document}

\begin{LHZZ}{XY}
write stuff here

refer to #1
\end{LHZZ}

\LHXY{parameter}

\end{document}

enter image description here


An implementation using xparse and expl3; the largest part of the code is the definition of the error messages in case the environment is called twice with the same argument.

\documentclass{article}
\usepackage{environ,xparse}

\ExplSyntaxOn
\NewEnviron{LHZZ}[1]
 {
  \cs_set_protected:cV { LH#1:n } \BODY
  \cs_if_free:cTF { LH#1 }
   {
    \cs_gset_eq:cc { LH#1 }{ LH#1:n }
   }
   {
    \msg_error:nnn { maestroglanz/LH } { already-defined } { #1 }
   }
 }
\cs_generate_variant:Nn \cs_set_protected:Nn { cV }
\msg_new:nnnn { maestroglanz/LH } { already-defined }
 {
  LH#1~already~defined
 }
 {
  You~used~\token_to_str:N \begin{LHZZ}{#1}~before
 }
\ExplSyntaxOff

\begin{document}

\begin{LHZZ}{XY}
write stuff here 

refer to #1
\end{LHZZ}

\LHXY{parameter}

\begin{LHZZ}{XY}a\end{LHZZ}

\end{document}

Without the error check, the code part would look like

\ExplSyntaxOn
\NewEnviron{LHZZ}[1]
 {
  \cs_set_protected:cV { LH#1:n } \BODY
  \cs_gset_eq:cc { LH#1 }{ LH#1:n }
 }
\cs_generate_variant:Nn \cs_set_protected:Nn { cV }
\ExplSyntaxOff

Update

With xparse released 2019-05-03 the code can become

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentEnvironment{LHZZ}{m +b}
 {
  \cs_set_protected:cn { LH#1:n } { #2 }
  \cs_if_free:cTF { LH#1 }
   {
    \cs_gset_eq:cc { LH#1 }{ LH#1:n }
   }
   {
    \msg_error:nnn { maestroglanz/LH } { already-defined } { #1 }
   }
 }{}
\msg_new:nnnn { maestroglanz/LH } { already-defined }
 {
  LH#1~already~defined
 }
 {
  You~used~\token_to_str:N \begin{LHZZ}{#1}~before
 }
\ExplSyntaxOff

\begin{document}

\begin{LHZZ}{XY}
write stuff here 

refer to #1
\end{LHZZ}

\LHXY{parameter}

\begin{LHZZ}{XY}a\end{LHZZ}

\end{document}
  • Could you explain briefly (if this is possible) when to use \cs_set_protected in a way which doesn't assume an understanding of the corresponding TeX macros? If this query asks something unmanageably big, please just ignore it. I don't know enough to have a sense of whether the question is a reasonable one or not. – cfr Apr 9 '16 at 23:09
  • @cfr Basically, you always use _protected, unless you have good reasons not to; the good reason is that you want the function is expandable in x arguments. For instance, the analog of \thesection should not be protected. – egreg Apr 9 '16 at 23:12
  • @cfr Yes, but it wasn't right. Your impression about \NewDocumentCommand is wrong as well. – egreg Apr 9 '16 at 23:30
  • OK. I just wanted to make sure I wasn't (that) crazy. – cfr Apr 9 '16 at 23:31
  • So I just went through one file adding protecteds and things still seem to work, which I guess means they should be protecteds. But I'm wary of changing the last 3 cases because the first has a note saying that you gave me the code and the other 2 are modelled on the first: % includegraphics bit courtesy of egreg (chat 2015-01-08) \cs_new:Npn \cfr_includegraphics:nn #1 #2 { \includegraphics[#1]{#2} } . I don't know if you can tell from this why this shouldn't be protected? – cfr Apr 10 '16 at 0:23
3

Here is your minimal example turned into a working one:

\documentclass[a4paper]{article}
\usepackage[T1]{fontenc}
\usepackage[ascii]{inputenc}
\usepackage{environ}

\makeatletter

\@ifdefinable\@my@temporary{} % check availability of the name
% That's my environment:
\NewEnviron{LHZZ}[1]{%
    \let \newcommand \relax
    \protected@xdef\@my@temporary{%
        \newcommand{\csname LH#1\endcsname}[1]{\BODY}}%
    \aftergroup\@my@temporary
}

\makeatother



\begin{document}

% That's the use of it:  
\begin{LHZZ}{XY}
    write stuff here 

    refer to ##1 % sorry, doubling "#" is necessary here
\end{LHZZ}

\LHXY{parameter} %the command \LHXY has been created indirectly by \begin{LHZZ}

\end{document}

Edit: I think using \newcommand instead of \global\edef (see Christian’s answer; note, however, that \global\edef is exactly the same as \xdef) does have some added value, because one retains, for example, the ability to define a default argument:

\documentclass[a4paper]{article}
\usepackage[T1]{fontenc}
\usepackage[ascii]{inputenc}
\usepackage{environ}

\makeatletter

\@ifdefinable\@my@temporary@x{} % check availability of the names
\@ifdefinable\@my@temporary@y{}
% That's my environment:
\NewEnviron{LHZZ}[1]{%
    \let \newcommand \relax
    \protected@xdef\@my@temporary@x{%
        \newcommand{\csname LH#1\endcsname}[1]{\BODY}
    }%
    % ... but this one works too (the astersik is there only to illustrate 
    % another possibility: define a non-"\long" command):
    \protected@xdef\@my@temporary@y{%
        \newcommand*{\csname MG#1\endcsname}[1][default]{\BODY}
    }%
    \aftergroup\@my@temporary@x
    \aftergroup\@my@temporary@y
}

\makeatother



\begin{document}

% That's the use of it:  
\begin{LHZZ}{XY}
    write stuff here 
    refer to ##1\par % sorry, doubling "#" is necessary here
\end{LHZZ}

Text before.

\LHXY{parameter} %the command \LHXY has been created indirectly by \begin{LHZZ}
\MGXY[parameter]
\MGXY

Text after.

\end{document}

Edit 2: Always double-check that the brain is connected before writing to TeX.SX! (Will it be, now?) Here is a cleaner implementation of the same basic idea:

\documentclass[a4paper]{article}
\usepackage[T1]{fontenc}
\usepackage[ascii]{inputenc}
\usepackage{environ}

\makeatletter

\@ifdefinable\@my@temporary@x{} % check availability of the names
\@ifdefinable\@my@temporary@y{}
% That's my environment:
\NewEnviron{LHZZ}[1]{%
    \xdef\@my@temporary@x{%
        \noexpand\newcommand{\csname LH#1\endcsname}[1]{%
            \unexpanded\expandafter{\BODY}%
        }%
    }%
    % ... but this one works too (the astersik is there only to illustrate 
    % another possibility: define a non-"\long" command):
    \xdef\@my@temporary@y{%
        \noexpand\newcommand*{\csname MG#1\endcsname}[1][default]{%
            \unexpanded\expandafter{\BODY}%
        }%
    }%
    \aftergroup\@my@temporary@x
    \aftergroup\@my@temporary@y
    % Obviously, in a real application you'd choose only one of the two 
    % possibiities.
}

\makeatother



\begin{document}

\begingroup % to show that normal scoping is obeyed

% That's the use of it:  
\begin{LHZZ}{XY}
    write stuff here 
    refer to \texttt{#1}\par % doubling the "#" is no longer necessary
\end{LHZZ}

Now \verb|\LHXY| is
\begin{flushleft}
    \ttfamily \meaning\LHXY
\end{flushleft}

Text before.

\LHXY{parameter} %the command \LHXY has been created indirectly by \begin{LHZZ}
\MGXY[parameter]
\MGXY

Text after.

\endgroup

Here \verb|\LHXY| is \textbf{%
    \ifdefined \LHXY defined\else not defined\fi
}.

\end{document}

But I’d better follow @egreg’s solicitation… (see comments ;-)

  • No ##1 is actually necessary; more important, you should use \protected@xdef, not \xdef. – egreg Apr 9 '16 at 13:54
  • @egreg: \protected@xdef, yes, of course! Thanks for noticing. But concerning the ##, if I try with a single #, it doesn’t work. – GuM Apr 9 '16 at 14:04
  • You have to work harder, for that. ;-) – egreg Apr 9 '16 at 14:05
  • Don't you like the expl3 version? – egreg Apr 9 '16 at 14:40
  • 1
    @MaestroGlanz: \csname LH#1\endcsname (resp., \csname MG#1\endcsname) and \BODY are expanded (the latter only by one level) when the temporary macros are defined. For example, if #1 is, say, XY, the macro \@my@temporary@y will be (globally) defined as \newcommand*\MGXY[1][default]{<first-level expansion of \BODY>}. This temporary macro will be executed when the LHZZ environment is over (\aftergroup), so that the \MGXY command will be defined in the right scope. Try inserting \show\@my@temporary@x and/or \show\@my@temporary@y before the two \aftergroups. – GuM Apr 13 '16 at 9:42
2

I think this is code golfing however...

\expandafter\newcommand\csname ....\endcsname{... won't work outside -- it's a local definition.

\global\expandafter\newcommand...won't work neither due to expansion, but \global\expandafter\def\csname... would work, but since the environment body shall be grabbed, the \BODY macro must be expanded first, otherwise it would be undefined outside of the environment, so either use \expandafter\xdef\csname.... or \global\expandafter\edef\csname...

\documentclass[12pt,a4paper]{article}
\usepackage[utf8]{inputenc}


\usepackage{environ}
\setlength{\parindent}{0pt}


\NewEnviron{LHZZ}[1]{%
  \long\global\expandafter\edef\csname LH#1\endcsname##1{\BODY\ ##1}
}

\begin{LHZZ}{XY}
  write stuff here 

  Other stuff here
\end{LHZZ}


\begin{document}


\LHXY{parameter} %the command \LHXY has been created indirectly by \begin{LHZZ}
\end{document}
  • Oh, sorry, I hadn’t seen this answer before posting mine (I did not refresh the page). – GuM Apr 9 '16 at 13:44
  • @GustavoMezzetti: No worries, you provided a different answer – user31729 Apr 9 '16 at 14:28
1

If you are using environ package then you can write something like this:

\NewEnviron{LHZZ}[1]{%
    \expandafter\gdef\csname LH#1\expandafter\endcsname
       \expandafter##\expandafter1\expandafter{\BODY}}

Note, that the code is much more compact than the usage of eTeX's \unexpanded, four hashes, etc...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.