13

I am trying to combine the solutions from

to parse optional parameters following the end of an environment. There are only three possible cases with these options:

  1. A subscript: _{subscript}
  2. A superscript: ^{superscript}
  3. Both subscript and superscript: _{subscript}^{superscript}

The case of ^{subscript}_{superscript} is not required.

I would have thought that adding the optional parameters to the \@MatrixWithParen helper macro would have sufficed, but it seems that the options don't get to that macro.

Attempted Solutions:

  1. Use \collect@body to invoke \@MatrixWithParen which looks for optional parameters. Based on what egreg comments, this is not the way to go. The MWE yields what is on the left, and the desired result is on the right:

    enter image description here enter image description here

  2. Store the content from \collect@body in a macro and use \AfterEndEnvironment to invoke a macro to parse the optional parameters. This also seems that the optional parameters are not available to the macro specified to be executed by \AfterEndEnvironment. Similarly \aftergroup and \AtEndEnvironment also did not work:

    enter image description here

    This non-solution has additional issues if there is more than one bmatrix environemnt, but I think that could be solved with additional programming. Trying to keep this question focused on parsing optional parameters following an end{environment}.

  3. Another thought I had was to modify \collect@body so that invokes a macro that allows for two optional parameters, but that is beyond my understanding.

Notes:

  • This question is not how to get a matrix with () instead of [] (which pmatrix can easily do), as this is just a MWE designed to illustrate my actual use case with minimal additional code. That is, as per my other question, I need #1 to be parameter to \@MatrixWithParen and not simply a subscript added outside of the macro (i.e., \@MatrixWithParen must be the one that invokes the actual subscript and superscript).

Related Question:

General Question:

  • How to parse optional parameters following a specfic \end{environment}?

1. Optional parameters parsed by macro invoked by \collect@body.

\documentclass{article}
\usepackage{mathtools}
\usepackage{xparse}
\usepackage{xcolor}

\makeatletter
\NewDocumentCommand{\@MatrixWithParen}{m k_ k^}{%
    \left(
        \begin{matrix}%
            #1%  content
        \end{matrix}
    \right)
    %% These seem to have no effect
    \IfValueT{#2}{_{\textcolor{red}{#2}}}%
    \IfValueT{#3}{^{\textcolor{blue}{#3}}}%
}%

\RenewDocumentEnvironment{bmatrix}{}{%
   {\ifnum0=`}\fi\collect@body\@MatrixWithParen%
}{%
   \ifnum0=`{\fi}%
}%
\makeatother

\begin{document}
\begin{align*}
   A &= \begin{bmatrix}                  %% Both subscript and superscript
            a_{11} & a_{12} \\
            a_{21} & a_{22} \\
        \end{bmatrix}_{2 \times 2}^{-1}
   \\
   B &= \begin{bmatrix}                  %% Only superscript
             1 & -1 \\
            -1 &  1 \\
        \end{bmatrix}^{2}
   \\
   C &= \begin{bmatrix}                  %% Only subscript
             a & b \\
             b & d \\
        \end{bmatrix}_{2}
\end{align*}
\end{document}

2: Attempt to use \AfterEndEnvironment:

\documentclass{article}
\usepackage{amsmath}
\usepackage{xparse}
\usepackage{xcolor}
\usepackage{etoolbox}

\makeatletter
\newcommand*{\@MatrixContents}{}%
\newcommand*{\@PostBMatrix}{\@PostBMatrixAux}%
\NewDocumentCommand{\@PostBMatrixAux}{k_k^}{%
    \@MatrixContents
    \IfValueT{#1}{_{\textcolor{red}{#1}}}%
    \IfValueT{#2}{^{\textcolor{blue}{#2}}}%
}
\NewDocumentCommand{\@MatrixWithParen}{m}{%
    \gdef\@MatrixContents{%
        \left(
            \begin{matrix}
                #1%  content
            \end{matrix}
        \right)
    }%
    %\aftergroup{\@PostBMatrix}%
    %\AtEndEnvironment{bmatrix}{\@PostBMatrix}%   Not sure why this has slightly
    \AfterEndEnvironment{bmatrix}{\@PostBMatrix}% different spacing than this.
}%

\RenewDocumentEnvironment{bmatrix}{}{%
   {\ifnum0=`}\fi\collect@body\@MatrixWithParen%
}{%
   \ifnum0=`{\fi}%
}%
\makeatother

\begin{document}
\[
    A = \begin{bmatrix}
            a_{11} & a_{12} \\
            a_{21} & a_{22} \\
        \end{bmatrix}_{2 \times 2}^{-1}
\]
\end{document}
  • 2
    With xparse you can use the argument specifiers in the end part of \NewDocumentEnvironment, but not define arguments for \end{<name>}; the arguments can only be given to \begin{<name>} – egreg Jul 14 '16 at 22:57
  • 1
    You seem to have unclear what \collect@body does; it should be followed by a macro with a single parameter, which is passed the body of the environment. – egreg Jul 14 '16 at 23:04
  • @egreg: My thought was that it would process the single parameter of \collect@body and then continue on in case there were additional parameters at the end of the environment. But, guess it does not work that way. So, what is the best way to be able to parse the parameters following a specific environment? – Peter Grill Jul 14 '16 at 23:14
  • Use a macro after \end{bmatrix}. Or place the arguments after \begin{bmatrix}. – egreg Jul 14 '16 at 23:28
  • The LaTeX model is quite clear here: arguments are given to \begin{foo} only. Why not just use an optional argument to a custom environment? – Joseph Wright Jul 15 '16 at 5:48
9
+500

The problem with \AfterEndEnvironment is that there's an extra \if@ignore\@ignorefalse\ignorespaces\fi bit of code after it. So the arguments k_k^ never reach their intended sub- and super-scripts. You can move around this with an extra argument u{\fi} (or, if you want to be more cautious, may be u{\if@ignore\@ignorefalse\ignorespaces\fi}, so it gives an error if that code is not present, I will use u{\fi} for simplicity).

Full code

\documentclass{scrartcl}

\usepackage{etoolbox}
\usepackage{mathtools}
\usepackage{xparse}
\usepackage{xcolor}

\makeatletter
\RenewDocumentEnvironment{bmatrix}{}
 {{\ifnum0=`}\fi\collect@body\savematrix}
 {\ifnum0=`{\fi}}

\newcommand\matrixbody{}
\NewDocumentCommand\savematrix{m}{\gdef\matrixbody{#1}}

\AfterEndEnvironment{bmatrix}{\printmatrix}
\NewDocumentCommand\printmatrix{u{\fi} k_ k^}
 {\left(\begin{matrix}%
    \matrixbody
  \end{matrix}\right)%
  \IfValueT{#2}{_{\textcolor{red}{\!#2}}}%
  \IfValueT{#3}{^{\textcolor{blue}{\!#3}}}}
\makeatother

\begin{document}

\begin{align*}
  A &= \begin{bmatrix}                  %% Both subscript and superscript
          a_{11} & a_{12} \\
          a_{21} & a_{22} \\
      \end{bmatrix}_{2 \times 2}^{-1}
  \\
  B &= \begin{bmatrix}                  %% Only superscript
           1 & -1 \\
          -1 &  1 \\
      \end{bmatrix}^{2}
  \\
  C &= \begin{bmatrix}                  %% Only subscript
           a & b \\
           b & d \\
      \end{bmatrix}_{2}
\end{align*}

\end{document}

enter image description here


As a general suggestion, if you are not into looking at the definition or at the source (which might be a bit tedious, if you don't know where to look) you can define an auxiliar macro that would print into the document “what's there” and will let you easily check what might be wrong. For instance, if you define

\def\qq#1\qq{#1\par\texttt{\detokenize{#1}}}

the macro will grab everything until \qq, will put it back (so the code compiles) and will write all the code that it saw in \tt font in a new paragraph (remember to put the delimiting \qq —or whatever name you choose— outside math mode). Here are a few examples.

Imagine you used \AtEndEnvironment and it didn't work, a fast way to check why it doesn't work is

\AtEndEnvironment{bmatrix}{\qq}
$\begin{bmatrix} a \end{bmatrix}$ \qq

which will show “what's there” right away

enter image description here

It's fast and direct, you just now exactly what's going on there. Now if you use \AfterEndEnvironment

\AfterEndEnvironment{bmatrix}{\qq}
$\begin{bmatrix} a \end{bmatrix}$ \qq 

enter image description here

Which is what I have done, hence we need to grab those four tokens and I decided to go with u{\fi} to grab until \fi.

It's not granted that it would work, but many times it will give you a fast overview of what's happening. You can just try it in case something goes wrong, it will take a second and it might solve your problem.

  • 1
    I know you know this, but really is an abuse of xparse: it's for defining document commands with LaTeX2e-like syntax, not arbitrary argument parsing. (Comment just to flag this up for any passing readers.) – Joseph Wright Jul 15 '16 at 6:22
  • Yes, I know it's a hack. But it's true that one might want to parse the sub/super-scripts after a matrix; the use of xparse here is only to save work, one could just program it manually with \@ifnextchar, etc. – Manuel Jul 15 '16 at 6:30
  • Looks good. Will try it out in my actual use case tomorrow. – Peter Grill Jul 15 '16 at 7:00
  • @PeterGrill If you are lazy to look at the code, there's always a trick you can use to check what's there. You define \def\qq#1\qq{#1\texttt{\detokenize{#1}}} for instance, and then do \AfterEndEnvironment{bmatrix}{\qq} \begin{bmatrix} a \end{bmatrix} \qq And that will print what's exactly “hidden” there. It's really helpful for a fast debug, and may easily solve some problems in many occasions. – Manuel Jul 15 '16 at 10:02
6
+400

Simpler and safer than @Manuel’s (very clever, anyway) solution:

\documentclass{scrartcl}

% \usepackage{etoolbox} isn't actually necessary
\usepackage{mathtools}
\usepackage{xparse}
\usepackage{xcolor}

\makeatletter

\renewenvironment*{bmatrix}{%
    {\ifnum0=`}\fi
    \collect@body\processmatrix
}{%
    \ifnum0=`{\fi}%
}

\NewDocumentCommand \processmatrix { m } {%
    \begingroup
    \RenewDocumentCommand \end {mk_k^}{%
        \endgroup
        \end{##1}%
        \left(\begin{matrix}#1\end{matrix}\right)%
        \IfValueT{##2}{_{\textcolor{red} {\!##2}}}%
        \IfValueT{##3}{^{\textcolor{blue}{\!##3}}}%
    }%
}

\makeatother



\begin{document}

\begin{align*}
  A &= \begin{bmatrix}                  %% Both subscript and superscript
          a_{11} & a_{12} \\
          a_{21} & a_{22} \\
      \end{bmatrix}_{2 \times 2}^{-1}
  \\
  B &= \begin{bmatrix}                  %% Only superscript
           1 & -1 \\
          -1 &  1 \\
      \end{bmatrix}^{2}
  \\
  C &= \begin{bmatrix}                  %% Only subscript
           a & b \\
           b & d \\
      \end{bmatrix}_{2}
  \\
  D &= \begin{bmatrix}                  %% Neither subscript nor superscript
           a & b \\
           b & d \\
      \end{bmatrix}
  \\
  E &= \begin{bmatrix}                  %% Example of nesting
           \begin{bmatrix} x & y \\ -y & x \end{bmatrix}^{h}
               & \begin{bmatrix} a & b \\ -b & a \end{bmatrix}^{k} \\
           \begin{bmatrix} a & -b \\ b & a \end{bmatrix}_{h}
               & \begin{bmatrix} x & -y \\ y & x \end{bmatrix}_{k}\\
      \end{bmatrix}_{2 \times 2}^{-1}
\end{align*}

\end{document}

It seems to cope correctly with nesting, too. This is abusing xparse, anyway.


Addition

One of the comments asks me to “elaborate on why this is ‘safer’”. Well, there is a general reason, and it is that this solution makes no particular assumption about the code contained in the \end macro, so it would continue to work even if the latter were to change; and this certainly qualifies as “being safer”, in the sense of “more robust”.

However, when I originally wrote that sentence I had a much more specific issue in mind, which—as I realized later—was based on a false premise; this issue was briefly discussed in a couple of comments to Manuel’s answer that I deleted when I became aware of my error (actually, after a grace period of 48 hours). I’ll see if there is a means to undelete them, thereby leaving them visible for reference.

Heaven knows why, I had assumed that the \AfterEndEnvironment command injected (a macro that expanded to) the code indicated in its second argument after the group implied by the environment being patched, by means of \aftergroup, which is not at all the case. (Few things are as treacherous as the assumptions you make semi-unconciously.) My idea was that, if it had been so, the mechanism devised by Manuel would have been thwarted if the \if@endpe switch had been set to true, as it is done, for example, by all environments based on either \list or \trivlist. “The solution—I said, more or less, in a comment—works well for the case presented in the question, but could prove inadequate in general, for example when applied to ‘list-making’ environments”.

Let us explain briefly the issue. The \if@endpe switch is part of the little hack by means of which the paragraph indentation is suppressed if a paragraph follows immediately an \end{environment} declaration, with no intervening blank lines. So, for instance, if you say

\begin{center}
    Text inside.
\end{center}
Text after.

then “Text after” is not indented, as it had been preceded by \noindent. The code that implements the \end macro, that is

\def\end#1{%
  \csname end#1\endcsname\@checkend{#1}%
  \expandafter\endgroup\if@endpe\@doendpe\fi
  \if@ignore\@ignorefalse\ignorespaces\fi}

looks at this switch and, if it is true, execute the \@doendpe macro, which takes care of setting and resetting the definition of \par and of \everypar in such a way that the desired effect is achieved. (I will not supply further details here: look at the definition of the \@endparenv macro in ltlists.dtx if you are curious.) Note, however, that it says \expandafter\endgroup\if@endpe, which means that the order of execution of the commands is as follows:

  1. First, the \if@endpe conditional is evaluated; if it is false, the tokens up to and including the matching \fi, that in this case is the \fi that follows \@doendpe, are immediately gobbled; if it is true, only the \if@endpe token disappears from the input stream, and execution continues with this conditional remaining “pending”.

  2. \endgroup is executed, and the tokens saved for insertion after the current group are delivered exactly at his point.

Thus we see that, if it were true that \AfterEndEnvironment used \aftergroup to accomplish its task, the \printmatrix macro used by Manuel would find the following tokens intervening before the two k-arguments:

  • if \if@endpe is false,

    \if@ignore\@ignorefalse\ignorespaces\fi
    

    with no pending conditional;

  • if \if@endpe is true,

    \@doendpe\fi\if@ignore\@ignorefalse\ignorespaces
    

    with the \if@endpe conditional still pending.

You can see that the second case might be problematic…

All this can be confirmed by means of some simple experiments, of course not using \AfterEndEnvironment, but rather \aftergroup to insert a macro similar to the \qq test macro, that Manuel suggest in the second part of his answer, “after” (in the sense specified above) a certain environment.

% My standard header for TeX.SX answers:
\documentclass[a4paper]{article} % To avoid confusion, let us explicitly 
                                 % declare the paper format.

\usepackage[T1]{fontenc}         % Not always necessary, but recommended.
% End of standard header.  What follows pertains to the problem at hand.

\def\qq#1\qq{ (%
    level:~\number\currentiflevel,
    type:~\number\currentiftype,
    branch:~\number\currentifbranch
    \space \detokenize{#1})#1%
}

\makeatletter

\newenvironment*{grabtest}{}{%
    \aftergroup\qq
    % \@endpetrue
}

\makeatother



\begin{document}

Text before.

\begin{grabtest}Text inside\end{grabtest}\qq

Text after.

\end{document}

Commenting or uncommenting the \@endpetrue command will exhibit, in the output, exactly the two outcomes described above.

The following source file shows what would happen if Manuel’s approach were used in conjunction with \aftergroup: this is precisely what I was thinking of when I qualified my solution as being “safer” than his.

% My standard header for TeX.SX answers:
\documentclass[a4paper]{article} % To avoid confusion, let us explicitly 
                                 % declare the paper format.

\usepackage[T1]{fontenc}         % Not always necessary, but recommended.
% End of standard header.  What follows pertains to the problem at hand.

\usepackage{xparse}

\makeatletter

\NewDocumentCommand \qq {u{\if@ignore\@ignorefalse\ignorespaces\fi} k_ k^ } {%
    \unskip\space(%
    \IfValueTF{#2}{subscript:   $#2$}{no subscript},
    \IfValueTF{#3}{superscript: $#3$}{no superscript})%
}

\newenvironment*{genericthing}{}{%
    \aftergroup\qq
    \@endpetrue
}

\makeatother



\begin{document}

Text before.

\begin{genericthing}Text inside\end{genericthing}_{a\times b}^{\pi}

Text after.

\end{document}

The example compiles, but you get a warning about an incomplete conditional:

(\end occurred when \iftrue on line 31 was incomplete)

In this case, this does not hurt, but in general it could be a problem!

One final remark: Manuel’s solution works correctly with nesting, notwithstanding the use of \global definitions, because the order in which these definitions are executed happens to be (but not by chance!) the “right” one.

  • Remember that you are not inside \ExplSyntaxOn so you may want to add a few %. – Manuel Jul 17 '16 at 10:06
  • @Manuel: Are you sure? The examples in the xparse manual use spaces freely… – GuM Jul 17 '16 at 10:09
  • Yes, but those spaces don't matter if you are inside \ExplSyntaxOn .. \ExplSyntaxOff. In this case the definition of the command \processmatrix is done outside, so you need to add % as usual. – Manuel Jul 17 '16 at 10:11
  • @Manuel: It seems you are right, I admit I didn’t know. Thank you very much, I’ll update! – GuM Jul 17 '16 at 10:21
  • Very clever, in any case ;) – Manuel Jul 17 '16 at 10:31

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.