Commit adccedc02fea78dd5a924605d254ea709d63473b
1 parent
88bacb64
Allow numeric range to be omitted qpdf --pages
Detect a missing page range and assume 1-z.
Showing
6 changed files
with
714 additions
and
8 deletions
ChangeLog
| 1 | +2013-07-07 Jay Berkenbilt <ejb@ql.org> | |
| 2 | + | |
| 3 | + * qpdf: allow omission of range in --pages. If range is omitted | |
| 4 | + such that an argument that is supposed to be a range is an invalid | |
| 5 | + range and a valid file name, the range of 1-z is assumed. This | |
| 6 | + makes it possible to merge a bunch of files with something like | |
| 7 | + qpdf --empty out.pdf --pages *.pdf -- | |
| 8 | + | |
| 1 | 9 | 2013-06-15 Jay Berkenbilt <ejb@ql.org> |
| 2 | 10 | |
| 3 | 11 | * Handle some additional broken files with missing /ID in trailer | ... | ... |
manual/qpdf-manual.xml
| ... | ... | @@ -614,7 +614,7 @@ make |
| 614 | 614 | file is given as the primary input file is used as the starting |
| 615 | 615 | point, but its pages are replaced with pages as specified. |
| 616 | 616 | |
| 617 | - <programlisting><option>--pages <replaceable>input-file</replaceable> [ <replaceable>--password=password</replaceable> ] <replaceable>page-range</replaceable> [ ... ] --</option> | |
| 617 | + <programlisting><option>--pages <replaceable>input-file</replaceable> [ <replaceable>--password=password</replaceable> ] [ <replaceable>page-range</replaceable> ] [ ... ] --</option> | |
| 618 | 618 | </programlisting> |
| 619 | 619 | Multiple input files may be specified. Each one is given as the |
| 620 | 620 | name of the input file, an optional password (if required to open |
| ... | ... | @@ -636,6 +636,15 @@ make |
| 636 | 636 | primary input. |
| 637 | 637 | </para> |
| 638 | 638 | <para> |
| 639 | + Starting with qpdf 4.2.0, it is possible to omit the page range. | |
| 640 | + If qpdf sees a value in the place where it expects a page range | |
| 641 | + and that value is not a valid range but is a valid file name, qpdf | |
| 642 | + will implicitly use the range <literal>1-z</literal>, meaning that | |
| 643 | + it will include all pages in the file. This makes it possible to | |
| 644 | + easily combine all pages in a set of files with a command like | |
| 645 | + <command>qpdf --empty out.pdf --pages *.pdf --</command>. | |
| 646 | + </para> | |
| 647 | + <para> | |
| 639 | 648 | It is not presently possible to specify the same page from the |
| 640 | 649 | same file directly more than once, but you can make this work by |
| 641 | 650 | specifying two different paths to the same file (such as by | ... | ... |
qpdf/qpdf.cc
| ... | ... | @@ -163,7 +163,7 @@ These options allow pages to be selected from one or more PDF files.\n\ |
| 163 | 163 | Whatever file is given as the primary input file is used as the\n\ |
| 164 | 164 | starting point, but its pages are replaced with pages as specified.\n\ |
| 165 | 165 | \n\ |
| 166 | ---pages file [ --password=password ] page-range ... --\n\ | |
| 166 | +--pages file [ --password=password ] [ page-range ] ... --\n\ | |
| 167 | 167 | \n\ |
| 168 | 168 | For each file that pages should be taken from, specify the file, a\n\ |
| 169 | 169 | password needed to open the file (if any), and a page range. The\n\ |
| ... | ... | @@ -183,6 +183,10 @@ pages to appear in reverse. Repeating a number will cause an error, but\n\ |
| 183 | 183 | the manual discusses a workaround should you really want to include the\n\ |
| 184 | 184 | same page twice.\n\ |
| 185 | 185 | \n\ |
| 186 | +If the page range is omitted, the range of 1-z is assumed. qpdf decides\n\ | |
| 187 | +that the page range is omitted if the range argument is either -- or a\n\ | |
| 188 | +valid file name and not a valid range.\n\ | |
| 189 | +\n\ | |
| 186 | 190 | See the manual for examples and a discussion of additional subtleties.\n\ |
| 187 | 191 | \n\ |
| 188 | 192 | \n\ |
| ... | ... | @@ -354,7 +358,8 @@ static void show_encryption(QPDF& pdf) |
| 354 | 358 | } |
| 355 | 359 | } |
| 356 | 360 | |
| 357 | -static std::vector<int> parse_numrange(char const* range, int max) | |
| 361 | +static std::vector<int> parse_numrange(char const* range, int max, | |
| 362 | + bool throw_error = false) | |
| 358 | 363 | { |
| 359 | 364 | std::vector<int> result; |
| 360 | 365 | char const* p = range; |
| ... | ... | @@ -436,7 +441,9 @@ static std::vector<int> parse_numrange(char const* range, int max) |
| 436 | 441 | for (size_t i = 0; i < work.size(); i += 2) |
| 437 | 442 | { |
| 438 | 443 | int num = work[i]; |
| 439 | - if ((num < 1) || (num > max)) | |
| 444 | + // max == 0 means we don't know the max and are just | |
| 445 | + // testing for valid syntax. | |
| 446 | + if ((max > 0) && ((num < 1) || (num > max))) | |
| 440 | 447 | { |
| 441 | 448 | throw std::runtime_error( |
| 442 | 449 | "number " + QUtil::int_to_string(num) + " out of range"); |
| ... | ... | @@ -480,6 +487,10 @@ static std::vector<int> parse_numrange(char const* range, int max) |
| 480 | 487 | } |
| 481 | 488 | catch (std::runtime_error e) |
| 482 | 489 | { |
| 490 | + if (throw_error) | |
| 491 | + { | |
| 492 | + throw e; | |
| 493 | + } | |
| 483 | 494 | if (p) |
| 484 | 495 | { |
| 485 | 496 | usage("error at * in numeric range " + |
| ... | ... | @@ -839,9 +850,9 @@ parse_pages_options( |
| 839 | 850 | { |
| 840 | 851 | usage("insufficient arguments to --pages"); |
| 841 | 852 | } |
| 842 | - char* file = argv[cur_arg++]; | |
| 843 | - char* password = 0; | |
| 844 | - char* range = argv[cur_arg++]; | |
| 853 | + char const* file = argv[cur_arg++]; | |
| 854 | + char const* password = 0; | |
| 855 | + char const* range = argv[cur_arg++]; | |
| 845 | 856 | if (strncmp(range, "--password=", 11) == 0) |
| 846 | 857 | { |
| 847 | 858 | // Oh, that's the password, not the range |
| ... | ... | @@ -853,6 +864,44 @@ parse_pages_options( |
| 853 | 864 | range = argv[cur_arg++]; |
| 854 | 865 | } |
| 855 | 866 | |
| 867 | + // See if the user omitted the range entirely, in which case | |
| 868 | + // we assume "1-z". | |
| 869 | + bool range_omitted = false; | |
| 870 | + if (strcmp(range, "--") == 0) | |
| 871 | + { | |
| 872 | + // The filename or password was the last argument | |
| 873 | + QTC::TC("qpdf", "qpdf pages range omitted at end"); | |
| 874 | + range_omitted = true; | |
| 875 | + } | |
| 876 | + else | |
| 877 | + { | |
| 878 | + try | |
| 879 | + { | |
| 880 | + parse_numrange(range, 0, true); | |
| 881 | + } | |
| 882 | + catch (std::runtime_error& e1) | |
| 883 | + { | |
| 884 | + // The range is invalid. Let's see if it's a file. | |
| 885 | + try | |
| 886 | + { | |
| 887 | + fclose(QUtil::safe_fopen(range, "rb")); | |
| 888 | + // Yup, it's a file. | |
| 889 | + QTC::TC("qpdf", "qpdf pages range omitted in middle"); | |
| 890 | + range_omitted = true; | |
| 891 | + } | |
| 892 | + catch (std::runtime_error& e2) | |
| 893 | + { | |
| 894 | + // Ignore. The range is invalid and not a file. | |
| 895 | + // We'll get an error message later. | |
| 896 | + } | |
| 897 | + } | |
| 898 | + } | |
| 899 | + if (range_omitted) | |
| 900 | + { | |
| 901 | + --cur_arg; | |
| 902 | + range = "1-z"; | |
| 903 | + } | |
| 904 | + | |
| 856 | 905 | result.push_back(PageSpec(file, password, range)); |
| 857 | 906 | } |
| 858 | 907 | return result; | ... | ... |
qpdf/qpdf.testcov
qpdf/qtest/qpdf.test
| ... | ... | @@ -567,7 +567,7 @@ foreach my $d (@nrange_tests) |
| 567 | 567 | show_ntests(); |
| 568 | 568 | # ---------- |
| 569 | 569 | $td->notify("--- Merging and Splitting ---"); |
| 570 | -$n_tests += 6; | |
| 570 | +$n_tests += 8; | |
| 571 | 571 | |
| 572 | 572 | # Select pages from the same file multiple times including selecting |
| 573 | 573 | # twice from an encrypted file and specifying the password only the |
| ... | ... | @@ -612,6 +612,16 @@ $td->runtest("avoid respecification of password", |
| 612 | 612 | $td->runtest("check output", |
| 613 | 613 | {$td->FILE => "a.pdf"}, |
| 614 | 614 | {$td->FILE => "pages-copy-encryption.pdf"}); |
| 615 | +$td->runtest("merge with implicit ranges", | |
| 616 | + {$td->COMMAND => | |
| 617 | + "qpdf --empty a.pdf" . | |
| 618 | + " --pages minimal.pdf 20-pages.pdf --password=user" . | |
| 619 | + " page-labels-and-outlines.pdf --" . | |
| 620 | + " --static-id"}, | |
| 621 | + {$td->STRING => "", $td->EXIT_STATUS => 0}); | |
| 622 | +$td->runtest("check output", | |
| 623 | + {$td->FILE => "a.pdf"}, | |
| 624 | + {$td->FILE => "merge-implicit-ranges.pdf"}); | |
| 615 | 625 | show_ntests(); |
| 616 | 626 | # ---------- |
| 617 | 627 | $td->notify("--- PDF From Scratch ---"); | ... | ... |
qpdf/qtest/qpdf/merge-implicit-ranges.pdf
0 → 100644
No preview for this file type