The OpenD Programming Language

deserializeText

Deserialize an Ion Text value to a D value.

  1. T deserializeText(const(char)[] text)
    T
    deserializeText
    (
    T
    )
    (
    scope const(char)[] text
    )
  2. void deserializeText(T value, const(char)[] text)

Parameters

text const(char)[]

The text to deserialize

Return Value

Type: T

The deserialized Ion Text value

Examples

Test struct deserialization

import mir.ion.value;
static struct Book
{
    string title;
    bool wouldRecommend;
    string description;
    uint numberOfNovellas;
    double price;
    float weight;
    string[] tags;
}

static immutable textData = `
{
    "title": "A Hero of Our Time",
    "wouldRecommend": true,
    "description": "",
    "numberOfNovellas": 5,
    "price": 7.99,
    "weight": 6.88,
    "tags": ["russian", "novel", "19th century"]
}`;

Book book = deserializeText!Book(textData);
assert(book.description.length == 0);
assert(book.numberOfNovellas == 5);
assert(book.price == 7.99);
assert(book.tags.length == 3);
assert(book.tags[0] == "russian");
assert(book.tags[1] == "novel");
assert(book.tags[2] == "19th century");
assert(book.title == "A Hero of Our Time");
assert(book.weight == 6.88f);
assert(book.wouldRecommend);

Test @nogc struct deserialization

import mir.ion.value;
import mir.bignum.decimal;
import mir.small_string;
import mir.small_array;
import mir.conv : to;
static struct Book
{
    SmallString!64 title;
    bool wouldRecommend;
    SmallString!64 description;
    uint numberOfNovellas;
    Decimal!1 price;
    double weight;
    SmallArray!(SmallString!(16), 10) tags;
}

static immutable textData = `
{
    "title": "A Hero of Our Time",
    "wouldRecommend": true,
    "description": "",
    "numberOfNovellas": 5,
    "price": 7.99,
    "weight": 6.88,
    "tags": ["russian", "novel", "19th century"]
}`;

Book book = deserializeText!Book(textData);
assert(book.description.length == 0);
assert(book.numberOfNovellas == 5);
assert(book.price.to!double == 7.99);
assert(book.tags.length == 3);
assert(book.tags[0] == "russian");
assert(book.tags[1] == "novel");
assert(book.tags[2] == "19th century");
assert(book.title == "A Hero of Our Time");
assert(book.weight == 6.88f);
assert(book.wouldRecommend);

Test that strings are being de-serialized properly

import mir.test: should;
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    const(char)[] output = ionData.text2ion.IonValueStream.serializeText;
    output.should == expected;
}

test(`"hello"`, `"hello"`);
test(`"hello\x20world"`, `"hello world"`);
test(`"hello\u2248world"`, `"hello≈world"`);
test(`"hello\U0001F44Dworld"`, `"hello👍world"`);

Test that timestamps are de-serialized properly

1 import mir.test;
2 import mir.ion.stream;
3 import mir.ion.conv : text2ion;
4 import mir.ion.value : IonTimestamp;
5 import std.datetime.date : TimeOfDay;
6 import mir.timestamp : Timestamp;
7 void test(const(char)[] ionData, Timestamp expected)
8 {
9     foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
10         Timestamp t = ionValue.get!(IonTimestamp).get;
11         t.should == expected;
12     }
13 }
14 
15 void testFail(const(char)[] ionData, Timestamp expected)
16 {
17     foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
18         Timestamp t = ionValue.get!(IonTimestamp).get;
19         assert(expected != t);
20     }
21 }
22 
23 test("2001-01T", Timestamp(2001, 1));
24 test("2001-01-02", Timestamp(2001, 1, 2));
25 test("2001-01-02T", Timestamp(2001, 1, 2));
26 test("2001-01-02T03:04", Timestamp(2001, 1, 2, 3, 4));
27 test("2001-01-02T03:04Z", Timestamp(2001, 1, 2, 3, 4).withOffset(0));
28 test("2001-01-02T03:04+00:00", Timestamp(2001, 1, 2, 3, 4).withOffset(0));
29 test("2001-01-02T03:05+00:01", Timestamp(2001, 1, 2, 3, 4).withOffset(1));
30 test("2001-01-02T05:05+02:01", Timestamp(2001, 1, 2, 3, 4).withOffset(2*60+1));
31 test("2001-01-02T03:04:05", Timestamp(2001, 1, 2, 3, 4, 5));
32 test("2001-01-02T03:04:05Z", Timestamp(2001, 1, 2, 3, 4, 5).withOffset(0));
33 test("2001-01-02T03:04:05+00:00", Timestamp(2001, 1, 2, 3, 4, 5).withOffset(0));
34 test("2001-01-02T03:05:05+00:01", Timestamp(2001, 1, 2, 3, 4, 5).withOffset(1));
35 test("2001-01-02T05:05:05+02:01", Timestamp(2001, 1, 2, 3, 4, 5).withOffset(2*60+1));
36 test("2001-01-02T03:04:05.666", Timestamp(2001, 1, 2, 3, 4, 5, -3, 666));
37 test("2001-01-02T03:04:05.666Z", Timestamp(2001, 1, 2, 3, 4, 5, -3, 666).withOffset(0));
38 test("2001-01-02T03:04:05.666666Z", Timestamp(2001, 1, 2, 3, 4, 5, -6, 666_666).withOffset(0));
39 test("2001-01-02T03:54:05.666+00:50", Timestamp(2001, 1, 2, 3, 4, 5, -3, 666).withOffset(50));
40 test("2001-01-02T03:54:05.666666+00:50", Timestamp(2001, 1, 2, 3, 4, 5, -6, 666_666).withOffset(50));
41 
42 // Time of day tests
43 test("03:04", Timestamp(0, 0, 0, 3, 4));
44 test("03:04Z", Timestamp(0, 0, 0, 3, 4).withOffset(0));
45 test("03:04+00:00", Timestamp(0, 0, 0, 3, 4).withOffset(0));
46 test("03:05+00:01", Timestamp(0, 0, 0, 3, 4).withOffset(1));
47 test("05:05+02:01", Timestamp(0, 0, 0, 3, 4).withOffset(2*60+1));
48 test("03:04:05", Timestamp(0, 0, 0, 3, 4, 5));
49 test("03:04:05Z", Timestamp(0, 0, 0, 3, 4, 5).withOffset(0));
50 test("03:04:05+00:00", Timestamp(0, 0, 0, 3, 4, 5).withOffset(0));
51 test("03:05:05+00:01", Timestamp(0, 0, 0, 3, 4, 5).withOffset(1));
52 test("05:05:05+02:01", Timestamp(0, 0, 0, 3, 4, 5).withOffset(2*60+1));
53 test("03:04:05.666", Timestamp(0, 0, 0, 3, 4, 5, -3, 666));
54 test("03:04:05.666Z", Timestamp(0, 0, 0, 3, 4, 5, -3, 666).withOffset(0));
55 test("03:04:05.666666Z", Timestamp(0, 0, 0, 3, 4, 5, -6, 666_666).withOffset(0));
56 test("03:54:05.666+00:50", Timestamp(0, 0, 0, 3, 4, 5, -3, 666).withOffset(50));
57 test("03:54:05.666666+00:50", Timestamp(0, 0, 0, 3, 4, 5, -6, 666_666).withOffset(50));
58 
59 // Mir doesn't like 03:04 only (as technically it's less precise then TimeOfDay)... ugh
60 test("03:04:05", Timestamp(TimeOfDay(3, 4, 5)));
61 test("03:04:05Z", Timestamp(TimeOfDay(3, 4, 5)).withOffset(0));
62 test("03:04:05+00:00", Timestamp(TimeOfDay(3, 4, 5)).withOffset(0));
63 test("03:05:05+00:01", Timestamp(TimeOfDay(3, 4, 5)).withOffset(1));
64 test("05:05:05+02:01", Timestamp(TimeOfDay(3, 4, 5)).withOffset(2*60+1));
65 
66 testFail("2001-01-02T03:04+00:50", Timestamp(2001, 1, 2, 3, 4));
67 testFail("2001-01-02T03:04:05+00:50", Timestamp(2001, 1, 2, 3, 4, 5));
68 testFail("2001-01-02T03:04:05.666Z", Timestamp(2001, 1, 2, 3, 4, 5).withOffset(0));
69 testFail("2001-01-02T03:54:05.666+00:50", Timestamp(2001, 1, 2, 3, 4, 5));
70 
71 // Fake timestamps for Duration encoding
72 import core.time : weeks, days, hours, minutes, seconds, hnsecs;
73 test("0005-02-88T07:40:04.9876543", Timestamp(5.weeks + 2.days + 7.hours + 40.minutes + 4.seconds + 9876543.hnsecs));
74 test("0005-02-99T07:40:04.9876543", Timestamp(-5.weeks - 2.days - 7.hours - 40.minutes - 4.seconds - 9876543.hnsecs));

Test that binary literals are de-serialized properly.

import mir.ion.value : IonUInt;
import mir.ion.stream;
import mir.ion.conv : text2ion;
void test(const(char)[] ionData, uint val)
{
    foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonUInt);
        assert(v.get!uint == val);
    }
}

test("0b00001", 0b1);
test("0b10101", 0b10101);
test("0b11111", 0b11111);
test("0b111111111111111111111", 0b1111_1111_1111_1111_1111_1);
test("0b1_1111_1111_1111_1111_1111", 0b1_1111_1111_1111_1111_1111);

Test that signed / unsigned integers are de-serialized properly.

import mir.ion.value : IonUInt, IonNInt;
import mir.ion.stream;
import mir.ion.conv : text2ion;
void test(const(char)[] ionData, ulong val)
{
    foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonUInt);
        assert(v.get!ulong == val);
    }
}

void testNeg(const(char)[] ionData, ulong val)
{
    foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonNInt);
        assert(v.get!long == -val);
    }
}

test("0xabc_def", 0xabc_def);
test("0xabcdef", 0xabcdef);
test("0xDEADBEEF", 0xDEADBEEF);
test("0xDEADBEEF", 0xDEAD_BEEF);
test("0xDEAD_BEEF", 0xDEAD_BEEF);
test("0xDEAD_BEEF", 0xDEADBEEF);
test("0x0123456789", 0x0123456789);
test("0x0123456789abcdef", 0x0123456789abcdef);
test("0x0123_4567_89ab_cdef", 0x0123_4567_89ab_cdef);

testNeg("-0xabc_def", 0xabc_def);
testNeg("-0xabc_def", 0xabc_def);
testNeg("-0xabcdef", 0xabcdef);
testNeg("-0xDEADBEEF", 0xDEADBEEF);
testNeg("-0xDEADBEEF", 0xDEAD_BEEF);
testNeg("-0xDEAD_BEEF", 0xDEAD_BEEF);
testNeg("-0xDEAD_BEEF", 0xDEADBEEF);
testNeg("-0x0123456789", 0x0123456789);
testNeg("-0x0123456789abcdef", 0x0123456789abcdef);
testNeg("-0x0123_4567_89ab_cdef", 0x0123_4567_89ab_cdef);

Test that infinity & negative infinity are deserialized properly.

import mir.test: should;
import mir.ion.value : IonFloat;
import mir.ion.conv : text2ion;
import mir.ion.stream;
void test(const(char)[] ionData, float expected)
{
    foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonFloat);
        v.get!float.should == expected;
    }
}

test("-inf", -float.infinity);
test("+inf", float.infinity);

Test that NaN is deserialized properly.

import mir.ion.value;
import mir.ion.conv : text2ion;
import mir.ion.stream;

alias isNaN = x => x != x;
void test(const(char)[] ionData)
{
    foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonFloat);
        assert(isNaN(v.get!float));
    }
}

test("nan");

Test that signed / unsigned integers and decimals and floats are all deserialized properly.

import mir.test: should;
import mir.ion.value;
import mir.ion.stream;
import mir.ion.conv : text2ion;
void test_uint(const(char)[] ionData, ulong expected)
{
    foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonUInt);
        v.get!ulong.should == expected;
    }
}

void test_nint(const(char)[] ionData, long expected)
{
    foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonNInt);
        v.get!long.should == expected;
    }
}

void test_dec(const(char)[] ionData, double expected)
{
    foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonDecimal);
        v.get!double.should == expected;
    }
}

void test_float(const(char)[] ionData, float expected)
{
    foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonFloat);
        v.get!float.should == expected;
    }
}

test_uint("123", 123);
test_nint("-123", -123);
test_dec("123.123123", 123.123123);
test_dec("123.123123", 123.123123);
test_dec("123.123123d0", 123.123123);
test_dec("123.123123d0", 123.123123);
test_dec("-123.123123", -123.123123);
test_dec("-123.123123d0", -123.123123);
test_dec("18446744073709551615.", 1844_6744_0737_0955_1615.0);
test_dec("-18446744073709551615.", -1844_6744_0737_0955_1615.0);
test_dec("18446744073709551616.", 1844_6744_0737_0955_1616.0);
test_dec("-18446744073709551616.", -1844_6744_0737_0955_1616.0);
test_float("123.456789e-6", 123.456789e-6);
test_float("-123.456789e-6", -123.456789e-6);

Test that quoted / unquoted symbols are deserialized properly.

import mir.ion.value;
import mir.ion.conv : text2ion;
import mir.ion.stream;
void test(const(char)[] ionData, string symbol)
{
    foreach (symbolTable, val; ionData.text2ion.IonValueStream) {
        auto sym = val.get!(IonSymbolID).get;
        assert(symbol == symbolTable[sym]);
    }
}

test("$0", "$0");
test("$ion", "$ion");
test("$ion_1_0", "$ion_1_0");
test("name", "name");
test("version", "version");
test("imports", "imports");
test("symbols", "symbols");
test("max_id", "max_id");
test("$ion_shared_symbol_table", "$ion_shared_symbol_table");
test("hello", "hello");
test("world", "world");
test("'foobaz'", "foobaz");
test("'👍'", "👍");
test("' '", " ");
test("'\\U0001F44D'", "👍");
test("'\\u2248'", "\u2248");
test("'true'", "true");
test("'false'", "false");
test("'nan'", "nan");
test("'null'", "null");

Test that all variations of the "null" value are deserialized properly.

import mir.ion.value;
import mir.ion.stream;
import mir.ion.conv : text2ion;
void test(const(char)[] ionData, IonTypeCode nullType)
{
    foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(IonNull);
        assert(v.code == nullType);
    }
}

test("null", IonTypeCode.null_);
test("null.bool", IonTypeCode.bool_);
test("null.int", IonTypeCode.uInt);
test("null.float", IonTypeCode.float_);
test("null.decimal", IonTypeCode.decimal);
test("null.timestamp", IonTypeCode.timestamp);
test("null.symbol", IonTypeCode.symbol);
test("null.string", IonTypeCode.string);
test("null.blob", IonTypeCode.blob);
test("null.clob", IonTypeCode.clob);
test("null.list", IonTypeCode.list);
test("null.struct", IonTypeCode.struct_);
test("null.sexp", IonTypeCode.sexp);

Test that blobs are getting de-serialized correctly.

import mir.ion.value;
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.lob;
void test(const(char)[] ionData, ubyte[] blobData)
{
    foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
        auto v = ionValue.get!(Blob);
        assert(v.data == blobData);
    }
}

test("{{ SGVsbG8sIHdvcmxkIQ== }}", cast(ubyte[])"Hello, world!");
test("{{ R29vZCBhZnRlcm5vb24hIPCfkY0= }}", cast(ubyte[])"Good afternoon! 👍");

Test that long/short clobs are getting de-serialized correctly.

   import mir.ion.value;
   import mir.ion.stream;
   import mir.ion.conv : text2ion;
   import mir.lob;
   void test(const(char)[] ionData, const(char)[] blobData)
   {
       foreach(symbolTable, scope ionValue; ionData.text2ion.IonValueStream) {
           auto v = ionValue.get!(Clob);
           assert(v.data == blobData);
       }
   }

   test(`{{ "This is a short clob."  }}`, "This is a short clob.");
   test(`
   {{ 
       '''This is a long clob,'''
       ''' which spans over multiple lines,'''
       ''' and can have a theoretically infinite length.'''
   }}`, "This is a long clob, which spans over multiple lines, and can have a theoretically infinite length.");
   test(`{{ 
           '''Long clobs can also have their data contained in one value,
but spread out across multiple lines.'''
         }}`, "Long clobs can also have their data contained in one value,\n but spread out across multiple lines.");
   test(`{{ '''Or, you can have multiple values on the same line,''' ''' like this!'''}}`, 
       "Or, you can have multiple values on the same line, like this!");

Test that structs are getting de-serialized properly

import mir.test: should;
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = ionData.text2ion.IonValueStream.serializeText;
    v.should == expected;
}

test(`1`, `1`);
test(`test::1`, `test::1`);

test(`{"test":"world", test: false, 'test': usd::123.456, '''test''': "asdf"}`,
     `{test:"world",test:false,test:usd::123.456,test:"asdf"}`);

test(`{'''foo'''
'''bar''': "foobar"}`,
     `{foobar:"foobar"}`);

test(`{a: 1, b: 2}`, `{a:1,b:2}`);

test(`{}`, `{}`);

Test that sexps are getting de-serialized properly.

import mir.test: should;
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = ionData.text2ion.IonValueStream.serializeText;
    v.should == expected;
}

test(`(this is a sexp list)`, "(this is a sexp list)");
test(`('+' '++' '+-+' '-++' '-' '--' '---' -3 - 3 '--' 3 '--'3 )`, 
    "('+' '++' '+-+' '-++' '-' '--' '---' -3 '-' 3 '--' 3 '--' 3)");
test(`(a_plus_plus_plus_operator::+++ a_3::3)`, `(a_plus_plus_plus_operator::'+++' a_3::3)`);
test(`(& (% -[42, 3]+(2)-))`, `('&' ('%' '-' [42,3] '+' (2) '-'))`);

Test that arrays are getting de-serialized properly.

import mir.test: should;
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = ionData.text2ion.IonValueStream.serializeText;
    v.should == expected;
}

test(`[hello, world]`, `[hello,world]`);
test(`[this::is::an::annotated::symbol, this::is::annotated::123.456]`,
     `[this::is::an::annotated::symbol,this::is::annotated::123.456]`);
test(`[date::of::birth::0001-01-01T00:00:00.0+00:00, date::of::birth::1970-01-01T]`,
     `[date::of::birth::0001-01-01T00:00:00.0Z,date::of::birth::1970-01-01]`);
test(`['hello', "hello", '''hello''', '''hello ''''''world''']`,
     `[hello,"hello","hello","hello world"]`);
test(`[0x123_456, 0xF00D_BAD]`, `[1193046,251714477]`);

Test that annotations work with symbols

import mir.test: should;
import mir.ion.stream;
import mir.ser.text;
import mir.ion.conv : text2ion;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = ionData.text2ion.IonValueStream.serializeText;
    v.should == expected;
}

test(`'test'::'hello'::'world'`, "test::hello::world");
test(`foo::bar`, "foo::bar");
test(`foo::'bar'`, "foo::bar");
test(`'foo'::bar`, "foo::bar");
test(`'foo bar'::cash`, "'foo bar'::cash");
test(`'foo\U0001F44D'::'baz\U0001F44D'`, "'foo\U0001F44D'::'baz\U0001F44D'");
test(`'\u2248'::'\u2248'`, "'\u2248'::'\u2248'");
test(`'\u2248'::foo`, "'\u2248'::foo");

Test that annotations work with floats

import mir.test: should;
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = ionData.text2ion.IonValueStream.serializeText;
    v.should == expected;
}

test(`usd::10.50e0`, "usd::10.5");
test(`'Value is good \U0001F44D'::12.34e0`, "'Value is good \U0001F44D'::12.34");
test(`'null'::150.00e0`, "'null'::150.0");

Test that annotations work with decimals

import mir.test: should;
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = ionData.text2ion.IonValueStream.serializeText;
    v.should == expected;
}

test(`Types::Speed::MetersPerSecondSquared::9.81`, "Types::Speed::MetersPerSecondSquared::9.81");
test(`Rate::USD::GBP::12.345`, "Rate::USD::GBP::12.345");
test(`usd::10.50d0`, "usd::10.50");
test(`'Value is good \U0001F44D'::12.34d0`, "'Value is good \U0001F44D'::12.34");
test(`'null'::150.00d0`, "'null'::150.00");
test(`'Cool'::27182818284590450000000000d-25`, "Cool::2.7182818284590450000000000");
test(`mass::2.718281828459045d0`, "mass::2.718281828459045");
test(`weight::0.000000027182818284590450000000000d+8`, "weight::2.7182818284590450000000000");
test(`coeff::-0.000000027182818284590450000000000d+8`, "coeff::-2.7182818284590450000000000");

Test that annotations work with strings

import mir.test: should;
import mir.ion.stream;
import mir.ion.conv : text2ion;
import mir.ser.text;
void test(const(char)[] ionData, const(char)[] expected)
{
    auto v = ionData.text2ion.IonValueStream.serializeText;
    v.should == expected;
}

test(`Password::"Super Secure Password"`, `Password::"Super Secure Password"`);
test(`Magic::String::"Hello, world!"`, `Magic::String::"Hello, world!"`);
test(`SSH::PublicKey::'''ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNrMk7QmmmNIusf10CwHQHs6Z9HJIiuknwoqtQLzEPxdMnNHKJexNnfF5QQ2v84BBhVjxvPgSqhdcVMEFy8PrGu44MqhK/cV6BGx430v2FnArWDO+9LUSd+3iwMJVZUQgZGtjSLAkZO+NOSPWZ+W0SODGgUfbNVu35GjVoA2+e1lOINUe22oZPnaD+gpJGUOx7j5JqpCblBZntvZyOjTPl3pc52rIGfxi1TYJnDXjqX76OinZceBzp5Oh0oUTrPbu55ig+b8bd4HtzLWxcqXBCnsw0OAKsAiXfLlBcrgZUsoAP9unrcqsqoJ2qEEumdsPqcpJakpO7/n0lMP6lRdSZ'''`,
     `SSH::PublicKey::"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNrMk7QmmmNIusf10CwHQHs6Z9HJIiuknwoqtQLzEPxdMnNHKJexNnfF5QQ2v84BBhVjxvPgSqhdcVMEFy8PrGu44MqhK/cV6BGx430v2FnArWDO+9LUSd+3iwMJVZUQgZGtjSLAkZO+NOSPWZ+W0SODGgUfbNVu35GjVoA2+e1lOINUe22oZPnaD+gpJGUOx7j5JqpCblBZntvZyOjTPl3pc52rIGfxi1TYJnDXjqX76OinZceBzp5Oh0oUTrPbu55ig+b8bd4HtzLWxcqXBCnsw0OAKsAiXfLlBcrgZUsoAP9unrcqsqoJ2qEEumdsPqcpJakpO7/n0lMP6lRdSZ"`);

Meta