In the ResX exporter for my data driven Resource Provider
I use a bit of code that iterates over the database resources and then
spits out ResX resources from the data as an option to get your
resources into your Web site. The code I've used in this stretch of
code uses an XmlWriter to quickly spit out the data.
But there
are a couple of non-critical problems in that code. Several people have
pointed out that the XML generated doesn't exactly match the ResX
format that Visual Studio uses (although as far as I can tell there's
no problem).
The problem is related to an xml:space="preserve"
attribute that Visual Studio sticks onto each resource. It does this so
leading and trailing spaces are preserved when the values are read.
Visual Studio generates:
<data name="Today" xml:space="preserve">
<value>Heute </value>
</data>
but my code generates:
<data name="Today" d2p1:space="preserve" xmlns:d2p1="xml">
<value>Heute </value>
</data>
So where's that coming from?
The code I use to generate this Xml Fragment is pretty simple:
xWriter.WriteStartElement("data");
xWriter.WriteAttributeString("name", ResourceId);
xWriter.WriteAttributeString("space","xml","preserve");
xWriter.WriteElementString("value", Value);
xWriter.WriteEndElement(); // data
As you can see the xml:space attribute is in fact written out
directly, but the XmlWriter thinks it knows better and tries to
explicitly define the Xml namespace. For the resource editor this
causes no harm as far as I can tell, but it is kinda ugly.
Now the document does have a schema, but originally had written out
the schema simply as a raw text string. There's a raw header and schema
definition:
public const string ResXDocumentTemplate =
@"<?xml version=""1.0"" encoding=""utf-8""?>
<root>
<xsd:schema id=""root"" xmlns="""" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata=""urn:schemas-microsoft-com:xml-msdata"">
<xsd:import namespace=""http://www.w3.org/XML/1998/namespace"" />
<xsd:element name=""root"" msdata:IsDataSet=""true"">
<xsd:complexType>
<xsd:choice maxOccurs=""unbounded"">
<xsd:element name=""metadata"">
<xsd:complexType>
<xsd:sequence>
<xsd:element name=""value"" type=""xsd:string"" minOccurs=""0"" />
</xsd:sequence>
<xsd:attribute name=""name"" use=""required"" type=""xsd:string"" />
<xsd:attribute name=""type"" type=""xsd:string"" />
<xsd:attribute name=""mimetype"" type=""xsd:string"" />
<xsd:attribute ref=""xml:space"" />
</xsd:complexType>
</xsd:element>
<xsd:element name=""assembly"">
<xsd:complexType>
<xsd:attribute name=""alias"" type=""xsd:string"" />
<xsd:attribute name=""name"" type=""xsd:string"" />
</xsd:complexType>
</xsd:element>
<xsd:element name=""data"">
<xsd:complexType>
<xsd:sequence>
<xsd:element name=""value"" type=""xsd:string"" minOccurs=""0"" msdata:Ordinal=""1"" />
<xsd:element name=""comment"" type=""xsd:string"" minOccurs=""0"" msdata:Ordinal=""2"" />
</xsd:sequence>
<xsd:attribute name=""name"" type=""xsd:string"" use=""required"" msdata:Ordinal=""1"" />
<xsd:attribute name=""type"" type=""xsd:string"" msdata:Ordinal=""3"" />
<xsd:attribute name=""mimetype"" type=""xsd:string"" msdata:Ordinal=""4"" />
<xsd:attribute ref=""xml:space"" />
</xsd:complexType>
</xsd:element>
<xsd:element name=""resheader"">
<xsd:complexType>
<xsd:sequence>
<xsd:element name=""value"" type=""xsd:string"" minOccurs=""0"" msdata:Ordinal=""1"" />
</xsd:sequence>
<xsd:attribute name=""name"" type=""xsd:string"" use=""required"" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name=""resmimetype"">
<value>text/microsoft-resx</value>
</resheader>
<resheader name=""version"">
<value>2.0</value>
</resheader>
<resheader name=""reader"">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name=""writer"">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
";
which is simply written into the XmlWriter as a raw string:
XmlTextWriter Writer = new XmlTextWriter(this.FormatResourceSetPath(ResourceSet, LocalResources) + Loc, Encoding.UTF8);
Writer.Indentation = 3;
Writer.IndentChar = ' ';
Writer.Formatting = Formatting.Indented;
xWriter = Writer as XmlWriter;
xWriter.WriteRaw(ResXDocumentTemplate);
I suppose that's a very lazy way of doing things but honestly I
didn't see much value in actually hand-coding the schema into
individual XmlWriter commands.
But the above has a couple of problems. First there's the xml:space
issue. There's also a formatting problem - although I have document
indentation enabled the formatting doesn't actually work properly - the
XmlWriter ends up writing a streaming document despite the settings.
The Xml document needs to be treated as an Xml Fragment rather than a
document because the writer doesn't know the the doc header was written
with WriteRaw. Finally the end document tag needs to be explicitly
written out as raw text.
All of this results in quite an ugly looking, albeit functional document.
So I started experimenting a little bit with writing out this
content more cleanly. I could write out the PI and root tag and then
just inject the scheme with WriteRaw which improved the document
parsing somewhat, but still there were schema issues.
But I think a better way is to do something like this instead - load up the 'template' as a full XmlDocument first:
// *** Load the document schema
XmlDocument doc = new XmlDocument();
doc.LoadXml(ResXDocumentTemplate);
and then write out portions of this XmlDocument into the XmlWriter. So when each ResX file is created there's code like this:
XmlTextWriter Writer = new XmlTextWriter(this.FormatResourceSetPath(ResourceSet, LocalResources) + Loc, Encoding.UTF8);
...
xWriter.WriteStartElement("root");
// *** Write out the schema
doc.DocumentElement.ChildNodes[0].WriteTo(xWriter);
// *** Write out the leading resheader elements
XmlNodeList Nodes = doc.DocumentElement.SelectNodes("resheader");
foreach(XmlNode Node in Nodes)
{
Node.WriteTo(xWriter);
}
This actually writes out each of the components properly into the
XmlWriter which in fact ends up producing a clean and properly
constructed XmlWriter document.
This solves all the formatting issues, with the exception of the
xml:space issue that started me down this path in the first place. It
still fails even with the schema properly written through the XmlWriter.
One solution and probably the cleanest at that is to modify the schema to automatically default to the preserve setting:
<xsd:element name=""data"">
<xsd:complexType>
<xsd:sequence>
<xsd:element name=""value"" type=""xsd:string"" minOccurs=""0"" msdata:Ordinal=""1"" />
<xsd:element name=""comment"" type=""xsd:string"" minOccurs=""0"" msdata:Ordinal=""2"" />
</xsd:sequence>
<xsd:attribute name=""name"" type=""xsd:string"" use=""required"" msdata:Ordinal=""1"" />
<xsd:attribute name=""type"" type=""xsd:string"" msdata:Ordinal=""3"" />
<xsd:attribute name=""mimetype"" type=""xsd:string"" msdata:Ordinal=""4"" />
<xsd:attribute ref=""xml:space"" default=""preserve""/>
</xsd:complexType>
and then simply don't generate the tag. I checked this out with
two-way conversion - saving values with spaces and exporting to Resx
checking for the spaces still being in the XML (they are) and then
round tripping the data back into the Resource Provider and double
checking the value to ensure that the spaces have made it (they do).
This effectively solves the problem above, but I still wonder how I
would go about generating xml:space="preserve" explicitly into the
XmlWriter. In other situations modifying the schema generically may
simply not be an option.
I'm not sure how to make the XmlWriter recognize the schema
properly. It does work for the Resx editor and for parsing the Xml file
as input using XmlDocument (ie. it passes validation), but now I am
really wondering how to make the proper Xml definition work.
So, how do I get the XmlWriter generate: xml:space="preserve"? Anybody of you Xml Wizards know how to do this?
Source:http://www.west-wind.com