diff --git a/CHANGELOG.md b/CHANGELOG.md index 92a4054..8ad9b3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,20 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [1.0.2] - 2015-05-14 + +### Added +* Add Value::XXXMember(...) overloads for std::string (#335) + +### Fixed * Include rapidjson.h for all internal/error headers. +* Parsing some numbers incorrectly in full-precision mode (`kFullPrecisionParseFlag`) (#342) +* Fix alignment of 64bit platforms (#328) +* Fix MemoryPoolAllocator::Clear() to clear user-buffer (0691502573f1afd3341073dd24b12c3db20fbde4) + +### Changed +* CMakeLists for include as a thirdparty in projects (#334, #337) +* Change Document::ParseStream() to use stack allocator for Reader (ffbe38614732af8e0b3abdc8b50071f386a4a685) ## [1.0.1] - 2015-04-25 @@ -60,6 +73,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## 0.1 - 2011-11-18 -[Unreleased]: https://github.com/miloyip/rapidjson/compare/v1.0.1...HEAD +[Unreleased]: https://github.com/miloyip/rapidjson/compare/v1.0.2...HEAD +[1.0.2]: https://github.com/miloyip/rapidjson/compare/v1.0.1...v1.0.2 [1.0.1]: https://github.com/miloyip/rapidjson/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/miloyip/rapidjson/compare/v1.0-beta...v1.0.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 380bdcd..68139ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,11 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8) -SET(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMakeModules) +SET(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules) PROJECT(RapidJSON CXX) set(LIB_MAJOR_VERSION "1") set(LIB_MINOR_VERSION "0") -set(LIB_PATCH_VERSION "1") +set(LIB_PATCH_VERSION "2") set(LIB_VERSION_STRING "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_PATCH_VERSION}") # compile in release with debug info mode by default @@ -17,7 +17,7 @@ SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) option(RAPIDJSON_BUILD_DOC "Build rapidjson documentation." ON) option(RAPIDJSON_BUILD_EXAMPLES "Build rapidjson examples." ON) option(RAPIDJSON_BUILD_TESTS "Build rapidjson perftests and unittests." ON) -option(RAPIDJSON_BUILD_THIRDPARTY_GTEST +option(RAPIDJSON_BUILD_THIRDPARTY_GTEST "Use gtest installation in `thirdparty/gtest` by default if available" OFF) option(RAPIDJSON_HAS_STDSTRING "" OFF) @@ -45,7 +45,7 @@ ELSEIF(WIN32) ENDIF() SET(CMAKE_INSTALL_DIR "${_CMAKE_INSTALL_DIR}" CACHE PATH "The directory cmake fiels are installed in") -include_directories(${CMAKE_SOURCE_DIR}/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) if(RAPIDJSON_BUILD_DOC) add_subdirectory(doc) diff --git a/CMakeModules/FindGTestSrc.cmake b/CMakeModules/FindGTestSrc.cmake index b5abc19..f942a8d 100644 --- a/CMakeModules/FindGTestSrc.cmake +++ b/CMakeModules/FindGTestSrc.cmake @@ -1,7 +1,7 @@ -SET(GTEST_SEARCH_PATH +SET(GTEST_SEARCH_PATH "${GTEST_SOURCE_DIR}" - "${CMAKE_SOURCE_DIR}/thirdparty/gtest") + "${CMAKE_CURRENT_LIST_DIR}/../thirdparty/gtest") IF(UNIX) IF(RAPIDJSON_BUILD_THIRDPARTY_GTEST) @@ -15,6 +15,7 @@ FIND_PATH(GTEST_SOURCE_DIR NAMES CMakeLists.txt src/gtest_main.cc PATHS ${GTEST_SEARCH_PATH}) + # Debian installs gtest include directory in /usr/include, thus need to look # for include directory separately from source directory. FIND_PATH(GTEST_INCLUDE_DIR diff --git a/appveyor.yml b/appveyor.yml index add4017..7d586e8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 1.0.1.{build} +version: 1.0.2.{build} configuration: - Debug diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 4e49c5f..c1f165a 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -3,9 +3,9 @@ find_package(Doxygen) IF(NOT DOXYGEN_FOUND) MESSAGE(STATUS "No Doxygen found. Documentation won't be built") ELSE() - file(GLOB SOURCES ${CMAKE_SOURCE_DIR}/include/*) - file(GLOB MARKDOWN_DOC ${CMAKE_SOURCE_DIR}/doc/*.md) - list(APPEND MARKDOWN_DOC ${CMAKE_SOURCE_DIR}/readme.md) + file(GLOB SOURCES ${CMAKE_CURRENT_LIST_DIR}/../include/*) + file(GLOB MARKDOWN_DOC ${CMAKE_CURRENT_LIST_DIR}/../doc/*.md) + list(APPEND MARKDOWN_DOC ${CMAKE_CURRENT_LIST_DIR}/../readme.md) CONFIGURE_FILE(Doxyfile.in Doxyfile @ONLY) CONFIGURE_FILE(Doxyfile.zh-cn.in Doxyfile.zh-cn @ONLY) @@ -15,7 +15,7 @@ ELSE() COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.zh-cn COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/html DEPENDS ${MARKDOWN_DOC} ${SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile* - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/../ ) add_custom_target(doc ALL DEPENDS html) diff --git a/doc/diagram/architecture.png b/doc/diagram/architecture.png index 05336df..556c7e7 100644 Binary files a/doc/diagram/architecture.png and b/doc/diagram/architecture.png differ diff --git a/doc/diagram/insituparsing.png b/doc/diagram/insituparsing.png index 27b2572..4400c88 100644 Binary files a/doc/diagram/insituparsing.png and b/doc/diagram/insituparsing.png differ diff --git a/doc/diagram/iterative-parser-states-diagram.png b/doc/diagram/iterative-parser-states-diagram.png index 656f8e7..f315494 100644 Binary files a/doc/diagram/iterative-parser-states-diagram.png and b/doc/diagram/iterative-parser-states-diagram.png differ diff --git a/doc/diagram/move1.png b/doc/diagram/move1.png index bf4938d..ab322d0 100644 Binary files a/doc/diagram/move1.png and b/doc/diagram/move1.png differ diff --git a/doc/diagram/move2.png b/doc/diagram/move2.png index 1d1f0bf..8d4fc5b 100644 Binary files a/doc/diagram/move2.png and b/doc/diagram/move2.png differ diff --git a/doc/diagram/move3.png b/doc/diagram/move3.png index a52ad46..558470f 100644 Binary files a/doc/diagram/move3.png and b/doc/diagram/move3.png differ diff --git a/doc/diagram/normalparsing.png b/doc/diagram/normalparsing.png index 7e9ff7d..702512c 100644 Binary files a/doc/diagram/normalparsing.png and b/doc/diagram/normalparsing.png differ diff --git a/doc/diagram/simpledom.png b/doc/diagram/simpledom.png index 111aff6..38d9c5d 100644 Binary files a/doc/diagram/simpledom.png and b/doc/diagram/simpledom.png differ diff --git a/doc/diagram/tutorial.png b/doc/diagram/tutorial.png index a2b0b54..8a12924 100644 Binary files a/doc/diagram/tutorial.png and b/doc/diagram/tutorial.png differ diff --git a/doc/diagram/utilityclass.png b/doc/diagram/utilityclass.png index 0088bb1..ce029a4 100644 Binary files a/doc/diagram/utilityclass.png and b/doc/diagram/utilityclass.png differ diff --git a/doc/misc/header.html b/doc/misc/header.html index 2dbe721..d43f2aa 100644 --- a/doc/misc/header.html +++ b/doc/misc/header.html @@ -16,6 +16,15 @@ $mathjax $extrastylesheet +
diff --git a/include/rapidjson/allocators.h b/include/rapidjson/allocators.h index b7042a5..6be2776 100644 --- a/include/rapidjson/allocators.h +++ b/include/rapidjson/allocators.h @@ -143,11 +143,13 @@ public: //! Deallocates all memory chunks, excluding the user-supplied buffer. void Clear() { - while(chunkHead_ != 0 && chunkHead_ != userBuffer_) { + while (chunkHead_ && chunkHead_ != userBuffer_) { ChunkHeader* next = chunkHead_->next; baseAllocator_->Free(chunkHead_); chunkHead_ = next; } + if (chunkHead_ && chunkHead_ == userBuffer_) + chunkHead_->size = 0; // Clear user buffer } //! Computes the total capacity of allocated memory chunks. @@ -179,7 +181,7 @@ public: if (chunkHead_ == 0 || chunkHead_->size + size > chunkHead_->capacity) AddChunk(chunk_capacity_ > size ? chunk_capacity_ : size); - void *buffer = reinterpret_cast(chunkHead_ + 1) + chunkHead_->size; + void *buffer = reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size; chunkHead_->size += size; return buffer; } @@ -197,7 +199,7 @@ public: return originalPtr; // Simply expand it if it is the last allocation and there is sufficient space - if (originalPtr == (char *)(chunkHead_ + 1) + chunkHead_->size - originalSize) { + if (originalPtr == (char *)(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size - originalSize) { size_t increment = static_cast(newSize - originalSize); increment = RAPIDJSON_ALIGN(increment); if (chunkHead_->size + increment <= chunkHead_->capacity) { @@ -229,7 +231,7 @@ private: void AddChunk(size_t capacity) { if (!baseAllocator_) ownBaseAllocator_ = baseAllocator_ = RAPIDJSON_NEW(BaseAllocator()); - ChunkHeader* chunk = reinterpret_cast(baseAllocator_->Malloc(sizeof(ChunkHeader) + capacity)); + ChunkHeader* chunk = reinterpret_cast(baseAllocator_->Malloc(RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + capacity)); chunk->capacity = capacity; chunk->size = 0; chunk->next = chunkHead_; diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index 7386773..889cdfa 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -844,6 +844,12 @@ public: template const GenericValue& operator[](const GenericValue& name) const { return const_cast(*this)[name]; } +#if RAPIDJSON_HAS_STDSTRING + //! Get a value from an object associated with name (string object). + GenericValue& operator[](const std::basic_string& name) { return (*this)[GenericValue(StringRef(name))]; } + const GenericValue& operator[](const std::basic_string& name) const { return (*this)[GenericValue(StringRef(name))]; } +#endif + //! Const member iterator /*! \pre IsObject() == true */ ConstMemberIterator MemberBegin() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(data_.o.members); } @@ -867,6 +873,18 @@ public: */ bool HasMember(const Ch* name) const { return FindMember(name) != MemberEnd(); } +#if RAPIDJSON_HAS_STDSTRING + //! Check whether a member exists in the object with string object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const std::basic_string& name) const { return FindMember(name) != MemberEnd(); } +#endif + //! Check whether a member exists in the object with GenericValue name. /*! This version is faster because it does not need a StrLen(). It can also handle string with null character. @@ -923,6 +941,18 @@ public: } template ConstMemberIterator FindMember(const GenericValue& name) const { return const_cast(*this).FindMember(name); } +#if RAPIDJSON_HAS_STDSTRING + //! Find member by string object name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + */ + MemberIterator FindMember(const std::basic_string& name) { return FindMember(StringRef(name)); } + ConstMemberIterator FindMember(const std::basic_string& name) const { return FindMember(StringRef(name)); } +#endif + //! Add a member (name-value pair) to the object. /*! \param name A string value as name of member. \param value Value of any type. @@ -969,6 +999,22 @@ public: return AddMember(name, v, allocator); } +#if RAPIDJSON_HAS_STDSTRING + //! Add a string object as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, std::basic_string& value, Allocator& allocator) { + GenericValue v(value, allocator); + return AddMember(name, v, allocator); + } +#endif + //! Add any primitive value as member (name-value pair) to the object. /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t \param name A string value as name of member. @@ -1087,6 +1133,10 @@ public: return RemoveMember(n); } +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string& name) { return RemoveMember(GenericValue(StringRef(name))); } +#endif + template bool RemoveMember(const GenericValue& name) { MemberIterator m = FindMember(name); @@ -1163,6 +1213,31 @@ public: return pos; } + //! Erase a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note Linear time complexity. + */ + bool EraseMember(const Ch* name) { + GenericValue n(StringRef(name)); + return EraseMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string& name) { return EraseMember(GenericValue(StringRef(name))); } +#endif + + template + bool EraseMember(const GenericValue& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + EraseMember(m); + return true; + } + else + return false; + } + //@} //!@name Array @@ -1741,7 +1816,7 @@ public: template GenericDocument& ParseStream(InputStream& is) { ValueType::SetNull(); // Remove existing root if exist - GenericReader reader(&GetAllocator()); + GenericReader reader(&stack_.GetAllocator()); ClearStackOnExit scope(*this); parseResult_ = reader.template Parse(is, *this); if (parseResult_) { diff --git a/include/rapidjson/internal/strtod.h b/include/rapidjson/internal/strtod.h index fa85286..ace65f6 100644 --- a/include/rapidjson/internal/strtod.h +++ b/include/rapidjson/internal/strtod.h @@ -191,8 +191,13 @@ inline bool StrtodDiyFp(const char* decimals, size_t length, size_t decimalPosit DiyFp rounded(v.f >> precisionSize, v.e + precisionSize); const uint64_t precisionBits = (v.f & ((uint64_t(1) << precisionSize) - 1)) * kUlp; const uint64_t halfWay = (uint64_t(1) << (precisionSize - 1)) * kUlp; - if (precisionBits >= halfWay + error) + if (precisionBits >= halfWay + error) { rounded.f++; + if (rounded.f & (DiyFp::kDpHiddenBit << 1)) { // rounding overflows mantissa (issue #340) + rounded.f >>= 1; + rounded.e++; + } + } *result = rounded.ToDouble(); diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h index b68829c..5d2aa8d 100644 --- a/include/rapidjson/pointer.h +++ b/include/rapidjson/pointer.h @@ -16,6 +16,7 @@ #define RAPIDJSON_POINTER_H_ #include "document.h" +#include "internal/itoa.h" RAPIDJSON_NAMESPACE_BEGIN @@ -160,47 +161,130 @@ public: //! Destructor. ~GenericPointer() { - if (nameBuffer_) { // If user-supplied tokens constructor is used, nameBuffer_ is nullptr and tokens_ are not deallocated. - Allocator::Free(nameBuffer_); + if (nameBuffer_) // If user-supplied tokens constructor is used, nameBuffer_ is nullptr and tokens_ are not deallocated. Allocator::Free(tokens_); - } RAPIDJSON_DELETE(ownAllocator_); } //! Assignment operator. GenericPointer& operator=(const GenericPointer& rhs) { - this->~GenericPointer(); + if (this != &rhs) { + // Do not delete ownAllcator + if (nameBuffer_) + Allocator::Free(tokens_); - tokenCount_ = rhs.tokenCount_; - parseErrorOffset_ = rhs.parseErrorOffset_; - parseErrorCode_ = rhs.parseErrorCode_; + tokenCount_ = rhs.tokenCount_; + parseErrorOffset_ = rhs.parseErrorOffset_; + parseErrorCode_ = rhs.parseErrorCode_; - if (rhs.nameBuffer_) { // Normally parsed tokens. - if (!allocator_) // allocator is independently owned. - ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); - - 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)); - std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch)); - - 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; + if (rhs.nameBuffer_) + CopyFromRaw(rhs); // Normally parsed tokens. + else { + tokens_ = rhs.tokens_; // User supplied const tokens. + nameBuffer_ = 0; + } } - else - tokens_ = rhs.tokens_; // User supplied const tokens. - return *this; } //@} + //!@name Append token + //@{ + + //! Append a token and return a new Pointer + /*! + \param token Token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Token& token, Allocator* allocator = 0) const { + GenericPointer r; + r.allocator_ = allocator; + Ch *p = r.CopyFromRaw(*this, 1, token.length + 1); + std::memcpy(p, token.name, (token.length + 1) * sizeof(Ch)); + r.tokens_[tokenCount_].name = p; + r.tokens_[tokenCount_].length = token.length; + r.tokens_[tokenCount_].index = token.index; + return r; + } + + //! Append a name token with length, and return a new Pointer + /*! + \param name Name to be appended. + \param length Length of name. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Ch* name, SizeType length, Allocator* allocator = 0) const { + Token token = { name, length, kPointerInvalidIndex }; + return Append(token, allocator); + } + + //! Append a name token without length, and return a new Pointer + /*! + \param name Name (const Ch*) to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >), (GenericPointer)) + Append(T* name, Allocator* allocator = 0) const { + return Append(name, StrLen(name), allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Append a name token, and return a new Pointer + /*! + \param name Name to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const std::basic_string& name, Allocator* allocator = 0) const { + return Append(name.c_str(), static_cast(name.size()), allocator); + } +#endif + + //! Append a index token, and return a new Pointer + /*! + \param index Index to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(SizeType index, Allocator* allocator = 0) const { + char buffer[21]; + SizeType length = (sizeof(SizeType) == 4 ? internal::u32toa(index, buffer): internal::u64toa(index, buffer)) - buffer; + buffer[length] = '\0'; + + if (sizeof(Ch) == 1) { + Token token = { (Ch*)buffer, length, index }; + return Append(token, allocator); + } + else { + Ch name[21]; + for (size_t i = 0; i <= length; i++) + name[i] = buffer[i]; + Token token = { name, length, index }; + return Append(token, allocator); + } + } + + //! Append a token by value, and return a new Pointer + /*! + \param value Value (either Uint or String) to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const ValueType& token, Allocator* allocator = 0) const { + if (token.IsString()) + return Append(token.GetString(), token.GetStringLength(), allocator); + else { + RAPIDJSON_ASSERT(token.IsUint64()); + RAPIDJSON_ASSERT(token.GetUint64() <= SizeType(~0)); + return Append(static_cast(token.GetUint64()), allocator); + } + } + //!@name Handling Parse Error //@{ @@ -240,7 +324,7 @@ public: 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) + (tokens_[i].length != 0 && std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch)* tokens_[i].length) != 0)) { return false; } @@ -602,37 +686,69 @@ public: ValueType* v = &root; const Token* last = tokens_ + (tokenCount_ - 1); - for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + for (const Token *t = tokens_; t != last; ++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; + + switch (v->GetType()) { + case kObjectType: + return v->EraseMember(GenericStringRef(last->name, last->length)); + case kArrayType: + if (last->index == kPointerInvalidIndex || last->index >= v->Size()) + return false; + v->Erase(v->Begin() + last->index); + return true; + default: + return false; + } } private: + //! Clone the content from rhs to this. + /*! + \param rhs Source pointer. + \param extraToken Extra tokens to be allocated. + \param extraNameBufferSize Extra name buffer size (in number of Ch) to be allocated. + \return Start of non-occupied name buffer, for storing extra names. + */ + Ch* CopyFromRaw(const GenericPointer& rhs, size_t extraToken = 0, size_t extraNameBufferSize = 0) { + if (!allocator_) // allocator is independently owned. + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); + + size_t nameBufferSize = rhs.tokenCount_; // null terminators for tokens + for (Token *t = rhs.tokens_; t != rhs.tokens_ + rhs.tokenCount_; ++t) + nameBufferSize += t->length; + + tokenCount_ = rhs.tokenCount_ + extraToken; + tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + (nameBufferSize + extraNameBufferSize) * sizeof(Ch))); + nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); + std::memcpy(tokens_, rhs.tokens_, rhs.tokenCount_ * sizeof(Token)); + std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch)); + + // Adjust pointers to name buffer + std::ptrdiff_t diff = nameBuffer_ - rhs.nameBuffer_; + for (Token *t = tokens_; t != tokens_ + rhs.tokenCount_; ++t) + t->name += diff; + + return nameBuffer_ + nameBufferSize; + } + //! Check whether a character should be percent-encoded. /*! According to RFC 3986 2.3 Unreserved Characters. @@ -657,11 +773,14 @@ private: if (!allocator_) ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator()); - // Create a buffer as same size of source - nameBuffer_ = (Ch*)allocator_->Malloc(length * sizeof(Ch)); - tokens_ = (Token*)allocator_->Malloc(length * sizeof(Token)); // Maximum possible tokens in the source + // Count number of '/' as tokenCount tokenCount_ = 0; - Ch* name = nameBuffer_; + for (const Ch* s = source; s != source + length; s++) + if (*s == '/') + tokenCount_++; + + Token* token = tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + length * sizeof(Ch))); + Ch* name = nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); size_t i = 0; // Detect if it is a URI fragment @@ -680,8 +799,7 @@ private: RAPIDJSON_ASSERT(source[i] == '/'); i++; // consumes '/' - Token& token = tokens_[tokenCount_++]; - token.name = name; + token->name = name; bool isNumber = true; while (i < length && source[i] != '/') { @@ -739,18 +857,20 @@ private: *name++ = c; } - token.length = name - token.name; + token->length = name - token->name; + if (token->length == 0) + isNumber = false; *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') + 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'); + 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; @@ -759,16 +879,15 @@ private: } } - token.index = isNumber ? n : kPointerInvalidIndex; + token->index = isNumber ? n : kPointerInvalidIndex; + token++; } 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: - Allocator::Free(nameBuffer_); Allocator::Free(tokens_); nameBuffer_ = 0; tokens_ = 0; diff --git a/include/rapidjson/rapidjson.h b/include/rapidjson/rapidjson.h index b06e82b..b0dabc7 100644 --- a/include/rapidjson/rapidjson.h +++ b/include/rapidjson/rapidjson.h @@ -69,7 +69,7 @@ */ #define RAPIDJSON_MAJOR_VERSION 1 #define RAPIDJSON_MINOR_VERSION 0 -#define RAPIDJSON_PATCH_VERSION 1 +#define RAPIDJSON_PATCH_VERSION 2 #define RAPIDJSON_VERSION_STRING \ RAPIDJSON_STRINGIFY(RAPIDJSON_MAJOR_VERSION.RAPIDJSON_MINOR_VERSION.RAPIDJSON_PATCH_VERSION) @@ -223,7 +223,7 @@ //! Whether using 64-bit architecture #ifndef RAPIDJSON_64BIT -#if defined(__LP64__) || defined(_WIN64) +#if defined(__LP64__) || defined(_WIN64) || defined(__EMSCRIPTEN__) #define RAPIDJSON_64BIT 1 #else #define RAPIDJSON_64BIT 0 diff --git a/include/rapidjson/reader.h b/include/rapidjson/reader.h index be0d9fb..9a17301 100644 --- a/include/rapidjson/reader.h +++ b/include/rapidjson/reader.h @@ -271,7 +271,7 @@ inline const char *SkipWhitespace_SIMD(const char* p) { // The rest of string using SIMD static const char whitespace[16] = " \n\r\t"; - const __m128i w = _mm_load_si128((const __m128i *)&whitespace[0]); + const __m128i w = _mm_loadu_si128((const __m128i *)&whitespace[0]); for (;; p += 16) { const __m128i s = _mm_load_si128((const __m128i *)p); @@ -967,13 +967,13 @@ private: else { if (use64bit) { if (minus) - cont = handler.Int64(-(int64_t)i64); + cont = handler.Int64(static_cast(~i64 + 1)); else cont = handler.Uint64(i64); } else { if (minus) - cont = handler.Int(-(int)i); + cont = handler.Int(static_cast(~i + 1)); else cont = handler.Uint(i); } @@ -1387,13 +1387,13 @@ private: } switch (src) { - case IterativeParsingStartState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentEmpty, is.Tell()); - case IterativeParsingFinishState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentRootNotSingular, is.Tell()); + case IterativeParsingStartState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentEmpty, is.Tell()); return; + case IterativeParsingFinishState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentRootNotSingular, is.Tell()); return; case IterativeParsingObjectInitialState: - case IterativeParsingMemberDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); - case IterativeParsingMemberKeyState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); - case IterativeParsingMemberValueState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); - case IterativeParsingElementState: RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); + case IterativeParsingMemberDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); return; + case IterativeParsingMemberKeyState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); return; + case IterativeParsingMemberValueState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); return; + case IterativeParsingElementState: RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); return; default: RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); } } diff --git a/readme.md b/readme.md index 98f81a7..8682df1 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ ![](doc/logo/rapidjson.png) -![](https://img.shields.io/badge/release-v1.0.1-blue.png) +![](https://img.shields.io/badge/release-v1.0.2-blue.png) ## A fast JSON parser/generator for C++ with both SAX/DOM style API @@ -10,8 +10,8 @@ Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights * [RapidJSON GitHub](https://github.com/miloyip/rapidjson/) * RapidJSON Documentation - * [English](http://miloyip.github.io/rapidjson/) - * [简体中文](http://miloyip.github.io/rapidjson/zh-cn/) + * [English](http://rapidjson.org/) + * [简体中文](http://rapidjson.org/zh-cn/) * [GitBook](https://www.gitbook.com/book/miloyip/rapidjson/) with downloadable PDF/EPUB/MOBI, without API reference. ## Build status diff --git a/readme.zh-cn.md b/readme.zh-cn.md index eb6c21d..5124f8e 100644 --- a/readme.zh-cn.md +++ b/readme.zh-cn.md @@ -1,6 +1,6 @@ ![](doc/logo/rapidjson.png) -![](https://img.shields.io/badge/release-v1.0.1-blue.png) +![](https://img.shields.io/badge/release-v1.0.2-blue.png) ## 高效的C++ JSON解析/生成器,提供SAX及DOM风格API @@ -10,8 +10,8 @@ Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights * [RapidJSON GitHub](https://github.com/miloyip/rapidjson/) * RapidJSON 文档 - * [English](http://miloyip.github.io/rapidjson/) - * [简体中文](http://miloyip.github.io/rapidjson/zh-cn/) + * [English](http://rapidjson.org/) + * [简体中文](http://rapidjson.org/zh-cn/) * [GitBook](https://www.gitbook.com/book/miloyip/rapidjson/)可下载PDF/EPUB/MOBI,但不含API参考手册。 ## Build 状态 diff --git a/test/perftest/rapidjsontest.cpp b/test/perftest/rapidjsontest.cpp index 05ecf6e..9be966a 100644 --- a/test/perftest/rapidjsontest.cpp +++ b/test/perftest/rapidjsontest.cpp @@ -298,11 +298,28 @@ TEST_F(RapidJson, internal_Pow10) { EXPECT_GT(sum, 0.0); } -TEST_F(RapidJson, SIMD_SUFFIX(Whitespace)) { +TEST_F(RapidJson, SkipWhitespace_Basic) { for (size_t i = 0; i < kTrialCount; i++) { - Document doc; - ASSERT_TRUE(doc.Parse(whitespace_).IsArray()); - } + rapidjson::StringStream s(whitespace_); + while (s.Peek() == ' ' || s.Peek() == '\n' || s.Peek() == '\r' || s.Peek() == '\t') + s.Take(); + ASSERT_EQ('[', s.Peek()); + } +} + +TEST_F(RapidJson, SIMD_SUFFIX(SkipWhitespace)) { + for (size_t i = 0; i < kTrialCount; i++) { + rapidjson::StringStream s(whitespace_); + rapidjson::SkipWhitespace(s); + ASSERT_EQ('[', s.Peek()); + } +} + +TEST_F(RapidJson, SkipWhitespace_strspn) { + for (size_t i = 0; i < kTrialCount; i++) { + const char* s = whitespace_ + std::strspn(whitespace_, " \t\r\n"); + ASSERT_EQ('[', *s); + } } TEST_F(RapidJson, UTF8_Validate) { diff --git a/test/unittest/CMakeLists.txt b/test/unittest/CMakeLists.txt index fb95b8e..fd2eb4d 100644 --- a/test/unittest/CMakeLists.txt +++ b/test/unittest/CMakeLists.txt @@ -21,7 +21,7 @@ set(UNITTEST_SOURCES if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wextra -Weffc++ -Wswitch-default -Wfloat-equal") elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wextra -Weffc++ -Wswitch-default -Wfloat-equal") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wextra -Weffc++ -Wswitch-default -Wfloat-equal -Wimplicit-fallthrough") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") add_definitions(-D_CRT_SECURE_NO_WARNINGS=1) endif() diff --git a/test/unittest/documenttest.cpp b/test/unittest/documenttest.cpp index 940b295..2ee6b10 100644 --- a/test/unittest/documenttest.cpp +++ b/test/unittest/documenttest.cpp @@ -241,7 +241,7 @@ TEST(Document, UserBuffer) { char parseBuffer[1024]; MemoryPoolAllocator<> valueAllocator(valueBuffer, sizeof(valueBuffer)); MemoryPoolAllocator<> parseAllocator(parseBuffer, sizeof(parseBuffer)); - DocumentType doc(&valueAllocator, sizeof(parseBuffer), &parseAllocator); + DocumentType doc(&valueAllocator, sizeof(parseBuffer) / 2, &parseAllocator); doc.Parse(" { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } "); EXPECT_FALSE(doc.HasParseError()); EXPECT_LE(valueAllocator.Size(), sizeof(valueBuffer)); diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp index cf2ab72..7ec3f72 100644 --- a/test/unittest/pointertest.cpp +++ b/test/unittest/pointertest.cpp @@ -39,12 +39,22 @@ TEST(Pointer, Parse) { EXPECT_EQ(0u, p.GetTokenCount()); } + { + Pointer p("/"); + EXPECT_TRUE(p.IsValid()); + EXPECT_EQ(1u, p.GetTokenCount()); + EXPECT_EQ(0u, p.GetTokens()[0].length); + EXPECT_STREQ("", p.GetTokens()[0].name); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); + } + { 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); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); } #if RAPIDJSON_HAS_STDSTRING @@ -54,6 +64,7 @@ TEST(Pointer, Parse) { EXPECT_EQ(1u, p.GetTokenCount()); EXPECT_EQ(3u, p.GetTokens()[0].length); EXPECT_STREQ("foo", p.GetTokens()[0].name); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); } #endif @@ -63,6 +74,7 @@ TEST(Pointer, Parse) { EXPECT_EQ(2u, p.GetTokenCount()); EXPECT_EQ(3u, p.GetTokens()[0].length); EXPECT_STREQ("foo", p.GetTokens()[0].name); + EXPECT_EQ(kPointerInvalidIndex, p.GetTokens()[0].index); EXPECT_EQ(1u, p.GetTokens()[1].length); EXPECT_STREQ("0", p.GetTokens()[1].name); EXPECT_EQ(0u, p.GetTokens()[1].index); @@ -481,6 +493,14 @@ TEST(Pointer, Assignment) { EXPECT_EQ(1u, q.GetTokens()[1].length); EXPECT_STREQ("0", q.GetTokens()[1].name); EXPECT_EQ(0u, q.GetTokens()[1].index); + q = q; + EXPECT_TRUE(q.IsValid()); + EXPECT_EQ(2u, q.GetTokenCount()); + EXPECT_EQ(3u, q.GetTokens()[0].length); + EXPECT_STREQ("foo", q.GetTokens()[0].name); + EXPECT_EQ(1u, q.GetTokens()[1].length); + EXPECT_STREQ("0", q.GetTokens()[1].name); + EXPECT_EQ(0u, q.GetTokens()[1].index); } // Static tokens @@ -498,6 +518,36 @@ TEST(Pointer, Assignment) { } } +TEST(Pointer, Append) { + { + Pointer p; + Pointer q = p.Append("foo"); + EXPECT_TRUE(Pointer("/foo") == q); + q = q.Append(1234); + EXPECT_TRUE(Pointer("/foo/1234") == q); + q = q.Append(""); + EXPECT_TRUE(Pointer("/foo/1234/") == q); + } + + { + Pointer p; + Pointer q = p.Append(Value("foo").Move()); + EXPECT_TRUE(Pointer("/foo") == q); + q = q.Append(Value(1234).Move()); + EXPECT_TRUE(Pointer("/foo/1234") == q); + q = q.Append(Value(kStringType).Move()); + EXPECT_TRUE(Pointer("/foo/1234/") == q); + } + +#if RAPIDJSON_HAS_STDSTRING + { + Pointer p; + Pointer q = p.Append(std::string("foo")); + EXPECT_TRUE(Pointer("/foo") == q); + } +#endif +} + TEST(Pointer, Equality) { EXPECT_TRUE(Pointer("/foo/0") == Pointer("/foo/0")); EXPECT_FALSE(Pointer("/foo/0") == Pointer("/foo/1")); @@ -823,7 +873,13 @@ TEST(Pointer, Erase) { d.Parse(kJson); EXPECT_FALSE(Pointer("").Erase(d)); + EXPECT_FALSE(Pointer("/nonexist").Erase(d)); + EXPECT_FALSE(Pointer("/nonexist/nonexist").Erase(d)); EXPECT_FALSE(Pointer("/foo/nonexist").Erase(d)); + EXPECT_FALSE(Pointer("/foo/nonexist/nonexist").Erase(d)); + EXPECT_FALSE(Pointer("/foo/0/nonexist").Erase(d)); + EXPECT_FALSE(Pointer("/foo/0/nonexist/nonexist").Erase(d)); + EXPECT_FALSE(Pointer("/foo/2/nonexist").Erase(d)); EXPECT_TRUE(Pointer("/foo/0").Erase(d)); EXPECT_EQ(1u, d["foo"].Size()); EXPECT_STREQ("baz", d["foo"][0].GetString()); @@ -831,6 +887,24 @@ TEST(Pointer, Erase) { EXPECT_TRUE(d["foo"].Empty()); EXPECT_TRUE(Pointer("/foo").Erase(d)); EXPECT_TRUE(Pointer("/foo").Get(d) == 0); + + Pointer("/a/0/b/0").Create(d); + + EXPECT_TRUE(Pointer("/a/0/b/0").Get(d) != 0); + EXPECT_TRUE(Pointer("/a/0/b/0").Erase(d)); + EXPECT_TRUE(Pointer("/a/0/b/0").Get(d) == 0); + + EXPECT_TRUE(Pointer("/a/0/b").Get(d) != 0); + EXPECT_TRUE(Pointer("/a/0/b").Erase(d)); + EXPECT_TRUE(Pointer("/a/0/b").Get(d) == 0); + + EXPECT_TRUE(Pointer("/a/0").Get(d) != 0); + EXPECT_TRUE(Pointer("/a/0").Erase(d)); + EXPECT_TRUE(Pointer("/a/0").Get(d) == 0); + + EXPECT_TRUE(Pointer("/a").Get(d) != 0); + EXPECT_TRUE(Pointer("/a").Erase(d)); + EXPECT_TRUE(Pointer("/a").Get(d) == 0); } TEST(Pointer, CreateValueByPointer) { diff --git a/test/unittest/readertest.cpp b/test/unittest/readertest.cpp index bee19a8..9106063 100644 --- a/test/unittest/readertest.cpp +++ b/test/unittest/readertest.cpp @@ -193,7 +193,7 @@ static void TestParseDouble() { EXPECT_DOUBLE_EQ(x, h.actual_); \ } \ } - + TEST_DOUBLE(fullPrecision, "0.0", 0.0); TEST_DOUBLE(fullPrecision, "-0.0", -0.0); // For checking issue #289 TEST_DOUBLE(fullPrecision, "1.0", 1.0); @@ -327,15 +327,44 @@ static void TestParseDouble() { if (fullPrecision) { EXPECT_EQ(d.Uint64Value(), a.Uint64Value()); if (d.Uint64Value() != a.Uint64Value()) - printf(" String: %sn Actual: %.17gnExpected: %.17gn", buffer, h.actual_, d.Value()); + printf(" String: %s\n Actual: %.17g\nExpected: %.17g\n", buffer, h.actual_, d.Value()); } else { - EXPECT_EQ(d.Sign(), a.Sign()); /* for 0.0 != -0.0 */ + EXPECT_EQ(d.Sign(), a.Sign()); // for 0.0 != -0.0 EXPECT_DOUBLE_EQ(d.Value(), h.actual_); } } } } + + // Issue #340 + TEST_DOUBLE(fullPrecision, "7.450580596923828e-9", 7.450580596923828e-9); + { + internal::Double d(1.0); + for (int i = 0; i < 324; i++) { + char buffer[32]; + *internal::dtoa(d.Value(), buffer) = '\0'; + + StringStream s(buffer); + ParseDoubleHandler h; + Reader reader; + ASSERT_EQ(kParseErrorNone, reader.Parse(s, h).Code()); + EXPECT_EQ(1u, h.step_); + internal::Double a(h.actual_); + if (fullPrecision) { + EXPECT_EQ(d.Uint64Value(), a.Uint64Value()); + if (d.Uint64Value() != a.Uint64Value()) + printf(" String: %s\n Actual: %.17g\nExpected: %.17g\n", buffer, h.actual_, d.Value()); + } + else { + EXPECT_EQ(d.Sign(), a.Sign()); // for 0.0 != -0.0 + EXPECT_DOUBLE_EQ(d.Value(), h.actual_); + } + + + d = d.Value() * 0.5; + } + } #undef TEST_DOUBLE } diff --git a/test/unittest/valuetest.cpp b/test/unittest/valuetest.cpp index 1922222..f14669a 100644 --- a/test/unittest/valuetest.cpp +++ b/test/unittest/valuetest.cpp @@ -957,6 +957,19 @@ TEST(Value, Object) { EXPECT_EQ(2u, o.MemberCount()); } +#if RAPIDJSON_HAS_STDSTRING + { + // AddMember(StringRefType, const std::string&, Allocator) + Value o(kObjectType); + o.AddMember("b", std::string("Banana"), allocator); + EXPECT_STREQ("Banana", o["b"].GetString()); + + // RemoveMember(const std::string&) + o.RemoveMember(std::string("b")); + EXPECT_TRUE(o.ObjectEmpty()); + } +#endif + #if RAPIDJSON_HAS_CXX11_RVALUE_REFS // AddMember(GenericValue&&, ...) variants { @@ -986,6 +999,10 @@ TEST(Value, Object) { EXPECT_TRUE(y.HasMember("A")); EXPECT_TRUE(y.HasMember("B")); +#if RAPIDJSON_HAS_STDSTRING + EXPECT_TRUE(x.HasMember(std::string("A"))); +#endif + name.SetString("C\0D"); EXPECT_TRUE(x.HasMember(name)); EXPECT_TRUE(y.HasMember(name)); @@ -1009,6 +1026,11 @@ TEST(Value, Object) { EXPECT_STREQ("Banana", y["B"].GetString()); EXPECT_STREQ("CherryD", y[C0D].GetString()); +#if RAPIDJSON_HAS_STDSTRING + EXPECT_STREQ("Apple", x["A"].GetString()); + EXPECT_STREQ("Apple", y[std::string("A")].GetString()); +#endif + // member iterator Value::MemberIterator itr = x.MemberBegin(); EXPECT_TRUE(itr != x.MemberEnd()); @@ -1160,6 +1182,24 @@ TEST(Value, Object) { EXPECT_TRUE(z.IsObject()); } +TEST(Value, EraseMember_String) { + Value::AllocatorType allocator; + Value x(kObjectType); + x.AddMember("A", "Apple", allocator); + x.AddMember("B", "Banana", allocator); + + EXPECT_TRUE(x.EraseMember("B")); + EXPECT_FALSE(x.HasMember("B")); + + EXPECT_FALSE(x.EraseMember("nonexist")); + + GenericValue, CrtAllocator> othername("A"); + EXPECT_TRUE(x.EraseMember(othername)); + EXPECT_FALSE(x.HasMember("A")); + + EXPECT_TRUE(x.MemberBegin() == x.MemberEnd()); +} + TEST(Value, BigNestedArray) { MemoryPoolAllocator<> allocator; Value x(kArrayType); diff --git a/travis-doxygen.sh b/travis-doxygen.sh index ad80536..1023108 100755 --- a/travis-doxygen.sh +++ b/travis-doxygen.sh @@ -78,6 +78,7 @@ gh_pages_prepare() gh_pages_commit() { cd "${TRAVIS_BUILD_DIR}/build/doc/html"; + echo "rapidjson.org" > CNAME git add --all; git diff-index --quiet HEAD || git commit -m "Automatic doxygen build"; }