CharReader/Builder
authorChristopher Dunn <cdunn2001@gmail.com>
Thu, 29 Jan 2015 20:29:40 +0000 (14:29 -0600)
committerChristopher Dunn <cdunn2001@gmail.com>
Sun, 8 Feb 2015 19:22:09 +0000 (13:22 -0600)
* CharReaderBuilder is similar to StreamWriterBuilder.
* use rdbuf(), since getline(string) is not required to handle EOF as delimiter

include/json/reader.h
src/lib_json/json_reader.cpp
src/test_lib_json/main.cpp

index bd2204b..1111a7b 100644 (file)
@@ -14,6 +14,7 @@
 #include <iosfwd>
 #include <stack>
 #include <string>
+#include <istream>
 
 // Disable warning C4251: <data member>: <type> needs to have dll-interface to
 // be used by...
@@ -78,7 +79,7 @@ public:
    document to read.
    * \param endDoc Pointer on the end of the UTF-8 encoded string of the
    document to read.
-   \               Must be >= beginDoc.
+   *               Must be >= beginDoc.
    * \param root [out] Contains the root value of the document if it was
    *             successfully parsed.
    * \param collectComments \c true to collect comment and allow writing them
@@ -238,8 +239,69 @@ private:
   std::string commentsBefore_;
   Features features_;
   bool collectComments_;
+};  // Reader
+
+/** Interface for reading JSON from a char array.
+ */
+class JSON_API CharReader {
+public:
+  virtual ~CharReader() {}
+  /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a>
+   document.
+   * The document must be a UTF-8 encoded string containing the document to read.
+   *
+   * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the
+   document to read.
+   * \param endDoc Pointer on the end of the UTF-8 encoded string of the
+   document to read.
+   *        Must be >= beginDoc.
+   * \param root [out] Contains the root value of the document if it was
+   *             successfully parsed.
+   * \param errs [out] Formatted error messages (if not NULL)
+   *        a user friendly string that lists errors in the parsed
+   * document.
+   * \return \c true if the document was successfully parsed, \c false if an
+   error occurred.
+   */
+  virtual bool parse(
+      char const* beginDoc, char const* endDoc,
+      Value* root, std::string* errs) = 0;
+
+  class Factory {
+  public:
+    /// \brief Allocate a CharReader via operator new().
+    virtual CharReader* newCharReader() const = 0;
+  };  // Factory
+};  // CharReader
+
+class CharReaderBuilder : public CharReader::Factory {
+  bool collectComments_;
+  Features features_;
+public:
+  CharReaderBuilder();
+
+  CharReaderBuilder& withCollectComments(bool v) {
+    collectComments_ = v;
+    return *this;
+  }
+
+  CharReaderBuilder& withFeatures(Features const& v) {
+    features_ = v;
+    return *this;
+  }
+
+  virtual CharReader* newCharReader() const;
 };
 
+/** Consume entire stream and use its begin/end.
+  * Someday we might have a real StreamReader, but for now this
+  * is convenient.
+  */
+bool parseFromStream(
+    CharReader::Factory const&,
+    std::istream&,
+    Value* root, std::string* errs);
+
 /** \brief Read from 'sin' into 'root'.
 
  Always keep comments from the input JSON.
index d2cff9a..119c3da 100644 (file)
@@ -14,6 +14,8 @@
 #include <cassert>
 #include <cstring>
 #include <istream>
+#include <sstream>
+#include <memory>
 
 #if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below
 #define snprintf _snprintf
 
 namespace Json {
 
+#if __cplusplus >= 201103L
+typedef std::unique_ptr<CharReader> CharReaderPtr;
+#else
+typedef std::auto_ptr<CharReader>   CharReaderPtr;
+#endif
+
 // Implementation of class Features
 // ////////////////////////////////
 
@@ -882,13 +890,61 @@ bool Reader::good() const {
   return !errors_.size();
 }
 
+class OldReader : public CharReader {
+  bool const collectComments_;
+  Reader reader_;
+public:
+  OldReader(
+    bool collectComments,
+    Features const& features)
+  : collectComments_(collectComments)
+  , reader_(features)
+  {}
+  virtual bool parse(
+      char const* beginDoc, char const* endDoc,
+      Value* root, std::string* errs) {
+    bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_);
+    if (errs) {
+      *errs = reader_.getFormattedErrorMessages();
+    }
+    return ok;
+  }
+};
+
+CharReaderBuilder::CharReaderBuilder()
+  : collectComments_(true)
+  , features_(Features::all())
+{}
+CharReader* CharReaderBuilder::newCharReader() const
+{
+  return new OldReader(collectComments_, features_);
+}
+
+//////////////////////////////////
+// global functions
+
+bool parseFromStream(
+    CharReader::Factory const& fact, std::istream& sin,
+    Value* root, std::string* errs)
+{
+  std::ostringstream ssin;
+  ssin << sin.rdbuf();
+  std::string doc = ssin.str();
+  char const* begin = doc.data();
+  char const* end = begin + doc.size();
+  // Note that we do not actually need a null-terminator.
+  CharReaderPtr const reader(fact.newCharReader());
+  return reader->parse(begin, end, root, errs);
+}
+
 std::istream& operator>>(std::istream& sin, Value& root) {
-  Json::Reader reader;
-  bool ok = reader.parse(sin, root, true);
+  CharReaderBuilder b;
+  std::string errs;
+  bool ok = parseFromStream(b, sin, &root, &errs);
   if (!ok) {
     fprintf(stderr,
             "Error from reader: %s",
-            reader.getFormattedErrorMessages().c_str());
+            errs.c_str());
 
     JSON_FAIL_MESSAGE("reader error");
   }
index 8af3e19..e57fbc3 100644 (file)
@@ -7,6 +7,7 @@
 #include <json/config.h>
 #include <json/json.h>
 #include <stdexcept>
+#include <cstring>
 
 // Make numeric limits more convenient to talk about.
 // Assumes int type in 32 bits.
@@ -1617,6 +1618,90 @@ JSONTEST_FIXTURE(ReaderTest, parseWithDetailError) {
   JSONTEST_ASSERT(errors.at(0).message == "Bad escape sequence in string");
 }
 
+struct CharReaderTest : JsonTest::TestCase {};
+
+JSONTEST_FIXTURE(CharReaderTest, parseWithNoErrors) {
+  Json::CharReaderBuilder b;
+  Json::CharReader* reader(b.newCharReader());
+  std::string errs;
+  Json::Value root;
+  char const doc[] = "{ \"property\" : \"value\" }";
+  bool ok = reader->parse(
+      doc, doc + std::strlen(doc),
+      &root, &errs);
+  JSONTEST_ASSERT(ok);
+  JSONTEST_ASSERT(errs.size() == 0);
+  delete reader;
+}
+
+JSONTEST_FIXTURE(CharReaderTest, parseWithNoErrorsTestingOffsets) {
+  Json::CharReaderBuilder b;
+  Json::CharReader* reader(b.newCharReader());
+  std::string errs;
+  Json::Value root;
+  char const doc[] =
+                         "{ \"property\" : [\"value\", \"value2\"], \"obj\" : "
+                         "{ \"nested\" : 123, \"bool\" : true}, \"null\" : "
+                         "null, \"false\" : false }";
+  bool ok = reader->parse(
+      doc, doc + std::strlen(doc),
+      &root, &errs);
+  JSONTEST_ASSERT(ok);
+  JSONTEST_ASSERT(errs.size() == 0);
+  delete reader;
+}
+
+JSONTEST_FIXTURE(CharReaderTest, parseWithOneError) {
+  Json::CharReaderBuilder b;
+  Json::CharReader* reader(b.newCharReader());
+  std::string errs;
+  Json::Value root;
+  char const doc[] =
+      "{ \"property\" :: \"value\" }";
+  bool ok = reader->parse(
+      doc, doc + std::strlen(doc),
+      &root, &errs);
+  JSONTEST_ASSERT(!ok);
+  JSONTEST_ASSERT(errs ==
+                  "* Line 1, Column 15\n  Syntax error: value, object or array "
+                  "expected.\n");
+  delete reader;
+}
+
+JSONTEST_FIXTURE(CharReaderTest, parseChineseWithOneError) {
+  Json::CharReaderBuilder b;
+  Json::CharReader* reader(b.newCharReader());
+  std::string errs;
+  Json::Value root;
+  char const doc[] =
+      "{ \"pr佐藤erty\" :: \"value\" }";
+  bool ok = reader->parse(
+      doc, doc + std::strlen(doc),
+      &root, &errs);
+  JSONTEST_ASSERT(!ok);
+  JSONTEST_ASSERT(errs ==
+                  "* Line 1, Column 19\n  Syntax error: value, object or array "
+                  "expected.\n");
+  delete reader;
+}
+
+JSONTEST_FIXTURE(CharReaderTest, parseWithDetailError) {
+  Json::CharReaderBuilder b;
+  Json::CharReader* reader(b.newCharReader());
+  std::string errs;
+  Json::Value root;
+  char const doc[] =
+      "{ \"property\" : \"v\\alue\" }";
+  bool ok = reader->parse(
+      doc, doc + std::strlen(doc),
+      &root, &errs);
+  JSONTEST_ASSERT(!ok);
+  JSONTEST_ASSERT(errs ==
+                  "* Line 1, Column 16\n  Bad escape sequence in string\nSee "
+                  "Line 1, Column 20 for detail.\n");
+  delete reader;
+}
+
 int main(int argc, const char* argv[]) {
   JsonTest::Runner runner;
   JSONTEST_REGISTER_FIXTURE(runner, ValueTest, checkNormalizeFloatingPointStr);
@@ -1647,6 +1732,13 @@ int main(int argc, const char* argv[]) {
   JSONTEST_REGISTER_FIXTURE(runner, ReaderTest, parseChineseWithOneError);
   JSONTEST_REGISTER_FIXTURE(runner, ReaderTest, parseWithDetailError);
 
+  JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithNoErrors);
+  JSONTEST_REGISTER_FIXTURE(
+      runner, CharReaderTest, parseWithNoErrorsTestingOffsets);
+  JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithOneError);
+  JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseChineseWithOneError);
+  JSONTEST_REGISTER_FIXTURE(runner, CharReaderTest, parseWithDetailError);
+
   JSONTEST_REGISTER_FIXTURE(runner, WriterTest, dropNullPlaceholders);
 
   return runner.runCommandLine(argc, argv);