How to serialize multiple objects into an existing XmlDocument, without having the namespaces on each component?

霸气de小男生 提交于 2019-12-29 09:15:14

问题


How to serialize multiple objects into a existing XmlDocument in .Net/C#?

I have a XmlDocument, which already contains data. I have multiple objects. Now I want to serialize them one by one and add them to the XmlDocument (AppendChild).

This is how it should be:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<project>
    <mySettings>...</mySettings>
    <component_1> anydata </component_1>
    ...
    <component_x> anydata </component_x>
</project>

When I use the XmlSerializer I get this definition for each component:

<component_1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    anydata
</component_1>

So this is what I get, when I serialize into a string and then create a XmlNode from string, which i append to my document:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<project>
    <mySettings>...</mySettings>
    <component_1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> anydata </component_1>
    ...
    <component_x xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> anydata </component_x>
</project>

I can remove the namespace by doing this:

XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
StringWriter xout = new StringWriter();
x.Serialize(xout, data, ns);

But then I get the namespaces on any object inside an object array. This object:

public class component_1
{
    object[] arr;
}

will be serialized to:

<component_1>
  <objectArray>
    <anyType xmlns:q1="http://www.w3.org/2001/XMLSchema" d3p1:type="q1:string" xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance">one</anyType>
    <anyType xmlns:q2="http://www.w3.org/2001/XMLSchema" d3p1:type="q2:string" xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance">two</anyType>
  </objectArray>
</component_1>

Is it possible to add all needed namespaces, etc. to my document and then serialize objects into XmlNodes and add them to my document, without having the namespaces on each component?

UPDATE: The function test() will serialize two objects and append them to a document. Last line will deserialize the first object.

using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
...

public class Component_1
{
    public string Value = "Component_1.Value";
    public object[] objectArray = new object[] { "one", "two" };
}
void test()
{
    object[] components = new object[] { new Component_1(), new Component_1() };

    XmlDocument doc = new XmlDocument();
    XmlNode rootNode = doc.AppendChild(doc.CreateElement("project"));

    foreach (var component in components)
        rootNode.AppendChild(doc.ReadNode(XmlTextReader.Create(new StringReader(serialize(component, true)))));

    Console.WriteLine(doc.OuterXml);
    Console.WriteLine(deserialize<Component_1>(rootNode.ChildNodes[0].OuterXml).Value);
}
string serialize(object obj, bool namespaces)
{
    StringBuilder sb = new StringBuilder();
    XmlWriter writer = XmlWriter.Create(sb, new XmlWriterSettings() { OmitXmlDeclaration = true });
    (new XmlSerializer(obj.GetType())).Serialize(writer, obj, namespaces ? null : new XmlSerializerNamespaces(new XmlQualifiedName[] { new XmlQualifiedName("", "") }));
    return sb.ToString();
}
T deserialize<T>(string xmlString)
{
    return (T)(new XmlSerializer(typeof(T))).Deserialize(new StringReader(xmlString));
}

Maybe it is possible to add namespaces to the document (rootNode) and when creating a new XmlNode from string with the function XmlDocument.ReadNode to resolve the namespaces in string by the namespaces from XmlDocument. But I don´t know how.

UPDATE 2:
Thanks Alex Filipovici, the serialization output is exact what I wanted.

void test2()
{
    object[] components = new object[] { new Component_1(), new Component_1() };

    var doc = new XmlDocument();

    var project = doc.AppendChild(doc.CreateElement("project"));

    doc.DocumentElement.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
    doc.DocumentElement.SetAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");

    var nav = project.CreateNavigator();

    var emptyNamepsaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });

    foreach (var component in components)
    {
        using (var writer = nav.AppendChild())
        {
            var serializer = new XmlSerializer(component.GetType());
            writer.WriteWhitespace("");
            serializer.Serialize(writer, component
                , emptyNamepsaces
                );
            writer.Close();
        }
    }

    foreach (XmlNode node in doc.GetElementsByTagName("anyType"))
    {
        string attributeType = "";
        foreach (XmlAttribute xmlAttribute in node.Attributes)
        {
            if (xmlAttribute.LocalName == "type")
            {
                attributeType = xmlAttribute.Value.Split(':')[1];
            }
        }
        node.Attributes.RemoveAll();
        node.CreateNavigator().CreateAttribute("", "type", "", attributeType);
    }
    doc.Save("output.xml");

    Component_1 c = deserialize<Component_1>(project.ChildNodes[0].OuterXml);

    Console.WriteLine(c.objectArray[0].GetType()); // -> System.Xml.XmlNode[] !
}

output:

<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Component_1>
    <Value>Component_1.Value</Value>
    <objectArray>
      <anyType type="string">one</anyType>
      <anyType type="string">two</anyType>
    </objectArray>
  </Component_1>
  <Component_1>
    <Value>Component_1.Value</Value>
    <objectArray>
      <anyType type="string">one</anyType>
      <anyType type="string">two</anyType>
    </objectArray>
  </Component_1>
</project>

But now the deserialization with the "T desirialize(string xmlString)" function from above fails. The object array contains XmlNodes.

Is it possible to tell XmlSerializer to use the namespaces from the project node, or do I have to insert them again?


回答1:


This will serialize objects and append them to a XmlDocument. While de-/serializing the code will resolve the namespaces. @Alex: Thanks for the example with XPathNavigator.

void test2()
{
    XmlDocument doc = new XmlDocument();
    XmlNode root = doc.AppendChild(doc.CreateElement("root"));

    doc.DocumentElement.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
    doc.DocumentElement.SetAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");

    serializeAppend(root, new object[] { 1, "two", 3.0 });  // serialize object and append it to XmlNode
    var obj = deserialize<object[]>(root.ChildNodes[0]);    // deserialize XmlNode to object
}
T deserialize<T>(XmlNode node)
{
    XPathNavigator nav = node.CreateNavigator();
    using (var reader = nav.ReadSubtree())
    {
        var serializer = new XmlSerializer(typeof(T));
        return (T)serializer.Deserialize(reader);
    }
}
void serializeAppend(XmlNode parentNode, object obj)
{
    XPathNavigator nav = parentNode.CreateNavigator();
    using (var writer = nav.AppendChild())
    {
        var serializer = new XmlSerializer(obj.GetType());
        writer.WriteWhitespace("");
        serializer.Serialize(writer, obj);
        writer.Close();
    }
}



回答2:


The code below will fulfill the requirement in the OP, to have a clean XML. It will remove all tributes from all elements, but it will add a type attribute to the anyType elements, so the original type could still be distinguish for each element.

static void Main(string[] args)
{
    object[] components = new object[] { new Component_1(), new Component_1() };

    var doc = new XmlDocument();
    doc.Load("source.xml");
    var project = doc.GetElementsByTagName("project")[0];

    var nav = project.CreateNavigator();

    var emptyNamepsaces = new XmlSerializerNamespaces(new[] { 
        XmlQualifiedName.Empty
    });

    foreach (var component in components)
    {
        using (var writer = nav.AppendChild())
        {
            var serializer = new XmlSerializer(component.GetType());
            writer.WriteWhitespace("");
            serializer.Serialize(writer, component
                , emptyNamepsaces
                );
            writer.Close();
        }
    }

    foreach (XmlNode node in doc.GetElementsByTagName("anyType"))
    {
        string attributeType = "";
        foreach (XmlAttribute xmlAttribute in node.Attributes)
        {
            if (xmlAttribute.LocalName == "type")
            { 
            attributeType=xmlAttribute.Value.Split(':')[1];
            }
        }
        node.Attributes.RemoveAll();
        node.CreateNavigator().CreateAttribute("","type","",attributeType);
    }
    doc.Save("output.xml");

}

If you want to deserialize the XML, you'll have to create a dictionary:

static Dictionary<string, Type> _typeCache;

add the expected XML types mapped to corresponding Type values to it:

_typeCache = new Dictionary<string, Type>();
_typeCache.Add("string", typeof(System.String));
_typeCache.Add("int", typeof(System.Int32));
_typeCache.Add("dateTime", typeof(System.DateTime));

and replace each XmlNode in the array by converting it to it's expected type accordingly:

Component_1 c = Deserialize<Component_1>(project.ChildNodes[0].OuterXml);

for (int i = 0; i < c.objectArray.Length; i++)
{
    var type = _typeCache[(((System.Xml.XmlNode[])(c.objectArray[i]))[0]).Value];
    var item = Convert.ChangeType((((System.Xml.XmlNode[])(c.objectArray[i]))[1]).Value, type);
    c.objectArray[i] = item;
}

Console.WriteLine(c.objectArray[0].GetType()); // -> System.String


来源:https://stackoverflow.com/questions/14759520/how-to-serialize-multiple-objects-into-an-existing-xmldocument-without-having-t

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!