From 852c25123c6f8fec619c1c8d49250bd6f698c6b1 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 10 Apr 2015 14:54:13 +0800 Subject: [PATCH 01/43] Implement parser/generator for JSON Pointer --- include/rapidjson/pointer.h | 208 ++++++++++++++++++++++++++++++++++ test/unittest/CMakeLists.txt | 1 + test/unittest/pointertest.cpp | 167 +++++++++++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100644 include/rapidjson/pointer.h create mode 100644 test/unittest/pointertest.cpp diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h new file mode 100644 index 0000000..28c94ad --- /dev/null +++ b/include/rapidjson/pointer.h @@ -0,0 +1,208 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POINTER_H_ +#define RAPIDJSON_POINTER_H_ + +#include "document.h" + +RAPIDJSON_NAMESPACE_BEGIN + +template +class GenericPointer { +public: + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + + struct Token { + const typename Ch* name; + SizeType length; + SizeType index; //!< A valid index if not equal to kInvalidIndex. + }; + + GenericPointer(const Ch* source, Allocator* allocator = 0) + : allocator_(allocator), + ownAllocator_(), + nameBuffer_(), + tokens_(), + tokenCount_(), + valid_(true) + { + Parse(source, internal::StrLen(source)); + } + + GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) + : allocator_(allocator), + ownAllocator_(), + nameBuffer_(), + tokens_(), + tokenCount_(), + valid_(true) + { + Parse(source, length); + } + + GenericPointer(const Token* tokens, size_t tokenCount) : + : allocator_(), + ownAllocator_(), + nameBuffer_(), + tokens_(tokens), + tokenCount_(tokenCount), + valid_(true) + { + } + + ~GenericPointer() { + if (nameBuffer_) { + Allocator::Free(nameBuffer_); + Allocator::Free(tokens_); + } + RAPIDJSON_DELETE(ownAllocator_); + } + + bool IsValid() const { return valid_; } + + const Token* GetTokens() const { return tokens_; } + + size_t GetTokenCount() const { return tokenCount_; } + + template + void Stringify(OutputStream& os) const { + RAPIDJSON_ASSERT(IsValid()); + for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + os.Put('/'); + for (size_t j = 0; j < t->length; j++) { + Ch c = t->name[j]; + if (c == '~') { os.Put('~'); os.Put('0'); } + else if (c == '/') { os.Put('~'); os.Put('1'); } + else os.Put(c); + } + } + } + + ValueType* Get(ValueType& root) const; + const ValueType* Get(const ValueType& root) const { + return Get(const_cast(root)); + } + + ValueType* Get(ValueType& root, const ValueType& defaultValue) const; + const ValueType* Get(const ValueType& root, const ValueType& defaultValue) const; + + // Move semantics, create parents if non-exist + void Set(ValueType& root, ValueType& value) const; + + // Create parents if non-exist + void Swap(ValueType& root, ValueType& value) const; + + static const size_t kDefaultTokenCapacity = 4; + static const SizeType kInvalidIndex = ~SizeType(0); + +private: + void Parse(const Ch* source, size_t length) { + // Create own allocator if user did not supply. + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + + // Create a buffer as same size of source + RAPIDJSON_ASSERT(nameBuffer_ == 0); + nameBuffer_ = (Ch*)allocator_->Malloc(length); + + RAPIDJSON_ASSERT(tokens_ == 0); + tokens_ = (Token*)allocator_->Malloc(length * sizeof(Token)); // Maximum possible tokens in the source + + tokenCount_ = 0; + Ch* name = nameBuffer_; + + for (size_t i = 0; i < length;) { + if (source[i++] != '/') // Consumes '/' + goto error; + + Token& token = tokens_[tokenCount_++]; + token.name = name; + bool isNumber = true; + + while (i < length && source[i] != '/') { + Ch c = source[i++]; + + // Escaping "~0" -> '~', "~1" -> '/' + if (c == '~') { + if (i < length) { + c = source[i++]; + if (c == '0') c = '~'; + else if (c == '1') c = '/'; + else goto error; + } + else + goto error; + } + + // First check for index: all of characters are digit + if (c < '0' || c > '9') + isNumber = false; + + *name++ = c; + } + token.length = name - token.name; + *name++ = '\0'; // Null terminator + + // Second check for index: more than one digit cannot have leading zero + if (isNumber && token.length > 1 && token.name[0] == '0') + isNumber = false; + + // String to SizeType conversion + SizeType n = 0; + if (isNumber) { + for (size_t j = 0; j < token.length; j++) { + SizeType m = n * 10 + static_cast(token.name[j] - '0'); + if (m < n) { // overflow detection + isNumber = false; + break; + } + n = m; + } + } + + token.index = isNumber ? n : kInvalidIndex; + } + + RAPIDJSON_ASSERT(name <= nameBuffer_ + length); // Should not overflow buffer + tokens_ = (Token*)allocator_->Realloc(tokens_, length * sizeof(Token), tokenCount_ * sizeof(Token)); // Shrink tokens_ + return; + + error: + Allocator::Free(nameBuffer_); + Allocator::Free(tokens_); + nameBuffer_ = 0; + tokens_ = 0; + tokenCount_ = 0; + valid_ = false; + return; + } + + GenericPointer(const GenericPointer& rhs); + GenericPointer& operator=(const GenericPointer& rhs); + + Allocator* allocator_; + Allocator* ownAllocator_; + Ch* nameBuffer_; + Token* tokens_; + size_t tokenCount_; + bool valid_; +}; + +typedef GenericPointer Pointer; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_POINTER_H_ diff --git a/test/unittest/CMakeLists.txt b/test/unittest/CMakeLists.txt index 5e4a3e9..6a776ec 100644 --- a/test/unittest/CMakeLists.txt +++ b/test/unittest/CMakeLists.txt @@ -6,6 +6,7 @@ set(UNITTEST_SOURCES filestreamtest.cpp jsoncheckertest.cpp namespacetest.cpp + pointertest.cpp readertest.cpp stringbuffertest.cpp strtodtest.cpp diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp new file mode 100644 index 0000000..28173f9 --- /dev/null +++ b/test/unittest/pointertest.cpp @@ -0,0 +1,167 @@ +// Copyright (C) 2011 Milo Yip +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "unittest.h" +#include "rapidjson/pointer.h" +#include "rapidjson/stringbuffer.h" +#include + +using namespace rapidjson; + +static const char cJson[] = "{\n" +" \"foo\":[\"bar\", \"baz\"],\n" +" \"\" : 0,\n" +" \"a/b\" : 1,\n" +" \"c%d\" : 2,\n" +" \"e^f\" : 3,\n" +" \"g|h\" : 4,\n" +" \"i\\\\j\" : 5,\n" +" \"k\\\"l\" : 6,\n" +" \" \" : 7,\n" +" \"m~n\" : 8\n" +"}"; + +TEST(Pointer, Parse) { + { + Pointer p(""); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(0, p.GetTokenCount()); + } + + { + Pointer p("/foo"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1, p.GetTokenCount()); + EXPECT_EQ(3, p.GetTokens()[0].length); + EXPECT_STREQ("foo", p.GetTokens()[0].name); + } + + { + Pointer p("/foo/0"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(2, p.GetTokenCount()); + EXPECT_EQ(3, p.GetTokens()[0].length); + EXPECT_STREQ("foo", p.GetTokens()[0].name); + EXPECT_EQ(1, p.GetTokens()[1].length); + EXPECT_STREQ("0", p.GetTokens()[1].name); + EXPECT_EQ(0, p.GetTokens()[1].index); + } + + { + // Unescape ~1 + Pointer p("/a~1b"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1, p.GetTokenCount()); + EXPECT_EQ(3, p.GetTokens()[0].length); + EXPECT_STREQ("a/b", p.GetTokens()[0].name); + } + + { + // Unescape ~0 + Pointer p("/m~0n"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1, p.GetTokenCount()); + EXPECT_EQ(3, p.GetTokens()[0].length); + EXPECT_STREQ("m~n", p.GetTokens()[0].name); + } + + { + // empty name + Pointer p("/"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1, p.GetTokenCount()); + EXPECT_EQ(0, p.GetTokens()[0].length); + EXPECT_STREQ("", p.GetTokens()[0].name); + } + + { + // empty and non-empty name + Pointer p("//a"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(2, p.GetTokenCount()); + EXPECT_EQ(0, p.GetTokens()[0].length); + EXPECT_STREQ("", p.GetTokens()[0].name); + EXPECT_EQ(1, p.GetTokens()[1].length); + EXPECT_STREQ("a", p.GetTokens()[1].name); + } + + { + // Null characters + Pointer p("/\0\0", 3); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1, p.GetTokenCount()); + EXPECT_EQ(2, p.GetTokens()[0].length); + EXPECT_EQ('\0', p.GetTokens()[0].name[0]); + EXPECT_EQ('\0', p.GetTokens()[0].name[1]); + EXPECT_EQ('\0', p.GetTokens()[0].name[2]); + } + + { + // Valid index + Pointer p("/123"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1, p.GetTokenCount()); + EXPECT_STREQ("123", p.GetTokens()[0].name); + EXPECT_EQ(123, p.GetTokens()[0].index); + } + + { + // Invalid index (with leading zero) + Pointer p("/01"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1, p.GetTokenCount()); + EXPECT_STREQ("01", p.GetTokens()[0].name); + EXPECT_EQ(Pointer::kInvalidIndex, p.GetTokens()[0].index); + } + + { + // Invalid index (overflow) + Pointer p("/4294967296"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1, p.GetTokenCount()); + EXPECT_STREQ("4294967296", p.GetTokens()[0].name); + EXPECT_EQ(Pointer::kInvalidIndex, p.GetTokens()[0].index); + } +} + +TEST(Pointer, Stringify) { + // Test by roundtrip + const char* sources[] = { + "", + "/foo", + "/foo/0", + "/", + "/a~1b", + "/c%d", + "/e^f", + "/g|h", + "/i\\j", + "/k\"l", + "/ ", + "/m~0n" + }; + + for (size_t i = 0; i < sizeof(sources) / sizeof(sources[0]); i++) { + Pointer p(sources[i]); + StringBuffer s; + p.Stringify(s); + EXPECT_STREQ(sources[i], s.GetString()); + } +} From c11547ebfa2304b8afd3fa2cadf5fdfc92c7f353 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 10 Apr 2015 17:43:11 +0800 Subject: [PATCH 02/43] Implement Pointer::Create(). Get(). GetWithDefault(). Set(). Swap() --- include/rapidjson/pointer.h | 92 ++++++++++++++++++++++++++++++++--- test/unittest/pointertest.cpp | 61 ++++++++++++++++++++++- 2 files changed, 143 insertions(+), 10 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 28c94ad..82a4458 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -31,6 +31,8 @@ public: SizeType index; //!< A valid index if not equal to kInvalidIndex. }; + static const SizeType kInvalidIndex = ~SizeType(0); + GenericPointer(const Ch* source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), @@ -91,22 +93,96 @@ public: } } - ValueType* Get(ValueType& root) const; + ValueType& Create(ValueType& root, typename ValueType::AllocatorType& allocator, bool* alreadyExist = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + bool exist = true; + for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + if (v->GetType() != kObjectType && v->GetType() != kArrayType) + if (t->index == kInvalidIndex) + v->SetObject(); + else + v->SetArray(); + + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) { + v->AddMember(Value(t->name, t->length, allocator).Move(), Value().Move(), allocator); + v = &(--v->MemberEnd())->value; // Assumes AddMember() appends at the end + exist = false; + } + else + v = &m->value; + } + break; + case kArrayType: + if (t->index == kInvalidIndex) + v->SetArray(); // Change to Array + if (t->index >= v->Size()) { + v->Reserve(t->index - 1, allocator); + while (t->index >= v->Size()) + v->PushBack(Value().Move(), allocator); + exist = false; + } + v = &((*v)[t->index]); + break; + } + } + + if (alreadyExist) + *alreadyExist = exist; + + return *v; + } + + ValueType* Get(ValueType& root) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) + return 0; + v = &m->value; + } + break; + case kArrayType: + if (t->index == kInvalidIndex || t->index >= v->Size()) + return 0; + v = &((*v)[t->index]); + break; + default: + return 0; + } + } + return v; + } + const ValueType* Get(const ValueType& root) const { return Get(const_cast(root)); } - ValueType* Get(ValueType& root, const ValueType& defaultValue) const; - const ValueType* Get(const ValueType& root, const ValueType& defaultValue) const; + ValueType& GetWithDefault(ValueType& root, const ValueType& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + Value& v = Create(root, allocator, &alreadyExist); + if (!alreadyExist) + v = Value(defaultValue, allocator); + return v; + } // Move semantics, create parents if non-exist - void Set(ValueType& root, ValueType& value) const; + ValueType& Set(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = value; + } // Create parents if non-exist - void Swap(ValueType& root, ValueType& value) const; - - static const size_t kDefaultTokenCapacity = 4; - static const SizeType kInvalidIndex = ~SizeType(0); + ValueType& Swap(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).Swap(value); + } private: void Parse(const Ch* source, size_t length) { diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 28173f9..dbe04c0 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -25,7 +25,7 @@ using namespace rapidjson; -static const char cJson[] = "{\n" +static const char kJson[] = "{\n" " \"foo\":[\"bar\", \"baz\"],\n" " \"\" : 0,\n" " \"a/b\" : 1,\n" @@ -131,7 +131,7 @@ TEST(Pointer, Parse) { EXPECT_EQ(Pointer::kInvalidIndex, p.GetTokens()[0].index); } - { + if (sizeof(SizeType) == 4) { // Invalid index (overflow) Pointer p("/4294967296"); EXPECT_TRUE(p.IsValid()); @@ -165,3 +165,60 @@ TEST(Pointer, Stringify) { EXPECT_STREQ(sources[i], s.GetString()); } } + +TEST(Pointer, Create) { + Document d; + EXPECT_EQ(&d, &Pointer("").Create(d, d.GetAllocator())); + EXPECT_EQ(&d["foo"], &Pointer("/foo").Create(d, d.GetAllocator())); + EXPECT_EQ(&d["foo"][0], &Pointer("/foo/0").Create(d, d.GetAllocator())); +} + +TEST(Pointer, Get) { + Document d; + d.Parse(kJson); + + EXPECT_EQ(&d, Pointer("").Get(d)); + EXPECT_EQ(&d["foo"], Pointer("/foo").Get(d)); + EXPECT_EQ(&d["foo"][0], Pointer("/foo/0").Get(d)); + EXPECT_EQ(&d[""], Pointer("/").Get(d)); + EXPECT_EQ(&d["a/b"], Pointer("/a~1b").Get(d)); + EXPECT_EQ(&d["c%d"], Pointer("/c%d").Get(d)); + EXPECT_EQ(&d["e^f"], Pointer("/e^f").Get(d)); + EXPECT_EQ(&d["g|h"], Pointer("/g|h").Get(d)); + EXPECT_EQ(&d["i\\j"], Pointer("/i\\j").Get(d)); + EXPECT_EQ(&d["k\"l"], Pointer("/k\"l").Get(d)); + EXPECT_EQ(&d[" "], Pointer("/ ").Get(d)); + EXPECT_EQ(&d["m~n"], Pointer("/m~0n").Get(d)); +} + +TEST(Pointer, GetWithDefault) { + Document d; + d.Parse(kJson); + + Document::AllocatorType& a = d.GetAllocator(); + const Value v("qux"); + EXPECT_TRUE(Value("bar") == Pointer("/foo/0").GetWithDefault(d, v, a)); + EXPECT_TRUE(Value("baz") == Pointer("/foo/1").GetWithDefault(d, v, a)); + EXPECT_TRUE(Value("qux") == Pointer("/foo/2").GetWithDefault(d, v, a)); +} + +TEST(Pointer, Set) { + Document d; + d.Parse(kJson); + Document::AllocatorType& a = d.GetAllocator(); + + Pointer("/foo/0").Set(d, Value(123).Move(), a); + EXPECT_EQ(123, d["foo"][0].GetInt()); + + Pointer("/foo/2").Set(d, Value(456).Move(), a); + EXPECT_EQ(456, d["foo"][2].GetInt()); +} + +TEST(Pointer, Swap) { + Document d; + d.Parse(kJson); + Document::AllocatorType& a = d.GetAllocator(); + Pointer("/foo/0").Swap(d, *Pointer("/foo/1").Get(d), a); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_STREQ("bar", d["foo"][1].GetString()); +} From cf0ff19cac40a6fea67d76ab5b70d7e907cd889f Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 10 Apr 2015 18:25:02 +0800 Subject: [PATCH 03/43] Add Pointer default/copy constructor, assignment operator. Test constructor with tokens --- include/rapidjson/pointer.h | 60 ++++++++++++++++++++++++--- test/unittest/pointertest.cpp | 76 +++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 6 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 82a4458..1ec157e 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -33,6 +33,16 @@ public: static const SizeType kInvalidIndex = ~SizeType(0); + GenericPointer() + : allocator_(), + ownAllocator_(), + nameBuffer_(), + tokens_(), + tokenCount_(), + valid_(true) + { + } + GenericPointer(const Ch* source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), @@ -55,16 +65,27 @@ public: Parse(source, length); } - GenericPointer(const Token* tokens, size_t tokenCount) : + GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), - tokens_(tokens), + tokens_(const_cast(tokens)), tokenCount_(tokenCount), valid_(true) { } + GenericPointer(const GenericPointer& rhs) + : allocator_(), + ownAllocator_(), + nameBuffer_(), + tokens_(), + tokenCount_(), + valid_() + { + *this = rhs; + } + ~GenericPointer() { if (nameBuffer_) { Allocator::Free(nameBuffer_); @@ -73,6 +94,36 @@ public: RAPIDJSON_DELETE(ownAllocator_); } + GenericPointer& operator=(const GenericPointer& rhs) { + this->~GenericPointer(); + + tokenCount_ = rhs.tokenCount_; + valid_ = rhs.valid_; + + if (rhs.nameBuffer_) { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + + size_t nameBufferSize = tokenCount_; // null terminators + for (Token *t = rhs.tokens_; t != rhs.tokens_ + tokenCount_; ++t) + nameBufferSize += t->length; + nameBuffer_ = (Ch*)allocator_->Malloc(nameBufferSize * sizeof(Ch)); + std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize); + + tokens_ = (Token*)allocator_->Malloc(tokenCount_ * sizeof(Token)); + std::memcpy(tokens_, rhs.tokens_, tokenCount_ * sizeof(Token)); + + // Adjust pointers to name buffer + std::ptrdiff_t diff = nameBuffer_ - rhs.nameBuffer_; + for (Token *t = rhs.tokens_; t != rhs.tokens_ + tokenCount_; ++t) + t->name += diff; + } + else + tokens_ = rhs.tokens_; + + return *this; + } + bool IsValid() const { return valid_; } const Token* GetTokens() const { return tokens_; } @@ -192,7 +243,7 @@ private: // Create a buffer as same size of source RAPIDJSON_ASSERT(nameBuffer_ == 0); - nameBuffer_ = (Ch*)allocator_->Malloc(length); + nameBuffer_ = (Ch*)allocator_->Malloc(length * sizeof(Ch)); RAPIDJSON_ASSERT(tokens_ == 0); tokens_ = (Token*)allocator_->Malloc(length * sizeof(Token)); // Maximum possible tokens in the source @@ -266,9 +317,6 @@ private: return; } - GenericPointer(const GenericPointer& rhs); - GenericPointer& operator=(const GenericPointer& rhs); - Allocator* allocator_; Allocator* ownAllocator_; Ch* nameBuffer_; diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index dbe04c0..4d4aece 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -166,6 +166,82 @@ TEST(Pointer, Stringify) { } } +// Construct a Pointer with static tokens, no dynamic allocation involved. +#define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, Pointer::kInvalidIndex } +#define INDEX(i) { #i, sizeof(#i) - 1, i } + +static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(0) }; // equivalent to "/foo/0" + +#undef NAME +#undef INDEX + +TEST(Pointer, ConstructorWithToken) { + Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(2, p.GetTokenCount()); + EXPECT_EQ(3, p.GetTokens()[0].length); + EXPECT_STREQ("foo", p.GetTokens()[0].name); + EXPECT_EQ(1, p.GetTokens()[1].length); + EXPECT_STREQ("0", p.GetTokens()[1].name); + EXPECT_EQ(0, p.GetTokens()[1].index); +} + +TEST(Pointer, CopyConstructor) { + { + Pointer p("/foo/0"); + Pointer q(p); + EXPECT_TRUE(q.IsValid()); + EXPECT_EQ(2, q.GetTokenCount()); + EXPECT_EQ(3, q.GetTokens()[0].length); + EXPECT_STREQ("foo", q.GetTokens()[0].name); + EXPECT_EQ(1, q.GetTokens()[1].length); + EXPECT_STREQ("0", q.GetTokens()[1].name); + EXPECT_EQ(0, q.GetTokens()[1].index); + } + + // Static tokens + { + Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + Pointer q(p); + EXPECT_TRUE(q.IsValid()); + EXPECT_EQ(2, q.GetTokenCount()); + EXPECT_EQ(3, q.GetTokens()[0].length); + EXPECT_STREQ("foo", q.GetTokens()[0].name); + EXPECT_EQ(1, q.GetTokens()[1].length); + EXPECT_STREQ("0", q.GetTokens()[1].name); + EXPECT_EQ(0, q.GetTokens()[1].index); + } +} + +TEST(Pointer, Assignment) { + { + Pointer p("/foo/0"); + Pointer q; + q = p; + EXPECT_TRUE(q.IsValid()); + EXPECT_EQ(2, q.GetTokenCount()); + EXPECT_EQ(3, q.GetTokens()[0].length); + EXPECT_STREQ("foo", q.GetTokens()[0].name); + EXPECT_EQ(1, q.GetTokens()[1].length); + EXPECT_STREQ("0", q.GetTokens()[1].name); + EXPECT_EQ(0, q.GetTokens()[1].index); + } + + // Static tokens + { + Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + Pointer q; + q = p; + EXPECT_TRUE(q.IsValid()); + EXPECT_EQ(2, q.GetTokenCount()); + EXPECT_EQ(3, q.GetTokens()[0].length); + EXPECT_STREQ("foo", q.GetTokens()[0].name); + EXPECT_EQ(1, q.GetTokens()[1].length); + EXPECT_STREQ("0", q.GetTokens()[1].name); + EXPECT_EQ(0, q.GetTokens()[1].index); + } +} + TEST(Pointer, Create) { Document d; EXPECT_EQ(&d, &Pointer("").Create(d, d.GetAllocator())); From b2d72ef751afc618ba38a22c29d1b607c23df0a9 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 10 Apr 2015 19:28:12 +0800 Subject: [PATCH 04/43] Add XXXByPointer() helper functions --- include/rapidjson/document.h | 2 +- include/rapidjson/pointer.h | 66 +++++++++++++++++++++++++++++ test/unittest/pointertest.cpp | 78 +++++++++++++++++++++++++++++++++-- 3 files changed, 142 insertions(+), 4 deletions(-) diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index d41a87e..3b7ef2b 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -427,6 +427,7 @@ public: typedef typename GenericMemberIterator::Iterator ConstMemberIterator; //!< Constant member iterator for iterating in object. typedef GenericValue* ValueIterator; //!< Value iterator for iterating in array. typedef const GenericValue* ConstValueIterator; //!< Constant value iterator for iterating in array. + typedef GenericValue ValueType; //!< Value type of itself. //!@name Constructors and destructor. //@{ @@ -1661,7 +1662,6 @@ template , typenam class GenericDocument : public GenericValue { public: typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. - typedef GenericValue ValueType; //!< Value type of the document. typedef Allocator AllocatorType; //!< Allocator type from template parameter. //! Constructor diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 1ec157e..99a4c1a 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -325,6 +325,72 @@ private: bool valid_; }; +template +typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer& pointer, typename T::AllocatorType& a) { + return pointer.Create(root, a); +} + +template +typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], typename T::AllocatorType& a) { + const Pointer pointer(source, N - 1); + return pointer.Create(root, a); +} + +template +typename T::ValueType* GetValueByPointer(T& root, const GenericPointer& pointer) { + return pointer.Get(root); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const GenericPointer& pointer) { + return pointer.Get(root); +} + +template +typename T::ValueType* GetValueByPointer(T& root, const CharType (&source)[N]) { + const Pointer pointer(source, N - 1); + return pointer.Get(root); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const CharType(&source)[N]) { + const Pointer pointer(source, N - 1); + return pointer.Get(root); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + const Pointer pointer(source, N - 1); + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + const Pointer pointer(source, N - 1); + return pointer.Set(root, value , a); +} + +template +typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Swap(root, value, a); +} + +template +typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + const Pointer pointer(source, N - 1); + return pointer.Swap(root, value, a); +} + typedef GenericPointer Pointer; RAPIDJSON_NAMESPACE_END diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 4d4aece..245fa44 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -244,9 +244,18 @@ TEST(Pointer, Assignment) { TEST(Pointer, Create) { Document d; - EXPECT_EQ(&d, &Pointer("").Create(d, d.GetAllocator())); - EXPECT_EQ(&d["foo"], &Pointer("/foo").Create(d, d.GetAllocator())); - EXPECT_EQ(&d["foo"][0], &Pointer("/foo/0").Create(d, d.GetAllocator())); + { + Value* v = &Pointer("").Create(d, d.GetAllocator()); + EXPECT_EQ(&d, v); + } + { + Value* v = &Pointer("/foo").Create(d, d.GetAllocator()); + EXPECT_EQ(&d["foo"], v); + } + { + Value* v = &Pointer("/foo/0").Create(d, d.GetAllocator()); + EXPECT_EQ(&d["foo"][0], v); + } } TEST(Pointer, Get) { @@ -265,6 +274,7 @@ TEST(Pointer, Get) { EXPECT_EQ(&d["k\"l"], Pointer("/k\"l").Get(d)); EXPECT_EQ(&d[" "], Pointer("/ ").Get(d)); EXPECT_EQ(&d["m~n"], Pointer("/m~0n").Get(d)); + EXPECT_TRUE(Pointer("/abc").Get(d) == 0); } TEST(Pointer, GetWithDefault) { @@ -298,3 +308,65 @@ TEST(Pointer, Swap) { EXPECT_STREQ("baz", d["foo"][0].GetString()); EXPECT_STREQ("bar", d["foo"][1].GetString()); } + +TEST(Pointer, CreateValueByPointer) { + Document d; + Document::AllocatorType& a = d.GetAllocator(); + + { + Value& v = CreateValueByPointer(d, Pointer("/foo/0"), a); + EXPECT_EQ(&d["foo"][0], &v); + } + { + Value& v = CreateValueByPointer(d, "/foo/1", a); + EXPECT_EQ(&d["foo"][1], &v); + } +} + +TEST(Pointer, GetValueByPointer) { + Document d; + d.Parse(kJson); + + EXPECT_EQ(&d["foo"][0], GetValueByPointer(d, Pointer("/foo/0"))); + EXPECT_EQ(&d["foo"][0], GetValueByPointer(d, "/foo/0")); + + // const version + const Value& v = d; + EXPECT_EQ(&d["foo"][0], GetValueByPointer(v, Pointer("/foo/0"))); + EXPECT_EQ(&d["foo"][0], GetValueByPointer(v, "/foo/0")); +} + +TEST(Pointer, GetValueByPointerWithDefault) { + Document d; + d.Parse(kJson); + + Document::AllocatorType& a = d.GetAllocator(); + const Value v("qux"); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, Pointer("/foo/0"), v, a)); + EXPECT_TRUE(Value("baz") == GetValueByPointerWithDefault(d, "/foo/1", v, a)); +} + +TEST(Pointer, SetValueByPointer) { + Document d; + d.Parse(kJson); + Document::AllocatorType& a = d.GetAllocator(); + + SetValueByPointer(d, Pointer("/foo/0"), Value(123).Move(), a); + EXPECT_EQ(123, d["foo"][0].GetInt()); + + SetValueByPointer(d, "/foo/2", Value(456).Move(), a); + EXPECT_EQ(456, d["foo"][2].GetInt()); +} + +TEST(Pointer, SwapValueByPointer) { + Document d; + d.Parse(kJson); + Document::AllocatorType& a = d.GetAllocator(); + SwapValueByPointer(d, Pointer("/foo/0"), *GetValueByPointer(d, "/foo/1"), a); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_STREQ("bar", d["foo"][1].GetString()); + + SwapValueByPointer(d, "/foo/0", *GetValueByPointer(d, "/foo/1"), a); + EXPECT_STREQ("bar", d["foo"][0].GetString()); + EXPECT_STREQ("baz", d["foo"][1].GetString()); +} From 601a62e5b3a8db89fd5f5c83ed7d9fe5cacfa093 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 10 Apr 2015 20:59:39 +0800 Subject: [PATCH 05/43] Try to fix a gcc/clang issue after removing Document::ValueType --- include/rapidjson/document.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index 3b7ef2b..e7a707d 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -1662,6 +1662,7 @@ template , typenam class GenericDocument : public GenericValue { public: typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericValue ValueType; //!< Value type of the document. typedef Allocator AllocatorType; //!< Allocator type from template parameter. //! Constructor From 1b9cab7f12cff1b820bce66afa6ed887314b788e Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 10 Apr 2015 21:40:18 +0800 Subject: [PATCH 06/43] Try to fix more gcc/clang compilation errors --- include/rapidjson/pointer.h | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 99a4c1a..2000b21 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -26,7 +26,7 @@ public: typedef typename EncodingType::Ch Ch; struct Token { - const typename Ch* name; + const Ch* name; SizeType length; SizeType index; //!< A valid index if not equal to kInvalidIndex. }; @@ -149,11 +149,12 @@ public: ValueType* v = &root; bool exist = true; for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { - if (v->GetType() != kObjectType && v->GetType() != kArrayType) + if (v->GetType() != kObjectType && v->GetType() != kArrayType) { if (t->index == kInvalidIndex) v->SetObject(); else v->SetArray(); + } switch (v->GetType()) { case kObjectType: @@ -220,8 +221,10 @@ public: ValueType& GetWithDefault(ValueType& root, const ValueType& defaultValue, typename ValueType::AllocatorType& allocator) const { bool alreadyExist; Value& v = Create(root, allocator, &alreadyExist); - if (!alreadyExist) - v = Value(defaultValue, allocator); + if (!alreadyExist) { + Value clone(defaultValue, allocator); + v = clone; + } return v; } @@ -332,7 +335,7 @@ typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], typename T::AllocatorType& a) { - const Pointer pointer(source, N - 1); + const GenericPointer pointer(source, N - 1); return pointer.Create(root, a); } @@ -348,13 +351,13 @@ const typename T::ValueType* GetValueByPointer(const T& root, const GenericPoint template typename T::ValueType* GetValueByPointer(T& root, const CharType (&source)[N]) { - const Pointer pointer(source, N - 1); + const GenericPointer pointer(source, N - 1); return pointer.Get(root); } template const typename T::ValueType* GetValueByPointer(const T& root, const CharType(&source)[N]) { - const Pointer pointer(source, N - 1); + const GenericPointer pointer(source, N - 1); return pointer.Get(root); } @@ -365,7 +368,7 @@ typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointe template typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { - const Pointer pointer(source, N - 1); + const GenericPointer pointer(source, N - 1); return pointer.GetWithDefault(root, defaultValue, a); } @@ -376,7 +379,7 @@ typename T::ValueType& SetValueByPointer(T& root, const GenericPointer typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { - const Pointer pointer(source, N - 1); + const GenericPointer pointer(source, N - 1); return pointer.Set(root, value , a); } @@ -387,7 +390,7 @@ typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { - const Pointer pointer(source, N - 1); + const GenericPointer pointer(source, N - 1); return pointer.Swap(root, value, a); } From dfc864b1d3544c8bbd1f51721ef78a097da97843 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 10 Apr 2015 22:06:56 +0800 Subject: [PATCH 07/43] Fix a bug in Pointer::Create() which makes it very slow --- include/rapidjson/pointer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 2000b21..421a2cb 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -173,7 +173,7 @@ public: if (t->index == kInvalidIndex) v->SetArray(); // Change to Array if (t->index >= v->Size()) { - v->Reserve(t->index - 1, allocator); + v->Reserve(t->index + 1, allocator); while (t->index >= v->Size()) v->PushBack(Value().Move(), allocator); exist = false; From 26be3be5c795f7bd829078188400df5a072e03a4 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 10 Apr 2015 22:12:59 +0800 Subject: [PATCH 08/43] Fix several clang/gcc warnings --- include/rapidjson/pointer.h | 4 ++ test/unittest/pointertest.cpp | 82 +++++++++++++++++------------------ 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 421a2cb..7954e12 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -180,6 +180,10 @@ public: } v = &((*v)[t->index]); break; + default: + // Impossible. + RAPIDJSON_ASSERT(false); + break; } } diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 245fa44..3147959 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -42,34 +42,34 @@ TEST(Pointer, Parse) { { Pointer p(""); EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(0, p.GetTokenCount()); + EXPECT_EQ(0u, p.GetTokenCount()); } { Pointer p("/foo"); EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(1, p.GetTokenCount()); - EXPECT_EQ(3, p.GetTokens()[0].length); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); EXPECT_STREQ("foo", p.GetTokens()[0].name); } { Pointer p("/foo/0"); EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(2, p.GetTokenCount()); - EXPECT_EQ(3, p.GetTokens()[0].length); + EXPECT_EQ(2u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); EXPECT_STREQ("foo", p.GetTokens()[0].name); - EXPECT_EQ(1, p.GetTokens()[1].length); + EXPECT_EQ(1u, p.GetTokens()[1].length); EXPECT_STREQ("0", p.GetTokens()[1].name); - EXPECT_EQ(0, p.GetTokens()[1].index); + EXPECT_EQ(0u, p.GetTokens()[1].index); } { // Unescape ~1 Pointer p("/a~1b"); EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(1, p.GetTokenCount()); - EXPECT_EQ(3, p.GetTokens()[0].length); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); EXPECT_STREQ("a/b", p.GetTokens()[0].name); } @@ -77,8 +77,8 @@ TEST(Pointer, Parse) { // Unescape ~0 Pointer p("/m~0n"); EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(1, p.GetTokenCount()); - EXPECT_EQ(3, p.GetTokens()[0].length); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); EXPECT_STREQ("m~n", p.GetTokens()[0].name); } @@ -86,8 +86,8 @@ TEST(Pointer, Parse) { // empty name Pointer p("/"); EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(1, p.GetTokenCount()); - EXPECT_EQ(0, p.GetTokens()[0].length); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(0u, p.GetTokens()[0].length); EXPECT_STREQ("", p.GetTokens()[0].name); } @@ -95,10 +95,10 @@ TEST(Pointer, Parse) { // empty and non-empty name Pointer p("//a"); EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(2, p.GetTokenCount()); - EXPECT_EQ(0, p.GetTokens()[0].length); + EXPECT_EQ(2u, p.GetTokenCount()); + EXPECT_EQ(0u, p.GetTokens()[0].length); EXPECT_STREQ("", p.GetTokens()[0].name); - EXPECT_EQ(1, p.GetTokens()[1].length); + EXPECT_EQ(1u, p.GetTokens()[1].length); EXPECT_STREQ("a", p.GetTokens()[1].name); } @@ -106,8 +106,8 @@ TEST(Pointer, Parse) { // Null characters Pointer p("/\0\0", 3); EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(1, p.GetTokenCount()); - EXPECT_EQ(2, p.GetTokens()[0].length); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(2u, p.GetTokens()[0].length); EXPECT_EQ('\0', p.GetTokens()[0].name[0]); EXPECT_EQ('\0', p.GetTokens()[0].name[1]); EXPECT_EQ('\0', p.GetTokens()[0].name[2]); @@ -117,7 +117,7 @@ TEST(Pointer, Parse) { // Valid index Pointer p("/123"); EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(1, p.GetTokenCount()); + EXPECT_EQ(1u, p.GetTokenCount()); EXPECT_STREQ("123", p.GetTokens()[0].name); EXPECT_EQ(123, p.GetTokens()[0].index); } @@ -126,7 +126,7 @@ TEST(Pointer, Parse) { // Invalid index (with leading zero) Pointer p("/01"); EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(1, p.GetTokenCount()); + EXPECT_EQ(1u, p.GetTokenCount()); EXPECT_STREQ("01", p.GetTokens()[0].name); EXPECT_EQ(Pointer::kInvalidIndex, p.GetTokens()[0].index); } @@ -135,7 +135,7 @@ TEST(Pointer, Parse) { // Invalid index (overflow) Pointer p("/4294967296"); EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(1, p.GetTokenCount()); + EXPECT_EQ(1u, p.GetTokenCount()); EXPECT_STREQ("4294967296", p.GetTokens()[0].name); EXPECT_EQ(Pointer::kInvalidIndex, p.GetTokens()[0].index); } @@ -178,12 +178,12 @@ static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(0) }; // equivalent TEST(Pointer, ConstructorWithToken) { Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(2, p.GetTokenCount()); - EXPECT_EQ(3, p.GetTokens()[0].length); + EXPECT_EQ(2u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); EXPECT_STREQ("foo", p.GetTokens()[0].name); - EXPECT_EQ(1, p.GetTokens()[1].length); + EXPECT_EQ(1u, p.GetTokens()[1].length); EXPECT_STREQ("0", p.GetTokens()[1].name); - EXPECT_EQ(0, p.GetTokens()[1].index); + EXPECT_EQ(0u, p.GetTokens()[1].index); } TEST(Pointer, CopyConstructor) { @@ -191,12 +191,12 @@ TEST(Pointer, CopyConstructor) { Pointer p("/foo/0"); Pointer q(p); EXPECT_TRUE(q.IsValid()); - EXPECT_EQ(2, q.GetTokenCount()); - EXPECT_EQ(3, q.GetTokens()[0].length); + EXPECT_EQ(2u, q.GetTokenCount()); + EXPECT_EQ(3u, q.GetTokens()[0].length); EXPECT_STREQ("foo", q.GetTokens()[0].name); - EXPECT_EQ(1, q.GetTokens()[1].length); + EXPECT_EQ(1u, q.GetTokens()[1].length); EXPECT_STREQ("0", q.GetTokens()[1].name); - EXPECT_EQ(0, q.GetTokens()[1].index); + EXPECT_EQ(0u, q.GetTokens()[1].index); } // Static tokens @@ -204,12 +204,12 @@ TEST(Pointer, CopyConstructor) { Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); Pointer q(p); EXPECT_TRUE(q.IsValid()); - EXPECT_EQ(2, q.GetTokenCount()); - EXPECT_EQ(3, q.GetTokens()[0].length); + EXPECT_EQ(2u, q.GetTokenCount()); + EXPECT_EQ(3u, q.GetTokens()[0].length); EXPECT_STREQ("foo", q.GetTokens()[0].name); - EXPECT_EQ(1, q.GetTokens()[1].length); + EXPECT_EQ(1u, q.GetTokens()[1].length); EXPECT_STREQ("0", q.GetTokens()[1].name); - EXPECT_EQ(0, q.GetTokens()[1].index); + EXPECT_EQ(0u, q.GetTokens()[1].index); } } @@ -219,12 +219,12 @@ TEST(Pointer, Assignment) { Pointer q; q = p; EXPECT_TRUE(q.IsValid()); - EXPECT_EQ(2, q.GetTokenCount()); - EXPECT_EQ(3, q.GetTokens()[0].length); + EXPECT_EQ(2u, q.GetTokenCount()); + EXPECT_EQ(3u, q.GetTokens()[0].length); EXPECT_STREQ("foo", q.GetTokens()[0].name); - EXPECT_EQ(1, q.GetTokens()[1].length); + EXPECT_EQ(1u, q.GetTokens()[1].length); EXPECT_STREQ("0", q.GetTokens()[1].name); - EXPECT_EQ(0, q.GetTokens()[1].index); + EXPECT_EQ(0u, q.GetTokens()[1].index); } // Static tokens @@ -233,12 +233,12 @@ TEST(Pointer, Assignment) { Pointer q; q = p; EXPECT_TRUE(q.IsValid()); - EXPECT_EQ(2, q.GetTokenCount()); - EXPECT_EQ(3, q.GetTokens()[0].length); + EXPECT_EQ(2u, q.GetTokenCount()); + EXPECT_EQ(3u, q.GetTokens()[0].length); EXPECT_STREQ("foo", q.GetTokens()[0].name); - EXPECT_EQ(1, q.GetTokens()[1].length); + EXPECT_EQ(1u, q.GetTokens()[1].length); EXPECT_STREQ("0", q.GetTokens()[1].name); - EXPECT_EQ(0, q.GetTokens()[1].index); + EXPECT_EQ(0u, q.GetTokens()[1].index); } } From e0743b2fb0ace5d3ceedc11c20f7c3b35df1f1fd Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 10 Apr 2015 22:43:46 +0800 Subject: [PATCH 09/43] Fix a clang/gcc warning --- test/unittest/pointertest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 3147959..c23e9b8 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -119,7 +119,7 @@ TEST(Pointer, Parse) { EXPECT_TRUE(p.IsValid()); EXPECT_EQ(1u, p.GetTokenCount()); EXPECT_STREQ("123", p.GetTokens()[0].name); - EXPECT_EQ(123, p.GetTokens()[0].index); + EXPECT_EQ(123u, p.GetTokens()[0].index); } { From c7bcdb9c0cf0b9e7b9e1b121fdc776030f0af676 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 10 Apr 2015 23:06:17 +0800 Subject: [PATCH 10/43] Fix a clang/gcc linker error static const member integer not working --- include/rapidjson/pointer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 7954e12..4f77ba2 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -31,7 +31,7 @@ public: SizeType index; //!< A valid index if not equal to kInvalidIndex. }; - static const SizeType kInvalidIndex = ~SizeType(0); + enum { kInvalidIndex = ~SizeType(0) }; GenericPointer() : allocator_(), From bd435f76abc4f8e1f9f8301f510bbf225312ee18 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 10 Apr 2015 23:32:28 +0800 Subject: [PATCH 11/43] Another trial to fix the kInvalidIndex problem --- include/rapidjson/pointer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 4f77ba2..66cbdef 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -31,7 +31,7 @@ public: SizeType index; //!< A valid index if not equal to kInvalidIndex. }; - enum { kInvalidIndex = ~SizeType(0) }; + static const SizeType kInvalidIndex = -1; GenericPointer() : allocator_(), From 6ee691550f22c419579a457a0d7b1c9bc642f1f2 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sat, 11 Apr 2015 14:48:33 +0800 Subject: [PATCH 12/43] Move GenericPointer::kInvalidIndex to rapidjson::kPointerInvalidIndex It is needed to prevent linking error for gcc/clang --- include/rapidjson/pointer.h | 14 +++++++------- test/unittest/pointertest.cpp | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 66cbdef..6ee6abc 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -19,6 +19,8 @@ RAPIDJSON_NAMESPACE_BEGIN +static const SizeType kPointerInvalidIndex = ~SizeType(0); + template class GenericPointer { public: @@ -28,11 +30,9 @@ public: struct Token { const Ch* name; SizeType length; - SizeType index; //!< A valid index if not equal to kInvalidIndex. + SizeType index; //!< A valid index if not equal to kPointerInvalidIndex. }; - static const SizeType kInvalidIndex = -1; - GenericPointer() : allocator_(), ownAllocator_(), @@ -150,7 +150,7 @@ public: bool exist = true; for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { if (v->GetType() != kObjectType && v->GetType() != kArrayType) { - if (t->index == kInvalidIndex) + if (t->index == kPointerInvalidIndex) v->SetObject(); else v->SetArray(); @@ -170,7 +170,7 @@ public: } break; case kArrayType: - if (t->index == kInvalidIndex) + if (t->index == kPointerInvalidIndex) v->SetArray(); // Change to Array if (t->index >= v->Size()) { v->Reserve(t->index + 1, allocator); @@ -207,7 +207,7 @@ public: } break; case kArrayType: - if (t->index == kInvalidIndex || t->index >= v->Size()) + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) return 0; v = &((*v)[t->index]); break; @@ -307,7 +307,7 @@ private: } } - token.index = isNumber ? n : kInvalidIndex; + token.index = isNumber ? n : kPointerInvalidIndex; } RAPIDJSON_ASSERT(name <= nameBuffer_ + length); // Should not overflow buffer diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index c23e9b8..98a006d 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -128,7 +128,7 @@ TEST(Pointer, Parse) { EXPECT_TRUE(p.IsValid()); EXPECT_EQ(1u, p.GetTokenCount()); EXPECT_STREQ("01", p.GetTokens()[0].name); - EXPECT_EQ(Pointer::kInvalidIndex, p.GetTokens()[0].index); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); } if (sizeof(SizeType) == 4) { @@ -137,7 +137,7 @@ TEST(Pointer, Parse) { EXPECT_TRUE(p.IsValid()); EXPECT_EQ(1u, p.GetTokenCount()); EXPECT_STREQ("4294967296", p.GetTokens()[0].name); - EXPECT_EQ(Pointer::kInvalidIndex, p.GetTokens()[0].index); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); } } @@ -167,7 +167,7 @@ TEST(Pointer, Stringify) { } // Construct a Pointer with static tokens, no dynamic allocation involved. -#define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, Pointer::kInvalidIndex } +#define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, kPointerInvalidIndex } #define INDEX(i) { #i, sizeof(#i) - 1, i } static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(0) }; // equivalent to "/foo/0" From 1ef380586de0d53df768d743e217b1cf3e2738dc Mon Sep 17 00:00:00 2001 From: miloyip Date: Sat, 2 May 2015 20:07:50 +0800 Subject: [PATCH 13/43] Fix a bug in Pointer::Create() and Add different overloads for Set() related implementations --- include/rapidjson/pointer.h | 105 +++++++++++++++++++++------- test/unittest/pointertest.cpp | 127 ++++++++++++++++++++++++++++++++-- 2 files changed, 201 insertions(+), 31 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 6ee6abc..e470585 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -43,7 +43,7 @@ public: { } - GenericPointer(const Ch* source, Allocator* allocator = 0) + explicit GenericPointer(const Ch* source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), @@ -156,22 +156,23 @@ public: v->SetArray(); } - switch (v->GetType()) { - case kObjectType: - { - typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); - if (m == v->MemberEnd()) { - v->AddMember(Value(t->name, t->length, allocator).Move(), Value().Move(), allocator); - v = &(--v->MemberEnd())->value; // Assumes AddMember() appends at the end - exist = false; - } - else - v = &m->value; + if (t->index == kPointerInvalidIndex) { + if (!v->IsObject()) + v->SetObject(); // Change to Object + + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) { + v->AddMember(Value(t->name, t->length, allocator).Move(), Value().Move(), allocator); + v = &(--v->MemberEnd())->value; // Assumes AddMember() appends at the end + exist = false; } - break; - case kArrayType: - if (t->index == kPointerInvalidIndex) + else + v = &m->value; + } + else { + if (!v->IsArray()) v->SetArray(); // Change to Array + if (t->index >= v->Size()) { v->Reserve(t->index + 1, allocator); while (t->index >= v->Size()) @@ -179,11 +180,6 @@ public: exist = false; } v = &((*v)[t->index]); - break; - default: - // Impossible. - RAPIDJSON_ASSERT(false); - break; } } @@ -237,6 +233,28 @@ public: return Create(root, allocator) = value; } + // Copy semantics, create parents if non-exist + ValueType& Set(ValueType& root, const ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).CopyFrom(value, allocator); + } + + ValueType& Set(ValueType& root, GenericStringRef value, typename ValueType::AllocatorType& allocator) const { + ValueType v(value); + return Create(root, allocator) = v; + } + + ValueType& Set(ValueType& root, const Ch* value, typename ValueType::AllocatorType& allocator) const { + ValueType v(value, allocator); + return Create(root, allocator) = v; + } + + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(ValueType& root, T value, typename ValueType::AllocatorType& allocator) const { + ValueType v(value); + return Create(root, allocator) = v; + } + // Create parents if non-exist ValueType& Swap(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator).Swap(value); @@ -340,7 +358,7 @@ typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], typename T::AllocatorType& a) { const GenericPointer pointer(source, N - 1); - return pointer.Create(root, a); + return CreateValueByPointer(root, pointer, a); } template @@ -356,13 +374,13 @@ const typename T::ValueType* GetValueByPointer(const T& root, const GenericPoint template typename T::ValueType* GetValueByPointer(T& root, const CharType (&source)[N]) { const GenericPointer pointer(source, N - 1); - return pointer.Get(root); + return GetValueByPointer(root, pointer); } template const typename T::ValueType* GetValueByPointer(const T& root, const CharType(&source)[N]) { const GenericPointer pointer(source, N - 1); - return pointer.Get(root); + return GetValueByPointer(root, pointer); } template @@ -373,7 +391,7 @@ typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointe template typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { const GenericPointer pointer(source, N - 1); - return pointer.GetWithDefault(root, defaultValue, a); + return GetValueByPointerWithDefault(root, pointer, defaultValue, a); } template @@ -381,10 +399,45 @@ typename T::ValueType& SetValueByPointer(T& root, const GenericPointer +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, GenericStringRef value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const GenericPointer& pointer, T2 value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { const GenericPointer pointer(source, N - 1); - return pointer.Set(root, value , a); + return SetValueByPointer(root, pointer, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], GenericStringRef value, typename T::AllocatorType& a) { + const GenericPointer pointer(source, N - 1); + return SetValueByPointer(root, pointer, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value, typename T::AllocatorType& a) { + const GenericPointer pointer(source, N - 1); + return SetValueByPointer(root, pointer, value, a); +} + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const CharType(&source)[N], T2 value, typename T::AllocatorType& a) { + const GenericPointer pointer(source, N - 1); + return SetValueByPointer(root, pointer, value, a); } template @@ -395,7 +448,7 @@ typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { const GenericPointer pointer(source, N - 1); - return pointer.Swap(root, value, a); + return SwapValueByPointer(root, pointer, value, a); } typedef GenericPointer Pointer; diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 98a006d..3efa0ec 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -293,11 +293,46 @@ TEST(Pointer, Set) { d.Parse(kJson); Document::AllocatorType& a = d.GetAllocator(); + // Value version Pointer("/foo/0").Set(d, Value(123).Move(), a); EXPECT_EQ(123, d["foo"][0].GetInt()); - Pointer("/foo/2").Set(d, Value(456).Move(), a); - EXPECT_EQ(456, d["foo"][2].GetInt()); + Pointer("/foo/null").Set(d, Value().Move(), a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + + // Generic version + Pointer("/foo/int").Set(d, -1, a); + EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); + + Pointer("/foo/uint").Set(d, 0x87654321, a); + EXPECT_EQ(0x87654321, GetValueByPointer(d, "/foo/uint")->GetUint()); + + const int64_t i64 = static_cast(RAPIDJSON_UINT64_C2(0x80000000, 0)); + Pointer("/foo/int64").Set(d, i64, a); + EXPECT_EQ(i64, GetValueByPointer(d, "/foo/int64")->GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + Pointer("/foo/uint64").Set(d, u64, a); + EXPECT_EQ(u64, GetValueByPointer(d, "/foo/uint64")->GetUint64()); + + Pointer("/foo/true").Set(d, true, a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/true")->IsTrue()); + + Pointer("/foo/false").Set(d, false, a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/false")->IsFalse()); + + // StringRef version + Pointer("/foo/hello").Set(d, "Hello", a); + EXPECT_STREQ("Hello", GetValueByPointer(d, "/foo/hello")->GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + Pointer("/foo/world").Set(d, buffer, a); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); } TEST(Pointer, Swap) { @@ -346,16 +381,98 @@ TEST(Pointer, GetValueByPointerWithDefault) { EXPECT_TRUE(Value("baz") == GetValueByPointerWithDefault(d, "/foo/1", v, a)); } -TEST(Pointer, SetValueByPointer) { +TEST(Pointer, SetValueByPointer_Pointer) { Document d; d.Parse(kJson); Document::AllocatorType& a = d.GetAllocator(); + // Value version SetValueByPointer(d, Pointer("/foo/0"), Value(123).Move(), a); EXPECT_EQ(123, d["foo"][0].GetInt()); - SetValueByPointer(d, "/foo/2", Value(456).Move(), a); - EXPECT_EQ(456, d["foo"][2].GetInt()); + SetValueByPointer(d, Pointer("/foo/null"), Value().Move(), a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + + // Generic version + SetValueByPointer(d, Pointer("/foo/int"), -1, a); + EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); + + SetValueByPointer(d, Pointer("/foo/uint"), 0x87654321, a); + EXPECT_EQ(0x87654321, GetValueByPointer(d, "/foo/uint")->GetUint()); + + const int64_t i64 = static_cast(RAPIDJSON_UINT64_C2(0x80000000, 0)); + SetValueByPointer(d, Pointer("/foo/int64"), i64, a); + EXPECT_EQ(i64, GetValueByPointer(d, "/foo/int64")->GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + SetValueByPointer(d, Pointer("/foo/uint64"), u64, a); + EXPECT_EQ(u64, GetValueByPointer(d, "/foo/uint64")->GetUint64()); + + SetValueByPointer(d, Pointer("/foo/true"), true, a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/true")->IsTrue()); + + SetValueByPointer(d, Pointer("/foo/false"), false, a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/false")->IsFalse()); + + // StringRef version + SetValueByPointer(d, Pointer("/foo/hello"), "Hello", a); + EXPECT_STREQ("Hello", GetValueByPointer(d, "/foo/hello")->GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + SetValueByPointer(d, Pointer("/foo/world"), buffer, a); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); +} + +TEST(Pointer, SetValueByPointer_String) { + Document d; + d.Parse(kJson); + Document::AllocatorType& a = d.GetAllocator(); + + // Value version + SetValueByPointer(d, "/foo/0", Value(123).Move(), a); + EXPECT_EQ(123, d["foo"][0].GetInt()); + + SetValueByPointer(d, "/foo/null", Value().Move(), a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + + // Generic version + SetValueByPointer(d, "/foo/int", -1, a); + EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); + + SetValueByPointer(d, "/foo/uint", 0x87654321, a); + EXPECT_EQ(0x87654321, GetValueByPointer(d, "/foo/uint")->GetUint()); + + const int64_t i64 = static_cast(RAPIDJSON_UINT64_C2(0x80000000, 0)); + SetValueByPointer(d, "/foo/int64", i64, a); + EXPECT_EQ(i64, GetValueByPointer(d, "/foo/int64")->GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + SetValueByPointer(d, "/foo/uint64", u64, a); + EXPECT_EQ(u64, GetValueByPointer(d, "/foo/uint64")->GetUint64()); + + SetValueByPointer(d, "/foo/true", true, a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/true")->IsTrue()); + + SetValueByPointer(d, "/foo/false", false, a); + EXPECT_TRUE(GetValueByPointer(d, "/foo/false")->IsFalse()); + + // StringRef version + SetValueByPointer(d, "/foo/hello", "Hello", a); + EXPECT_STREQ("Hello", GetValueByPointer(d, "/foo/hello")->GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + SetValueByPointer(d, "/foo/world", buffer, a); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); } TEST(Pointer, SwapValueByPointer) { From 2ece55abc7001962870f7ee4effbd1c852ee182e Mon Sep 17 00:00:00 2001 From: miloyip Date: Sat, 2 May 2015 20:44:30 +0800 Subject: [PATCH 14/43] Implement pointer parse error --- include/rapidjson/pointer.h | 114 ++++++++++++++++++++++------------ test/unittest/pointertest.cpp | 24 +++++++ 2 files changed, 97 insertions(+), 41 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index e470585..c0b7ba1 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -21,6 +21,13 @@ RAPIDJSON_NAMESPACE_BEGIN static const SizeType kPointerInvalidIndex = ~SizeType(0); +enum PointerParseErrorCode { + kPointerParseErrorNone = 0, + + kPointerParseErrorTokenMustBeginWithSolidus, + kPointerParseErrorInvalidEscape +}; + template class GenericPointer { public: @@ -33,55 +40,60 @@ public: SizeType index; //!< A valid index if not equal to kPointerInvalidIndex. }; - GenericPointer() - : allocator_(), + GenericPointer() : + allocator_(), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), - valid_(true) + parseErrorOffset_(), + parseErrorCode_(kPointerParseErrorNone) { } - explicit GenericPointer(const Ch* source, Allocator* allocator = 0) - : allocator_(allocator), - ownAllocator_(), - nameBuffer_(), - tokens_(), - tokenCount_(), - valid_(true) + explicit GenericPointer(const Ch* source, Allocator* allocator = 0) : + allocator_(allocator), + ownAllocator_(), + nameBuffer_(), + tokens_(), + tokenCount_(), + parseErrorOffset_(), + parseErrorCode_(kPointerParseErrorNone) { Parse(source, internal::StrLen(source)); } - GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) - : allocator_(allocator), - ownAllocator_(), - nameBuffer_(), - tokens_(), - tokenCount_(), - valid_(true) + GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) : + allocator_(allocator), + ownAllocator_(), + nameBuffer_(), + tokens_(), + tokenCount_(), + parseErrorOffset_(), + parseErrorCode_(kPointerParseErrorNone) { Parse(source, length); } - GenericPointer(const Token* tokens, size_t tokenCount) - : allocator_(), - ownAllocator_(), - nameBuffer_(), - tokens_(const_cast(tokens)), - tokenCount_(tokenCount), - valid_(true) + GenericPointer(const Token* tokens, size_t tokenCount) : + allocator_(), + ownAllocator_(), + nameBuffer_(), + tokens_(const_cast(tokens)), + tokenCount_(tokenCount), + parseErrorOffset_(), + parseErrorCode_(kPointerParseErrorNone) { } - GenericPointer(const GenericPointer& rhs) - : allocator_(), - ownAllocator_(), - nameBuffer_(), - tokens_(), - tokenCount_(), - valid_() + GenericPointer(const GenericPointer& rhs) : + allocator_(), + ownAllocator_(), + nameBuffer_(), + tokens_(), + tokenCount_(), + parseErrorOffset_(), + parseErrorCode_(kPointerParseErrorNone) { *this = rhs; } @@ -98,7 +110,8 @@ public: this->~GenericPointer(); tokenCount_ = rhs.tokenCount_; - valid_ = rhs.valid_; + parseErrorOffset_ = rhs.parseErrorOffset_; + parseErrorCode_ = rhs.parseErrorCode_; if (rhs.nameBuffer_) { if (!allocator_) @@ -124,7 +137,11 @@ public: return *this; } - bool IsValid() const { return valid_; } + bool IsValid() const { return parseErrorCode_ == kPointerParseErrorNone; } + + size_t GetParseErrorOffset() const { return parseErrorOffset_; } + + PointerParseErrorCode GetParseErrorCode() const { return parseErrorCode_; } const Token* GetTokens() const { return tokens_; } @@ -276,9 +293,16 @@ private: tokenCount_ = 0; Ch* name = nameBuffer_; - for (size_t i = 0; i < length;) { - if (source[i++] != '/') // Consumes '/' - goto error; + size_t i = 0; + + if (length != 0 && source[i] != '/') { + parseErrorCode_ = kPointerParseErrorTokenMustBeginWithSolidus; + goto error; + } + + while (i < length) { + RAPIDJSON_ASSERT(source[i] == '/'); + i++; // consumes '/' Token& token = tokens_[tokenCount_++]; token.name = name; @@ -290,13 +314,19 @@ private: // Escaping "~0" -> '~', "~1" -> '/' if (c == '~') { if (i < length) { - c = source[i++]; + c = source[i]; if (c == '0') c = '~'; else if (c == '1') c = '/'; - else goto error; + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + i++; } - else + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; goto error; + } } // First check for index: all of characters are digit @@ -330,6 +360,7 @@ private: RAPIDJSON_ASSERT(name <= nameBuffer_ + length); // Should not overflow buffer tokens_ = (Token*)allocator_->Realloc(tokens_, length * sizeof(Token), tokenCount_ * sizeof(Token)); // Shrink tokens_ + parseErrorCode_ = kPointerParseErrorNone; return; error: @@ -338,7 +369,7 @@ private: nameBuffer_ = 0; tokens_ = 0; tokenCount_ = 0; - valid_ = false; + parseErrorOffset_ = i; return; } @@ -347,7 +378,8 @@ private: Ch* nameBuffer_; Token* tokens_; size_t tokenCount_; - bool valid_; + size_t parseErrorOffset_; + PointerParseErrorCode parseErrorCode_; }; template diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 3efa0ec..c58b858 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -139,6 +139,30 @@ TEST(Pointer, Parse) { EXPECT_STREQ("4294967296", p.GetTokens()[0].name); EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); } + + { + // kPointerParseErrorTokenMustBeginWithSolidus + Pointer p(" "); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorTokenMustBeginWithSolidus, p.GetParseErrorCode()); + EXPECT_EQ(0u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidEscape + Pointer p("/~"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidEscape, p.GetParseErrorCode()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidEscape + Pointer p("/~2"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidEscape, p.GetParseErrorCode()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); + } } TEST(Pointer, Stringify) { From 2ddbd09031b90765e31fc31a7607e3e0248a17d1 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sat, 2 May 2015 21:30:40 +0800 Subject: [PATCH 15/43] Add '-' support for Create() and Set() --- include/rapidjson/pointer.h | 39 +++++++++++++++++++---------------- test/unittest/pointertest.cpp | 13 ++++++++++++ 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index c0b7ba1..bc2795f 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -166,27 +166,30 @@ public: ValueType* v = &root; bool exist = true; for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { - if (v->GetType() != kObjectType && v->GetType() != kArrayType) { - if (t->index == kPointerInvalidIndex) - v->SetObject(); - else - v->SetArray(); - } - - if (t->index == kPointerInvalidIndex) { - if (!v->IsObject()) - v->SetObject(); // Change to Object - - typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); - if (m == v->MemberEnd()) { - v->AddMember(Value(t->name, t->length, allocator).Move(), Value().Move(), allocator); - v = &(--v->MemberEnd())->value; // Assumes AddMember() appends at the end + if (t->index == kPointerInvalidIndex) { // object name + // Handling of '-' for last element of array + if (t->name[0] == '-' && t->length == 1) { + if (!v->IsArray()) + v->SetArray(); // Change to Array + v->PushBack(Value().Move(), allocator); + v = &((*v)[v->Size() - 1]); exist = false; } - else - v = &m->value; + else { + if (!v->IsObject()) + v->SetObject(); // Change to Object + + typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); + if (m == v->MemberEnd()) { + v->AddMember(Value(t->name, t->length, allocator).Move(), Value().Move(), allocator); + v = &(--v->MemberEnd())->value; // Assumes AddMember() appends at the end + exist = false; + } + else + v = &m->value; + } } - else { + else { // array index if (!v->IsArray()) v->SetArray(); // Change to Array diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index c58b858..887a7f8 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -280,6 +280,14 @@ TEST(Pointer, Create) { Value* v = &Pointer("/foo/0").Create(d, d.GetAllocator()); EXPECT_EQ(&d["foo"][0], v); } + { + Value* v = &Pointer("/foo/-").Create(d, d.GetAllocator()); + EXPECT_EQ(&d["foo"][1], v); + } + { + Value* v = &Pointer("/foo/-/-").Create(d, d.GetAllocator()); + EXPECT_EQ(&d["foo"][2][0], v); + } } TEST(Pointer, Get) { @@ -310,6 +318,8 @@ TEST(Pointer, GetWithDefault) { EXPECT_TRUE(Value("bar") == Pointer("/foo/0").GetWithDefault(d, v, a)); EXPECT_TRUE(Value("baz") == Pointer("/foo/1").GetWithDefault(d, v, a)); EXPECT_TRUE(Value("qux") == Pointer("/foo/2").GetWithDefault(d, v, a)); + EXPECT_TRUE(Value("last") == Pointer("/foo/-").GetWithDefault(d, Value("last").Move(), a)); + EXPECT_STREQ("last", d["foo"][3].GetString()); } TEST(Pointer, Set) { @@ -321,6 +331,9 @@ TEST(Pointer, Set) { Pointer("/foo/0").Set(d, Value(123).Move(), a); EXPECT_EQ(123, d["foo"][0].GetInt()); + Pointer("/foo/-").Set(d, Value(456).Move(), a); + EXPECT_EQ(456, d["foo"][2].GetInt()); + Pointer("/foo/null").Set(d, Value().Move(), a); EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); From 32b45f6e6d15b229ea1907e9569e4792bff6e207 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sat, 2 May 2015 21:52:49 +0800 Subject: [PATCH 16/43] Add GetWithDefault() overloads --- include/rapidjson/pointer.h | 22 +++++++++++++++++++++ test/unittest/pointertest.cpp | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index bc2795f..16d2e60 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -248,6 +248,28 @@ public: return v; } + ValueType& GetWithDefault(ValueType& root, GenericStringRef defaultValue, typename ValueType::AllocatorType& allocator) const { + ValueType v(defaultValue); + return GetWithDefault(root, v, allocator); + } + + ValueType& GetWithDefault(ValueType& root, const Ch* defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + Value& v = Create(root, allocator, &alreadyExist); + if (!alreadyExist) { + Value clone(defaultValue, allocator); // This has overhead, so do it inside if. + v = clone; + } + return v; + } + + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(ValueType& root, T defaultValue, typename ValueType::AllocatorType& allocator) const { + ValueType v(defaultValue); + return GetWithDefault(root, v, allocator); + } + // Move semantics, create parents if non-exist ValueType& Set(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator) = value; diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 887a7f8..8368d51 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -313,6 +313,7 @@ TEST(Pointer, GetWithDefault) { Document d; d.Parse(kJson); + // Value version Document::AllocatorType& a = d.GetAllocator(); const Value v("qux"); EXPECT_TRUE(Value("bar") == Pointer("/foo/0").GetWithDefault(d, v, a)); @@ -320,6 +321,41 @@ TEST(Pointer, GetWithDefault) { EXPECT_TRUE(Value("qux") == Pointer("/foo/2").GetWithDefault(d, v, a)); EXPECT_TRUE(Value("last") == Pointer("/foo/-").GetWithDefault(d, Value("last").Move(), a)); EXPECT_STREQ("last", d["foo"][3].GetString()); + + EXPECT_TRUE(Pointer("/foo/null").GetWithDefault(d, Value().Move(), a).IsNull()); + EXPECT_TRUE(Pointer("/foo/null").GetWithDefault(d, "x", a).IsNull()); + + // Generic version + EXPECT_EQ(-1, Pointer("/foo/int").GetWithDefault(d, -1, a).GetInt()); + EXPECT_EQ(-1, Pointer("/foo/int").GetWithDefault(d, -2, a).GetInt()); + EXPECT_EQ(0x87654321, Pointer("/foo/uint").GetWithDefault(d, 0x87654321, a).GetUint()); + EXPECT_EQ(0x87654321, Pointer("/foo/uint").GetWithDefault(d, 0x12345678, a).GetUint()); + + const int64_t i64 = static_cast(RAPIDJSON_UINT64_C2(0x80000000, 0)); + EXPECT_EQ(i64, Pointer("/foo/int64").GetWithDefault(d, i64, a).GetInt64()); + EXPECT_EQ(i64, Pointer("/foo/int64").GetWithDefault(d, i64 + 1, a).GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + EXPECT_EQ(u64, Pointer("/foo/uint64").GetWithDefault(d, u64, a).GetUint64()); + EXPECT_EQ(u64, Pointer("/foo/uint64").GetWithDefault(d, u64 - 1, a).GetUint64()); + + EXPECT_TRUE(Pointer("/foo/true").GetWithDefault(d, true, a).IsTrue()); + EXPECT_TRUE(Pointer("/foo/true").GetWithDefault(d, false, a).IsTrue()); + + EXPECT_TRUE(Pointer("/foo/false").GetWithDefault(d, false, a).IsFalse()); + EXPECT_TRUE(Pointer("/foo/false").GetWithDefault(d, true, a).IsFalse()); + + // StringRef version + EXPECT_STREQ("Hello", Pointer("/foo/hello").GetWithDefault(d, "Hello", a).GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + EXPECT_STREQ("World", Pointer("/foo/world").GetWithDefault(d, buffer, a).GetString()); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); } TEST(Pointer, Set) { From d0d18847c5f01fb9378777f25761f54b39b5bdc1 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sat, 2 May 2015 22:08:03 +0800 Subject: [PATCH 17/43] Add GetValueByPointerWithDefault() overloads --- include/rapidjson/pointer.h | 35 ++++++++++++++ test/unittest/pointertest.cpp | 90 ++++++++++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 16d2e60..027d68b 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -445,12 +445,47 @@ typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointe return pointer.GetWithDefault(root, defaultValue, a); } +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, GenericStringRef defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, T2 defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + template typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { const GenericPointer pointer(source, N - 1); return GetValueByPointerWithDefault(root, pointer, defaultValue, a); } +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], GenericStringRef defaultValue, typename T::AllocatorType& a) { + const GenericPointer pointer(source, N - 1); + return GetValueByPointerWithDefault(root, pointer, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + const GenericPointer pointer(source, N - 1); + return GetValueByPointerWithDefault(root, pointer, defaultValue, a); +} + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValue, typename T::AllocatorType& a) { + const GenericPointer pointer(source, N - 1); + return GetValueByPointerWithDefault(root, pointer, defaultValue, a); +} + template typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { return pointer.Set(root, value, a); diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 8368d51..a18646a 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -444,14 +444,102 @@ TEST(Pointer, GetValueByPointer) { EXPECT_EQ(&d["foo"][0], GetValueByPointer(v, "/foo/0")); } -TEST(Pointer, GetValueByPointerWithDefault) { +TEST(Pointer, GetValueByPointerWithDefault_Pointer) { Document d; d.Parse(kJson); Document::AllocatorType& a = d.GetAllocator(); const Value v("qux"); EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, Pointer("/foo/0"), v, a)); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, Pointer("/foo/0"), v, a)); + EXPECT_TRUE(Value("baz") == GetValueByPointerWithDefault(d, Pointer("/foo/1"), v, a)); + EXPECT_TRUE(Value("qux") == GetValueByPointerWithDefault(d, Pointer("/foo/2"), v, a)); + EXPECT_TRUE(Value("last") == GetValueByPointerWithDefault(d, Pointer("/foo/-"), Value("last").Move(), a)); + EXPECT_STREQ("last", d["foo"][3].GetString()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/null"), Value().Move(), a).IsNull()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/null"), "x", a).IsNull()); + + // Generic version + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, Pointer("/foo/int"), -1, a).GetInt()); + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, Pointer("/foo/int"), -2, a).GetInt()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, Pointer("/foo/uint"), 0x87654321, a).GetUint()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, Pointer("/foo/uint"), 0x12345678, a).GetUint()); + + const int64_t i64 = static_cast(RAPIDJSON_UINT64_C2(0x80000000, 0)); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, Pointer("/foo/int64"), i64, a).GetInt64()); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, Pointer("/foo/int64"), i64 + 1, a).GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, Pointer("/foo/uint64"), u64, a).GetUint64()); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, Pointer("/foo/uint64"), u64 - 1, a).GetUint64()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/true"), true, a).IsTrue()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/true"), false, a).IsTrue()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/false"), false, a).IsFalse()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/false"), true, a).IsFalse()); + + // StringRef version + EXPECT_STREQ("Hello", GetValueByPointerWithDefault(d, Pointer("/foo/hello"), "Hello", a).GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + EXPECT_STREQ("World", GetValueByPointerWithDefault(d, Pointer("/foo/world"), buffer, a).GetString()); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, Pointer("/foo/world"))->GetString()); +} + +TEST(Pointer, GetValueByPointerWithDefault_String) { + Document d; + d.Parse(kJson); + + Document::AllocatorType& a = d.GetAllocator(); + const Value v("qux"); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, "/foo/0", v, a)); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, "/foo/0", v, a)); EXPECT_TRUE(Value("baz") == GetValueByPointerWithDefault(d, "/foo/1", v, a)); + EXPECT_TRUE(Value("qux") == GetValueByPointerWithDefault(d, "/foo/2", v, a)); + EXPECT_TRUE(Value("last") == GetValueByPointerWithDefault(d, "/foo/-", Value("last").Move(), a)); + EXPECT_STREQ("last", d["foo"][3].GetString()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/null", Value().Move(), a).IsNull()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/null", "x", a).IsNull()); + + // Generic version + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, "/foo/int", -1, a).GetInt()); + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, "/foo/int", -2, a).GetInt()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, "/foo/uint", 0x87654321, a).GetUint()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, "/foo/uint", 0x12345678, a).GetUint()); + + const int64_t i64 = static_cast(RAPIDJSON_UINT64_C2(0x80000000, 0)); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, "/foo/int64", i64, a).GetInt64()); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, "/foo/int64", i64 + 1, a).GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, "/foo/uint64", u64, a).GetUint64()); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, "/foo/uint64", u64 - 1, a).GetUint64()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/true", true, a).IsTrue()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/true", false, a).IsTrue()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/false", false, a).IsFalse()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/false", true, a).IsFalse()); + + // StringRef version + EXPECT_STREQ("Hello", GetValueByPointerWithDefault(d, "/foo/hello", "Hello", a).GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + EXPECT_STREQ("World", GetValueByPointerWithDefault(d, "/foo/world", buffer, a).GetString()); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); } TEST(Pointer, SetValueByPointer_Pointer) { From fd9386589fb3ee97aaa349045e2f2c11589e852a Mon Sep 17 00:00:00 2001 From: miloyip Date: Sat, 2 May 2015 22:46:30 +0800 Subject: [PATCH 18/43] Add overloads for document as root, with no allocator parameter. --- include/rapidjson/pointer.h | 174 +++++++++++++++++++ test/unittest/pointertest.cpp | 310 ++++++++++++++++++++++++++++++++++ 2 files changed, 484 insertions(+) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 027d68b..cd52b43 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -209,6 +209,11 @@ public: return *v; } + template + ValueType& Create(GenericDocument& root, bool* alreadyExist = 0) const { + return Create(root, root.GetAllocator(), alreadyExist); + } + ValueType* Get(ValueType& root) const { RAPIDJSON_ASSERT(IsValid()); ValueType* v = &root; @@ -270,6 +275,27 @@ public: return GetWithDefault(root, v, allocator); } + template + ValueType& GetWithDefault(GenericDocument& root, const ValueType& defaultValue) const { + return GetWithDefault(root, defaultValue, root.GetAllocator()); + } + + template + ValueType& GetWithDefault(GenericDocument& root, GenericStringRef defaultValue) const { + return GetWithDefault(root, defaultValue, root.GetAllocator()); + } + + template + ValueType& GetWithDefault(GenericDocument& root, const Ch* defaultValue) const { + return GetWithDefault(root, defaultValue, root.GetAllocator()); + } + + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(GenericDocument& root, T defaultValue) const { + return GetWithDefault(root, defaultValue, root.GetAllocator()); + } + // Move semantics, create parents if non-exist ValueType& Set(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator) = value; @@ -297,6 +323,35 @@ public: return Create(root, allocator) = v; } + template + ValueType& Set(GenericDocument& root, ValueType& value) const { + return Create(root) = value; + } + + template + ValueType& Set(GenericDocument& root, const ValueType& value) const { + return Create(root).CopyFrom(value, root.GetAllocator()); + } + + template + ValueType& Set(GenericDocument& root, GenericStringRef value) const { + ValueType v(value); + return Create(root) = v; + } + + template + ValueType& Set(GenericDocument& root, const Ch* value) const { + ValueType v(value, root.GetAllocator()); + return Create(root) = v; + } + + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(GenericDocument& root, T value) const { + ValueType v(value); + return Create(root) = v; + } + // Create parents if non-exist ValueType& Swap(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator).Swap(value); @@ -407,6 +462,8 @@ private: PointerParseErrorCode parseErrorCode_; }; +////////////////////////////////////////////////////////////////////////////// + template typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer& pointer, typename T::AllocatorType& a) { return pointer.Create(root, a); @@ -418,6 +475,21 @@ typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], return CreateValueByPointer(root, pointer, a); } +// No allocator parameter + +template +typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer& pointer) { + return pointer.Create(root); +} + +template +typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N]) { + const GenericPointer pointer(source, N - 1); + return CreateValueByPointer(root, pointer); +} + +////////////////////////////////////////////////////////////////////////////// + template typename T::ValueType* GetValueByPointer(T& root, const GenericPointer& pointer) { return pointer.Get(root); @@ -440,6 +512,8 @@ const typename T::ValueType* GetValueByPointer(const T& root, const CharType(&so return GetValueByPointer(root, pointer); } +////////////////////////////////////////////////////////////////////////////// + template typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { return pointer.GetWithDefault(root, defaultValue, a); @@ -486,6 +560,56 @@ GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValu return GetValueByPointerWithDefault(root, pointer, defaultValue, a); } +// No allocator parameter + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::ValueType& defaultValue) { + return pointer.GetWithDefault(root, defaultValue); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, GenericStringRef defaultValue) { + return pointer.GetWithDefault(root, defaultValue); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::Ch* defaultValue) { + return pointer.GetWithDefault(root, defaultValue); +} + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, T2 defaultValue) { + return pointer.GetWithDefault(root, defaultValue); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue) { + const GenericPointer pointer(source, N - 1); + return GetValueByPointerWithDefault(root, pointer, defaultValue); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], GenericStringRef defaultValue) { + const GenericPointer pointer(source, N - 1); + return GetValueByPointerWithDefault(root, pointer, defaultValue); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue) { + const GenericPointer pointer(source, N - 1); + return GetValueByPointerWithDefault(root, pointer, defaultValue); +} + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValue) { + const GenericPointer pointer(source, N - 1); + return GetValueByPointerWithDefault(root, pointer, defaultValue); +} + +////////////////////////////////////////////////////////////////////////////// + template typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { return pointer.Set(root, value, a); @@ -532,6 +656,56 @@ SetValueByPointer(T& root, const CharType(&source)[N], T2 value, typename T::All return SetValueByPointer(root, pointer, value, a); } +// No allocator parameter + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value) { + return pointer.Set(root, value); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, GenericStringRef value) { + return pointer.Set(root, value); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value) { + return pointer.Set(root, value); +} + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const GenericPointer& pointer, T2 value) { + return pointer.Set(root, value); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value) { + const GenericPointer pointer(source, N - 1); + return SetValueByPointer(root, pointer, value); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], GenericStringRef value) { + const GenericPointer pointer(source, N - 1); + return SetValueByPointer(root, pointer, value); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value) { + const GenericPointer pointer(source, N - 1); + return SetValueByPointer(root, pointer, value); +} + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const CharType(&source)[N], T2 value) { + const GenericPointer pointer(source, N - 1); + return SetValueByPointer(root, pointer, value); +} + +////////////////////////////////////////////////////////////////////////////// + template typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { return pointer.Swap(root, value, a); diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index a18646a..13015ba 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -288,6 +288,18 @@ TEST(Pointer, Create) { Value* v = &Pointer("/foo/-/-").Create(d, d.GetAllocator()); EXPECT_EQ(&d["foo"][2][0], v); } + + { + // Document with no allocator + Value* v = &Pointer("/foo/-").Create(d); + EXPECT_EQ(&d["foo"][3], v); + } + + { + // Value (not document) must give allocator + Value* v = &Pointer("/-").Create(d["foo"], d.GetAllocator()); + EXPECT_EQ(&d["foo"][4], v); + } } TEST(Pointer, Get) { @@ -358,6 +370,54 @@ TEST(Pointer, GetWithDefault) { EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); } +TEST(Pointer, GetWithDefault_NoAllocator) { + Document d; + d.Parse(kJson); + + // Value version + const Value v("qux"); + EXPECT_TRUE(Value("bar") == Pointer("/foo/0").GetWithDefault(d, v)); + EXPECT_TRUE(Value("baz") == Pointer("/foo/1").GetWithDefault(d, v)); + EXPECT_TRUE(Value("qux") == Pointer("/foo/2").GetWithDefault(d, v)); + EXPECT_TRUE(Value("last") == Pointer("/foo/-").GetWithDefault(d, Value("last").Move())); + EXPECT_STREQ("last", d["foo"][3].GetString()); + + EXPECT_TRUE(Pointer("/foo/null").GetWithDefault(d, Value().Move()).IsNull()); + EXPECT_TRUE(Pointer("/foo/null").GetWithDefault(d, "x").IsNull()); + + // Generic version + EXPECT_EQ(-1, Pointer("/foo/int").GetWithDefault(d, -1).GetInt()); + EXPECT_EQ(-1, Pointer("/foo/int").GetWithDefault(d, -2).GetInt()); + EXPECT_EQ(0x87654321, Pointer("/foo/uint").GetWithDefault(d, 0x87654321).GetUint()); + EXPECT_EQ(0x87654321, Pointer("/foo/uint").GetWithDefault(d, 0x12345678).GetUint()); + + const int64_t i64 = static_cast(RAPIDJSON_UINT64_C2(0x80000000, 0)); + EXPECT_EQ(i64, Pointer("/foo/int64").GetWithDefault(d, i64).GetInt64()); + EXPECT_EQ(i64, Pointer("/foo/int64").GetWithDefault(d, i64 + 1).GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + EXPECT_EQ(u64, Pointer("/foo/uint64").GetWithDefault(d, u64).GetUint64()); + EXPECT_EQ(u64, Pointer("/foo/uint64").GetWithDefault(d, u64 - 1).GetUint64()); + + EXPECT_TRUE(Pointer("/foo/true").GetWithDefault(d, true).IsTrue()); + EXPECT_TRUE(Pointer("/foo/true").GetWithDefault(d, false).IsTrue()); + + EXPECT_TRUE(Pointer("/foo/false").GetWithDefault(d, false).IsFalse()); + EXPECT_TRUE(Pointer("/foo/false").GetWithDefault(d, true).IsFalse()); + + // StringRef version + EXPECT_STREQ("Hello", Pointer("/foo/hello").GetWithDefault(d, "Hello").GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + EXPECT_STREQ("World", Pointer("/foo/world").GetWithDefault(d, buffer).GetString()); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); +} + TEST(Pointer, Set) { Document d; d.Parse(kJson); @@ -408,6 +468,55 @@ TEST(Pointer, Set) { EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); } +TEST(Pointer, Set_NoAllocator) { + Document d; + d.Parse(kJson); + + // Value version + Pointer("/foo/0").Set(d, Value(123).Move()); + EXPECT_EQ(123, d["foo"][0].GetInt()); + + Pointer("/foo/-").Set(d, Value(456).Move()); + EXPECT_EQ(456, d["foo"][2].GetInt()); + + Pointer("/foo/null").Set(d, Value().Move()); + EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + + // Generic version + Pointer("/foo/int").Set(d, -1); + EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); + + Pointer("/foo/uint").Set(d, 0x87654321); + EXPECT_EQ(0x87654321, GetValueByPointer(d, "/foo/uint")->GetUint()); + + const int64_t i64 = static_cast(RAPIDJSON_UINT64_C2(0x80000000, 0)); + Pointer("/foo/int64").Set(d, i64); + EXPECT_EQ(i64, GetValueByPointer(d, "/foo/int64")->GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + Pointer("/foo/uint64").Set(d, u64); + EXPECT_EQ(u64, GetValueByPointer(d, "/foo/uint64")->GetUint64()); + + Pointer("/foo/true").Set(d, true); + EXPECT_TRUE(GetValueByPointer(d, "/foo/true")->IsTrue()); + + Pointer("/foo/false").Set(d, false); + EXPECT_TRUE(GetValueByPointer(d, "/foo/false")->IsFalse()); + + // StringRef version + Pointer("/foo/hello").Set(d, "Hello"); + EXPECT_STREQ("Hello", GetValueByPointer(d, "/foo/hello")->GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + Pointer("/foo/world").Set(d, buffer); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); +} + TEST(Pointer, Swap) { Document d; d.Parse(kJson); @@ -431,6 +540,19 @@ TEST(Pointer, CreateValueByPointer) { } } +TEST(Pointer, CreateValueByPointer_NoAllocator) { + Document d; + + { + Value& v = CreateValueByPointer(d, Pointer("/foo/0")); + EXPECT_EQ(&d["foo"][0], &v); + } + { + Value& v = CreateValueByPointer(d, "/foo/1"); + EXPECT_EQ(&d["foo"][1], &v); + } +} + TEST(Pointer, GetValueByPointer) { Document d; d.Parse(kJson); @@ -542,6 +664,102 @@ TEST(Pointer, GetValueByPointerWithDefault_String) { EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); } +TEST(Pointer, GetValueByPointerWithDefault_Pointer_NoAllocator) { + Document d; + d.Parse(kJson); + + const Value v("qux"); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, Pointer("/foo/0"), v)); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, Pointer("/foo/0"), v)); + EXPECT_TRUE(Value("baz") == GetValueByPointerWithDefault(d, Pointer("/foo/1"), v)); + EXPECT_TRUE(Value("qux") == GetValueByPointerWithDefault(d, Pointer("/foo/2"), v)); + EXPECT_TRUE(Value("last") == GetValueByPointerWithDefault(d, Pointer("/foo/-"), Value("last").Move())); + EXPECT_STREQ("last", d["foo"][3].GetString()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/null"), Value().Move()).IsNull()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/null"), "x").IsNull()); + + // Generic version + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, Pointer("/foo/int"), -1).GetInt()); + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, Pointer("/foo/int"), -2).GetInt()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, Pointer("/foo/uint"), 0x87654321).GetUint()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, Pointer("/foo/uint"), 0x12345678).GetUint()); + + const int64_t i64 = static_cast(RAPIDJSON_UINT64_C2(0x80000000, 0)); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, Pointer("/foo/int64"), i64).GetInt64()); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, Pointer("/foo/int64"), i64 + 1).GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, Pointer("/foo/uint64"), u64).GetUint64()); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, Pointer("/foo/uint64"), u64 - 1).GetUint64()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/true"), true).IsTrue()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/true"), false).IsTrue()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/false"), false).IsFalse()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, Pointer("/foo/false"), true).IsFalse()); + + // StringRef version + EXPECT_STREQ("Hello", GetValueByPointerWithDefault(d, Pointer("/foo/hello"), "Hello").GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + EXPECT_STREQ("World", GetValueByPointerWithDefault(d, Pointer("/foo/world"), buffer).GetString()); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, Pointer("/foo/world"))->GetString()); +} + +TEST(Pointer, GetValueByPointerWithDefault_String_NoAllocator) { + Document d; + d.Parse(kJson); + + const Value v("qux"); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, "/foo/0", v)); + EXPECT_TRUE(Value("bar") == GetValueByPointerWithDefault(d, "/foo/0", v)); + EXPECT_TRUE(Value("baz") == GetValueByPointerWithDefault(d, "/foo/1", v)); + EXPECT_TRUE(Value("qux") == GetValueByPointerWithDefault(d, "/foo/2", v)); + EXPECT_TRUE(Value("last") == GetValueByPointerWithDefault(d, "/foo/-", Value("last").Move())); + EXPECT_STREQ("last", d["foo"][3].GetString()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/null", Value().Move()).IsNull()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/null", "x").IsNull()); + + // Generic version + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, "/foo/int", -1).GetInt()); + EXPECT_EQ(-1, GetValueByPointerWithDefault(d, "/foo/int", -2).GetInt()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, "/foo/uint", 0x87654321).GetUint()); + EXPECT_EQ(0x87654321, GetValueByPointerWithDefault(d, "/foo/uint", 0x12345678).GetUint()); + + const int64_t i64 = static_cast(RAPIDJSON_UINT64_C2(0x80000000, 0)); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, "/foo/int64", i64).GetInt64()); + EXPECT_EQ(i64, GetValueByPointerWithDefault(d, "/foo/int64", i64 + 1).GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, "/foo/uint64", u64).GetUint64()); + EXPECT_EQ(u64, GetValueByPointerWithDefault(d, "/foo/uint64", u64 - 1).GetUint64()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/true", true).IsTrue()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/true", false).IsTrue()); + + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/false", false).IsFalse()); + EXPECT_TRUE(GetValueByPointerWithDefault(d, "/foo/false", true).IsFalse()); + + // StringRef version + EXPECT_STREQ("Hello", GetValueByPointerWithDefault(d, "/foo/hello", "Hello").GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + EXPECT_STREQ("World", GetValueByPointerWithDefault(d, "/foo/world", buffer).GetString()); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); +} + TEST(Pointer, SetValueByPointer_Pointer) { Document d; d.Parse(kJson); @@ -636,6 +854,98 @@ TEST(Pointer, SetValueByPointer_String) { EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); } +TEST(Pointer, SetValueByPointer_Pointer_NoAllocator) { + Document d; + d.Parse(kJson); + + // Value version + SetValueByPointer(d, Pointer("/foo/0"), Value(123).Move()); + EXPECT_EQ(123, d["foo"][0].GetInt()); + + SetValueByPointer(d, Pointer("/foo/null"), Value().Move()); + EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + + // Generic version + SetValueByPointer(d, Pointer("/foo/int"), -1); + EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); + + SetValueByPointer(d, Pointer("/foo/uint"), 0x87654321); + EXPECT_EQ(0x87654321, GetValueByPointer(d, "/foo/uint")->GetUint()); + + const int64_t i64 = static_cast(RAPIDJSON_UINT64_C2(0x80000000, 0)); + SetValueByPointer(d, Pointer("/foo/int64"), i64); + EXPECT_EQ(i64, GetValueByPointer(d, "/foo/int64")->GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + SetValueByPointer(d, Pointer("/foo/uint64"), u64); + EXPECT_EQ(u64, GetValueByPointer(d, "/foo/uint64")->GetUint64()); + + SetValueByPointer(d, Pointer("/foo/true"), true); + EXPECT_TRUE(GetValueByPointer(d, "/foo/true")->IsTrue()); + + SetValueByPointer(d, Pointer("/foo/false"), false); + EXPECT_TRUE(GetValueByPointer(d, "/foo/false")->IsFalse()); + + // StringRef version + SetValueByPointer(d, Pointer("/foo/hello"), "Hello"); + EXPECT_STREQ("Hello", GetValueByPointer(d, "/foo/hello")->GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + SetValueByPointer(d, Pointer("/foo/world"), buffer); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); +} + +TEST(Pointer, SetValueByPointer_String_NoAllocator) { + Document d; + d.Parse(kJson); + + // Value version + SetValueByPointer(d, "/foo/0", Value(123).Move()); + EXPECT_EQ(123, d["foo"][0].GetInt()); + + SetValueByPointer(d, "/foo/null", Value().Move()); + EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + + // Generic version + SetValueByPointer(d, "/foo/int", -1); + EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); + + SetValueByPointer(d, "/foo/uint", 0x87654321); + EXPECT_EQ(0x87654321, GetValueByPointer(d, "/foo/uint")->GetUint()); + + const int64_t i64 = static_cast(RAPIDJSON_UINT64_C2(0x80000000, 0)); + SetValueByPointer(d, "/foo/int64", i64); + EXPECT_EQ(i64, GetValueByPointer(d, "/foo/int64")->GetInt64()); + + const uint64_t u64 = RAPIDJSON_UINT64_C2(0xFFFFFFFFF, 0xFFFFFFFFF); + SetValueByPointer(d, "/foo/uint64", u64); + EXPECT_EQ(u64, GetValueByPointer(d, "/foo/uint64")->GetUint64()); + + SetValueByPointer(d, "/foo/true", true); + EXPECT_TRUE(GetValueByPointer(d, "/foo/true")->IsTrue()); + + SetValueByPointer(d, "/foo/false", false); + EXPECT_TRUE(GetValueByPointer(d, "/foo/false")->IsFalse()); + + // StringRef version + SetValueByPointer(d, "/foo/hello", "Hello"); + EXPECT_STREQ("Hello", GetValueByPointer(d, "/foo/hello")->GetString()); + + // Copy string version + { + char buffer[256]; + strcpy(buffer, "World"); + SetValueByPointer(d, "/foo/world", buffer); + memset(buffer, 0, sizeof(buffer)); + } + EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); +} + TEST(Pointer, SwapValueByPointer) { Document d; d.Parse(kJson); From 2ee15de4a9f5813594820eb510dfcd2090238b18 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sat, 2 May 2015 22:58:41 +0800 Subject: [PATCH 19/43] Add no allocator overloads for Swap --- include/rapidjson/pointer.h | 18 +++++++++++++++++- test/unittest/pointertest.cpp | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index cd52b43..c0b2253 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -357,6 +357,11 @@ public: return Create(root, allocator).Swap(value); } + template + ValueType& Swap(GenericDocument& root, ValueType& value) const { + return Create(root).Swap(value); + } + private: void Parse(const Ch* source, size_t length) { // Create own allocator if user did not supply. @@ -462,6 +467,8 @@ private: PointerParseErrorCode parseErrorCode_; }; +typedef GenericPointer Pointer; + ////////////////////////////////////////////////////////////////////////////// template @@ -717,7 +724,16 @@ typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], t return SwapValueByPointer(root, pointer, value, a); } -typedef GenericPointer Pointer; +template +typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value) { + return pointer.Swap(root, value); +} + +template +typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value) { + const GenericPointer pointer(source, N - 1); + return SwapValueByPointer(root, pointer, value); +} RAPIDJSON_NAMESPACE_END diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 13015ba..75ca5a3 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -526,6 +526,14 @@ TEST(Pointer, Swap) { EXPECT_STREQ("bar", d["foo"][1].GetString()); } +TEST(Pointer, Swap_NoAllocator) { + Document d; + d.Parse(kJson); + Pointer("/foo/0").Swap(d, *Pointer("/foo/1").Get(d)); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_STREQ("bar", d["foo"][1].GetString()); +} + TEST(Pointer, CreateValueByPointer) { Document d; Document::AllocatorType& a = d.GetAllocator(); @@ -958,3 +966,15 @@ TEST(Pointer, SwapValueByPointer) { EXPECT_STREQ("bar", d["foo"][0].GetString()); EXPECT_STREQ("baz", d["foo"][1].GetString()); } + +TEST(Pointer, SwapValueByPointer_NoAllocator) { + Document d; + d.Parse(kJson); + SwapValueByPointer(d, Pointer("/foo/0"), *GetValueByPointer(d, "/foo/1")); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_STREQ("bar", d["foo"][1].GetString()); + + SwapValueByPointer(d, "/foo/0", *GetValueByPointer(d, "/foo/1")); + EXPECT_STREQ("bar", d["foo"][0].GetString()); + EXPECT_STREQ("baz", d["foo"][1].GetString()); +} From 28f14bd68f30872417a6c96dc4be1e7195c24f5d Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 09:51:15 +0800 Subject: [PATCH 20/43] Add parsing of URI fragment representation of JSON pointer --- include/rapidjson/pointer.h | 44 ++++++++- test/unittest/pointertest.cpp | 178 ++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 2 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index c0b2253..4086751 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -25,7 +25,9 @@ enum PointerParseErrorCode { kPointerParseErrorNone = 0, kPointerParseErrorTokenMustBeginWithSolidus, - kPointerParseErrorInvalidEscape + kPointerParseErrorInvalidEscape, + kPointerParseErrorInvalidPercentEncoding, + kPointerParseErrorCharacterMustPercentEncode }; template @@ -363,6 +365,12 @@ public: } private: + //! Parse a JSON String or its URI fragment representation into tokens. + /*! + \param source Either a JSON Pointer string, or its URI fragment representation. Not need to be null terminated. + \param length Length of the source string. + \note Source cannot be JSON String Representation of JSON Pointer, e.g. In "/\u0000", \u0000 will not be unescaped. + */ void Parse(const Ch* source, size_t length) { // Create own allocator if user did not supply. if (!allocator_) @@ -380,7 +388,14 @@ private: size_t i = 0; - if (length != 0 && source[i] != '/') { + // Detect if it is a URI fragment + bool uriFragment = false; + if (source[i] == '#') { + uriFragment = true; + i++; + } + + if (i != length && source[i] != '/') { parseErrorCode_ = kPointerParseErrorTokenMustBeginWithSolidus; goto error; } @@ -395,6 +410,31 @@ private: while (i < length && source[i] != '/') { Ch c = source[i++]; + + if (uriFragment) { + // Decoding percent-encoding for URI fragment + if (c == '%') { + c = 0; + for (int j = 0; j < 2; j++) { + c <<= 4; + Ch h = source[i]; + if (h >= '0' && h <= '9') c += h - '0'; + else if (h >= 'A' && h <= 'F') c += h - 'A' + 10; + else if (h >= 'a' && h <= 'f') c += h - 'a' + 10; + else { + parseErrorCode_ = kPointerParseErrorInvalidPercentEncoding; + goto error; + } + i++; + } + } + else if (!((c >= '0' && c <= '9') || (c >= 'A' && c <='Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || c =='~')) { + // RFC 3986 2.3 Unreserved Characters + i--; + parseErrorCode_ = kPointerParseErrorCharacterMustPercentEncode; + goto error; + } + } // Escaping "~0" -> '~', "~1" -> '/' if (c == '~') { diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 75ca5a3..32f659b 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -165,6 +165,184 @@ TEST(Pointer, Parse) { } } +TEST(Pointer, Parse_URIFragment) { + { + Pointer p("#"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(0u, p.GetTokenCount()); + } + + { + Pointer p("#/foo"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("foo", p.GetTokens()[0].name); + } + + { + Pointer p("#/foo/0"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(2u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("foo", p.GetTokens()[0].name); + EXPECT_EQ(1u, p.GetTokens()[1].length); + EXPECT_STREQ("0", p.GetTokens()[1].name); + EXPECT_EQ(0u, p.GetTokens()[1].index); + } + + { + // Unescape ~1 + Pointer p("#/a~1b"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("a/b", p.GetTokens()[0].name); + } + + { + // Unescape ~0 + Pointer p("#/m~0n"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("m~n", p.GetTokens()[0].name); + } + + { + // empty name + Pointer p("#/"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(0u, p.GetTokens()[0].length); + EXPECT_STREQ("", p.GetTokens()[0].name); + } + + { + // empty and non-empty name + Pointer p("#//a"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(2u, p.GetTokenCount()); + EXPECT_EQ(0u, p.GetTokens()[0].length); + EXPECT_STREQ("", p.GetTokens()[0].name); + EXPECT_EQ(1u, p.GetTokens()[1].length); + EXPECT_STREQ("a", p.GetTokens()[1].name); + } + + { + // Null characters + Pointer p("#/%00%00"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(2u, p.GetTokens()[0].length); + EXPECT_EQ('\0', p.GetTokens()[0].name[0]); + EXPECT_EQ('\0', p.GetTokens()[0].name[1]); + EXPECT_EQ('\0', p.GetTokens()[0].name[2]); + } + + { + // Percentage Escapes + EXPECT_STREQ("c%d", Pointer("#/c%25d").GetTokens()[0].name); + EXPECT_STREQ("e^f", Pointer("#/e%5Ef").GetTokens()[0].name); + EXPECT_STREQ("g|h", Pointer("#/g%7Ch").GetTokens()[0].name); + EXPECT_STREQ("i\\j", Pointer("#/i%5Cj").GetTokens()[0].name); + EXPECT_STREQ("k\"l", Pointer("#/k%22l").GetTokens()[0].name); + EXPECT_STREQ(" ", Pointer("#/%20").GetTokens()[0].name); + } + + { + // Valid index + Pointer p("#/123"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ("123", p.GetTokens()[0].name); + EXPECT_EQ(123u, p.GetTokens()[0].index); + } + + { + // Invalid index (with leading zero) + Pointer p("#/01"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ("01", p.GetTokens()[0].name); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); + } + + if (sizeof(SizeType) == 4) { + // Invalid index (overflow) + Pointer p("#/4294967296"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ("4294967296", p.GetTokens()[0].name); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); + } + + { + // kPointerParseErrorTokenMustBeginWithSolidus + Pointer p("# "); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorTokenMustBeginWithSolidus, p.GetParseErrorCode()); + EXPECT_EQ(1u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidEscape + Pointer p("#/~"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidEscape, p.GetParseErrorCode()); + EXPECT_EQ(3u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidEscape + Pointer p("#/~2"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidEscape, p.GetParseErrorCode()); + EXPECT_EQ(3u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidPercentEncoding + Pointer p("#/%"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidPercentEncoding, p.GetParseErrorCode()); + EXPECT_EQ(3u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidPercentEncoding + Pointer p("#/%g0"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidPercentEncoding, p.GetParseErrorCode()); + EXPECT_EQ(3u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorInvalidPercentEncoding + Pointer p("#/%0g"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidPercentEncoding, p.GetParseErrorCode()); + EXPECT_EQ(4u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorCharacterMustPercentEncode + Pointer p("#/ "); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorCharacterMustPercentEncode, p.GetParseErrorCode()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); + } + + { + // kPointerParseErrorCharacterMustPercentEncode + Pointer p("#/\\"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorCharacterMustPercentEncode, p.GetParseErrorCode()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); + } + +} + TEST(Pointer, Stringify) { // Test by roundtrip const char* sources[] = { From 0eb6cb8e5f2ce8bce04768c801e569765378bb97 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 14:14:05 +0800 Subject: [PATCH 21/43] Add equality/inequality operator, URI fragment stringify and UTF-8 Percent Encoding/Decoding --- include/rapidjson/pointer.h | 162 ++++++++++++++++++++++++++++------ test/unittest/pointertest.cpp | 77 ++++++++++++++-- 2 files changed, 207 insertions(+), 32 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 4086751..2d42412 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -123,7 +123,7 @@ public: for (Token *t = rhs.tokens_; t != rhs.tokens_ + tokenCount_; ++t) nameBufferSize += t->length; nameBuffer_ = (Ch*)allocator_->Malloc(nameBufferSize * sizeof(Ch)); - std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize); + std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch)); tokens_ = (Token*)allocator_->Malloc(tokenCount_ * sizeof(Token)); std::memcpy(tokens_, rhs.tokens_, tokenCount_ * sizeof(Token)); @@ -149,20 +149,34 @@ public: size_t GetTokenCount() const { return tokenCount_; } - template - void Stringify(OutputStream& os) const { - RAPIDJSON_ASSERT(IsValid()); - for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { - os.Put('/'); - for (size_t j = 0; j < t->length; j++) { - Ch c = t->name[j]; - if (c == '~') { os.Put('~'); os.Put('0'); } - else if (c == '/') { os.Put('~'); os.Put('1'); } - else os.Put(c); + bool operator==(const GenericPointer& rhs) const { + if (!IsValid() || !rhs.IsValid() || tokenCount_ != rhs.tokenCount_) + return false; + + for (size_t i = 0; i < tokenCount_; i++) { + if (tokens_[i].index != rhs.tokens_[i].index || + tokens_[i].length != rhs.tokens_[i].length || + std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch) * tokens_[i].length) != 0) + { + return false; } } + + return true; } + bool operator!=(const GenericPointer& rhs) const { return !(*this == rhs); } + + template + bool Stringify(OutputStream& os) const { + return Stringify(os); + } + + template + bool StringifyUriFragment(OutputStream& os) const { + return Stringify(os); + } + ValueType& Create(ValueType& root, typename ValueType::AllocatorType& allocator, bool* alreadyExist = 0) const { RAPIDJSON_ASSERT(IsValid()); ValueType* v = &root; @@ -365,6 +379,11 @@ public: } private: + bool NeedPercentEncode(Ch c) const { + // RFC 3986 2.3 Unreserved Characters + return !((c >= '0' && c <= '9') || (c >= 'A' && c <='Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || c =='~'); + } + //! Parse a JSON String or its URI fragment representation into tokens. /*! \param source Either a JSON Pointer string, or its URI fragment representation. Not need to be null terminated. @@ -409,32 +428,37 @@ private: bool isNumber = true; while (i < length && source[i] != '/') { - Ch c = source[i++]; + Ch c = source[i]; if (uriFragment) { // Decoding percent-encoding for URI fragment if (c == '%') { - c = 0; - for (int j = 0; j < 2; j++) { - c <<= 4; - Ch h = source[i]; - if (h >= '0' && h <= '9') c += h - '0'; - else if (h >= 'A' && h <= 'F') c += h - 'A' + 10; - else if (h >= 'a' && h <= 'f') c += h - 'a' + 10; - else { - parseErrorCode_ = kPointerParseErrorInvalidPercentEncoding; - goto error; - } + PercentDecodeStream is(&source[i]); + GenericInsituStringStream os(name); + Ch* begin = os.PutBegin(); + Transcoder, EncodingType> transcoder; + if (!transcoder.Transcode(is, os) || !is.IsValid()) { + parseErrorCode_ = kPointerParseErrorInvalidPercentEncoding; + goto error; + } + size_t len = os.PutEnd(begin); + i += is.Tell() - 1; + if (len == 1) + c = *name; + else { + name += len; + isNumber = false; i++; + continue; } } - else if (!((c >= '0' && c <= '9') || (c >= 'A' && c <='Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || c =='~')) { - // RFC 3986 2.3 Unreserved Characters - i--; + else if (NeedPercentEncode(c)) { parseErrorCode_ = kPointerParseErrorCharacterMustPercentEncode; goto error; } } + + i++; // Escaping "~0" -> '~', "~1" -> '/' if (c == '~') { @@ -498,6 +522,92 @@ private: return; } + template + bool Stringify(OutputStream& os) const { + RAPIDJSON_ASSERT(IsValid()); + + if (uriFragment) + os.Put('#'); + + for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + os.Put('/'); + for (size_t j = 0; j < t->length; j++) { + Ch c = t->name[j]; + if (c == '~') { + os.Put('~'); + os.Put('0'); + } + else if (c == '/') { + os.Put('~'); + os.Put('1'); + } + else if (uriFragment && NeedPercentEncode(c)) { + // Transcode to UTF8 sequence + GenericStringStream source(&t->name[j]); + PercentEncodeStream target(os); + Transcoder > transcoder; + if (!transcoder.Transcode(source, target)) + return false; + j += source.Tell() - 1; + } + else + os.Put(c); + } + } + return true; + } + + class PercentDecodeStream { + public: + PercentDecodeStream(const Ch* source) : src_(source), head_(source), valid_(true) {} + + Ch Take() { + if (*src_ != '%') { + valid_ = false; + return 0; + } + src_++; + Ch c = 0; + for (int j = 0; j < 2; j++) { + c <<= 4; + Ch h = *src_; + if (h >= '0' && h <= '9') c += h - '0'; + else if (h >= 'A' && h <= 'F') c += h - 'A' + 10; + else if (h >= 'a' && h <= 'f') c += h - 'a' + 10; + else { + valid_ = false; + return 0; + } + src_++; + } + return c; + } + + size_t Tell() const { return src_ - head_; } + + bool IsValid() const { return valid_; } + + private: + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. + bool valid_; + }; + + template + class PercentEncodeStream { + public: + PercentEncodeStream(OutputStream& os) : os_(os) {} + void Put(char c) { // UTF-8 must be byte + unsigned char u = static_cast(c); + static const char hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + os_.Put('%'); + os_.Put(hexDigits[u >> 4]); + os_.Put(hexDigits[u & 15]); + } + private: + OutputStream& os_; + }; + Allocator* allocator_; Allocator* ownAllocator_; Ch* nameBuffer_; diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 32f659b..3a6742e 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -277,6 +277,46 @@ TEST(Pointer, Parse_URIFragment) { EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); } + { + // Decode UTF-8 perecent encoding to UTF-8 + Pointer p("#/%C2%A2"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ("\xC2\xA2", p.GetTokens()[0].name); + } + + { + // Decode UTF-8 perecent encoding to UTF-16 + GenericPointer > > p(L"#/%C2%A2"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ(L"\xA2", p.GetTokens()[0].name); + } + + { + // Decode UTF-8 perecent encoding to UTF-16 + GenericPointer > > p(L"#/%C2%A2"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ(L"\xA2", p.GetTokens()[0].name); + } + + { + // Decode UTF-8 perecent encoding to UTF-16 + GenericPointer > > p(L"#/%C2%A2"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ(L"\x00A2", p.GetTokens()[0].name); + } + + { + // Decode UTF-8 perecent encoding to UTF-16 + GenericPointer > > p(L"#/%E2%82%AC"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_STREQ(L"\x20AC", p.GetTokens()[0].name); + } + { // kPointerParseErrorTokenMustBeginWithSolidus Pointer p("# "); @@ -306,7 +346,7 @@ TEST(Pointer, Parse_URIFragment) { Pointer p("#/%"); EXPECT_FALSE(p.IsValid()); EXPECT_EQ(kPointerParseErrorInvalidPercentEncoding, p.GetParseErrorCode()); - EXPECT_EQ(3u, p.GetParseErrorOffset()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); } { @@ -314,7 +354,7 @@ TEST(Pointer, Parse_URIFragment) { Pointer p("#/%g0"); EXPECT_FALSE(p.IsValid()); EXPECT_EQ(kPointerParseErrorInvalidPercentEncoding, p.GetParseErrorCode()); - EXPECT_EQ(3u, p.GetParseErrorOffset()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); } { @@ -322,7 +362,7 @@ TEST(Pointer, Parse_URIFragment) { Pointer p("#/%0g"); EXPECT_FALSE(p.IsValid()); EXPECT_EQ(kPointerParseErrorInvalidPercentEncoding, p.GetParseErrorCode()); - EXPECT_EQ(4u, p.GetParseErrorOffset()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); } { @@ -335,12 +375,11 @@ TEST(Pointer, Parse_URIFragment) { { // kPointerParseErrorCharacterMustPercentEncode - Pointer p("#/\\"); + Pointer p("#/\n"); EXPECT_FALSE(p.IsValid()); EXPECT_EQ(kPointerParseErrorCharacterMustPercentEncode, p.GetParseErrorCode()); EXPECT_EQ(2u, p.GetParseErrorOffset()); } - } TEST(Pointer, Stringify) { @@ -357,7 +396,10 @@ TEST(Pointer, Stringify) { "/i\\j", "/k\"l", "/ ", - "/m~0n" + "/m~0n", + "/\xC2\xA2", + "/\xE2\x82\xAC", + "/\xF0\x9D\x84\x9E" }; for (size_t i = 0; i < sizeof(sources) / sizeof(sources[0]); i++) { @@ -365,6 +407,13 @@ TEST(Pointer, Stringify) { StringBuffer s; p.Stringify(s); EXPECT_STREQ(sources[i], s.GetString()); + + // Stringify to URI fragment + StringBuffer s2; + p.StringifyUriFragment(s2); + Pointer p2(s2.GetString(), s2.GetSize()); + EXPECT_TRUE(p2.IsValid()); + EXPECT_TRUE(p == p2); } } @@ -444,6 +493,22 @@ TEST(Pointer, Assignment) { } } +TEST(Pointer, Equality) { + EXPECT_TRUE(Pointer("/foo/0") == Pointer("/foo/0")); + EXPECT_FALSE(Pointer("/foo/0") == Pointer("/foo/1")); + EXPECT_FALSE(Pointer("/foo/0") == Pointer("/foo/0/1")); + EXPECT_FALSE(Pointer("/foo/0") == Pointer("a")); + EXPECT_FALSE(Pointer("a") == Pointer("a")); // Invalid always not equal +} + +TEST(Pointer, Inequality) { + EXPECT_FALSE(Pointer("/foo/0") != Pointer("/foo/0")); + EXPECT_TRUE(Pointer("/foo/0") != Pointer("/foo/1")); + EXPECT_TRUE(Pointer("/foo/0") != Pointer("/foo/0/1")); + EXPECT_TRUE(Pointer("/foo/0") != Pointer("a")); + EXPECT_TRUE(Pointer("a") != Pointer("a")); // Invalid always not equal +} + TEST(Pointer, Create) { Document d; { From bb0e8289283f61d4ca637ebbd2f5cb7e432f9513 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 18:55:55 +0800 Subject: [PATCH 22/43] Some std::string overloads for Pointer --- include/rapidjson/pointer.h | 226 +++++++++++++++++++--------------- test/unittest/CMakeLists.txt | 2 + test/unittest/pointertest.cpp | 64 ++++++++++ 3 files changed, 192 insertions(+), 100 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 2d42412..b60e84b 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -42,61 +42,25 @@ public: SizeType index; //!< A valid index if not equal to kPointerInvalidIndex. }; - GenericPointer() : - allocator_(), - ownAllocator_(), - nameBuffer_(), - tokens_(), - tokenCount_(), - parseErrorOffset_(), - parseErrorCode_(kPointerParseErrorNone) - { - } + GenericPointer() : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} - explicit GenericPointer(const Ch* source, Allocator* allocator = 0) : - allocator_(allocator), - ownAllocator_(), - nameBuffer_(), - tokens_(), - tokenCount_(), - parseErrorOffset_(), - parseErrorCode_(kPointerParseErrorNone) - { + explicit GenericPointer(const Ch* source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { Parse(source, internal::StrLen(source)); } - GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) : - allocator_(allocator), - ownAllocator_(), - nameBuffer_(), - tokens_(), - tokenCount_(), - parseErrorOffset_(), - parseErrorCode_(kPointerParseErrorNone) - { +#if RAPIDJSON_HAS_STDSTRING + explicit GenericPointer(const std::basic_string& source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source.c_str(), source.size()); + } +#endif + + GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { Parse(source, length); } - GenericPointer(const Token* tokens, size_t tokenCount) : - allocator_(), - ownAllocator_(), - nameBuffer_(), - tokens_(const_cast(tokens)), - tokenCount_(tokenCount), - parseErrorOffset_(), - parseErrorCode_(kPointerParseErrorNone) - { - } + GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(const_cast(tokens)), tokenCount_(tokenCount), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} - GenericPointer(const GenericPointer& rhs) : - allocator_(), - ownAllocator_(), - nameBuffer_(), - tokens_(), - tokenCount_(), - parseErrorOffset_(), - parseErrorCode_(kPointerParseErrorNone) - { + GenericPointer(const GenericPointer& rhs) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { *this = rhs; } @@ -255,9 +219,7 @@ public: return v; } - const ValueType* Get(const ValueType& root) const { - return Get(const_cast(root)); - } + const ValueType* Get(const ValueType& root) const { return Get(const_cast(root)); } ValueType& GetWithDefault(ValueType& root, const ValueType& defaultValue, typename ValueType::AllocatorType& allocator) const { bool alreadyExist; @@ -284,6 +246,18 @@ public: return v; } +#if RAPIDJSON_HAS_STDSTRING + ValueType& GetWithDefault(ValueType& root, const std::basic_string& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + Value& v = Create(root, allocator, &alreadyExist); + if (!alreadyExist) { + Value clone(defaultValue, allocator); // This has overhead, so do it inside if. + v = clone; + } + return v; + } +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) GetWithDefault(ValueType& root, T defaultValue, typename ValueType::AllocatorType& allocator) const { @@ -306,6 +280,13 @@ public: return GetWithDefault(root, defaultValue, root.GetAllocator()); } +#if RAPIDJSON_HAS_STDSTRING + template + ValueType& GetWithDefault(GenericDocument& root, const std::basic_string& defaultValue) const { + return GetWithDefault(root, defaultValue, root.GetAllocator()); + } +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) GetWithDefault(GenericDocument& root, T defaultValue) const { @@ -332,6 +313,13 @@ public: return Create(root, allocator) = v; } +#if RAPIDJSON_HAS_STDSTRING + ValueType& Set(ValueType& root, const std::basic_string& value, typename ValueType::AllocatorType& allocator) const { + ValueType v(value, allocator); + return Create(root, allocator) = v; + } +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) Set(ValueType& root, T value, typename ValueType::AllocatorType& allocator) const { @@ -351,21 +339,25 @@ public: template ValueType& Set(GenericDocument& root, GenericStringRef value) const { - ValueType v(value); - return Create(root) = v; + return Create(root) = value; } template ValueType& Set(GenericDocument& root, const Ch* value) const { - ValueType v(value, root.GetAllocator()); - return Create(root) = v; + return Create(root) = ValueType(value, root.GetAllocator()).Move(); } +#if RAPIDJSON_HAS_STDSTRING + template + ValueType& Set(GenericDocument& root, const std::basic_string& value) const { + return Create(root) = ValueType(value, root.GetAllocator()).Move(); + } +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) Set(GenericDocument& root, T value) const { - ValueType v(value); - return Create(root) = v; + return Create(root) = value; } // Create parents if non-exist @@ -628,8 +620,7 @@ typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], typename T::AllocatorType& a) { - const GenericPointer pointer(source, N - 1); - return CreateValueByPointer(root, pointer, a); + return GenericPointer(source, N - 1).Create(root, a); } // No allocator parameter @@ -641,8 +632,7 @@ typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N]) { - const GenericPointer pointer(source, N - 1); - return CreateValueByPointer(root, pointer); + return GenericPointer(source, N - 1).Create(root); } ////////////////////////////////////////////////////////////////////////////// @@ -659,14 +649,12 @@ const typename T::ValueType* GetValueByPointer(const T& root, const GenericPoint template typename T::ValueType* GetValueByPointer(T& root, const CharType (&source)[N]) { - const GenericPointer pointer(source, N - 1); - return GetValueByPointer(root, pointer); + return GenericPointer(source, N - 1).Get(root); } template const typename T::ValueType* GetValueByPointer(const T& root, const CharType(&source)[N]) { - const GenericPointer pointer(source, N - 1); - return GetValueByPointer(root, pointer); + return GenericPointer(source, N - 1).Get(root); } ////////////////////////////////////////////////////////////////////////////// @@ -686,6 +674,13 @@ typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointe return pointer.GetWithDefault(root, defaultValue, a); } +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, T2 defaultValue, typename T::AllocatorType& a) { @@ -694,27 +689,30 @@ GetValueByPointerWithDefault(T& root, const GenericPointer typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { - const GenericPointer pointer(source, N - 1); - return GetValueByPointerWithDefault(root, pointer, defaultValue, a); + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); } template typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], GenericStringRef defaultValue, typename T::AllocatorType& a) { - const GenericPointer pointer(source, N - 1); - return GetValueByPointerWithDefault(root, pointer, defaultValue, a); + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); } template typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue, typename T::AllocatorType& a) { - const GenericPointer pointer(source, N - 1); - return GetValueByPointerWithDefault(root, pointer, defaultValue, a); + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); } +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValue, typename T::AllocatorType& a) { - const GenericPointer pointer(source, N - 1); - return GetValueByPointerWithDefault(root, pointer, defaultValue, a); + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); } // No allocator parameter @@ -734,6 +732,13 @@ typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointe return pointer.GetWithDefault(root, defaultValue); } +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const std::basic_string& defaultValue) { + return pointer.GetWithDefault(root, defaultValue); +} +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, T2 defaultValue) { @@ -742,27 +747,30 @@ GetValueByPointerWithDefault(T& root, const GenericPointer typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue) { - const GenericPointer pointer(source, N - 1); - return GetValueByPointerWithDefault(root, pointer, defaultValue); + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue); } template typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], GenericStringRef defaultValue) { - const GenericPointer pointer(source, N - 1); - return GetValueByPointerWithDefault(root, pointer, defaultValue); + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue); } template typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue) { - const GenericPointer pointer(source, N - 1); - return GetValueByPointerWithDefault(root, pointer, defaultValue); + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue); } +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const std::basic_string& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue); +} +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValue) { - const GenericPointer pointer(source, N - 1); - return GetValueByPointerWithDefault(root, pointer, defaultValue); + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue); } ////////////////////////////////////////////////////////////////////////////// @@ -782,6 +790,13 @@ typename T::ValueType& SetValueByPointer(T& root, const GenericPointer +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const std::basic_string& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) SetValueByPointer(T& root, const GenericPointer& pointer, T2 value, typename T::AllocatorType& a) { @@ -790,27 +805,30 @@ SetValueByPointer(T& root, const GenericPointer& pointer, template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { - const GenericPointer pointer(source, N - 1); - return SetValueByPointer(root, pointer, value, a); + return GenericPointer(source, N - 1).Set(root, value, a); } template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], GenericStringRef value, typename T::AllocatorType& a) { - const GenericPointer pointer(source, N - 1); - return SetValueByPointer(root, pointer, value, a); + return GenericPointer(source, N - 1).Set(root, value, a); } template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value, typename T::AllocatorType& a) { - const GenericPointer pointer(source, N - 1); - return SetValueByPointer(root, pointer, value, a); + return GenericPointer(source, N - 1).Set(root, value, a); } +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const std::basic_string& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) SetValueByPointer(T& root, const CharType(&source)[N], T2 value, typename T::AllocatorType& a) { - const GenericPointer pointer(source, N - 1); - return SetValueByPointer(root, pointer, value, a); + return GenericPointer(source, N - 1).Set(root, value, a); } // No allocator parameter @@ -830,6 +848,13 @@ typename T::ValueType& SetValueByPointer(T& root, const GenericPointer +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const std::basic_string& value) { + return pointer.Set(root, value); +} +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) SetValueByPointer(T& root, const GenericPointer& pointer, T2 value) { @@ -838,27 +863,30 @@ SetValueByPointer(T& root, const GenericPointer& pointer, template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value) { - const GenericPointer pointer(source, N - 1); - return SetValueByPointer(root, pointer, value); + return GenericPointer(source, N - 1).Set(root, value); } template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], GenericStringRef value) { - const GenericPointer pointer(source, N - 1); - return SetValueByPointer(root, pointer, value); + return GenericPointer(source, N - 1).Set(root, value); } template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value) { - const GenericPointer pointer(source, N - 1); - return SetValueByPointer(root, pointer, value); + return GenericPointer(source, N - 1).Set(root, value); } +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const std::basic_string& value) { + return GenericPointer(source, N - 1).Set(root, value); +} +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) SetValueByPointer(T& root, const CharType(&source)[N], T2 value) { - const GenericPointer pointer(source, N - 1); - return SetValueByPointer(root, pointer, value); + return GenericPointer(source, N - 1).Set(root, value); } ////////////////////////////////////////////////////////////////////////////// @@ -870,8 +898,7 @@ typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { - const GenericPointer pointer(source, N - 1); - return SwapValueByPointer(root, pointer, value, a); + return GenericPointer(source, N - 1).Swap(root, value, a); } template @@ -881,8 +908,7 @@ typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value) { - const GenericPointer pointer(source, N - 1); - return SwapValueByPointer(root, pointer, value); + return GenericPointer(source, N - 1).Swap(root, value); } RAPIDJSON_NAMESPACE_END diff --git a/test/unittest/CMakeLists.txt b/test/unittest/CMakeLists.txt index 6a776ec..1ff88b1 100644 --- a/test/unittest/CMakeLists.txt +++ b/test/unittest/CMakeLists.txt @@ -22,6 +22,8 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") add_definitions(-D_CRT_SECURE_NO_WARNINGS=1) endif() +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DRAPIDJSON_HAS_STDSTRING=1") + add_library(namespacetest STATIC namespacetest.cpp) add_executable(unittest ${UNITTEST_SOURCES}) diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 3a6742e..417893a 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -53,6 +53,16 @@ TEST(Pointer, Parse) { EXPECT_STREQ("foo", p.GetTokens()[0].name); } + #if RAPIDJSON_HAS_STDSTRING + { + Pointer p(std::string("/foo")); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(3u, p.GetTokens()[0].length); + EXPECT_STREQ("foo", p.GetTokens()[0].name); + } + #endif + { Pointer p("/foo/0"); EXPECT_TRUE(p.IsValid()); @@ -611,6 +621,10 @@ TEST(Pointer, GetWithDefault) { memset(buffer, 0, sizeof(buffer)); } EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("C++", Pointer("/foo/C++").GetWithDefault(d, std::string("C++"), a).GetString()); +#endif } TEST(Pointer, GetWithDefault_NoAllocator) { @@ -659,6 +673,10 @@ TEST(Pointer, GetWithDefault_NoAllocator) { memset(buffer, 0, sizeof(buffer)); } EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("C++", Pointer("/foo/C++").GetWithDefault(d, std::string("C++")).GetString()); +#endif } TEST(Pointer, Set) { @@ -709,6 +727,11 @@ TEST(Pointer, Set) { memset(buffer, 0, sizeof(buffer)); } EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + Pointer("/foo/c++").Set(d, std::string("C++"), a); + EXPECT_STREQ("C++", GetValueByPointer(d, "/foo/c++")->GetString()); +#endif } TEST(Pointer, Set_NoAllocator) { @@ -758,6 +781,11 @@ TEST(Pointer, Set_NoAllocator) { memset(buffer, 0, sizeof(buffer)); } EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + Pointer("/foo/c++").Set(d, std::string("C++")); + EXPECT_STREQ("C++", GetValueByPointer(d, "/foo/c++")->GetString()); +#endif } TEST(Pointer, Swap) { @@ -864,6 +892,10 @@ TEST(Pointer, GetValueByPointerWithDefault_Pointer) { memset(buffer, 0, sizeof(buffer)); } EXPECT_STREQ("World", GetValueByPointer(d, Pointer("/foo/world"))->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("C++", GetValueByPointerWithDefault(d, Pointer("/foo/C++"), std::string("C++"), a).GetString()); +#endif } TEST(Pointer, GetValueByPointerWithDefault_String) { @@ -913,6 +945,10 @@ TEST(Pointer, GetValueByPointerWithDefault_String) { memset(buffer, 0, sizeof(buffer)); } EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("C++", GetValueByPointerWithDefault(d, "/foo/C++", std::string("C++"), a).GetString()); +#endif } TEST(Pointer, GetValueByPointerWithDefault_Pointer_NoAllocator) { @@ -961,6 +997,10 @@ TEST(Pointer, GetValueByPointerWithDefault_Pointer_NoAllocator) { memset(buffer, 0, sizeof(buffer)); } EXPECT_STREQ("World", GetValueByPointer(d, Pointer("/foo/world"))->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("C++", GetValueByPointerWithDefault(d, Pointer("/foo/C++"), std::string("C++")).GetString()); +#endif } TEST(Pointer, GetValueByPointerWithDefault_String_NoAllocator) { @@ -1009,6 +1049,10 @@ TEST(Pointer, GetValueByPointerWithDefault_String_NoAllocator) { memset(buffer, 0, sizeof(buffer)); } EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("C++", GetValueByPointerWithDefault(d, Pointer("/foo/C++"), std::string("C++")).GetString()); +#endif } TEST(Pointer, SetValueByPointer_Pointer) { @@ -1056,6 +1100,11 @@ TEST(Pointer, SetValueByPointer_Pointer) { memset(buffer, 0, sizeof(buffer)); } EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + SetValueByPointer(d, Pointer("/foo/c++"), std::string("C++"), a); + EXPECT_STREQ("C++", GetValueByPointer(d, "/foo/c++")->GetString()); +#endif } TEST(Pointer, SetValueByPointer_String) { @@ -1103,6 +1152,11 @@ TEST(Pointer, SetValueByPointer_String) { memset(buffer, 0, sizeof(buffer)); } EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + SetValueByPointer(d, "/foo/c++", std::string("C++"), a); + EXPECT_STREQ("C++", GetValueByPointer(d, "/foo/c++")->GetString()); +#endif } TEST(Pointer, SetValueByPointer_Pointer_NoAllocator) { @@ -1149,6 +1203,11 @@ TEST(Pointer, SetValueByPointer_Pointer_NoAllocator) { memset(buffer, 0, sizeof(buffer)); } EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + SetValueByPointer(d, Pointer("/foo/c++"), std::string("C++")); + EXPECT_STREQ("C++", GetValueByPointer(d, "/foo/c++")->GetString()); +#endif } TEST(Pointer, SetValueByPointer_String_NoAllocator) { @@ -1195,6 +1254,11 @@ TEST(Pointer, SetValueByPointer_String_NoAllocator) { memset(buffer, 0, sizeof(buffer)); } EXPECT_STREQ("World", GetValueByPointer(d, "/foo/world")->GetString()); + +#if RAPIDJSON_HAS_STDSTRING + SetValueByPointer(d, "/foo/c++", std::string("C++")); + EXPECT_STREQ("C++", GetValueByPointer(d, "/foo/c++")->GetString()); +#endif } TEST(Pointer, SwapValueByPointer) { From 6582160a12717064b943b6903dd309324698e216 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 19:14:58 +0800 Subject: [PATCH 23/43] Fix out-of-bound access in percent decode --- include/rapidjson/pointer.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index b60e84b..cd685f0 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -425,7 +425,7 @@ private: if (uriFragment) { // Decoding percent-encoding for URI fragment if (c == '%') { - PercentDecodeStream is(&source[i]); + PercentDecodeStream is(&source[i], source + length); GenericInsituStringStream os(name); Ch* begin = os.PutBegin(); Transcoder, EncodingType> transcoder; @@ -551,10 +551,11 @@ private: class PercentDecodeStream { public: - PercentDecodeStream(const Ch* source) : src_(source), head_(source), valid_(true) {} + PercentDecodeStream(const Ch* source, const Ch* end) : src_(source), head_(source), end_(end), valid_(true) {} Ch Take() { - if (*src_ != '%') { + // %XX triplet + if (src_ + 3 > end_ || *src_ != '%') { valid_ = false; return 0; } @@ -582,6 +583,7 @@ private: private: const Ch* src_; //!< Current read position. const Ch* head_; //!< Original head of the string. + const Ch* end_; bool valid_; }; From f55002c9a2be6efdbe09fee9b2a94624f27d03a6 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 19:27:12 +0800 Subject: [PATCH 24/43] Try to fix valgrind error --- test/unittest/pointertest.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 417893a..b58b346 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -295,22 +295,6 @@ TEST(Pointer, Parse_URIFragment) { EXPECT_STREQ("\xC2\xA2", p.GetTokens()[0].name); } - { - // Decode UTF-8 perecent encoding to UTF-16 - GenericPointer > > p(L"#/%C2%A2"); - EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(1u, p.GetTokenCount()); - EXPECT_STREQ(L"\xA2", p.GetTokens()[0].name); - } - - { - // Decode UTF-8 perecent encoding to UTF-16 - GenericPointer > > p(L"#/%C2%A2"); - EXPECT_TRUE(p.IsValid()); - EXPECT_EQ(1u, p.GetTokenCount()); - EXPECT_STREQ(L"\xA2", p.GetTokens()[0].name); - } - { // Decode UTF-8 perecent encoding to UTF-16 GenericPointer > > p(L"#/%C2%A2"); From b55a9bcacbf0e00ea092c514b74252dc35a46ae3 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 19:58:25 +0800 Subject: [PATCH 25/43] Try diagnosis Valgrind error --- test/unittest/pointertest.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index b58b346..797aa8f 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -300,6 +300,7 @@ TEST(Pointer, Parse_URIFragment) { GenericPointer > > p(L"#/%C2%A2"); EXPECT_TRUE(p.IsValid()); EXPECT_EQ(1u, p.GetTokenCount()); + printf("%x %x %u\n", p.GetTokens()[0].name[0], p.GetTokens()[0].name[1], p.GetTokens()[0].length); EXPECT_STREQ(L"\x00A2", p.GetTokens()[0].name); } @@ -308,6 +309,7 @@ TEST(Pointer, Parse_URIFragment) { GenericPointer > > p(L"#/%E2%82%AC"); EXPECT_TRUE(p.IsValid()); EXPECT_EQ(1u, p.GetTokenCount()); + printf("%x %x %u\n", p.GetTokens()[0].name[0], p.GetTokens()[0].name[1], p.GetTokens()[0].length); EXPECT_STREQ(L"\x20AC", p.GetTokens()[0].name); } From 369cf2a8b7777b436310455e0406732e67482da1 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 20:11:55 +0800 Subject: [PATCH 26/43] Fix wcscmp() causing false alarm in Valgrind --- test/unittest/pointertest.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 797aa8f..63ec084 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -300,8 +300,8 @@ TEST(Pointer, Parse_URIFragment) { GenericPointer > > p(L"#/%C2%A2"); EXPECT_TRUE(p.IsValid()); EXPECT_EQ(1u, p.GetTokenCount()); - printf("%x %x %u\n", p.GetTokens()[0].name[0], p.GetTokens()[0].name[1], p.GetTokens()[0].length); - EXPECT_STREQ(L"\x00A2", p.GetTokens()[0].name); + EXPECT_EQ(0x00A2, p.GetTokens()[0].name[0]); + EXPECT_EQ(1u, p.GetTokens()[0].length); } { @@ -309,8 +309,8 @@ TEST(Pointer, Parse_URIFragment) { GenericPointer > > p(L"#/%E2%82%AC"); EXPECT_TRUE(p.IsValid()); EXPECT_EQ(1u, p.GetTokenCount()); - printf("%x %x %u\n", p.GetTokens()[0].name[0], p.GetTokens()[0].name[1], p.GetTokens()[0].length); - EXPECT_STREQ(L"\x20AC", p.GetTokens()[0].name); + EXPECT_EQ(0x20AC, p.GetTokens()[0].name[0]); + EXPECT_EQ(1u, p.GetTokens()[0].length); } { From bfd47a70ed691ab5faada47e42e080dd9fce5b66 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 20:30:46 +0800 Subject: [PATCH 27/43] Fix merge conflict --- test/unittest/CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/unittest/CMakeLists.txt b/test/unittest/CMakeLists.txt index 4ff95d6..fb95b8e 100644 --- a/test/unittest/CMakeLists.txt +++ b/test/unittest/CMakeLists.txt @@ -8,11 +8,8 @@ set(UNITTEST_SOURCES itoatest.cpp jsoncheckertest.cpp namespacetest.cpp -<<<<<<< HEAD pointertest.cpp -======= prettywritertest.cpp ->>>>>>> master readertest.cpp simdtest.cpp stringbuffertest.cpp From 3c73975513f4f3c0276d936e6684686eb2e40b88 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 20:52:44 +0800 Subject: [PATCH 28/43] Fix 2 FILE* leaks in documenttest.cpp --- test/unittest/documenttest.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unittest/documenttest.cpp b/test/unittest/documenttest.cpp index 71cd777..7e5d766 100644 --- a/test/unittest/documenttest.cpp +++ b/test/unittest/documenttest.cpp @@ -146,6 +146,7 @@ TEST(Document, ParseStream_EncodedInputStream) { StringBuffer bos2; Writer writer(bos2); reader.Parse(is, writer); + fclose(fp); EXPECT_EQ(bos.GetSize(), bos2.GetSize()); EXPECT_EQ(0, memcmp(bos.GetString(), bos2.GetString(), bos2.GetSize())); @@ -184,6 +185,7 @@ TEST(Document, ParseStream_AutoUTFInputStream) { StringBuffer bos2; Writer writer(bos2); reader.Parse(is, writer); + fclose(fp); EXPECT_EQ(bos.GetSize(), bos2.GetSize()); EXPECT_EQ(0, memcmp(bos.GetString(), bos2.GetString(), bos2.GetSize())); From ae61b7973cff48247ebd1a874e4f47270392e8ca Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 21:02:34 +0800 Subject: [PATCH 29/43] Standardize CrtAllocator::Realloc() for newSize = 0 --- include/rapidjson/allocators.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/rapidjson/allocators.h b/include/rapidjson/allocators.h index d68b74c..16bf038 100644 --- a/include/rapidjson/allocators.h +++ b/include/rapidjson/allocators.h @@ -68,7 +68,14 @@ public: else return NULL; // standardize to returning NULL. } - void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { (void)originalSize; return std::realloc(originalPtr, newSize); } + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + (void)originalSize; + if (newSize == 0) { + std::free(originalPtr); + return NULL; + } + return std::realloc(originalPtr, newSize); + } static void Free(void *ptr) { std::free(ptr); } }; From 1c98609adac2401a5916563fe3bb344913f82732 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 21:23:13 +0800 Subject: [PATCH 30/43] Standardize MemoryPoolAllocator::Realloc() also, and improve coverage --- include/rapidjson/allocators.h | 3 +++ test/unittest/allocatorstest.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/include/rapidjson/allocators.h b/include/rapidjson/allocators.h index 16bf038..b7042a5 100644 --- a/include/rapidjson/allocators.h +++ b/include/rapidjson/allocators.h @@ -189,6 +189,9 @@ public: if (originalPtr == 0) return Malloc(newSize); + if (newSize == 0) + return NULL; + // Do not shrink if new size is smaller than original if (originalSize >= newSize) return originalPtr; diff --git a/test/unittest/allocatorstest.cpp b/test/unittest/allocatorstest.cpp index 3f33724..7b4deed 100644 --- a/test/unittest/allocatorstest.cpp +++ b/test/unittest/allocatorstest.cpp @@ -42,6 +42,9 @@ void TestAllocator(Allocator& a) { EXPECT_EQ(i, r[i]); Allocator::Free(r); + + // Realloc to zero size + EXPECT_TRUE(a.Realloc(a.Malloc(1), 1, 0) == 0); } TEST(Allocator, CrtAllocator) { From c35d47f83cff9d254d1aefb450c840f957cfad13 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 21:37:12 +0800 Subject: [PATCH 31/43] Change copyright header of pointer test --- test/unittest/pointertest.cpp | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 63ec084..d74d75f 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -1,22 +1,16 @@ -// Copyright (C) 2011 Milo Yip +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. // -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at // -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. +// http://opensource.org/licenses/MIT // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. #include "unittest.h" #include "rapidjson/pointer.h" From 524974deec46f437a2e46f490be556c802635774 Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 21:58:55 +0800 Subject: [PATCH 32/43] Add Validation of UTF-8 sequence for percent encoding, also improves coverage --- include/rapidjson/pointer.h | 4 ++-- test/unittest/pointertest.cpp | 27 +++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index cd685f0..7ba5174 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -429,7 +429,7 @@ private: GenericInsituStringStream os(name); Ch* begin = os.PutBegin(); Transcoder, EncodingType> transcoder; - if (!transcoder.Transcode(is, os) || !is.IsValid()) { + if (!transcoder.Validate(is, os) || !is.IsValid()) { parseErrorCode_ = kPointerParseErrorInvalidPercentEncoding; goto error; } @@ -538,7 +538,7 @@ private: GenericStringStream source(&t->name[j]); PercentEncodeStream target(os); Transcoder > transcoder; - if (!transcoder.Transcode(source, target)) + if (!transcoder.Validate(source, target)) return false; j += source.Tell() - 1; } diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index d74d75f..cc067e7 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -340,7 +340,7 @@ TEST(Pointer, Parse_URIFragment) { } { - // kPointerParseErrorInvalidPercentEncoding + // kPointerParseErrorInvalidPercentEncoding (invalid hex) Pointer p("#/%g0"); EXPECT_FALSE(p.IsValid()); EXPECT_EQ(kPointerParseErrorInvalidPercentEncoding, p.GetParseErrorCode()); @@ -348,13 +348,21 @@ TEST(Pointer, Parse_URIFragment) { } { - // kPointerParseErrorInvalidPercentEncoding + // kPointerParseErrorInvalidPercentEncoding (invalid hex) Pointer p("#/%0g"); EXPECT_FALSE(p.IsValid()); EXPECT_EQ(kPointerParseErrorInvalidPercentEncoding, p.GetParseErrorCode()); EXPECT_EQ(2u, p.GetParseErrorOffset()); } + { + // kPointerParseErrorInvalidPercentEncoding (incomplete UTF-8 sequence) + Pointer p("#/%C2"); + EXPECT_FALSE(p.IsValid()); + EXPECT_EQ(kPointerParseErrorInvalidPercentEncoding, p.GetParseErrorCode()); + EXPECT_EQ(2u, p.GetParseErrorOffset()); + } + { // kPointerParseErrorCharacterMustPercentEncode Pointer p("#/ "); @@ -395,16 +403,23 @@ TEST(Pointer, Stringify) { for (size_t i = 0; i < sizeof(sources) / sizeof(sources[0]); i++) { Pointer p(sources[i]); StringBuffer s; - p.Stringify(s); + EXPECT_TRUE(p.Stringify(s)); EXPECT_STREQ(sources[i], s.GetString()); // Stringify to URI fragment StringBuffer s2; - p.StringifyUriFragment(s2); + EXPECT_TRUE(p.StringifyUriFragment(s2)); Pointer p2(s2.GetString(), s2.GetSize()); EXPECT_TRUE(p2.IsValid()); EXPECT_TRUE(p == p2); } + + { + // Strigify to URI fragment with an invalid UTF-8 sequence + Pointer p("/\xC2"); + StringBuffer s; + EXPECT_FALSE(p.StringifyUriFragment(s)); + } } // Construct a Pointer with static tokens, no dynamic allocation involved. @@ -552,6 +567,10 @@ TEST(Pointer, Get) { EXPECT_EQ(&d[" "], Pointer("/ ").Get(d)); EXPECT_EQ(&d["m~n"], Pointer("/m~0n").Get(d)); EXPECT_TRUE(Pointer("/abc").Get(d) == 0); + EXPECT_TRUE(Pointer("/foo/2").Get(d) == 0); // Out of boundary + EXPECT_TRUE(Pointer("/foo/a").Get(d) == 0); // "/foo" is an array, cannot query by "a" + EXPECT_TRUE(Pointer("/foo/0/0").Get(d) == 0); // "/foo/0" is an string, cannot further query + EXPECT_TRUE(Pointer("/foo/0/a").Get(d) == 0); // "/foo/0" is an string, cannot further query } TEST(Pointer, GetWithDefault) { From 45bed001ee8b56953f4e59f719302d8f2a5d424f Mon Sep 17 00:00:00 2001 From: miloyip Date: Sun, 3 May 2015 23:44:58 +0800 Subject: [PATCH 33/43] Remove unusable StringRef overloads --- include/rapidjson/pointer.h | 60 ------------------------------------- 1 file changed, 60 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 7ba5174..635601d 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -231,11 +231,6 @@ public: return v; } - ValueType& GetWithDefault(ValueType& root, GenericStringRef defaultValue, typename ValueType::AllocatorType& allocator) const { - ValueType v(defaultValue); - return GetWithDefault(root, v, allocator); - } - ValueType& GetWithDefault(ValueType& root, const Ch* defaultValue, typename ValueType::AllocatorType& allocator) const { bool alreadyExist; Value& v = Create(root, allocator, &alreadyExist); @@ -270,11 +265,6 @@ public: return GetWithDefault(root, defaultValue, root.GetAllocator()); } - template - ValueType& GetWithDefault(GenericDocument& root, GenericStringRef defaultValue) const { - return GetWithDefault(root, defaultValue, root.GetAllocator()); - } - template ValueType& GetWithDefault(GenericDocument& root, const Ch* defaultValue) const { return GetWithDefault(root, defaultValue, root.GetAllocator()); @@ -303,11 +293,6 @@ public: return Create(root, allocator).CopyFrom(value, allocator); } - ValueType& Set(ValueType& root, GenericStringRef value, typename ValueType::AllocatorType& allocator) const { - ValueType v(value); - return Create(root, allocator) = v; - } - ValueType& Set(ValueType& root, const Ch* value, typename ValueType::AllocatorType& allocator) const { ValueType v(value, allocator); return Create(root, allocator) = v; @@ -337,11 +322,6 @@ public: return Create(root).CopyFrom(value, root.GetAllocator()); } - template - ValueType& Set(GenericDocument& root, GenericStringRef value) const { - return Create(root) = value; - } - template ValueType& Set(GenericDocument& root, const Ch* value) const { return Create(root) = ValueType(value, root.GetAllocator()).Move(); @@ -666,11 +646,6 @@ typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointe return pointer.GetWithDefault(root, defaultValue, a); } -template -typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, GenericStringRef defaultValue, typename T::AllocatorType& a) { - return pointer.GetWithDefault(root, defaultValue, a); -} - template typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::Ch* defaultValue, typename T::AllocatorType& a) { return pointer.GetWithDefault(root, defaultValue, a); @@ -694,11 +669,6 @@ typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&sou return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); } -template -typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], GenericStringRef defaultValue, typename T::AllocatorType& a) { - return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); -} - template typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue, typename T::AllocatorType& a) { return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); @@ -724,11 +694,6 @@ typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointe return pointer.GetWithDefault(root, defaultValue); } -template -typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, GenericStringRef defaultValue) { - return pointer.GetWithDefault(root, defaultValue); -} - template typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::Ch* defaultValue) { return pointer.GetWithDefault(root, defaultValue); @@ -752,11 +717,6 @@ typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&sou return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue); } -template -typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], GenericStringRef defaultValue) { - return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue); -} - template typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue) { return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue); @@ -782,11 +742,6 @@ typename T::ValueType& SetValueByPointer(T& root, const GenericPointer -typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, GenericStringRef value, typename T::AllocatorType& a) { - return pointer.Set(root, value, a); -} - template typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value, typename T::AllocatorType& a) { return pointer.Set(root, value, a); @@ -810,11 +765,6 @@ typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], ty return GenericPointer(source, N - 1).Set(root, value, a); } -template -typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], GenericStringRef value, typename T::AllocatorType& a) { - return GenericPointer(source, N - 1).Set(root, value, a); -} - template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value, typename T::AllocatorType& a) { return GenericPointer(source, N - 1).Set(root, value, a); @@ -840,11 +790,6 @@ typename T::ValueType& SetValueByPointer(T& root, const GenericPointer -typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, GenericStringRef value) { - return pointer.Set(root, value); -} - template typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value) { return pointer.Set(root, value); @@ -868,11 +813,6 @@ typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], ty return GenericPointer(source, N - 1).Set(root, value); } -template -typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], GenericStringRef value) { - return GenericPointer(source, N - 1).Set(root, value); -} - template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value) { return GenericPointer(source, N - 1).Set(root, value); From fc7b0a04a1703f142bba03090496ea2a8e96bbb2 Mon Sep 17 00:00:00 2001 From: miloyip Date: Mon, 4 May 2015 10:06:31 +0800 Subject: [PATCH 34/43] Add const Value& version of SetValueByPointer() and improve coverage --- include/rapidjson/pointer.h | 20 ++++++++++++++++++++ test/unittest/pointertest.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 635601d..d54676e 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -742,6 +742,11 @@ typename T::ValueType& SetValueByPointer(T& root, const GenericPointer +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + template typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value, typename T::AllocatorType& a) { return pointer.Set(root, value, a); @@ -765,6 +770,11 @@ typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], ty return GenericPointer(source, N - 1).Set(root, value, a); } +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value, typename T::AllocatorType& a) { return GenericPointer(source, N - 1).Set(root, value, a); @@ -790,6 +800,11 @@ typename T::ValueType& SetValueByPointer(T& root, const GenericPointer +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::ValueType& value) { + return pointer.Set(root, value); +} + template typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value) { return pointer.Set(root, value); @@ -813,6 +828,11 @@ typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], ty return GenericPointer(source, N - 1).Set(root, value); } +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::ValueType& value) { + return GenericPointer(source, N - 1).Set(root, value); +} + template typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value) { return GenericPointer(source, N - 1).Set(root, value); diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index cc067e7..004fa22 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -693,6 +693,11 @@ TEST(Pointer, Set) { Pointer("/foo/null").Set(d, Value().Move(), a); EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + // Const Value version + const Value foo(d["foo"], a); + Pointer("/clone").Set(d, foo, a); + EXPECT_EQ(foo, *GetValueByPointer(d, "/clone")); + // Generic version Pointer("/foo/int").Set(d, -1, a); EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); @@ -747,6 +752,11 @@ TEST(Pointer, Set_NoAllocator) { Pointer("/foo/null").Set(d, Value().Move()); EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + // Const Value version + const Value foo(d["foo"], d.GetAllocator()); + Pointer("/clone").Set(d, foo); + EXPECT_EQ(foo, *GetValueByPointer(d, "/clone")); + // Generic version Pointer("/foo/int").Set(d, -1); EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); @@ -1066,6 +1076,11 @@ TEST(Pointer, SetValueByPointer_Pointer) { SetValueByPointer(d, Pointer("/foo/null"), Value().Move(), a); EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + // Const Value version + const Value foo(d["foo"], d.GetAllocator()); + SetValueByPointer(d, Pointer("/clone"), foo, a); + EXPECT_EQ(foo, *GetValueByPointer(d, "/clone")); + // Generic version SetValueByPointer(d, Pointer("/foo/int"), -1, a); EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); @@ -1118,6 +1133,11 @@ TEST(Pointer, SetValueByPointer_String) { SetValueByPointer(d, "/foo/null", Value().Move(), a); EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + // Const Value version + const Value foo(d["foo"], d.GetAllocator()); + SetValueByPointer(d, "/clone", foo, a); + EXPECT_EQ(foo, *GetValueByPointer(d, "/clone")); + // Generic version SetValueByPointer(d, "/foo/int", -1, a); EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); @@ -1169,6 +1189,11 @@ TEST(Pointer, SetValueByPointer_Pointer_NoAllocator) { SetValueByPointer(d, Pointer("/foo/null"), Value().Move()); EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + // Const Value version + const Value foo(d["foo"], d.GetAllocator()); + SetValueByPointer(d, Pointer("/clone"), foo); + EXPECT_EQ(foo, *GetValueByPointer(d, "/clone")); + // Generic version SetValueByPointer(d, Pointer("/foo/int"), -1); EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); @@ -1220,6 +1245,11 @@ TEST(Pointer, SetValueByPointer_String_NoAllocator) { SetValueByPointer(d, "/foo/null", Value().Move()); EXPECT_TRUE(GetValueByPointer(d, "/foo/null")->IsNull()); + // Const Value version + const Value foo(d["foo"], d.GetAllocator()); + SetValueByPointer(d, "/clone", foo); + EXPECT_EQ(foo, *GetValueByPointer(d, "/clone")); + // Generic version SetValueByPointer(d, "/foo/int", -1); EXPECT_EQ(-1, GetValueByPointer(d, "/foo/int")->GetInt()); From 1135ef662274db5598b0b33b168914b4d97b623d Mon Sep 17 00:00:00 2001 From: miloyip Date: Mon, 4 May 2015 10:08:23 +0800 Subject: [PATCH 35/43] Fix VC2013 false alarm warning --- include/rapidjson/pointer.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index d54676e..fc65496 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -408,8 +408,7 @@ private: PercentDecodeStream is(&source[i], source + length); GenericInsituStringStream os(name); Ch* begin = os.PutBegin(); - Transcoder, EncodingType> transcoder; - if (!transcoder.Validate(is, os) || !is.IsValid()) { + if (!Transcoder, EncodingType>().Validate(is, os) || !is.IsValid()) { parseErrorCode_ = kPointerParseErrorInvalidPercentEncoding; goto error; } @@ -517,8 +516,7 @@ private: // Transcode to UTF8 sequence GenericStringStream source(&t->name[j]); PercentEncodeStream target(os); - Transcoder > transcoder; - if (!transcoder.Validate(source, target)) + if (!Transcoder >().Validate(source, target)) return false; j += source.Tell() - 1; } From e7bcedb4f4d3383f29f69a0e711053754338c52c Mon Sep 17 00:00:00 2001 From: miloyip Date: Mon, 4 May 2015 10:21:30 +0800 Subject: [PATCH 36/43] Simplify code --- include/rapidjson/pointer.h | 40 ++++++++++--------------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index fc65496..fbd70ad 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -224,40 +224,27 @@ public: ValueType& GetWithDefault(ValueType& root, const ValueType& defaultValue, typename ValueType::AllocatorType& allocator) const { bool alreadyExist; Value& v = Create(root, allocator, &alreadyExist); - if (!alreadyExist) { - Value clone(defaultValue, allocator); - v = clone; - } - return v; + return alreadyExist ? v : v.CopyFrom(defaultValue, allocator); } ValueType& GetWithDefault(ValueType& root, const Ch* defaultValue, typename ValueType::AllocatorType& allocator) const { bool alreadyExist; Value& v = Create(root, allocator, &alreadyExist); - if (!alreadyExist) { - Value clone(defaultValue, allocator); // This has overhead, so do it inside if. - v = clone; - } - return v; + return alreadyExist ? v : v.SetString(defaultValue, allocator); } #if RAPIDJSON_HAS_STDSTRING ValueType& GetWithDefault(ValueType& root, const std::basic_string& defaultValue, typename ValueType::AllocatorType& allocator) const { bool alreadyExist; Value& v = Create(root, allocator, &alreadyExist); - if (!alreadyExist) { - Value clone(defaultValue, allocator); // This has overhead, so do it inside if. - v = clone; - } - return v; + return alreadyExist ? v : v.SetString(defaultValue, allocator); } #endif template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) GetWithDefault(ValueType& root, T defaultValue, typename ValueType::AllocatorType& allocator) const { - ValueType v(defaultValue); - return GetWithDefault(root, v, allocator); + return GetWithDefault(root, ValueType(defaultValue).Move(), allocator); } template @@ -294,22 +281,19 @@ public: } ValueType& Set(ValueType& root, const Ch* value, typename ValueType::AllocatorType& allocator) const { - ValueType v(value, allocator); - return Create(root, allocator) = v; + return Create(root, allocator) = ValueType(value, allocator).Move(); } #if RAPIDJSON_HAS_STDSTRING ValueType& Set(ValueType& root, const std::basic_string& value, typename ValueType::AllocatorType& allocator) const { - ValueType v(value, allocator); - return Create(root, allocator) = v; + return Create(root, allocator) = ValueType(value, allocator).Move(); } #endif template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) Set(ValueType& root, T value, typename ValueType::AllocatorType& allocator) const { - ValueType v(value); - return Create(root, allocator) = v; + return Create(root, allocator) = ValueType(value).Move(); } template @@ -363,20 +347,19 @@ private: \note Source cannot be JSON String Representation of JSON Pointer, e.g. In "/\u0000", \u0000 will not be unescaped. */ void Parse(const Ch* source, size_t length) { + RAPIDJSON_ASSERT(source != NULL); + RAPIDJSON_ASSERT(nameBuffer_ == 0); + RAPIDJSON_ASSERT(tokens_ == 0); + // Create own allocator if user did not supply. if (!allocator_) ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); // Create a buffer as same size of source - RAPIDJSON_ASSERT(nameBuffer_ == 0); nameBuffer_ = (Ch*)allocator_->Malloc(length * sizeof(Ch)); - - RAPIDJSON_ASSERT(tokens_ == 0); tokens_ = (Token*)allocator_->Malloc(length * sizeof(Token)); // Maximum possible tokens in the source - tokenCount_ = 0; Ch* name = nameBuffer_; - size_t i = 0; // Detect if it is a URI fragment @@ -401,7 +384,6 @@ private: while (i < length && source[i] != '/') { Ch c = source[i]; - if (uriFragment) { // Decoding percent-encoding for URI fragment if (c == '%') { From 56568fd73f28fe19ece89daae3883d214d888b5d Mon Sep 17 00:00:00 2001 From: miloyip Date: Mon, 4 May 2015 10:25:31 +0800 Subject: [PATCH 37/43] Add GenericValue::ValueType and fix warning for SetString(std::string, Allocator) --- include/rapidjson/document.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index a649768..7386773 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -427,6 +427,7 @@ public: typedef typename GenericMemberIterator::Iterator ConstMemberIterator; //!< Constant member iterator for iterating in object. typedef GenericValue* ValueIterator; //!< Value iterator for iterating in array. typedef const GenericValue* ConstValueIterator; //!< Constant value iterator for iterating in array. + typedef GenericValue ValueType; //!< Value type of itself. //!@name Constructors and destructor. //@{ @@ -1417,7 +1418,7 @@ public: \post IsString() == true && GetString() != s.data() && strcmp(GetString(),s.data() == 0 && GetStringLength() == s.size() \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. */ - GenericValue& SetString(const std::basic_string& s, Allocator& allocator) { return SetString(s.data(), s.size(), allocator); } + GenericValue& SetString(const std::basic_string& s, Allocator& allocator) { return SetString(s.data(), SizeType(s.size()), allocator); } #endif //@} From b6a54f724480831f397938740d14e5d3d76ad6cb Mon Sep 17 00:00:00 2001 From: miloyip Date: Mon, 4 May 2015 13:32:44 +0800 Subject: [PATCH 38/43] Add API doc for GenericPointer, rename some (template) parameters --- include/rapidjson/pointer.h | 530 +++++++++++++++++++++++++++--------- 1 file changed, 405 insertions(+), 125 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index fbd70ad..3ba9e1c 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -19,59 +19,155 @@ RAPIDJSON_NAMESPACE_BEGIN -static const SizeType kPointerInvalidIndex = ~SizeType(0); +static const SizeType kPointerInvalidIndex = ~SizeType(0); //!< Represents an invalid index in GenericPointer::Token +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode +*/ enum PointerParseErrorCode { - kPointerParseErrorNone = 0, + kPointerParseErrorNone = 0, //!< The parse is successful - kPointerParseErrorTokenMustBeginWithSolidus, - kPointerParseErrorInvalidEscape, - kPointerParseErrorInvalidPercentEncoding, - kPointerParseErrorCharacterMustPercentEncode + kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/' + kPointerParseErrorInvalidEscape, //!< Invalid escape + kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment + kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment }; +/////////////////////////////////////////////////////////////////////////////// +// GenericPointer + +//! Represents a JSON Pointer. Use Pointer for UTF8 encoding and default allocator. +/*! + This class implements RFC 6901 "JavaScript Object Notation (JSON) Pointer" + (https://tools.ietf.org/html/rfc6901). + + A JSON pointer is for identifying a specific value in a JSON document + (GenericDocument). It can simplify coding of DOM tree manipulation, because it + can access multiple-level depth of DOM tree with single API call. + + After it parses a string representation (e.g. "/foo/0" or URI fragment + representation (e.g. "#/foo/0") into its internal representation (tokens), + it can be used to resolve a specific value in multiple documents, or sub-tree + of documents. + + Contrary to GenericValue, Pointer can be copy constructed and copy assigned. + Apart from assignment, a Pointer cannot be modified after construction. + + Although Pointer is very convenient, please aware that constructing Pointer + involves parsing and dynamic memory allocation. A special constructor with user- + supplied tokens eliminates these. + + GenericPointer depends on GenericDocument and GenericValue. + + \tparam ValueType The value type of the DOM tree. E.g. GenericValue > + \tparam Allocator The allocator type for allocating memory for internal representation. + + \note GenericPointer uses same encoding of ValueType. + However, Allocator of GenericPointer is independent of Allocator of Value. +*/ template class GenericPointer { public: - typedef typename ValueType::EncodingType EncodingType; - typedef typename EncodingType::Ch Ch; + typedef typename ValueType::EncodingType EncodingType; //!< Encoding type from Value + typedef typename EncodingType::Ch Ch; //!< Character type from Value + //! A token is the basic units of internal representation. + /*! + A JSON pointer string representation "/foo/123" is parsed to two tokens: + "foo" and 123. 123 will be represented in both numeric form and string form. + They are resolved according to the actual value type (object or array). + + For token that are not numbers, or the numeric value is out of bound + (greater than limits of SizeType), they are only treated as string form + (i.e. the token's index will be equal to kPointerInvalidIndex). + + This struct is public so that user can create a Pointer without parsing and + allocation, using a special constructor. + */ struct Token { - const Ch* name; - SizeType length; - SizeType index; //!< A valid index if not equal to kPointerInvalidIndex. + const Ch* name; //!< Name of the token. It has null character at the end but it can contain null character. + SizeType length; //!< Length of the name. + SizeType index; //!< A valid array index, if it is not equal to kPointerInvalidIndex. }; + //!@name Constructors and destructor. + //@{ + + //! Default constructor. GenericPointer() : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A null-terminated, string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + */ explicit GenericPointer(const Ch* source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { Parse(source, internal::StrLen(source)); } #if RAPIDJSON_HAS_STDSTRING + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ explicit GenericPointer(const std::basic_string& source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { Parse(source.c_str(), source.size()); } #endif + //! Constructor that parses a string or URI fragment representation, with length of the source string. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param length Length of source. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Slightly faster than the overload without length. + */ GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { Parse(source, length); } + //! Constructor with user-supplied tokens. + /*! + This constructor let user supplies const array of tokens. + This prevents the parsing process and eliminates allocation. + This is preferred for memory constrained environments. + + \param tokens An constant array of tokens representing the JSON pointer. + \param tokenCount Number of tokens. + + \b Example + \code + #define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, kPointerInvalidIndex } + #define INDEX(i) { #i, sizeof(#i) - 1, i } + + static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(123) }; + static const Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + // Equivalent to static const Pointer p("/foo/123"); + + #undef NAME + #undef INDEX + \endcode + */ GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(const_cast(tokens)), tokenCount_(tokenCount), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + //! Copy constructor. GenericPointer(const GenericPointer& rhs) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { *this = rhs; } + //! Destructor. ~GenericPointer() { - if (nameBuffer_) { + if (nameBuffer_) { // If user-supplied tokens constructor is used, nameBuffer_ is nullptr and tokens_ are not deallocated. Allocator::Free(nameBuffer_); Allocator::Free(tokens_); } RAPIDJSON_DELETE(ownAllocator_); } + //! Assignment operator. GenericPointer& operator=(const GenericPointer& rhs) { this->~GenericPointer(); @@ -79,11 +175,11 @@ public: parseErrorOffset_ = rhs.parseErrorOffset_; parseErrorCode_ = rhs.parseErrorCode_; - if (rhs.nameBuffer_) { - if (!allocator_) + if (rhs.nameBuffer_) { // Normally parsed tokens. + if (!allocator_) // allocator is independently owned. ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); - size_t nameBufferSize = tokenCount_; // null terminators + size_t nameBufferSize = tokenCount_; // null terminators for tokens for (Token *t = rhs.tokens_; t != rhs.tokens_ + tokenCount_; ++t) nameBufferSize += t->length; nameBuffer_ = (Ch*)allocator_->Malloc(nameBufferSize * sizeof(Ch)); @@ -98,21 +194,45 @@ public: t->name += diff; } else - tokens_ = rhs.tokens_; + tokens_ = rhs.tokens_; // User supplied const tokens. return *this; } + //@} + + //!@name Handling Parse Error + //@{ + + //! Check whether this is a valid pointer. bool IsValid() const { return parseErrorCode_ == kPointerParseErrorNone; } + //! Get the parsing error offset in code unit. size_t GetParseErrorOffset() const { return parseErrorOffset_; } + //! Get the parsing error code. PointerParseErrorCode GetParseErrorCode() const { return parseErrorCode_; } + //@} + + //!@name Tokens + //@{ + + //! Get the token array (const version only). const Token* GetTokens() const { return tokens_; } + //! Get the number of tokens. size_t GetTokenCount() const { return tokenCount_; } + //@} + + //!@name Equality/inequality operators + //@{ + + //! Equality operator. + /*! + \note When any pointers are invalid, always returns false. + */ bool operator==(const GenericPointer& rhs) const { if (!IsValid() || !rhs.IsValid() || tokenCount_ != rhs.tokenCount_) return false; @@ -129,18 +249,57 @@ public: return true; } + //! Inequality operator. + /*! + \note When any pointers are invalid, always returns true. + */ bool operator!=(const GenericPointer& rhs) const { return !(*this == rhs); } + //@} + + //!@name Stringify + //@{ + + //! Stringify the pointer into string representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ template bool Stringify(OutputStream& os) const { return Stringify(os); } + //! Stringify the pointer into URI fragment representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ template bool StringifyUriFragment(OutputStream& os) const { return Stringify(os); } - + + //@} + + //!@name Create value + //@{ + + //! Create a value in a subtree. + /*! + If the value is not exist, it creates all parent values and a JSON Null value. + So it always succeed and return the newly created or existing value. + + Remind that it may change types of parents according to tokens, so it + potentially removes previously stored values. For example, if a document + was an array, and "/foo" is used to create a value, then the document + will be changed to an object, and all existing array elements are lost. + + \param root Root value of a DOM subtree to be resolved. It can be any value other than document root. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created (a JSON Null value), or already exists value. + */ ValueType& Create(ValueType& root, typename ValueType::AllocatorType& allocator, bool* alreadyExist = 0) const { RAPIDJSON_ASSERT(IsValid()); ValueType* v = &root; @@ -189,11 +348,28 @@ public: return *v; } + //! Creates a value in a document. + /*! + \param document A document to be resolved. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created, or already exists value. + */ template - ValueType& Create(GenericDocument& root, bool* alreadyExist = 0) const { - return Create(root, root.GetAllocator(), alreadyExist); + ValueType& Create(GenericDocument& document, bool* alreadyExist = 0) const { + return Create(document, document.GetAllocator(), alreadyExist); } + //@} + + //!@name Query value + //@{ + + //! Query a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Pointer to the value if it can be resolved. Otherwise null. + */ ValueType* Get(ValueType& root) const { RAPIDJSON_ASSERT(IsValid()); ValueType* v = &root; @@ -219,14 +395,35 @@ public: return v; } + //! Query a const value in a const subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Pointer to the value if it can be resolved. Otherwise null. + */ const ValueType* Get(const ValueType& root) const { return Get(const_cast(root)); } + //@} + + //!@name Query a value with default + //@{ + + //! Query a value in a subtree with default value. + /*! + Similar to Get(), but if the specified value do not exists, it creates all parents and clone the default value. + So that this function always succeed. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param defaultValue Default value to be cloned if the value was not exists. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ ValueType& GetWithDefault(ValueType& root, const ValueType& defaultValue, typename ValueType::AllocatorType& allocator) const { bool alreadyExist; Value& v = Create(root, allocator, &alreadyExist); return alreadyExist ? v : v.CopyFrom(defaultValue, allocator); } + //! Query a value in a subtree with default null-terminated string. ValueType& GetWithDefault(ValueType& root, const Ch* defaultValue, typename ValueType::AllocatorType& allocator) const { bool alreadyExist; Value& v = Create(root, allocator, &alreadyExist); @@ -234,6 +431,7 @@ public: } #if RAPIDJSON_HAS_STDSTRING + //! Query a value in a subtree with default std::basic_string. ValueType& GetWithDefault(ValueType& root, const std::basic_string& defaultValue, typename ValueType::AllocatorType& allocator) const { bool alreadyExist; Value& v = Create(root, allocator, &alreadyExist); @@ -241,102 +439,162 @@ public: } #endif + //! Query a value in a subtree with default primitive value. + /*! + \tparam T \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) GetWithDefault(ValueType& root, T defaultValue, typename ValueType::AllocatorType& allocator) const { return GetWithDefault(root, ValueType(defaultValue).Move(), allocator); } + //! Query a value in a document with default value. template - ValueType& GetWithDefault(GenericDocument& root, const ValueType& defaultValue) const { - return GetWithDefault(root, defaultValue, root.GetAllocator()); + ValueType& GetWithDefault(GenericDocument& document, const ValueType& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); } + //! Query a value in a document with default null-terminated string. template - ValueType& GetWithDefault(GenericDocument& root, const Ch* defaultValue) const { - return GetWithDefault(root, defaultValue, root.GetAllocator()); + ValueType& GetWithDefault(GenericDocument& document, const Ch* defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); } #if RAPIDJSON_HAS_STDSTRING + //! Query a value in a document with default std::basic_string. template - ValueType& GetWithDefault(GenericDocument& root, const std::basic_string& defaultValue) const { - return GetWithDefault(root, defaultValue, root.GetAllocator()); + ValueType& GetWithDefault(GenericDocument& document, const std::basic_string& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); } #endif + //! Query a value in a document with default primitive value. + /*! + \tparam T \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) - GetWithDefault(GenericDocument& root, T defaultValue) const { - return GetWithDefault(root, defaultValue, root.GetAllocator()); + GetWithDefault(GenericDocument& document, T defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); } - // Move semantics, create parents if non-exist + //@} + + //!@name Set a value + //@{ + + //! Set a value in a subtree, with move semantics. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be set. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ ValueType& Set(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator) = value; } - // Copy semantics, create parents if non-exist + //! Set a value in a subtree, with copy semantics. ValueType& Set(ValueType& root, const ValueType& value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator).CopyFrom(value, allocator); } + //! Set a null-terminated string in a subtree. ValueType& Set(ValueType& root, const Ch* value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator) = ValueType(value, allocator).Move(); } #if RAPIDJSON_HAS_STDSTRING + //! Set a std::basic_string in a subtree. ValueType& Set(ValueType& root, const std::basic_string& value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator) = ValueType(value, allocator).Move(); } #endif + //! Set a primitive value in a subtree. + /*! + \tparam T \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) Set(ValueType& root, T value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator) = ValueType(value).Move(); } + //! Set a value in a document, with move semantics. template - ValueType& Set(GenericDocument& root, ValueType& value) const { - return Create(root) = value; + ValueType& Set(GenericDocument& document, ValueType& value) const { + return Create(document) = value; } + //! Set a value in a document, with copy semantics. template - ValueType& Set(GenericDocument& root, const ValueType& value) const { - return Create(root).CopyFrom(value, root.GetAllocator()); + ValueType& Set(GenericDocument& document, const ValueType& value) const { + return Create(document).CopyFrom(value, document.GetAllocator()); } + //! Set a null-terminated string in a document. template - ValueType& Set(GenericDocument& root, const Ch* value) const { - return Create(root) = ValueType(value, root.GetAllocator()).Move(); + ValueType& Set(GenericDocument& document, const Ch* value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); } #if RAPIDJSON_HAS_STDSTRING + //! Sets a std::basic_string in a document. template - ValueType& Set(GenericDocument& root, const std::basic_string& value) const { - return Create(root) = ValueType(value, root.GetAllocator()).Move(); + ValueType& Set(GenericDocument& document, const std::basic_string& value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); } #endif + //! Set a primitive value in a document. + /*! + \tparam T \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) - Set(GenericDocument& root, T value) const { - return Create(root) = value; + Set(GenericDocument& document, T value) const { + return Create(document) = value; } - // Create parents if non-exist + //@} + + //!@name Swap a value + //@{ + + //! Swap a value with a value in a subtree. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be swapped. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ ValueType& Swap(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { return Create(root, allocator).Swap(value); } + //! Swap a value with a value in a document. template - ValueType& Swap(GenericDocument& root, ValueType& value) const { - return Create(root).Swap(value); + ValueType& Swap(GenericDocument& document, ValueType& value) const { + return Create(document).Swap(value); } + //@} + private: + //! Check whether a character should be percent-encoded. + /*! + According to RFC 3986 2.3 Unreserved Characters. + \param c The character (code unit) to be tested. + */ bool NeedPercentEncode(Ch c) const { - // RFC 3986 2.3 Unreserved Characters return !((c >= '0' && c <= '9') || (c >= 'A' && c <='Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || c =='~'); } @@ -475,6 +733,12 @@ private: return; } + //! Stringify to string or URI fragment representation. + /*! + \tparam uriFragment True for stringifying to URI fragment representation. False for string representation. + \tparam OutputStream type of output stream. + \param os The output stream. + */ template bool Stringify(OutputStream& os) const { RAPIDJSON_ASSERT(IsValid()); @@ -509,13 +773,23 @@ private: return true; } + //! A helper stream for decoding a percent-encoded sequence into code unit. + /*! + This stream decodes %XY triplet into code unit (0-255). + If it encounters invalid characters, it sets output code unit as 0 and + mark invalid, and to be checked by IsValid(). + */ class PercentDecodeStream { public: + //! Constructor + /*! + \param source Start of the stream + \param end Past-the-end of the stream. + */ PercentDecodeStream(const Ch* source, const Ch* end) : src_(source), head_(source), end_(end), valid_(true) {} Ch Take() { - // %XX triplet - if (src_ + 3 > end_ || *src_ != '%') { + if (*src_ != '%' || src_ + 3 > end_) { // %XY triplet valid_ = false; return 0; } @@ -537,16 +811,16 @@ private: } size_t Tell() const { return src_ - head_; } - bool IsValid() const { return valid_; } private: const Ch* src_; //!< Current read position. const Ch* head_; //!< Original head of the string. - const Ch* end_; - bool valid_; + const Ch* end_; //!< Past-the-end position. + bool valid_; //!< Whether the parsing is valid. }; + //! A helper stream to encode character (UTF-8 code unit) into percent-encoded sequence. template class PercentEncodeStream { public: @@ -562,17 +836,21 @@ private: OutputStream& os_; }; - Allocator* allocator_; - Allocator* ownAllocator_; - Ch* nameBuffer_; - Token* tokens_; - size_t tokenCount_; - size_t parseErrorOffset_; - PointerParseErrorCode parseErrorCode_; + Allocator* allocator_; //!< The current allocator. It is either user-supplied or equal to ownAllocator_. + Allocator* ownAllocator_; //!< Allocator owned by this Pointer. + Ch* nameBuffer_; //!< A buffer containing all names in tokens. + Token* tokens_; //!< A list of tokens. + size_t tokenCount_; //!< Number of tokens in tokens_. + size_t parseErrorOffset_; //!< Offset in code unit when parsing fail. + PointerParseErrorCode parseErrorCode_; //!< Parsing error code. }; +//! GenericPointer for Value (UTF-8, default allocator). typedef GenericPointer Pointer; +//!@name Helper functions for GenericPointer +//@{ + ////////////////////////////////////////////////////////////////////////////// template @@ -587,14 +865,14 @@ typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], // No allocator parameter -template -typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer& pointer) { - return pointer.Create(root); +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const GenericPointer& pointer) { + return pointer.Create(document); } -template -typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N]) { - return GenericPointer(source, N - 1).Create(root); +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Create(document); } ////////////////////////////////////////////////////////////////////////////// @@ -669,50 +947,50 @@ GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValu // No allocator parameter -template -typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::ValueType& defaultValue) { - return pointer.GetWithDefault(root, defaultValue); +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); } -template -typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::Ch* defaultValue) { - return pointer.GetWithDefault(root, defaultValue); +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* defaultValue) { + return pointer.GetWithDefault(document, defaultValue); } #if RAPIDJSON_HAS_STDSTRING -template -typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const std::basic_string& defaultValue) { - return pointer.GetWithDefault(root, defaultValue); +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const std::basic_string& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); } #endif -template -RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) -GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, T2 defaultValue) { - return pointer.GetWithDefault(root, defaultValue); +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, T2 defaultValue) { + return pointer.GetWithDefault(document, defaultValue); } -template -typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue) { - return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue); +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); } -template -typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue) { - return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue); +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); } #if RAPIDJSON_HAS_STDSTRING -template -typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const std::basic_string& defaultValue) { - return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue); +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const std::basic_string& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); } #endif -template -RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) -GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValue) { - return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue); +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], T2 defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); } ////////////////////////////////////////////////////////////////////////////// @@ -775,60 +1053,60 @@ SetValueByPointer(T& root, const CharType(&source)[N], T2 value, typename T::All // No allocator parameter -template -typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value) { - return pointer.Set(root, value); +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Set(document, value); } -template -typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::ValueType& value) { - return pointer.Set(root, value); +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& value) { + return pointer.Set(document, value); } -template -typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value) { - return pointer.Set(root, value); +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* value) { + return pointer.Set(document, value); } #if RAPIDJSON_HAS_STDSTRING -template -typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const std::basic_string& value) { - return pointer.Set(root, value); +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const std::basic_string& value) { + return pointer.Set(document, value); } #endif -template -RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) -SetValueByPointer(T& root, const GenericPointer& pointer, T2 value) { - return pointer.Set(root, value); +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const GenericPointer& pointer, T2 value) { + return pointer.Set(document, value); } -template -typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value) { - return GenericPointer(source, N - 1).Set(root, value); +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); } -template -typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::ValueType& value) { - return GenericPointer(source, N - 1).Set(root, value); +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); } -template -typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value) { - return GenericPointer(source, N - 1).Set(root, value); +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* value) { + return GenericPointer(source, N - 1).Set(document, value); } #if RAPIDJSON_HAS_STDSTRING -template -typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const std::basic_string& value) { - return GenericPointer(source, N - 1).Set(root, value); +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const std::basic_string& value) { + return GenericPointer(source, N - 1).Set(document, value); } #endif -template -RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) -SetValueByPointer(T& root, const CharType(&source)[N], T2 value) { - return GenericPointer(source, N - 1).Set(root, value); +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const CharType(&source)[N], T2 value) { + return GenericPointer(source, N - 1).Set(document, value); } ////////////////////////////////////////////////////////////////////////////// @@ -843,16 +1121,18 @@ typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], t return GenericPointer(source, N - 1).Swap(root, value, a); } -template -typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value) { - return pointer.Swap(root, value); +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Swap(document, value); } -template -typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value) { - return GenericPointer(source, N - 1).Swap(root, value); +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Swap(document, value); } +//@} + RAPIDJSON_NAMESPACE_END #endif // RAPIDJSON_POINTER_H_ From 436625f83cbeabc8c6c30b7a7a099b7406f1aee8 Mon Sep 17 00:00:00 2001 From: miloyip Date: Mon, 4 May 2015 15:02:43 +0800 Subject: [PATCH 39/43] Fix ambiguous cases in Pointer::Create() --- include/rapidjson/pointer.h | 44 +++++++++++++++++------------------ test/unittest/pointertest.cpp | 25 +++++++++++++++++++- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 3ba9e1c..89dfa48 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -305,19 +305,31 @@ public: ValueType* v = &root; bool exist = true; for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { - if (t->index == kPointerInvalidIndex) { // object name - // Handling of '-' for last element of array - if (t->name[0] == '-' && t->length == 1) { - if (!v->IsArray()) - v->SetArray(); // Change to Array - v->PushBack(Value().Move(), allocator); - v = &((*v)[v->Size() - 1]); - exist = false; - } - else { + if (v->IsArray() && t->name[0] == '-' && t->length == 1) { + v->PushBack(Value().Move(), allocator); + v = &((*v)[v->Size() - 1]); + exist = false; + } + else { + if (t->index == kPointerInvalidIndex) { // must be object name if (!v->IsObject()) v->SetObject(); // Change to Object + } + else { // object name or array index + if (!v->IsArray() && !v->IsObject()) + v->SetArray(); // Change to Array + } + if (v->IsArray()) { + if (t->index >= v->Size()) { + v->Reserve(t->index + 1, allocator); + while (t->index >= v->Size()) + v->PushBack(Value().Move(), allocator); + exist = false; + } + v = &((*v)[t->index]); + } + else { typename ValueType::MemberIterator m = v->FindMember(GenericStringRef(t->name, t->length)); if (m == v->MemberEnd()) { v->AddMember(Value(t->name, t->length, allocator).Move(), Value().Move(), allocator); @@ -328,18 +340,6 @@ public: v = &m->value; } } - else { // array index - if (!v->IsArray()) - v->SetArray(); // Change to Array - - if (t->index >= v->Size()) { - v->Reserve(t->index + 1, allocator); - while (t->index >= v->Size()) - v->PushBack(Value().Move(), allocator); - exist = false; - } - v = &((*v)[t->index]); - } } if (alreadyExist) diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 004fa22..72bfdbf 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -532,9 +532,13 @@ TEST(Pointer, Create) { Value* v = &Pointer("/foo/-").Create(d, d.GetAllocator()); EXPECT_EQ(&d["foo"][1], v); } + { Value* v = &Pointer("/foo/-/-").Create(d, d.GetAllocator()); - EXPECT_EQ(&d["foo"][2][0], v); + // "foo/-" is a newly created null value x. + // "foo/-/-" finds that x is not an array, it converts x to empty object + // and treats - as "-" member name + EXPECT_EQ(&d["foo"][2]["-"], v); } { @@ -1314,3 +1318,22 @@ TEST(Pointer, SwapValueByPointer_NoAllocator) { EXPECT_STREQ("bar", d["foo"][0].GetString()); EXPECT_STREQ("baz", d["foo"][1].GetString()); } + +TEST(Pointer, Ambiguity) { + { + Document d; + d.Parse("{\"0\" : [123]}"); + EXPECT_EQ(123, Pointer("/0/0").Get(d)->GetInt()); + Pointer("/0/a").Set(d, 456); // Change array [123] to object {456} + EXPECT_EQ(456, Pointer("/0/a").Get(d)->GetInt()); + } + + { + Document d; + EXPECT_FALSE(d.Parse("[{\"0\": 123}]").HasParseError()); + EXPECT_EQ(123, Pointer("/0/0").Get(d)->GetInt()); + Pointer("/0/1").Set(d, 456); // 1 is treated as "1" to index object + EXPECT_EQ(123, Pointer("/0/0").Get(d)->GetInt()); + EXPECT_EQ(456, Pointer("/0/1").Get(d)->GetInt()); + } +} From 5543a090c02c17f1422342d566f2a1fd38d58e60 Mon Sep 17 00:00:00 2001 From: miloyip Date: Mon, 4 May 2015 16:32:02 +0800 Subject: [PATCH 40/43] Draft Pointer guide --- doc/Doxyfile.in | 1 + doc/pointer.md | 218 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 doc/pointer.md diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 6920143..b806205 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -769,6 +769,7 @@ INPUT = readme.md \ include/ \ doc/features.md \ doc/tutorial.md \ + doc/pointer.md \ doc/stream.md \ doc/encoding.md \ doc/dom.md \ diff --git a/doc/pointer.md b/doc/pointer.md new file mode 100644 index 0000000..7388097 --- /dev/null +++ b/doc/pointer.md @@ -0,0 +1,218 @@ +# Pointer + +## Status: experimental, shall be included in v1.1 + +JSON Pointer is a standardized ([RFC6901]) way to select a value inside a JSON Document (DOM). This can be analogous to XPath for XML document. However, JSON Pointer is much simpler, and a single JSON Pointer only pointed to a single value. + +Using RapidJSON's implementation of JSON Pointer can simplify some manipulations of the DOM. + +[TOC] + +# JSON Pointer {#JsonPointer} + +A JSON Pointer is a list of zero-to-many tokens, each prefixed by `/`. Each token can be a string or a number. For example, given a JSON: +~~~javascript +{ + "foo" : ["bar", "baz"], + "pi" : 3.1416 +} +~~~ + +The following JSON Pointers resolve this JSON as: + +1. `"/foo"` → `[ "bar", "baz" ]` +2. `"/foo/0` → `"bar"` +3. `"/foo/1` → `"baz"` +4. `"/pi` → 3.1416 + +Note that, an empty JSON Pointer `""` (zero token) resolves to the whole JSON. + +# Basic Usage {#BasicUsage} + +The following example code is self-explanatory. + +~~~cpp +#include "rapidjson/pointer.h" + +// ... +Document d; + +// Create DOM by Set() +Pointer("/project").Set(d, "RapidJSON"); +Pointer("/stars").Set(d, 10); +// { "project" : "RapidJSON", "stars" : 10 } + +// Access DOM by Get(). It return nullptr if the value is not exist. +if (Value* stars = Pointer("/stars").Get(d)) + stars->SetInt(stars->GetInt() + 1); +// { "project" : "RapidJSON", "stars" : 11 } + +// Set() and Create() automatically generate parents if not exist. +Pointer("/a/b/0").Create(d); +// { "project" : "RapidJSON", "stars" : 11, "a" : { "b" : [ null ] } } + +// GetWithDefault() returns reference. And it deep clones the default value. +Value& hello = Pointer("/hello").GetWithDefault(d, "world"); +// { "project" : "RapidJSON", "stars" : 11, "a" : { "b" : [ null ] }, "hello" : "world" } + +// Swap() is similar to Set() +Value x("C++"); +Pointer("/hello").Swap(d, x); +// { "project" : "RapidJSON", "stars" : 11, "a" : { "b" : [ null ] }, "hello" : "C++" } +// x becomes "world" +~~~ + +# Helper Functions {#HelperFunctions} + +Since object-oriented calling convention may be non-intuitive, RapidJSON also provides helper functions, which just wrap the member functions with free-functions. + +The following example does exactly the same as the above one. + +~~~cpp +Document d; + +SetValueByPointer(d, "/project", "RapidJSON"); +SetValueByPointer(d, "/stars", 10); + +if (Value* stars = GetValueByPointer(d, "/stars")) + stars->SetInt(stars->GetInt() + 1); + +CreateValueByPointer(d, "/a/b/0"); + +Value& hello = GetValueByPointerWithDefault(d, "/hello", "world"); + +Value x("C++"); +SwapValueByPointer(d, "/hello", x); +~~~ + +The conventions are shown here for comparison: + +1. `Pointer(source).(root, ...)` +2. `ValueByPointer(root, Pointer(source), ...)` +3. `ValueByPointer(root, source, ...)` + +# Resolving Pointer {#ResolvingPointer} + +`Pointer::Get()` or `GetValueByPointer()` function does not modify the DOM. If the tokens cannot match a value in the DOM, it returns `nullptr`. User can use this to see whether a value is exists. + +Note that, numerical tokens can represent an array index or member name. The resolving process will match the values according to the types of value. + +~~~javascript +{ + "0" : 123, + "1" : [456] +} +~~~ + +1. `"/0"` → `123` +2. `"/1/0"` → `456` + +The token `"0"` is treated as member name in the first pointer. It is treated as an array index in the second pointer. + +The other functions, including `Create()`, `GetWithDefault()`, `Set()` and `Swap()`, will change the DOM. These functions will always succeed. They will create the parent values if they do not exist. If the parent values do not match with the tokens, they will also be forced to change their type. Changing the type also mean fully removal of that DOM subtree. + +Parsing the above JSON into `d`, + +~~~cpp +SetValueByPointer(d, "1/a", 789); // { "0" : 123, "1" : { "a" : 789 } } +~~~ + +## Resolving Minus Sign Token + +Besides, [RFC6901] defines a special token `-` (single minus sign), which means the pass-the-end value of an array. `Get()` only treats this token as a member name '"-"'. Yet the other functions can resolve this for array, equivalent to calling `Value::PushBack()` to the array. + +~~~cpp +Document d; +d.Parse("{\"foo\":[123]}"); +SetValueByPointer(d, "/foo/-", 456); // { "foo" : [123, 456] } +SetValueByPointer(d, "/-", 789); // { "foo" : [123, 456], "-" : 789 } +~~~ + +## Resolving Document and Value + +When using `p.Get(root)` or `GetValueByPointer(root, p)`, `root` is a (const) `Value&`. That means, it can be a subtree of the DOM. + +The other functions have two groups of signature. One group uses `Document& document` as parameter, another one uses `Value& root`. The first group uses `document.GetAllocator()` for creating values. And the second group needs user to supply an allocator, like the functions in DOM. + +All examples above do not require an allocator parameter, because the parameter is a `Document&`. But if you want to resolve a pointer to a subtree. You need to supply it as in the following example: + +~~~cpp +class Person { +public: + Person() { + document_ = new Document(); + // CreateValueByPointer() here no need allocator + SetLocation(CreateValueByPointer(*document_, "/residence"), ...); + SetLocation(CreateValueByPointer(*document_, "/office"), ...); + }; + +private: + void SetLocation(Value& location, const char* country, const char* addresses[2]) { + Value::Allocator& a = document_->GetAllocator(); + // SetValueByPointer() here need allocator + SetValueByPointer(location, "/country", country, a); + SetValueByPointer(location, "/address/0", address[0], a); + SetValueByPointer(location, "/address/1", address[1], a); + } + + // ... + + Document* document_; +}; +~~~ + +# Error Handling {#ErrorHandling} + +A `Pointer` parses a source string in its constructor. If there is parsing error, `Pointer::IsValid()` returns false. And you can use `Pointer::GetParseErrorCode()` and `GetParseErrorOffset()` to retrieve the error information. + +Note that, all resolving functions assumes valid pointer. Resolving with an invalid pointer causes assertion failure. + +# URI Fragment Representation {#URIFragment} + +In addition to the string representation of JSON pointer that we are using till now, [RFC6901] also defines the URI fragment representation of JSON pointer. URI fragment is specified in [RFC3986] "Uniform Resource Identifier (URI): Generic Syntax". + +The main differences are that a the URI fragment always has a `#` (pound sign) in the beginning, and some characters are encoded by percent-encoding in UTF-8 sequence. For example, the following table shows different C/C++ string literals of different representations. + +String Representation | URI Fragment Representation | Pointer Tokens (UTF-8) +----------------------|-----------------------------|------------------------ +`"/foo/0"` | `"#/foo/0"` | `{"foo", 0}` +`"/a~1b"` | `"#/a~1b"` | `{"a/b"}` +`"/m~0n"` | `"#/m~0n"` | `{"m~n"}` +`"/ "` | `"#/%20"` | `{" "}` +`"/\0"` | `"#/%00"` | `{"\0"}` +`"/\xE2\x82\xAC"` | `"#/%E2%82%AC` | `{"\xE2\x82\xAC"}` + +RapidJSON fully support URI fragment representation. It automatically detects the pound sign during parsing. + +# Stringify + +You may also stringify a `Pointer` to a string or other output streams. This can be done by: + +~~~ +Pointer p(...); +StringBuffer sb; +p.Stringify(sb); +std::cout << sb.GetString() << std::endl; +~~~ + +It can also stringify to URI fragment reprsentation by `StringifyUriFragment()`. + +# User-Supplied Tokens {#UserSuppliedTokens} + +If a pointer will be resolved multiple times, it should be construct once, and then apply it to different DOMs or in different times. This reduce time and memory allocation for constructing `Pointer` multiple times. + +We can go one step further, to completely eliminate the parsing process and dynamic memory allocation, we can establish the token array directly: + +~~~cpp +#define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, kPointerInvalidIndex } +#define INDEX(i) { #i, sizeof(#i) - 1, i } + +static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(123) }; +static const Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); +// Equivalent to static const Pointer p("/foo/123"); +~~~ + +This may be useful for memory constrained systems. + +[RFC3986]: https://tools.ietf.org/html/rfc3986 +[RFC6901]: https://tools.ietf.org/html/rfc6901 From 1086297f134864058fa450f76d7cac30a2372f46 Mon Sep 17 00:00:00 2001 From: miloyip Date: Mon, 4 May 2015 17:05:01 +0800 Subject: [PATCH 41/43] Fix Pointer guide punctuations --- doc/pointer.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/pointer.md b/doc/pointer.md index 7388097..df9e953 100644 --- a/doc/pointer.md +++ b/doc/pointer.md @@ -21,9 +21,9 @@ A JSON Pointer is a list of zero-to-many tokens, each prefixed by `/`. Each toke The following JSON Pointers resolve this JSON as: 1. `"/foo"` → `[ "bar", "baz" ]` -2. `"/foo/0` → `"bar"` -3. `"/foo/1` → `"baz"` -4. `"/pi` → 3.1416 +2. `"/foo/0"` → `"bar"` +3. `"/foo/1"` → `"baz"` +4. `"/pi"` → 3.1416 Note that, an empty JSON Pointer `""` (zero token) resolves to the whole JSON. @@ -180,7 +180,7 @@ String Representation | URI Fragment Representation | Pointer Tokens (UTF-8) `"/m~0n"` | `"#/m~0n"` | `{"m~n"}` `"/ "` | `"#/%20"` | `{" "}` `"/\0"` | `"#/%00"` | `{"\0"}` -`"/\xE2\x82\xAC"` | `"#/%E2%82%AC` | `{"\xE2\x82\xAC"}` +`"/\xE2\x82\xAC"` | `"#/%E2%82%AC"` | `{"\xE2\x82\xAC"}` RapidJSON fully support URI fragment representation. It automatically detects the pound sign during parsing. From 5bec8b99bb8ef8bb2f1563b78d8d82149f3d313a Mon Sep 17 00:00:00 2001 From: miloyip Date: Mon, 4 May 2015 17:28:37 +0800 Subject: [PATCH 42/43] Minor modification to Pointer guide --- doc/pointer.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/pointer.md b/doc/pointer.md index df9e953..01703f0 100644 --- a/doc/pointer.md +++ b/doc/pointer.md @@ -23,7 +23,7 @@ The following JSON Pointers resolve this JSON as: 1. `"/foo"` → `[ "bar", "baz" ]` 2. `"/foo/0"` → `"bar"` 3. `"/foo/1"` → `"baz"` -4. `"/pi"` → 3.1416 +4. `"/pi"` → `3.1416` Note that, an empty JSON Pointer `""` (zero token) resolves to the whole JSON. @@ -40,24 +40,29 @@ Document d; // Create DOM by Set() Pointer("/project").Set(d, "RapidJSON"); Pointer("/stars").Set(d, 10); + // { "project" : "RapidJSON", "stars" : 10 } // Access DOM by Get(). It return nullptr if the value is not exist. if (Value* stars = Pointer("/stars").Get(d)) stars->SetInt(stars->GetInt() + 1); + // { "project" : "RapidJSON", "stars" : 11 } // Set() and Create() automatically generate parents if not exist. Pointer("/a/b/0").Create(d); + // { "project" : "RapidJSON", "stars" : 11, "a" : { "b" : [ null ] } } // GetWithDefault() returns reference. And it deep clones the default value. Value& hello = Pointer("/hello").GetWithDefault(d, "world"); + // { "project" : "RapidJSON", "stars" : 11, "a" : { "b" : [ null ] }, "hello" : "world" } // Swap() is similar to Set() Value x("C++"); Pointer("/hello").Swap(d, x); + // { "project" : "RapidJSON", "stars" : 11, "a" : { "b" : [ null ] }, "hello" : "C++" } // x becomes "world" ~~~ @@ -109,7 +114,7 @@ Note that, numerical tokens can represent an array index or member name. The res The token `"0"` is treated as member name in the first pointer. It is treated as an array index in the second pointer. -The other functions, including `Create()`, `GetWithDefault()`, `Set()` and `Swap()`, will change the DOM. These functions will always succeed. They will create the parent values if they do not exist. If the parent values do not match with the tokens, they will also be forced to change their type. Changing the type also mean fully removal of that DOM subtree. +The other functions, including `Create()`, `GetWithDefault()`, `Set()` and `Swap()`, will change the DOM. These functions will always succeed. They will create the parent values if they do not exist. If the parent values do not match the tokens, they will also be forced to change their type. Changing the type also mean fully removal of that DOM subtree. Parsing the above JSON into `d`, From a3e5fcf490e4d4018ffda536a0b7859e578c850e Mon Sep 17 00:00:00 2001 From: miloyip Date: Mon, 4 May 2015 18:22:40 +0800 Subject: [PATCH 43/43] Minor grammar corrections --- doc/pointer.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/pointer.md b/doc/pointer.md index 01703f0..c75fe0d 100644 --- a/doc/pointer.md +++ b/doc/pointer.md @@ -43,7 +43,7 @@ Pointer("/stars").Set(d, 10); // { "project" : "RapidJSON", "stars" : 10 } -// Access DOM by Get(). It return nullptr if the value is not exist. +// Access DOM by Get(). It return nullptr if the value does not exist. if (Value* stars = Pointer("/stars").Get(d)) stars->SetInt(stars->GetInt() + 1); @@ -98,7 +98,7 @@ The conventions are shown here for comparison: # Resolving Pointer {#ResolvingPointer} -`Pointer::Get()` or `GetValueByPointer()` function does not modify the DOM. If the tokens cannot match a value in the DOM, it returns `nullptr`. User can use this to see whether a value is exists. +`Pointer::Get()` or `GetValueByPointer()` function does not modify the DOM. If the tokens cannot match a value in the DOM, it returns `nullptr`. User can use this to check whether a value exists. Note that, numerical tokens can represent an array index or member name. The resolving process will match the values according to the types of value. @@ -124,7 +124,7 @@ SetValueByPointer(d, "1/a", 789); // { "0" : 123, "1" : { "a" : 789 } } ## Resolving Minus Sign Token -Besides, [RFC6901] defines a special token `-` (single minus sign), which means the pass-the-end value of an array. `Get()` only treats this token as a member name '"-"'. Yet the other functions can resolve this for array, equivalent to calling `Value::PushBack()` to the array. +Besides, [RFC6901] defines a special token `-` (single minus sign), which represents the pass-the-end element of an array. `Get()` only treats this token as a member name '"-"'. Yet the other functions can resolve this for array, equivalent to calling `Value::PushBack()` to the array. ~~~cpp Document d;