4

I implemented a short macro that processes a comma separated argument list to format them.

So by calling

\macroa{ABC, DEF}

it will format the text to ABC DEF

Now what I would like to have is either a macro or command (better macro), by calling

\macrob{{ABC, DEF}, {GHI, JKL}, {MNO, PQR}}

that can list the given lists by calling the macro I defined, macroa, which the amount of lists to be passed into macrob is variadic but surely less than 10. The result is to have output ABC DEF; GHI JKL; MNO PQR.


Second question is more advance but not essential. If I would like to consider the second argument of each list to determine the use semi-colon or comma, would there be a simple implementation that modify macrob to achieve? Say, I would like an empty second argument to use comma joining the next list passed.

\macroc{{ABC, }, {GHI, }, {MNO, PQR}, {STU, VWX}}

ABC, GHI, MNO PQR; STU VWX


Finally I would like to know the technical term for such "list".

Thank you very much for your help!


Have the problem partially solved. Thanks @egreg !!!

\documentclass[12pt,a4paper,notitlepage,openright]{report}
\usepackage{xparse}

\makeatletter

\def\awarddegree#1{\gdef\@awarddegree{#1 }}
\def\@awarddegree{\@latex@warning@no@line{No \noexpand\awarddegree given}}

\def\pastdegree#1{\expandafter\pastdegree@i#1\@nil}
\def\pastdegree@i#1,#2\@nil{\gdef\@pastdegree{#1 \textit{#2}}}
\def\@pastdegree{\@latex@warning@no@line{No \noexpand\pastdegree given}}

\def\atry#1{\expandafter\atry@i#1\@nil}
\def\atry@i#1,#2\@nil{\gdef\@atry{\pastdegree{#1}\@pastdegree; \pastdegree{#2}\@pastdegree}}
\def\@atry{\@latex@warning@no@line{No \noexpand\atry given}}

\makeatother


\atry{{B.A.,H.K.},{M.Phil.,Oxon.}}

\begin{document}

\makeatletter

\@atry

\makeatother

\end{document}

I see that the

  1. There should be no spaces in between elements in the list or nested list, then the elements can be passed successfully. (Through \tracingmarcros=1, discover such debugging method in another post)
  2. For the \def\atry@i#1,#2\@nil line, previously, there were only \gdef\@atry{\pastdegree{#1} \pastdegree{#2}} and resulted in no input. So observing the use of \@macro and \macro, I sees that \macro is probably "definition" in C's sense, and \@macro is the calling/use of the "definition", so I changed the line to the above solution.

Thanks so much again @egreg . I learnt a lot tonight!


Final Solution

\makeatletter

\def\pastdegree#1{\expandafter\pastdegree@i#1\@nil}
\def\pastdegree@i#1,#2\@nil{\gdef\@pastdegree{#1\IfStrEq{#2}{}{}{ }\textit{#2}}}
\def\@pastdegree{\@latex@warning@no@line{No \noexpand\pastdegree given}}

\newcommand{\ifsameinstitute}[1]{\IfEndWith{#1}{,}{,}{;}}

% WRITING A MACRO WITH COMMA EXPANSION LIST
%
% http://tex.stackexchange.com/questions/15716/how-do-i-write-a-macro-having-comma-separated-and-variable-number-of-arguments
\def\degreelist#1{\expandafter\degreelist@i#1,,,,,,,,\@nil}
\def\degreelist@i#1,#2,#3,#4,#5,#6,#7,#8,#9\@nil{%
    \ifx$#2$ \gdef\@degreelist{\pastdegree{#1}\@pastdegree}
    \else

    \ifx$#3$ \gdef\@degreelist{\pastdegree{#1}\@pastdegree\ifsameinstitute{#1} %
                               \pastdegree{#2}\@pastdegree}
    \else

    \ifx$#4$ \gdef\@degreelist{\pastdegree{#1}\@pastdegree\ifsameinstitute{#1} %
                               \pastdegree{#2}\@pastdegree\ifsameinstitute{#2} %
                               \pastdegree{#3}\@pastdegree}
    \else

    \ifx$#5$ \gdef\@degreelist{\pastdegree{#1}\@pastdegree\ifsameinstitute{#1} %
                               \pastdegree{#2}\@pastdegree\ifsameinstitute{#2} %
                               \pastdegree{#3}\@pastdegree\ifsameinstitute{#3} %
                               \pastdegree{#4}\@pastdegree}
    \else

    \ifx$#6$ \gdef\@degreelist{\pastdegree{#1}\@pastdegree\ifsameinstitute{#1} %
                               \pastdegree{#2}\@pastdegree\ifsameinstitute{#2} %
                               \pastdegree{#3}\@pastdegree\ifsameinstitute{#3} %
                               \pastdegree{#4}\@pastdegree\ifsameinstitute{#4} %
                               \pastdegree{#5}\@pastdegree}
    \else

    \ifx$#7$ \gdef\@degreelist{\pastdegree{#1}\@pastdegree\ifsameinstitute{#1} %
                               \pastdegree{#2}\@pastdegree\ifsameinstitute{#2} %
                               \pastdegree{#3}\@pastdegree\ifsameinstitute{#3} %
                               \pastdegree{#4}\@pastdegree\ifsameinstitute{#4} %
                               \pastdegree{#5}\@pastdegree\ifsameinstitute{#5} %
                               \pastdegree{#6}\@pastdegree}
    \else

    \ifx$#8$ \gdef\@degreelist{\pastdegree{#1}\@pastdegree\ifsameinstitute{#1} %
                               \pastdegree{#2}\@pastdegree\ifsameinstitute{#2} %
                               \pastdegree{#3}\@pastdegree\ifsameinstitute{#3} %
                               \pastdegree{#4}\@pastdegree\ifsameinstitute{#4} %
                               \pastdegree{#5}\@pastdegree\ifsameinstitute{#5} %
                               \pastdegree{#6}\@pastdegree\ifsameinstitute{#6} %
                               \pastdegree{#7}\@pastdegree}
    \else  
             \gdef\@degreelist{\pastdegree{#1}\@pastdegree\ifsameinstitute{#1} %
                               \pastdegree{#2}\@pastdegree\ifsameinstitute{#2} %
                               \pastdegree{#3}\@pastdegree\ifsameinstitute{#3} %
                               \pastdegree{#4}\@pastdegree\ifsameinstitute{#4} %
                               \pastdegree{#5}\@pastdegree\ifsameinstitute{#5} %
                               \pastdegree{#6}\@pastdegree\ifsameinstitute{#6} %
                               \pastdegree{#7}\@pastdegree\ifsameinstitute{#7} %
                               \pastdegree{#8}\@pastdegree}
    \fi\fi\fi\fi\fi\fi\fi
}
\def\@degreelist{\@latex@warning@no@line{No \noexpand\degreelist given}}

\makeatother

\degreelist{{B.A.,},{M.Phil.,H.K.},{Ph.D.,Cantab.}}

\begin{document}

{\@degreelist}

\end{document}

Printout of Post-Nominals

enter image description here

  • I believe that it is better not to use \makeatletter after \begin{document}. I think that your point (2) above reflects a confusion but I don't know C so it is hard to be sure. I don't understand what you mean by a macro as opposed to a command. (You say you'd prefer a macro to a command but I'm not familiar with the distinction this refers to.) – cfr Nov 20 '14 at 23:26
  • 1
    @cfr I agree. In C you can put a line at the top of the file like #define MAX 100 and when you compile, the preprocessor will substitute 100 for wherever you wrote MAX; this makes it easier to adjust. In TeX I don't think there is a distinction between macros and commands, since the way to define a macro in LaTeX is \newcommand. As I understand it, everything in TeX is a command---the letter "a" in a TeX file is actually a command, "typeset the letter a". Everything that isn't a primitive command is a macro that expands to primitives. – musarithmia Nov 21 '14 at 2:55
  • @AndrewCashner Yes, that's more-or-less my understanding, too. Everything (typeset) is a box. And everything (in the universe) is a command. ;) – cfr Nov 21 '14 at 3:34
  • Thank you for your comments. As I said, I am a newbie in LaTeX. Feels quite embarrassing when I coded for so many years and has to ask for elementary questions on how to code. I got what I need now. Will upload the codes in an edit of the above. I didn't use xparse but just primitive commands to write the solution. For the "\macro" vs. "\@macro", I feel that "\macro{}" is like a definition, (allocation of space and define contents within) and "\@macro" is the real access of what is defined with the variable. Based on "\author{}" prints nothing out but "\@author" does. That's what I meant. – Dominic Hung Nov 21 '14 at 5:39
4

The implementation is not the best, but just to see it can be done:

\documentclass{article}
\usepackage{xparse}

\NewDocumentCommand{\macroa}{ >{\SplitArgument{1}{,}}m }
 {%
  \processtwo#1%
 }

\NewDocumentCommand{\processtwo}{ m m }
 {%
  \IfNoValueTF{#2}
   {%
    \semicolonfalse
    #1, %
   }
   {%
    \semicolontrue
    #1 \textbf{\textit{#2}}%
   }%
 }

\newcounter{liststep}
\newif\ifsemicolon

\NewDocumentCommand{\macrob}{ >{\SplitList{,}}m }
 {%
  \setcounter{liststep}{0}%
  \semicolonfalse
  \ProcessList{#1}{\macroamod}%
 }

\NewDocumentCommand\macroamod{m}
 {%
  \ifnum\value{liststep}>0 \ifsemicolon; \else\semicolontrue\fi\fi
  \macroa{#1}%
  \stepcounter{liststep}%
 }

\begin{document}

\macroa{ABC, DEF}

\macrob{{ABC, DEF}, {GHI, JKL}, {MNO, PQR}}

\macrob{{ABC}, {GHI}, {MNO, PQR}, {STU, VWX}}

\end{document}

Possibly, knowing what's the purpose,something less clumsy can be devised, also regarding the interface.

enter image description here

A better implementation, with a neater syntax.

  1. A list of strings separated by spaces is considered titles from a single institution, which is the last specified if there are more than one item

  2. Such lists can be concatenated by separating them with a semicolon.

Here's the code.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\degreelist}{m}
 {
  \dominic_degree_list:n { #1 }
 }

\seq_new:N \l_dominic_degrees_input_seq
\seq_new:N \l_dominic_degrees_output_seq
\seq_new:N \l_dominic_inst_seq
\tl_new:N \l_dominic_inst_tl

\cs_new_protected:Npn \dominic_degree_list:n #1
 {
  \seq_set_split:Nnn \l_dominic_degrees_input_seq { ; } { #1 }
  \seq_set_map:NNn \l_dominic_degrees_output_seq \l_dominic_degrees_input_seq
   {
    \dominic_inst:n { ##1 }
   }
  \seq_use:Nn \l_dominic_degrees_output_seq { ;~ }
 }

\cs_new_protected:Npn \dominic_inst:n #1
 {
  \seq_set_split:Nnn \l_dominic_inst_seq { ~ } { #1 }
  \int_compare:nTF { \seq_count:N \l_dominic_inst_seq == 1 }
   {
    #1
   }
   {
    \seq_pop_right:NN \l_dominic_inst_seq \l_dominic_inst_tl
    \seq_use:Nn \l_dominic_inst_seq { ,~ }
    \c_space_tl
    \textbf{ \textit{ \l_dominic_inst_tl } }
   }
 }
\ExplSyntaxOff

\begin{document}

\degreelist{ABC DEF}

\degreelist{ABC DEF; GHI JKL; MNO PQR}

\degreelist{ABC; GHI MNO PQR; STU VWX}

\end{document}

enter image description here

  • The purpose is to build a thesis template, for a field that let colleagues to fill in this list of lists to generate a single string that list their degree post-nominals complying to thesis regulation. Say, B.A., M.A. H.K., M.Litt. Oxon . So what I hope is a macro that accepts \degreelist{{B.A., }, {M.A., H.K.}, {M.Litt., Oxon.}} that can call the macro \pastdegree that I defined to have the printout done. – Dominic Hung Nov 20 '14 at 18:08
  • This is the macro I defined \def\pastdegree#1{\expandafter\pastdegree@i#1\@nil} \def\pastdegree@i#1,#2\@nil{\gdef\@pastdegree{#1\textit{#2}}} \def\@pastdegree{\@latex@warning@no@line{No \noexpand\pastdegree given}} Do you think I can reuse this macro with your code? So ashame asking for such beginner question, but I am rather new to latex. – Dominic Hung Nov 20 '14 at 18:14
  • @DominicHung The method I suggested in the revised version has a much simpler syntax for inputting the data. – egreg Nov 20 '14 at 18:39
  • I am trying to learn from your code mix and match and see of the behaviour. The xparse is surely powerful. But I want to see, if only purely macro can be use. (Just to understand how "data structures" and "function call" works in LaTeX). I tried this... but got error "You can't use `\spacefactor' in vertical mode. \@->\spacefactor \@m l.21 \@ atry{{B.A., H.K.}, {M.Phil., Oxon.}} " \def\atry#1{\expandafter\atry@i#1\@nil} \def\atry@i#1,#2\@nil{\gdef\@atry{\@pastdegree{#1} \@pastdegree{#2}}} \def\@atry{\@latex@warning@no@line{No \noexpand\atry given}} \@atry{{B.A., H.K.}, {M.Phil., Oxon.}} – Dominic Hung Nov 20 '14 at 19:02
  • I have troubleshooted that by including \makeatletter \makeatother pairs and the code compiled with no problem at all. ... \atry{{B.A., H.K.}, {M.Phil., Oxon.}} \begin{document} \makeatletter \@atry \makeatother But the problem now is that it doesn't work the styling of the tuple but just showing plain text without any italic. So I take the whole {B.A., H.K.} is passed as the first argument for \pastdegree ? I tried to mix the macro \pastdegree defined by me as shown with your xparse's \NewDocumentCommand\macrob and the behaviour is the same. So how I can unroll a list with just macro? – Dominic Hung Nov 20 '14 at 19:36
3

Although in older posts of mine, I was reluctant to recommend the use of LuaLaTeX, the code has now matured enough and the LuaTeX Team is doing such a great job, that I would heartily recommend that any "thesis class" seriously considers using LuaLaTeX. Thesis templates are about enforcing styles and parsing is one of the stengths of Lua. As you wish to format honorifics (and the style differs from Style Manual to Style Manual) I would recommend you have a second look at the UI of such code.

It would be simpler to ask the user to type:

\honorific {BSc (Lond.) Ph.D. MSc. M.Phil Oxon., OBE}

than have all sorts of rules for the input.

With a little bit of Lua code you can parse the string, split the string array into a table and then look-up values in a hash table.

\documentclass{article}
\usepackage{luacode,filecontents}
\begin{filecontents*}{honorific.lua}
-- module honorific
-- @return Univ. of Anywhere formatted honorific suffix

local M = {}
local t = {}

t['phd']   = {'PhD', '\\textup{PhD}'}
t['ma']    = {'MA', '\\textup{MA}'}
t['mphil'] = {MPhil, '\\textbf{MPhil}'}
t['msc']   = {MSc, 'MSc'}
t['obe']   = {OBE, '\\textit{Stiff Upper Lip}'}

function split(str, pat)
   local t = {}  
   local fpat = "(.-)" .. pat
   local last_end = 1
   local s, e, cap = str:find(fpat, 1)
   while s do
      if s ~= 1 or cap ~= "" then
     table.insert(t,cap)
      end
      last_end = e+1
      s, e, cap = str:find(fpat, last_end)
   end
   if last_end <= #str then
      cap = str:sub(last_end)
      table.insert(t, cap)
   end
   return t
end

function split_honorific(str)
   return split(str, '[%s]')
end

M.honorific = function (text)
-- split string
text = split_honorific(text)

for k,v in ipairs (text) do
  local tmp = string.lower(v)
         tmp = string.gsub(tmp,'%.','')

  if t[tmp]  then 
     tex.print(t[tmp][2]) 
  else
      tex.print(v)
  end
end

end  

return M
\end{filecontents*}
\makeatletter

 \def\honorific#1{%
    \luadirect{
    m = require'honorific'
    m.honorific('#1')
   }%
 }
\makeatother

\begin{document}
\honorific {BSc (Lond.) Ph.D. 
               MSc. M.Phil Oxon., OBE}
\end{document}

This type of problem appears simple at first, but if one had to extend it to cover for example formalities as shown in the Oxford Calendar Lua would have been the only option. What I like about the Lua solution is one can simplify the author interface. One could extend the code above and not even have to ask the user to use any macro. One simply could parse the input file, before passing it to TeX and just insert macros and replacements of strings as required. Note the style is just fictitious for demonstration purposes.

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.