Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I would like to change the \newcommand \shcpoint into an xparse \NewDocumentCommand. How can I achieve this?

\newcounter{totalpoints}
\regtotcounter{totalpoints} 

\newcommand{\shcpoint}[1]{%
  \addtocounter{totalpoints}{#1}%
  \ifnum#1<2
    (\emph{#1 point})\hphantom{\em s}%
  \else
    (\emph{#1 points})%
  \fi\quad
}

This is my attempt:

\NewDocumentCommand{\shcpoint}{ m }
{%
  \addtocounter{totalpoints}{#1}%
  \ifnum#1<2
    (\emph{#1 point})\hphantom{\em s}%
  \else
    (\emph{#1 points})%
  \fi\quad
}

My question then extends to: Is there something equivalent to \addtocounter, \ifnum, \else and \fi in LaTeX 3 syntax?

share|improve this question
2  
Take a quick look at texdoc expl3; it's worth it –  Sean Allred Feb 12 at 16:01
    
@SeanAllred Looking at it right now. Thnx –  azetina Feb 12 at 16:03
1  
My apologies; texdoc interface3. I really should stop looking at TeX.SX first thing in the morning :-) –  Sean Allred Feb 12 at 16:42
add comment

2 Answers

up vote 12 down vote accepted

You don't really gain much if you measure in terms of code. The gain is in flexibility and clarity. Here's a possible implementation:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\shcpoint}{ m }
 {
  \azetina_shcpoint:n { #1 }
 }

\NewDocumentCommand{\printtotal}{ }
 {
  \int_to_arabic:n { \g_azetina_totalpoints_int }
 }

\int_new:N \g_azetina_totalpoints_int

\cs_new_protected:Npn \azetina_shcpoint:n #1
 {
  \int_gadd:Nn \g_azetina_totalpoints_int { #1 }
  \int_compare:nTF { #1 < 2 }
    {
     (\emph{#1 ~ point})\hphantom{\em s}
    }
    {
     (\emph{#1 ~ points})
    }
  \quad
}
\ExplSyntaxOff

\begin{document}

\shcpoint{1} X

\shcpoint{3} X

\shcpoint{1} X

Total points: \printtotal

\end{document}

enter image description here

Some points to note.

  1. When a command defined with \NewDocumentCommand does more than can be implemented with user level commands, it should call a programmer's level function.

  2. Since we want to enter the programming environment, we need to issue \ExplSyntaxOn.

  3. Since your counter totalpoints is used to keep track of the points and not for typesetting values other than at the end, I declare it as an integer (global) variable.

  4. I define also the command for printing the variable's value; since it uses just one internal function, it's not necessary to define our own internal.

  5. In the programming environment spaces are ignored (unless they are syntactically meaningful for separating command names), so a space in output is usually denoted by ~.

  6. The internal function has one argument, so its name ends in :n.

  7. \azetina_shcpoint:n is “protected” because it performs an assignment.

  8. We do an “integer comparison”:

    \int_compare:nTF { <condition> }
     {
      <true case>
     }
     {
      <false case>
     }
    

David Carlisle correctly points out that there can be a tiny difference when “point” or ”points” is used. Here's a modified version of the code that shouldn't have the problem and also illustrate a further point: the comparison can also be

\int_compare:nT { <condition> } { <true case> }

omitting the false case if nothing should be done. Here we measure the text with the “s” (assuming that the number of points per question is less than 10 and using 0 as the default width for the digits) and typeset the final result in a box of the correct width.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\shcpoint}{ m }
 {
  \azetina_shcpoint:n { #1 }
 }

\NewDocumentCommand{\printtotal}{ }
 {
  \int_to_arabic:n { \g_azetina_totalpoints_int }
 }

\int_new:N \g_azetina_totalpoints_int
\box_new:N \l_azetina_points_box
\cs_new_protected:Npn \azetina_shcpoint:n #1
 {
  \int_gadd:Nn \g_azetina_totalpoints_int { #1 }
  \hbox_set:Nn \l_azetina_points_box { (0 ~ \textit{points}) }
  \makebox[\box_wd:N \l_azetina_points_box][l]
   {
    (\textit{#1 ~ point\int_compare:nT { #1 > 1 } { s }})
   }
  \quad
}
\ExplSyntaxOff

\begin{document}

\shcpoint{1} X

\shcpoint{3} X

\shcpoint{1} X

Total points: \printtotal

\end{document}

enter image description here

If you try with \showoutput, you can see that all the relevant boxes have width 42.7429pt.

share|improve this answer
    
Interesting approach with LaTeX 3 syntax. I will study your code and read the expl3 documentation that @SeanAllred suggested. –  azetina Feb 12 at 16:42
    
Will foo ~ bar between \ExpSyntaxOn … \ExpSyntaxOff “expand” to something like foo bar or foo~bar in the document? In case we want an usual (breakable) space what should we do? foo \ bar? –  Manuel Feb 12 at 18:22
1  
@Manuel foo ~ bar in the programming environment is the same as foo bar in the usual setting. If you want an unbreakable space in the programming environment, use \nobreakspace –  egreg Feb 12 at 18:36
add comment

The current expl3 programming layer is really aimed below this, so \addtocounter is fine I think. However the code doesn't do what I guess is the intention of setting point or points to the same width. Also it should be using \textit (or better \itshape) rather than \emph.

\sbox0{\emph{points}\quad}\showthe\wd0
\sbox0{\emph{point}\hphantom{\em s}\quad}\showthe\wd0

produces

> 36.63177pt.
l.13 \sbox0{\emph{points}\quad}\showthe\wd0

? 
> 36.75957pt.
l.14 ...h{point}\hphantom{\em s}\quad}\showthe\wd0

Which as you see are not equal. To see why consider

\documentclass{article}

    \begin{document}

    \showoutput

    1 \emph{points}\quad

    2 \emph{point}\hphantom{\em s}\quad


    \end{document}

which produces

....\hbox(0.0+0.0)x15.0
....\OT1/cmr/m/n/10 1
....\glue 3.33333 plus 1.66666 minus 1.11111
....\OT1/cmr/m/it/10 p
....\kern-0.51112
....\OT1/cmr/m/it/10 o
....\OT1/cmr/m/it/10 i
....\OT1/cmr/m/it/10 n
....\OT1/cmr/m/it/10 t
....\OT1/cmr/m/it/10 s
....\kern 0.82082
....\penalty 10000
....\glue(\parfillskip) 0.0 plus 1.0fil
....\glue(\rightskip) 0.0
...\glue(\parskip) 0.0 plus 1.0
...\glue(\baselineskip) 3.50197
...\hbox(6.55359+1.94444)x345.0, glue set 294.9071fil
....\hbox(0.0+0.0)x15.0
....\OT1/cmr/m/n/10 2
....\glue 3.33333 plus 1.66666 minus 1.11111
....\OT1/cmr/m/it/10 p
....\kern-0.51112
....\OT1/cmr/m/it/10 o
....\OT1/cmr/m/it/10 i
....\OT1/cmr/m/it/10 n
....\OT1/cmr/m/it/10 t
....\kern 0.94861
....\hbox(0.0+0.0)x4.08887
....\penalty 10000
....\glue(\parfillskip) 0.0 plus 1.0fil
....\glue(\rightskip) 0.0

Where you can see the difference is the different \kern which is the italic correction (added by \emph) for s and t respectively. You would see an even bigger difference if the trailing command had been . rather than \quad as then the italic correction would be suppressed in the first case but not the second.

In this case you could use

{\itshape points}\quad}

and

{\itshape point}\hphantom{\itshape s}\quad}

But that relies on there being no kern specified in the font metrics between t and s.

a safer construct would be

\settowidth\dimen@{\itshape points}%
\makebox[\dimen@][l]{point}\quad
share|improve this answer
    
Very good pointer! Invaluable contribution. Thanks. I really did not notice that. –  azetina Feb 12 at 16:38
1  
@azetina You probably made the mistake of looking at the pdf output rather than \showoutput output. I don't ever do that, the log is far more revealing:-) –  David Carlisle Feb 12 at 16:45
    
True, that is so true :-) –  azetina Feb 12 at 16:47
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.