没有<?xml>文本声明的.NET XML序列化

21

我试图生成以下格式的XML:

<?xml version="1.0"?>
<!DOCTYPE APIRequest SYSTEM
"https://url">
<APIRequest>
  <Head>
      <Key>123</Key>
  </Head>
  <ObjectClass>
    <Field>Value</Field
  </ObjectClass>
</APIRequest>

我有一个被XMLSerialization属性装饰的类(ObjectClass),就像这样:

[XmlRoot("ObjectClass")]
public class ObjectClass
{
    [XmlElement("Field")]
    public string Field { get; set; }
}

我的想法很简单,为了使它工作起来,我要在序列化时这样做:

ObjectClass inst = new ObjectClass();
XmlSerializer serializer = new XmlSerializer(inst.GetType(), "");

StringWriter w = new StringWriter();
w.WriteLine(@"<?xml version=""1.0""?>");
w.WriteLine("<!DOCTYPE APIRequest SYSTEM");
w.WriteLine(@"""https://url"">");
w.WriteLine("<APIRequest>");
w.WriteLine("<Head>");
w.WriteLine(@"<Field>Value</Field>");
w.WriteLine(@"</Head>");

XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", ""); 
serializer.Serialize(w, inst, ns);

w.WriteLine("</APIRequest>");

不过,这会生成以下这样的XML:

<?xml version="1.0"?>
<!DOCTYPE APIRequest SYSTEM
"https://url">
<APIRequest>
  <Head>
      <Key>123</Key>
  </Head>
  <?xml version="1.0" encoding="utf-16"?>
  <ObjectClass>
    <Field>Value</Field>
  </ObjectClass>
</APIRequest>

即序列化语句会自动添加<?xml文本声明。

我知道我的方法有问题,所以有人可以指点我吗?

顺便说一下,我认为只是创建一个带有ObjectClass的APIRequest类没有实际意义(因为每种类型的ObjectClass都需要这些样板代码),但如果我错了,请纠正我。


谢谢,不过有.NET 2.0的任何想法吗? - Matt Mitchell
自动化吗?你似乎正在手动添加声明:w.WriteLine(@“<?xml version = \”1.0\”?>”);。 - Cerebrus
1
@Cerebrus,他不希望在<APIRequest>标签内部有<?xml?>。 - sisve
@John DataContractSeializer不会创建<?xml...>头,因此他可以通过该类序列化每个对象。 - Arsen Mkrtchyan
DataContractSerializer也不会发出DTD声明。 - John Saunders
7个回答

34

试试这个:

internal static string ToXml(object obj)
{
  string retval = null;
  if (obj != null)
  {
    StringBuilder sb = new StringBuilder();
    using(XmlWriter writer = XmlWriter.Create(sb, new XmlWriterSettings() { OmitXmlDeclaration = true }))
    {
      new XmlSerializer(obj.GetType()).Serialize(writer, obj);
    }
    retval = sb.ToString();
  }
  return retval;
}

25

不要使用字符串拼接来构建XML,这是有害的。

<?xml version="1.0" encoding="utf-16"?>
<!DOCTYPE APIRequest SYSTEM "https://url">
<APIRequest>
  <Head>
    <Key>123</Key>
  </Head>
  <ObjectClass>
    <Field>Value</Field>
  </ObjectClass>
</APIRequest>

代码:

using System;
using System.Diagnostics;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

public static class Program {
    public static void Main() {
        var obj = new ObjectClass { Field = "Value" };

        var settings = new XmlWriterSettings {
            Indent = true
        };

        var xml = new StringBuilder();
        using (var writer = XmlWriter.Create(xml, settings)) {
            Debug.Assert(writer != null);

            writer.WriteDocType("APIRequest", null, "https://url", null);
            writer.WriteStartElement("APIRequest");
            writer.WriteStartElement("Head");
            writer.WriteElementString("Key", "123");
            writer.WriteEndElement(); // </Head>

            var nsSerializer = new XmlSerializerNamespaces();
            nsSerializer.Add("", "");

            var xmlSerializer = new XmlSerializer(obj.GetType(), "");
            xmlSerializer.Serialize(writer, obj, nsSerializer);

            writer.WriteEndElement(); // </APIRequest>
        }

        Console.WriteLine(xml.ToString());
        Console.ReadLine();
    }
}

[XmlRoot("ObjectClass")]
public class ObjectClass {
    [XmlElement("Field")]
    public string Field { get; set; }
}

谢谢,我知道字符串连接很糟糕,但我以为它至少能工作 - 看来不行!谢谢 :-) - Matt Mitchell
我不会说它是邪恶的。虽然不是首选,但如果您遇到性能至关重要的情况,并且已经确定了字符串连接是性能问题的根源,那么使用字符串连接可能会带来巨大的收益。当然,这是情境相关的,我几乎总是使用XmlWriter,但在某些情况下,这是有道理的。 - casperOne

2
如果出于性能等原因,您不想依赖于XML编写器,则可以执行以下操作:

代码示例:

// Read into memory stream and set namespaces to empty strings
XmlSerializerNamespaces nsSerializer = new XmlSerializerNamespaces();
nsSerializer.Add(string.Empty, string.Empty);
XmlSerializer xs = new XmlSerializer(typeof(Model.AudioItem));
xs.Serialize(ms, item, nsSerializer);

// Read into UTF-8 stream and read off first line (i.e "<?xml version="1.0"?>")
StreamReader sr = new StreamReader(ms);
ms.Position = 0;
sr.ReadLine();

sr.ReadToEnd().ToString() 现在包含了裸露的序列化信息。


对于ReadLine技巧,感觉有点不正当,但又很聪明。我喜欢它。关于缩进等方面有什么顾虑吗? - sirthomas

1
继承自 XmlTextWriter 并重写相关方法以省略 XML 声明。
Private Class MyXmlTextWriter
Inherits XmlTextWriter
Sub New(ByVal sb As StringBuilder)
    MyBase.New(New StringWriter(sb))
End Sub
Sub New(ByVal w As TextWriter)
    MyBase.New(w)
End Sub

Public Overrides Sub WriteStartDocument()
    ' Don't emit XML declaration
End Sub
Public Overrides Sub WriteStartDocument(ByVal standalone As Boolean)
    ' Don't emit XML declaration
End Sub
End Class

使用派生的 MyXmlTextWriter 实例调用 Serialize。

Dim tw As New MyXmlTextWriter(sb)
Dim objXmlSerializer As New XmlSerializer(type)
objXmlSerializer.Serialize(tw, obj)

2
@Doug D:我很难相信你认为这是一个更好的解决方案。 - John Saunders

1

Scott Hanselman在这方面有一篇不错的文章。我之前也用过Kzu的示例(Scott的博客指向了它),效果非常好。


2
@Tone:有点跟风吧?这只是一个月前Doug D说过的话。-1。 - John Saunders

-1
if (!string.IsNullOrEmpty(strXML) && strXML.Contains(@"<?xml"))
strXML = strXML.Remove(0, strXML.IndexOf(@"?>", 0) + 2);

strXML = strXML.Remove(0, sXMLContent.IndexOf(@"?>", 0) + 2); strXML = strXML.Remove(0, sXMLContent.IndexOf(@"?>", 0) + 2); - sas
1
你能否解释一下你的答案? - CoderPi

-2

一行代码,用于从字符串中删除第一行:

String.Join("\n", strXML.Split('\n').Skip(1).ToArray())

不够优雅,但很简洁。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接