Using XML to Store Colours and Strings in Unity - c#

I'm building an application where the user needs to be able to change the font colour and size (and the colour of the main camera), plus input an 'unlimited' amount of other data.
I'll admit that I've never used XML before, and am a relative beginner to C# as well. I've done a couple of days of searching on this topic already and the tutorials were all either writing from the wrong language or too vague in their explanations for me to completely understand what I was doing (I've included a list of the links I visited at the end of this question).
Ideally, I'd want to save to two locations, with the following format (apologies for the actual data - I'm not sure of the format some of it saves in):
Application.persistentDataPath, "prefs.xml"
<settings>
<font>
<colour>#FFFFFFFF</colour>
<size>14</size>
</font>
<camera>
<colour>#00000000</colour>
</camera>
</settings>
Application.persistentDataPath, "data.xml"
<data>
<section1>
<item1>
<date>01/01/2001</date>
<details>Details here</details>
<imagepath>Application.persistentDataPath, "image1.jpg"</imagepath>
</item1>
<item2>
<date>03/01/2001</date>
<details>Details here</details>
</item2>
</section1>
<section2>
<item1>
<date>02/01/2001</date>
<details>Details here</details>
</item1>
</section2>
</data>
To choose the colours, I'm using a package from the asset store called Color Picker and using two instances of this, with their game objects named as FontPicker and BackgroundPicker. I know that the next two scripts are completely over the place, but I'm putting them in as reference to what I've fiddled around with so far and what my variable names look like (script mainly edited using the YouTube video linked as reference). The code I currently have in my Settings.cs script is as follows:
public class Settings : MonoBehaviour {
public Color SelectedColor {get; set;}
public void Write (string fileName) {
using (var stream = new FileStream(fileName, FileMode.Create)) {
XmlSerializer XML = new XmlSerializer(typeof(ColorPicker));
XML.Serialize(stream, this);
}
}
public void FontColour () {
var FontPicker = GameObject.Find("FontPicker").GetComponent(ColorPicker);
// I get the following compiler errors in the Unity editor:
// Assets/Scripts/Settings.cs(23,77): error CS0119: Expression denotes a `type', where a `variable', `value' or `method group' was expected
// Assets/Scripts/Settings.cs(23,64): error CS1502: The best overloaded method match for `UnityEngine.GameObject.GetComponent(System.Type)' has some invalid arguments
// Assets/Scripts/Settings.cs(23,64): error CS1503: Argument `#1' cannot convert `object' expression to type `System.Type'
Color = ColorPicker.SelectedColor;
// Assets/Scripts/Settings.cs(24,37): error CS0120: An object reference is required to access non-static member `ColorPicker.SelectedColor'
// These same compiler errors are present in the next function
}
public void BackgroundColour () {
var BackgroundPicker = GameObject.Find("BackgroundPicker").GetComponent(ColorPicker);
Color = ColorPicker.SelectedColor;
}
}
I was hoping I'd be able to write to XML from within these separate functions so that I can call them individually when a colour is selected, but I have no idea where everything's meant to go and which bit of code is meant to be in which script.
From the YouTube video linked below, I also have this script, which ties in in some way:
[XmlRoot ("Preferences")]
public class XMLSerialiser {
[XmlArray ("Font"), XmlArrayItem("Colour")]
public Settings[] Settings;
public void Write(string path){
var serialiser = new XmlSerializer(typeof(XMLSerialiser));
using (var stream = new FileStream(path, FileMode.Create)) {
serialiser.Serialize(stream, this);
}
}
public static Settings Read(string path) {
var serialiser = new XmlSerializer(typeof(XMLSerialiser));
using (var stream = new FileStream(path, FileMode.Open)) {
return serialiser.Deserialize(stream) as XMLSerialiser;
}
}
}
What I'd be very grateful for is if someone could either point me in the direction of a good tutorial or explain to me, in very simple terms, how I would go about doing this - what I would have to write, which script I'd have to write it in, and where I'd have to put it in the Unity Editor. I'm very sorry my explanation took up as much space as it did.
Links used:
How to store and retrieve objects using XML?
http://unitynoobs.blogspot.co.uk/2011/02/xml-loading-data-from-xml-file.html
XML Data management in .NET
https://www.youtube.com/watch?v=zAn-ZbJqS90
http://wiki.unity3d.com/index.php?title=Saving_and_Loading_Data%3A_XmlSerializer
http://unitynoobs.blogspot.co.uk/2011/04/xml-writing-to-existing-xml-file.html
Append XML string block to existing XmlDocument
How can I build XML in C#?
Part of what I'm getting confused with is the fact that many of these links seems to use completely different methods to go about it. Take a look at the following two links, for example:
Writing a XML file to iOS with Unity (Using C#)
How to write an XML file in C# in Unity?
Is there a difference between these methods? Is one more preferable than the other?

So I managed to wrap my head around where I wanted things to go, thanks to a few more days of research (and especially this link).
I'm answering my own question so it may help someone else who has the same problem (and because I guess it's a bit silly to leave it unanswered now I have the solution).
To get around the issue of overwriting things, I used the method shown in the link, making an if loop to check whether the file existed. In the first section (the case where the file does not already exist), I used xmlWriter to put things together. XmlWriter appears to be a lot simpler than the method I use in the other section, with the downside that you don't seem to be able to use it without overwriting everything (hence only using it when the file doesn't already exist).
In the case that the file does exist, xDocument is brilliant. I modified the code from the link so that it replaced elements rather than adding new ones (although that can still be done). For this to work, include system.linq and system.xml (possibly system.xml.linq as well). Here is an example:
// Loads the document
XDocument xDocument = XDocument.Load("document.xml");
// Specifies the encoding
new XDeclaration("1.0","utf-8","yes");
// Follows the route settings-font and replaces the current colour
// element with a new one
xDocument.Element("Settings").Element("Font").Element("Colour").ReplaceWith(new XElement("Colour",fontColourHex));
// As above
xDocument.Element("Settings").Element("Background").Element("Colour").ReplaceWith(new XElement("Colour",backgroundColourHex));
// One of the more important things to note is that everything saved in an
// element ought to be a string, and the xml file doesn't like element
// names with spaces in them (use an underscore instead)
xDocument.Element("Settings").Element("Font").Element("Size_Modifier").ReplaceWith(new XElement("Size_Modifier",globalFontSizeModifier.ToString()));
// Saves the document
xDocument.Save("document.xml");
If you're wondering why there is no whitespace between words in this code, it's because, for some reason, Mono doesn't like it. I did find a fix somewhere else for it, but I couldn't get it to work and deleting the whitespace does the job fine.
The resultant file looked like this:
<?xml version="1.0" encoding="utf-8"?>
<Settings>
<Font>
<Colour>FFFFFFFF</Colour>
<Size_Modifier>0</Size_Modifier>
</Font>
<Background>
<Colour>000000FF</Colour>
</Background>
</Settings>
After I modified it and tied it to a couple of functions, all I had to do was create a new game object with the script attached and use it as needed.

Related

Read/Write array to a file

I need guidance, someone to point me in the right direction. As the tittle says, I need to save information to a file: Date, string, integer and an array of integers. And I also need to be able to access that information later, when an user wants to review it.
Optional: File is plain text and I can directly check it and it is understandable.
Bonus points if chosen method can be "easily" converted to working with a database in the future instead of individual files.
I'm pretty new to C# and what I've found so far is that I should turn the array into a string with separators.
So, what'd you guys suggest?
// JSON.Net
string json = JsonConvert.SerializeObject(objOrArray);
File.WriteAllText(path, json);
// (note: can also use File.Create etc if don't need the string in memory)
or...
using(var file = File.Create(path)) { // protobuf-net
Serializer.Serialize(file, objOrArray);
}
The first is readable; the second will be smaller. Both will cope fine with "Date, string, integer and an array of integers", or an array of such objects. Protobuf-net would require adding some attributes to help it, but really simple.
As for working with a database as columns... the array of integers is the glitch there, because most databases don't support "array of integers" as a column type. I'd say "separation of concerns" - have a separate model for DB persistence. If you are using the database purely to store documents, then: pretty much every DB will support CLOB and BLOB data, so either is usable. Many databases now have inbuilt JSON support (helper methods, etc), which might make JSON as a CLOB more tempting.
I would probably serialize this to json and save it somewhere. Json.Net is a very popular way.
The advantage of this is also creating a class that can be later used to work with an Object-Relational Mapper.
var userInfo = new UserInfoModel();
// write the data (overwrites)
using (var stream = new StreamWriter(#"path/to/your/file.json", append: false))
{
stream.Write(JsonConvert.SerializeObject(userInfo));
}
//read the data
using (var stream = new StreamReader(#"path/to/your/file.json"))
{
userInfo = JsonConvert.DeserializeObject<UserInfoModel>(stream.ReadToEnd());
}
public class UserInfoModel
{
public DateTime Date { get; set; }
// etc.
}
for the Plaintext File you're right.
Use 1 Line for each Entry:
Date
string
Integer
Array of Integer
If you read the File in your code you can easily seperate them by reading line to line.
Make a string with a specific Seperator out of the Array:
[1,2,3] -> "1,2,3"
When you read the line you can Split the String by "," and gets a Array of Strings. Parse each Entry to int into an Array of Int with the same length.
How to read and write the File get a look at Easiest way to read from and write to files
If you really wants the switch to a database at a point, try a JSON Format for your File. It is easy to handle and there are some good Plugins to work with.
Mfg
Henne
The way I got started with C# is via the game Space Engineers from the Steam Platform, the Mods need to save a file Locally (%AppData%\Local\Temp\SpaceEngineers\ or %AppData%\Roaming\SpaceEngineers\Storage\) for various settings, and their logging is similar to what #H. Sandberg mentioned (line by line, perhaps a separator to parse with later), the upside to this is that it's easy to retrieve, easy to append, easy to overwrite, and I'm pretty sure it's even possible to retrieve File Size, which when combined with File Deletion and File Creation can prevent runaway File Sizes as this allows you to set an Upper Limit to check against, allowing you to run it on a Server with minimal impact (probably best to include a minimum Date filter {make sure X is at least Y days old before deleting it for being over Z Bytes} to prevent Debugging Data Loss {"Why was it over that limit?"})
As far as the actual Code behind the idea, I'm approximately at the same Skill Level as the OP, which is to say; Rookie, but I would advise looking at the Coding in the Space Engineers Mods for some Samples (plus it's not half bad for a Beta Game), as they are almost all written in C#. Also, the Programmable Blocks compile in C# as well, so you'll be able to use that to both assist in learning C# and reinforce and utilize what you already know (although certain C# commands aren't allowed for security reasons, utilizing the Mod API you'll have more flexibility to do things such as Creating/Maintaining Log Files, Retrieving/Modifying Object Properties, etc.), You are even capable of printing Text to various in Game Text Monitors.
I apologise if my Syntax needs some work, and I'm sorry I am not currently capable of just whipping up some Code to solve your issue, but I do know
using System;
Console.WriteLine("Hello World");
so at least it's not a total loss, but my example Code likely won't compile, since it's likely missing things like: an Output Location, perhaps an API reference or two, and probably a few other settings. Like I said, I'm New, but that is a valid C# Command, I know I got that part correct.
Edit: here's a better attempt:
using System;
class Test
{
static void Main()
{
string a = "Hello Hal, ";
string b = "Please open the Airlock Doors.";
string c = "I'm sorry Dave, "
string d = "I'm afraid I can't do that."
Console.WriteLine(a + b);
Console.WriteLine(c + d);
Console.Read();
}
}
This:
"Hello Hal, Please open the Airlock Doors."
"I'm sorry Dave, I'm afraid I can't do that."
Should be the result. (the "Quotation Marks" shouldn't appear in the readout {the last Code Block}, that's simply to improve readability)

Roslyn: Continuously shadow-copy & syntax-transform a document as it is being changed

I'm writing a script editor that needs to transform the user's input (which is similar to script syntax) into valid C# according to some rules I define. For example, if the users puts in
using System;
public string hello()
{
return "Hi!" // whoops, semicolon here is missing!
}
I'd need to transform this to
using System;
public class ContainerClass
{
public string hello()
{
return "Hi!" // whoops, semicolon here is missing!
}
}
my transformation will insert new nodes (such as the class declaration) and might move around existing ones, but it will never modify or remove existing ones. (I know SourceCodeKind = Script does something vaguely similar, but I can't use that for a variety of reasons).
Now I need to come up with a way to do this transformation given the following considerations:
Since I need to run the transformation each time the user changes the original document (i.e. just types a single letter), I can't afford to re-parse the entire thing every time from a performance perspective. For example, if the user inserts the missing semicolon after ";", ideally I would just insert the same (or cloned) node into my already transformed document, instead of re-parsing everything. I suppose that rules out standard ways of modification such as DocumentEditor.
I need to have a way to re-map locations from my transformed document to locations in the original document. Since I will never delete nodes, I think theoretically this should be possible (but how?).
This is necessary e.g. as I would end up with diagnostic messages (and intellisense information etc.) pointing to locations in the transformed document, and need to get the original document's location for these to actually show them to the user.
Can anyone thing of a more or less direct way to do this? Is there maybe even some Roslyn helper classes for use cases like this?
My ideas below. I'm not quite sure they'd work, and I think they'd be very hard to implement, so I'm hoping there is some easier way to be honest;
For #1, my only idea was to get get the text changes (Document.GetTextChangesAsync) of the original document after its source code changes; and then somehow try to find out what nodes have been affected by this (maybe get nodes that intersect the edited area in the old and new document, then compute which ones have been deleted, added or modified) - and then to apply these changes in my transformed document. This seems awfully complex though.
For #2, my only idea so far was to enable tracking for nodes of the original document. Then I would find whatever node a location points to in the transformed document, and find the node in the original document this originated from (and then find the location of that node).
But the problem is that e.g. the code above would produce a diagnostic error pointing towards the location right after "Hi!", wich the location span's length of 0, as there's a semicolon missing. So the location doesn't really point to a node at all. Maybe I could try finding adjacent nodes in that case?!

T4 Code Generation In the Abstract (No Automatic Compilation, No Automatic File Management)

We have a circumstance where we basically want to generate string representation of a code file by passing in arguments for the template contents and information the template needs to build itself:
//*** PSEUDO CODE *** //
//loaded from an embedded resource file in a .dll. no physical file on file system
string templateContents = ...;
//has properties used by the template
object complexParameter = ...;
string generatedCode = generator.MakeCode(templateContents, complexParameter);
However, we're currently running into problems trying to get the T4 template generation to do what we want. The actual code we're using is:
var templatingEngine = new Engine();
//T4TextTemplateHost is our own class implementing ITextTemplatingEngineHost & IServiceProvider
var templateHost = new T4TextTemplateHost(references, imports)
{
Properties = parameters,
//this is supposed to be a file path? the generation bombs if this is left null
TemplateFile = "Dummy Value"
};
var templateContents = GetTemplateFileContents();
var retVal = templatingEngine.ProcessTemplate(templateContents, templateHost);
//if a CompilerError occurs, we get NO code, just a "ErrorGeneratingOutput" message
foreach (CompilerError error in templateHost.Errors)
//this information is pretty worthless: a compile error with line number for a
//non-existant code file
retVal += String.Format("{0}{2}Line: {1}{2}{2}", error.ErrorText,
error.Line, Environment.NewLine);
The problem is that the code generator seems to expect a physical file somewhere, and when things go wrong, we don't get code back, we get useless error messages back. It is our strong preference not NOT have the code automatically compiled, especially when the generated code has an error (we want a full, broken file to examine when troubleshooting).
We also want the output as a string, that we can take and do with whatever we wish.
Is there a way to make T4 code generation work more like the pseudo code example? We're on the verge of abandoning the T4 tool in favor of something like CodeSmith because T4 seems like it's too limited/geared toward a very specific way of managing templates and processing output.
I don't think it is possible to get T4 to generate anything if there are errors in the template you pass in. T4 will try to convert your template into codedom with extra statements that write out to a stringwriter, the final stringwriter is then returned as the result. If there are any errors in the template, the code will not complie and thus it will have nothing to return to you. The errors you get back should resolve to the lines in the template you passed in, at least that has been my experience.
I am not sure if Code Smith works in a different way but depending on the complexity of what you are trying to render you might have some luck using Nustache if it's simple enough. It's a dot net version of mustache templates. It supports basic looping and if/then type control blocks. I have successfully used it with embedded text files to generate simple templates for emailing and reports.

getting element offset from XMLReader

how's everyone doing this morning?
I'm writing a program that will parse a(several) xml files.
This stage of the program is going to be focusing on adding/editing skills/schools/abilities/etc for a tabletop rpg (L5R). What I learn by this one example should carry me through the rest of the program.
So I've got the xml reading set up using XMLReader. The file I'm reading looks like...
<skills>
<skill>
<name>some name</name>
<description>a skill</description>
<type>high</type>
<stat>perception</stat>
<page>42</page>
<availability>all</availability>
</skill>
</skills>
I set up a Skill class, which holds the data, and a SkillEdit class which reads in the data, and will eventually have methods for editing and adding.
I'm currently able to read in everything right, but I had the thought that since description can vary in length, once I write the edit method the best way to ensure no data is overwritten would be to just append the edited skill to the end of the file and wipe out its previous entry.
In order for me to do that, I would need to know where skill's file offset is, and where /skill's file offset is. I can't seem to find any way of getting those offsets though.
Is there a way to do that, or can you guys suggest a better implementation for editing an already existing skill?
If you read your XML into LINQ to XML's XDocument (or XElement), everything could become very easy. You can read, edit, add stuff, etc. to XML files using a simple interface.
e.g.,
var xmlStr = #"<skills>
<skill>
<name>some name</name>
<description>a skill</description>
<type>high</type>
<stat>perception</stat>
<page>42</page>
<availability>all</availability>
</skill>
</skills>
";
var doc = XDocument.Parse(xmlStr);
// find the skill "some name"
var mySkill = doc
.Descendants("skill") // out of all skills
.Where(e => e.Element("name").Value == "some name") // that has the element name "some name"
.SingleOrDefault(); // select it
if (mySkill != null) // if found...
{
var skillType = mySkill.Element("type").Value; // read the type
var skillPage = (int)mySkill.Element("page"); // read the page (as an int)
mySkill.Element("description").Value = "an AWESOME skill"; // change the description
// etc...
}
No need to calculate offsets, manual, step-by-step reading or maintaining other state, it is all taken care of for you.
Don't do it! In general, you can't reliably know anything about physical offsets in the serialized XML because of possible character encoding differences, entity references, embedded comments and a host of other things that can cause the physical and logical layers to have a complex relationship.
If your XML is just sitting on the file system, your safest option is to have a method in your skill class which serializes to XML (you already have one to read XML already), and re-serialize whole objects when you need to.
Tyler,
Umm, sounds like you're suffering from a text-book case of premature optimization... Have you PROVEN that reading and writing the COMPLETE skill list to/from the xml-file is TOO slow? No? Well until it's been proven that there IS NO PERFORMANCE ISSUE, right? So we just write the simplest code that works (i.e. does what we want, without worrying too much about performance), and then move on directly to the next bit of trick functionality... testing as we go.
Iff (which is short for if-and-only-if) I had a PROVEN performance problem then-and-only-then I'd consider writing each skill to individual XML-file, to avert the necessisity for rewriting a potentially large list of skills each time a single skill was modified... But this is "reference data", right? I mean you wouldn't de/serialize your (volatile) game data to/from an XML file, would you? Because an RDBMS is known to be much better at that job, right? So you're NOT going to be rewriting this file often?
Cheers. Keith.

Transform XML into HTML using XSLT

I saw several questions related to XML, XSLT and HTML on stackoverflow, but I beleive my question is a little different. Here is what I need:
I need to send an email to my customers. The wordings of the email can differ based upon the scenario. Each scenario has a corresponding format saved in the database. For example, one scenario might require this email:
Scenario 1: We have opened Ticket XXX/Reference Number XXX for your call on XXX. Kindly call us at XXX to track progress.
Another scenario might required this email:
Scenario 2: Thanks for your interest in our Product XXX. As discussed we will send our representative on XXX to your office located at XXX.
Also, the format might need to be altered a bit depending upon data availability. e.g. if I need to send email corresponding to scenario 1, and I don't have Reference Number available, I want to remove the reference number part completely on the fly (not in database) i.e. I want something like this:
Scenario 3: We have opened Ticket XXX for your call on XXX. Kindly call us at XXX to track progress.
The formats for scenarios 1 and 2 are stored in the database as XSLT corresponding to the strings you see above. Format for Scenario 3 has to be produced on the fly. The XXX part has to be replaced with actual data. This data is available in an XML serializable object that I have.
I want to serialize this object, produce an XML in memory, modify the XSLT a little (on the fly) to reflect the data I have, transform the XML in memory to HTML using the XSLT for the scenario and then pass the HTML as a string parameter to an email method I have. The email part works. I need to work on the Object->XML in memory->Slight XSLT modification-> HTML using appropriate XSLT.
I would appreciate if you can include code examples and not just the approach I need to follow.
EDIT:
Here is the working code:
using (xsltStream = new MemoryStream(emailInfo.Body))
{
// Create an XmlReader from the Stream
XmlReader reader = XmlReader.Create(xsltStream);
// Create and load the transform with document function enabled.
XslCompiledTransform transform = new XslCompiledTransform();
XsltSettings settings = new XsltSettings();
settings.EnableDocumentFunction = true;
transform.Load(reader, settings, null);
// Execute the transformation.
transform.Transform(doc, writer);
}
The formats for scenarios 1 and 2 are stored in the database as XSLT
I think I would be quite inclined to store the formats as XML rather than as XSLT:
<message>Thanks for your interest in our Product <product/>. As discussed we will send our representative on <date/> to your office located at <officeLocation/>.</message>
Then you use a standard stylesheet to transform this document, using data from another document.
Having said this, I only recommend this because I did it myself the way you are describing and regret it, as it's too difficult to make changes or introduce new variations.
Based upon comments from #harpo, #Alexei Levenkov and #Alejandro, I was able to work out a working version of the code which uses multiple templates. Since I can't mark the comments as answers, I will mark this as answer and add the code in my question.
using (xsltStream = new MemoryStream(emailInfo.Body))
{
// Create an XmlReader from the Stream
XmlReader reader = XmlReader.Create(xsltStream);
// Create and load the transform with document function enabled.
XslCompiledTransform transform = new XslCompiledTransform();
XsltSettings settings = new XsltSettings();
settings.EnableDocumentFunction = true;
transform.Load(reader, settings, null);
// Execute the transformation.
transform.Transform(doc, writer);
}

Categories