Support "r" in page ranges (fixes #155)

This commit is contained in:
Jay Berkenbilt 2018-03-04 06:59:12 -05:00
parent e577dfc87e
commit 666f794393
4 changed files with 75 additions and 15 deletions

View File

@ -1,3 +1,10 @@
2018-03-04 Jay Berkenbilt <ejb@ql.org>
* On the command line when specifying page ranges, support
preceding a page number by "r" to indicate that it should be
counted from the end. For example, the range r3-r1 would indicate
the last three pages of a document.
2018-03-03 Jay Berkenbilt <ejb@ql.org>
* Ignore zlib data check errors while uncompressing streams. This

View File

@ -818,13 +818,15 @@ make
</para>
<para>
The page range is a set of numbers separated by commas, ranges of
numbers separated dashes, or combinations of those. The character
&ldquo;z&rdquo; represents the last page. Pages can appear in any
order. Ranges can appear with a high number followed by a low
number, which causes the pages to appear in reverse. Repeating a
number will cause an error, but you can use the workaround
discussed above should you really want to include the same page
twice.
numbers separated dashes, or combinations of those. The character
&ldquo;z&rdquo; represents the last page. A number preceded by an
&ldquo;r&rdquo; indicates to count from the end, so
&ldquo;r3-r1&rdquo; would be the last three pages of the document.
Pages can appear in any order. Ranges can appear with a high
number followed by a low number, which causes the pages to appear
in reverse. Repeating a number will cause an error, but you can
use the workaround discussed above should you really want to
include the same page twice.
</para>
<para>
Example page ranges:
@ -840,6 +842,17 @@ make
<literal>z-1</literal>: all pages in the document in reverse
</para>
</listitem>
<listitem>
<para>
<literal>r3-r1</literal>: the last three pages of the document
</para>
</listitem>
<listitem>
<para>
<literal>r1-r3</literal>: the last three pages of the document
in reverse order
</para>
</listitem>
</itemizedlist>
</para>
<para>

View File

@ -358,11 +358,12 @@ input.\n\
\n\
The page range is a set of numbers separated by commas, ranges of\n\
numbers separated dashes, or combinations of those. The character\n\
\"z\" represents the last page. Pages can appear in any order. Ranges\n\
can appear with a high number followed by a low number, which causes the\n\
pages to appear in reverse. Repeating a number will cause an error, but\n\
the manual discusses a workaround should you really want to include the\n\
same page twice.\n\
\"z\" represents the last page. A number preceded by an \"r\" indicates\n\
to count from the end, so \"r3-r1\" would be the last three pages of the\n\
document. Pages can appear in any order. Ranges can appear with a\n\
high number followed by a low number, which causes the pages to appear in\n\
reverse. Repeating a number will cause an error, but the manual discusses\n\
a workaround should you really want to include the same page twice.\n\
\n\
If the page range is omitted, the range of 1-z is assumed. qpdf decides\n\
that the page range is omitted if the range argument is either -- or a\n\
@ -577,6 +578,22 @@ static void show_encryption(QPDF& pdf, Options& o)
}
}
static int maybe_from_end(int num, bool from_end, int max)
{
if (from_end)
{
if (num > max)
{
num = 0;
}
else
{
num = max + 1 - num;
}
}
return num;
}
static std::vector<int> parse_numrange(char const* range, int max,
bool throw_error = false)
{
@ -593,6 +610,7 @@ static std::vector<int> parse_numrange(char const* range, int max,
st_after_number } state = st_top;
bool last_separator_was_dash = false;
int cur_number = 0;
bool from_end = false;
while (*p)
{
char ch = *p;
@ -616,14 +634,25 @@ static std::vector<int> parse_numrange(char const* range, int max,
state = st_after_number;
cur_number = max;
}
else if (ch == 'r')
{
if (! (state == st_top))
{
throw std::runtime_error("r not expected");
}
state = st_in_number;
from_end = true;
}
else if ((ch == ',') || (ch == '-'))
{
if (! ((state == st_in_number) || (state == st_after_number)))
{
throw std::runtime_error("unexpected separator");
}
cur_number = maybe_from_end(cur_number, from_end, max);
work.push_back(cur_number);
cur_number = 0;
from_end = false;
if (ch == ',')
{
state = st_top;
@ -649,6 +678,7 @@ static std::vector<int> parse_numrange(char const* range, int max,
}
if ((state == st_in_number) || (state == st_after_number))
{
cur_number = maybe_from_end(cur_number, from_end, max);
work.push_back(cur_number);
}
else

View File

@ -1092,9 +1092,19 @@ my @nrange_tests = (
["1-10,1234,5",
"qpdf: error in numeric range 1-10,1234,5: number 1234 out of range",
2],
["1,3,5-10,z-13,13,9,z,2",
"numeric range 1,3,5-10,z-13,13,9,z,2" .
" -> 1 3 5 6 7 8 9 10 15 14 13 13 9 15 2",
["1,r,3",
"qpdf: error in numeric range 1,r,3: number 16 out of range",
2],
["1,r16,3",
"qpdf: error in numeric range 1,r16,3: number 0 out of range",
2],
["1,3,5-10,z-13,13,9,z,2,r2-r4",
"numeric range 1,3,5-10,z-13,13,9,z,2,r2-r4" .
" -> 1 3 5 6 7 8 9 10 15 14 13 13 9 15 2 14 13 12",
0],
["r1-r15", # r\d+ at end
"numeric range r1-r15" .
" -> 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1",
0],
);
$n_tests += scalar(@nrange_tests);