From 8c01e7e1ce5e39d675170749a3ec297608b27463 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Mon, 4 May 2015 21:50:26 +0800 Subject: [PATCH] Add Pointer::Erase() and EraseValueByPointer() --- doc/pointer.md | 11 +++++++ include/rapidjson/pointer.h | 60 +++++++++++++++++++++++++++++++++-- test/unittest/pointertest.cpp | 45 ++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/doc/pointer.md b/doc/pointer.md index c75fe0d..d4d1cf2 100644 --- a/doc/pointer.md +++ b/doc/pointer.md @@ -65,6 +65,12 @@ Pointer("/hello").Swap(d, x); // { "project" : "RapidJSON", "stars" : 11, "a" : { "b" : [ null ] }, "hello" : "C++" } // x becomes "world" + +// Erase a member or element, return true if the value exists +bool success = Pointer("/a").Erase(d); +assert(success); + +// { "project" : "RapidJSON", "stars" : 10 } ~~~ # Helper Functions {#HelperFunctions} @@ -88,6 +94,9 @@ Value& hello = GetValueByPointerWithDefault(d, "/hello", "world"); Value x("C++"); SwapValueByPointer(d, "/hello", x); + +bool success = EraseValueByPointer(d, "/a"); +assert(success); ~~~ The conventions are shown here for comparison: @@ -166,6 +175,8 @@ private: }; ~~~ +`Erase()` or `EraseValueByPointer()` does not need allocator. And they return `true` if the value is erased successfully. + # 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. diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index 89dfa48..b68829c 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -304,7 +304,7 @@ public: RAPIDJSON_ASSERT(IsValid()); ValueType* v = &root; bool exist = true; - for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { if (v->IsArray() && t->name[0] == '-' && t->length == 1) { v->PushBack(Value().Move(), allocator); v = &((*v)[v->Size() - 1]); @@ -373,7 +373,7 @@ public: ValueType* Get(ValueType& root) const { RAPIDJSON_ASSERT(IsValid()); ValueType* v = &root; - for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { switch (v->GetType()) { case kObjectType: { @@ -588,6 +588,50 @@ public: //@} + //! Erase 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 Whether the resolved value is found and erased. + + \note Erasing with an empty pointer \c Pointer(""), i.e. the root, always fail and return false. + */ + bool Erase(ValueType& root) const { + RAPIDJSON_ASSERT(IsValid()); + if (tokenCount_ == 0) // Cannot erase the root + return false; + + ValueType* v = &root; + const Token* last = tokens_ + (tokenCount_ - 1); + for (const 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 false; + if (t == last) { + v->EraseMember(m); + return true; + } + v = &m->value; + } + break; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + return false; + if (t == last) { + v->Erase(v->Begin() + t->index); + return true; + } + v = &((*v)[t->index]); + break; + default: + return false; + } + } + return false; + } + private: //! Check whether a character should be percent-encoded. /*! @@ -1131,6 +1175,18 @@ typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, con return GenericPointer(source, N - 1).Swap(document, value); } +////////////////////////////////////////////////////////////////////////////// + +template +bool EraseValueByPointer(T& root, const GenericPointer& pointer) { + return pointer.Erase(root); +} + +template +bool EraseValueByPointer(T& root, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Erase(root); +} + //@} RAPIDJSON_NAMESPACE_END diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index 72bfdbf..cf2ab72 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -818,6 +818,21 @@ TEST(Pointer, Swap_NoAllocator) { EXPECT_STREQ("bar", d["foo"][1].GetString()); } +TEST(Pointer, Erase) { + Document d; + d.Parse(kJson); + + EXPECT_FALSE(Pointer("").Erase(d)); + EXPECT_FALSE(Pointer("/foo/nonexist").Erase(d)); + EXPECT_TRUE(Pointer("/foo/0").Erase(d)); + EXPECT_EQ(1u, d["foo"].Size()); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_TRUE(Pointer("/foo/0").Erase(d)); + EXPECT_TRUE(d["foo"].Empty()); + EXPECT_TRUE(Pointer("/foo").Erase(d)); + EXPECT_TRUE(Pointer("/foo").Get(d) == 0); +} + TEST(Pointer, CreateValueByPointer) { Document d; Document::AllocatorType& a = d.GetAllocator(); @@ -1319,6 +1334,36 @@ TEST(Pointer, SwapValueByPointer_NoAllocator) { EXPECT_STREQ("baz", d["foo"][1].GetString()); } +TEST(Pointer, EraseValueByPointer_Pointer) { + Document d; + d.Parse(kJson); + + EXPECT_FALSE(EraseValueByPointer(d, Pointer(""))); + EXPECT_FALSE(Pointer("/foo/nonexist").Erase(d)); + EXPECT_TRUE(EraseValueByPointer(d, Pointer("/foo/0"))); + EXPECT_EQ(1u, d["foo"].Size()); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_TRUE(EraseValueByPointer(d, Pointer("/foo/0"))); + EXPECT_TRUE(d["foo"].Empty()); + EXPECT_TRUE(EraseValueByPointer(d, Pointer("/foo"))); + EXPECT_TRUE(Pointer("/foo").Get(d) == 0); +} + +TEST(Pointer, EraseValueByPointer_String) { + Document d; + d.Parse(kJson); + + EXPECT_FALSE(EraseValueByPointer(d, "")); + EXPECT_FALSE(Pointer("/foo/nonexist").Erase(d)); + EXPECT_TRUE(EraseValueByPointer(d, "/foo/0")); + EXPECT_EQ(1u, d["foo"].Size()); + EXPECT_STREQ("baz", d["foo"][0].GetString()); + EXPECT_TRUE(EraseValueByPointer(d, "/foo/0")); + EXPECT_TRUE(d["foo"].Empty()); + EXPECT_TRUE(EraseValueByPointer(d, "/foo")); + EXPECT_TRUE(Pointer("/foo").Get(d) == 0); +} + TEST(Pointer, Ambiguity) { { Document d;