
我正在尋找一個可以在不同上下文中使用多個值的命令的定義。其用例是在同時使用兩種語言的自訂類別中,或者我猜變數可能根據環境具有不同值的任何情況。查了相關資料\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>}
,會定義一個新的宏,並在呼叫 via 時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}