9

Having to treat a considerable amount of page ranges, I'd like to have a robust and simple command that would parse and print them correctly.

  1. The most simple case is e.g.

    264,15-26

    which means:

    page 264, lines 15 to 26

  2. Then we have things like

    264,15-266,26

    which translates to

    page 264 line 15 to page 266 line 26

  3. More tricky, we'd have something like

    264,15-266,26+277,13-14+312,14-316,23

    which means we actually have three sequences here:

    from page 264 line 15 to page 266 line 26 and page 277 lines 13-14 and from page 312 line 14 to page 316 line 23

    That's to say, the command should work whatever the amount of "type 1" or "type 2" sequences is provided to it.

I'd like to put whatever the page range is (type 1 or 2 or 3) in a single command and have every one of them printed in the readable and accurate way... Pretty tricky for poor old humanities student like me!

3 Answers 3

6

Here is a LuaLaTeX solution:

Minimal Working Example

\documentclass{article}
\directlua{dofile("parse.lua")}
\def\parserange#1{\directlua{tex.sprint(parsePageRanges("#1"))}}
\begin{document}

\begin{itemize}
\item \parserange{264,15-26}
\item \parserange{264,15-266,26}
\item \parserange{264,15-266,26+277,13-14+312,14-316,23}
\end{itemize}
\end{document}

Result

Result

Lua code

To be saved in the file parse.lua

function split(text, sep)
    sep = sep or "\n"
    local lines = {}
    local pos = 1
    while true do
        local b,e = text:find(sep, pos)
        if not b then table.insert(lines, text:sub(pos)) break end
        table.insert(lines, text:sub(pos, b-1))
        pos = e + 1
    end
    return lines
end

function parsePageRanges(s)
    ranges=split(s, "+")
    parsed_ranges = {}
    for i,range in ipairs(ranges) do
         ends = split(range, "-")
         if #ends ~=2 then
             return("Error in page range ``"..range.."''")
         end
         r={}
         r[1] = split(ends[1], ",")
         r[2] = split(ends[2], ",")
         if #r[2]==1 then
             p1=r[1][1]; l1=r[1][2]; l2=r[2][1]
             table.insert(parsed_ranges, "page "..p1..", lines "..l1.." to "..l2)
         else
             p1=r[1][1]; l1=r[1][2]; p2=r[2][1]; l2=r[2][2]
             table.insert(parsed_ranges, "from page "..p1.." line "..l1.." to page "..p2.." line "..l2)
         end
    end
    return table.concat(parsed_ranges, " and ")
end
5

Here's a LaTeX3 implementation.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

\NewDocumentCommand{\parsepages}{m}
 {
  \pierre_parse_pages:n { #1 }
 }

\seq_new:N \l_pierre_blocks_seq
\seq_new:N \l_pierre_output_seq
\seq_new:N \l_pierre_temp_seq

\cs_new_protected:Npn \pierre_parse_pages:n #1
 {
  % decompose the argument into blocks delimited by +
  \seq_set_split:Nnn \l_pierre_blocks_seq { + } { #1 }
  \seq_clear:N \l_pierre_output_seq
  % process each block
  \seq_map_inline:Nn \l_pierre_blocks_seq
   {
    \pierre_parse_block:n { ##1 }
   }
  % print the result
  \seq_use:Nnnn \l_pierre_output_seq {~and~} {~and~} {~and~}
 }

\cs_new_protected:Npn \pierre_parse_block:n #1
 {
  \tl_if_in:nnTF { #1 } { - }
   {% we have a "complex block"; either a,b-c,d or a,b-c
    \pierre_parse_complex_block:n { #1 }
   }
   {% a "simple block" a,b
    \pierre_parse_simple_block:w #1 \q_stop
   }
 }

\cs_new_protected:Npn \pierre_parse_simple_block:w #1 , #2 \q_stop
 {
  \seq_put_right:Nn \l_pierre_output_seq
   {
    page~#1~line~#2
   }
 }

\cs_new_protected:Npn \pierre_parse_complex_block:n #1
 {
  \seq_set_split:Nnn \l_pierre_temp_seq { , } { #1 }
  \int_compare:nTF { \seq_count:N \l_pierre_temp_seq = 2 }
   {% only one comma: a,b-c
    \pierre_single_page:w #1 \q_stop
   }
   {% two commas: a,b-c,d
    \pierre_multi_page:w #1 \q_stop
   }
 }

\cs_new_protected:Npn \pierre_single_page:w #1 , #2 - #3 \q_stop
 {
  \seq_put_right:Nn \l_pierre_output_seq
   {
    page~#1~lines~#2~to~#3
   }
 }

\cs_new_protected:Npn \pierre_multi_page:w #1 , #2 - #3 , #4 \q_stop
 {
  \seq_put_right:Nn \l_pierre_output_seq
   {
    from~page~#1~line~#2~to~page~#3~line~#4
   }
 }
\ExplSyntaxOff

\begin{document}

% just for formatting the example
\setlength{\parindent}{0pt}
\setlength{\parskip}{1ex}

\parsepages{264,15}

\parsepages{264,15-26}

\parsepages{264,15-266,26}

\parsepages{264,15-266,26+277,13-14+312,14-316,23}

\end{document}

enter image description here

0

A pdflatex solution with the listofitems package.

REVISED ANSWER

I fear my original answer (below) misunderstood the context of when a "from" should be inserted. In this revision, "from" is applied when the range of a sequence spans across a page boundary.

\documentclass{article}
\usepackage{listofitems}
\newcommand\parsepages[1]{\bgroup%
  \def\+{ and }%
  \def\-{ to }%
  \def\,{, line\checkplural{} }%
  \setsepchar{+/-/,}%
  \readlist*\mypages{#1}%
  \foreachitem\i\in\mypages[]{%
    \foreachitem\j\in\mypages[\icnt]{%
      \foreachitem\k\in\mypages[\icnt,\jcnt]{%
        \ifnum\kcnt<\listlen\mypages[\icnt,\jcnt]\relax\checkpages page \fi%
        \k \ifnum\kcnt<\listlen\mypages[\icnt,\jcnt]\relax
             \csname\mypagessep[\icnt,\jcnt,\kcnt]\endcsname \fi%
      }\ifnum\jcnt<\listlen\mypages[\icnt]\relax%
         \csname\mypagessep[\icnt,\jcnt]\endcsname\fi%
    }\ifnum\icnt<\listlen\mypages[]\relax\csname\mypagessep[\icnt]\endcsname\fi%
  }%
\egroup}
\newcommand\checkplural{\ifnum\numexpr\jcnt+1=\listlen\mypages[\icnt]\relax%
  \ifnum\kcnt=\listlen\mypages[\icnt,\numexpr\jcnt+1]\relax s\fi\fi}
\newcommand\checkpages{\ifnum\jcnt=\listlen\mypages[\icnt]\relax\else%
  \ifnum\listlen\mypages[\icnt,\the\numexpr\jcnt+1\relax]>1\relax from \fi\fi}
\begin{document}
\begin{enumerate}
\item \verb|\parsepages{264,15-26}|\par
\parsepages{264,15-26}
\item \verb|\parsepages{264,15-266,26}|\par
\parsepages{264,15-266,26}
\item\verb|\parsepages{264,15-266,26+277,13-14+312,14-316,23}|\par
\parsepages{264,15-266,26+277,13-14+312,14-316,23}
\end{enumerate}
\end{document}

enter image description here

ORIGINAL ANSWER

In this answer, "from" is inserted when the answer involves multiple sequences. Then, "from" is applied to the 1st and last sequence.

\documentclass{article}
\usepackage{listofitems}
\newcommand\parsepages[1]{%
  \def\+{ and \checkfrom}%
  \def\-{ to }%
  \def\,{, line\checkess{} }
  \setsepchar{+/-/,}
  \readlist*\mypages{#1}%
  \foreachitem\i\in\mypages[]{%
    \ifnum\listlen\mypages[]>1\relax%
      \ifnum\icnt=1\relax from \fi\fi%
    \foreachitem\j\in\mypages[\icnt]{%
      \foreachitem\k\in\mypages[\icnt,\jcnt]{%
        \ifnum\kcnt<\listlen\mypages[\icnt,\jcnt]\relax%
           page %
        \fi%
        \k \ifnum\kcnt<\listlen\mypages[\icnt,\jcnt]\relax
           \csname\mypagessep[\icnt,\jcnt,\kcnt]\endcsname \fi%
      }\ifnum\jcnt<\listlen\mypages[\icnt]\relax%
         \csname\mypagessep[\icnt,\jcnt]\endcsname\fi%
    }\ifnum\icnt<\listlen\mypages[]\relax%
       \csname\mypagessep[\icnt]\endcsname\fi%
  }%
}
\newcommand\checkfrom{\ifnum\numexpr\icnt+1=\listlen\mypages[]\relax from \fi}
\newcommand\checkess{\ifnum\numexpr\jcnt+1=\listlen\mypages[\icnt]\relax%
  \ifnum\kcnt=\listlen\mypages[\icnt,\numexpr\jcnt+1]\relax s\fi\fi}
\begin{document}
\begin{enumerate}
\item \verb|\parsepages{264,15-26}|\par
\parsepages{264,15-26}
\item \verb|\parsepages{264,15-266,26}|\par
\parsepages{264,15-266,26}
\item\verb|\parsepages{264,15-266,26+277,13-14+312,14-316,23}|\par
\parsepages{264,15-266,26+277,13-14+312,14-316,23}
\end{enumerate}
\end{document}

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.