From ddf0a0e83ce881b6e123deb4d943c0ec75e032cf Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Fri, 14 Jun 2019 22:03:11 +0900 Subject: [PATCH] Update rapidjson-amalgamation to include error/en.h and cursorstreamwrapper.h. Now schema validation is getting to work. --- README.md | 1 + loader_example.cc | 6 + rapidjson-amalgamation.h | 531 ++++++++++++++++++++++++++------------- tiny_gltf.h | 206 ++++++++++++++- 4 files changed, 557 insertions(+), 187 deletions(-) diff --git a/README.md b/README.md index e085c02..7ac0e2b 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ ## For developer Generate single rapidjson file using this node.js script: https://github.com/Tencent/rapidjson/issues/863 +Add `cursorstreamwrapper.h` and `error/en.h` inclusion in `rapidjson-all.h` before running merge script. ## TODOs diff --git a/loader_example.cc b/loader_example.cc index c0eec95..6bcff91 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -2,6 +2,7 @@ // TODO(syoyo): Print extensions and extras for each glTF object. // #define TINYGLTF_IMPLEMENTATION +#define TINYGLTF_ENABLE_SCHEMA_VALIDATOR #define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION #include "tiny_gltf.h" @@ -640,8 +641,13 @@ int main(int argc, char **argv) { } else { std::cout << "Reading ASCII glTF" << std::endl; // assume ascii glTF. +#if defined(TINYGLTF_ENABLE_SCHEMA_VALIDATOR) + ret = + gltf_ctx.LoadASCIIFromFileWithValidation(&model, &err, &warn, input_filename.c_str()); +#else ret = gltf_ctx.LoadASCIIFromFile(&model, &err, &warn, input_filename.c_str()); +#endif } if (!warn.empty()) { diff --git a/rapidjson-amalgamation.h b/rapidjson-amalgamation.h index 2578092..0d15082 100644 --- a/rapidjson-amalgamation.h +++ b/rapidjson-amalgamation.h @@ -1581,7 +1581,7 @@ RAPIDJSON_NAMESPACE_END // End file:allocators.h -// Begin file: document.h +// Begin file: error/en.h // 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. @@ -1596,13 +1596,240 @@ RAPIDJSON_NAMESPACE_END // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -#ifndef RAPIDJSON_DOCUMENT_H_ -#define RAPIDJSON_DOCUMENT_H_ - -/*! \file document.h */ +#ifndef RAPIDJSON_ERROR_EN_H_ +#define RAPIDJSON_ERROR_EN_H_ -// Begin file: reader.h +// Begin file: error.h +// 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_ERROR_ERROR_H_ +#define RAPIDJSON_ERROR_ERROR_H_ + + +// Begin file: ../rapidjson.h +// already included +// End file:../rapidjson.h + + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +/*! \file error.h */ + +/*! \defgroup RAPIDJSON_ERRORS RapidJSON error handling */ + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_CHARTYPE + +//! Character type of error messages. +/*! \ingroup RAPIDJSON_ERRORS + The default character type is \c char. + On Windows, user can define this macro as \c TCHAR for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_CHARTYPE +#define RAPIDJSON_ERROR_CHARTYPE char +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_STRING + +//! Macro for converting string literial to \ref RAPIDJSON_ERROR_CHARTYPE[]. +/*! \ingroup RAPIDJSON_ERRORS + By default this conversion macro does nothing. + On Windows, user can define this macro as \c _T(x) for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_STRING +#define RAPIDJSON_ERROR_STRING(x) x +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseErrorCode + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericReader::Parse, GenericReader::GetParseErrorCode +*/ +enum ParseErrorCode { + kParseErrorNone = 0, //!< No error. + + kParseErrorDocumentEmpty, //!< The document is empty. + kParseErrorDocumentRootNotSingular, //!< The document root must not follow by other values. + + kParseErrorValueInvalid, //!< Invalid value. + + kParseErrorObjectMissName, //!< Missing a name for object member. + kParseErrorObjectMissColon, //!< Missing a colon after a name of object member. + kParseErrorObjectMissCommaOrCurlyBracket, //!< Missing a comma or '}' after an object member. + + kParseErrorArrayMissCommaOrSquareBracket, //!< Missing a comma or ']' after an array element. + + kParseErrorStringUnicodeEscapeInvalidHex, //!< Incorrect hex digit after \\u escape in string. + kParseErrorStringUnicodeSurrogateInvalid, //!< The surrogate pair in string is invalid. + kParseErrorStringEscapeInvalid, //!< Invalid escape character in string. + kParseErrorStringMissQuotationMark, //!< Missing a closing quotation mark in string. + kParseErrorStringInvalidEncoding, //!< Invalid encoding in string. + + kParseErrorNumberTooBig, //!< Number too big to be stored in double. + kParseErrorNumberMissFraction, //!< Miss fraction part in number. + kParseErrorNumberMissExponent, //!< Miss exponent in number. + + kParseErrorTermination, //!< Parsing was terminated. + kParseErrorUnspecificSyntaxError //!< Unspecific syntax error. +}; + +//! Result of parsing (wraps ParseErrorCode) +/*! + \ingroup RAPIDJSON_ERRORS + \code + Document doc; + ParseResult ok = doc.Parse("[42]"); + if (!ok) { + fprintf(stderr, "JSON parse error: %s (%u)", + GetParseError_En(ok.Code()), ok.Offset()); + exit(EXIT_FAILURE); + } + \endcode + \see GenericReader::Parse, GenericDocument::Parse +*/ +struct ParseResult { + //!! Unspecified boolean type + typedef bool (ParseResult::*BooleanType)() const; +public: + //! Default constructor, no error. + ParseResult() : code_(kParseErrorNone), offset_(0) {} + //! Constructor to set an error. + ParseResult(ParseErrorCode code, size_t offset) : code_(code), offset_(offset) {} + + //! Get the error code. + ParseErrorCode Code() const { return code_; } + //! Get the error offset, if \ref IsError(), 0 otherwise. + size_t Offset() const { return offset_; } + + //! Explicit conversion to \c bool, returns \c true, iff !\ref IsError(). + operator BooleanType() const { return !IsError() ? &ParseResult::IsError : NULL; } + //! Whether the result is an error. + bool IsError() const { return code_ != kParseErrorNone; } + + bool operator==(const ParseResult& that) const { return code_ == that.code_; } + bool operator==(ParseErrorCode code) const { return code_ == code; } + friend bool operator==(ParseErrorCode code, const ParseResult & err) { return code == err.code_; } + + bool operator!=(const ParseResult& that) const { return !(*this == that); } + bool operator!=(ParseErrorCode code) const { return !(*this == code); } + friend bool operator!=(ParseErrorCode code, const ParseResult & err) { return err != code; } + + //! Reset error code. + void Clear() { Set(kParseErrorNone); } + //! Update error code and offset. + void Set(ParseErrorCode code, size_t offset = 0) { code_ = code; offset_ = offset; } + +private: + ParseErrorCode code_; + size_t offset_; +}; + +//! Function pointer type of GetParseError(). +/*! \ingroup RAPIDJSON_ERRORS + + This is the prototype for \c GetParseError_X(), where \c X is a locale. + User can dynamically change locale in runtime, e.g.: +\code + GetParseErrorFunc GetParseError = GetParseError_En; // or whatever + const RAPIDJSON_ERROR_CHARTYPE* s = GetParseError(document.GetParseErrorCode()); +\endcode +*/ +typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode); + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_ERROR_H_ + +// End file:error.h + + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(covered-switch-default) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Maps error code of parsing into error message. +/*! + \ingroup RAPIDJSON_ERRORS + \param parseErrorCode Error code obtained in parsing. + \return the error message. + \note User can make a copy of this function for localization. + Using switch-case is safer for future modification of error codes. +*/ +inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErrorCode) { + switch (parseErrorCode) { + case kParseErrorNone: return RAPIDJSON_ERROR_STRING("No error."); + + case kParseErrorDocumentEmpty: return RAPIDJSON_ERROR_STRING("The document is empty."); + case kParseErrorDocumentRootNotSingular: return RAPIDJSON_ERROR_STRING("The document root must not be followed by other values."); + + case kParseErrorValueInvalid: return RAPIDJSON_ERROR_STRING("Invalid value."); + + case kParseErrorObjectMissName: return RAPIDJSON_ERROR_STRING("Missing a name for object member."); + case kParseErrorObjectMissColon: return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member."); + case kParseErrorObjectMissCommaOrCurlyBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member."); + + case kParseErrorArrayMissCommaOrSquareBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element."); + + case kParseErrorStringUnicodeEscapeInvalidHex: return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string."); + case kParseErrorStringUnicodeSurrogateInvalid: return RAPIDJSON_ERROR_STRING("The surrogate pair in string is invalid."); + case kParseErrorStringEscapeInvalid: return RAPIDJSON_ERROR_STRING("Invalid escape character in string."); + case kParseErrorStringMissQuotationMark: return RAPIDJSON_ERROR_STRING("Missing a closing quotation mark in string."); + case kParseErrorStringInvalidEncoding: return RAPIDJSON_ERROR_STRING("Invalid encoding in string."); + + case kParseErrorNumberTooBig: return RAPIDJSON_ERROR_STRING("Number too big to be stored in double."); + case kParseErrorNumberMissFraction: return RAPIDJSON_ERROR_STRING("Miss fraction part in number."); + case kParseErrorNumberMissExponent: return RAPIDJSON_ERROR_STRING("Miss exponent in number."); + + case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error."); + case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error."); + + default: return RAPIDJSON_ERROR_STRING("Unknown error."); + } +} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_EN_H_ + +// End file:error/en.h + + +// Begin file: cursorstreamwrapper.h // 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. @@ -1617,15 +1844,8 @@ RAPIDJSON_NAMESPACE_END // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -#ifndef RAPIDJSON_READER_H_ -#define RAPIDJSON_READER_H_ - -/*! \file reader.h */ - - -// Begin file: allocators.h -// already included -// End file:allocators.h +#ifndef RAPIDJSON_CURSORSTREAMWRAPPER_H_ +#define RAPIDJSON_CURSORSTREAMWRAPPER_H_ // Begin file: stream.h @@ -2584,6 +2804,121 @@ RAPIDJSON_NAMESPACE_END // End file:stream.h +#if defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4702) // unreachable code +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + + +//! Cursor stream wrapper for counting line and column number if error exists. +/*! + \tparam InputStream Any stream that implements Stream Concept +*/ +template > +class CursorStreamWrapper : public GenericStreamWrapper { +public: + typedef typename Encoding::Ch Ch; + + CursorStreamWrapper(InputStream& is): + GenericStreamWrapper(is), line_(1), col_(0) {} + + // counting line and column number + Ch Take() { + Ch ch = this->is_.Take(); + if(ch == '\n') { + line_ ++; + col_ = 0; + } else { + col_ ++; + } + return ch; + } + + //! Get the error line number, if error exists. + size_t GetLine() const { return line_; } + //! Get the error column number, if error exists. + size_t GetColumn() const { return col_; } + +private: + size_t line_; //!< Current Line + size_t col_; //!< Current Column +}; + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_POP +#endif + +#if defined(__GNUC__) +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_CURSORSTREAMWRAPPER_H_ + +// End file:cursorstreamwrapper.h + + +// Begin file: document.h +// 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_DOCUMENT_H_ +#define RAPIDJSON_DOCUMENT_H_ + +/*! \file document.h */ + + +// Begin file: reader.h +// 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_READER_H_ +#define RAPIDJSON_READER_H_ + +/*! \file reader.h */ + + +// Begin file: allocators.h +// already included +// End file:allocators.h + + +// Begin file: stream.h +// already included +// End file:stream.h + + // Begin file: encodedstream.h // Tencent is pleased to support the open source community by making RapidJSON available. // @@ -4582,172 +4917,7 @@ RAPIDJSON_DIAG_OFF(effc++) // Begin file: error/error.h -// 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_ERROR_ERROR_H_ -#define RAPIDJSON_ERROR_ERROR_H_ - - -// Begin file: ../rapidjson.h // already included -// End file:../rapidjson.h - - -#ifdef __clang__ -RAPIDJSON_DIAG_PUSH -RAPIDJSON_DIAG_OFF(padded) -#endif - -/*! \file error.h */ - -/*! \defgroup RAPIDJSON_ERRORS RapidJSON error handling */ - -/////////////////////////////////////////////////////////////////////////////// -// RAPIDJSON_ERROR_CHARTYPE - -//! Character type of error messages. -/*! \ingroup RAPIDJSON_ERRORS - The default character type is \c char. - On Windows, user can define this macro as \c TCHAR for supporting both - unicode/non-unicode settings. -*/ -#ifndef RAPIDJSON_ERROR_CHARTYPE -#define RAPIDJSON_ERROR_CHARTYPE char -#endif - -/////////////////////////////////////////////////////////////////////////////// -// RAPIDJSON_ERROR_STRING - -//! Macro for converting string literial to \ref RAPIDJSON_ERROR_CHARTYPE[]. -/*! \ingroup RAPIDJSON_ERRORS - By default this conversion macro does nothing. - On Windows, user can define this macro as \c _T(x) for supporting both - unicode/non-unicode settings. -*/ -#ifndef RAPIDJSON_ERROR_STRING -#define RAPIDJSON_ERROR_STRING(x) x -#endif - -RAPIDJSON_NAMESPACE_BEGIN - -/////////////////////////////////////////////////////////////////////////////// -// ParseErrorCode - -//! Error code of parsing. -/*! \ingroup RAPIDJSON_ERRORS - \see GenericReader::Parse, GenericReader::GetParseErrorCode -*/ -enum ParseErrorCode { - kParseErrorNone = 0, //!< No error. - - kParseErrorDocumentEmpty, //!< The document is empty. - kParseErrorDocumentRootNotSingular, //!< The document root must not follow by other values. - - kParseErrorValueInvalid, //!< Invalid value. - - kParseErrorObjectMissName, //!< Missing a name for object member. - kParseErrorObjectMissColon, //!< Missing a colon after a name of object member. - kParseErrorObjectMissCommaOrCurlyBracket, //!< Missing a comma or '}' after an object member. - - kParseErrorArrayMissCommaOrSquareBracket, //!< Missing a comma or ']' after an array element. - - kParseErrorStringUnicodeEscapeInvalidHex, //!< Incorrect hex digit after \\u escape in string. - kParseErrorStringUnicodeSurrogateInvalid, //!< The surrogate pair in string is invalid. - kParseErrorStringEscapeInvalid, //!< Invalid escape character in string. - kParseErrorStringMissQuotationMark, //!< Missing a closing quotation mark in string. - kParseErrorStringInvalidEncoding, //!< Invalid encoding in string. - - kParseErrorNumberTooBig, //!< Number too big to be stored in double. - kParseErrorNumberMissFraction, //!< Miss fraction part in number. - kParseErrorNumberMissExponent, //!< Miss exponent in number. - - kParseErrorTermination, //!< Parsing was terminated. - kParseErrorUnspecificSyntaxError //!< Unspecific syntax error. -}; - -//! Result of parsing (wraps ParseErrorCode) -/*! - \ingroup RAPIDJSON_ERRORS - \code - Document doc; - ParseResult ok = doc.Parse("[42]"); - if (!ok) { - fprintf(stderr, "JSON parse error: %s (%u)", - GetParseError_En(ok.Code()), ok.Offset()); - exit(EXIT_FAILURE); - } - \endcode - \see GenericReader::Parse, GenericDocument::Parse -*/ -struct ParseResult { - //!! Unspecified boolean type - typedef bool (ParseResult::*BooleanType)() const; -public: - //! Default constructor, no error. - ParseResult() : code_(kParseErrorNone), offset_(0) {} - //! Constructor to set an error. - ParseResult(ParseErrorCode code, size_t offset) : code_(code), offset_(offset) {} - - //! Get the error code. - ParseErrorCode Code() const { return code_; } - //! Get the error offset, if \ref IsError(), 0 otherwise. - size_t Offset() const { return offset_; } - - //! Explicit conversion to \c bool, returns \c true, iff !\ref IsError(). - operator BooleanType() const { return !IsError() ? &ParseResult::IsError : NULL; } - //! Whether the result is an error. - bool IsError() const { return code_ != kParseErrorNone; } - - bool operator==(const ParseResult& that) const { return code_ == that.code_; } - bool operator==(ParseErrorCode code) const { return code_ == code; } - friend bool operator==(ParseErrorCode code, const ParseResult & err) { return code == err.code_; } - - bool operator!=(const ParseResult& that) const { return !(*this == that); } - bool operator!=(ParseErrorCode code) const { return !(*this == code); } - friend bool operator!=(ParseErrorCode code, const ParseResult & err) { return err != code; } - - //! Reset error code. - void Clear() { Set(kParseErrorNone); } - //! Update error code and offset. - void Set(ParseErrorCode code, size_t offset = 0) { code_ = code; offset_ = offset; } - -private: - ParseErrorCode code_; - size_t offset_; -}; - -//! Function pointer type of GetParseError(). -/*! \ingroup RAPIDJSON_ERRORS - - This is the prototype for \c GetParseError_X(), where \c X is a locale. - User can dynamically change locale in runtime, e.g.: -\code - GetParseErrorFunc GetParseError = GetParseError_En; // or whatever - const RAPIDJSON_ERROR_CHARTYPE* s = GetParseError(document.GetParseErrorCode()); -\endcode -*/ -typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode); - -RAPIDJSON_NAMESPACE_END - -#ifdef __clang__ -RAPIDJSON_DIAG_POP -#endif - -#endif // RAPIDJSON_ERROR_ERROR_H_ - // End file:error/error.h @@ -16733,3 +16903,4 @@ RAPIDJSON_DIAG_POP // Begin file: writer.h // already included // End file:writer.h + diff --git a/tiny_gltf.h b/tiny_gltf.h index 9c7a479..b702597 100644 --- a/tiny_gltf.h +++ b/tiny_gltf.h @@ -921,13 +921,13 @@ class TinyGLTF { #pragma clang diagnostic ignored "-Wc++98-compat" #endif - TinyGLTF() : bin_data_(nullptr), bin_size_(0), is_binary_(false) {} + TinyGLTF(); #ifdef __clang__ #pragma clang diagnostic pop #endif - ~TinyGLTF() {} + ~TinyGLTF(); /// /// Loads glTF ASCII asset from a file. @@ -1048,8 +1048,12 @@ class TinyGLTF { nullptr; #endif void *write_image_user_data_ = reinterpret_cast(&fs); + + class PImpl; + PImpl *pimpl_; }; + #ifdef __clang__ #pragma clang diagnostic pop // -Wpadded #endif @@ -1200,7 +1204,7 @@ namespace tinygltf { #if defined(TINYGLTF_ENABLE_SCHEMA_VALIDATOR) // Include glTF Schema as embedded C string -#include "glTF.schema.resolved.inc" +#include "gltf.schema.resolved.inc" #endif // Equals function for Value, for recursivity @@ -1617,6 +1621,55 @@ std::string base64_decode(std::string const &encoded_string) { #pragma clang diagnostic pop #endif +class TinyGLTF::PImpl +{ + public: + + PImpl() { +#if defined(TINYGLTF_ENABLE_SCHEMA_VALIDATOR) + validator_ = nullptr; +#endif + } + + ~PImpl() + { +#if defined(TINYGLTF_ENABLE_SCHEMA_VALIDATOR) + if (schema_doc_) { + delete schema_doc_; + } + + if (schema_) { + delete schema_; + } + + if (validator_) { + delete validator_; + } + +#endif + } + +#if defined(TINYGLTF_ENABLE_SCHEMA_VALIDATOR) + + std::string schema_json_string_; + rapidjson::Document *schema_doc_; + rapidjson::SchemaDocument *schema_; + rapidjson::SchemaValidator *validator_; +#endif +}; + + +TinyGLTF::TinyGLTF() : bin_data_(nullptr), bin_size_(0), is_binary_(false) { + pimpl_ = new TinyGLTF::PImpl(); +} + +TinyGLTF::~TinyGLTF() +{ + if (pimpl_) { + delete pimpl_; + } +} + static bool LoadExternalFile(std::vector *out, std::string *err, std::string *warn, const std::string &filename, const std::string &basedir, bool required, @@ -3722,13 +3775,25 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, return false; } + // To enable error report with lines and columns, + // We'll use CursorStreamWrapper. + // https://github.com/Tencent/rapidjson/pull/1070 + // It looks StringStream does not have a constrcutor with (str, len), + // so create a temporary string and ensure it ends with NULL character. + // although this is a bit redundant... + std::string in_str(str, length); + + rapidjson::StringStream sis(in_str.c_str()); + + rapidjson::CursorStreamWrapper csw(sis); + rapidjson::Document v; #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \ defined(_CPPUNWIND)) && \ !defined(TINYGLTF_NOEXCEPTION) try { - v.Parse(str); + v.ParseStream(csw); } catch (const std::exception &e) { if (err) { @@ -3738,14 +3803,49 @@ bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn, } if (v.HasParseError()) { - // TODO(syoyo): Report error + if (err) { + std::stringstream ss; + ss << "JSON parse error: (byte offset " << v.GetErrorOffset() + << ", line " << csw.GetLine() + << ", column " << csw.GetColumn() + << "): " << rapidjson::GetParseError_En(v.GetParseError()); + (*err) = ss.str(); + } return false; } #else { - v.Parse(str); + v.ParseStream(csw); if (v.HasParseError()) { - // TODO(syoyo): Report error + if (err) { + std::stringstream ss; + ss << "JSON parse error: (byte offset " << v.GetErrorOffset() + << ", line " << csw.GetLine() + << ", column " << csw.GetColumn() + << "): " << rapidjson::GetParseError_En(v.GetParseError()); + (*err) = ss.str(); + } + return false; + } + } +#endif + +#if defined(TINYGLTF_ENABLE_SCHEMA_VALIDATOR) + if (pimpl_->validator_) { + if (!v.Accept(*(pimpl_->validator_))) { + // Input JSON is invalid according to the schema + // Output diagnostic information + rapidjson::StringBuffer sb; + pimpl_->validator_->GetInvalidSchemaPointer().StringifyUriFragment(sb); + + std::stringstream ss; + printf("Invalid schema: %s\n", sb.GetString()); + ss << "glTF schema validation error. Invalid keyword: " << pimpl_->validator_->GetInvalidSchemaKeyword(); + sb.Clear(); + + if (err) { + (*err) = ss.str(); + } return false; } } @@ -4334,6 +4434,53 @@ bool TinyGLTF::LoadASCIIFromString(Model *model, std::string *err, check_sections); } +#if defined(TINYGLTF_ENABLE_SCHEMA_VALIDATOR) +bool TinyGLTF::LoadASCIIFromStringWithValidation(Model *model, std::string *err, + std::string *warn, const char *str, + unsigned int length, + const std::string &base_dir, + unsigned int check_sections) { + is_binary_ = false; + bin_data_ = nullptr; + bin_size_ = 0; + + if (!pimpl_->validator_) { + + pimpl_->schema_json_string_ = std::string(); + + // Construct glTF JSON schema string. + const size_t num_str = sizeof(kglTFSchemaStrings)/sizeof(kglTFSchemaStrings[0]); + for (size_t i = 0; i < num_str; i++) { + pimpl_->schema_json_string_ += std::string(kglTFSchemaStrings[i]); + } + + // Validate input glTF string + if (pimpl_->schema_doc_) { + delete pimpl_->schema_doc_; + } + pimpl_->schema_doc_ = new rapidjson::Document(); + pimpl_->schema_doc_->Parse(pimpl_->schema_json_string_.c_str(), pimpl_->schema_json_string_.size()); + + if (pimpl_->schema_doc_->HasParseError()) { + if(err) { + (*err) += "Internal error. Failed to parse glTF schema JSON."; + } + return false; + } + + if (pimpl_->schema_) { + delete pimpl_->schema_; + } + pimpl_->schema_ = new rapidjson::SchemaDocument(*pimpl_->schema_doc_); + + pimpl_->validator_ = new rapidjson::SchemaValidator(*pimpl_->schema_); + } + + return LoadFromString(model, err, warn, str, length, base_dir, + check_sections); +} +#endif + bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err, std::string *warn, const std::string &filename, unsigned int check_sections) { @@ -4377,6 +4524,51 @@ bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err, return ret; } +#if defined(TINYGLTF_ENABLE_SCHEMA_VALIDATOR) +bool TinyGLTF::LoadASCIIFromFileWithValidation(Model *model, std::string *err, + std::string *warn, const std::string &filename, + unsigned int check_sections) { + std::stringstream ss; + + if (fs.ReadWholeFile == nullptr) { + // Programmer error, assert() ? + ss << "Failed to read file: " << filename + << ": one or more FS callback not set" << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + std::vector data; + std::string fileerr; + bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data); + if (!fileread) { + ss << "Failed to read file: " << filename << ": " << fileerr << std::endl; + if (err) { + (*err) = ss.str(); + } + return false; + } + + size_t sz = data.size(); + if (sz == 0) { + if (err) { + (*err) = "Empty file."; + } + return false; + } + + std::string basedir = GetBaseDir(filename); + + bool ret = LoadASCIIFromStringWithValidation( + model, err, warn, reinterpret_cast(&data.at(0)), + static_cast(data.size()), basedir, check_sections); + + return ret; +} +#endif + bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err, std::string *warn, const unsigned char *bytes,