我正在寻找一个命令定义,该命令可以具有多个值以用于不同的上下文。此用例将是在同时使用两种语言的自定义类中,或者我猜是任何变量可能根据环境具有不同值的情况。在搜索了相关信息\newcommand
和与之相关的东西后,我仍然对事情有点困惑。
据我了解,一个简单的实现是:
\newcommand\varname{text value}
但是,在我看来,对相同的东西使用不同的命令会很不好看。例如,我面前有一个旧类,其中包含芬兰语版本的命令Doctype
和Doctypefin
。相反,我想使用这样的命令:
% Definitions
\title{en}{Title Of The Document}
\title{fi}{Dokumentin otsikko}
% Use in class environments or tex files
\title{en} % -> "Title Of The Document"
\title{fi} % -> "Dokumentin otsikko"
有什么方法可以构建这样的宏或定义吗?
理想情况下,指定未见过的类别(这里是语言)不会有问题,但我认为要求在类文件中指定可接受的值是合理的。此外,就像下面的示例一样,如果这是一个问题,我认为使用单独的命令来输出变量是可以接受的。
我尝试记录其他类命令的构造方式。有一个方便的MakeStringVar
命令可以构造一个变量,如果未设置,则显示默认文本。该默认文本功能对于新命令非常有用,因为类中的许多环境都使用定义将文本输出到标题页等。
\newcommand\MakeStringVar[2][\relax]{%
\ifx#1\relax%
\expandafter\newcommand\csname Emit#2\endcsname{%
{\scriptsize (Use {\tt\textbackslash #2} to replace this text.)}}%
\else%
\expandafter\newcommand\csname Emit#2\endcsname{#1}%
\fi%
\expandafter\newcommand\csname #2\endcsname[1]{%
\expandafter\renewcommand\csname Emit#2\endcsname{##1}%
}%
}
它的使用方式如下:
\MakeStringVar{Major} % Definition in class
\Major{Major subject name} % Set value in pre-document
\EmitMajor % Used in environments in class -> "Major subject name"
但我对 LaTeX 还是个新手,所以我不知道从哪里开始。我觉得上面的命令可以以某种方式扩展,但我真的不知道 LaTeX 宏的局限性。
答案1
您可以考虑使用以下方法\@namedef
:
\documentclass{article}
\makeatletter
\newcommand\deftitle[2][en]{%
\global\@namedef{title:#1}{#2}%
}
\newcommand\usetitle[1][en]{\@nameuse{title:#1}}
\makeatother
\begin{document}
\deftitle{Default Language (English) Title} % same as \deftitle[en]{...}
\deftitle[it]{Italian Title}
\deftitle[fr]{French Title}
\usetitle[it]
\usetitle % same as \usetitle[en]
\usetitle[fr]
\end{document}
当用户调用时,会定义\deftitle[en]{<content>}
一个新的宏,并且当通过调用时它会扩展为。title:en
<content>
\@nameuse
编辑:这里有一个构造此类宏的通用方法:
\makeatletter
\newcommand\newconstructor[1]{%
\expandafter\newcommand\csname def#1\endcsname[2][en]{%
\global\@namedef{#1:##1}{##2}%
}%
\expandafter\newcommand\csname use#1\endcsname[1][en]{\@nameuse{#1:##1}}%
}
\makeatother
现在,例如,像以前一样\newconstructor{title}
定义\deftitle
和\usetitle
。
答案2
在此实现中,可以使用方便的键值界面输入各种版本(如果值包含逗号,则用括号括起来)。
还可以定义键的别名。对于语言,我认为最好使用带有完整语言名称的长键,这样\languagename
可以用来获取相关字符串。但是,也可以在文档中使用键的别名,前提是事先定义它们。
您不需要在变量定义时添加所有版本,因为您可以\addtovarstring
稍后使用。
\documentclass{article}
\usepackage[english,finnish]{babel}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\definevarstring}{mO{}}
{
\prop_new:c { g_felix_varstring_#1_prop }
\felix_varstring_add:nn { #1 } { #2 }
}
\NewDocumentCommand{\addtovarstring}{mm}
{
\felix_varstring_add:nn { #1 } { #2 }
}
\NewDocumentCommand{\definealias}{m}
{
\prop_gset_from_keyval:Nn \g_felix_varstring_alias_prop { #1 }
}
\NewExpandableDocumentCommand{\getvarstring}{mm}
{
\prop_if_in:cfTF { g_felix_varstring_#1_prop } { #2 }
{
\prop_item:cf { g_felix_varstring_#1_prop } { #2 }
}
{
\prop_if_in:NnT \g_felix_varstring_alias_prop { #2 }
{
\prop_item:cf { g_felix_varstring_#1_prop }
{
\prop_item:Nn \g_felix_varstring_alias_prop { #2 }
}
}
}
}
\cs_generate_variant:Nn \prop_item:Nn { cf }
\prg_generate_conditional_variant:Nnn \prop_if_in:Nn { cf } { T,F,TF,p }
\prop_new:N \g_felix_varstring_alias_prop
\cs_new_protected:Nn \felix_varstring_add:nn
{
\prop_gset_from_keyval:cn { g_felix_varstring_#1_prop } { #2 }
}
\ExplSyntaxOff
\definealias{fi=finnish,en=english}
\definevarstring{title}[% long versions for languages
english=Title of the document,
finnish=Dokumentin otsikko,
]
\begin{document}
\author{A. Uthor}
\title{\getvarstring{title}{\languagename}}
\maketitle
\selectlanguage{english}
\getvarstring{title}{\languagename}
\getvarstring{title}{en}---\getvarstring{title}{english}
\getvarstring{title}{fi}---\getvarstring{title}{finnish}
\end{document}
答案3
前段时间写了一个\SetProperty
——\GetProperty
接口。
也许它对你有用。
%% This coding example was written by Ulrich Diez in November 16, 2018.
%% It was modified by Ulrich Diez in February 28, 2019.
%%
%% Copyright 2018, 2019 Ulrich Diez (e-mail: [email protected])
%%
%% This work may be distributed and/or modified under the conditions of
%% the LaTeX Project Public License, either version 1.3c of this license
%% or (at your option) any later version.
%%
%% The latest version of this license is in
%%
%% <http://www.latex-project.org/lppl.txt>
%%
%% and version 1.3x or later is part of all distributions of LaTeX
%% version 2005/12/01 or later.
%%
%% This work has the LPPL maintenance status `unmaintained'.
%%
%% This work consists of this coding example.
%%
\errorcontextlines=10000
\documentclass[landscape]{article}
\makeatletter
%%==== Begin of code for the \SetProperty-\GetProperty-Interface =======
\RequirePackage{ifluatex, ifxetex}
%%======================================================================
%% Paraphernalia:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond,
%% \UD@exchange, \UD@removespace, \UD@name, \UD@CheckWhetherNull,
%% \UD@loopcall,
%%......................................................................
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%----------------------------------------------------------------------
%% Put a control sequence token in place instead of the string denoting
%% its name.
%%......................................................................
%% \UD@name<emptiness or tokens other than braces>{<Name of
%% Control Sequence>}
%%
%% yields:
%%
%% <emptiness or tokens other than braces>\Controlsequence
%%
%% E.g.,
%%
%% \UD@name foo{bar} -> foo\bar
%% \UD@name{bar} -> \bar
%% \UD@name\newcommand*{wEirdName}[1]{Arg 1: (#1)}
%% -> \newcommand*\wEirdName[1]{Arg 1: (#1)}
%%
\newcommand\UD@name{}\long\def\UD@name#1#{\romannumeral\UD@@name{#1}}%
\newcommand\UD@@name[2]{%
\expandafter\UD@exchange\expandafter{\csname#2\endcsname}{0 #1}%
}%
%%----------------------------------------------------------------------
%% Check whether argument is empty:
%%......................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
%%
%% (\romannumeral expansion was introduced in order to overcome the
%% concerns and worries about improperly balanced
%% \if..\else..\fi constructs.)
%%
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
\UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%----------------------------------------------------------------------
%% Expandable Loop:
%% \UD@loopcall{<action>}%
%% {<action if list empty>}%
%% {<preset>}%
%% {{<e_k>}{<e_(k+1)>}..{e_n}}% <- this is the list
%%
%% If list is empty: <action if list empty>
%% Else:
%% <action>{<e_k>}<preset> \UD@loopcall{<action>}%
%% {<action if list empty>}%
%% {<preset>}{{<e_(k+1)>}..{e_n}}
%%
%% <action> can be defined to mesh into the iteration-process, e.g.,
%% (ex)changing arguments like the <action if list empty>-argument for
%% the next \UD@loopcall-iteration, e.g., terminating iteration
%% prematurely under some circumstances.
%%......................................................................
\newcommand\UD@RemoveTillUD@nil{}%
\long\def\UD@RemoveTillUD@nil#1#2\UD@nil{{#1}}%
\newcommand\UD@Extractfirstloop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@exchange{#1}}%
{%
\expandafter\UD@Extractfirstloop
\expandafter{\UD@RemoveTillUD@nil#1}%
}%
}%
\newcommand\UD@loopcall[4]{%
\UD@CheckWhetherNull{#4}{#2}{%
\expandafter\UD@exchange
\expandafter{\expandafter{\UD@firstoftwo{}#4}}%
{\UD@Extractfirstloop{#4\UD@nil}{#1}#3\UD@loopcall{#1}{#2}{#3}}%
}%
}%
%%======================================================================
%% Expandable comparison of two strings:
%%
%% Derived from David Kastrup's \ifstrequal-test from the
%% TeX Pearl Diving Site;
%% Pearls of 2005;
%% Title: David Kastrup - Comparing two strings known to consist
%% only of characters ;
%% Url: <http://www.gust.org.pl/projects/pearls/2005p/david-kastrup/bachotex2005-david-kastrup-pearl1.pdf>
%%......................................................................
%% \UD@ifstrequal{<String 1>}{<String 2>}%
%% {%
%% <Tokens to be delivered in case strings are equal>
%% }%
%% {%
%% <Tokens to be delivered in case strings are
%% not equal>
%% }%
%%
%% (<String 1> gets expanded during comparison.
%% <String 2> gets not expanded during comparison.
%%
%% I bloated this thing up for ensuring it also takes spaces into
%% account.
%%
\@ifdefinable\UD@strchksp{%
\long\def\UD@strchksp#1#2#3#4 #5\relax{%
#1{\if\UD@strequal\UD@secondoftwo{}{#4}%
\expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
{\if\UD@strequal\UD@secondoftwo{}{#5}%
\expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
{\UD@strequalstart#1{#2}{#3} \relax}%
{\UD@strequalstart#1{#2}{#3}{ }#5\relax}}}%
{\UD@strequalstart#1{#2}{#3}#4 #5\relax}%
}%
}
\newcommand\UD@strequal[2]{\number\UD@strchksp#1{}{}#2 \relax}
\newcommand\UD@strequalstart[4]{%
\if#4\relax\UD@strequalstop\fi
\UD@strchksp#1{\if#4#2}{#3\fi}%
}%
\@ifdefinable\UD@strequalstop{%
\long\def\UD@strequalstop\fi\UD@strchksp#1#2#3#4{\fi#2#4\relax'#313 }%
}
\newcommand\UD@ifstrequal[2]{%
\romannumeral0%
\if\UD@strequal\@firstofone{#2.}{#1.}%
\expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
{\UD@exchange{ \UD@firstoftwo}}{\UD@exchange{ \UD@secondoftwo}}%
{\expandafter\expandafter\expandafter}%
}%
%%======================================================================
%% Total expansion and stringification of argument:
%%......................................................................
%% \UD@stringify{<argument that expands to character tokens>}
%%
%% Does \csname..\endcsname with the argument, then \string,
%% then removal of \escapechar if that was added.
%%
\newcommand\UD@stringify[1]{%
\romannumeral0%
\expandafter\expandafter\expandafter\UD@exchange
\expandafter\expandafter\expandafter{%
\expandafter\string\csname#1\endcsname}%
{%
\ifnum\the\escapechar<0 %
\expandafter\UD@secondoftwo\else\expandafter\UD@firstoftwo\fi
{%
\ifnum\the\escapechar>\ifxetex 1114111 %
\else\ifluatex 1114111 \else 255 \fi\fi
\expandafter\UD@secondoftwo\else\expandafter\UD@firstoftwo\fi
{%
\ifnum\the\escapechar=32 %
\expandafter\UD@secondoftwo\else\expandafter\UD@firstoftwo\fi
{\UD@firstoftwo{ }}{}%
}{ }%
}{ }%
}%
}%
%%======================================================================
%% Property-Management:
%%......................................................................
%%
%% The concept is about maintaining macros that expand to lists of
%% 2-tuple-arguments.
%% The first component of the tuple holds the name of a property.
%% The second component holds the value of the property.
%%
%% E.g., the macro \macro could be defined to expand to:
%%
%% {{property name 1}{property value 1}}%
%% {{property name 2}{property value 2}}%
%% ...
%% {{property name K}{property value K}}%
%%
%%
%% \SetProperty{\macro}%
%% {<name of property>}%
%% {<new value of property>}
%%
%% In case \macro is undefined, \macro will be defined empty.
%%
%% In case a property <name of property> does not exist within the
%% macro \macro, it will be added to the macro and it will get the
%% value <new value of property>.
%%
%% In case a property <name of property> does exist within the macro
%% \macro, its value will get replaced by <new value of property>.
%%
%% Before further evaluation <name of property> will be expanded via
%% \csname..\endcsname-expansion and afterwards "stringified" by
%% applying \string and removing a leading escapechar if one was
%% added.
%%
%% Example:
%%
%% \SetProperty{\macro}{property name 2}{changed property value 2}
%%
%% would make \macro to expand to
%%
%% {{property name 1}{property value 1}}%
%% {{property name 2}{changed property value 2}}%
%% ...
%% {{property name K}{property value K}}%
%%
%% and
%%
%% \SetProperty{\macro}{property name (K+1)}{property value (K+1)}
%%
%% would make \macro to expand to
%%
%% {{property name 1}{property value 1}}%
%% {{property name 2}{changed property value 2}}%
%% ...
%% {{property name K}{property value K}}%
%% {{property name (K+1)}{property value (K+1)}}%
%%
%%
%% \GetProperty{\macro}%
%% {<name of property>}%
%% {%
%% <tokens to be delivered in case
%% property is not available>
%% }
%%
%% In case \macro is undefined or property <name of property> does not
%% exist within the macro \macro,
%% <tokens to be delivered in case property is not available>
%% will be delivered.
%%
%% In case a property <name of property> does exist within the macro
%% \macro, its value will be delivered.
%%
%% Before further evaluation <name of property> will be expanded via
%% \csname..\endcsname-expansion and afterwards "stringified" by
%% applying \string and removing a leading escapechar if one was
%% added.
%%
%% \GetProperty is expandable and delivers the result after two
%% expansion steps / after being hit "twice" by \expandafter.
%%
%% E.g., with the macro \macro being defined to expand to
%%
%% {{property name 1}{property value 1}}%
%% {{property name 2}{property value 2}}%
%% ...
%% {{property name K}{property value K}}%
%%
%% , the sequence
%%
%% \GetProperty{\macro}{property name 2}{Huh?}
%%
%% will expand to:
%%
%% property value 2
%%
\newcommand\UD@ExpandProperties[3]{%
\expandafter\UD@PassFirstToSecond\expandafter{#1}%
{\expandafter\expandafter\expandafter\UD@PassFirstToSecond
\expandafter\expandafter\expandafter{%
\UD@stringify{#2}}{#3}}%
}%
\newcommand\UD@AtIfPropertyListUndefined[1]{%
\@ifundefined{%
\expandafter\UD@exchange\expandafter{\string#1}%
{%
\ifnum\the\escapechar<0 %
\expandafter\UD@secondoftwo\else\expandafter\UD@firstoftwo\fi
{%
\ifnum\the\escapechar>\ifxetex 1114111 %
\else\ifluatex 1114111 \else 255 \fi\fi
\expandafter\UD@secondoftwo\else\expandafter\UD@firstoftwo\fi
{%
\ifnum\the\escapechar=32 %
\expandafter\UD@secondoftwo\else\expandafter\UD@firstoftwo\fi
{\UD@firstoftwo{}}{\UD@removespace}%
}{}%
}{}%
}%
}%
}%
\newcommand\SetProperty[2]{%
\@bsphack
\UD@AtIfPropertyListUndefined{#1}{\newcommand*#1{}}{}%
\romannumeral0%
\UD@ExpandProperties{#1}{#2}{\UD@@setproperty}{#1}%
}%
\newcommand\UD@@setproperty[4]{%
\UD@loopcall{\UD@@@setproperty}%
{ \global\def#3{{{#1}{#4}}}\@esphack}%
{{#1}{#4}{ \global\def#3}{}}%
{#2}%
}%
\newcommand\UD@@@setproperty{}%
\long\def\UD@@@setproperty#1#2#3#4#5\UD@loopcall#6#7#8#9{%
\UD@ifstrequal{\UD@firstoftwo#1}{#2}%
{%
#4{#5{{#2}{#3}}#9}\@esphack
}{%
\UD@loopcall{#6}%
{#4{#5{#1}{{#2}{#3}}}\@esphack}%
{{#2}{#3}{#4}{#5{#1}}}%
{#9}%
}%
}%
\newcommand\GetProperty[3]{%
\romannumeral0%
\UD@AtIfPropertyListUndefined{#1}{ #3}{%
\UD@ExpandProperties{#1}{#2}{\UD@@getproperty}{ #3}%
}%
}%
\newcommand\UD@@getproperty[3]{%
\UD@loopcall{\UD@@@getproperty}%
{#3}%
{{#1}}%
{#2}%
}%
\newcommand\UD@@@getproperty{}%
\long\def\UD@@@getproperty#1#2\UD@loopcall#3#4#5#6{%
\UD@ifstrequal{\UD@firstoftwo#1}{#2}%
{\UD@exchange{ }\expandafter\UD@secondoftwo#1}%
{%
\UD@loopcall{#3}%
{#4}%
{#5}%
{#6}%
}%
}%
%%==== End of code for the \SetProperty-\GetProperty-Interface =========
%%==== Layout of this example ==========================================
% - No headers / no footers
\pagestyle{empty}
% - paragraph-breaking:
\parindent=0ex
\parskip=\medskipamount
% - horizontal margins:
\setlength\textwidth{\paperwidth}
\addtolength\textwidth{-3cm}
\setlength\oddsidemargin{1.5cm}%
\addtolength\oddsidemargin{-1in}%
\addtolength\oddsidemargin{-\hoffset}%
\setlength\evensidemargin{\oddsidemargin}%
\setlength\marginparwidth{1.5cm}%
\addtolength\marginparwidth{-2\marginparsep}%
% - vertical margins:
\setlength\topmargin{1.5cm}
\addtolength\topmargin{-1in}
\addtolength\topmargin{-\voffset}
% - no headheight/headsep etc as there are no headers as
% pagestyle=empty:
\setlength\headsep{0pt}
\setlength\headheight{0pt}
\setlength\footskip{0pt}
\setlength\textheight{\paperheight}
\addtolength\textheight{-3cm}%
\addtolength\textheight{-\footskip}%
\addtolength\textheight{-\headsep}%
\addtolength\textheight{-\headheight}%
% - allow linebreaks after closing braces in typewriter font:
\DeclareFontFamily{\encodingdefault}{\ttdefault}{\hyphenchar\font=`\}}
% - in case of pdftex also adjust the underlying paper
\@ifundefined{pdfpagewidth}{}{\pdfpagewidth=\paperwidth}%
\@ifundefined{pdfpageheight}{}{\pdfpageheight=\paperheight}%
\@ifundefined{pagewidth}{}{\pagewidth=\paperwidth}%
\@ifundefined{pageheight}{}{\pageheight=\paperheight}%
%%==== Layout-changes etc done.=========================================
\newcommand\Errortext[3]{%
\nfss@text{\reset@font\bfseries#3}%
\GenericError{\space\@spaces\@spaces}%
{Error: \string#1: Translation into language #2 not available}%
{Source for further information on this error is neither available nor needed.}%
{You can use \string\SetProperty for specifying a translation.}%
}%
\SetProperty{\mymacro}{English}{This is a sentence in English.}
\SetProperty{\mymacro}{Francais}{Ceci est une phrase en fran\c cais.}
\SetProperty{\mymacro}{Italiano}{Questa \`e una frase in italiano.}
\SetProperty{\mymacro}{Deutsch}{Dies ist ein Satz in deutscher Sprache.}
\makeatother
\begin{document}
\verb|\SetProperty{\mymacro}{English}{This is a sentence in English.}|\\
\verb|\SetProperty{\mymacro}{Francais}{Ceci est une phrase en fran\c cais.}|\\
\verb|\SetProperty{\mymacro}{Italiano}{Questa \`e una frase in italiano.}|\\
\verb|\SetProperty{\mymacro}{Deutsch}{Dies ist ein Satz in deutscher Sprache.}|
\\\null\hrulefill\null
\verb|\GetProperty{\mymacro}{English}{\Errortext{\mymacro}{English}{No translation available.}}|\\
\GetProperty{\mymacro}{English}{\Errortext{\mymacro}{English}{No translation available.}}
\verb|\GetProperty{\mymacro}{Francais}{\Errortext{\mymacro}{Francais}{Aucune traduction disponible.}}|\\
\GetProperty{\mymacro}{Francais}{\Errortext{\mymacro}{Francais}{Aucune traduction disponible.}}
\verb|\GetProperty{\mymacro}{Italiano}{\Errortext{\mymacro}{Italiano}{Nessuna traduzione disponibile.}}|\\
\GetProperty{\mymacro}{Italiano}{\Errortext{\mymacro}{Italiano}{Nessuna traduzione disponibile.}}
\verb|\GetProperty{\mymacro}{Deutsch}{\Errortext{\mymacro}{Deutsch}{Keine Übersetzung vorhanden.}}|\\
\GetProperty{\mymacro}{Deutsch}{\Errortext{\mymacro}{Deutsch}{Keine Übersetzung vorhanden.}}
\verb|% This will raise an error:|\\
\verb|\GetProperty{\mymacro}{Esperanto}{\Errortext{\mymacro}{Esperanto}{Neniu traduko havebla.}}|\\
% This will raise an error:
\GetProperty{\mymacro}{Esperanto}{\Errortext{\mymacro}{Esperanto}{Neniu traduko havebla.}}
\end{document}