4

I would like a semantic, configurable way to input ranges of pages, poetic lines, and music measure numbers. Using a macro would enable me to make sure all such ranges are formatted consistently, and enable me to change the format as needed, e.g., from "measures" to "bars" or (in Spanish) "compases", or from "ll." to "lines" or "líneas."

(It seems like the authors of the various packages built on bibleverse have dealt with a similar challenge in different ways.)

Now I have the macros \loc and \loca defined in the MWE below. I have to use a different one for a plural abbreviation, since I don't know how to test the arguments to see if it is a range. And I would prefer to include the range type (bar, line, page) as a macro rather than inputting it literally.

The ideal might be something like this:

Commands:

\newcommand{\range}[2]{#1~#2} 
  % if #2 has more than one number, then do #1--#2
\newcommand{\measures}{mm.} 
   % except I would like the abbreviation to vary based on
   % whether the number is plural (m. vs. mm.)
\newcommand{\lines}{ll.}

Usage:

\range{\lines}{15} --> expands to l.~15 because there is only one number \range{\measures}{3 4} --> expands to mm.~3--4 because there are two numbers

This may be much more complex than I think, and if so please feel free to point me to the relevant reference source to figure this out for myself.

For what little it may be worth, I can figure out a basic algorithm for this in C, which I include also below.

\documentclass{article}

% Ranges of measures, lines (Latin locus, place; loca, region)
\newcommand{\loc}[2]{#1.~#2}    % e.g., m.~13
\newcommand{\loca}[2]{#1#1.~#2} % e.g., mm.~13--14

% Or I could do this
\newcommand{\measure}[1]{m.~#1}
\newcommand{\measures}[1]{mm.~#1}
\newcommand{\poemline}[1]{l.~#1}
\newcommand{\poemlines}[1]{ll.~#1}

\begin{document}

At \loca{l}{13--14} in the poem, the composer puts accents on the wrong syllables (\loca{m}{44--46}). 

At \poemlines{15--16} the composer puts accents on the right syllables (\measures{47--48}).

\end{document}
/* range.c
 * Usage: range <rangetag> <start> <end>
 * Examples: 
 *  range measures 13 14 --> mm.~13--14
 *  range measures 13    --> m.~13
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MEASURETAG 'm'
#define LINETAG 'l'
#define TAGPUNCT '.'

int main(int argc, char *argv[])
{
    char rangetag;
    char tagpunct = TAGPUNCT;

    if ((argc > 4) || (argc < 3)) {
        fprintf(stderr, "Incorrect number of arguments.\n");
        exit(EXIT_FAILURE);
    }

    if (strcmp(argv[1], "measures") == 0) 
        rangetag = MEASURETAG;
    else if (strcmp(argv[1], "lines") == 0)
        rangetag = LINETAG;
    else {
        fprintf(stderr, "Unknown range label.\n");
        exit(EXIT_FAILURE);
    }

    if (argc == 4)  /* If there are start and end range arguments */
        printf("%c%c%c~%s--%s\n",
            rangetag, rangetag, tagpunct, argv[2], argv[3]);
    else /* If only one range argument */
        printf("%c%c~%s\n", rangetag, tagpunct, argv[2]);

    return(0);
}
2
  • \range[lines]{2}[4] if the last optional argument is omitted (or empty) then no range is printed. Looking for something like that? It could be easily done with package xparse. – Johannes_B Nov 18 '14 at 16:01
  • @Johannes_B If there is only one range number, then print the label specified for single items (e.g., "l.~") and then the number. If there are two range numbers, then print the plural label and the numbers separated by an en dash (e.g., "ll.~1--2"). – musarithmia Nov 18 '14 at 16:04
6

It's quite easy with xparse:

\documentclass{article}
\usepackage{xparse}

\NewDocumentCommand{\range}{ m >{\SplitArgument{1}{ }}m }
 {%
  \dorange{#1}#2%
 }

\NewDocumentCommand{\dorange}{ m m m }
 {%
  \IfNoValueTF{#3}{#1~#2}{#1[]~#2--#3}%
 }

\NewDocumentCommand{\defineabbreviation}{ m m m }
 {%
  \NewDocumentCommand{#1}{o}{\IfNoValueTF{##1}{#2}{#3}}%
 }

\defineabbreviation{\lines}{l.}{ll.}
\defineabbreviation{\measures}{m.}{mm.}

\begin{document}

\range{\lines}{1}

\range{\lines}{2 3}

\range{\measures}{4}

\range{\measures}{5 6}

\end{document}

The \range command absorbs the first argument and splits the second one at a space, returning {<before space>}{<after space>} to be used as #2. If there's no space, <after space> will be “NoValue” (a special signal).

Then we pass control to \dorange that first checks if its third argument is “NoValue” (no range case) and calls

#1~#2

otherwise it calls

#1[]~#2

Here #1 should be a macro defined with \defineabbreviation; the input

\defineabbreviation{\lines}{l.}{ll.}

actually executes

\NewDocumentCommand{\lines}{o}{\IfNoValueTF{#1}{l.}{ll.}}

so when \dorange calls \lines~{1} the result will be

l. 1

and when it calls \lines[]~2--3 the result will be

ll. 2–3

(because an empty optional argument is not “NoValue”).

enter image description here

1
  • Thank you for explaining this so clearly. You have persuaded me to begin exploring xparse. – musarithmia Nov 18 '14 at 16:32

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.