1

I want to write a command for writing a poll. My Plan is:

\poll[<sumofvotes>]{<Poll title>}{
OptionA: <Votes for A>
OptionB: <Votes for B>*
OptionC: <Votes for C>
OptionD: <Votes for D>
}

Where the asterisk means that this option had won.

This command should create a table where one column is the part before the : and the other is the part after the colon. If a colon ends with an asterisk, this line will be highlighted (with a background color) and the text "Winning Option" should be appended to the line.

I also want to sum up all votes and print an error if the sum of the votes is unequal the sumofvotes parameter - if such a parameter is present.

however, I am running into two problems:

  1. How can I split text on newlines? (Tried \readlist* with \setsepchar{\n} but didnt work - how is a newline in latex referenced?)
  2. how can I generate the table? (Using \foreachitem in the body of tabular seems not to work)
  • You might split at newlines, but it's awkward and fragile. It's much easier using commas. – egreg Feb 3 at 23:42
  • hmm, my experience is, that people tend to forget commas, especially when fast-typing like in the situation you'd be using this class (writing a protocol live during a meeting) – Tobi Feb 3 at 23:46
  • 1
    TeX transforms endlines into spaces (or \par). Changing this makes the command very fragile. – egreg Feb 4 at 0:17
  • I would purposefully provide the input separated with a known separator such as \\ and use that as the \setsepchar. – Steven B. Segletes Feb 4 at 0:31
  • whatever separator I am using for 1., I cannot make the second part work (even if I use commas as the first separator) – Tobi Feb 4 at 0:59
2

It can be cleaned up. I just wanted to show the basic approach one might take.

One key here is that the \foreachitem loop variables of the list must be expanded as it is being stuffed into the tabular array. For this, I found it easier to use a token list.

\documentclass{article}
\usepackage{listofitems,xcolor}
\newcommand\winstyle{\bfseries\color{red}}
\newtoks\tabtoks
\newcommand\addtotabtoks[1]{\tabtoks\expandafter{\the\tabtoks#1}}
\newcommand\xaddtotabtoks[1]{\expandafter\addtotabtoks\expandafter{#1}}
\newcommand\xxaddtotabtoks[1]{\expandafter\xaddtotabtoks\expandafter{#1}}
\newcommand\poll[3][0]{%
  \setsepchar{\\/:/*}%
  \tabtoks{}%
  \readlist*\votes{#3}%
  \def\votetally{0}%
  \foreachitem\x\in\votes[]{%
    \ifnum\listlen\votes[\xcnt,2]>1\relax\addtotabtoks{\winstyle}\fi%
    \xxaddtotabtoks{\votes[\xcnt,1] & }%
    \ifnum\listlen\votes[\xcnt,2]>1\relax\addtotabtoks{\winstyle}\fi%
    \xxaddtotabtoks{\votes[\xcnt,2,1]&}%
    \ifnum\listlen\votes[\xcnt,2]>1\relax
      \addtotabtoks{\winstyle~Winning Option\\}\else
      \addtotabtoks{\\}\fi%
    \edef\votetally{\the\numexpr\votetally+\votes[\xcnt,2,1]\relax}
  }%
  \centerline{\textsc{#2} (\votetally{} votes)}%
  \centerline{\begin{tabular}{lrc}\the\tabtoks\end{tabular}}%
}
\begin{document}
\poll{Poll Title}{
Option A: 6\\
Option B: 11*\\
Option C: 3
}
\end{document}

enter image description here

You will see I did nothing with optional variable #1, but you could end the macro with and \ifnum test comparing #1 and \votetally, and burp an error message if they fail to agree.

2

Using endlines as delimiters is quite a problem with TeX, so I suggest the standard line terminator \\.

\documentclass{article}
\usepackage{xparse}
\usepackage[table,dvipsnames]{xcolor}
\usepackage{booktabs}

\colorlet{winning}{SkyBlue}

\ExplSyntaxOn

\NewDocumentCommand{\poll}{o m m}
 {
  \tobi_poll:nn { #2 } { #3 }
  \IfValueT{#1}
   {
    \tobi_poll_check:n { #1 }
   }
 }

\seq_new:N \l_tobi_poll_votes_seq
\int_new:N \l_tobi_poll_votes_total_int
\tl_new:N \l__tobi_poll_table_tl
\tl_new:N \l__tobi_poll_vote_tl
\seq_new:N \l__tobi_poll_row_seq

\msg_new:nnnn { poll } { not-matching }
 {
  Votes~don't~match~(#2,~expected~#1)
 }
 {
  The~total~number~of~votes~should~be~#1,~you~have~#2
 }

\cs_generate_variant:Nn \seq_set_split:Nnn { NV }

\cs_new_protected:Nn \tobi_poll:nn
 {
  \seq_set_split:Nnn \l_tobi_poll_votes_seq { \\ } { #2 }
  \tl_clear:N \l__tobi_poll_table_tl
  \int_zero:N \l_tobi_poll_votes_total_int
  \seq_map_function:NN \l_tobi_poll_votes_seq \__tobi_poll_row:n
  \__tobi_poll_maketable:n { #1 }
 }

\cs_new_protected:Nn \__tobi_poll_row:n
 {
  % the sequence will contain the option as item 1 and the votes as item 2
  \seq_set_split:NVn \l__tobi_poll_row_seq \c_colon_str { #1 }
  % store the votes, we need to look for *
  \tl_set:Nx \l__tobi_poll_vote_tl
   {
    \seq_item:Nn \l__tobi_poll_row_seq { 2 }
   }
  \str_if_eq:eeTF { * } { \str_item:Nn \l__tobi_poll_vote_tl { -1 } }
   {% remove the trailing asterisk
    \tl_set:Nx \l__tobi_poll_vote_tl
     {
      \tl_range:Nnn \l__tobi_poll_vote_tl { 1 } { -2 }
     }
    \tl_put_right:Nx \l__tobi_poll_table_tl
     {
      \exp_not:N \rowcolor{winning} % background color
      \seq_item:Nn \l__tobi_poll_row_seq { 1 } &
      \l__tobi_poll_vote_tl
      & (Winning ~ option) % with the winning option
      \exp_not:N \\
     }
   }
   {% no asterisk
    \tl_put_right:Nx \l__tobi_poll_table_tl
     {
      \seq_item:Nn \l__tobi_poll_row_seq { 1 } &
      \l__tobi_poll_vote_tl
      \exp_not:N \\
     }
   }
  % add up the votes
  \int_add:Nn \l_tobi_poll_votes_total_int { \l__tobi_poll_vote_tl }
 }

\cs_new_protected:Nn \tobi_poll_check:n
 {
  \int_compare:nF { #1 = \l_tobi_poll_votes_total_int }
   {
    \msg_error:nnxx { poll } { not-matching }
     { #1 }
     { \int_to_arabic:n { \l_tobi_poll_votes_total_int } }
   }
 }

\cs_new_protected:Nn \__tobi_poll_maketable:n
 {
  \begin{tabular}{lrl}
  \toprule
  \multicolumn{3}{c}
   {
    #1~(\int_to_arabic:n {\l_tobi_poll_votes_total_int}~votes)} \\
  \midrule
  \l__tobi_poll_table_tl
  \bottomrule
  \end{tabular}
 }

\ExplSyntaxOff

\begin{document}

\poll{Example poll}{
  A: 12  \\
  B: 15* \\
  C: 10  \\
  D: 8
}
\qquad
\poll[45]{Example poll}{
  A: 12  \\
  B: 15* \\
  C: 10  \\
  D: 8
}

\bigskip

\poll[120]{Example poll}{
  A: 12  \\
  B: 15* \\
  C: 10  \\
  D: 8
}

\end{document}

The last \poll tells the user

! Package poll Error: Votes don't match (45, expected 120)

For immediate help type H <return>.
 ...                                              

l.128 }

? h

The total number of votes should be 120, you have 45

? 

enter image description here

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.