Linq-to-XML XElement.Remove()会留下不需要的空格

我有一个从字节数组创建的XDocument(通过tcp / ip接收)。

然后我搜索特定的xml节点(XElements),并在通过调用XElement.Remove()从Xdocument中检索值’pop’之后。 在我的所有解析完成后,我希望能够记录我没有解析的xml(XDocument中剩余的xml)。 问题是在调用XElement.Remove()时会留下额外的空格。 我想知道删除这些额外空格的最佳方法,同时保留剩余xml中的其余格式。

示例/示例代码

如果我通过套接字收到以下xml:

   Gambardella, Matthew XML Developer's Guide Computer 44.95 2000-10-01 An in-depth look at creating applications with XML.   

我使用以下代码来解析此xml并删除一些XElements:

 private void socket_messageReceived(object sender, MessageReceivedEventArgs e) { XDocument xDoc; try { using (MemoryStream xmlStream = new MemoryStream(e.XmlAsBytes)) using (XmlTextReader reader = new XmlTextReader(xmlStream)) { xDoc = XDocument.Load(reader); } XElement Author = xDoc.Root.Descendants("author").FirstOrDefault(); XElement Title = xDoc.Root.Descendants("title").FirstOrDefault(); XElement Genre = xDoc.Root.Descendants("genre").FirstOrDefault(); // Do something with Author, Title, and Genre here... if (Author != null) Author.Remove(); if (Title != null) Title.Remove(); if (Genre != null) Genre.Remove(); LogUnparsedXML(xDoc.ToString()); } catch (Exception ex) { // Exception Handling here... } } 

然后发送到LogUnparsedXML消息的结果xml字符串将是:

    44.95 2000-10-01 An in-depth look at creating applications with XML.   

在这个人为的例子中,这似乎不是什么大不了的事,但在我的实际应用中,剩下的xml看起来很邋。。 我已经尝试使用XDocument.ToString重载,它使SaveOptions枚举无效。 我还尝试使用SaveOptions枚举调用xDoc.Save来保存到文件。 我尝试尝试使用XElement.Nodes().OfType()来尝试删除空白的几个不同的linq查询,但我常常把我希望保留的空白与我的空白一起我试图摆脱。

在此先感谢您的帮助。

以可移植的方式回答并不容易,因为解决方案在很大程度上取决于XDocument.Load()如何生成空白文本节点(并且有几种LINQ to XML实现可能不同意这些细微的细节)。

也就是说,看起来你永远不会从元素中删除最后一个孩子( )。 如果确实如此,那么我们不必担心父元素的结束标记的缩进,我们可以删除该元素及其所有后续文本节点,直到我们到达另一个元素。 TakeWhile()将完成这项工作。

编辑:嗯,似乎你需要删除最后一个孩子。 因此,事情会变得更加复杂。 下面的代码实现了以下算法:

  • 如果元素不是其父元素的最后一个元素:
    • 删除所有后续文本节点,直到我们到达下一个元素。
  • 除此以外:
    • 删除所有后续文本节点,直到找到包含换行符的节点,
    • 如果该节点仅包含换行符:
      • 删除该节点。
    • 除此以外:
      • 创建一个只包含换行符后找到的空格的新节点,
      • 在原始节点之后插入该节点,
      • 删除原始节点。
  • 删除元素本身。

结果代码是:

 public static void RemoveWithNextWhitespace(this XElement element) { IEnumerable textNodes = element.NodesAfterSelf() .TakeWhile(node => node is XText).Cast(); if (element.ElementsAfterSelf().Any()) { // Easy case, remove following text nodes. textNodes.ToList().ForEach(node => node.Remove()); } else { // Remove trailing whitespace. textNodes.TakeWhile(text => !text.Value.Contains("\n")) .ToList().ForEach(text => text.Remove()); // Fetch text node containing newline, if any. XText newLineTextNode = element.NodesAfterSelf().OfType().FirstOrDefault(); if (newLineTextNode != null) { string value = newLineTextNode.Value; if (value.Length > 1) { // Composite text node, trim until newline (inclusive). newLineTextNode.AddAfterSelf( new XText(value.SubString(value.IndexOf('\n') + 1))); } // Remove original node. newLineTextNode.Remove(); } } element.Remove(); } 

从那里,你可以做:

 if (Author != null) Author.RemoveWithNextWhitespace(); if (Title != null) Title.RemoveWithNextWhitespace(); if (Genre != null) Genre.RemoveWithNextWhitespace(); 

虽然我建议你用类似于从数组或params方法调用的循环来替换上面的内容,以避免代码冗余。