How to change or inherit the default namespace when setting InnerXml - c#

When setting the InnerXml of an XmlElement having a default namespace, all tags without explicit namespaces are parsed as if they have xmlns="", instead of inheriting that XmlElement's default namespace (which is what happens when parsing a real XML document).
My question is: how to parse a complex XML string as a document fragment and assign it to an XmlElement, and inheriting the target XmlElement's namespace prefixes, default namespace, etc. when parsing that string?
Disclaimer:
I am totally aware of what XML namespaces are and what is the exact behavior of XmlElement.InnerXml regarding to XML namespaces. I'm not asking why XmlElement.InnerXml is doing what it currently does, or whether such behavior is good or bad. I'm asking if I can change this behavior or use some other techniques to achieve what I've described above.
I'm implementing some kind of XML template system, which allows users to insert some rather complex XML strings as fragments into another XML document. It will be insane to require users to always use explicit namespaces (the overhead of writing redundant namespace declarations can easily defeat the benefit of templating). I want a method to parse them and insert the resulting fragments into the main document as if they are literally copy-and-pasted into the target.
I'm aware that it is possible to preserve the default namespaces with pure DOM operations (like XmlDocument.CreateElement), but I don't want to manually implement an XML parser and convert XML strings into DOM operations.
I also don't want to do "serialize the whole XML document, do string manipulation, and parse it back" kind of things.
Is it possible?

As mentioned in this comment by Martin Honnen as well as this answer by Gideon Engelberth to Can I use predefined namespaces when loading an XDocument?, you can use an XmlParserContext to predefine a value for a namespace (including the default namespace) when parsing XML via XmlReader. Using an XmlReader configured with an appropriate context, you can load your inner XML directly into your XmlElement and and inherit any required namespaces from its scope.
To inherit just the default namespace, create the following extension methods:
public static partial class XmlNodeExtensions
{
public static void SetInnerXmlAndInheritDefaultNamespace(this XmlElement xmlElement, string innerXml)
{
using (var textReader = new StringReader(innerXml))
xmlElement.SetInnerXmlAndInheritDefaultNamespace(textReader);
}
public static void SetInnerXmlAndInheritDefaultNamespace(this XmlElement xmlElement, TextReader innerTextReader)
{
XmlNamespaceManager mgr = new XmlNamespaceManager(new NameTable());
mgr.AddNamespace("", xmlElement.GetNamespaceOfPrefix(""));
XmlParserContext ctx = new XmlParserContext(null, mgr, null, XmlSpace.Default);
using (var reader = XmlReader.Create(innerTextReader, new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment, CloseInput = false }, ctx))
using (var writer = xmlElement.CreateNavigator().AppendChild())
{
writer.WriteNode(reader, true);
}
}
}
To inherit all namespaces (the requirements in your question aren't entirely clear), create the following:
public static partial class XmlNodeExtensions
{
public static void SetInnerXmlAndInheritNamespaces(this XmlElement xmlElement, string innerXml)
{
using (var textReader = new StringReader(innerXml))
xmlElement.SetInnerXmlAndInheritNamespaces(textReader);
}
public static void SetInnerXmlAndInheritNamespaces(this XmlElement xmlElement, TextReader innerTextReader)
{
XmlNamespaceManager mgr = new XmlNamespaceManager(new NameTable());
var navigator = xmlElement.CreateNavigator();
foreach (var pair in navigator.GetNamespacesInScope(XmlNamespaceScope.ExcludeXml))
mgr.AddNamespace(pair.Key, pair.Value);
XmlParserContext ctx = new XmlParserContext(null, mgr, null, XmlSpace.Default);
using (var writer = navigator.AppendChild())
using (var reader = XmlReader.Create(innerTextReader, new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment, CloseInput = false }, ctx))
{
writer.WriteNode(reader, true);
}
}
}
Assuming xmlElement is the existing XmlElement to which you want to insert an innerXml string, you can do:
xmlElement.SetInnerXmlAndInheritDefaultNamespace(innerXml);
E.g. if your XML Document looks like:
<Root xmlns="defaultNameSpace" xmlns:d="dataNodeNamespace"><d:Data></d:Data></Root>
And you add the following XML to <d:Data>:
<ElementToAdd Id="10101"><InnerValue>my inner value</InnerValue></ElementToAdd><ElementToAdd Id="20202"><InnerValue>another inner value</InnerValue></ElementToAdd>
The result will be:
<Root xmlns="defaultNameSpace" xmlns:d="dataNodeNamespace">
<d:Data>
<ElementToAdd Id="10101">
<InnerValue>my inner value</InnerValue>
</ElementToAdd>
<ElementToAdd Id="20202">
<InnerValue>another inner value</InnerValue>
</ElementToAdd>
</d:Data>
</Root>
Demo fiddle here and here.

Related

Creating XmlNode with specified spread sheet type

I'm little bit new in xml manipulation. I want to create a XmlNode.
I've already tried to OwnerDocument.CreateElement method and also tried OwnerDocument.CreateNode method, but I can't create the following XmlNode:
<Data ss:Type="String"/>
Can you help me with this problem? I already tried everything which I found, but nothing.
XmlDocument has been superseded in the .NET framework by the newer XDocument API, which plays nicer with Linq and in general is a modern library to use for XML manipulation.
Here is an example how you can use that API to insert an element into an existing XML document, with an attribute that has a namespace prefix which is previously declared.
XDocument ownerDocument = XDocument.Parse("<OwnerDocument></OwnerDocument>");
XNamespace ssNameSpace = "http://whatever/somenamespace";
// add namespace declaration to the root, set ss as the namespace prefix
// you only need to do this if the document doesn't already have the namespace declaration
ownerDocument.Root.Add(new XAttribute(XNamespace.Xmlns + "ss", ssNameSpace.NamespaceName));
// add our new data element to the root, and add the type attribute prefixed with the ss namespace
ownerDocument.Root.Add(new XElement("Data", new XAttribute(ssNameSpace + "Type", "String")));
That will produce the following XML:
<OwnerDocument xmlns:ss="http://whatever/somenamespace">
<Data ss:Type="String" />
</OwnerDocument>
If you are really tied to using XmlDocument, you can acheive the same there as follows:
XmlDocument ownerDocument = new XmlDocument();
ownerDocument.LoadXml("<OwnerDocument></OwnerDocument>");
// add namespace declaration to the root, set ss as the namespace prefix
var nsDeclarationAttribute = ownerDocument.CreateAttribute("xmlns:ss");
nsDeclarationAttribute.Value = "http://whatever/somenamespace";
ownerDocument.DocumentElement.Attributes.Append(nsDeclarationAttribute);
// add data element, and add a type attribute to that
var dataElement = ownerDocument.CreateElement("Data");
var typeAttribute = ownerDocument.CreateAttribute("Type", "http://whatever/somenamespace");
typeAttribute.Value = "String";
dataElement.Attributes.Append(typeAttribute);
// append to main document
ownerDocument.DocumentElement.AppendChild(dataElement);

Preserve whitespace-only element content when deserializing XML using XmlSerializer

I have a class InputConfig which contains a List<IncludeExcludeRule>:
public class InputConfig
{
// The rest of the class omitted
private List<IncludeExcludeRule> includeExcludeRules;
public List<IncludeExcludeRule> IncludeExcludeRules
{
get { return includeExcludeRules; }
set { includeExcludeRules = value; }
}
}
public class IncludeExcludeRule
{
// Other members omitted
private int idx;
private string function;
public int Idx
{
get { return idx; }
set { idx = value; }
}
public string Function
{
get { return function; }
set { function = value; }
}
}
Using ...
FileStream fs = new FileStream(path, FileMode.Create);
XmlSerializer xmlSerializer = new XmlSerializer(typeof(InputConfig));
xmlSerializer.Serialize(fs, this);
fs.Close();
... and ...
StreamReader sr = new StreamReader(path);
XmlSerializer reader = new XmlSerializer(typeof(InputConfig));
InputConfig inputConfig = (InputConfig)reader.Deserialize(sr);
It works like a champ! Easy stuff, except that I need to preserve whitespace in the member function when deserializing. The generated XML file demonstrates that the whitespace was preserved when serializing, but it is lost on deserializing.
<IncludeExcludeRules>
<IncludeExcludeRule>
<Idx>17</Idx>
<Name>LIEN</Name>
<Operation>E =</Operation>
<Function> </Function>
</IncludeExcludeRule>
</IncludeExcludeRules>
The MSDN documentation for XmlAttributeAttribute seems to address this very issue under the header Remarks, yet I don't understand how to put it to use. It provides this example:
// Set this to 'default' or 'preserve'.
[XmlAttribute("space",
Namespace = "http://www.w3.org/XML/1998/namespace")]
public string Space
Huh? Set what to 'default' or 'preserve'? I'm sure I'm close, but this just isn't making sense. I have to think there's just a single line XmlAttribute to insert in the class before the member to preserve whitespace on deserialize.
There are many instances of similar questions here and elsewhere, but they all seem to involve the use of XmlReader and XmlDocument, or mucking about with individual nodes and such. I'd like to avoid that depth.
To preserve all whitespace during XML deserialization, simply create and use an XmlReader:
StreamReader sr = new StreamReader(path);
XmlReader xr = XmlReader.Create(sr);
XmlSerializer reader = new XmlSerializer(typeof(InputConfig));
InputConfig inputConfig = (InputConfig)reader.Deserialize(xr);
Unlike XmlSerializer.Deserialize(XmlReader), XmlSerializer.Deserialize(TextReader) preserves only significant whitespace marked by the xml:space="preserve" attribute.
The cryptic documentation means that you need to specify an additional field with the [XmlAttribute("space", Namespace = "http://www.w3.org/XML/1998/namespace")] whose value is default or preserve. XmlAttribute controls the name of the generated attribute for a field or property. The attribute's value is the field's value.
For example, this class:
public class Group
{
[XmlAttribute (Namespace = "http://www.cpandl.com")]
public string GroupName;
[XmlAttribute(DataType = "base64Binary")]
public Byte [] GroupNumber;
[XmlAttribute(DataType = "date", AttributeName = "CreationDate")]
public DateTime Today;
[XmlAttribute("space", Namespace = "http://www.w3.org/XML/1998/namespace")]
public string Space ="preserve";
}
Will be serialized to:
<?xml version="1.0" encoding="utf-16"?>
<Group xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
d1p1:GroupName=".NET"
GroupNumber="ZDI="
CreationDate="2001-01-10"
xml:space="preserve"
xmlns:d1p1="http://www.cpandl.com" />
I believe the part you are missing is to add the xml:space="preserve" to the field, e.g.:
<Function xml:space="preserve"> </Function>
For more details, here is the relevant section in the XML Specification
With annotation in the class definition, according to the MSDN blog it should be:
[XmlAttribute("space=preserve")]
but I remember it being
[XmlAttribute("xml:space=preserve")]
Michael Liu's answer above worked for me, but with one caveat. I would have commented on his answer, but my "reputation" is not adequate enough.
I found that using XmlReader did not fully fix the issue, and the reason for this is that the .net property in question had the attribute:
XmlText(DataType="normalizedString")
To rectify this I found that adding the additional attribute worked:
[XmlAttribute("xml:space=preserve")]
Obviously, if you have no control over the .net class then you have a problem.

Using an XmlTextAttribute on an array of mixed types removes whitespace strings

I am writing a set of objects that must serialize to and from Xml, following a strict specification that I cannot change. One element in this specification can contain a mix of strings and elements in-line.
A simple example of this Xml output would be this:
<root>Leading text <tag>tag1</tag> <tag>tag2</tag></root>
Note the whitespace characters between the closing of the first tag, and the start of the second tag. Here are the objects that represents this structure:
[XmlRoot("root")]
public class Root
{
[XmlText(typeof(string))]
[XmlElement("tag", typeof(Tag))]
public List<object> Elements { get; set; }
//this is simply for the sake of example.
//gives us four objects in the elements array
public static Root Create()
{
Root root = new Root();
root.Elements.Add("Leading text ");
root.Elements.Add(new Tag() { Text = "tag1" });
root.Elements.Add(" ");
root.Elements.Add(new Tag() { Text = "tag2" });
return root;
}
public Root()
{
Elements = new List<object>();
}
}
public class Tag
{
[XmlText]
public string Text {get;set;}
}
Calling Root.Create(), and saving to a file using this method looks perfect:
public XDocument SerializeToXml(Root obj)
{
XmlSerializer serializer = new XmlSerializer(typeof(Root));
XDocument doc = new XDocument();
using (var writer = doc.CreateWriter())
{
serializer.Serialize(writer, obj);
}
return doc;
}
Serialization looks exactly like the xml structure at the beginning of this post.
Now when I want to serialize an xml file back into a Root object, I call this:
public static Root FromFile(string file)
{
XmlSerializer serializer = new XmlSerializer(typeof(Root));
XmlReaderSettings settings = new XmlReaderSettings();
XmlReader reader = XmlTextReader.Create(file, settings);
//whitespace gone here
Root root = serializer.Deserialize(reader) as Root;
return root;
}
The problem is here. The whitespace string is eliminated. When I call Root.Create(), there are four objects in the Elements array. One of them is a space. This serializes just fine, but when deserializing, there are only 3 objects in Elements. The whitespace string gets eliminated.
Any ideas on what I'm doing wrong? I've tried using xml:space="preserve", as well as a host of XmlReader, XmlTextReader, etc. variations. Note that when I use a StringBuilder to read the XmlTextReader, the xml contains the spaces as I'd expect. Only when calling Deserialize(stream) do I lose the spaces.
Here's a link to an entire working example. It's LinqPad friendly, just copy/paste: http://pastebin.com/8MkUQviB The example opens two files, one a perfect serialized xml file, the second being a deserialized then reserialized version of the first file. Note you'll have to reference System.Xml.Serialization.
Thanks for reading this novel. I hope someone has some ideas. Thank you!
It looks like a bug. Workaround seems to be replace all whitespaces and crlf in XML text nodes by
entities. Semantic equal entities (
) does not work.
<root>Leading text <tag>tag1</tag> <tag>tag2</tag></root>
is working for me.

C# Xml Parsing from StringBuilder

I have a StringBuilder with the contents of an XML file. Inside the XML file is a root tag called <root> and contains multiple <node> tags.
I'd like to parse through the XML to read values of tags within in s, but not sure how to do it.
Will I have to use some C# XML data type for this?
Thanks in advance
StringBuilder sb = new StringBuilder (xml);
TextReader textReader = new StringReader (sb.ToString ());
XDocument xmlDocument = XDocument.Load (textReader);
var nodeValueList = from node in xmlDocument.Descendants ("node")
select node.Value;
You should use classes available in either System.Xml or System.Xml.Linq to parse XML.
XDocument is part of the LINQ extensions for XML and is particularly easy to use if you need to parse through an arbitrary structure. I would suggest using it rather than XmlDocument (unless you have legacy code or are not on .NET 3.5).
Creating an XDocument from a StringBuilder is straightforward:
var doc = XDocument.Parse( stringBuilder.ToString() );
From here, you can use FirstNode, Descendents(), and the many other properties and methods available to walk and examine the XML structure. And since XDocument is designed to work well with LINQ, you can also write queries like:
var someData = from node in doc.Descendants ("yourNodeType")
select node.Value; // etc..
If you are just looking the specifically named nodes then you don't need to load the document into memory, you can process it yourself with an XmlReader.
using(var sr = new StringReader(stringBuilder.ToString)) {
using(var xr = XmlReader.Create(sr)) {
while(xr.Read()) {
if(xr.IsStartElement() && xr.LocalName == "node")
xr.ReadElementString(); //Do something here
}
}
}
use XDocument.Parse(...)
There are several objects at your disposal for working with XML. Look at the System.Xml namespace for objects such as XmlDocument as well as the XmlReader and XmlWriter families of objects. If using C# 3.0+, look at the System.Xml.Linq namespace and the XDocument class.
If you're looking to read all the values in the XML file , you could look into deserializing the XML into a C# data Object.
Deserializing XML into class obj in C#
Yes, I suggest you use an XmlDocument object to parse the content of your string.
Here is an example who print all inner text contained in your tags:
var doc=new XmlDocument();
doc.LoadXml(stringBuilder.TosTring());
XmlNodeList elemList = doc.GetElementsByTagName("node");
for (int i=0; i < elemList.Count; i++)
{
XmlNode node=elemList[i];
Console.WriteLine(node.InnerText);
}
using Node object members, you can also easily extract all you attributes .

XML Serialization in C# without XML attribute nodes

I have an XML document format from a legacy system that I have to support in a future application. I want to be able to both serialize and deserialize the XML between XML and C# objects, however, using the objects generated by xsd.exe, the C# serialization includes the xmlns:xsi..., xsi:... etc XML attributes on the root element of the document that gets generated. Is there anyway to disable this so that absolutely no XML attribute nodes get put out in the resulting XML ? The XML document should be elements only.
Duplicate? XmlSerializer: remove unnecessary xsi and xsd namespaces
Yes, use the XmlSerializerNamespaces class.
Example:
var s= new System.Xml.Serialization.XmlSerializer(typeof(TypeToSerialize));
var ns= new System.Xml.Serialization.XmlSerializerNamespaces();
ns.Add( "", "");
System.IO.StreamWriter writer= System.IO.File.CreateText(filePath);
s.Serialize(writer, objectToSerialize, ns);
writer.Close();
See also: XmlSerializer: remove unnecessary xsi and xsd namespaces
There is no way to force XML Serializer to ignore xsi attributes (unless you implement IXmlSerializable and force custom serialization or use XmlAttributeOverrides). However the only time xsi: attributes show up is when you have a nullable element. If you do need to use nullable elements you can of course post-process the XML to remove all xsi: occurences. However if you do this think about how you will deserialize the XML back into an object, if xsi:nil is missing on an element and the element is defined as a nullable integer you will run into an exception.
#Cheeso, please correct me if i am wrong.
I have the following code.
public class TestSer
{
public int? MyProperty { get; set; }
}
TestSer ser = new TestSer();
ser.MyProperty = null;
StringBuilder bldr = new StringBuilder();
var ns = new System.Xml.Serialization.XmlSerializerNamespaces();
ns.Add("", "");
XmlSerializer s = new XmlSerializer(typeof(TestSer));
using (StringWriter writer = new StringWriter(bldr))
{
s.Serialize(writer, ser, ns);
}
I get the following output.
<?xml version="1.0" encoding="utf-16"?>
<TestSer>
<MyProperty d2p1:nil="true" xmlns:d2p1="http://www.w3.org/2001/XMLSchema-instance" />
</TestSer>
This isn't exactly element only as the question asks for.

Categories

Resources