11

I am modifiyng some macros to enforce a section style that should end with a full stop. The full stop is added automatically, but sometimes people add the full stop and I get two. How can I modify the code to detect the punctuation and add it only if it is needed? I guess I need to use futurelet, but I don't know how to use it.

lockstep
  • 250,273
Chuang
  • 579
  • 4
    It would be helpful to compose a fully compilable MWE that illustrates the problem including the \documentclass and the appropriate packages so that those trying to help don't have to recreate it. This will also clarify any ambiguities in your question. – Peter Grill Mar 02 '12 at 05:07
  • @PeterGrill I am sorry the code was really long. Will give one in my next question. – Chuang Mar 02 '12 at 05:24
  • Ok. But keep in mind, everyone's real code is really long. The point is to create a new document as small as possible that reproduces the problem. In this case you could just define a macro that adds punctuation and passes it two test cases: one with the punctuation, one without? – Peter Grill Mar 02 '12 at 16:38

2 Answers2

15

Firstly it is always best to post a MWE. As you have not posted one I am posting a general answer, for exact formatting of the section you will need to add your own formatting commands.

You don't need a \futurelet. TeX keeps track of special codes for letters and punctuation to place the right amount of space after a stop. We can check the value of the \spacefactor macro and add a stop or do nothing if it is already there.

\newcommand\@addpunctuation[1]{\ifnum\spacefactor>1000\else#1\fi}

The rest of the code are minor details. Here is the MWE.

\documentclass{article}
\makeatletter
\newcommand\@addpunctuation[1]{\ifnum\spacefactor>1000\else#1\fi}
\newcommand\buildhead[1]{#1\@addpunctuation.}
\DeclareRobustCommand{\addhead}[1]{\buildhead{#1}}
\DeclareRobustCommand\Section[1]{\addhead{#1}}
\def\section{\secdef \starcmd \unstarcmd}
\def\starcmd[#1]#2{\bfseries\Large\addhead{#2}}
\newcommand\unstarcmd[1]{\bfseries\setcounter{section}{O}\renewcommand\thesection{\Alph{section} \addhead{#1}}}
\makeatother
\parindent0pt
\begin{document}
\makeatletter

\section[small title]{Testing}\\
\section{Testing}\\ % remains as is 
\Section{Testing}\\
\Section{Testing.}
\end{document}

If you want the macro to work both for \frenchspacing and \nonfrenchspacing, you can try:

\newcommand\@addpunctuation[1]{%
   \nonfrenchspacing
   \ifnum\sfcode`.=1000
     \ifnum\spacefactor>1000\else#1\fi
   \else
      \ifnum\spacefactor>1000\else#1\fi
      \nonfrenchspacing
  \fi
}

and test with

\frenchspacing

Test. \fbox{A test test}

\nonfrenchspacing

Test. \fbox{A test test}

You can also visit the link quoted by Philippe Goutet in the comments below.

David Carlisle
  • 757,742
yannisl
  • 117,160
  • The last example entry has two dots if \frenchspacing is active. – morbusg Mar 02 '12 at 06:14
  • @morbusg \frenchspacing sets all the sfcodes to 1000 so one would have to modify the above to check for frenchspacing. Perhaps a better technique is to check the value of the sfcode for the stop and then do the check. – yannisl Mar 02 '12 at 06:23
  • 1
    @morbusg: see \frenchspacing not setting the space factor codes correctly? for how the ams solved the problem with its \@addpunct macro. – Philippe Goutet Mar 02 '12 at 10:56
  • @PhilippeGoutet Thanks for the link. I posted an alternative in the answer which I had already worked out, would you please care to check it out and comment on it? – yannisl Mar 02 '12 at 11:09
  • @YiannisLazarides: Using your \@addpunctuation macro with \frenchspacing, I get two dots. The problem with the idea of starting \@addpunctuation with \nonfrenchspacing is that the space factor has already been set, so it's too late as \frenchspacing Text.\nonfrenchspacing\the\spacefactor will show. I don't think you can get around redefining \frenchspacing as the AMS did. – Philippe Goutet Mar 02 '12 at 13:26
  • @PhilippeGoutet I agree: \@addpunct as in amsmath needs a redefinition of \frenchspacing that slightly increases the space factor of the punctuation symbols; this doesn't influence the typesetting (only by a few scaled points, actually), but allows for recognizing them. – egreg Mar 02 '12 at 13:51
  • yes \spacefactor is being tracked while the horizontal list is being build, so changing \sfcode settings afterwards has no effect. So if you want uniform spacing with identical sfcodes then your nice trick simply doesn't work. – Frank Mittelbach Mar 02 '12 at 14:05
  • @FrankMittelbach Can you please try my workaround at the bottom of the question? This seems to work. Of course will not work with AMS. – yannisl Mar 02 '12 at 14:10
  • @YiannisLazarides: your last code is what we were talking about and unfortunately doesn't work. I think the confusion comes from a typo: the last \nonfrenchspacing should be a \frenchspacing and so your macro switches the rest of the document to \nonfrenchspacing thus \frenchspacing Text.\@addpunctuation{.} Text.\@addpunctuation{.} will not work correctly the first time but will the second as \frenchspacing is no longer in effect. – Philippe Goutet Mar 03 '12 at 07:46
  • @PhilippeGoutet Thanks I see what you mean. – yannisl Mar 03 '12 at 08:49
4

The problem can be solved at a macro level without 'meddling' with the space factor.

\documentclass{article}
\makeatletter
\usepackage{catoptions}
\robust@def*\newsection#1{%
  \begingroup
  \edef\currtitle{\cpttrimspaces{#1}}%
  \def\currlabel{}\def\lasttok{}%
  \xifinsetFT{\detokenize{\label}}{\cptoxdetok\currtitle}{}{%
    \def\reserved@a##1\label##2##3\cpt@nil{%
      \def\currtitle{##1##3}%
      \def\currlabel{\noexpand\label{##2}}%
    }%
    \expandafter\reserved@a\currtitle\cpt@nil
  }%
  \def\getlasttok##1{%
    \expandafter\ifx\@car##1\@nil\cpt@nnil
    \else
      \edef\lasttok{\unexpanded{##1}}%
      \expandafter\getlasttok
    \fi
  }%
  \expandafter\getlasttok\currtitle\cpt@nnil
  \s@expandarg\ifinsetTF\lasttok{\dots\ldots\textellipsis}{%
    \@tempswatrue
  }{%
    \begingroup
    \lccode`\X=133
    \lowercase{\endgroup\expandafter\expandafter\expandafter
      \ifstrcmpTF\expandafter\@car\lasttok\@nil X}{%
      \@tempswatrue
    }{%
      \@tempswafalse\let\elt\relax
      \def\siso@do##1{%
        \xifinsetFT{\detokenize{##1}\elt}{\cptoxdetok\currtitle\elt}{}{%
          \@tempswatrue\loopbreak
        }%
      }%
      \siso@@loop{.!?:\dots\ldots\textellipsis}%
    }%
  }%
  \def\elt{\expandcsonce\currtitle}%
  \cptexpandarg{\endgroup\section}{\if@tempswa\elt\else\elt.\fi\currlabel}%
}
\makeatother

\begin{document}
\newsection{Test section.\label{sec:1}}
As seen later in Section~\ref{sec:this} \ldots.
\newsection{Test section}
\newsection{Test section.}
\newsection{Is this a test section?}
\newsection{Heh, this is test section!}
\newsection{Test section\dots}
\newsection{Test section\ldots}
\newsection{Test section\textellipsis}
\newsection{Test section\label{sec:this}\ldots}
As seen earlier in Section~\ref{sec:1} \ldots.

% I have accounted for the ASCII character 133 but you may not get it to print:
\newsection{Test section …}
\end{document}
Ahmed Musa
  • 11,742
  • This will not work with \dots: \newsection{Test section\dots} will give four dots instead of three – Philippe Goutet Mar 03 '12 at 07:48
  • @PhilippeGoutet: Please do you have other failure modes, so that I can try to address them at once? – Ahmed Musa Mar 03 '12 at 14:50
  • Well, it should work with all the punctuation so it is safer to also add , and : and ; but that should be all. Of course, the solution should also work with \ldots, \textellipsis or other \dots variants as well as the symbol (in unicode, U+2026). – Philippe Goutet Mar 03 '12 at 16:05
  • @PhilippeGoutet: I haven't seen section and paragraph headings ending with comma (,) or semicolon (;). So I think we should leave them out. – Ahmed Musa Mar 03 '12 at 17:03
  • Well, for inline sections, you could imagine things like \paragraph{Start of the title,} end of the title. (I've done this myself a few times). Anyway, as a comma or a semi-colon should never be followed by a full stop, just as well add them, it's safer. You never know what can cross the mind of your end users. – Philippe Goutet Mar 03 '12 at 18:54