Roy Triesscheijn’s Weblog

My programming world

XNA XML ContentImporter

Posted by Roy Triesscheijn on 7th August 2010

Quite often you’d like to use XML files in your games. You can use the standard XNA XML Importer for this, however then you’d need to write specific class that serialize to and from the special XNA XML documents that have a <XNAContent> tree.

If you’re trying to build your levels in XML or if you’re trying to set a high volume of settings this might not be the easiest way. Easier would be to just parse a hand written XML document yourself. Luckily this is very easy to accomplish.

The naive way of doing this is by just writing a custom content importer that would look similar to this:

[ContentImporter(".xml", DisplayName = "XML Content Importer", DefaultProcessor = "None")]  //TImport was defined as System.Xml.XmlDocument a bit higher
    public class XMLContentImporter : ContentImporter<TImport>
    {
        public override TImport Import(string filename, ContentImporterContext context)
        {
            XmlDocument document = new XmlDocument();
            try
            {
                document.Load(filename);
            }
            catch (Exception e)
            {
                //Write error logger here or update the exception with more information
                throw e;
            }
            return document;
        }
    }

(Don’t forget to add a reference to your content pipeline project to the Content project inside your game’s project)

Now the following code doesn’t work and will throw an error similar to this:

Error   1   Building content threw InvalidOperationException: Cyclic reference found while serializing System.Xml.XmlDocument. You may be missing a ContentSerializerAttribute.SharedResource flag.

Now I’m not going to be holier than the pope, I actually made the same error as you can blatantly see here.

Unfortunately the error message is very unhelpful in this case, and might even throw you off completely since the error is not visual from the actual content file itself. Any XML file while give you this error.

Luckily user Saw was sitting around in #XNA on Efnet (IRC). He told me that the error comes from the fact that I’ve written no ContentWriter (and ContentReader). This means that reflection is used to serialize the XML document.

An XmlDocument has a lot of XMLNodes. An XmlNode has the member variable parent, which is an XmlNode, which has the member variable ‘parent’, which…. Well you get the idea. Probably the recursion error comes from there.

He then showed me how two write a very simple reader and writer. If we extend our content importer with these two extra classes this problem is easily solved.

[ContentTypeWriter()]
    public class CustomXmlWriter : ContentTypeWriter<XmlDocument>
    {
        protected override void Write(ContentWriter output, XmlDocument value)
        {
            value.Save(output.BaseStream);
        }

        public override string GetRuntimeReader(TargetPlatform targetPlatform)
        {
            return typeof(CustomXmlReader).AssemblyQualifiedName;
        }
    }

    public class CustomXmlReader : ContentTypeReader<XmlDocument>
    {
        protected override XmlDocument Read(ContentReader input, XmlDocument existingInstance)
        {
            if (existingInstance == null)
            {
                existingInstance = new XmlDocument();
            }
            existingInstance.Load(input.BaseStream);
            return existingInstance;
        }
    }

The custom content writer just uses the XMLDocuments built in Save function which is aware of these cycles and deals with them properly, the other method is overload to tell XNA which content reader to use for this type. The content reader again uses the simple built in Load function, nothing special here.

However now compile your project again, see the errors are gone :) .

Let’s add a simple XML file to your content project, I’ve named it hello.xml.

<?xml version="1.0" encoding="utf-8" ?>
<MyTag>Hello World!</MyTag>

Click the file in the solution explorer and set the build action to ‘Compile’ and the Content Processor to ‘XML Content Importer’ (you can change the name by changing the DisplayName attribute). Note: you might need to recompile before you can do this.

Load and display it like this:

XmlDocument xml = Content.Load<XmlDocument>(@"Tests\xmltest");
XmlNode node = xml.SelectSingleNode("//MyTag");
Console.Out.WriteLine(node.InnerText);

The expression “//MyTag” is an XPATH expression to query the XML document, you can learn more about that here.

I hope you have a lot of fun with this code, and that it makes life a little bit easier for you, either by handling XML files for you, or by being a small source on how to write custom ContentTypeWriters and readers.

Tags: , , , , ,
Posted in Blog, General Coding, General Gamedesign, XNA | No Comments »

Tutorial: Using the XNA Content Pipeline for localization: Part 2, Assets

Posted by Roy Triesscheijn on 20th February 2010

(read about part 1 here)

SgtConker has uploaded the second part of my tutorial on localization. This time we automatically localize assets and the code is going to make a lot more sense. (The way strings where localized might look a bit odd without seeing part 2).

Anyway without further introduction, you can find part 2 at sgtconker.com here.

First screen

kick it on GameDevKicks.com

Tags: , , , , ,
Posted in Blog, General Gamedesign, XNA | No Comments »

Tutorial: Using the XNA Content Pipeline for localization: Part 1, Strings

Posted by Roy Triesscheijn on 30th January 2010

Lately I’ve been working on making localization easier. I came up with an idea to use the content pipeline and content manager to automatically localize your strings*, textures and sounds (and any other content type you might want to localize).

In part one we create a new content pipeline extension and a new content manager without much effort. And we start to replace our strings with calls to our new content manager (that replaces the default one). In the end you will be able to load a string using Content.Load<String>(“Package.Key”); The correct string will be loaded from our xnb files (imported using a custom xml importer) in the language set in the content manager.

In part two we are going to extend this example to load other content as normal (Content.Load<Texture2D>(@”Folder\Name”);). And if you’ve created a localized version this will be loaded instead of the original file.

Part 1 is now up at SgtConker.Com: http://www.sgtconker.com/2010/01/article-using-xna-content-pipeline-extensions-for-localization/

Feel free to post any comments and questions either here or at sgtconker.com.

Part 2 will probably be up end of next week.

kick it on GameDevKicks.com

Tags: , , , , , ,
Posted in Blog, General Gamedesign, XNA | No Comments »