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

12

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

然后,我搜索特定的xml节点(XElements),并在检索到值“pop”后通过调用XElement.Remove()来弹出它。完成所有解析后,我想能够记录我没有解析的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枚举保存到文件中。我尝试过使用几个不同的LINQ查询,这些查询使用XElement.Nodes().OfType<XText>()来尝试删除空格,但通常会把我想保留的空格和我试图去除的空格一起删除。感谢提前提供的帮助。Joe
3个回答

8

很难用一种可移植的方式回答,因为解决方案严重依赖于XDocument.Load()如何生成空白文本节点(而且有几个LINQ to XML实现可能会在这个微小的细节上产生分歧)。

话虽如此,看起来您从未删除<book>元素的最后一个子元素(<description>)。如果确实是这样的话,那么我们就不必担心父元素的结束标记的缩进,我们可以只删除该元素及其后面的所有文本节点,直到我们到达另一个元素。 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方法调用的循环,以避免代码冗余。


1
我有一个比被接受的答案更简单的解决方案,适用于我的情况,并且似乎也适用于你的情况。也许有一些更复杂的情况它不能解决,但我不确定。
以下是代码:
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格式正确。例如,在您的用例中,如果我删除“description”元素,它会留下类似于以下内容的东西:
<catalog>
   <book id="bk101">
      <genre>Computer</genre>
      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
         </book>
</catalog>

1
通过XmlReader读取xml默认会保留空白,包括您在此处看到的不重要的空白。
您应该在忽略空格的情况下阅读它,设置适当的xml reader设置:
using (var reader = XmlReader.Create(xmlStream, new XmlReaderSettings { IgnoreWhitespace = true }))

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接