From c4478e5249f935abe852b11275ffe48c29d8f997 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Sat, 9 Nov 2019 11:54:16 -0500 Subject: [PATCH] Allow odd/even modifiers in numeric range (fixes #364) --- libqpdf/QUtil.cc | 35 +++++++++++++++++++++++++++++++++++ libtests/qtest/numrange.test | 18 ++++++++++++++++++ manual/qpdf-manual.xml | 28 +++++++++++++++++++++++++--- qpdf/qpdf.cc | 5 +++-- 4 files changed, 81 insertions(+), 5 deletions(-) diff --git a/libqpdf/QUtil.cc b/libqpdf/QUtil.cc index db52bdb3..5fda01c9 100644 --- a/libqpdf/QUtil.cc +++ b/libqpdf/QUtil.cc @@ -1116,6 +1116,8 @@ QUtil::parse_numrange(char const* range, int max) std::vector work; static int const comma = -1; static int const dash = -2; + size_t start_idx = 0; + size_t skip = 1; enum { st_top, st_in_number, @@ -1182,6 +1184,14 @@ QUtil::parse_numrange(char const* range, int max) work.push_back(dash); } } + else if (ch == ':') + { + if (! ((state == st_in_number) || (state == st_after_number))) + { + throw std::runtime_error("unexpected colon"); + } + break; + } else { throw std::runtime_error("unexpected character"); @@ -1197,6 +1207,22 @@ QUtil::parse_numrange(char const* range, int max) { throw std::runtime_error("number expected"); } + if (*p == ':') + { + if (strcmp(p, ":odd") == 0) + { + skip = 2; + } + else if (strcmp(p, ":even") == 0) + { + skip = 2; + start_idx = 1; + } + else + { + throw std::runtime_error("unexpected even/odd modifier"); + } + } p = 0; for (size_t i = 0; i < work.size(); i += 2) @@ -1245,6 +1271,15 @@ QUtil::parse_numrange(char const* range, int max) } } } + if ((start_idx > 0) || (skip != 1)) + { + auto t = result; + result.clear(); + for (size_t i = start_idx; i < t.size(); i += skip) + { + result.push_back(t.at(i)); + } + } } catch (std::runtime_error const& e) { diff --git a/libtests/qtest/numrange.test b/libtests/qtest/numrange.test index 9acf6ea4..896c44d2 100644 --- a/libtests/qtest/numrange.test +++ b/libtests/qtest/numrange.test @@ -49,6 +49,24 @@ my @nrange_tests = ( "numeric range r1-r15" . " -> 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1", 0], + ["1-10:quack", + "error at * in numeric range 1-10*:quack: unexpected even/odd modifier", + 2], + ["1-10:", + "error at * in numeric range 1-10*:: unexpected even/odd modifier", + 2], + ["1-10,r:", + "error at * in numeric range 1-10,r*:: unexpected even/odd modifier", + 2], + ["1-10,:", + "error at * in numeric range 1-10,*:: unexpected colon", + 2], + ["1-6,8-12:odd", + "numeric range 1-6,8-12:odd -> 1 3 5 8 10 12", + 0], + ["1-6,8-12:even", + "numeric range 1-6,8-12:even -> 2 4 6 9 11", + 0], ); foreach my $d (@nrange_tests) { diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml index 58d21435..c332bedc 100644 --- a/manual/qpdf-manual.xml +++ b/manual/qpdf-manual.xml @@ -1391,9 +1391,12 @@ make r3-r1 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. + appear in reverse. Numbers may be repeated in a page range. A page + range may be optionally appended with :even or + :odd to indicate only the even or odd pages in + the given range. Note that even and odd refer to the positions + within the specified, range, not whether the original number is + even or odd. Example page ranges: @@ -1420,6 +1423,18 @@ make in reverse order + + + 1-20:even: even pages from 2 to 20 + + + + + 5,7-9,12:odd: pages 5, 8, and, 12, which are + the pages in odd positions from among the original range, which + represents pages 5, 7, 8, 9, and 12. + + @@ -4663,6 +4678,13 @@ print "\n"; . + + + Allow :even or :odd to + be appended to numeric ranges for specification of the even + or odd pages from among the pages specified in the range. + + diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc index 2bd98ccb..0e634412 100644 --- a/qpdf/qpdf.cc +++ b/qpdf/qpdf.cc @@ -1286,8 +1286,9 @@ ArgParser::argHelp() << "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" + << "reverse. Numbers may be repeated. A page range may be appended with :odd\n" + << "to indicate odd pages in the selected range or :even to indicate even\n" + << "pages.\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"