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

Posted

技术标签:

【中文标题】Linq-to-XML XElement.Remove() 留下不需要的空白【英文标题】:Linq-to-XML XElement.Remove() leaves unwanted whitespace 【发布时间】:2011-10-14 15:22:48 【问题描述】:

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

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

示例/示例代码

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

<?xml version="1.0"?>
<catalog>
   <book id="bk101">
      <author>Gambardella, Matthew</author>
      <title>XML Developer's Guide</title>
      <genre>Computer</genre>
      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
      <description>An in-depth look at creating applications with XML.</description>
   </book>
</catalog>

我使用以下代码来解析这个 xml 并删除一些 XElement:

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 字符串将是:

<?xml version="1.0"?>
<catalog>
   <book id="bk101">



      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
      <description>An in-depth look at creating applications with XML.</description>
   </book>
</catalog>

在这个人为的示例中,它可能看起来没什么大不了的,但在我的实际应用程序中,剩余的 xml 看起来很草率。我尝试使用 XDocument.ToString 重载,它需要 SaveOptions 枚举无济于事。我还尝试调用 xDoc.Save 以使用 SaveOptions 枚举保存到文件中。我确实尝试过使用XElement.Nodes().OfType&lt;XText&gt;() 尝试删除空白的几个不同的 linq 查询,但我经常最终将我希望保留的空白与我试图摆脱的空白一起使用。

提前感谢您的帮助。

【问题讨论】:

【参考方案1】:

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

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

编辑:好吧,看来您毕竟需要删除最后一个孩子。因此,事情会变得更加复杂。下面的代码实现了以下算法:

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

生成的代码是:

public static void RemoveWithNextWhitespace(this XElement element)

    IEnumerable<XText> textNodes
        = element.NodesAfterSelf()
                 .TakeWhile(node => node is XText).Cast<XText>();
    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<XText>().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 方法调用之类的东西,以避免代码冗余。

【讨论】:

【参考方案2】:

我有一个比接受的答案更简单的解决方案,它适用于我的情况,并且似乎也适用于你的情况。不过,也许有一些更复杂的情况它不起作用,我不确定。

代码如下:

public static void RemoveWithNextWhitespace(this XElement element)

    if (element.PreviousNode is XText textNode)
    
        textNode.Remove();
    

    element
    .Remove();

这是我对您的用例的 LINQPad 查询:

void Main()

    var xDoc = XDocument.Parse(@"<?xml version=""1.0""?>
<catalog>
   <book id=""bk101"">
      <author>Gambardella, Matthew</author>
      <title>XML Developer's Guide</title>
      <genre>Computer</genre>
      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
      <description>An in-depth look at creating applications with XML.</description>
   </book>
</catalog>", LoadOptions.PreserveWhitespace);

    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.RemoveWithNextWhitespace();
    if (Title != null) Title.RemoveWithNextWhitespace();
    if (Genre != null) Genre.RemoveWithNextWhitespace();

    xDoc.ToString().Dump();


static class Ext

    public static void RemoveWithNextWhitespace(this XElement element)
    
        if (element.PreviousNode is XText textNode)
        
            textNode.Remove();
        

        element
        .Remove();
    

我自己不只使用已接受的答案的主要原因是因为它在某些情况下没有使我的 XML 格式正确。例如在您的用例中,如果我删除了“描述”元素,它将留下如下所示的内容:

<catalog>
   <book id="bk101">
      <genre>Computer</genre>
      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
         </book>
</catalog>

【讨论】:

【参考方案3】:

默认情况下,通过XmlReader 读取 xml 将保留空白,包括您在此处看到的无关紧要的空白。

您应该通过设置适当的 xml 阅读器设置以忽略空格的方式阅读它:

using (var reader = XmlReader.Create(xmlStream, new XmlReaderSettings  IgnoreWhitespace = true ))

请注意,这不会删除重要的空格(例如混合内容或范围保留空格中的空格),因此您的格式将保留。

【讨论】:

以上是关于Linq-to-XML XElement.Remove() 留下不需要的空白的主要内容,如果未能解决你的问题,请参考以下文章

C# LINQ-to-XML 选择子元素属性最大的元素

c# LINQ-to-XML 更新(保存)排序后文件中的元素列表

在 C# 中使用 LINQ-To-XML 解析具有多个列表和类对象的 XML 数据

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

如何在 XML 文档中的特定位置添加 XElement