From 6f43bf8de36b08c55b172b4f4133c79657651666 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Wed, 18 May 2022 18:22:57 -0400 Subject: 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. --- libqpdf/QPDFJob.cc | 280 +++++++++++++++---------------------- libqpdf/QPDFJob_argv.cc | 7 - libqpdf/QPDFJob_config.cc | 30 ++-- libqpdf/QPDFJob_json.cc | 6 - libqpdf/QPDF_json.cc | 132 ++++++++++++++--- libqpdf/qpdf/auto_job_decl.hh | 1 - libqpdf/qpdf/auto_job_help.hh | 17 +-- libqpdf/qpdf/auto_job_init.hh | 11 +- libqpdf/qpdf/auto_job_json_decl.hh | 1 - libqpdf/qpdf/auto_job_json_init.hh | 17 +-- libqpdf/qpdf/auto_job_schema.hh | 4 +- 11 files changed, 273 insertions(+), 233 deletions(-) (limited to 'libqpdf') 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 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"); } } @@ -1060,6 +1057,26 @@ QPDFJob::getWantedJSONObjects() return wanted_og; } +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) { @@ -1070,22 +1087,17 @@ QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf) std::set wanted_og = getWantedJSONObjects(); std::vector 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); } @@ -1122,108 +1134,6 @@ QPDFJob::doJSONObjectinfo(Pipeline* p, bool& first, QPDF& pdf) JSON::writeDictionaryClose(p, first_object, 1); } -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 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("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 wanted_og = getWantedJSONObjects(); - std::vector 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) { @@ -1603,12 +1513,12 @@ QPDFJob::json_schema(int json_version, std::set* 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"({ "": "json representation of object" })")); - } + } + if (json_version == 1) { if (all_keys || keys->count("objectinfo")) { JSON objectinfo = schema.addDictionaryMember("objectinfo", JSON::parse(R"({ @@ -1619,17 +1529,6 @@ QPDFJob::json_schema(int json_version, std::set* keys) "length": "if stream, its length, otherwise null" } } -})")); - } - } 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": { - "": "json representation of object" - } })")); } } @@ -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 fn, char const* password, bool empty, - bool used_for_input) + bool used_for_input, + bool main_input) { auto pdf = std::make_shared(); 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 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 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(&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 @@ -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 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 fc; + std::shared_ptr 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::safe_fopen(this->m->outfilename.get(), "w")); + fp = std::make_shared("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("json output", *this->m->cout); + } + std::set 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 @@ -100,13 +100,6 @@ ArgParser::argReplaceInput() this->gave_output = true; } -void -ArgParser::argCreateFromJson(std::string const& arg) -{ - c_main->createFromJson(arg); - this->gave_input = true; -} - void ArgParser::argVersion() { 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 @@ -250,12 +250,6 @@ Handlers::setupEmpty() addBare([this]() { c_main->emptyInput(); }); } -void -Handlers::setupCreateFromJson() -{ - addParameter([this](char const* p) { c_main->createFromJson(p); }); -} - void Handlers::setupOutputFile() { 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 #include +#include #include #include #include @@ -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 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 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("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 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 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", -- cgit v1.2.3-54-g00ecf