问题
I've got 2 XDocuments. One is some meta data, the other is a lot of data.
On the Xbox (XNA), I'd like to be able to save both to a file stream, meta data XDoc first, then the actual data XDoc.
I'd then like to be able to access just the meta data XDoc (ignoring the rest of the file stream), and also to be able to access the meta data XDoc and the data XDoc.
Currently i'm saving/loading as follows:
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.");
}
}
To save meta data followed by actual data i'm just calling serialise twice on the same stream.
I get a file as below:
<?xml version="1.0" encoding="utf-8"?>
<SaveData Version="1.0">
....
</SaveData><?xml version="1.0" encoding="utf-8"?>
<SaveData Version="1.0">
....
</SaveData>
The problem comes when i try and read the first 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.
Any help would be greatly appreciated.
回答1:
The XML declaration (<?xml version=....?>
) can only appear once at the beginning of the document - that is the error you are seeing. You also need a root node, so you can't serialize two different documents together under one file in this manner. If you fixed the XML declaration, this would probably be the next error you get.
If you want to save and load the documents from one file, then you need to combine them into a single document for serialization/deserialization.
回答2:
I ended up writing my own Stream, which can be thought of as a multistream. It allows you to treat one stream as multiple stream in succession. i.e. pass a multistream to an xml parser (or anything else) and i'll read up to a marker, which says 'this is the end of the stream'. If you then pass that same stream to another xml parser, it'll read from that marker, to the next one or 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();
}
}
来源:https://stackoverflow.com/questions/6017164/save-load-2-xdocuments-to-from-one-stream