【问题标题】:XML, LINQ and XDocument.Save problemsXML、LINQ 和 XDocument.Save 问题
【发布时间】:2017-06-22 22:44:00
【问题描述】:

我在对 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 用于 OpenAsync,则文件最终为 0 字节。

有人知道我如何在仍然使用 XDocument.Save 的同时正确编写此文件吗?

【问题讨论】:

  • 在调用 Save() 后,您是关闭还是释放流?
  • 等一下,不完整,需要测试。
  • 我省略了 Dispose 但它的作用相同。

标签: c# xml windows-8 windows-runtime


【解决方案1】:

为什么不直接使用 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));

【讨论】:

    【解决方案2】:

    事实证明,这个问题比乍看起来要复杂。

    我们需要解决的问题包括

    • 异步写入文件
    • 高效写入,即使用缓冲 IO
    • 覆盖整个文件,必要时修剪现有文件
    • 让我们的写操作等待

    经过大量实验,我选择的解决方案是将 XML 写入 System.IO.MemoryStream,然后将该内存流复制到 storagefile。我知道这需要内存来临时复制数据。但它可以工作,速度很快(缓冲 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;
            }
        }
    }
    

    【讨论】:

    • 同样的事情也会发生。仅替换文件的开头。只要文件获得更多内容,这就会起作用,但是如果您删除内容,旧的东西仍然会被留下。有人告诉我 StorageFile.ReplaceWithStreamedFileAsync 会成功,但我不知道如何使用它。
    • “如果你删除内容,旧的东西仍然被留下” -> 是的,我更新了答案来解决这个问题。
    • 谢谢。这么简单的任务应该没那么难。
    【解决方案3】:

    我让它工作了,但我需要对你的代码的行为做更多的研究。试试这个...

    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 的文件。

    【讨论】:

    • 感谢您的回复。我认为我不能使用 CreateFileAsync,因为它需要一个我目前没有或不知道如何获取的 StorageFolder。我正在使用 FileOpenPicker 或 AccessListEntryView 打开文件,而我只有一个 StorageFile。我正在使用 FileIO.ReadTextAsync() 来读取文件。
    【解决方案4】:

    您好,今天我必须编写一个 XML 文件,如果我提供有效的流,XDocument.Save 基本上可以正常工作。

    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 文件,使它看起来像上一个。该文件包含一个硬编码的 DataStructures 列表,我们基于该列表创建一个 XDocument。最后,我们要求用户提供一个可写流来保存我们的 XDocument。 (我们也可以从独立存储中获取可写流)。最后,我们只需使用适当的 Stream 调用 write 方法。

    【讨论】:

      【解决方案5】:

      我正在写入一个 FileStream,它会刷新到磁盘。

      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();
        };
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多