Loading an XML file via the Monogame content pipeline? - c#

I am currently trying to port a game I am working on from XNA to Monogame, but I am having some trouble getting the content pipeline to cooperate. My game uses a number of XML files as XNB assets to represent objects in the game, which I created following the instructions here. However, trying to move this across word-for-word into Monogame produces the following error:
An unhandled exception of type 'Microsoft.Xna.Framework.Content.ContentLoadException' occurred in MonoGame.Framework.dll
Additional information: Could not load Parts\Window.xnb asset as a non-content file!
Here is the class I have been using to define the content of the XML files:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace FileTypes
{
public class PartFile
{
public struct State
{
public Rectangle[] frames;
public int[] collision;
}
public Vector2 size;
public string image;
public string defaultState = "default";
public bool fillBG;
public int ticksPerFrame;
public Dictionary<string, State> states;
public string[] modules;
}
}
Here is one of the XML files in the project:
<?xml version="1.0" encoding="utf-8" ?>
<XnaContent>
<Asset Type="FileTypes.PartFile">
<size>2 3</size>
<image>Computer</image>
<defaultState>default</defaultState>
<fillBG>false</fillBG>
<ticksPerFrame>7</ticksPerFrame>
<states>
<Item>
<Key>default</Key>
<Value>
<frames>
0 0 40 60
40 0 40 60
</frames>
<collision>
0 0
0 0
0 0
</collision>
</Value>
</Item>
<Item>
<Key>active</Key>
<Value>
<frames>
80 0 40 60
120 0 40 60
</frames>
<collision>
0 0
0 0
0 0
</collision>
</Value>
</Item>
</states>
<modules>
<Item>testModule</Item>
</modules>
</Asset>
</XnaContent>
And finally here is a sample of the code I use to load the file:
FileTypes.PartFile srcPart = content.Load<FileTypes.PartFile>("Parts\\" + name);
Does anybody know what I need to do in order to get my code working in Monogame? I've been looking around the internet for a fair while, but so far I've yet to find a solution to my issue. Alternatively, if I've been going about the entire system wrong all this time and there's a far easier way to handle what I want to do, I'd love to hear it.
Thanks in advance!

I wrote a rather long tutorial about loading custom content with the MonoGame Pipeline. Here's the summary
Create a new project to hold your content importer, processor and writer
Create the content importer
Create the content processor
Create the content type writer
Create the content type reader
Reference the DLL from the Pipeline tool
Read the content into your game
Creating the importer, processor, reader and writer is the same as XNA. You can refer to the MSDN documentation for that.
The tricky part is getting your DLL to work with the Pipeline tool. To add it as a reference look for the the References property of the root tree node.

Unless I'm mistaken, you need to set the build action of the file to "Content" and the copy to output directory to "copy if newer" in the properties tab for the file

Content.Load<T> uses Microsoft.Xna.Framework.Content.Pipeline.IntermediateSerializer for an xml. Monogame doesn't support Microsoft.Xna.Framework.Content.Pipeline. It means you cannot deserialize an intermediate xml.

Monogame comes with content processors for XML. Open up the content pipeline app and add an XML file; you will see it has the importer XmlImporter and processor PassThroughProcessor.
Take a look at #craftworkgames excellent tutorials for how to extend the behaviour.

Related

cXML .net XMLReader error The parameter entity replacement text must nest properly within markup declarations

Is there something I need to configure in the XmlReaderSettings to encourage .net (4.8, 6, 7) to handle some cXML without throwing the following exception:
Unhandled exception. System.Xml.Schema.XmlSchemaException: The parameter entity replacement text must nest properly within markup declarations.
Sample cXML input
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE cXML SYSTEM "http://xml.cxml.org/schemas/cXML/1.2.041/cXML.dtd">
<cXML payloadID="donkeys#example.com" timestamp="2023-02-13T01:01:01Z">
<Header>
</Header>
<Request deploymentMode="production">
</Request>
</cXML>
Sample Application
using System.Xml;
using System.Xml.Linq;
namespace Donkeys
{
internal class Program
{
static void Main()
{
XmlReaderSettings settings = new()
{
XmlResolver = new XmlUrlResolver(),
DtdProcessing = DtdProcessing.Parse,
ValidationType = ValidationType.DTD,
};
FileStream fs = File.OpenRead("test.xml"); // sample cXML from question
XmlReader reader = XmlReader.Create(fs, settings);
XDocument.Load(reader); // this blows up
}
}
}
I'm looking to use the XmlUrlResolver to cache the DTDs but without ignoring the validation I get the error above but i'm not really sure why?
So far I've tried different validation flags but they don't validate at all unless I use ValidationType.DTD which goes pop.
The actual resolver seems to work fine; if I subclass it, it is returning the DTD (as a MemoryStream) as expected.
I can add an event handler to ignore the issue but this feels lamer than I'd like.
using System.Xml;
using System.Xml.Linq;
namespace Donkeys
{
internal class Program
{
static void Main()
{
XmlReaderSettings settings = new()
{
XmlResolver = new XmlUrlResolver(),
DtdProcessing = DtdProcessing.Parse,
ValidationType = ValidationType.DTD,
IgnoreComments = true
};
settings.ValidationEventHandler += Settings_ValidationEventHandler;
FileStream fs = File.OpenRead("test.xml");
XmlReader reader = XmlReader.Create(fs, settings);
XDocument dogs = XDocument.Load(reader);
}
private static void Settings_ValidationEventHandler(object? sender, System.Xml.Schema.ValidationEventArgs e)
{
// this seems fragile
if (e.Message.ToLower() == "The parameter entity replacement text must nest properly within markup declarations.".ToLower()) // and this would be a const
return;
throw e.Exception;
}
}
}
I've spent some time over the last few days looking into this and trying to get my head around what's going on here.
As far as I can tell, the error The parameter entity replacement text must nest properly within markup declarations is being reported incorrectly. My understanding of the spec is that this message means that you have mismatched < and > elements in the replacement text of a parameter entity in a DTD.
The following example is taken from this O'Reilly book sample page and demonstrates something that genuinely should reproduce this error:
<!ENTITY % finish_it ">">
<!ENTITY % bad "won't work" %finish_it;
Indeed the .NET DTD parser reports the same error for these two lines of DTD.
This doesn't mean you can't have < and > characters in parameter entity replacement text at all: the following two lines will declare an empty element with name Z, albeit in a somewhat round-about way:
<!ENTITY % Nested "<!ELEMENT Z EMPTY>">
%Nested;
The .NET DTD parser parses this successfully.
However, the .NET DTD parser appears to be objecting to this line in the cXML DTD, which defines the Object.ANY parameter entity:
<!ENTITY % Object.ANY '|xades:QualifyingProperties|cXMLSignedInfo|Extrinsic'>
There are of course no < and > characters in the replacement text, so the error is baffling.
This is by no means a new problem. I found this unanswered Stack Overflow question which basically reports the same problem. Also, this MSDN Forum post basically has the same problem, and it was asked in 2007. So is this unclear but intentional behaviour, or a bug that has been in .NET for 15+ years? I don't know.
For those who do want to look into things further, the following is about the minimum necessary to reproduce the problem. The necessary C# code to read the XML file can be taken from the question and adapted, I don't see the need to repeat it here:
example.dtd:
<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT A EMPTY>
<!ENTITY % Rest '|A' >
<!ELEMENT example (#PCDATA %Rest;)*>
example.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE example SYSTEM "example.dtd">
<example/>
There are various ways to tweak this to get rid of the error. One way is to move the | character from the parameter entity into the ELEMENT example declaration. Replacing #PCDATA with another element (which you would also have to define) is another way.
But enough of the theory behind the problem. How can you actually move forwards with this?
I would take a local copy of the cXML DTD and adjust it to work around this error. You can download the DTD from the URL in your sample cXML input. The %Object.ANY; parameter entity is only used once in the DTD: I would replace this one occurrence with the replacement text, |xades:QualifyingProperties|cXMLSignedInfo|Extrinsic.
You then need to adjust the .NET XML parser to use your modified copy of the cXML DTD instead of fetching the the one from the given URL. You create a custom URL resolver for this, for example:
using System.Xml;
namespace Donkeys
{
internal class CXmlUrlResolver : XmlResolver
{
private static readonly Uri CXml1_2_041 = new Uri("http://xml.cxml.org/schemas/cXML/1.2.041/cXML.dtd");
private readonly XmlResolver urlResolver;
public CXmlUrlResolver()
{
this.urlResolver = new XmlUrlResolver();
}
public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
{
if (absoluteUri == CXml1_2_041)
{
// Return a Stream that reads from your custom version of the DTD,
// for example:
return File.OpenRead(#"SomeFilePathHere\cXML-1.2.401.dtd");
}
return this.urlResolver.GetEntity(absoluteUri, role, ofObjectToReturn);
}
}
}
This checks to see what URI is being requested, and if it matches the cXML URI, returns a stream that reads from your customised copy of the DTD. If some other URI is given, it passes the request to the nested XMLResolver, which then deals with it. You will of course need to use an instance of CXmlUrlResolver instead of XmlUrlResolver() when creating your XmlReaderSettings.
I don't know how many versions of cXML you will have to deal with, but if you are dealing with multiple versions, you might have to create a custom copy of the DTD for each version, and have your resolver return the correct local copy for each different URI.
A similar approach is given at this MSDN Forums post from 2008, which also deals with difficulties parsing cXML with .NET. This features a custom URL resolver created by subclassing XmlUrlResolver. Those who prefer composition over inheritance may prefer my custom URL resolver instead.

ASP.NET Core 3.1 using XmlSerializerFormatters()

I currently have an application based on .Net Core 2.2 which works. I need to move this project forward to .Net Core 3.1 but I cannot seem to get the XML Deserialized in the controller. In both apps I created a WCF connected service successfully. The WDSL now has more classes defined but are basically the same. I diffed the files and
Left handside is newly generated fill:
< [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
---
> [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.1-preview-30310-0943")]
This repeats with every class in Reference.cs. My problem is my Postman tests fail with the new controllers. By using Calculatus Eliminatus I have managed to track down the difference, The old parsing would accept:
<?xml version="1.0" encoding="UTF-8"?>
<ConnectedServiceRequestX xmlns="http://somename.com/api/01">
<Timestamp>2021-04-05T16:35:43</Timestamp>
<ApiKey>TopSecretKey</ApiKey>
<CustomerId>ABC</CustomerId>
</ConnectedServiceRequestX>
The new parser only works if the posted XML is like this:
<?xml version="1.0" encoding="UTF-8"?>
<ConnectedServiceRequestX>
<Timestamp xmlns="http://somename.com/api/01">2021-04-05T16:35:43</Timestamp>
<ApiKey xmlns="http://somename.com/api/01">TopSecretKey</ApiKey>
<CustomerId xmlns="http://somename.com/api/01">ABC</CustomerId>
</ConnectedServiceRequestX>
The new parser throws an exception when putting xmlns="http://somename.com/api/01" at class level XML Item. I need to support the older XML input as I have no ownership of the system accessing our service. This is a case where a big corporation is dictating the interface that they will use to access our data and we are a small outfit.
I am inclined to think there is some option I can supply to .XmlSerializerFormatters() such that the xmlns will default to what namespace is provided on the class level XML Item. Any help is appreciated.
The following should work :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(FILENAME);
XmlSerializer serializer = new XmlSerializer(typeof(ConnectedServiceRequestX));
ConnectedServiceRequestX request = (ConnectedServiceRequestX)serializer.Deserialize(reader);
}
}
[XmlRoot(Namespace = "http://somename.com/api/01")]
public class ConnectedServiceRequestX
{
public DateTime Timestamp { get; set; }
public string ApiKey { get; set; }
public string CustomerId { get; set; }
}
}
After studying the diffs between the Generated files (April 2019 and today) I noticed a one line difference preceding some of the classes. There are 60 plus classes in the C# file generated from the WDSL (I apologize for not noticing earlier). Anyhow, The line is as follows:
[XmlRootAttribute("ConnectedServiceRequestX", Namespace="http://somename.com/api/01", IsNullable = false)]
These lines we added at some point after the class generation but prior to their initial entry into source control. They require:
using System.Xml.Serialization;
to be added as well. What this does is allows the xmlns attribute to be placed in the outer tag (Class Item tag) and not have to be replicated in the inner tags as follows:
<?xml version="1.0" encoding="UTF-8"?>
<ConnectedServiceRequestX xmlns="http://somename.com/api/01">
<Timestamp>2021-04-05T16:35:43</Timestamp>
<ApiKey>TopSecretKey</ApiKey>
<CustomerId>ABC</CustomerId>
</ConnectedServiceRequestX>
Which is how my legacy tests with Postman were designed. I wrote (generated) this code when I was learning ASP.Net Web Services and I do not remember modifying the generated files to get the Connected Service / XML Post to work. So the short answer is when generating code (adding a Connected Service - WCF/WDSL) with Visual Studio there still may be some modifications to the Reference.cs file which allow more friendly XML to be posted to the endpoints.
I am now up and running (ASP.NET Core 3.1) using my legacy tests which give me confidence to go to production with the updated app. I hope this helps others.

Monogame XML serialization with Content pipeline

I've been trying to implement an XML system for items in my game.. but I just cant get it to work.
I am using Monogame and the content pipeline that comes with it.
I've made an Inventory Class and an Item Class.
Here are the snippets from the Inventory Class (which would have the serialization):
public class Inventory
{
[XmlElement("Item")]
public static List<Item> itemList;
public Inventory (Vector2 _position)
{
itemList = new List<Item>();
}
public void LoadContent()
{
XmlSerializer deserializer = new XmlSerializer(typeof(Item));
TextReader reader = new StreamReader("Content/Items/itemEntities.xml");
object obj = deserializer.Deserialize(reader);
Inventory XmlData = (Inventory)obj;
reader.Close();
}
}
And then I creaded an XML file:
<?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:ns="Microsoft.Xna.Framework">
<Asset Type="Game.Item[]">
<Item>
<itemType>Weapon</itemType>
<itemRarity>Rare</itemRarity>
<itemID>0001</itemID>
<positionID>
<X>1</X>
<Y>1</Y>
</positionID>
<name>The sword</name>
<description>Description</description>
</Item>
<Item>
<itemType>Equipment</itemType>
<itemRarity>Uncommon</itemRarity>
<itemID>0002</itemID>
<positionID>
<X>1</X>
<Y>1</Y>
</positionID>
<name>The Item</name>
<description>Description</description>
</Item>
<Item>
<itemType>Drone</itemType>
<itemRarity>Common</itemRarity>
<itemID>0003</itemID>
<positionID>
<X>1</X>
<Y>1</Y>
</positionID>
<name>The Drone</name>
<description>Description</description>
</Item>
</Asset>
</XnaContent>
The problem now is that I get the following error:
error: Importer 'XmlImporter' had unexpected failure!
Microsoft.Xna.Framework.Content.Pipeline.InvalidContentException: Could not resolve type 'Game.Item[]'.
I have read on some other questions here that I would have to make a reference, but I just can't find a way to make that. In the solution explorer I can see references, but when I click add, I dont see anything in Projects, only standard stuff like Frameworks etc. are there.
Oh and I have a constuctor with no arguments in Item class, so that should be fine.
One more thing. I have tried to write the list of items into an XML file and it worked perfectly..
Thanks in advance!
You need to add a reference to a compiled dll lib, which means, your types need to be defined in an external project.
Create another project that compiles into dll, define all your structs and classes there (at least those that need serialization), and add reference to the dll from the content manager by doing the following:
open content pipeline manager.
select root node ("Content").
On the properties tab click on "References".
Click on 'Add' and select the path to the dll file you generated from the other project.
I recently asked a similar question on the monogame community and currently its impossible to serialize types from your own project into / from xml without using another project that builds into a dll.

Cannot find ContentTypeReader

I have found a number of threads on this error but I haven't found a solution. I am using a number of class libraries from XNAExpert.com that are designed to animate a skinned mesh. I'm using XNA 4.0, Win Xp and programming games for Windows. Here is complete error:
Cannot find ContentTypeReader SkinnedModel.SkeletonReader, SkinnedModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
The tutorial can be found here . Here is the code from the reader class within SkinnedModel project:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
namespace SkinnedModel
{
public class SkeletonReader : ContentTypeReader<Skeleton>
{
protected override Skeleton Read(ContentReader input, Skeleton existingInstance)
{
List<Bone> boneList = input.ReadObject<List<Bone>>();
return new Skeleton(boneList);
}
}
}
Here is the code from the writer class from within SkinnedModelProcessor project:
[ContentTypeWriter]
public class SkeletonWriter : ContentTypeWriter<Skeleton>
{
protected override void Write(ContentWriter output, Skeleton value)
{
output.WriteObject(value.BoneList);
}
public override string GetRuntimeReader(TargetPlatform targetPlatform)
{
return typeof(SkeletonReader).AssemblyQualifiedName;
}
}
As you can see the type returned is the Assembly Qualified Name for each reader...Is anyone aware of another reason why I may be having trouble?
Solution for me was to just delete the ContentTypeReader and create a new one.
My problem seemed to be caused by having a mirrored project (I had Windows game library and Windows Phone game library). On Windows client the ContentReader was successfully found, but not on the Windows Phone client.
As i read it the SkeletonReader is known to the SkeletonWriter. I cannot think of a valid way to setup the projects so that this is true.
Project Main (Links To Content)
SkeletonReader
Skeleton
Project Content (Links To ContentExtendion)
SkeletonFile (Has Processor set to SkeletonProcessor)
Project ContentExtendion (Cannot link circular)
SkeletonContent (Is Input For Writer)
SkeletonWriter
SkeletonProcessor
Look at your ProjectSetup i think your assemblies are not linked correctly.
And return a fixed string in GetRuntimeReader - if you setup the projects correctly you will loose the connection to the SkeletonReader.
There is a quite complete tutorial on the content pipeline on the interwebs.

Reading event logs saved to XML format from .Net

I'm trying to read an event log saved as an XML file from .Net / C#, the event log xml format looks (approximately) like this:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Events>
<Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'>...</Event>
<Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'>...</Event>
</Events>
Where the ... bit is a relatively complex series of types defined in a schema file event.xsd.
My plan was to use XSD.exe to generate a C# wrapper type for reading this event log XML from this schema file and then deserialise the XML using the XmlSerializer class, like so:
using (FileStream stream = File.OpenRead(filename))
{
XmlSerializer serialiser = new XmlSerializer(typeof(Events));
return (Events)serialiser.Deserialize(stream);
}
The trouble is that the schema file doesn't contain a definition for the Events element (because its not in the schema), and so the above doesn't compile as there is no Events type.
I've tried a couple of variations, including using the type EventType[] instead of Events (which resulted in the exception " was not expected."). I also attempting to craft my own C# container Events type:
public class Events
{
[XmlElement]
public EventType[] Items
{
get;
set;
}
}
However the above simply results in the Items array being null.
How can I read Events Logs saved to XML format from C#?
So I managed this by using the following class:
[Serializable]
[XmlType(AnonymousType = true)]
[XmlRoot(Namespace = "", IsNullable = false)]
public class Events
{
[XmlElement("Event", Namespace = "http://schemas.microsoft.com/win/2004/08/events/event")]
public EventType[] Items
{
get;
set;
}
}
I'm not entirely certain what it is that made this work where previously it failed (I suspect its the Namespace property), however I found this out by using xsd.exe to generate a schema from a saved event log file and and then again to generate C# classes from that file, like so:
xsd /c eventlog.xml
xsd /c eventlog.xsd eventlog_app1.xsd
(Because it writes two xsd files you need to name both of them on the command line). I then looked at the resulting C# and compared / experimented until it worked.

Categories

Resources