From 84126175e40193800834a142cfc4b152deef3779 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 10 Jul 2014 01:32:50 +0800 Subject: [PATCH 1/4] Wrote a part of SAX --- doc/sax.md | 247 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 242 insertions(+), 5 deletions(-) diff --git a/doc/sax.md b/doc/sax.md index 6442b6b..c0a1af0 100644 --- a/doc/sax.md +++ b/doc/sax.md @@ -1,11 +1,248 @@ # SAX -## Reader +The term "SAX" originated from [Simple API for XML](http://en.wikipedia.org/wiki/Simple_API_for_XML). We borrowed this term for JSON parsing and generation. -### Handler +In RapidJSON, `Reader` (typedef of `GenericReader<...>`) is the SAX-style parser for JSON, and `Writer` (typedef of `GenericWriter<...>`) is the SAX-style generator for JSON. -### Parse Error +[TOC] -## Writer +# Reader {#Reader} + +`Reader` parses a JSON from a stream. While it reads characters from the stream, it analyze the characters according to the syntax of JSON, and publish events to a handler. + +For example, here is a JSON. + +~~~~~~~~~~js +{ + "hello": "world", + "t": true , + "f": false, + "n": null, + "i": 123, + "pi": 3.1416, + "a": [1, 2, 3, 4] +} +~~~~~~~~~~ + +While a `Reader` parses the JSON, it will publish the following events to the handler sequentially: + +~~~~~~~~~~ +BeginObject() +String("hello", 5, true) +String("world", 5, true) +String("t", 1, true) +Bool(true) +String("f", 1, true) +Bool(false) +String("n", 1, true) +Null() +String("i") +UInt(123) +String("pi") +Double(3.1416) +String("a") +BeginArray() +Uint(1) +Uint(2) +Uint(3) +Uint(4) +EndArray(4) +EndObject(7) +~~~~~~~~~~ + +These events can be easily match up with the JSON, except some event parameters need further explanation. Let's see the code which produces exactly the same output as above: + +~~~~~~~~~~cpp +#include "rapidjson/reader.h" +#include + +using namespace rapidjson; +using namespace std; + +struct MyHandler { + void Null() { cout << "Null()" << endl; } + void Bool(bool b) { cout << "Bool(" << (b ? "true" : "false") << ")" << endl; } + void Int(int i) { cout << "Int(" << i << ")" << endl; } + void Uint(unsigned u) { cout << "Uint(" << u << ")" << endl; } + void Int64(int64_t i) { cout << "Int64(" << i << ")" << endl; } + void Uint64(uint64_t u) { cout << "Uint64(" << u << ")" << endl; } + void Double(double d) { { cout << "Double(" << d << ")" << endl; } + void String(const char* str, SizeType length, bool copy) { + cout << "String(" << str << ", " << length << ", " << (b ? "true" : "false") << ")" << endl; } + void StartObject() { cout << "StartObject()" << endl; } + void EndObject(SizeType memberCount) { cout << "EndObject(" << memberCount << ")" << endl; } + void StartArray() { cout << "StartArray()" << endl; } + void EndArray(SizeType elementCount) { cout << "EndArray(" << elementCount << ")" << endl; } +}; + +void main() { + const char* json = "..."; + + MyHandler handler; + Reader reader; + StringStream ss(json); + reader.Parse(ss, handler); +} +~~~~~~~~~~ + +Note that, RapidJSON uses template to statically bind the `Reader` type and the handler type, instead of using class with virtual functions. This paradigm can improve the performance by inlining functions. + +## Handler {#Handler} + +As the previous example showed, user needs to implement a handler, which consumes the events (function calls) from `Reader`. The handler concept has the following member type and member functions. + +~~~~~~~~~~cpp +concept Handler { + typename Ch; + + void Null(); + void Bool(bool b); + void Int(int i); + void Uint(unsigned i); + void Int64(int64_t i); + void Uint64(uint64_t i); + void Double(double d); + void String(const Ch* str, SizeType length, bool copy); + void StartObject(); + void EndObject(SizeType memberCount); + void StartArray(); + void EndArray(SizeType elementCount); +}; +~~~~~~~~~~ + +`Null()` is called when the `Reader` encounters a JSON null value. + +`Bool(bool)` is called when the `Reader` encounters a JSON true or false value. + +When the `Reader` encounters a JSON number, it chooses a suitable C++ type mapping. And then it calls *one* function out of `Int(int)`, `Uint(unsigned)`, `Int64(int64_t)`, `Uint64(uint64_t)` and `Double(double)`. + +`String(const char* str, SizeType length, bool copy)` is called when the `Reader` encounters a string. The first parameter is pointer to the string. The second parameter is the length of the string (excluding the null terminator). Note that RapidJSON supports null character `'\0'` inside a string. If such situation happens, `strlen(str) < length`. The last `copy` indicates whether the handler needs to make a copy of the string. For normal parsing, `copy = true`. Only when *insitu* parsing is used, `copy = false`. And beware that, the character type depends on the target encoding, which will be explained later. + +When the `Reader` encounters the beginning of an object, it calls `StartObject()`. An object in JSON is a set of name-value pairs. If the object contains members it first calls `String()` for the name of member, and then calls functions depending on the type of the value. These calls of name-value pairs repeats until calling `EndObject(SizeType memberCount)`. Note that the `memberCount` parameter is just an aid for the handler, user may not need this parameter. + +Array is similar to object but simpler. At the beginning of an array, the `Reader` calls `BeginArary()`. If there is elements, it calls functions according to the types of element. Similarly, in the last call `EndArray(SizeType elementCount)`, the parameter `elementCount` is just an aid for the handler. + +## GenericReader {#GenericReader} + +As mentioned before, `Reader` is a typedef of a template class `GenericReader`: + +~~~~~~~~~~cpp +namespace rapidjson { + +template > +class GenericReader { + // ... +}; + +typedef GenericReader, UTF8<> > Reader; + +} // namespace rapidjson +~~~~~~~~~~ + +The `Reader` uses UTF-8 as both source and target encoding. The source encoding means the encoding in the JSON stream. The target encoding means the encoding of the `str` parameter in `String()` calls. For example, to parse a UTF-8 stream and outputs UTF-16 string events, you can define a reader by: + +~~~~~~~~~~cpp +GenericReader, UTF16<> > reader; +~~~~~~~~~~ + +Note that, the default character type of `UTF16` is `wchar_t`. So this `reader`needs to call `String(const wchar_t*, SizeType, bool)` of the handler. + +The third template parameter `Allocator` is the allocator type for internal data structure (actually a stack). + +## Parsing {#Parsing} + +The one and only one function of `Reader` is to parse JSON. + +~~~~~~~~~~cpp +template +bool Parse(InputStream& is, Handler& handler); + +// with parseFlags = kDefaultParseFlags +template +bool Parse(InputStream& is, Handler& handler); +~~~~~~~~~~ + +If an error occurs during parsing, it will return `false`. User can also calls `bool HasParseEror()`, `ParseErrorCode GetParseErrorCode()` and `size_t GetErrorOffset()` to obtain the error states. Actually `Document` uses these `Reader` functions to obtain parse errors. Please refer to [DOM](doc/dom.md) for details about parse error. + +# Writer {#Writer} + +## PrettyWriter {#PrettyWriter} + +# Techniques {#Techniques} + +## Parsing JSON to Custom Data Structure {#CustomDataStructure} + +`Document`'s parsing capability is completely based on `Reader`. Actually `Document` is a handler which receives events from a reader to build a DOM during parsing. + +User may uses `Reader` to build other data structures directly. This eliminates building of DOM, thus reducing memory and improving performance. + +Example: +~~~~~~~~~~cpp +// Note: Ad hoc, not yet tested. +using namespace std; +using namespace rapidjson; + +typedef map MessageMap; + +struct MessageHandler : public GenericBaseHandler<> { + MessageHandler() : mState(kExpectStart) { + } + + bool Default() { + return false; + } + + bool StartObject() { + if (!kBeforeStart) + return false; + mState = mExpectName; + return true; + } + + bool String(const Ch* str, SizeType length, bool copy) { + if (mState == kExpectName) { + name_ = string(str, length); + return true; + } + else if (mState == kExpectValue) { + messages_.insert(MessageMap::value_type(name_, string(str, length))); + return true; + } + else + return false; + } + + bool EndObject() { + return mState == kExpectName; + } + + MessageMap messages_; + enum State { + kExpectObjectStart, + kExpectName, + kExpectValue, + }mState; + std::string name_; +}; + +void ParseMessages(const char* json, MessageMap& messages) { + Reader reader; + MessageHandler handler; + StringStream ss(json); + if (reader.Parse(ss, handler)) + messages.swap(handler.messages_); +} + +main() { + MessageMap messages; + ParseMessages("{ \"greeting\" : \"Hello!\", \"farewell\" : \"bye-bye!\" }", messages); +} +~~~~~~~~~~ + +~~~~~~~~~~cpp +// Parse a NxM array +const char* json = "[3, 4, [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]" +~~~~~~~~~~ + +## Filtering of JSON {#Filtering} -### PrettyWriter From 05811e73d4607afa6d6372bfa5479e873916d447 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sat, 12 Jul 2014 23:29:23 +0800 Subject: [PATCH 2/4] Update SAX documentation --- doc/sax.md | 64 +++++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/doc/sax.md b/doc/sax.md index c0a1af0..119af2d 100644 --- a/doc/sax.md +++ b/doc/sax.md @@ -50,7 +50,7 @@ EndArray(4) EndObject(7) ~~~~~~~~~~ -These events can be easily match up with the JSON, except some event parameters need further explanation. Let's see the code which produces exactly the same output as above: +These events can be easily match up with the JSON, except some event parameters need further explanation. Let's see the simplereader example which produces exactly the same output as above: ~~~~~~~~~~cpp #include "rapidjson/reader.h" @@ -60,26 +60,28 @@ using namespace rapidjson; using namespace std; struct MyHandler { - void Null() { cout << "Null()" << endl; } - void Bool(bool b) { cout << "Bool(" << (b ? "true" : "false") << ")" << endl; } - void Int(int i) { cout << "Int(" << i << ")" << endl; } - void Uint(unsigned u) { cout << "Uint(" << u << ")" << endl; } - void Int64(int64_t i) { cout << "Int64(" << i << ")" << endl; } - void Uint64(uint64_t u) { cout << "Uint64(" << u << ")" << endl; } - void Double(double d) { { cout << "Double(" << d << ")" << endl; } - void String(const char* str, SizeType length, bool copy) { - cout << "String(" << str << ", " << length << ", " << (b ? "true" : "false") << ")" << endl; } - void StartObject() { cout << "StartObject()" << endl; } - void EndObject(SizeType memberCount) { cout << "EndObject(" << memberCount << ")" << endl; } - void StartArray() { cout << "StartArray()" << endl; } - void EndArray(SizeType elementCount) { cout << "EndArray(" << elementCount << ")" << endl; } + bool Null() { cout << "Null()" << endl; return true; } + bool Bool(bool b) { cout << "Bool(" << boolalpha << b << ")" << endl; return true; } + bool Int(int i) { cout << "Int(" << i << ")" << endl; return true; } + bool Uint(unsigned u) { cout << "Uint(" << u << ")" << endl; return true; } + bool Int64(int64_t i) { cout << "Int64(" << i << ")" << endl; return true; } + bool Uint64(uint64_t u) { cout << "Uint64(" << u << ")" << endl; return true; } + bool Double(double d) { cout << "Double(" << d << ")" << endl; return true; } + bool String(const char* str, SizeType length, bool copy) { + cout << "String(" << str << ", " << length << ", " << boolalpha << copy << ")" << endl; + return true; + } + bool StartObject() { cout << "StartObject()" << endl; return true; } + bool EndObject(SizeType memberCount) { cout << "EndObject(" << memberCount << ")" << endl; return true; } + bool StartArray() { cout << "StartArray()" << endl; return true; } + bool EndArray(SizeType elementCount) { cout << "EndArray(" << elementCount << ")" << endl; return true; } }; void main() { - const char* json = "..."; + const char json[] = " { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "; MyHandler handler; - Reader reader; + Reader reader; StringStream ss(json); reader.Parse(ss, handler); } @@ -93,20 +95,18 @@ As the previous example showed, user needs to implement a handler, which consume ~~~~~~~~~~cpp concept Handler { - typename Ch; - - void Null(); - void Bool(bool b); - void Int(int i); - void Uint(unsigned i); - void Int64(int64_t i); - void Uint64(uint64_t i); - void Double(double d); - void String(const Ch* str, SizeType length, bool copy); - void StartObject(); - void EndObject(SizeType memberCount); - void StartArray(); - void EndArray(SizeType elementCount); + bool Null(); + bool Bool(bool b); + bool Int(int i); + bool Uint(unsigned i); + bool Int64(int64_t i); + bool Uint64(uint64_t i); + bool Double(double d); + bool String(const Ch* str, SizeType length, bool copy); + bool StartObject(); + bool EndObject(SizeType memberCount); + bool StartArray(); + bool EndArray(SizeType elementCount); }; ~~~~~~~~~~ @@ -122,6 +122,10 @@ When the `Reader` encounters the beginning of an object, it calls `StartObject() Array is similar to object but simpler. At the beginning of an array, the `Reader` calls `BeginArary()`. If there is elements, it calls functions according to the types of element. Similarly, in the last call `EndArray(SizeType elementCount)`, the parameter `elementCount` is just an aid for the handler. +Every handler functions returns a `bool`. Normally it should returns `true`. If the handler encounters an error, it can return `false` to notify event publisher to stop further processing. + +For example, when we parse a JSON with `Reader` and the handler detected that the JSON does not conform to the required schema, then the handler can return `false` and let the `Reader` stop further parsing. And the `Reader` will be in error state with error code `kParseErrorTermination`. + ## GenericReader {#GenericReader} As mentioned before, `Reader` is a typedef of a template class `GenericReader`: From be478343a5438f10d862345a384f6d17bfc74c49 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sun, 13 Jul 2014 03:21:38 +0800 Subject: [PATCH 3/4] Update SAX documentation: writer, techniques --- doc/sax.md | 212 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 183 insertions(+), 29 deletions(-) diff --git a/doc/sax.md b/doc/sax.md index 119af2d..a0e928c 100644 --- a/doc/sax.md +++ b/doc/sax.md @@ -50,7 +50,7 @@ EndArray(4) EndObject(7) ~~~~~~~~~~ -These events can be easily match up with the JSON, except some event parameters need further explanation. Let's see the simplereader example which produces exactly the same output as above: +These events can be easily match up with the JSON, except some event parameters need further explanation. Let's see the `simplereader` example which produces exactly the same output as above: ~~~~~~~~~~cpp #include "rapidjson/reader.h" @@ -170,8 +170,117 @@ If an error occurs during parsing, it will return `false`. User can also calls ` # Writer {#Writer} +`Reader` converts (parses) JSON into events. `Writer` does exactly the opposite. It converts events into JSON. + +`Writer` is very easy to use. If your application only need to converts some data into JSON, it may be a good choice of using `Writer` directly, instead of building a `Document` and then stringifying it with a `Writer`. + +In `simplewriter` example, we do exactly the reverse of `simplereader`. + +~~~~~~~~~~cpp +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" +#include + +using namespace rapidjson; +using namespace std; + +void main() { + StringBuffer s; + Writer writer(s); + + writer.StartObject(); + writer.String("hello"); + writer.String("world"); + writer.String("t"); + writer.Bool(true); + writer.String("f"); + writer.Bool(false); + writer.String("n"); + writer.Null(); + writer.String("i"); + writer.Uint(123); + writer.String("pi"); + writer.Double(3.1416); + writer.String("a"); + writer.StartArray(); + for (unsigned i = 0; i < 4; i++) + writer.Uint(i); + writer.EndArray(); + writer.EndObject(); + + cout << s.GetString() << endl; +} +~~~~~~~~~~ + +~~~~~~~~~~ +{"hello":"world","t":true,"f":false,"n":null,"i":123,"pi":3.1416,"a":[0,1,2,3]} +~~~~~~~~~~ + +There is two `String()` overloads. One is the same as defined in handler concept with 3 parameters. It can handle string with null characters. Another one is the simpler version used in the above example. + +Note that, the example code does not pass any parameters in `EndArray()` and `EndObject()`. An `SizeType` can be passed but it will be simply ignored by `Writer`. + +You may doubt that, + +> "why not just using `sprintf()` or `std::stringstream` to build a JSON?" + +There are various reasons: +1. `Writer` must output a well-formed JSON. If there is incorrect event sequence (e.g. `Int()` just after `StartObject()`), it generates assertion fail in debug mode. +2. `Writer::String()` can handle string escaping (e.g. converting code point `U+000A` to `\n`) and Unicode transcoding. +3. `Writer` handles number output consistently. For example, user can set precision for `Double()`. +3. `Writer` implements the event handler concept. It can be used to handle events from `Reader`, `Document` or other event publisher. +4. `Writer` can be optimized for different platforms. + +Anyway, using `Writer` API is even simpler than generating a JSON by ad hoc methods. + +## Template {#WriterTemplate} + +`Writer` has a minor design difference to `Reader`. `Writer` is a template class, not a typedef. There is no `GenericWriter`. The following is the declaration. + +~~~~~~~~~~cpp +namespace rapidjson { + +template, typename TargetEncoding = UTF8<>, typename Allocator = CrtAllocator<> > +class Writer { +public: + Writer(OutputStream& os, Allocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) +// ... +}; + +} // namespace rapidjson +~~~~~~~~~~ + +The `OutputStream` template parameter is the type of output stream. It cannot be deduced and must be specified by user. + +The `SourceEncoding` template parameter specifies the encoding to be used in `String(const Ch*, ...)`. + +The `TargetEncoding` template parameter specifies the encoding in the output stream. + +The last one, `Allocator` is the type of allocator, which is used for allocating internal data structure (a stack). + +Besides, the constructor of `Writer` has a `levelDepth` parameter. This parameter affects the initial memory allocated for storing information per hierarchy level. + +## Precision (#WriterPrecision) +When using `Double()`, the precision of output can be specified, for example: + +~~~~~~~~~~cpp +writer.SetDoublePrecision(4); +writer.StartArary(); +writer.Double(3.14159265359); +writer.EndArray(); +~~~~~~~~~~ +~~~~~~~~~~ +[3.1416] +~~~~~~~~~~ + ## PrettyWriter {#PrettyWriter} +While the output of `Writer` is the most condensed JSON without white-spaces, suitable for network transfer or storage, it is not easily readable by human. + +Therefore, RapidJSON provides a `PrettyWriter`, which adds indentation and line feeds in the output. + +The usage of `PrettyWriter` is exactly the same as `Writer`, expect that `PrettyWriter` provides a `SetIndent(Ch indentChar, unsigned indentCharCount)` function. The default is 4 spaces. + # Techniques {#Techniques} ## Parsing JSON to Custom Data Structure {#CustomDataStructure} @@ -180,52 +289,59 @@ If an error occurs during parsing, it will return `false`. User can also calls ` User may uses `Reader` to build other data structures directly. This eliminates building of DOM, thus reducing memory and improving performance. -Example: +In the following `messagereader` example, `ParseMessages()` parses a JSON which should be an object with key-string pairs. + ~~~~~~~~~~cpp -// Note: Ad hoc, not yet tested. +#include "rapidjson/reader.h" +#include "rapidjson/error/en.h" +#include +#include +#include + using namespace std; using namespace rapidjson; typedef map MessageMap; -struct MessageHandler : public GenericBaseHandler<> { - MessageHandler() : mState(kExpectStart) { - } - - bool Default() { - return false; +struct MessageHandler : public BaseReaderHandler<> { + MessageHandler() : state_(kExpectObjectStart) { } bool StartObject() { - if (!kBeforeStart) + switch (state_) { + case kExpectObjectStart: + state_ = kExpectNameOrObjectEnd; + return true; + default: return false; - mState = mExpectName; - return true; + } } - bool String(const Ch* str, SizeType length, bool copy) { - if (mState == kExpectName) { + bool String(const char* str, SizeType length, bool) { + switch (state_) { + case kExpectNameOrObjectEnd: name_ = string(str, length); + state_ = kExpectValue; return true; - } - else if (mState == kExpectValue) { + case kExpectValue: messages_.insert(MessageMap::value_type(name_, string(str, length))); + state_ = kExpectNameOrObjectEnd; return true; - } - else + default: return false; + } } - bool EndObject() { - return mState == kExpectName; - } + bool EndObject(SizeType) { return state_ == kExpectNameOrObjectEnd; } + + bool Default() { return false; } // All other events are invalid. MessageMap messages_; enum State { kExpectObjectStart, - kExpectName, + kExpectNameOrObjectEnd, kExpectValue, - }mState; + }state_; std::string name_; }; @@ -234,19 +350,57 @@ void ParseMessages(const char* json, MessageMap& messages) { MessageHandler handler; StringStream ss(json); if (reader.Parse(ss, handler)) - messages.swap(handler.messages_); + messages.swap(handler.messages_); // Only change it if success. + else { + ParseErrorCode e = reader.GetParseErrorCode(); + size_t o = reader.GetErrorOffset(); + cout << "Error: " << GetParseError_En(e) << endl;; + cout << " at offset " << o << " near '" << string(json).substr(o, 10) << "...'" << endl; + } } -main() { +int main() { MessageMap messages; - ParseMessages("{ \"greeting\" : \"Hello!\", \"farewell\" : \"bye-bye!\" }", messages); + + const char* json1 = "{ \"greeting\" : \"Hello!\", \"farewell\" : \"bye-bye!\" }"; + cout << json1 << endl; + ParseMessages(json1, messages); + + for (MessageMap::const_iterator itr = messages.begin(); itr != messages.end(); ++itr) + cout << itr->first << ": " << itr->second << endl; + + cout << endl << "Parse a JSON with invalid schema." << endl; + const char* json2 = "{ \"greeting\" : \"Hello!\", \"farewell\" : \"bye-bye!\", \"foo\" : {} }"; + cout << json2 << endl; + ParseMessages(json2, messages); + + return 0; } ~~~~~~~~~~ -~~~~~~~~~~cpp -// Parse a NxM array -const char* json = "[3, 4, [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]" ~~~~~~~~~~ +{ "greeting" : "Hello!", "farewell" : "bye-bye!" } +farewell: bye-bye! +greeting: Hello! + +Parse a JSON with invalid schema. +{ "greeting" : "Hello!", "farewell" : "bye-bye!", "foo" : {} } +Error: Terminate parsing due to Handler error. + at offset 59 near '} }...' +~~~~~~~~~~ + +The first JSON (`json1`) was successfully parsed into `MessageMap`. Since it is a `std::map`, the print out are sorted by the key, which is different from the JSON's order. + +In the second JSON (`json2`), `foo`'s value is an empty object. As it is an object, `MessageHandler::StartObject()` will be called. However, at that moment `state_ = kExpectValue`, so that function returns `false` and cause the parsing process be terminated. The error code is `kParseErrorTermination`. ## Filtering of JSON {#Filtering} +As mentioned earlier, `Writer` can handle the events published by `Reader`. `condense` example simply set a `Writer` as handler of a `Reader`, so it can remove all white-spaces in JSON. `pretty` example uses the same relationship, but replacing `Writer` by `PrettyWriter`. So `pretty` can be used to reformat a JSON with indentation and line feed. + +Actually, we can add intermediate layer(s) to filter the contents of JSON via these SAX-style API. For example, `capitalize` example capitalize all strings in a JSON. + +~~~~~~~~~~cpp +TODO +~~~~~~~~~~ + +More complicated filters can be developed. However, since SAX-style API can only provide information about a single event at a time, user may need to book-keeping the contextual information (e.g. the path from root value, storage of other related values). Some processing may be easier to be implemented in DOM than SAX. \ No newline at end of file From 4998f1ca9ebce2d9456026ec0f193584152c108c Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sun, 13 Jul 2014 04:01:11 +0800 Subject: [PATCH 4/4] Add capitalize example to sax documentation --- doc/sax.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/doc/sax.md b/doc/sax.md index a0e928c..df3ff53 100644 --- a/doc/sax.md +++ b/doc/sax.md @@ -400,7 +400,78 @@ As mentioned earlier, `Writer` can handle the events published by `Reader`. `con Actually, we can add intermediate layer(s) to filter the contents of JSON via these SAX-style API. For example, `capitalize` example capitalize all strings in a JSON. ~~~~~~~~~~cpp -TODO +#include "rapidjson/reader.h" +#include "rapidjson/writer.h" +#include "rapidjson/filereadstream.h" +#include "rapidjson/filewritestream.h" +#include "rapidjson/error/en.h" +#include +#include + +using namespace rapidjson; + +template +struct CapitalizeFilter { + CapitalizeFilter(OutputHandler& out) : out_(out), buffer_() { + } + + bool Null() { return out_.Null(); } + bool Bool(bool b) { return out_.Bool(b); } + bool Int(int i) { return out_.Int(i); } + bool Uint(unsigned u) { return out_.Uint(u); } + bool Int64(int64_t i) { return out_.Int64(i); } + bool Uint64(uint64_t u) { return out_.Uint64(u); } + bool Double(double d) { return out_.Double(d); } + bool String(const char* str, SizeType length, bool) { + buffer_.clear(); + for (SizeType i = 0; i < length; i++) + buffer_.push_back(std::toupper(str[i])); + return out_.String(&buffer_.front(), length, true); // true = output handler need to copy the string + } + bool StartObject() { return out_.StartObject(); } + bool EndObject(SizeType memberCount) { return out_.EndObject(memberCount); } + bool StartArray() { return out_.StartArray(); } + bool EndArray(SizeType elementCount) { return out_.EndArray(elementCount); } + + OutputHandler& out_; + std::vector buffer_; +}; + +int main(int, char*[]) { + // Prepare JSON reader and input stream. + Reader reader; + char readBuffer[65536]; + FileReadStream is(stdin, readBuffer, sizeof(readBuffer)); + + // Prepare JSON writer and output stream. + char writeBuffer[65536]; + FileWriteStream os(stdout, writeBuffer, sizeof(writeBuffer)); + Writer writer(os); + + // JSON reader parse from the input stream and let writer generate the output. + CapitalizeFilter > filter(writer); + if (!reader.Parse(is, filter)) { + fprintf(stderr, "\nError(%u): %s\n", (unsigned)reader.GetErrorOffset(), GetParseError_En(reader.GetParseErrorCode())); + return 1; + } + + return 0; +} +~~~~~~~~~~ + +Note that, it is incorrect to simply capitalize the JSON as a string. For example: +~~~~~~~~~~js +["Hello\\nWorld"] +~~~~~~~~~~ + +Simply capitalizing the whole JSON would contain incorrect escape character: +~~~~~~~~~~js +["HELLO\\NWORLD"] +~~~~~~~~~~ + +The correct result by `capitalize`: +~~~~~~~~~~js +["HELLO\\nWORLD"] ~~~~~~~~~~ More complicated filters can be developed. However, since SAX-style API can only provide information about a single event at a time, user may need to book-keeping the contextual information (e.g. the path from root value, storage of other related values). Some processing may be easier to be implemented in DOM than SAX. \ No newline at end of file