diff --git a/ChangeLog b/ChangeLog index 53e1e8d6..8cf93186 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2022-05-04 Jay Berkenbilt + * JSON: add a new "blob" type that takes a function to write data + into. The blob is serialized as a base64-encoded representation of + whatever is written to the function. + * FileInputSource has new constructors that eliminate the need to call setFilename or setFile in most cases. diff --git a/TODO b/TODO index b53d175f..11204e6f 100644 --- a/TODO +++ b/TODO @@ -51,11 +51,6 @@ library, when context is available, to have a pipeline rather than a FILE* or std::ostream. This makes it possible for people to capture output more flexibly. -Have a json blob defined by a function that takes a pipeline and -writes data to the pipeline. It's writer should create a Pl_Base64 -> -Pl_Concatenate in front of the pipeline passed to write and call the -function with that. - For json output, do not unparse to string. Use the writers instead. Write incrementally. This changes ordering only, but we should be able manually update the test output for those cases. Objects should be diff --git a/cSpell.json b/cSpell.json index cba39d9a..1cc0363e 100644 --- a/cSpell.json +++ b/cSpell.json @@ -199,6 +199,7 @@ "itemizedlist", "jarr", "jbig", + "jblob", "jdimension", "jdouble", "jerr", diff --git a/include/qpdf/JSON.hh b/include/qpdf/JSON.hh index 435c2362..4f4162fc 100644 --- a/include/qpdf/JSON.hh +++ b/include/qpdf/JSON.hh @@ -122,6 +122,13 @@ class JSON QPDF_DLL static JSON makeNull(); + // A blob serializes as a string. The function will be called by + // JSON with a pipeline and should write binary data to the + // pipeline but not call finish(). JSON will call finish() at the + // right time. + QPDF_DLL + static JSON makeBlob(std::function); + QPDF_DLL bool isArray() const; @@ -323,6 +330,13 @@ class JSON virtual ~JSON_null() = default; virtual void write(Pipeline*, size_t depth) const; }; + struct JSON_blob: public JSON_value + { + JSON_blob(std::function fn); + virtual ~JSON_blob() = default; + virtual void write(Pipeline*, size_t depth) const; + std::function fn; + }; JSON(std::shared_ptr); diff --git a/libqpdf/JSON.cc b/libqpdf/JSON.cc index 71ea33d7..8549b7ed 100644 --- a/libqpdf/JSON.cc +++ b/libqpdf/JSON.cc @@ -1,6 +1,8 @@ #include #include +#include +#include #include #include #include @@ -168,6 +170,22 @@ JSON::JSON_null::write(Pipeline* p, size_t) const *p << "null"; } +JSON::JSON_blob::JSON_blob(std::function fn) : + fn(fn) +{ +} + +void +JSON::JSON_blob::write(Pipeline* p, size_t) const +{ + *p << "\""; + Pl_Concatenate cat("blob concatenate", p); + Pl_Base64 base64("blob base64", &cat, Pl_Base64::a_encode); + fn(&base64); + base64.finish(); + *p << "\""; +} + void JSON::write(Pipeline* p, size_t depth) const { @@ -306,6 +324,12 @@ JSON::makeNull() return JSON(std::make_shared()); } +JSON +JSON::makeBlob(std::function fn) +{ + return JSON(std::make_shared(fn)); +} + bool JSON::isArray() const { diff --git a/libtests/json.cc b/libtests/json.cc index 3844222f..b386e6eb 100644 --- a/libtests/json.cc +++ b/libtests/json.cc @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -113,6 +114,19 @@ test_main() {"c", "[\n true\n]"}, }; assert(dvalue == xdvalue); + auto blob_data = [](Pipeline* p) { + *p << "\x01\x02\x03\x04\x05\xff\xfe\xfd\xfc\xfb"; + }; + JSON jblob = JSON::makeDictionary(); + jblob.addDictionaryMember("normal", JSON::parse(R"("string")")); + jblob.addDictionaryMember("blob", JSON::makeBlob(blob_data)); + // cSpell:ignore AQIDBAX + check( + jblob, + "{\n" + " \"blob\": \"AQIDBAX//v38+w==\",\n" + " \"normal\": \"string\"\n" + "}"); } static void