I’m trying to get this XML loader to work but I kinda got stuck trying to fit all the objects into a data container… As far as I can tell I need a container to hold the entire list which I understand. But when I have to add all the items together I don’t see what I have to do. I am not even sure if my xml is properly structured at this point. Can I get someone to doublecheck if its correct and hopefully than I can come up with the correct data container…
<Objects>
<Object name="Bed">
<Identifier type="default">
<line>
<text>The bed looks comfortable.</text>
<expression>0</expression>
</line>
<line>
<text>I should get some rest later.</text>
<expression>1</expression>
</line>
</Identifier>
<Identifier type="alternate">
<line>
<text>I don't have time for this.</text>
<expression>2</expression>
</line>
</Identifier>
</Object>
</Objects>
using System.Collections.Generic;
using System.Xml.Serialization;
public class Objects
{
public class ObjectData
{
[XmlAttribute("name")] public string name;
[XmlAttribute("Identifier")] public string identifier; //this might be wrong
//what do I do here to get it as a Dictionary<string, string>
}
[XmlRoot("Objects")]
public class ObjectsContainer
{
[XmlArray("Object")] [XmlArrayItem("Identifier")]
public List<ObjectData> objectCollection = new List<ObjectData>();
}
}
The way I intend to call this is by calling a LoadText(string _object, string identifier)
The first thing that you need to clarify is whether the supplied XML conforms to the desired structure. Currently, an object named “Bed” appears to have multiple identifiers, according to your XML, whereas your code represents an object having name and an associated identifier.
Perhaps try to describe the structure your going for. Or just let us know if the XML you posted is in the desired format.
The Xml data I am trying to get is as followed (I will be referring to my initial post, not my first reply for clarification):
public class XML
{
public class lineData
{
public string text;
public Sprite sprite;
}
public class SceneObject
{
public Dictionary<string, List<lineData>> options;
}
public class ObjectContainer
{
public Dictionary<string, SceneObject> objects;
}
}
Workflow planned:
public void DisplayData(string objectName, string type)
{
List<pair<string, Sprite>> data = XmlData.Get(objectName, type);
Printer.Instance.Print(data[0]); // or any other element
}
EDIT: I changed my explanation in words to a class structure, should be a lot easier to understand this way.
Wait a minute, if you have the code for the codewise-structure you’re looking for, just write a sample file. Then you’ll know how the xml file looks like. That’s the easiest thing you can do.
I’m not sure what happens to the Sprite though. You may wanna save some sort of identifier (resource path, GUID whatsoever) and load it on demand.
Thing is that I am very inexperienced in loading and using xml data. but i know what kind of container I wanted.
As far as I can tell this xml file is structured to work. But I dont know how to get the data in this container.
For the sprite you have a point, was going to index them in the editor but your idea seems a lot better
When you use annotated types and let the XMLSerializer to the heavy lifting, the basic pattern can be the following:
using (var streamWriter = new System.IO.StreamWriter(<path>))
{
var serializer = new System.Xml.Serialization.XmlSerializer(typeof( <some type> ));
try
{
serializer.Serialize(streamWriter, <some instance of the type>);
}
catch(Exception exc)
{
// TODO: handle exceptional behaviour
}
}
The deserialization process is similar.
However, it’s cumbersome to re-write this all the time, so most of the people turn this into a basic, generic solution, that is sufficient for many cases.
It’s untested, but the idea should get you started:
public class MyXmlSerializer
{
public void Serialize<T>(T t, string path)
{
// TODO: check arguments etc.
using (var stream = new StreamWriter(""))
{
var serializer = new XmlSerializer(typeof(T));
try
{
serializer.Serialize(stream, t);
}
catch(Exception exc)
{
// TODO: handle exceptional behaviour
}
}
}
// TODO: Deserialization method
}
Once you have a functional wrapper, you can concentrate on modelling your serializable data types.
I also use something similar, just a little more complex and with some more abstraction so that all kinds of output and input formats can be supported.
I am using this method to get the data from an XML file (relates to my initial reply, where it is used and works) I wrote it a while ago, but it worked for the one type, Im sure it ll work for this one since its the same xml structure more or less. Code is sourced from MSDN, I just made it more abstract so it worked for any type.
public static T GetXmlData<T>(string path) where T : class
{
var serializer = new XmlSerializer(typeof(T));
var stream = new FileStream(path, FileMode.Open);
var container = serializer.Deserialize(stream) as T;
stream.Close();
return container;
}
Thing is, I don’t know (I know, but I don’t understand how to apply it to my case) how to tell the deserializer what part of the xml goes where. If I am correct this is what those annotated types are for.
Ye it’s pretty similar, except that it doesn’t handle exceptions and in that case, the stream might not be disposed properly right in time.
The attributes that you used are exactly for that: They contain the meta information that the serializer uses in order to build the XML structure with custom names. If you omit the attributes, it’ll just take the names of the types and members.
So it seems you’ve got all the tools that you need.
I’m not sure how I can assist any further as you already have the data structures defined, all that you have to do is writing a file. If the result is not as expected, then you’ll need to tell us how the final XML should look like. I’m a little confused if you’re looking to turn your code structure into the OP’s XML format, or not.
The original XML file format I had set up can be seen as more of a draft. I was hoping that you would be able to tell me how to fix my data with the provided container (i.e. the list of classes) as I can’t think about how I should set up the annotations to load the data as I have it specified in my classes.
If I run the GetXmlData function I get an error that dictionaries are not supported. This means that the structure I have set up can not be serialized (and writing my own serialization for it would be a bit much…).
NotSupportedException: Cannot serialize member Utilities.Xml+ObjectContainer.objects of type System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[Utilities.Xml+SceneObject, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]], because it implements IDictionary.
Because of this I edited my structure to have more classes that can be serialized (as I just found out by googling dictionaries cannot be default serialized by unity and thats probably causing the issues). I now have this. I hope this makes sense.
public static class Xml
{
public static T GetXmlData<T>(string path) where T : class
{
var serializer = new XmlSerializer(typeof(T));
var stream = new FileStream(path, FileMode.Open);
var container = serializer.Deserialize(stream) as T;
stream.Close();
return container;
}
public class lineData
{
[XmlElement("text")]
public string text;
[XmlElement("sprite")]
public Sprite sprite;
}
public class IdentifierData
{
[XmlAttribute("id")]
public string id;
[XmlArray("Identifier")] [XmlArrayItem("line")]
public List<lineData> lines;
}
public class SceneObject
{
[XmlAttribute("name")]
public string name;
[XmlArray("Object")] [XmlArrayItem("Identifier")]
public List<IdentifierData> ids;
}
public class ObjectContainer
{
[XmlArray("Objects")] [XmlArrayItem("Object")]
public List<SceneObject> objects;
}
}
Having this alongside my xml file (the original one) throws this error. I notice that there is no way to check what is going on internally though. it either works or it doesnt…
InvalidOperationException: <Objects xmlns=''> was not expected.
Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderObjectContainer.Read9_ObjectContainer () (at <2f38dded14c94992aa513cdbff9aad96>:0)
System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <7d97106330684add86d080ecf65bfe69>:0)
Rethrow as InvalidOperationException: There is an error in XML document (1, 2).
System.Xml.Serialization.XmlSerializer.Deserialize (System.Xml.XmlReader xmlReader, System.String encodingStyle, System.Xml.Serialization.XmlDeserializationEvents events) (at <0c52c19ebf1c43be83457d73b02480d8>:0)
System.Xml.Serialization.XmlSerializer.Deserialize (System.Xml.XmlReader xmlReader, System.String encodingStyle) (at <0c52c19ebf1c43be83457d73b02480d8>:0)
System.Xml.Serialization.XmlSerializer.Deserialize (System.IO.Stream stream) (at <0c52c19ebf1c43be83457d73b02480d8>:0)
Utilities.Xml.GetXmlData[T] (System.String path) (at Assets/Scripts/XmlReader.cs:14)
Utilities.Printer.Start () (at Assets/Scripts/Printer.cs:21)
Alright, I completely missed that this doesn’t work with the XmlSerializer.
I see, but this means that you’re missing the list’s parent node. I remember that I had found some workaround in order to omit the list’s parent note. I think it had been posted on StackOverflow years ago, but I cannot remember what it was (I tried to search for it, but couldn’t find it right now).Perhaps someone else knows that workaround, or even a legit way to do that.
This simply says that it expected a different node. If you pass the ObjectContainer to the serializer, than that’s gonna be the root node. You’re likely missing that outer node.
Even if you fixed that, you’d still miss those other list nodes.
I mean alternatively I could use a different xml reader from a different namespace since there are multiple anyway…
But im not going to give up though, im damn stubborn.
What do you mean exactly with “you are missing the parent node”?
Sorry, I was about to respond again but forgot to finish the response and didn’t post it.
So I’ve digged around and the workaround I spoke about earlier was just as easy as decorating the lists with an XmlElement attribute specifying an empty string as the name, with the downside that you cannot use XmlArray and XmlArrayItem attributes.
As far as I can tell, this produces your desired structure for the outcome, but as a consequence, you’re bound to using the members names.
At least this hides the extra tags for the collections and only writes the entries, but I could imagine you’re not 100% satisfied with that either.
Next up:
You can check whether your target backend uses the attributes OnSerialized and OnDeserialized for the XmlSerializer, but this appears to not be supported for xml serialization in general, at lest that’s what the results of a quick search suggest. I do know that it didn’t work in Unity 5.x with .Net 3.5 (I always assumed this was only a mono detail), so maybe don’t get too excited about it.
Another workaround, as opposed to completely re-writing the serializer, write such surrogate types like you’ve already done, and keep track of registered surrogate types in your wrapper around the serializer, or pass an extra argument which serves as some sort of converter from / to the surrogate type. Or create an extension-based solution.
Re-reading and approaching what you are describing, the second method seems to be easier to apply (and understand but that might be me) I just have to figure out why I am getting issues related to the structure described in the XML. But thats more a debugging issue and a I-dont-know-how than anything else.
Edit 2: would it be advisable to get this data from the list structure and put it in a dictionary for easy searching? Or should I loop the list and search for it myself (putting it into a dictionary would be rather slow at start, but might save time when I need to set the data.)