XML, LINQ and XDocument.Save problems
本文关键字:Save problems XDocument and LINQ XML | 更新日期: 2023-09-27 18:35:12
我对 xml 文件进行更改后在保存它们时遇到问题。 我今天花了一整天的时间试图弄清楚这一点,但我一无所获。
我有这个 xml 文档:
<?xml version=1.0" encoding="utf-8"?>
<content>
<weapon id="1234" name="blahblah">
<note info="blah blah" />
</weapon>
<weapon id="5678" name="blahblah2">
<note info="blah blah" />
</weapon>
</content>
这是我到目前为止想出的并不完全有效的(编辑以显示我如何读取文件):
FileOpenPicker openPicker = new FileOpenPicker();
openPicker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.DocumentsLibrary;
openPicker.FileTypeFilter.Add(".xml");
StorageFile gfile = await openPicker.PickSingleFileAsync()
fileContent = await FileIO.ReadTextAsync(gfile, Windows.Storage.Streams.UnicodeEncoding.Utf8);
Xdocument xml = XDocument.Parse(fileContent);
xml.Descendants("weapon").Where(c => c.Attribute("id").Value.Equals("5678")).FirstorDefault().Remove();
IRandomAccessStream writeStream = await gfile.OpenAsync(FileAccessMode.ReadWrite);
Stream stream = writeStream.AsStreamForWrite();
xml.Save(stream);
生成的 xml 文档将是这样的:
<?xml version=1.0" encoding="utf-8"?>
<content>
<weapon id="1234" name="blahblah">
<note info="blah blah" />
</content>apon>
<weapon id="5678" name="blahblah2">
<note info="blah blah" />
</weapon>
</content>
如果我尝试使用 FileAccessMode.ReadWriteNoCopyOnWrite for OpenAsync,文件最终会变成 0 字节。
有人知道我如何在使用 XDocument.Save 的同时正确编写此文件吗?
事实证明,这个问题比乍一看要复杂得多。
我们需要解决的问题包括
- 异步写入文件
- 高效写入,即使用缓冲 IO
- 覆盖整个文件,必要时修剪现有文件
- 使我们的写入操作可等待
经过大量实验,我选择的解决方案是将XML写入System.IO.MemoryStream,然后将该内存流复制到存储文件中。我确实了解这需要内存来临时复制数据。但它可以工作,速度快(缓冲 IO,少量本机 Windows 调用,只有三个等待),正确修剪,并且等待操作实际上可以正常工作。请注意,我尝试过的其他一些方法并非如此。这是解决方案:
MemoryStream ms = new MemoryStream()
xml.Save(ms, SaveOptions.DisableFormatting);
await ms.CopyToAsync(gfile);
CopyToAsync 扩展方法:
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Storage;
using Windows.Storage.Streams;
internal static class Extensions
{
public static async Task CopyToAsync(this MemoryStream source, StorageFile file)
{
using (IRandomAccessStream raStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
using (IOutputStream stream = raStream.GetOutputStreamAt(0))
{
await stream.WriteAsync(source.GetWindowsRuntimeBuffer());
await stream.FlushAsync();
}
raStream.Size = raStream.Position;
}
}
}
为什么不直接使用 System.IO.File.WriteAllText?
XDocument xml = XDocument.Load(xmlFilePath);
System.IO.File.WriteAllText(xmlFilePath, string.Format(@"<?xml version=""1.0""?>{0}{1}", Environment.NewLine, xml));
我让它工作了,但我需要对你的代码行为做更多的研究。 试试这个...
var local = Windows.Storage.ApplicationData.Current.LocalFolder;
var file = await local.GetFileAsync("test.xml");
var data = await FileIO.ReadTextAsync(file);
var xml = XDocument.Parse(data);
xml.Descendants("weapon").Where(c => c.Attribute("id").Value.Equals("5678")).FirstOrDefault().Remove();
file = await local.CreateFileAsync("test.xml", CreationCollisionOption.ReplaceExisting);
var writeStream = await file.OpenStreamForWriteAsync() as Stream;
xml.Save(writeStream);
writeStream.Flush();
其中 test.xml 是本地文件夹中包含原始 XML 的文件。
今天我必须编写一个XML文件,如果我提供有效的流,基本上它运行良好。
WinRT 可能很棘手,因为它对文件系统的访问权限有限。
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public List<DataStructure> DataList { get; set; }
public MainPage()
{
this.InitializeComponent();
DataList = Enumerable.Range(0, 25).Select(i => new DataStructure() { Id = i, Data = string.Format("Number : {0}", i) }).ToList();
this.Loaded += MainPage_Loaded;
}
async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
this.Loaded -= MainPage_Loaded;
//var xmlDocument =
// new XDocument(
// new XElement("DataList",
// DataList.Select(dataItem =>
// new XElement("DataItem",
// new XAttribute("id", dataItem.Id), new XAttribute("data", dataItem.Data)))));
var rootNode = new XElement("DataList");
var xmlDocument = new XDocument(rootNode);
foreach (var dataItem in DataList)
{
rootNode.Add(new XElement("DataItem",
new XAttribute("id", dataItem.Id), new XAttribute("data", dataItem.Data)));
}
FileSavePicker savePicker = new FileSavePicker();
savePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
// Dropdown of file types the user can save the file as
savePicker.FileTypeChoices.Add("XML Document", new List<string>() { ".xml" });
// Default file name if the user does not type one in or select a file to replace
savePicker.SuggestedFileName = "New Xml Document";
StorageFile file = await savePicker.PickSaveFileAsync();
// Process picked file
if (file != null)
{
// Store file for future access
var fileToken = Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList.Add(file);
var writterStream = await file.OpenStreamForWriteAsync();
xmlDocument.Save(writterStream);
}
}
我在VS2012(RC)上创建了一个WinRT默认项目。然后我修改了 MainPage.cs 文件,使其看起来像上一个文件。该文件包含一个硬编码的数据结构列表,我们基于列表创建一个XDocument。最后,我们要求用户提供一个可写的流来保存我们的XDocument。(我们还可以从独立存储中获取可写流)。最后,我们只是使用正确的 Stream 调用 write 方法。
我正在写入一个刷新到磁盘的文件流。
public static async Task SaveXMLAsync(XDocument linqXml, StorageFolder localFolder, string filename)
{
// Create a storage file
StorageFile file = await localFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
// Write the XML to a File Stream.
using (FileStream sourceStream = new FileStream(file.Path, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
{
linqXml.Save(sourceStream);
// Async flush to disk.
await sourceStream.FlushAsync();
};
}