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 | 2013-06-15 Jay Berkenbilt <ejb@ql.org> | 9 | 2013-06-15 Jay Berkenbilt <ejb@ql.org> |
| 2 | 10 | ||
| 3 | * Handle some additional broken files with missing /ID in trailer | 11 | * Handle some additional broken files with missing /ID in trailer |
manual/qpdf-manual.xml
| @@ -614,7 +614,7 @@ make | @@ -614,7 +614,7 @@ make | ||
| 614 | file is given as the primary input file is used as the starting | 614 | file is given as the primary input file is used as the starting |
| 615 | point, but its pages are replaced with pages as specified. | 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 | </programlisting> | 618 | </programlisting> |
| 619 | Multiple input files may be specified. Each one is given as the | 619 | Multiple input files may be specified. Each one is given as the |
| 620 | name of the input file, an optional password (if required to open | 620 | name of the input file, an optional password (if required to open |
| @@ -636,6 +636,15 @@ make | @@ -636,6 +636,15 @@ make | ||
| 636 | primary input. | 636 | primary input. |
| 637 | </para> | 637 | </para> |
| 638 | <para> | 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 | It is not presently possible to specify the same page from the | 648 | It is not presently possible to specify the same page from the |
| 640 | same file directly more than once, but you can make this work by | 649 | same file directly more than once, but you can make this work by |
| 641 | specifying two different paths to the same file (such as by | 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,7 +163,7 @@ These options allow pages to be selected from one or more PDF files.\n\ | ||
| 163 | Whatever file is given as the primary input file is used as the\n\ | 163 | Whatever file is given as the primary input file is used as the\n\ |
| 164 | starting point, but its pages are replaced with pages as specified.\n\ | 164 | starting point, but its pages are replaced with pages as specified.\n\ |
| 165 | \n\ | 165 | \n\ |
| 166 | ---pages file [ --password=password ] page-range ... --\n\ | 166 | +--pages file [ --password=password ] [ page-range ] ... --\n\ |
| 167 | \n\ | 167 | \n\ |
| 168 | For each file that pages should be taken from, specify the file, a\n\ | 168 | For each file that pages should be taken from, specify the file, a\n\ |
| 169 | password needed to open the file (if any), and a page range. The\n\ | 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,6 +183,10 @@ pages to appear in reverse. Repeating a number will cause an error, but\n\ | ||
| 183 | the manual discusses a workaround should you really want to include the\n\ | 183 | the manual discusses a workaround should you really want to include the\n\ |
| 184 | same page twice.\n\ | 184 | same page twice.\n\ |
| 185 | \n\ | 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 | See the manual for examples and a discussion of additional subtleties.\n\ | 190 | See the manual for examples and a discussion of additional subtleties.\n\ |
| 187 | \n\ | 191 | \n\ |
| 188 | \n\ | 192 | \n\ |
| @@ -354,7 +358,8 @@ static void show_encryption(QPDF& pdf) | @@ -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 | std::vector<int> result; | 364 | std::vector<int> result; |
| 360 | char const* p = range; | 365 | char const* p = range; |
| @@ -436,7 +441,9 @@ static std::vector<int> parse_numrange(char const* range, int max) | @@ -436,7 +441,9 @@ static std::vector<int> parse_numrange(char const* range, int max) | ||
| 436 | for (size_t i = 0; i < work.size(); i += 2) | 441 | for (size_t i = 0; i < work.size(); i += 2) |
| 437 | { | 442 | { |
| 438 | int num = work[i]; | 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 | throw std::runtime_error( | 448 | throw std::runtime_error( |
| 442 | "number " + QUtil::int_to_string(num) + " out of range"); | 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,6 +487,10 @@ static std::vector<int> parse_numrange(char const* range, int max) | ||
| 480 | } | 487 | } |
| 481 | catch (std::runtime_error e) | 488 | catch (std::runtime_error e) |
| 482 | { | 489 | { |
| 490 | + if (throw_error) | ||
| 491 | + { | ||
| 492 | + throw e; | ||
| 493 | + } | ||
| 483 | if (p) | 494 | if (p) |
| 484 | { | 495 | { |
| 485 | usage("error at * in numeric range " + | 496 | usage("error at * in numeric range " + |
| @@ -839,9 +850,9 @@ parse_pages_options( | @@ -839,9 +850,9 @@ parse_pages_options( | ||
| 839 | { | 850 | { |
| 840 | usage("insufficient arguments to --pages"); | 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 | if (strncmp(range, "--password=", 11) == 0) | 856 | if (strncmp(range, "--password=", 11) == 0) |
| 846 | { | 857 | { |
| 847 | // Oh, that's the password, not the range | 858 | // Oh, that's the password, not the range |
| @@ -853,6 +864,44 @@ parse_pages_options( | @@ -853,6 +864,44 @@ parse_pages_options( | ||
| 853 | range = argv[cur_arg++]; | 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 | result.push_back(PageSpec(file, password, range)); | 905 | result.push_back(PageSpec(file, password, range)); |
| 857 | } | 906 | } |
| 858 | return result; | 907 | return result; |
qpdf/qpdf.testcov
| @@ -265,3 +265,5 @@ QPDF not caching overridden objstm object 0 | @@ -265,3 +265,5 @@ QPDF not caching overridden objstm object 0 | ||
| 265 | QPDFWriter original obj non-zero gen 0 | 265 | QPDFWriter original obj non-zero gen 0 |
| 266 | QPDF_optimization indirect outlines 0 | 266 | QPDF_optimization indirect outlines 0 |
| 267 | QPDF xref space 2 | 267 | QPDF xref space 2 |
| 268 | +qpdf pages range omitted at end 0 | ||
| 269 | +qpdf pages range omitted in middle 0 |
qpdf/qtest/qpdf.test
| @@ -567,7 +567,7 @@ foreach my $d (@nrange_tests) | @@ -567,7 +567,7 @@ foreach my $d (@nrange_tests) | ||
| 567 | show_ntests(); | 567 | show_ntests(); |
| 568 | # ---------- | 568 | # ---------- |
| 569 | $td->notify("--- Merging and Splitting ---"); | 569 | $td->notify("--- Merging and Splitting ---"); |
| 570 | -$n_tests += 6; | 570 | +$n_tests += 8; |
| 571 | 571 | ||
| 572 | # Select pages from the same file multiple times including selecting | 572 | # Select pages from the same file multiple times including selecting |
| 573 | # twice from an encrypted file and specifying the password only the | 573 | # twice from an encrypted file and specifying the password only the |
| @@ -612,6 +612,16 @@ $td->runtest("avoid respecification of password", | @@ -612,6 +612,16 @@ $td->runtest("avoid respecification of password", | ||
| 612 | $td->runtest("check output", | 612 | $td->runtest("check output", |
| 613 | {$td->FILE => "a.pdf"}, | 613 | {$td->FILE => "a.pdf"}, |
| 614 | {$td->FILE => "pages-copy-encryption.pdf"}); | 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 | show_ntests(); | 625 | show_ntests(); |
| 616 | # ---------- | 626 | # ---------- |
| 617 | $td->notify("--- PDF From Scratch ---"); | 627 | $td->notify("--- PDF From Scratch ---"); |
qpdf/qtest/qpdf/merge-implicit-ranges.pdf
0 → 100644
No preview for this file type