mame/3rdparty/rapidjson/doc/sax.zh-cn.md
2016-02-17 14:11:23 +01:00

18 KiB
Raw Blame History

SAX

"SAX"此术语源于Simple API for XML。我们借了此术语去套用在JSON的解析及生成。

在RapidJSON中ReaderGenericReader<...>的typedef是JSON的SAX风格解析器WriterGenericWriter<...>的typedef则是JSON的SAX风格生成器。

[TOC]

Reader

Reader从输入流解析一个JSON。当它从流中读取字符时它会基于JSON的语法去分析字符并向处理器发送事件。

例如以下是一个JSON。

{
    "hello": "world",
    "t": true ,
    "f": false,
    "n": null,
    "i": 123,
    "pi": 3.1416,
    "a": [1, 2, 3, 4]
}

当一个Reader解析此JSON时它会顺序地向处理器发送以下的事件

StartObject()
Key("hello", 5, true)
String("world", 5, true)
Key("t", 1, true)
Bool(true)
Key("f", 1, true)
Bool(false)
Key("n", 1, true)
Null()
Key("i")
UInt(123)
Key("pi")
Double(3.1416)
Key("a")
StartArray()
Uint(1)
Uint(2)
Uint(3)
Uint(4)
EndArray(4)
EndObject(7)

除了一些事件参数需要再作解释这些事件可以轻松地与JSON对上。我们可以看看simplereader例子怎样产生和以上完全相同的结果:

#include "rapidjson/reader.h"
#include <iostream>

using namespace rapidjson;
using namespace std;

struct MyHandler {
    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 Key(const char* str, SizeType length, bool copy) { 
        cout << "Key(" << str << ", " << length << ", " << boolalpha << copy << ")" << 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[] = " { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } ";

    MyHandler handler;
    Reader reader;
    StringStream ss(json);
    reader.Parse(ss, handler);
}

注意RapidJSON使用模板去静态挷定Reader类型及处理器的类形,而不是使用含虚函数的类。这个范式可以通过把函数内联而改善性能。

处理器

如前例所示使用者需要实现一个处理器handler用于处理来自Reader的事件(函数调用)。处理器必须包含以下的成员函数。

class Handler {
    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 Key(const Ch* str, SizeType length, bool copy);
    bool EndObject(SizeType memberCount);
    bool StartArray();
    bool EndArray(SizeType elementCount);
};

Reader遇到JSON null值时会调用Null()

Reader遇到JSON true或false值时会调用Bool(bool)

Reader遇到JSON number它会选择一个合适的C++类型映射,然后调用Int(int)Uint(unsigned)Int64(int64_t)Uint64(uint64_t)Double(double)其中之一个

Reader遇到JSON string它会调用String(const char* str, SizeType length, bool copy)。第一个参数是字符串的指针。第二个参数是字符串的长度不包含空终止符号。注意RapidJSON支持字串中含有空字符'\0'。若出现这种情况,便会有strlen(str) < length。最后的copy参数表示处理器是否需要复制该字符串。在正常解析时,copy = true。仅当使用原位解析时,copy = false。此外,还要注意字符的类型与目标编码相关,我们稍后会再谈这一点。

Reader遇到JSON object的开始之时它会调用StartObject()。JSON的object是一个键值对成员的集合。若object包含成员它会先为成员的名字调用Key(),然后再按值的类型调用函数。它不断调用这些键值对,直至最终调用EndObject(SizeType memberCount)。注意memberCount参数对处理器来说只是协助性质,使用者可能不需要此参数。

JSON array与object相似但更简单。在array开始时Reader会调用BeginArary()。若array含有元素它会按元素的类型来读用函数。相似地最后它会调用EndArray(SizeType elementCount),其中elementCount参数对处理器来说只是协助性质。

每个处理器函数都返回一个bool。正常它们应返回true。若处理器遇到错误,它可以返回false去通知事件发送方停止继续处理。

例如,当我们用Reader解析一个JSON时处理器检测到该JSON并不符合所需的schema那么处理器可以返回false,令Reader停止之后的解析工作。而Reader会进入一个错误状态,并以kParseErrorTermination错误码标识。

GenericReader

前面提及,ReaderGenericReader模板类的typedef

namespace rapidjson {

template <typename SourceEncoding, typename TargetEncoding, typename Allocator = MemoryPoolAllocator<> >
class GenericReader {
    // ...
};

typedef GenericReader<UTF8<>, UTF8<> > Reader;

} // namespace rapidjson

Reader使用UTF-8作为来源及目标编码。来源编码是指JSON流的编码。目标编码是指String()str参数所用的编码。例如要解析一个UTF-8流并输出至UTF-16 string事件你需要这么定义一个reader

GenericReader<UTF8<>, UTF16<> > reader;

注意到UTF16的缺省类型是wchar_t。因此这个reader需要调用处理器的String(const wchar_t*, SizeType, bool)

第三个模板参数Allocator是内部数据结构(实际上是一个堆栈)的分配器类型。

解析

Reader的唯一功能就是解析JSON。

template <unsigned parseFlags, typename InputStream, typename Handler>
bool Parse(InputStream& is, Handler& handler);

// 使用 parseFlags = kDefaultParseFlags
template <typename InputStream, typename Handler>
bool Parse(InputStream& is, Handler& handler);

若在解析中出现错误,它会返回false。使用者可调用bool HasParseEror(), ParseErrorCode GetParseErrorCode()size_t GetErrorOffset()获取错误状态。实际上Document使用这些Reader函数去获取解析错误。请参考DOM去了解有关解析错误的细节。

Writer

Reader把JSON转换解析成为事件。Writer做完全相反的事情。它把事件转换成JSON。

Writer是非常容易使用的。若你的应用程序只需把一些数据转换成JSON可能直接使用Writer,会比建立一个Document然后用Writer把它转换成JSON更加方便。

simplewriter例子里,我们做simplereader完全相反的事情。

#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include <iostream>

using namespace rapidjson;
using namespace std;

void main() {
    StringBuffer s;
    Writer<StringBuffer> writer(s);
    
    writer.StartObject();
    writer.Key("hello");
    writer.String("world");
    writer.Key("t");
    writer.Bool(true);
    writer.Key("f");
    writer.Bool(false);
    writer.Key("n");
    writer.Null();
    writer.Key("i");
    writer.Uint(123);
    writer.Key("pi");
    writer.Double(3.1416);
    writer.Key("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]}

String()Key()各有两个重载。一个是如处理器concept般有3个参数。它能处理含空字符的字符串。另一个是如上中使用的较简单版本。

注意到,例子代码中的EndArray()EndObject()并没有参数。可以传递一个SizeType的参数,但它会被Writer忽略。

你可能会怀疑,为什么不使用sprintf()std::stringstream去建立一个JSON

这有几个原因:

  1. Writer必然会输出一个结构良好well-formed的JSON。若然有错误的事件次序Int()紧随StartObject()出现),它会在调试模式中产生断言失败。
  2. Writer::String()可处理字符串转义(如把码点U+000A转换成\n及进行Unicode转码。
  3. Writer一致地处理number的输出。
  4. Writer实现了事件处理器concept。可用于处理来自ReaderDocument或其他事件发生器。
  5. Writer可对不同平台进行优化。

无论如何,使用Writer API去生成JSON甚至乎比这些临时方法更简单。

模板

WriterReader有少许设计区别。Writer是一个模板类而不是一个typedef。 并没有GenericWriter。以下是Writer的声明。

namespace rapidjson {

template<typename OutputStream, typename SourceEncoding = UTF8<>, typename TargetEncoding = UTF8<>, typename Allocator = CrtAllocator<> >
class Writer {
public:
    Writer(OutputStream& os, Allocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth)
// ...
};

} // namespace rapidjson

OutputStream模板参数是输出流的类型。它的类型不可以被自动推断,必须由使用者提供。

SourceEncoding模板参数指定了String(const Ch*, ...)的编码。

TargetEncoding模板参数指定输出流的编码。

最后一个Allocator是分配器的类型,用于分配内部数据结构(一个堆栈)。

此外,Writer的构造函数有一levelDepth参数。存储每层阶信息的初始内存分配量受此参数影响。

PrettyWriter

Writer所输出的是没有空格字符的最紧凑JSON适合网络传输或储存但不适合人类阅读。

因此RapidJSON提供了一个PrettyWriter,它在输出中加入缩进及换行。

PrettyWriter的用法与Writer几乎一样,不同之处是PrettyWriter提供了一个SetIndent(Ch indentChar, unsigned indentCharCount)函数。缺省的缩进是4个空格。

完整性及重置

一个Writer只可输出单个JSON其根节点可以是任何JSON类型。当处理完单个根节点事件String()),或匹配的最后EndObject()EndArray()事件输出的JSON是结构完整well-formed及完整的。使用者可调用Writer::IsComplete()去检测完整性。

当JSON完整时Writer不能再接受新的事件。不然其输出便会是不合法的(例如有超过一个根节点)。为了重新利用Writer对象,使用者可调用Writer::Reset(OutputStream& os)去重置其所有内部状态及设置新的输出流。

技巧

解析JSON至自定义结构

Document的解析功能完全依靠Reader。实际上Document是一个处理器在解析JSON时接收事件去建立一个DOM。

使用者可以直接使用Reader去建立其他数据结构。这消除了建立DOM的步骤从而减少了内存开销并改善性能。

在以下的messagereader例子中,ParseMessages()解析一个JSON该JSON应该是一个含键值对的object。

#include "rapidjson/reader.h"
#include "rapidjson/error/en.h"
#include <iostream>
#include <string>
#include <map>

using namespace std;
using namespace rapidjson;

typedef map<string, string> MessageMap;

struct MessageHandler
    : public BaseReaderHandler<UTF8<>, MessageHandler> {
    MessageHandler() : state_(kExpectObjectStart) {
    }

    bool StartObject() {
        switch (state_) {
        case kExpectObjectStart:
            state_ = kExpectNameOrObjectEnd;
            return true;
        default:
            return false;
        }
    }

    bool String(const char* str, SizeType length, bool) {
        switch (state_) {
        case kExpectNameOrObjectEnd:
            name_ = string(str, length);
            state_ = kExpectValue;
            return true;
        case kExpectValue:
            messages_.insert(MessageMap::value_type(name_, string(str, length)));
            state_ = kExpectNameOrObjectEnd;
            return true;
        default:
            return false;
        }
    }

    bool EndObject(SizeType) { return state_ == kExpectNameOrObjectEnd; }

    bool Default() { return false; } // All other events are invalid.

    MessageMap messages_;
    enum State {
        kExpectObjectStart,
        kExpectNameOrObjectEnd,
        kExpectValue,
    }state_;
    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_);   // 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;
    }
}

int main() {
    MessageMap 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;
}
{ "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 '} }...'

第一个JSONjson1)被成功地解析至MessageMap。由于MessageMap是一个std::map打印次序按键值排序。此次序与JSON中的次序不同。

在第二个JSONjson2)中,foo的值是一个空object。由于它是一个objectMessageHandler::StartObject()会被调用。然而,在state_ = kExpectValue的情况下,该函数会返回false,并导致解析过程终止。错误代码是kParseErrorTermination

过滤JSON

如前面提及过,Writer可处理Reader发出的事件。example/condense/condense.cpp例子简单地设置Writer作为一个Reader的处理器因此它能移除JSON中的所有空白字符。example/pretty/pretty.cpp例子使用同样的关系,只是以PrettyWriter取代Writer。因此pretty能够重新格式化JSON加入缩进及换行。

实际上我们可以使用SAX风格API去加入多个中间层去过滤JSON的内容。例如capitalize例子可以把所有JSON string改为大写。

#include "rapidjson/reader.h"
#include "rapidjson/writer.h"
#include "rapidjson/filereadstream.h"
#include "rapidjson/filewritestream.h"
#include "rapidjson/error/en.h"
#include <vector>
#include <cctype>

using namespace rapidjson;

template<typename OutputHandler>
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 Key(const char* str, SizeType length, bool copy) { return String(str, length, copy); }
    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<char> 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<FileWriteStream> writer(os);

    // JSON reader parse from the input stream and let writer generate the output.
    CapitalizeFilter<Writer<FileWriteStream> > filter(writer);
    if (!reader.Parse(is, filter)) {
        fprintf(stderr, "\nError(%u): %s\n", (unsigned)reader.GetErrorOffset(), GetParseError_En(reader.GetParseErrorCode()));
        return 1;
    }

    return 0;
}

注意到不可简单地把JSON当作字符串去改为大写。例如

["Hello\nWorld"]

简单地把整个JSON转为大写的话会产生错误的转义符

["HELLO\NWORLD"]

capitalize就会产生正确的结果:

["HELLO\nWORLD"]

我们还可以开发更复杂的过滤器。然而由于SAX风格API在某一时间点只能提供单一事件的信息使用者需要自行记录一些上下文信息例如从根节点起的路径、储存其他相关值。对于处理某些情况用DOM会比SAX更容易实现。