private void _PreParse(string xmlContent, string filename, ref PrototypeParseParameters parameters, List <SerializedData> result) { ListPool <SerializedData> .GetIfNull(ref result); ListPool <ParsingError> .GetIfNull(ref errors); var xElement = XElement.Parse(xmlContent); // Validity checks if (!ParsingValidation.ContainerElementName(xElement, filename, errors) || !ParsingValidation.ContainerTypeAttribute(xElement, filename, errors)) { return; } // Get type XAttribute typeAttribute = xElement.Attribute(PrototypeContainerAttributeType); var type = GetSerializableTypeCacheFor(typeAttribute.Value, ref parameters); if (!ParsingValidation.TypeFound(xElement, typeAttribute, type, filename, errors)) { return; } // Iterate over nodes foreach (var xNode in xElement.Nodes()) { var elementType = type; var nodeXElement = xNode as XElement; // Validity checks if (!ParsingValidation.NodeIsElement(xNode, filename, errors) || !ParsingValidation.PrototypeElementName(nodeXElement, filename, errors)) { continue; } var elementTypeAttribute = nodeXElement.Attribute(PrototypeContainerAttributeType); if (!ReferenceEquals(elementTypeAttribute, null)) { elementType = GetSerializableTypeCacheFor(elementTypeAttribute.Value, ref parameters); if (!ParsingValidation.TypeFound(nodeXElement, elementTypeAttribute, elementType, filename, errors)) { continue; } } // Prepare var data = new SerializedData(elementType, nodeXElement, filename); result.Add(data); } }
/// <summary> /// Actually loads the data from the previous parse <see cref="ParseFields(List{ParsingError})"/>. /// It will for every field with string data deserialize this data using <see cref="IPrototypeDataSerializer"/>. /// /// For every sub-data field (<seealso cref="ParseFields(List{ParsingError})"/>) a <see cref="SerializedData"/> object is being written to <see cref="fields"/>. /// The sub-data object will have <see cref="PrepareParse(SerializableTypeCache, XElement, string)"/>, <see cref="ParseFields(List{ParsingError})"/> and <see cref="LoadFields(List{ParsingError}, PrototypeParserState)"/> called. /// </summary> public void LoadFields(List <ParsingError> errors, PrototypeParserState state) { foreach (var xNode in xElement.Nodes()) { if (!ParsingValidation.NodeIsElement(xNode, this.filename, errors)) // Malformed XML { continue; } var xElement = xNode as XElement; var elementName = xElement.Name.LocalName; // Field unknown? if (!ParsingValidation.FieldKnown(xElement, targetType, elementName, filename, errors)) { continue; } debug.Add(elementName, xElement); var fieldData = targetType.GetFieldData(elementName); var fieldType = fieldData.serializableTypeCache; bool isCollection = SerializedCollectionData.IsCollection(fieldData.fieldInfo.FieldType); IPrototypeDataSerializer serializer = PrototypeCaches.GetBestSerializerFor(fieldData.fieldInfo.FieldType); if (!ReferenceEquals(serializer, null)) { try { if (!ParsingValidation.SerializerWasFound(xElement, serializer, elementName, targetType == null ? null : targetType.type, fieldType == null ? null : fieldType.type, filename, errors)) { continue; } fields.Add(elementName, serializer.Deserialize(fieldData.fieldInfo.FieldType, xElement, state)); } catch (Exception ex) { errors.Add(new ParsingError(ParsingErrorSeverity.ERROR, filename, -1, "Serializer threw exception on field " + elementName + " on type " + targetType.type + ":\n\n" + ex.ToString() + "\n\nSkipping field!")); } } else if (isCollection) { var col = new SerializedCollectionData(fieldData.fieldInfo.FieldType, xElement, this.filename); col.ParseAndLoadData(errors, state); fields.Add(elementName, col); // Collection override action? var collectionOverrideAttrib = xElement.Attribute(PrototypeParser.PrototypeAttributeCollectionOverrideAction); if (!ReferenceEquals(collectionOverrideAttrib, null)) { collectionOverrideActions.Set(elementName, (CollectionOverrideAction)Enum.Parse(typeof(CollectionOverrideAction), collectionOverrideAttrib.Value)); } } else { // A known serializable // Prototype reference? if (fieldData.isPrototype) { fields.Add(elementName, new SerializedPrototypeReference() { identifier = xElement.Value as string }); } else { // Determine which type to serialize var targetType = fieldData.serializableTypeCache; string typeName = fieldData.fieldInfo.Name; // Check if element explicitly overwrites the type to support polymorphism // The field type might be some base class type and the xml overwrites this type with a class extending from the base var classAttrib = xElement.Attribute(PrototypeParser.PrototypeAttributeType); if (!ReferenceEquals(classAttrib, null)) { targetType = PrototypeCaches.GetSerializableTypeCacheFor(classAttrib.Value, state.parameters.standardNamespace); typeName = classAttrib.Value; } // Field not serializable? if (!ParsingValidation.DataFieldSerializerValid(xElement, targetType, typeName, elementName, filename, errors)) { continue; } // Resolve field name type var d = new SerializedData(targetType, xElement as XElement, this.filename); d.LoadFields(errors, state); fields.Add(elementName, d); } } } }
private void _Parse(List <SerializedData> data, ref PrototypeParseParameters parameters) { ListPool <ParsingError> .GetIfNull(ref errors); // Get prototypes with others inheriting from first // Key = type which is inheriting from something, Value = the type its inheriting from Dictionary <SerializedData, List <SerializedData> > inheritingFrom = new Dictionary <SerializedData, List <SerializedData> >(); // This is only used for topo sort! Dictionary <SerializedData, object> instances = new Dictionary <SerializedData, object>(); Dictionary <string, SerializedData> idMapping = new Dictionary <string, SerializedData>(); List <SerializedData> invalid = new List <SerializedData>(); // Pre-parse names, create instances and apply name foreach (var d in data) { if (!ParsingValidation.ElementHasId(d.xElement, d.filename, errors)) { invalid.Add(d); continue; } // Read name var attribName = d.xElement.Attribute(PrototypeAttributeIdentifier); idMapping.Add(attribName.Value, d); // Check if abstract prototype data var attribAbstract = d.xElement.Attribute(PrototypeAttributeAbstract); bool isAbstract = !ReferenceEquals(attribAbstract, null) && string.Equals("True", attribAbstract.Value); if (!isAbstract) { var obj = d.targetType.Create(); instances.Add(d, obj); (obj as IPrototype).identifier = attribName.Value; this.prototypes.Add(obj as IPrototype); } } // Remove invalidated entries foreach (var d in invalid) { data.Remove(d); } invalid.Clear(); foreach (var d in data) { SerializedData inheritedData; if (!string.IsNullOrEmpty(d.inherits) && idMapping.TryGetValue(d.inherits, out inheritedData)) { inheritingFrom.GetOrCreate(d).Add(inheritedData); } } PrototypeParserState state = new PrototypeParserState() { parameters = parameters }; // Remove invalidated entries foreach (var d in invalid) { data.Remove(d); } // Step 1 - sort by inheritance List <SerializedData> empty = new List <SerializedData>(); var sorted = data.TSort((sd) => inheritingFrom.ContainsKey(sd) ? inheritingFrom[sd] : empty, true).ToList(); // Step 2 - Preloads the fields and creates sub-data objects foreach (var d in sorted) { d.LoadFields(errors, state); } // Step 3 - run sorting algorithm for reference resolve foreach (var d in data) { d.ResolveReferenceFields(this.prototypes, errors, state); } // Step 4 - Final data apply List <SerializedData> inheritingFromTmp = new List <SerializedData>(); foreach (var d in sorted) { if (!instances.ContainsKey(d)) { continue; } // Apply inherited data first if (!string.IsNullOrEmpty(d.inherits)) { // Look up all inherited data in bottom to top order inheritingFromTmp.Clear(); var inheritedData = d.inherits; while (!string.IsNullOrEmpty(inheritedData)) { SerializedData serializedData = null; if (!this.serializedData.TryGetValue(inheritedData, out serializedData) && !idMapping.TryGetValue(inheritedData, out serializedData)) { this.errors.Add(new ParsingError(ParsingErrorSeverity.ERROR, d.filename, (d.xElement as IXmlLineInfo).LinePosition, "Could not find the prototype '" + inheritedData + "' for prototype '" + (instances[d] as IPrototype).identifier + "'! Ignoring inheritance!")); } else { inheritingFromTmp.Add(serializedData); } // Recursion inheritedData = serializedData.inherits; } // Reverse so we apply in top to bottom order inheritingFromTmp.Reverse(); // Apply foreach (var _d in inheritingFromTmp) { _d.ApplyTo(instances[d], errors, state); } } // Apply data over inherited d.ApplyTo(instances[d], errors, state); } // Step 5 - record serialized data in result foreach (var kvp in idMapping) { this.serializedData.Add(kvp.Key, kvp.Value); } }