// Copyright Epic Games, Inc. All Rights Reserved. #include "zencore/compactbinary.h" #include "zencore/compactbinarybuilder.h" #include "zencore/compactbinaryvalue.h" #include #include #include #include #include #include #include #include namespace zen { ////////////////////////////////////////////////////////////////////////// class CbYamlWriter { public: explicit CbYamlWriter(StringBuilderBase& InBuilder) : m_Builder(InBuilder) {} void WriteField(CbFieldView Field) { CbValue Accessor = Field.GetValue(); CbFieldType Type = Accessor.GetType(); switch (Type) { case CbFieldType::Object: case CbFieldType::UniformObject: WriteMapEntries(Field, 0); break; case CbFieldType::Array: case CbFieldType::UniformArray: WriteSeqEntries(Field, 0); break; default: WriteScalarValue(Field); m_Builder << '\n'; break; } } void WriteMapEntry(CbFieldView Field, int32_t Indent) { WriteIndent(Indent); WriteMapEntryContent(Field, Indent); } void WriteSeqEntry(CbFieldView Field, int32_t Indent) { CbValue Accessor = Field.GetValue(); CbFieldType Type = Accessor.GetType(); if (Type == CbFieldType::Object || Type == CbFieldType::UniformObject) { bool First = true; for (CbFieldView MapChild : Field) { if (First) { WriteIndent(Indent); m_Builder << "- "; First = false; } else { WriteIndent(Indent + 1); } WriteMapEntryContent(MapChild, Indent + 1); } } else if (Type == CbFieldType::Array || Type == CbFieldType::UniformArray) { WriteIndent(Indent); m_Builder << "-\n"; WriteSeqEntries(Field, Indent + 1); } else { WriteIndent(Indent); m_Builder << "- "; WriteScalarValue(Field); m_Builder << '\n'; } } private: void WriteMapEntries(CbFieldView MapField, int32_t Indent) { for (CbFieldView Child : MapField) { WriteIndent(Indent); WriteMapEntryContent(Child, Indent); } } void WriteMapEntryContent(CbFieldView Field, int32_t Indent) { std::u8string_view Name = Field.GetU8Name(); m_Builder << std::string_view(reinterpret_cast(Name.data()), Name.size()); CbValue Accessor = Field.GetValue(); CbFieldType Type = Accessor.GetType(); if (IsContainer(Type)) { m_Builder << ":\n"; WriteFieldValue(Field, Indent + 1); } else { m_Builder << ": "; WriteScalarValue(Field); m_Builder << '\n'; } } void WriteSeqEntries(CbFieldView SeqField, int32_t Indent) { for (CbFieldView Child : SeqField) { CbValue Accessor = Child.GetValue(); CbFieldType Type = Accessor.GetType(); if (Type == CbFieldType::Object || Type == CbFieldType::UniformObject) { bool First = true; for (CbFieldView MapChild : Child) { if (First) { WriteIndent(Indent); m_Builder << "- "; First = false; } else { WriteIndent(Indent + 1); } WriteMapEntryContent(MapChild, Indent + 1); } } else if (Type == CbFieldType::Array || Type == CbFieldType::UniformArray) { WriteIndent(Indent); m_Builder << "-\n"; WriteSeqEntries(Child, Indent + 1); } else { WriteIndent(Indent); m_Builder << "- "; WriteScalarValue(Child); m_Builder << '\n'; } } } void WriteFieldValue(CbFieldView Field, int32_t Indent) { CbValue Accessor = Field.GetValue(); CbFieldType Type = Accessor.GetType(); switch (Type) { case CbFieldType::Object: case CbFieldType::UniformObject: WriteMapEntries(Field, Indent); break; case CbFieldType::Array: case CbFieldType::UniformArray: WriteSeqEntries(Field, Indent); break; case CbFieldType::CustomById: WriteCustomById(Field.GetValue().AsCustomById(), Indent); break; case CbFieldType::CustomByName: WriteCustomByName(Field.GetValue().AsCustomByName(), Indent); break; default: WriteScalarValue(Field); m_Builder << '\n'; break; } } void WriteScalarValue(CbFieldView Field) { CbValue Accessor = Field.GetValue(); switch (Accessor.GetType()) { case CbFieldType::Null: m_Builder << "null"; break; case CbFieldType::BoolFalse: m_Builder << "false"; break; case CbFieldType::BoolTrue: m_Builder << "true"; break; case CbFieldType::IntegerPositive: m_Builder << Accessor.AsIntegerPositive(); break; case CbFieldType::IntegerNegative: m_Builder << Accessor.AsIntegerNegative(); break; case CbFieldType::Float32: if (const float Value = Accessor.AsFloat32(); std::isfinite(Value)) m_Builder.Append(fmt::format("{}", Value)); else m_Builder << "null"; break; case CbFieldType::Float64: if (const double Value = Accessor.AsFloat64(); std::isfinite(Value)) m_Builder.Append(fmt::format("{}", Value)); else m_Builder << "null"; break; case CbFieldType::String: { const std::u8string_view U8String = Accessor.AsU8String(); WriteString(std::string_view(reinterpret_cast(U8String.data()), U8String.size())); } break; case CbFieldType::Hash: WriteString(Accessor.AsHash().ToHexString()); break; case CbFieldType::ObjectAttachment: case CbFieldType::BinaryAttachment: WriteString(Accessor.AsAttachment().ToHexString()); break; case CbFieldType::Uuid: WriteString(fmt::format("{}", Accessor.AsUuid())); break; case CbFieldType::DateTime: WriteString(DateTime(Accessor.AsDateTimeTicks()).ToIso8601()); break; case CbFieldType::TimeSpan: if (const TimeSpan Span(Accessor.AsTimeSpanTicks()); Span.GetDays() == 0) WriteString(Span.ToString("%h:%m:%s.%n")); else WriteString(Span.ToString("%d.%h:%m:%s.%n")); break; case CbFieldType::ObjectId: WriteString(fmt::format("{}", Accessor.AsObjectId())); break; case CbFieldType::Binary: WriteBase64(Accessor.AsBinary()); break; default: ZEN_ASSERT_FORMAT(false, "invalid field type: {}", uint8_t(Accessor.GetType())); break; } } void WriteCustomById(CbCustomById Custom, int32_t Indent) { WriteIndent(Indent); m_Builder << "Id: "; m_Builder.Append(fmt::format("{}", Custom.Id)); m_Builder << '\n'; WriteIndent(Indent); m_Builder << "Data: "; WriteBase64(Custom.Data); m_Builder << '\n'; } void WriteCustomByName(CbCustomByName Custom, int32_t Indent) { WriteIndent(Indent); m_Builder << "Name: "; WriteString(std::string_view(reinterpret_cast(Custom.Name.data()), Custom.Name.size())); m_Builder << '\n'; WriteIndent(Indent); m_Builder << "Data: "; WriteBase64(Custom.Data); m_Builder << '\n'; } void WriteBase64(MemoryView Value) { ZEN_ASSERT(Value.GetSize() <= 512 * 1024 * 1024); ExtendableStringBuilder<256> Buf; const uint32_t EncodedSize = Base64::GetEncodedDataSize(uint32_t(Value.GetSize())); const size_t EncodedIndex = Buf.AddUninitialized(size_t(EncodedSize)); Base64::Encode(static_cast(Value.GetData()), uint32_t(Value.GetSize()), Buf.Data() + EncodedIndex); WriteString(Buf.ToView()); } void WriteString(std::string_view Str) { if (NeedsQuoting(Str)) { m_Builder << '\''; for (char C : Str) { if (C == '\'') m_Builder << "''"; else m_Builder << C; } m_Builder << '\''; } else { m_Builder << Str; } } void WriteIndent(int32_t Indent) { for (int32_t I = 0; I < Indent; ++I) m_Builder << " "; } static bool NeedsQuoting(std::string_view Str) { if (Str.empty()) return false; char First = Str[0]; if (First == ' ' || First == '\n' || First == '\t' || First == '\r' || First == '*' || First == '&' || First == '%' || First == '@' || First == '`') return true; if (Str.size() >= 2 && Str[0] == '<' && Str[1] == '<') return true; char Last = Str.back(); if (Last == ' ' || Last == '\n' || Last == '\t' || Last == '\r') return true; for (char C : Str) { if (C == '#' || C == ':' || C == '-' || C == '?' || C == ',' || C == '\n' || C == '{' || C == '}' || C == '[' || C == ']' || C == '\'' || C == '"') return true; } return false; } static bool IsContainer(CbFieldType Type) { switch (Type) { case CbFieldType::Object: case CbFieldType::UniformObject: case CbFieldType::Array: case CbFieldType::UniformArray: case CbFieldType::CustomById: case CbFieldType::CustomByName: return true; default: return false; } } StringBuilderBase& m_Builder; }; void CompactBinaryToYaml(const CbObjectView& Object, StringBuilderBase& Builder) { CbYamlWriter Writer(Builder); Writer.WriteField(Object.AsFieldView()); } void CompactBinaryToYaml(const CbArrayView& Array, StringBuilderBase& Builder) { CbYamlWriter Writer(Builder); Writer.WriteField(Array.AsFieldView()); } void CompactBinaryToYaml(MemoryView Data, StringBuilderBase& InBuilder) { std::vector Fields = ReadCompactBinaryStream(Data); if (Fields.empty()) return; CbYamlWriter Writer(InBuilder); if (Fields.size() == 1) { Writer.WriteField(Fields[0]); return; } if (Fields[0].HasName()) { for (const CbFieldView& Field : Fields) Writer.WriteMapEntry(Field, 0); } else { for (const CbFieldView& Field : Fields) Writer.WriteSeqEntry(Field, 0); } } #if ZEN_WITH_TESTS void cbyaml_forcelink() { } TEST_CASE("uson.yaml") { using namespace std::literals; SUBCASE("simple") { CbObjectWriter Writer; Writer << "KeyOne" << "ValueOne"; Writer << "KeyTwo" << "ValueTwo"; CbObject Obj = Writer.Save(); StringBuilder<128> Sb; CbYamlWriter YamlWriter(Sb); YamlWriter.WriteField(Obj.AsFieldView()); CHECK_EQ(Sb.ToView(), "KeyOne: ValueOne\nKeyTwo: ValueTwo\n"sv); } SUBCASE("scalar_fields") { CbObjectWriter Writer; Writer << "small_int"sv << 10; Writer << "neg_small_int"sv << -10; Writer << "small_real"sv << 10.5f; Writer << "neg_small_real"sv << -10.5f; Writer.AddNull("null_val"sv); Writer.AddDateTimeTicks("date"sv, 622'033'000'000'000'000ull); Writer.AddHash("hash"sv, IoHash::FromHexString("0011223344556677889900112233445566778899"sv)); Writer.AddObjectId("oid"sv, Oid::FromHexString("112233445566778899001122"sv)); Writer.AddTimeSpanTicks("dt"sv, 3'000'000'000'000ull); Writer.AddUuid("guid"sv, Guid::FromString("E0596ADC-996A-4BA4-ACA3-A2A378AB2796")); Writer.AddBool("yes"sv, true); Writer.AddBool("no"sv, false); CbObject Obj = Writer.Save(); ExtendableStringBuilder<128> Sb; CbYamlWriter YamlWriter(Sb); YamlWriter.WriteField(Obj.AsFieldView()); CHECK_EQ(Sb.ToView(), "small_int: 10\n" "neg_small_int: -10\n" "small_real: 10.5\n" "neg_small_real: -10.5\n" "null_val: null\n" "date: '1972-02-23T14:26:40.000Z'\n" "hash: 0011223344556677889900112233445566778899\n" "oid: 112233445566778899001122\n" "dt: '+3.11:20:00.000000000'\n" "guid: 'e0596adc-996a-4ba4-aca3-a2a378ab2796'\n" "yes: true\n" "no: false\n"sv); } SUBCASE("complex_fields") { CbObjectWriter Writer; Writer.BeginObject("sub"); Writer.AddBool("no"sv, false); Writer.BeginObject("sub"); Writer.AddBool("yes"sv, true); Writer.BeginArray("seq"); Writer.AddInteger(1); Writer.AddInteger(2); Writer.AddInteger(3); Writer.EndArray(); Writer.EndObject(); Writer.EndObject(); Writer.BeginArray("seq"); Writer.AddInteger(1); Writer.AddInteger(2); Writer.AddInteger(3); Writer.EndArray(); Writer.BeginArray("mixed_seq"); Writer.AddInteger(1); Writer.AddString("hello"sv); Writer.AddFloat(44.4f); Writer.BeginObject(); Writer.AddBool("yes"sv, true); Writer.AddBool("no"sv, false); Writer.EndObject(); Writer.EndArray(); CbObject Obj = Writer.Save(); ExtendableStringBuilder<128> Sb; CbYamlWriter YamlWriter(Sb); YamlWriter.WriteField(Obj.AsFieldView()); CHECK_EQ(Sb.ToView(), R"(sub: no: false sub: yes: true seq: - 1 - 2 - 3 seq: - 1 - 2 - 3 mixed_seq: - 1 - hello - 44.4 - yes: true no: false )"sv); } } #endif } // namespace zen