Commit adccedc02fea78dd5a924605d254ea709d63473b

Authored by Jay Berkenbilt
1 parent 88bacb64

Allow numeric range to be omitted qpdf --pages

Detect a missing page range and assume 1-z.
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&amp; 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&lt;int&gt; 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&lt;int&gt; 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
... ... @@ -265,3 +265,5 @@ QPDF not caching overridden objstm object 0
265 265 QPDFWriter original obj non-zero gen 0
266 266 QPDF_optimization indirect outlines 0
267 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 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-&gt;runtest(&quot;avoid respecification of password&quot;,
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