1 module plist.types.dict; 2 import plist.types.element; 3 import std.array; 4 import std.algorithm.sorting; 5 import std.string; 6 import std.algorithm.searching; 7 8 class PlistElementDict : PlistElement { 9 void instantiate(DOMEntity!string entity) { 10 _entity = entity; 11 if (entity.type == EntityType.elementEmpty) return; 12 auto children = entity.children; 13 import std.conv : to; 14 if((children.length % 2) != 0) 15 throw new PlistParsingException("Length should be a multiple of 2, got " ~ to!string(children.length)); 16 17 for (int i = 0; i < children.length; i += 2) { 18 import plist.conv; 19 auto _key = children[i]; 20 auto _val = children[i + 1]; 21 if (_key.type != EntityType.elementStart) 22 throw new PlistParsingException("Expected elementStart while parsing dict\n"); 23 if (_key.name != PlistElementType.PLIST_ELEMENT_KEY) 24 throw new PlistParsingException("Expected a <key> element before a value\n"); 25 if (_key.children.length != 1) 26 throw new PlistParsingException("Expected a valid string after key element\n"); 27 if (_key.children[0].type != EntityType.text) 28 throw new PlistParsingException("Did not get a text element after key\n"); 29 string key = _key.children[0].text; 30 if (_entries.keys.canFind(key)) 31 throw new PlistParsingException("Cannot override an existing key within dict\n"); 32 if (!validateDataType(_val)) 33 throw new PlistParsingException("Expected a valid data type for value in dict\n"); 34 auto val = coerceRuntime(cast(PlistElementType)_val.name, _val); 35 _entries[key] = val; 36 } 37 } 38 39 string type() { 40 return PlistElementType.PLIST_ELEMENT_DICT; 41 } 42 43 ref PlistElement opIndex(string index) { 44 if (_entries.keys.canFind(index)) { 45 return _entries[index]; 46 } 47 assert(0); 48 } 49 50 51 /* 52 Only return true if we were able to fill all members of the field 53 */ 54 55 // Complexity is infinity 56 bool coerceToNative(T)(ref T obj) { 57 import std.traits; 58 enum field_names = FieldNameTuple!T; 59 import plist.conv; 60 import plist.types; 61 // This static foreach lets us go over all fields defined in the struct 62 bool fail = false; 63 static foreach(i, field; Fields!T) { 64 // We have to wrap this entire thing within a lambda 65 // and potentially make execution flow go ALL over the 66 // damn place, since alias is global, but within a lambda 67 // it isn't... 68 fail = () { 69 // IF and only if the programmer has defined the corresponding key within the plist, proceed 70 mixin("alias F = T." ~ field_names[i] ~ ";"); 71 static if (hasUDA!(F, PlistKey)) { 72 // There should ONLY be one mapping to a Plist value 73 static assert(getUDAs!(F, PlistKey).length == 1, "Cannot have more then one key to deserialize"); 74 static string key = getUDAs!(F, PlistKey)[0].key; 75 // Do this check at runtime, since we populate the dict at runtime (and have no prior knowledge 76 auto results = _entries.keys.find(key); 77 if (!results.empty) { 78 PlistElement element = _entries[key]; 79 // And ensure that the type that they want us to convert to (within the struct) is actually compatible 80 // for example, PlistElementType.PLIST_ELEMENT_DATE to std.datetime.SysTime, 81 // PlistElementType.PLIST_ELEMENT_STRING to "string", etc 82 // SINCE the type field is defined at runtime, 83 // we have to do this weird hack in order to get it to be compile-time 84 // where we generate a MASSIVE branch comparing types 85 86 // As well, we have to check if it's a boolean, 87 // since for some GOD awful reason, true/false is 88 // defined as it's own type -_- 89 if (element.type == "bool") { 90 // Hack to ensure compilers don't try to cast every field to a boolean 91 static if (typeIsCoercible!field(PlistElementType.PLIST_ELEMENT_BOOLEAN_TRUE)) { 92 enum type = "PlistElementBoolean"; 93 mixin (type ~ " val = cast(" ~ type ~ ")element;"); 94 mixin ("obj." ~ field_names[i] ~ " = val.value;"); 95 return true; 96 } 97 else { 98 throw new PlistSerdeException("Field " ~ field_names[i] ~ " was not coercible to a " ~ element.type); 99 } 100 } else if (element.type == "dict") { 101 import std.datetime : SysTime; 102 // God have mercy on my soul for this 103 // SysTime is also a dict, but we need to ignore it 104 static if (is(typeof(F) == struct) && !is(typeof(F) == SysTime)) { 105 // Now recurse! 106 PlistElementDict wrappedDict = cast(PlistElementDict)element; 107 return wrappedDict.coerceToNative!(typeof(F))(mixin("obj." ~ field_names[i])); 108 } else { 109 throw new PlistSerdeException("Field " ~ field_names[i] ~ " was not coercible to a " ~ element.type); 110 } 111 } else if (element.type == "array") { 112 import std.range.primitives; 113 import std.datetime : SysTime; 114 static if (is(typeof(F) == PlistElementArray)) { 115 mixin ("obj." ~ field_names[i] ~ " = cast(PlistElementArray)element;"); 116 return true; // strange that I have to bail out here 117 } 118 else static if (isArray!(typeof(F)) 119 && isDynamicArray!(typeof(F)) 120 && is(ElementType!(typeof(F)) == struct) 121 && !is(ElementType!(typeof(F)) == SysTime)) { 122 PlistElementArray arr = cast(PlistElementArray)element; 123 PlistElement[] elements = arr.entries(); 124 /* Reset the length of the array to 0 */ 125 mixin("obj." ~ field_names[i] ~ ".length = 0;"); 126 bool passed = true; 127 foreach(e; elements) { 128 /* If it's not a dict, ignore it */ 129 if (e.type() == "dict") { 130 PlistElementDict wrappedDict = cast(PlistElementDict)e; 131 mixin("obj." ~ field_names[i] ~ ".length++;"); 132 passed = wrappedDict.coerceToNative!(ElementType!(typeof(F)))( 133 mixin("obj." ~ field_names[i] ~ "[$ - 1]")); 134 if (!passed) 135 return passed; 136 } 137 } 138 return passed; 139 } else { 140 throw new PlistSerdeException("Field " ~ field_names[i] ~ " was not coercible to a " ~ element.type); 141 } 142 } 143 144 static foreach(etype; [EnumMembers!PlistElementType]) { 145 if (element.type == etype) { // Implicit that element.type will never be bool here 146 static if (typeIsCoercible!field(etype)) { 147 enum type = getElementClassFromType(etype); 148 mixin (type ~ " val = cast(" ~ type ~ ")element;"); 149 mixin ("obj." ~ field_names[i] ~ " = val.value;"); 150 return true; // Jump out of the massive branch quicker 151 } else { 152 throw new PlistSerdeException("Field " ~ field_names[i] ~ " was not coercible to a " ~ element.type); 153 } 154 } 155 } 156 } 157 else { 158 static if (hasUDA!(F, PlistOptional)) { 159 return true; 160 } else { 161 throw new PlistSerdeException("Missing field " ~ key); 162 } 163 } 164 } else { 165 return true; 166 } 167 168 // catch-all 169 return false; 170 }(); 171 172 if (!fail) // Jump out quick, in case one field wasn't able to deserialize, followed by one that WAS 173 return fail; 174 } 175 return fail; 176 } 177 178 179 @property string[] keys() { 180 auto keys = _entries.keys.sort!("a.toLower() < b.toLower()")(); 181 return keys.array; 182 } 183 184 override string toString() { 185 import std.format : format; 186 string ret = format!"<PlistElementDict keys: %s, n_keys: %s>"(_entries.keys, _entries.keys.length); 187 return ret; 188 } 189 190 void write(ref XMLWriter!(Appender!string) writer) { 191 writer.writeStartTag("dict"); 192 // order doesn't matter, so sort it alphabetically 193 foreach (key; keys()) { 194 PlistElement e = _entries[key]; 195 writer.openStartTag("key"); 196 writer.closeStartTag(); 197 writer.writeText(key, Newline.no); 198 writer.writeEndTag("key", Newline.no); 199 e.write(writer); 200 } 201 writer.writeEndTag("dict"); 202 203 } 204 205 private { 206 DOMEntity!string _entity; 207 PlistElement[string] _entries; 208 } 209 } 210