diff --git a/toonz/sources/common/tvariant.cpp b/toonz/sources/common/tvariant.cpp index 9d26d86..40524b8 100644 --- a/toonz/sources/common/tvariant.cpp +++ b/toonz/sources/common/tvariant.cpp @@ -1,7 +1,11 @@ - #include +#include +#include +#include + + //--------------------------------------------------------- int @@ -220,3 +224,311 @@ TVariant::getMemSize() const { s += sizeof(*i) - sizeof(*this) + i->second.getMemSize(); return s; } + +//--------------------------------------------------------- + +void +TVariant::toStream(std::ostream &stream, bool pretty, int level) const { + struct Writer { + const TVariant &data; + std::ostream &stream; + bool pretty; + int level; + + Writer(const TVariant &data, std::ostream &stream, bool pretty, int level): + data(data), stream(stream), pretty(pretty), level(level) { } + + void writeNewLine() + { if (pretty) stream << std::endl; } + void writeSpace() + { if (pretty) stream << " "; } + void writeTab(int level) + { if (pretty) for(int i = 2*level; i; --i) stream << " "; } + void writeChar(char c) + { stream.put(c); } + void writeWord(const char *word) + { stream << word; } + + void writeString(const std::string &str) { + writeWord("\""); + for(const char *c = str.c_str(); *c; ++c) { + switch (*c) { + case '\"': writeWord("\\\""); break; + case '\\': writeWord("\\\\"); break; + case '\b': writeWord("\\b"); break; + case '\f': writeWord("\\f"); break; + case '\n': writeWord("\\n"); break; + case '\r': writeWord("\\r"); break; + case '\t': writeWord("\\t"); break; + default: writeChar(*c); break; + } + } + writeWord("\""); + } + + void writeDouble(double x) { + char buf[256]; + snprintf(buf, sizeof(buf), "%.12lg", x); + stream << buf; + } + + void writeList(const TVariantList &list) { + writeWord("["); + if (!list.empty()) { + writeNewLine(); + TVariantList::const_iterator i = list.begin(); + while(true) { + writeTab(level + 1); + i->toStream(stream, pretty, level + 1); + if (++i == list.end()) { writeNewLine(); break; } + writeWord(","); + writeNewLine(); + } + writeTab(level); + } else writeSpace(); + writeWord("]"); + } + + void writeMap(const TVariantMap &map) { + writeWord("{"); + if (!map.empty()) { + writeNewLine(); + TVariantMap::const_iterator i = map.begin(); + while(true) { + writeTab(level + 1); + writeString(i->first.str()); + writeWord(":"); + writeSpace(); + i->second.toStream(stream, pretty, level + 1); + if (++i == map.end()) { writeNewLine(); break; } + writeWord(","); + writeNewLine(); + } + writeTab(level); + } else writeSpace(); + writeWord("}"); + } + + void write() { + switch(data.getType()) { + case Bool: writeWord(data.getBool() ? "true" : "false"); break; + case Double: writeDouble(data.getDouble()); break; + case String: writeString(data.getString()); break; + case List: writeList(data.getList()); break; + case Map: writeMap(data.getMap()); break; + case None: + default: writeWord("null"); break; + } + if (!stream) throw TException("write to stream failed"); + } + }; + + Writer(*this, stream, pretty, level).write(); +} + +//--------------------------------------------------------- + +void +TVariant::fromStream(std::istream &stream, int *currentRow, int *currentCol) { + struct Reader { + TVariant &data; + std::istream &stream; + int &row; + int &col; + Reader(TVariant &data, std::istream &stream, int &row, int &col): + data(data), stream(stream), row(row), col(col) { } + + void warning(const std::string &msg) + { std::cerr << "TVariant load:" << row << ":" << col << ": " << msg << std::endl; } + void error(const std::string &msg) + { throw TVariantSyntaxException(row, col, msg); } + void error() + { error("cannot recognize type of data"); } + + int peek() + { return stream.peek(); } + + int get() { + int c = stream.get(); + if (c == '\n') ++row, col = 1; else ++col; + if (!stream) error("unexpected end of file"); + return c; + } + + void skipSpaces() + { while(isspace(peek())) get(); } + + bool readWord(const char *word) { + if (peek() != *word) return false; + while(*word) + if (get() == *word) ++word; else error(); + return true; + } + + bool readNull() { + if (readWord("null") || peek() == EOF) + { data.reset(); return true; } + return false; + } + + bool readBool() { + if (readWord("true")) { data.setBool(true); return true; } + if (readWord("false")) { data.setBool(false); return true; } + return false; + } + + bool isdouble(int c) + { return isdigit(c) || c == '-' || c == '+' || c == '.'; } + + bool readDouble() { + if (!isdouble(peek())) return false; + std::string str; str.reserve(20); + while(isdouble(peek()) || isalpha(peek())) str.push_back((char)get()); + double d = 0.0; + try { d = std::stod(str); } + catch (const std::exception &e) { warning("wrong number: " + str); } + data.setDouble(d); + return true; + } + + void readHexUnicode(std::string &str) { + // read utf16 code + // JSON standard requires exact four hex digits + // but we're kind and allows 0-4 hex digits + int code = 0; + for(int i = 0; i < 4; ++i) { + char c = peek(); + if (c >= '0' && c <= '9') + code = 16*code + get() - '0'; + else if (c >= 'a' && c <= 'f') + code = 16*code + get() - 'a'; + else if (c >= 'A' && c <= 'F') + code = 16*code + get() - 'A'; + else break; + } + if (code == 0) + { warning("\\u token with zero code"); return; } + + // 16 bits of utf16 character be encoded up to three utf8 bytes + // in the following format: + // 11000xxx 10xxxxxx 0xxxxxxx + if (code >= 1 << 13) { // 11000xxx + str.push_back((char)(192 | (code >> 13))); + code &= (1 << 13) - 1; + } + if (code >= 1 << 6) { // 10xxxxxx + str.push_back((char)(128 | (code >> 6))); + code &= (1 << 6) - 1; + } + str.push_back((char)code); // 0xxxxxxx + } + + void readString(std::string &str) { + if (get() != '\"') error("expected quote"); + while(true) { + int c = get(); + if (c == '\"') break; + else + if (c == '\\') { + switch(int cc = get()) { + case '\"': str.push_back('\"'); break; + case '\\': str.push_back('\\'); break; + case '/': str.push_back( '/'); break; + case 'b': str.push_back('\b'); break; + case 'f': str.push_back('\f'); break; + case 'n': str.push_back('\n'); break; + case 'r': str.push_back('\r'); break; + case 't': str.push_back('\t'); break; + case 'u': readHexUnicode(str); break; + default: str.push_back((char)cc); break; + } + } else + str.push_back((char)c); + } + } + + bool readString() { + if (peek() != '\"') return false; + std::string str; + readString(str); + data.setString(str); + return true; + } + + bool readList() { + if (peek() != '[') return false; + get(); // skip bracket + data.reset(); + data.setType(List); + while(true) { + skipSpaces(); + if (peek() == ']') { get(); break; } + if (data.size() && get() != ',') error("expected comma or close bracket"); + skipSpaces(); + if (peek() == ']') { get(); break; } // to allow comma at the end + data[data.size()].fromStream(stream, &row, &col); + } + return true; + } + + bool readMap() { + if (peek() != '{') return false; + get(); // skip brace + data.reset(); + data.setType(Map); + while(true) { + skipSpaces(); + if (peek() == '}') { get(); break; } + if (data.size() && get() != ',') error("expected comma or close brace"); + skipSpaces(); + if (peek() == '}') { get(); break; } // to allow comma at the end + std::string key; + readString(key); + skipSpaces(); + if (get() != ':') error("expected colon"); + if (data.contains(key)) warning("duplicate key: " + key); + data[key].fromStream(stream, &row, &col); + } + return true; + } + + void read() { + skipSpaces(); + if ( !readNull() + && !readBool() + && !readDouble() + && !readString() + && !readList() + && !readMap() ) + error(); + } + }; + + reset(); + int row = 1, col = 1; + Reader( + *this, + stream, + (currentRow ? *currentRow : row), + (currentCol ? *currentCol : col) ).read(); +} + +//--------------------------------------------------------- + +std::string +TVariant::toString(bool pretty, int level) const { + std::stringstream stream(std::ios_base::out); + toStream(stream, pretty, level); + return stream.str(); +} + +//--------------------------------------------------------- + +void +TVariant::fromString(const std::string &str, int *currentRow, int *currentCol) { + std::stringstream stream(str, std::ios_base::in); + fromStream(stream, currentRow, currentCol); +} + +//--------------------------------------------------------- + diff --git a/toonz/sources/include/tvariant.h b/toonz/sources/include/tvariant.h index 2ee2165..5c12682 100644 --- a/toonz/sources/include/tvariant.h +++ b/toonz/sources/include/tvariant.h @@ -4,8 +4,10 @@ #define TVARIANT_INCLUDED #include +#include #include +#include #include #include #include @@ -28,6 +30,18 @@ typedef std::map TVariantMap; //------------------------------------------------------------------- +class TVariantSyntaxException final: public TException { +public: + explicit TVariantSyntaxException( + int row = 0, + int col = 0, + const std::string &msg = std::string() + ): + TException(std::to_string(row) + ":" + std::to_string(col) + ": " + msg) { } +}; + +//------------------------------------------------------------------- + class DVAPI TVariantPathEntry { private: int m_index; @@ -287,7 +301,7 @@ public: inline const TVariant& operator[] (const std::string &field) const { return (*this)[TStringId::find(field)]; } inline TVariant& operator[] (const std::string &field) - { return (*this)[TStringId::find(field)]; } + { return (*this)[TStringId(field)]; } inline void remove(const std::string &field) { remove(TStringId::find(field)); } @@ -363,7 +377,11 @@ public: size_t getMemSize() const; // serialization - // TODO: + void toStream(std::ostream &stream, bool pretty = false, int level = 0) const; + void fromStream(std::istream &stream, int *currentRow = 0, int *currentCol = 0); + + std::string toString(bool pretty = false, int level = 0) const; + void fromString(const std::string &str, int *currentRow = 0, int *currentCol = 0); }; #endif