Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
634 views
in Technique[技术] by (71.8m points)

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

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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();
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...