Commit c4478e5249f935abe852b11275ffe48c29d8f997

Authored by Jay Berkenbilt
1 parent c9cc8362

Allow odd/even modifiers in numeric range (fixes #364)

libqpdf/QUtil.cc
... ... @@ -1116,6 +1116,8 @@ QUtil::parse_numrange(char const* range, int max)
1116 1116 std::vector<int> work;
1117 1117 static int const comma = -1;
1118 1118 static int const dash = -2;
  1119 + size_t start_idx = 0;
  1120 + size_t skip = 1;
1119 1121  
1120 1122 enum { st_top,
1121 1123 st_in_number,
... ... @@ -1182,6 +1184,14 @@ QUtil::parse_numrange(char const* range, int max)
1182 1184 work.push_back(dash);
1183 1185 }
1184 1186 }
  1187 + else if (ch == ':')
  1188 + {
  1189 + if (! ((state == st_in_number) || (state == st_after_number)))
  1190 + {
  1191 + throw std::runtime_error("unexpected colon");
  1192 + }
  1193 + break;
  1194 + }
1185 1195 else
1186 1196 {
1187 1197 throw std::runtime_error("unexpected character");
... ... @@ -1197,6 +1207,22 @@ QUtil::parse_numrange(char const* range, int max)
1197 1207 {
1198 1208 throw std::runtime_error("number expected");
1199 1209 }
  1210 + if (*p == ':')
  1211 + {
  1212 + if (strcmp(p, ":odd") == 0)
  1213 + {
  1214 + skip = 2;
  1215 + }
  1216 + else if (strcmp(p, ":even") == 0)
  1217 + {
  1218 + skip = 2;
  1219 + start_idx = 1;
  1220 + }
  1221 + else
  1222 + {
  1223 + throw std::runtime_error("unexpected even/odd modifier");
  1224 + }
  1225 + }
1200 1226  
1201 1227 p = 0;
1202 1228 for (size_t i = 0; i < work.size(); i += 2)
... ... @@ -1245,6 +1271,15 @@ QUtil::parse_numrange(char const* range, int max)
1245 1271 }
1246 1272 }
1247 1273 }
  1274 + if ((start_idx > 0) || (skip != 1))
  1275 + {
  1276 + auto t = result;
  1277 + result.clear();
  1278 + for (size_t i = start_idx; i < t.size(); i += skip)
  1279 + {
  1280 + result.push_back(t.at(i));
  1281 + }
  1282 + }
1248 1283 }
1249 1284 catch (std::runtime_error const& e)
1250 1285 {
... ...
libtests/qtest/numrange.test
... ... @@ -49,6 +49,24 @@ my @nrange_tests = (
49 49 "numeric range r1-r15" .
50 50 " -> 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1",
51 51 0],
  52 + ["1-10:quack",
  53 + "error at * in numeric range 1-10*:quack: unexpected even/odd modifier",
  54 + 2],
  55 + ["1-10:",
  56 + "error at * in numeric range 1-10*:: unexpected even/odd modifier",
  57 + 2],
  58 + ["1-10,r:",
  59 + "error at * in numeric range 1-10,r*:: unexpected even/odd modifier",
  60 + 2],
  61 + ["1-10,:",
  62 + "error at * in numeric range 1-10,*:: unexpected colon",
  63 + 2],
  64 + ["1-6,8-12:odd",
  65 + "numeric range 1-6,8-12:odd -> 1 3 5 8 10 12",
  66 + 0],
  67 + ["1-6,8-12:even",
  68 + "numeric range 1-6,8-12:even -> 2 4 6 9 11",
  69 + 0],
52 70 );
53 71 foreach my $d (@nrange_tests)
54 72 {
... ...
manual/qpdf-manual.xml
... ... @@ -1391,9 +1391,12 @@ make
1391 1391 <literal>r3-r1</literal> would be the last three pages of the
1392 1392 document. Pages can appear in any order. Ranges can appear with a
1393 1393 high number followed by a low number, which causes the pages to
1394   - appear in reverse. Repeating a number will cause an error, but you
1395   - can use the workaround discussed above should you really want to
1396   - include the same page twice.
  1394 + appear in reverse. Numbers may be repeated in a page range. A page
  1395 + range may be optionally appended with <literal>:even</literal> or
  1396 + <literal>:odd</literal> to indicate only the even or odd pages in
  1397 + the given range. Note that even and odd refer to the positions
  1398 + within the specified, range, not whether the original number is
  1399 + even or odd.
1397 1400 </para>
1398 1401 <para>
1399 1402 Example page ranges:
... ... @@ -1420,6 +1423,18 @@ make
1420 1423 in reverse order
1421 1424 </para>
1422 1425 </listitem>
  1426 + <listitem>
  1427 + <para>
  1428 + <literal>1-20:even</literal>: even pages from 2 to 20
  1429 + </para>
  1430 + </listitem>
  1431 + <listitem>
  1432 + <para>
  1433 + <literal>5,7-9,12:odd</literal>: pages 5, 8, and, 12, which are
  1434 + the pages in odd positions from among the original range, which
  1435 + represents pages 5, 7, 8, 9, and 12.
  1436 + </para>
  1437 + </listitem>
1423 1438 </itemizedlist>
1424 1439 </para>
1425 1440 <para>
... ... @@ -4663,6 +4678,13 @@ print &quot;\n&quot;;
4663 4678 <xref linkend="ref.crypto"/>.
4664 4679 </para>
4665 4680 </listitem>
  4681 + <listitem>
  4682 + <para>
  4683 + Allow <literal>:even</literal> or <literal>:odd</literal> to
  4684 + be appended to numeric ranges for specification of the even
  4685 + or odd pages from among the pages specified in the range.
  4686 + </para>
  4687 + </listitem>
4666 4688 </itemizedlist>
4667 4689 </listitem>
4668 4690 </itemizedlist>
... ...
qpdf/qpdf.cc
... ... @@ -1286,8 +1286,9 @@ ArgParser::argHelp()
1286 1286 << "to count from the end, so \"r3-r1\" would be the last three pages of the\n"
1287 1287 << "document. Pages can appear in any order. Ranges can appear with a\n"
1288 1288 << "high number followed by a low number, which causes the pages to appear in\n"
1289   - << "reverse. Repeating a number will cause an error, but the manual discusses\n"
1290   - << "a workaround should you really want to include the same page twice.\n"
  1289 + << "reverse. Numbers may be repeated. A page range may be appended with :odd\n"
  1290 + << "to indicate odd pages in the selected range or :even to indicate even\n"
  1291 + << "pages.\n"
1291 1292 << "\n"
1292 1293 << "If the page range is omitted, the range of 1-z is assumed. qpdf decides\n"
1293 1294 << "that the page range is omitted if the range argument is either -- or a\n"
... ...