From 852c25123c6f8fec619c1c8d49250bd6f698c6b1 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Fri, 10 Apr 2015 14:54:13 +0800 Subject: [PATCH] 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()); + } +}