7

Assume you're defining a key=value align for some environment realign that you'll use in the following way:

\usepackage{xkeyval}% http://ctan.org/pkg/xkeyval
\newenvironment{realign}[1][]
  {\setkeys{fam}{#1}\myalignment}% \begin{realign}
  {}% \end{realign}

You define the align choice key to set \myalignment by using (taken from the xkeyval documentation):

\define@choicekey*{fam}{align}[\val\nr]{left,center,right}{%
  \ifcase\nr\relax
    \def\myalignment{\raggedright}% align=left
  \or
    \def\myalignment{\centering}% align=center
  \or
    \def\myalignment{\raggedleft}% align=right
  \fi
}

However, now you have the awesome idea of creating corresponding boolean keys left, center and right such that left|center|right=true is equivalent to align=left|center|right. Is it possible to define the boolean keys inside a macro definition, for example, via something like \newboolkey{left}:

\newcommand{\newboolkey}[1]{
  \define@boolkey{fam}[@bool@]{#1}[true]{%
    \csname if@bool@#1\endcsname%
      \setkeys{fam}{align=#1}%
    \fi%
  }%
}

This doesn't work, since the argument #1 of \newboolkey doesn't translate properly to the definition of the left key.

Of course, removing \define@boolkey from \newboolkey and explicitly stating the key (instead of #1) works. However, I'm not interested in creating a boolean key in this way (by hand). I'd like to use a macro definition to do this since it should be automated and flexible.

Here's a non-working minimal example:

\documentclass{article}
\usepackage{lipsum}% http://ctan.org/pkg/lipsum
\usepackage{xkeyval}% http://ctan.org/pkg/xkeyval
\newenvironment{realign}[1][]
  {\setkeys{fam}{#1}\myalignment}% \begin{realign}
  {}% \end{realign}
\makeatletter
\define@choicekey*{fam}{align}[\val\nr]{left,center,right}{%
  \ifcase\nr\relax
    \def\myalignment{\raggedright}% align=left
  \or
    \def\myalignment{\centering}% align=center
  \or
    \def\myalignment{\raggedleft}% align=right
  \fi
}
\newcommand{\newboolkey}[1]{
  \define@boolkey{fam}[@bool@]{#1}[true]{%
    \csname if@bool@#1\endcsname%
      \setkeys{fam}{align=#1}%
    \fi%
  }%
}

\makeatother
\begin{document}
\begin{realign}[align=left]
\lipsum[1]
\end{realign}

\newboolkey{left}% Create a boolean equivalent for align=left
\makeatletter
\if@bool@left\else% Check if key is defined.
  \texttt{left} key defined
\fi

\begin{realign}[left]% Doesn't work
\lipsum[1]
\end{realign}
\end{document}
3
  • You forgot a \myalignment in your definition of realign. (I recommend an other keyval package -- kvoptions ;-) ) Apr 27, 2012 at 19:49
  • @MarcoDaniel: I am curious: why do you recommend not using xkeyval? Apr 27, 2012 at 23:56
  • @GonzaloMedina: The setting of a family and a prefix by \SetupKeyvalOptions are very interesting. The package kvoptions uses kvsetkeys. I have the feeling it is more user friendly. However it doesn't provide \define@choicekey. In my next package I will use l3keys ;-) Apr 28, 2012 at 8:03

3 Answers 3

8

Marco's already posted a version, but this is I think a bit closer to what you were trying, just fixed the #1/##1 confusion.

\documentclass{article}
\usepackage{lipsum}% http://ctan.org/pkg/lipsum
\usepackage{xkeyval}% http://ctan.org/pkg/xkeyval
\newenvironment{realign}[1][]
  {\let\myalignment\relax\setkeys{fam}{#1}\myalignment}% \begin{realign}
  {}% \end{realign}
\makeatletter
\define@choicekey*{fam}{align}[\val\nr]{left,center,right}{%
  \ifcase\nr\relax
    \def\myalignment{\raggedright}% align=left
  \or
    \def\myalignment{\centering}% align=center
  \or
    \def\myalignment{\raggedleft}% align=right
  \fi
}
\newcommand{\newboolkey}[1]{
  \define@boolkey{fam}[@bool@]{#1}[true]{%
    \csname if##1\endcsname
      \setkeys{fam}{align=#1}%
    \csname fi\endcsname
  }%
}

\makeatother
\begin{document}
\begin{realign}[align=left]
\lipsum[1]
\end{realign}

\newboolkey{left}% Create a boolean equivalent for align=left
\makeatletter
\if@bool@left\else% Check if key is defined.
  \texttt{left} key defined
\fi

\begin{realign}[left]% Doesn't work
\lipsum[1]
\end{realign}
\end{document}
3
  • 1
    Nice trick ;-) -- I thought about \iftrue but couldn't use it ;-) So I took \in@ ;-) Apr 27, 2012 at 19:59
  • So why is that you cannot combine a \csname if...\endcsname with a regular \fi and have to use \csname fi\endcsname?
    – Werner
    Apr 27, 2012 at 20:49
  • 3
    @Werner I didn't look at exactly xkeyval was doing here but \csname if...\endcsname ...\fi is "an accident waiting to happen" if it appears within a conditional \ifxxx construct in a false branch or unused ifcase branch it will mess up the nesting as TeX will not see the \csname if... as an if but it will see the \fi so the \fi will end some random outer context rather than the intended inner if test. Apr 27, 2012 at 21:05
6

You can test the argument of define@boolkey. It's easier to handle:

\newcommand{\newboolkey}[1]{%
  \define@boolkey{fam}[@bool@]{#1}[true]{%
   \in@{true}{##1}
   \ifin@
     \setkeys{fam}{align=#1}%
  \fi
  }%
}

If you want to use your code you can use:

\newcommand{\newboolkey}[1]{
  \define@boolkey{fam}[@bool@]{#1}[true]{%
    \csname if@bool@#1\endcsname%
      \setkeys{fam}{align=#1}%
    \csname  fi \endcsname%
  }%
}

The complete code which works for me:

\setcounter{errorcontextlines}{999}
\documentclass{article}
\usepackage{lipsum}% http://ctan.org/pkg/lipsum
\usepackage{xkeyval}% http://ctan.org/pkg/xkeyval
\newenvironment{realign}[1][]%
  {\setkeys{fam}{#1}\myalignment}% \begin{realign}
  {}% \end{realign}
\makeatletter
\let\myalignment\relax
\define@choicekey*{fam}{align}[\val\nr]{left,center,right}{%
  \ifcase\nr\relax
    \def\myalignment{\raggedright}% align=left
  \or
    \def\myalignment{\centering}% align=center
  \or
    \def\myalignment{\raggedleft}% align=right
  \fi
}
\newcommand{\newboolkey}[1]{%
  \define@boolkey{fam}[@bool@]{#1}[true]{%
   \in@{true}{##1}
   \ifin@
     \setkeys{fam}{align=#1}%
  \fi
  }%
}

\makeatother
\begin{document}
\begin{realign}[align=center]
\lipsum[1]
\end{realign}

\hrulefill

\newboolkey{left}% Create a boolean equivalent for align=left



\begin{realign}[left]
\lipsum[1]
\end{realign}
\end{document}
0

I saw that you wanted to define more than one boolean key. I decided to use ltxkeys package. I would have used key environment for this task, but here is what you wanted. I would also first have thought of the command \ltxkeys@declarekeys.

\documentclass{article}
\usepackage{lipsum}
\usepackage{ltxkeys}
\makeatletter
\newenvironment{realign}[1][]{%
  \let\myalignment\relax
  \ltxkeys@setkeys[WN]{fam}{#1}\myalignment
}{}
% 1. The *-form of \ltxkeys@choicekey will convert the user input to lowercase 
%    before checking the value against the nominations and executing the callbacks.
% 2. The !-form will fully expand the user input before checking it against
%    the nominations.
% 3. The +-form expects two callbacks (one for when the user input is valid,
%    and the other for when the user input isn't in the nominations).

% \val and \mpcalign hold the same content.
\ltxkeys@choicekey*+![WN]{fam}[mpc]{align}[\val\nr]{%
  center/.code=\let\myalignment\centering,
  left/.code=\let\myalignment\raggedright,
  right/.code=\let\myalignment\raggedleft
}[center]{%
  % Callback for when value is correct, ie, in the list {left,right,center}.
}{%
  \@latexerr{Invalid value for key 'align'}\@ehd
}
% I thought you meant to define more than one boolean key:
\ltxkeys@boolkeys[WN]{fam}[mpb]{left,right,center}[true]{%
  \ifboolFT{mpb\ltxkeys@tkey}{}{%
    \cptexpanded{\ltxkeys@setkeys[WN]{fam}{align=\ltxkeys@tkey}}%
  }%
}
\let\ltxsetkeys\ltxkeys@setkeys
\makeatother
\begin{document}
\begin{realign}[align=left]
\lipsum[1]
\end{realign}
\begin{realign}[align=right]
\lipsum[1]
\end{realign}
\ltxsetkeys[WN]{fam}{right=true}
\ltxsetkeys[WN]{fam}{left=false}
\def\say#1{%
  \cptdocommalist{#1}{%
    \endgraf Key \texttt{##1} set
    \ifboolTF{mpb##1}{true}{false}.
  }%
}
\say{left,right,center}
\end{document}

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .