将 2 个 XDocuments 保存到/从一个流中加载

本文关键字:一个 加载 XDocuments 保存 | 更新日期: 2023-09-27 17:56:49

我有2个XDocuments。一个是一些元数据,另一个是大量数据。

在Xbox(XNA)上,我希望能够将两者保存到文件流中,首先是元数据XDoc,然后是实际数据XDoc。

然后,我希望能够只访问元数据

XDoc(忽略文件流的其余部分),并且能够访问元数据XDoc和数据XDoc。

目前我正在保存/加载如下:

public void Serialise(Stream SaveStream, object Obj)
{
    XDocument XDoc = new XDocument(new XElement(@"SaveData", new XAttribute(@"Version", @"1.0"),
                                                GetXMLElement(Obj)));
    XDoc.Save(SaveStream);
}
public object Deserialise(Stream ObjectStream)
{
    XDocument XDoc = XDocument.Load(ObjectStream); // Error line
    switch (XDoc.Element(@"SaveData").Attribute(@"Version").Value)
    {
        case @"1.0":
            return GetObject(XDoc.Element(@"SaveData").FirstNode as XElement);
        default:
            throw new NotSupportedException("This save file version (" + XDoc.Element(@"SaveData").Attribute(@"Version").Value +
                                            " is not supported, please upgrade your game.");
    }
}

为了保存元数据,然后保存实际数据,我只是在同一流上调用 serialise 两次。

我得到一个文件如下:

<?xml version="1.0" encoding="utf-8"?>
<SaveData Version="1.0">
    ....
</SaveData><?xml version="1.0" encoding="utf-8"?>
<SaveData Version="1.0">
    ....
</SaveData>

当我尝试阅读第一个 XDoc:Unexpected XML declaration. The XML declaration must be the first node in the document, and no white space characters are allowed to appear before it. Line 18, position 14.

任何帮助将不胜感激。

将 2 个 XDocuments 保存到/从一个流中加载

XML 声明 ( <?xml version=....?> ) 只能在文档的开头出现一次 - 这就是您看到的错误。 您还需要一个根节点,因此不能以这种方式在一个文件下序列化两个不同的文档。 如果修复了 XML 声明,这可能是您得到的下一个错误。

如果要从一个文件保存和加载文档,则需要将它们合并到单个文档中以进行序列化/反序列化。

我最终编写了自己的 Stream,可以将其视为多流。它允许您将一个流视为连续的多个流。即将多流传递给XML解析器(或其他任何东西),我将读取一个标记,上面写着"这是流的结束"。如果随后将同一流传递给另一个 xml 解析器,它将从该标记读取到下一个或 EOF:

public class MultiStream : Stream
{
    private readonly byte[] _RandomBytes = "410801dd-6f14-4d68-8e0e-29686d212cb2".Select(c => (byte)c).ToArray();
    private Queue<byte> _RollingBytesRead;
    private Stream _UnderlyingStream;
    private bool UnderlyingEOF = false;
    private bool EOFMarker = false;
    private int BufferedBytesToRead = 0;
    public MultiStream(Stream UnderlyingStream)
        : base()
    {
        _UnderlyingStream = UnderlyingStream;
        _RollingBytesRead = new Queue<byte>(_RandomBytes.Length);
    }
    public override bool CanRead
    {
        get { return !UnderlyingEOF || _UnderlyingStream.CanRead; }
    }
    public override bool CanSeek
    {
        get { return false; }
    }
    public override bool CanWrite
    {
        get { return _UnderlyingStream.CanWrite; }
    }
    public override void Flush()
    {
        _UnderlyingStream.Flush();
    }
    public override long Length
    {
        get { throw new NotSupportedException(); }
    }
    public override long Position
    {
        get
        {
            throw new NotSupportedException();
        }
        set
        {
            throw new NotSupportedException();
        }
    }
    public override int ReadByte()
    {
        if (EOFMarker)
            return -1;
        // This should read the next byte from the underlying stream, check for the random bytes EOF marker, then return the next byte from the buffer
        // If our buffer is smaller than the random bytes and we've not hit the EOF, then we need to fill it
        while (!UnderlyingEOF && _RollingBytesRead.Count < _RandomBytes.Length)
        {
            int BytesRead = _UnderlyingStream.ReadByte();
            if (BytesRead == -1)
            {
                UnderlyingEOF = true;
                BufferedBytesToRead = _RollingBytesRead.Count;
            }
            else
            {
                _RollingBytesRead.Enqueue((byte)BytesRead);
            }
        }
        if (EncounteredEndOfStreamBytes()) // Now check to see if the buffer matches our EOF marker
        {
            // If it does stop now, since we don't want to output any of the EOF marker.
            BufferedBytesToRead = 0;
            _RollingBytesRead.Clear();
            EOFMarker = true;
            return -1;
        }
        else if (UnderlyingEOF) // If we've already encountered the end of the underlying stream and have a buffer,
                                // then output the next byte since it's not part of the EOF marker, it's part of the stream
        {
            if (BufferedBytesToRead != 0)
            {
                BufferedBytesToRead--;
                return _RollingBytesRead.Dequeue();
            }
            else
            {
                return -1;
            }
        }
        else
        {
            int ByteRead = _UnderlyingStream.ReadByte();
            if (ByteRead == -1)
            {
                // We've reached the end so we should output the buffer
                UnderlyingEOF = true;
                BufferedBytesToRead = _RollingBytesRead.Count;
                // Recurse once just to avoid repeating code above
                return ReadByte();
            }
            else
            {
                byte BufferedByte = _RollingBytesRead.Dequeue();
                _RollingBytesRead.Enqueue((byte)ByteRead);
                return BufferedByte;
            }
        }
    }
    public override int Read(byte[] buffer, int offset, int count)
    {
        bool EncounteredEOF = false;
        int BufferIndex = 0;
        while (offset > 0)
        {
            if (ReadByte() == -1)
            {
                EncounteredEOF = true;
            }
            offset--;
        }
        while (!EncounteredEOF && count > 0)
        {
            // Read the next byte (includes checks for our end of stream marker) and actually returns the buffered byte (not the next underlying stream read byte)
            int ByteRead = ReadByte();
            if (ByteRead == -1)
            {
                break;
            }
            else
            {
                buffer[BufferIndex] = (byte)ByteRead;
                count--;
                BufferIndex++;
            }
        }
        return BufferIndex;
    }
    private bool EncounteredEndOfStreamBytes()
    {
        if (_RollingBytesRead.Count != _RandomBytes.Length)
            return false;
        byte[] QueueArray = _RollingBytesRead.ToArray();
        for (int i = 0; i < _RandomBytes.Length; i++)
        {
            if (_RandomBytes[i] != QueueArray[i])
                return false;
        }
        return true;
    }
    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotSupportedException();
    }
    public override void SetLength(long value)
    {
        throw new NotSupportedException();
    }
    public override void Write(byte[] buffer, int offset, int count)
    {
        _UnderlyingStream.Write(buffer, offset, count);
    }
    public void WriteStreamSeperator()
    {
        Write(_RandomBytes, 0, _RandomBytes.Length);
    }
    public void AdvanceToNextStream()
    {
        if (UnderlyingEOF)
            throw new InvalidOperationException("No more streams");
        // If we're not currently at an EOF marker, advance until we get to one.
        while (!EOFMarker)
        {
            ReadByte();
        }
        EOFMarker = false;
        _RollingBytesRead.Clear();
    }
}