1

如何在 .Net/C# 中将多个对象序列化为现有的 XmlDocument?

我有一个 XmlDocument,它已经包含数据。我有多个对象。现在我想将它们一一序列化并将它们添加到 XmlDocument (AppendChild) 中。

应该是这样的:

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

当我使用 XmlSerializer 时,我得到每个组件的这个定义:

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

所以这就是我得到的,当我序列化成一个字符串然后从字符串创建一个 XmlNode 时,我将它附加到我的文档中:

<?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>

我可以这样做删除命名空间:

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

但是后来我得到了对象数组中任何对象的命名空间。这个对象:

public class component_1
{
    object[] arr;
}

将被序列化为:

<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>

是否可以将所有需要的名称空间等添加到我的文档中,然后将对象序列化为 XmlNodes 并将它们添加到我的文档中,而无需在每个组件上都有名称空间?

更新:函数 test() 将序列化两个对象并将它们附加到文档中。最后一行将反序列化第一个对象。

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

也许可以将命名空间添加到文档 (rootNode) 中,并且在使用函数 XmlDocument.ReadNode 从字符串创建新的 XmlNode 时,可以通过 XmlDocument 中的命名空间解析字符串中的命名空间。但我不知道怎么做。

更新 2:
感谢 Alex Filipovici,序列化输出正是我想要的。

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[] !
}

输出:

<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>

但是现在使用上面的“T desirialize(string xmlString)”函数进行反序列化失败了。对象数组包含 XmlNode。

是否可以告诉 XmlSerializer 使用项目节点中的命名空间,还是我必须再次插入它们?

4

2 回答 2

4

这将序列化对象并将它们附加到 XmlDocument。在反序列化代码时,将解析命名空间。@Alex:感谢 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();
    }
}
于 2013-02-09T20:22:12.570 回答
1

下面的代码将满足 OP 中的要求,即拥有一个干净的XML。它将从所有元素中删除所有贡品,但它会type为元素添加一个属性anyType,因此仍然可以区分每个元素的原始类型。

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");

}

如果要反序列化 XML,则必须创建一个字典:

static Dictionary<string, Type> _typeCache;

将映射到相应Type值的预期 XML 类型添加到它:

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

XmlNode并通过将其转换为相应的预期类型来替换数组中的每个:

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
于 2013-02-08T12:08:41.213 回答