mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 10:58:58 +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
|
||||
when generating linearized files. Specifying
|
||||
“<option>-</option>” as <option>outfilename</option>
|
||||
means to write to standard output. However, you can't specify the
|
||||
same file as both the input and the output because qpdf reads data
|
||||
from the input file as it writes to the output file. QPDF attempts
|
||||
to detect this case and fail without overwriting the output file.
|
||||
means to write to standard output. If you want to overwrite the
|
||||
input file with the output, use the option
|
||||
<option>--replace-input</option> and omit the output file name.
|
||||
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>
|
||||
Most options require an output file, but some testing or
|
||||
@ -449,6 +451,21 @@ make
|
||||
</para>
|
||||
</listitem>
|
||||
</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>
|
||||
<term><option>--copy-encryption=file</option></term>
|
||||
<listitem>
|
||||
@ -4419,6 +4436,15 @@ print "\n";
|
||||
CLI Enhancements
|
||||
</para>
|
||||
<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>
|
||||
<para>
|
||||
The <option>--recompress-flate</option> instructs
|
||||
|
118
qpdf/qpdf.cc
118
qpdf/qpdf.cc
@ -23,6 +23,7 @@
|
||||
#include <qpdf/QPDFOutlineDocumentHelper.hh>
|
||||
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
|
||||
#include <qpdf/QPDFExc.hh>
|
||||
#include <qpdf/QPDFSystemError.hh>
|
||||
|
||||
#include <qpdf/QPDFWriter.hh>
|
||||
#include <qpdf/QIntC.hh>
|
||||
@ -180,6 +181,7 @@ struct Options
|
||||
overlay("overlay"),
|
||||
under_overlay(0),
|
||||
require_outfile(true),
|
||||
replace_input(false),
|
||||
infilename(0),
|
||||
outfilename(0)
|
||||
{
|
||||
@ -283,6 +285,7 @@ struct Options
|
||||
std::vector<PageSpec> page_specs;
|
||||
std::map<std::string, RotationSpec> rotations;
|
||||
bool require_outfile;
|
||||
bool replace_input;
|
||||
char const* infilename;
|
||||
char const* outfilename;
|
||||
};
|
||||
@ -712,6 +715,7 @@ class ArgParser
|
||||
void argUOrepeat(char* parameter);
|
||||
void argUOpassword(char* parameter);
|
||||
void argEndUnderOverlay();
|
||||
void argReplaceInput();
|
||||
|
||||
void usage(std::string const& message);
|
||||
void checkCompletion();
|
||||
@ -940,6 +944,7 @@ ArgParser::initOptionTable()
|
||||
&ArgParser::argIiMinBytes, "minimum-bytes");
|
||||
(*t)["overlay"] = oe_bare(&ArgParser::argOverlay);
|
||||
(*t)["underlay"] = oe_bare(&ArgParser::argUnderlay);
|
||||
(*t)["replace-input"] = oe_bare(&ArgParser::argReplaceInput);
|
||||
|
||||
t = &this->encrypt40_option_table;
|
||||
(*t)["--"] = oe_bare(&ArgParser::argEndEncrypt);
|
||||
@ -1080,6 +1085,9 @@ ArgParser::argHelp()
|
||||
<< "will be interpreted as an argument. No interpolation is done. Line\n"
|
||||
<< "terminators are stripped. @- can be specified to read from standard input.\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"
|
||||
<< "provided last take precedence.\n"
|
||||
<< "\n"
|
||||
@ -1097,6 +1105,8 @@ ArgParser::argHelp()
|
||||
<< "--progress give progress indicators while writing output\n"
|
||||
<< "--no-warn suppress warnings\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"
|
||||
<< "--encryption-file-password=password\n"
|
||||
<< " password used to open the file from which encryption\n"
|
||||
@ -2316,6 +2326,12 @@ ArgParser::argEndUnderOverlay()
|
||||
o.under_overlay = 0;
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::argReplaceInput()
|
||||
{
|
||||
o.replace_input = true;
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::handleArgFileArguments()
|
||||
{
|
||||
@ -3048,15 +3064,28 @@ ArgParser::doFinalChecks()
|
||||
{
|
||||
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)
|
||||
{
|
||||
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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
@ -3065,7 +3094,8 @@ ArgParser::doFinalChecks()
|
||||
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)
|
||||
{
|
||||
@ -3088,7 +3118,7 @@ ArgParser::doFinalChecks()
|
||||
{
|
||||
QTC::TC("qpdf", "qpdf same file error");
|
||||
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);
|
||||
}
|
||||
if ((! pdf.getWarnings().empty()) && (exit_code != EXIT_ERROR))
|
||||
{
|
||||
std::cerr << whoami
|
||||
<< ": operation succeeded with warnings" << std::endl;
|
||||
exit_code = EXIT_WARNING;
|
||||
}
|
||||
if (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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
QPDFWriter w(pdf, o.outfilename);
|
||||
set_writer_options(pdf, o, w);
|
||||
w.write();
|
||||
if (o.verbose)
|
||||
{
|
||||
// Private scope so QPDFWriter will close the output file
|
||||
QPDFWriter w(pdf, o.outfilename);
|
||||
set_writer_options(pdf, o, w);
|
||||
w.write();
|
||||
}
|
||||
if (o.verbose && o.outfilename)
|
||||
{
|
||||
std::cout << whoami << ": wrote file "
|
||||
<< 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[])
|
||||
@ -5156,7 +5254,7 @@ int realmain(int argc, char* argv[])
|
||||
handle_under_overlay(pdf, o);
|
||||
handle_transformations(pdf, o);
|
||||
|
||||
if (o.outfilename == 0)
|
||||
if ((o.outfilename == 0) && (! o.replace_input))
|
||||
{
|
||||
do_inspection(pdf, o);
|
||||
}
|
||||
|
@ -189,6 +189,47 @@ foreach my $d (['auto-ü', 1], ['auto-öπ', 2])
|
||||
$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();
|
||||
# ----------
|
||||
$td->notify("--- Final Version ---");
|
||||
@ -4233,5 +4274,5 @@ sub get_md5_checksum
|
||||
sub cleanup
|
||||
{
|
||||
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
|
||||
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
|
||||
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
|
||||
11/0: compressed; stream = 5, index = 7
|
||||
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