mirror of
https://github.com/qpdf/qpdf.git
synced 2024-09-28 04:59:05 +00:00
Add --replace-input option (fixes #321)
This commit is contained in:
parent
babd12c9b2
commit
d492bb0a90
@ -331,10 +331,12 @@ make
|
|||||||
<option>outfilename</option> does not have to be seekable, even
|
<option>outfilename</option> does not have to be seekable, even
|
||||||
when generating linearized files. Specifying
|
when generating linearized files. Specifying
|
||||||
“<option>-</option>” as <option>outfilename</option>
|
“<option>-</option>” as <option>outfilename</option>
|
||||||
means to write to standard output. However, you can't specify the
|
means to write to standard output. If you want to overwrite the
|
||||||
same file as both the input and the output because qpdf reads data
|
input file with the output, use the option
|
||||||
from the input file as it writes to the output file. QPDF attempts
|
<option>--replace-input</option> and omit the output file name.
|
||||||
to detect this case and fail without overwriting the output file.
|
You can't specify the same file as both the input and the output.
|
||||||
|
If you do this, qpdf will tell you about the
|
||||||
|
<option>--replace-input</option> option.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
Most options require an output file, but some testing or
|
Most options require an output file, but some testing or
|
||||||
@ -449,6 +451,21 @@ make
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--replace-input</option></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
If specified, the output file name should be omitted. This
|
||||||
|
option tells qpdf to replace the input file with the output.
|
||||||
|
It does this by writing to
|
||||||
|
<filename>.~qpdf-temp.<replaceable>infilename</replaceable>#</filename>
|
||||||
|
and, when done, overwriting the input file with the temporary
|
||||||
|
file. If there were any warnings, the original input is saved
|
||||||
|
as
|
||||||
|
<filename><replaceable>infilename</replaceable>.~qpdf-orig</filename>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>--copy-encryption=file</option></term>
|
<term><option>--copy-encryption=file</option></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
@ -4419,6 +4436,15 @@ print "\n";
|
|||||||
CLI Enhancements
|
CLI Enhancements
|
||||||
</para>
|
</para>
|
||||||
<itemizedlist>
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
The <option>--replace-input</option> option may be given in
|
||||||
|
place of an output file name. This causes qpdf to overwrite
|
||||||
|
the input file with the output. See the description of
|
||||||
|
<option>--replace-input</option> in <xref
|
||||||
|
linkend="ref.basic-options"/> for more details.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The <option>--recompress-flate</option> instructs
|
The <option>--recompress-flate</option> instructs
|
||||||
|
118
qpdf/qpdf.cc
118
qpdf/qpdf.cc
@ -23,6 +23,7 @@
|
|||||||
#include <qpdf/QPDFOutlineDocumentHelper.hh>
|
#include <qpdf/QPDFOutlineDocumentHelper.hh>
|
||||||
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
|
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
|
||||||
#include <qpdf/QPDFExc.hh>
|
#include <qpdf/QPDFExc.hh>
|
||||||
|
#include <qpdf/QPDFSystemError.hh>
|
||||||
|
|
||||||
#include <qpdf/QPDFWriter.hh>
|
#include <qpdf/QPDFWriter.hh>
|
||||||
#include <qpdf/QIntC.hh>
|
#include <qpdf/QIntC.hh>
|
||||||
@ -180,6 +181,7 @@ struct Options
|
|||||||
overlay("overlay"),
|
overlay("overlay"),
|
||||||
under_overlay(0),
|
under_overlay(0),
|
||||||
require_outfile(true),
|
require_outfile(true),
|
||||||
|
replace_input(false),
|
||||||
infilename(0),
|
infilename(0),
|
||||||
outfilename(0)
|
outfilename(0)
|
||||||
{
|
{
|
||||||
@ -283,6 +285,7 @@ struct Options
|
|||||||
std::vector<PageSpec> page_specs;
|
std::vector<PageSpec> page_specs;
|
||||||
std::map<std::string, RotationSpec> rotations;
|
std::map<std::string, RotationSpec> rotations;
|
||||||
bool require_outfile;
|
bool require_outfile;
|
||||||
|
bool replace_input;
|
||||||
char const* infilename;
|
char const* infilename;
|
||||||
char const* outfilename;
|
char const* outfilename;
|
||||||
};
|
};
|
||||||
@ -712,6 +715,7 @@ class ArgParser
|
|||||||
void argUOrepeat(char* parameter);
|
void argUOrepeat(char* parameter);
|
||||||
void argUOpassword(char* parameter);
|
void argUOpassword(char* parameter);
|
||||||
void argEndUnderOverlay();
|
void argEndUnderOverlay();
|
||||||
|
void argReplaceInput();
|
||||||
|
|
||||||
void usage(std::string const& message);
|
void usage(std::string const& message);
|
||||||
void checkCompletion();
|
void checkCompletion();
|
||||||
@ -940,6 +944,7 @@ ArgParser::initOptionTable()
|
|||||||
&ArgParser::argIiMinBytes, "minimum-bytes");
|
&ArgParser::argIiMinBytes, "minimum-bytes");
|
||||||
(*t)["overlay"] = oe_bare(&ArgParser::argOverlay);
|
(*t)["overlay"] = oe_bare(&ArgParser::argOverlay);
|
||||||
(*t)["underlay"] = oe_bare(&ArgParser::argUnderlay);
|
(*t)["underlay"] = oe_bare(&ArgParser::argUnderlay);
|
||||||
|
(*t)["replace-input"] = oe_bare(&ArgParser::argReplaceInput);
|
||||||
|
|
||||||
t = &this->encrypt40_option_table;
|
t = &this->encrypt40_option_table;
|
||||||
(*t)["--"] = oe_bare(&ArgParser::argEndEncrypt);
|
(*t)["--"] = oe_bare(&ArgParser::argEndEncrypt);
|
||||||
@ -1080,6 +1085,9 @@ ArgParser::argHelp()
|
|||||||
<< "will be interpreted as an argument. No interpolation is done. Line\n"
|
<< "will be interpreted as an argument. No interpolation is done. Line\n"
|
||||||
<< "terminators are stripped. @- can be specified to read from standard input.\n"
|
<< "terminators are stripped. @- can be specified to read from standard input.\n"
|
||||||
<< "\n"
|
<< "\n"
|
||||||
|
<< "The output file can be - to indicate writing to standard output, or it can\n"
|
||||||
|
<< "be --replace-input to cause qpdf to replace the input file with the output.\n"
|
||||||
|
<< "\n"
|
||||||
<< "Note that when contradictory options are provided, whichever options are\n"
|
<< "Note that when contradictory options are provided, whichever options are\n"
|
||||||
<< "provided last take precedence.\n"
|
<< "provided last take precedence.\n"
|
||||||
<< "\n"
|
<< "\n"
|
||||||
@ -1097,6 +1105,8 @@ ArgParser::argHelp()
|
|||||||
<< "--progress give progress indicators while writing output\n"
|
<< "--progress give progress indicators while writing output\n"
|
||||||
<< "--no-warn suppress warnings\n"
|
<< "--no-warn suppress warnings\n"
|
||||||
<< "--linearize generated a linearized (web optimized) file\n"
|
<< "--linearize generated a linearized (web optimized) file\n"
|
||||||
|
<< "--replace-input use in place of specifying an output file; qpdf will\n"
|
||||||
|
<< " replace the input file with the output\n"
|
||||||
<< "--copy-encryption=file copy encryption parameters from specified file\n"
|
<< "--copy-encryption=file copy encryption parameters from specified file\n"
|
||||||
<< "--encryption-file-password=password\n"
|
<< "--encryption-file-password=password\n"
|
||||||
<< " password used to open the file from which encryption\n"
|
<< " password used to open the file from which encryption\n"
|
||||||
@ -2316,6 +2326,12 @@ ArgParser::argEndUnderOverlay()
|
|||||||
o.under_overlay = 0;
|
o.under_overlay = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ArgParser::argReplaceInput()
|
||||||
|
{
|
||||||
|
o.replace_input = true;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ArgParser::handleArgFileArguments()
|
ArgParser::handleArgFileArguments()
|
||||||
{
|
{
|
||||||
@ -3048,15 +3064,28 @@ ArgParser::doFinalChecks()
|
|||||||
{
|
{
|
||||||
usage("missing -- at end of options");
|
usage("missing -- at end of options");
|
||||||
}
|
}
|
||||||
|
if (o.replace_input)
|
||||||
|
{
|
||||||
|
if (o.outfilename)
|
||||||
|
{
|
||||||
|
usage("--replace-input may not be used when"
|
||||||
|
" an output file is specified");
|
||||||
|
}
|
||||||
|
else if (o.split_pages)
|
||||||
|
{
|
||||||
|
usage("--split-pages may not be used with --replace-input");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (o.infilename == 0)
|
if (o.infilename == 0)
|
||||||
{
|
{
|
||||||
usage("an input file name is required");
|
usage("an input file name is required");
|
||||||
}
|
}
|
||||||
else if (o.require_outfile && (o.outfilename == 0))
|
else if (o.require_outfile && (o.outfilename == 0) && (! o.replace_input))
|
||||||
{
|
{
|
||||||
usage("an output file name is required; use - for standard output");
|
usage("an output file name is required; use - for standard output");
|
||||||
}
|
}
|
||||||
else if ((! o.require_outfile) && (o.outfilename != 0))
|
else if ((! o.require_outfile) &&
|
||||||
|
((o.outfilename != 0) || o.replace_input))
|
||||||
{
|
{
|
||||||
usage("no output file may be given for this option");
|
usage("no output file may be given for this option");
|
||||||
}
|
}
|
||||||
@ -3065,7 +3094,8 @@ ArgParser::doFinalChecks()
|
|||||||
o.externalize_inline_images = true;
|
o.externalize_inline_images = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o.require_outfile && (strcmp(o.outfilename, "-") == 0))
|
if (o.require_outfile && o.outfilename &&
|
||||||
|
(strcmp(o.outfilename, "-") == 0))
|
||||||
{
|
{
|
||||||
if (o.split_pages)
|
if (o.split_pages)
|
||||||
{
|
{
|
||||||
@ -3088,7 +3118,7 @@ ArgParser::doFinalChecks()
|
|||||||
{
|
{
|
||||||
QTC::TC("qpdf", "qpdf same file error");
|
QTC::TC("qpdf", "qpdf same file error");
|
||||||
usage("input file and output file are the same;"
|
usage("input file and output file are the same;"
|
||||||
" this would cause input file to be lost");
|
" use --replace-input to intentionally overwrite the input file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3861,6 +3891,12 @@ static void do_inspection(QPDF& pdf, Options& o)
|
|||||||
{
|
{
|
||||||
do_show_pages(pdf, o);
|
do_show_pages(pdf, o);
|
||||||
}
|
}
|
||||||
|
if ((! pdf.getWarnings().empty()) && (exit_code != EXIT_ERROR))
|
||||||
|
{
|
||||||
|
std::cerr << whoami
|
||||||
|
<< ": operation succeeded with warnings" << std::endl;
|
||||||
|
exit_code = EXIT_WARNING;
|
||||||
|
}
|
||||||
if (exit_code)
|
if (exit_code)
|
||||||
{
|
{
|
||||||
exit(exit_code);
|
exit(exit_code);
|
||||||
@ -5109,18 +5145,80 @@ static void do_split_pages(QPDF& pdf, Options& o)
|
|||||||
|
|
||||||
static void write_outfile(QPDF& pdf, Options& o)
|
static void write_outfile(QPDF& pdf, Options& o)
|
||||||
{
|
{
|
||||||
if (strcmp(o.outfilename, "-") == 0)
|
std::string temp_out;
|
||||||
|
if (o.replace_input)
|
||||||
|
{
|
||||||
|
// Use a file name that is hidden by default in the OS to
|
||||||
|
// avoid having it become momentarily visible in a
|
||||||
|
// graphical file manager or in case it gets left behind
|
||||||
|
// because of some kind of error.
|
||||||
|
temp_out = ".~qpdf-temp." + std::string(o.infilename) + "#";
|
||||||
|
// o.outfilename will be restored to 0 before temp_out
|
||||||
|
// goes out of scope.
|
||||||
|
o.outfilename = temp_out.c_str();
|
||||||
|
}
|
||||||
|
else if (strcmp(o.outfilename, "-") == 0)
|
||||||
{
|
{
|
||||||
o.outfilename = 0;
|
o.outfilename = 0;
|
||||||
}
|
}
|
||||||
QPDFWriter w(pdf, o.outfilename);
|
{
|
||||||
set_writer_options(pdf, o, w);
|
// Private scope so QPDFWriter will close the output file
|
||||||
w.write();
|
QPDFWriter w(pdf, o.outfilename);
|
||||||
if (o.verbose)
|
set_writer_options(pdf, o, w);
|
||||||
|
w.write();
|
||||||
|
}
|
||||||
|
if (o.verbose && o.outfilename)
|
||||||
{
|
{
|
||||||
std::cout << whoami << ": wrote file "
|
std::cout << whoami << ": wrote file "
|
||||||
<< o.outfilename << std::endl;
|
<< o.outfilename << std::endl;
|
||||||
}
|
}
|
||||||
|
if (o.replace_input)
|
||||||
|
{
|
||||||
|
o.outfilename = 0;
|
||||||
|
}
|
||||||
|
if (o.replace_input)
|
||||||
|
{
|
||||||
|
// We must close the input before we can rename files
|
||||||
|
pdf.closeInputSource();
|
||||||
|
std::string backup;
|
||||||
|
bool warnings = pdf.anyWarnings();
|
||||||
|
if (warnings)
|
||||||
|
{
|
||||||
|
// If there are warnings, the user may care about this
|
||||||
|
// file, so give it a non-hidden name that will be
|
||||||
|
// lexically grouped with the original file.
|
||||||
|
backup = std::string(o.infilename) + ".~qpdf-orig";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
backup = ".~qpdf-orig." + std::string(o.infilename) + "#";
|
||||||
|
}
|
||||||
|
QUtil::rename_file(o.infilename, backup.c_str());
|
||||||
|
QUtil::rename_file(temp_out.c_str(), o.infilename);
|
||||||
|
if (warnings)
|
||||||
|
{
|
||||||
|
std::cerr << whoami
|
||||||
|
<< ": there are warnings; original file kept in "
|
||||||
|
<< backup << std::endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QUtil::remove_file(backup.c_str());
|
||||||
|
}
|
||||||
|
catch (QPDFSystemError& e)
|
||||||
|
{
|
||||||
|
std::cerr
|
||||||
|
<< whoami
|
||||||
|
<< ": unable to delete original file ("
|
||||||
|
<< e.what() << ");"
|
||||||
|
<< " original file left in " << backup
|
||||||
|
<< ", but the input was successfully replaced"
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int realmain(int argc, char* argv[])
|
int realmain(int argc, char* argv[])
|
||||||
@ -5156,7 +5254,7 @@ int realmain(int argc, char* argv[])
|
|||||||
handle_under_overlay(pdf, o);
|
handle_under_overlay(pdf, o);
|
||||||
handle_transformations(pdf, o);
|
handle_transformations(pdf, o);
|
||||||
|
|
||||||
if (o.outfilename == 0)
|
if ((o.outfilename == 0) && (! o.replace_input))
|
||||||
{
|
{
|
||||||
do_inspection(pdf, o);
|
do_inspection(pdf, o);
|
||||||
}
|
}
|
||||||
|
@ -189,6 +189,47 @@ foreach my $d (['auto-ü', 1], ['auto-öπ', 2])
|
|||||||
$td->NORMALIZE_NEWLINES);
|
$td->NORMALIZE_NEWLINES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
show_ntests();
|
||||||
|
# ----------
|
||||||
|
$td->notify("--- Replace Input ---");
|
||||||
|
$n_tests += 8;
|
||||||
|
|
||||||
|
# Use Unicode file names to test replace input so we can be sure it
|
||||||
|
# works for that case.
|
||||||
|
$td->runtest("create unicode filenames",
|
||||||
|
{$td->COMMAND => "test_unicode_filenames"},
|
||||||
|
{$td->STRING => "created Unicode filenames\n",
|
||||||
|
$td->EXIT_STATUS => 0},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
|
||||||
|
foreach my $d (['auto-ü', 1], ['auto-öπ', 2])
|
||||||
|
{
|
||||||
|
my ($u, $n) = @$d;
|
||||||
|
$td->runtest("replace input $u",
|
||||||
|
{$td->COMMAND => "qpdf --deterministic-id" .
|
||||||
|
" --object-streams=generate --replace-input $u.pdf"},
|
||||||
|
{$td->STRING => "", $td->EXIT_STATUS => 0},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
$td->runtest("check output ($u)",
|
||||||
|
{$td->FILE => "$u.pdf"},
|
||||||
|
{$td->FILE => "replace-input.pdf"},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
}
|
||||||
|
|
||||||
|
system("cp xref-with-short-size.pdf auto-warn.pdf") == 0 or die;
|
||||||
|
$td->runtest("replace input with warnings",
|
||||||
|
{$td->COMMAND =>
|
||||||
|
"qpdf --deterministic-id --replace-input auto-warn.pdf"},
|
||||||
|
{$td->FILE => "replace-warn.out", $td->EXIT_STATUS => 3},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
|
||||||
|
$td->runtest("check output",
|
||||||
|
{$td->FILE => "auto-warn.pdf"},
|
||||||
|
{$td->FILE => "warn-replace.pdf"});
|
||||||
|
$td->runtest("check orig output",
|
||||||
|
{$td->FILE => "auto-warn.pdf.~qpdf-orig"},
|
||||||
|
{$td->FILE => "xref-with-short-size.pdf"});
|
||||||
|
|
||||||
show_ntests();
|
show_ntests();
|
||||||
# ----------
|
# ----------
|
||||||
$td->notify("--- Final Version ---");
|
$td->notify("--- Final Version ---");
|
||||||
@ -4233,5 +4274,5 @@ sub get_md5_checksum
|
|||||||
sub cleanup
|
sub cleanup
|
||||||
{
|
{
|
||||||
system("rm -rf *.ps *.pnm ?.pdf ?.qdf *.enc* tif1 tif2 tiff-cache");
|
system("rm -rf *.ps *.pnm ?.pdf ?.qdf *.enc* tif1 tif2 tiff-cache");
|
||||||
system("rm -rf *split-out* ???-kfo.pdf *.tmpout \@file.pdf auto-*.pdf");
|
system("rm -rf *split-out* ???-kfo.pdf *.tmpout \@file.pdf auto-*");
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
WARNING: bad-jpeg.pdf (offset 735): error decoding stream data for object 6 0: Not a JPEG file: starts with 0x77 0x77
|
WARNING: bad-jpeg.pdf (offset 735): error decoding stream data for object 6 0: Not a JPEG file: starts with 0x77 0x77
|
||||||
qpdf: operation succeeded with warnings; resulting file may have some problems
|
qpdf: operation succeeded with warnings
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
WARNING: empty-object.pdf (object 7 0, offset 575): empty object treated as null
|
WARNING: empty-object.pdf (object 7 0, offset 575): empty object treated as null
|
||||||
null
|
null
|
||||||
qpdf: operation succeeded with warnings; resulting file may have some problems
|
qpdf: operation succeeded with warnings
|
||||||
|
BIN
qpdf/qtest/qpdf/replace-input.pdf
Normal file
BIN
qpdf/qtest/qpdf/replace-input.pdf
Normal file
Binary file not shown.
3
qpdf/qtest/qpdf/replace-warn.out
Normal file
3
qpdf/qtest/qpdf/replace-warn.out
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
WARNING: auto-warn.pdf (xref stream, offset 16227): Cross-reference stream data has the wrong size; expected = 52; actual = 56
|
||||||
|
qpdf: there are warnings; original file kept in auto-warn.pdf.~qpdf-orig
|
||||||
|
qpdf: operation succeeded with warnings; resulting file may have some problems
|
BIN
qpdf/qtest/qpdf/warn-replace.pdf
Normal file
BIN
qpdf/qtest/qpdf/warn-replace.pdf
Normal file
Binary file not shown.
@ -11,4 +11,4 @@ WARNING: xref-with-short-size.pdf (xref stream, offset 16227): Cross-reference s
|
|||||||
10/0: compressed; stream = 5, index = 3
|
10/0: compressed; stream = 5, index = 3
|
||||||
11/0: compressed; stream = 5, index = 7
|
11/0: compressed; stream = 5, index = 7
|
||||||
12/0: compressed; stream = 5, index = 8
|
12/0: compressed; stream = 5, index = 8
|
||||||
qpdf: operation succeeded with warnings; resulting file may have some problems
|
qpdf: operation succeeded with warnings
|
||||||
|
Loading…
Reference in New Issue
Block a user