diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..620b9fc --- /dev/null +++ b/.clang-format @@ -0,0 +1,60 @@ +--- +BasedOnStyle: Google +--- +Language: Cpp + +BreakBeforeBraces: Attach +PointerAlignment: Right +IndentWidth: 2 +Standard: Cpp11 +TabWidth: 2 +UseTab: ForIndentation + +AccessModifierOffset: 0 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: DontAlign +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: BeforeColon +BreakStringLiterals: true +ColumnLimit: 160 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +FixNamespaceComments: true +IndentCaseLabels: true +IndentWrappedFunctionNames: true +KeepEmptyLinesAtTheStartOfBlocks: false +NamespaceIndentation: None +ReflowComments: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9389ed0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,74 @@ +name: ci +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +defaults: + run: + shell: bash + +jobs: + build: + name: ${{ matrix.platform.name }}-${{ matrix.config.name }}-${{ matrix.type.name }} + runs-on: ${{ matrix.platform.os }} + strategy: + fail-fast: false + matrix: + platform: + - { name: Windows VS2019 x86, os: windows-2019, flags: -GNinja } + - { name: Windows VS2019 x64, os: windows-2019, flags: -GNinja } + - { name: Windows VS2022 x86, os: windows-2022, flags: -GNinja } + - { name: Windows VS2022 x64, os: windows-2022, flags: -GNinja } + - { name: Windows LLVM/Clang, os: windows-2022, flags: -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -GNinja } + - { name: Windows MinGW, os: windows-2022, flags: -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_COMPILER=gcc -GNinja } + - { name: Linux GCC, os: ubuntu-22.04, flags: -GNinja } + - { name: Linux Clang, os: ubuntu-22.04, flags: -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -GNinja } + config: + - { name: Shared, flags: -DBUILD_SHARED_LIBS=TRUE } + - { name: Static, flags: -DBUILD_SHARED_LIBS=FALSE } + type: + - { name: Release } + - { name: Debug, flags: -DCMAKE_BUILD_TYPE=Debug } + + include: + - platform: { name: Android, os: ubuntu-22.04 } + config: { name: x86 (API 33), flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=33 -DCMAKE_ANDROID_NDK=$ANDROID_SDK_ROOT/ndk/26.1.10909125 -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared, arch: x86, api: 33 } + type: { name: Release } + - platform: { name: Android, os: ubuntu-22.04 } + config: { name: x86_64 (API 33), flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=x86_64 -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=33 -DCMAKE_ANDROID_NDK=$ANDROID_SDK_ROOT/ndk/26.1.10909125 -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared, arch: x86_64, api: 33 } + type: { name: Release } + - platform: { name: Android, os: ubuntu-22.04 } + config: { name: armeabi-v7a (API 33), flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=33 -DCMAKE_ANDROID_NDK=$ANDROID_SDK_ROOT/ndk/26.1.10909125 -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared, arch: armeabi-v7a, api: 33 } + type: { name: Debug, flags: -DCMAKE_BUILD_TYPE=Debug } + - platform: { name: Android, os: ubuntu-22.04 } + config: { name: arm64-v8a (API 33), flags: -GNinja -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=33 -DCMAKE_ANDROID_NDK=$ANDROID_SDK_ROOT/ndk/26.1.10909125 -DBUILD_SHARED_LIBS=TRUE -DCMAKE_ANDROID_STL_TYPE=c++_shared, arch: arm64-v8a, api: 33 } + type: { name: Debug, flags: -DCMAKE_BUILD_TYPE=Debug } + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set VS Arch + if: contains(matrix.platform.name, 'Windows VS') && !contains(matrix.platform.name, 'MSBuild') + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ contains(matrix.platform.name, 'x86') && 'x86' || 'x64' }} + + - name: Get CMake and Ninja + uses: lukka/get-cmake@latest + with: + cmakeVersion: ${{ runner.os == 'Windows' && '3.27' || '3.27' }} + ninjaVersion: latest + + - name: Install Android Components + if: matrix.platform.name == 'Android' + run: | + echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "build-tools;33.0.2" + echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "ndk;26.1.10909125" + + - name: build + run: | + mkdir build && cd build + cmake ${{ matrix.config.flags }} ${{ matrix.platform.flags }} .. + cmake --build . --config ${{ matrix.type.name }} diff --git a/.gitignore b/.gitignore index 3a0f98c..1b83f54 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ meta/documentation/generated/ *.xcworkspace/ xcuserdata/ -build +build* *.pbxuser *.perspective diff --git a/CMakeLists.txt b/CMakeLists.txt index b4b687a..a4c5a55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.7.2...3.14) +cmake_minimum_required(VERSION 3.7.2...3.17) project(JsonBox) @@ -35,17 +35,16 @@ set(JSONBOX_HEADERS # set options for android if(CMAKE_SYSTEM_NAME MATCHES "Android") - message("-- Compiling for Android") - set(BUILD_SHARED_LIBS TRUE) + message("-- Compiling for Android") if (CMAKE_ANDROID_API LESS 14) message(FATAL_ERROR "Android API level (${CMAKE_ANDROID_API}) must be equal or greater than 14.") endif() if(CMAKE_VS_PLATFORM_NAME STREQUAL "Tegra-Android") message(WARNING "CMake might not properly support setting the STL. Make sure to adjust all generated library projects!") endif() - set(ANDROID_NDK "$ENV{ANDROID_NDK}") - set(CMAKE_INSTALL_PREFIX ${CMAKE_ANDROID_NDK}/sources/third_party/jsonbox) - set(DEFAULT_INSTALL_MISC_DIR ${CMAKE_ANDROID_NDK}/sources/third_party/jsonbox) + set(ANDROID_NDK "$ENV{ANDROID_NDK}") + set(CMAKE_INSTALL_PREFIX ${CMAKE_ANDROID_NDK}/sources/third_party/JsonBox) + set(DEFAULT_INSTALL_MISC_DIR ${CMAKE_ANDROID_NDK}/sources/third_party/JsonBox) set(LIB_SUFFIX "/${CMAKE_ANDROID_ARCH_ABI}") if(CMAKE_ANDROID_STL_TYPE MATCHES "_shared") add_definitions("-DSTL_LIBRARY=${CMAKE_ANDROID_STL_TYPE}") diff --git a/cmake/Modules/FindJSONBOX.cmake b/cmake/Modules/FindJSONBOX.cmake deleted file mode 100644 index edd5782..0000000 --- a/cmake/Modules/FindJSONBOX.cmake +++ /dev/null @@ -1,47 +0,0 @@ -# -# Try to find JsonBox library and include path. -# Once done this will define -# -# JSONBOX_FOUND -# JSONBOX_INCLUDE_DIR -# JSONBOX_LIBRARIES -# JSONBOX_ROOT -# - -set(FIND_JSONBOX_PATHS - ${JSONBOX_ROOT} - $ENV{JSONBOX_ROOT} - /usr/local - /usr - /sw - /opt/local - /opt/csw - /opt) - -if(JSONBOX_ROOT) - SET(JSONBOX_INCLUDE_DIR "${JSONBOX_ROOT}/include") - SET(JSONBOX_LIBRARIES "${JSONBOX_ROOT}/lib/libJsonBox.a") - SET(JSONBOX_FOUND 1) -endif(JSONBOX_ROOT) - -find_path(JSONBOX_INCLUDE_DIR JsonBox/include/JsonBox.h - PATH_SUFFIXES include - PATHS ${FIND_JSONBOX_PATHS}) - -find_library(JSONBOX_LIBRARY - NAMES JsonBox - PATH_SUFFIXES lib - PATHS ${FIND_JSONBOX_PATHS}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(JSONBOX DEFAULT_MSG JSONBOX_LIBRARY JSONBOX_INCLUDE_DIR) - -set(JSONBOX_INCLUDE_DIR "${JSONBOX_INCLUDE_DIR}") -set(JSONBOX_LIBRARIES "${JSONBOX_LIBRARY}") - -if(NOT JSONBOX_FOUND) - set(FIND_JSONBOX_ERROR "Could NOT find JSONBOX") - if(JSONBOX_FIND_REQUIRED) - message(FATAL_ERROR ${FIND_JSONBOX_ERROR}) - endif() -endif() diff --git a/cmake/Modules/FindJsonBox.cmake b/cmake/Modules/FindJsonBox.cmake new file mode 100644 index 0000000..e1c8f88 --- /dev/null +++ b/cmake/Modules/FindJsonBox.cmake @@ -0,0 +1,66 @@ +# +# Try to find JsonBox library and include path. +# Once done this will define +# +# JsonBox_FOUND +# JsonBox_INCLUDE_DIR +# JsonBox_LIBRARIES +# JsonBox_LIBRARY +# JsonBox_ROOT +# + +set(FIND_JsonBox_PATHS + ${JsonBox_ROOT} + $ENV{JsonBox_ROOT} + /usr/local + /usr/local/include + /usr + /sw + /opt/local + /opt/csw + /opt) + +if(JsonBox_ROOT) + SET(LIB_SUFFIX "/") + if(CMAKE_SYSTEM_NAME MATCHES "Android") + SET(LIB_SUFFIX "/${CMAKE_ANDROID_ARCH_ABI}") + endif(CMAKE_SYSTEM_NAME MATCHES "Android") + SET(JsonBox_INCLUDE_DIR "${JsonBox_ROOT}/include") + if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + if(CMAKE_BUILD_TYPE MATCHES "Debug") + SET(JsonBox_LIBRARY "${JsonBox_ROOT}/lib${LIB_SUFFIX}/JsonBox_d.lib") + else() + SET(JsonBox_LIBRARY "${JsonBox_ROOT}/lib${LIB_SUFFIX}/JsonBox.lib") + endif() + else() + if(CMAKE_BUILD_TYPE MATCHES "Debug") + SET(JsonBox_LIBRARY "${JsonBox_ROOT}/lib${LIB_SUFFIX}/libJsonBox_d.a") + else() + SET(JsonBox_LIBRARY "${JsonBox_ROOT}/lib${LIB_SUFFIX}/libJsonBox.a") + endif() + endif() + SET(JsonBox_LIBRARIES ${JsonBox_LIBRARY}) + SET(JsonBox_FOUND 1) +endif(JsonBox_ROOT) + +find_path(JsonBox_INCLUDE_DIR include/JsonBox.h + PATH_SUFFIXES include + PATHS ${FIND_JsonBox_PATHS}) + +find_library(JsonBox_LIBRARY + NAMES JsonBox + PATH_SUFFIXES lib + PATHS ${FIND_JsonBox_PATHS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(JsonBox DEFAULT_MSG JsonBox_LIBRARY JsonBox_INCLUDE_DIR) + +set(JsonBox_INCLUDE_DIR "${JsonBox_INCLUDE_DIR}") +set(JsonBox_LIBRARIES "${JsonBox_LIBRARY}") + +if(NOT JsonBox_FOUND) + set(FIND_JsonBox_ERROR "Could NOT find JsonBox") + if(JsonBox_FIND_REQUIRED) + message(FATAL_ERROR ${FIND_JsonBox_ERROR}) + endif() +endif() diff --git a/readme.markdown b/readme.markdown index 528f456..d3a6886 100644 --- a/readme.markdown +++ b/readme.markdown @@ -1,6 +1,8 @@ # Json Box +[![ci](https://github.com/cristianglezm/JsonBox/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/cristianglezm/JsonBox/actions/workflows/ci.yml) + [JSON](http://json.org/) (JavaScript Object Notation) is a lightweight data-interchange format. Json Box is a C++ library used to read and write JSON with ease and speed. @@ -28,10 +30,11 @@ Android mkdir build cd build && mkdir armeabi-v7a cd armeabi-v7a -cmake -DCMAKE_SYSTEM_NAME=Android -DCMAKE_ANDROID_NDK= \ +export ANDROID_NDK= +cmake -DCMAKE_SYSTEM_NAME=Android -DCMAKE_ANDROID_NDK=$ANDROID_NDK \ -DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a -DCMAKE_ANDROID_STL_TYPE=c++_static \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=clang \ - -DCMAKE_INSTALL_PREFIX=/sources/third_party/JsonBox ../.. + -DCMAKE_INSTALL_PREFIX=$ANDROID_NDK/sources/third_party/JsonBox ../.. make install ``` diff --git a/src/Android.mk b/src/Android.mk index e45993f..27e7d60 100644 --- a/src/Android.mk +++ b/src/Android.mk @@ -1,5 +1,18 @@ LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := JsonBox_d +LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libJsonBox_d.so +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include + +prebuilt_path := $(call local-prebuilt-path,$(LOCAL_SRC_FILES)) +prebuilt := $(strip $(wildcard $(prebuilt_path))) + +ifdef prebuilt + include $(PREBUILT_SHARED_LIBRARY) +endif + include $(CLEAR_VARS) LOCAL_MODULE := JsonBox LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libJsonBox.so @@ -23,4 +36,17 @@ prebuilt := $(strip $(wildcard $(prebuilt_path))) ifdef prebuilt include $(PREBUILT_STATIC_LIBRARY) -endif \ No newline at end of file +endif + +include $(CLEAR_VARS) + +LOCAL_MODULE := JsonBox_d-static +LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libJsonBox_d.a +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include + +prebuilt_path := $(call local-prebuilt-path,$(LOCAL_SRC_FILES)) +prebuilt := $(strip $(wildcard $(prebuilt_path))) + +ifdef prebuilt + include $(PREBUILT_STATIC_LIBRARY) +endif diff --git a/src/Value.cpp b/src/Value.cpp index 063e752..cdf612f 100644 --- a/src/Value.cpp +++ b/src/Value.cpp @@ -602,7 +602,7 @@ namespace JsonBox { } bool Value::tryGetBoolean(bool defaultValue) const { - return (type == BOOLEAN) ? (*data.boolValue) : (EMPTY_BOOL); + return (type == BOOLEAN) ? (*data.boolValue) : (defaultValue); } void Value::setBoolean(bool newBoolean) { @@ -846,17 +846,114 @@ namespace JsonBox { whiteSpace == Whitespace::HORIZONTAL_TAB || whiteSpace == Whitespace::NEW_LINE || whiteSpace == Whitespace::CARRIAGE_RETURN; - } + } + + static std::string codePointToUTF8(unsigned int cp) + { + std::string result; + + // based on description from http://en.wikipedia.org/wiki/UTF-8 + + if (cp <= 0x7f) + { + result.resize(1); + result[0] = static_cast(cp); + } + else if (cp <= 0x7FF) + { + result.resize(2); + result[1] = static_cast(0x80 | (0x3f & cp)); + result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); + } + else if (cp <= 0xFFFF) + { + result.resize(3); + result[2] = static_cast(0x80 | (0x3f & cp)); + result[1] = 0x80 | static_cast((0x3f & (cp >> 6))); + result[0] = 0xE0 | static_cast((0xf & (cp >> 12))); + } + else if (cp <= 0x10FFFF) + { + result.resize(4); + result[3] = static_cast(0x80 | (0x3f & cp)); + result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); + result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); + } + + return result; + } + + static bool decodeUnicodeEscapeSequence( std::istream &input, unsigned int &unicode ) + { + int tmpCounter = 0; + unicode = 0; + while (tmpCounter < 4 && !input.eof()) + { + char c; + input.get(c); + unicode *= 16; + if ( c >= '0' && c <= '9' ) + unicode += c - '0'; + else if ( c >= 'a' && c <= 'f' ) + unicode += c - 'a' + 10; + else if ( c >= 'A' && c <= 'F' ) + unicode += c - 'A' + 10; + else + { + std::cout << "JsonBox::error: Bad unicode escape sequence in string: hexadecimal digit expected."; + return false; + } + ++tmpCounter; + } + if(tmpCounter < 4) + { + std::cout << "JsonBox::error: Bad unicode escape sequence in string."; + return false; + } + return true; + } + + static bool decodeUnicodeCodePoint( std::istream &input, unsigned int &unicode ) + { + if ( !decodeUnicodeEscapeSequence( input, unicode ) ) + return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) + { + // surrogate pairs + unsigned int surrogatePair; + char c1 = '\0', c2 = '\0'; + bool goodStart = !input.eof() && input.get(c1) && !input.eof() && input.get(c2); + if(!goodStart) + { + std::cout << "JsonBox::error: expecting another \\u token to begin the second half of a unicode surrogate pair"; + return false; + } + + if (c1 == '\\' && c2 == 'u' && !input.eof()) + { + if (decodeUnicodeEscapeSequence( input, surrogatePair )) + { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } + else + return false; + } + else + { + std::cout << "JsonBox::error: expecting another \\u token to begin the second half of a unicode surrogate pair"; + return false; + } + } + return true; + } void Value::readString(std::istream &input, std::string &result) { - bool noErrors = true, noUnicodeError = true; - char currentCharacter, tmpCharacter; - std::stringstream constructing; - std::string tmpStr(4, ' '); - std::stringstream tmpSs; - int32_t tmpInt; - String32 tmpStr32; - unsigned int tmpCounter; + bool noErrors = true; + char currentCharacter, tmpCharacter; + std::stringstream constructing; + std::string tmpStr(4, ' '); + std::stringstream tmpSs; // As long as there aren't any errors and that we haven't reached the // end of the input stream. @@ -905,39 +1002,15 @@ namespace JsonBox { constructing << Strings::Std::TAB; break; - case Strings::Json::Escape::BEGIN_UNICODE: - // TODO: Check for utf16 surrogate pairs. - tmpCounter = 0; - tmpStr.clear(); - tmpStr = " "; - noUnicodeError = true; - - while (tmpCounter < 4 && !input.eof()) { - input.get(tmpCharacter); - - if (isHexDigit(tmpCharacter)) { - tmpStr[tmpCounter] = tmpCharacter; - - } else { - // Invalid \u character, skipping it. - noUnicodeError = false; - } - - ++tmpCounter; - } - - if (noUnicodeError) { - tmpSs.clear(); - tmpSs.str(""); - tmpSs << std::hex << tmpStr; - tmpSs >> tmpInt; - tmpStr32.clear(); - tmpStr32.push_back(tmpInt); - tmpStr = Convert::encodeToUTF8(tmpStr32); - constructing << tmpStr; - } - - break; + case Strings::Json::Escape::BEGIN_UNICODE: + { + unsigned int unicode; + if ( !decodeUnicodeCodePoint( input, unicode ) ) + return; + constructing << codePointToUTF8(unicode); + } + + break; default: break;