【问题标题】:error in XML document. Unexpected XML declaration. XML declaration must be the first node in the documentXML 文档中的错误。意外的 XML 声明。 XML 声明必须是文档中的第一个节点
【发布时间】:2017-11-02 03:39:28
【问题描述】:

XML 文档中存在错误 (8, 20)。内部 1:意外的 XML 声明。 XML 声明必须是文档中的第一个节点,并且之前不允许出现空白字符。

好的,我明白这个错误了。

然而,我是如何得到它的,这让我感到困惑。

我使用 Microsoft 的序列化工具创建文档。然后,我转身尝试再次使用 Microsoft 的反序列化工具将其读回。

我无法控制以正确格式写入 XML 文件 - 我可以看到。

这是我用来读写的单一例程。

private string xmlPath = System.Web.Hosting.HostingEnvironment.MapPath(WebConfigurationManager.AppSettings["DATA_XML"]);
private object objLock = new Object();
public string ErrorMessage { get; set; }

public StoredMsgs Operation(string from, string message, FileAccess access) {
    StoredMsgs list = null;
    lock (objLock) {
        ErrorMessage = null;
        try {
            if (!File.Exists(xmlPath)) {
                var root = new XmlRootAttribute(rootName);
                var serializer = new XmlSerializer(typeof(StoredMsgs), root);
                if (String.IsNullOrEmpty(message)) {
                    from = "Code Window";
                    message = "Created File";
                }
                var item = new StoredMsg() {
                    From = from,
                    Date = DateTime.Now.ToString("s"),
                    Message = message
                };
                using (var stream = File.Create(xmlPath)) {
                    list = new StoredMsgs();
                    list.Add(item);
                    serializer.Serialize(stream, list);
                }
            } else {
                var root = new XmlRootAttribute("MessageHistory");
                var serializer = new XmlSerializer(typeof(StoredMsgs), root);
                var item = new StoredMsg() {
                    From = from,
                    Date = DateTime.Now.ToString("s"),
                    Message = message
                };
                using (var stream = File.Open(xmlPath, FileMode.Open, FileAccess.ReadWrite)) {
                    list = (StoredMsgs)serializer.Deserialize(stream);
                    if ((access == FileAccess.ReadWrite) || (access == FileAccess.Write)) {
                        list.Add(item);
                        serializer.Serialize(stream, list);
                    }
                }
            }
        } catch (Exception error) {
            var sb = new StringBuilder();
            int index = 0;
            sb.AppendLine(String.Format("Top Level Error: <b>{0}</b>", error.Message));
            var err = error.InnerException;
            while (err != null) {
                index++;
                sb.AppendLine(String.Format("\tInner {0}: {1}", index, err.Message));
                err = err.InnerException;
            }
            ErrorMessage = sb.ToString();
        }
    }
    return list;
}

我的日常有什么问题吗?如果微软写了这个文件,在我看来它应该可以读回来。

它应该足够通用,任何人都可以使用。

这是我的 StoredMsg 类:

[Serializable()]
[XmlType("StoredMessage")]
public class StoredMessage {
    public StoredMessage() {
    }
    [XmlElement("From")]
    public string From { get; set; }
    [XmlElement("Date")]
    public string Date { get; set; }
    [XmlElement("Message")]
    public string Message { get; set; }
}

[Serializable()]
[XmlRoot("MessageHistory")]
public class MessageHistory : List<StoredMessage> {
}

它生成的文件在我看来没有任何问题。

我在这里看到了解决方案:

Error: The XML declaration must be the first node in the document

但是,在这种情况下,似乎有人已经有了他们想要阅读的 XML 文档。他们只需要修复它。

我有一个 XML 文档创建了我的 Microsoft,所以它应该被 Microsoft 读回。

【问题讨论】:

  • 一个 XML 文档最多可以有一个声明(在开始时),并且最多可以有一个根元素。
  • 问题是您正在添加到文件中。您反序列化,然后重新序列化到相同的流无需倒带并将大小调整为零。这会为您提供多个 root elements,这是 XML 标准所不允许的。
  • 查看此处了解处理具有多个根元素的文档的方法:XML Error: There are multiple root elements 您还需要omit the XML declaration

标签: c# xml c#-4.0 serialization


【解决方案1】:

问题是您正在添加到文件中。您反序列化,然后重新序列化到相同的流,而无需倒带并将大小调整为零。这会给你多个root elements

<?xml version="1.0"?>
<StoredMessage>
</StoredMessage
<?xml version="1.0"?>
<StoredMessage>
</StoredMessage

根据 XML 标准,多个根元素和多个 XML 声明是 invalid,因此 .NET XML 解析器默认在这种情况下抛出异常。

有关可能的解决方案,请参阅XML Error: There are multiple root elements,它建议您:

  1. 将您的StoredMessage 元素列表包含在一些合成的外部元素中,例如StoredMessageList.

    这需要您从文件中加载消息列表,添加新消息,然后在添加单个项目时截断文件并重新序列化整个列表。因此,性能可能比您当前的方法更差,但 XML 将是有效的。

  2. 在反序列化包含串联根元素的文件时,使用XmlReaderSettings.ConformanceLevel = ConformanceLevel.Fragment 创建一个 XML 写入器,并迭代遍历串联的根节点并分别反序列化每个节点,如图所示,例如 here。使用ConformanceLevel.Fragment 允许阅读器解析具有多个根元素的流(尽管多个 XML 声明 仍然会导致抛出错误)。

    稍后,当使用 XmlSerializer 在文件末尾添加新元素时,查找文件末尾并使用从 XmlWriter.Create(TextWriter, XmlWriterSettings) 返回的 XML 写入器进行序列化 与XmlWriterSettings.OmitXmlDeclaration = true。这可以防止输出多个 XML 声明,如 here 所述。

对于选项 #2,您的 Operation 将如下所示:

private string xmlPath = System.Web.Hosting.HostingEnvironment.MapPath(WebConfigurationManager.AppSettings["DATA_XML"]);
private object objLock = new Object();
public string ErrorMessage { get; set; }

const string rootName = "MessageHistory";
static readonly XmlSerializer serializer = new XmlSerializer(typeof(StoredMessage), new XmlRootAttribute(rootName));

public MessageHistory Operation(string from, string message, FileAccess access)
{
    var list = new MessageHistory();
    lock (objLock)
    {
        ErrorMessage = null;
        try
        {
            using (var file = File.Open(xmlPath, FileMode.OpenOrCreate))
            {
                list.AddRange(XmlSerializerHelper.ReadObjects<StoredMessage>(file, false, serializer));
                if (list.Count == 0 && String.IsNullOrEmpty(message))
                {
                    from = "Code Window";
                    message = "Created File";
                }
                var item = new StoredMessage()
                {
                    From = from,
                    Date = DateTime.Now.ToString("s"),
                    Message = message
                };
                if ((access == FileAccess.ReadWrite) || (access == FileAccess.Write))
                {
                    file.Seek(0, SeekOrigin.End);
                    var writerSettings = new XmlWriterSettings
                    {
                        OmitXmlDeclaration = true,
                        Indent = true, // Optional; remove if compact XML is desired.
                    };
                    using (var textWriter = new StreamWriter(file))
                    {
                        if (list.Count > 0)
                            textWriter.WriteLine();
                        using (var xmlWriter = XmlWriter.Create(textWriter, writerSettings))
                        {
                            serializer.Serialize(xmlWriter, item);
                        }
                    }
                }
                list.Add(item);
            }
        }
        catch (Exception error)
        {
            var sb = new StringBuilder();
            int index = 0;
            sb.AppendLine(String.Format("Top Level Error: <b>{0}</b>", error.Message));
            var err = error.InnerException;
            while (err != null)
            {
                index++;
                sb.AppendLine(String.Format("\tInner {0}: {1}", index, err.Message));
                err = err.InnerException;
            }
            ErrorMessage = sb.ToString();
        }
    }
    return list;
}

使用以下改编自Read nodes of a xml file in C#的扩展方法:

public partial class XmlSerializerHelper
{
    public static List<T> ReadObjects<T>(Stream stream, bool closeInput = true, XmlSerializer serializer = null)
    {
        var list = new List<T>();

        serializer = serializer ?? new XmlSerializer(typeof(T));
        var settings = new XmlReaderSettings
        {
            ConformanceLevel = ConformanceLevel.Fragment,
            CloseInput = closeInput,
        };
        using (var xmlTextReader = XmlReader.Create(stream, settings))
        {
            while (xmlTextReader.Read())
            {   // Skip whitespace
                if (xmlTextReader.NodeType == XmlNodeType.Element)
                {
                    using (var subReader = xmlTextReader.ReadSubtree())
                    {
                        var logEvent = (T)serializer.Deserialize(subReader);
                        list.Add(logEvent);
                    }
                }
            }
        }

        return list;
    }    
}

请注意,如果您要使用自定义XmlRootAttribute 创建XmlSerializer,则必须cache the serializer to avoid a memory leak

示例fiddle

【讨论】:

  • 我一直在看那个链接的例子。有什么我想念的吗?什么将关闭的 XML 片段写入根节点? Serialize不写关闭根节点吗?
  • 看起来我最终可能会放弃微软的工具,转而手动读写节点。他们的解决方案似乎是个好主意,但它似乎不适用于我想要使用它的目的。
  • 如果在 XmlWriter 上将 ConformanceLevel 设置为 Fragment,则 Serialize 将失败,并显示:InvalidOperationException: WriteStartDocument 无法在使用 ConformanceLevel.Fragment 创建的编写器上调用。
  • @poizan42 - 我回答这个问题已经有一段时间了。如果您希望将多个根对象序列化为单个连接文件,请查看更新的 Consecutive XML serialization causes - Token StartElement in state EndRootElement would result in an invalid XML document,它有一个 working fiddle
  • 这并没有改变这样一个事实,即您不能将 Serialize 与 ConformanceLevel 设置为 Fragment 的标准 XmlTextWriter 一起使用。请注意,您链接中的示例并没有这样做,即使它将 OmitXmlDeclaration 设置为 true ,这在这种情况下无效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-03
  • 1970-01-01
  • 2014-12-20
  • 2013-12-13
  • 2013-05-14
  • 2019-02-25
  • 1970-01-01
相关资源
最近更新 更多