aboutsummaryrefslogtreecommitdiffstats
path: root/libqpdf
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2022-05-19 00:22:57 +0200
committerJay Berkenbilt <ejb@ql.org>2022-05-20 15:16:25 +0200
commit6f43bf8de36b08c55b172b4f4133c79657651666 (patch)
treeec17bbf42d9ea78d44ab3b9d2cac363cc6b9bc68 /libqpdf
parent23fc6756f1894e1af35853eb2251f08d5b25cf30 (diff)
downloadqpdf-6f43bf8de36b08c55b172b4f4133c79657651666.tar.zst
Major rework -- see long comments
* Replace --create-from-json=file with --json-input, which causes the regular input to be treated as json. * Eliminate --to-json * In --json=2, bring back "objects" and eliminate "objectinfo". Stream data is never present. * In --json-output=2, write "qpdf-v2" with "objects" and include stream data.
Diffstat (limited to 'libqpdf')
-rw-r--r--libqpdf/QPDFJob.cc280
-rw-r--r--libqpdf/QPDFJob_argv.cc7
-rw-r--r--libqpdf/QPDFJob_config.cc30
-rw-r--r--libqpdf/QPDFJob_json.cc6
-rw-r--r--libqpdf/QPDF_json.cc132
-rw-r--r--libqpdf/qpdf/auto_job_decl.hh1
-rw-r--r--libqpdf/qpdf/auto_job_help.hh17
-rw-r--r--libqpdf/qpdf/auto_job_init.hh11
-rw-r--r--libqpdf/qpdf/auto_job_json_decl.hh1
-rw-r--r--libqpdf/qpdf/auto_job_json_init.hh17
-rw-r--r--libqpdf/qpdf/auto_job_schema.hh4
11 files changed, 273 insertions, 233 deletions
diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc
index 41e57c58..3190e39f 100644
--- a/libqpdf/QPDFJob.cc
+++ b/libqpdf/QPDFJob.cc
@@ -402,6 +402,7 @@ QPDFJob::Members::Members() :
list_attachments(false),
json_version(0),
json_stream_data(qpdf_sj_none),
+ json_stream_data_set(false),
test_json_schema(false),
check(false),
optimize_images(false),
@@ -418,7 +419,9 @@ QPDFJob::Members::Members() :
require_outfile(true),
replace_input(false),
check_is_encrypted(false),
- check_requires_password(false)
+ check_requires_password(false),
+ json_input(false),
+ json_output(0)
{
}
@@ -533,7 +536,8 @@ QPDFJob::run()
checkConfiguration();
std::shared_ptr<QPDF> pdf_ph;
try {
- pdf_ph = processFile(m->infilename.get(), m->password.get(), true);
+ pdf_ph =
+ processFile(m->infilename.get(), m->password.get(), true, true);
} catch (QPDFExc& e) {
if ((e.getErrorCode() == qpdf_e_password) &&
(m->check_is_encrypted || m->check_requires_password)) {
@@ -704,15 +708,8 @@ QPDFJob::checkConfiguration()
" overwrite the input file");
}
- if (m->json_version == 1) {
- if (m->json_keys.count("qpdf")) {
- usage("json key \"qpdf\" is not valid for json version 1");
- }
- } else {
- if (m->json_keys.count("objects") || m->json_keys.count("objectinfo")) {
- usage("json keys \"objects\" and \"objectinfo\" are only valid for "
- "json version 1");
- }
+ if (m->json_keys.count("objectinfo")) {
+ usage("json key \"objectinfo\" is only valid for json version 1");
}
}
@@ -1061,6 +1058,26 @@ QPDFJob::getWantedJSONObjects()
}
void
+QPDFJob::doJSONObject(
+ Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle& obj)
+{
+ if (this->m->json_version == 1) {
+ JSON::writeDictionaryItem(p, first, key, obj.getJSON(1, true), 1);
+ } else {
+ auto j = JSON::makeDictionary();
+ if (obj.isStream()) {
+ j.addDictionaryMember("stream", JSON::makeDictionary())
+ .addDictionaryMember(
+ "dict", obj.getDict().getJSON(this->m->json_version, true));
+ } else {
+ j.addDictionaryMember(
+ "value", obj.getJSON(this->m->json_version, true));
+ }
+ JSON::writeDictionaryItem(p, first, key, j, 1);
+ }
+}
+
+void
QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf)
{
JSON::writeDictionaryKey(p, first, "objects", 0);
@@ -1070,22 +1087,17 @@ QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf)
std::set<QPDFObjGen> wanted_og = getWantedJSONObjects();
std::vector<QPDFObjectHandle> objects = pdf.getAllObjects();
for (auto& obj: objects) {
+ std::string key = obj.unparse();
+ if (this->m->json_version > 1) {
+ key = "obj:" + key;
+ }
if (all_objects || wanted_og.count(obj.getObjGen())) {
- JSON::writeDictionaryItem(
- p,
- first_object,
- obj.unparse(),
- obj.getJSON(this->m->json_version, true),
- 1);
+ doJSONObject(p, first_object, key, obj);
}
}
if (all_objects || m->json_objects.count("trailer")) {
- JSON::writeDictionaryItem(
- p,
- first_object,
- "trailer",
- pdf.getTrailer().getJSON(this->m->json_version, true),
- 1);
+ auto trailer = pdf.getTrailer();
+ doJSONObject(p, first_object, "trailer", trailer);
}
JSON::writeDictionaryClose(p, first_object, 1);
}
@@ -1123,108 +1135,6 @@ QPDFJob::doJSONObjectinfo(Pipeline* p, bool& first, QPDF& pdf)
}
void
-QPDFJob::doJSONStream(
- Pipeline* p,
- bool& first,
- QPDF& pdf,
- QPDFObjectHandle& obj,
- std::string const& file_prefix)
-{
- Pipeline* stream_p = nullptr;
- FILE* f = nullptr;
- std::shared_ptr<Pl_StdioFile> f_pl;
- std::string filename;
- if (this->m->json_stream_data == qpdf_sj_file) {
- filename = file_prefix + "-" + QUtil::int_to_string(obj.getObjectID());
- f = QUtil::safe_fopen(filename.c_str(), "wb");
- f_pl = std::make_shared<Pl_StdioFile>("stream data", f);
- stream_p = f_pl.get();
- }
- auto j = JSON::makeDictionary();
- j.addDictionaryMember(
- "stream",
- obj.getStreamJSON(
- this->m->json_version,
- this->m->json_stream_data,
- this->m->decode_level,
- stream_p,
- filename));
-
- JSON::writeDictionaryItem(p, first, "obj:" + obj.unparse(), j, 2);
- if (f) {
- f_pl->finish();
- f_pl = nullptr;
- fclose(f);
- }
-}
-
-void
-QPDFJob::doJSONObject(
- Pipeline* p,
- bool& first,
- QPDF& pdf,
- std::string const& key,
- QPDFObjectHandle& obj)
-{
- auto j = JSON::makeDictionary();
- j.addDictionaryMember("value", obj.getJSON(this->m->json_version, true));
- JSON::writeDictionaryItem(p, first, key, j, 2);
-}
-
-void
-QPDFJob::doJSONQpdf(Pipeline* p, bool& first, QPDF& pdf)
-{
- std::string file_prefix = this->m->json_stream_prefix;
- if (this->m->json_stream_data == qpdf_sj_file) {
- if (file_prefix.empty()) {
- if (this->m->infilename.get()) {
- file_prefix = this->m->infilename.get();
- }
- if (file_prefix.empty()) {
- usage(
- "please specify --json-stream-prefix since the input file "
- "name is unknown");
- }
- }
- }
-
- JSON::writeDictionaryKey(p, first, "qpdf", 0);
- bool first_qpdf = true;
- JSON::writeDictionaryOpen(p, first_qpdf, 1);
- JSON::writeDictionaryItem(
- p, first_qpdf, "jsonversion", JSON::makeInt(this->m->json_version), 1);
- JSON::writeDictionaryItem(
- p, first_qpdf, "pdfversion", JSON::makeString(pdf.getPDFVersion()), 1);
- JSON::writeDictionaryItem(
- p,
- first_qpdf,
- "maxobjectid",
- JSON::makeInt(QIntC::to_longlong(pdf.getObjectCount())),
- 1);
- JSON::writeDictionaryKey(p, first_qpdf, "objects", 1);
- bool first_object = true;
- JSON::writeDictionaryOpen(p, first_object, 2);
- bool all_objects = m->json_objects.empty();
- std::set<QPDFObjGen> wanted_og = getWantedJSONObjects();
- std::vector<QPDFObjectHandle> objects = pdf.getAllObjects();
- for (auto& obj: objects) {
- if (all_objects || wanted_og.count(obj.getObjGen())) {
- if (obj.isStream()) {
- doJSONStream(p, first_object, pdf, obj, file_prefix);
- } else {
- doJSONObject(p, first_object, pdf, "obj:" + obj.unparse(), obj);
- }
- }
- }
- if (all_objects || m->json_objects.count("trailer")) {
- auto trailer = pdf.getTrailer();
- doJSONObject(p, first_object, pdf, "trailer", trailer);
- }
- JSON::writeDictionaryClose(p, first_object, 2);
- JSON::writeDictionaryClose(p, first_qpdf, 1);
-}
-
-void
QPDFJob::doJSONPages(Pipeline* p, bool& first, QPDF& pdf)
{
JSON::writeDictionaryKey(p, first, "pages", 0);
@@ -1603,12 +1513,12 @@ QPDFJob::json_schema(int json_version, std::set<std::string>* keys)
// The list of selectable top-level keys id duplicated in the
// following places: job.yml, QPDFJob::json_schema, and
// QPDFJob::doJSON.
- if (json_version == 1) {
- if (all_keys || keys->count("objects")) {
- schema.addDictionaryMember("objects", JSON::parse(R"({
+ if (all_keys || keys->count("objects")) {
+ schema.addDictionaryMember("objects", JSON::parse(R"({
"<n n R|trailer>": "json representation of object"
})"));
- }
+ }
+ if (json_version == 1) {
if (all_keys || keys->count("objectinfo")) {
JSON objectinfo =
schema.addDictionaryMember("objectinfo", JSON::parse(R"({
@@ -1621,17 +1531,6 @@ QPDFJob::json_schema(int json_version, std::set<std::string>* keys)
}
})"));
}
- } else {
- if (all_keys || keys->count("qpdf")) {
- schema.addDictionaryMember("qpdf", JSON::parse(R"({
- "jsonversion": "qpdf json output version",
- "pdfversion": "PDF version from PDF header",
- "maxobjectid": "Highest object ID; needed for adding new objects",
- "objects": {
- "<obj:n n R|trailer>": "json representation of object"
- }
-})"));
- }
}
if (all_keys || keys->count("pages")) {
JSON page = schema.addDictionaryMember("pages", JSON::parse(R"([
@@ -1843,17 +1742,15 @@ QPDFJob::doJSON(QPDF& pdf, Pipeline* p)
// repairing the page tree. To see the original file with any page
// tree problems and the page tree not flattened, select
// objects/objectinfo without other keys.
+ if (all_keys || m->json_keys.count("objects")) {
+ doJSONObjects(p, first, pdf);
+ }
if (this->m->json_version == 1) {
- if (all_keys || m->json_keys.count("objects")) {
- doJSONObjects(p, first, pdf);
- }
+ // "objectinfo" is not needed for version >1 since you can
+ // tell streams from other objects in "objects".
if (all_keys || m->json_keys.count("objectinfo")) {
doJSONObjectinfo(p, first, pdf);
}
- } else {
- if (all_keys || m->json_keys.count("qpdf")) {
- doJSONQpdf(p, first, pdf);
- }
}
JSON::writeDictionaryClose(p, first, 0);
@@ -1939,16 +1836,15 @@ QPDFJob::doProcessOnce(
std::function<void(QPDF*, char const*)> fn,
char const* password,
bool empty,
- bool used_for_input)
+ bool used_for_input,
+ bool main_input)
{
auto pdf = std::make_shared<QPDF>();
setQPDFOptions(*pdf);
if (empty) {
- if (!this->m->create_from_json.empty()) {
- pdf->createFromJSON(this->m->create_from_json);
- } else {
- pdf->emptyPDF();
- }
+ pdf->emptyPDF();
+ } else if (main_input && this->m->json_input) {
+ pdf->createFromJSON(this->m->infilename.get());
} else {
fn(pdf.get(), password);
}
@@ -1964,7 +1860,8 @@ QPDFJob::doProcess(
std::function<void(QPDF*, char const*)> fn,
char const* password,
bool empty,
- bool used_for_input)
+ bool used_for_input,
+ bool main_input)
{
// If a password has been specified but doesn't work, try other
// passwords that are equivalent in different character encodings.
@@ -1990,7 +1887,7 @@ QPDFJob::doProcess(
m->suppress_password_recovery) {
// There is no password, or we're not doing recovery, so just
// do the normal processing with the supplied password.
- return doProcessOnce(fn, password, empty, used_for_input);
+ return doProcessOnce(fn, password, empty, used_for_input, main_input);
}
// Get a list of otherwise encoded strings. Keep in scope for this
@@ -2018,7 +1915,7 @@ QPDFJob::doProcess(
bool warned = false;
for (auto iter = passwords.begin(); iter != passwords.end(); ++iter) {
try {
- return doProcessOnce(fn, *iter, empty, used_for_input);
+ return doProcessOnce(fn, *iter, empty, used_for_input, main_input);
} catch (QPDFExc& e) {
auto next = iter;
++next;
@@ -2042,12 +1939,16 @@ QPDFJob::doProcess(
std::shared_ptr<QPDF>
QPDFJob::processFile(
- char const* filename, char const* password, bool used_for_input)
+ char const* filename,
+ char const* password,
+ bool used_for_input,
+ bool main_input)
{
auto f1 = std::mem_fn<void(char const*, char const*)>(&QPDF::processFile);
auto fn =
std::bind(f1, std::placeholders::_1, filename, std::placeholders::_2);
- return doProcess(fn, password, strcmp(filename, "") == 0, used_for_input);
+ return doProcess(
+ fn, password, strcmp(filename, "") == 0, used_for_input, main_input);
}
std::shared_ptr<QPDF>
@@ -2056,7 +1957,7 @@ QPDFJob::processInputSource(
{
auto f1 = std::mem_fn(&QPDF::processInputSource);
auto fn = std::bind(f1, std::placeholders::_1, is, std::placeholders::_2);
- return doProcess(fn, password, false, used_for_input);
+ return doProcess(fn, password, false, used_for_input, false);
}
void
@@ -2067,7 +1968,8 @@ QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo)
}
QPDFPageDocumentHelper main_pdh(pdf);
int main_npages = QIntC::to_int(main_pdh.getAllPages().size());
- uo->pdf = processFile(uo->filename.c_str(), uo->password.get(), true);
+ uo->pdf =
+ processFile(uo->filename.c_str(), uo->password.get(), true, false);
QPDFPageDocumentHelper uo_pdh(*(uo->pdf));
int uo_npages = QIntC::to_int(uo_pdh.getAllPages().size());
try {
@@ -2324,8 +2226,8 @@ QPDFJob::copyAttachments(QPDF& pdf)
cout << prefix << ": copying attachments from " << to_copy.path
<< std::endl;
});
- auto other =
- processFile(to_copy.path.c_str(), to_copy.password.c_str(), false);
+ auto other = processFile(
+ to_copy.path.c_str(), to_copy.password.c_str(), false, false);
QPDFEmbeddedFileDocumentHelper other_efdh(*other);
auto other_attachments = other_efdh.getEmbeddedFiles();
for (auto const& iter: other_attachments) {
@@ -3176,6 +3078,7 @@ QPDFJob::setWriterOptions(QPDF& pdf, QPDFWriter& w)
std::shared_ptr<QPDF> encryption_pdf = processFile(
m->encryption_file.c_str(),
m->encryption_file_password.get(),
+ false,
false);
w.copyEncryptionParameters(*encryption_pdf);
}
@@ -3332,10 +3235,13 @@ QPDFJob::writeOutfile(QPDF& pdf)
// goes out of scope.
m->outfilename = temp_out;
} else if (strcmp(m->outfilename.get(), "-") == 0) {
- m->outfilename = 0;
+ m->outfilename = nullptr;
}
- {
- // Private scope so QPDFWriter will close the output file
+ if (this->m->json_output) {
+ writeJSON(pdf);
+ } else {
+ // QPDFWriter must have block scope so the output file will be
+ // closed after write() finishes.
QPDFWriter w(pdf, m->outfilename.get());
setWriterOptions(pdf, w);
w.write();
@@ -3375,3 +3281,47 @@ QPDFJob::writeOutfile(QPDF& pdf)
}
}
}
+
+void
+QPDFJob::writeJSON(QPDF& pdf)
+{
+ // File pipeline must have block scope so it will be closed
+ // after write.
+ std::shared_ptr<QUtil::FileCloser> fc;
+ std::shared_ptr<Pipeline> fp;
+ std::string file_prefix = this->m->json_stream_prefix;
+ if (m->outfilename.get()) {
+ QTC::TC("qpdf", "QPDFJob write json to file");
+ if (file_prefix.empty()) {
+ file_prefix = this->m->outfilename.get();
+ }
+ fc = std::make_shared<QUtil::FileCloser>(
+ QUtil::safe_fopen(this->m->outfilename.get(), "w"));
+ fp = std::make_shared<Pl_StdioFile>("json output", fc->f);
+ } else if (
+ (this->m->json_stream_data == qpdf_sj_file) && file_prefix.empty()) {
+ QTC::TC("qpdf", "QPDFJob need json-stream-prefix for stdout");
+ usage("please specify --json-stream-prefix since the input file "
+ "name is unknown");
+ } else {
+ QTC::TC("qpdf", "QPDFJob write json to stdout");
+ fp = std::make_shared<Pl_OStream>("json output", *this->m->cout);
+ }
+ std::set<std::string> json_objects;
+ if (this->m->json_objects.count("trailer")) {
+ json_objects.insert("trailer");
+ }
+ auto wanted = getWantedJSONObjects();
+ for (auto const& og: wanted) {
+ std::ostringstream s;
+ s << "obj:" << og.getObj() << " " << og.getGen() << " R";
+ json_objects.insert(s.str());
+ }
+ pdf.writeJSON(
+ this->m->json_output,
+ fp.get(),
+ this->m->decode_level,
+ this->m->json_stream_data,
+ file_prefix,
+ json_objects);
+}
diff --git a/libqpdf/QPDFJob_argv.cc b/libqpdf/QPDFJob_argv.cc
index 3058d166..a6fb5df7 100644
--- a/libqpdf/QPDFJob_argv.cc
+++ b/libqpdf/QPDFJob_argv.cc
@@ -101,13 +101,6 @@ ArgParser::argReplaceInput()
}
void
-ArgParser::argCreateFromJson(std::string const& arg)
-{
- c_main->createFromJson(arg);
- this->gave_input = true;
-}
-
-void
ArgParser::argVersion()
{
auto whoami = this->ap.getProgname();
diff --git a/libqpdf/QPDFJob_config.cc b/libqpdf/QPDFJob_config.cc
index 1aa680e8..ecdeafe3 100644
--- a/libqpdf/QPDFJob_config.cc
+++ b/libqpdf/QPDFJob_config.cc
@@ -27,8 +27,7 @@ QPDFJob::Config::emptyInput()
// Various places in QPDFJob.cc know that the empty string for
// infile means empty. We set it to something other than a
// null pointer as an indication that some input source has
- // been specified. The --create-from-json option also sets
- // infilename to empty. This approach means that passing "" as
+ // been specified. This approach means that passing "" as
// the argument to inputFile in job JSON, or equivalently
// using "" as a positional command-line argument would be the
// same as --empty. This probably isn't worth blocking or
@@ -265,6 +264,7 @@ QPDFJob::Config::jsonObject(std::string const& parameter)
QPDFJob::Config*
QPDFJob::Config::jsonStreamData(std::string const& parameter)
{
+ o.m->json_stream_data_set = true;
if (parameter == "none") {
o.m->json_stream_data = qpdf_sj_none;
} else if (parameter == "inline") {
@@ -286,22 +286,28 @@ QPDFJob::Config::jsonStreamPrefix(std::string const& parameter)
}
QPDFJob::Config*
-QPDFJob::Config::toJson()
+QPDFJob::Config::jsonInput()
{
- json("latest");
- jsonStreamData("inline");
- jsonKey("qpdf");
- decodeLevel("none");
+ o.m->json_input = true;
return this;
}
QPDFJob::Config*
-QPDFJob::Config::createFromJson(std::string const& parameter)
+QPDFJob::Config::jsonOutput(std::string const& parameter)
{
- // See comments in emptyInput() about setting infilename to the
- // empty string.
- o.m->infilename = QUtil::make_shared_cstr("");
- o.m->create_from_json = parameter;
+ std::string v = parameter;
+ if (parameter == "latest") {
+ v = "2";
+ }
+ if (v != "2") {
+ usage("only version 2 is supported for --json-output");
+ }
+ o.m->json_output = QUtil::string_to_int(v.c_str());
+ if (!o.m->json_stream_data_set) {
+ // No need to set json_stream_data_set -- that indicates
+ // explicit use of --json-stream-data.
+ o.m->json_stream_data = qpdf_sj_inline;
+ }
return this;
}
diff --git a/libqpdf/QPDFJob_json.cc b/libqpdf/QPDFJob_json.cc
index 9538153a..fcdeb666 100644
--- a/libqpdf/QPDFJob_json.cc
+++ b/libqpdf/QPDFJob_json.cc
@@ -251,12 +251,6 @@ Handlers::setupEmpty()
}
void
-Handlers::setupCreateFromJson()
-{
- addParameter([this](char const* p) { c_main->createFromJson(p); });
-}
-
-void
Handlers::setupOutputFile()
{
addParameter([this](char const* p) { c_main->outputFile(p); });
diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc
index 1037a2cf..c421ce41 100644
--- a/libqpdf/QPDF_json.cc
+++ b/libqpdf/QPDF_json.cc
@@ -2,6 +2,7 @@
#include <qpdf/FileInputSource.hh>
#include <qpdf/Pl_Base64.hh>
+#include <qpdf/Pl_StdioFile.hh>
#include <qpdf/QIntC.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QUtil.hh>
@@ -13,7 +14,7 @@
// | st_initial
// { | -> st_top
-// "qpdf": { | -> st_qpdf
+// "qpdf-v2": { | -> st_qpdf
// "objects": { | -> st_objects
// "obj:1 0 R": { | -> st_object_top
// "value": { | -> st_object
@@ -93,7 +94,6 @@ QPDF::JSONReactor::JSONReactor(
parse_error(false),
saw_qpdf(false),
saw_objects(false),
- saw_json_version(false),
saw_pdf_version(false),
saw_trailer(false),
state(st_initial),
@@ -154,21 +154,17 @@ QPDF::JSONReactor::containerEnd(JSON const& value)
QTC::TC("qpdf", "QPDF_json missing qpdf");
error(0, "\"qpdf\" object was not seen");
} else {
- if (!this->saw_json_version) {
- QTC::TC("qpdf", "QPDF_json missing json version");
- error(0, "\"qpdf.jsonversion\" was not seen");
- }
if (must_be_complete && !this->saw_pdf_version) {
QTC::TC("qpdf", "QPDF_json missing pdf version");
- error(0, "\"qpdf.pdfversion\" was not seen");
+ error(0, "\"qpdf-v2.pdfversion\" was not seen");
}
if (!this->saw_objects) {
QTC::TC("qpdf", "QPDF_json missing objects");
- error(0, "\"qpdf.objects\" was not seen");
+ error(0, "\"qpdf-v2.objects\" was not seen");
} else {
if (must_be_complete && !this->saw_trailer) {
QTC::TC("qpdf", "QPDF_json missing trailer");
- error(0, "\"qpdf.objects.trailer\" was not seen");
+ error(0, "\"qpdf-v2.objects.trailer\" was not seen");
}
}
}
@@ -279,7 +275,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
QTC::TC("qpdf", "QPDF_json ignoring in st_ignore");
// ignore
} else if (state == st_top) {
- if (key == "qpdf") {
+ if (key == "qpdf-v2") {
this->saw_qpdf = true;
nestedState(key, value, st_qpdf);
} else {
@@ -289,14 +285,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
next_state = st_ignore;
}
} else if (state == st_qpdf) {
- if (key == "jsonversion") {
- this->saw_json_version = true;
- std::string v;
- if (!(value.getNumber(v) && (v == "2"))) {
- QTC::TC("qpdf", "QPDF_json bad json version");
- error(value.getStart(), "only JSON version 2 is supported");
- }
- } else if (key == "pdfversion") {
+ if (key == "pdfversion") {
this->saw_pdf_version = true;
bool version_okay = false;
std::string v;
@@ -567,3 +556,110 @@ QPDF::importJSON(std::shared_ptr<InputSource> is, bool must_be_complete)
// std::cout << oh.unparseResolved() << std::endl;
// }
}
+
+void
+QPDF::writeJSONStream(
+ int version,
+ Pipeline* p,
+ bool& first,
+ std::string const& key,
+ QPDFObjectHandle& obj,
+ qpdf_stream_decode_level_e decode_level,
+ qpdf_json_stream_data_e json_stream_data,
+ std::string const& file_prefix)
+{
+ Pipeline* stream_p = nullptr;
+ FILE* f = nullptr;
+ std::shared_ptr<Pl_StdioFile> f_pl;
+ std::string filename;
+ if (json_stream_data == qpdf_sj_file) {
+ filename = file_prefix + "-" + QUtil::int_to_string(obj.getObjectID());
+ f = QUtil::safe_fopen(filename.c_str(), "wb");
+ f_pl = std::make_shared<Pl_StdioFile>("stream data", f);
+ stream_p = f_pl.get();
+ }
+ auto j = JSON::makeDictionary();
+ j.addDictionaryMember(
+ "stream",
+ obj.getStreamJSON(
+ version, json_stream_data, decode_level, stream_p, filename));
+
+ JSON::writeDictionaryItem(p, first, key, j, 2);
+ if (f) {
+ f_pl->finish();
+ f_pl = nullptr;
+ fclose(f);
+ }
+}
+
+void
+QPDF::writeJSONObject(
+ int version,
+ Pipeline* p,
+ bool& first,
+ std::string const& key,
+ QPDFObjectHandle& obj)
+{
+ auto j = JSON::makeDictionary();
+ j.addDictionaryMember("value", obj.getJSON(version, true));
+ JSON::writeDictionaryItem(p, first, key, j, 2);
+}
+
+void
+QPDF::writeJSON(
+ int version,
+ Pipeline* p,
+ qpdf_stream_decode_level_e decode_level,
+ qpdf_json_stream_data_e json_stream_data,
+ std::string const& file_prefix,
+ std::set<std::string> wanted_objects)
+{
+ if (version != 2) {
+ throw std::runtime_error(
+ "QPDF::writeJSON: only version 2 is supported");
+ }
+ bool first = true;
+ JSON::writeDictionaryOpen(p, first, 0);
+ JSON::writeDictionaryKey(p, first, "qpdf-v2", 0);
+ bool first_qpdf = true;
+ JSON::writeDictionaryOpen(p, first_qpdf, 1);
+ JSON::writeDictionaryItem(
+ p, first_qpdf, "pdfversion", JSON::makeString(getPDFVersion()), 1);
+ JSON::writeDictionaryItem(
+ p,
+ first_qpdf,
+ "maxobjectid",
+ JSON::makeInt(QIntC::to_longlong(getObjectCount())),
+ 1);
+ JSON::writeDictionaryKey(p, first_qpdf, "objects", 1);
+ bool first_object = true;
+ JSON::writeDictionaryOpen(p, first_object, 2);
+ bool all_objects = wanted_objects.empty();
+ std::vector<QPDFObjectHandle> objects = getAllObjects();
+ for (auto& obj: objects) {
+ std::string key = "obj:" + obj.unparse();
+ if (all_objects || wanted_objects.count(key)) {
+ if (obj.isStream()) {
+ writeJSONStream(
+ version,
+ p,
+ first_object,
+ key,
+ obj,
+ decode_level,
+ json_stream_data,
+ file_prefix);
+ } else {
+ writeJSONObject(version, p, first_object, key, obj);
+ }
+ }
+ }
+ if (all_objects || wanted_objects.count("trailer")) {
+ auto trailer = getTrailer();
+ writeJSONObject(version, p, first_object, "trailer", trailer);
+ }
+ JSON::writeDictionaryClose(p, first_object, 2);
+ JSON::writeDictionaryClose(p, first_qpdf, 1);
+ JSON::writeDictionaryClose(p, first, 0);
+ *p << "\n";
+}
diff --git a/libqpdf/qpdf/auto_job_decl.hh b/libqpdf/qpdf/auto_job_decl.hh
index 8c656986..02f0ca91 100644
--- a/libqpdf/qpdf/auto_job_decl.hh
+++ b/libqpdf/qpdf/auto_job_decl.hh
@@ -28,7 +28,6 @@ void argOverlay();
void argPages();
void argReplaceInput();
void argUnderlay();
-void argCreateFromJson(std::string const&);
void argPagesPositional(std::string const&);
void argPagesPassword(std::string const&);
void argEndPages();
diff --git a/libqpdf/qpdf/auto_job_help.hh b/libqpdf/qpdf/auto_job_help.hh
index e17412d0..7d88155b 100644
--- a/libqpdf/qpdf/auto_job_help.hh
+++ b/libqpdf/qpdf/auto_job_help.hh
@@ -798,10 +798,6 @@ depth in the JSON section of the manual. "version" may be a
specific version or "latest". Run qpdf --json-help for a
description of the generated JSON object.
)");
-ap.addOptionHelp("--to-json", "json", "serialize to JSON", R"(Shortcut for options useful for serializing PDF to JSON:
---json=latest --json-stream-data=inline
- --json-key=qpdf --decode-level=none
-)");
ap.addOptionHelp("--json-help", "json", "show format of JSON output", R"(Describe the format of the JSON output by writing to standard
output a JSON object with the same keys and with values
containing descriptive text.
@@ -836,11 +832,16 @@ name as the prefix for stream data files. Whatever is given here
will be appended with -nnn to create the name of the file that
will contain the data for the stream stream in object nnn.
)");
-ap.addOptionHelp("--create-from-json", "json", "create PDF from qpdf JSON", R"(--create-from-json=qpdf-json-file
+ap.addOptionHelp("--json-output", "json", "serialize to JSON", R"(--json-output=version
-Create a PDF file from the prior output of qpdf --json. See the
-"QPDF JSON Format" section of the manual for information about
-how to use this option.
+The output file will be qpdf JSON format at the given version.
+Only version 2 is supported. See also --json-stream-data
+and --json-stream-prefix
+)");
+ap.addOptionHelp("--json-input", "json", "input file is qpdf JSON", R"(Treat the input file as a JSON file in qpdf JSON format as
+written by qpdf --json-output. See the "QPDF JSON Format"
+section of the manual for information about how to use this
+option.
)");
ap.addOptionHelp("--update-from-json", "json", "update a PDF from qpdf JSON", R"(--update-from-json=qpdf-json-file
diff --git a/libqpdf/qpdf/auto_job_init.hh b/libqpdf/qpdf/auto_job_init.hh
index 9446811f..f582c6ec 100644
--- a/libqpdf/qpdf/auto_job_init.hh
+++ b/libqpdf/qpdf/auto_job_init.hh
@@ -19,9 +19,10 @@ static char const* decode_level_choices[] = {"none", "generalized", "specialized
static char const* object_streams_choices[] = {"disable", "preserve", "generate", 0};
static char const* remove_unref_choices[] = {"auto", "yes", "no", 0};
static char const* flatten_choices[] = {"all", "print", "screen", 0};
-static char const* json_version_choices[] = {"1", "2", "latest", 0};
-static char const* json_key_choices[] = {"acroform", "attachments", "encrypt", "objectinfo", "objects", "outlines", "pagelabels", "pages", "qpdf", 0};
+static char const* json_key_choices[] = {"acroform", "attachments", "encrypt", "objectinfo", "objects", "outlines", "pagelabels", "pages", 0};
+static char const* json_output_choices[] = {"2", "latest", 0};
static char const* json_stream_data_choices[] = {"none", "inline", "file", 0};
+static char const* json_version_choices[] = {"1", "2", "latest", 0};
static char const* print128_choices[] = {"full", "low", "none", 0};
static char const* modify128_choices[] = {"all", "annotate", "form", "assembly", "none", 0};
@@ -49,6 +50,7 @@ this->ap.addBare("flatten-rotation", [this](){c_main->flattenRotation();});
this->ap.addBare("generate-appearances", [this](){c_main->generateAppearances();});
this->ap.addBare("ignore-xref-streams", [this](){c_main->ignoreXrefStreams();});
this->ap.addBare("is-encrypted", [this](){c_main->isEncrypted();});
+this->ap.addBare("json-input", [this](){c_main->jsonInput();});
this->ap.addBare("keep-inline-images", [this](){c_main->keepInlineImages();});
this->ap.addBare("linearize", [this](){c_main->linearize();});
this->ap.addBare("list-attachments", [this](){c_main->listAttachments();});
@@ -79,7 +81,6 @@ this->ap.addBare("static-id", [this](){c_main->staticId();});
this->ap.addBare("suppress-password-recovery", [this](){c_main->suppressPasswordRecovery();});
this->ap.addBare("suppress-recovery", [this](){c_main->suppressRecovery();});
this->ap.addBare("test-json-schema", [this](){c_main->testJsonSchema();});
-this->ap.addBare("to-json", [this](){c_main->toJson();});
this->ap.addBare("underlay", b(&ArgParser::argUnderlay));
this->ap.addBare("verbose", [this](){c_main->verbose();});
this->ap.addBare("warning-exit-0", [this](){c_main->warningExit0();});
@@ -104,7 +105,6 @@ this->ap.addRequiredParameter("rotate", [this](std::string const& x){c_main->rot
this->ap.addRequiredParameter("show-attachment", [this](std::string const& x){c_main->showAttachment(x);}, "attachment");
this->ap.addRequiredParameter("show-object", [this](std::string const& x){c_main->showObject(x);}, "trailer");
this->ap.addRequiredParameter("json-stream-prefix", [this](std::string const& x){c_main->jsonStreamPrefix(x);}, "stream-file-prefix");
-this->ap.addRequiredParameter("create-from-json", p(&ArgParser::argCreateFromJson), "qpdf-json file");
this->ap.addRequiredParameter("update-from-json", [this](std::string const& x){c_main->updateFromJson(x);}, "qpdf-json file");
this->ap.addOptionalParameter("collate", [this](std::string const& x){c_main->collate(x);});
this->ap.addOptionalParameter("split-pages", [this](std::string const& x){c_main->splitPages(x);});
@@ -112,13 +112,14 @@ this->ap.addChoices("compress-streams", [this](std::string const& x){c_main->com
this->ap.addChoices("decode-level", [this](std::string const& x){c_main->decodeLevel(x);}, true, decode_level_choices);
this->ap.addChoices("flatten-annotations", [this](std::string const& x){c_main->flattenAnnotations(x);}, true, flatten_choices);
this->ap.addChoices("json-key", [this](std::string const& x){c_main->jsonKey(x);}, true, json_key_choices);
+this->ap.addChoices("json-output", [this](std::string const& x){c_main->jsonOutput(x);}, true, json_output_choices);
+this->ap.addChoices("json-stream-data", [this](std::string const& x){c_main->jsonStreamData(x);}, true, json_stream_data_choices);
this->ap.addChoices("keep-files-open", [this](std::string const& x){c_main->keepFilesOpen(x);}, true, yn_choices);
this->ap.addChoices("normalize-content", [this](std::string const& x){c_main->normalizeContent(x);}, true, yn_choices);
this->ap.addChoices("object-streams", [this](std::string const& x){c_main->objectStreams(x);}, true, object_streams_choices);
this->ap.addChoices("password-mode", [this](std::string const& x){c_main->passwordMode(x);}, true, password_mode_choices);
this->ap.addChoices("remove-unreferenced-resources", [this](std::string const& x){c_main->removeUnreferencedResources(x);}, true, remove_unref_choices);
this->ap.addChoices("stream-data", [this](std::string const& x){c_main->streamData(x);}, true, stream_data_choices);
-this->ap.addChoices("json-stream-data", [this](std::string const& x){c_main->jsonStreamData(x);}, true, json_stream_data_choices);
this->ap.addChoices("json", [this](std::string const& x){c_main->json(x);}, false, json_version_choices);
this->ap.registerOptionTable("pages", b(&ArgParser::argEndPages));
this->ap.addPositional(p(&ArgParser::argPagesPositional));
diff --git a/libqpdf/qpdf/auto_job_json_decl.hh b/libqpdf/qpdf/auto_job_json_decl.hh
index 8160ca5a..f02dc657 100644
--- a/libqpdf/qpdf/auto_job_json_decl.hh
+++ b/libqpdf/qpdf/auto_job_json_decl.hh
@@ -8,7 +8,6 @@
void setupInputFile();
void setupPassword();
void setupEmpty();
-void setupCreateFromJson();
void setupOutputFile();
void setupReplaceInput();
void beginEncrypt(JSON);
diff --git a/libqpdf/qpdf/auto_job_json_init.hh b/libqpdf/qpdf/auto_job_json_init.hh
index ee124d0d..125250e6 100644
--- a/libqpdf/qpdf/auto_job_json_init.hh
+++ b/libqpdf/qpdf/auto_job_json_init.hh
@@ -12,9 +12,10 @@ static char const* decode_level_choices[] = {"none", "generalized", "specialized
static char const* object_streams_choices[] = {"disable", "preserve", "generate", 0};
static char const* remove_unref_choices[] = {"auto", "yes", "no", 0};
static char const* flatten_choices[] = {"all", "print", "screen", 0};
-static char const* json_version_choices[] = {"1", "2", "latest", 0};
-static char const* json_key_choices[] = {"acroform", "attachments", "encrypt", "objectinfo", "objects", "outlines", "pagelabels", "pages", "qpdf", 0};
+static char const* json_key_choices[] = {"acroform", "attachments", "encrypt", "objectinfo", "objects", "outlines", "pagelabels", "pages", 0};
+static char const* json_output_choices[] = {"2", "latest", 0};
static char const* json_stream_data_choices[] = {"none", "inline", "file", 0};
+static char const* json_version_choices[] = {"1", "2", "latest", 0};
static char const* print128_choices[] = {"full", "low", "none", 0};
static char const* modify128_choices[] = {"all", "annotate", "form", "assembly", "none", 0};
@@ -30,9 +31,9 @@ popHandler(); // key: passwordFile
pushKey("empty");
setupEmpty();
popHandler(); // key: empty
-pushKey("createFromJson");
-setupCreateFromJson();
-popHandler(); // key: createFromJson
+pushKey("jsonInput");
+addBare([this]() { c_main->jsonInput(); });
+popHandler(); // key: jsonInput
pushKey("outputFile");
setupOutputFile();
popHandler(); // key: outputFile
@@ -105,6 +106,9 @@ popHandler(); // key: progress
pushKey("splitPages");
addParameter([this](std::string const& p) { c_main->splitPages(p); });
popHandler(); // key: splitPages
+pushKey("jsonOutput");
+addChoices(json_output_choices, true, [this](std::string const& p) { c_main->jsonOutput(p); });
+popHandler(); // key: jsonOutput
pushKey("encrypt");
beginDict(bindJSON(&Handlers::beginEncrypt), bindBare(&Handlers::endEncrypt)); // .encrypt
pushKey("userPassword");
@@ -262,9 +266,6 @@ popHandler(); // key: jsonStreamData
pushKey("jsonStreamPrefix");
addParameter([this](std::string const& p) { c_main->jsonStreamPrefix(p); });
popHandler(); // key: jsonStreamPrefix
-pushKey("toJson");
-addBare([this]() { c_main->toJson(); });
-popHandler(); // key: toJson
pushKey("updateFromJson");
addParameter([this](std::string const& p) { c_main->updateFromJson(p); });
popHandler(); // key: updateFromJson
diff --git a/libqpdf/qpdf/auto_job_schema.hh b/libqpdf/qpdf/auto_job_schema.hh
index 0b9e29a9..0fc187fe 100644
--- a/libqpdf/qpdf/auto_job_schema.hh
+++ b/libqpdf/qpdf/auto_job_schema.hh
@@ -3,7 +3,7 @@ static constexpr char const* JOB_SCHEMA_DATA = R"({
"password": "password for encrypted file",
"passwordFile": "read password from a file",
"empty": "use empty file as input",
- "createFromJson": "create PDF from qpdf JSON",
+ "jsonInput": "input file is qpdf JSON",
"outputFile": "output filename",
"replaceInput": "overwrite input with output",
"qdf": "enable viewing PDF code in a text editor",
@@ -28,6 +28,7 @@ static constexpr char const* JOB_SCHEMA_DATA = R"({
"forceVersion": "set output PDF version",
"progress": "show progress when writing",
"splitPages": "write pages to separate files",
+ "jsonOutput": "serialize to JSON",
"encrypt": {
"userPassword": "user password",
"ownerPassword": "owner password",
@@ -87,7 +88,6 @@ static constexpr char const* JOB_SCHEMA_DATA = R"({
],
"jsonStreamData": "how to handle streams in json output",
"jsonStreamPrefix": "prefix for json stream data files",
- "toJson": "serialize to JSON",
"updateFromJson": "update a PDF from qpdf JSON",
"allowWeakCrypto": "allow insecure cryptographic algorithms",
"keepFilesOpen": "manage keeping multiple files open",