我想创建一个环境,生成一个新命令。谷歌上有一些类似的例子,但没有一个完全合适的。
最小(不)工作示例最好地展示了我想要的东西:
\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}
上述环境应该(但不)等于以下内容:
% not part of the code !!!!!
\newcommand{\LHXY}[1]{
write stuff here
refer to #1
}
以下是文档中的调用。
\begin{document}
\LHXY{parameter} %the command \LHXY has been created indirectly by \begin{LHZZ}
\end{document}
输出应为:
在这里写东西
参考参数
它不起作用。它说\csname
已经定义。但我不想定义\csname
。它只是参数中的第一个命令。我必须更改什么才能使其工作?
编辑:这样做是有目的的,似乎是在玩代码高尔夫。这应该会引出一个可供不熟悉 Tex 的人使用的软件包。该软件包用于轻松制作婚礼、教堂服务等小册子。
编辑 2:我尽可能坚持使用给定的解决方案之一,并且就我所理解的而言。不幸的是,我需要一个可选参数。要了解这个古怪代码的含义,请查看示例歌本及其调用。作为仍然无法正常工作的 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}
答案1
命令形式更简单:
\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}
如果您确实坚持使用环境,请使用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}
xparse
使用和的实现expl3
;代码的最大部分是当环境使用相同的参数被调用两次时错误消息的定义。
\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}
如果没有错误检查,代码部分将如下所示
\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
更新
随着xparse
2019-05-03 的发布,代码可以变成
\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}
答案2
这是您的最小示例,已变成一个有效的示例:
\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}
编辑:我认为使用\newcommand
而不是\global\edef
(参见Christian的回答;但请注意,这\global\edef
与完全相同\xdef
)确实具有一些附加价值,因为人们保留了定义默认参数的能力:
\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}
编辑2:在写入 TeX.SX 之前,请务必仔细检查大脑是否已连接!(现在会吗?)以下是相同基本思想的更清晰的实现:
\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}
但我最好听从@egreg 的恳求……(见评论;-)
答案3
不过我认为这是代码高尔夫......
\expandafter\newcommand\csname ....\endcsname{...
在外面不起作用——这是一个本地定义。
\global\expandafter\newcommand...won't work neither due to expansion, but \global\expandafter\def\csname...
可以工作,但由于必须抓取环境主体,因此\BODY
必须先扩展宏,否则它在环境之外将是未定义的,因此要么使用\expandafter\xdef\csname....
或\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}
答案4
如果您正在使用environ
包,那么您可以编写如下内容:
\NewEnviron{LHZZ}[1]{%
\expandafter\gdef\csname LH#1\expandafter\endcsname
\expandafter##\expandafter1\expandafter{\BODY}}
请注意,该代码比使用 eTeX \unexpanded
、四个哈希等更加紧凑……